Combat: MISSILE SPLASH DAMAGE -- Explosion::SplashDamage reconstructed (task #62)
Area-of-effect blast on missile detonation, from the T0 source EXPLODE.cpp:50-254 (@0042fad0) + the arcade decomp. Fires on ANY missile impact (world or mech) -- the +0x360 gate sits in the Missile::Perform collision branch (part_013.c:10097). Only missiles splash; the AC's tracer is not a Missile. Radius source CORRECTED: it is the ROUND's own GameModel (type-0xf) +0x50, seeded from the launcher's linked AmmoBin ammoModelFile @0x1e8 (part_013.c:8778) -- NOT the launcher's ExplosionModelFile (which resolved to 0). Live-resolved radius = 30 for both MP missile launchers; 0 for AC/Emitters. Burst falloff = baseBurst / dist^exp floored at 1 (arcade 1.25 = decomp 0x3ff40000; WinTesla EXPLODE.cpp:209 drifted to 1.2f). damageType/amount pass through unchanged; only burstCount, radial damageForce, and impactPoint are set. Excludes shooter + direct victim; dist>radius gated; delivered via msgmgr (cross-pod) or Dispatch. New: BTResolveSplashRadius / BTApplySplashDamage (mech4.cpp), hooked at both the world-impact and contact detonation paths; BTAmmoRoundModelResource bridge (ammobin). Env: BT_SPLASH_LOG, BT_SPLASH_TEST (synthetic near-miss), BT_AF_MISSILE (missile autofire). Verified: near-miss dist=15 -> 1 burst to a bystander; live missiles detonate + exclude the direct victim; AC does not splash; no crash. Deferred (T3): per-player enable sub-gate missile+0x360 = BTPlayer+0x264 (writers read as a per-frame toggle, not a config flag) -- port treats SplashRadius>0 as the enable. KB: combat-damage.md + open-questions.md updated. 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
4c54f7ef0c
commit
12fbc023a8
@@ -439,6 +439,44 @@ the un-exported master-perf writer (0x4a9770-0x4ab188 [T4]); the exact authentic
|
||||
writer emitted (msg+0x24) is likewise unrecovered, so the killing-blow magnitude is the faithful
|
||||
stand-in until it is. [T1 mechanism / T4 exact authentic value]
|
||||
|
||||
## Missile splash damage — `Explosion::SplashDamage` (task #62, 2026-07-13) [T1/T2]
|
||||
Area-of-effect blast on **missile** detonation — NOT the vestigial whole-mech `DeathSplashDamage`
|
||||
above (mech+0x520, never read). The T0 source is `engine/MUNGA/EXPLODE.cpp:50-254` (`@0042fad0`),
|
||||
called ONLY from the Missile's per-frame `Perform` (`@004bef78`, part_013.c:10097) inside the
|
||||
collision branch — so it fires on **any** missile impact (mover OR world geometry), gated on
|
||||
`Missile+0x360`. The AC's tracer is not a Missile and never splashes.
|
||||
|
||||
**The model [T0, `EXPLODE.cpp`]:** build a Y-axis cylinder of `SplashRadius` at the blast, collect
|
||||
every `Mover` in it (+ static world), then for each target that has `damageZones` and is not the
|
||||
direct victim (`entityHit`), the detonator (`this`), or `excluded`:
|
||||
- `burstCount = baseBurst / pow(dist, 1.2f)`, `Min_Clamp(…, 1)` — dist = `|target.localOrigin −
|
||||
blastCenter|` (center-to-center). The **arcade** exponent is **1.25** (decomp `0x3ff40000` at
|
||||
part_004.c:865; the decompiler hid the x87 FDIV + the `FUN_004dcd94`=ROUND that WinTesla writes as
|
||||
truncation). Point-blank AMPLIFIES (dist<1 → >baseBurst); no distance floor (real victims are never
|
||||
at dist~0 — the one AT the center is the excluded direct victim).
|
||||
- `damageType`/`damageAmount` pass through UNCHANGED; only `burstCount`, `damageForce` (radial), and
|
||||
`impactPoint` (= blastCenter+offset) are set. Delivered via `TakeDamageMessage(inflicting, zone=-1)`.
|
||||
|
||||
**The SplashRadius source [T1]:** it is the ROUND's GameModel resource `+0x50`, NOT the launcher's
|
||||
`ExplosionModelFile`. The launcher seeds each spawned Missile's model from its linked **AmmoBin**
|
||||
`ammoModelFile @0x1e8` (part_013.c:8778); the Missile ctor `SearchList(that_id, GameModelResourceType)`
|
||||
→ `+0x50` → `Missile+0x364` (part_013.c:10184). The MissileThruster GameModel parser
|
||||
`FUN_004bf8ec` writes `"SplashRadius"` to `+0x50` of the type-`0xf`/size-`0x54` record. Live-resolved
|
||||
radius for both MP missile launchers = **30** (`BT_ROSTER` dump).
|
||||
|
||||
**Port impl (`mech4.cpp`):** `BTResolveSplashRadius` gates on `MissileLauncher::ClassDerivations`,
|
||||
resolves launcher → `BTWeaponAmmoBin` → `BTAmmoRoundModelResource` (+0x1e8) → GameModel +0x50.
|
||||
`BTApplySplashDamage` walks `BTGetTargetCandidates` (excludes shooter), skips the direct victim +
|
||||
destroyed/zoneless mechs, applies the `dist>radius` gate + the burst falloff, delivers through the
|
||||
shooter's msgmgr (cross-pod) or `Dispatch`. Hooked at BOTH detonation paths (world-impact + contact).
|
||||
**Verified:** radius resolves to 30 for missiles / 0 for AC+Emitters; near-miss dist=15 → 1 burst
|
||||
delivered to a bystander; live missiles detonate + exclude the direct victim; AC does not splash; no
|
||||
crash. Env `BT_SPLASH_LOG`, `BT_SPLASH_TEST` (synthetic near-miss), `BT_AF_MISSILE` (missile autofire).
|
||||
**Deferred [T3]:** the per-player enable sub-gate `Missile+0x360 = BTPlayer+0x264` (part_013.c:8757) —
|
||||
its decomp writers (4668/10512) read as a per-frame state toggle, not a clean config flag, and the
|
||||
port's `showDamageInflicted` label is itself a guess; port treats authored `SplashRadius>0` as the
|
||||
enable. See [[open-questions]].
|
||||
|
||||
## Key Relationships
|
||||
- Weapons/roster: [[subsystems]]. Aim source: [[locomotion]] (drive/facing). Effects: [[rendering]].
|
||||
- P5 forensics: `docs/HARD_PROBLEMS.md`. Data: [[decomp-reference]] §4-5.
|
||||
|
||||
@@ -38,8 +38,20 @@ New unaccounted functionality no prior list knew (addresses verified absent from
|
||||
Settle via vtable lookup (plausibly a config/roster scroller).
|
||||
- **FUN_00454a70** (193 ln) — the PNAME1-8/PLACE1-8.bgf MP name-billboard loader (the known
|
||||
"MP target identification" gap's exact function).
|
||||
- **Explosion::SplashDamage @0042fad0** — substantial, unreferenced, directly in the combat
|
||||
path; one-time diff vs engine EXPLODE.cpp would rule out 1995-vs-WinTesla falloff drift.
|
||||
- ✅ **Explosion::SplashDamage @0042fad0 — RECONSTRUCTED (task #62, 2026-07-13) [T1/T2].** ONE call
|
||||
site, **Missile::Perform (FUN_004bef78, part_013.c:10097)**, in the collision branch (fires on ANY
|
||||
missile impact — mover OR world), gated on `missile+0x360`, radius = `missile+0x364`. **Radius source
|
||||
corrected:** it is the ROUND's OWN GameModel (type-0xf) `+0x50`, seeded from the launcher's linked
|
||||
**AmmoBin `ammoModelFile @0x1e8`** (part_013.c:8778 → missile ctor SearchList @10184) — NOT the
|
||||
launcher's `ExplosionModelFile`. The MissileThruster parser `FUN_004bf8ec` writes `"SplashRadius"` to
|
||||
+0x50 (size-0x54 record). Burst falloff `baseBurst / dist^exp` floored at 1 (arcade 1.25 / WinTesla
|
||||
`EXPLODE.cpp:209` 1.2f). Only missiles splash (AC tracer is not a Missile). The engine `EXPLODE.cpp`
|
||||
diff is DONE — the T0 source confirmed the model; the only 1995-vs-WinTesla drift is the exponent.
|
||||
Port: `BTResolveSplashRadius`/`BTApplySplashDamage` (mech4.cpp), see [[combat-damage]]. **Remaining
|
||||
T3:** the per-player enable sub-gate `missile+0x360 = BTPlayer+0x264` (its writers read as a per-frame
|
||||
toggle, not a config flag; the `showDamageInflicted@0x264` label is a guess) — port treats authored
|
||||
`SplashRadius>0` as the enable. The mech's `DeathSplashDamage`/`DeathSplashRadius` (mech+0x520/0x524)
|
||||
stay **VESTIGIAL** (written, never read; no mech-death splash mechanic — death is visual only).
|
||||
- **The binary's own VelociRender BGF/BMF/BSL/VTX loader (~4,550 ln, dead code)** — a free
|
||||
ground-truth cross-check for [[bgf-format]]/[[asset-formats]] claims.
|
||||
Full ranked audit: the workflow output (2026-07-13); top-10 ranking cross-checked against this
|
||||
|
||||
@@ -185,6 +185,17 @@ int BTAmmoBinFeeding(void *bin)
|
||||
return (bin != 0 && ((AmmoBin *)bin)->ammoAlarm.Level() == AmmoBin::Feeding) ? 1 : 0;
|
||||
}
|
||||
|
||||
// The round's GameModel resource ID (ammoModelFile @0x1E8, word 0x7A). In the
|
||||
// arcade the MissileLauncher seeds each spawned Missile's model from AmmoBin+0x1e8
|
||||
// (part_013.c:8778) and the Missile ctor reads SplashRadius from that model's
|
||||
// GameModel record +0x50 (part_013.c:10184). The port re-expresses missiles as
|
||||
// local flying rounds, so BTResolveSplashRadius (mech4.cpp) walks THIS path to
|
||||
// recover the authored blast radius. Complete-type read of the named member.
|
||||
int BTAmmoRoundModelResource(void *bin)
|
||||
{
|
||||
return (bin != 0) ? ((AmmoBin *)bin)->ammoModelFile : 0;
|
||||
}
|
||||
|
||||
int AmmoBin::FeedAmmo()
|
||||
{
|
||||
if (simulationState == 1) // this+0x40 == 1 (destroyed)
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
friend struct AmmoBinLayoutCheck; // compile-time offset locks (ammobin.cpp)
|
||||
friend int *BTAmmoBinCountPtr(void *bin); // panel ammo counter bridge (Streak fix)
|
||||
friend int BTAmmoBinFeeding(void *bin); // panel reload-state bridge
|
||||
friend int BTAmmoRoundModelResource(void *bin); // splash-radius resolve (round GameModel +0x50)
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Shared Data Support
|
||||
//
|
||||
|
||||
@@ -154,6 +154,7 @@
|
||||
#include <hostmgr.hpp> // HostManager::GetEntityPointer (band-effect attacker resolve)
|
||||
#include <messmgr.hpp> // SubsystemMessageManager (task #7 consolidated delivery)
|
||||
#include <mechweap.hpp> // MechWeapon::GetExplosionResourceID (per-round detonation)
|
||||
#include <mislanch.hpp> // MissileLauncher::ClassDerivations (splash-radius gate)
|
||||
#if !defined(PLAYER_HPP)
|
||||
# include <player.hpp> // Player::VehicleDeadMessage -- the death->respawn notification (task #52)
|
||||
#endif
|
||||
@@ -922,6 +923,150 @@ static void
|
||||
Register_Object(e);
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// SPLASH DAMAGE (task #62) -- Explosion::SplashDamage @0042fad0 / engine
|
||||
// EXPLODE.cpp:50-254 [T0]. A detonating projectile's blast damages every mech
|
||||
// within its authored SplashRadius. Damage MODEL (T0 EXPLODE.cpp:209-246,
|
||||
// decomp part_004.c:856-911): the per-burst AMOUNT + damage TYPE pass through
|
||||
// UNCHANGED; the distance falloff is applied to the BURST COUNT --
|
||||
// bursts = round( baseBurst / dist^1.25 ), floored at 1
|
||||
// (arcade exponent 1.25 -- part_004.c:865 = 0x3ff40000; the WinTesla source
|
||||
// EXPLODE.cpp:209 drifted to 1.2f -- we use the arcade value). So a mech
|
||||
// inside the radius takes the round's damage; a point-blank one takes extra
|
||||
// bursts. Base burst = the incoming Damage's burstCount (Damage+0x2c).
|
||||
//
|
||||
// The SplashRadius is the ROUND's GameModel resource +0x50. ONLY missiles
|
||||
// splash: in the arcade a MissileLauncher spawns a Missile ENTITY whose Perform
|
||||
// (part_013.c:9887) is the sole SplashDamage caller (@10097); the AC's tracer is
|
||||
// not a Missile and never splashes. The Missile's model id comes from the
|
||||
// launcher's linked AmmoBin (ammoModelFile @0x1e8, part_013.c:8778), NOT the
|
||||
// launcher's ExplosionModelFile; the Missile ctor reads SplashRadius from that
|
||||
// model's type-0xf record +0x50 (@10184). So the port gates on MissileLauncher
|
||||
// and resolves radius via launcher -> AmmoBin -> round model -> +0x50.
|
||||
//
|
||||
// PORT: the binary enumerates interest-zone movers; reconstructed mechs aren't
|
||||
// reliably registered there, so we walk the live-mech registry
|
||||
// (BTGetTargetCandidates, which already excludes the shooter -- no self-splash)
|
||||
// -- guaranteed populated + cheaper. The DIRECT victim is excluded (it already
|
||||
// took the direct hit; matches EXPLODE.cpp:179 excluding entityHit) so it is
|
||||
// not double-damaged. Delivery reuses the direct-hit path (messmgr bundle /
|
||||
// Dispatch), so replicant victims reroute cross-pod.
|
||||
//###########################################################################
|
||||
static Scalar
|
||||
BTResolveSplashRadius(Entity *shooter, int weapon_subsys)
|
||||
{
|
||||
extern int BTIsRegisteredMech(Entity *e);
|
||||
if (shooter == 0 || weapon_subsys < 0 || application == 0
|
||||
|| application->GetResourceFile() == 0 || !BTIsRegisteredMech(shooter))
|
||||
return 0.0f;
|
||||
Mech *sm = (Mech *)shooter;
|
||||
if (weapon_subsys >= sm->GetSubsystemCount())
|
||||
return 0.0f;
|
||||
Subsystem *w = sm->GetSubsystem(weapon_subsys);
|
||||
// ONLY missile launchers spawn Missile entities that carry splash (see banner).
|
||||
if (w == 0 || !w->IsDerivedFrom(MissileLauncher::ClassDerivations))
|
||||
return 0.0f;
|
||||
// The round's model id lives on the launcher's linked AmmoBin (+0x1e8),
|
||||
// resolved through the same ammoBinLink the fire path uses.
|
||||
extern void *BTWeaponAmmoBin(void *weapon);
|
||||
extern int BTAmmoRoundModelResource(void *bin);
|
||||
void *bin = BTWeaponAmmoBin(w);
|
||||
if (bin == 0)
|
||||
return 0.0f;
|
||||
ResourceDescription::ResourceID res =
|
||||
(ResourceDescription::ResourceID)BTAmmoRoundModelResource(bin);
|
||||
if (res <= 0)
|
||||
return 0.0f;
|
||||
// The round's GameModel record (type 0xf): SplashRadius @ +0x50 (binary
|
||||
// part_013.c:10184 reads resource_data+0x50; the MissileThruster parser
|
||||
// FUN_004bf8ec writes "SplashRadius" to +0x50 of the type-0xf/size-0x54 record).
|
||||
ResourceDescription *rd = application->GetResourceFile()->SearchList(
|
||||
res, ResourceDescription::GameModelResourceType);
|
||||
if (rd == 0)
|
||||
return 0.0f;
|
||||
rd->Lock();
|
||||
Scalar radius = *(const Scalar *)((const unsigned char *)rd->resourceAddress + 0x50);
|
||||
rd->Unlock();
|
||||
if (!(radius > 0.0f) || radius > 1.0e4f) // NaN / garbage / no-splash guard
|
||||
radius = 0.0f;
|
||||
return radius;
|
||||
}
|
||||
|
||||
void
|
||||
BTApplySplashDamage(Entity *shooter, int weapon_subsys, const Point3D ¢er,
|
||||
Entity *directVictim, const Damage &dmgTemplate)
|
||||
{
|
||||
Scalar radius = BTResolveSplashRadius(shooter, weapon_subsys);
|
||||
static const int s_log = getenv("BT_SPLASH_LOG") ? 1 : 0;
|
||||
if (s_log)
|
||||
DEBUG_STREAM << "[splash] HOOK weapon=" << weapon_subsys
|
||||
<< " radius=" << radius << "\n" << std::flush;
|
||||
if (radius <= 0.0f)
|
||||
return; // this weapon authors no splash
|
||||
|
||||
extern int BTIsRegisteredMech(Entity *e);
|
||||
extern int BTGetTargetCandidates(Entity *shooter, Entity **out, int maxOut);
|
||||
Entity *cand[32];
|
||||
int nc = BTGetTargetCandidates(shooter, cand, 32); // excludes the shooter
|
||||
const Scalar baseBurst = (Scalar)dmgTemplate.burstCount;
|
||||
if (s_log)
|
||||
DEBUG_STREAM << "[splash] detonation radius=" << radius << " candidates="
|
||||
<< nc << " directVictim=" << (void *)directVictim << "\n" << std::flush;
|
||||
|
||||
SubsystemMessageManager *mgr = (weapon_subsys >= 0 && BTIsRegisteredMech(shooter))
|
||||
? (SubsystemMessageManager *)((Mech *)shooter)->GetMessageManager() : 0;
|
||||
|
||||
for (int ci = 0; ci < nc; ++ci)
|
||||
{
|
||||
Entity *e = cand[ci];
|
||||
if (e == 0 || e == directVictim || !BTIsRegisteredMech(e))
|
||||
continue;
|
||||
Mech *m = (Mech *)e;
|
||||
if (m->IsMechDestroyed() || m->damageZoneCount <= 0)
|
||||
continue;
|
||||
Point3D p = m->localOrigin.linearPosition;
|
||||
Scalar dx = p.x - center.x, dy = p.y - center.y, dz = p.z - center.z;
|
||||
Scalar dist = (Scalar)sqrtf((float)(dx*dx + dy*dy + dz*dz));
|
||||
if (dist > radius) // RADIUS GATE -- required: the
|
||||
continue; // falloff floors at 1, so without
|
||||
// this every mech would take a burst
|
||||
// EXPLODE.cpp:209 -- burstCount = baseBurst / dist^exp, floored at 1.
|
||||
// Exponent is the ARCADE 1.25 (decomp 0x3ff40000); the WinTesla source
|
||||
// drifted to 1.2f. Point-blank AMPLIFIES (dist<1 -> >baseBurst bursts) --
|
||||
// authentic; a real splash victim is never at dist~0 (the mech AT the
|
||||
// blast is the excluded direct victim), so the div-guard epsilon (mechs
|
||||
// have body size) only ever fires in the synthetic BT_SPLASH_TEST.
|
||||
Scalar d = (dist > 0.01f) ? dist : 0.01f; // div-guard (no arcade floor)
|
||||
int bursts = (int)(0.5f + baseBurst / powf((float)d, 1.25f)); // arcade ROUND
|
||||
if (bursts < 1) bursts = 1; // EXPLODE.cpp:210 Min_Clamp
|
||||
|
||||
Damage dmg = dmgTemplate; // amount + type pass through
|
||||
dmg.burstCount = bursts;
|
||||
dmg.impactPoint = center;
|
||||
dmg.damageForce = Vector3D(dx, dy, dz); // EXPLODE.cpp:216 radial knockback
|
||||
|
||||
if (mgr != 0)
|
||||
{
|
||||
Entity::TakeDamageMessage td(
|
||||
Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage),
|
||||
shooter->GetEntityID(), -1 /*unaimed -> cylinder resolves*/,
|
||||
dmg, weapon_subsys);
|
||||
mgr->AddDamageMessage(e, &td);
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity::TakeDamageMessage td(
|
||||
Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage),
|
||||
shooter->GetEntityID(), -1, dmg);
|
||||
e->Dispatch(&td);
|
||||
}
|
||||
if (s_log)
|
||||
DEBUG_STREAM << "[splash] victim=" << (void *)e << " dist=" << dist
|
||||
<< " radius=" << radius << " bursts=" << bursts
|
||||
<< " amount=" << dmg.damageAmount << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
// Called from ProjectileWeapon / MissileLauncher::FireWeapon (via the extern below)
|
||||
// with the live muzzle, the SHOOTER mech (to resolve the real launch port), the owner's
|
||||
// locked target entity + point, the launch speed (|muzzleVelocity|), and per-shot damage.
|
||||
@@ -1150,6 +1295,24 @@ static void
|
||||
{
|
||||
Point3D hp; hp.x = hx; hp.y = hy; hp.z = hz;
|
||||
BTSpawnRoundDetonation(p.shooter, p.weaponSubsys, hp);
|
||||
// SPLASH (task #62): the binary missile detonates on ANY
|
||||
// geometry and still runs SplashDamage (the +0x360 gate sits
|
||||
// inside the collision branch @part_013.c:10045, which fires
|
||||
// for a world hit as well as a mover hit). A missile that
|
||||
// bursts on terrain near a mech still catches it in the blast.
|
||||
// No direct victim here, and no-op for non-missile rounds
|
||||
// (BTApplySplashDamage returns radius 0 for the AC / p.damage 0).
|
||||
if (p.damage > 0.0f)
|
||||
{
|
||||
Damage sdmg;
|
||||
sdmg.damageType = Damage::ExplosiveDamageType;
|
||||
sdmg.damageAmount = p.damage;
|
||||
sdmg.burstCount = 1;
|
||||
sdmg.impactPoint = hp;
|
||||
extern void BTApplySplashDamage(Entity *, int, const Point3D &,
|
||||
Entity *, const Damage &);
|
||||
BTApplySplashDamage(p.shooter, p.weaponSubsys, hp, 0, sdmg);
|
||||
}
|
||||
}
|
||||
extern void BTPfxTrailPuff(int, float, float, float, float, float, float, int);
|
||||
for (int pf = 0; pf < 4; ++pf)
|
||||
@@ -1243,6 +1406,14 @@ static void
|
||||
<< " subsys=" << p.weaponSubsys
|
||||
<< (mgr ? " (msgmgr bundled)" : " (direct)")
|
||||
<< " (zone cyl-resolved)\n" << std::flush;
|
||||
|
||||
// SPLASH (task #62): a detonating round with an authored
|
||||
// SplashRadius also damages every OTHER mech in the blast
|
||||
// (tgt excluded -- it took the direct hit above). No-op for
|
||||
// weapons with no splash radius (lasers never reach here).
|
||||
extern void BTApplySplashDamage(Entity *, int, const Point3D &,
|
||||
Entity *, const Damage &);
|
||||
BTApplySplashDamage(p.shooter, p.weaponSubsys, p.pos, tgt, dmg);
|
||||
}
|
||||
}
|
||||
p.active = 0;
|
||||
@@ -3543,11 +3714,15 @@ void
|
||||
LBE4ControlsManager::ButtonJoystickThumbLow, // 0x46
|
||||
LBE4ControlsManager::ButtonJoystickThumbHigh, // 0x47
|
||||
};
|
||||
// DEV: BT_AF_MISSILE=1 also pulses the missile group (ThumbHigh) so a
|
||||
// headless rig can exercise the launcher (splash verification, task #62).
|
||||
static const int s_afMissile = getenv("BT_AF_MISSILE") ? 1 : 0;
|
||||
const int autofireMsl = (s_afMissile && gBTDrive.fireForced && targetInArc) ? s_afPhase : 0;
|
||||
int want[4];
|
||||
want[0] = ((gBTLaserKey && targetInArc) || autofire) ? 1 : 0;
|
||||
want[1] = (gBTPinkyKey && targetInArc) ? 1 : 0; // key '4' (was unmapped)
|
||||
want[2] = (gBTPPCKey && targetInArc) ? 1 : 0;
|
||||
want[3] = (gBTMissileKey && targetInArc) ? 1 : 0;
|
||||
want[3] = ((gBTMissileKey && targetInArc) || autofireMsl) ? 1 : 0;
|
||||
if (cm != 0)
|
||||
{
|
||||
for (int b = 0; b < 4; ++b)
|
||||
@@ -3915,9 +4090,16 @@ void
|
||||
if (rs == 0)
|
||||
continue;
|
||||
Derivation *rd = rs->GetDerivation();
|
||||
// Resolve the authored splash radius for every weapon so we can
|
||||
// confirm the GameModel +0x50 plumbing yields real values without
|
||||
// needing a live missile hit + bystander (task #62 verification).
|
||||
Scalar splash = -1.0f;
|
||||
if (rs->IsDerivedFrom(*MechWeapon::GetClassDerivations()))
|
||||
splash = BTResolveSplashRadius((Entity *)this, ri);
|
||||
DEBUG_STREAM << "[roster] " << ri
|
||||
<< " classID=" << (int)rs->GetClassID()
|
||||
<< " " << (rd ? rd->className : "?") << "\n" << std::flush;
|
||||
<< " " << (rd ? rd->className : "?")
|
||||
<< " splashRadius=" << splash << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3930,6 +4112,40 @@ void
|
||||
DEBUG_STREAM << "[tick] subsystems simulated: " << subsystemsTicked
|
||||
<< " (executable) of " << subsystemsPresent
|
||||
<< " present / roster " << subsystemCount << "\n" << std::flush;
|
||||
|
||||
// DEV: BT_SPLASH_TEST=1 -- deterministic splash verification (task #62).
|
||||
// Once per second the LOCAL mech synthesizes a missile detonation AT the
|
||||
// enemy's position (directVictim=0, so the enemy IS a splash candidate)
|
||||
// using its first missile launcher. Exercises the FULL path (radius
|
||||
// resolve -> candidate walk -> distance-scaled burst -> damage delivery)
|
||||
// without depending on the live targeting/autofire path.
|
||||
static const int s_splashTest = getenv("BT_SPLASH_TEST") ? 1 : 0;
|
||||
if (s_splashTest && gEnemyMech != 0 && (Entity *)this != gEnemyMech)
|
||||
{
|
||||
int mslIdx = -1;
|
||||
for (int i = 0; i < subsystemCount; ++i)
|
||||
{
|
||||
Subsystem *s = GetSubsystem(i);
|
||||
if (s != 0 && s->IsDerivedFrom(MissileLauncher::ClassDerivations))
|
||||
{ mslIdx = i; break; }
|
||||
}
|
||||
if (mslIdx >= 0)
|
||||
{
|
||||
// Detonate as a NEAR-MISS 15 units from the enemy (a realistic
|
||||
// splash geometry: the round bursts beside the mech, not at its
|
||||
// exact center), so the burst falloff exercises the typical case.
|
||||
Point3D ep = ((Mech *)gEnemyMech)->localOrigin.linearPosition;
|
||||
Point3D burst = ep; burst.x += 15.0f;
|
||||
Damage sd;
|
||||
sd.damageType = Damage::ExplosiveDamageType;
|
||||
sd.damageAmount = 0.03f; // small test dose
|
||||
sd.burstCount = 1;
|
||||
sd.impactPoint = burst;
|
||||
extern void BTApplySplashDamage(Entity *, int, const Point3D &,
|
||||
Entity *, const Damage &);
|
||||
BTApplySplashDamage((Entity *)this, mslIdx, burst, 0, sd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user