using System.Collections.Generic; using RimWorld; using UnityEngine; using Verse; using Verse.AI; using Verse.AI.Group; using Verse.Sound; namespace ArachnaeSwarm { public class HediffCompProperties_ConfigurableMutant : HediffCompProperties { // --- 可配置的Hediff --- public HediffDef risingHediff; public HediffDef corpseHediff; // --- 可配置的时间和范围 --- public FloatRange resurrectionSecondsRange = new FloatRange(5f, 10f); public FloatRange stunSecondsRange = new FloatRange(1f, 3f); public FloatRange alertSecondsRange = new FloatRange(0.5f, 3f); public IntRange selfRaiseHoursRange = new IntRange(3, 4); public IntRange checkForTargetTicksInterval = new IntRange(900, 1800); // --- 可配置的杂项值 --- public float particleSpawnMTBSeconds = 1f; public float extinguishFireMTB = 45f; public float bioferriteOnDeathChance = 0.04f; public int bioferriteAmountOnDeath = 10; public HediffCompProperties_ConfigurableMutant() { this.compClass = typeof(HediffComp_ConfigurableMutant); } } public class HediffComp_ConfigurableMutant : HediffComp { public HediffCompProperties_ConfigurableMutant Props => (HediffCompProperties_ConfigurableMutant)this.props; } public class Hediff_ConfigurableMutant : HediffWithComps { // 运行时状态字段,保持不变 public float headRotation; private float resurrectTimer; private float selfRaiseTimer; private float alertTimer; private int nextTargetCheckTick = -99999; private Thing alertedTarget; private Effecter riseEffecter; private Sustainer riseSustainer; private float corpseDamagePct = 1f; private HediffComp_ConfigurableMutant PropsComp => this.TryGetComp(); public bool IsRising => PropsComp?.Props.risingHediff != null && pawn.health.hediffSet.HasHediff(PropsComp.Props.risingHediff); public override void PostMake() { base.PostMake(); headRotation = Rand.RangeSeeded(-20f, 20f, pawn.thingIDNumber); if (!pawn.Dead) { pawn.timesRaisedAsShambler++; } } public override void PostAdd(DamageInfo? dinfo) { if (!ModLister.CheckAnomaly("Shambler")) { pawn.health.RemoveHediff(this); } else { base.PostAdd(dinfo); } } public override void Notify_Spawned() { base.Notify_Spawned(); pawn.Map.mapPawns.RegisterShambler(pawn); } public override void TickInterval(int delta) { base.TickInterval(delta); var compProps = PropsComp?.Props; if (compProps == null) return; // 如果没有Comp,则不执行任何操作 if (IsRising) { if ((float)Find.TickManager.TicksGame > resurrectTimer) { FinishRising(); } if (!pawn.Spawned) return; if ((float)Find.TickManager.TicksGame > resurrectTimer - 15f) { riseSustainer?.End(); } else if (IsRising) { if (riseSustainer == null || riseSustainer.Ended) { riseSustainer = SoundDefOf.Pawn_Shambler_Rise.TrySpawnSustainer(SoundInfo.InMap(pawn, MaintenanceType.PerTick)); } if (riseEffecter == null) { riseEffecter = EffecterDefOf.ShamblerRaise.Spawn(pawn, pawn.Map); } if (pawn.Drawer.renderer.CurAnimation != AnimationDefOf.ShamblerRise) { pawn.Drawer.renderer.SetAnimation(AnimationDefOf.ShamblerRise); } riseSustainer.Maintain(); riseEffecter.EffectTick(pawn, TargetInfo.Invalid); } return; } if (Rand.MTBEventOccurs(compProps.particleSpawnMTBSeconds, 60f, 1f)) { FleckMaker.ThrowShamblerParticles(pawn); } if (pawn.IsBurning() && !pawn.Downed && Rand.MTBEventOccurs(compProps.extinguishFireMTB, 60f, 1f)) { ((Fire)pawn.GetAttachment(ThingDefOf.Fire))?.Destroy(); pawn.records.Increment(RecordDefOf.FiresExtinguished); } if (pawn.Spawned && pawn.mutant != null && !pawn.mutant.IsPassive && !pawn.Drafted) { if (Find.TickManager.TicksGame > nextTargetCheckTick) { nextTargetCheckTick = Find.TickManager.TicksGame + compProps.checkForTargetTicksInterval.RandomInRange; Thing thing = MutantUtility.FindShamblerTarget(pawn); if (thing != null) { Notify_DelayedAlert(thing); MutantUtility.ActivateNearbyShamblers(pawn, thing); } } if (alertedTarget != null && (float)Find.TickManager.TicksGame > alertTimer) { pawn.mindState.enemyTarget = alertedTarget; pawn.mindState.lastEngageTargetTick = Find.TickManager.TicksGame; // 直接给public字段赋值,绕过internal方法 alertedTarget = null; if (DebugViewSettings.drawShamblerAlertMote) { MoteMaker.MakeColonistActionOverlay(pawn, ThingDefOf.Mote_ShamblerAlert); } SoundDefOf.Pawn_Shambler_Alert.PlayOneShot(pawn); } } if (ShouldSelfRaise()) { StartRising(); } } private bool ShouldSelfRaise() { if (pawn.DevelopmentalStage == DevelopmentalStage.Baby) return false; return pawn.Downed && pawn.CarriedBy == null && (float)Find.TickManager.TicksGame > selfRaiseTimer; } public void StartRising(int lifespanTicks = -1) { var compProps = PropsComp?.Props; if (compProps?.risingHediff == null) { Log.Error($"[ConfigurableMutant] risingHediff is not defined in XML for {this.def.defName}"); return; } if (!pawn.Dead && !pawn.Downed) { Log.Error("Tried to raise non dead/downed pawn as shambler"); if(pawn.mutant != null) pawn.mutant.Turn(clearLord: true); return; } MutantUtility.RestoreBodyParts(pawn); pawn.Notify_DisabledWorkTypesChanged(); if (!pawn.Dead || ResurrectionUtility.TryResurrect(pawn, new ResurrectionParams { noLord = true, restoreMissingParts = false, removeDiedThoughts = false })) { pawn.jobs?.EndCurrentJob(JobCondition.InterruptForced); resurrectTimer = Find.TickManager.TicksGame + compProps.resurrectionSecondsRange.RandomInRange.SecondsToTicks(); pawn.health.AddHediff(compProps.risingHediff); } } private void CancelRising() { var risingHediff = PropsComp?.Props.risingHediff; if (risingHediff == null) return; riseSustainer?.End(); resurrectTimer = -99999f; if (pawn.health.hediffSet.TryGetHediff(risingHediff, out var hediff)) { pawn.health.RemoveHediff(hediff); } if (!pawn.Dead) { pawn.Kill(null, null); } } private void FinishRising(bool stun = true) { var compProps = PropsComp?.Props; if (compProps?.risingHediff != null && pawn.health.hediffSet.TryGetHediff(compProps.risingHediff, out var hediff)) { pawn.health.RemoveHediff(hediff); } if (pawn.ParentHolder is Pawn_CarryTracker pawn_CarryTracker) { pawn_CarryTracker.TryDropCarriedThing(pawn_CarryTracker.pawn.Position, ThingPlaceMode.Near, out var _); pawn_CarryTracker.pawn.jobs.EndCurrentJob(JobCondition.InterruptForced); } if (pawn.mutant != null && !pawn.mutant.HasTurned) { pawn.mutant.Turn(); } pawn.timesRaisedAsShambler++; MutantUtility.RestoreUntilNotDowned(pawn); if (pawn.Spawned && stun) { pawn.Rotation = Rot4.South; if(compProps != null) pawn.stances.stunner.StunFor(compProps.stunSecondsRange.RandomInRange.SecondsToTicks(), pawn, addBattleLog: false, showMote: false); } pawn.Drawer.renderer.SetAnimation(null); StartSelfRaiseTimer(); } private void StartSelfRaiseTimer() { var selfRaiseHoursRange = PropsComp?.Props.selfRaiseHoursRange ?? new IntRange(3, 4); selfRaiseTimer = Find.TickManager.TicksGame + 2500 * selfRaiseHoursRange.RandomInRange; } public override void Notify_PawnPostApplyDamage(DamageInfo dinfo, float totalDamageDealt) { base.Notify_PawnPostApplyDamage(dinfo, totalDamageDealt); if (dinfo.Instigator != null && pawn.HostileTo(dinfo.Instigator)) { if (pawn.Spawned && dinfo.Instigator is IAttackTarget && dinfo.Instigator.Spawned && pawn.CanSee(dinfo.Instigator)) { pawn.mindState.enemyTarget = dinfo.Instigator; pawn.mindState.lastEngageTargetTick = Find.TickManager.TicksGame; // 直接给public字段赋值,绕过internal方法 pawn.jobs.EndCurrentJob(JobCondition.InterruptOptional); pawn.GetLord()?.Notify_PawnAcquiredTarget(pawn, dinfo.Instigator); } MutantUtility.ActivateNearbyShamblers(pawn, dinfo.Instigator); } } public void Notify_DelayedAlert(Thing target) { var alertSecondsRange = PropsComp?.Props.alertSecondsRange ?? new FloatRange(0.5f, 3f); if (pawn.mutant != null && !pawn.mutant.IsPassive && !pawn.Drafted && pawn.mindState.enemyTarget == null) { alertTimer = Find.TickManager.TicksGame + alertSecondsRange.RandomInRange.SecondsToTicks(); alertedTarget = target; pawn.GetLord()?.Notify_PawnAcquiredTarget(pawn, target); } } public override void Notify_Downed() { StartSelfRaiseTimer(); } public override void Notify_PawnKilled() { corpseDamagePct = pawn.health.summaryHealth.SummaryHealthPercent; base.Notify_PawnKilled(); } public override void Notify_PawnDied(DamageInfo? dinfo, Hediff culprit = null) { if (IsRising) { CancelRising(); } var compProps = PropsComp?.Props; if (compProps != null && pawn.timesRaisedAsShambler == 1 && Rand.Chance(compProps.bioferriteOnDeathChance) && pawn.SpawnedOrAnyParentSpawned) { Thing thing = ThingMaker.MakeThing(ThingDefOf.Bioferrite); thing.stackCount = compProps.bioferriteAmountOnDeath; GenPlace.TryPlaceThing(thing, pawn.PositionHeld, pawn.MapHeld, ThingPlaceMode.Near, out var lastResultingThing); lastResultingThing.SetForbidden(value: true); } if (pawn.Corpse != null) { pawn.Corpse.HitPoints = Mathf.Max(Mathf.RoundToInt((float)pawn.Corpse.MaxHitPoints * corpseDamagePct), 10); } if (compProps?.corpseHediff != null) { pawn.health.AddHediff(compProps.corpseHediff); } base.Notify_PawnDied(dinfo, culprit); } public override void PreRemoved() { pawn.MapHeld?.mapPawns.DeregisterShambler(pawn); base.PreRemoved(); } public override void PostRemoved() { base.PostRemoved(); if (pawn.Dead) return; if (IsRising) CancelRising(); if (pawn.IsMutant) { if (pawn.mutant.HasTurned) pawn.mutant.Revert(); else pawn.mutant = null; } } public override IEnumerable GetGizmos() { foreach (Gizmo gizmo in base.GetGizmos()) { yield return gizmo; } if (DebugSettings.ShowDevGizmos && pawn.Downed && !IsRising) { var command_Action = new Command_Action { defaultLabel = "Self Raise", action = () => StartRising() }; yield return command_Action; } } public override string GetInspectString() { if (ShouldSelfRaise()) return "ShamblerRegenerating".Translate(); if (IsRising) return "ShamblerRising".Translate(); if (pawn.CurJobDef == JobDefOf.Wait_Wander || pawn.CurJobDef == JobDefOf.Wait_MaintainPosture) return "ShamblerStanding".Translate(); if (pawn.CurJobDef == JobDefOf.GotoWander || pawn.CurJobDef == JobDefOf.Goto) return "ShamblerShuffling".Translate(); return ""; } public override void ExposeData() { base.ExposeData(); Scribe_Values.Look(ref alertTimer, "alertTimer", 0f); Scribe_Values.Look(ref nextTargetCheckTick, "nextTargetCheckTick", 0); Scribe_References.Look(ref alertedTarget, "alertedTarget"); Scribe_Values.Look(ref resurrectTimer, "resurrectTimer", 0f); Scribe_Values.Look(ref selfRaiseTimer, "selfRaiseTimer", 0f); Scribe_Values.Look(ref headRotation, "headRotation", 0f); } } }