diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 87431c1..e435abb 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/Defs/JobDefs/ARA_Jobs_Interactive.xml b/1.6/1.6/Defs/JobDefs/ARA_Jobs_Interactive.xml index 89ae943..eb0159d 100644 --- a/1.6/1.6/Defs/JobDefs/ARA_Jobs_Interactive.xml +++ b/1.6/1.6/Defs/JobDefs/ARA_Jobs_Interactive.xml @@ -8,4 +8,11 @@ true + + ARA_AddToQueueJob + ArachnaeSwarm.JobDriver_AddToQueue + 正在添加生产订单。 + true + + \ No newline at end of file diff --git a/1.6/1.6/Defs/Thing_building/ARA_BioforgeIncubator.xml b/1.6/1.6/Defs/Thing_building/ARA_BioforgeIncubator.xml index 1341595..61098fb 100644 --- a/1.6/1.6/Defs/Thing_building/ARA_BioforgeIncubator.xml +++ b/1.6/1.6/Defs/Thing_building/ARA_BioforgeIncubator.xml @@ -103,6 +103,8 @@ 生物质 + true + true diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/InteractiveProducer_Keys.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/InteractiveProducer_Keys.xml index 00392aa..1daa519 100644 --- a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/InteractiveProducer_Keys.xml +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/InteractiveProducer_Keys.xml @@ -17,4 +17,8 @@ 未在生产 未孵化,需要阿拉克涅工艺种交互 + + 所需营养 + 孵化 {0} + \ No newline at end of file diff --git a/Source/ArachnaeSwarm/ARA_CompInteractiveProducer/CompRefuelableNutrition.cs b/Source/ArachnaeSwarm/ARA_CompInteractiveProducer/CompRefuelableNutrition.cs index 1c029b0..a76339d 100644 --- a/Source/ArachnaeSwarm/ARA_CompInteractiveProducer/CompRefuelableNutrition.cs +++ b/Source/ArachnaeSwarm/ARA_CompInteractiveProducer/CompRefuelableNutrition.cs @@ -10,37 +10,32 @@ namespace ArachnaeSwarm public CompProperties_RefuelableNutrition() { compClass = typeof(CompRefuelableNutrition); + // 默认启用这些Gizmo,除非在XML中明确设置为false + this.targetFuelLevelConfigurable = true; + this.showAllowAutoRefuelToggle = true; } } [StaticConstructorOnStartup] public class CompRefuelableNutrition : CompRefuelable { - private static readonly Texture2D FuelIcon = ContentFinder.Get("UI/Icons/ThingCategories/FoodMeals"); - - // This rate is controlled externally, e.g., by a producer comp. Units: nutrition per day. public float currentConsumptionRate = 0f; - public float NutritionStored => Fuel; - public new CompProperties_RefuelableNutrition Props => (CompProperties_RefuelableNutrition)props; public override void CompTick() { - // Call the base tick for things like vacuum logic, but we will handle fuel consumption ourselves. + // 调用基类的Tick,让它处理真空等情况。 + // 基类的燃料消耗逻辑将因为 fuelConsumptionRate 为0而无效。 base.CompTick(); - // External consumption logic - if (currentConsumptionRate > 0) + // 我们自己的动态消耗逻辑 + if (currentConsumptionRate > 0 && HasFuel) { - // Convert per-day rate to per-tick rate and consume float consumptionPerTick = currentConsumptionRate / 60000f; ConsumeFuel(consumptionPerTick); } } - - // Note: The base class's ConsumeFuel is sufficient. - // public void ConsumeFuel(float amount) { ... } public new void Refuel(List fuelThings) { @@ -48,49 +43,52 @@ namespace ArachnaeSwarm if (fuelNeeded < 0.001f) return; float totalNutritionGained = 0; - List thingsToProcess = new List(fuelThings); + var thingsToProcess = new List(fuelThings); foreach (var thing in thingsToProcess) { if (fuelNeeded <= 0) break; - float nutritionPerUnit = thing.GetStatValue(StatDefOf.Nutrition); if (nutritionPerUnit <= 0) continue; int numToTake = Mathf.CeilToInt(fuelNeeded / nutritionPerUnit); numToTake = Mathf.Min(numToTake, thing.stackCount); - + float nutritionFromThis = numToTake * nutritionPerUnit; base.Refuel(nutritionFromThis); totalNutritionGained += nutritionFromThis; thing.SplitOff(numToTake).Destroy(); - fuelNeeded = TargetFuelLevel - Fuel; } if (totalNutritionGained > 0 && Props.fuelGizmoLabel != null) { - // Removed PawnUtility.ShouldSendNotificationAbout check as it requires a Pawn. Messages.Message("MessageRefueled".Translate(parent.LabelShort, totalNutritionGained.ToString("0.##"), Props.fuelGizmoLabel), parent, MessageTypeDefOf.PositiveEvent); } } + public override string CompInspectStringExtra() { - // Build the string from scratch to avoid the base class's incorrect time calculation. string text = Props.FuelLabel + ": " + Fuel.ToStringDecimalIfSmall() + " / " + Props.fuelCapacity.ToStringDecimalIfSmall(); - - // If we have a custom consumption rate, calculate and display our own time estimate. + if (currentConsumptionRate > 0f && HasFuel) { int numTicks = (int)(Fuel / (currentConsumptionRate / 60000f)); text += " (" + numTicks.ToStringTicksToPeriod() + ")"; } - + else if (!HasFuel && !Props.outOfFuelMessage.NullOrEmpty()) + { + text += "\n" + Props.outOfFuelMessage; + } + + if (Props.targetFuelLevelConfigurable) + { + text += "\n" + "ConfiguredTargetFuelLevel".Translate(TargetFuelLevel.ToStringDecimalIfSmall()); + } + return text; } - - // Removed CompGetGizmosExtra override because Command_Refuel is a private class in CompRefuelable. } } \ No newline at end of file diff --git a/Source/ArachnaeSwarm/ARA_SpawnPawnFromList/CompQueuedPawnSpawner.cs b/Source/ArachnaeSwarm/ARA_SpawnPawnFromList/CompQueuedPawnSpawner.cs index 078b9e8..ddd0d22 100644 --- a/Source/ArachnaeSwarm/ARA_SpawnPawnFromList/CompQueuedPawnSpawner.cs +++ b/Source/ArachnaeSwarm/ARA_SpawnPawnFromList/CompQueuedPawnSpawner.cs @@ -4,11 +4,10 @@ using System.Linq; using System.Text; using Verse; using Verse.AI; +using UnityEngine; namespace ArachnaeSwarm { - - // CompProperties: 在XML中配置组件的属性 public class CompProperties_QueuedPawnSpawner : CompProperties { public List spawnablePawns; @@ -22,38 +21,22 @@ namespace ArachnaeSwarm } } - // 主组件类 public class CompQueuedPawnSpawner : ThingComp { private List productionOrders = new List(); - - // 缓存对其他组件的引用以提高性能 + public QueuedPawnSpawnEntry selectedEntry; + private CompRefuelableNutrition _fuelComp; private CompAffectedByFacilities _facilitiesComp; public CompProperties_QueuedPawnSpawner Props => (CompProperties_QueuedPawnSpawner)props; - private CompRefuelableNutrition FuelComp - { - get - { - if (_fuelComp == null) _fuelComp = parent.GetComp(); - return _fuelComp; - } - } - private CompAffectedByFacilities FacilitiesComp - { - get - { - if (_facilitiesComp == null) _facilitiesComp = parent.GetComp(); - return _facilitiesComp; - } - } + private CompRefuelableNutrition FuelComp => _fuelComp ?? (_fuelComp = parent.GetComp()); + private CompAffectedByFacilities FacilitiesComp => _facilitiesComp ?? (_facilitiesComp = parent.GetComp()); public override void Initialize(CompProperties props) { base.Initialize(props); - // 在初始化时获取一次,避免在每次访问时都调用GetComp _fuelComp = parent.GetComp(); _facilitiesComp = parent.GetComp(); } @@ -64,39 +47,37 @@ namespace ArachnaeSwarm if (FuelComp != null && (!FuelComp.HasFuel || FuelComp.NutritionStored < Props.minNutritionToStart)) { - yield return new FloatMenuOption("CannotStartProduction".Translate() + ": " + "NoFuel".Translate(), null); + yield return new FloatMenuOption("CannotStartProduction".Translate(), null); yield break; } - foreach (QueuedPawnSpawnEntry entry in Props.spawnablePawns) + foreach (var entry in Props.spawnablePawns) { - if (entry.pawnKind == null) continue; - if (entry.requiredResearch != null && !entry.requiredResearch.IsFinished) { - string disabledText = "ARA_Incubate".Translate(entry.pawnKind.label) + " (" + "Requires".Translate() + ": " + entry.requiredResearch.label + ")"; - yield return new FloatMenuOption(disabledText, null); + yield return new FloatMenuOption("ARA_Incubate".Translate(entry.pawnKind.label) + " (" + "Requires".Translate() + ": " + entry.requiredResearch.label + ")", null); } else { - string label = "ARA_Incubate".Translate(entry.pawnKind.label); - if (entry.totalNutritionNeeded > 0) + yield return new FloatMenuOption("ARA_Incubate".Translate(entry.pawnKind.label), () => { - label += $" ({"NutritionNeeded".Translate()}: {entry.totalNutritionNeeded.ToString("0.0")})"; - } - yield return new FloatMenuOption(label, () => AddToQueue(entry, selPawn)); + this.selectedEntry = entry; + Job job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_AddToQueueJob"), parent); + selPawn.jobs.TryTakeOrderedJob(job, JobTag.Misc); + }); } } } - private void AddToQueue(QueuedPawnSpawnEntry entry, Pawn selPawn) + public void AddToQueue() { - productionOrders.Add(new QueuedProductionOrder { entry = entry }); - // 给交互的Pawn一个反馈,表示订单已接受 - if (selPawn.jobs?.curJob != null) + if (selectedEntry == null) { - selPawn.jobs.EndCurrentJob(JobCondition.Succeeded); + Log.Error("Tried to add to queue but selectedEntry was null."); + return; } + productionOrders.Add(new QueuedProductionOrder { entry = selectedEntry }); + selectedEntry = null; } public override void CompTick() @@ -106,7 +87,6 @@ namespace ArachnaeSwarm var producingOrders = productionOrders.Where(o => o.spawnUntilTick > 0).ToList(); bool hasFuel = FuelComp?.HasFuel ?? true; - // 处理无燃料情况:仅暂停生产 if (!hasFuel && FuelComp != null && producingOrders.Any()) { foreach (var order in producingOrders) @@ -115,11 +95,9 @@ namespace ArachnaeSwarm } } - // 动态计算总燃料消耗速率 if (FuelComp != null) { float totalConsumptionRatePerDay = 0f; - // 只计算有燃料时正在生产的订单 if(hasFuel) { foreach (var order in producingOrders) @@ -133,7 +111,6 @@ namespace ArachnaeSwarm FuelComp.currentConsumptionRate = totalConsumptionRatePerDay; } - // 检查并完成订单 productionOrders.RemoveAll(order => { if (order.spawnUntilTick > 0 && Find.TickManager.TicksGame >= order.spawnUntilTick) @@ -145,11 +122,10 @@ namespace ArachnaeSwarm return false; }); - // 检查并启动新订单 int currentlyProducingCount = productionOrders.Count(o => o.spawnUntilTick > 0); if (currentlyProducingCount < Props.productionQueueLimit) { - QueuedProductionOrder waitingOrder = productionOrders.FirstOrDefault(o => o.spawnUntilTick == -1); + var waitingOrder = productionOrders.FirstOrDefault(o => o.spawnUntilTick == -1); if (waitingOrder != null) { float speedFactor = 1f; @@ -166,8 +142,7 @@ namespace ArachnaeSwarm public override string CompInspectStringExtra() { StringBuilder sb = new StringBuilder(); - if (FuelComp != null) sb.AppendLine(FuelComp.CompInspectStringExtra()); - + int producingCount = productionOrders.Count(o => o.spawnUntilTick > 0); int queuedCount = productionOrders.Count - producingCount; @@ -198,6 +173,31 @@ namespace ArachnaeSwarm { base.PostExposeData(); Scribe_Collections.Look(ref productionOrders, "productionOrders", LookMode.Deep, new object[0]); + Scribe_Deep.Look(ref selectedEntry, "selectedEntry"); + } + + public override IEnumerable CompGetGizmosExtra() + { + foreach (Gizmo gizmo in base.CompGetGizmosExtra()) + { + yield return gizmo; + } + + if (productionOrders.Any()) + { + var lastOrder = productionOrders.Last(); + + yield return new Command_Action + { + defaultLabel = "CommandCancelProduction".Translate() + ": " + lastOrder.entry.pawnKind.LabelCap, + defaultDesc = "CommandCancelProductionDesc".Translate(), + icon = ContentFinder.Get("UI/Designators/Cancel"), + action = () => + { + productionOrders.Remove(lastOrder); + } + }; + } } } } \ No newline at end of file diff --git a/Source/ArachnaeSwarm/ARA_SpawnPawnFromList/CompSpawnPawnFromList.cs b/Source/ArachnaeSwarm/ARA_SpawnPawnFromList/CompSpawnPawnFromList.cs index 94450bb..5ffb744 100644 --- a/Source/ArachnaeSwarm/ARA_SpawnPawnFromList/CompSpawnPawnFromList.cs +++ b/Source/ArachnaeSwarm/ARA_SpawnPawnFromList/CompSpawnPawnFromList.cs @@ -4,6 +4,7 @@ using Verse; using RimWorld; using Verse.AI; using Verse.AI.Group; +using UnityEngine; // Add this for ContentFinder namespace ArachnaeSwarm { @@ -13,7 +14,8 @@ namespace ArachnaeSwarm private int spawnUntilTick = -1; private PawnKindDef spawningPawnKind; - private PawnSpawnEntry selectedEntry; + // This component uses its own PawnSpawnEntry, separate from the Queued spawner + private PawnSpawnEntry selectedEntry; public bool IsHatching => spawnUntilTick > 0; public override IEnumerable CompFloatMenuOptions(Pawn selPawn) @@ -34,20 +36,17 @@ namespace ArachnaeSwarm { if (entry.pawnKind == null) continue; - // 检查科技需求 if (entry.requiredResearch != null && !entry.requiredResearch.IsFinished) { - // 科技未完成,显示灰色不可点击选项 string disabledText = "ARA_Incubate".Translate(entry.pawnKind.label) + " (" + "Requires".Translate() + ": " + entry.requiredResearch.label + ")"; yield return new FloatMenuOption(disabledText, null); } else { - // 科技已完成或无需求,显示正常选项 yield return new FloatMenuOption("ARA_Incubate".Translate(entry.pawnKind.label), () => { Job job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_IncubateJob"), parent); - this.selectedEntry = entry; // 保存整个入口信息 + this.selectedEntry = entry; selPawn.jobs.TryTakeOrderedJob(job); }); } @@ -55,7 +54,6 @@ namespace ArachnaeSwarm } } - public void StartIncubation() { if (this.selectedEntry == null) return; @@ -73,8 +71,6 @@ namespace ArachnaeSwarm } } - - private void SpawnPawn(PawnKindDef pawnKind) { try @@ -138,7 +134,6 @@ namespace ArachnaeSwarm } } - public override string CompInspectStringExtra() { if (spawnUntilTick > 0) @@ -161,7 +156,31 @@ namespace ArachnaeSwarm base.PostExposeData(); Scribe_Values.Look(ref spawnUntilTick, "spawnUntilTick", -1); Scribe_Defs.Look(ref spawningPawnKind, "spawningPawnKind"); - // selectedEntry is transient and does not need to be saved. + // selectedEntry is not saved because the interaction is a one-off action. + } + + // Moved to the correct position, at the class level. + public override IEnumerable CompGetGizmosExtra() + { + foreach (var g in base.CompGetGizmosExtra()) + { + yield return g; + } + + if (IsHatching) + { + yield return new Command_Action + { + defaultLabel = "CommandCancelProduction".Translate(), + defaultDesc = "CommandCancelProductionDesc".Translate(), + icon = ContentFinder.Get("UI/Designators/Cancel"), + action = () => + { + spawnUntilTick = -1; + spawningPawnKind = null; + } + }; + } } } } \ No newline at end of file diff --git a/Source/ArachnaeSwarm/ARA_SpawnPawnFromList/JobDriver_AddToQueue.cs b/Source/ArachnaeSwarm/ARA_SpawnPawnFromList/JobDriver_AddToQueue.cs new file mode 100644 index 0000000..a606f7e --- /dev/null +++ b/Source/ArachnaeSwarm/ARA_SpawnPawnFromList/JobDriver_AddToQueue.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using Verse; +using Verse.AI; + +namespace ArachnaeSwarm +{ + public class JobDriver_AddToQueue : JobDriver + { + private const TargetIndex IncubatorInd = TargetIndex.A; + + public override bool TryMakePreToilReservations(bool errorOnFailed) + { + return pawn.Reserve(job.GetTarget(IncubatorInd), job, 1, -1, null, errorOnFailed); + } + + protected override IEnumerable MakeNewToils() + { + this.FailOnDespawnedNullOrForbidden(IncubatorInd); + + yield return Toils_Goto.GotoThing(IncubatorInd, PathEndMode.Touch); + + Toil addToQueue = new Toil(); + addToQueue.initAction = () => + { + CompQueuedPawnSpawner comp = job.GetTarget(IncubatorInd).Thing.TryGetComp(); + if (comp != null) + { + comp.AddToQueue(); + } + }; + addToQueue.defaultCompleteMode = ToilCompleteMode.Instant; + yield return addToQueue; + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index aa94edc..8947351 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -73,6 +73,7 @@ +