1
This commit is contained in:
@@ -35,16 +35,17 @@ namespace WulaFallenEmpire
|
||||
breakdownableComp = GetComp<CompBreakdownable>();
|
||||
}
|
||||
|
||||
// 在 Building_GlobalWorkTable 类中修改 Tick 方法
|
||||
protected override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
|
||||
// 改为每tick处理,以实现精确的工作量控制
|
||||
if (Find.TickManager.TicksGame % ProcessInterval == 0 &&
|
||||
|
||||
// 修复:改为每60 ticks(1秒)处理一次,避免每tick处理导致的精度问题
|
||||
if (Find.TickManager.TicksGame % 60 == 0 &&
|
||||
Find.TickManager.TicksGame != lastProcessTick)
|
||||
{
|
||||
lastProcessTick = Find.TickManager.TicksGame;
|
||||
|
||||
|
||||
if (CurrentlyUsableForGlobalBills())
|
||||
{
|
||||
globalOrderStack.ProcessOrders();
|
||||
@@ -84,7 +85,7 @@ namespace WulaFallenEmpire
|
||||
action = StartAirdropTargeting,
|
||||
defaultLabel = "WULA_AirdropProducts".Translate(),
|
||||
defaultDesc = "WULA_AirdropProductsDesc".Translate(),
|
||||
icon = ContentFinder<Texture2D>.Get("UI/Commands/Airdrop"),
|
||||
icon = ContentFinder<Texture2D>.Get("Wula/UI/Commands/WULA_AirdropProducts"),
|
||||
disabledReason = "WULA_CannotAirdrop".Translate()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,14 +3,17 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class Building_ResourceSubmitter : Building_Storage
|
||||
public class Building_ResourceSubmitter : Building
|
||||
{
|
||||
private CompPowerTrader powerComp;
|
||||
private CompRefuelable refuelableComp;
|
||||
private CompFlickable flickableComp;
|
||||
public CompPowerTrader powerComp;
|
||||
public CompRefuelable refuelableComp;
|
||||
public CompFlickable flickableComp;
|
||||
public CompResourceSubmitter submitterComp;
|
||||
|
||||
public override void SpawnSetup(Map map, bool respawningAfterLoad)
|
||||
{
|
||||
@@ -18,11 +21,9 @@ namespace WulaFallenEmpire
|
||||
powerComp = GetComp<CompPowerTrader>();
|
||||
refuelableComp = GetComp<CompRefuelable>();
|
||||
flickableComp = GetComp<CompFlickable>();
|
||||
submitterComp = GetComp<CompResourceSubmitter>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查建筑是否可用(电力、燃料、开关等)
|
||||
/// </summary>
|
||||
public bool IsOperational
|
||||
{
|
||||
get
|
||||
@@ -37,309 +38,31 @@ namespace WulaFallenEmpire
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取建筑的中心位置(用于生成 Skyfaller)
|
||||
/// </summary>
|
||||
public IntVec3 CenterPosition
|
||||
public string GetInoperativeReason()
|
||||
{
|
||||
get
|
||||
{
|
||||
// 对于偶数尺寸的建筑,返回中心附近的单元格
|
||||
var center = Position + new IntVec3(def.Size.x / 2, 0, def.Size.z / 2);
|
||||
// 确保在建筑范围内
|
||||
return center;
|
||||
}
|
||||
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();
|
||||
return "WULA_UnknownReason".Translate();
|
||||
}
|
||||
|
||||
public override IEnumerable<Gizmo> 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<Texture2D>.Get("UI/Commands/Upload"),
|
||||
disabledReason = GetDisabledReason()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取存储的物品列表 - 修复版本
|
||||
/// </summary>
|
||||
private List<Thing> GetStoredItems()
|
||||
{
|
||||
var items = new List<Thing>();
|
||||
|
||||
// 方法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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取禁用原因
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提交内容到资源储存器
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试提交物品到资源储存器
|
||||
/// </summary>
|
||||
private bool TrySubmitItems(List<Thing> items)
|
||||
{
|
||||
try
|
||||
{
|
||||
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||
if (globalStorage == null)
|
||||
{
|
||||
Log.Error("GlobalStorageWorldComponent not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
int submittedCount = 0;
|
||||
var processedItems = new List<Thing>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否为装备或武器
|
||||
/// </summary>
|
||||
private bool IsEquipment(ThingDef thingDef)
|
||||
{
|
||||
return thingDef.IsApparel || thingDef.IsWeapon || thingDef.category == ThingCategory.Building;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建提交效果(Skyfaller)
|
||||
/// </summary>
|
||||
private void CreateSubmissionEffect()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取 Skyfaller 定义
|
||||
ThingDef skyfallerDef = DefDatabase<ThingDef>.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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 备用效果(如果 Skyfaller 失败)
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修复的检查字符串方法 - 避免空行问题
|
||||
/// </summary>
|
||||
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()}");
|
||||
if (stringBuilder.Length > 0) stringBuilder.AppendLine();
|
||||
stringBuilder.Append($"{"WULA_Status".Translate()}: {"WULA_Inoperative".Translate()} - {GetInoperativeReason()}");
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class Command_LoadToResourceSubmitter : Command
|
||||
{
|
||||
public CompResourceSubmitter submitterComp;
|
||||
|
||||
public override void ProcessInput(Event ev)
|
||||
{
|
||||
base.ProcessInput(ev);
|
||||
|
||||
if (submitterComp?.parent == null) return;
|
||||
|
||||
// 打开装载界面,类似运输舱的界面
|
||||
Find.WindowStack.Add(new Dialog_LoadResourceSubmitter(submitterComp));
|
||||
}
|
||||
}
|
||||
}
|
||||
391
Source/WulaFallenEmpire/GlobalWorkTable/CompResourceSubmitter.cs
Normal file
391
Source/WulaFallenEmpire/GlobalWorkTable/CompResourceSubmitter.cs
Normal file
@@ -0,0 +1,391 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompResourceSubmitter : ThingComp, IThingHolder
|
||||
{
|
||||
public ThingOwner innerContainer;
|
||||
public List<TransferableOneWay> leftToLoad;
|
||||
|
||||
private bool massUsageDirty = true;
|
||||
private float cachedMassUsage;
|
||||
|
||||
public CompProperties_ResourceSubmitter Props => (CompProperties_ResourceSubmitter)props;
|
||||
|
||||
public float MassUsage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (massUsageDirty)
|
||||
{
|
||||
massUsageDirty = false;
|
||||
cachedMassUsage = CollectionsMassCalculator.MassUsage(innerContainer, IgnorePawnsInventoryMode.IgnoreIfAssignedToUnload, includePawnsMass: false);
|
||||
}
|
||||
return cachedMassUsage;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OverMassCapacity => MassUsage > Props.massCapacity;
|
||||
|
||||
public bool AnythingLeftToLoad => FirstThingLeftToLoad != null;
|
||||
|
||||
public Thing FirstThingLeftToLoad
|
||||
{
|
||||
get
|
||||
{
|
||||
if (leftToLoad == null) return null;
|
||||
for (int i = 0; i < leftToLoad.Count; i++)
|
||||
{
|
||||
if (leftToLoad[i].CountToTransfer != 0 && leftToLoad[i].HasAnyThing)
|
||||
return leftToLoad[i].AnyThing;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public CompResourceSubmitter()
|
||||
{
|
||||
innerContainer = new ThingOwner<Thing>(this);
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
Scribe_Deep.Look(ref innerContainer, "innerContainer", this);
|
||||
Scribe_Collections.Look(ref leftToLoad, "leftToLoad", LookMode.Deep);
|
||||
|
||||
if (Scribe.mode == LoadSaveMode.Saving)
|
||||
{
|
||||
leftToLoad?.RemoveWhere(t => t == null);
|
||||
}
|
||||
}
|
||||
|
||||
public ThingOwner GetDirectlyHeldThings()
|
||||
{
|
||||
return innerContainer;
|
||||
}
|
||||
|
||||
public void GetChildHolders(List<IThingHolder> outChildren)
|
||||
{
|
||||
ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, GetDirectlyHeldThings());
|
||||
}
|
||||
|
||||
// 在 CompResourceSubmitter 类中添加或更新以下方法:
|
||||
public override void PostSpawnSetup(bool respawningAfterLoad)
|
||||
{
|
||||
base.PostSpawnSetup(respawningAfterLoad);
|
||||
massUsageDirty = true;
|
||||
}
|
||||
|
||||
public override void PostDeSpawn(Map map, DestroyMode mode = DestroyMode.Vanish)
|
||||
{
|
||||
base.PostDeSpawn(map, mode);
|
||||
if (mode != DestroyMode.WillReplace)
|
||||
{
|
||||
innerContainer.TryDropAll(parent.Position, map, ThingPlaceMode.Near);
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Gizmo> CompGetGizmosExtra()
|
||||
{
|
||||
foreach (Gizmo g in base.CompGetGizmosExtra())
|
||||
{
|
||||
yield return g;
|
||||
}
|
||||
|
||||
// 装载命令
|
||||
Command_LoadToResourceSubmitter loadCommand = new Command_LoadToResourceSubmitter();
|
||||
loadCommand.defaultLabel = "WULA_LoadResourceSubmitter".Translate();
|
||||
loadCommand.defaultDesc = "WULA_LoadResourceSubmitterDesc".Translate();
|
||||
loadCommand.icon = ContentFinder<Texture2D>.Get("UI/Commands/LoadTransporter");
|
||||
loadCommand.submitterComp = this;
|
||||
|
||||
// 禁用检查
|
||||
if (!parent.Spawned)
|
||||
{
|
||||
loadCommand.Disable("WULA_NotSpawned".Translate());
|
||||
}
|
||||
else if (!IsOperational())
|
||||
{
|
||||
loadCommand.Disable(GetInoperativeReason());
|
||||
}
|
||||
|
||||
yield return loadCommand;
|
||||
|
||||
// 取消装载/卸载命令
|
||||
if (innerContainer.Any || AnythingLeftToLoad)
|
||||
{
|
||||
Command_Action cancelCommand = new Command_Action();
|
||||
cancelCommand.defaultLabel = innerContainer.Any ? "WULA_Unload".Translate() : "WULA_CancelLoad".Translate();
|
||||
cancelCommand.defaultDesc = innerContainer.Any ? "WULA_UnloadDesc".Translate() : "WULA_CancelLoadDesc".Translate();
|
||||
cancelCommand.icon = ContentFinder<Texture2D>.Get("UI/Designators/Cancel");
|
||||
cancelCommand.action = CancelLoad;
|
||||
yield return cancelCommand;
|
||||
}
|
||||
|
||||
// 发射命令
|
||||
Command_Action launchCommand = new Command_Action();
|
||||
launchCommand.defaultLabel = "WULA_LaunchSubmitter".Translate();
|
||||
launchCommand.defaultDesc = "WULA_LaunchSubmitterDesc".Translate();
|
||||
launchCommand.icon = ContentFinder<Texture2D>.Get("UI/Commands/Launch");
|
||||
launchCommand.action = TryLaunch;
|
||||
|
||||
// 发射条件检查
|
||||
if (!parent.Spawned)
|
||||
{
|
||||
launchCommand.Disable("WULA_NotSpawned".Translate());
|
||||
}
|
||||
else if (!IsOperational())
|
||||
{
|
||||
launchCommand.Disable(GetInoperativeReason());
|
||||
}
|
||||
else if (!innerContainer.Any)
|
||||
{
|
||||
launchCommand.Disable("WULA_NoItemsToSubmit".Translate());
|
||||
}
|
||||
else if (OverMassCapacity)
|
||||
{
|
||||
launchCommand.Disable("WULA_OverMassCapacity".Translate(MassUsage.ToString("F1"), Props.massCapacity.ToString("F1")));
|
||||
}
|
||||
|
||||
yield return launchCommand;
|
||||
}
|
||||
|
||||
public override string CompInspectStringExtra()
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.Append("WULA_SubmitterContents".Translate() + ": " + innerContainer.ContentsString.CapitalizeFirst());
|
||||
|
||||
string massString = "WULA_Mass".Translate() + ": " + MassUsage.ToString("F1") + " / " + Props.massCapacity.ToString("F1") + " kg";
|
||||
stringBuilder.AppendLine().Append(OverMassCapacity ? massString.Colorize(ColorLibrary.RedReadable) : massString);
|
||||
|
||||
if (!IsOperational())
|
||||
{
|
||||
stringBuilder.AppendLine().Append("WULA_Status".Translate() + ": " + "WULA_Inoperative".Translate().Colorize(ColorLibrary.RedReadable));
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
public void AddToTheToLoadList(TransferableOneWay t, int count)
|
||||
{
|
||||
if (!t.HasAnyThing || count <= 0) return;
|
||||
|
||||
if (leftToLoad == null)
|
||||
{
|
||||
leftToLoad = new List<TransferableOneWay>();
|
||||
}
|
||||
|
||||
TransferableOneWay existing = TransferableUtility.TransferableMatching(t.AnyThing, leftToLoad, TransferAsOneMode.PodsOrCaravanPacking);
|
||||
if (existing != null)
|
||||
{
|
||||
for (int i = 0; i < t.things.Count; i++)
|
||||
{
|
||||
if (!existing.things.Contains(t.things[i]))
|
||||
{
|
||||
existing.things.Add(t.things[i]);
|
||||
}
|
||||
}
|
||||
if (existing.CanAdjustBy(count).Accepted)
|
||||
{
|
||||
existing.AdjustBy(count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TransferableOneWay newTransferable = new TransferableOneWay();
|
||||
leftToLoad.Add(newTransferable);
|
||||
newTransferable.things.AddRange(t.things);
|
||||
newTransferable.AdjustTo(count);
|
||||
}
|
||||
}
|
||||
|
||||
public void Notify_ThingAdded(Thing t)
|
||||
{
|
||||
SubtractFromToLoadList(t, t.stackCount);
|
||||
massUsageDirty = true;
|
||||
}
|
||||
|
||||
public void Notify_ThingRemoved(Thing t)
|
||||
{
|
||||
massUsageDirty = true;
|
||||
}
|
||||
|
||||
public void Notify_ThingAddedAndMergedWith(Thing t, int mergedCount)
|
||||
{
|
||||
SubtractFromToLoadList(t, mergedCount);
|
||||
massUsageDirty = true;
|
||||
}
|
||||
|
||||
private int SubtractFromToLoadList(Thing t, int count)
|
||||
{
|
||||
if (leftToLoad == null) return 0;
|
||||
|
||||
TransferableOneWay transferable = TransferableUtility.TransferableMatchingDesperate(t, leftToLoad, TransferAsOneMode.PodsOrCaravanPacking);
|
||||
if (transferable == null || transferable.CountToTransfer <= 0) return 0;
|
||||
|
||||
int num = Mathf.Min(count, transferable.CountToTransfer);
|
||||
transferable.AdjustBy(-num);
|
||||
|
||||
if (transferable.CountToTransfer <= 0)
|
||||
{
|
||||
leftToLoad.Remove(transferable);
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
private void CancelLoad()
|
||||
{
|
||||
if (leftToLoad != null)
|
||||
{
|
||||
leftToLoad.Clear();
|
||||
}
|
||||
innerContainer.TryDropAll(parent.Position, parent.Map, ThingPlaceMode.Near);
|
||||
massUsageDirty = true;
|
||||
}
|
||||
|
||||
private void TryLaunch()
|
||||
{
|
||||
if (!IsOperational())
|
||||
{
|
||||
Messages.Message(GetInoperativeReason(), MessageTypeDefOf.RejectInput);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!innerContainer.Any)
|
||||
{
|
||||
Messages.Message("WULA_NoItemsToSubmit".Translate(), MessageTypeDefOf.RejectInput);
|
||||
return;
|
||||
}
|
||||
|
||||
if (OverMassCapacity)
|
||||
{
|
||||
Messages.Message("WULA_OverMassCapacity".Translate(MassUsage.ToString("F1"), Props.massCapacity.ToString("F1")), MessageTypeDefOf.RejectInput);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SubmitContentsToStorage())
|
||||
{
|
||||
CreateLaunchEffect();
|
||||
parent.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
private bool SubmitContentsToStorage()
|
||||
{
|
||||
try
|
||||
{
|
||||
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||
if (globalStorage == null)
|
||||
{
|
||||
Log.Error("GlobalStorageWorldComponent not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
int submittedCount = 0;
|
||||
List<Thing> processedItems = new List<Thing>();
|
||||
|
||||
// 复制列表以避免修改时迭代
|
||||
List<Thing> itemsToProcess = innerContainer.ToList();
|
||||
|
||||
foreach (Thing item in itemsToProcess)
|
||||
{
|
||||
if (item == null || item.Destroyed) continue;
|
||||
|
||||
if (IsEquipment(item.def))
|
||||
{
|
||||
globalStorage.AddToOutputStorage(item.def, item.stackCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
globalStorage.AddToInputStorage(item.def, item.stackCount);
|
||||
}
|
||||
|
||||
processedItems.Add(item);
|
||||
submittedCount += item.stackCount;
|
||||
}
|
||||
|
||||
// 从容器中移除已提交的物品
|
||||
foreach (Thing item in processedItems)
|
||||
{
|
||||
innerContainer.Remove(item);
|
||||
}
|
||||
|
||||
Messages.Message("WULA_ItemsSubmitted".Translate(submittedCount), MessageTypeDefOf.PositiveEvent);
|
||||
Log.Message($"Successfully submitted {submittedCount} items to global storage");
|
||||
return submittedCount > 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Error submitting items to storage: {ex}");
|
||||
Messages.Message("WULA_SubmissionFailed".Translate(), MessageTypeDefOf.NegativeEvent);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsEquipment(ThingDef thingDef)
|
||||
{
|
||||
return thingDef.IsApparel || thingDef.IsWeapon || thingDef.category == ThingCategory.Building;
|
||||
}
|
||||
|
||||
private void CreateLaunchEffect()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用自定义的 Skyfaller 定义
|
||||
ThingDef skyfallerDef = DefDatabase<ThingDef>.GetNamedSilentFail("ResourceSubmitterSkyfaller");
|
||||
if (skyfallerDef == null)
|
||||
{
|
||||
// 备用:使用运输舱效果
|
||||
skyfallerDef = DefDatabase<ThingDef>.GetNamedSilentFail("DropPodIncoming");
|
||||
}
|
||||
|
||||
if (skyfallerDef != null)
|
||||
{
|
||||
Skyfaller skyfaller = (Skyfaller)ThingMaker.MakeThing(skyfallerDef);
|
||||
GenSpawn.Spawn(skyfaller, parent.Position, parent.Map);
|
||||
}
|
||||
|
||||
// 视觉效果
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
FleckMaker.ThrowLightningGlow(parent.DrawPos, parent.Map, 2f);
|
||||
}
|
||||
FleckMaker.ThrowSmoke(parent.DrawPos, parent.Map, 3f);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Error creating launch effect: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsOperational()
|
||||
{
|
||||
var building = parent as Building_ResourceSubmitter;
|
||||
return building?.IsOperational ?? false;
|
||||
}
|
||||
|
||||
private string GetInoperativeReason()
|
||||
{
|
||||
var building = parent as Building_ResourceSubmitter;
|
||||
return building?.GetInoperativeReason() ?? "WULA_UnknownReason".Translate();
|
||||
}
|
||||
}
|
||||
|
||||
public class CompProperties_ResourceSubmitter : CompProperties
|
||||
{
|
||||
public float massCapacity = 150f;
|
||||
|
||||
public CompProperties_ResourceSubmitter()
|
||||
{
|
||||
compClass = typeof(CompResourceSubmitter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class Dialog_LoadResourceSubmitter : Window
|
||||
{
|
||||
private CompResourceSubmitter submitterComp;
|
||||
private Vector2 scrollPosition;
|
||||
private float scrollViewHeight;
|
||||
private List<TransferableOneWay> transferables;
|
||||
|
||||
public override Vector2 InitialSize => new Vector2(800f, 600f);
|
||||
|
||||
public Dialog_LoadResourceSubmitter(CompResourceSubmitter submitterComp)
|
||||
{
|
||||
this.submitterComp = submitterComp;
|
||||
forcePause = true;
|
||||
doCloseX = true;
|
||||
doCloseButton = true;
|
||||
absorbInputAroundWindow = true;
|
||||
|
||||
transferables = new List<TransferableOneWay>();
|
||||
RefreshTransferables();
|
||||
}
|
||||
|
||||
public override void DoWindowContents(Rect inRect)
|
||||
{
|
||||
Rect titleRect = new Rect(0f, 0f, inRect.width, 35f);
|
||||
Text.Font = GameFont.Medium;
|
||||
Widgets.Label(titleRect, "WULA_LoadResourceSubmitter".Translate());
|
||||
Text.Font = GameFont.Small;
|
||||
|
||||
// 简化的物品列表
|
||||
Rect listRect = new Rect(0f, 40f, inRect.width, inRect.height - 100f);
|
||||
DoSimpleTransferableList(listRect);
|
||||
|
||||
// 按钮区域
|
||||
Rect buttonRect = new Rect(0f, inRect.height - 55f, inRect.width, 30f);
|
||||
DoButtons(buttonRect);
|
||||
|
||||
// 状态信息
|
||||
Rect statusRect = new Rect(0f, inRect.height - 25f, inRect.width, 25f);
|
||||
DoStatusInfo(statusRect);
|
||||
}
|
||||
|
||||
private void DoSimpleTransferableList(Rect rect)
|
||||
{
|
||||
Widgets.DrawMenuSection(rect);
|
||||
Rect outRect = rect.ContractedBy(10f);
|
||||
Rect viewRect = new Rect(0f, 0f, outRect.width - 16f, scrollViewHeight);
|
||||
|
||||
Widgets.BeginScrollView(outRect, ref scrollPosition, viewRect);
|
||||
|
||||
float curY = 0f;
|
||||
|
||||
// 列标题
|
||||
Rect headerRect = new Rect(0f, curY, viewRect.width, 25f);
|
||||
DoColumnHeaders(headerRect);
|
||||
curY += 30f;
|
||||
|
||||
if (transferables.Count == 0)
|
||||
{
|
||||
Rect emptyRect = new Rect(0f, curY, viewRect.width, 30f);
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
Widgets.Label(emptyRect, "WULA_NoItemsAvailable".Translate());
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
curY += 35f;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var transferable in transferables)
|
||||
{
|
||||
if (transferable.things.Count == 0) continue;
|
||||
|
||||
Thing sampleThing = transferable.things[0];
|
||||
Rect rowRect = new Rect(0f, curY, viewRect.width, 30f);
|
||||
|
||||
if (DoTransferableRow(rowRect, transferable, sampleThing))
|
||||
{
|
||||
TooltipHandler.TipRegion(rowRect, GetThingTooltip(sampleThing, transferable.CountToTransfer));
|
||||
}
|
||||
|
||||
curY += 35f;
|
||||
}
|
||||
}
|
||||
|
||||
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 availableRect = new Rect(rect.x + columnWidth * 2f, rect.y, columnWidth, rect.height);
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
Widgets.Label(availableRect, "WULA_Available".Translate());
|
||||
|
||||
// 装载数量列
|
||||
Rect loadRect = new Rect(rect.x + columnWidth * 3f, rect.y, columnWidth, rect.height);
|
||||
Widgets.Label(loadRect, "WULA_ToLoad".Translate());
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
|
||||
// 标题下划线
|
||||
Widgets.DrawLineHorizontal(rect.x, rect.yMax - 2f, rect.width);
|
||||
}
|
||||
|
||||
private bool DoTransferableRow(Rect rect, TransferableOneWay transferable, Thing sampleThing)
|
||||
{
|
||||
Widgets.DrawHighlightIfMouseover(rect);
|
||||
|
||||
float columnWidth = rect.width / 4f;
|
||||
|
||||
// 图标
|
||||
Rect iconRect = new Rect(rect.x + 2f, rect.y + 2f, 26f, 26f);
|
||||
Widgets.ThingIcon(iconRect, sampleThing);
|
||||
|
||||
// 名称
|
||||
Rect nameRect = new Rect(rect.x + 32f, rect.y, columnWidth * 2f - 32f, rect.height);
|
||||
Text.Anchor = TextAnchor.MiddleLeft;
|
||||
string label = sampleThing.LabelCap;
|
||||
if (label.Length > 25)
|
||||
{
|
||||
label = label.Substring(0, 25) + "...";
|
||||
}
|
||||
Widgets.Label(nameRect, label);
|
||||
|
||||
// 可用数量
|
||||
int availableCount = transferable.things.Sum(t => t.stackCount);
|
||||
Rect availableRect = new Rect(rect.x + columnWidth * 2f, rect.y, columnWidth, rect.height);
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
Widgets.Label(availableRect, availableCount.ToString());
|
||||
|
||||
// 装载数量调整
|
||||
Rect adjustRect = new Rect(rect.x + columnWidth * 3f, rect.y, columnWidth, rect.height);
|
||||
DoCountAdjust(adjustRect, transferable, availableCount);
|
||||
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
|
||||
return Mouse.IsOver(rect);
|
||||
}
|
||||
|
||||
private void DoCountAdjust(Rect rect, TransferableOneWay transferable, int availableCount)
|
||||
{
|
||||
int currentCount = transferable.CountToTransfer;
|
||||
|
||||
Rect labelRect = new Rect(rect.x, rect.y, 40f, rect.height);
|
||||
Rect minusRect = new Rect(rect.x + 45f, rect.y, 25f, rect.height);
|
||||
Rect plusRect = new Rect(rect.x + 75f, rect.y, 25f, rect.height);
|
||||
Rect maxRect = new Rect(rect.x + 105f, rect.y, 35f, rect.height);
|
||||
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
|
||||
// 当前数量
|
||||
Widgets.Label(labelRect, currentCount.ToString());
|
||||
|
||||
// 减少按钮
|
||||
if (Widgets.ButtonText(minusRect, "-") && currentCount > 0)
|
||||
{
|
||||
transferable.AdjustBy(-1);
|
||||
}
|
||||
|
||||
// 增加按钮
|
||||
if (Widgets.ButtonText(plusRect, "+") && currentCount < availableCount)
|
||||
{
|
||||
transferable.AdjustBy(1);
|
||||
}
|
||||
|
||||
// 最大按钮
|
||||
if (Widgets.ButtonText(maxRect, "WULA_Max".Translate()) && availableCount > 0)
|
||||
{
|
||||
Find.WindowStack.Add(new Dialog_Slider(
|
||||
"WULA_SetLoadCount".Translate(transferable.AnyThing.LabelCap),
|
||||
0, availableCount,
|
||||
value => transferable.AdjustTo(value),
|
||||
currentCount
|
||||
));
|
||||
}
|
||||
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
}
|
||||
|
||||
private void DoButtons(Rect rect)
|
||||
{
|
||||
float buttonWidth = rect.width / 2f - 5f;
|
||||
|
||||
// 加载所有按钮
|
||||
Rect loadAllRect = new Rect(rect.x, rect.y, buttonWidth, rect.height);
|
||||
if (Widgets.ButtonText(loadAllRect, "WULA_LoadAll".Translate()))
|
||||
{
|
||||
foreach (var transferable in transferables)
|
||||
{
|
||||
transferable.AdjustTo(transferable.things.Sum(t => t.stackCount));
|
||||
}
|
||||
}
|
||||
|
||||
// 清除所有按钮
|
||||
Rect clearAllRect = new Rect(rect.x + buttonWidth + 10f, rect.y, buttonWidth, rect.height);
|
||||
if (Widgets.ButtonText(clearAllRect, "WULA_ClearAll".Translate()))
|
||||
{
|
||||
foreach (var transferable in transferables)
|
||||
{
|
||||
transferable.AdjustTo(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DoStatusInfo(Rect rect)
|
||||
{
|
||||
Widgets.DrawMenuSection(rect);
|
||||
Rect innerRect = rect.ContractedBy(5f);
|
||||
|
||||
Text.Anchor = TextAnchor.MiddleLeft;
|
||||
|
||||
// 计算总质量
|
||||
float totalMass = 0f;
|
||||
foreach (var transferable in transferables)
|
||||
{
|
||||
if (transferable.CountToTransfer > 0)
|
||||
{
|
||||
float thingMass = transferable.AnyThing.GetStatValue(StatDefOf.Mass);
|
||||
totalMass += thingMass * transferable.CountToTransfer;
|
||||
}
|
||||
}
|
||||
|
||||
// 质量信息
|
||||
string massText = "WULA_Mass".Translate() + ": " + totalMass.ToString("F1") + " / " + submitterComp.Props.massCapacity.ToString("F1") + " kg";
|
||||
if (totalMass > submitterComp.Props.massCapacity)
|
||||
{
|
||||
massText = massText.Colorize(ColorLibrary.RedReadable);
|
||||
}
|
||||
|
||||
Widgets.Label(innerRect, massText);
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
}
|
||||
|
||||
private string GetThingTooltip(Thing thing, int count)
|
||||
{
|
||||
float mass = thing.GetStatValue(StatDefOf.Mass);
|
||||
float value = thing.MarketValue * count;
|
||||
return $"{thing.LabelCap}\n{"WULA_Mass".Translate()}: {mass:F2} kg\n{"WULA_Value".Translate()}: {value}\n{"WULA_Description".Translate()}: {thing.def.description}";
|
||||
}
|
||||
|
||||
private void RefreshTransferables()
|
||||
{
|
||||
transferables.Clear();
|
||||
|
||||
// 获取地图上所有可搬运的物品
|
||||
foreach (Thing thing in submitterComp.parent.Map.listerThings.ThingsInGroup(ThingRequestGroup.HaulableAlways))
|
||||
{
|
||||
if ((thing.IsInValidStorage() || thing.Spawned) && !thing.Position.Fogged(thing.Map))
|
||||
{
|
||||
AddToTransferables(thing);
|
||||
}
|
||||
}
|
||||
|
||||
// 按物品类型排序
|
||||
transferables.SortBy(t => t.AnyThing.def.label);
|
||||
}
|
||||
|
||||
private void AddToTransferables(Thing thing)
|
||||
{
|
||||
if (thing.def.category == ThingCategory.Item)
|
||||
{
|
||||
TransferableOneWay transferable = TransferableUtility.TransferableMatching(thing, transferables, TransferAsOneMode.PodsOrCaravanPacking);
|
||||
if (transferable == null)
|
||||
{
|
||||
transferable = new TransferableOneWay();
|
||||
transferables.Add(transferable);
|
||||
transferable.things.Add(thing);
|
||||
}
|
||||
else
|
||||
{
|
||||
transferable.things.Add(thing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostClose()
|
||||
{
|
||||
base.PostClose();
|
||||
|
||||
// 应用装载设置到提交器
|
||||
if (submitterComp != null)
|
||||
{
|
||||
submitterComp.leftToLoad?.Clear();
|
||||
|
||||
foreach (TransferableOneWay transferable in transferables)
|
||||
{
|
||||
if (transferable.CountToTransfer > 0)
|
||||
{
|
||||
submitterComp.AddToTheToLoadList(transferable, transferable.CountToTransfer);
|
||||
}
|
||||
}
|
||||
|
||||
// 开始装载工作
|
||||
StartLoadingJobs();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartLoadingJobs()
|
||||
{
|
||||
if (submitterComp?.parent?.Map == null) return;
|
||||
|
||||
foreach (TransferableOneWay transferable in transferables)
|
||||
{
|
||||
if (transferable.CountToTransfer > 0)
|
||||
{
|
||||
foreach (Thing thing in transferable.things)
|
||||
{
|
||||
if (transferable.CountToTransfer <= 0) break;
|
||||
|
||||
// 创建搬运到提交器的工作
|
||||
Job job = JobMaker.MakeJob(JobDefOf.HaulToContainer, thing, submitterComp.parent);
|
||||
job.count = Mathf.Min(thing.stackCount, transferable.CountToTransfer);
|
||||
job.haulMode = HaulMode.ToContainer;
|
||||
|
||||
// 寻找殖民者执行工作
|
||||
Pawn pawn = FindBestPawnForJob(job);
|
||||
if (pawn != null)
|
||||
{
|
||||
pawn.jobs.TryTakeOrderedJob(job);
|
||||
transferable.AdjustBy(-job.count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Messages.Message("WULA_LoadingJobsCreated".Translate(), MessageTypeDefOf.PositiveEvent);
|
||||
}
|
||||
|
||||
private Pawn FindBestPawnForJob(Job job)
|
||||
{
|
||||
return submitterComp.parent.Map.mapPawns.FreeColonistsSpawned
|
||||
.Where(p => p.workSettings.WorkIsActive(WorkTypeDefOf.Hauling))
|
||||
.FirstOrDefault(p => p.CanReserveAndReach(job.targetA, PathEndMode.ClosestTouch, Danger.Some));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
@@ -12,7 +13,6 @@ namespace WulaFallenEmpire
|
||||
public int targetCount = 1;
|
||||
public int currentCount = 0;
|
||||
public bool paused = true; // 初始状态为暂停
|
||||
public float progress = 0f;
|
||||
|
||||
// 生产状态
|
||||
public ProductionState state = ProductionState.Waiting;
|
||||
@@ -24,18 +24,94 @@ namespace WulaFallenEmpire
|
||||
Completed // 完成
|
||||
}
|
||||
|
||||
public string Label => recipe.LabelCap;
|
||||
public string Description => $"{currentCount}/{targetCount} {recipe.products[0].thingDef.label}"; private float _progress = 0f;
|
||||
public float progress
|
||||
{
|
||||
get => _progress;
|
||||
set
|
||||
{
|
||||
// 确保进度在有效范围内
|
||||
_progress = Mathf.Clamp01(value);
|
||||
|
||||
// 如果检测到异常值,记录警告
|
||||
if (value < 0f || value > 1f)
|
||||
{
|
||||
Log.Warning($"Progress clamped from {value} to {_progress} for {recipe?.defName ?? "unknown"}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ExposeData()
|
||||
{
|
||||
Scribe_Defs.Look(ref recipe, "recipe");
|
||||
Scribe_Values.Look(ref targetCount, "targetCount", 1);
|
||||
Scribe_Values.Look(ref currentCount, "currentCount", 0);
|
||||
Scribe_Values.Look(ref paused, "paused", true);
|
||||
Scribe_Values.Look(ref progress, "progress", 0f);
|
||||
Scribe_Values.Look(ref _progress, "progress", 0f); // 序列化私有字段
|
||||
Scribe_Values.Look(ref state, "state", ProductionState.Waiting);
|
||||
|
||||
// 修复:加载后验证数据
|
||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||
{
|
||||
// 使用属性设置器来钳制值
|
||||
progress = _progress;
|
||||
|
||||
// 确保状态正确
|
||||
UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
public string Label => recipe.LabelCap;
|
||||
public string Description => $"{currentCount}/{targetCount} {recipe.products[0].thingDef.label}";
|
||||
// 修复:改进状态更新逻辑
|
||||
public void UpdateState()
|
||||
{
|
||||
if (state == ProductionState.Completed)
|
||||
return;
|
||||
|
||||
if (currentCount >= targetCount)
|
||||
{
|
||||
state = ProductionState.Completed;
|
||||
progress = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasEnoughResources())
|
||||
{
|
||||
if (state == ProductionState.Waiting && !paused)
|
||||
{
|
||||
state = ProductionState.Producing;
|
||||
progress = 0f; // 开始生产时重置进度
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state == ProductionState.Producing)
|
||||
{
|
||||
state = ProductionState.Waiting;
|
||||
progress = 0f; // 资源不足时重置进度
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 修复:改进生产完成逻辑
|
||||
public void Produce()
|
||||
{
|
||||
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||
if (globalStorage == null)
|
||||
return;
|
||||
foreach (var product in recipe.products)
|
||||
{
|
||||
globalStorage.AddToOutputStorage(product.thingDef, product.count);
|
||||
}
|
||||
|
||||
currentCount++;
|
||||
progress = 0f; // 生产完成后重置进度
|
||||
|
||||
if (currentCount >= targetCount)
|
||||
{
|
||||
state = ProductionState.Completed;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有足够资源 - 修复逻辑
|
||||
public bool HasEnoughResources()
|
||||
@@ -103,68 +179,51 @@ namespace WulaFallenEmpire
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 生产一个产品
|
||||
public void Produce()
|
||||
// 修复:添加获取正确工作量的方法
|
||||
public float GetWorkAmount()
|
||||
{
|
||||
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||
if (globalStorage == null) return;
|
||||
if (recipe == null)
|
||||
return 1000f;
|
||||
|
||||
foreach (var product in recipe.products)
|
||||
{
|
||||
globalStorage.AddToOutputStorage(product.thingDef, product.count);
|
||||
}
|
||||
|
||||
currentCount++;
|
||||
progress = 0f;
|
||||
|
||||
if (currentCount >= targetCount)
|
||||
{
|
||||
state = ProductionState.Completed;
|
||||
}
|
||||
}
|
||||
// 如果配方有明确的工作量且大于0,使用配方的工作量
|
||||
if (recipe.workAmount > 0)
|
||||
return recipe.workAmount;
|
||||
|
||||
// 新增方法:检查并更新状态
|
||||
public void UpdateState()
|
||||
{
|
||||
if (state == ProductionState.Completed) return;
|
||||
|
||||
if (HasEnoughResources())
|
||||
// 否则,使用第一个产品的WorkToMake属性
|
||||
if (recipe.products != null && recipe.products.Count > 0)
|
||||
{
|
||||
if (state == ProductionState.Waiting && !paused)
|
||||
ThingDef productDef = recipe.products[0].thingDef;
|
||||
if (productDef != null)
|
||||
{
|
||||
state = ProductionState.Producing;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state == ProductionState.Producing)
|
||||
{
|
||||
state = ProductionState.Waiting;
|
||||
progress = 0f; // 重置进度
|
||||
// 获取产品的WorkToMake统计值
|
||||
float workToMake = productDef.GetStatValueAbstract(StatDefOf.WorkToMake);
|
||||
if (workToMake > 0)
|
||||
return workToMake;
|
||||
|
||||
// 如果WorkToMake也是0或无效,使用产品的市场价值作为估算
|
||||
float marketValue = productDef.GetStatValueAbstract(StatDefOf.MarketValue);
|
||||
if (marketValue > 0)
|
||||
return marketValue * 10f; // 基于市场价值的估算
|
||||
}
|
||||
}
|
||||
|
||||
return 1000f; // 默认工作量
|
||||
}
|
||||
|
||||
// 获取配方材料信息的字符串
|
||||
// 修复:在信息显示中使用正确的工作量
|
||||
public string GetIngredientsInfo()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// 添加标题
|
||||
sb.AppendLine("WULA_RequiredIngredients".Translate() + ":");
|
||||
|
||||
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||
|
||||
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(" - ");
|
||||
@@ -174,9 +233,7 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
sb.Append(" / ");
|
||||
}
|
||||
|
||||
sb.Append($"{requiredCount} {thingDef.label}");
|
||||
|
||||
// 添加可用数量信息
|
||||
if (availableCount < requiredCount)
|
||||
{
|
||||
@@ -187,10 +244,8 @@ namespace WulaFallenEmpire
|
||||
sb.Append($" ({availableCount}/{requiredCount})");
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
// 添加产品信息
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("WULA_Products".Translate() + ":");
|
||||
@@ -198,46 +253,37 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
sb.AppendLine($" - {product.count} {product.thingDef.label}");
|
||||
}
|
||||
|
||||
// 添加工作量信息
|
||||
// 修复:使用正确的工作量信息
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("WULA_WorkAmount".Translate() + ": " + recipe.workAmount.ToStringWorkAmount());
|
||||
|
||||
sb.AppendLine("WULA_WorkAmount".Translate() + ": " + GetWorkAmount().ToStringWorkAmount());
|
||||
return sb.ToString();
|
||||
}
|
||||
// 获取简化的材料信息(用于Tooltip)
|
||||
|
||||
// 修复:在Tooltip中也使用正确的工作量
|
||||
public string GetIngredientsTooltip()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine(recipe.LabelCap);
|
||||
sb.AppendLine();
|
||||
|
||||
// 材料需求
|
||||
sb.AppendLine("WULA_RequiredIngredients".Translate() + ":");
|
||||
var globalStorage = Find.World.GetComponent<GlobalStorageWorldComponent>();
|
||||
|
||||
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($" <color=green>{ingredientSB}</color>");
|
||||
@@ -247,7 +293,6 @@ namespace WulaFallenEmpire
|
||||
sb.AppendLine($" <color=red>{ingredientSB}</color>");
|
||||
}
|
||||
}
|
||||
|
||||
// 产品
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("WULA_Products".Translate() + ":");
|
||||
@@ -255,7 +300,9 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
sb.AppendLine($" {product.count} {product.thingDef.label}");
|
||||
}
|
||||
|
||||
// 修复:使用正确的工作量
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("WULA_WorkAmount".Translate() + ": " + GetWorkAmount().ToStringWorkAmount());
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// GlobalProductionOrderStack.cs (调整为每秒1工作量)
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
@@ -10,10 +10,10 @@ namespace WulaFallenEmpire
|
||||
public Building_GlobalWorkTable table;
|
||||
public List<GlobalProductionOrder> orders = new List<GlobalProductionOrder>();
|
||||
|
||||
// 调整为每秒1工作量 - RimWorld中1秒=60ticks
|
||||
private const float WorkPerSecond = 1f;
|
||||
// 修复:明确的工作量定义
|
||||
private const float WorkPerSecond = 60f; // 每秒60工作量(标准RimWorld速度)
|
||||
private const float TicksPerSecond = 60f;
|
||||
private const float WorkPerTick = WorkPerSecond / TicksPerSecond;
|
||||
private const float WorkPerTick = WorkPerSecond / TicksPerSecond; // 每tick 1工作量
|
||||
|
||||
public GlobalProductionOrderStack(Building_GlobalWorkTable table)
|
||||
{
|
||||
@@ -24,6 +24,12 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
Scribe_References.Look(ref table, "table");
|
||||
Scribe_Collections.Look(ref orders, "orders", LookMode.Deep);
|
||||
|
||||
// 修复:加载后验证和修复数据
|
||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||
{
|
||||
FixAllOrders();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddOrder(GlobalProductionOrder order)
|
||||
@@ -50,8 +56,11 @@ namespace WulaFallenEmpire
|
||||
|
||||
public void ProcessOrders()
|
||||
{
|
||||
foreach (var order in orders)
|
||||
// 修复:使用倒序遍历避免修改集合问题
|
||||
for (int i = orders.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var order = orders[i];
|
||||
|
||||
// 首先更新状态
|
||||
order.UpdateState();
|
||||
|
||||
@@ -61,49 +70,176 @@ namespace WulaFallenEmpire
|
||||
// 生产中
|
||||
if (order.state == GlobalProductionOrder.ProductionState.Producing)
|
||||
{
|
||||
// 计算每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($"[WARNING] Failed to consume resources for {order.recipe.defName}");
|
||||
}
|
||||
}
|
||||
ProcessProducingOrder(order, i);
|
||||
}
|
||||
else if (order.state == GlobalProductionOrder.ProductionState.Waiting && !order.paused)
|
||||
{
|
||||
// 调试:检查为什么订单在等待状态
|
||||
if (Find.TickManager.TicksGame % 1200 == 0) // 每20秒检查一次
|
||||
{
|
||||
Log.Message($"[DEBUG] Order {order.recipe.defName} is waiting. " +
|
||||
$"HasEnoughResources: {order.HasEnoughResources()}, paused: {order.paused}");
|
||||
}
|
||||
ProcessWaitingOrder(order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessProducingOrder(GlobalProductionOrder order, int index)
|
||||
{
|
||||
// 修复:使用正确的方法获取工作量
|
||||
float workAmount = GetWorkAmountForOrder(order);
|
||||
|
||||
// 防止除零错误
|
||||
if (workAmount <= 0)
|
||||
{
|
||||
Log.Error($"Invalid workAmount ({workAmount}) for recipe {order.recipe.defName}");
|
||||
order.state = GlobalProductionOrder.ProductionState.Waiting;
|
||||
order.progress = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
// 修复:正确计算进度增量
|
||||
float progressIncrement = WorkPerTick / workAmount;
|
||||
|
||||
// 修复:确保进度不会变成负数
|
||||
float newProgress = Mathf.Max(0f, order.progress + progressIncrement);
|
||||
order.progress = newProgress;
|
||||
|
||||
// 调试信息
|
||||
if (Find.TickManager.TicksGame % 300 == 0) // 每5秒输出一次
|
||||
{
|
||||
Log.Message($"[DEBUG] Order {order.recipe.defName}: " +
|
||||
$"progress={order.progress:P2}, " +
|
||||
$"workAmount={workAmount}, " +
|
||||
$"increment={progressIncrement:E4}, " +
|
||||
$"state={order.state}");
|
||||
}
|
||||
|
||||
// 修复:使用精确比较完成条件
|
||||
if (order.progress >= 1.0f)
|
||||
{
|
||||
CompleteProduction(order, index);
|
||||
}
|
||||
}
|
||||
|
||||
// 修复:新增方法 - 正确获取订单的工作量
|
||||
private float GetWorkAmountForOrder(GlobalProductionOrder order)
|
||||
{
|
||||
if (order?.recipe == null)
|
||||
return 1000f; // 默认值
|
||||
|
||||
// 如果配方有明确的工作量且大于0,使用配方的工作量
|
||||
if (order.recipe.workAmount > 0)
|
||||
return order.recipe.workAmount;
|
||||
|
||||
// 否则,使用第一个产品的WorkToMake属性
|
||||
if (order.recipe.products != null && order.recipe.products.Count > 0)
|
||||
{
|
||||
ThingDef productDef = order.recipe.products[0].thingDef;
|
||||
if (productDef != null)
|
||||
{
|
||||
// 获取产品的WorkToMake统计值
|
||||
float workToMake = productDef.GetStatValueAbstract(StatDefOf.WorkToMake);
|
||||
if (workToMake > 0)
|
||||
return workToMake;
|
||||
|
||||
// 如果WorkToMake也是0或无效,使用产品的市场价值作为估算
|
||||
float marketValue = productDef.GetStatValueAbstract(StatDefOf.MarketValue);
|
||||
if (marketValue > 0)
|
||||
return marketValue * 10f; // 基于市场价值的估算
|
||||
}
|
||||
}
|
||||
|
||||
// 最后的回退方案
|
||||
Log.Warning($"Could not determine work amount for recipe {order.recipe.defName}, using default value");
|
||||
return 1000f; // 默认工作量
|
||||
}
|
||||
|
||||
private void ProcessWaitingOrder(GlobalProductionOrder order)
|
||||
{
|
||||
// 检查是否应该开始生产
|
||||
if (order.HasEnoughResources())
|
||||
{
|
||||
order.state = GlobalProductionOrder.ProductionState.Producing;
|
||||
order.progress = 0f;
|
||||
|
||||
if (Find.TickManager.TicksGame % 600 == 0) // 每10秒记录一次
|
||||
{
|
||||
Log.Message($"[INFO] Order {order.recipe.defName} started producing");
|
||||
}
|
||||
}
|
||||
else if (Find.TickManager.TicksGame % 1200 == 0) // 每20秒检查一次
|
||||
{
|
||||
Log.Message($"[DEBUG] Order {order.recipe.defName} is waiting. " +
|
||||
$"HasEnoughResources: {order.HasEnoughResources()}");
|
||||
}
|
||||
}
|
||||
|
||||
private void CompleteProduction(GlobalProductionOrder order, int index)
|
||||
{
|
||||
// 修复:生产完成,消耗资源
|
||||
if (order.ConsumeResources())
|
||||
{
|
||||
order.Produce();
|
||||
order.UpdateState();
|
||||
|
||||
Log.Message($"[SUCCESS] Produced {order.recipe.products[0].thingDef.defName}, " +
|
||||
$"count: {order.currentCount}/{order.targetCount}");
|
||||
|
||||
// 重置进度
|
||||
order.progress = 0f;
|
||||
|
||||
// 如果订单完成,移除它
|
||||
if (order.state == GlobalProductionOrder.ProductionState.Completed)
|
||||
{
|
||||
orders.RemoveAt(index);
|
||||
Log.Message($"[COMPLETE] Order {order.recipe.defName} completed and removed");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 修复:资源不足,回到等待状态
|
||||
order.state = GlobalProductionOrder.ProductionState.Waiting;
|
||||
order.progress = 0f;
|
||||
Log.Message($"[WARNING] Failed to consume resources for {order.recipe.defName}, resetting");
|
||||
}
|
||||
}
|
||||
|
||||
// 修复:全面数据修复方法
|
||||
private void FixAllOrders()
|
||||
{
|
||||
for (int i = orders.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var order = orders[i];
|
||||
|
||||
// 修复进度值
|
||||
if (float.IsNaN(order.progress) || float.IsInfinity(order.progress))
|
||||
{
|
||||
order.progress = 0f;
|
||||
Log.Warning($"Fixed invalid progress for {order.recipe?.defName ?? "unknown"}");
|
||||
}
|
||||
else if (order.progress < 0f)
|
||||
{
|
||||
order.progress = 0f;
|
||||
Log.Warning($"Fixed negative progress for {order.recipe?.defName ?? "unknown"}");
|
||||
}
|
||||
else if (order.progress > 1f)
|
||||
{
|
||||
order.progress = 1f;
|
||||
Log.Warning($"Fixed excessive progress for {order.recipe?.defName ?? "unknown"}");
|
||||
}
|
||||
|
||||
// 修复状态
|
||||
if (order.recipe == null)
|
||||
{
|
||||
Log.Warning($"Removing order with null recipe");
|
||||
orders.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 强制更新状态
|
||||
order.UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
public void FixNegativeProgress()
|
||||
{
|
||||
FixAllOrders();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ namespace WulaFallenEmpire
|
||||
// 如果暂停,在状态前添加暂停标识
|
||||
if (order.paused && order.state != GlobalProductionOrder.ProductionState.Completed)
|
||||
{
|
||||
statusText = $"[Paused] {statusText}";
|
||||
statusText = $"[||] {statusText}";
|
||||
}
|
||||
|
||||
Widgets.Label(statusRect, statusText);
|
||||
|
||||
@@ -1,409 +0,0 @@
|
||||
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<GlobalStorageWorldComponent>();
|
||||
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<ThingDef, int> 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<ThingDef, int> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user