Wreck sink (quadratic burial) + ldbr debris field -- the 1996 script completed
Answers "does the wreck fade away?": YES, by sinking. FUN_00456410 (the 1996 sink renderable) computes offsetY = rate * t^2; the hulk's authored rate is -0.025 -> the ~7-unit hulk is fully underground ~17s after the kill. The script also pairs the standing hulk with the LDBR strewn-debris field (12x13u flat scatter), parented together and sinking together. Also verified from the mesh data: BLHDBR (1537 verts -- more than the intact torso) IS the authored Blackhawk wreck: the classic standing-leg-in-rubble sculpt. The "just a leg standing there" report is the authentic art. (THRDBR -- the mesh the 1996 script hardcoded -- parses to ZERO vertices; more evidence the hardcode was an unfinished dev shortcut.) Implementation: SwapToWreck adds the ldbr piece; TickWreck applies the quadratic sink per frame (driven from the dead mech's UpdateDeathState), hides both pieces at burial and reports it so the wreck-smoke re-arm stops with the wreck. DPLStaticChildRenderable::SetOffsetTranslation added (Execute re-reads OrientationMatrix per frame -- same in-place idiom as SetDrawObj). Lifecycle verified live: kill -> 'blhdbr.bgf' + ldbr debris -> smoke re-arm @10s -> wreck buried @~17s -> smoke stops. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c0fa6bf21a
commit
5236d4263a
@@ -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;
|
||||
|
||||
|
||||
@@ -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<int, HierarchicalDrawComponent*>::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<Entity*, MechRenderTree>::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).
|
||||
//
|
||||
|
||||
@@ -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 <mech>dbr hulk
|
||||
DPLStaticChildRenderable *wreckHulk; // the <mech>dbr piece (sinks)
|
||||
DPLStaticChildRenderable *wreckDebris; // the ldbr scatter (sinks)
|
||||
float wreckAge; // seconds since the swap
|
||||
std::map<int, HierarchicalDrawComponent*> segRenderable; // slot -> joint renderable
|
||||
std::map<int, int> segGState; // slot -> last applied graphic state
|
||||
};
|
||||
std::map<Entity*, MechRenderTree> 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
|
||||
|
||||
//===========================================================================//
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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('<H', data, pos)
|
||||
tag = tagword & 0x2fff
|
||||
wide = (tagword >> 14) & 3
|
||||
if wide == 1:
|
||||
ln, = struct.unpack_from('<H', data, pos + 2)
|
||||
hdr = 4
|
||||
else:
|
||||
ln = data[pos + 2]
|
||||
hdr = 3
|
||||
payload = pos + hdr
|
||||
if tag in VTX_TAGS:
|
||||
stride = VTX_TAGS[tag]
|
||||
n = ln // stride
|
||||
for i in range(n):
|
||||
x, y, z = struct.unpack_from('<3f', data, payload + i * stride)
|
||||
for a, v in enumerate((x, y, z)):
|
||||
if v < lo[a]: lo[a] = v
|
||||
if v > 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)
|
||||
Reference in New Issue
Block a user