This commit is contained in:
2025-09-10 12:14:30 +08:00
parent 54de15873f
commit cc5206c3c6
10 changed files with 760 additions and 149 deletions

View File

@@ -189,6 +189,7 @@
<Compile Include="Hediff_NecroticVirus_Configurable.cs" />
<Compile Include="NecroticTransformationUtility.cs" />
<Compile Include="ProphecyGearEffect.cs" />
<Compile Include="Hediff_ConfigurableMutant.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 自定义清理任务删除obj文件夹中的临时文件 -->

View File

@@ -10,6 +10,9 @@ namespace ArachnaeSwarm
{
// 在XML中需要指定的MutantDef的defName
public MutantDef mutantDef;
// 在XML中配置触发转变所需的严重性阈值
public float triggerSeverity = 0.7f;
public HediffCompProperties_NecroticTransformation()
{

View File

@@ -0,0 +1,374 @@
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方法
pawn.jobs.EndCurrentJob(JobCondition.InterruptForced);
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);
}
}
}

View File

@@ -36,6 +36,12 @@ namespace ArachnaeSwarm
return;
}
// 检查严重性是否达到XML中配置的阈值
if (this.Severity < comp.Props.triggerSeverity)
{
return;
}
try
{
if (pawn.Corpse == null || pawn.Corpse.Destroyed)
@@ -46,9 +52,10 @@ namespace ArachnaeSwarm
Map map = pawn.Corpse.Map;
IntVec3 position = pawn.Corpse.Position;
if (!MutantUtility.CanResurrectAsShambler(pawn.Corpse))
// 使用我们自己的、更安全的检查方法
if (!NecroticTransformationUtility.CanResurrect(pawn.Corpse))
{
Log.Warning($"[NecroticVirus] Cannot resurrect {pawn.LabelShort} as a shambler-like creature.");
Log.Warning($"[NecroticVirus] Pawn {pawn.LabelShort} does not meet conditions for resurrection.");
return;
}

View File

@@ -5,9 +5,41 @@ namespace ArachnaeSwarm
{
public static class NecroticTransformationUtility
{
/// <summary>
/// 检查一个尸体是否可以被我们的逻辑转化为变异体。
/// 这是对原版 MutantUtility.CanResurrectAsShambler 的复制,但移除了对 canBecomeShambler 的检查。
/// </summary>
public static bool CanResurrect(Corpse corpse, bool ignoreIndoors = false)
{
if (corpse?.InnerPawn == null) return false;
if (!corpse.InnerPawn.RaceProps.IsFlesh) return false;
// 我们移除了对 corpse.InnerPawn.RaceProps.canBecomeShambler 的检查
if (corpse.InnerPawn.IsMutant) return false;
if (corpse is UnnaturalCorpse) return false;
Room room = corpse.PositionHeld.GetRoom(corpse.MapHeld);
if (room != null && !ignoreIndoors && corpse.PositionHeld.Roofed(corpse.MapHeld) && (room.ProperRoom || room.IsDoorway))
{
return false;
}
if (!Find.Storyteller.difficulty.childShamblersAllowed && !corpse.InnerPawn.ageTracker.Adult)
{
return false;
}
Hediff_DeathRefusal firstHediff = corpse.InnerPawn.health.hediffSet.GetFirstHediff<Hediff_DeathRefusal>();
if (firstHediff != null && (firstHediff.InProgress || firstHediff.UsesLeft > 0))
{
return false;
}
return true;
}
/// <summary>
/// 将一个Pawn复活为指定的自定义变异体.
/// 这个方法是模仿原版 MutantUtility.ResurrectAsShambler,
/// 这个方法是模仿原版 MutantUtility.ResurrectAsShambler,
/// 但允许传入一个自定义的 MutantDef.
/// </summary>
public static void ResurrectAsCustomMutant(Pawn pawn, MutantDef mutantDef, Faction faction = null, int lifespanTicks = -1)