Files
ArachnaeSwarm/Source/ArachnaeSwarm/Hediff_ConfigurableMutant.cs
2025-09-10 14:47:33 +08:00

373 lines
14 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.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<HediffComp_ConfigurableMutant>();
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<Gizmo> 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);
}
}
}