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