diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index 1681aa7..c2e64a9 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 eb0159d..bf7dc77 100644
--- a/1.6/1.6/Defs/JobDefs/ARA_Jobs_Interactive.xml
+++ b/1.6/1.6/Defs/JobDefs/ARA_Jobs_Interactive.xml
@@ -4,14 +4,21 @@
ARA_StartInteractiveProduction
ArachnaeSwarm.JobDriver_StartProduction
- 正在启动生产 TargetA.
+ 正在启动孵化 TargetA.
true
ARA_AddToQueueJob
ArachnaeSwarm.JobDriver_AddToQueue
- 正在添加生产订单。
+ 正在添加虫族孵化订单。
+ true
+
+
+
+ ARA_AddProcessToQueueJob
+ ArachnaeSwarm.JobDriver_AddProcessToQueue
+ 正在添加物品孵化订单。
true
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 61098fb..31b6c0b 100644
--- a/1.6/1.6/Defs/Thing_building/ARA_BioforgeIncubator.xml
+++ b/1.6/1.6/Defs/Thing_building/ARA_BioforgeIncubator.xml
@@ -23,7 +23,130 @@
-
+
+
+ ARA_BioforgeIncubator_Thing
+
+ 一个大型的、需要消耗大量营养物质的孵化设施,可以同时孵化多个单位,并能通过链接外部设备来提高效率。
+
+ Things/Building/AncientHeatVent
+ Graphic_Single
+ CutoutComplex
+ (7,7)
+
+ (7,7)
+ Normal
+
+ 0
+
+ 50
+
+ false
+ 0
+ Building
+ PassThroughOnly
+ ARA_Creep
+ 50
+
+ 250
+ 2800
+ 1.0
+
+
+ PlaceWorker_PreventInteractionSpotOverlap
+
+ 0.8
+ (0,0,-1)
+ true
+ ARA_Buildings
+ 2600
+ Item
+
+ Laboratory
+ 0.8
+
+
+
+
+
+
+ 3
+ 1.0
+
+ ArachnaeNode_Race_WeaponSmith
+
+
+
+ 10
+ 30
+ 0.00001
+
+
+ Legendary
+ 0.99
+
+
+ Masterwork
+ 0.90
+
+
+ Excellent
+ 0.70
+
+
+ Good
+ 0.50
+
+
+ Normal
+ 0.20
+
+
+ Poor
+ 0.10
+
+
+
+
+
+
+ ARA_RW_Basic_Acid_Bladder_Gun
+ 80000
+ 30
+ ARA_Technology_7VXI
+
+
+ ARA_RW_Basic_Fist_Needle_Gun
+ 40000
+ 10
+ ARA_Technology_5PAV
+
+
+
+
+
+
+ 100.0
+
+
+ Foods
+
+
+ 生物质
+ true
+ true
+
+
+
+
+
+ ARA_IncubationAccelerator
+
+
+
+
+
+
ARA_BioforgeIncubator
diff --git a/Source/ArachnaeSwarm/ARA_CompInteractiveProducer/CompQueuedInteractiveProducer.cs b/Source/ArachnaeSwarm/ARA_CompInteractiveProducer/CompQueuedInteractiveProducer.cs
new file mode 100644
index 0000000..b754dbc
--- /dev/null
+++ b/Source/ArachnaeSwarm/ARA_CompInteractiveProducer/CompQueuedInteractiveProducer.cs
@@ -0,0 +1,255 @@
+using RimWorld;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Verse;
+using Verse.AI;
+using UnityEngine;
+
+namespace ArachnaeSwarm
+{
+ // Data contract for a single production order in the queue
+ public class QueuedProcessOrder : IExposable
+ {
+ public ProcessDef process; // Using the existing ProcessDef
+ public int productionUntilTick = -1;
+
+ // Quality-related fields
+ public int ticksUnderOptimalConditions;
+ public float temperaturePenaltyPercent;
+
+ public void ExposeData()
+ {
+ Scribe_Deep.Look(ref process, "process");
+ Scribe_Values.Look(ref productionUntilTick, "productionUntilTick", -1);
+ Scribe_Values.Look(ref ticksUnderOptimalConditions, "ticksUnderOptimalConditions", 0);
+ Scribe_Values.Look(ref temperaturePenaltyPercent, "temperaturePenaltyPercent", 0f);
+ }
+ }
+
+ // Properties for the new queued producer component
+ public class CompProperties_QueuedInteractiveProducer : CompProperties
+ {
+ public List processes;
+ public List whitelist;
+ public int productionQueueLimit = 1;
+ public float minNutritionToStart = 0.1f;
+
+ // Quality-related properties from CompInteractiveProducer
+ public float minSafeTemperature = 7f;
+ public float maxSafeTemperature = 32f;
+ public float penaltyPerDegreePerTick = 0.00001f;
+ public List qualityThresholds;
+ public IntRange spawnCount = new IntRange(1, 1);
+
+ public CompProperties_QueuedInteractiveProducer()
+ {
+ compClass = typeof(CompQueuedInteractiveProducer);
+ }
+ }
+
+ // The main component logic
+ public class CompQueuedInteractiveProducer : ThingComp
+ {
+ private List productionOrders = new List();
+ public ProcessDef selectedProcess; // For passing to the JobDriver
+
+ private CompRefuelableNutrition _fuelComp;
+ private CompAffectedByFacilities _facilitiesComp;
+
+ public CompProperties_QueuedInteractiveProducer Props => (CompProperties_QueuedInteractiveProducer)props;
+ private CompRefuelableNutrition FuelComp => _fuelComp ?? (_fuelComp = parent.GetComp());
+ private CompAffectedByFacilities FacilitiesComp => _facilitiesComp ?? (_facilitiesComp = parent.GetComp());
+
+ public override void Initialize(CompProperties props)
+ {
+ base.Initialize(props);
+ _fuelComp = parent.GetComp();
+ _facilitiesComp = parent.GetComp();
+ }
+
+ public override IEnumerable CompFloatMenuOptions(Pawn selPawn)
+ {
+ if (Props.whitelist == null || !Props.whitelist.Contains(selPawn.kindDef)) yield break;
+ if (FuelComp != null && (!FuelComp.HasFuel || FuelComp.NutritionStored < Props.minNutritionToStart))
+ {
+ yield return new FloatMenuOption("CannotStartProduction".Translate(), null);
+ yield break;
+ }
+
+ foreach (var process in Props.processes)
+ {
+ if (process.requiredResearch != null && !process.requiredResearch.IsFinished)
+ {
+ yield return new FloatMenuOption("StartProduction".Translate(process.thingDef.label) + " (" + "Requires".Translate() + ": " + process.requiredResearch.label + ")", null);
+ }
+ else
+ {
+ yield return new FloatMenuOption("StartProduction".Translate(process.thingDef.label), () =>
+ {
+ this.selectedProcess = process;
+ Job job = JobMaker.MakeJob(DefDatabase.GetNamed("ARA_AddProcessToQueueJob"), parent);
+ selPawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
+ });
+ }
+ }
+ }
+
+ public void AddToQueue()
+ {
+ if (selectedProcess == null) return;
+ productionOrders.Add(new QueuedProcessOrder { process = selectedProcess });
+ selectedProcess = null;
+ }
+
+ public override void CompTick()
+ {
+ base.CompTick();
+ var producingOrders = productionOrders.Where(o => o.productionUntilTick > 0).ToList();
+ bool hasFuel = FuelComp?.HasFuel ?? true;
+ float ambientTemperature = parent.AmbientTemperature;
+ bool isTempSafe = ambientTemperature >= Props.minSafeTemperature && ambientTemperature <= Props.maxSafeTemperature;
+
+ // Update progress and penalties for active orders
+ foreach(var order in producingOrders)
+ {
+ if(hasFuel && isTempSafe)
+ {
+ order.ticksUnderOptimalConditions++;
+ }
+ if (!isTempSafe)
+ {
+ float tempDelta = (ambientTemperature > Props.maxSafeTemperature) ? ambientTemperature - Props.maxSafeTemperature : Props.minSafeTemperature - ambientTemperature;
+ order.temperaturePenaltyPercent = Mathf.Min(1f, order.temperaturePenaltyPercent + tempDelta * Props.penaltyPerDegreePerTick);
+ }
+ if (!hasFuel)
+ {
+ order.productionUntilTick++; // Pause production
+ }
+ }
+
+ // Update fuel consumption
+ if (FuelComp != null)
+ {
+ float totalConsumptionRatePerDay = 0f;
+ if(hasFuel)
+ {
+ foreach (var order in producingOrders)
+ {
+ if (order.process.totalNutritionNeeded > 0 && order.process.productionTicks > 0)
+ {
+ totalConsumptionRatePerDay += (order.process.totalNutritionNeeded / order.process.productionTicks) * 60000f;
+ }
+ }
+ }
+ FuelComp.currentConsumptionRate = totalConsumptionRatePerDay;
+ }
+
+ // Finish completed orders
+ productionOrders.RemoveAll(order =>
+ {
+ if (order.productionUntilTick > 0 && Find.TickManager.TicksGame >= order.productionUntilTick)
+ {
+ FinishProduction(order);
+ return true;
+ }
+ return false;
+ });
+
+ // Start new orders
+ int currentlyProducingCount = productionOrders.Count(o => o.productionUntilTick > 0);
+ if (currentlyProducingCount < Props.productionQueueLimit)
+ {
+ var waitingOrder = productionOrders.FirstOrDefault(o => o.productionUntilTick == -1);
+ if (waitingOrder != null)
+ {
+ float speedFactor = 1f + (FacilitiesComp?.GetStatOffset(StatDef.Named("ARA_IncubationSpeedFactor")) ?? 0f);
+ int modifiedDelay = (int)(waitingOrder.process.productionTicks / speedFactor);
+ waitingOrder.productionUntilTick = Find.TickManager.TicksGame + modifiedDelay;
+ }
+ }
+ }
+
+ private void FinishProduction(QueuedProcessOrder order)
+ {
+ float progress = (float)order.ticksUnderOptimalConditions / order.process.productionTicks;
+ float finalQualityPercent = Mathf.Clamp01(progress - order.temperaturePenaltyPercent);
+ QualityCategory finalQuality = QualityCategory.Awful;
+ if (!Props.qualityThresholds.NullOrEmpty())
+ {
+ foreach (var threshold in Props.qualityThresholds.OrderByDescending(q => q.threshold))
+ {
+ if (finalQualityPercent >= threshold.threshold) { finalQuality = threshold.quality; break; }
+ }
+ }
+
+ 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);
+ }
+ }
+
+ public override string CompInspectStringExtra()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ int producingCount = productionOrders.Count(o => o.productionUntilTick > 0);
+ int queuedCount = productionOrders.Count - producingCount;
+
+ sb.AppendLine($"生产槽位: {producingCount} / {Props.productionQueueLimit}");
+ if (queuedCount > 0) sb.AppendLine($"等待队列: {queuedCount}");
+
+ if (FacilitiesComp != null)
+ {
+ float speedFactor = 1f + FacilitiesComp.GetStatOffset(StatDef.Named("ARA_IncubationSpeedFactor"));
+ if(speedFactor != 1f) sb.AppendLine($"生产速度: {speedFactor.ToStringPercent()}");
+ }
+
+ var producingNow = productionOrders.Where(o => o.productionUntilTick > 0).OrderBy(o => o.productionUntilTick);
+ if (producingNow.Any())
+ {
+ sb.AppendLine("正在生产:");
+ foreach (var order in producingNow)
+ {
+ int remainingTicks = order.productionUntilTick - Find.TickManager.TicksGame;
+ sb.AppendLine($" - {order.process.thingDef.LabelCap}: {remainingTicks.ToStringTicksToPeriod(true, false, true, true)}");
+ }
+ }
+
+ return sb.ToString().TrimEnd();
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Collections.Look(ref productionOrders, "productionOrders", LookMode.Deep);
+ Scribe_Deep.Look(ref selectedProcess, "selectedProcess");
+ }
+
+ 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.process.thingDef.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_CompInteractiveProducer/JobDriver_AddProcessToQueue.cs b/Source/ArachnaeSwarm/ARA_CompInteractiveProducer/JobDriver_AddProcessToQueue.cs
new file mode 100644
index 0000000..021b9a8
--- /dev/null
+++ b/Source/ArachnaeSwarm/ARA_CompInteractiveProducer/JobDriver_AddProcessToQueue.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using RimWorld;
+using Verse;
+using Verse.AI;
+
+namespace ArachnaeSwarm
+{
+ public class JobDriver_AddProcessToQueue : JobDriver
+ {
+ private const TargetIndex BuildingInd = TargetIndex.A;
+
+ protected Building Building => (Building)job.GetTarget(BuildingInd).Thing;
+
+ public override bool TryMakePreToilReservations(bool errorOnFailed)
+ {
+ return pawn.Reserve(Building, job, 1, -1, null, errorOnFailed);
+ }
+
+ protected override IEnumerable MakeNewToils()
+ {
+ this.FailOnDespawnedNullOrForbidden(BuildingInd);
+
+ yield return Toils_Goto.GotoThing(BuildingInd, PathEndMode.InteractionCell);
+
+ Toil work = ToilMaker.MakeToil("MakeNewToils");
+ work.initAction = delegate
+ {
+ // Call the new component's method
+ Building.GetComp().AddToQueue();
+ };
+ work.defaultCompleteMode = ToilCompleteMode.Instant;
+ yield return work;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index 8947351..48555c9 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -118,6 +118,8 @@
+
+