diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index 6a540b9..cdff7de 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
new file mode 100644
index 0000000..c0ce780
--- /dev/null
+++ b/1.6/1.6/Defs/Thing_building/Building_SmartThermostat.xml
@@ -0,0 +1,54 @@
+
+
+
+
+ ARA_SmartThermostat
+
+ 一个先进的、不耗电的温控设备。它是一个可逆的热泵,能自动制热或制冷以维持设定的目标温度。必须像制冷器一样安装在墙上。
+ ArachnaeSwarm.Building_SmartThermostat
+
+ Things/Building/Misc/TempControl/Cooler
+ Graphic_Multi
+
+ Building
+ Impassable
+ true
+ 1
+ true
+
+ 2000
+ 100
+ 0.7
+
+ Rare
+
+ 120
+ 4
+
+ Medium
+
+ PlaceWorker_Cooler
+
+ true
+
+ true
+
+
+
+
+
+
+
+
+ 21
+
+
+
+
+ Temperature
+
+ AirConditioning
+
+
+
+
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index 38d1876..63ae437 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -210,6 +210,17 @@
+
+
+
+
+
+
+
+
+
+
+
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/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..e64123d
--- /dev/null
+++ b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/CompMultiFuelSpawner.cs
@@ -0,0 +1,128 @@
+using System.Collections.Generic;
+using System.Linq;
+using RimWorld;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class SpawnerProduct
+ {
+ public ThingDef thingDef;
+ public int count = 1;
+ }
+
+ // --- Properties Class ---
+ 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);
+ }
+ }
+
+ // --- Component Class ---
+ 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().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))
+ {
+ ticksUntilSpawn--;
+ if (ticksUntilSpawn <= 0)
+ {
+ foreach (var comp in fuelComps)
+ {
+ comp.Notify_UsedThisTick();
+ }
+
+ 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.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/CompRefuelableWithKey.cs b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/CompRefuelableWithKey.cs
new file mode 100644
index 0000000..cd1e432
--- /dev/null
+++ b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/CompRefuelableWithKey.cs
@@ -0,0 +1,24 @@
+using RimWorld;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ // 1. New Properties class that adds the save key
+ public class CompProperties_RefuelableWithKey : CompProperties_Refuelable
+ {
+ public string saveKeysPrefix;
+
+ public CompProperties_RefuelableWithKey()
+ {
+ compClass = typeof(CompRefuelableWithKey);
+ }
+ }
+
+ // 2. New Component class. It's empty for now.
+ // Its purpose is to be a safe target for our Harmony patch.
+ public class CompRefuelableWithKey : CompRefuelable
+ {
+ // We will override PostExposeData using a Harmony patch
+ // to avoid re-implementing the entire class.
+ }
+}
\ 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..311bcdf
--- /dev/null
+++ b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/Patch_CompRefuelableWithKey.cs
@@ -0,0 +1,59 @@
+using System.Reflection;
+using HarmonyLib;
+using RimWorld;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ // We patch the base class method
+ [HarmonyPatch(typeof(CompRefuelable), "PostExposeData")]
+ public static class Patch_CompRefuelableWithKey_PostExposeData
+ {
+ public static bool Prefix(CompRefuelable __instance)
+ {
+ // But we only act if the instance is our custom subclass
+ if (!(__instance is CompRefuelableWithKey refuelableWithKey))
+ {
+ // If it's not our class, run the original method
+ return true;
+ }
+
+ // Get the private fields from the base CompRefuelable class using reflection
+ FieldInfo fuelField = AccessTools.Field(typeof(CompRefuelable), "fuel");
+ FieldInfo configuredTargetFuelLevelField = AccessTools.Field(typeof(CompRefuelable), "configuredTargetFuelLevel");
+ FieldInfo allowAutoRefuelField = AccessTools.Field(typeof(CompRefuelable), "allowAutoRefuel");
+
+ // Get the props from our custom component
+ 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());
+ // If no prefix, let the original method run
+ return true;
+ }
+
+ // Get current values from the instance
+ float fuel = (float)fuelField.GetValue(refuelableWithKey);
+ float configuredTargetFuelLevel = (float)configuredTargetFuelLevelField.GetValue(refuelableWithKey);
+ bool allowAutoRefuel = (bool)allowAutoRefuelField.GetValue(refuelableWithKey);
+
+ // Scribe the values with our prefix
+ Scribe_Values.Look(ref fuel, prefix + "_fuel", 0f);
+ Scribe_Values.Look(ref configuredTargetFuelLevel, prefix + "_configuredTargetFuelLevel", -1f);
+ Scribe_Values.Look(ref allowAutoRefuel, prefix + "_allowAutoRefuel", true);
+
+ // Set the new values back to the instance
+ if (Scribe.mode == LoadSaveMode.LoadingVars)
+ {
+ fuelField.SetValue(refuelableWithKey, fuel);
+ configuredTargetFuelLevelField.SetValue(refuelableWithKey, configuredTargetFuelLevel);
+ allowAutoRefuelField.SetValue(refuelableWithKey, allowAutoRefuel);
+ }
+
+ // Prevent the original PostExposeData from running
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/Utility.cs b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/Utility.cs
new file mode 100644
index 0000000..ae08615
--- /dev/null
+++ b/Source/ArachnaeSwarm/WULA_MutiFuelSpawner/Utility.cs
@@ -0,0 +1,303 @@
+using System.Collections.Generic;
+using System.Linq;
+using RimWorld;
+using UnityEngine;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public static class Lifespan_Utility
+ {
+ public static IEnumerable deathThought = new List
+ {
+ ThoughtDefOf.KnowColonistDied,
+ ThoughtDefOf.PawnWithGoodOpinionDied,
+ ThoughtDefOf.WitnessedDeathFamily,
+ ThoughtDefOf.WitnessedDeathAlly,
+ };
+
+ public static bool IsDeathThought(this ThoughtDef tDef)
+ {
+ return (deathThought.Contains(tDef));
+ }
+
+ public static Thing ThingInCaseOfDeath(Pawn p)
+ {
+ Thing refThing;
+ if (p.Dead)
+ {
+ if (p.Corpse == null)
+ return null;
+ refThing = p.Corpse;
+ }
+ else
+ refThing = p;
+
+ return refThing;
+ }
+
+ public static void TrySpawnFilth(Thing refT, float filthRadius, ThingDef filthDef)
+ {
+ if (
+ refT.Map != null
+ && CellFinder.TryFindRandomReachableNearbyCell(
+ refT.Position,
+ refT.Map,
+ filthRadius,
+ TraverseParms.For(TraverseMode.NoPassClosedDoors),
+ x => x.Standable(refT.Map),
+ x => true,
+ out IntVec3 result
+ )
+ )
+ FilthMaker.TryMakeFilth(result, refT.Map, filthDef);
+ }
+
+ public static void ThrowCustomSmoke(ThingDef moteDef, Vector3 loc, Map map, float size)
+ {
+ if (loc.ShouldSpawnMotesAt(map) && !map.moteCounter.SaturatedLowPriority)
+ {
+ MoteThrown obj = (MoteThrown)ThingMaker.MakeThing(moteDef);
+ obj.Scale = Rand.Range(1.5f, 2.5f) * size;
+ obj.rotationRate = Rand.Range(-30f, 30f);
+ obj.exactPosition = loc;
+ obj.SetVelocity(Rand.Range(30, 40), Rand.Range(0.5f, 0.7f));
+ GenSpawn.Spawn(obj, loc.ToIntVec3(), map);
+ }
+ }
+
+ public static bool TryDoSpawn(
+ Pawn pawn,
+ ThingDef thingDef,
+ int thingNum,
+ int spawnMaxAdjacent,
+ bool tryToUnstack,
+ bool inheritFaction,
+ bool spawnForbidden,
+ bool showMessageIfOwned
+ )
+ {
+ Thing refThing = ThingInCaseOfDeath(pawn);
+ IntVec3 spawnPos;
+ Map map;
+ if (refThing == null)
+ return false;
+ else
+ {
+ map = refThing.Map;
+ spawnPos = refThing.Position;
+ }
+
+ if (spawnMaxAdjacent >= 0)
+ {
+ int num = 0;
+ for (int i = 0; i < 9; i++)
+ {
+ IntVec3 c = spawnPos + GenAdj.AdjacentCellsAndInside[i];
+ if (!c.InBounds(map))
+ continue;
+
+ List thingList = c.GetThingList(map);
+
+ for (int j = 0; j < thingList.Count; j++)
+ {
+ if (thingList[j].def == thingDef)
+ {
+ if (tryToUnstack)
+ continue;
+
+ num += thingList[j].stackCount;
+ if (num >= spawnMaxAdjacent)
+ return false;
+ }
+ }
+ }
+ }
+ if (TryFindSpawnCell(refThing, thingDef, thingNum, tryToUnstack, out IntVec3 result))
+ {
+ Thing thing = ThingMaker.MakeThing(thingDef);
+ thing.stackCount = thingNum;
+ if (thing == null)
+ Log.Error("Could not spawn anything for " + refThing);
+
+ if (inheritFaction && thing.Faction != refThing.Faction)
+ thing.SetFaction(refThing.Faction);
+
+ GenPlace.TryPlaceThing(
+ thing,
+ result,
+ map,
+ ThingPlaceMode.Direct,
+ out Thing lastResultingThing
+ );
+ if (spawnForbidden)
+ lastResultingThing.SetForbidden(value: true);
+
+ if (showMessageIfOwned && refThing.Faction == Faction.OfPlayer)
+ Messages.Message(
+ "MessageCompSpawnerSpawnedItem".Translate(thingDef.LabelCap),
+ thing,
+ MessageTypeDefOf.PositiveEvent
+ );
+
+ return true;
+ }
+ return false;
+ }
+
+ public static bool TryFindSpawnCell(
+ Thing parent,
+ ThingDef thingToSpawn,
+ int spawnCount,
+ bool tryToUnstack,
+ out IntVec3 result
+ )
+ {
+ foreach (IntVec3 item in GenAdj.CellsAdjacent8Way(parent).InRandomOrder())
+ {
+ if (item.Walkable(parent.Map))
+ {
+ Building edifice = item.GetEdifice(parent.Map);
+ if (edifice == null || !thingToSpawn.IsEdifice())
+ {
+ Building_Door building_Door = edifice as Building_Door;
+ if (
+ (building_Door == null || building_Door.FreePassage)
+ && (
+ parent.def.passability == Traversability.Impassable
+ || GenSight.LineOfSight(parent.Position, item, parent.Map)
+ )
+ )
+ {
+ bool flag = false;
+ List thingList = item.GetThingList(parent.Map);
+
+ for (int i = 0; i < thingList.Count; i++)
+ {
+ Thing thing = thingList[i];
+ if (
+ thing.def.category == ThingCategory.Item
+ && (
+ thing.def != thingToSpawn
+ || thing.stackCount > thingToSpawn.stackLimit - spawnCount
+ )
+ )
+ {
+ flag = true;
+ break;
+ }
+ }
+
+ if (!flag)
+ {
+ result = item;
+ return true;
+ }
+ }
+ }
+ }
+ }
+ result = IntVec3.Invalid;
+ return false;
+ }
+
+ public static bool RemoveBadMemoriesOfDeadPawn(Pawn deadPawn, bool myDebug = false)
+ {
+ bool didIt = false;
+ if (deadPawn == null)
+ {
+ Log.Warning("removingRelationAndThoughts, null pawn");
+ return didIt;
+ }
+ string deadName = deadPawn.LabelShortCap;
+ Log.Warning(">>>>>" + deadName + " dissappeared, the world must not know");
+
+ foreach (
+ Pawn p in Find.CurrentMap.mapPawns.AllPawnsSpawned.Where(pH =>
+ pH != deadPawn
+ && pH.needs.mood?.thoughts?.memories != null
+ && pH.needs.mood.thoughts.memories.AnyMemoryConcerns(deadPawn)
+ )
+ )
+ {
+ Log.Warning(p.LabelShortCap + " has memories of " + deadName);
+
+ Log.Warning(
+ "pre removal mem count: " + p.needs.mood.thoughts.memories.Memories.Count
+ );
+ p.needs.mood.thoughts.memories.Memories.RemoveAll(TM =>
+ TM.otherPawn == deadPawn && TM.MoodOffset() <= 0f
+ );
+ Log.Warning(
+ "post removal mem count: " + p.needs.mood.thoughts.memories.Memories.Count
+ );
+ }
+
+ return didIt;
+ }
+
+ public static void removingRelationAndThoughts(Pawn deadPawn, bool myDebug = false)
+ {
+ if (deadPawn == null)
+ {
+ Log.Warning("removingRelationAndThoughts, null pawn");
+ return;
+ }
+ string deadName = deadPawn.LabelShortCap;
+
+ Log.Warning(">>>>>" + deadName + " dissappeared, the world must not know");
+ foreach (
+ Pawn p in Find.CurrentMap.mapPawns.AllPawnsSpawned.Where(pH =>
+ //!pH.AnimalOrWildMan() &&
+ pH != deadPawn
+ && !pH.GetRelations(deadPawn).EnumerableNullOrEmpty()
+ )
+ )
+ {
+ string pName = p.LabelShortCap;
+ Log.Warning("Considering :" + pName);
+ IEnumerable relationT = PawnRelationUtility.GetRelations(
+ deadPawn,
+ p
+ );
+ if (relationT.EnumerableNullOrEmpty())
+ continue;
+
+ List pThoughts = new List();
+ if (p.needs.mood == null || p.needs.mood.thoughts == null)
+ continue;
+ //p.needs.mood.thoughts.memories.AnyMemoryConcerns()
+ if (pThoughts.NullOrEmpty())
+ return;
+ int tNum = 0;
+ foreach (Thought thought in pThoughts)
+ {
+ Log.Warning(pName + "'s Thought n" + tNum);
+ tNum++;
+
+ if (thought.pawn == null || deadPawn == null)
+ continue;
+
+ if (IsDeathThought(thought.def))
+ {
+ if (
+ !(
+ thought is Thought_MemorySocial TMS
+ && TMS.otherPawn != null
+ && TMS.otherPawn == deadPawn
+ )
+ )
+ continue;
+
+ deadPawn.needs.mood.thoughts.memories.RemoveMemory(TMS);
+
+ Log.Warning(
+ "removed " + pName + "'s thought " + thought.def.defName
+ );
+ }
+ }
+ }
+ Log.Warning("<<<<<" + deadName);
+ }
+ }
+}
\ No newline at end of file