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 @@ + +