Issue #27: projectiles carry the weapon's real damage type (ballistic)
Playtest matchlog forensics: across 2,591 applied-damage events in two rounds, Ballistic (type 1) damage NEVER appeared -- Collision/Explosive/ Laser/Energy only -- despite 136 projectile impacts and autocannon/Gauss mechs. The weapon parses its authentic damageData.damageType correctly (mechweap.cpp:316 maps "BallisticDamage" -> 1), but BTPushProjectile never carried the type and every projectile-impact site in mech4.cpp hardcoded ExplosiveDamageType. So autocannon/Gauss rounds were scaled against armor by damageScale[Explosive] instead of damageScale[Ballistic] (the model indexes a distinct scale cell per damage type) -- every ballistic weapon did the wrong damage all night. Lasers/PPCs were unaffected (their SendDamageMessage path carries the weapon's own damageData); missiles happen to BE Explosive so only autocannon & Gauss were mis-scaled. BTProjectile gains a damageType field, threaded from the firing weapon (damageData.damageType) at launch through to the impact dispatch; missiles resolve Explosive from their own streamed type, autocannon/ Gauss now resolve Ballistic. The missile-splash template stays Explosive. Enum is anonymous so the field casts to Enumeration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
76eaddc4a8
commit
02a41f165f
@@ -258,6 +258,19 @@ Band effects: `MechDeathHandler::Tick` fires the CURRENT band descriptor on any
|
||||
(the binary's changed-flag semantics, workflow-verified) — a damaged mech under fire smokes/
|
||||
burns per hit; the earlier crossing-only gate was over-tight (the metronome was SHKWAVE).
|
||||
|
||||
## Ballistic damage type -- FIXED (2026-07-23, issue #27) [T2]
|
||||
Playtest matchlog forensics (2,591 applied-damage events over 2 rounds): the damageType
|
||||
histogram had Collision/Explosive/Laser/Energy but **Ballistic (type 1) NEVER appeared**,
|
||||
despite 136 projectile impacts + autocannon/Gauss mechs. The weapon parses its authentic
|
||||
`damageData.damageType` right (mechweap.cpp:316, "BallisticDamage"->1), but `BTPushProjectile`
|
||||
dropped it and every mech4.cpp projectile-impact site hardcoded `ExplosiveDamageType` -- so
|
||||
autocannon/Gauss scaled against armor via `damageScale[Explosive]` not `damageScale[Ballistic]`
|
||||
(distinct cells: `damageLevel += amount * damageScale[type]`). Lasers/PPCs were correct (their
|
||||
`SendDamageMessage` carries the weapon damageData); only the projectile path flattened. FIX:
|
||||
`BTProjectile` gains a `damageType` field, threaded from the weapon at launch through to impact
|
||||
(missiles still resolve Explosive from their own type; autocannon/Gauss now Ballistic). The
|
||||
missile-SPLASH template stays Explosive. [T2 verify: an autocannon mech produces type=1 DMG.]
|
||||
|
||||
## Damage delivery + the real damage model
|
||||
`Entity::TakeDamageMessage(id, size, inflictingEntityID, zone, Damage&)` → `target->Dispatch`.
|
||||
**Base handler IGNORES zone==−1** (`Entity::TakeDamageMessageHandler`, ENTITY.cpp:878 — returns on
|
||||
|
||||
@@ -805,6 +805,10 @@ struct BTProjectile {
|
||||
Scalar damage; // <= 0 -> VISUAL-ONLY round (replicant-side salvo mirror)
|
||||
int guided; // 1 = missile (seeker loft + steering); 0 = ballistic (straight)
|
||||
Entity *shooter; // the firing mech (messmgr explosion bundling at impact)
|
||||
int damageType; // the FIRING WEAPON's authentic damageData.damageType
|
||||
// (issue: was hardcoded Explosive at impact, so
|
||||
// autocannon/Gauss Ballistic rounds scaled wrong --
|
||||
// Ballistic type never reached the armor model).
|
||||
int weaponSubsys; // firing weapon's roster index (-1 = unthreaded) --
|
||||
// resolves the weapon's ExplosionModelFile (mslhit/
|
||||
// acanhit) in the messmgr, task #7 bundling
|
||||
@@ -1143,7 +1147,7 @@ void
|
||||
void
|
||||
BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, const Point3D &targetPos,
|
||||
Scalar speed, Scalar damage, const Vector3D *launch_velocity, int guided,
|
||||
int weapon_subsys, int splash_burst, int muzzle_seg)
|
||||
int weapon_subsys, int splash_burst, int muzzle_seg, int damage_type)
|
||||
{
|
||||
// MUZZLE (muzzle wave, 2026-07-12): the passed muzzle is now the weapon's
|
||||
// AUTHENTIC mount segment (GetMuzzlePoint reads the real segmentIndex --
|
||||
@@ -1267,6 +1271,7 @@ void
|
||||
p.aimOffsetY = (target != 0 && BTIsRegisteredMech((Entity *)target))
|
||||
? (tpos.y - ((Mech *)target)->localOrigin.linearPosition.y) : 0.0f;
|
||||
p.damage = damage;
|
||||
p.damageType = damage_type; // weapon's authentic type (issue fix)
|
||||
p.guided = guided; // autocannon shells fly straight (no seeker in the binary's plain Projectile)
|
||||
p.shooter = (Entity *)shooter;
|
||||
p.weaponSubsys = weapon_subsys;
|
||||
@@ -1471,7 +1476,7 @@ static void
|
||||
// zone from the cylinder hit-location table. (Previously this
|
||||
// aimed the internal vital zone directly -> invisible insta-kill.)
|
||||
Damage dmg;
|
||||
dmg.damageType = Damage::ExplosiveDamageType;
|
||||
dmg.damageType = (Enumeration)p.damageType; // issue: was hardcoded Explosive
|
||||
dmg.damageAmount = p.damage;
|
||||
dmg.burstCount = 1;
|
||||
dmg.impactPoint = p.pos;
|
||||
@@ -1557,7 +1562,7 @@ static void
|
||||
// The icon's own handler maps -1 -> 0 and runs the death
|
||||
// transition (-> the FULL 'trkdead' explosion, res 32).
|
||||
Damage dmg;
|
||||
dmg.damageType = Damage::ExplosiveDamageType;
|
||||
dmg.damageType = (Enumeration)p.damageType; // issue: was hardcoded Explosive
|
||||
dmg.damageAmount = p.damage;
|
||||
dmg.burstCount = 1;
|
||||
dmg.impactPoint = p.pos;
|
||||
|
||||
@@ -73,7 +73,8 @@
|
||||
extern void BTPushProjectile(const Point3D &muzzle, void *shooter, void *target,
|
||||
const Point3D &targetPos, Scalar speed, Scalar damage,
|
||||
const Vector3D *launch_velocity = 0, int guided = 1,
|
||||
int weapon_subsys = -1, int splash_burst = 0, int muzzle_seg = -1);
|
||||
int weapon_subsys = -1, int splash_burst = 0, int muzzle_seg = -1,
|
||||
int damage_type = 2 /*ExplosiveDamageType default*/);
|
||||
|
||||
//###########################################################################
|
||||
// Port-side salvo replication state (missile-visibility wave).
|
||||
@@ -318,7 +319,8 @@ void MissileLauncher::FireWeapon()
|
||||
&launchVelocity, 1,
|
||||
subsystemID /*messmgr explosion bundling at impact (task #7)*/,
|
||||
(i == 0) ? nmiss : 0 /*salvo-lead: cluster splash baseBurst*/,
|
||||
GetSegmentIndex() /*task #67: launch through the MOUNT frame (torso twist)*/);
|
||||
GetSegmentIndex() /*task #67: launch through the MOUNT frame (torso twist)*/,
|
||||
(int)damageData.damageType /*missile = Explosive (authentic)*/);
|
||||
|
||||
// Salvo replication (missile-visibility wave): bump the fire counter + stamp
|
||||
// the aim point; the extended update record carries both to peer nodes.
|
||||
|
||||
@@ -97,7 +97,8 @@
|
||||
extern void BTPushProjectile(const Point3D &muzzle, void *shooter, void *target,
|
||||
const Point3D &targetPos, Scalar speed, Scalar damage,
|
||||
const Vector3D *launch_velocity = 0, int guided = 1,
|
||||
int weapon_subsys = -1, int splash_burst = 0, int muzzle_seg = -1); // AC: no cluster splash (default 0)
|
||||
int weapon_subsys = -1, int splash_burst = 0, int muzzle_seg = -1,
|
||||
int damage_type = 2 /*ExplosiveDamageType default*/); // AC: no cluster splash (default 0)
|
||||
|
||||
|
||||
//#############################################################################
|
||||
@@ -890,7 +891,9 @@ void
|
||||
+ launchVelocity.z*launchVelocity.z);
|
||||
BTPushProjectile(muzzle, o, target, targetPos, speed, damageData.damageAmount,
|
||||
0 /*aim straight at the pick*/, 0 /*BALLISTIC: no seeker on a shell*/,
|
||||
subsystemID /*messmgr explosion bundling at impact (task #7)*/);
|
||||
subsystemID /*messmgr explosion bundling at impact (task #7)*/,
|
||||
0 /*splash_burst*/, -1 /*muzzle_seg*/,
|
||||
(int)damageData.damageType /*issue: autocannon = Ballistic, was Explosive*/);
|
||||
|
||||
// task #61: mark this shot for REPLICATION so the peer sees the enemy's
|
||||
// cannon. ++fireCounter; the CALLER's Loaded case makes the two
|
||||
|
||||
Reference in New Issue
Block a user