Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c43bc0f0bb | ||
|
|
b954781675 | ||
|
|
acfc078f91 | ||
|
|
b28594c604 | ||
|
|
7a6d5380e1 | ||
|
|
b6f9e6193a |
Binary file not shown.
@@ -6,17 +6,17 @@
|
|||||||
<defName>ARA_Flyer_TrackingCharge</defName>
|
<defName>ARA_Flyer_TrackingCharge</defName>
|
||||||
<thingClass>ArachnaeSwarm.PawnFlyer_TrackingCharge</thingClass>
|
<thingClass>ArachnaeSwarm.PawnFlyer_TrackingCharge</thingClass>
|
||||||
<pawnFlyer>
|
<pawnFlyer>
|
||||||
<flightDurationMin>0.5</flightDurationMin>
|
<flightSpeed>0.5</flightSpeed>
|
||||||
<flightDurationMax>2.0</flightDurationMax>
|
<heightFactor>0</heightFactor>
|
||||||
</pawnFlyer>
|
</pawnFlyer>
|
||||||
</ThingDef>
|
</ThingDef>
|
||||||
|
|
||||||
<!-- 2. The final Ability Definition -->
|
<!-- 2. The final Ability Definition -->
|
||||||
<AbilityDef>
|
<AbilityDef>
|
||||||
<defName>ARA_Ability_TrackingCharge</defName>
|
<defName>ARA_Ability_TrackingCharge</defName>
|
||||||
<label>Tracking Charge</label>
|
<label>追踪冲撞</label>
|
||||||
<description>Launch yourself towards a target, dealing damage to everything in your path. The damage increases the further you travel.</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>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<canTargetMechs>true</canTargetMechs>
|
<canTargetMechs>true</canTargetMechs>
|
||||||
<canTargetSelf>false</canTargetSelf>
|
<canTargetSelf>false</canTargetSelf>
|
||||||
</targetParams>
|
</targetParams>
|
||||||
<range>30</range>
|
<range>60</range>
|
||||||
<warmupTime>1.0</warmupTime>
|
<warmupTime>1.0</warmupTime>
|
||||||
</verbProperties>
|
</verbProperties>
|
||||||
<comps>
|
<comps>
|
||||||
@@ -36,9 +36,16 @@
|
|||||||
<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>Blunt</collisionDamageDef>
|
<collisionDamageDef>Blunt</collisionDamageDef>
|
||||||
<flyerDef>ARA_Flyer_TrackingCharge</flyerDef>
|
<flyerDef>ARA_Flyer_TrackingCharge</flyerDef>
|
||||||
|
<collisionRadius>1.5</collisionRadius> <!-- Larger collision radius -->
|
||||||
|
<impactSound>Pawn_Melee_BigBash_HitPawn</impactSound>
|
||||||
|
<damageHostileOnly>true</damageHostileOnly> <!-- Set to false to damage everyone in the path -->
|
||||||
|
</li>
|
||||||
|
<li Class="CompProperties_AbilityEffecterOnCaster">
|
||||||
|
<effecterDef>WarTrumpet</effecterDef>
|
||||||
|
<maintainTicks>20</maintainTicks> <!-- Long enough for the "2nd wave" to spawn -->
|
||||||
</li>
|
</li>
|
||||||
</comps>
|
</comps>
|
||||||
</AbilityDef>
|
</AbilityDef>
|
||||||
@@ -147,6 +147,9 @@
|
|||||||
<apparelTags>
|
<apparelTags>
|
||||||
</apparelTags>
|
</apparelTags>
|
||||||
<apparelMoney>0</apparelMoney>
|
<apparelMoney>0</apparelMoney>
|
||||||
|
<abilities>
|
||||||
|
<li>ARA_Ability_TrackingCharge</li>
|
||||||
|
</abilities>
|
||||||
</PawnKindDef>
|
</PawnKindDef>
|
||||||
<PawnKindDef ParentName="ArachnaeNodeABasePawnKind">
|
<PawnKindDef ParentName="ArachnaeNodeABasePawnKind">
|
||||||
<defName>ArachnaeNode_Race_WeaponSmith</defName>
|
<defName>ArachnaeNode_Race_WeaponSmith</defName>
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ namespace ArachnaeSwarm
|
|||||||
public float inertiaDistance = 3f;
|
public float inertiaDistance = 3f;
|
||||||
public DamageDef collisionDamageDef;
|
public DamageDef collisionDamageDef;
|
||||||
public ThingDef flyerDef;
|
public ThingDef flyerDef;
|
||||||
|
public float collisionRadius = 1.5f;
|
||||||
|
public SoundDef impactSound;
|
||||||
|
public bool damageHostileOnly = true;
|
||||||
|
|
||||||
public CompProperties_TrackingCharge()
|
public CompProperties_TrackingCharge()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Reflection;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Verse.AI;
|
using Verse.AI;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Verse.Sound;
|
||||||
|
|
||||||
namespace ArachnaeSwarm
|
namespace ArachnaeSwarm
|
||||||
{
|
{
|
||||||
@@ -17,12 +18,14 @@ namespace ArachnaeSwarm
|
|||||||
public float inertiaDistance;
|
public float inertiaDistance;
|
||||||
public DamageDef collisionDamageDef;
|
public DamageDef collisionDamageDef;
|
||||||
public LocalTargetInfo primaryTarget;
|
public LocalTargetInfo primaryTarget;
|
||||||
|
public float collisionRadius;
|
||||||
|
public SoundDef impactSound;
|
||||||
|
public bool damageHostileOnly;
|
||||||
|
public int maxFlightTicks;
|
||||||
|
|
||||||
// --- Internal state ---
|
// --- Internal state ---
|
||||||
private Vector3 currentSpeed;
|
|
||||||
private float distanceTraveled = 0f;
|
|
||||||
private bool homing = true;
|
private bool homing = true;
|
||||||
private int inertiaTicks = -1;
|
private bool hasHitPrimaryTarget = false;
|
||||||
private Vector3 exactPosition;
|
private Vector3 exactPosition;
|
||||||
|
|
||||||
// --- Reflection Fields ---
|
// --- Reflection Fields ---
|
||||||
@@ -66,7 +69,7 @@ namespace ArachnaeSwarm
|
|||||||
pawn.Destroy();
|
pawn.Destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SpawnSetup(Map map, bool respawningAfterLoad)
|
public override void SpawnSetup(Map map, bool respawningAfterLoad)
|
||||||
{
|
{
|
||||||
base.SpawnSetup(map, respawningAfterLoad);
|
base.SpawnSetup(map, respawningAfterLoad);
|
||||||
@@ -75,82 +78,80 @@ namespace ArachnaeSwarm
|
|||||||
this.exactPosition = base.DrawPos;
|
this.exactPosition = base.DrawPos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Tick()
|
protected override void Tick()
|
||||||
{
|
{
|
||||||
int ticksFlying = (int)TicksFlyingInfo.GetValue(this);
|
// --- THE CORRECT APPROACH ---
|
||||||
|
// Let the base class handle all flight mechanics (position, timing, etc.)
|
||||||
|
// We only intervene to do two things:
|
||||||
|
// 1. Continuously update the destination to "steer" the flyer.
|
||||||
|
// 2. Perform our own collision checks (for primary target and AOE).
|
||||||
|
|
||||||
if (ticksFlying == 0)
|
if (homing && primaryTarget.HasThing && primaryTarget.Thing.Spawned)
|
||||||
{
|
{
|
||||||
Vector3 startVec = (Vector3)StartVecInfo.GetValue(this);
|
// Steer the flyer by constantly updating its destination cell.
|
||||||
IntVec3 destCell = (IntVec3)DestCellInfo.GetValue(this);
|
DestCellInfo.SetValue(this, primaryTarget.Thing.Position);
|
||||||
Vector3 destinationPos = GenThing.TrueCenter(destCell, Rot4.North, this.FlyingThing.def.size, this.def.Altitude);
|
|
||||||
Vector3 direction = (destinationPos - startVec).normalized;
|
|
||||||
this.currentSpeed = direction * this.def.pawnFlyer.flightSpeed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.exactPosition += this.currentSpeed;
|
|
||||||
this.distanceTraveled += this.currentSpeed.magnitude;
|
|
||||||
|
|
||||||
if (inertiaTicks > 0)
|
// --- Primary Target Collision Check ---
|
||||||
|
if (!hasHitPrimaryTarget && primaryTarget.HasThing && primaryTarget.Thing.Spawned)
|
||||||
{
|
{
|
||||||
inertiaTicks--;
|
if ((this.DrawPos - primaryTarget.Thing.DrawPos).sqrMagnitude < this.collisionRadius * this.collisionRadius)
|
||||||
if (inertiaTicks <= 0) { Land(); return; }
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (homing && primaryTarget.HasThing && primaryTarget.Thing.Spawned)
|
|
||||||
{
|
{
|
||||||
Vector3 desiredDirection = (primaryTarget.Thing.DrawPos - this.exactPosition).normalized;
|
// --- Impact! ---
|
||||||
this.currentSpeed = Vector3.RotateTowards(this.currentSpeed, desiredDirection, this.homingSpeed * 0.017f, 999f).normalized * this.def.pawnFlyer.flightSpeed;
|
if (this.impactSound != null)
|
||||||
}
|
{
|
||||||
else
|
SoundStarter.PlayOneShot(this.impactSound, new TargetInfo(this.Position, this.Map));
|
||||||
{
|
}
|
||||||
homing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
float calculatedDamage = this.initialDamage + (this.distanceTraveled * this.damagePerTile);
|
Vector3 startPosition = (Vector3)StartVecInfo.GetValue(this);
|
||||||
var dinfo = new DamageInfo(this.collisionDamageDef, calculatedDamage, 1f, -1, this.FlyingPawn);
|
float distance = (this.DrawPos - startPosition).magnitude;
|
||||||
|
float calculatedDamage = this.initialDamage + (distance * this.damagePerTile);
|
||||||
|
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)
|
|
||||||
{
|
|
||||||
primaryTarget.Thing.TakeDamage(dinfo);
|
primaryTarget.Thing.TakeDamage(dinfo);
|
||||||
|
hasHitPrimaryTarget = true;
|
||||||
|
|
||||||
homing = false;
|
homing = false;
|
||||||
this.inertiaTicks = (int)(this.inertiaDistance / this.currentSpeed.magnitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var thing in GenRadial.RadialDistinctThingsAround(this.exactPosition.ToIntVec3(), this.Map, 1.0f, false))
|
Vector3 direction = (this.DrawPos - startPosition).normalized;
|
||||||
|
IntVec3 inertiaEndPos = (this.DrawPos + (direction * this.inertiaDistance)).ToIntVec3();
|
||||||
|
DestCellInfo.SetValue(this, inertiaEndPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- AOE Damage Logic ---
|
||||||
|
float distanceTravelled = ((Vector3)StartVecInfo.GetValue(this) - this.DrawPos).magnitude;
|
||||||
|
float currentAOEDamage = this.initialDamage + (distanceTravelled * this.damagePerTile);
|
||||||
|
|
||||||
|
foreach (var thing in GenRadial.RadialDistinctThingsAround(this.Position, this.Map, this.collisionRadius, false))
|
||||||
|
{
|
||||||
|
if (thing != this.FlyingPawn && thing != this && thing != primaryTarget.Thing)
|
||||||
{
|
{
|
||||||
if (thing == this.FlyingPawn || thing == this || thing == primaryTarget.Thing) continue;
|
if (thing is Pawn pawn && !pawn.Downed)
|
||||||
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);
|
if (!this.damageHostileOnly || pawn.HostileTo(this.FlyingPawn))
|
||||||
|
{
|
||||||
|
var aoeDinfo = new DamageInfo(this.collisionDamageDef, currentAOEDamage, 1f, -1, this.FlyingPawn);
|
||||||
|
pawn.TakeDamage(aoeDinfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (thing.def.destroyable && thing.def.building != null)
|
||||||
|
{
|
||||||
|
var aoeDinfo = new DamageInfo(this.collisionDamageDef, currentAOEDamage, 1f, -1, this.FlyingPawn);
|
||||||
|
thing.TakeDamage(aoeDinfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
DestCellInfo.SetValue(this, this.exactPosition.ToIntVec3());
|
|
||||||
TicksFlightTimeInfo.SetValue(this, ticksFlying + 2);
|
|
||||||
}
|
|
||||||
catch (System.Exception ex)
|
|
||||||
{
|
|
||||||
Log.ErrorOnce($"Exception during reflection in PawnFlyer_TrackingCharge: {ex}", this.thingIDNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
TicksFlyingInfo.SetValue(this, ticksFlying + 1);
|
|
||||||
|
|
||||||
int flightTime = (int)TicksFlightTimeInfo.GetValue(this);
|
// Let the base class do its thing. This is crucial.
|
||||||
if (!this.exactPosition.ToIntVec3().InBounds(this.Map) || ticksFlying > flightTime * 2)
|
base.Tick();
|
||||||
{
|
|
||||||
Land();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Land()
|
protected override void RespawnPawn()
|
||||||
{
|
{
|
||||||
if (this.Destroyed) return;
|
// This is the correct place to call the base method.
|
||||||
base.RespawnPawn();
|
base.RespawnPawn();
|
||||||
this.Destroy();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,11 +21,21 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- This is now a fully custom Thing, so we spawn it directly ---
|
// --- This is now a fully custom Thing, so we spawn it directly ---
|
||||||
var trackingCharge = (PawnFlyer_TrackingCharge)ThingMaker.MakeThing(props.flyerDef);
|
var trackingCharge = (PawnFlyer_TrackingCharge)ThingMaker.MakeThing(props.flyerDef);
|
||||||
|
|
||||||
@@ -36,10 +46,20 @@ namespace ArachnaeSwarm
|
|||||||
trackingCharge.inertiaDistance = props.inertiaDistance;
|
trackingCharge.inertiaDistance = props.inertiaDistance;
|
||||||
trackingCharge.collisionDamageDef = props.collisionDamageDef;
|
trackingCharge.collisionDamageDef = props.collisionDamageDef;
|
||||||
trackingCharge.primaryTarget = this.currentTarget;
|
trackingCharge.primaryTarget = this.currentTarget;
|
||||||
|
trackingCharge.collisionRadius = props.collisionRadius;
|
||||||
|
trackingCharge.impactSound = props.impactSound;
|
||||||
|
trackingCharge.damageHostileOnly = props.damageHostileOnly;
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
|
// --- FIX for Comp Effects ---
|
||||||
|
// --- The Standard Pattern to trigger Comps like EffecterOnCaster ---
|
||||||
|
// After our custom verb logic (spawning the flyer) is done,
|
||||||
|
// we call the ability's Activate method with invalid targets.
|
||||||
|
// This triggers the standard Comp cycle without re-casting the verb.
|
||||||
|
this.ability.Activate(LocalTargetInfo.Invalid, LocalTargetInfo.Invalid);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user