PFX continuous-rate emission (data-verified) + persistent wreck smoke
The one-burst emitter batching was wrong: in EVERY shipped .PFX, maxIssue/rate == release_period (DAFC 25/100~=0.2, DNBOOM 35/150~=0.2, DDAM2 35/16~=2, DDTHSMK 30/3=10) -- release_period is the EMISSION WINDOW and rate is particles/second emitted continuously across it. The death smoke plume is authored as a 3/sec trickle for 10 seconds (+6s particle life), not one same-frame puff. Emitters now integrate rate*dt until maximum_issue. Wreck smoke (port addition, [T3]): a destroyed mech re-arms the death/rubble smoke plume (psfx 1, DDTHSMK) every 10s -- its own authored window -- so the dead hulk visibly keeps smoking instead of standing there looking alive (the freeze-death is invisible on an already-stationary target). New member Mech::wreckSmokeTimer; UpdateDeathState takes dt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d9254736ab
commit
b44c0f98f6
+15
-11
@@ -423,7 +423,7 @@ struct BTPfxEmitter
|
||||
// victim's mech-local space -- e.g. DAFC.PFX sprays -Z, out through the
|
||||
// front armor toward the shooter). Identity when no frame is known.
|
||||
D3DXVECTOR3 ax, ay, az;
|
||||
float sinceRelease;
|
||||
float emitAccum; // fractional particles owed (rate * dt integration)
|
||||
int issued;
|
||||
int active;
|
||||
};
|
||||
@@ -534,7 +534,7 @@ void BTStartPfxFrame(int effect_number, float x, float y, float z,
|
||||
e.ax = xrow ? D3DXVECTOR3(xrow[0], xrow[1], xrow[2]) : D3DXVECTOR3(1, 0, 0);
|
||||
e.ay = yrow ? D3DXVECTOR3(yrow[0], yrow[1], yrow[2]) : D3DXVECTOR3(0, 1, 0);
|
||||
e.az = zrow ? D3DXVECTOR3(zrow[0], zrow[1], zrow[2]) : D3DXVECTOR3(0, 0, 1);
|
||||
e.sinceRelease = 1e9f; // first batch releases immediately
|
||||
e.emitAccum = 1.0f; // first particle immediately
|
||||
e.issued = 0;
|
||||
e.active = 1;
|
||||
gBTPfxEmitters.push_back(e);
|
||||
@@ -602,17 +602,21 @@ void BTDrawPfx(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
|
||||
BTPfxEmitter &e = gBTPfxEmitters[ei];
|
||||
if (!e.active) continue;
|
||||
const BTPfxDef &d = *e.def;
|
||||
e.sinceRelease += dt;
|
||||
if (e.sinceRelease >= d.releasePeriod)
|
||||
// CONTINUOUS emission at `rate` particles/second until maximum_issue is
|
||||
// exhausted. Data-verified semantics: in EVERY shipped .PFX,
|
||||
// maxIssue/rate == release_period (DAFC 25/100~=0.2, DNBOOM 35/150~=0.2,
|
||||
// DDAM2 35/16~=2, DDTHSMK 30/3=10) -- release_period IS the emission
|
||||
// WINDOW, not a batch interval. (The old one-burst batching dumped a
|
||||
// 10-second smoke plume in a single frame's puff.)
|
||||
e.emitAccum += d.rate * dt;
|
||||
int n = (int)e.emitAccum;
|
||||
if (n > 0)
|
||||
{
|
||||
// one batch: rate particles/sec over the release period, total-capped
|
||||
int batch = (int)(d.rate * (d.releasePeriod > 1e-4f ? d.releasePeriod : dt));
|
||||
if (batch < 1) batch = 1;
|
||||
if (e.issued + batch > d.maxIssue) batch = d.maxIssue - e.issued;
|
||||
for (int b = 0; b < batch; ++b)
|
||||
e.emitAccum -= (float)n;
|
||||
if (e.issued + n > d.maxIssue) n = d.maxIssue - e.issued;
|
||||
for (int b = 0; b < n; ++b)
|
||||
BTPfxSpawn(e);
|
||||
e.issued += batch;
|
||||
e.sinceRelease = 0.0f;
|
||||
e.issued += n;
|
||||
if (e.issued >= d.maxIssue)
|
||||
e.active = 0; // done issuing -> instance ends
|
||||
}
|
||||
|
||||
@@ -1318,6 +1318,7 @@ Mech::Mech(
|
||||
deathHandler = (int)new MechDeathHandler(this); // FUN_0042a984
|
||||
critRes->Unlock();
|
||||
}
|
||||
wreckSmokeTimer = 0.0f;
|
||||
|
||||
//
|
||||
// Cylinder hit-location table (resource type 0x1d = DamageLookupTableStream) --
|
||||
|
||||
@@ -497,6 +497,10 @@ protected:
|
||||
// "ammoExpended"; binary slot this[0x111] is this pointer.)
|
||||
int damageLookupTable; // @0x444 this[0x111] (FUN_0049ea48)
|
||||
int deathHandler; // @0x850 this[0x214] (FUN_0042a984)
|
||||
// Wreck-smoke cadence (port addition, [T3]): re-fires the death/rubble
|
||||
// smoke plume (psfx 1, DDTHSMK) every 10s (its authored emission window)
|
||||
// while the mech is a destroyed wreck, so a dead mech keeps smoking.
|
||||
Scalar wreckSmokeTimer;
|
||||
|
||||
// Three ref-counted creation-name objects (badge/color/insignia).
|
||||
void *resourceNameA; // @0x844 this[0x211]
|
||||
@@ -677,7 +681,7 @@ protected:
|
||||
// --- death sequence (the un-exported master-perf death branch, rebuilt
|
||||
// from its exported consumers + the RP VTV::DeathShutdown analog) ---
|
||||
Logical IsMechDestroyed(); // damage-side death flag: graphicAlarm level >= 9
|
||||
void UpdateDeathState(); // collapse movementMode + subsystem DeathShutdown + freeze
|
||||
void UpdateDeathState(Scalar dt); // death freeze + subsystem DeathShutdown + wreck smoke
|
||||
|
||||
// --- clip loaders (mech3) -------------------------------------------
|
||||
ResourceDescription::ResourceID *
|
||||
|
||||
@@ -943,12 +943,32 @@ Logical
|
||||
}
|
||||
|
||||
void
|
||||
Mech::UpdateDeathState()
|
||||
Mech::UpdateDeathState(Scalar dt)
|
||||
{
|
||||
if (!IsMechDestroyed()) // alive
|
||||
return;
|
||||
if (movementMode == 9) // already settled (disabled/frozen)
|
||||
{
|
||||
// WRECK SMOKE (port addition, [T3]): keep the dead hulk smoking -- re-fire
|
||||
// the death/rubble smoke plume (psfx 1 = DDTHSMK, "the mech death/rubble
|
||||
// smoke plume") each time its authored 10-second emission window elapses.
|
||||
// The .PFX itself trickles 3 particles/sec for 10s; re-arming it on that
|
||||
// same cadence reads as a continuously burning wreck. The one-shot death
|
||||
// visuals (dnboom + skins) stay in the kill path.
|
||||
wreckSmokeTimer -= dt;
|
||||
if (wreckSmokeTimer <= 0.0f)
|
||||
{
|
||||
wreckSmokeTimer = 10.0f; // DDTHSMK's release window
|
||||
extern void BTStartPfx(int effect_number, float x, float y, float z);
|
||||
BTStartPfx(1,
|
||||
(float)localOrigin.linearPosition.x,
|
||||
(float)(localOrigin.linearPosition.y + kMuzzleHeight),
|
||||
(float)localOrigin.linearPosition.z);
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[death] wreck smoke plume re-armed (psfx 1)\n" << std::flush;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// DEATH = FREEZE, not collapse (decomp-verified 2026-07-08, task #32).
|
||||
@@ -1001,7 +1021,7 @@ void
|
||||
// vital kill it collapses + shuts the subsystems down + settles to disabled;
|
||||
// the drive's movementMode write below is guarded on !IsMechDestroyed so a
|
||||
// dead mech keeps its death state instead of reverting to a live gait.
|
||||
UpdateDeathState();
|
||||
UpdateDeathState(dt);
|
||||
|
||||
// MechDeathHandler (@0042aa2c): per-frame destroyed-skin + explosion effects as
|
||||
// this mech's zones cross damage thresholds. The binary ticks it off the mech's
|
||||
|
||||
Reference in New Issue
Block a user