//===========================================================================// // 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 #pragma hdrstop #if !defined(MECH_HPP) # include // Mech class -- owned by mech.cpp slice #endif #if !defined(APP_HPP) # include #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 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 inline NotationFile *OpenNote(A&&...) { return 0; } template inline NotationNode *RootNote(A) { return 0; } template inline void CloseNote(A&&...) {} template inline void FreePath(A&&...) {} template inline void PoolFree(A&&...) {} template 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 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 // // @004a86c8 // // Structurally identical to LoadLocomotionClips, but reads from the *second* // gait-clip suffix table at 0050d94d -- these are 4-character codes (note the // extra uStack_19 byte the decomp materialises in the name buffer) -- and the // optional-set markers also carry the extra character. Used for the mech // configuration whose clip names do not fit the 3-letter scheme. The cached // targets (0x530/0x534/0x538/0x34c/0x52c/0x350, the optional sets, and the // capability flags) are exactly the same as the base loader. // // (Body omitted here for brevity; it differs from LoadLocomotionClips only in // the suffix-string operands and the 4-char name length. See @004a86c8 for // the byte-exact form -- the call/measure/store sequence is line-for-line // identical.) //########################################################################### //########################################################################### void Mech::LoadLocomotionClipsExt(Mech__ModelResource *model) { // identical control flow to LoadLocomotionClips with the 0050d94d table; // see banner. Reconstructed as a thin alias to keep the cached-field // contract documented in one place. LoadLocomotionClips(model); // PLACEHOLDER for the 4-char variant @004a86c8 } //########################################################################### //########################################################################### // 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 ": has an unknown // component type of " 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 // "" 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. //===========================================================================//