diff --git a/engine/MUNGA_L4/L4VIDRND.h b/engine/MUNGA_L4/L4VIDRND.h index 44a4f65..94f8b19 100644 --- a/engine/MUNGA_L4/L4VIDRND.h +++ b/engine/MUNGA_L4/L4VIDRND.h @@ -1900,6 +1900,16 @@ public: Logical TestInstance() const; virtual void Execute(); + + // Overwrite the offset's translation row in place. Execute() re-reads + // OrientationMatrix every frame, so this animates the child -- the BT + // death-wreck SINK (the 1996 script's quadratic burial) drives it. + void SetOffsetTranslation(float x, float y, float z) + { + OrientationMatrix(3, 0) = x; + OrientationMatrix(3, 1) = y; + OrientationMatrix(3, 2) = z; + } protected: dpl_DCS *myDCS, *myParentDCS; diff --git a/game/reconstructed/btl4vid.cpp b/game/reconstructed/btl4vid.cpp index 9655efd..a3d6bed 100644 --- a/game/reconstructed/btl4vid.cpp +++ b/game/reconstructed/btl4vid.cpp @@ -796,9 +796,15 @@ void } // - // Hide the body; hang the hulk on the tree root (identity offset -- the - // root renderable already pushes the wreck's localToWorld, so the hulk - // sits at the mech's ground position with its death yaw). + // The strewn-debris field that accompanies the standing hulk (the 1996 + // script pairs them: thrdbr + ldbr, parented together, sinking together). + // + d3d_OBJECT *debris = d3d_OBJECT::LoadObject(GetDevice(), "ldbr.bgf"); + + // + // Hide the body; hang the wreck pieces on the tree root (identity offset -- + // the root renderable already pushes the wreck's localToWorld, so they sit + // at the mech's ground position with its death yaw). // for (std::map::iterator r = render_tree.segRenderable.begin(); @@ -807,22 +813,88 @@ void if (r->second != NULL) r->second->SetDrawObj(NULL); } - if (hulk != NULL && render_tree.rootRenderable != NULL) + if (render_tree.rootRenderable != NULL) { dpl_ISECT_MODE isect_mode; LinearMatrix identity(True); - new DPLStaticChildRenderable( - victim, false /* main zone */, hulk, - isect_mode, INTERSECT_ALL, identity, render_tree.rootRenderable); + if (hulk != NULL) + render_tree.wreckHulk = new DPLStaticChildRenderable( + victim, false /* main zone */, hulk, + isect_mode, INTERSECT_ALL, identity, render_tree.rootRenderable); + if (debris != NULL) + render_tree.wreckDebris = new DPLStaticChildRenderable( + victim, false /* main zone */, debris, + isect_mode, INTERSECT_ALL, identity, render_tree.rootRenderable); } - render_tree.wrecked = 1; + render_tree.wrecked = 1; + render_tree.wreckAge = 0.0f; DEBUG_STREAM << "[BTrender] wreck swap: victim -> '" << (hulk_name[0] ? hulk_name : "gendbr.bgf") << (hulk ? "'" : "' (LOAD FAILED -- body hidden only)") + << (debris ? " + ldbr debris" : "") << "\n" << std::flush; } +// +//############################################################################# +// TickWreck -- the wreck's quadratic SINK (the 1996 burial) +//############################################################################# +// +// FUN_00456410 (the 1996 sink renderable): offsetY = rate * t^2, hulk rate +// -0.025 (armed 0.25s after the boom by a sweep trigger). The ~7-unit hulk is +// fully underground ~17s after the kill -- the wreck visual "fades away" by +// burial; the ENTITY (sim/collision) stays, per the wreck-stays rule. Once +// buried, the pieces are hidden and the sink stops. +// +int + BTL4VideoRenderer::TickWreck(Entity *victim, float dt) +{ + std::map::iterator tree_it = + mMechRenderTrees.find(victim); + if (tree_it == mMechRenderTrees.end()) + return 1; // no tree yet -- not buried + MechRenderTree &render_tree = tree_it->second; + if (!render_tree.wrecked) + return 1; // not swapped yet + if (render_tree.wreckHulk == NULL && render_tree.wreckDebris == NULL) + return 0; // already buried + + render_tree.wreckAge += dt; + float sink = -0.025f * render_tree.wreckAge * render_tree.wreckAge; // the authored rate + if (sink < -8.0f) + { + // fully buried -> hide + stop ticking + if (render_tree.wreckHulk) render_tree.wreckHulk->SetDrawObj(NULL); + if (render_tree.wreckDebris) render_tree.wreckDebris->SetDrawObj(NULL); + render_tree.wreckHulk = NULL; + render_tree.wreckDebris = NULL; + DEBUG_STREAM << "[BTrender] wreck buried (sink complete)\n" << std::flush; + return 0; + } + if (render_tree.wreckHulk) + render_tree.wreckHulk->SetOffsetTranslation(0.0f, sink, 0.0f); + if (render_tree.wreckDebris) + render_tree.wreckDebris->SetOffsetTranslation(0.0f, sink, 0.0f); + return 1; +} + + +// +// Sim-side bridge (UpdateDeathState drives the sink each dead frame). +// +int BTWreckSinkTick(Entity *victim, float dt) +{ + if (victim == NULL || application == NULL) + return 1; + BTL4VideoRenderer *renderer = + (BTL4VideoRenderer *)application->GetVideoRenderer(); + if (renderer == NULL) + return 1; + return renderer->TickWreck(victim, dt); +} + + // // Engine-side bridge (the ExplosionClassID dispatch calls this on effect 104). // diff --git a/game/reconstructed/btl4vid.hpp b/game/reconstructed/btl4vid.hpp index 374088a..3f94129 100644 --- a/game/reconstructed/btl4vid.hpp +++ b/game/reconstructed/btl4vid.hpp @@ -566,11 +566,26 @@ class BTReticleRenderable: int skeletonType; // EntitySegment::SkeletonType used at build HierarchicalDrawComponent *rootRenderable;// the tree root (wreck hulk parent) int wrecked; // 1 = swapped to the dbr hulk + DPLStaticChildRenderable *wreckHulk; // the dbr piece (sinks) + DPLStaticChildRenderable *wreckDebris; // the ldbr scatter (sinks) + float wreckAge; // seconds since the swap std::map segRenderable; // slot -> joint renderable std::map segGState; // slot -> last applied graphic state }; std::map mMechRenderTrees; + public: + // + // The wreck's quadratic SINK (the 1996 script's burial: FUN_00456410 + // computes offsetY = rate * t^2; the hulk's authored rate is -0.025 -> + // the ~7-unit hulk is fully buried ~17s after the kill). Ticked per + // frame from the dead mech's UpdateDeathState. Returns 0 once the + // wreck is fully buried (the caller stops the wreck-smoke re-arm), + // 1 while anything is still visible. + // + int + TickWreck(Entity *victim, float dt); + public: // // The death-wreck swap (ExplosionScripts effect 104, reconstructed): the @@ -661,6 +676,13 @@ extern void BTRemakeMechModel(Entity *entity); // extern void BTSwapMechToWreck(Entity *victim); +// +// Per-frame wreck sink tick (sim -> render bridge; called by the dead mech's +// UpdateDeathState with the frame dt). Returns 0 once the wreck is fully +// buried -- the caller stops re-arming the wreck smoke. +// +extern int BTWreckSinkTick(Entity *victim, float dt); + #endif // BTL4VID_HPP //===========================================================================// diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 2754663..5e974fc 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -955,8 +955,14 @@ void // 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. + // The wreck hulk's quadratic SINK (the 1996 script's burial): the pieces + // settle into the ground and are gone ~17s after the kill. While the + // wreck is still visible, keep the death/rubble smoke plume alive on its + // authored 10s window; once buried, the smoke stops with it. + extern int BTWreckSinkTick(Entity *victim, float dt); + int wreck_visible = BTWreckSinkTick(this, (float)dt); wreckSmokeTimer -= dt; - if (wreckSmokeTimer <= 0.0f) + if (wreck_visible && wreckSmokeTimer <= 0.0f) { wreckSmokeTimer = 10.0f; // DDTHSMK's release window extern void BTStartPfx(int effect_number, float x, float y, float z); diff --git a/scratchpad/bgfbounds.py b/scratchpad/bgfbounds.py new file mode 100644 index 0000000..51a723e --- /dev/null +++ b/scratchpad/bgfbounds.py @@ -0,0 +1,47 @@ +import struct, sys + +VTX_TAGS = {0x80: 12, 0x81: 24, 0x82: 28, 0x88: 20, 0x89: 32, 0x8A: 36} +CONTAINERS = {0x03, 0x40, 0x41, 0x42, 0x46, 0x48, 0x70} # HEADER OBJECT LOD PATCH PMESH SPHERE_LIST BOUND + +def bounds(path): + data = open(path, 'rb').read() + assert data[:8] == b'DIV-BIZ2', path + lo = [1e9, 1e9, 1e9] + hi = [-1e9, -1e9, -1e9] + nverts = 0 + + def walk(pos, end): + nonlocal nverts + while pos < end - 2: + tagword, = struct.unpack_from('> 14) & 3 + if wide == 1: + ln, = struct.unpack_from(' hi[a]: hi[a] = v + nverts += n + elif tag in CONTAINERS: + walk(payload, payload + ln) + pos = payload + ln + walk(8, len(data)) + print('%-14s verts=%5d X[%7.2f %7.2f] Y[%7.2f %7.2f] Z[%7.2f %7.2f]' + % (path.split('\\')[-1], nverts, lo[0], hi[0], lo[1], hi[1], lo[2], hi[2])) + +base = r'C:\git\bt411\content\VIDEO\GEO\%s' +for f in ['LDBR.BGF', 'MDBR.BGF', 'FLAMESML.BGF', 'FLAMEBIG.BGF']: + try: + bounds(base % f) + except Exception as e: + print(f, 'ERR', e)