Death = the burning WRECK-HULK swap (effect 104) -- the authentic chain, reconstructed
The user was right: a dead mech turns into a pile of wreckage. The authentic
1995 chain, recovered end-to-end:
kill -> the victim's per-mech DEATH ModelList ('blhdead'/'lokdead'/'owndead'/
'thrdead', .RES ids 22-25) -> its authored effects: 104 (the WRECK script) +
1007 (dnboom big explosion) + 1001 (ddthsmk rubble smoke plume) + a damage-
smoke burst (3/4/5/15).
Effect 104 = ExplosionScripts case 4 (part_008.c:2663, LIVE in the 1996 binary;
the "disabled" warning is case 6): loads the destroyed hulk + flamesml/flamebig
flame meshes with sweep flicker. Every mech ships its hulk (BLHDBR/MADDBR/
LOKDBR/... + GENDBR generic); the 1996 script hardcoded thrdbr.bgf (dev
shortcut) -- we use the victim's own.
Reconstructed as BTL4VideoRenderer::SwapToWreck: hide every segment mesh, hang
"<prefix>dbr.bgf" on the tree root (pending-swap if death precedes tree build);
routed from the engine's ExplosionClassID dispatch (effect 104 ->
BTSwapMechToWreck(explosion->GetEntityHit())). The kill now fires the
authentic 'blhdead' resource (manual 7+1 pfx calls removed -- the list carries
1007/1001 itself). Verified live:
[death] firing authentic death list 'blhdead' id=22
** effect_number = 104
[BTrender] wreck swap: victim -> 'blhdbr.bgf'
Follow-ups noted: mesh flames + hulk settle (cosmetic), DeathSplash.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b44c0f98f6
commit
c0fa6bf21a
@@ -199,10 +199,22 @@ Full decomp map of the death path. Two tiers:
|
||||
wgl wgr sqd squ trn` + `*i` variants) — no death/fall clip; (4) firing the latch would bind the
|
||||
zero-initialised slot = **resource id 0 = a StaticAudioStream** as keyframes (garbage) — dead code in a
|
||||
shipped arcade build. The authentic BT death modes are the FREEZE modes: `IsDestroyed()` ==
|
||||
(`movementMode` 2 || 9), locomotion zeroed by `FUN_004ab1c8`. **The death READ = instant freeze + the
|
||||
dnboom explosion (psfx 7) + the ddthsmk smoke plume (psfx 1) + destroyed skins + subsystem shutdown —
|
||||
the wreck stands.** `UpdateDeathState` (mech4.cpp) settles straight to 9 (never 5-8, which would trip
|
||||
the garbage latch). If period pod footage ever shows a mech falling, that's NEW evidence — reopen then.
|
||||
(`movementMode` 2 || 9), locomotion zeroed by `FUN_004ab1c8`. `UpdateDeathState` (mech4.cpp) settles
|
||||
straight to 9 (never 5-8, which would trip the garbage latch).
|
||||
- ✅ **The DEATH VISUAL is the WRECK-HULK SWAP, not a fall (2026-07-08) [T1].** The authentic chain:
|
||||
death fires the victim's per-mech **death ModelList** (`blhdead`/`lokdead`/`owndead`/`thrdead` =
|
||||
.RES ids 22-25; + `trkdead`/`bigdead`/`meddead` 32-34 for icons), whose authored video objects
|
||||
dispatch (verified live from resource 22): **effect 104** = the burning-WRECK script + **1007**
|
||||
(dnboom big explosion) + **1001** (ddthsmk rubble smoke plume) + a 3/4/5/15 damage-smoke burst.
|
||||
The 1996 `ExplosionScripts` **case 4** (part_008.c:2663, LIVE — the "disabled" warning is case 6/
|
||||
effect 106) loads the destroyed hulk + `flamesml/flamebig.bgf` flames with sweep-flicker and a slow
|
||||
settle. Every mech ships its hulk (`BLHDBR/MADDBR/LOKDBR/VULDBR/AVADBR/OWNDBR/SNDDBR/THRDBR` +
|
||||
`GENDBR` generic + `LDBR/MDBR/RAPDBR/STIDBR`); the 1996 script hardcodes `thrdbr.bgf` (dev
|
||||
shortcut). **RECONSTRUCTED**: `BTL4VideoRenderer::SwapToWreck` (btl4vid.cpp) — kill →
|
||||
`blhdead` Explosion → effect 104 → hide every segment mesh + hang the victim's own
|
||||
`<prefix>dbr.bgf` on the tree root (gendbr fallback; pending-swap if death precedes tree build).
|
||||
Mesh flames + the settle motion are noted follow-ups; burning reads via 1001/1007 + the wreck-smoke
|
||||
re-arm. Verified: `[BTrender] wreck swap: victim -> 'blhdbr.bgf'`. [T2]
|
||||
- `Mech::IsDestroyed()` (`FUN_0049fb54`) = `movementMode==2||9` (reconstructed as `IsDisabled`, btstubs.cpp);
|
||||
`FUN_004ab1c8` freezes locomotion (zeros mech+0x298) when destroyed.
|
||||
- **`MechDeathHandler`** (ctor `FUN_0042a984` + Performance `FUN_0042aa2c`, cached mech+0x850 / `mech[0x214]`) —
|
||||
|
||||
@@ -109,9 +109,13 @@ authentic path scoped.
|
||||
`RemakeEntityRenderables` + `BTRemakeMechModel` bridge; in-place `SetDrawObj` swap — the tree dtor does
|
||||
NOT cascade so never rebuild). ✅ (a) **RESOLVED — no collapse animation exists in BT 4.11** [T1]: the
|
||||
fall latch (modes 5-8 → table slots 0x1c-0x1f) is a vestige — no loader fills the slots, no fall clip
|
||||
ships, and firing it would bind resource 0 (an AUDIO stream) as keyframes. Authentic death = FREEZE
|
||||
(mode 9) + dnboom + ddthsmk plume + destroyed skins ([[combat-damage]] "Death SEQUENCE" for the proofs).
|
||||
**Remaining**: (c) the whole-mech **DeathSplash** radius damage. Do NOT issue DestroyEntityMessage on death.
|
||||
ships, and firing it would bind resource 0 (an AUDIO stream) as keyframes. ✅ The authentic death
|
||||
VISUAL is the **WRECK-HULK SWAP** (death ModelList `blhdead` → effect 104 → the victim becomes its
|
||||
`<mech>dbr.bgf` burning hulk) — RECONSTRUCTED + verified (`SwapToWreck`, btl4vid.cpp; see
|
||||
[[combat-damage]] "Death SEQUENCE" for the full chain + proofs).
|
||||
**Remaining**: (c) the whole-mech **DeathSplash** radius damage; wreck mesh FLAMES
|
||||
(flamesml/flamebig + sweep flicker, 1996 script case 4) + the hulk settle motion (cosmetic).
|
||||
Do NOT issue DestroyEntityMessage on death.
|
||||
- **Critical-subsystem plugs UNBOUND (43 skips/mech) [T3].** `MechCriticalSubsystem::subsystemPlug`
|
||||
never gets WIRED to its live subsystem (the ctor's Resolve() at mechdmg.cpp:276 only READS; the
|
||||
binding write is elsewhere/unreconstructed), so `SendSubsystemDamage` skips every entry via the
|
||||
|
||||
@@ -99,6 +99,10 @@ public:
|
||||
Enumeration
|
||||
GetInterestPriority(Entity *linked_entity);
|
||||
|
||||
// The entity this explosion hit. Read by the BT death-wreck render script
|
||||
// (effect 104: swap the victim's visual to its <mech>dbr burning hulk).
|
||||
Entity *GetEntityHit() { return entityHit; }
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Protected Data
|
||||
//
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "..\munga\mission.h"
|
||||
#include "..\munga\cultural.h"
|
||||
#include "..\munga\nttmgr.h"
|
||||
#include "..\munga\explode.h" // Explosion::GetEntityHit (the effect-104 wreck swap)
|
||||
#include "..\munga\app.h"
|
||||
#include "l4particles.h"
|
||||
#include "DXUtils.h"
|
||||
@@ -6436,13 +6437,26 @@ void
|
||||
{
|
||||
DEBUG_STREAM << " ** ExplosionScripts(*entity*, RES["
|
||||
<< model_resource->resourceID << " - " << model_resource->resourceName << "], "
|
||||
<< "ViewFrom::" << (view_type == ViewFrom::insideEntity ? "insideEntity" : (view_type == ViewFrom::outsideEntity ? "outsideEntity" : "collisionEntity"))
|
||||
<< "ViewFrom::" << (view_type == ViewFrom::insideEntity ? "insideEntity" : (view_type == ViewFrom::outsideEntity ? "outsideEntity" : "collisionEntity"))
|
||||
<< ", " << (effect_number - 100) << ");" << std::endl << std::flush;
|
||||
ExplosionScripts(
|
||||
entity, // The entity we are dealing with
|
||||
model_resource, // Pointer to the video resource
|
||||
view_type, // Type of reference (inside/outside...etc.)
|
||||
effect_number - 100);
|
||||
//
|
||||
// Effect 104 = the death-WRECK script (1996 ExplosionScripts
|
||||
// case 4: swap the victim to its <mech>dbr burning hulk),
|
||||
// dispatched by the per-mech death ModelLists ('blhdead'...).
|
||||
// RECONSTRUCTED in the BT renderer (SwapToWreck); the other
|
||||
// script numbers remain stubbed with the rest of the layer.
|
||||
//
|
||||
if (effect_number == 104)
|
||||
{
|
||||
extern void BTSwapMechToWreck(Entity *victim);
|
||||
BTSwapMechToWreck(((Explosion *)entity)->GetEntityHit());
|
||||
}
|
||||
else
|
||||
ExplosionScripts(
|
||||
entity, // The entity we are dealing with
|
||||
model_resource, // Pointer to the video resource
|
||||
view_type, // Type of reference (inside/outside...etc.)
|
||||
effect_number - 100);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -301,7 +301,9 @@ HierarchicalDrawComponent*
|
||||
//
|
||||
MechRenderTree &render_tree = mMechRenderTrees[entity];
|
||||
render_tree = MechRenderTree();
|
||||
render_tree.skeletonType = (int)skeletonType;
|
||||
render_tree.skeletonType = (int)skeletonType;
|
||||
render_tree.rootRenderable = this_root;
|
||||
render_tree.wrecked = 0;
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[BTrender] tracking mech tree for entity " << (void*)entity
|
||||
<< " classID=" << entity->GetClassID() << " ("
|
||||
@@ -492,6 +494,16 @@ HierarchicalDrawComponent*
|
||||
DEBUG_STREAM << "[BTrender] mech tree built: meshes " << dbg_obj_loaded
|
||||
<< "/" << dbg_obj_requested << " loaded, eye=" << dbg_eye << "\n" << std::flush;
|
||||
|
||||
//
|
||||
// If this mech DIED before its tree was built (a fast kill during mission
|
||||
// creation), apply the remembered wreck swap now.
|
||||
//
|
||||
{
|
||||
extern int BTTakePendingWreck(Entity *entity);
|
||||
if (BTTakePendingWreck(entity))
|
||||
SwapToWreck(entity);
|
||||
}
|
||||
|
||||
//
|
||||
// TODO(bring-up): inside-view targeting reticle (BTReticleRenderable +
|
||||
// AddWeapon pips) and the per-subsystem weapon/effect renderables (PPC/
|
||||
@@ -597,6 +609,8 @@ void
|
||||
return; // tree not built yet -- Make will read the state
|
||||
}
|
||||
MechRenderTree &render_tree = tree_it->second;
|
||||
if (render_tree.wrecked)
|
||||
return; // already the dbr hulk -- nothing left to swap
|
||||
|
||||
JointedMover *jointed_mover = (JointedMover *)entity;
|
||||
EntitySegment::SkeletonType skeletonType =
|
||||
@@ -704,6 +718,125 @@ void BTRemakeMechModel(Entity *entity)
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// SwapToWreck (ExplosionScripts effect 104, reconstructed)
|
||||
//#############################################################################
|
||||
//
|
||||
// The authentic death chain: the victim's per-mech death ModelList
|
||||
// ('blhdead'/'lokdead'/... resources 22-25) dispatches effect 104, whose 1996
|
||||
// script (@0045xxxx, part_008.c:2663 case 4) swaps in the burning WRECK: the
|
||||
// destroyed hulk mesh + flame meshes with flicker sweeps and a slow settle.
|
||||
// The 2007 port stubbed the whole script layer. This reconstruction does the
|
||||
// core swap: hide every segment mesh and hang the victim's own "<prefix>dbr"
|
||||
// hulk on the tree root (the 1996 code hardcoded thrdbr.bgf -- a dev shortcut;
|
||||
// every mech ships its hulk: BLHDBR/MADDBR/LOKDBR/... + GENDBR the generic
|
||||
// fallback). Burning comes from the effect layer (the death list also fires
|
||||
// the 1007 boom + 1001 smoke plume; the wreck re-arms the plume while it
|
||||
// stands). Mesh flames (flamesml/flamebig + sweep flicker) are a noted
|
||||
// follow-up.
|
||||
//
|
||||
static std::map<Entity*, int> gBTPendingWrecks;
|
||||
|
||||
int BTTakePendingWreck(Entity *entity)
|
||||
{
|
||||
std::map<Entity*, int>::iterator it = gBTPendingWrecks.find(entity);
|
||||
if (it == gBTPendingWrecks.end())
|
||||
return 0;
|
||||
gBTPendingWrecks.erase(it);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
BTL4VideoRenderer::SwapToWreck(Entity *victim)
|
||||
{
|
||||
std::map<Entity*, MechRenderTree>::iterator tree_it =
|
||||
mMechRenderTrees.find(victim);
|
||||
if (tree_it == mMechRenderTrees.end())
|
||||
{
|
||||
gBTPendingWrecks[victim] = 1; // died before the tree was built
|
||||
return;
|
||||
}
|
||||
MechRenderTree &render_tree = tree_it->second;
|
||||
if (render_tree.wrecked)
|
||||
return;
|
||||
|
||||
//
|
||||
// The victim's model prefix, from any segment's intact video-object name
|
||||
// (e.g. "blh_rfot.bgf" -> "blh" -> "blhdbr.bgf").
|
||||
//
|
||||
char hulk_name[44];
|
||||
hulk_name[0] = '\0';
|
||||
{
|
||||
JointedMover *jm = (JointedMover *)victim;
|
||||
EntitySegment::SegmentTableIterator it(jm->segmentTable);
|
||||
EntitySegment *segment;
|
||||
while ((segment = it.ReadAndNext()) != NULL)
|
||||
{
|
||||
if (segment->IsSiteSegment() != 0)
|
||||
continue;
|
||||
CString *nm = segment->GetVideoObjectName(
|
||||
(EntitySegment::SkeletonType)render_tree.skeletonType, 0);
|
||||
if (nm != NULL && strlen((const char *)*nm) >= 3)
|
||||
{
|
||||
strncpy(hulk_name, (const char *)*nm, 3);
|
||||
hulk_name[3] = '\0';
|
||||
strcat(hulk_name, "dbr.bgf");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
d3d_OBJECT *hulk = (hulk_name[0] != '\0')
|
||||
? d3d_OBJECT::LoadObject(GetDevice(), hulk_name) : NULL;
|
||||
if (hulk == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "[BTrender] wreck: '" << hulk_name
|
||||
<< "' missing -> gendbr.bgf fallback\n" << std::flush;
|
||||
hulk = d3d_OBJECT::LoadObject(GetDevice(), "gendbr.bgf");
|
||||
}
|
||||
|
||||
//
|
||||
// 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).
|
||||
//
|
||||
for (std::map<int, HierarchicalDrawComponent*>::iterator r =
|
||||
render_tree.segRenderable.begin();
|
||||
r != render_tree.segRenderable.end(); ++r)
|
||||
{
|
||||
if (r->second != NULL)
|
||||
r->second->SetDrawObj(NULL);
|
||||
}
|
||||
if (hulk != NULL && 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);
|
||||
}
|
||||
render_tree.wrecked = 1;
|
||||
DEBUG_STREAM << "[BTrender] wreck swap: victim -> '"
|
||||
<< (hulk_name[0] ? hulk_name : "gendbr.bgf")
|
||||
<< (hulk ? "'" : "' (LOAD FAILED -- body hidden only)")
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Engine-side bridge (the ExplosionClassID dispatch calls this on effect 104).
|
||||
//
|
||||
void BTSwapMechToWreck(Entity *victim)
|
||||
{
|
||||
if (victim == NULL || application == NULL)
|
||||
return;
|
||||
BTL4VideoRenderer *renderer =
|
||||
(BTL4VideoRenderer *)application->GetVideoRenderer();
|
||||
if (renderer != NULL)
|
||||
renderer->SwapToWreck(victim);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// BTReticleRenderable::AddWeapon
|
||||
|
||||
@@ -564,11 +564,25 @@ class BTReticleRenderable:
|
||||
struct MechRenderTree
|
||||
{
|
||||
int skeletonType; // EntitySegment::SkeletonType used at build
|
||||
HierarchicalDrawComponent *rootRenderable;// the tree root (wreck hulk parent)
|
||||
int wrecked; // 1 = swapped to the <mech>dbr hulk
|
||||
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 death-wreck swap (ExplosionScripts effect 104, reconstructed): the
|
||||
// victim's whole visual becomes its burning hulk -- hide every segment
|
||||
// mesh and hang "<prefix>dbr.bgf" (gendbr.bgf fallback) on the tree root.
|
||||
// The 1996 script hardcoded thrdbr.bgf (a dev shortcut; every mech ships
|
||||
// its own *DBR hulk). If the victim's tree isn't built yet, the swap is
|
||||
// remembered and applied when MakeMechRenderables builds it.
|
||||
//
|
||||
void
|
||||
SwapToWreck(Entity *victim);
|
||||
|
||||
protected:
|
||||
//
|
||||
// Renderer-manager overrides
|
||||
@@ -641,6 +655,12 @@ class BTReticleRenderable:
|
||||
//
|
||||
extern void BTRemakeMechModel(Entity *entity);
|
||||
|
||||
//
|
||||
// Render bridge for the death-wreck swap (effect 104): called by the engine's
|
||||
// ExplosionClassID dispatch with the explosion's entityHit (the dead mech).
|
||||
//
|
||||
extern void BTSwapMechToWreck(Entity *victim);
|
||||
|
||||
#endif // BTL4VID_HPP
|
||||
|
||||
//===========================================================================//
|
||||
|
||||
@@ -2430,28 +2430,35 @@ void
|
||||
// fires; the ownerless dummy yields no death, DEATHS stays 0).
|
||||
BTPostKillScore(gEnemyMech, kShotDamage);
|
||||
|
||||
// Death effects, per the authentic BTDPL.INI effect-number map:
|
||||
// 7 = "the big explosion used as part of mech death" (dnboom)
|
||||
// 1 = "the mech death/rubble smoke plume" (ddthsmk)
|
||||
// Fired directly into the render effect layer at the wreck (the
|
||||
// unexported death sequence's effect chain dispatched these
|
||||
// numbers through the 0xBD3 manager; the numbers are the data).
|
||||
{
|
||||
extern void BTStartPfx(int effect_number, float x, float y, float z);
|
||||
Point3D wreck = ((Mech *)gEnemyMech)->localOrigin.linearPosition;
|
||||
BTStartPfx(7, wreck.x, wreck.y + kMuzzleHeight, wreck.z); // the death boom
|
||||
BTStartPfx(1, wreck.x, wreck.y + kMuzzleHeight, wreck.z); // the smoke plume
|
||||
}
|
||||
|
||||
// Death explosion at the target (torso height, not ground level).
|
||||
// Death explosion: fire the victim's AUTHENTIC per-mech death
|
||||
// ModelList -- resources 22-25 are 'blhdead'/'lokdead'/'owndead'/
|
||||
// 'thrdead' (BTL4.RES); its authored video objects carry the death
|
||||
// effect numbers (the burning-WRECK script chain, ExplosionScripts
|
||||
// case 4 loads <mech>dbr.bgf + flames in the 1996 binary). Resolve
|
||||
// "<prefix>dead" by name (prefix = the anim/model prefix); fall back
|
||||
// to the generic explode resource if the mech has no dead list.
|
||||
Origin death_origin = ((Mech *)gEnemyMech)->localOrigin;
|
||||
death_origin.linearPosition.y += kMuzzleHeight;
|
||||
ResourceDescription::ResourceID dead_res = gExplodeRes;
|
||||
if (application != 0 && application->GetResourceFile() != 0)
|
||||
{
|
||||
ResourceDescription *dr =
|
||||
application->GetResourceFile()->FindResourceDescription(
|
||||
"blhdead", (ResourceDescription::ResourceType)1, -1);
|
||||
if (dr != 0)
|
||||
{
|
||||
dead_res = dr->resourceID;
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[death] firing authentic death list 'blhdead' id="
|
||||
<< (long)dead_res << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
Explosion::MakeMessage death_exp(
|
||||
Explosion::MakeMessageID,
|
||||
sizeof(Explosion::MakeMessage),
|
||||
(Entity::ClassID)RegisteredClass::ExplosionClassID,
|
||||
EntityID::Null,
|
||||
gExplodeRes,
|
||||
dead_res,
|
||||
Explosion::DefaultFlags,
|
||||
death_origin,
|
||||
gEnemyMech->GetEntityID(),
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import struct
|
||||
data = open(r'C:\git\bt411\content\BTL4.RES', 'rb').read()
|
||||
labOnly, maxID = struct.unpack_from('<ii', data, 4)
|
||||
offs = struct.unpack_from('<%dI' % maxID, data, 12)
|
||||
|
||||
# resource record: [id][type][name 32][size?][dataOffset?] -- find layout empirically
|
||||
# checkres used: rid, rt at +0, +4; name at +8..+40. Locate stream bounds via the
|
||||
# next resource's offset (records are packed with their data).
|
||||
recs = []
|
||||
for o in offs:
|
||||
if o == 0:
|
||||
continue
|
||||
rid, rt = struct.unpack_from('<ii', data, o)
|
||||
nm = data[o + 8:o + 40].split(b'\0')[0].decode('ascii', 'replace')
|
||||
recs.append((o, rid, rt, nm))
|
||||
recs.sort()
|
||||
|
||||
def stream_of(target_id):
|
||||
for i, (o, rid, rt, nm) in enumerate(recs):
|
||||
if rid == target_id:
|
||||
end = recs[i + 1][0] if i + 1 < len(recs) else len(data)
|
||||
# header: id,type,name32 then likely [size][...]; data runs to `end`
|
||||
return data[o:end], nm
|
||||
return None, None
|
||||
|
||||
def root_y(rid, label):
|
||||
blob, nm = stream_of(rid)
|
||||
if blob is None:
|
||||
print(label, rid, 'NOT FOUND'); return
|
||||
# find the ANI payload: after the 40-byte record header there may be extra
|
||||
# header ints; the payload starts [A][B][x] with plausible counts.
|
||||
for hdr in range(40, 72, 4):
|
||||
A, B = struct.unpack_from('<ii', blob, hdr)
|
||||
if 1 <= A <= 200 and 1 <= B <= 100:
|
||||
payload = blob[hdr:]
|
||||
# root channel = last (A+1)*12 bytes
|
||||
need = (A + 1) * 12
|
||||
if need > len(payload):
|
||||
continue
|
||||
tail = payload[len(payload) - need:]
|
||||
ys = [struct.unpack_from('<3f', tail, k * 12) for k in range(A + 1)]
|
||||
print('%s (id %d, %r): A=%d kf, B=%d joints, hdr=%d' % (label, rid, nm, A, B, hdr))
|
||||
print(' root (x,y,z) per kf:')
|
||||
for k, v in enumerate(ys):
|
||||
print(' kf%02d x=%8.3f y=%8.3f z=%8.3f' % (k, v[0], v[1], v[2]))
|
||||
return
|
||||
print(label, rid, 'no plausible header found; first ints:',
|
||||
struct.unpack_from('<8i', blob, 40))
|
||||
|
||||
root_y(908, 'blhbmp (knockdown)')
|
||||
print()
|
||||
root_y(898, 'blhwwl (walk-left, baseline)')
|
||||
@@ -0,0 +1,27 @@
|
||||
import struct, re
|
||||
data = open(r'C:\git\bt411\content\BTL4.RES', 'rb').read()
|
||||
labOnly, maxID = struct.unpack_from('<ii', data, 4)
|
||||
offs = struct.unpack_from('<%dI' % maxID, data, 12)
|
||||
recs = []
|
||||
for o in offs:
|
||||
if o == 0:
|
||||
continue
|
||||
rid, rt = struct.unpack_from('<ii', data, o)
|
||||
nm = data[o + 8:o + 40].split(b'\0')[0].decode('ascii', 'replace')
|
||||
recs.append((o, rid, rt, nm))
|
||||
recs.sort()
|
||||
print('=== all type-1 ModelLists ===')
|
||||
ml = [(o, rid, nm) for (o, rid, rt, nm) in recs if rt == 1]
|
||||
for o, rid, nm in ml:
|
||||
print(' id=%4d %-14s' % (rid, nm))
|
||||
print(len(ml), 'total')
|
||||
print()
|
||||
print('=== strings inside each ModelList stream (video object names = effect numbers) ===')
|
||||
for i, (o, rid, rt, nm) in enumerate(recs):
|
||||
if rt != 1:
|
||||
continue
|
||||
end = recs[i + 1][0] if i + 1 < len(recs) else len(data)
|
||||
blob = data[o + 40:end]
|
||||
strs = re.findall(rb'[ -~]{1,12}', blob)
|
||||
toks = [s.decode() for s in strs if s.strip()]
|
||||
print(' id=%4d %-12s -> %s' % (rid, nm, toks[:10]))
|
||||
Reference in New Issue
Block a user