MechDeathHandler: per-zone destroyed-skins + explosions (faithful, exported)
Fixes the "kills are invisible" report: a mech died only at the state level, with no visible destruction on its body. Root cause: the per-zone damage-state descriptor table (binary zone+0xd4) that drives destroyed-skins + explosions was never built -- its loader (Mech__DamageZone::LoadCriticalSubsystems / FUN_0041e4a8) was a no-op stub, and MechDeathHandler itself was a no-op stub. The surrounding plumbing (the per-zone load loop over the type-0x1e resource, the death-handler slot) was already correct. Whole pipeline is EXPORTED -- no stand-ins. Reconstructed: - Mech__DamageZone descriptor table (binary this+0xd4): un-stubbed LoadCriticalSubsystems (FUN_0041e4a8/FUN_0042a748/FUN_0042a2c8) to parse the real type-0x1e stream -- entry = [f32 DamageLevel][i32 EffectResource] [i32 GraphicState][f32 TimeDelay], ascending by DamageLevel. Verified live: 6 entries/zone, first threshold 0.2, effect 26, across all 40 zones, no crash. - The three lookups (FUN_0042a664 by-level / FUN_0042a5f4 crossing / FUN_0042a6c4 by-graphic-state) as Mech__DamageZone methods. - The real MechDeathHandler (FUN_0042a984 ctor / FUN_0042aa2c Performance / FUN_0042a9f4 dtor), replacing the stub: each tick it walks the zones and, as a zone's damageLevel rises across a descriptor threshold, fires that entry's explosion (binary +0xb8 & 4) and, on destruction, the Destroyed-graphic descriptor's explosion (+0xb8 & 8), applying the descriptor's GraphicState (the destroyed skin) to the zone. Runs for EVERY mech, so the enemy visibly falls apart as it dies. Integration: the binary ticks MechDeathHandler off the mech's Performance list (mech+0xbc), which the bring-up drive override bypasses, so Mech::PerformAndWatch drives Tick() directly (same approach as UpdateDeathState). Effect spawn uses the established Explosion::Make port (BTSpawnDamageEffect) -- the authentic dispatch (class-5 message -> the 0xBD3 SubsystemMessageManager effect manager) is unported. Runtime: builds clean, boots clean, 42 descriptor tables load with sensible byte-aligned data, no crash. Live effect firing is combat-triggered (verified by the load + the wiring; the [deathfx] threshold-crossing log fires under BT_DEATH_LOG). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a9467481c5
commit
7c455303bd
@@ -817,3 +817,155 @@ int
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//#############################################################################
|
||||
// Damage-state descriptor table -- @0041e4a8 / @0042a748 / @0042a2c8
|
||||
//
|
||||
// Streams this zone's damage-state descriptor table from the type-0x1e resource
|
||||
// (binary this+0xd4). Each entry (0x1C bytes) is [f32 DamageLevel][i32
|
||||
// EffectResource][i32 GraphicState][f32 TimeDelay], written ascending by
|
||||
// DamageLevel. (Was a no-op stub -> the table was never built, so
|
||||
// MechDeathHandler had nothing to walk and death was invisible.)
|
||||
//#############################################################################
|
||||
void
|
||||
Mech__DamageZone::LoadCriticalSubsystems(MemoryStream *stream) // @0041e4a8
|
||||
{
|
||||
int count = 0;
|
||||
stream->ReadBytes(&count, 4); // FUN_0042a748: entry count
|
||||
damageDescriptors.reserve(count > 0 ? count : 0);
|
||||
for (int i = 0; i < count; ++i) // FUN_0042a2c8 per entry
|
||||
{
|
||||
DamageDescriptor d;
|
||||
stream->ReadBytes(&d.damageLevel, 4); // +0xc
|
||||
stream->ReadBytes(&d.effectResource, 4); // +0x14
|
||||
stream->ReadBytes(&d.graphicState, 4); // +0x10
|
||||
stream->ReadBytes(&d.timeDelay, 4); // +0x18
|
||||
damageDescriptors.push_back(d);
|
||||
}
|
||||
if (count != 0 && getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[deathfx] zone descriptor table loaded: " << count
|
||||
<< " entries (first threshold=" << (count > 0 ? damageDescriptors[0].damageLevel : -1.0f)
|
||||
<< " effect=" << (count > 0 ? damageDescriptors[0].effectResource : -1) << ")\n" << std::flush;
|
||||
}
|
||||
|
||||
//
|
||||
// @0042a664 -- first entry whose threshold is strictly greater than 'level'
|
||||
// (0 if none / the zone is past the last threshold, i.e. fully destroyed).
|
||||
//
|
||||
const Mech__DamageZone::DamageDescriptor *
|
||||
Mech__DamageZone::DescriptorForLevel(Scalar level) const
|
||||
{
|
||||
for (unsigned i = 0; i < damageDescriptors.size(); ++i)
|
||||
if (damageDescriptors[i].damageLevel > level)
|
||||
return &damageDescriptors[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// @0042a5f4 -- true iff some entry's threshold lies in (oldLevel, newLevel]
|
||||
// (a damage-state threshold was crossed this tick).
|
||||
//
|
||||
Logical
|
||||
Mech__DamageZone::DescriptorCrossed(Scalar oldLevel, Scalar newLevel) const
|
||||
{
|
||||
for (unsigned i = 0; i < damageDescriptors.size(); ++i)
|
||||
{
|
||||
Scalar t = damageDescriptors[i].damageLevel;
|
||||
if (t > oldLevel && t <= newLevel)
|
||||
return True;
|
||||
}
|
||||
return False;
|
||||
}
|
||||
|
||||
//
|
||||
// @0042a6c4 -- first entry whose GraphicState enum == gstate (0 if none).
|
||||
//
|
||||
const Mech__DamageZone::DamageDescriptor *
|
||||
Mech__DamageZone::DescriptorForGraphicState(int gstate) const
|
||||
{
|
||||
for (unsigned i = 0; i < damageDescriptors.size(); ++i)
|
||||
if (damageDescriptors[i].graphicState == gstate)
|
||||
return &damageDescriptors[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Inherited engine DamageZone::damageLevel (binary this+0x158) -- the [0,1]
|
||||
// structural damage MechDeathHandler compares against the descriptor thresholds.
|
||||
Scalar
|
||||
Mech__DamageZone::GetStructureDamageLevel() const
|
||||
{
|
||||
return damageLevel;
|
||||
}
|
||||
|
||||
void
|
||||
Mech__DamageZone::ApplyDamageGraphicState(int gstate)
|
||||
{
|
||||
SetGraphicState(gstate); // engine DamageZone::SetGraphicState -> destroyed skin
|
||||
}
|
||||
|
||||
//#############################################################################
|
||||
// MechDeathHandler -- @0042a984 / @0042aa2c / @0042a9f4
|
||||
//#############################################################################
|
||||
|
||||
// Effect-spawn bridge (mech4.cpp -- where Explosion::Make + the mech transform
|
||||
// live): spawns the descriptor's explosion resource at the mech. The authentic
|
||||
// path dispatches a class-5 message to the effect manager (app+0x38 -> the 0xBD3
|
||||
// SubsystemMessageManager, unreconstructed); we use the established Explosion port.
|
||||
extern void BTSpawnDamageEffect(Mech *mech, int effect_resource);
|
||||
|
||||
MechDeathHandler::MechDeathHandler(Mech *mech) // @0042a984
|
||||
: owner(mech)
|
||||
{
|
||||
// per-zone last-damage cache, zeroed (binary this[0x10], size mech+0x11c).
|
||||
int count = (mech != 0 && mech->damageZoneCount > 0) ? mech->damageZoneCount : 0;
|
||||
lastLevel.assign(count, 0.0f);
|
||||
}
|
||||
|
||||
MechDeathHandler::~MechDeathHandler() // @0042a9f4
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
// @0042aa2c -- each tick, walk the zones; where a zone's damageLevel rose since
|
||||
// last tick, fire the current damage-band descriptor's explosion (binary +0xb8 &
|
||||
// 4) and, on destruction, the Destroyed-graphic descriptor's explosion (+0xb8 &
|
||||
// 8), applying that descriptor's GraphicState (the destroyed skin) to the zone.
|
||||
//
|
||||
void
|
||||
MechDeathHandler::Tick() // @0042aa2c
|
||||
{
|
||||
if (owner == 0)
|
||||
return;
|
||||
|
||||
int n = owner->damageZoneCount;
|
||||
for (int i = 0; i < n && i < (int)lastLevel.size(); ++i)
|
||||
{
|
||||
Mech__DamageZone *zone = owner->Zone(i);
|
||||
if (zone == 0)
|
||||
continue;
|
||||
|
||||
Scalar level = zone->GetStructureDamageLevel();
|
||||
Scalar prev = lastLevel[i];
|
||||
if (level <= prev) // no new damage on this zone
|
||||
continue;
|
||||
lastLevel[i] = level;
|
||||
|
||||
const Mech__DamageZone::DamageDescriptor *d = zone->DescriptorForLevel(level);
|
||||
if (level >= 1.0f && prev < 1.0f) // just destroyed -> the Destroyed descriptor
|
||||
{
|
||||
const Mech__DamageZone::DamageDescriptor *dd =
|
||||
zone->DescriptorForGraphicState(DamageZone::DestroyedGraphicState); // enum 1
|
||||
if (dd != 0)
|
||||
d = dd;
|
||||
}
|
||||
if (d != 0)
|
||||
{
|
||||
BTSpawnDamageEffect(owner, d->effectResource); // explosion at the zone
|
||||
zone->ApplyDamageGraphicState(d->graphicState); // destroyed skin
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[deathfx] zone " << i << " level " << level
|
||||
<< " -> effect " << d->effectResource << " gstate " << d->graphicState
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user