This commit is contained in:
2025-09-16 12:20:36 +08:00
parent 040bb17f94
commit c336450e87
10 changed files with 986 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<ThingDef ParentName="BuildingBase">
<defName>ARA_SmartThermostat</defName>
<label>智能恒温器</label>
<description>一个先进的、不耗电的温控设备。它是一个可逆的热泵,能自动制热或制冷以维持设定的目标温度。必须像制冷器一样安装在墙上。</description>
<thingClass>ArachnaeSwarm.Building_SmartThermostat</thingClass> <!-- 使用我们新的建筑类 -->
<graphicData>
<texPath>Things/Building/Misc/TempControl/Cooler</texPath> <!-- 暂时使用制冷器的贴图 -->
<graphicClass>Graphic_Multi</graphicClass>
</graphicData>
<altitudeLayer>Building</altitudeLayer>
<passability>Impassable</passability>
<blockWind>true</blockWind>
<fillPercent>1</fillPercent>
<coversFloor>true</coversFloor>
<statBases>
<WorkToBuild>2000</WorkToBuild>
<MaxHitPoints>100</MaxHitPoints>
<Flammability>0.7</Flammability>
</statBases>
<tickerType>Rare</tickerType>
<costList>
<Steel>120</Steel>
<ComponentIndustrial>4</ComponentIndustrial>
</costList>
<terrainAffordanceNeeded>Medium</terrainAffordanceNeeded>
<placeWorkers>
<li>PlaceWorker_Cooler</li> <!-- 使用制冷器的放置规则,确保它被正确地安装在墙上 -->
</placeWorkers>
<drawPlaceWorkersWhileSelected>true</drawPlaceWorkersWhileSelected>
<building>
<canPlaceOverWall>true</canPlaceOverWall>
</building>
<comps>
<!-- 提供开关按钮 -->
<li Class="CompProperties_Flickable"/>
<!-- 提供温度控制UI和逻辑 -->
<li Class="CompProperties_TempControl">
<!-- 这是设备的热交换功率。数值越大,制冷/制热速度越快。-->
<energyPerSecond>21</energyPerSecond>
</li>
<li Class="CompProperties_Breakdownable"/>
</comps>
<designationCategory>Temperature</designationCategory>
<researchPrerequisites>
<li>AirConditioning</li>
</researchPrerequisites>
</ThingDef>
</Defs>

View File

@@ -210,6 +210,17 @@
<Compile Include="HediffComp_TerrainBasedSeverity.cs" />
<Compile Include="CompAbilityEffect_PsychicBrainburn.cs" />
<Compile Include="CompProperties_PsychicBrainburn.cs" />
<Compile Include="Building_SmartThermostat.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="WULA_HediffDamgeShield\DRMDamageShield.cs" />
<Compile Include="WULA_HediffDamgeShield\Hediff_DamageShield.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="WULA_MutiFuelSpawner\CompMultiFuelSpawner.cs" />
<Compile Include="WULA_MutiFuelSpawner\CompRefuelableWithKey.cs" />
<Compile Include="WULA_MutiFuelSpawner\Patch_CompRefuelableWithKey.cs" />
<Compile Include="WULA_MutiFuelSpawner\Utility.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 自定义清理任务删除obj文件夹中的临时文件 -->

View File

@@ -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<CompFlickable>();
}
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;
}
}
}

View File

@@ -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<Hediff_DamageShield>();
return hediff?.ShieldCharges ?? 0;
}
set
{
Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff<Hediff_DamageShield>();
if (hediff != null)
{
hediff.ShieldCharges = (int)value;
}
}
}
public float MaxEnergy
{
get
{
Hediff_DamageShield hediff = PawnOwner?.health?.hediffSet.GetFirstHediff<Hediff_DamageShield>();
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<Hediff_DamageShield>();
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<Hediff_DamageShield>();
if (hediff != null)
{
hediff.ShieldCharges = (int)hediff.def.initialSeverity; // 重置时恢复到初始层数
}
}
public void KeepDisplaying()
{
lastKeepDisplayTick = Find.TickManager.TicksGame;
}
}
}

View File

@@ -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<DRMDamageShield>();
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<DRMDamageShield>();
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<DRMDamageShield>(); // 每次 Tick 获取,确保是最新的
if (comp != null && comp.IsActive)
{
comp.Energy = ShieldCharges;
comp.MaxEnergy = (int)def.maxSeverity;
}
}
}
}

View File

@@ -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<SpawnerProduct> 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<CompRefuelableWithKey> fuelComps;
public CompProperties_MultiFuelSpawner Props => (CompProperties_MultiFuelSpawner)props;
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
if (!respawningAfterLoad)
{
ResetCountdown();
}
fuelComps = parent.GetComps<CompRefuelableWithKey>().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<CompPowerTrader>()?.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;
}
}
}

View File

@@ -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.
}
}

View File

@@ -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;
}
}
}

View File

@@ -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<ThoughtDef> deathThought = new List<ThoughtDef>
{
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<Thing> 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<Thing> 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<PawnRelationDef> relationT = PawnRelationUtility.GetRelations(
deadPawn,
p
);
if (relationT.EnumerableNullOrEmpty())
continue;
List<Thought> pThoughts = new List<Thought>();
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);
}
}
}