diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index 1571d3f..0d977f5 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/AbilityDefs/AbilityDef_PsychicBrainburn.xml b/1.6/1.6/Defs/AbilityDefs/AbilityDef_PsychicBrainburn.xml
new file mode 100644
index 0000000..534d5b9
--- /dev/null
+++ b/1.6/1.6/Defs/AbilityDefs/AbilityDef_PsychicBrainburn.xml
@@ -0,0 +1,43 @@
+
+
+
+
+ ARA_PsychicBrainburn
+
+ 通过一次强力的心灵冲击,直接摧毁目标生物的意识核心,使其永久失去知觉。
+ UI/Abilities/Slaughter
+ 5000
+ false
+ 300
+ true
+ false
+ false
+ true
+ true
+ 0.5
+ Mote_HoraxSmallSpellWarmup
+ HoraxianAbilityCasting
+ AnomalyAbilityWarmup
+ true
+
+ Verb_CastAbility
+ 1.5
+ 25
+
+ true
+ false
+ false
+
+
+
+
+
+ Skip_Entry
+
+
+ false
+
+
+
+
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/HediffDefs/ARA_HediffDef_TerrainExample.xml b/1.6/1.6/Defs/HediffDefs/ARA_HediffDef_TerrainExample.xml
new file mode 100644
index 0000000..a14163a
--- /dev/null
+++ b/1.6/1.6/Defs/HediffDefs/ARA_HediffDef_TerrainExample.xml
@@ -0,0 +1,61 @@
+
+
+
+
+ ARA_TerrainBasedHediff
+
+ 根据所处地形而强化自身。
+ HediffWithComps
+ 1.0
+ -0.01
+ false
+
+
+
+ 60
+
+
+
+
+
+ ARA_InsectCreep
+
+
+
+ 0.0167
+
+
+ -0.0083
+
+
+
+
+
+ false
+ 0
+
+
+
+ 0.4
+
+
+
+ Consciousness
+ 0.1
+
+
+
+
+ 0.8
+
+
+
+ Consciousness
+ 0.25
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/Thing_Misc/Apparels/ARA_Apparel.xml b/1.6/1.6/Defs/Thing_Misc/Apparels/ARA_Apparel.xml
index 3ed920d..7c32b27 100644
--- a/1.6/1.6/Defs/Thing_Misc/Apparels/ARA_Apparel.xml
+++ b/1.6/1.6/Defs/Thing_Misc/Apparels/ARA_Apparel.xml
@@ -182,6 +182,11 @@
0.25
0
+
+
+ ARA_TerrainBasedHediff
+
+
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 4901023..a0dd17c 100644
--- a/1.6/1.6/Defs/Thing_building/ARA_BioforgeIncubator.xml
+++ b/1.6/1.6/Defs/Thing_building/ARA_BioforgeIncubator.xml
@@ -9,6 +9,10 @@
Things/Building/Misc/ToolCabinet
Graphic_Multi
+
+ (0.7, 0.4, 0.7)
+ (0,0,-0.1)
+
(1,1)
@@ -32,6 +36,10 @@
Graphic_Single
CutoutComplex
(5,6)
+
+ (4.0, 0.5, 4.0)
+ (0,0,-0.1)
+
(5,5)
Normal
@@ -149,12 +157,16 @@
一个大型的、需要消耗大量营养物质的孵化设施,可以同时孵化多个单位,并能通过链接外部设备来提高效率。
- Things/Building/AncientHeatVent
+ ArachnaeSwarm/Building/ARA_BioforgeIncubatorPawn
Graphic_Single
CutoutComplex
- (7,7)
+ (8,8)
+
+ (6.0, 0.6, 4.0)
+ (0,0,-0.1)
+
- (7,7)
+ (7,5)
Normal
0
@@ -236,4 +248,99 @@
+
+
+ ARA_JellyVat
+
+ 一个活体虫族器官,通过分别消化植物和肉类物质,来缓慢培育出营养丰富的阿拉克涅虫蜜。需要同时填充素食和肉类才能工作。
+ Building
+
+ ArachnaeSwarm/Building/ARA_JellyVat
+ Graphic_Single
+ CutoutComplex
+ (2.2,2.2)
+
+ (1.6, 0.5, 1.6)
+ (0,0,-0.1)
+
+
+ (2,2)
+ 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
+
+
+
+
+
+
+ 120000
+ 120000
+
+
+
+ ARA_InsectJelly
+ 150
+
+
+ true
+
+
+
+
+ veg_vat
+ 素食
+
+
+ PlantFoodRaw
+
+
+ 50
+ 12.5
+ true
+
+
+
+
+ meat_vat
+ 肉食
+
+
+ MeatRaw
+ AnimalProductRaw
+
+
+ 50
+ 12.5
+ true
+
+
+
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/Thing_building/ARA_Building.xml b/1.6/1.6/Defs/Thing_building/ARA_Building.xml
index c7d6ad0..e8902df 100644
--- a/1.6/1.6/Defs/Thing_building/ARA_Building.xml
+++ b/1.6/1.6/Defs/Thing_building/ARA_Building.xml
@@ -58,10 +58,15 @@
ArachnaeSwarm/Building/Linked/ARA_InsectWall
Graphic_Single
CutoutComplex
+
+ (0.7, 0.4, 0.2)
+ (0,0,-0.1)
+
ARA_Buildings
true
+ 0
0
-6
@@ -119,6 +124,10 @@
ArachnaeSwarm/Building/Door/ARA_InsectDoor
Graphic_Multi
+
+ (0.7, 0.6, 0.7)
+ (0,0,-0.1)
+
@@ -151,7 +160,7 @@
ARA_Buildings
true
- 1.0
+ 0
true
RealtimeOnly
EatVegetarian
@@ -342,6 +351,10 @@
Graphic_Multi
CutoutComplex
(3,4.5)
+
+ (2.5, 0.5, 2.5)
+ (0,0,-0.1)
+
false
0
@@ -388,6 +401,10 @@
Graphic_Multi
CutoutComplex
(2,2)
+
+ (0.8, 0.4, 1.7)
+ (0,0,-0.1)
+
False
diff --git a/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml b/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml
index ef60aab..216692a 100644
--- a/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml
+++ b/1.6/1.6/Defs/Thing_building/ARA_InteractiveEggSac.xml
@@ -174,7 +174,7 @@
6
- (0.9, 0.9 ,0.5, 0)
+ (230, 230, 128, 0)
@@ -288,7 +288,7 @@
6
- (0.9, 0.9 ,0.5, 0)
+ (230, 230, 128, 0)
diff --git a/1.6/1.6/Defs/Thing_building/ARA_InteractiveProducer.xml b/1.6/1.6/Defs/Thing_building/ARA_InteractiveProducer.xml
index 45eeed9..cec828e 100644
--- a/1.6/1.6/Defs/Thing_building/ARA_InteractiveProducer.xml
+++ b/1.6/1.6/Defs/Thing_building/ARA_InteractiveProducer.xml
@@ -6,6 +6,10 @@
ArachnaeSwarm/Building/ARA_Cocoon
Graphic_Single
(1.1,1.1)
+
+ (0.7, 0.4, 0.7)
+ (0,0,-0.1)
+
(1,1)
Building
@@ -261,4 +265,5 @@
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/Thing_building/Building_SmartThermostat.xml b/1.6/1.6/Defs/Thing_building/Building_SmartThermostat.xml
new file mode 100644
index 0000000..bb8b505
--- /dev/null
+++ b/1.6/1.6/Defs/Thing_building/Building_SmartThermostat.xml
@@ -0,0 +1,146 @@
+
+
+
+
+ ARA_SmartThermostat
+
+ 一个不耗电的温控虫虫。它是一个可逆的热泵,能自动制热或制冷以维持设定的目标温度。必须像制冷器一样安装在墙上。
+ ArachnaeSwarm.Building_SmartThermostat
+
+ Things/Building/Misc/TempControl/Cooler
+ Graphic_Multi
+
+ (0.6, 0.4, 0.3)
+ (0,0,-0.1)
+
+
+ Building
+ Impassable
+ true
+ 1
+ true
+ true
+ true
+ true
+ false
+ 0
+
+ 400
+ 100
+ 1.0
+
+ Rare
+
+ 30
+
+ Medium
+
+ PlaceWorker_Vent
+
+ true
+
+ true
+ true
+ true
+
+
+ AirConditioning
+
+ ARA_Buildings
+
+
+
+ UI/Commands/Vent
+ CommandDesignateOpenCloseVentLabel
+ CommandDesignateOpenCloseVentDesc
+
+
+
+
+
+ 34
+
+
+
+
+
+
+
+ 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_BioforgeIncubatorPawn.png b/Content/Textures/ArachnaeSwarm/Building/ARA_BioforgeIncubatorPawn.png
new file mode 100644
index 0000000..7244342
Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Building/ARA_BioforgeIncubatorPawn.png differ
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/Content/Textures/ArachnaeSwarm/Building/ARA_JellyVat.png b/Content/Textures/ArachnaeSwarm/Building/ARA_JellyVat.png
new file mode 100644
index 0000000..9c4c71d
Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Building/ARA_JellyVat.png differ
diff --git a/Source/ArachnaeSwarm/ARA_CompInteractiveProducer/CompRefuelableNutrition.cs b/Source/ArachnaeSwarm/ARA_CompInteractiveProducer/CompRefuelableNutrition.cs
index a76339d..52e59be 100644
--- a/Source/ArachnaeSwarm/ARA_CompInteractiveProducer/CompRefuelableNutrition.cs
+++ b/Source/ArachnaeSwarm/ARA_CompInteractiveProducer/CompRefuelableNutrition.cs
@@ -17,7 +17,7 @@ namespace ArachnaeSwarm
}
[StaticConstructorOnStartup]
- public class CompRefuelableNutrition : CompRefuelable
+ public class CompRefuelableNutrition : CompRefuelable, IFuelSource
{
public float currentConsumptionRate = 0f;
public float NutritionStored => Fuel;
@@ -90,5 +90,13 @@ namespace ArachnaeSwarm
return text;
}
+
+ public new void Notify_UsedThisTick()
+ {
+ if (Props.consumeFuelOnlyWhenUsed)
+ {
+ ConsumeFuel(Props.fuelConsumptionRate / 60000f);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index 8932bae..edcf401 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -133,6 +133,8 @@
+
+
@@ -205,6 +207,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/Building_SmartThermostat.cs b/Source/ArachnaeSwarm/Building_SmartThermostat.cs
new file mode 100644
index 0000000..8641159
--- /dev/null
+++ b/Source/ArachnaeSwarm/Building_SmartThermostat.cs
@@ -0,0 +1,81 @@
+using UnityEngine;
+using Verse;
+using RimWorld;
+
+namespace ArachnaeSwarm
+{
+ public class Building_SmartThermostat : Building_TempControl
+ {
+ private CompFlickable flickableComp;
+ private const float HeatOutputMultiplier = 1.25f;
+ private const float EfficiencyLossPerDegreeDifference = 1f / 130f;
+
+ public override void SpawnSetup(Map map, bool respawningAfterLoad)
+ {
+ base.SpawnSetup(map, respawningAfterLoad);
+ flickableComp = GetComp();
+ }
+
+ public override void TickRare()
+ {
+ // 如果设备被玩家关闭,则不工作
+ if (flickableComp != null && !flickableComp.SwitchIsOn)
+ {
+ compTempControl.operatingAtHighPower = false;
+ return;
+ }
+
+ IntVec3 indoorCell = Position + IntVec3.South.RotatedBy(Rotation);
+ IntVec3 outdoorCell = Position + IntVec3.North.RotatedBy(Rotation);
+
+ if (indoorCell.Impassable(Map) || outdoorCell.Impassable(Map))
+ {
+ compTempControl.operatingAtHighPower = false;
+ return;
+ }
+
+ float indoorTemp = indoorCell.GetTemperature(Map);
+ float outdoorTemp = outdoorCell.GetTemperature(Map);
+ float targetTemp = compTempControl.TargetTemperature;
+
+ float tempDifference = indoorTemp - outdoorTemp;
+ float efficiency = 1f - Mathf.Abs(tempDifference) * EfficiencyLossPerDegreeDifference;
+ if (efficiency < 0f)
+ {
+ efficiency = 0f;
+ }
+
+ bool operating = false;
+
+ if (indoorTemp > targetTemp) // 制冷
+ {
+ float coolingEnergy = compTempControl.Props.energyPerSecond * efficiency * 4.1666665f;
+ float tempChange = GenTemperature.ControlTemperatureTempChange(indoorCell, Map, -coolingEnergy, targetTemp);
+
+ if (!Mathf.Approximately(tempChange, 0f))
+ {
+ indoorCell.GetRoom(Map).Temperature += tempChange;
+ GenTemperature.PushHeat(outdoorCell, Map, coolingEnergy * HeatOutputMultiplier);
+ operating = true;
+ }
+ }
+ else if (indoorTemp < targetTemp) // 制热
+ {
+ float heatingEnergy = compTempControl.Props.energyPerSecond * efficiency * 4.1666665f;
+ float tempChange = GenTemperature.ControlTemperatureTempChange(indoorCell, Map, heatingEnergy, targetTemp);
+
+ if (!Mathf.Approximately(tempChange, 0f))
+ {
+ if (outdoorTemp > -100)
+ {
+ indoorCell.GetRoom(Map).Temperature += tempChange;
+ GenTemperature.PushHeat(outdoorCell, Map, -heatingEnergy / HeatOutputMultiplier);
+ operating = true;
+ }
+ }
+ }
+
+ compTempControl.operatingAtHighPower = operating;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/CompAbilityEffect_PsychicBrainburn.cs b/Source/ArachnaeSwarm/CompAbilityEffect_PsychicBrainburn.cs
new file mode 100644
index 0000000..a45879d
--- /dev/null
+++ b/Source/ArachnaeSwarm/CompAbilityEffect_PsychicBrainburn.cs
@@ -0,0 +1,80 @@
+using Verse;
+using RimWorld;
+using System.Linq;
+
+namespace ArachnaeSwarm
+{
+ public class CompAbilityEffect_PsychicBrainburn : CompAbilityEffect
+ {
+ public new CompProperties_PsychicBrainburn Props => (CompProperties_PsychicBrainburn)props;
+
+ public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
+ {
+ base.Apply(target, dest);
+
+ Pawn pawn = target.Pawn;
+ if (pawn == null)
+ {
+ return;
+ }
+
+ // 查找作为意识来源的身体部位
+ BodyPartRecord brain = pawn.health.hediffSet.GetNotMissingParts()
+ .FirstOrDefault(p => p.def.tags.Contains(BodyPartTagDefOf.ConsciousnessSource));
+
+ if (brain != null)
+ {
+ // 施加巨大伤害以摧毁大脑
+ float damageAmount = 99999f;
+ float penetration = 999f;
+ pawn.TakeDamage(new DamageInfo(DamageDefOf.Burn, damageAmount, penetration, -1f, parent.pawn, brain));
+
+ // 如果在XML中定义了效果,则生成它
+ if (Props.effecterDef != null)
+ {
+ Props.effecterDef.Spawn(pawn.Position, pawn.Map, 1f);
+ }
+ }
+ }
+
+ public override bool Valid(LocalTargetInfo target, bool throwMessages = false)
+ {
+ Pawn pawn = target.Pawn;
+ if (pawn == null)
+ {
+ return false;
+ }
+
+ // 检查目标是否是血肉生物(如果XML中设置为需要)
+ if (Props.requiresFlesh && !pawn.RaceProps.IsFlesh)
+ {
+ if (throwMessages)
+ {
+ Messages.Message("MessageCannotUseOnMechanical".Translate(pawn.Named("PAWN")), pawn, MessageTypeDefOf.RejectInput);
+ }
+ return false;
+ }
+
+ // 检查目标是否有意识来源部位
+ BodyPartRecord brain = pawn.health.hediffSet.GetNotMissingParts()
+ .FirstOrDefault(p => p.def.tags.Contains(BodyPartTagDefOf.ConsciousnessSource));
+
+ if (brain == null)
+ {
+ if (throwMessages)
+ {
+ Messages.Message("MessageTargetHasNoBrain".Translate(pawn.Named("PAWN")), pawn, MessageTypeDefOf.RejectInput);
+ }
+ return false;
+ }
+
+ return base.Valid(target, throwMessages);
+ }
+
+ public override bool AICanTargetNow(LocalTargetInfo target)
+ {
+ // AI不应主动使用此技能
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/CompProperties_PsychicBrainburn.cs b/Source/ArachnaeSwarm/CompProperties_PsychicBrainburn.cs
new file mode 100644
index 0000000..dfb5982
--- /dev/null
+++ b/Source/ArachnaeSwarm/CompProperties_PsychicBrainburn.cs
@@ -0,0 +1,20 @@
+using Verse;
+using RimWorld;
+
+namespace ArachnaeSwarm
+{
+ public class CompProperties_PsychicBrainburn : CompProperties_AbilityEffect
+ {
+ // 如果为true,则技能只能对血肉生物使用。
+ // 如果为false,则可以对机械体等非血肉生物使用。
+ public bool requiresFlesh = true;
+
+ // 在目标身上产生的视觉效果。
+ public EffecterDef effecterDef;
+
+ public CompProperties_PsychicBrainburn()
+ {
+ compClass = typeof(CompAbilityEffect_PsychicBrainburn);
+ }
+ }
+}
\ 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/Source/ArachnaeSwarm/HediffCompProperties_TerrainBasedSeverity.cs b/Source/ArachnaeSwarm/HediffCompProperties_TerrainBasedSeverity.cs
new file mode 100644
index 0000000..979d143
--- /dev/null
+++ b/Source/ArachnaeSwarm/HediffCompProperties_TerrainBasedSeverity.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class HediffCompProperties_TerrainBasedSeverity : HediffCompProperties
+ {
+ // 检查效果的时间间隔(以ticks为单位)
+ public int interval = 60;
+
+ // 当角色站在此列表中的任何地形上时,严重性的变化值
+ public float severityOnTerrain = 0f;
+
+ // 当角色不在任何目标地形上时,严重性的变化值
+ public float severityOffTerrain = 0f;
+
+ // 目标地形的defName列表
+ public List terrainDefs;
+
+ public HediffCompProperties_TerrainBasedSeverity()
+ {
+ compClass = typeof(HediffComp_TerrainBasedSeverity);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/HediffComp_TerrainBasedSeverity.cs b/Source/ArachnaeSwarm/HediffComp_TerrainBasedSeverity.cs
new file mode 100644
index 0000000..ffa58c2
--- /dev/null
+++ b/Source/ArachnaeSwarm/HediffComp_TerrainBasedSeverity.cs
@@ -0,0 +1,42 @@
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class HediffComp_TerrainBasedSeverity : HediffComp
+ {
+ public HediffCompProperties_TerrainBasedSeverity Props => (HediffCompProperties_TerrainBasedSeverity)props;
+
+ public override void CompPostTick(ref float severityAdjustment)
+ {
+ Pawn pawn = parent.pawn;
+
+ // 按照设定的时间间隔执行
+ if (pawn.IsHashIntervalTick(Props.interval))
+ {
+ // 确保角色在地图上
+ if (pawn.Spawned)
+ {
+ // 获取角色当前位置的地形
+ TerrainDef currentTerrain = pawn.Position.GetTerrain(pawn.Map);
+
+ // 检查当前地形是否存在于XML定义的列表中
+ if (Props.terrainDefs != null && Props.terrainDefs.Contains(currentTerrain))
+ {
+ // 如果在目标地形上,增加 severityOnTerrain
+ severityAdjustment += Props.severityOnTerrain;
+ }
+ else
+ {
+ // 如果不在目标地形上,增加 severityOffTerrain
+ severityAdjustment += Props.severityOffTerrain;
+ }
+ }
+ else
+ {
+ // 如果角色不在地图上(例如在运输舱里),则总是应用 off-terrain 的效果
+ severityAdjustment += Props.severityOffTerrain;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/Utils/CompTempControl_Fixed.cs b/Source/ArachnaeSwarm/Utils/CompTempControl_Fixed.cs
new file mode 100644
index 0000000..97d9f80
--- /dev/null
+++ b/Source/ArachnaeSwarm/Utils/CompTempControl_Fixed.cs
@@ -0,0 +1,33 @@
+using RimWorld;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ // First, we need a new properties class that points to our new component class
+ public class CompProperties_TempControl_Fixed : CompProperties_TempControl
+ {
+ public CompProperties_TempControl_Fixed()
+ {
+ compClass = typeof(CompTempControl_Fixed);
+ }
+ }
+
+ // This is our new component class that inherits from the original
+ public class CompTempControl_Fixed : CompTempControl
+ {
+ // We override the problematic method
+ public override string CompInspectStringExtra()
+ {
+ // Call the original method to get its string
+ string baseString = base.CompInspectStringExtra();
+
+ // If the string is not null, trim any whitespace from the end and return it
+ if (!string.IsNullOrEmpty(baseString))
+ {
+ return baseString.TrimEnd();
+ }
+
+ return baseString;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/WULA_HediffDamgeShield/DRMDamageShield.cs b/Source/ArachnaeSwarm/WULA_HediffDamgeShield/DRMDamageShield.cs
new file mode 100644
index 0000000..84710b5
--- /dev/null
+++ b/Source/ArachnaeSwarm/WULA_HediffDamgeShield/DRMDamageShield.cs
@@ -0,0 +1,211 @@
+using RimWorld;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+using Verse;
+using Verse.Sound;
+using HarmonyLib; // For AccessTools
+
+namespace ArachnaeSwarm
+{
+ // 自定义 CompProperties_Shield 变体
+ public class DRMCompShieldProp : CompProperties
+ {
+ public int startingTicksToReset = 3200;
+ public float minDrawSize = 1.2f;
+ public float maxDrawSize = 1.55f;
+ public float energyLossPerDamage = 0.033f;
+ public float energyOnReset = 0.2f;
+ public bool blocksRangedWeapons = true;
+
+ public DRMCompShieldProp()
+ {
+ compClass = typeof(DRMDamageShield);
+ }
+ }
+
+ public class DRMDamageShield : ThingComp
+ {
+ // 从 Hediff_DamageShield 获取层数作为能量
+ public float Energy
+ {
+ get
+ {
+ Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff();
+ return hediff?.ShieldCharges ?? 0;
+ }
+ set
+ {
+ Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff();
+ if (hediff != null)
+ {
+ hediff.ShieldCharges = (int)value;
+ }
+ }
+ }
+
+ public float MaxEnergy
+ {
+ get
+ {
+ Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff();
+ return hediff?.def.maxSeverity ?? 0;
+ }
+ set
+ {
+ // MaxEnergy 由 HediffDef 控制,这里不需要设置
+ }
+ }
+
+ public bool IsActive = false; // 控制护盾是否激活,由 Hediff_DamageShield 管理
+
+ // 复制自 CompShield
+ protected int ticksToReset = -1;
+ protected int lastKeepDisplayTick = -9999;
+ private Vector3 impactAngleVect;
+ private int lastAbsorbDamageTick = -9999;
+
+ public DRMCompShieldProp Props => (DRMCompShieldProp)props;
+
+ public ShieldState ShieldState
+ {
+ get
+ {
+ if (PawnOwner == null || !IsActive || Energy <= 0)
+ {
+ return ShieldState.Disabled;
+ }
+ if (ticksToReset <= 0)
+ {
+ return ShieldState.Active;
+ }
+ return ShieldState.Resetting;
+ }
+ }
+
+ protected Pawn PawnOwner
+ {
+ get
+ {
+ return parent as Pawn;
+ }
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref ticksToReset, "ticksToReset", -1);
+ Scribe_Values.Look(ref lastKeepDisplayTick, "lastKeepDisplayTick", 0);
+ Scribe_Values.Look(ref IsActive, "isActive", false);
+ }
+
+ public override void CompTick()
+ {
+ base.CompTick();
+ if (PawnOwner == null || !IsActive)
+ {
+ return;
+ }
+
+ if (ShieldState == ShieldState.Resetting)
+ {
+ ticksToReset--;
+ if (ticksToReset <= 0)
+ {
+ Reset();
+ }
+ }
+ else if (ShieldState == ShieldState.Active)
+ {
+ // 护盾能量(层数)通过 Hediff_DamageShield 的 Tick 方法管理,这里不需要额外回复
+ // 如果需要自动回复层数,可以在这里实现
+ }
+ }
+
+ public override void PostPreApplyDamage(ref DamageInfo dinfo, out bool absorbed)
+ {
+ absorbed = false;
+ // 获取 Hediff_DamageShield 实例
+ Hediff_DamageShield damageShield = PawnOwner?.health?.hediffSet.GetFirstHediff();
+
+ if (ShieldState != ShieldState.Active || !IsActive || damageShield == null || damageShield.ShieldCharges <= 0)
+ {
+ return;
+ }
+
+ // 我们的护盾阻挡所有伤害类型,但不包含手术
+ // 如果伤害类型不被认为是“有益的”(例如,不是手术),则阻挡
+ if (!dinfo.Def.consideredHelpful)
+ {
+ // 消耗一层护盾
+ damageShield.ShieldCharges--;
+
+ // 触发护盾吸收效果
+ Notify_DamageAbsorbed(dinfo);
+
+ // 护盾抖动效果
+ PawnOwner.Drawer.renderer.wiggler.SetToCustomRotation(Rand.Range(-0.05f, 0.05f));
+ // 移除文字提示
+ // 移除粒子效果
+
+ absorbed = true; // 伤害被吸收
+
+ // 如果护盾层数归零,触发护盾击穿效果
+ if (damageShield.ShieldCharges <= 0)
+ {
+ Notify_ShieldBreak();
+ }
+ }
+ }
+
+ public void Notify_DamageAbsorbed(DamageInfo dinfo)
+ {
+ // 复制自 CompShield.AbsorbedDamage
+ SoundDefOf.EnergyShield_AbsorbDamage.PlayOneShot(new TargetInfo(PawnOwner.Position, PawnOwner.Map));
+ impactAngleVect = Vector3Utility.HorizontalVectorFromAngle(dinfo.Angle);
+ // 移除 FleckMaker.Static 和 FleckMaker.ThrowDustPuff
+ lastAbsorbDamageTick = Find.TickManager.TicksGame;
+ KeepDisplaying();
+ }
+
+ public void Notify_ShieldBreak()
+ {
+ // 复制自 CompShield.Break
+ if (parent.Spawned)
+ {
+ float scale = Mathf.Lerp(Props.minDrawSize, Props.maxDrawSize, Energy / MaxEnergy); // 根据当前能量比例调整大小
+ EffecterDefOf.Shield_Break.SpawnAttached(parent, parent.MapHeld, scale);
+ // 移除 FleckMaker.Static 和 FleckMaker.ThrowDustPuff
+ }
+ ticksToReset = Props.startingTicksToReset;
+ // 护盾层数归零将由 Hediff_DamageShield 负责移除 Hediff
+ }
+
+ private void Reset()
+ {
+ // 复制自 CompShield.Reset
+ if (PawnOwner.Spawned)
+ {
+ SoundDefOf.EnergyShield_Reset.PlayOneShot(new TargetInfo(PawnOwner.Position, PawnOwner.Map));
+ // 移除 FleckMaker.ThrowLightningGlow
+ }
+ ticksToReset = -1;
+ // 能量恢复由 Hediff_DamageShield 负责,这里不需要设置 Energy
+ // 这里可以添加逻辑,让 Hediff_DamageShield 恢复层数
+ Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff();
+ if (hediff != null)
+ {
+ hediff.ShieldCharges = (int)hediff.def.initialSeverity; // 重置时恢复到初始层数
+ }
+ }
+
+ public void KeepDisplaying()
+ {
+ lastKeepDisplayTick = Find.TickManager.TicksGame;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/WULA_HediffDamgeShield/Hediff_DamageShield.cs b/Source/ArachnaeSwarm/WULA_HediffDamgeShield/Hediff_DamageShield.cs
new file mode 100644
index 0000000..1edcd38
--- /dev/null
+++ b/Source/ArachnaeSwarm/WULA_HediffDamgeShield/Hediff_DamageShield.cs
@@ -0,0 +1,115 @@
+using Verse;
+using System; // Add for Activator
+using System.Text;
+using RimWorld;
+using UnityEngine;
+using HarmonyLib; // Needed for AccessTools if you use it here directly
+
+namespace ArachnaeSwarm
+{
+ public class Hediff_DamageShield : HediffWithComps
+ {
+ // 伤害抵挡层数
+ public int ShieldCharges
+ {
+ get => (int)severityInt;
+ set => severityInt = value;
+ }
+
+ // 获取或创建 DRMDamageShield 组件
+ public DRMDamageShield ShieldComp
+ {
+ get
+ {
+ DRMDamageShield comp = pawn.GetComp();
+ if (comp == null)
+ {
+ comp = (DRMDamageShield)Activator.CreateInstance(typeof(DRMDamageShield));
+ comp.parent = pawn;
+ comp.props = new DRMCompShieldProp(); // 确保有属性,即使是默认的
+ pawn.AllComps.Add(comp);
+ comp.Initialize(comp.props);
+ }
+ return comp;
+ }
+ }
+
+
+ public override string LabelInBrackets
+ {
+ get
+ {
+ if (ShieldCharges > 0)
+ {
+ return "层数: " + ShieldCharges;
+ }
+ return null;
+ }
+ }
+
+ public override string TipStringExtra
+ {
+ get
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append(base.TipStringExtra);
+ if (ShieldCharges > 0)
+ {
+ sb.AppendLine(" - 每层抵挡一次伤害。当前层数: " + ShieldCharges);
+ }
+ else
+ {
+ sb.AppendLine(" - 没有可用的抵挡层数。");
+ }
+ return sb.ToString();
+ }
+ }
+
+ public override void ExposeData()
+ {
+ base.ExposeData();
+ // severityInt 会自动保存,所以不需要额外处理 ShieldCharges
+ }
+
+ public override void PostAdd(DamageInfo? dinfo)
+ {
+ base.PostAdd(dinfo);
+ // 确保 Pawn 拥有 DRMCompShield 组件
+ DRMDamageShield comp = ShieldComp; // 访问属性以确保组件被添加
+ if (comp != null)
+ {
+ comp.IsActive = true; // 激活护盾组件
+ // 能量同步将在 Tick() 中完成
+ }
+ }
+
+ public override void PostRemoved()
+ {
+ base.PostRemoved();
+ // 当 Hediff 被移除时,移除对应的 DRMDamageShield 组件
+ DRMDamageShield comp = pawn.GetComp();
+ if (comp != null)
+ {
+ pawn.AllComps.Remove(comp);
+ comp.IsActive = false; // 确保禁用
+ }
+ }
+
+ public override void Tick()
+ {
+ base.Tick();
+ // 如果层数归零,移除 Hediff
+ if (ShieldCharges <= 0)
+ {
+ pawn.health.RemoveHediff(this);
+ }
+ // 同步能量到 ShieldComp
+ DRMDamageShield comp = pawn.GetComp(); // 每次 Tick 获取,确保是最新的
+ if (comp != null && comp.IsActive)
+ {
+ comp.Energy = ShieldCharges;
+ comp.MaxEnergy = (int)def.maxSeverity;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/CompMultiFuelSpawner.cs b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/CompMultiFuelSpawner.cs
new file mode 100644
index 0000000..7810269
--- /dev/null
+++ b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/CompMultiFuelSpawner.cs
@@ -0,0 +1,127 @@
+using System.Collections.Generic;
+using System.Linq;
+using RimWorld;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class SpawnerProduct
+ {
+ public ThingDef thingDef;
+ public int count = 1;
+ }
+
+ public class CompProperties_MultiFuelSpawner : CompProperties
+ {
+ public List products;
+ public IntRange spawnIntervalRange = new IntRange(100, 100);
+ public bool spawnForbidden;
+ public bool inheritFaction;
+ public bool showMessageIfOwned;
+
+ public CompProperties_MultiFuelSpawner()
+ {
+ compClass = typeof(CompMultiFuelSpawner);
+ }
+ }
+
+ public class CompMultiFuelSpawner : ThingComp
+ {
+ private int ticksUntilSpawn;
+ private List fuelComps;
+
+ public CompProperties_MultiFuelSpawner Props => (CompProperties_MultiFuelSpawner)props;
+
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ base.PostSpawnSetup(respawningAfterLoad);
+ if (!respawningAfterLoad)
+ {
+ ResetCountdown();
+ }
+ fuelComps = parent.GetComps().OfType().ToList();
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref ticksUntilSpawn, "ticksUntilSpawn", 0);
+ }
+
+ public override void CompTick()
+ {
+ base.CompTick();
+
+ if (fuelComps.NullOrEmpty()) return;
+
+ bool allFuelsOk = fuelComps.All(c => c.HasFuel);
+
+ if (allFuelsOk && (parent.GetComp()?.PowerOn ?? true))
+ {
+ // CORRECTED LOGIC: Consume fuel every tick
+ foreach (var comp in fuelComps)
+ {
+ comp.Notify_UsedThisTick();
+ }
+
+ ticksUntilSpawn--;
+ if (ticksUntilSpawn <= 0)
+ {
+ TryDoSpawn();
+ ResetCountdown();
+ }
+ }
+ }
+
+ public void TryDoSpawn()
+ {
+ if (Props.products.NullOrEmpty()) return;
+
+ foreach (var product in Props.products)
+ {
+ Thing thing = ThingMaker.MakeThing(product.thingDef);
+ thing.stackCount = product.count;
+
+ if (Props.inheritFaction && thing.Faction != parent.Faction)
+ {
+ thing.SetFaction(parent.Faction);
+ }
+
+ if (GenPlace.TryPlaceThing(thing, parent.Position, parent.Map, ThingPlaceMode.Near, out Thing resultingThing))
+ {
+ if (Props.spawnForbidden)
+ {
+ resultingThing.SetForbidden(true);
+ }
+
+ if (Props.showMessageIfOwned && parent.Faction == Faction.OfPlayer)
+ {
+ Messages.Message("MessageCompSpawnerSpawnedItem".Translate(resultingThing.LabelCap), resultingThing, MessageTypeDefOf.PositiveEvent);
+ }
+ }
+ }
+ }
+
+ private void ResetCountdown()
+ {
+ ticksUntilSpawn = Props.spawnIntervalRange.RandomInRange;
+ }
+
+ public override string CompInspectStringExtra()
+ {
+ string text = base.CompInspectStringExtra();
+
+ if (!fuelComps.NullOrEmpty() && fuelComps.All(c => c.HasFuel))
+ {
+ if (!text.NullOrEmpty())
+ {
+ text += "\n";
+ }
+ string productsStr = Props.products.Select(p => (string)p.thingDef.LabelCap).ToCommaList();
+ text += "NextSpawnedItemIn".Translate(productsStr) + ": " + ticksUntilSpawn.ToStringTicksToPeriod();
+ }
+
+ return text;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/CompRefuelableNutrition_WithKey.cs b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/CompRefuelableNutrition_WithKey.cs
new file mode 100644
index 0000000..d299841
--- /dev/null
+++ b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/CompRefuelableNutrition_WithKey.cs
@@ -0,0 +1,70 @@
+using RimWorld;
+using Verse;
+using System.Reflection;
+using HarmonyLib;
+
+namespace ArachnaeSwarm
+{
+ public class CompProperties_RefuelableNutrition_WithKey : CompProperties_RefuelableNutrition
+ {
+ public string saveKeysPrefix;
+
+ public CompProperties_RefuelableNutrition_WithKey()
+ {
+ compClass = typeof(CompRefuelableNutrition_WithKey);
+ }
+ }
+
+ public class CompRefuelableNutrition_WithKey : CompRefuelableNutrition, IFuelSource
+ {
+ public new CompProperties_RefuelableNutrition_WithKey Props => (CompProperties_RefuelableNutrition_WithKey)props;
+
+ public override void PostExposeData()
+ {
+ string prefix = Props.saveKeysPrefix;
+ if (prefix.NullOrEmpty())
+ {
+ Log.ErrorOnce($"CompRefuelableNutrition_WithKey on {parent.def.defName} has a null or empty saveKeysPrefix. Defaulting to standard save.", GetHashCode());
+ base.PostExposeData();
+ return;
+ }
+
+ // --- Accessing private fields from CompRefuelable (base of CompRefuelableNutrition) ---
+ FieldInfo fuelField = AccessTools.Field(typeof(CompRefuelable), "fuel");
+ FieldInfo configuredTargetFuelLevelField = AccessTools.Field(typeof(CompRefuelable), "configuredTargetFuelLevel");
+ FieldInfo allowAutoRefuelField = AccessTools.Field(typeof(CompRefuelable), "allowAutoRefuel");
+
+ // Get current values
+ float currentFuel = (float)fuelField.GetValue(this);
+ float currentConfiguredLevel = (float)configuredTargetFuelLevelField.GetValue(this);
+ bool currentAllowAuto = (bool)allowAutoRefuelField.GetValue(this);
+
+ // Scribe with prefix
+ Scribe_Values.Look(ref currentFuel, prefix + "_fuel", 0f);
+ Scribe_Values.Look(ref currentConfiguredLevel, prefix + "_configuredTargetFuelLevel", -1f);
+ Scribe_Values.Look(ref currentAllowAuto, prefix + "_allowAutoRefuel", true);
+
+ // Set values back if loading
+ if (Scribe.mode == LoadSaveMode.LoadingVars)
+ {
+ fuelField.SetValue(this, currentFuel);
+ configuredTargetFuelLevelField.SetValue(this, currentConfiguredLevel);
+ allowAutoRefuelField.SetValue(this, currentAllowAuto);
+ }
+
+ // --- Accessing private fields from CompRefuelableNutrition ---
+ // (Assuming there are any. If not, this part is not needed)
+ // Example:
+ // FieldInfo someOtherField = AccessTools.Field(typeof(CompRefuelableNutrition), "someOtherPrivateField");
+ // ... and so on
+ }
+
+ public new void Notify_UsedThisTick()
+ {
+ if (Props.consumeFuelOnlyWhenUsed)
+ {
+ ConsumeFuel(Props.fuelConsumptionRate / 60000f);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/CompRefuelableWithKey.cs b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/CompRefuelableWithKey.cs
new file mode 100644
index 0000000..39e4107
--- /dev/null
+++ b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/CompRefuelableWithKey.cs
@@ -0,0 +1,28 @@
+using RimWorld;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class CompProperties_RefuelableWithKey : CompProperties_Refuelable
+ {
+ public string saveKeysPrefix;
+
+ public CompProperties_RefuelableWithKey()
+ {
+ compClass = typeof(CompRefuelableWithKey);
+ }
+ }
+
+ public class CompRefuelableWithKey : CompRefuelable, IFuelSource
+ {
+ public new CompProperties_RefuelableWithKey Props => (CompProperties_RefuelableWithKey)props;
+
+ public new void Notify_UsedThisTick()
+ {
+ if (Props.consumeFuelOnlyWhenUsed)
+ {
+ ConsumeFuel(Props.fuelConsumptionRate / 60000f);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/IFuelSource.cs b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/IFuelSource.cs
new file mode 100644
index 0000000..5aa4459
--- /dev/null
+++ b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/IFuelSource.cs
@@ -0,0 +1,8 @@
+namespace ArachnaeSwarm
+{
+ public interface IFuelSource
+ {
+ bool HasFuel { get; }
+ void Notify_UsedThisTick();
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/Patch_CompRefuelableWithKey.cs b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/Patch_CompRefuelableWithKey.cs
new file mode 100644
index 0000000..7cd7009
--- /dev/null
+++ b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/Patch_CompRefuelableWithKey.cs
@@ -0,0 +1,50 @@
+using System.Reflection;
+using HarmonyLib;
+using RimWorld;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ [HarmonyPatch(typeof(CompRefuelable), "PostExposeData")]
+ public static class Patch_CompRefuelableWithKey_PostExposeData
+ {
+ public static bool Prefix(CompRefuelable __instance)
+ {
+ if (!(__instance is CompRefuelableWithKey refuelableWithKey))
+ {
+ return true; // If it's not our class, run the original method
+ }
+
+ var props = (CompProperties_RefuelableWithKey)refuelableWithKey.Props;
+ string prefix = props.saveKeysPrefix;
+
+ if (prefix.NullOrEmpty())
+ {
+ Log.ErrorOnce($"CompRefuelableWithKey on {refuelableWithKey.parent.def.defName} has a null or empty saveKeysPrefix. Defaulting to standard save.", refuelableWithKey.GetHashCode());
+ return true;
+ }
+
+ // Use reflection to get/set private fields from the base class
+ FieldInfo fuelField = AccessTools.Field(typeof(CompRefuelable), "fuel");
+ FieldInfo configuredTargetFuelLevelField = AccessTools.Field(typeof(CompRefuelable), "configuredTargetFuelLevel");
+ FieldInfo allowAutoRefuelField = AccessTools.Field(typeof(CompRefuelable), "allowAutoRefuel");
+
+ float fuel = (float)fuelField.GetValue(refuelableWithKey);
+ float configuredTargetFuelLevel = (float)configuredTargetFuelLevelField.GetValue(refuelableWithKey);
+ bool allowAutoRefuel = (bool)allowAutoRefuelField.GetValue(refuelableWithKey);
+
+ Scribe_Values.Look(ref fuel, prefix + "_fuel", 0f);
+ Scribe_Values.Look(ref configuredTargetFuelLevel, prefix + "_configuredTargetFuelLevel", -1f);
+ Scribe_Values.Look(ref allowAutoRefuel, prefix + "_allowAutoRefuel", true);
+
+ if (Scribe.mode == LoadSaveMode.LoadingVars)
+ {
+ fuelField.SetValue(refuelableWithKey, fuel);
+ configuredTargetFuelLevelField.SetValue(refuelableWithKey, configuredTargetFuelLevel);
+ allowAutoRefuelField.SetValue(refuelableWithKey, allowAutoRefuel);
+ }
+
+ return false; // Prevent the original PostExposeData from running
+ }
+ }
+}
\ 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