diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index 9da37bc..995e1cc 100644
Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ
diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.pdb b/1.6/1.6/Assemblies/ArachnaeSwarm.pdb
index 3b3c8bc..38f6111 100644
Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.pdb and b/1.6/1.6/Assemblies/ArachnaeSwarm.pdb differ
diff --git a/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml b/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml
index ae73e4b..3fa4f05 100644
--- a/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml
+++ b/1.6/1.6/Defs/Thing_building/ARA_NutrientNetworkBuilding.xml
@@ -62,6 +62,8 @@
ARA_AutoSniperCannon
ARA_Pawn_Ootheca
ARA_Equipment_Ootheca
+ ARA_BioforgeIncubator
+ ARA_BioforgeIncubator_Thing
80
10
diff --git a/1.6/1.6/Defs/Thing_building/ARA_Ootheca.xml b/1.6/1.6/Defs/Thing_building/ARA_Ootheca.xml
index 26847d0..ed68cb9 100644
--- a/1.6/1.6/Defs/Thing_building/ARA_Ootheca.xml
+++ b/1.6/1.6/Defs/Thing_building/ARA_Ootheca.xml
@@ -585,7 +585,7 @@
3
1.0
- ArachnaeNode_Race_WeaponSmith
+ ArachnaeBase_Race_Larva
@@ -640,10 +640,6 @@
ARA_GrowthVat
-
- ARA_InsectCreep
- 8
-
@@ -710,8 +706,40 @@
5
0.5
- ARA_ArachnaeQueen
+ ArachnaeBase_Race_Larva
+
+
+ 10
+ 30
+ 0.00001
+
+
+ Legendary
+ 0.99
+
+
+ Masterwork
+ 0.90
+
+
+ Excellent
+ 0.70
+
+
+ Good
+ 0.50
+
+
+ Normal
+ 0.20
+
+
+ Poor
+ 0.10
+
+
+
ArachnaeNode_Race_Myrmecocystus
@@ -757,7 +785,7 @@
- 20.0
+ 300.0
ARA_InsectJelly
@@ -775,10 +803,6 @@
ARA_GrowthVat
-
- ARA_InsectCreep
- 8
-
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index 1db2803..4b9deeb 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -123,10 +123,14 @@
+
+
+
+
diff --git a/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompQueuedInteractiveProducerWithFlux.cs b/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompQueuedInteractiveProducerWithFlux.cs
index 64f8591..bc4a678 100644
--- a/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompQueuedInteractiveProducerWithFlux.cs
+++ b/Source/ArachnaeSwarm/Building_Comps/ARA_CompInteractiveProducer/CompQueuedInteractiveProducerWithFlux.cs
@@ -4,30 +4,89 @@ using System.Linq;
using System.Text;
using UnityEngine;
using Verse;
+using Verse.AI;
namespace ArachnaeSwarm
{
- public class CompProperties_QueuedInteractiveProducerWithFlux : CompProperties_QueuedInteractiveProducer
+ // 带状态和品质的物品订单(通量品质系统)
+ public class QueuedItemOrder : IExposable
{
+ public ProcessDef process;
+ public string tempThingDefName;
+ public OrderStatus status = OrderStatus.WaitingForLarva;
+ public int productionUntilTick = -1;
+
+ // 通量品质系统
+ public float qualityProgress = 0f;
+ public float qualityTotal = 0f;
+
+ public float QualityPercent => qualityTotal > 0 ? qualityProgress / qualityTotal : 0f;
+
+ public void ExposeData()
+ {
+ if (Scribe.mode == LoadSaveMode.Saving && process != null)
+ {
+ tempThingDefName = process.thingDef.defName;
+ }
+ Scribe_Values.Look(ref tempThingDefName, "thingDefName");
+ Scribe_Values.Look(ref status, "status", OrderStatus.WaitingForLarva);
+ Scribe_Values.Look(ref productionUntilTick, "productionUntilTick", -1);
+ Scribe_Values.Look(ref qualityProgress, "qualityProgress", 0f);
+ Scribe_Values.Look(ref qualityTotal, "qualityTotal", 0f);
+ }
+ }
+
+ public class CompProperties_QueuedInteractiveProducerWithFlux : CompProperties
+ {
+ public List whitelist;
+ public int productionQueueLimit = 3;
+ public float minNutritionToStart = 0.1f;
+ public List qualityThresholds;
+ public IntRange spawnCount = new IntRange(1, 1);
+
public CompProperties_QueuedInteractiveProducerWithFlux()
{
compClass = typeof(CompQueuedInteractiveProducerWithFlux);
}
}
- public class CompQueuedInteractiveProducerWithFlux : CompQueuedInteractiveProducer, IFluxController
+ public class CompQueuedInteractiveProducerWithFlux : ThingComp, IFluxController, ILarvaActivatable
{
// === 通量系统字段 ===
private float neutronFlux = 0.5f;
private FluxMode fluxMode = FluxMode.Balance;
- // === 接口实现 ===
+ // === 生产队列 ===
+ private List orders = new List();
+
+ // === 幼虫管理 ===
+ private List assignedLarvae = new List();
+
+ // === 组件引用 ===
+ private CompRefuelableNutrition _fuelComp;
+ private CompAffectedByFacilities _facilitiesComp;
+ private List _cachedProcesses;
+
+ public CompProperties_QueuedInteractiveProducerWithFlux Props => (CompProperties_QueuedInteractiveProducerWithFlux)props;
+ private CompRefuelableNutrition FuelComp => _fuelComp ?? (_fuelComp = parent.GetComp());
+ private CompAffectedByFacilities FacilitiesComp => _facilitiesComp ?? (_facilitiesComp = parent.GetComp());
+
+ public List Processes
+ {
+ get
+ {
+ if (_cachedProcesses == null) BuildProcessList();
+ return _cachedProcesses;
+ }
+ }
+
+ // === IFluxController 接口实现 ===
public float NeutronFlux => neutronFlux;
public float RawFlux => neutronFlux;
public FluxMode CurrentFluxMode => fluxMode;
public float FluxEfficiency => IFluxControllerExtensions.GetEfficiency(neutronFlux);
public bool IsAutoMode => fluxMode != FluxMode.Manual;
- public bool IsIncubating => IsAnyOrderActive;
+ public bool IsIncubating => orders.Any(o => o.status == OrderStatus.Incubating);
public bool IsDormant => neutronFlux < 0.05f;
public void SetNeutronFlux(float value) => neutronFlux = Mathf.Clamp01(value);
@@ -49,82 +108,199 @@ namespace ArachnaeSwarm
_ => "?"
};
- private bool IsAnyOrderActive => productionOrders.Any(o => o.productionUntilTick > 0);
+ public override void Initialize(CompProperties props)
+ {
+ base.Initialize(props);
+ _fuelComp = parent.GetComp();
+ _facilitiesComp = parent.GetComp();
+ BuildProcessList();
+ }
- // 覆盖 Tick 逻辑
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ base.PostSpawnSetup(respawningAfterLoad);
+ if (_cachedProcesses == null) BuildProcessList();
+ }
+
+ // === 订单管理 ===
+ public int WaitingForLarvaCount => orders.Count(o => o.status == OrderStatus.WaitingForLarva);
+ public int IncubatingCount => orders.Count(o => o.status == OrderStatus.Incubating);
+
+ public void AddOrder(ProcessDef process)
+ {
+ if (orders.Count >= Props.productionQueueLimit)
+ {
+ Messages.Message("队列已满!", MessageTypeDefOf.RejectInput);
+ return;
+ }
+ orders.Add(new QueuedItemOrder { process = process, status = OrderStatus.WaitingForLarva });
+ }
+
+ public void RemoveOrder(QueuedItemOrder order) => orders.Remove(order);
+ public void RemoveOrderByIndex(int index) { if (index >= 0 && index < orders.Count) orders.RemoveAt(index); }
+
+ // === Gizmo 用的订单信息 ===
+ public List GetOrdersForGizmo()
+ {
+ var result = new List();
+ foreach (var order in orders)
+ {
+ result.Add(new OrderDisplayInfo
+ {
+ label = order.process?.thingDef?.LabelCap ?? "?",
+ status = order.status,
+ productionProgress = GetProgress(order),
+ qualityProgress = order.QualityPercent,
+ estimatedQuality = GetQualityCategory(order).GetLabel()
+ });
+ }
+ return result;
+ }
+
+ private float GetProgress(QueuedItemOrder order)
+ {
+ if (order.status != OrderStatus.Incubating || order.process == null) return 0f;
+ int totalTicks = order.process.productionTicks;
+ int elapsed = totalTicks - order.productionUntilTick;
+ return totalTicks > 0 ? Mathf.Clamp01((float)elapsed / totalTicks) : 0f;
+ }
+
+ private QualityCategory GetQualityCategory(QueuedItemOrder order)
+ {
+ if (Props.qualityThresholds.NullOrEmpty()) return QualityCategory.Normal;
+ float qualityPercent = order.QualityPercent;
+ foreach (var threshold in Props.qualityThresholds.OrderByDescending(q => q.threshold))
+ {
+ if (qualityPercent >= threshold.threshold) return threshold.quality;
+ }
+ return Props.qualityThresholds.Any()
+ ? Props.qualityThresholds.OrderBy(q => q.threshold).First().quality
+ : QualityCategory.Awful;
+ }
+
+ // === 幼虫激活逻辑 ===
+ private void CallLarvae()
+ {
+ int neededLarvae = WaitingForLarvaCount - assignedLarvae.Count;
+ if (neededLarvae <= 0)
+ {
+ Messages.Message("没有需要激活的订单", MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ int called = 0;
+ var availableLarvae = FindAvailableLarvae(neededLarvae);
+ foreach (var larva in availableLarvae)
+ {
+ var job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_OperateIncubator"), parent);
+ if (larva.jobs.TryTakeOrderedJob(job, JobTag.Misc))
+ {
+ assignedLarvae.Add(larva);
+ called++;
+ }
+ }
+
+ if (called > 0)
+ Messages.Message($"已呼叫 {called} 只幼虫", MessageTypeDefOf.PositiveEvent);
+ else
+ Messages.Message("未找到可用的幼虫!", MessageTypeDefOf.RejectInput);
+ }
+
+ private List FindAvailableLarvae(int maxCount)
+ {
+ var result = new List();
+ float searchRadius = 50f;
+ if (parent.Map == null) return result;
+
+ foreach (var pawn in parent.Map.mapPawns.AllPawnsSpawned)
+ {
+ if (result.Count >= maxCount) break;
+ if (pawn.def.defName == "ArachnaeBase_Race_Larva" &&
+ !pawn.Downed && !pawn.Dead &&
+ pawn.Faction == parent.Faction &&
+ !assignedLarvae.Contains(pawn) &&
+ parent.Position.DistanceTo(pawn.Position) <= searchRadius)
+ {
+ result.Add(pawn);
+ }
+ }
+ return result;
+ }
+
+ public void NotifyLarvaArrived(Pawn larva) { }
+
+ public void NotifyLarvaOperationComplete(Pawn larva)
+ {
+ var waitingOrder = orders.FirstOrDefault(o => o.status == OrderStatus.WaitingForLarva);
+ if (waitingOrder != null)
+ {
+ waitingOrder.status = OrderStatus.Incubating;
+ waitingOrder.productionUntilTick = waitingOrder.process.productionTicks;
+ waitingOrder.qualityTotal = waitingOrder.process.productionTicks;
+ waitingOrder.qualityProgress = 0f;
+ }
+ assignedLarvae.Remove(larva);
+ }
+
+ // === Tick 逻辑 ===
public override void CompTick()
{
- float hasFuelVal = FuelComp?.Fuel ?? 10f;
- bool hasFuel = hasFuelVal > 0.01f;
+ base.CompTick();
- // 自动模式
- if (IsAutoMode && parent.IsHashIntervalTick(250) && IsAnyOrderActive)
+ assignedLarvae.RemoveAll(l => l == null || l.Dead || l.Destroyed);
+
+ bool hasFuel = (FuelComp?.Fuel ?? 10f) > 0.01f;
+
+ // 自动模式调节
+ if (IsAutoMode && parent.IsHashIntervalTick(250) && IsIncubating)
{
CalculateAutoFlux();
}
- if (IsAnyOrderActive)
+ // 消耗燃料
+ if (IsIncubating && FuelComp != null && neutronFlux > 0.01f)
{
- // 消耗燃料
- if (FuelComp != null && neutronFlux > 0.01f)
+ float fuelPerTick = (80f * FluxEfficiency * IncubatingCount) / 60000f;
+ FuelComp.ConsumeFuel(fuelPerTick);
+ }
+
+ // 处理正在生产的订单
+ if (hasFuel && !IsDormant)
+ {
+ float speedFactor = 1f + (FacilitiesComp?.GetStatOffset(StatDef.Named("ARA_IncubationSpeedFactor")) ?? 0f);
+ float fluxSpeed = speedFactor * FluxEfficiency * 5f;
+
+ foreach (var order in orders.Where(o => o.status == OrderStatus.Incubating && o.productionUntilTick > 0))
{
- float fuelPerTick = (80f * FluxEfficiency) / 60000f; // 物品孵化池消耗略高
- FuelComp.ConsumeFuel(fuelPerTick);
- }
-
- if (hasFuel)
- {
- float ambientTemperature = parent.AmbientTemperature;
- bool isTempSafe = ambientTemperature >= Props.minSafeTemperature && ambientTemperature <= Props.maxSafeTemperature;
-
- // 计算通量速度倍率:100%活性时5倍速度
- float speedFactor = 1f + (FacilitiesComp?.GetStatOffset(StatDef.Named("ARA_IncubationSpeedFactor")) ?? 0f);
- float fluxSpeed = speedFactor * FluxEfficiency * 5f;
-
- foreach (var order in productionOrders.Where(o => o.productionUntilTick > 0))
+ // 进度推进
+ float extraProgress = fluxSpeed - 1f;
+ if (extraProgress > 0)
{
- // 1. 进度推进(受通量倍率影响)
- // 由于基类逻辑是每 Tick 递减,我们这里手动控制递减量
- float progressStep = fluxSpeed;
- int ticksToSubtract = Mathf.FloorToInt(progressStep);
- if (Rand.Value < (progressStep - ticksToSubtract)) ticksToSubtract++;
- order.productionUntilTick = Mathf.Max(0, order.productionUntilTick - ticksToSubtract);
-
- // 2. 品质累积(基础:每 Tick 增加 1)
- // 注意:如果 fluxSpeed > 1,生产变快,但品质累积速度不变,
- // 这样总的 ticksUnderOptimalConditions 就会减少,从而降低品质分数。
- // 这完美地实现了“速度越快,品质越低”的平衡逻辑。
- if (!IsDormant)
- {
- if (isTempSafe)
- {
- order.ticksUnderOptimalConditions++;
- }
- else
- {
- float tempDelta = (ambientTemperature > Props.maxSafeTemperature) ? ambientTemperature - Props.maxSafeTemperature : Props.minSafeTemperature - ambientTemperature;
- order.temperaturePenaltyPercent = Mathf.Min(1f, order.temperaturePenaltyPercent + tempDelta * Props.penaltyPerDegreePerTick);
- }
- }
- else
- {
- // 休眠状态下品质会缓慢衰减
- order.ticksUnderOptimalConditions = Mathf.Max(0, order.ticksUnderOptimalConditions - 2);
- }
+ int extraTicks = Mathf.FloorToInt(extraProgress);
+ if (Rand.Value < (extraProgress - extraTicks)) extraTicks++;
+ order.productionUntilTick = Mathf.Max(0, order.productionUntilTick - extraTicks);
}
+
+ // 通量品质系统:低通量时品质增长快
+ float qualityBonus = 1f + (1f - neutronFlux) * 0.5f;
+ float qualityGain = speedFactor * qualityBonus;
+ order.qualityProgress = Mathf.Min(order.qualityProgress + qualityGain, order.qualityTotal);
+ }
+ }
+ else if (IsDormant)
+ {
+ // 休眠时品质下降
+ foreach (var order in orders.Where(o => o.status == OrderStatus.Incubating))
+ {
+ float qualityDecay = (order.qualityTotal * 0.1f) / 60000f;
+ order.qualityProgress = Mathf.Max(0f, order.qualityProgress - qualityDecay);
}
}
- // 处理订单完成和消耗率更新(UI用)
- UpdateLogic();
- }
-
- private void UpdateLogic()
- {
// 完成订单
- productionOrders.RemoveAll(order =>
+ orders.RemoveAll(order =>
{
- if (order.productionUntilTick == 0)
+ if (order.status == OrderStatus.Incubating && order.productionUntilTick == 0)
{
FinishProduction(order);
return true;
@@ -132,26 +308,10 @@ namespace ArachnaeSwarm
return false;
});
- // 启动新订单
- int currentlyProducingCount = productionOrders.Count(o => o.productionUntilTick > 0);
- if (currentlyProducingCount < Props.productionQueueLimit)
- {
- var waitingOrder = productionOrders.FirstOrDefault(o => o.productionUntilTick == -1);
- if (waitingOrder != null)
- {
- // 初始 Tick 数,会被后续 Tick 动态加速
- waitingOrder.productionUntilTick = waitingOrder.process.productionTicks;
- }
- }
-
- // 更新燃料消耗率显示
+ // 更新燃料消耗率
if (FuelComp != null)
{
- float totalConsumption = 0f;
- if (IsAnyOrderActive && NeutronFlux > 0.01f)
- {
- totalConsumption = currentlyProducingCount * 80f * FluxEfficiency;
- }
+ float totalConsumption = IsIncubating && NeutronFlux > 0.01f ? IncubatingCount * 80f * FluxEfficiency : 0f;
FuelComp.currentConsumptionRate = totalConsumption;
}
}
@@ -159,38 +319,191 @@ namespace ArachnaeSwarm
private void CalculateAutoFlux()
{
if (fluxMode == FluxMode.Manual) return;
-
- float targetFlux = 0.5f;
-
- // 简单逻辑:平衡模式趋向于 0.5;品质模式倾向于 0.1;速度模式倾向于 1.0
- switch (fluxMode)
- {
- case FluxMode.Speed: targetFlux = 1.0f; break;
- case FluxMode.Quality: targetFlux = 0.1f; break;
- case FluxMode.Balance:
- default: targetFlux = 0.5f; break;
- }
- // 资源保护
- if (FuelComp != null && FuelComp.Fuel < 50f) targetFlux = Mathf.Min(targetFlux, 0.2f);
-
+ // 找到品质最低的订单来决定通量
+ var incubating = orders.Where(o => o.status == OrderStatus.Incubating).ToList();
+ if (!incubating.Any()) return;
+
+ float minQuality = incubating.Min(o => o.QualityPercent);
+ float avgProgress = incubating.Average(o => GetProgress(o));
+ float gap = minQuality - avgProgress;
+
+ float targetFlux = fluxMode switch
+ {
+ FluxMode.Speed => gap > 0.1f ? 1.0f : 0.8f,
+ FluxMode.Quality => gap < 0 ? 0.2f : (gap < 0.1f ? 0.4f : 0.6f),
+ _ => gap > 0.1f ? 0.7f : (gap < -0.1f ? 0.3f : 0.5f)
+ };
+
+ if (FuelComp != null && FuelComp.Fuel < 50f)
+ targetFlux = Mathf.Min(targetFlux, 0.2f);
+
neutronFlux = Mathf.Lerp(neutronFlux, targetFlux, 0.05f);
}
+ private void FinishProduction(QueuedItemOrder order)
+ {
+ if (order.process == null) return;
+ try
+ {
+ QualityCategory finalQuality = GetQualityCategory(order);
+ for (int i = 0; i < Props.spawnCount.RandomInRange; i++)
+ {
+ Thing product = ThingMaker.MakeThing(order.process.thingDef);
+ product.TryGetComp()?.SetQuality(finalQuality, ArtGenerationContext.Colony);
+ GenPlace.TryPlaceThing(product, parent.Position, parent.Map, ThingPlaceMode.Near);
+ }
+ }
+ catch (System.Exception ex)
+ {
+ ArachnaeLog.Debug($"Error in FinishProduction: {ex.Message}");
+ }
+ }
+
+ // === UI ===
+ public override string CompInspectStringExtra()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine($"订单: {orders.Count} / {Props.productionQueueLimit}");
+ sb.AppendLine($"等待幼虫: {WaitingForLarvaCount} 正在生产: {IncubatingCount}");
+ return sb.ToString().TrimEnd();
+ }
+
public override IEnumerable CompGetGizmosExtra()
{
foreach (var g in base.CompGetGizmosExtra()) yield return g;
- if (parent.Faction == Faction.OfPlayer)
+ if (parent.Faction != Faction.OfPlayer) yield break;
+
+ yield return new Gizmo_NeutronFlux(this);
+ yield return new Gizmo_DualProgressBar(this);
+
+ if (orders.Count < Props.productionQueueLimit)
{
- yield return new Gizmo_NeutronFlux(this);
+ yield return new Command_Action
+ {
+ defaultLabel = $"添加订单 ({orders.Count}/{Props.productionQueueLimit})",
+ defaultDesc = "选择要生产的物品(可多次点击)",
+ icon = ContentFinder.Get("ArachnaeSwarm/UI/Commands/ARA_NodeSwarmIcon", false),
+ action = ShowOrderMenu
+ };
}
+
+ int needed = WaitingForLarvaCount - assignedLarvae.Count;
+ if (needed > 0)
+ {
+ yield return new Command_Action
+ {
+ defaultLabel = $"呼叫幼虫 ({needed})",
+ defaultDesc = $"呼叫 {needed} 只幼虫来激活等待中的订单",
+ icon = ContentFinder.Get("ArachnaeSwarm/UI/Commands/ARA_CallLarva", false),
+ action = CallLarvae
+ };
+ }
+ }
+
+ private void ShowOrderMenu()
+ {
+ var options = new List();
+ foreach (var process in Processes)
+ {
+ if (process.requiredResearch != null && !process.requiredResearch.IsFinished)
+ {
+ options.Add(new FloatMenuOption(process.thingDef.LabelCap + " (需要研究: " + process.requiredResearch.LabelCap + ")", null));
+ }
+ else
+ {
+ var capturedProcess = process;
+ options.Add(new FloatMenuOption(process.thingDef.LabelCap, () => {
+ AddOrder(capturedProcess);
+ if (orders.Count < Props.productionQueueLimit) ShowOrderMenu();
+ }));
+ }
+ }
+
+ if (options.Count > 0)
+ Find.WindowStack.Add(new FloatMenu(options, "选择生产目标"));
+ else
+ Messages.Message("没有可生产的物品(检查建筑是否正确配置)", MessageTypeDefOf.RejectInput);
+ }
+
+ private void BuildProcessList()
+ {
+ _cachedProcesses = new List();
+ foreach (ThingDef thingDef in DefDatabase.AllDefs)
+ {
+ if (thingDef.IsApparel || thingDef.IsWeapon)
+ {
+ var incubationCompProps = thingDef.GetCompProperties();
+ if (incubationCompProps != null)
+ {
+ bool isMatch = (!incubationCompProps.cocoonDefs.NullOrEmpty() && incubationCompProps.cocoonDefs.Contains(parent.def))
+ || (incubationCompProps.cocoonDef != null && incubationCompProps.cocoonDef == parent.def);
+
+ if (isMatch)
+ {
+ ResearchProjectDef researchPrerequisite = thingDef.recipeMaker?.researchPrerequisite
+ ?? thingDef.recipeMaker?.researchPrerequisites?.FirstOrDefault()
+ ?? thingDef.researchPrerequisites?.FirstOrDefault();
+
+ _cachedProcesses.Add(new ProcessDef
+ {
+ thingDef = thingDef,
+ productionTicks = GetIncubationTimeTicks(thingDef),
+ totalNutritionNeeded = GetIncubationCost(thingDef),
+ requiredResearch = researchPrerequisite
+ });
+ }
+ }
+ }
+ }
+ _cachedProcesses.SortBy(p => p.thingDef.label);
+ }
+
+ private int GetIncubationTimeTicks(ThingDef thingDef)
+ {
+ StatDef stat = DefDatabase.GetNamedSilentFail("ARA_IncubationTime");
+ if (stat != null && thingDef.statBases != null)
+ {
+ var sv = thingDef.statBases.FirstOrDefault(s => s.stat == stat);
+ if (sv != null) return Mathf.RoundToInt(sv.value * 60000f);
+ }
+ return 60000;
+ }
+
+ private float GetIncubationCost(ThingDef thingDef)
+ {
+ StatDef stat = DefDatabase.GetNamedSilentFail("ARA_IncubationCost");
+ if (stat != null && thingDef.statBases != null)
+ {
+ var sv = thingDef.statBases.FirstOrDefault(s => s.stat == stat);
+ if (sv != null) return sv.value;
+ }
+ return 10f;
}
public override void PostExposeData()
{
base.PostExposeData();
+ Scribe_Collections.Look(ref orders, "orders", LookMode.Deep);
+ Scribe_Collections.Look(ref assignedLarvae, "assignedLarvae", LookMode.Reference);
Scribe_Values.Look(ref neutronFlux, "neutronFlux", 0.5f);
Scribe_Values.Look(ref fluxMode, "fluxMode", FluxMode.Balance);
+
+ if (orders == null) orders = new List();
+ if (assignedLarvae == null) assignedLarvae = new List();
+
+ if (Scribe.mode == LoadSaveMode.PostLoadInit)
+ {
+ var _ = Processes;
+ foreach (var order in orders)
+ {
+ if (!string.IsNullOrEmpty(order.tempThingDefName) && order.process == null)
+ {
+ order.process = _cachedProcesses.FirstOrDefault(p => p.thingDef.defName == order.tempThingDefName);
+ }
+ }
+ orders.RemoveAll(o => o.process == null);
+ }
}
}
}
diff --git a/Source/ArachnaeSwarm/Building_Comps/ARA_SpawnPawnFromList/CompQueuedPawnSpawnerWithFlux.cs b/Source/ArachnaeSwarm/Building_Comps/ARA_SpawnPawnFromList/CompQueuedPawnSpawnerWithFlux.cs
index 60cf8de..9005f18 100644
--- a/Source/ArachnaeSwarm/Building_Comps/ARA_SpawnPawnFromList/CompQueuedPawnSpawnerWithFlux.cs
+++ b/Source/ArachnaeSwarm/Building_Comps/ARA_SpawnPawnFromList/CompQueuedPawnSpawnerWithFlux.cs
@@ -4,30 +4,96 @@ using System.Linq;
using System.Text;
using UnityEngine;
using Verse;
+using Verse.AI;
namespace ArachnaeSwarm
{
- public class CompProperties_QueuedPawnSpawnerWithFlux : CompProperties_QueuedPawnSpawner
+ // 订单状态
+ public enum OrderStatus
{
+ WaitingForLarva = 0, // 等待幼虫激活
+ Incubating = 1, // 正在孵化
+ }
+
+ // 用于 Gizmo 显示的订单信息
+ public struct OrderDisplayInfo
+ {
+ public string label;
+ public OrderStatus status;
+ public float productionProgress;
+ public float qualityProgress;
+ public string estimatedQuality;
+ }
+
+ // 带状态和品质的订单
+ public class QueuedPawnOrder : IExposable
+ {
+ public QueuedPawnSpawnEntry entry;
+ public OrderStatus status = OrderStatus.WaitingForLarva;
+ public int spawnUntilTick = -1;
+
+ // 品质系统
+ public float qualityProgress = 0f;
+ public float qualityTotal = 0f;
+
+ public float QualityPercent => qualityTotal > 0 ? qualityProgress / qualityTotal : 0f;
+
+ public void ExposeData()
+ {
+ Scribe_Deep.Look(ref entry, "entry");
+ Scribe_Values.Look(ref status, "status", OrderStatus.WaitingForLarva);
+ Scribe_Values.Look(ref spawnUntilTick, "spawnUntilTick", -1);
+ Scribe_Values.Look(ref qualityProgress, "qualityProgress", 0f);
+ Scribe_Values.Look(ref qualityTotal, "qualityTotal", 0f);
+ }
+ }
+
+ public class CompProperties_QueuedPawnSpawnerWithFlux : CompProperties
+ {
+ public List spawnablePawns;
+ public List whitelist;
+ public int productionQueueLimit = 5;
+ public float minNutritionToStart = 0.1f;
+
+ // 质量系统
+ public float minSafeTemperature = 7f;
+ public float maxSafeTemperature = 32f;
+ public float penaltyPerDegreePerTick = 0.00001f;
+ public List qualityThresholds;
+
public CompProperties_QueuedPawnSpawnerWithFlux()
{
compClass = typeof(CompQueuedPawnSpawnerWithFlux);
}
}
- public class CompQueuedPawnSpawnerWithFlux : CompQueuedPawnSpawner, IFluxController
+ public class CompQueuedPawnSpawnerWithFlux : ThingComp, IFluxController, ILarvaActivatable
{
// === 通量系统字段 ===
private float neutronFlux = 0.5f;
private FluxMode fluxMode = FluxMode.Balance;
- // === 接口实现 ===
+ // === 生产队列 ===
+ private List orders = new List();
+
+ // === 幼虫管理 ===
+ private List assignedLarvae = new List();
+
+ // === 组件引用 ===
+ private CompRefuelableNutrition _fuelComp;
+ private CompAffectedByFacilities _facilitiesComp;
+
+ public CompProperties_QueuedPawnSpawnerWithFlux Props => (CompProperties_QueuedPawnSpawnerWithFlux)props;
+ private CompRefuelableNutrition FuelComp => _fuelComp ?? (_fuelComp = parent.GetComp());
+ private CompAffectedByFacilities FacilitiesComp => _facilitiesComp ?? (_facilitiesComp = parent.GetComp());
+
+ // === IFluxController 接口实现 ===
public float NeutronFlux => neutronFlux;
public float RawFlux => neutronFlux;
public FluxMode CurrentFluxMode => fluxMode;
public float FluxEfficiency => IFluxControllerExtensions.GetEfficiency(neutronFlux);
public bool IsAutoMode => fluxMode != FluxMode.Manual;
- public bool IsIncubating => IsAnyOrderActive;
+ public bool IsIncubating => orders.Any(o => o.status == OrderStatus.Incubating);
public bool IsDormant => neutronFlux < 0.05f;
public void SetNeutronFlux(float value) => neutronFlux = Mathf.Clamp01(value);
@@ -49,163 +115,344 @@ namespace ArachnaeSwarm
_ => "?"
};
- // 辅助属性
- private bool IsAnyOrderActive => productionOrders.Any(o => o.spawnUntilTick > 0);
+ public override void Initialize(CompProperties props)
+ {
+ base.Initialize(props);
+ _fuelComp = parent.GetComp();
+ _facilitiesComp = parent.GetComp();
+ }
- // 覆盖 Tick 逻辑以处理通量效果
+ // === 订单管理 ===
+ public void AddOrder(QueuedPawnSpawnEntry entry)
+ {
+ if (orders.Count >= Props.productionQueueLimit)
+ {
+ Messages.Message("队列已满!", MessageTypeDefOf.RejectInput);
+ return;
+ }
+ orders.Add(new QueuedPawnOrder { entry = entry, status = OrderStatus.WaitingForLarva });
+ }
+
+ public void RemoveOrder(QueuedPawnOrder order) => orders.Remove(order);
+ public void RemoveOrderByIndex(int index) { if (index >= 0 && index < orders.Count) orders.RemoveAt(index); }
+
+ public int WaitingForLarvaCount => orders.Count(o => o.status == OrderStatus.WaitingForLarva);
+ public int IncubatingCount => orders.Count(o => o.status == OrderStatus.Incubating);
+
+ // === Gizmo 用的订单信息 ===
+ public List GetOrdersForGizmo()
+ {
+ var result = new List();
+ foreach (var order in orders)
+ {
+ float prodProgress = GetProgress(order);
+ result.Add(new PawnOrderDisplayInfo
+ {
+ label = order.entry.pawnKind.LabelCap,
+ status = order.status,
+ progress = prodProgress,
+ qualityProgress = order.QualityPercent,
+ remainingTime = order.status == OrderStatus.Incubating && order.spawnUntilTick > 0
+ ? (order.spawnUntilTick - Find.TickManager.TicksGame).ToStringTicksToPeriod()
+ : "等待中",
+ estimatedQuality = GetEstimatedQuality(order).GetLabel()
+ });
+ }
+ return result;
+ }
+
+ private float GetProgress(QueuedPawnOrder order)
+ {
+ if (order.status != OrderStatus.Incubating || order.spawnUntilTick <= 0) return 0f;
+ int totalTicks = order.entry.delayTicks;
+ int startTick = order.spawnUntilTick - totalTicks;
+ int elapsed = Find.TickManager.TicksGame - startTick;
+ return totalTicks > 0 ? Mathf.Clamp01((float)elapsed / totalTicks) : 0f;
+ }
+
+ private QualityCategory GetEstimatedQuality(QueuedPawnOrder order)
+ {
+ if (Props.qualityThresholds.NullOrEmpty()) return QualityCategory.Normal;
+ float qualityPercent = order.QualityPercent;
+ foreach (var threshold in Props.qualityThresholds.OrderByDescending(q => q.threshold))
+ {
+ if (qualityPercent >= threshold.threshold) return threshold.quality;
+ }
+ return Props.qualityThresholds.Any()
+ ? Props.qualityThresholds.OrderBy(q => q.threshold).First().quality
+ : QualityCategory.Awful;
+ }
+
+ // === 幼虫激活逻辑 ===
+ private void CallLarvae()
+ {
+ int neededLarvae = WaitingForLarvaCount - assignedLarvae.Count;
+ if (neededLarvae <= 0)
+ {
+ Messages.Message("没有需要激活的订单", MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ int called = 0;
+ var availableLarvae = FindAvailableLarvae(neededLarvae);
+ foreach (var larva in availableLarvae)
+ {
+ var job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_OperateIncubator"), parent);
+ if (larva.jobs.TryTakeOrderedJob(job, JobTag.Misc))
+ {
+ assignedLarvae.Add(larva);
+ called++;
+ }
+ }
+
+ if (called > 0)
+ Messages.Message($"已呼叫 {called} 只幼虫", MessageTypeDefOf.PositiveEvent);
+ else
+ Messages.Message("未找到可用的幼虫!", MessageTypeDefOf.RejectInput);
+ }
+
+ private List FindAvailableLarvae(int maxCount)
+ {
+ var result = new List();
+ float searchRadius = 50f;
+ if (parent.Map == null) return result;
+
+ foreach (var pawn in parent.Map.mapPawns.AllPawnsSpawned)
+ {
+ if (result.Count >= maxCount) break;
+ if (pawn.def.defName == "ArachnaeBase_Race_Larva" &&
+ !pawn.Downed && !pawn.Dead &&
+ pawn.Faction == parent.Faction &&
+ !assignedLarvae.Contains(pawn) &&
+ parent.Position.DistanceTo(pawn.Position) <= searchRadius)
+ {
+ result.Add(pawn);
+ }
+ }
+ return result;
+ }
+
+ public void NotifyLarvaArrived(Pawn larva) { }
+
+ public void NotifyLarvaOperationComplete(Pawn larva)
+ {
+ var waitingOrder = orders.FirstOrDefault(o => o.status == OrderStatus.WaitingForLarva);
+ if (waitingOrder != null)
+ {
+ waitingOrder.status = OrderStatus.Incubating;
+ waitingOrder.spawnUntilTick = Find.TickManager.TicksGame + waitingOrder.entry.delayTicks;
+ waitingOrder.qualityTotal = waitingOrder.entry.delayTicks;
+ waitingOrder.qualityProgress = 0f;
+ }
+ assignedLarvae.Remove(larva);
+ }
+
+ // === Tick 逻辑 ===
public override void CompTick()
{
- // 注意:我们直接重写逻辑,而不是仅仅调用 base.CompTick,
- // 这样我们可以更精确地控制进度增加速度。
-
+ base.CompTick();
+
+ assignedLarvae.RemoveAll(l => l == null || l.Dead || l.Destroyed);
+
bool hasFuel = FuelComp?.HasFuel ?? true;
// 自动模式调节
- if (IsAutoMode && parent.IsHashIntervalTick(250) && IsAnyOrderActive)
+ if (IsAutoMode && parent.IsHashIntervalTick(250) && IsIncubating)
{
CalculateAutoFlux();
}
- if (IsAnyOrderActive)
+ // 消耗燃料
+ if (IsIncubating && FuelComp != null && neutronFlux > 0.01f)
{
- // 消耗燃料(基于通量效率)
- if (FuelComp != null && neutronFlux > 0.01f)
- {
- float fuelPerTick = (50f * FluxEfficiency) / 60000f;
- FuelComp.ConsumeFuel(fuelPerTick);
- }
+ float fuelPerTick = (50f * FluxEfficiency * IncubatingCount) / 60000f;
+ FuelComp.ConsumeFuel(fuelPerTick);
+ }
- if (!hasFuel)
- {
- // 没燃料时进度停滞
- }
- else if (IsDormant)
- {
- // 休眠逻辑:目前排队组件暂不支持品质损耗,仅停滞
- }
- else
- {
- // 正常孵化:受通量加速影响
- float speedFactor = 1f + (FacilitiesComp?.GetStatOffset(StatDef.Named("ARA_IncubationSpeedFactor")) ?? 0f);
- float fluxSpeed = speedFactor * FluxEfficiency * 5f;
+ // 处理正在孵化的订单
+ if (hasFuel && !IsDormant)
+ {
+ float speedFactor = 1f + (FacilitiesComp?.GetStatOffset(StatDef.Named("ARA_IncubationSpeedFactor")) ?? 0f);
+ float fluxSpeed = speedFactor * FluxEfficiency * 5f;
- // 更新所有激活订单的进度
- // 由于基类逻辑是基于 TickManager.TicksGame 的(spawnUntilTick),
- // 我们需要手动调整这个目标 Tick 来实现加速。
- //
- // 复杂点:基类认为每 Tick 进度 +1。我们要实现每 Tick 进度 +fluxSpeed。
- // 所以我们每 Tick 实际上让目标 Tick 靠近当前 Tick (fluxSpeed - 1) 个单位。
- foreach (var order in productionOrders.Where(o => o.spawnUntilTick > 0))
+ foreach (var order in orders.Where(o => o.status == OrderStatus.Incubating))
+ {
+ // 进度推进
+ float extraProgress = fluxSpeed - 1f;
+ if (extraProgress > 0)
{
- // 计算需要减少的剩余时间量
- float extraProgress = fluxSpeed - 1f;
- // 我们通过增加一个随机概率或累积器来处理小数
- // 这里简单处理:直接修改 spawnUntilTick
- // (注意:如果 fluxSpeed < 1,spawnUntilTick 会延后)
- if (extraProgress > 0)
- {
- // 实际应该减少的值。如果 fluxSpeed=5,则每 tick 应该减少 5 tick 的等待时间。
- // 基类自然减少了 1,我们额外减少 4。
- int extraTicks = Mathf.FloorToInt(extraProgress);
- if (Rand.Value < (extraProgress - extraTicks)) extraTicks++;
- order.spawnUntilTick -= extraTicks;
- }
- else if (extraProgress < 0)
- {
- // 减速逻辑
- float delayExtra = 1f - fluxSpeed;
- int delayTicks = Mathf.FloorToInt(delayExtra);
- if (Rand.Value < (delayExtra - delayTicks)) delayTicks++;
- order.spawnUntilTick += delayTicks;
- }
+ int extraTicks = Mathf.FloorToInt(extraProgress);
+ if (Rand.Value < (extraProgress - extraTicks)) extraTicks++;
+ order.spawnUntilTick -= extraTicks;
}
+
+ // 品质累积(低通量时累积更快)
+ float qualityBonus = 1f + (1f - neutronFlux) * 0.5f;
+ float qualityGain = speedFactor * qualityBonus;
+ order.qualityProgress = Mathf.Min(order.qualityProgress + qualityGain, order.qualityTotal);
}
}
- // 处理订单完成和启动新订单(逻辑基本同基类,但我们需要确保燃料消耗正常更新)
- TickProductionLogic();
- }
-
- private void TickProductionLogic()
- {
// 完成订单
- productionOrders.RemoveAll(order =>
+ orders.RemoveAll(order =>
{
- if (order.spawnUntilTick > 0 && Find.TickManager.TicksGame >= order.spawnUntilTick)
+ if (order.status == OrderStatus.Incubating &&
+ order.spawnUntilTick > 0 &&
+ Find.TickManager.TicksGame >= order.spawnUntilTick)
{
- Pawn pawn = PawnGenerator.GeneratePawn(new PawnGenerationRequest(order.entry.pawnKind, parent.Faction));
- if (pawn != null) GenPlace.TryPlaceThing(pawn, parent.Position, parent.Map, ThingPlaceMode.Near);
+ CompleteOrder(order);
return true;
}
return false;
});
+ }
- // 启动待办订单
- int currentlyProducingCount = productionOrders.Count(o => o.spawnUntilTick > 0);
- if (currentlyProducingCount < Props.productionQueueLimit)
+ private void CompleteOrder(QueuedPawnOrder order)
+ {
+ Pawn pawn = PawnGenerator.GeneratePawn(new PawnGenerationRequest(order.entry.pawnKind, parent.Faction));
+ if (pawn != null)
{
- var waitingOrder = productionOrders.FirstOrDefault(o => o.spawnUntilTick == -1);
- if (waitingOrder != null)
- {
- // 初始目标 Tick,之后会在 CompTick 中被通量系统动态调整
- waitingOrder.spawnUntilTick = Find.TickManager.TicksGame + waitingOrder.entry.delayTicks;
- }
+ // 应用品质效果到 Pawn
+ ApplyQualityEffects(pawn, order.QualityPercent);
+ GenPlace.TryPlaceThing(pawn, parent.Position, parent.Map, ThingPlaceMode.Near);
}
+ }
- // 更新燃料消耗显示(UI用)
- if (FuelComp != null)
- {
- float totalConsumptionPerDay = 0f;
- if (IsAnyOrderActive && NeutronFlux > 0.01f)
- {
- // 基础:每激活一个槽位在 100% 活性下消耗 50/天?
- // 或者统一由组件控制
- totalConsumptionPerDay = currentlyProducingCount * 50f * FluxEfficiency;
- }
- FuelComp.currentConsumptionRate = totalConsumptionPerDay;
- }
+ private void ApplyQualityEffects(Pawn pawn, float qualityPercent)
+ {
+ // 基于品质百分比调整能力
+ // 0.0 = 最差, 1.0 = 最佳
+ float statBonus = qualityPercent * 0.3f; // 最多+30%
+
+ // 可以在这里添加特定的品质效果
+ // 例如:pawn.health, pawn.skills 等
}
private void CalculateAutoFlux()
{
if (fluxMode == FluxMode.Manual) return;
-
- // 排队组件目前主要影响速度。平衡模式下,如果燃料充足,保持中等偏高速度。
- float targetFlux = 0.5f;
-
- switch (fluxMode)
- {
- case FluxMode.Speed: targetFlux = 1.0f; break;
- case FluxMode.Quality: targetFlux = 0.2f; break; // 排队组件目前无品质机制,所以此模式意义较小
- case FluxMode.Balance:
- default: targetFlux = 0.6f; break;
- }
- // 资源保护
- if (FuelComp != null && FuelComp.Fuel < 20f) targetFlux = Mathf.Min(targetFlux, 0.3f);
-
+ // 找到品质最低的订单来决定通量
+ var incubating = orders.Where(o => o.status == OrderStatus.Incubating).ToList();
+ if (!incubating.Any()) return;
+
+ float minQuality = incubating.Min(o => o.QualityPercent);
+ float avgProgress = incubating.Average(o => GetProgress(o));
+ float gap = minQuality - avgProgress;
+
+ float targetFlux = fluxMode switch
+ {
+ FluxMode.Speed => gap > 0.1f ? 1.0f : 0.8f,
+ FluxMode.Quality => gap < 0 ? 0.2f : (gap < 0.1f ? 0.4f : 0.6f),
+ _ => gap > 0.1f ? 0.7f : (gap < -0.1f ? 0.3f : 0.5f)
+ };
+
+ if (FuelComp != null && FuelComp.Fuel < 20f)
+ targetFlux = Mathf.Min(targetFlux, 0.3f);
+
neutronFlux = Mathf.Lerp(neutronFlux, targetFlux, 0.05f);
}
+ // === UI ===
+ public override string CompInspectStringExtra()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine($"订单: {orders.Count} / {Props.productionQueueLimit}");
+ sb.AppendLine($"等待幼虫: {WaitingForLarvaCount} 正在孵化: {IncubatingCount}");
+ return sb.ToString().TrimEnd();
+ }
+
public override IEnumerable CompGetGizmosExtra()
{
foreach (var g in base.CompGetGizmosExtra()) yield return g;
-
- if (parent.Faction == Faction.OfPlayer)
+ if (parent.Faction != Faction.OfPlayer) yield break;
+
+ // 通量控制 Gizmo
+ yield return new Gizmo_NeutronFlux(this);
+
+ // 进度 Gizmo
+ yield return new Gizmo_PawnProgressBar(this);
+
+ // 添加订单按钮
+ if (orders.Count < Props.productionQueueLimit)
{
- // 注意:进度 Gizmo 由 Building 负责显示,
- // 但如果是通用 Building 挂载此组件,可能需要在此提供。
- // 鉴于目前是特定 Building 类,我们暂不在此重复,保持现有的逻辑。
- // 不过,我们需要确保通量控制 Gizmo 可用。
- yield return new Gizmo_NeutronFlux(this);
+ yield return new Command_Action
+ {
+ defaultLabel = $"添加订单 ({orders.Count}/{Props.productionQueueLimit})",
+ defaultDesc = "选择要孵化的单位类型(可多次点击)",
+ icon = ContentFinder.Get("ArachnaeSwarm/UI/Commands/ARA_NodeSwarmIcon", false),
+ action = ShowOrderMenu
+ };
}
+
+ // 呼叫幼虫按钮
+ int needed = WaitingForLarvaCount - assignedLarvae.Count;
+ if (needed > 0)
+ {
+ yield return new Command_Action
+ {
+ defaultLabel = $"呼叫幼虫 ({needed})",
+ defaultDesc = $"呼叫 {needed} 只幼虫来激活等待中的订单",
+ icon = ContentFinder.Get("ArachnaeSwarm/UI/Commands/ARA_CallLarva", false),
+ action = CallLarvae
+ };
+ }
+ }
+
+ private void ShowOrderMenu()
+ {
+ var options = new List();
+
+ foreach (var entry in Props.spawnablePawns)
+ {
+ if (entry.requiredResearch != null && !entry.requiredResearch.IsFinished)
+ {
+ options.Add(new FloatMenuOption(
+ entry.pawnKind.LabelCap + " (需要研究: " + entry.requiredResearch.LabelCap + ")",
+ null
+ ));
+ }
+ else
+ {
+ var capturedEntry = entry;
+ options.Add(new FloatMenuOption(
+ entry.pawnKind.LabelCap,
+ () => {
+ AddOrder(capturedEntry);
+ if (orders.Count < Props.productionQueueLimit)
+ ShowOrderMenu();
+ }
+ ));
+ }
+ }
+
+ if (options.Count > 0)
+ Find.WindowStack.Add(new FloatMenu(options, "选择孵化目标"));
+ else
+ Messages.Message("没有可用的孵化选项", MessageTypeDefOf.RejectInput);
}
public override void PostExposeData()
{
base.PostExposeData();
+ Scribe_Collections.Look(ref orders, "orders", LookMode.Deep);
+ Scribe_Collections.Look(ref assignedLarvae, "assignedLarvae", LookMode.Reference);
Scribe_Values.Look(ref neutronFlux, "neutronFlux", 0.5f);
Scribe_Values.Look(ref fluxMode, "fluxMode", FluxMode.Balance);
+
+ if (orders == null) orders = new List();
+ if (assignedLarvae == null) assignedLarvae = new List();
}
}
+
+ // 督虫订单显示信息(带品质)
+ public struct PawnOrderDisplayInfo
+ {
+ public string label;
+ public OrderStatus status;
+ public float progress;
+ public float qualityProgress;
+ public string remainingTime;
+ public string estimatedQuality;
+ }
}
diff --git a/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Building_EquipmentOotheca.cs b/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Building_EquipmentOotheca.cs
index 74b916d..eaa0eda 100644
--- a/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Building_EquipmentOotheca.cs
+++ b/Source/ArachnaeSwarm/Buildings/Building_EquipmentOotheca/Building_EquipmentOotheca.cs
@@ -8,7 +8,7 @@ using System;
namespace ArachnaeSwarm
{
- public class Building_EquipmentOotheca : Building, IFluxController
+ public class Building_EquipmentOotheca : Building, IFluxController, ILarvaActivatable
{
// === 通量系统字段 ===
private float neutronFlux = 0.5f;
diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs
index 32714ac..95e0639 100644
--- a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs
+++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Building_Ootheca.cs
@@ -8,7 +8,7 @@ using Verse.AI;
namespace ArachnaeSwarm
{
- public class Building_Ootheca : Building, IFluxController
+ public class Building_Ootheca : Building, IFluxController, ILarvaActivatable
{
// === 通量系统字段 ===
private float neutronFlux = 0.5f;
diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_DualProgressBar.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_DualProgressBar.cs
new file mode 100644
index 0000000..c4f97e8
--- /dev/null
+++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_DualProgressBar.cs
@@ -0,0 +1,182 @@
+using RimWorld;
+using System.Collections.Generic;
+using UnityEngine;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ ///
+ /// 双向进度条 Gizmo - 用于物品孵化池
+ /// 从中间向两边生长:左边品质进度,右边生产进度
+ /// 支持滚动显示多个订单
+ ///
+ public class Gizmo_DualProgressBar : Gizmo
+ {
+ private const float BarHeight = 22f;
+ private const float Spacing = 4f;
+ private const float Padding = 8f;
+ private const float TitleHeight = 26f;
+ private const float MaxVisibleOrders = 4;
+
+ private readonly CompQueuedInteractiveProducerWithFlux comp;
+ private float scrollPosition = 0f;
+
+ public Gizmo_DualProgressBar(CompQueuedInteractiveProducerWithFlux comp)
+ {
+ this.comp = comp;
+ this.Order = -98f;
+ }
+
+ public override float GetWidth(float maxWidth) => 220f;
+
+ public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
+ {
+ var orders = comp.GetOrdersForGizmo();
+ int orderCount = orders.Count;
+
+ // 计算高度
+ int visibleCount = Mathf.Min(orderCount, (int)MaxVisibleOrders);
+ float contentHeight = TitleHeight + Spacing;
+ contentHeight += Mathf.Max(1, visibleCount) * (BarHeight + Spacing);
+ contentHeight += Padding;
+
+ float totalHeight = Mathf.Max(75f, contentHeight);
+
+ Rect gizmoRect = new Rect(topLeft.x, topLeft.y - (totalHeight - 75f), GetWidth(maxWidth), totalHeight);
+ Widgets.DrawWindowBackground(gizmoRect);
+
+ float curY = gizmoRect.y + Padding;
+
+ // 标题
+ Rect titleRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, TitleHeight);
+ Text.Font = GameFont.Small;
+ Text.Anchor = TextAnchor.MiddleCenter;
+
+ string title = $"孵化进度 ({orderCount}/{comp.Props.productionQueueLimit})";
+ Widgets.Label(titleRect, title);
+
+ curY += TitleHeight + Spacing;
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ // 订单列表区域
+ Rect listRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, visibleCount * (BarHeight + Spacing));
+
+ if (orderCount > 0)
+ {
+ // 滚动处理
+ if (orderCount > MaxVisibleOrders)
+ {
+ float scrollMax = (orderCount - MaxVisibleOrders) * (BarHeight + Spacing);
+ if (Mouse.IsOver(listRect))
+ {
+ scrollPosition -= Event.current.delta.y * 0.5f;
+ scrollPosition = Mathf.Clamp(scrollPosition, 0f, scrollMax);
+ }
+ }
+
+ GUI.BeginClip(listRect);
+ float drawY = -scrollPosition;
+
+ for (int i = 0; i < orderCount; i++)
+ {
+ var order = orders[i];
+ Rect barRect = new Rect(0, drawY, listRect.width, BarHeight);
+
+ if (barRect.yMax > 0 && barRect.y < listRect.height)
+ {
+ DrawOrderBar(barRect, order, i);
+ }
+ drawY += BarHeight + Spacing;
+ }
+
+ GUI.EndClip();
+
+ // 滚动条指示
+ if (orderCount > MaxVisibleOrders)
+ {
+ float scrollMax = (orderCount - MaxVisibleOrders) * (BarHeight + Spacing);
+ float scrollBarHeight = listRect.height * (MaxVisibleOrders / (float)orderCount);
+ float scrollBarY = curY + (scrollPosition / scrollMax) * (listRect.height - scrollBarHeight);
+
+ Rect scrollBarRect = new Rect(gizmoRect.xMax - 6f, scrollBarY, 4f, scrollBarHeight);
+ Widgets.DrawBoxSolid(scrollBarRect, new Color(1f, 1f, 1f, 0.3f));
+ }
+ }
+ else
+ {
+ Rect emptyRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, BarHeight);
+ Text.Font = GameFont.Tiny;
+ GUI.color = Color.gray;
+ Text.Anchor = TextAnchor.MiddleCenter;
+ Widgets.Label(emptyRect, "无订单 - 点击添加");
+ GUI.color = Color.white;
+ }
+
+ Text.Font = GameFont.Small;
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ return new GizmoResult(GizmoState.Clear);
+ }
+
+ private void DrawOrderBar(Rect rect, OrderDisplayInfo order, int index)
+ {
+ // 背景
+ Widgets.DrawBoxSolid(rect, new Color(0.08f, 0.08f, 0.1f, 0.9f));
+
+ if (order.status == OrderStatus.WaitingForLarva)
+ {
+ // 等待幼虫状态 - 显示标签
+ Text.Font = GameFont.Tiny;
+ Text.Anchor = TextAnchor.MiddleLeft;
+ Rect labelRect = new Rect(rect.x + 4f, rect.y, rect.width - 28f, rect.height);
+ GUI.color = new Color(1f, 0.8f, 0.4f);
+ Widgets.Label(labelRect, $"🐛 {order.label} [等待幼虫]");
+ GUI.color = Color.white;
+ }
+ else
+ {
+ // 正在孵化 - 双向进度条
+ float midX = rect.x + rect.width / 2f;
+ float halfWidth = (rect.width - 28f) / 2f; // 留出取消按钮空间
+
+ // 品质进度(向左生长,青色)
+ float qualityWidth = halfWidth * order.qualityProgress;
+ Rect qualityRect = new Rect(midX - qualityWidth, rect.y + 2f, qualityWidth, rect.height - 4f);
+ Color qualityColor = Color.Lerp(new Color(0.2f, 0.5f, 0.6f), new Color(0.3f, 0.8f, 0.9f), order.qualityProgress);
+ Widgets.DrawBoxSolid(qualityRect, qualityColor);
+
+ // 生产进度(向右生长,绿色)
+ float progressWidth = halfWidth * order.productionProgress;
+ Rect progressRect = new Rect(midX, rect.y + 2f, progressWidth, rect.height - 4f);
+ Color progressColor = Color.Lerp(new Color(0.3f, 0.5f, 0.3f), new Color(0.4f, 0.9f, 0.4f), order.productionProgress);
+ Widgets.DrawBoxSolid(progressRect, progressColor);
+
+ // 中线
+ Widgets.DrawLineVertical(midX, rect.y + 2f, rect.height - 4f);
+
+ // 标签
+ Text.Font = GameFont.Tiny;
+ Text.Anchor = TextAnchor.MiddleCenter;
+ Rect labelRect = new Rect(rect.x, rect.y, rect.width - 24f, rect.height);
+ string displayText = $"{order.label} {order.productionProgress.ToStringPercent("F0")}";
+ GUI.color = Color.white;
+ Widgets.Label(labelRect, displayText);
+
+ // 品质提示
+ string tooltip = $"{order.label}\n生产进度: {order.productionProgress.ToStringPercent()}\n品质进度: {order.qualityProgress.ToStringPercent()}\n预计品质: {order.estimatedQuality}";
+ TooltipHandler.TipRegion(rect, tooltip);
+ }
+
+ // 取消按钮(右侧X)
+ Rect cancelRect = new Rect(rect.xMax - 20f, rect.y + 2f, 18f, rect.height - 4f);
+ if (Widgets.ButtonText(cancelRect, "X", false))
+ {
+ comp.RemoveOrderByIndex(index);
+ }
+
+ // 边框
+ Widgets.DrawBox(rect);
+ Text.Anchor = TextAnchor.UpperLeft;
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_PawnProgressBar.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_PawnProgressBar.cs
new file mode 100644
index 0000000..1c1aabb
--- /dev/null
+++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_PawnProgressBar.cs
@@ -0,0 +1,173 @@
+using RimWorld;
+using System.Collections.Generic;
+using UnityEngine;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ ///
+ /// 双向进度条 Gizmo - 用于督虫孵化池
+ /// 从中间向两边生长:左边品质进度,右边生产进度
+ /// 支持滚动显示多个订单
+ ///
+ public class Gizmo_PawnProgressBar : Gizmo
+ {
+ private const float BarHeight = 22f;
+ private const float Spacing = 4f;
+ private const float Padding = 8f;
+ private const float TitleHeight = 26f;
+ private const float MaxVisibleOrders = 4;
+
+ private readonly CompQueuedPawnSpawnerWithFlux comp;
+ private float scrollPosition = 0f;
+
+ public Gizmo_PawnProgressBar(CompQueuedPawnSpawnerWithFlux comp)
+ {
+ this.comp = comp;
+ this.Order = -98f;
+ }
+
+ public override float GetWidth(float maxWidth) => 220f;
+
+ public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
+ {
+ var orders = comp.GetOrdersForGizmo();
+ int orderCount = orders.Count;
+
+ int visibleCount = Mathf.Min(orderCount, (int)MaxVisibleOrders);
+ float contentHeight = TitleHeight + Spacing;
+ contentHeight += Mathf.Max(1, visibleCount) * (BarHeight + Spacing);
+ contentHeight += Padding;
+
+ float totalHeight = Mathf.Max(75f, contentHeight);
+
+ Rect gizmoRect = new Rect(topLeft.x, topLeft.y - (totalHeight - 75f), GetWidth(maxWidth), totalHeight);
+ Widgets.DrawWindowBackground(gizmoRect);
+
+ float curY = gizmoRect.y + Padding;
+
+ // 标题
+ Rect titleRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, TitleHeight);
+ Text.Font = GameFont.Small;
+ Text.Anchor = TextAnchor.MiddleCenter;
+
+ string title = $"孵化进度 ({orderCount}/{comp.Props.productionQueueLimit})";
+ Widgets.Label(titleRect, title);
+
+ curY += TitleHeight + Spacing;
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ Rect listRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, visibleCount * (BarHeight + Spacing));
+
+ if (orderCount > 0)
+ {
+ if (orderCount > MaxVisibleOrders)
+ {
+ float scrollMax = (orderCount - MaxVisibleOrders) * (BarHeight + Spacing);
+ if (Mouse.IsOver(listRect))
+ {
+ scrollPosition -= Event.current.delta.y * 0.5f;
+ scrollPosition = Mathf.Clamp(scrollPosition, 0f, scrollMax);
+ }
+ }
+
+ GUI.BeginClip(listRect);
+ float drawY = -scrollPosition;
+
+ for (int i = 0; i < orderCount; i++)
+ {
+ var order = orders[i];
+ Rect barRect = new Rect(0, drawY, listRect.width, BarHeight);
+
+ if (barRect.yMax > 0 && barRect.y < listRect.height)
+ {
+ DrawOrderBar(barRect, order, i);
+ }
+ drawY += BarHeight + Spacing;
+ }
+
+ GUI.EndClip();
+
+ if (orderCount > MaxVisibleOrders)
+ {
+ float scrollMax = (orderCount - MaxVisibleOrders) * (BarHeight + Spacing);
+ float scrollBarHeight = listRect.height * (MaxVisibleOrders / (float)orderCount);
+ float scrollBarY = curY + (scrollPosition / scrollMax) * (listRect.height - scrollBarHeight);
+
+ Rect scrollBarRect = new Rect(gizmoRect.xMax - 6f, scrollBarY, 4f, scrollBarHeight);
+ Widgets.DrawBoxSolid(scrollBarRect, new Color(1f, 1f, 1f, 0.3f));
+ }
+ }
+ else
+ {
+ Rect emptyRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, BarHeight);
+ Text.Font = GameFont.Tiny;
+ GUI.color = Color.gray;
+ Text.Anchor = TextAnchor.MiddleCenter;
+ Widgets.Label(emptyRect, "无订单 - 点击添加");
+ GUI.color = Color.white;
+ }
+
+ Text.Font = GameFont.Small;
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ return new GizmoResult(GizmoState.Clear);
+ }
+
+ private void DrawOrderBar(Rect rect, PawnOrderDisplayInfo order, int index)
+ {
+ Widgets.DrawBoxSolid(rect, new Color(0.08f, 0.08f, 0.1f, 0.9f));
+
+ if (order.status == OrderStatus.WaitingForLarva)
+ {
+ Text.Font = GameFont.Tiny;
+ Text.Anchor = TextAnchor.MiddleLeft;
+ Rect labelRect = new Rect(rect.x + 4f, rect.y, rect.width - 28f, rect.height);
+ GUI.color = new Color(1f, 0.8f, 0.4f);
+ Widgets.Label(labelRect, $"🐛 {order.label} [等待幼虫]");
+ GUI.color = Color.white;
+ }
+ else
+ {
+ // 双向进度条
+ float midX = rect.x + rect.width / 2f;
+ float halfWidth = (rect.width - 28f) / 2f;
+
+ // 品质进度(向左生长,青色)
+ float qualityWidth = halfWidth * order.qualityProgress;
+ Rect qualityRect = new Rect(midX - qualityWidth, rect.y + 2f, qualityWidth, rect.height - 4f);
+ Color qualityColor = Color.Lerp(new Color(0.2f, 0.5f, 0.6f), new Color(0.3f, 0.8f, 0.9f), order.qualityProgress);
+ Widgets.DrawBoxSolid(qualityRect, qualityColor);
+
+ // 生产进度(向右生长,绿色)
+ float progressWidth = halfWidth * order.progress;
+ Rect progressRect = new Rect(midX, rect.y + 2f, progressWidth, rect.height - 4f);
+ Color progressColor = Color.Lerp(new Color(0.3f, 0.5f, 0.3f), new Color(0.4f, 0.9f, 0.4f), order.progress);
+ Widgets.DrawBoxSolid(progressRect, progressColor);
+
+ // 中线
+ Widgets.DrawLineVertical(midX, rect.y + 2f, rect.height - 4f);
+
+ // 标签
+ Text.Font = GameFont.Tiny;
+ Text.Anchor = TextAnchor.MiddleCenter;
+ Rect labelRect = new Rect(rect.x, rect.y, rect.width - 24f, rect.height);
+ GUI.color = Color.white;
+ Widgets.Label(labelRect, $"{order.label} {order.progress.ToStringPercent("F0")}");
+
+ // 品质提示
+ string tooltip = $"{order.label}\n生产进度: {order.progress.ToStringPercent()}\n品质进度: {order.qualityProgress.ToStringPercent()}\n预计品质: {order.estimatedQuality}\n剩余时间: {order.remainingTime}";
+ TooltipHandler.TipRegion(rect, tooltip);
+ }
+
+ Rect cancelRect = new Rect(rect.xMax - 20f, rect.y + 2f, 18f, rect.height - 4f);
+ if (Widgets.ButtonText(cancelRect, "X", false))
+ {
+ comp.RemoveOrderByIndex(index);
+ }
+
+ Widgets.DrawBox(rect);
+ Text.Anchor = TextAnchor.UpperLeft;
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_QueuedIncubationProgress.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_QueuedIncubationProgress.cs
new file mode 100644
index 0000000..852ed3a
--- /dev/null
+++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/Gizmo_QueuedIncubationProgress.cs
@@ -0,0 +1,174 @@
+using RimWorld;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ ///
+ /// 多订单进度 Gizmo - 用于大型孵化池
+ /// 支持显示多个同时进行的订单进度
+ ///
+ public class Gizmo_QueuedIncubationProgress : Gizmo
+ {
+ private const float BarHeight = 18f;
+ private const float Spacing = 4f;
+ private const float Padding = 6f;
+ private const float TitleHeight = 24f;
+
+ private readonly ThingComp comp;
+ private readonly string title;
+
+ // 用于获取订单信息的委托
+ public delegate List<(string label, float progress, string tooltip)> GetOrdersDelegate();
+ public delegate void SelectTargetDelegate();
+ public delegate void CallLarvaDelegate();
+ public delegate bool CanAddOrderDelegate();
+ public delegate bool HasSelectedTargetDelegate();
+
+ private readonly GetOrdersDelegate getOrders;
+ private readonly SelectTargetDelegate selectTarget;
+ private readonly CallLarvaDelegate callLarva;
+ private readonly CanAddOrderDelegate canAddOrder;
+ private readonly HasSelectedTargetDelegate hasSelectedTarget;
+
+ public Gizmo_QueuedIncubationProgress(
+ ThingComp comp,
+ string title,
+ GetOrdersDelegate getOrders,
+ SelectTargetDelegate selectTarget,
+ CallLarvaDelegate callLarva,
+ CanAddOrderDelegate canAddOrder,
+ HasSelectedTargetDelegate hasSelectedTarget)
+ {
+ this.comp = comp;
+ this.title = title;
+ this.getOrders = getOrders;
+ this.selectTarget = selectTarget;
+ this.callLarva = callLarva;
+ this.canAddOrder = canAddOrder;
+ this.hasSelectedTarget = hasSelectedTarget;
+ this.Order = -99f;
+ }
+
+ public override float GetWidth(float maxWidth) => 200f;
+
+ public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
+ {
+ var orders = getOrders?.Invoke() ?? new List<(string, float, string)>();
+ int orderCount = orders.Count;
+
+ // 计算动态高度
+ float contentHeight = TitleHeight + Spacing;
+ if (orderCount > 0)
+ {
+ contentHeight += orderCount * (BarHeight + Spacing);
+ }
+ else
+ {
+ contentHeight += BarHeight + Spacing; // 空状态提示
+ }
+ contentHeight += Padding;
+
+ float totalHeight = Mathf.Max(75f, contentHeight);
+
+ Rect gizmoRect = new Rect(topLeft.x, topLeft.y - (totalHeight - 75f), GetWidth(maxWidth), totalHeight);
+ Widgets.DrawWindowBackground(gizmoRect);
+
+ float curY = gizmoRect.y + Padding;
+
+ // 标题区域
+ Rect titleRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, TitleHeight);
+ Text.Font = GameFont.Small;
+ Text.Anchor = TextAnchor.MiddleCenter;
+
+ // 标题可点击用于选择目标
+ bool canAdd = canAddOrder?.Invoke() ?? false;
+ bool hasTarget = hasSelectedTarget?.Invoke() ?? false;
+
+ if (canAdd && !hasTarget)
+ {
+ GUI.color = new Color(0.5f, 0.9f, 1f);
+ if (Widgets.ButtonText(titleRect, title + " ▼"))
+ {
+ selectTarget?.Invoke();
+ }
+ GUI.color = Color.white;
+ }
+ else if (hasTarget)
+ {
+ GUI.color = new Color(0.5f, 1f, 0.5f);
+ if (Widgets.ButtonText(titleRect, "呼叫幼虫"))
+ {
+ callLarva?.Invoke();
+ }
+ GUI.color = Color.white;
+ }
+ else
+ {
+ Widgets.Label(titleRect, title);
+ }
+
+ curY += TitleHeight + Spacing;
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ // 订单进度条
+ if (orderCount > 0)
+ {
+ foreach (var order in orders)
+ {
+ Rect barRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, BarHeight);
+ DrawOrderBar(barRect, order.label, order.progress, order.tooltip);
+ curY += BarHeight + Spacing;
+ }
+ }
+ else
+ {
+ Rect emptyRect = new Rect(gizmoRect.x + Padding, curY, gizmoRect.width - Padding * 2, BarHeight);
+ Text.Font = GameFont.Tiny;
+ GUI.color = Color.gray;
+ Widgets.Label(emptyRect, "无正在进行的订单");
+ GUI.color = Color.white;
+ }
+
+ Text.Font = GameFont.Small;
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ return new GizmoResult(GizmoState.Clear);
+ }
+
+ private void DrawOrderBar(Rect rect, string label, float progress, string tooltip)
+ {
+ // 背景
+ Widgets.DrawBoxSolid(rect, new Color(0.1f, 0.1f, 0.1f, 0.8f));
+
+ // 进度条
+ Rect filledRect = new Rect(rect.x, rect.y, rect.width * Mathf.Clamp01(progress), rect.height);
+ Color barColor = Color.Lerp(new Color(0.4f, 0.6f, 0.4f), new Color(0.3f, 0.8f, 0.3f), progress);
+ Widgets.DrawBoxSolid(filledRect, barColor);
+
+ // 边框
+ Widgets.DrawBox(rect);
+
+ // 标签
+ Text.Font = GameFont.Tiny;
+ Text.Anchor = TextAnchor.MiddleLeft;
+ Rect labelRect = new Rect(rect.x + 4f, rect.y, rect.width - 50f, rect.height);
+ Widgets.Label(labelRect, label);
+
+ // 百分比
+ Text.Anchor = TextAnchor.MiddleRight;
+ Rect pctRect = new Rect(rect.x, rect.y, rect.width - 4f, rect.height);
+ Widgets.Label(pctRect, progress.ToStringPercent("F0"));
+
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ // 提示
+ if (!string.IsNullOrEmpty(tooltip))
+ {
+ TooltipHandler.TipRegion(rect, tooltip);
+ }
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/JobDriver_OperateIncubator.cs b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/JobDriver_OperateIncubator.cs
index ae3ccae..1663515 100644
--- a/Source/ArachnaeSwarm/Buildings/Building_Ootheca/JobDriver_OperateIncubator.cs
+++ b/Source/ArachnaeSwarm/Buildings/Building_Ootheca/JobDriver_OperateIncubator.cs
@@ -1,5 +1,6 @@
using RimWorld;
using System.Collections.Generic;
+using System.Linq;
using Verse;
using Verse.AI;
@@ -9,12 +10,33 @@ namespace ArachnaeSwarm
{
private const int OperationDuration = 180; // 3 seconds = 180 ticks
- // 目标建筑
- private Building_Ootheca Ootheca => (Building_Ootheca)job.targetA.Thing;
+ // 目标:可以是建筑本身实现接口,也可以是建筑的组件实现接口
+ private ILarvaActivatable GetActivatable()
+ {
+ var thing = job.targetA.Thing;
+ if (thing == null) return null;
+
+ // 首先检查建筑本身是否实现接口
+ if (thing is ILarvaActivatable activatable)
+ return activatable;
+
+ // 然后检查建筑的组件
+ if (thing is ThingWithComps thingWithComps)
+ {
+ foreach (var comp in thingWithComps.AllComps)
+ {
+ if (comp is ILarvaActivatable compActivatable)
+ return compActivatable;
+ }
+ }
+
+ return null;
+ }
+
+ private Thing TargetThing => job.targetA.Thing;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
- // 动物和殖民者都可以预订
return pawn.Reserve(job.targetA, job, 1, -1, null, errorOnFailed);
}
@@ -22,26 +44,25 @@ namespace ArachnaeSwarm
{
// 验证目标建筑
this.FailOnDespawnedNullOrForbidden(TargetIndex.A);
- this.FailOn(() => Ootheca == null);
+ this.FailOn(() => GetActivatable() == null);
// 1. 移动到建筑
yield return Toils_Goto.GotoThing(TargetIndex.A, PathEndMode.InteractionCell)
.FailOnSomeonePhysicallyInteracting(TargetIndex.A);
- // 2. 等待片刻(让动物有时间转身)
+ // 2. 等待片刻
yield return Toils_General.WaitWith(TargetIndex.A, 10, true, true);
- // 3. 开始操作(3秒)
+ // 3. 开始操作
var operate = new Toil();
operate.initAction = () =>
{
- // 通知建筑幼虫已到达
- Ootheca?.NotifyLarvaArrived(pawn);
+ GetActivatable()?.NotifyLarvaArrived(pawn);
};
operate.tickAction = () =>
{
- // 面向建筑
- pawn.rotationTracker.FaceCell(Ootheca.Position);
+ if (TargetThing != null)
+ pawn.rotationTracker.FaceCell(TargetThing.Position);
};
operate.defaultCompleteMode = ToilCompleteMode.Delay;
operate.defaultDuration = OperationDuration;
@@ -54,13 +75,10 @@ namespace ArachnaeSwarm
{
initAction = () =>
{
- // 操作完成,删除幼虫并开始孵化
- if (Ootheca != null && pawn != null && pawn.def.defName == "ArachnaeBase_Race_Larva")
+ var activatable = GetActivatable();
+ if (activatable != null && pawn != null && pawn.def.defName == "ArachnaeBase_Race_Larva")
{
- // 通知建筑幼虫操作完成
- Ootheca.NotifyLarvaOperationComplete(pawn);
-
- // 删除幼虫
+ activatable.NotifyLarvaOperationComplete(pawn);
pawn.Destroy(DestroyMode.Vanish);
}
},
@@ -70,7 +88,7 @@ namespace ArachnaeSwarm
public override string GetReport()
{
- if (Ootheca != null)
+ if (GetActivatable() != null)
{
return "ActivatingOotheca".Translate();
}
diff --git a/Source/ArachnaeSwarm/Buildings/ILarvaActivatable.cs b/Source/ArachnaeSwarm/Buildings/ILarvaActivatable.cs
new file mode 100644
index 0000000..ab0100c
--- /dev/null
+++ b/Source/ArachnaeSwarm/Buildings/ILarvaActivatable.cs
@@ -0,0 +1,14 @@
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ ///
+ /// 可由幼虫激活的孵化建筑接口
+ /// Building_Ootheca 和 Building_EquipmentOotheca 都实现此接口
+ ///
+ public interface ILarvaActivatable
+ {
+ void NotifyLarvaArrived(Pawn larva);
+ void NotifyLarvaOperationComplete(Pawn larva);
+ }
+}