Files
ArachnaeSwarm/Source/ArachnaeSwarm/Hediffs/ARA_Spawner/HediffComp_Spawner.cs
ProjectKoi-Kalo\Kalo f9624818f5 fix: 修复多个组件的空引用、调试日志和编码问题
### HediffComp_GestaltNode
- 添加 pawn 空引用检查,防止 NullReferenceException
- 将 Log.Message 改为 ArachnaeLog.Debug 统一日志管理
- 新增 Notify_PawnDied 方法处理 Pawn 死亡时的过渡状态清理
- 修复 UpdateTransitionState 中重复声明 pawn 变量的问题

### CompAbilityEffect_LaunchMultiProjectile
- 添加目标有效性检查,处理目标死亡或消失的情况
- 实现动态目标追踪,更新目标位置
- 移除未使用的 parametersInitialized 字段
- 新增 ForceReinitialize 方法支持状态变化时重新初始化

### CompHediffGiver
- 改进异常处理,记录警告日志而非静默吞掉异常
- 重构 IsSymmetricalPart 方法,使用翻译键和 BodyPartTagDef 支持本地化

### HediffComp_Spawner
- 将 DebugSettings.debugLogging 改为 Prefs.DevMode
- 修复所有 UTF-8 编码乱码注释(约30处)

### Comp_PawnResearchBlueprintReader
- 修复灵能科研点消耗时机,确保先检查→再消耗→最后添加进度
- 提高研究进度添加的原子性
2026-02-15 00:24:08 +08:00

993 lines
37 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using RimWorld.Planet;
using Verse;
namespace ArachnaeSwarm
{
public class HediffComp_Spawner : HediffComp
{
public HediffCompProperties_Spawner Props
{
get
{
return (HediffCompProperties_Spawner)this.props;
}
}
public override void CompExposeData()
{
Scribe_Values.Look<int>(ref this.ticksUntilSpawn, "ticksUntilSpawn", 0, false);
Scribe_Values.Look<int>(ref this.initialTicksUntilSpawn, "initialTicksUntilSpawn", 0, false);
Scribe_Values.Look<float>(ref this.calculatedMinDaysB4Next, "calculatedMinDaysB4Next", 0f, false);
Scribe_Values.Look<float>(ref this.calculatedMaxDaysB4Next, "calculatedMaxDaysB4Next", 0f, false);
Scribe_Values.Look<int>(ref this.calculatedQuantity, "calculatedQuantity", 0, false);
Scribe_Values.Look<int>(ref this.graceTicks, "graceTicks", 0, false);
}
public override void CompPostMake()
{
this.myDebug = this.Props.debug;
Warn(string.Concat(new string[]
{
">>> ",
this.parent.pawn.Label,
" - ",
this.parent.def.defName,
" - CompPostMake start"
}), this.myDebug);
TraceProps();
CheckProps();
CalculateValues();
CheckCalculatedValues();
TraceCalculatedValues();
if (this.initialTicksUntilSpawn == 0)
{
Warn("Reseting countdown bc initialTicksUntilSpawn == 0 (comppostmake)", this.myDebug);
ResetCountdown();
}
}
public override void CompPostTick(ref float severityAdjustment)
{
this.pawn = this.parent.pawn;
if (!OkPawn(this.pawn))
{
return;
}
if (this.blockSpawn)
{
return;
}
if (this.graceTicks > 0)
{
this.graceTicks--;
return;
}
if (this.Props.hungerRelative && IsHungry(this.pawn, this.myDebug))
{
int num = (int)(this.RandomGraceDays() * 60000f);
this.hungerReset++;
this.graceTicks = num;
return;
}
if (this.Props.healthRelative && IsInjured(this.pawn, this.myDebug))
{
int num2 = (int)(this.RandomGraceDays() * 60000f);
this.healthReset++;
this.graceTicks = num2;
return;
}
this.hungerReset = (this.healthReset = 0);
if (this.CheckShouldSpawn())
{
Warn("Reseting countdown bc spawned thing", this.myDebug);
CalculateValues();
CheckCalculatedValues();
ResetCountdown();
if (Rand.Chance(this.Props.randomGrace))
{
int num3 = (int)(this.RandomGraceDays() * 60000f);
this.graceTicks = num3;
}
}
}
private void TraceProps()
{
Warn(string.Concat(new string[]
{
"Props => minDaysB4Next: ",
this.Props.minDaysB4Next.ToString(),
"; maxDaysB4Next: ",
this.Props.maxDaysB4Next.ToString(),
"; randomGrace: ",
this.Props.randomGrace.ToString(),
"; graceDays: ",
this.Props.graceDays.ToString(),
"; hungerRelative: ",
this.Props.hungerRelative.ToString(),
"; healthRelative: ",
this.Props.healthRelative.ToString(),
"; destroyRandomBodyPart: ",
this.Props.destroyRandomBodyPart.ToString(),
"; "
}), this.myDebug);
if (this.Props.animalThing)
{
Warn(string.Concat(new string[]
{
"animalThing: ",
this.Props.animalThing.ToString(),
"; animalName: ",
this.Props.animalToSpawn.defName,
"; factionOfPlayerAnimal: ",
this.Props.factionOfPlayerAnimal.ToString(),
"; "
}), this.myDebug);
}
if (this.Props.ageWeightedQuantity)
{
Warn(string.Concat(new string[]
{
"ageWeightedQuantity:",
this.Props.ageWeightedQuantity.ToString(),
"; olderBiggerQuantity:",
this.Props.olderBiggerQuantity.ToString(),
"; ",
this.myDebug.ToString()
}), false);
if (this.Props.exponentialQuantity)
{
Warn(string.Concat(new string[]
{
"exponentialQuantity:",
this.Props.exponentialQuantity.ToString(),
"; exponentialRatioLimit:",
this.Props.exponentialRatioLimit.ToString(),
"; "
}), this.myDebug);
}
}
Warn(string.Concat(new string[]
{
"ageWeightedPeriod:",
this.Props.ageWeightedPeriod.ToString(),
"; olderSmallerPeriod:",
this.Props.olderSmallerPeriod.ToString(),
"; ",
this.myDebug.ToString()
}), false);
}
private void CalculateValues()
{
float num = GetPawnAgeOverlifeExpectancyRatio(this.parent.pawn, this.myDebug);
num = ((num > 1f) ? 1f : num);
this.calculatedMinDaysB4Next = this.Props.minDaysB4Next;
this.calculatedMaxDaysB4Next = this.Props.maxDaysB4Next;
if (this.Props.ageWeightedPeriod)
{
float num2 = this.Props.olderSmallerPeriod ? (-num) : num;
this.calculatedMinDaysB4Next = this.Props.minDaysB4Next * (1f + num2);
this.calculatedMaxDaysB4Next = this.Props.maxDaysB4Next * (1f + num2);
Warn(string.Concat(new string[]
{
" ageWeightedPeriod: ",
this.Props.ageWeightedPeriod.ToString(),
" ageRatio: ",
num.ToString(),
" minDaysB4Next: ",
this.Props.minDaysB4Next.ToString(),
" maxDaysB4Next: ",
this.Props.maxDaysB4Next.ToString(),
" daysAgeRatio: ",
num2.ToString(),
" calculatedMinDaysB4Next: ",
this.calculatedMinDaysB4Next.ToString(),
"; calculatedMaxDaysB4Next: ",
this.calculatedMaxDaysB4Next.ToString(),
"; "
}), this.myDebug);
}
this.calculatedQuantity = this.Props.spawnCount;
if (this.Props.ageWeightedQuantity)
{
float num3 = this.Props.olderBiggerQuantity ? num : (-num);
Warn("quantityAgeRatio: " + num3.ToString(), this.myDebug);
this.calculatedQuantity = (int)Math.Round((double)this.Props.spawnCount * (double)(1f + num3));
if (this.Props.exponentialQuantity)
{
num3 = 1f - num;
if (num3 == 0f)
{
Warn(">ERROR< quantityAgeRatio is f* up : " + num3.ToString(), this.myDebug);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
float num4 = this.Props.olderBiggerQuantity ? (1f / num3) : (num3 * num3);
bool flag = false;
bool flag2 = false;
if (num4 > (float)this.Props.exponentialRatioLimit)
{
num4 = (float)this.Props.exponentialRatioLimit;
flag = true;
}
this.calculatedQuantity = (int)Math.Round((double)this.Props.spawnCount * (double)num4);
if (this.calculatedQuantity < 1)
{
this.calculatedQuantity = 1;
flag2 = true;
}
Warn(string.Concat(new string[]
{
" exponentialQuantity: ",
this.Props.exponentialQuantity.ToString(),
"; expoFactor: ",
num4.ToString(),
"; gotLimited: ",
flag.ToString(),
"; gotAugmented: ",
flag2.ToString()
}), this.myDebug);
}
Warn("; Props.spawnCount: " + this.Props.spawnCount.ToString() + "; calculatedQuantity: " + this.calculatedQuantity.ToString(), this.myDebug);
}
}
private void CheckCalculatedValues()
{
if (this.calculatedQuantity > this.errorSpawnCount)
{
Warn(string.Concat(new string[]
{
">ERROR< calculatedQuantity is too high: ",
this.calculatedQuantity.ToString(),
"(>",
this.errorSpawnCount.ToString(),
"), check and adjust your hediff props"
}), this.myDebug);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.calculatedMinDaysB4Next < this.errorMinDaysB4Next)
{
this.calculatedMinDaysB4Next = this.errorMinDaysB4Next;
}
if (this.calculatedMaxDaysB4Next < this.errorMinDaysB4Next)
{
this.calculatedMaxDaysB4Next = this.errorMinDaysB4Next;
}
}
private void TraceCalculatedValues()
{
Warn("calculatedMinDaysB4Next:" + this.calculatedMinDaysB4Next.ToString(), this.myDebug);
Warn("calculatedMaxDaysB4Next:" + this.calculatedMaxDaysB4Next.ToString(), this.myDebug);
Warn("calculatedQuantity:" + this.calculatedQuantity.ToString(), this.myDebug);
}
private void CheckProps()
{
if (this.Props.animalThing && this.Props.animalToSpawn == null)
{
Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with animalflag but without animalToSpawn", true);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.minDaysB4Next <= 0f)
{
Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with null/negative minDaysB4Next", true);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.maxDaysB4Next <= 0f)
{
Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with null/negative maxDaysB4Next", true);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.maxDaysB4Next < this.Props.minDaysB4Next)
{
Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with maxDaysB4Next < minDaysB4Next", true);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.spawnCount <= 0)
{
Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with null/negative spawnCount", true);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (!this.Props.animalThing && this.Props.thingToSpawn == null)
{
Warn(this.parent.pawn.Label + " has a hediffcomp_spawner without thingToSpawn", true);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.ageWeightedQuantity && this.Props.exponentialQuantity && this.Props.exponentialRatioLimit > this.errorExponentialLimit)
{
Warn(string.Concat(new string[]
{
this.parent.pawn.Label,
" has a hediffcomp_spawner with exponentialRatioLimit>",
this.errorExponentialLimit.ToString(),
" this is not allowed. It will be set to ",
this.errorExponentialLimit.ToString()
}), true);
this.Props.exponentialRatioLimit = this.errorExponentialLimit;
}
}
private bool CheckShouldSpawn()
{
this.ticksUntilSpawn--;
if (this.ticksUntilSpawn <= 0)
{
if (this.TryDoSpawn())
{
return true;
}
Warn("Did not spawn, reseting countdown", this.myDebug);
ResetCountdown();
}
return false;
}
public bool TryDoSpawn()
{
Pawn pawn = this.parent.pawn;
if (this.Props.animalThing)
{
// 动物生成逻辑保持不变
if (this.Props.spawnMaxAdjacent > 0 && pawn.Map.mapPawns.AllPawns.Where(delegate(Pawn mP)
{
ThingDef defToCompare = this.Props.animalThing ? this.Props.animalToSpawn?.race : this.Props.thingToSpawn;
if (defToCompare?.race == null)
{
return false;
}
return mP.def == defToCompare && mP.Position.InHorDistOf(pawn.Position, (float)this.Props.spawnMaxAdjacent);
}).Count<Pawn>() >= this.Props.spawnMaxAdjacent)
{
return false;
}
if (this.Props.animalToSpawn == null)
{
return false;
}
Faction faction = this.Props.factionOfPlayerAnimal ? Faction.OfPlayer : null;
int i = 0;
while (i < this.calculatedQuantity)
{
IntVec3 intVec;
if (!this.TryFindSpawnCell(out intVec))
{
return false;
}
Pawn pawn2 = PawnGenerator.GeneratePawn(this.Props.animalToSpawn, faction);
if (pawn2 == null)
{
return false;
}
GenSpawn.Spawn(pawn2, intVec, pawn.Map, WipeMode.Vanish);
pawn2.SetFaction(faction, null);
FilthMaker.TryMakeFilth(intVec, pawn.Map, ThingDefOf.Filth_AmnioticFluid, pawn.LabelIndefinite(), 5, FilthSourceFlags.None);
if (!this.Props.spawnForbidden)
{
pawn2.playerSettings.Master = pawn;
pawn2.training.Train(TrainableDefOf.Obedience, pawn, true);
}
i++;
continue;
}
if (PawnUtility.ShouldSendNotificationAbout(pawn))
{
Messages.Message(this.Props.spawnVerb.Translate(pawn.Named("PAWN")), pawn, MessageTypeDefOf.PositiveEvent, true);
}
return true;
}
else
{
// 重新设计物品生成逻辑:按优先级顺序尝试
bool success = TrySpawnItemWithPriority(pawn);
// === 新增:如果配置了销毁随机部位,在成功生成物品后执行 ===
if (success && this.Props.destroyRandomBodyPart)
{
DestroyRandomBodyPart(pawn);
}
return success;
}
}
// 新增:销毁随机身体部位(参考 CompAbilityEffect_DestroyOwnBodyPart
private void DestroyRandomBodyPart(Pawn pawn)
{
try
{
if (pawn == null || pawn.Dead)
{
Warn($"Cannot destroy body part for null or dead pawn", this.myDebug);
return;
}
// 获取所有可以销毁的身体部位
List<BodyPartRecord> possibleParts = GetDestroyableBodyParts(pawn);
if (possibleParts.Count == 0)
{
Warn($"No destroyable body parts found for {pawn.LabelShort}", this.myDebug);
return;
}
// 随机选择一个部位
BodyPartRecord partToDestroy = possibleParts.RandomElement();
if (partToDestroy == null)
{
Warn($"Selected null body part for {pawn.LabelShort}", this.myDebug);
return;
}
// 记录部位名称
string partName = partToDestroy.def?.label ?? "未知部位";
// 添加缺失部位hediff
pawn.health.AddHediff(HediffDefOf.MissingBodyPart, partToDestroy);
Warn($"Destroyed {partName} on {pawn.LabelShort}", this.myDebug);
}
catch (Exception ex)
{
Log.Error($"Error destroying random body part for {pawn?.LabelShort}: {ex.Message}");
}
}
// 新增:获取可以销毁的身体部位列表
private List<BodyPartRecord> GetDestroyableBodyParts(Pawn pawn)
{
List<BodyPartRecord> destroyableParts = new List<BodyPartRecord>();
if (pawn?.health?.hediffSet == null)
return destroyableParts;
// 获取所有身体部位
List<BodyPartRecord> allParts = pawn.RaceProps.body.AllParts;
foreach (BodyPartRecord part in allParts)
{
// 排除核心部位(避免死亡)
if (IsCriticalBodyPart(part))
continue;
// 排除已经缺失的部位
if (pawn.health.hediffSet.PartIsMissing(part))
continue;
// 排除已经有严重伤害的部位(可选)
if (pawn.health.hediffSet.PartOrAnyAncestorHasDirectlyAddedParts(part))
continue;
if (PartOrAnyDescendantHasDirectlyAddedParts(pawn, part))
continue;
destroyableParts.Add(part);
}
return destroyableParts;
}
// 新增:检查是否是关键身体部位
private bool IsCriticalBodyPart(BodyPartRecord part)
{
if (part == null)
return false;
// 根据标签判断是否为关键部位
if (part.def.tags != null)
{
// 这些标签通常表示关键部位
if (part.def.tags.Contains(BodyPartTagDefOf.ConsciousnessSource) || // 意识源(大脑)
part.def.tags.Contains(BodyPartTagDefOf.BloodPumpingSource) || // 血液泵源(心脏)
part.def.tags.Contains(BodyPartTagDefOf.BreathingSource) || // 呼吸源(肺)
part.def.tags.Contains(BodyPartTagDefOf.MetabolismSource) // 代谢源(肝脏)
)
{
return true;
}
}
// 根据深度判断(深度较深的通常是内部器官)
if (part.depth == BodyPartDepth.Inside && part.parent == null)
return true;
return false;
}
// 新增:按优先级顺序尝试生成物品
private bool TrySpawnItemWithPriority(Pawn pawn)
{
Thing thing = ThingMaker.MakeThing(this.Props.thingToSpawn, null);
if (thing == null)
{
Warn("Failed to create thing: " + this.Props.thingToSpawn?.defName, this.myDebug);
return false;
}
thing.stackCount = this.calculatedQuantity;
ThingDef originalThingDef = thing.def;
int originalStackCount = thing.stackCount;
bool success = false;
string spawnMethod = "";
Thing spawnedThing = null;
int addedInventoryCount = 0;
if (!success)
{
success = TrySpawnAtNearbyEmptyCell(pawn, thing, ref spawnMethod, out spawnedThing);
}
if (!success)
{
success = TrySpawnAtPawnPosition(pawn, thing, ref spawnMethod, out spawnedThing);
}
if (!success)
{
success = TrySpawnInInventory(pawn, thing, ref spawnMethod, out spawnedThing, out addedInventoryCount);
}
if (success)
{
bool verified = VerifySpawnSuccess(
pawn,
originalThingDef,
originalStackCount,
spawnMethod,
spawnedThing,
addedInventoryCount
);
if (!verified && spawnMethod != "inventory")
{
Warn($"Spawn verification failed for {spawnMethod}, attempting inventory fallback", this.myDebug);
Thing fallbackThing = ThingMaker.MakeThing(originalThingDef, null);
fallbackThing.stackCount = originalStackCount;
success = TrySpawnInInventory(pawn, fallbackThing, ref spawnMethod, out spawnedThing, out addedInventoryCount);
if (success)
{
spawnMethod = "inventory_fallback";
verified = VerifySpawnSuccess(
pawn,
originalThingDef,
originalStackCount,
spawnMethod,
spawnedThing,
addedInventoryCount
);
if (!verified)
{
Warn("Inventory fallback also failed verification", this.myDebug);
success = false;
}
}
}
if (success && verified)
{
if (PawnUtility.ShouldSendNotificationAbout(pawn))
{
Thing messageThing = spawnedThing ?? thing;
bool spawnedInInventory = spawnMethod == "inventory" || spawnMethod == "inventory_fallback";
Messages.Message(
this.Props.spawnVerb.Translate(pawn.Named("PAWN"), messageThing.Named("THING")),
spawnedInInventory ? pawn : messageThing,
MessageTypeDefOf.PositiveEvent,
true
);
}
Warn($"Successfully spawned {originalStackCount}x {originalThingDef.defName} via {spawnMethod}", this.myDebug);
return true;
}
}
Warn($"Failed to spawn {originalStackCount}x {originalThingDef.defName} after all attempts", this.myDebug);
return false;
}
private bool TrySpawnAtNearbyEmptyCell(Pawn pawn, Thing thing, ref string spawnMethod, out Thing spawnedThing)
{
spawnedThing = null;
IntVec3 spawnCell;
if (TryFindSpawnCell(out spawnCell))
{
if (this.Props.spawnForbidden)
{
thing.SetForbidden(true, true);
}
Thing resultingThing;
if (GenPlace.TryPlaceThing(thing, spawnCell, pawn.Map, ThingPlaceMode.Direct, out resultingThing, null, null, default(Rot4)))
{
spawnMethod = "nearby_cell";
spawnedThing = resultingThing;
return true;
}
}
return false;
}
private bool TrySpawnAtPawnPosition(Pawn pawn, Thing thing, ref string spawnMethod, out Thing spawnedThing)
{
spawnedThing = null;
IntVec3 pawnPosition = pawn.Position;
if (pawnPosition.IsValid && pawnPosition.Walkable(pawn.Map))
{
if (this.Props.spawnForbidden)
{
thing.SetForbidden(true, true);
}
Thing resultingThing;
if (GenPlace.TryPlaceThing(thing, pawnPosition, pawn.Map, ThingPlaceMode.Direct, out resultingThing, null, null, default(Rot4)))
{
spawnMethod = "pawn_position";
spawnedThing = resultingThing;
return true;
}
}
return false;
}
private bool TrySpawnInInventory(Pawn pawn, Thing thing, ref string spawnMethod, out Thing spawnedThing, out int addedCount)
{
spawnedThing = null;
addedCount = 0;
if (pawn.inventory == null)
{
Warn($"Pawn {pawn.Label} does not have an inventory", this.myDebug);
return false;
}
int before = CountDefInInventory(pawn, thing.def);
if (pawn.inventory.innerContainer.TryAdd(thing))
{
int after = CountDefInInventory(pawn, thing.def);
addedCount = Math.Max(0, after - before);
spawnMethod = "inventory";
spawnedThing = pawn.inventory.innerContainer.FirstOrDefault(t => t.def == thing.def);
return true;
}
return false;
}
private bool VerifySpawnSuccess(Pawn pawn, ThingDef thingDef, int expectedCount, string spawnMethod, Thing spawnedThing, int addedInventoryCount)
{
bool verificationSuccess = false;
switch (spawnMethod)
{
case "nearby_cell":
case "pawn_position":
verificationSuccess =
spawnedThing != null &&
spawnedThing.def == thingDef &&
spawnedThing.Map == pawn.Map &&
spawnedThing.Position.InHorDistOf(pawn.Position, 2f);
break;
case "inventory":
case "inventory_fallback":
verificationSuccess = addedInventoryCount >= expectedCount;
break;
}
if (!verificationSuccess)
{
Warn($"Spawn verification failed for {thingDef.defName} via {spawnMethod}", this.myDebug);
}
else
{
Warn($"Spawn verification successful for {thingDef.defName} via {spawnMethod}", this.myDebug);
}
return verificationSuccess;
}
private int CountDefInInventory(Pawn pawn, ThingDef thingDef)
{
if (pawn?.inventory?.innerContainer == null || thingDef == null)
{
return 0;
}
return pawn.inventory.innerContainer.Where(t => t.def == thingDef).Sum(t => t.stackCount);
}
private bool TryFindSpawnCell(out IntVec3 result)
{
result = IntVec3.Invalid;
bool result2;
if (this.pawn == null)
{
result2 = false;
}
else
{
Map map = this.pawn.Map;
if (map == null)
{
result2 = false;
}
else
{
// 修改这里将半径从3减少到2让生成位置更靠近Pawn
int searchRadius = 2;
// 首先尝试在Pawn的相邻单元格生成半径为1
result = CellFinder.RandomClosewalkCellNear(this.pawn.Position, map, 1, null);
// 如果相邻单元格找不到合适位置再尝试稍远一点半径为2
if (!result.IsValid)
{
result = CellFinder.RandomClosewalkCellNear(this.pawn.Position, map, searchRadius, null);
}
// 如果还是找不到尝试Pawn当前位置作为最后手段
if (!result.IsValid && this.pawn.Position.IsValid && this.pawn.Position.Walkable(map))
{
result = this.pawn.Position;
}
result2 = result.IsValid;
}
}
return result2;
}
private void ResetCountdown()
{
this.ticksUntilSpawn = (int)(this.RandomDays2wait() * 60000f);
this.initialTicksUntilSpawn = this.ticksUntilSpawn;
}
private float RandomDays2wait()
{
return Rand.Range(this.calculatedMinDaysB4Next, this.calculatedMaxDaysB4Next);
}
private float RandomGraceDays()
{
return Rand.Range(this.Props.graceDays / 2f, this.Props.graceDays);
}
public override string CompTipStringExtra
{
get
{
if (!this.myDebug)
{
return null;
}
string text = "ticksUntilSpawn: " + this.ticksUntilSpawn.ToString() + "\n";
string text2 = text;
text = string.Concat(new string[]
{
text2,
"initialTicksUntilSpawn: ",
this.initialTicksUntilSpawn.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"graceTicks: ",
this.graceTicks.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"hunger resets: ",
this.hungerReset.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"health resets: ",
this.healthReset.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"calculatedMinDaysB4Next: ",
this.calculatedMinDaysB4Next.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"calculatedMaxDaysB4Next: ",
this.calculatedMaxDaysB4Next.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"calculatedQuantity: ",
this.calculatedQuantity.ToString(),
"\n"
});
return text + "blockSpawn: " + this.blockSpawn.ToString();
}
}
// === 整合的 Tools 方法 ===
private void DestroyParentHediff(Hediff parentHediff, bool debug = false)
{
if (parentHediff.pawn != null && parentHediff.def.defName != null && debug)
{
Warn(parentHediff.pawn.Label + "'s Hediff: " + parentHediff.def.defName + " says goodbye.", debug);
}
parentHediff.Severity = 0f;
}
private float GetPawnAgeOverlifeExpectancyRatio(Pawn pawn, bool debug = false)
{
float result = 1f;
if (pawn == null)
{
if (debug)
{
Warn("GetPawnAgeOverlifeExpectancyRatio pawn NOT OK", debug);
}
return result;
}
result = pawn.ageTracker.AgeBiologicalYearsFloat / pawn.RaceProps.lifeExpectancy;
if (debug)
{
Warn(string.Concat(new string[]
{
pawn.Label,
" Age: ",
pawn.ageTracker.AgeBiologicalYearsFloat.ToString(),
"; lifeExpectancy: ",
pawn.RaceProps.lifeExpectancy.ToString(),
"; ratio:",
result.ToString()
}), debug);
}
return result;
}
private bool OkPawn(Pawn pawn)
{
return pawn != null && pawn.Map != null;
}
private bool IsHungry(Pawn pawn, bool debug = false)
{
if (pawn == null)
{
if (debug)
{
Warn("pawn is null - IsHungry", debug);
}
return false;
}
bool starving = pawn.needs.food != null && pawn.needs.food.CurCategory == HungerCategory.Starving;
if (debug && starving)
{
Warn(pawn.Label + " is hungry", debug);
}
return starving;
}
private bool IsInjured(Pawn pawn, bool debug = false)
{
if (pawn == null)
{
if (debug)
{
Warn("pawn is null - IsInjured", debug);
}
return false;
}
float injurySeverity = 0f;
List<Hediff> hediffs = pawn.health.hediffSet.hediffs;
for (int i = 0; i < hediffs.Count; i++)
{
if (hediffs[i] is Hediff_Injury && !hediffs[i].IsPermanent())
{
injurySeverity += hediffs[i].Severity;
}
}
bool injured = injurySeverity > 0f;
if (debug && injured)
{
Warn(pawn.Label + " is injured", debug);
}
return injured;
}
private bool PartOrAnyDescendantHasDirectlyAddedParts(Pawn pawn, BodyPartRecord part)
{
if (pawn?.health?.hediffSet == null || part == null)
{
return false;
}
if (pawn.health.hediffSet.HasDirectlyAddedPartFor(part))
{
return true;
}
if (part.parts == null || part.parts.Count == 0)
{
return false;
}
for (int i = 0; i < part.parts.Count; i++)
{
if (PartOrAnyDescendantHasDirectlyAddedParts(pawn, part.parts[i]))
{
return true;
}
}
return false;
}
private void Warn(string warning, bool debug = false)
{
if (debug && Prefs.DevMode)
{
Log.Message($"[HediffComp_Spawner] {warning}");
}
}
private int ticksUntilSpawn;
private int initialTicksUntilSpawn;
private int hungerReset;
private int healthReset;
private int graceTicks;
private Pawn pawn;
private float calculatedMaxDaysB4Next = 2f;
private float calculatedMinDaysB4Next = 1f;
private int calculatedQuantity = 1;
private bool blockSpawn;
private bool myDebug;
private readonly float errorMinDaysB4Next = 0.001f;
private readonly int errorExponentialLimit = 20;
private readonly int errorSpawnCount = 750;
}
}