This commit is contained in:
2025-09-07 17:11:53 +08:00
parent b6f9e6193a
commit 7a6d5380e1
4 changed files with 41 additions and 13 deletions

Binary file not shown.

View File

@@ -6,8 +6,8 @@
<defName>ARA_Flyer_TrackingCharge</defName> <defName>ARA_Flyer_TrackingCharge</defName>
<thingClass>ArachnaeSwarm.PawnFlyer_TrackingCharge</thingClass> <thingClass>ArachnaeSwarm.PawnFlyer_TrackingCharge</thingClass>
<pawnFlyer> <pawnFlyer>
<flightSpeed>0.1</flightSpeed> <flightSpeed>0.5</flightSpeed>
<heightFactor>0.1</heightFactor> <heightFactor>0</heightFactor>
</pawnFlyer> </pawnFlyer>
</ThingDef> </ThingDef>
@@ -16,7 +16,7 @@
<defName>ARA_Ability_TrackingCharge</defName> <defName>ARA_Ability_TrackingCharge</defName>
<label>追踪冲撞</label> <label>追踪冲撞</label>
<description>阿拉克涅盾头种对目标发起蓄势冲撞,对路径上的一切造成伤害。飞行的距离越远,伤害越高。</description> <description>阿拉克涅盾头种对目标发起蓄势冲撞,对路径上的一切造成伤害。飞行的距离越远,伤害越高。</description>
<iconPath>UI/Abilities/Charge</iconPath> <!-- Placeholder Icon --> <iconPath>UI/Commands/WarTrumpet</iconPath> <!-- Placeholder Icon -->
<cooldownTicksRange>600</cooldownTicksRange> <cooldownTicksRange>600</cooldownTicksRange>
<verbProperties> <verbProperties>
<verbClass>ArachnaeSwarm.Verb_CastAbilityTrackingCharge</verbClass> <verbClass>ArachnaeSwarm.Verb_CastAbilityTrackingCharge</verbClass>
@@ -36,8 +36,8 @@
<homingSpeed>1.5</homingSpeed> <homingSpeed>1.5</homingSpeed>
<initialDamage>15</initialDamage> <initialDamage>15</initialDamage>
<damagePerTile>2</damagePerTile> <damagePerTile>2</damagePerTile>
<inertiaDistance>4</inertiaDistance> <inertiaDistance>6</inertiaDistance>
<collisionDamageDef>Crush</collisionDamageDef> <collisionDamageDef>Blunt</collisionDamageDef>
<flyerDef>ARA_Flyer_TrackingCharge</flyerDef> <flyerDef>ARA_Flyer_TrackingCharge</flyerDef>
</li> </li>
</comps> </comps>

View File

@@ -17,6 +17,7 @@ namespace ArachnaeSwarm
public float inertiaDistance; public float inertiaDistance;
public DamageDef collisionDamageDef; public DamageDef collisionDamageDef;
public LocalTargetInfo primaryTarget; public LocalTargetInfo primaryTarget;
public int maxFlightTicks;
// --- Internal state --- // --- Internal state ---
private Vector3 currentSpeed; private Vector3 currentSpeed;
@@ -24,6 +25,7 @@ namespace ArachnaeSwarm
private bool homing = true; private bool homing = true;
private int inertiaTicks = -1; private int inertiaTicks = -1;
private Vector3 exactPosition; private Vector3 exactPosition;
private bool hasHitPrimaryTarget = false;
// --- Reflection Fields --- // --- Reflection Fields ---
private static FieldInfo TicksFlyingInfo; private static FieldInfo TicksFlyingInfo;
@@ -112,8 +114,9 @@ namespace ArachnaeSwarm
float calculatedDamage = this.initialDamage + (this.distanceTraveled * this.damagePerTile); float calculatedDamage = this.initialDamage + (this.distanceTraveled * this.damagePerTile);
var dinfo = new DamageInfo(this.collisionDamageDef, calculatedDamage, 1f, -1, this.FlyingPawn); var dinfo = new DamageInfo(this.collisionDamageDef, calculatedDamage, 1f, -1, this.FlyingPawn);
if (homing && primaryTarget.HasThing && (this.exactPosition - primaryTarget.Thing.DrawPos).sqrMagnitude < 1.5f * 1.5f) if (!hasHitPrimaryTarget && homing && primaryTarget.HasThing && primaryTarget.Thing.Spawned && (this.exactPosition - primaryTarget.Thing.DrawPos).sqrMagnitude < 1.5f * 1.5f)
{ {
hasHitPrimaryTarget = true; // Mark as hit to prevent re-triggering
primaryTarget.Thing.TakeDamage(dinfo); primaryTarget.Thing.TakeDamage(dinfo);
homing = false; homing = false;
this.inertiaTicks = (int)(this.inertiaDistance / this.currentSpeed.magnitude); this.inertiaTicks = (int)(this.inertiaDistance / this.currentSpeed.magnitude);
@@ -121,7 +124,9 @@ namespace ArachnaeSwarm
foreach (var thing in GenRadial.RadialDistinctThingsAround(this.exactPosition.ToIntVec3(), this.Map, 1.0f, false)) foreach (var thing in GenRadial.RadialDistinctThingsAround(this.exactPosition.ToIntVec3(), this.Map, 1.0f, false))
{ {
if (thing == this.FlyingPawn || thing == this || thing == primaryTarget.Thing) continue; // Avoid damaging self or the primary target (which is handled above)
if (thing == this.FlyingPawn || thing == this || (hasHitPrimaryTarget && thing == primaryTarget.Thing)) continue;
if (thing is Pawn pawn && !pawn.Downed && pawn.HostileTo(this.FlyingPawn)) pawn.TakeDamage(dinfo); if (thing is Pawn pawn && !pawn.Downed && pawn.HostileTo(this.FlyingPawn)) pawn.TakeDamage(dinfo);
else if (thing.def.destroyable && thing.def.building != null) thing.TakeDamage(dinfo); else if (thing.def.destroyable && thing.def.building != null) thing.TakeDamage(dinfo);
} }
@@ -129,8 +134,10 @@ namespace ArachnaeSwarm
try try
{ {
// We still need to update the destination for the base flyer logic to work correctly
DestCellInfo.SetValue(this, this.exactPosition.ToIntVec3()); DestCellInfo.SetValue(this, this.exactPosition.ToIntVec3());
TicksFlightTimeInfo.SetValue(this, ticksFlying + 2); // --- FIX for infinite flight ---
// The old TicksFlightTimeInfo update logic is removed.
} }
catch (System.Exception ex) catch (System.Exception ex)
{ {
@@ -139,8 +146,8 @@ namespace ArachnaeSwarm
TicksFlyingInfo.SetValue(this, ticksFlying + 1); TicksFlyingInfo.SetValue(this, ticksFlying + 1);
int flightTime = (int)TicksFlightTimeInfo.GetValue(this); // --- RELIABLE TIMEOUT & BOUNDS CHECK ---
if (!this.exactPosition.ToIntVec3().InBounds(this.Map) || ticksFlying > flightTime * 2) if (ticksFlying > this.maxFlightTicks || !this.exactPosition.ToIntVec3().InBounds(this.Map))
{ {
Land(); Land();
} }

View File

@@ -21,6 +21,16 @@ namespace ArachnaeSwarm
return false; return false;
} }
// --- Best Practice: Cache Map and Position FIRST ---
// Per MCP analysis, Caster.Map is the most reliable source.
// Cache this before ANY other logic.
Map map = this.Caster.Map;
if (map == null)
{
Log.Error($"Verb_CastAbilityTrackingCharge: Caster {this.Caster.LabelCap} has a null map. Cannot cast.");
return false;
}
if (this.CasterPawn == null || !this.CasterPawn.Spawned) if (this.CasterPawn == null || !this.CasterPawn.Spawned)
{ {
return false; return false;
@@ -37,9 +47,20 @@ namespace ArachnaeSwarm
trackingCharge.collisionDamageDef = props.collisionDamageDef; trackingCharge.collisionDamageDef = props.collisionDamageDef;
trackingCharge.primaryTarget = this.currentTarget; trackingCharge.primaryTarget = this.currentTarget;
// --- Dynamic Timeout Calculation ---
float flightSpeed = props.flyerDef.pawnFlyer.flightSpeed;
if (flightSpeed <= 0)
{
Log.Error("flyerDef has no flightSpeed, cannot calculate timeout.");
return false;
}
// Timeout is twice the time it would take to fly the max range in a straight line
int maxTicks = (int)((this.verbProps.range / flightSpeed) * 2.0f);
trackingCharge.maxFlightTicks = maxTicks;
// Setup and spawn // Setup and spawn
trackingCharge.StartFlight(this.CasterPawn, this.currentTarget.Cell); trackingCharge.StartFlight(this.CasterPawn, this.currentTarget.Cell);
GenSpawn.Spawn(trackingCharge, this.CasterPawn.Position, this.CasterPawn.Map); GenSpawn.Spawn(trackingCharge, this.CasterPawn.Position, map); // Use the cached map
return true; return true;
} }