From 12fbc023a893fe9290dbff04eb3709c09f27e102 Mon Sep 17 00:00:00 2001 From: arcattack Date: Mon, 13 Jul 2026 19:06:24 -0500 Subject: [PATCH] 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) --- context/combat-damage.md | 38 ++++++ context/open-questions.md | 16 ++- game/reconstructed/ammobin.cpp | 11 ++ game/reconstructed/ammobin.hpp | 1 + game/reconstructed/mech4.cpp | 220 ++++++++++++++++++++++++++++++++- 5 files changed, 282 insertions(+), 4 deletions(-) diff --git a/context/combat-damage.md b/context/combat-damage.md index 337998a..26c118f 100644 --- a/context/combat-damage.md +++ b/context/combat-damage.md @@ -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. diff --git a/context/open-questions.md b/context/open-questions.md index c82d23a..08a65bf 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -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 diff --git a/game/reconstructed/ammobin.cpp b/game/reconstructed/ammobin.cpp index acfb96b..7dbe10e 100644 --- a/game/reconstructed/ammobin.cpp +++ b/game/reconstructed/ammobin.cpp @@ -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) diff --git a/game/reconstructed/ammobin.hpp b/game/reconstructed/ammobin.hpp index ad6360d..de82ff7 100644 --- a/game/reconstructed/ammobin.hpp +++ b/game/reconstructed/ammobin.hpp @@ -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 // diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 0f31f94..7ecb071 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -154,6 +154,7 @@ #include // HostManager::GetEntityPointer (band-effect attacker resolve) #include // SubsystemMessageManager (task #7 consolidated delivery) #include // MechWeapon::GetExplosionResourceID (per-round detonation) +#include // MissileLauncher::ClassDerivations (splash-radius gate) #if !defined(PLAYER_HPP) # include // 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); + } + } } //