Files
BT412/game/reconstructed/mech3.cpp
T
arcattackandClaude Fable 5 2e9c78d604 Gait: the AUTHENTIC walk lean -- interior/exterior clip sets, level cockpit + leaning mechs (task #59)
The MadCat (and every leg-mech) leans -8deg into a walk / -11deg into a run --
an authored jointhip (hingex) pose in the EXTERIOR gait clips that the walk/run
CYCLE clips never rebind, so it HOLDS.  The 1995 game keeps a separate INTERIOR
('i'-suffix) clip set for the mech you PILOT: those shake jointshakey (cockpit
rattle) and OMIT jointhip, so your own view stays level while everyone else sees
you lean.  The port had this entirely dead -- the local cockpit mech pitched
-8deg into the ground (user-reported "staring at the ground").

ROOT CAUSE (two stacked bugs):
- The authentic ctor clip-set gate (@part_012.c:10308-10320) was reconstructed
  as no-op stubs: LoadLowDetailBody/LoadHighDetailBody MISLABELED the
  FUN_004a80d4/86c8 GAIT-CLIP loader addresses as a "body LOD" pair, so the gate
  did nothing and a separate unconditional LoadLocomotionClips always loaded
  EXTERIOR clips.
- LoadLocomotionClipsExt (the interior loader) was a stub aliased to the
  exterior loader.

FIX:
- Reconstructed the real 4-char INTERIOR loader (byte-exact vs @004a86c8; the
  'i'-suffix table dumped from the exe @0x10d74d: swri/wwri/wwli/...; all 18
  base clips confirmed present in BTL4.RES for mad/blh/ava).
- Fixed the ctor gate: getenv("L4VIEWEXT") || (instanceFlags&0xC)==4 -> exterior
  (replicant / forced external view); else -> interior (local cockpit master).
  Removed the mislabeling no-op stubs.
- PORT ADAPTATION (MaintainViewClipSet, per-frame in PerformAndWatch): the port
  never sets the replicant COPY bit (all MP mechs build as local masters --
  heat-sim/scoring/torso-watcher-connect all run on both, by design), so the
  ctor gate lands everyone on interior.  Pick the set by VIEWPOINT instead: the
  mech you pilot keeps interior (level cockpit), every other mech flips once to
  exterior (the lean).  Reloads only on a viewpoint-status change; the sets
  share stride data so the swap is seamless.  Model pointers stashed at ctor.

VERIFIED live (BT_HIP_LOG probe): your walking mech writes jointhip=0 (level);
the peer's replica in the other pod's view leans at exactly -8.0/-11.1deg --
the authored clip values.  A/B control: L4VIEWEXT=1 forces the walking master
back to 74 sustained -8/-11 writes.

KB: locomotion.md ("NO walk lean" audit conclusion CORRECTED -- the lean is
authored + sustained + clip-set-specific), asset-formats.md (.ANI parses
pitch/yaw/roll for all joint types; KeyJointPos translation; cycle clips carry
no pitch), open-questions.md (item closed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 14:46:22 -05:00

939 lines
46 KiB
C++

//===========================================================================//
// File: mech3.cpp //
// Project: BattleTech Brick: Entity Manager //
// Contents: Mech locomotion-clip loading + offline subsystem-stream authoring //
// -- third implementation slice //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// --/--/95 ?? Initial coding. //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
// PROPRIETARY AND CONFIDENTIAL //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary (Ghidra pseudo-C in
// all/part_012.c, cluster 0x004a7f50-0x004a9b5a) cross-referenced with the
// mech2.cpp animation member map, the CLASSMAP subsystem ClassID registry,
// and the surviving subsystem-name string table at .rdata:0050da25.
//
// The decompiler tagged every function in this window as file=? (the
// MECH*.CPP attribution survived only on the four mech2 gait functions).
// Attribution to mech3.cpp is by RANGE (this is the lower half of the
// 0x4a8054-0x4ac868 gap between mech2.cpp @0x4a5028 and heat.cpp @0x4ac868)
// and by COHESION: the six functions below form two clean groups --
//
// (A) per-frame-independent MODEL BUILD: walk the model's animation-clip
// table, bind each gait clip to the leg SequenceController, integrate
// its keyframes to recover the per-cycle stride length, and cache the
// resulting speed caps / stride lengths used by the mech2 gait state
// machines (legCycleSpeed slewing reads 0x530/0x534/0x538/0x52c/...).
// @004a7f50 Mech::ResolveAnimationClip (helper; mech2/mech3 boundary)
// @004a8054 Mech::MeasureClipStride (helper)
// @004a80d4 Mech::LoadLocomotionClips (1522 bytes)
// @004a86c8 Mech::LoadLocomotionClipsExt (1525 bytes; 4-char codes)
//
// (B) OFFLINE resource authoring (tool path): parse the "gamedata"/
// "Subsystems" notation block of a model file and emit one streamed
// Subsystem resource per component, dispatching on the component's
// type-name string to the right Subsystem::CreateStreamedSubsystem.
// @004a8cc0 Mech::CreateSubsystemStream (static; type dispatch)
// @004a9390 Mech::CreateSubsystemsStream (static; container build)
// @004a9770 Mech::SubsystemDefaultData (static; name->SharedData)
//
// These are Mech METHODS / static members. The Mech class declaration is
// owned by mech.cpp / mech.hpp; this file declares no header of its own.
// Member offsets it touches are documented in the "Mech model-build member
// map" block below for the mech.hpp owner to fold in (see report). Byte
// offsets are noted in comments (e.g. "@0x534"); the decomp addresses the
// object as int words, so this+0xNN there == byte offset 0xNN in C++.
//
//---------------------------------------------------------------------------//
// Helper / engine routine name mapping used below:
// FUN_004277a8 SequenceController::SelectSequence(clip, dbg...)
// (binds a clip handle into the leg SequenceController @0x65c;
// after the bind, the controller's keyframe table is
// readable at 0x670 count / 0x680 times[] / 0x690 data[])
// FUN_00406ff8 ResourceManager::Find(file68, name, 0x10, -1) -> Logical
// (true if a resource by that name exists; used to probe
// for optional jump-jet / extra clip sets)
// FUN_004a7f50 Mech::ResolveAnimationClip(prefix, suffix) -> &ResourceID
// FUN_004a8054 Mech::MeasureClipStride(slot, &total, &lastKey)
// FUN_00404088 NotationFile::FindBlock(parent, group, key, &out) -> Logical
// FUN_004064fc ResourceDirectories::ResolveInclude(dirs, name) -> path
// FUN_00403e84/00403ecc NotationFile open / close(refcount)
// FUN_0040485c NotationFile::Root(file) -> node
// FUN_004022b0/004022e8/004022d0 pool alloc / free / free-path
// FUN_00406db4 ResourceFile::WriteResource(file,name,type,...,blob,len,-1)
// FUN_004dbb24 DebugStream::operator<<(stream,str)
// FUN_004db92c DebugStream::operator<<(stream,char)
// FUN_004d9c38 DebugStream::flush/emit
// FUN_004d4c78/004d4a78/004d4b9c/004d4b58 CString find/len/copy/cmp
// FUN_0041a1a4 Object::IsDerivedFrom(classID) (used by the runtime path)
//
// DAT pools referenced (read-only):
// DAT_004efc94 the global Application; +0x68 == its ResourceManager
// DAT_0050d8d5.. 3-letter gait-clip suffix table (see suffix map below)
// DAT_0050d94d.. 4-char gait-clip suffix table for the *Ext loader
// s_*ClassID_0050da25.. component type-name strings (see dispatch below)
//
#include <bt.hpp>
#pragma hdrstop
#if !defined(MECH_HPP)
# include <mech.hpp> // Mech class -- owned by mech.cpp slice
#endif
#if !defined(APP_HPP)
# include <app.hpp>
#endif
//===========================================================================//
// Reconstruction stand-ins LOCAL to this translation unit.
// The offline "Subsystems" authoring path dispatches each streamed component
// to its subsystem class's static CreateStreamedSubsystem / DefaultData.
// Those ~21 classes live in sibling modules not visible here (and PPC /
// GaussRifle / SubsystemMessageManager have no reconstructed class at all),
// so the uniform factory surface is declared here as minimal stubs. The
// bodies/data are resolved at link against the real subsystem modules; these
// declarations only let the dispatch compile. Kept LOCAL so they never
// collide with the real sibling classes.
//===========================================================================//
typedef Mech::SharedData SharedData; // namespace-scope alias (== Entity__SharedData)
// 7-arg (non-weapon) streamed-subsystem factory shape.
#define STREAMED_SUBSYS(NAME) \
struct NAME { \
static SharedData DefaultData; \
static int CreateStreamedSubsystem(NotationFile *, const char *, \
const char *, void *, NotationFile *, \
const ResourceDirectories *, int); \
}
// 8-arg (weapon) streamed-subsystem factory shape (leading pass counter).
#define STREAMED_WEAPON(NAME) \
struct NAME { \
static SharedData DefaultData; \
static int CreateStreamedSubsystem(int, NotationFile *, \
const char *, const char *, void *, NotationFile *, \
const ResourceDirectories *, int); \
}
STREAMED_SUBSYS(HeatSink);
STREAMED_SUBSYS(Reservoir);
STREAMED_SUBSYS(Condenser);
STREAMED_SUBSYS(PoweredSubsystem);
STREAMED_SUBSYS(Myomers);
STREAMED_SUBSYS(Generator);
STREAMED_SUBSYS(HUD);
STREAMED_SUBSYS(Searchlight);
STREAMED_SUBSYS(ThermalSight);
STREAMED_SUBSYS(Sensor);
STREAMED_SUBSYS(Gyroscope);
STREAMED_SUBSYS(Torso);
STREAMED_WEAPON(Emitter);
STREAMED_WEAPON(PPC);
STREAMED_WEAPON(ProjectileWeapon);
STREAMED_WEAPON(MissileLauncher);
STREAMED_WEAPON(GaussRifle);
STREAMED_WEAPON(AmmoBin);
// MechTech is forward-declared in mech.hpp; complete it (non-weapon shape).
struct MechTech
{
static SharedData DefaultData;
static int CreateStreamedSubsystem(NotationFile *, const char *,
const char *, void *, NotationFile *,
const ResourceDirectories *, int);
};
// MechControlsMapper is forward-declared in mech.hpp; only its DefaultData is used.
struct MechControlsMapper { static SharedData DefaultData; };
// SubsystemMessageManager: leading pass, no trailing index, no DefaultData.
struct SubsystemMessageManager
{
static int CreateStreamedSubsystem(int, NotationFile *, const char *,
const char *, void *, NotationFile *, const ResourceDirectories *);
};
//---------------------------------------------------------------------------//
// CreateSubsystemsStream (offline tool path) artifact shims.
// The container builder drives NotationFile / ResourceDirectories and a
// pool/blob writer through an invented API; these local proxies (cast onto
// the real incoming pointers) + free helpers carry that surface.
//---------------------------------------------------------------------------//
struct NotationNode
{
NotationNode *firstChild;
NotationNode *next;
void Release(int) {}
};
struct NoteX // (cast of NotationFile*)
{
template<class...A> int FindBlock(A&&...) { return 0; }
};
struct DirsX2 { const char *ResolveInclude(const char *, const char *) { return ""; } }; // (cast of ResourceDirectories*)
struct BlobWriter
{
int *p;
BlobWriter() : p(0) {}
void Init(void *blob, int, int) { p = (int *)blob; }
int *put32(int v) { if (p) *p++ = v; return p; }
int *current() { return p; }
void advance(int words) { p += words; }
int length() { return 0; }
};
template<class...A> inline NotationFile *OpenNote(A&&...) { return 0; }
template<class A> inline NotationNode *RootNote(A) { return 0; }
template<class...A> inline void CloseNote(A&&...) {}
template<class...A> inline void FreePath(A&&...) {}
template<class...A> inline void PoolFree(A&&...) {}
template<class...A> inline void PoolFreeBuf(A&&...){}
inline void *PoolAlloc(size_t bytes) { return ::operator new(bytes); }
inline const char *StrFind(const char *s, const char *) { return s; }
inline int StrLen(const char *s) { return (int)(s ? strlen(s) : 0); }
inline char *StrCopyN(char *d, const char *s, int n) { if (d && s) strncpy(d, s, n); return d; }
template<class...A> inline void *FUN_00406db4(A&&...) { return 0; } // ResourceFile::WriteResource
//###########################################################################
//################ Mech model-build member map (offsets) ################
//###########################################################################
//
// For the mech.hpp owner. Scalar unless noted. Names continue the mech2
// map; "?" flags an uncertain semantic. The speed-cap / stride members are
// shared with the mech2 gait state machines (those are the *readers*; the
// loaders below are the *writers*).
//
// @0x34c reverseStrideLength written = (s_c+s_d)/(d_c+d_d) (slots 0xc/0xd)
// @0x350 gimpStrideLength written = -(s_12+s_13)/(d_12+d_13); stored negative
// @0x52c gimpSpeedMax = last-keyframe stride of the gimp-base clip
// @0x530 standSpeed = last-keyframe stride of the stand->walk clip
// @0x534 walkStrideLength = (s_6+s_7)/(d_6+d_7) (forward walk/run)
// @0x538 reverseSpeedMax = last-keyframe stride of the reverse-base clip
// @0x53c jumpRunSpeedMax = last-keyframe stride of the run-jump clip
// @0x540 jumpWalkSpeedMax = last-keyframe stride of the walk-jump clip
// @0x544 jumpRunStrideLength = s/d of fall-forward jump clip (slot 0x18)
// @0x548 jumpWalkStrideLength = s/d of fall-backward jump clip (slot 0x19)
// @0x580 jumpCapable (int) set 1 iff the model defines the jump clip set
// @0x584 hasReverseGimpSet (int)? set 1 iff the second optional clip set exists
// @0x588 hasCrashSet (int)? set 1 iff the third optional clip set exists
// @0x5b8 groundCycleRate forward-cycle slew rate, on-ground (-> 0x344)
// @0x5bc airborneCycleRate forward-cycle slew rate, airborne (-> 0x344)
// @0x5c4 gyroRumbleTimer (float) reset to 0 at the top of a clip load
// @0x5cc animationClips[] (ptr[]) gait-state -> clip handle (mech2); indexed
// by MeasureClipStride
// @0x5d4 crashClipB (ptr) optional-set clip handle
// @0x5d8 crashClipA (ptr) optional-set clip handle
// @0x5dc crashClipC (ptr) optional-set clip handle
// @0x5e0..0x638 namedClip[] (ptr[]) the ~22 resolved gait-clip handles, one
// per 3-letter suffix (table below)
// @0x64c gimpBaseClip (ptr) the "bmp" clip handle
//
// --- SequenceController internals (object embedded at 0x65c = legAnimation):
// @0x670 keyframeCount (int) == legAnimation.keyframeCount
// @0x680 keyframeTimes (ptr) == legAnimation.keyframeTimes[]
// @0x690 keyframeData (ptr) == legAnimation.keyframeData[] (0xc stride;
// +8 of each record == cumulative stride value)
//
// Gait-clip suffix table @0050d8d5 (3-letter; concatenated onto the model's
// 3-letter AnimationPrefix to form the clip resource name):
// swr wwr wwl wsr wsl wrr wrl rrr rrl rwr rwl bmp sbr sbl bsr bsl bbr bbl
// wgl wgr ggr ggl gsl gsr (+ squ/sqd/trn for the jump/turn set @0050d91d)
//
//###########################################################################
//###########################################################################
// ResolveAnimationClip
//
// @004a7f50 (sits in the 0x4a7924-0x4a8054 sliver between mech2.cpp's last
// gait function and the mech3 loaders; mech2.cpp did not capture
// it, and both loaders below depend on it, so it is recovered here.)
//
// Concatenate the model's 3-letter AnimationPrefix with a clip suffix and ask
// the resource manager to resolve the combined name to a clip ResourceID.
// Returns &ResourceID (eax); the loaders read *result as the handle.
//###########################################################################
//###########################################################################
ResourceDescription::ResourceID *
Mech::ResolveAnimationClip(const char *prefix, const char *suffix)
{
char clipName[12]; // local_10[3] + local_d[9] -- 3+suffix, NUL-term
Strcpy(clipName, prefix); // inline strcpy (prefix, 3 chars)
Strcat(clipName, suffix); // inline strcat (suffix)
// RECONSTRUCTED (was the FUN_00406ff8 mechrecon stub that returned NULL):
// the decompiled FUN_00406ff8(app+0x68, name, 0x10, -1) is the engine's
// ResourceFile::FindResourceDescription(name, AnimationResourceType(==0x10), -1)
// -- a by-name lookup over the resource descriptions. resourceID is public.
ResourceDescription *desc = application->GetResourceFile()->FindResourceDescription(
clipName, ResourceDescription::AnimationResourceType, ResourceDescription::NullResourceID);
return desc ? &desc->resourceID : (ResourceDescription::ResourceID *)0;
}
//###########################################################################
//###########################################################################
// MeasureClipStride
//
// @004a8054
//
// Bind the gait clip stored at animationClips[slot] into the leg
// SequenceController, then walk its keyframe table and integrate the
// per-keyframe stride deltas. Returns (via out params) the total cycle
// distance and the value of the final keyframe -- the loaders divide
// total/last to recover a normalized cycle speed, and use the bare last
// keyframe as a speed cap.
//###########################################################################
//###########################################################################
void
Mech::MeasureClipStride(int slot, Scalar *total, Scalar *lastKey)
{
legAnimation.SelectSequence( // FUN_004277a8(this+0x65c, ...)
animationClips[slot], // *(this+0x5cc + slot*4)
PTR_LAB_0050d708, DAT_0050d70c, DAT_0050d710);
*total = 0.0f;
*lastKey = 0.0f;
for (int i = 0; i < legAnimation.keyframeCount; ++i) // this+0x670
{
// times[i] @0x680 ; data[i].stride @ (0x690 + i*0xc + 8)
Scalar t = legAnimation.keyframeTimes[i];
*total += (t - *lastKey) * legAnimation.keyframeData[i].stride;
*lastKey = t;
}
}
//###########################################################################
//###########################################################################
// LoadLocomotionClips
//
// @004a80d4
//
// Resolve and cache every gait clip named by the 3-letter suffix table, bind
// the base clips to recover the gait speed caps (standSpeed @0x530, walk
// stride @0x534, reverseSpeedMax @0x538, reverse stride @0x34c, gimpSpeedMax
// @0x52c, gimp stride @0x350), then probe for the three OPTIONAL clip sets
// (jump / reverse-gimp / crash) and, if their marker resource exists, resolve
// those too and set the matching capability flag (jumpCapable @0x580 etc.).
//###########################################################################
//###########################################################################
void
Mech::LoadLocomotionClips(Mech__ModelResource *model)
{
const char *prefix = model->animationPrefix; // param_2 + 0x40
gyroRumbleTimer = 0.0f; // this+0x5c4 (float; bit-identical to the old int 0)
// --- stand -> walk : standSpeed is that clip's final-keyframe stride ----
namedClip[ 0] = *ResolveAnimationClip(prefix, "swr"); // 0x5e0
legAnimation.SelectSequence(namedClip[0], PTR_LAB_0050d714, DAT_0050d718, DAT_0050d71c);
standSpeed = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x530
// --- forward walk/run : stride = (s6+s7)/(d6+d7) ------------------------
Scalar s0, d0, s1, d1;
namedClip[ 1] = *ResolveAnimationClip(prefix, "wwr"); // 0x5e4
MeasureClipStride(6, &s0, &d0);
namedClip[ 2] = *ResolveAnimationClip(prefix, "wwl"); // 0x5e8
MeasureClipStride(7, &s1, &d1);
walkStrideLength = (s0 + s1) / (d0 + d1); // 0x534
namedClip[ 3] = *ResolveAnimationClip(prefix, "wsr"); // 0x5ec walk->stand R
namedClip[ 4] = *ResolveAnimationClip(prefix, "wsl"); // 0x5f0 walk->stand L
// --- reverse base : reverseSpeedMax is that clip's final-keyframe stride -
namedClip[ 5] = *ResolveAnimationClip(prefix, "wrr"); // 0x5f4
legAnimation.SelectSequence(namedClip[5], PTR_LAB_0050d720, DAT_0050d724, DAT_0050d728);
reverseSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x538
namedClip[ 6] = *ResolveAnimationClip(prefix, "wrl"); // 0x5f8
// --- reverse cycle : stride = (s_c+s_d)/(d_c+d_d) -----------------------
namedClip[ 7] = *ResolveAnimationClip(prefix, "rrr"); // 0x5fc
MeasureClipStride(0x0c, &s0, &d0);
namedClip[ 8] = *ResolveAnimationClip(prefix, "rrl"); // 0x600
MeasureClipStride(0x0d, &s1, &d1);
reverseStrideLength = (s0 + s1) / (d0 + d1); // 0x34c
//
// REVERSE + BUMP set -- SLOT MAP BINARY-VERIFIED (raw part_012.c:13660-13683
// + the .data suffix table @0050d901-19 read from the exe). The earlier
// draft shifted everything one slot after inventing a "bmp" gait state:
// the reverse CYCLE states 0x12/0x13 got the back-to-STAND clips, so
// reversing repeatedly played "coming to a halt" frames (the user's
// "jerks to inappropriate frames"). True map (state == animationClips
// index; 0x5cc + 4*state):
// bmp -> the separate bump-clip member @0x64c (collision stagger)
// 0x10/0x11 = sbr/sbl (stand->back entry), 0x12/0x13 = bbr/bbl (the
// back CYCLE), 0x14/0x15 = bsr/bsl (back->stand exit).
// ("gimp*" member names are historical -- these are the REVERSE figures.)
//
animationClips[14] = *ResolveAnimationClip(prefix, "rwr"); // 0x604 state 0xE run->walk R
animationClips[15] = *ResolveAnimationClip(prefix, "rwl"); // 0x608 state 0xF run->walk L
// CRASH/BUMP clip -- ALIASING FIX (task #15 crash-anim): @0x64c is NOT a
// separate member; it IS animationClips[0x20] (0x5cc + 32*4 = 0x64c) -- the
// clip SetLegAnimation(0x20) binds on a wall impact above the crash
// threshold (iv2 > 40, master perf @4aaae2). The old separate-member
// (gimpBaseClip) write left slot 32 EMPTY -- the same one-array bug class
// as namedClip. Kept dual-written until the dead member is removed.
animationClips[0x20] = *ResolveAnimationClip(prefix, "bmp"); // 0x64c bump/stagger clip
gimpBaseClip = animationClips[0x20];
animationClips[16] = *ResolveAnimationClip(prefix, "sbr"); // 0x60c state 0x10 stand->back R
// reverse-entry speed cap: measured from SBR (the draft measured bmp)
legAnimation.SelectSequence(animationClips[16], PTR_LAB_0050d72c, DAT_0050d730, DAT_0050d734);
gimpSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x52c
animationClips[17] = *ResolveAnimationClip(prefix, "sbl"); // 0x610 state 0x11 stand->back L
animationClips[20] = *ResolveAnimationClip(prefix, "bsr"); // 0x61c state 0x14 back->stand R
animationClips[21] = *ResolveAnimationClip(prefix, "bsl"); // 0x620 state 0x15 back->stand L
// --- reverse cycle : stride = -(s+s)/(d+d), stored negative -------------
// NOTE: the decomp sums the slot-0x13 measurement with the *carried-over*
// measurement (local_10/local_14 from the walk slots above); s1/d1 below
// therefore still hold those figures. Reproduced as-is.
animationClips[18] = *ResolveAnimationClip(prefix, "bbr"); // 0x614 state 0x12 back cycle R
MeasureClipStride(0x12, &s0, &d0);
animationClips[19] = *ResolveAnimationClip(prefix, "bbl"); // 0x618 state 0x13 back cycle L
MeasureClipStride(0x13, &s0, &d0);
gimpStrideLength = -((s0 + s1) / (d0 + d1)); // 0x350
//
// ---- OPTIONAL clip set 1: JUMP JETS ----
// Probe for the run-jump marker; if present, the model is jump-capable.
//
jumpCapable = 0; // this+0x580
if (ResolveAnimationClip(prefix, "wgl") != 0) // FUN_00406ff8 probe (real)
{
jumpCapable = 1;
// SLOT MAP BINARY-VERIFIED (raw :13787-13812 + .data @0050d921-35):
// wgl->0x624 [22], wgr->0x628 [23], ggr->0x62c [24], ggl->0x630 [25],
// gsl->0x634 [26], gsr->0x638 [27]. (The draft omitted wgl and
// shifted the rest one slot low.)
animationClips[22] = *ResolveAnimationClip(prefix, "wgl"); // 0x624 state 0x16
legAnimation.SelectSequence(animationClips[22], PTR_LAB_0050d738, DAT_0050d73c, DAT_0050d740);
jumpRunSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x53c
animationClips[23] = *ResolveAnimationClip(prefix, "wgr"); // 0x628 state 0x17
legAnimation.SelectSequence(animationClips[23], PTR_LAB_0050d744, DAT_0050d748, DAT_0050d74c);
jumpWalkSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x540
animationClips[24] = *ResolveAnimationClip(prefix, "ggr"); // 0x62c state 0x18
MeasureClipStride(0x18, &s0, &d0);
jumpRunStrideLength = s0 / d0; // 0x544
animationClips[25] = *ResolveAnimationClip(prefix, "ggl"); // 0x630 state 0x19
MeasureClipStride(0x19, &s0, &d0);
jumpWalkStrideLength = s0 / d0; // 0x548
animationClips[26] = *ResolveAnimationClip(prefix, "gsl"); // 0x634 state 0x1A
animationClips[27] = *ResolveAnimationClip(prefix, "gsr"); // 0x638 state 0x1B
}
//
// ---- OPTIONAL clip set 2: SQUAT (jump-jet crouch) ----
// BINARY-VERIFIED (raw part_012.c:13971-14003 + .data strings @0050d939-41):
// probe "squ" -> flag @0x584; loads squ -> 0x5d8 == animationClips[3] and
// sqd -> 0x5d4 == animationClips[2]. (The earlier draft guessed the
// suffixes/slots and stored into side members SetLeg/BodyAnimation never
// consult -- states 2/3 read animationClips[] directly.)
//
hasReverseGimpSet = 0; // this+0x584 (squatCapable)
if (ResolveAnimationClip(prefix, "squ") != 0)
{
hasReverseGimpSet = 1;
animationClips[3] = *ResolveAnimationClip(prefix, "squ"); // 0x5d8
animationClips[2] = *ResolveAnimationClip(prefix, "sqd"); // 0x5d4
}
//
// ---- OPTIONAL clip set 3: TURN-IN-PLACE ----
// BINARY-VERIFIED (raw :13994-14003 + .data @0050d945/49): probe "trn" ->
// flag @0x588 (turnCapable); loads trn -> 0x5dc == animationClips[4] --
// the state-4 turn-in-place clip (SetLegAnimation(4) arms it).
//
hasCrashSet = 0; // this+0x588 (turnCapable)
if (ResolveAnimationClip(prefix, "trn") != 0)
{
hasCrashSet = 1;
animationClips[4] = *ResolveAnimationClip(prefix, "trn"); // 0x5dc
}
DEBUG_STREAM << "[loadclips] end: fScale=" << forwardThrottleScale
<< " gimpSpeedMax=" << gimpSpeedMax << " jumpCapable=" << jumpCapable << "\n" << std::flush;
}
//###########################################################################
//###########################################################################
// LoadLocomotionClipsExt (the INTERIOR clip set)
//
// @004a86c8
//
// The 4-character suffix table @0050d94d is the 3-letter codes with a trailing
// 'i' (swri, wwri, wwli, ... -- byte-verified from BTL4OPT.EXE @file 0x10d74d).
// These are the INTERIOR ('i') gait clips, bound to the LOCAL cockpit MASTER
// (the ctor gate @part_012.c:10308-10320): they animate `jointshakey` (the
// cockpit shake node) and OMIT the `jointhip` walk-lean channel the exterior
// clips carry -- so the pilot's own view stays level while a walking mech
// leans into its stride for everyone ELSE (the exterior clips, network
// replicants + the L4VIEWEXT external view). The call/measure/store sequence
// and the cached targets (0x530/0x534/0x538/0x34c/0x52c/0x350, the optional
// sets, the capability flags) are LINE-FOR-LINE identical to the 3-char
// loader; only the suffix operands differ (each gains the 'i'). [T1: byte-
// exact against @004a86c8 + the suffix-table dump.]
//###########################################################################
//###########################################################################
void
Mech::LoadLocomotionClipsExt(Mech__ModelResource *model)
{
const char *prefix = model->animationPrefix; // param_2 + 0x40
gyroRumbleTimer = 0.0f; // this+0x5c4
// --- stand -> walk : standSpeed is that clip's final-keyframe stride ----
namedClip[ 0] = *ResolveAnimationClip(prefix, "swri"); // 0x5e0
legAnimation.SelectSequence(namedClip[0], PTR_LAB_0050d714, DAT_0050d718, DAT_0050d71c);
standSpeed = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x530
// --- forward walk/run : stride = (s6+s7)/(d6+d7) ------------------------
Scalar s0, d0, s1, d1;
namedClip[ 1] = *ResolveAnimationClip(prefix, "wwri"); // 0x5e4
MeasureClipStride(6, &s0, &d0);
namedClip[ 2] = *ResolveAnimationClip(prefix, "wwli"); // 0x5e8
MeasureClipStride(7, &s1, &d1);
walkStrideLength = (s0 + s1) / (d0 + d1); // 0x534
namedClip[ 3] = *ResolveAnimationClip(prefix, "wsri"); // 0x5ec walk->stand R
namedClip[ 4] = *ResolveAnimationClip(prefix, "wsli"); // 0x5f0 walk->stand L
// --- reverse base : reverseSpeedMax is that clip's final-keyframe stride -
namedClip[ 5] = *ResolveAnimationClip(prefix, "wrri"); // 0x5f4
legAnimation.SelectSequence(namedClip[5], PTR_LAB_0050d720, DAT_0050d724, DAT_0050d728);
reverseSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x538
namedClip[ 6] = *ResolveAnimationClip(prefix, "wrli"); // 0x5f8
// --- reverse cycle : stride = (s_c+s_d)/(d_c+d_d) -----------------------
namedClip[ 7] = *ResolveAnimationClip(prefix, "rrri"); // 0x5fc
MeasureClipStride(0x0c, &s0, &d0);
namedClip[ 8] = *ResolveAnimationClip(prefix, "rrli"); // 0x600
MeasureClipStride(0x0d, &s1, &d1);
reverseStrideLength = (s0 + s1) / (d0 + d1); // 0x34c
// REVERSE + BUMP set (same slot map as the 3-char loader; see its banner) --
animationClips[14] = *ResolveAnimationClip(prefix, "rwri"); // 0x604 state 0xE run->walk R
animationClips[15] = *ResolveAnimationClip(prefix, "rwli"); // 0x608 state 0xF run->walk L
animationClips[0x20] = *ResolveAnimationClip(prefix, "bmpi");// 0x64c bump/stagger clip
gimpBaseClip = animationClips[0x20];
animationClips[16] = *ResolveAnimationClip(prefix, "sbri"); // 0x60c state 0x10 stand->back R
legAnimation.SelectSequence(animationClips[16], PTR_LAB_0050d72c, DAT_0050d730, DAT_0050d734);
gimpSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x52c
animationClips[17] = *ResolveAnimationClip(prefix, "sbli"); // 0x610 state 0x11 stand->back L
animationClips[20] = *ResolveAnimationClip(prefix, "bsri"); // 0x61c state 0x14 back->stand R
animationClips[21] = *ResolveAnimationClip(prefix, "bsli"); // 0x620 state 0x15 back->stand L
// --- reverse cycle : stride = -(s+s)/(d+d), stored negative (carried s1/d1)
animationClips[18] = *ResolveAnimationClip(prefix, "bbri"); // 0x614 state 0x12 back cycle R
MeasureClipStride(0x12, &s0, &d0);
animationClips[19] = *ResolveAnimationClip(prefix, "bbli"); // 0x618 state 0x13 back cycle L
MeasureClipStride(0x13, &s0, &d0);
gimpStrideLength = -((s0 + s1) / (d0 + d1)); // 0x350
//
// ---- OPTIONAL clip set 1: JUMP JETS (probe "wgli") ----
//
jumpCapable = 0; // this+0x580
if (ResolveAnimationClip(prefix, "wgli") != 0)
{
jumpCapable = 1;
animationClips[22] = *ResolveAnimationClip(prefix, "wgli"); // 0x624 state 0x16
legAnimation.SelectSequence(animationClips[22], PTR_LAB_0050d738, DAT_0050d73c, DAT_0050d740);
jumpRunSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x53c
animationClips[23] = *ResolveAnimationClip(prefix, "wgri"); // 0x628 state 0x17
legAnimation.SelectSequence(animationClips[23], PTR_LAB_0050d744, DAT_0050d748, DAT_0050d74c);
jumpWalkSpeedMax = legAnimation.keyframeData[legAnimation.keyframeCount].stride; // 0x540
animationClips[24] = *ResolveAnimationClip(prefix, "ggri"); // 0x62c state 0x18
MeasureClipStride(0x18, &s0, &d0);
jumpRunStrideLength = s0 / d0; // 0x544
animationClips[25] = *ResolveAnimationClip(prefix, "ggli"); // 0x630 state 0x19
MeasureClipStride(0x19, &s0, &d0);
jumpWalkStrideLength = s0 / d0; // 0x548
animationClips[26] = *ResolveAnimationClip(prefix, "gsli"); // 0x634 state 0x1A
animationClips[27] = *ResolveAnimationClip(prefix, "gsri"); // 0x638 state 0x1B
}
//
// ---- OPTIONAL clip set 2: SQUAT (probe "squi") ----
//
hasReverseGimpSet = 0; // this+0x584 (squatCapable)
if (ResolveAnimationClip(prefix, "squi") != 0)
{
hasReverseGimpSet = 1;
animationClips[3] = *ResolveAnimationClip(prefix, "squi"); // 0x5d8
animationClips[2] = *ResolveAnimationClip(prefix, "sqdi"); // 0x5d4
}
//
// ---- OPTIONAL clip set 3: TURN-IN-PLACE (probe "trni") ----
//
hasCrashSet = 0; // this+0x588 (turnCapable)
if (ResolveAnimationClip(prefix, "trni") != 0)
{
hasCrashSet = 1;
animationClips[4] = *ResolveAnimationClip(prefix, "trni"); // 0x5dc
}
DEBUG_STREAM << "[loadclips] INTERIOR ('i') set: fScale=" << forwardThrottleScale
<< " walkStride=" << walkStrideLength << " jumpCapable=" << jumpCapable
<< "\n" << std::flush;
}
//###########################################################################
//###########################################################################
// MaintainViewClipSet (port adaptation, task #59)
//
// The authentic clip-set gate (@part_012.c:10308-10320) keys on the replicant
// COPY bit (instanceFlags & 0xC == 4), which the port never sets -- ALL MP mechs
// are built as local masters (replication rides the mech registry + update
// records, not the copy-performance path; the heat sim / scoring / torso
// watcher-connect all run on both, by design). So the ctor gate lands every
// mech on the INTERIOR set. We restore the authentic RESULT by selecting the
// set by VIEWPOINT instead, maintained per frame: the mech you PILOT (== the
// viewpoint entity) keeps the INTERIOR set (level cockpit -- you never see your
// own mech externally in the real pod); every OTHER mech gets the EXTERIOR set
// (the authored -8deg jointhip walk lean, which you DO see from your cockpit /
// the chase cam). Reloads only on a viewpoint-status CHANGE (no per-frame
// thrash), and the two sets share identical stride data so the swap is seamless.
// The model pointer is stashed at ctor (the GameModel resource persists for the
// mission). Fixed array -- mech counts are tiny, no allocator in the tick path.
//###########################################################################
//###########################################################################
namespace {
struct BTClipEntry { const Mech *mech; Mech__ModelResource *model; int interior; };
BTClipEntry s_clipReg[32];
int s_clipRegCount = 0;
}
void BTStashClipState(const Mech *m, Mech__ModelResource *model, int interior)
{
for (int i = 0; i < s_clipRegCount; ++i)
if (s_clipReg[i].mech == m)
{
s_clipReg[i].model = model;
s_clipReg[i].interior = interior;
return;
}
if (s_clipRegCount < 32)
{
s_clipReg[s_clipRegCount].mech = m;
s_clipReg[s_clipRegCount].model = model;
s_clipReg[s_clipRegCount].interior = interior;
++s_clipRegCount;
}
}
void BTUnstashClipState(const Mech *m)
{
for (int i = 0; i < s_clipRegCount; ++i)
if (s_clipReg[i].mech == m)
{
s_clipReg[i] = s_clipReg[--s_clipRegCount];
return;
}
}
void
Mech::MaintainViewClipSet()
{
if (application == 0 || !BTEnvOn("BT_GAIT_CUTOVER", 1))
return;
if (getenv("L4VIEWEXT") != 0) // forced-external debug view: leave EXTERIOR everywhere
return;
Entity *vp = application->GetViewpointEntity();
if (vp == 0) // viewpoint not assigned yet -- keep the current set
return;
BTClipEntry *e = 0;
for (int i = 0; i < s_clipRegCount; ++i)
if (s_clipReg[i].mech == this) { e = &s_clipReg[i]; break; }
if (e == 0 || e->model == 0) // not registered / no model -- nothing to flip
return;
const int wantInterior = ((Entity *)this == vp) ? 1 : 0;
if (wantInterior == e->interior) // already holding the right set
return;
if (wantInterior)
LoadLocomotionClipsExt(e->model); // interior: level cockpit (your own mech)
else
LoadLocomotionClips(e->model); // exterior: the authored walk lean (every other mech)
e->interior = wantInterior;
DEBUG_STREAM << "[clipfix] mech " << (void *)this << " -> "
<< (wantInterior ? "INTERIOR (level)" : "EXTERIOR (lean)") << "\n" << std::flush;
}
//###########################################################################
//###########################################################################
// CreateSubsystemStream (offline tool path)
//
// @004a8cc0
//
// Dispatch one model component to the correct Subsystem subclass's
// CreateStreamedSubsystem, keyed on the component's type-name string. The
// long if/else ladder is a sequence of string compares against the component
// class names; the recognized set (and the factory each maps to) is the
// authoritative inverse of the runtime ctor switch documented in CLASSMAP.
// Returns 1 on success, -1 (0xffffffff) on an unknown component type.
//###########################################################################
//###########################################################################
/*static*/ int
Mech::CreateSubsystemStream(
int subsystem_index, // param_1
const char *type_name, // param_2
int pass, // param_3 (weapon counter)
NotationFile *model_file, // param_4
const char *model_name, // param_5
const char *subsystem_name, // param_6
SubsystemResourceBlob *out_blob, // param_7
NotationFile *subsystem_file, // param_8
const ResourceDirectories *directories) // param_9
{
int result;
if (Streq(type_name, "HeatSinkClassID"))
result = HeatSink::CreateStreamedSubsystem(model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004ae9dc
else if (Streq(type_name, "ReservoirClassID"))
result = Reservoir::CreateStreamedSubsystem(model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004af580
else if (Streq(type_name, "CondenserClassID"))
result = Condenser::CreateStreamedSubsystem(model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004ae658
else if (Streq(type_name, "PoweredSubsystemClassID"))
result = PoweredSubsystem::CreateStreamedSubsystem(model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004b13ac
else if (Streq(type_name, "MyomersClassID"))
result = Myomers::CreateStreamedSubsystem(model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004b9140
else if (Streq(type_name, "GeneratorClassID"))
result = Generator::CreateStreamedSubsystem(model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004b23bc
else if (Streq(type_name, "HUDClassID"))
result = HUD::CreateStreamedSubsystem(model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004b81d4
else if (Streq(type_name, "SearchlightClassID"))
result = Searchlight::CreateStreamedSubsystem(model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004b85a8
else if (Streq(type_name, "ThermalSightClassID"))
result = ThermalSight::CreateStreamedSubsystem(model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004b87d4
else if (Streq(type_name, "SensorClassID"))
result = Sensor::CreateStreamedSubsystem(model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004b1dcc
else if (Streq(type_name, "GyroscopeClassID"))
result = Gyroscope::CreateStreamedSubsystem(model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004b3eb4
else if (Streq(type_name, "TorsoClassID"))
result = Torso::CreateStreamedSubsystem(model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004b6fec
else if (Streq(type_name, "EmitterClassID"))
result = Emitter::CreateStreamedSubsystem(pass, model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004bb478
else if (Streq(type_name, "PPCClassID"))
result = PPC::CreateStreamedSubsystem(pass, model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004bb8e0
else if (Streq(type_name, "ProjectileWeaponClassID"))
result = ProjectileWeapon::CreateStreamedSubsystem(pass, model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index);// FUN_004bc7cc
else if (Streq(type_name, "MissileLauncherClassID"))
result = MissileLauncher::CreateStreamedSubsystem(pass, model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004bd08c
else if (Streq(type_name, "GaussRifleClassID"))
result = GaussRifle::CreateStreamedSubsystem(pass, model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004bdd14
else if (Streq(type_name, "AmmoBinClassID"))
result = AmmoBin::CreateStreamedSubsystem(pass, model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, subsystem_index); // FUN_004bd6f0
else if (Streq(type_name, "SubsystemMessageManagerClassID"))
result = SubsystemMessageManager::CreateStreamedSubsystem(pass, model_file, model_name, subsystem_name, out_blob, subsystem_file, directories); // FUN_0049be10
else if (Streq(type_name, "MechTechClassID"))
result = MechTech::CreateStreamedSubsystem(model_file, model_name, subsystem_name, out_blob, subsystem_file, directories, pass); // FUN_004ad48c
else
{
// Unknown component type -> log "<model>:<subsystem> has an unknown
// component type of <type_name>" and fail the stream.
DebugStream << model_name;
DebugStream << ':' << subsystem_name
<< " has an unknown component type of " << type_name;
DebugStream.Emit();
return -1;
}
return (result == 0) ? -1 : 1;
}
//###########################################################################
//###########################################################################
// CreateSubsystemsStream (offline tool path)
//
// @004a9390
//
// Build the streamed-Subsystems container resource for a model. Open the
// model's "gamedata"/"Subsystems" sub-file, iterate every subsystem entry,
// resolve each entry's "Type" (and optional "Include" file), and call
// CreateSubsystemStream once per component, packing each emitted resource into
// a contiguous blob (0x770 bytes reserved per subsystem -- the largest
// streamed Subsystem record). Finally write the packed blob as resource type
// 0x11 via ResourceFile::WriteResource and return its ResourceID.
//###########################################################################
//###########################################################################
/*static*/ ResourceDescription::ResourceID
Mech::CreateSubsystemsStream(
NotationFile *model_file, // param_1 (host)
const char *model_name, // param_2
NotationFile *model_notation, // param_3
const ResourceDirectories *directories) // param_4
{
const char *subFileName;
if (!((NoteX *)model_notation)->FindBlock("gamedata", "Subsystems", &subFileName)) // FUN_00404088
{
DebugStream << model_name << " is missing sub file specification.";
return (ResourceDescription::ResourceID)-1;
}
const char *subPath = ((DirsX2 *)directories)->ResolveInclude(model_name, subFileName); // FUN_004064fc
NotationFile *subFile = OpenNote(Alloc(0x20), subPath, 1); // FUN_00403e84
NotationNode *root = RootNote(subFile); // FUN_0040485c
// Count subsystem entries.
int count = 0;
for (NotationNode *n = root->firstChild; n; n = n->next)
++count;
if (count == 0)
{
DebugStream << model_name << " has no subsystems.";
root->Release(3);
FreePath(subPath);
CloseNote(subFile, 3);
return (ResourceDescription::ResourceID)-1;
}
// Reserve 0x770 bytes per subsystem; first word of the blob is the count.
int *blob = (int *)PoolAlloc(count * 0x770); // FUN_004022b0
BlobWriter cursor; cursor.Init(blob, count * 0x770, 0); // FUN_004030dc
*cursor.put32(count); // *local_50++ = count
for (NotationNode *n = root->firstChild; n; n = n->next)
{
const char *entryName = (const char *)(n + 2); // puVar5 + 2
const char *typeNode;
if (!((NoteX *)subFile)->FindBlock(entryName, "Type", &typeNode)) // FUN_00404088
{
DebugStream << model_name << ':' << entryName << " missing Type.";
goto fail;
}
// Optional "Include" file -- if present, build a synthetic type name
// "<Type without trailing ClassID>" and stream the include first.
const char *includeName;
NotationFile *includeFile = 0;
int pass = 1;
if (((NoteX *)subFile)->FindBlock(entryName, "Include", &includeName)) // FUN_00404088
{
const char *inc = ((DirsX2 *)directories)->ResolveInclude(model_name, includeName);
includeFile = OpenNote(Alloc(0x20), inc, 1);
// Strip the "ClassID" suffix off the type name for the include pass.
const char *tail = StrFind(typeNode, "ClassID"); // FUN_004d4c78
int len = StrLen(typeNode); // FUN_004d4a78
char *base = (char *)PoolAlloc(len + 1);
int keep = (int)(tail - typeNode);
base = StrCopyN(base, typeNode, keep); // FUN_004d4b9c
base[keep] = '\0';
int rc = CreateSubsystemStream(pass++, typeNode, (int)model_file,
model_notation, model_name, base,
cursor.current(), includeFile, directories); // FUN_004a8cc0
PoolFree(base);
if (rc == -1) goto fail;
}
// Stream the component itself.
if (CreateSubsystemStream(pass, typeNode, (int)model_file,
model_notation, model_name, entryName,
cursor.current(), subFile, directories) == -1)
goto fail;
if (includeFile)
{
FreePath(includeName);
CloseNote(includeFile, 3);
}
cursor.advance(cursor.current()[9]); // += emitted record size
}
{
ResourceDescription::ResourceID *id =
(ResourceDescription::ResourceID *)FUN_00406db4( // WriteResource
model_file, model_name, 0x11, 1, 0,
blob, cursor.length(), -1);
PoolFreeBuf(blob);
root->Release(3);
FreePath(subPath);
CloseNote(subFile, 3);
return *id;
}
fail:
PoolFreeBuf(blob);
root->Release(3);
FreePath(subPath);
CloseNote(subFile, 3);
return (ResourceDescription::ResourceID)-1;
}
//###########################################################################
//###########################################################################
// SubsystemDefaultData (offline tool path)
//
// @004a9770
//
// Map a component type-name string to the static SharedData/ClassDerivations
// object the streaming machinery should clone defaults from. The very first
// match (DAT_0050dc1f) returns &Mech::DefaultData @0050bde4 -- i.e. the model
// itself is the first "component". The remaining names return each
// subsystem class's static default-data block (addresses confirmed against
// CLASSMAP / the per-module reconstructions). Returns 0 for an unknown name.
//###########################################################################
//###########################################################################
/*static*/ SharedData *
Mech::SubsystemDefaultData(const char *type_name)
{
if (Streq(type_name, "")) return &Mech::DefaultData; // 0050bde4 (DAT_0050dc1f == "")
if (Streq(type_name, "ControlsMapper")) return &MechControlsMapper::DefaultData; // 0050ee00
if (Streq(type_name, "AmmoBinClassID")) return &AmmoBin::DefaultData; // 005125bc
if (Streq(type_name, "HeatSinkClassID")) return &HeatSink::DefaultData; // 0050e580
if (Streq(type_name, "ReservoirClassID")) return &Reservoir::DefaultData; // 0050e640
if (Streq(type_name, "CondenserClassID")) return &Condenser::DefaultData; // 0050e4ec
if (Streq(type_name, "PoweredSubsystemClassID"))return &PoweredSubsystem::DefaultData; // 0050f4ac
if (Streq(type_name, "MyomersClassID")) return &Myomers::DefaultData; // 0051154c
if (Streq(type_name, "GeneratorClassID")) return &Generator::DefaultData; // 0050fb50
if (Streq(type_name, "HUDClassID")) return &HUD::DefaultData; // 00511078
if (Streq(type_name, "SensorClassID")) return &Sensor::DefaultData; // 0050fa1c
if (Streq(type_name, "SearchlightClassID")) return &Searchlight::DefaultData; // 00511198
if (Streq(type_name, "ThermalSightClassID")) return &ThermalSight::DefaultData; // 00511228
if (Streq(type_name, "GyroscopeClassID")) return &Gyroscope::DefaultData; // 0050fdb0
if (Streq(type_name, "TorsoClassID")) return &Torso::DefaultData; // 00510af8
if (Streq(type_name, "EmitterClassID")) return &Emitter::DefaultData; // 00511d78
if (Streq(type_name, "PPCClassID")) return &PPC::DefaultData; // 005120c4
if (Streq(type_name, "ProjectileWeaponClassID"))return &ProjectileWeapon::DefaultData; // 00512198
if (Streq(type_name, "MissileLauncherClassID")) return &MissileLauncher::DefaultData; // 00512478
if (Streq(type_name, "GaussRifleClassID")) return &GaussRifle::DefaultData; // 00512478 (shares MissileLauncher table)
if (Streq(type_name, "ControlsMapper")) return &MechControlsMapper::DefaultData; // 0050ee00 (2nd alias)
if (Streq(type_name, "MechTech")) return &MechTech::DefaultData; // 0050e270
return 0;
}
//===========================================================================//
// End of recovered mech3.cpp slice.
//===========================================================================//