暂存
This commit is contained in:
@@ -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文件夹中的临时文件 -->
|
||||
|
||||
@@ -10,6 +10,9 @@ namespace ArachnaeSwarm
|
||||
{
|
||||
// 在XML中需要指定的MutantDef的defName
|
||||
public MutantDef mutantDef;
|
||||
|
||||
// 在XML中配置触发转变所需的严重性阈值
|
||||
public float triggerSeverity = 0.7f;
|
||||
|
||||
public HediffCompProperties_NecroticTransformation()
|
||||
{
|
||||
|
||||
374
Source/ArachnaeSwarm/Hediff_ConfigurableMutant.cs
Normal file
374
Source/ArachnaeSwarm/Hediff_ConfigurableMutant.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user