整理一下

This commit is contained in:
2025-08-29 16:10:25 +08:00
parent d759562b83
commit dce9a31cd6
51 changed files with 52 additions and 52 deletions

View File

@@ -0,0 +1,268 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Verse;
using Verse.AI;
using RimWorld;
namespace WulaFallenEmpire
{
public class HediffCompProperties_TopTurret : HediffCompProperties
{
public HediffCompProperties_TopTurret()
{
this.compClass = typeof(HediffComp_TopTurret);
}
public ThingDef turretDef;
public float angleOffset;
public bool autoAttack = true;
}
[StaticConstructorOnStartup]
public class HediffComp_TopTurret : HediffComp, IAttackTargetSearcher
{
public Thing Thing
{
get
{
return this.Pawn;
}
}
private HediffCompProperties_TopTurret Props
{
get
{
return (HediffCompProperties_TopTurret)this.props;
}
}
public Verb CurrentEffectiveVerb
{
get
{
return this.AttackVerb;
}
}
public LocalTargetInfo LastAttackedTarget
{
get
{
return this.lastAttackedTarget;
}
}
public int LastAttackTargetTick
{
get
{
return this.lastAttackTargetTick;
}
}
public CompEquippable GunCompEq
{
get
{
return this.gun.TryGetComp<CompEquippable>();
}
}
public Verb AttackVerb
{
get
{
return this.GunCompEq.PrimaryVerb;
}
}
private bool WarmingUp
{
get
{
return this.burstWarmupTicksLeft > 0;
}
}
private bool CanShoot
{
get
{
Pawn pawn;
if ((pawn = (this.Pawn)) != null)
{
if (!pawn.Spawned || pawn.Downed || pawn.Dead || !pawn.Awake())
{
return false;
}
if (pawn.stances.stunner.Stunned)
{
return false;
}
if (this.TurretDestroyed)
{
return false;
}
if (pawn.IsColonyMechPlayerControlled && !this.fireAtWill)
{
return false;
}
}
CompCanBeDormant compCanBeDormant = this.Pawn.TryGetComp<CompCanBeDormant>();
return compCanBeDormant == null || compCanBeDormant.Awake;
}
}
public bool TurretDestroyed
{
get
{
Pawn pawn;
return (pawn = (this.Pawn)) != null && this.AttackVerb.verbProps.linkedBodyPartsGroup != null && this.AttackVerb.verbProps.ensureLinkedBodyPartsGroupAlwaysUsable && PawnCapacityUtility.CalculateNaturalPartsAverageEfficiency(pawn.health.hediffSet, this.AttackVerb.verbProps.linkedBodyPartsGroup) <= 0f;
}
}
private Material TurretMat
{
get
{
if (this.turretMat == null)
{
this.turretMat = MaterialPool.MatFrom(this.Props.turretDef.graphicData.texPath);
}
return this.turretMat;
}
}
public bool AutoAttack
{
get
{
return this.Props.autoAttack;
}
}
public override void CompPostMake()
{
base.CompPostMake();
this.MakeGun();
}
private void MakeGun()
{
this.gun = ThingMaker.MakeThing(this.Props.turretDef, null);
this.UpdateGunVerbs();
}
private void UpdateGunVerbs()
{
List<Verb> allVerbs = this.gun.TryGetComp<CompEquippable>().AllVerbs;
for (int i = 0; i < allVerbs.Count; i++)
{
Verb verb = allVerbs[i];
verb.caster = this.Pawn;
verb.castCompleteCallback = delegate ()
{
this.burstCooldownTicksLeft = this.AttackVerb.verbProps.defaultCooldownTime.SecondsToTicks();
};
}
}
public override void CompPostTick(ref float severityAdjustment)
{
base.CompPostTick(ref severityAdjustment);
if (!this.CanShoot)
{
return;
}
if (this.currentTarget.IsValid)
{
this.curRotation = (this.currentTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset;
}
this.AttackVerb.VerbTick();
if (this.AttackVerb.state != VerbState.Bursting)
{
if (this.WarmingUp)
{
this.burstWarmupTicksLeft--;
if (this.burstWarmupTicksLeft == 0)
{
this.AttackVerb.TryStartCastOn(this.currentTarget, false, true, false, true);
this.lastAttackTargetTick = Find.TickManager.TicksGame;
this.lastAttackedTarget = this.currentTarget;
return;
}
}
else
{
if (this.burstCooldownTicksLeft > 0)
{
this.burstCooldownTicksLeft--;
}
if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10))
{
this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f);
if (this.currentTarget.IsValid)
{
this.burstWarmupTicksLeft = 1;
return;
}
this.ResetCurrentTarget();
}
}
}
}
private void ResetCurrentTarget()
{
this.currentTarget = LocalTargetInfo.Invalid;
this.burstWarmupTicksLeft = 0;
}
public override void CompExposeData()
{
base.CompExposeData();
Scribe_Values.Look<int>(ref this.burstCooldownTicksLeft, "burstCooldownTicksLeft", 0, false);
Scribe_Values.Look<int>(ref this.burstWarmupTicksLeft, "burstWarmupTicksLeft", 0, false);
Scribe_TargetInfo.Look(ref this.currentTarget, "currentTarget");
Scribe_Deep.Look<Thing>(ref this.gun, "gun", Array.Empty<object>());
Scribe_Values.Look<bool>(ref this.fireAtWill, "fireAtWill", true, false);
if (Scribe.mode == LoadSaveMode.PostLoadInit)
{
if (this.gun == null)
{
Log.Error("CompTurrentGun had null gun after loading. Recreating.");
this.MakeGun();
return;
}
this.UpdateGunVerbs();
}
}
private const int StartShootIntervalTicks = 10;
private static readonly CachedTexture ToggleTurretIcon = new CachedTexture("UI/Gizmos/ToggleTurret");
public Thing gun;
protected int burstCooldownTicksLeft;
protected int burstWarmupTicksLeft;
protected LocalTargetInfo currentTarget = LocalTargetInfo.Invalid;
private bool fireAtWill = true;
private LocalTargetInfo lastAttackedTarget = LocalTargetInfo.Invalid;
private int lastAttackTargetTick;
private float curRotation;
[Unsaved(false)]
public Material turretMat;
}
}

View File

@@ -0,0 +1,23 @@
using RimWorld;
using System.Collections.Generic;
using Verse;
namespace WulaFallenEmpire
{
public class HediffCompProperties_GiveHediffsInRangeToRace : HediffCompProperties
{
public float range;
public TargetingParameters targetingParameters;
public HediffDef hediff;
public ThingDef mote;
public bool hideMoteWhenNotDrafted;
public float initialSeverity = 1f;
public bool onlyPawnsInSameFaction = true;
public List<ThingDef> targetRaces; // 新增:可配置的目标种族列表
public HediffCompProperties_GiveHediffsInRangeToRace()
{
compClass = typeof(HediffComp_GiveHediffsInRangeToRace);
}
}
}

View File

@@ -0,0 +1,37 @@
using Verse;
namespace WulaFallenEmpire
{
// 定义了在XML中可以设置的属性
public class HediffCompProperties_DamageResponse : HediffCompProperties
{
// 每点伤害值所转化的严重性数值
public float severityIncreasePerDamage = 0f;
public HediffCompProperties_DamageResponse()
{
this.compClass = typeof(HediffComp_DamageResponse);
}
}
// 组件的实际逻辑
public class HediffComp_DamageResponse : HediffComp
{
// 一个方便获取上面属性的捷径
private HediffCompProperties_DamageResponse Props => (HediffCompProperties_DamageResponse)this.props;
// 当Hediff的持有者Pawn受到伤害后这个方法会被游戏调用
public override void Notify_PawnPostApplyDamage(DamageInfo dinfo, float totalDamageDealt)
{
base.Notify_PawnPostApplyDamage(dinfo, totalDamageDealt);
// 如果伤害值大于0并且我们在XML中设置了转化率
if (totalDamageDealt > 0 && Props.severityIncreasePerDamage > 0f)
{
// 增加父Hediff的严重性
// this.parent 指向这个组件所属的Hediff实例
this.parent.Severity += totalDamageDealt * Props.severityIncreasePerDamage;
}
}
}
}

View File

@@ -0,0 +1,63 @@
using RimWorld;
using System.Collections.Generic;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
public class HediffComp_GiveHediffsInRangeToRace : HediffComp
{
private Mote mote;
public HediffCompProperties_GiveHediffsInRangeToRace Props => (HediffCompProperties_GiveHediffsInRangeToRace)props;
public override void CompPostTick(ref float severityAdjustment)
{
if (!parent.pawn.Awake() || parent.pawn.health == null || parent.pawn.health.InPainShock || !parent.pawn.Spawned)
{
return;
}
if (!Props.hideMoteWhenNotDrafted || parent.pawn.Drafted)
{
if (Props.mote != null && (mote == null || mote.Destroyed))
{
mote = MoteMaker.MakeAttachedOverlay(parent.pawn, Props.mote, Vector3.zero);
}
if (mote != null)
{
mote.Maintain();
}
}
IReadOnlyList<Pawn> pawns = ((!Props.onlyPawnsInSameFaction || parent.pawn.Faction == null) ? parent.pawn.Map.mapPawns.AllPawnsSpawned : parent.pawn.Map.mapPawns.SpawnedPawnsInFaction(parent.pawn.Faction));
foreach (Pawn pawn in pawns)
{
// 修改点检查种族是否在我们的目标列表中如果列表为空或null则不进行任何操作
if ((Props.targetRaces.NullOrEmpty() || !Props.targetRaces.Contains(pawn.def)) || pawn.Dead || pawn.health == null || pawn == parent.pawn || !(pawn.Position.DistanceTo(parent.pawn.Position) <= Props.range) || !Props.targetingParameters.CanTarget(pawn))
{
continue;
}
Hediff hediff = pawn.health.hediffSet.GetFirstHediffOfDef(Props.hediff);
if (hediff == null)
{
hediff = pawn.health.AddHediff(Props.hediff, pawn.health.hediffSet.GetBrain());
hediff.Severity = Props.initialSeverity;
HediffComp_Link hediffComp_Link = hediff.TryGetComp<HediffComp_Link>();
if (hediffComp_Link != null)
{
hediffComp_Link.drawConnection = true;
hediffComp_Link.other = parent.pawn;
}
}
HediffComp_Disappears hediffComp_Disappears = hediff.TryGetComp<HediffComp_Disappears>();
if (hediffComp_Disappears == null)
{
Log.Error("HediffComp_GiveHediffsInRangeToRace has a hediff in props which does not have a HediffComp_Disappears");
}
else
{
hediffComp_Disappears.ticksToDisappear = 5;
}
}
}
}
}

View File

@@ -0,0 +1,115 @@
using System.Collections.Generic;
using System.Linq;
using Verse;
using RimWorld;
namespace WulaFallenEmpire
{
// HediffCompProperties用于在XML中配置组件的属性
public class HediffCompProperties_RegenerateBackstory : HediffCompProperties
{
public List<string> spawnCategories; // 存储背景故事类别的列表
public bool regenerateChildhood = false; // 控制是否重新生成幼年时期故事默认为false
public HediffCompProperties_RegenerateBackstory()
{
this.compClass = typeof(HediffComp_RegenerateBackstory);
}
}
// HediffComp的实际逻辑实现
public class HediffComp_RegenerateBackstory : HediffComp
{
private HediffCompProperties_RegenerateBackstory Props => (HediffCompProperties_RegenerateBackstory)this.props;
public override void CompPostMake()
{
base.CompPostMake();
// 在Hediff被添加到Pawn时立即触发背景故事重新生成
// 使用一个延迟操作确保Pawn完全初始化后再修改其故事
// 否则可能导致Pawn.story为null
LongEventHandler.QueueLongEvent(RegenerateBackstory, "RegeneratingBackstory", false, null);
}
private void RegenerateBackstory()
{
Pawn pawn = this.parent.pawn;
if (pawn == null || pawn.story == null)
{
Log.Warning($"[WulaFallenEmpire] HediffComp_RegenerateBackstory: Pawn or Pawn.story is null for hediff {this.parent.def.defName}. Cannot regenerate backstory.");
return;
}
// 获取指定的背景故事类别
List<string> categories = new List<string>();
if (Props.spawnCategories != null && Props.spawnCategories.Any())
{
categories = Props.spawnCategories;
}
else
{
Log.Warning($"[WulaFallenEmpire] HediffComp_RegenerateBackstory: No spawnCategories specified for hediff {this.parent.def.defName}. Using all available categories.");
categories = DefDatabase<BackstoryDef>.AllDefs.SelectMany(bs => bs.spawnCategories).Distinct().ToList(); // 如果没有指定类别,则使用所有类别
}
// 尝试重新生成背景故事
BackstoryDef newChildhood = null;
BackstoryDef newAdulthood = null;
// 根据 regenerateChildhood 的值决定是否重新生成幼年时期故事
if (Props.regenerateChildhood)
{
// 筛选符合类别的幼年背景故事
List<BackstoryDef> availableChildhoodBackstories = DefDatabase<BackstoryDef>.AllDefsListForReading
.Where(bs => bs.slot == BackstorySlot.Childhood && bs.spawnCategories.Any(cat => categories.Contains(cat)))
.ToList();
// 随机选择幼年背景故事
if (availableChildhoodBackstories.Any())
{
newChildhood = availableChildhoodBackstories.RandomElement();
}
else
{
Log.Warning($"[WulaFallenEmpire] HediffComp_RegenerateBackstory: No childhood backstories found for categories: {string.Join(", ", Props.spawnCategories ?? new List<string>())}.");
}
} else
{
// 如果 regenerateChildhood 为 false则保留原有的幼年时期故事
newChildhood = pawn.story.Childhood;
}
// 筛选符合类别的成年背景故事
List<BackstoryDef> availableAdulthoodBackstories = DefDatabase<BackstoryDef>.AllDefsListForReading
.Where(bs => bs.slot == BackstorySlot.Adulthood && bs.spawnCategories.Any(cat => categories.Contains(cat)))
.ToList();
// 随机选择成年背景故事
if (availableAdulthoodBackstories.Any())
{
newAdulthood = availableAdulthoodBackstories.RandomElement();
}
else
{
Log.Warning($"[WulaFallenEmpire] HediffComp_RegenerateBackstory: No adulthood backstories found for categories: {string.Join(", ", Props.spawnCategories ?? new List<string>())}.");
}
// 应用新的背景故事
if (newChildhood != null || newAdulthood != null)
{
pawn.story.Childhood = newChildhood;
pawn.story.Adulthood = newAdulthood;
Log.Message($"[WulaFallenEmpire] Regenerated backstory for {pawn.NameShortColored}: Childhood='{newChildhood?.title ?? "None"}', Adulthood='{newAdulthood?.title ?? "None"}'.");
}
else
{
Log.Warning($"[WulaFallenEmpire] HediffComp_RegenerateBackstory: Failed to find any suitable backstories for {pawn.NameShortColored} with categories: {string.Join(", ", Props.spawnCategories ?? new List<string>())}.");
}
// 删除当前的Hediff
pawn.health.RemoveHediff(this.parent);
Log.Message($"[WulaFallenEmpire] Removed hediff {this.parent.def.defName} from {pawn.NameShortColored} after backstory regeneration.");
}
}
}

View File

@@ -0,0 +1,244 @@
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Verse;
using Verse.Noise;
namespace WulaFallenEmpire
{
public class HediffComp_TimedExplosion : HediffComp
{
// 倒计时相关字段
public int ticksToDisappear;
public int disappearsAfterTicks;
public int seed;
// 配置属性快捷访问
public HediffCompProperties_TimedExplosion Props =>
(HediffCompProperties_TimedExplosion)props;
// 消失判定属性
public override bool CompShouldRemove
{
get
{
if (ticksToDisappear > 0) return false;
if (Props.requiredMentalState != null)
{
return parent.pawn.MentalStateDef != Props.requiredMentalState;
}
return true;
}
}
// 进度计算
public float Progress =>
1f - (float)ticksToDisappear / Mathf.Max(1, disappearsAfterTicks);
public int EffectiveTicksToDisappear => ticksToDisappear / TicksLostPerTick;
public float NoisyProgress => AddNoiseToProgress(Progress, seed);
public virtual int TicksLostPerTick => 1;
public override string CompLabelInBracketsExtra
{
get
{
if (Props.showRemainingTime)
{
if (EffectiveTicksToDisappear < 2500)
{
return EffectiveTicksToDisappear.ToStringSecondsFromTicks("F0");
}
return EffectiveTicksToDisappear.ToStringTicksToPeriod(allowSeconds: true, shortForm: true, canUseDecimals: true, allowYears: true, Props.canUseDecimalsShortForm);
}
return base.CompLabelInBracketsExtra;
}
}
private static float AddNoiseToProgress(float progress, int seed)
{
float num = (float)Perlin.GetValue(progress, 0.0, 0.0, 9.0, seed);
float num2 = 0.25f * (1f - progress);
return Mathf.Clamp01(progress + num2 * num);
}
// 初始化
public override void CompPostMake()
{
base.CompPostMake();
disappearsAfterTicks = Props.disappearsAfterTicks.RandomInRange;
seed = Rand.Int;
ticksToDisappear = disappearsAfterTicks;
}
// 每帧更新
public override void CompPostTick(ref float severityAdjustment)
{
ticksToDisappear--;
if (CompShouldRemove)
{
parent.pawn.health.RemoveHediff(parent);
}
}
// 移除后处理
public override void CompPostPostRemoved()
{
base.CompPostPostRemoved();
// 处理新鲜伤口状态
if (!Props.leaveFreshWounds && parent.Part != null)
{
foreach (BodyPartRecord part in parent.Part.GetPartAndAllChildParts())
{
Hediff_MissingPart hediff = parent.pawn.health.hediffSet.GetMissingPartFor(part) as Hediff_MissingPart;
if (hediff != null)
{
hediff.IsFresh = false;
}
}
}
// 触发爆炸逻辑
if (ShouldTriggerExplosion())
{
TriggerExplosion();
DestroyGearIfNeeded();
}
// 发送消息通知
if (!Props.messageOnDisappear.NullOrEmpty() && PawnUtility.ShouldSendNotificationAbout(parent.pawn))
{
Messages.Message(
Props.messageOnDisappear.Formatted(parent.pawn.Named("PAWN")),
parent.pawn,
MessageTypeDefOf.NeutralEvent
);
}
// 发送信件通知
if (!Props.letterTextOnDisappear.NullOrEmpty() &&
!Props.letterLabelOnDisappear.NullOrEmpty() &&
PawnUtility.ShouldSendNotificationAbout(parent.pawn))
{
Find.LetterStack.ReceiveLetter(
Props.letterLabelOnDisappear.Formatted(parent.pawn.Named("PAWN")),
Props.letterTextOnDisappear.Formatted(parent.pawn.Named("PAWN")),
LetterDefOf.NegativeEvent,
parent.pawn
);
}
}
// 爆炸条件检查
private bool ShouldTriggerExplosion()
{
return parent.pawn.Spawned &&
Props.explosionRadius > 0.01f &&
Props.damageDef != null &&
parent.pawn.Map != null;
}
// 执行爆炸
private void TriggerExplosion()
{
GenExplosion.DoExplosion(
center: parent.pawn.Position,
map: parent.pawn.Map,
radius: Props.explosionRadius,
damType: Props.damageDef,
instigator: parent.pawn,
damAmount: Props.damageAmount,
armorPenetration: Props.armorPenetration,
explosionSound: Props.soundDef,
weapon: null,
projectile: null,
intendedTarget: null,
postExplosionSpawnThingDef: Props.postExplosionSpawnThingDef,
postExplosionSpawnChance: Props.postExplosionSpawnChance,
postExplosionSpawnThingCount: Props.postExplosionSpawnThingCount,
postExplosionGasType: null,
applyDamageToExplosionCellsNeighbors: false,
chanceToStartFire: Props.chanceToStartFire,
damageFalloff: Props.damageFalloff,
direction: null,
ignoredThings: new List<Thing> { parent.pawn }
);
}
// 装备销毁
private void DestroyGearIfNeeded()
{
if (!Props.destroyGear) return;
if (parent.pawn.equipment != null)
{
parent.pawn.equipment.DestroyAllEquipment(DestroyMode.Vanish);
}
if (parent.pawn.apparel != null)
{
parent.pawn.apparel.DestroyAll(DestroyMode.Vanish);
}
}
// 数据持久化
public override void CompExposeData()
{
Scribe_Values.Look(ref ticksToDisappear, "ticksToDisappear", 0);
Scribe_Values.Look(ref disappearsAfterTicks, "disappearsAfterTicks", 0);
Scribe_Values.Look(ref seed, "seed", 0);
}
// 调试信息
public override string CompDebugString()
{
return $"倒计时: {ticksToDisappear}\n爆炸半径: {Props.explosionRadius}";
}
}
public class HediffCompProperties_TimedExplosion : HediffCompProperties
{
[Header("消失设置")]
public IntRange disappearsAfterTicks = new IntRange(600, 1200);
public bool showRemainingTime = true;
public bool canUseDecimalsShortForm;
public MentalStateDef requiredMentalState;
public bool leaveFreshWounds = true;
[Header("爆炸设置")]
public float explosionRadius = 3f;
public DamageDef damageDef;
public int damageAmount = 20;
public float armorPenetration;
public SoundDef soundDef;
public float chanceToStartFire;
public bool damageFalloff = true;
[Header("后续效果")]
public bool destroyGear;
public GasType gasType;
public ThingDef postExplosionSpawnThingDef;
public float postExplosionSpawnChance;
public int postExplosionSpawnThingCount = 1;
[Header("通知设置")]
[MustTranslate]
public string messageOnDisappear;
[MustTranslate]
public string letterLabelOnDisappear;
[MustTranslate]
public string letterTextOnDisappear;
public bool sendLetterOnDisappearIfDead = true;
public HediffCompProperties_TimedExplosion()
{
compClass = typeof(HediffComp_TimedExplosion);
}
}
}

View File

@@ -0,0 +1,118 @@
using System;
using Verse;
namespace WulaFallenEmpire.MoharHediffs
{
public class HediffCompProperties_Spawner : HediffCompProperties
{
public HediffCompProperties_Spawner()
{
this.compClass = typeof(HediffComp_Spawner);
}
/// <summary>
/// 要生成的物品的ThingDef。如果animalThing为false则使用此项。
/// </summary>
public ThingDef thingToSpawn;
/// <summary>
/// 每次生成的基础物品数量。
/// </summary>
public int spawnCount = 1;
/// <summary>
/// 如果为true则生成一个Pawn动物。如果为false则生成一个Thing。
/// </summary>
public bool animalThing;
/// <summary>
/// 要生成的动物的PawnKindDef。如果animalThing为true则使用此项。
/// </summary>
public PawnKindDef animalToSpawn;
/// <summary>
/// 如果为true生成的动物将属于玩家派系。
/// </summary>
public bool factionOfPlayerAnimal;
/// <summary>
/// 下一次生成事件发生前的最少天数。
/// </summary>
public float minDaysB4Next = 1f;
/// <summary>
/// 下一次生成事件发生前的最大天数。
/// </summary>
public float maxDaysB4Next = 2f;
/// <summary>
/// 生成后进入宽限期延迟下一次生成的几率0.0到1.0)。
/// </summary>
public float randomGrace;
/// <summary>
/// 如果触发,宽限期的持续时间(天)。
/// </summary>
public float graceDays = 0.5f;
/// <summary>
/// 附近允许的相同Pawn的最大数量。如果超过该数量则暂停生成。-1为禁用。
/// </summary>
public int spawnMaxAdjacent = -1;
/// <summary>
/// 如果为true生成的物品将被禁用。
/// </summary>
public bool spawnForbidden;
/// <summary>
/// 如果为true当宿主Pawn饥饿时生成将暂停。
/// </summary>
public bool hungerRelative;
/// <summary>
/// 如果为true当宿主Pawn受伤时生成将暂停。
/// </summary>
public bool healthRelative;
/// <summary>
/// 如果为true生成数量将根据宿主的年龄进行调整。
/// </summary>
public bool ageWeightedQuantity;
/// <summary>
/// 如果为true生成周期两次生成之间的时间将根据宿主的年龄进行调整。
/// </summary>
public bool ageWeightedPeriod;
/// <summary>
/// 如果为true且ageWeightedPeriod为true则随着宿主年龄增长生成周期变短。如果为false则变长。
/// </summary>
public bool olderSmallerPeriod;
/// <summary>
/// 如果为true且ageWeightedQuantity为true则随着宿主年龄增长生成数量变多。如果为false则变少。
/// </summary>
public bool olderBiggerQuantity;
/// <summary>
/// 如果为true且ageWeightedQuantity为true则随年龄增长的数量缩放将是指数性的而非线性的。
/// </summary>
public bool exponentialQuantity;
/// <summary>
/// 指数级数量缩放的最大乘数,以防止出现荒谬的数字。
/// </summary>
public int exponentialRatioLimit = 15;
/// <summary>
/// 生成时显示的消息的翻译键(例如,“{PAWN}下了一个蛋。”)。
/// </summary>
public string spawnVerb = "delivery";
/// <summary>
/// 如果为true则为此组件启用详细的调试日志记录。
/// </summary>
public bool debug;
}
}

View File

@@ -0,0 +1,565 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using RimWorld.Planet;
using Verse;
namespace WulaFallenEmpire.MoharHediffs
{
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;
Tools.Warn(string.Concat(new string[]
{
">>> ",
this.parent.pawn.Label,
" - ",
this.parent.def.defName,
" - CompPostMake start"
}), this.myDebug);
this.TraceProps();
this.CheckProps();
this.CalculateValues();
this.CheckCalculatedValues();
this.TraceCalculatedValues();
if (this.initialTicksUntilSpawn == 0)
{
Tools.Warn("Reseting countdown bc initialTicksUntilSpawn == 0 (comppostmake)", this.myDebug);
this.ResetCountdown();
}
}
public override void CompPostTick(ref float severityAdjustment)
{
this.pawn = this.parent.pawn;
if (!Tools.OkPawn(this.pawn))
{
return;
}
if (this.blockSpawn)
{
return;
}
if (this.graceTicks > 0)
{
this.graceTicks--;
return;
}
if (this.Props.hungerRelative && this.pawn.IsHungry(this.myDebug))
{
int num = (int)(this.RandomGraceDays() * 60000f);
this.hungerReset++;
this.graceTicks = num;
return;
}
if (this.Props.healthRelative && this.pawn.IsInjured(this.myDebug))
{
int num2 = (int)(this.RandomGraceDays() * 60000f);
this.healthReset++;
this.graceTicks = num2;
return;
}
this.hungerReset = (this.healthReset = 0);
if (this.CheckShouldSpawn())
{
Tools.Warn("Reseting countdown bc spawned thing", this.myDebug);
this.CalculateValues();
this.CheckCalculatedValues();
this.ResetCountdown();
if (Rand.Chance(this.Props.randomGrace))
{
int num3 = (int)(this.RandomGraceDays() * 60000f);
this.graceTicks = num3;
}
}
}
private void TraceProps()
{
Tools.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(),
"; "
}), this.myDebug);
if (this.Props.animalThing)
{
Tools.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)
{
Tools.Warn(string.Concat(new string[]
{
"ageWeightedQuantity:",
this.Props.ageWeightedQuantity.ToString(),
"; olderBiggerQuantity:",
this.Props.olderBiggerQuantity.ToString(),
"; ",
this.myDebug.ToString()
}), false);
if (this.Props.exponentialQuantity)
{
Tools.Warn(string.Concat(new string[]
{
"exponentialQuantity:",
this.Props.exponentialQuantity.ToString(),
"; exponentialRatioLimit:",
this.Props.exponentialRatioLimit.ToString(),
"; "
}), this.myDebug);
}
}
Tools.Warn(string.Concat(new string[]
{
"ageWeightedPeriod:",
this.Props.ageWeightedPeriod.ToString(),
"; olderSmallerPeriod:",
this.Props.olderSmallerPeriod.ToString(),
"; ",
this.myDebug.ToString()
}), false);
}
private void CalculateValues()
{
float num = Tools.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);
Tools.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);
Tools.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)
{
Tools.Warn(">ERROR< quantityAgeRatio is f* up : " + num3.ToString(), this.myDebug);
this.blockSpawn = true;
Tools.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;
}
Tools.Warn(string.Concat(new string[]
{
" exponentialQuantity: ",
this.Props.exponentialQuantity.ToString(),
"; expoFactor: ",
num4.ToString(),
"; gotLimited: ",
flag.ToString(),
"; gotAugmented: ",
flag2.ToString()
}), this.myDebug);
}
Tools.Warn("; Props.spawnCount: " + this.Props.spawnCount.ToString() + "; calculatedQuantity: " + this.calculatedQuantity.ToString(), this.myDebug);
}
}
private void CheckCalculatedValues()
{
if (this.calculatedQuantity > this.errorSpawnCount)
{
Tools.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;
Tools.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()
{
Tools.Warn("calculatedMinDaysB4Next:" + this.calculatedMinDaysB4Next.ToString(), this.myDebug);
Tools.Warn("calculatedMaxDaysB4Next:" + this.calculatedMaxDaysB4Next.ToString(), this.myDebug);
Tools.Warn("calculatedQuantity:" + this.calculatedQuantity.ToString(), this.myDebug);
}
private void CheckProps()
{
if (this.Props.animalThing && this.Props.animalToSpawn == null)
{
Tools.Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with animalflag but without animalToSpawn", true);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.minDaysB4Next <= 0f)
{
Tools.Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with null/negative minDaysB4Next", true);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.maxDaysB4Next <= 0f)
{
Tools.Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with null/negative maxDaysB4Next", true);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.maxDaysB4Next < this.Props.minDaysB4Next)
{
Tools.Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with maxDaysB4Next < minDaysB4Next", true);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.spawnCount <= 0)
{
Tools.Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with null/negative spawnCount", true);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (!this.Props.animalThing && this.Props.thingToSpawn == null)
{
Tools.Warn(this.parent.pawn.Label + " has a hediffcomp_spawner without thingToSpawn", true);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.ageWeightedQuantity && this.Props.exponentialQuantity && this.Props.exponentialRatioLimit > this.errorExponentialLimit)
{
Tools.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;
}
Tools.Warn("Did not spawn, reseting countdown", this.myDebug);
this.ResetCountdown();
}
return false;
}
private PawnKindDef MyPawnKindDefNamed(string myDefName)
{
return DefDatabase<PawnKindDef>.GetNamed(myDefName, true);
}
public bool TryDoSpawn()
{
Pawn pawn = this.parent.pawn;
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.animalThing)
{
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) || PawnUtility.ShouldSendNotificationAbout(pawn))
{
Messages.Message(this.Props.spawnVerb.Translate(pawn.Named("PAWN")), pawn, MessageTypeDefOf.PositiveEvent, true);
}
return true;
}
else
{
IntVec3 intVec2;
if (!this.TryFindSpawnCell(out intVec2))
{
return false;
}
Thing thing = ThingMaker.MakeThing(this.Props.thingToSpawn, null);
if (thing == null)
{
return false;
}
thing.stackCount = this.calculatedQuantity;
if (this.Props.spawnForbidden)
{
thing.SetForbidden(true, true);
}
GenPlace.TryPlaceThing(thing, intVec2, pawn.Map, ThingPlaceMode.Direct, null, null, default(Rot4));
if (PawnUtility.ShouldSendNotificationAbout(pawn))
{
Messages.Message(this.Props.spawnVerb.Translate(pawn.Named("PAWN"), thing.Named("THING")), thing, MessageTypeDefOf.PositiveEvent, true);
}
return true;
}
}
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
{
result = CellFinder.RandomClosewalkCellNear(this.pawn.Position, map, 5, null);
result2 = true;
}
}
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();
}
}
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;
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire.MoharHediffs
{
public static class Tools
{
public static void DestroyParentHediff(Hediff parentHediff, bool debug = false)
{
if (parentHediff.pawn != null && parentHediff.def.defName != null && debug)
{
Log.Warning(parentHediff.pawn.Label + "'s Hediff: " + parentHediff.def.defName + " says goodbye.");
}
parentHediff.Severity = 0f;
}
public static float GetPawnAgeOverlifeExpectancyRatio(Pawn pawn, bool debug = false)
{
float result = 1f;
if (pawn == null)
{
if (debug)
{
Log.Warning("GetPawnAgeOverlifeExpectancyRatio pawn NOT OK");
}
return result;
}
result = pawn.ageTracker.AgeBiologicalYearsFloat / pawn.RaceProps.lifeExpectancy;
if (debug)
{
Log.Warning(string.Concat(new string[]
{
pawn.Label,
" Age: ",
pawn.ageTracker.AgeBiologicalYearsFloat.ToString(),
"; lifeExpectancy: ",
pawn.RaceProps.lifeExpectancy.ToString(),
"; ratio:",
result.ToString()
}));
}
return result;
}
public static bool IsInjured(this Pawn pawn, bool debug = false)
{
if (pawn == null)
{
if (debug)
{
Log.Warning("pawn is null - wounded ");
}
return false;
}
float num = 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())
{
num += hediffs[i].Severity;
}
}
if (debug && num > 0f)
{
Log.Warning(pawn.Label + " is wounded ");
}
return num > 0f;
}
public static bool IsHungry(this Pawn pawn, bool debug = false)
{
if (pawn == null)
{
if (debug)
{
Log.Warning("pawn is null - IsHungry ");
}
return false;
}
bool flag = pawn.needs.food != null && pawn.needs.food.CurCategory == HungerCategory.Starving;
if (debug && flag)
{
Log.Warning(pawn.Label + " is hungry ");
}
return flag;
}
public static bool OkPawn(Pawn pawn)
{
return pawn != null && pawn.Map != null;
}
public static void Warn(string warning, bool debug = false)
{
if (debug)
{
Log.Warning(warning);
}
}
}
}