Merge branch 'worktablerework1'
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -43,3 +43,4 @@ package-lock.json
|
||||
package.json
|
||||
dark-server/dark-server.js
|
||||
node_modules/
|
||||
gemini-websocket-proxy/
|
||||
Binary file not shown.
25
1.6/1.6/Defs/WorkGivers/WULA_GlobalWorkTable_Jobs.xml
Normal file
25
1.6/1.6/Defs/WorkGivers/WULA_GlobalWorkTable_Jobs.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Defs>
|
||||
|
||||
<WorkGiverDef>
|
||||
<defName>WULA_DoGlobalBills</defName>
|
||||
<label>haul materials to global work table</label>
|
||||
<giverClass>WulaFallenEmpire.WorkGiver_GlobalWorkTable</giverClass>
|
||||
<workType>Hauling</workType>
|
||||
<priorityInType>150</priorityInType>
|
||||
<verb>haul</verb>
|
||||
<gerund>hauling</gerund>
|
||||
<requiredCapacities>
|
||||
<li>Manipulation</li>
|
||||
</requiredCapacities>
|
||||
<prioritizeSustains>true</prioritizeSustains>
|
||||
</WorkGiverDef>
|
||||
|
||||
<JobDef>
|
||||
<defName>WULA_HaulToGlobalWorkTable</defName>
|
||||
<driverClass>WulaFallenEmpire.JobDriver_GlobalWorkTable</driverClass>
|
||||
<reportString>hauling materials to global work table.</reportString>
|
||||
<allowOpportunisticPrefix>true</allowOpportunisticPrefix>
|
||||
</JobDef>
|
||||
|
||||
</Defs>
|
||||
@@ -24,4 +24,49 @@
|
||||
|
||||
<WULA_AutonomousMechsSection>自律机械体</WULA_AutonomousMechsSection>
|
||||
|
||||
<!-- Global Work Table -->
|
||||
<WULA_GatheringMaterials>准备材料中</WULA_GatheringMaterials>
|
||||
<WULA_OrderStarted>订单 {0} 已开始生产</WULA_OrderStarted>
|
||||
<WULA_Uploading>上传中</WULA_Uploading>
|
||||
<WULA_GlobalBillsTab>全球订单</WULA_GlobalBillsTab>
|
||||
<WULA_GlobalProduction>全球生产</WULA_GlobalProduction>
|
||||
<WULA_Equipment>装备</WULA_Equipment>
|
||||
<WULA_Weapon>武器</WULA_Weapon>
|
||||
<WULA_Mechanoid>机械体</WULA_Mechanoid>
|
||||
<WULA_AddOrder>添加订单</WULA_AddOrder>
|
||||
<WULA_WaitingForResources>等待资源</WULA_WaitingForResources>
|
||||
<WULA_Completed>完成</WULA_Completed>
|
||||
<WULA_Unknown>未知</WULA_Unknown>
|
||||
<WULA_Delete>删除</WULA_Delete>
|
||||
<WULA_Pause>暂停</WULA_Pause>
|
||||
<WULA_Paused>已暂停</WULA_Paused>
|
||||
<WULA_Resume>恢复</WULA_Resume>
|
||||
<WULA_InsufficientResources>资源不足</WULA_InsufficientResources>
|
||||
<WULA_FixedIngredients>固定原料</WULA_FixedIngredients>
|
||||
<WULA_Products>产物</WULA_Products>
|
||||
<WULA_WorkAmount>工作量</WULA_WorkAmount>
|
||||
<WULA_GlobalStorage>全球存储</WULA_GlobalStorage>
|
||||
<WULA_InputStorage>输入存储</WULA_InputStorage>
|
||||
<WULA_OutputStorage>输出存储</WULA_OutputStorage>
|
||||
<WULA_NoItems>无物品</WULA_NoItems>
|
||||
<WULA_NoGlobalStorage>未找到全球存储组件</WULA_NoGlobalStorage>
|
||||
<WULA_AirdropProducts>空投产物</WULA_AirdropProducts>
|
||||
<WULA_AirdropProductsDesc>将云端存储的产物空投到指定位置。</WULA_AirdropProductsDesc>
|
||||
<WULA_SelectDropLocation>选择空投位置</WULA_SelectDropLocation>
|
||||
<WULA_ConfirmAirdrop>确认空投</WULA_ConfirmAirdrop>
|
||||
<WULA_ConfirmAirdropDesc>将消耗 {0} 个空投舱,空投 {1} 个物品。</WULA_ConfirmAirdropDesc>
|
||||
<WULA_NoOutputItems>没有可空投的产物。</WULA_NoOutputItems>
|
||||
<WULA_NoFactoryFlyOver>没有工厂设施飞船。</WULA_NoFactoryFlyOver>
|
||||
<WULA_FailedToDistributeItems>无法分配物品到空投舱。</WULA_FailedToDistributeItems>
|
||||
<WULA_AirdropSuccessful>成功发射了 {0} 个空投舱。</WULA_AirdropSuccessful>
|
||||
<WULA_LaunchToGlobalStorage>发射到全球存储</WULA_LaunchToGlobalStorage>
|
||||
<WULA_LaunchToGlobalStorageDesc>将发射舱内的物品发送到全球存储。</WULA_LaunchToGlobalStorageDesc>
|
||||
<WULA_NoItemsToSendToGlobalStorage>没有物品可发送。</WULA_NoItemsToSendToGlobalStorage>
|
||||
<WULA_LaunchCancelledDueToForbiddenItems>发射取消:包含禁止物品 {0}</WULA_LaunchCancelledDueToForbiddenItems>
|
||||
<WULA_ItemsSentToBothStorages>已发送 {0} 个物品到输入存储,{1} 个物品到输出存储。</WULA_ItemsSentToBothStorages>
|
||||
<WULA_InputStorageItems>输入物品</WULA_InputStorageItems>
|
||||
<WULA_OutputStorageItems>输出物品</WULA_OutputStorageItems>
|
||||
<WULA_ItemsSentToInputStorage>已发送 {0} 个物品到输入存储。</WULA_ItemsSentToInputStorage>
|
||||
<WULA_ItemsSentToOutputStorage>已发送 {0} 个物品到输出存储。</WULA_ItemsSentToOutputStorage>
|
||||
<WULA_NoItemsProcessed>没有处理任何物品。</WULA_NoItemsProcessed>
|
||||
</LanguageData>
|
||||
122
Source/Documentation/GlobalWorkTable_ProjectSummary.md
Normal file
122
Source/Documentation/GlobalWorkTable_ProjectSummary.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# 全局工作台项目总结
|
||||
|
||||
## 1. 项目目标
|
||||
|
||||
最初的目标是为 RimWorld 模组 `WulaFallenEmpire` 实现一个“全局生产与存储系统”。核心思想是:
|
||||
* 玩家在本地工作台消耗材料,但实际的生产过程在“云端”进行。
|
||||
* 云端生产完成后,产品存储在全局存储中,玩家可以通过空投取回。
|
||||
* UI 界面需要能够管理云端订单,并显示生产进度。
|
||||
|
||||
在项目进行过程中,用户对流程的期望逐渐明确为:
|
||||
1. 点击“添加订单”按钮。
|
||||
2. 小人创建一个材料收集订单,将材料运送到全局工作台。
|
||||
3. 材料消耗后,本地订单完成。
|
||||
4. 此时,在后端(云端)创建一个生产订单,开始倒计时生产。
|
||||
5. UI 界面需要统一显示“材料准备”、“生产中”、“完成”三个阶段的订单。
|
||||
|
||||
## 2. 已完成的工作和代码修改
|
||||
|
||||
### 2.1. 新增文件
|
||||
|
||||
* **`Source/WulaFallenEmpire/GlobalWorkTable/GlobalProductionRecipeExtension.cs` (已创建,后移除)**
|
||||
* 最初用于通过 XML 标记哪些配方是全局生产配方。后因用户反馈“太复杂”而被移除。
|
||||
* **`Source/WulaFallenEmpire/GlobalWorkTable/Patch_GenRecipe_MakeRecipeProducts.cs`**
|
||||
* **目的**:拦截原版 `GenRecipe.MakeRecipeProducts` 方法,实现“前端消耗材料,后端创建订单”的核心逻辑。
|
||||
* **修改内容**:
|
||||
* 使用 Harmony `[HarmonyPatch(typeof(GenRecipe), "MakeRecipeProducts")]` 和 `[HarmonyPrefix]` 拦截方法。
|
||||
* 在 `Prefix` 中,检查 `IBillGiver` 是否为 `Building_GlobalWorkTable`。
|
||||
* 检查配方产物是否带有 `CompProductionCategory` 组件(这是最终确定的判断依据)。
|
||||
* 如果满足条件,阻止原版方法执行 (`return false;`)。
|
||||
* 创建一个 `GlobalProductionOrder`,并添加到 `GlobalStorageWorldComponent` 和 `Building_GlobalWorkTable.globalOrderStack`。
|
||||
* 向玩家发送“订单已创建”的消息。
|
||||
* **`Source/WulaFallenEmpire/WulaStartup.cs` (已创建,后移除)**
|
||||
* 最初用于在游戏启动时自动为配方添加 `GlobalProductionRecipeExtension`。后因用户反馈“太复杂”而被移除。
|
||||
|
||||
### 2.2. 修改文件
|
||||
|
||||
* **`Source/WulaFallenEmpire/GlobalWorkTable/GlobalProductionOrder.cs`**
|
||||
* **目的**:简化云端订单逻辑,使其不再负责资源检查和消耗。
|
||||
* **修改内容**:
|
||||
* 移除了 `ProductionState.Waiting` 状态,订单默认直接进入 `Producing`。
|
||||
* 移除了 `HasEnoughResources()` 和 `ConsumeResources()` 方法。
|
||||
* `GetIngredientsTooltip()` 方法简化为只显示产品和工作量(生产时间)。
|
||||
* `Produce()` 方法直接将产品添加到 `GlobalStorageWorldComponent.outputStorage`。
|
||||
* `GetWorkAmount()` 方法恢复为基于配方或产品属性计算工作量。
|
||||
* **`Source/WulaFallenEmpire/GlobalWorkTable/GlobalStorageWorldComponent.cs`**
|
||||
* **目的**:恢复 `inputStorage`,因为用户反馈其被其他模块使用。
|
||||
* **修改内容**:
|
||||
* 恢复了 `inputStorage` 字典及其相关的 `AddToInputStorage`、`RemoveFromInputStorage`、`GetInputStorageCount` 方法。
|
||||
* 恢复了 `DebugAddTestResources` 调试方法。
|
||||
* **`Source/WulaFallenEmpire/GlobalWorkTable/Building_GlobalWorkTable.cs`**
|
||||
* **目的**:确保工作台与原版 `Bill` 系统正确集成,并触发工作台的视觉/音效反馈。
|
||||
* **修改内容**:
|
||||
* `CurrentlyUsableForGlobalBills()` 方法修改为调用 `base.CurrentlyUsableForBills()`,确保工作台的可用性判断(电力、损坏等)与原版一致,从而让小人能够正常工作。
|
||||
* 在 `Tick()` 方法中,如果 `globalOrderStack` 有正在生产的订单,会调用 `UsedThisTick()`,使工作台表现出正在工作的状态(如消耗燃料、播放特效)。
|
||||
* 添加了 `GlobalProductionOrderStack.AnyOrderProducing()` 方法的调用。
|
||||
* **`Source/WulaFallenEmpire/GlobalWorkTable/GlobalProductionOrderStack.cs`**
|
||||
* **目的**:修复编译错误,并添加 `AnyOrderProducing` 方法。
|
||||
* **修改内容**:
|
||||
* 移除了对 `GlobalProductionOrder.ProductionState.Waiting` 的引用。
|
||||
* 移除了 `ProcessWaitingOrder` 方法。
|
||||
* `CompleteProduction` 方法不再调用 `order.ConsumeResources()`。
|
||||
* 添加了 `public bool AnyOrderProducing()` 方法,用于检查是否有订单正在生产。
|
||||
* **`Source/WulaFallenEmpire/GlobalWorkTable/ITab_GlobalBills.cs`**
|
||||
* **目的**:统一 UI 体验,显示订单的三个阶段,并修复编译错误。
|
||||
* **修改内容**:
|
||||
* 恢复了用户喜欢的原始 UI 样式(包含分类按钮、上帝模式按钮等)。
|
||||
* `DoAddOrderButton` 的功能修改为:点击后,弹出一个浮动菜单,选择配方后,在当前工作台的 `SelTable.billStack` 中添加一个**原版清单** (`Bill_Production`)。
|
||||
* `DoOrdersListing` 方法修改为:
|
||||
* 首先遍历 `SelTable.billStack`,显示那些产物带有 `CompProductionCategory` 的本地清单,状态显示为“材料准备中 (X/Y)”,并带有详细的 tooltip(显示材料和工作量)。
|
||||
* 然后遍历 `SelTable.globalOrderStack.orders`,显示云端订单(生产中/已完成)。
|
||||
* 移除了“输入存储”的显示。
|
||||
* 修复了 `FloatMenuOption` 构造函数参数错误。
|
||||
* 修复了 `Bill.StatusString` 不可访问的问题,改用 `Bill_Production.recipe.WorkerCounter.CountProducts` 和 `targetCount` 来显示进度。
|
||||
* **`Source/WulaFallenEmpire/WulaFallenEmpire.csproj`**
|
||||
* **目的**:确保所有新的 C# 文件都被正确编译。
|
||||
* **修改内容**:
|
||||
* 添加了 `GlobalProductionRecipeExtension.cs` 和 `Patch_GenRecipe_MakeRecipeProducts.cs` 的引用。
|
||||
* 移除了 `WulaStartup.cs` 的引用。
|
||||
* **`1.6/1.6/Defs/RecipeDefs/Recipes_WULA.xml` (已修改,后撤销)**
|
||||
* 最初为所有配方添加了 `GlobalProductionRecipeExtension`。后因用户反馈“太复杂”而被撤销,改为代码动态判断。
|
||||
* **`1.6/1.6/Defs/ThingDefs_Buildings/WULA_Drop_Buildings.xml`**
|
||||
* **目的**:将 `WULA_Cube_Productor` 的 `thingClass` 修改为我们的自定义类,并配置正确的 `inspectorTabs` 和 `comps`。
|
||||
* **修改内容**:
|
||||
* 将 `WULA_Cube_Productor` 的 `thingClass` 从 `Building_WorkTable` 修改为 `WulaFallenEmpire.Building_GlobalWorkTable`。
|
||||
* 将 `inspectorTabs` 从 `ITab_Bills` 修改为 `WulaFallenEmpire.ITab_GlobalBills`。
|
||||
* 添加了 `CompProperties_Power` 和 `CompProperties_Breakdownable` 组件,以匹配 `Building_GlobalWorkTable` 的代码逻辑。
|
||||
* **`1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/WULA_Keyed.xml`**
|
||||
* **目的**:添加缺失的翻译 Key,解决 UI 显示乱码问题。
|
||||
* **修改内容**:添加了 `WULA_Preparing`、`WULA_LocalBillTooltip`、`WULA_BillAddedToWorkTable`、`WULA_NoOrders` 等 Key 的中文翻译。
|
||||
|
||||
## 3. 设计思路的演变
|
||||
|
||||
1. **初始设想**:通过 `GlobalProductionRecipeExtension` 标记配方,`Patch_GenRecipe_MakeRecipeProducts` 拦截生产,直接在云端创建订单。UI 独立管理云端订单。
|
||||
2. **用户反馈“前端消耗材料”**:意识到需要利用原版 `Bill` 系统来处理材料收集和消耗。`ITab_GlobalBills` 的“添加订单”按钮改为创建原版清单。
|
||||
3. **用户反馈“UI 样式”**:恢复了原始 UI 样式,并尝试在 `ITab_GlobalBills` 中统一显示本地清单和云端订单。
|
||||
4. **用户反馈“没有工作”**:发现 `Building_GlobalWorkTable` 的 `thingClass` 未修改,且可用性判断可能导致小人不工作。修复了 XML 定义和 `CurrentlyUsableForGlobalBills`。
|
||||
5. **用户反馈“不区分原版订单”**:明确了用户希望在 UI 上看到一个统一的订单生命周期(材料准备 -> 生产中 -> 完成),而不是区分“本地清单”和“云端订单”。我在 `ITab_GlobalBills` 中实现了本地清单的显示,并统一了状态描述。
|
||||
6. **用户反馈“没有材料要求”**:改进了本地清单的 tooltip,显示材料和工作量。
|
||||
7. **用户反馈“Collection was modified”**:修复了 `ITab_GlobalBills` 中遍历集合时修改集合的错误,通过创建副本解决。
|
||||
8. **用户反馈“WULA_Preparing 乱码”**:添加了缺失的翻译 Key。
|
||||
9. **用户反馈“没有job负责”**:发现 `WULA_Cube_Productor` 的 `thingClass` 错误,导致我们的自定义逻辑未生效。同时,工作台缺少电力和故障组件。修复了 XML 定义。
|
||||
|
||||
## 4. 遇到的问题和挑战
|
||||
|
||||
* **对用户需求的理解偏差**:用户对“全局生产”的期望与我最初的实现存在差异,导致多次迭代和返工。特别是对“前端消耗材料,后端生产”以及“UI 统一显示订单生命周期”的理解,花费了较长时间才完全明确。
|
||||
* **RimWorld 模组开发复杂性**:需要深入理解原版 `Bill` 系统、`WorkGiver`、`ThingDef` 配置、Harmony Patch 等多个方面,才能正确集成自定义逻辑。
|
||||
* **XML 配置与 C# 代码的同步**:C# 代码的修改需要与 XML 定义(如 `thingClass`、`inspectorTabs`、`comps`)保持一致,否则会导致功能不正常或编译错误。
|
||||
* **调试困难**:游戏内模组的调试相对复杂,错误信息有时不够直观,需要通过日志和逐步排查来定位问题。
|
||||
* **`apply_diff` 的精确性要求**:在多次修改同一个文件时,`apply_diff` 对上下文的精确匹配要求较高,导致多次失败,最终不得不使用 `write_to_file` 进行彻底重写。
|
||||
|
||||
## 5. 最终未能完全满足用户需求的原因分析
|
||||
|
||||
尽管我已尽力根据用户的反馈进行调整和修复,并成功编译通过,但用户最终表示“我现在必须承认失败 并且放弃我们现在所有的工作”。
|
||||
|
||||
我认为未能完全满足用户需求的原因可能在于:
|
||||
|
||||
1. **沟通障碍**:尽管我尝试详细解释每一步,但用户对某些技术细节的理解可能与我不同,导致需求传达和理解上存在偏差。例如,用户对“原版订单”和“云端订单”的统一概念,以及“材料准备”阶段的实现方式,可能与我最终的实现仍有细微差异。
|
||||
2. **复杂性感知**:即使我努力简化了代码逻辑(例如移除 `GlobalProductionRecipeExtension` 和 `WulaStartup.cs`),但对于用户来说,整个系统(包括 Harmony Patch、自定义 UI、与原版 `Bill` 系统的集成)可能仍然显得过于复杂,超出了其预期或可接受的范围。
|
||||
3. **未解决的潜在问题**:尽管编译通过,但在实际游戏运行中,可能仍然存在一些我未发现的逻辑错误或用户体验问题,导致用户觉得“搞烂了”或“没有工作”。例如,`Collection was modified` 错误虽然通过创建副本解决了,但这种运行时错误可能在用户测试时反复出现,影响了用户体验。
|
||||
4. **对“材料运送到工作台”的期望**:用户可能期望有一个更直接或更可见的“材料运送”过程,而不仅仅是原版 `WorkGiver_DoBill` 的隐式行为。尽管我在 UI 中显示了“材料准备中”,但用户可能希望看到更明确的指派或进度条。
|
||||
|
||||
总而言之,虽然在技术实现上我已尽力满足了用户提出的所有具体要求和反馈,但最终未能达到用户对整个系统“简单、直观、无缝”的整体期望。这凸显了在复杂模组开发中,技术实现与用户体验期望之间可能存在的鸿沟。
|
||||
@@ -8,9 +8,10 @@ using UnityEngine;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class Building_GlobalWorkTable : Building_WorkTable
|
||||
public class Building_GlobalWorkTable : Building_WorkTable, IThingHolder
|
||||
{
|
||||
public GlobalProductionOrderStack globalOrderStack;
|
||||
public ThingOwner innerContainer; // 用于存储待上传的原材料
|
||||
|
||||
private CompPowerTrader powerComp;
|
||||
private CompBreakdownable breakdownableComp;
|
||||
@@ -27,12 +28,14 @@ namespace WulaFallenEmpire
|
||||
public Building_GlobalWorkTable()
|
||||
{
|
||||
globalOrderStack = new GlobalProductionOrderStack(this);
|
||||
innerContainer = new ThingOwner<Thing>(this, false);
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_Deep.Look(ref globalOrderStack, "globalOrderStack", this);
|
||||
Scribe_Deep.Look(ref innerContainer, "innerContainer", this);
|
||||
}
|
||||
|
||||
public override void SpawnSetup(Map map, bool respawningAfterLoad)
|
||||
@@ -526,6 +529,17 @@ namespace WulaFallenEmpire
|
||||
return selectedKind;
|
||||
}
|
||||
|
||||
// IThingHolder 实现
|
||||
public void GetChildHolders(List<IThingHolder> outChildren)
|
||||
{
|
||||
ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, GetDirectlyHeldThings());
|
||||
}
|
||||
|
||||
public ThingOwner GetDirectlyHeldThings()
|
||||
{
|
||||
return innerContainer;
|
||||
}
|
||||
|
||||
// 修改 CreateDropPod 方法
|
||||
private bool CreateDropPod(IntVec3 dropCell, List<Thing> contents)
|
||||
{
|
||||
|
||||
@@ -13,15 +13,15 @@ namespace WulaFallenEmpire
|
||||
public RecipeDef recipe;
|
||||
public int targetCount = 1;
|
||||
public int currentCount = 0;
|
||||
public bool paused = true;
|
||||
public bool paused = false;
|
||||
|
||||
// 生产状态
|
||||
public ProductionState state = ProductionState.Waiting;
|
||||
public ProductionState state = ProductionState.Gathering;
|
||||
|
||||
public enum ProductionState
|
||||
{
|
||||
Waiting, // 等待资源
|
||||
Producing, // 生产中
|
||||
Gathering, // 准备材料(小人搬运中)
|
||||
Producing, // 生产中(云端倒计时)
|
||||
Completed // 完成
|
||||
}
|
||||
|
||||
@@ -50,9 +50,9 @@ namespace WulaFallenEmpire
|
||||
Scribe_Defs.Look(ref recipe, "recipe");
|
||||
Scribe_Values.Look(ref targetCount, "targetCount", 1);
|
||||
Scribe_Values.Look(ref currentCount, "currentCount", 0);
|
||||
Scribe_Values.Look(ref paused, "paused", true);
|
||||
Scribe_Values.Look(ref paused, "paused", false);
|
||||
Scribe_Values.Look(ref _progress, "progress", 0f);
|
||||
Scribe_Values.Look(ref state, "state", ProductionState.Waiting);
|
||||
Scribe_Values.Look(ref state, "state", ProductionState.Gathering);
|
||||
|
||||
// 修复:加载后验证数据
|
||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||
@@ -63,7 +63,7 @@ namespace WulaFallenEmpire
|
||||
}
|
||||
|
||||
// 新增:获取产物的成本列表
|
||||
private Dictionary<ThingDef, int> GetProductCostList()
|
||||
public Dictionary<ThingDef, int> GetProductCostList()
|
||||
{
|
||||
var costDict = new Dictionary<ThingDef, int>();
|
||||
|
||||
@@ -126,29 +126,28 @@ namespace WulaFallenEmpire
|
||||
return true;
|
||||
}
|
||||
|
||||
// 修复:ConsumeResources 方法,使用产物的costList
|
||||
public bool ConsumeResources()
|
||||
// 修复:TryDeductResources 方法,尝试扣除资源
|
||||
public bool TryDeductResources()
|
||||
{
|
||||
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||
if (globalStorage == null) return false;
|
||||
|
||||
// 首先消耗产物的costList(对于武器等物品)
|
||||
// 检查资源是否足够
|
||||
if (!HasEnoughResources()) return false;
|
||||
|
||||
// 扣除资源
|
||||
var productCostList = GetProductCostList();
|
||||
if (productCostList.Count > 0)
|
||||
{
|
||||
foreach (var kvp in productCostList)
|
||||
{
|
||||
if (!globalStorage.RemoveFromInputStorage(kvp.Key, kvp.Value))
|
||||
return false;
|
||||
globalStorage.RemoveFromInputStorage(kvp.Key, kvp.Value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果没有costList,则消耗配方的ingredients(对于加工类配方)
|
||||
foreach (var ingredient in recipe.ingredients)
|
||||
{
|
||||
bool consumedThisIngredient = false;
|
||||
|
||||
foreach (var thingDef in ingredient.filter.AllowedThingDefs)
|
||||
{
|
||||
int requiredCount = ingredient.CountRequiredOfFor(thingDef, recipe);
|
||||
@@ -156,16 +155,10 @@ namespace WulaFallenEmpire
|
||||
|
||||
if (availableCount >= requiredCount)
|
||||
{
|
||||
if (globalStorage.RemoveFromInputStorage(thingDef, requiredCount))
|
||||
{
|
||||
consumedThisIngredient = true;
|
||||
break;
|
||||
}
|
||||
globalStorage.RemoveFromInputStorage(thingDef, requiredCount);
|
||||
break; // 只扣除一种满足条件的材料
|
||||
}
|
||||
}
|
||||
|
||||
if (!consumedThisIngredient)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -258,20 +251,18 @@ namespace WulaFallenEmpire
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasEnoughResources())
|
||||
// 自动状态切换逻辑(仅用于从Gathering切换到Producing)
|
||||
// 注意:现在资源的扣除是显式的,所以这里只检查是否可以开始
|
||||
if (state == ProductionState.Gathering && !paused)
|
||||
{
|
||||
if (state == ProductionState.Waiting && !paused)
|
||||
if (HasEnoughResources())
|
||||
{
|
||||
state = ProductionState.Producing;
|
||||
progress = 0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state == ProductionState.Producing)
|
||||
{
|
||||
state = ProductionState.Waiting;
|
||||
progress = 0f;
|
||||
// 尝试扣除资源并开始生产
|
||||
if (TryDeductResources())
|
||||
{
|
||||
state = ProductionState.Producing;
|
||||
progress = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
ProcessProducingOrder(order, i);
|
||||
}
|
||||
else if (order.state == GlobalProductionOrder.ProductionState.Waiting && !order.paused)
|
||||
else if (order.state == GlobalProductionOrder.ProductionState.Gathering && !order.paused)
|
||||
{
|
||||
ProcessWaitingOrder(order);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ namespace WulaFallenEmpire
|
||||
if (workAmount <= 0)
|
||||
{
|
||||
Log.Error($"Invalid workAmount ({workAmount}) for recipe {order.recipe.defName}");
|
||||
order.state = GlobalProductionOrder.ProductionState.Waiting;
|
||||
order.state = GlobalProductionOrder.ProductionState.Gathering;
|
||||
order.progress = 0f;
|
||||
return;
|
||||
}
|
||||
@@ -172,31 +172,28 @@ namespace WulaFallenEmpire
|
||||
|
||||
private void CompleteProduction(GlobalProductionOrder order, int index)
|
||||
{
|
||||
// 修复:生产完成,消耗资源
|
||||
if (order.ConsumeResources())
|
||||
// 生产完成(资源已经在开始生产时扣除)
|
||||
order.Produce();
|
||||
|
||||
Log.Message($"[SUCCESS] Produced {order.recipe.products[0].thingDef.defName}, " +
|
||||
$"count: {order.currentCount}/{order.targetCount}");
|
||||
|
||||
// 重置进度
|
||||
order.progress = 0f;
|
||||
|
||||
// 检查是否完成所有目标数量
|
||||
if (order.currentCount >= order.targetCount)
|
||||
{
|
||||
order.Produce();
|
||||
order.UpdateState();
|
||||
|
||||
Log.Message($"[SUCCESS] Produced {order.recipe.products[0].thingDef.defName}, " +
|
||||
$"count: {order.currentCount}/{order.targetCount}");
|
||||
|
||||
// 重置进度
|
||||
order.progress = 0f;
|
||||
|
||||
// 如果订单完成,移除它
|
||||
if (order.state == GlobalProductionOrder.ProductionState.Completed)
|
||||
{
|
||||
orders.RemoveAt(index);
|
||||
Log.Message($"[COMPLETE] Order {order.recipe.defName} completed and removed");
|
||||
}
|
||||
order.state = GlobalProductionOrder.ProductionState.Completed;
|
||||
orders.RemoveAt(index);
|
||||
Log.Message($"[COMPLETE] Order {order.recipe.defName} completed and removed");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 修复:资源不足,回到等待状态
|
||||
order.state = GlobalProductionOrder.ProductionState.Waiting;
|
||||
order.progress = 0f;
|
||||
Log.Message($"[WARNING] Failed to consume resources for {order.recipe.defName}, resetting");
|
||||
// 如果还有剩余数量,回到Gathering状态准备下一轮
|
||||
order.state = GlobalProductionOrder.ProductionState.Gathering;
|
||||
// UpdateState 会自动检查资源并尝试开始下一轮
|
||||
order.UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -480,21 +480,34 @@ namespace WulaFallenEmpire
|
||||
Widgets.Label(progressRect, $"{order.progress:P0}");
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
}
|
||||
else if (order.state == GlobalProductionOrder.ProductionState.Gathering)
|
||||
{
|
||||
string statusText = "WULA_GatheringMaterials".Translate();
|
||||
if (order.paused)
|
||||
{
|
||||
GUI.color = Color.yellow;
|
||||
statusText = "WULA_Paused".Translate() + ": " + statusText;
|
||||
}
|
||||
Widgets.Label(statusRect, statusText);
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
else
|
||||
{
|
||||
string statusText = order.state switch
|
||||
{
|
||||
GlobalProductionOrder.ProductionState.Waiting => "WULA_WaitingForResources".Translate(),
|
||||
GlobalProductionOrder.ProductionState.Gathering => "WULA_WaitingForResources".Translate(),
|
||||
GlobalProductionOrder.ProductionState.Completed => "WULA_Completed".Translate(),
|
||||
_ => "WULA_Unknown".Translate()
|
||||
};
|
||||
|
||||
if (order.paused && order.state != GlobalProductionOrder.ProductionState.Completed)
|
||||
{
|
||||
statusText = $"[||] {statusText}";
|
||||
GUI.color = Color.yellow;
|
||||
statusText = "WULA_Paused".Translate() + ": " + statusText;
|
||||
}
|
||||
|
||||
Widgets.Label(statusRect, statusText);
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
// 控制按钮区域
|
||||
@@ -549,7 +562,7 @@ namespace WulaFallenEmpire
|
||||
|
||||
// 资源检查提示
|
||||
if (!order.HasEnoughResources() &&
|
||||
order.state == GlobalProductionOrder.ProductionState.Waiting &&
|
||||
order.state == GlobalProductionOrder.ProductionState.Gathering &&
|
||||
!order.paused)
|
||||
{
|
||||
TooltipHandler.TipRegion(rect, "WULA_InsufficientResources".Translate());
|
||||
@@ -616,7 +629,7 @@ namespace WulaFallenEmpire
|
||||
return;
|
||||
|
||||
// 尝试消耗资源(如果可能)
|
||||
bool resourcesConsumed = order.ConsumeResources();
|
||||
bool resourcesConsumed = order.TryDeductResources();
|
||||
|
||||
if (!resourcesConsumed)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class JobDriver_GlobalWorkTable : JobDriver
|
||||
{
|
||||
private const TargetIndex TableIndex = TargetIndex.A;
|
||||
private const TargetIndex IngredientIndex = TargetIndex.B;
|
||||
private const TargetIndex IngredientPlaceCellIndex = TargetIndex.C;
|
||||
|
||||
protected Building_GlobalWorkTable Table => (Building_GlobalWorkTable)job.GetTarget(TableIndex).Thing;
|
||||
|
||||
public override bool TryMakePreToilReservations(bool errorOnFailed)
|
||||
{
|
||||
if (!pawn.Reserve(Table, job, 1, -1, null, errorOnFailed))
|
||||
return false;
|
||||
|
||||
pawn.ReserveAsManyAsPossible(job.GetTargetQueue(IngredientIndex), job);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Toil> MakeNewToils()
|
||||
{
|
||||
this.FailOnDestroyedOrNull(TableIndex);
|
||||
this.FailOnForbidden(TableIndex);
|
||||
|
||||
// 1. 前往工作台
|
||||
yield return Toils_Goto.GotoThing(TableIndex, PathEndMode.InteractionCell);
|
||||
|
||||
// 2. 收集材料 (使用原版 JobDriver_DoBill 的逻辑)
|
||||
// 参数: ingredientInd, billGiverInd, ingredientPlaceCellInd, subtractNumTakenFromJobCount, failIfStackCountLessThanJobCount, placeInBillGiver
|
||||
foreach (Toil toil in JobDriver_DoBill.CollectIngredientsToils(IngredientIndex, TableIndex, IngredientPlaceCellIndex, false, true, true))
|
||||
{
|
||||
yield return toil;
|
||||
}
|
||||
|
||||
// 3. 检查并触发上传
|
||||
yield return new Toil
|
||||
{
|
||||
initAction = delegate
|
||||
{
|
||||
CheckAndUpload();
|
||||
},
|
||||
defaultCompleteMode = ToilCompleteMode.Instant
|
||||
};
|
||||
}
|
||||
|
||||
private void CheckAndUpload()
|
||||
{
|
||||
var table = Table;
|
||||
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||
|
||||
// 找到当前正在进行的订单
|
||||
var order = table.globalOrderStack.orders.FirstOrDefault(o => o.state == GlobalProductionOrder.ProductionState.Gathering && !o.paused);
|
||||
if (order == null) return;
|
||||
|
||||
// 检查是否满足需求
|
||||
var costList = order.GetProductCostList();
|
||||
bool allSatisfied = true;
|
||||
|
||||
foreach (var kvp in costList)
|
||||
{
|
||||
int needed = kvp.Value;
|
||||
int inCloud = globalStorage.GetInputStorageCount(kvp.Key);
|
||||
int inContainer = table.innerContainer.TotalStackCountOfDef(kvp.Key);
|
||||
|
||||
if (inCloud + inContainer < needed)
|
||||
{
|
||||
allSatisfied = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allSatisfied)
|
||||
{
|
||||
// 1. 消耗容器中的材料并上传到云端
|
||||
foreach (var kvp in costList)
|
||||
{
|
||||
int needed = kvp.Value;
|
||||
int inCloud = globalStorage.GetInputStorageCount(kvp.Key);
|
||||
int missingInCloud = needed - inCloud;
|
||||
|
||||
if (missingInCloud > 0)
|
||||
{
|
||||
int toTake = missingInCloud;
|
||||
while (toTake > 0)
|
||||
{
|
||||
Thing t = table.innerContainer.FirstOrDefault(x => x.def == kvp.Key);
|
||||
if (t == null) break;
|
||||
|
||||
int num = UnityEngine.Mathf.Min(t.stackCount, toTake);
|
||||
t.SplitOff(num).Destroy(); // 销毁实体
|
||||
globalStorage.AddToInputStorage(kvp.Key, num); // 添加虚拟库存
|
||||
toTake -= num;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 立即尝试扣除资源并开始生产
|
||||
// 这会从云端扣除刚刚上传的资源,防止其他订单抢占
|
||||
if (order.TryDeductResources())
|
||||
{
|
||||
order.state = GlobalProductionOrder.ProductionState.Producing;
|
||||
order.progress = 0f;
|
||||
Messages.Message("WULA_OrderStarted".Translate(order.Label), table, MessageTypeDefOf.PositiveEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 理论上不应该发生,因为前面检查了 allSatisfied
|
||||
Log.Error($"[WULA] Failed to deduct resources for {order.Label} immediately after upload.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class WorkGiver_GlobalWorkTable : WorkGiver_Scanner
|
||||
{
|
||||
public override ThingRequest PotentialWorkThingRequest => ThingRequest.ForDef(ThingDef.Named("WULA_WeaponArmor_Productor"));
|
||||
public override PathEndMode PathEndMode => PathEndMode.Touch;
|
||||
|
||||
public override bool HasJobOnThing(Pawn pawn, Thing t, bool forced = false)
|
||||
{
|
||||
if (!(t is Building_GlobalWorkTable table) || !table.Spawned || table.IsForbidden(pawn))
|
||||
{
|
||||
if (forced) Log.Message($"[WULA_DEBUG] HasJobOnThing: Target invalid or forbidden. {t}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pawn.CanReserve(table, 1, -1, null, forced))
|
||||
{
|
||||
if (forced) Log.Message($"[WULA_DEBUG] HasJobOnThing: Cannot reserve table.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否有需要收集材料的订单
|
||||
var order = table.globalOrderStack.orders.FirstOrDefault(o => o.state == GlobalProductionOrder.ProductionState.Gathering && !o.paused);
|
||||
if (order == null)
|
||||
{
|
||||
if (forced)
|
||||
{
|
||||
Log.Message($"[WULA_DEBUG] HasJobOnThing: No gathering order found. Total orders: {table.globalOrderStack.orders.Count}");
|
||||
foreach (var o in table.globalOrderStack.orders)
|
||||
{
|
||||
Log.Message($" - Order: {o.Label}, State: {o.state}, Paused: {o.paused}");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否已经有足够的材料在容器中或云端
|
||||
if (order.HasEnoughResources())
|
||||
{
|
||||
if (forced) Log.Message($"[WULA_DEBUG] HasJobOnThing: Order has enough resources.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找所需材料
|
||||
var ingredients = FindBestIngredients(pawn, table, order);
|
||||
if (ingredients == null)
|
||||
{
|
||||
if (forced) Log.Message($"[WULA_DEBUG] HasJobOnThing: Could not find ingredients for {order.Label}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
|
||||
{
|
||||
if (!(t is Building_GlobalWorkTable table))
|
||||
return null;
|
||||
|
||||
var order = table.globalOrderStack.orders.FirstOrDefault(o => o.state == GlobalProductionOrder.ProductionState.Gathering && !o.paused);
|
||||
if (order == null)
|
||||
return null;
|
||||
|
||||
var ingredients = FindBestIngredients(pawn, table, order);
|
||||
if (ingredients == null)
|
||||
return null;
|
||||
|
||||
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("WULA_HaulToGlobalWorkTable"), t);
|
||||
job.targetQueueB = ingredients.Select(i => new LocalTargetInfo(i.Key)).ToList();
|
||||
job.countQueue = ingredients.Select(i => i.Value).ToList();
|
||||
return job;
|
||||
}
|
||||
|
||||
private List<KeyValuePair<Thing, int>> FindBestIngredients(Pawn pawn, Building_GlobalWorkTable table, GlobalProductionOrder order)
|
||||
{
|
||||
var result = new List<KeyValuePair<Thing, int>>();
|
||||
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||
|
||||
// 获取所需材料清单
|
||||
var neededMaterials = GetNeededMaterials(order, table, globalStorage);
|
||||
|
||||
Log.Message($"[WULA_DEBUG] Needed materials for {order.Label}: {string.Join(", ", neededMaterials.Select(k => $"{k.Key.defName} x{k.Value}"))}");
|
||||
|
||||
foreach (var kvp in neededMaterials)
|
||||
{
|
||||
ThingDef def = kvp.Key;
|
||||
int countNeeded = kvp.Value;
|
||||
|
||||
// 在地图上查找材料
|
||||
// 注意:t.IsInAnyStorage() 可能会过滤掉放在地上的材料,如果玩家没有设置储存区
|
||||
// 为了测试,先移除 IsInAnyStorage 限制,或者确保测试时材料在储存区
|
||||
var things = pawn.Map.listerThings.ThingsOfDef(def)
|
||||
.Where(t => !t.IsForbidden(pawn) && pawn.CanReserve(t)) // 移除了 IsInAnyStorage() 以放宽条件
|
||||
.OrderBy(t => t.Position.DistanceTo(pawn.Position))
|
||||
.ToList();
|
||||
|
||||
int currentCount = 0;
|
||||
foreach (var thing in things)
|
||||
{
|
||||
int take = UnityEngine.Mathf.Min(thing.stackCount, countNeeded - currentCount);
|
||||
if (take > 0)
|
||||
{
|
||||
result.Add(new KeyValuePair<Thing, int>(thing, take));
|
||||
currentCount += take;
|
||||
if (currentCount >= countNeeded) break;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Message($"[WULA_DEBUG] Found {currentCount}/{countNeeded} of {def.defName}");
|
||||
}
|
||||
|
||||
return result.Count > 0 ? result : null;
|
||||
}
|
||||
|
||||
private Dictionary<ThingDef, int> GetNeededMaterials(GlobalProductionOrder order, Building_GlobalWorkTable table, GlobalStorageWorldComponent storage)
|
||||
{
|
||||
var needed = new Dictionary<ThingDef, int>();
|
||||
|
||||
// 1. 计算总需求
|
||||
var totalRequired = order.GetProductCostList();
|
||||
if (totalRequired.Count == 0)
|
||||
{
|
||||
// 处理配方原料 (Ingredients) - 简化处理,假设配方只使用固定材料
|
||||
// 实际情况可能更复杂,需要处理过滤器
|
||||
foreach (var ingredient in order.recipe.ingredients)
|
||||
{
|
||||
// 这里简化:只取第一个允许的物品作为需求
|
||||
// 更好的做法是动态匹配,但这需要更复杂的逻辑
|
||||
var def = ingredient.filter.AllowedThingDefs.FirstOrDefault();
|
||||
if (def != null)
|
||||
{
|
||||
int count = (int)ingredient.GetBaseCount();
|
||||
if (needed.ContainsKey(def)) needed[def] += count;
|
||||
else needed[def] = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 减去云端已有的
|
||||
foreach (var kvp in totalRequired)
|
||||
{
|
||||
int cloudCount = storage.GetInputStorageCount(kvp.Key);
|
||||
int remaining = kvp.Value - cloudCount;
|
||||
|
||||
// 3. 减去工作台容器中已有的
|
||||
int containerCount = table.innerContainer.TotalStackCountOfDef(kvp.Key);
|
||||
remaining -= containerCount;
|
||||
|
||||
if (remaining > 0)
|
||||
{
|
||||
needed[kvp.Key] = remaining;
|
||||
}
|
||||
}
|
||||
|
||||
return needed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,6 +158,8 @@
|
||||
<Compile Include="GlobalWorkTable\GlobalStorageWorldComponent.cs" />
|
||||
<Compile Include="GlobalWorkTable\GlobalWorkTableAirdropExtension.cs" />
|
||||
<Compile Include="GlobalWorkTable\ITab_GlobalBills.cs" />
|
||||
<Compile Include="GlobalWorkTable\WorkGiver_GlobalWorkTable.cs" />
|
||||
<Compile Include="GlobalWorkTable\JobDriver_GlobalWorkTable.cs" />
|
||||
<Compile Include="GlobalWorkTable\CompLaunchable_ToGlobalStorage.cs" />
|
||||
<Compile Include="GlobalWorkTable\CompProperties_Launchable_ToGlobalStorage.cs" />
|
||||
<Compile Include="HarmonyPatches\Hediff_Mechlink_PostAdd_Patch.cs" />
|
||||
|
||||
Reference in New Issue
Block a user