Combat visible + killable: Wword root-cause fix, .PFX effect layer, RemakeEntity swap
The 'can't kill the enemy / no visible damage' cluster, root-caused and fixed faithfully: - STEP-6 unaimed path was INERT: the cylinder table was 'cached' at Wword(0x111) -- the recon ABSORBER bank (stores nothing, reads 0) -- so every unaimed hit silently no-op'd. Promoted to the named member Mech::damageLookupTable (binary this[0x111], was mislabeled ammoExpended). New gotcha class recorded (reconstruction-gotchas §2) + sweep; 2 dead multiplayer branches logged. - Fire path migrated off the stale vital-zone aim onto the completed STEP-6 unaimed dispatch (zone=-1 + beam entry point -> cylinder resolves the exterior zone). No more invisible 1-shot kills; death via the authentic cascade (~14 center-mass hits). Wreck stays TARGETED on kill (beams stop on it); scoring latches off. - SendSubsystemDamage AV fixed: unbound critical-subsystem plug guard (43 unbound plugs/mech logged as an open question -- the binding itself is a gap). - RemakeEntity (render damage swap): the 1996 render state machine's missing Remake state, reconstructed as an in-place SetDrawObj mesh swap keyed by each segment's damage-zone graphic state (tree dtor doesn't cascade -> never rebuild). Destroyed arms/guns visibly wreck (the only variants the RES registers). - BT .PFX particle layer (L4VIDEO.cpp): the 1995 explosion/damage effect layer, unported since 2007 (DPLIndependantEffect/ReadPSFX/ExplosionScripts all stubs). Parses the authentic VIDEO/*.PFX definitions via the [pfx_day] psfxN mapping; premultiplied blending renders BOTH families from the same data (additive-style fire + occluding smoke -- DDAM2 is 30% grey, DDTHSMK ramps negative: impossible additively); depth-sorted billboards with a radial-masked grit sprite; impact-frame orientation (.PFX offsets are authored mech-local, -Z = out of the struck armor toward the shooter) for weapon hits AND damage bands (via lastInflictingID, now maintained -- was declared but never written). Both effect-number encodings route (raw dpl <100 + WinTesla 1000+slot carried by the band resources). Death fires the authentic dnboom (7) + ddthsmk smoke plume (1). - Effects anchor at the impact point / damaged zone's segment, not the mech origin (no more fire at the feet). - Dev force-input gates BT_AUTOFIRE / BT_AUTODRIVE for headless fire-chain verification; BT_PFX_ADD=1 flips the particle blend for A/B. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
7c455303bd
commit
a3d67cc639
@@ -575,6 +575,24 @@ void
|
||||
{
|
||||
Subsystem *s = criticalSubsystems[i]->subsystemPlug.Resolve(); // +4
|
||||
MechCriticalSubsystem *cs = criticalSubsystems[i];
|
||||
|
||||
// UNBOUND-PLUG GUARD. Resolve() (FUN_00417ab4) returns the subsystem bound
|
||||
// to the DZSlot, or 0 if plug+8 was never wired. In the 1995 binary every
|
||||
// critical subsystem exists and is bound, so it dereferences s with no check.
|
||||
// In this reconstruction a critical subsystem can be UNBOUND (its type is not
|
||||
// yet built, or its slot never connected), leaving s == 0 -> the original
|
||||
// code AV'd here (mov [edx+0xc], edx=0) as soon as a zone with such a plug was
|
||||
// destroyed. Skip the unbound entry rather than crash; log it so the missing
|
||||
// binding can be tracked. NOT a behavioural stand-in: a bound plug takes the
|
||||
// authentic path below unchanged. [T3 -- see open-questions: crit-subsys binding]
|
||||
if (s == 0)
|
||||
{
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[deathfx] crit-subsys " << i << "/" << criticalSubsystemCount
|
||||
<< " UNBOUND (plug not wired) -- skipped\n" << std::flush;
|
||||
continue;
|
||||
}
|
||||
|
||||
Mech__DamageZone *zone = (Mech__DamageZone *)((SubProxy2 *)s)->damageZone; // subsystem[0x38] @0xE0
|
||||
|
||||
zone->damageLevel += (cs->damagePercentage - cs->damagePercentageUsed);
|
||||
@@ -911,7 +929,12 @@ void
|
||||
// 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);
|
||||
extern void BTSpawnDamageEffect(Mech *mech, int effect_resource, int segment_index);
|
||||
|
||||
// Render bridge (btl4vid.cpp): swap a mech's wrecked segment meshes onto its
|
||||
// already-built render tree when a damage zone's graphic state changes (the
|
||||
// unported "RemakeEntity" render state). Entity* param -> Mech* binds fine.
|
||||
extern void BTRemakeMechModel(Entity *entity);
|
||||
|
||||
MechDeathHandler::MechDeathHandler(Mech *mech) // @0042a984
|
||||
: owner(mech)
|
||||
@@ -960,8 +983,27 @@ void
|
||||
}
|
||||
if (d != 0)
|
||||
{
|
||||
BTSpawnDamageEffect(owner, d->effectResource); // explosion at the zone
|
||||
zone->ApplyDamageGraphicState(d->graphicState); // destroyed skin
|
||||
BTSpawnDamageEffect(owner, d->effectResource, // explosion AT the zone's
|
||||
zone->segmentIndex); // segment (world position)
|
||||
zone->ApplyDamageGraphicState(d->graphicState); // destroyed skin (graphic state)
|
||||
// A graphic-state change means the segment's MODEL changed (intact ->
|
||||
// destroyed variant, keyed by GetVideoObjectName(skl, gstate)). Fire
|
||||
// the engine's model-rebuild flag so the renderer re-runs
|
||||
// MakeMechRenderables and loads the destroyed segment geometry -- the
|
||||
// same mechanism CulturalIcons use to visibly deform on damage
|
||||
// (CULTURAL.cpp:91; EXPTBL.cpp:549 does this per zone in the engine's
|
||||
// ExplosionTable). Without this the tree stays as built at spawn (intact).
|
||||
// The render's "RemakeEntity" state (which consumes this flag) was
|
||||
// NOT ported -- the mech tree is built once at spawn -- so we drive the
|
||||
// equivalent directly: set the flag (authentic signal; also flags the
|
||||
// zone for net damage replication) AND call the render bridge, which
|
||||
// swaps the wrecked segment mesh onto the already-built tree in place.
|
||||
// The single-threaded frame loop makes the immediate swap safe.
|
||||
if (d->graphicState != DamageZone::ExistsGraphicState)
|
||||
{
|
||||
owner->ForceUpdate(Entity::DamageZoneUpdateModelFlag);
|
||||
BTRemakeMechModel(owner); // RemakeEntity: swap in the destroyed mesh
|
||||
}
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[deathfx] zone " << i << " level " << level
|
||||
<< " -> effect " << d->effectResource << " gstate " << d->graphicState
|
||||
|
||||
Reference in New Issue
Block a user