diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index 56971e3..4760967 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/Thing_building/Building_SmartThermostat.xml b/1.6/1.6/Defs/Thing_building/Building_SmartThermostat.xml
index 71a92fa..bb8b505 100644
--- a/1.6/1.6/Defs/Thing_building/Building_SmartThermostat.xml
+++ b/1.6/1.6/Defs/Thing_building/Building_SmartThermostat.xml
@@ -65,4 +65,82 @@
+
+ ARA_GrowthVat
+
+ 用来存放猎物的茧。
+ ArachnaeSwarm.Building_NutrientVat
+ true
+ Normal
+
+ ArachnaeSwarm/Building/ARA_GrowthVat
+ Graphic_Single
+ CutoutComplex
+ (2.5,2.5)
+
+ (0.85, 0.3, 1.7)
+
+
+ true
+ North
+ (1,2)
+
+ 500
+ 8000
+ 30
+ 0.5
+
+
+ 150
+ 4
+
+ Building
+ PassThroughOnly
+ 42
+ true
+ MapMeshAndRealTime
+ 0.5
+ false
+ ARA_Buildings
+ 2200
+ true
+ (0,0,-1)
+ false
+
+ ITab_BiosculpterNutritionStorage
+ ITab_Genes
+
+
+ GrowthVats
+
+
+ false
+ 120
+ Laboratory
+
+ 4
+
+
+
+ 100.0
+
+
+ Foods
+
+
+ 生物质
+ true
+ true
+
+
+
+
+
+ ArachnaeSwarm/Building/ARA_GrowthVatTop
+
+ Graphic_Single
+
+
+
+
\ No newline at end of file
diff --git a/Content/Textures/ArachnaeSwarm/Building/ARA_GrowthVat.png b/Content/Textures/ArachnaeSwarm/Building/ARA_GrowthVat.png
new file mode 100644
index 0000000..c56bd54
Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Building/ARA_GrowthVat.png differ
diff --git a/Content/Textures/ArachnaeSwarm/Building/ARA_GrowthVatTop.png b/Content/Textures/ArachnaeSwarm/Building/ARA_GrowthVatTop.png
new file mode 100644
index 0000000..32e63ac
Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Building/ARA_GrowthVatTop.png differ
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index e340c96..edcf401 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -133,6 +133,8 @@
+
+
diff --git a/Source/ArachnaeSwarm/Building_NutrientVat.cs b/Source/ArachnaeSwarm/Building_NutrientVat.cs
new file mode 100644
index 0000000..c9bd04f
--- /dev/null
+++ b/Source/ArachnaeSwarm/Building_NutrientVat.cs
@@ -0,0 +1,369 @@
+using RimWorld;
+using System.Collections.Generic;
+using System.Text;
+using UnityEngine;
+using Verse;
+using Verse.AI;
+
+namespace ArachnaeSwarm
+{
+ [StaticConstructorOnStartup]
+ public class Building_NutrientVat : Building_Enterable, IThingHolder, IThingHolderWithDrawnPawn
+ {
+ private CompRefuelableNutrition cachedRefuelableComp;
+ private Graphic cachedTopGraphic;
+
+ // IThingHolderWithDrawnPawn implementation
+ public float HeldPawnDrawPos_Y => DrawPos.y + 0.03658537f;
+ public float HeldPawnBodyAngle => base.Rotation.AsAngle;
+ public PawnPosture HeldPawnPosture => PawnPosture.LayingOnGroundFaceUp;
+
+ private Graphic TopGraphic
+ {
+ get
+ {
+ if (cachedTopGraphic == null)
+ {
+ var modExtension = def.GetModExtension();
+ if (modExtension != null && !modExtension.topGraphicPath.NullOrEmpty())
+ {
+ cachedTopGraphic = GraphicDatabase.Get(modExtension.graphicClass, modExtension.topGraphicPath, ShaderDatabase.Transparent, def.graphicData.drawSize, Color.white, Color.white);
+ }
+ }
+ return cachedTopGraphic;
+ }
+ }
+
+ // Constants for BioStarvation
+ private const float BiostarvationGainPerDayNoFood = 0.5f;
+ private const float BiostarvationFallPerDayFed = 0.1f;
+
+ public override Vector3 PawnDrawOffset => Vector3.zero;
+
+ public CompRefuelableNutrition RefuelableComp
+ {
+ get
+ {
+ if (cachedRefuelableComp == null)
+ {
+ cachedRefuelableComp = this.TryGetComp();
+ }
+ return cachedRefuelableComp;
+ }
+ }
+
+ public bool HasNutrition => RefuelableComp != null && RefuelableComp.HasFuel;
+
+ public float BiostarvationDailyOffset
+ {
+ get
+ {
+ if (!base.Working)
+ {
+ return 0f;
+ }
+ return HasNutrition ? -BiostarvationFallPerDayFed : BiostarvationGainPerDayNoFood;
+ }
+ }
+
+ public float NutritionConsumedPerDay
+ {
+ get
+ {
+ if (selectedPawn != null)
+ {
+ // Let's use the base consumption rate from the original GrowthVat
+ float num = 3f;
+ Hediff biostarvation = selectedPawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.BioStarvation);
+ if (biostarvation != null && biostarvation.Severity > 0)
+ {
+ // Increase consumption when biostarving, same as original
+ num *= 1.1f;
+ }
+ return num;
+ }
+ return 0f;
+ }
+ }
+
+ public override void SpawnSetup(Map map, bool respawningAfterLoad)
+ {
+ base.SpawnSetup(map, respawningAfterLoad);
+ cachedRefuelableComp = this.TryGetComp();
+ }
+
+ public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish)
+ {
+ if (mode != DestroyMode.WillReplace)
+ {
+ if (selectedPawn != null && innerContainer.Contains(selectedPawn))
+ {
+ Notify_PawnRemoved();
+ }
+ }
+ base.DeSpawn(mode);
+ }
+
+ protected override void Tick()
+ {
+ base.Tick();
+
+ if (selectedPawn != null && (selectedPawn.Destroyed || !innerContainer.Contains(selectedPawn)))
+ {
+ OnStop();
+ return;
+ }
+
+ if (base.Working && selectedPawn != null)
+ {
+ // Update BioStarvation
+ float biostarvationOffset = BiostarvationDailyOffset / 60000f * HediffDefOf.BioStarvation.maxSeverity;
+ Hediff biostarvation = selectedPawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.BioStarvation);
+
+ if (biostarvation != null)
+ {
+ biostarvation.Severity += biostarvationOffset;
+ if (biostarvation.ShouldRemove)
+ {
+ selectedPawn.health.RemoveHediff(biostarvation);
+ }
+ }
+ else if (biostarvationOffset > 0f)
+ {
+ Hediff hediff = HediffMaker.MakeHediff(HediffDefOf.BioStarvation, selectedPawn);
+ hediff.Severity = biostarvationOffset;
+ selectedPawn.health.AddHediff(hediff);
+ }
+
+ // Check for failure
+ if (biostarvation != null && biostarvation.Severity >= HediffDefOf.BioStarvation.maxSeverity)
+ {
+ Fail();
+ return;
+ }
+
+ // Update nutrition consumption rate on the component
+ RefuelableComp.currentConsumptionRate = NutritionConsumedPerDay;
+ }
+ else
+ {
+ // If not working, consumption is zero
+ if(RefuelableComp != null)
+ {
+ RefuelableComp.currentConsumptionRate = 0f;
+ }
+ }
+ }
+
+ public override AcceptanceReport CanAcceptPawn(Pawn pawn)
+ {
+ if (base.Working)
+ {
+ return "Occupied".Translate();
+ }
+ if (selectedPawn != null && selectedPawn != pawn)
+ {
+ return "WaitingForPawn".Translate(selectedPawn.Named("PAWN"));
+ }
+ if (pawn.health.hediffSet.HasHediff(HediffDefOf.BioStarvation))
+ {
+ return "PawnBiostarving".Translate(pawn.Named("PAWN"));
+ }
+ return pawn.IsColonist && !pawn.IsQuestLodger();
+ }
+
+ public override void TryAcceptPawn(Pawn pawn)
+ {
+ if (!CanAcceptPawn(pawn))
+ {
+ return;
+ }
+
+ selectedPawn = pawn;
+ bool deselected = pawn.DeSpawnOrDeselect();
+ if (innerContainer.TryAddOrTransfer(pawn))
+ {
+ startTick = Find.TickManager.TicksGame;
+ }
+ if (deselected)
+ {
+ Find.Selector.Select(pawn, playSound: false, forceDesignatorDeselect: false);
+ }
+ }
+
+ private void Finish()
+ {
+ if (selectedPawn != null && innerContainer.Contains(selectedPawn))
+ {
+ Notify_PawnRemoved();
+ innerContainer.TryDrop(selectedPawn, InteractionCell, base.Map, ThingPlaceMode.Near, 1, out var _);
+ OnStop();
+ }
+ }
+
+ private void Fail()
+ {
+ if (selectedPawn != null && innerContainer.Contains(selectedPawn))
+ {
+ Notify_PawnRemoved();
+ innerContainer.TryDrop(selectedPawn, InteractionCell, base.Map, ThingPlaceMode.Near, 1, out var _);
+ Hediff firstHediffOfDef = selectedPawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.BioStarvation);
+ selectedPawn.Kill(null, firstHediffOfDef);
+ }
+ OnStop();
+ }
+
+ private void OnStop()
+ {
+ selectedPawn = null;
+ startTick = -1;
+ if (RefuelableComp != null)
+ {
+ RefuelableComp.currentConsumptionRate = 0f;
+ }
+ }
+
+ private void Notify_PawnRemoved()
+ {
+ // You can add sound effects here if you want, e.g., SoundDefOf.GrowthVat_Open.PlayOneShot(SoundInfo.InMap(this));
+ }
+
+ public override IEnumerable GetGizmos()
+ {
+ // Keep base gizmos
+ foreach (Gizmo gizmo in base.GetGizmos())
+ {
+ yield return gizmo;
+ }
+
+ if (base.Working)
+ {
+ yield return new Command_Action
+ {
+ defaultLabel = "CommandCancelGrowth".Translate(), // Label can be changed
+ defaultDesc = "CommandCancelGrowthDesc".Translate(), // Desc can be changed
+ icon = ContentFinder.Get("UI/Designators/Cancel"),
+ action = () =>
+ {
+ Finish();
+ innerContainer.TryDropAll(InteractionCell, base.Map, ThingPlaceMode.Near);
+ }
+ };
+ }
+ else
+ {
+ if (selectedPawn != null)
+ {
+ yield return new Command_Action
+ {
+ defaultLabel = "CommandCancelLoad".Translate(),
+ defaultDesc = "CommandCancelLoadDesc".Translate(),
+ icon = ContentFinder.Get("UI/Designators/Cancel"),
+ action = () =>
+ {
+ if (selectedPawn?.CurJobDef == JobDefOf.EnterBuilding)
+ {
+ selectedPawn.jobs.EndCurrentJob(JobCondition.InterruptForced);
+ }
+ OnStop();
+ }
+ };
+ }
+
+ var command_Action = new Command_Action
+ {
+ defaultLabel = "InsertPerson".Translate() + "...",
+ defaultDesc = "InsertPersonGrowthVatDesc".Translate(), // Desc can be changed
+ icon = Building_GrowthVat.InsertPawnIcon.Texture,
+ action = () =>
+ {
+ List list = new List();
+ foreach (Pawn p in base.Map.mapPawns.AllPawnsSpawned)
+ {
+ if ((bool)CanAcceptPawn(p))
+ {
+ list.Add(new FloatMenuOption(p.LabelCap, () => SelectPawn(p), p, Color.white));
+ }
+ }
+ if (!list.Any())
+ {
+ list.Add(new FloatMenuOption("NoViablePawns".Translate(), null));
+ }
+ Find.WindowStack.Add(new FloatMenu(list));
+ }
+ };
+ if (!base.AnyAcceptablePawns)
+ {
+ command_Action.Disable("NoViablePawns".Translate());
+ }
+ yield return command_Action;
+ }
+ }
+
+ public override string GetInspectString()
+ {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.Append(base.GetInspectString());
+
+ if (base.Working && selectedPawn != null)
+ {
+ stringBuilder.AppendLineIfNotEmpty().Append("CasketContains".Translate().ToString() + ": " + selectedPawn.NameShortColored.Resolve());
+
+ Hediff biostarvation = selectedPawn.health.hediffSet.GetFirstHediffOfDef(HediffDefOf.BioStarvation);
+ if (biostarvation != null && biostarvation.Severity > 0f)
+ {
+ string text = ((BiostarvationDailyOffset >= 0f) ? "+" : string.Empty);
+ stringBuilder.AppendLineIfNotEmpty().Append(string.Format("{0}: {1} ({2})", "Biostarvation".Translate(), biostarvation.Severity.ToStringPercent(), "PerDay".Translate(text + BiostarvationDailyOffset.ToStringPercent())));
+ }
+ }
+ else if (selectedPawn != null)
+ {
+ stringBuilder.AppendLineIfNotEmpty().Append("WaitingForPawn".Translate(selectedPawn.Named("PAWN")).Resolve());
+ }
+
+ // The inspect string from CompRefuelableNutrition will be automatically added by the game.
+ return stringBuilder.ToString();
+ }
+
+ public override IEnumerable GetFloatMenuOptions(Pawn selPawn)
+ {
+ foreach (FloatMenuOption floatMenuOption in base.GetFloatMenuOptions(selPawn))
+ {
+ yield return floatMenuOption;
+ }
+ if (!selPawn.CanReach(this, PathEndMode.InteractionCell, Danger.Deadly))
+ {
+ yield return new FloatMenuOption("CannotEnterBuilding".Translate(this) + ": " + "NoPath".Translate().CapitalizeFirst(), null);
+ yield break;
+ }
+ AcceptanceReport acceptanceReport = CanAcceptPawn(selPawn);
+ if (acceptanceReport.Accepted)
+ {
+ yield return FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("EnterBuilding".Translate(this), () => SelectPawn(selPawn)), selPawn, this);
+ }
+ else if (!acceptanceReport.Reason.NullOrEmpty())
+ {
+ yield return new FloatMenuOption("CannotEnterBuilding".Translate(this) + ": " + acceptanceReport.Reason.CapitalizeFirst(), null);
+ }
+ }
+
+ public override void DynamicDrawPhaseAt(DrawPhase phase, Vector3 drawLoc, bool flip = false)
+ {
+ if (base.Working && selectedPawn != null && innerContainer.Contains(selectedPawn))
+ {
+ selectedPawn.Drawer.renderer.DynamicDrawPhaseAt(phase, drawLoc + PawnDrawOffset, null, neverAimWeapon: true);
+ }
+ base.DynamicDrawPhaseAt(phase, drawLoc, flip);
+ }
+
+ protected override void DrawAt(Vector3 drawLoc, bool flip = false)
+ {
+ base.DrawAt(drawLoc, flip);
+ // Draw the top graphic if it exists
+ if (TopGraphic != null)
+ {
+ TopGraphic.Draw(DrawPos + Altitudes.AltIncVect * 2f, base.Rotation, this);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/DefModExtension_NutrientVat.cs b/Source/ArachnaeSwarm/DefModExtension_NutrientVat.cs
new file mode 100644
index 0000000..896ebad
--- /dev/null
+++ b/Source/ArachnaeSwarm/DefModExtension_NutrientVat.cs
@@ -0,0 +1,11 @@
+using System;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class DefModExtension_NutrientVat : DefModExtension
+ {
+ public string topGraphicPath;
+ public Type graphicClass = typeof(Graphic_Multi); // Default to Graphic_Multi if not specified in XML
+ }
+}
\ No newline at end of file
diff --git a/非公开资源/Content/Textures/Building/ARA_GrowthVat.psd b/非公开资源/Content/Textures/Building/ARA_GrowthVat.psd
new file mode 100644
index 0000000..df45341
Binary files /dev/null and b/非公开资源/Content/Textures/Building/ARA_GrowthVat.psd differ