diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll
index 9177c77e..10a818e6 100644
Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ
diff --git a/1.6/1.6/Defs/NeedDefs/WULA_Needs.xml b/1.6/1.6/Defs/NeedDefs/WULA_Needs.xml
index 8f0e420e..7d1923d0 100644
--- a/1.6/1.6/Defs/NeedDefs/WULA_Needs.xml
+++ b/1.6/1.6/Defs/NeedDefs/WULA_Needs.xml
@@ -40,7 +40,6 @@
0.05
0.1
5
- PatientBedRest
WULA_Maintenance_MinorBreakdown
WULA_Maintenance_MajorBreakdown
WULA_Maintenance_CriticalFailuren
diff --git a/1.6/1.6/Defs/ThingDefs_Buildings/Buildings_Drop.xml b/1.6/1.6/Defs/ThingDefs_Buildings/Buildings_Drop.xml
index 07595880..027dce73 100644
--- a/1.6/1.6/Defs/ThingDefs_Buildings/Buildings_Drop.xml
+++ b/1.6/1.6/Defs/ThingDefs_Buildings/Buildings_Drop.xml
@@ -235,6 +235,15 @@
一台仿制乌拉帝国科技而建造的塑性构造体,不仅要消耗大量木头用以提供生物能,还只能生产基础的衣物和能源核心用以维持生存——不过它很轻,可以随探险队一起移动。
WulaFallenEmpire.Building_GlobalWorkTable
MapMeshAndRealTime
+ Normal
+
+
+ 50
+ 15
+ 1
+ 10
+
+
Wula/Building/WULA_Cube_Productor_BIO
Graphic_Multi
@@ -259,7 +268,6 @@
2000
180
1.0
- 0.5
(1,1)
WULA_Buildings
@@ -282,21 +290,6 @@
WulaFallenEmpire.ITab_GlobalBills
-
- 300.0
- 150.0
-
-
- WoodLog
-
-
- true
- true
-
-
- CompHeatPusherPowered
- 4
-
PlaceWorker_PreventInteractionSpotOverlap
@@ -307,4 +300,53 @@
0.10
+
+ WULA_ResourceSubmitter
+
+ 乌拉帝国从地面向舰队提交资源的特殊交换仓。
+ WulaFallenEmpire.Building_ResourceSubmitter
+
+ Things/Building/Furniture/Shelf
+ Graphic_Multi
+ (1,1)
+
+ (1,1)
+ false
+ Normal
+ BuildingOnTop
+ PassThroughOnly
+ false
+ Misc12
+ 0.5
+ WULA_Buildings
+ 2200
+
+ 250
+ 1600
+ 0.5
+
+
+ 20
+
+
+
+ WulaFallenEmpire.ITab_ResourceSubmitterContents
+
+
+ PlaceWorker_NotUnderRoof
+
+ 0.65
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/ThingDefs_Buildings/Buildings_WULA.xml b/1.6/1.6/Defs/ThingDefs_Buildings/Buildings_WULA.xml
index 6f367791..9a59d70c 100644
--- a/1.6/1.6/Defs/ThingDefs_Buildings/Buildings_WULA.xml
+++ b/1.6/1.6/Defs/ThingDefs_Buildings/Buildings_WULA.xml
@@ -59,84 +59,6 @@
-
- WULA_Cube_Productor_BIO
-
- 一台仿制乌拉帝国科技而建造的塑性构造体,不仅要消耗大量木头用以提供生物能,还只能生产基础的衣物和能源核心用以维持生存——不过它很轻,可以随探险队一起移动。
- Building_WorkTable_HeatPush
- MapMeshAndRealTime
-
- Wula/Building/WULA_Cube_Productor_BIO
- Graphic_Multi
- (1,1)
-
- false
-
-
- (0.75, 0.75, 0.5)
-
-
- ConstructMetal
-
- 50
-
- Building
- false
- 0.5
- True
-
- 5
- 2000
- 180
- 1.0
- 0.5
-
- (1,1)
- WULA_Buildings
- 2120
- PassThroughOnly
- 50
- True
- (0,0,-1)
- Item
-
- WULA_Base_Technology
-
-
-
- Make_WULA_Charge_Cube
- Recharge_WULA_Charge_Cube
- Wula_Make_Zro
-
-
- ITab_Bills
-
-
-
- 300.0
- 150.0
-
-
- WoodLog
-
-
- true
- true
-
-
- CompHeatPusherPowered
- 4
-
-
-
- PlaceWorker_PreventInteractionSpotOverlap
-
-
-
- BillsTab
- 0.10
-
-
WULA_Cube_Productor_Energy
diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/Misc_Gameplay.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/Misc_Gameplay.xml
index 4061e0cb..3f1e4f1d 100644
--- a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/Misc_Gameplay.xml
+++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/Misc_Gameplay.xml
@@ -148,12 +148,87 @@
全球生产
生产订单
添加生产订单
- Resume
- Pause
- Delete
+ 恢复
+ 暂停
+ 删除
等待资源
- Completed
- Unknown
+ 完成
+ 未知
全球存储中的资源不足
无可用配方
+
+ 可用配方
+ 产物
+ 工作量
+
+
+ 查看存储
+ 输入存储(原材料)
+ 输出存储(产品)
+ 未找到全局存储组件
+ 无物品
+ 存储统计
+ 种输入物品
+ 种输出物品
+
+
+ 空投产品
+ 将产物储存器中的所有物品通过空投舱分发到指定区域
+ 无法执行空投:工作台未就绪
+ 没有可空投的产品
+ 目标距离超出最大空投范围({0})
+ 没有有效的空投落点。请选择另一个位置
+ 无法分配物品到空投舱
+ 成功空投了{0}个空投舱
+
+ 资源提交器
+ 提交到储存器
+ 将所有存储物品转移到全局储存
+ 存储物品
+ {0}个物品已提交到储存器
+ 提交物品失败
+ 没有可提交的物品
+ 设备不可用
+ 无电力
+ 无燃料
+ 开关关闭
+ 不可用
+ 状态
+
+ 提交器内容
+ 运行正常
+ 无法运行
+ 状态
+ 物品
+ 堆叠
+ 存储中无物品
+ 物品名称
+ 数量
+ {0}\n总数: {1}\n堆叠数: {2}\n价值: {3} 白银
+ 提交到存储
+ 将所有存储物品提交到全局存储
+ 设备无法运行
+ 无物品可提交
+ 没有可以提交的物品
+ 查看全局存储
+ 查看全局输入和输出存储内容
+ 未知原因
+ 全局存储
+ 输入存储
+ 输出存储
+ 无物品
+ 价值
+
+物品
+堆叠
+物品名称
+数量
+存储中无物品
+{0}\n总数: {1}\n堆叠: {2}\n价值: {3}
+设备不可用
+没有可提交的物品
+查看全局存储
+查看全局存储中的物品
+未知原因
+价值
\ No newline at end of file
diff --git a/Source/WulaFallenEmpire/GlobalWorkTable/Building_GlobalWorkTable.cs b/Source/WulaFallenEmpire/GlobalWorkTable/Building_GlobalWorkTable.cs
index 1d13f56e..69dd6b61 100644
--- a/Source/WulaFallenEmpire/GlobalWorkTable/Building_GlobalWorkTable.cs
+++ b/Source/WulaFallenEmpire/GlobalWorkTable/Building_GlobalWorkTable.cs
@@ -1,7 +1,10 @@
-// Building_GlobalWorkTable.cs (修复版)
+// Building_GlobalWorkTable.cs (调整为每秒处理)
using RimWorld;
using System.Collections.Generic;
+using System.Linq;
using Verse;
+using Verse.AI;
+using UnityEngine;
namespace WulaFallenEmpire
{
@@ -10,8 +13,9 @@ namespace WulaFallenEmpire
public GlobalProductionOrderStack globalOrderStack;
private CompPowerTrader powerComp;
+ private CompBreakdownable breakdownableComp;
private int lastProcessTick = -1;
- private const int ProcessInterval = 60; // 每60tick处理一次
+ private const int ProcessInterval = 1; // 改为每tick处理,以实现每秒1工作量
public Building_GlobalWorkTable()
{
@@ -28,34 +32,281 @@ namespace WulaFallenEmpire
{
base.SpawnSetup(map, respawningAfterLoad);
powerComp = GetComp();
+ breakdownableComp = GetComp();
}
- public override void Tick()
+ protected override void Tick()
{
base.Tick();
- // 每60tick处理一次生产订单
+ // 改为每tick处理,以实现精确的工作量控制
if (Find.TickManager.TicksGame % ProcessInterval == 0 &&
Find.TickManager.TicksGame != lastProcessTick)
{
lastProcessTick = Find.TickManager.TicksGame;
- if (powerComp == null || powerComp.PowerOn)
+ if (CurrentlyUsableForGlobalBills())
{
- Log.Message($"[DEBUG] Processing orders at tick {Find.TickManager.TicksGame}");
globalOrderStack.ProcessOrders();
}
- else
- {
- Log.Message("[DEBUG] No power, skipping order processing");
- }
}
}
public bool CurrentlyUsableForGlobalBills()
{
- return (powerComp == null || powerComp.PowerOn) &&
- (GetComp() == null || !GetComp().BrokenDown);
+ if (powerComp != null && !powerComp.PowerOn)
+ return false;
+
+ if (breakdownableComp != null && breakdownableComp.BrokenDown)
+ return false;
+
+ return true;
+ }
+
+ // 新增:获取空投扩展参数
+ public GlobalWorkTableAirdropExtension AirdropExtension =>
+ def.GetModExtension();
+
+ // 新增:添加空投命令到技能栏
+ public override IEnumerable GetGizmos()
+ {
+ foreach (Gizmo g in base.GetGizmos())
+ {
+ yield return g;
+ }
+
+ // 只有在有输出物品时才显示空投按钮
+ var globalStorage = Find.World.GetComponent();
+ if (globalStorage != null && globalStorage.outputStorage.Any(kvp => kvp.Value > 0))
+ {
+ yield return new Command_Action
+ {
+ action = StartAirdropTargeting,
+ defaultLabel = "WULA_AirdropProducts".Translate(),
+ defaultDesc = "WULA_AirdropProductsDesc".Translate(),
+ icon = ContentFinder.Get("UI/Commands/Airdrop"),
+ disabledReason = "WULA_CannotAirdrop".Translate()
+ };
+ }
+ }
+
+ // 新增:开始空投目标选择
+ private void StartAirdropTargeting()
+ {
+ // 检查是否有输出物品
+ var globalStorage = Find.World.GetComponent();
+ if (globalStorage == null || !globalStorage.outputStorage.Any(kvp => kvp.Value > 0))
+ {
+ Messages.Message("WULA_NoProductsToAirdrop".Translate(), MessageTypeDefOf.RejectInput);
+ return;
+ }
+ // 启动目标选择
+ Find.Targeter.BeginTargeting(new TargetingParameters
+ {
+ canTargetLocations = true,
+ canTargetPawns = false,
+ canTargetBuildings = false,
+ canTargetItems = false
+ }, OnAirdropTargetSelected, null, OnAirdropTargetingCancelled);
+ }
+
+ private void OnAirdropTargetSelected(LocalTargetInfo target)
+ {
+ ExecuteAirdrop(target.Cell);
+ }
+
+ private void OnAirdropTargetingCancelled()
+ {
+ // 目标选择取消,不做任何操作
+ }
+
+ // 新增:执行空投逻辑
+ private void ExecuteAirdrop(IntVec3 targetCell)
+ {
+ var globalStorage = Find.World.GetComponent();
+ if (globalStorage == null)
+ return;
+
+ // 获取空投参数
+ var airdropExt = AirdropExtension;
+ float maxRange = airdropExt?.maxRange ?? 50f;
+ float randomRange = airdropExt?.randomRange ?? 15f;
+ int minPods = airdropExt?.minPods ?? 1;
+ int maxPods = airdropExt?.maxPods ?? 10;
+
+ // 检查目标距离
+ if (targetCell.DistanceTo(Position) > maxRange)
+ {
+ Messages.Message("WULA_AirdropTargetTooFar".Translate(maxRange), MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ // 查找有效的落点
+ List validDropSpots = FindValidDropSpots(targetCell, randomRange, maxPods);
+
+ if (validDropSpots.Count == 0)
+ {
+ Messages.Message("WULA_NoValidDropSpots".Translate(), MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ // 计算实际空投舱数量
+ int actualPodCount = Mathf.Clamp(validDropSpots.Count, minPods, maxPods);
+
+ // 分配物品到空投舱
+ List> podContents = DistributeItemsToPods(globalStorage, actualPodCount);
+
+ if (podContents.Count == 0)
+ {
+ Messages.Message("WULA_FailedToDistributeItems".Translate(), MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ // 生成空投舱
+ int successfulDrops = 0;
+ for (int i = 0; i < Mathf.Min(actualPodCount, podContents.Count); i++)
+ {
+ if (CreateDropPod(validDropSpots[i], podContents[i]))
+ {
+ successfulDrops++;
+ }
+ }
+
+ Messages.Message("WULA_AirdropSuccessful".Translate(successfulDrops), MessageTypeDefOf.PositiveEvent);
+ }
+
+ // 新增:查找有效落点
+ private List FindValidDropSpots(IntVec3 center, float radius, int maxSpots)
+ {
+ List validSpots = new List();
+ Map map = Map;
+
+ // 在指定半径内搜索有效格子
+ foreach (IntVec3 cell in GenRadial.RadialCellsAround(center, radius, true))
+ {
+ if (!cell.IsValid || !cell.InBounds(map))
+ continue;
+
+ // 检查是否为厚岩顶
+ if (map.roofGrid.RoofAt(cell)?.isThickRoof ?? false)
+ continue;
+
+ // 检查是否可以放置空投舱
+ if (DropCellFinder.IsGoodDropSpot(cell, map, false, true))
+ {
+ validSpots.Add(cell);
+
+ if (validSpots.Count >= maxSpots)
+ break;
+ }
+ }
+
+ // 如果有效格子太少,放宽条件(但仍然排除厚岩顶)
+ if (validSpots.Count < 3)
+ {
+ foreach (IntVec3 cell in GenRadial.RadialCellsAround(center, radius, true))
+ {
+ if (!cell.IsValid || !cell.InBounds(map))
+ continue;
+
+ if (map.roofGrid.RoofAt(cell)?.isThickRoof ?? false)
+ continue;
+
+ if (!validSpots.Contains(cell) && cell.Standable(map))
+ {
+ validSpots.Add(cell);
+
+ if (validSpots.Count >= maxSpots)
+ break;
+ }
+ }
+ }
+
+ return validSpots;
+ }
+
+ // 新增:分配物品到空投舱
+ private List> DistributeItemsToPods(GlobalStorageWorldComponent storage, int podCount)
+ {
+ List> podContents = new List>();
+
+ // 初始化空投舱内容列表
+ for (int i = 0; i < podCount; i++)
+ {
+ podContents.Add(new List());
+ }
+
+ // 获取所有输出物品并转换为Thing列表
+ List allItems = new List();
+ foreach (var kvp in storage.outputStorage.ToList())
+ {
+ if (kvp.Value <= 0) continue;
+
+ ThingDef thingDef = kvp.Key;
+ int remainingCount = kvp.Value;
+
+ // 按照堆叠限制分割物品
+ while (remainingCount > 0)
+ {
+ int stackSize = Mathf.Min(remainingCount, thingDef.stackLimit);
+ Thing thing = ThingMaker.MakeThing(thingDef);
+ thing.stackCount = stackSize;
+ allItems.Add(thing);
+ remainingCount -= stackSize;
+ }
+ }
+
+ if (allItems.Count == 0)
+ return podContents;
+
+ // 平均分配物品到空投舱
+ int currentPod = 0;
+ foreach (Thing item in allItems)
+ {
+ podContents[currentPod].Add(item);
+ currentPod = (currentPod + 1) % podCount;
+ }
+
+ // 从存储中移除已分配的物品
+ foreach (var kvp in storage.outputStorage.ToList())
+ {
+ storage.outputStorage[kvp.Key] = 0;
+ }
+
+ return podContents;
+ }
+
+ // 新增:创建空投舱
+ private bool CreateDropPod(IntVec3 dropCell, List contents)
+ {
+ try
+ {
+ if (contents == null || contents.Count == 0)
+ return false;
+
+ // 创建空投舱信息
+ ActiveTransporterInfo dropPodInfo = new ActiveTransporterInfo();
+
+ // 添加所有物品到空投舱
+ foreach (Thing thing in contents)
+ {
+ dropPodInfo.innerContainer.TryAdd(thing, true);
+ }
+
+ // 设置空投舱参数
+ dropPodInfo.openDelay = 180; // 3秒后打开
+ dropPodInfo.leaveSlag = true;
+
+ // 生成空投舱
+ DropPodUtility.MakeDropPodAt(dropCell, Map, dropPodInfo);
+
+ return true;
+ }
+ catch (System.Exception ex)
+ {
+ Log.Error($"Failed to create drop pod at {dropCell}: {ex}");
+ return false;
+ }
}
}
}
diff --git a/Source/WulaFallenEmpire/GlobalWorkTable/Building_ResourceSubmitter.cs b/Source/WulaFallenEmpire/GlobalWorkTable/Building_ResourceSubmitter.cs
new file mode 100644
index 00000000..c1c65721
--- /dev/null
+++ b/Source/WulaFallenEmpire/GlobalWorkTable/Building_ResourceSubmitter.cs
@@ -0,0 +1,348 @@
+using RimWorld;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ public class Building_ResourceSubmitter : Building_Storage
+ {
+ private CompPowerTrader powerComp;
+ private CompRefuelable refuelableComp;
+ private CompFlickable flickableComp;
+
+ public override void SpawnSetup(Map map, bool respawningAfterLoad)
+ {
+ base.SpawnSetup(map, respawningAfterLoad);
+ powerComp = GetComp();
+ refuelableComp = GetComp();
+ flickableComp = GetComp();
+ }
+
+ ///
+ /// 检查建筑是否可用(电力、燃料、开关等)
+ ///
+ public bool IsOperational
+ {
+ get
+ {
+ if (powerComp != null && !powerComp.PowerOn)
+ return false;
+ if (refuelableComp != null && !refuelableComp.HasFuel)
+ return false;
+ if (flickableComp != null && !flickableComp.SwitchIsOn)
+ return false;
+ return true;
+ }
+ }
+
+ ///
+ /// 获取建筑的中心位置(用于生成 Skyfaller)
+ ///
+ public IntVec3 CenterPosition
+ {
+ get
+ {
+ // 对于偶数尺寸的建筑,返回中心附近的单元格
+ var center = Position + new IntVec3(def.Size.x / 2, 0, def.Size.z / 2);
+ // 确保在建筑范围内
+ return center;
+ }
+ }
+
+ public override IEnumerable GetGizmos()
+ {
+ foreach (Gizmo g in base.GetGizmos())
+ {
+ yield return g;
+ }
+
+ // 添加提交到资源储存器的命令
+ yield return new Command_Action
+ {
+ action = SubmitContentsToStorage,
+ defaultLabel = "WULA_SubmitToStorage".Translate(),
+ defaultDesc = "WULA_SubmitToStorageDesc".Translate(),
+ icon = ContentFinder.Get("UI/Commands/Upload"),
+ disabledReason = GetDisabledReason()
+ };
+ }
+
+ ///
+ /// 获取存储的物品列表 - 修复版本
+ ///
+ private List GetStoredItems()
+ {
+ var items = new List();
+
+ // 方法1:通过直接持有的物品获取(如果建筑本身是容器)
+ if (this is IThingHolder thingHolder)
+ {
+ ThingOwner directlyHeldThings = thingHolder.GetDirectlyHeldThings();
+ if (directlyHeldThings != null)
+ {
+ items.AddRange(directlyHeldThings);
+ }
+ }
+
+ // 方法2:通过存储设置获取地图上的物品
+ if (items.Count == 0)
+ {
+ // 获取建筑的存储设置
+ var storageSettings = GetStoreSettings();
+ if (storageSettings != null)
+ {
+ // 查找地图上被此建筑接受的物品
+ foreach (Thing thing in Map.listerThings.ThingsInGroup(ThingRequestGroup.HaulableAlways))
+ {
+ if (thing.Position.InHorDistOf(Position, 2f) && storageSettings.AllowedToAccept(thing))
+ {
+ items.Add(thing);
+ }
+ }
+ }
+ }
+
+ return items;
+ }
+
+ ///
+ /// 获取禁用原因
+ ///
+ private string GetDisabledReason()
+ {
+ if (!IsOperational)
+ {
+ if (powerComp != null && !powerComp.PowerOn)
+ return "WULA_NoPower".Translate();
+ if (refuelableComp != null && !refuelableComp.HasFuel)
+ return "WULA_NoFuel".Translate();
+ if (flickableComp != null && !flickableComp.SwitchIsOn)
+ return "WULA_SwitchOff".Translate();
+ }
+
+ if (GetStoredItems().Count == 0)
+ return "WULA_NoItemsToSubmit".Translate();
+
+ return string.Empty;
+ }
+
+ ///
+ /// 提交内容到资源储存器
+ ///
+ private void SubmitContentsToStorage()
+ {
+ if (!IsOperational)
+ {
+ Messages.Message("WULA_DeviceNotOperational".Translate(), MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ var storedItems = GetStoredItems();
+ if (storedItems.Count == 0)
+ {
+ Messages.Message("WULA_NoItemsToSubmit".Translate(), MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ // 执行提交逻辑
+ if (TrySubmitItems(storedItems))
+ {
+ // 生成 Skyfaller 演出效果
+ CreateSubmissionEffect();
+
+ Messages.Message("WULA_ItemsSubmitted".Translate(storedItems.Count), MessageTypeDefOf.PositiveEvent);
+ }
+ else
+ {
+ Messages.Message("WULA_SubmissionFailed".Translate(), MessageTypeDefOf.NegativeEvent);
+ }
+ }
+
+ ///
+ /// 尝试提交物品到资源储存器
+ ///
+ private bool TrySubmitItems(List items)
+ {
+ try
+ {
+ var globalStorage = Find.World.GetComponent();
+ if (globalStorage == null)
+ {
+ Log.Error("GlobalStorageWorldComponent not found");
+ return false;
+ }
+
+ int submittedCount = 0;
+ var processedItems = new List();
+
+ foreach (Thing item in items)
+ {
+ if (item == null || item.Destroyed)
+ continue;
+
+ // 检查是否为装备或武器
+ if (IsEquipment(item.def))
+ {
+ // 装备和武器直接添加到输出存储
+ globalStorage.AddToOutputStorage(item.def, item.stackCount);
+ processedItems.Add(item);
+ submittedCount += item.stackCount;
+ }
+ else
+ {
+ // 其他物品添加到输入存储
+ globalStorage.AddToInputStorage(item.def, item.stackCount);
+ processedItems.Add(item);
+ submittedCount += item.stackCount;
+ }
+ }
+
+ // 从世界中移除已提交的物品
+ foreach (Thing item in processedItems)
+ {
+ // 如果物品在建筑的直接容器中
+ if (this is IThingHolder thingHolder)
+ {
+ ThingOwner directlyHeldThings = thingHolder.GetDirectlyHeldThings();
+ if (directlyHeldThings != null && directlyHeldThings.Contains(item))
+ {
+ directlyHeldThings.Remove(item);
+ }
+ }
+
+ // 如果物品在地图上,直接销毁
+ if (item.Spawned)
+ {
+ item.Destroy();
+ }
+ }
+
+ Log.Message($"Successfully submitted {submittedCount} items to global storage");
+ return submittedCount > 0;
+ }
+ catch (System.Exception ex)
+ {
+ Log.Error($"Error submitting items to storage: {ex}");
+ return false;
+ }
+ }
+
+ ///
+ /// 检查是否为装备或武器
+ ///
+ private bool IsEquipment(ThingDef thingDef)
+ {
+ return thingDef.IsApparel || thingDef.IsWeapon || thingDef.category == ThingCategory.Building;
+ }
+
+ ///
+ /// 创建提交效果(Skyfaller)
+ ///
+ private void CreateSubmissionEffect()
+ {
+ try
+ {
+ // 获取 Skyfaller 定义
+ ThingDef skyfallerDef = DefDatabase.GetNamedSilentFail("DropPodIncoming");
+ if (skyfallerDef == null)
+ {
+ // 备用方案:使用简单的效果
+ CreateFallbackEffect();
+ return;
+ }
+
+ // 创建空的 Skyfaller
+ Skyfaller skyfaller = (Skyfaller)ThingMaker.MakeThing(skyfallerDef);
+
+ // 设置位置(建筑中心)
+ IntVec3 dropPos = CenterPosition;
+
+ // 确保位置有效
+ if (!dropPos.IsValid || !dropPos.InBounds(Map))
+ {
+ dropPos = Position; // 回退到建筑位置
+ }
+
+ // 生成 Skyfaller
+ GenSpawn.Spawn(skyfaller, dropPos, Map);
+
+ // 可选:添加一些视觉效果
+ FleckMaker.ThrowLightningGlow(dropPos.ToVector3Shifted(), Map, 2f);
+
+ Log.Message("Created submission skyfaller effect");
+ }
+ catch (System.Exception ex)
+ {
+ Log.Error($"Error creating skyfaller effect: {ex}");
+ CreateFallbackEffect();
+ }
+ }
+
+ ///
+ /// 备用效果(如果 Skyfaller 失败)
+ ///
+ private void CreateFallbackEffect()
+ {
+ try
+ {
+ IntVec3 center = CenterPosition;
+
+ // 生成闪光效果
+ for (int i = 0; i < 3; i++)
+ {
+ FleckMaker.ThrowLightningGlow(center.ToVector3Shifted(), Map, 1.5f);
+ }
+
+ // 生成烟雾效果
+ FleckMaker.ThrowSmoke(center.ToVector3Shifted(), Map, 2f);
+
+ Log.Message("Created fallback submission effect");
+ }
+ catch (System.Exception ex)
+ {
+ Log.Error($"Error creating fallback effect: {ex}");
+ }
+ }
+
+ ///
+ /// 修复的检查字符串方法 - 避免空行问题
+ ///
+ public override string GetInspectString()
+ {
+ StringBuilder stringBuilder = new StringBuilder();
+
+ // 获取基础检查字符串
+ string baseString = base.GetInspectString();
+ if (!baseString.NullOrEmpty())
+ {
+ stringBuilder.Append(baseString);
+ }
+
+ // 获取存储信息
+ var storedItems = GetStoredItems();
+ int itemCount = storedItems.Count;
+ int totalStack = storedItems.Sum(item => item.stackCount);
+
+ // 添加存储信息
+ if (stringBuilder.Length > 0)
+ {
+ stringBuilder.AppendLine();
+ }
+ stringBuilder.Append($"{"WULA_StoredItems".Translate()}: {itemCount} ({totalStack} {"WULA_Items".Translate()})");
+
+ // 添加状态信息(如果不工作)
+ if (!IsOperational)
+ {
+ if (stringBuilder.Length > 0)
+ {
+ stringBuilder.AppendLine();
+ }
+ stringBuilder.Append($"{"WULA_Status".Translate()}: {"WULA_Inoperative".Translate()}");
+ }
+
+ return stringBuilder.ToString();
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/GlobalWorkTable/GlobalProductionOrder.cs b/Source/WulaFallenEmpire/GlobalWorkTable/GlobalProductionOrder.cs
index f66fe8f1..54af7293 100644
--- a/Source/WulaFallenEmpire/GlobalWorkTable/GlobalProductionOrder.cs
+++ b/Source/WulaFallenEmpire/GlobalWorkTable/GlobalProductionOrder.cs
@@ -1,6 +1,7 @@
// GlobalProductionOrder.cs (修复版)
using RimWorld;
using System.Collections.Generic;
+using System.Text;
using Verse;
namespace WulaFallenEmpire
@@ -36,63 +37,71 @@ namespace WulaFallenEmpire
public string Label => recipe.LabelCap;
public string Description => $"{currentCount}/{targetCount} {recipe.products[0].thingDef.label}";
- // 检查是否有足够资源 - 修复逻辑,只检查costList
+ // 检查是否有足够资源 - 修复逻辑
public bool HasEnoughResources()
{
var globalStorage = Find.World.GetComponent();
- if (globalStorage == null)
- {
- Log.Warning("GlobalStorageWorldComponent not found");
- return false;
- }
+ if (globalStorage == null) return false;
- // 只检查costList,不检查ingredients
- if (recipe.costList != null && recipe.costList.Count > 0)
+ // 遍历所有配料要求
+ foreach (var ingredient in recipe.ingredients)
{
- foreach (var cost in recipe.costList)
+ bool hasEnoughForThisIngredient = false;
+
+ // 检查这个配料的所有允许物品类型
+ foreach (var thingDef in ingredient.filter.AllowedThingDefs)
{
- int required = cost.count;
- int available = globalStorage.GetInputStorageCount(cost.thingDef);
+ int requiredCount = ingredient.CountRequiredOfFor(thingDef, recipe);
+ int availableCount = globalStorage.GetInputStorageCount(thingDef);
- Log.Message($"[DEBUG] Checking {cost.thingDef.defName}: required={required}, available={available}");
-
- if (available < required)
+ if (availableCount >= requiredCount)
{
- Log.Message($"[DEBUG] Insufficient {cost.thingDef.defName}");
- return false;
+ hasEnoughForThisIngredient = true;
+ break; // 这个配料有足够的资源
}
}
- Log.Message("[DEBUG] All resources available");
- return true;
- }
- else
- {
- Log.Warning($"Recipe {recipe.defName} has no costList");
- return false;
+
+ // 如果任何一个配料没有足够资源,整个配方就无法生产
+ if (!hasEnoughForThisIngredient)
+ return false;
}
+
+ return true;
}
- // 消耗资源 - 修复逻辑,只消耗costList
+ // 消耗资源 - 修复逻辑
public bool ConsumeResources()
{
var globalStorage = Find.World.GetComponent();
if (globalStorage == null) return false;
- // 只消耗costList中的资源
- if (recipe.costList != null)
+ // 遍历所有配料要求
+ foreach (var ingredient in recipe.ingredients)
{
- foreach (var cost in recipe.costList)
+ bool consumedThisIngredient = false;
+
+ // 尝试消耗这个配料的允许物品类型
+ foreach (var thingDef in ingredient.filter.AllowedThingDefs)
{
- if (!globalStorage.RemoveFromInputStorage(cost.thingDef, cost.count))
+ int requiredCount = ingredient.CountRequiredOfFor(thingDef, recipe);
+ int availableCount = globalStorage.GetInputStorageCount(thingDef);
+
+ if (availableCount >= requiredCount)
{
- Log.Warning($"Failed to consume {cost.count} {cost.thingDef.defName}");
- return false;
+ if (globalStorage.RemoveFromInputStorage(thingDef, requiredCount))
+ {
+ consumedThisIngredient = true;
+ break; // 成功消耗这个配料
+ }
}
- Log.Message($"[DEBUG] Consumed {cost.count} {cost.thingDef.defName}");
}
- return true;
+
+ // 如果任何一个配料无法消耗,整个生产失败
+ if (!consumedThisIngredient)
+ return false;
}
- return false;
+
+ return true;
}
// 生产一个产品
@@ -104,7 +113,6 @@ namespace WulaFallenEmpire
foreach (var product in recipe.products)
{
globalStorage.AddToOutputStorage(product.thingDef, product.count);
- Log.Message($"[DEBUG] Produced {product.count} {product.thingDef.defName}");
}
currentCount++;
@@ -113,8 +121,142 @@ namespace WulaFallenEmpire
if (currentCount >= targetCount)
{
state = ProductionState.Completed;
- Log.Message("[DEBUG] Order completed");
}
}
+
+ // 新增方法:检查并更新状态
+ public void UpdateState()
+ {
+ if (state == ProductionState.Completed) return;
+
+ if (HasEnoughResources())
+ {
+ if (state == ProductionState.Waiting && !paused)
+ {
+ state = ProductionState.Producing;
+ }
+ }
+ else
+ {
+ if (state == ProductionState.Producing)
+ {
+ state = ProductionState.Waiting;
+ progress = 0f; // 重置进度
+ }
+ }
+ }
+
+ // 获取配方材料信息的字符串
+ public string GetIngredientsInfo()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ // 添加标题
+ sb.AppendLine("WULA_RequiredIngredients".Translate() + ":");
+
+ var globalStorage = Find.World.GetComponent();
+
+ foreach (var ingredient in recipe.ingredients)
+ {
+ bool firstAllowedThing = true;
+
+ foreach (var thingDef in ingredient.filter.AllowedThingDefs)
+ {
+ int requiredCount = ingredient.CountRequiredOfFor(thingDef, recipe);
+ int availableCount = globalStorage?.GetInputStorageCount(thingDef) ?? 0;
+
+ if (firstAllowedThing)
+ {
+ sb.Append(" - ");
+ firstAllowedThing = false;
+ }
+ else
+ {
+ sb.Append(" / ");
+ }
+
+ sb.Append($"{requiredCount} {thingDef.label}");
+
+ // 添加可用数量信息
+ if (availableCount < requiredCount)
+ {
+ sb.Append($" ({availableCount}/{requiredCount})");
+ }
+ else
+ {
+ sb.Append($" ({availableCount}/{requiredCount})");
+ }
+ }
+
+ sb.AppendLine();
+ }
+
+ // 添加产品信息
+ sb.AppendLine();
+ sb.AppendLine("WULA_Products".Translate() + ":");
+ foreach (var product in recipe.products)
+ {
+ sb.AppendLine($" - {product.count} {product.thingDef.label}");
+ }
+
+ // 添加工作量信息
+ sb.AppendLine();
+ sb.AppendLine("WULA_WorkAmount".Translate() + ": " + recipe.workAmount.ToStringWorkAmount());
+
+ return sb.ToString();
+ }
+ // 获取简化的材料信息(用于Tooltip)
+ public string GetIngredientsTooltip()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.AppendLine(recipe.LabelCap);
+ sb.AppendLine();
+
+ // 材料需求
+ sb.AppendLine("WULA_RequiredIngredients".Translate() + ":");
+ var globalStorage = Find.World.GetComponent();
+
+ foreach (var ingredient in recipe.ingredients)
+ {
+ bool ingredientSatisfied = false;
+ StringBuilder ingredientSB = new StringBuilder();
+
+ foreach (var thingDef in ingredient.filter.AllowedThingDefs)
+ {
+ int requiredCount = ingredient.CountRequiredOfFor(thingDef, recipe);
+ int availableCount = globalStorage?.GetInputStorageCount(thingDef) ?? 0;
+
+ if (ingredientSB.Length > 0)
+ ingredientSB.Append(" / ");
+
+ ingredientSB.Append($"{requiredCount} {thingDef.label}");
+
+ if (availableCount >= requiredCount)
+ {
+ ingredientSatisfied = true;
+ }
+ }
+
+ if (ingredientSatisfied)
+ {
+ sb.AppendLine($" {ingredientSB}");
+ }
+ else
+ {
+ sb.AppendLine($" {ingredientSB}");
+ }
+ }
+
+ // 产品
+ sb.AppendLine();
+ sb.AppendLine("WULA_Products".Translate() + ":");
+ foreach (var product in recipe.products)
+ {
+ sb.AppendLine($" {product.count} {product.thingDef.label}");
+ }
+
+ return sb.ToString();
+ }
}
}
diff --git a/Source/WulaFallenEmpire/GlobalWorkTable/GlobalProductionOrderStack.cs b/Source/WulaFallenEmpire/GlobalWorkTable/GlobalProductionOrderStack.cs
index c3b8ac49..1c7c82ec 100644
--- a/Source/WulaFallenEmpire/GlobalWorkTable/GlobalProductionOrderStack.cs
+++ b/Source/WulaFallenEmpire/GlobalWorkTable/GlobalProductionOrderStack.cs
@@ -1,4 +1,4 @@
-// GlobalProductionOrderStack.cs (修复版)
+// GlobalProductionOrderStack.cs (调整为每秒1工作量)
using RimWorld;
using System.Collections.Generic;
using Verse;
@@ -10,6 +10,11 @@ namespace WulaFallenEmpire
public Building_GlobalWorkTable table;
public List orders = new List();
+ // 调整为每秒1工作量 - RimWorld中1秒=60ticks
+ private const float WorkPerSecond = 1f;
+ private const float TicksPerSecond = 60f;
+ private const float WorkPerTick = WorkPerSecond / TicksPerSecond;
+
public GlobalProductionOrderStack(Building_GlobalWorkTable table)
{
this.table = table;
@@ -25,7 +30,6 @@ namespace WulaFallenEmpire
{
orders.Add(order);
- // 添加到全局存储中统一管理
var globalStorage = Find.World.GetComponent();
if (globalStorage != null)
{
@@ -48,48 +52,55 @@ namespace WulaFallenEmpire
{
foreach (var order in orders)
{
+ // 首先更新状态
+ order.UpdateState();
+
if (order.paused || order.state == GlobalProductionOrder.ProductionState.Completed)
continue;
- // 检查资源并更新状态
- if (order.state == GlobalProductionOrder.ProductionState.Waiting)
- {
- if (order.HasEnoughResources())
- {
- order.state = GlobalProductionOrder.ProductionState.Producing;
- Log.Message($"[DEBUG] Order {order.recipe.defName} started producing");
- }
- else
- {
- continue;
- }
- }
-
// 生产中
if (order.state == GlobalProductionOrder.ProductionState.Producing)
{
- // 更清晰的进度计算
- float progressPerTick = 1f / (order.recipe.workAmount * 5f); // 调整系数以控制生产速度
- order.progress += progressPerTick;
+ // 计算每tick的工作量进度
+ float workAmount = order.recipe.workAmount;
+ float progressIncrement = WorkPerTick / workAmount;
+
+ order.progress += progressIncrement;
+
+ // 调试信息 - 减少频率以免太吵
+ if (Find.TickManager.TicksGame % 600 == 0) // 每10秒输出一次调试信息
+ {
+ Log.Message($"[DEBUG] Order {order.recipe.defName} progress: {order.progress:P0}, " +
+ $"workAmount: {workAmount}, increment: {progressIncrement:F6}");
+ }
if (order.progress >= 1f)
{
- // 消耗资源并生产 - 在结束时扣除资源
+ // 生产完成,消耗资源
if (order.ConsumeResources())
{
order.Produce();
+ order.UpdateState();
+
+ Log.Message($"[SUCCESS] Produced {order.recipe.products[0].thingDef.defName}, " +
+ $"count: {order.currentCount}/{order.targetCount}, " +
+ $"workAmount: {workAmount}");
}
else
{
- // 资源被其他订单消耗,回到等待状态
order.state = GlobalProductionOrder.ProductionState.Waiting;
order.progress = 0f;
- Log.Message("[DEBUG] Resources consumed by another order, returning to waiting state");
+ Log.Message($"[WARNING] Failed to consume resources for {order.recipe.defName}");
}
}
- else
+ }
+ else if (order.state == GlobalProductionOrder.ProductionState.Waiting && !order.paused)
+ {
+ // 调试:检查为什么订单在等待状态
+ if (Find.TickManager.TicksGame % 1200 == 0) // 每20秒检查一次
{
- Log.Message($"[DEBUG] Order {order.recipe.defName} progress: {order.progress:P0}");
+ Log.Message($"[DEBUG] Order {order.recipe.defName} is waiting. " +
+ $"HasEnoughResources: {order.HasEnoughResources()}, paused: {order.paused}");
}
}
}
diff --git a/Source/WulaFallenEmpire/GlobalWorkTable/GlobalWorkTableAirdropExtension.cs b/Source/WulaFallenEmpire/GlobalWorkTable/GlobalWorkTableAirdropExtension.cs
new file mode 100644
index 00000000..e4f1b0c5
--- /dev/null
+++ b/Source/WulaFallenEmpire/GlobalWorkTable/GlobalWorkTableAirdropExtension.cs
@@ -0,0 +1,17 @@
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ public class GlobalWorkTableAirdropExtension : DefModExtension
+ {
+ public float maxRange = 50f; // 最大空投范围
+ public float randomRange = 15f; // 随机散布范围
+ public int minPods = 1; // 最少空投舱数量
+ public int maxPods = 10; // 最多空投舱数量
+
+ // 必须添加无参数构造函数
+ public GlobalWorkTableAirdropExtension() { }
+
+ // 可以添加其他参数,比如冷却时间、消耗资源等
+ }
+}
diff --git a/Source/WulaFallenEmpire/GlobalWorkTable/ITab_GlobalBills.cs b/Source/WulaFallenEmpire/GlobalWorkTable/ITab_GlobalBills.cs
index fc269678..3b3e732f 100644
--- a/Source/WulaFallenEmpire/GlobalWorkTable/ITab_GlobalBills.cs
+++ b/Source/WulaFallenEmpire/GlobalWorkTable/ITab_GlobalBills.cs
@@ -1,5 +1,7 @@
-// ITab_GlobalBills.cs (修复版)
+// ITab_GlobalBills.cs (添加存储查看功能)
using System.Collections.Generic;
+using System.Linq;
+using System.Text;
using RimWorld;
using UnityEngine;
using Verse;
@@ -33,6 +35,10 @@ namespace WulaFallenEmpire
Widgets.Label(new Rect(mainRect.x, mainRect.y, mainRect.width, 30f), "WULA_GlobalProduction".Translate());
Text.Font = GameFont.Small;
+ // 存储查看按钮 - 放在标题旁边
+ Rect storageButtonRect = new Rect(mainRect.xMax - 120f, mainRect.y, 120f, 25f);
+ DoStorageButton(storageButtonRect);
+
// 开发模式按钮区域
if (Prefs.DevMode)
{
@@ -51,6 +57,95 @@ namespace WulaFallenEmpire
{
Find.WindowStack.Add(new FloatMenu(GenerateRecipeOptions()));
}
+
+ // 绘制鼠标悬停信息 - 使用简单的Tooltip方式
+ if (mouseoverOrder != null)
+ {
+ // 使用Tooltip显示材料信息,不绘制额外窗口
+ // 信息已经在DoOrderRow中的TooltipHandler显示
+ }
+ }
+
+ // 新增:存储查看按钮
+ private void DoStorageButton(Rect rect)
+ {
+ // 绘制按钮
+ if (Widgets.ButtonText(rect, "WULA_ViewStorage".Translate()))
+ {
+ // 点击按钮时也可以做一些事情,比如打开详细存储窗口
+ // 暂时只显示Tooltip
+ SoundDefOf.Click.PlayOneShotOnCamera();
+ }
+
+ // 鼠标悬停时显示存储信息Tooltip
+ if (Mouse.IsOver(rect))
+ {
+ TooltipHandler.TipRegion(rect, GetStorageTooltip());
+ }
+ }
+
+ // 新增:获取存储信息的Tooltip
+ private string GetStorageTooltip()
+ {
+ var globalStorage = Find.World.GetComponent();
+ if (globalStorage == null)
+ return "WULA_NoGlobalStorage".Translate();
+
+ StringBuilder sb = new StringBuilder();
+
+ // 输入存储(原材料)
+ sb.AppendLine("WULA_InputStorage".Translate() + ":");
+ sb.AppendLine();
+
+ var inputItems = globalStorage.inputStorage
+ .Where(kvp => kvp.Value > 0)
+ .OrderByDescending(kvp => kvp.Value)
+ .ThenBy(kvp => kvp.Key.label)
+ .ToList();
+
+ if (inputItems.Count == 0)
+ {
+ sb.AppendLine("WULA_NoItems".Translate());
+ }
+ else
+ {
+ foreach (var kvp in inputItems)
+ {
+ sb.AppendLine($" {kvp.Value} {kvp.Key.label}");
+ }
+ }
+
+ sb.AppendLine();
+
+ // 输出存储(产品)
+ sb.AppendLine("WULA_OutputStorage".Translate() + ":");
+ sb.AppendLine();
+
+ var outputItems = globalStorage.outputStorage
+ .Where(kvp => kvp.Value > 0)
+ .OrderByDescending(kvp => kvp.Value)
+ .ThenBy(kvp => kvp.Key.label)
+ .ToList();
+
+ if (outputItems.Count == 0)
+ {
+ sb.AppendLine("WULA_NoItems".Translate());
+ }
+ else
+ {
+ foreach (var kvp in outputItems)
+ {
+ sb.AppendLine($" {kvp.Value} {kvp.Key.label}");
+ }
+ }
+
+ // 添加存储统计信息
+ sb.AppendLine();
+ sb.AppendLine("WULA_StorageStats".Translate());
+ sb.AppendLine($" {inputItems.Count} {("WULA_InputItems".Translate())}");
+ sb.AppendLine($" {outputItems.Count} {("WULA_OutputItems".Translate())}");
+
+ return sb.ToString();
}
private void DoDevButtons(Rect rect)
@@ -211,15 +306,28 @@ namespace WulaFallenEmpire
GlobalProductionOrder.ProductionState.Completed => "WULA_Completed".Translate(),
_ => "WULA_Unknown".Translate()
};
+
+ // 如果暂停,在状态前添加暂停标识
+ if (order.paused && order.state != GlobalProductionOrder.ProductionState.Completed)
+ {
+ statusText = $"[Paused] {statusText}";
+ }
+
Widgets.Label(statusRect, statusText);
}
// 控制按钮
float buttonY = rect.y + padding;
Rect pauseButtonRect = new Rect(rect.xMax - 90f, buttonY, 40f, 25f);
- if (Widgets.ButtonText(pauseButtonRect, order.paused ? "WULA_Resume".Translate() : "WULA_Pause".Translate()))
+
+ string pauseButtonText = order.paused ? "WULA_Resume".Translate() : "WULA_Pause".Translate();
+ if (Widgets.ButtonText(pauseButtonRect, pauseButtonText))
{
order.paused = !order.paused;
+
+ // 暂停/恢复时更新状态
+ order.UpdateState();
+
SoundDefOf.Click.PlayOneShotOnCamera();
}
@@ -230,9 +338,10 @@ namespace WulaFallenEmpire
SoundDefOf.Click.PlayOneShotOnCamera();
}
- // 资源检查提示 - 修复逻辑
- bool shouldShowRedBorder = (order.state == GlobalProductionOrder.ProductionState.Waiting && !order.HasEnoughResources());
- if (shouldShowRedBorder)
+ // 资源检查提示 - 只在等待资源且不暂停时显示红色边框
+ if (!order.HasEnoughResources() &&
+ order.state == GlobalProductionOrder.ProductionState.Waiting &&
+ !order.paused)
{
TooltipHandler.TipRegion(rect, "WULA_InsufficientResources".Translate());
GUI.color = Color.red;
@@ -240,6 +349,12 @@ namespace WulaFallenEmpire
GUI.color = Color.white;
}
+ // 添加材料信息的Tooltip - 这是核心功能
+ if (Mouse.IsOver(rect))
+ {
+ TooltipHandler.TipRegion(rect, order.GetIngredientsTooltip());
+ }
+
return Mouse.IsOver(rect);
}
diff --git a/Source/WulaFallenEmpire/GlobalWorkTable/ITab_ResourceSubmitterContents.cs b/Source/WulaFallenEmpire/GlobalWorkTable/ITab_ResourceSubmitterContents.cs
new file mode 100644
index 00000000..6ec4ad58
--- /dev/null
+++ b/Source/WulaFallenEmpire/GlobalWorkTable/ITab_ResourceSubmitterContents.cs
@@ -0,0 +1,409 @@
+using RimWorld;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ public class ITab_ResourceSubmitterContents : ITab
+ {
+ private Vector2 scrollPosition;
+ private float scrollViewHeight;
+
+ private static readonly Vector2 WinSize = new Vector2(420f, 480f);
+
+ protected Building_ResourceSubmitter SelSubmitter => (Building_ResourceSubmitter)base.SelThing;
+
+ public ITab_ResourceSubmitterContents()
+ {
+ size = WinSize;
+ labelKey = "WULA_SubmitterContents";
+ tutorTag = "SubmitterContents";
+ }
+
+ protected override void FillTab()
+ {
+ Rect mainRect = new Rect(0f, 0f, WinSize.x, WinSize.y).ContractedBy(10f);
+
+ // 标题区域
+ Text.Font = GameFont.Medium;
+ Widgets.Label(new Rect(mainRect.x, mainRect.y, mainRect.width, 30f), "WULA_SubmitterContents".Translate());
+ Text.Font = GameFont.Small;
+
+ // 状态信息
+ Rect statusRect = new Rect(mainRect.x, mainRect.y + 35f, mainRect.width, 40f);
+ DoStatusInfo(statusRect);
+
+ // 存储物品列表
+ Rect itemsRect = new Rect(mainRect.x, mainRect.y + 80f, mainRect.width, mainRect.height - 120f);
+ DoItemsListing(itemsRect);
+
+ // 操作按钮区域
+ Rect buttonsRect = new Rect(mainRect.x, mainRect.yMax - 35f, mainRect.width, 30f);
+ DoActionButtons(buttonsRect);
+ }
+
+ private void DoStatusInfo(Rect rect)
+ {
+ Widgets.DrawMenuSection(rect);
+ Rect innerRect = rect.ContractedBy(5f);
+
+ // 运行状态
+ string statusText = SelSubmitter.IsOperational ?
+ "WULA_Operational".Translate() : "WULA_Inoperative".Translate();
+ Color statusColor = SelSubmitter.IsOperational ? Color.green : Color.red;
+
+ Text.Anchor = TextAnchor.MiddleLeft;
+ Rect statusLabelRect = new Rect(innerRect.x, innerRect.y, innerRect.width * 0.6f, innerRect.height);
+ Widgets.Label(statusLabelRect, "WULA_Status".Translate() + ": " + statusText);
+
+ // 物品数量
+ var storedItems = SelSubmitter.GetStoredItems();
+ int totalItems = storedItems.Count;
+ int totalStacks = storedItems.Sum(item => item.stackCount);
+
+ Rect countRect = new Rect(innerRect.x + innerRect.width * 0.6f, innerRect.y, innerRect.width * 0.4f, innerRect.height);
+ Widgets.Label(countRect, $"{totalItems} {"WULA_Items".Translate()} ({totalStacks} {"WULA_Stacks".Translate()})");
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ // 状态颜色指示
+ GUI.color = statusColor;
+ Widgets.DrawBox(new Rect(statusLabelRect.x - 15f, statusLabelRect.y + 7f, 10f, 10f), 1);
+ GUI.color = Color.white;
+ }
+
+ private void DoItemsListing(Rect rect)
+ {
+ Widgets.DrawMenuSection(rect);
+ Rect outRect = rect.ContractedBy(5f);
+ Rect viewRect = new Rect(0f, 0f, outRect.width - 16f, scrollViewHeight);
+
+ var storedItems = SelSubmitter.GetStoredItems();
+
+ Widgets.BeginScrollView(outRect, ref scrollPosition, viewRect);
+
+ float curY = 0f;
+
+ // 列标题
+ Rect headerRect = new Rect(0f, curY, viewRect.width, 25f);
+ DoColumnHeaders(headerRect);
+ curY += 30f;
+
+ if (storedItems.Count == 0)
+ {
+ Rect emptyRect = new Rect(0f, curY, viewRect.width, 30f);
+ Text.Anchor = TextAnchor.MiddleCenter;
+ Widgets.Label(emptyRect, "WULA_NoItemsInStorage".Translate());
+ Text.Anchor = TextAnchor.UpperLeft;
+ curY += 35f;
+ }
+ else
+ {
+ // 按物品类型分组显示
+ var groupedItems = storedItems
+ .GroupBy(item => item.def)
+ .OrderByDescending(g => g.Sum(item => item.stackCount))
+ .ThenBy(g => g.Key.label);
+
+ foreach (var group in groupedItems)
+ {
+ ThingDef thingDef = group.Key;
+ int totalCount = group.Sum(item => item.stackCount);
+ int stackCount = group.Count();
+
+ Rect itemRect = new Rect(0f, curY, viewRect.width, 28f);
+ if (DoItemRow(itemRect, thingDef, totalCount, stackCount))
+ {
+ // 鼠标悬停时显示详细信息
+ string tooltip = GetItemTooltip(thingDef, totalCount, stackCount);
+ TooltipHandler.TipRegion(itemRect, tooltip);
+ }
+
+ curY += 32f;
+
+ // 分隔线
+ if (curY < viewRect.height - 5f)
+ {
+ Widgets.DrawLineHorizontal(0f, curY - 2f, viewRect.width);
+ curY += 5f;
+ }
+ }
+ }
+
+ if (Event.current.type == EventType.Layout)
+ {
+ scrollViewHeight = curY;
+ }
+
+ Widgets.EndScrollView();
+ }
+
+ private void DoColumnHeaders(Rect rect)
+ {
+ float columnWidth = rect.width / 4f;
+
+ // 物品名称列
+ Rect nameRect = new Rect(rect.x, rect.y, columnWidth * 2f, rect.height);
+ Text.Anchor = TextAnchor.MiddleLeft;
+ Widgets.Label(nameRect, "WULA_ItemName".Translate());
+
+ // 数量列
+ Rect countRect = new Rect(rect.x + columnWidth * 2f, rect.y, columnWidth, rect.height);
+ Text.Anchor = TextAnchor.MiddleCenter;
+ Widgets.Label(countRect, "WULA_Count".Translate());
+
+ // 堆叠列
+ Rect stacksRect = new Rect(rect.x + columnWidth * 3f, rect.y, columnWidth, rect.height);
+ Widgets.Label(stacksRect, "WULA_Stacks".Translate());
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ // 标题下划线
+ Widgets.DrawLineHorizontal(rect.x, rect.yMax - 2f, rect.width);
+ }
+
+ private bool DoItemRow(Rect rect, ThingDef thingDef, int totalCount, int stackCount)
+ {
+ Widgets.DrawHighlightIfMouseover(rect);
+
+ float columnWidth = rect.width / 4f;
+
+ // 物品图标
+ Rect iconRect = new Rect(rect.x + 2f, rect.y + 2f, 24f, 24f);
+ Widgets.ThingIcon(iconRect, thingDef);
+
+ // 物品名称
+ Rect nameRect = new Rect(rect.x + 30f, rect.y, columnWidth * 2f - 30f, rect.height);
+ Text.Anchor = TextAnchor.MiddleLeft;
+ string label = thingDef.LabelCap;
+ if (label.Length > 25)
+ {
+ label = label.Substring(0, 25) + "...";
+ }
+ Widgets.Label(nameRect, label);
+
+ // 总数量
+ Rect countRect = new Rect(rect.x + columnWidth * 2f, rect.y, columnWidth, rect.height);
+ Text.Anchor = TextAnchor.MiddleCenter;
+ Widgets.Label(countRect, totalCount.ToString());
+
+ // 堆叠数量
+ Rect stacksRect = new Rect(rect.x + columnWidth * 3f, rect.y, columnWidth, rect.height);
+ Widgets.Label(stacksRect, stackCount.ToString());
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ return Mouse.IsOver(rect);
+ }
+
+ private string GetItemTooltip(ThingDef thingDef, int totalCount, int stackCount)
+ {
+ return string.Format("WULA_ItemTooltip".Translate(),
+ thingDef.LabelCap,
+ totalCount,
+ stackCount,
+ thingDef.BaseMarketValue * totalCount);
+ }
+
+ private void DoActionButtons(Rect rect)
+ {
+ float buttonWidth = rect.width / 2f - 5f;
+
+ // 提交按钮
+ Rect submitRect = new Rect(rect.x, rect.y, buttonWidth, rect.height);
+ bool hasItems = SelSubmitter.GetStoredItems().Count > 0;
+ bool isOperational = SelSubmitter.IsOperational;
+
+ string submitLabel = "WULA_SubmitToStorage".Translate();
+ string submitDesc = "WULA_SubmitToStorageDesc".Translate();
+
+ if (!isOperational)
+ {
+ submitLabel = "WULA_DeviceInoperative".Translate();
+ submitDesc = GetInoperativeReason();
+ }
+ else if (!hasItems)
+ {
+ submitLabel = "WULA_NoItemsToSubmit".Translate();
+ submitDesc = "WULA_NoItemsToSubmitDesc".Translate();
+ }
+
+ if (Widgets.ButtonText(submitRect, submitLabel))
+ {
+ if (isOperational && hasItems)
+ {
+ SelSubmitter.SubmitContentsToStorage();
+ }
+ else if (!isOperational)
+ {
+ Messages.Message(GetInoperativeReason(), MessageTypeDefOf.RejectInput);
+ }
+ else
+ {
+ Messages.Message("WULA_NoItemsToSubmit".Translate(), MessageTypeDefOf.RejectInput);
+ }
+ }
+
+ // 工具提示
+ if (Mouse.IsOver(submitRect))
+ {
+ TooltipHandler.TipRegion(submitRect, submitDesc);
+ }
+
+ // 查看全局存储按钮
+ Rect storageRect = new Rect(rect.x + buttonWidth + 10f, rect.y, buttonWidth, rect.height);
+ if (Widgets.ButtonText(storageRect, "WULA_ViewGlobalStorage".Translate()))
+ {
+ Find.WindowStack.Add(new Dialog_GlobalStorage());
+ }
+
+ if (Mouse.IsOver(storageRect))
+ {
+ TooltipHandler.TipRegion(storageRect, "WULA_ViewGlobalStorageDesc".Translate());
+ }
+ }
+
+ private string GetInoperativeReason()
+ {
+ var submitter = SelSubmitter;
+
+ if (submitter.powerComp != null && !submitter.powerComp.PowerOn)
+ return "WULA_NoPower".Translate();
+
+ if (submitter.refuelableComp != null && !submitter.refuelableComp.HasFuel)
+ return "WULA_NoFuel".Translate();
+
+ if (submitter.flickableComp != null && !submitter.flickableComp.SwitchIsOn)
+ return "WULA_SwitchOff".Translate();
+
+ return "WULA_UnknownReason".Translate();
+ }
+
+ public override void TabUpdate()
+ {
+ base.TabUpdate();
+ }
+ }
+
+ // 简单的全局存储查看对话框
+ public class Dialog_GlobalStorage : Window
+ {
+ private Vector2 scrollPosition;
+ private float scrollViewHeight;
+
+ public override Vector2 InitialSize => new Vector2(500f, 600f);
+
+ public Dialog_GlobalStorage()
+ {
+ forcePause = false;
+ doCloseX = true;
+ doCloseButton = true;
+ closeOnClickedOutside = true;
+ absorbInputAroundWindow = true;
+ }
+
+ public override void DoWindowContents(Rect inRect)
+ {
+ var globalStorage = Find.World.GetComponent();
+ if (globalStorage == null)
+ {
+ Widgets.Label(inRect, "WULA_NoGlobalStorage".Translate());
+ return;
+ }
+
+ Rect titleRect = new Rect(0f, 0f, inRect.width, 30f);
+ Text.Font = GameFont.Medium;
+ Widgets.Label(titleRect, "WULA_GlobalStorage".Translate());
+ Text.Font = GameFont.Small;
+
+ // 输入存储
+ Rect inputRect = new Rect(0f, 40f, inRect.width, (inRect.height - 100f) / 2f);
+ DoStorageSection(inputRect, globalStorage.inputStorage, "WULA_InputStorage".Translate());
+
+ // 输出存储
+ Rect outputRect = new Rect(0f, 40f + (inRect.height - 100f) / 2f + 10f, inRect.width, (inRect.height - 100f) / 2f);
+ DoStorageSection(outputRect, globalStorage.outputStorage, "WULA_OutputStorage".Translate());
+ }
+
+ private void DoStorageSection(Rect rect, Dictionary storage, string label)
+ {
+ Widgets.DrawMenuSection(rect);
+ Rect innerRect = rect.ContractedBy(5f);
+
+ // 标题
+ Text.Font = GameFont.Medium;
+ Widgets.Label(new Rect(innerRect.x, innerRect.y, innerRect.width, 25f), label);
+ Text.Font = GameFont.Small;
+
+ Rect listRect = new Rect(innerRect.x, innerRect.y + 30f, innerRect.width, innerRect.height - 35f);
+ DoStorageList(listRect, storage);
+ }
+
+ private void DoStorageList(Rect rect, Dictionary storage)
+ {
+ Rect outRect = rect;
+ Rect viewRect = new Rect(0f, 0f, rect.width - 16f, scrollViewHeight);
+
+ var items = storage
+ .Where(kvp => kvp.Value > 0)
+ .OrderByDescending(kvp => kvp.Value)
+ .ThenBy(kvp => kvp.Key.label)
+ .ToList();
+
+ Widgets.BeginScrollView(outRect, ref scrollPosition, viewRect);
+
+ float curY = 0f;
+
+ if (items.Count == 0)
+ {
+ Rect emptyRect = new Rect(0f, curY, viewRect.width, 30f);
+ Text.Anchor = TextAnchor.MiddleCenter;
+ Widgets.Label(emptyRect, "WULA_NoItems".Translate());
+ Text.Anchor = TextAnchor.UpperLeft;
+ curY += 35f;
+ }
+ else
+ {
+ foreach (var kvp in items)
+ {
+ Rect itemRect = new Rect(0f, curY, viewRect.width, 25f);
+ DoStorageItemRow(itemRect, kvp.Key, kvp.Value);
+ curY += 28f;
+ }
+ }
+
+ if (Event.current.type == EventType.Layout)
+ {
+ scrollViewHeight = curY;
+ }
+
+ Widgets.EndScrollView();
+ }
+
+ private void DoStorageItemRow(Rect rect, ThingDef thingDef, int count)
+ {
+ Widgets.DrawHighlightIfMouseover(rect);
+
+ // 图标
+ Rect iconRect = new Rect(rect.x + 2f, rect.y + 2f, 20f, 20f);
+ Widgets.ThingIcon(iconRect, thingDef);
+
+ // 名称
+ Rect nameRect = new Rect(rect.x + 25f, rect.y, rect.width - 80f, rect.height);
+ Text.Anchor = TextAnchor.MiddleLeft;
+ Widgets.Label(nameRect, thingDef.LabelCap);
+
+ // 数量
+ Rect countRect = new Rect(rect.xMax - 50f, rect.y, 50f, rect.height);
+ Text.Anchor = TextAnchor.MiddleRight;
+ Widgets.Label(countRect, count.ToString());
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ // 工具提示
+ if (Mouse.IsOver(rect))
+ {
+ string tooltip = $"{thingDef.LabelCap}\n{count} {"WULA_Items".Translate()}\n{"WULA_Value".Translate()}: {thingDef.BaseMarketValue * count}";
+ TooltipHandler.TipRegion(rect, tooltip);
+ }
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj
index ecabd352..71bdf8a4 100644
--- a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj
+++ b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj
@@ -107,9 +107,11 @@
+
+