373 lines
14 KiB
C#
373 lines
14 KiB
C#
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);
|
||
}
|
||
}
|
||
} |