//===========================================================================// // File: mechrecon.hpp // // Project: BattleTech Brick: Entity Manager // // Contents: Reconstruction support shim for the "mech core" family // // (mech / mech2..4 / mechsub / mechdmg / dmgtable). // //---------------------------------------------------------------------------// // This header is NOT part of the shipped game. It supplies the small set of // // reconstruction helpers, decompiler-artifact globals and proxy stand-in // // types that the recovered mech-core bodies reference but the modern engine // // headers do not provide under those exact names. It exists only so the // // reconstructed translation units COMPILE against the WinTesla engine; the // // proxies preserve the call structure of the recovered code (no logic is // // deleted). Behaviour-equivalent semantics are filled where trivial. // // // // MUST be included AFTER / the engine headers (so Scalar, Quaternion // // Vector3D, AffineMatrix, CString, Subsystem ... are already declared). // //===========================================================================// #if !defined(MECHRECON_HPP) # define MECHRECON_HPP #include #include #include #include // Quaternion / Vector3D / AffineMatrix used below (kIdentityQuat etc.). Include // directly so mechrecon.hpp is self-sufficient regardless of the includer's order // (myomers.cpp pulls this via mechsub.hpp BEFORE bt.hpp). ROTATION.hpp DEFINES // Quaternion (affnmtrx.hpp only forward-declares it). #include #include //===========================================================================// // Universal decompiler-artifact value. // // Stands in for the result of an unresolved FUN_/helper artifact. Converts // to any scalar, pointer or default-constructible type so the recovered // expression typechecks. //===========================================================================// struct Recon { Recon() {} template Recon(const T &) {} template operator T() const { return T(); } }; //===========================================================================// // Memory shim -- the decomp's "Memory::Allocate / Memory::Free" map onto the // engine's per-object MemoryBlock allocators; for the offline reconstruction a // plain heap allocation is behaviour-equivalent. //===========================================================================// namespace Memory { inline void *Allocate(size_t bytes) { return ::operator new(bytes); } inline void *AllocateArray(size_t bytes){ return ::operator new(bytes); } inline void Free(void *where) { ::operator delete(where); } } inline void *Alloc(size_t bytes) { return ::operator new(bytes); } inline void *AllocateArray(size_t bytes){ return ::operator new(bytes); } //===========================================================================// // Ref-count / name helpers (decomp artifacts). //===========================================================================// template inline void *NewRefCount(A) { return 0; } inline void *NewRefCounted() { return 0; } template inline void BindName(A, B) {} template inline void BindName(A, B, C) {} template inline void ReleaseRefCounted(A) {} template inline void ZeroBlock(A, int) {} template inline void Assign(A, B) {} template inline Recon NewTableEntry(A) { return Recon(); } //===========================================================================// // Small dynamic string used by the resource-table builders // ( TextString("Layer") + itoa(n) ). //===========================================================================// struct TextString { char buf[64]; TextString() { buf[0] = 0; } TextString(const char *s) { buf[0] = 0; if (s) { strncpy(buf, s, 63); buf[63] = 0; } } TextString &operator+=(const char *s) { if (s) strncat(buf, s, 63 - strlen(buf)); return *this; } TextString operator+ (const char *s) const { TextString t(*this); t += s; return t; } operator const char *() const { return buf; } }; //===========================================================================// // Misc free-function artifacts (variadic -> Recon). //===========================================================================// template inline Recon WorldTransform(A&&...) { return Recon(); } template inline Recon ResourceFind(A&&...) { return Recon(); } template inline Recon LoadSkeleton(A&&...) { return Recon(); } template inline int ResourceManagerHas(A&&...) { return 0; } template inline void GenerateFaultDefault(A&&...) {} // NOTE: renamed from Random() to avoid colliding with the engine's // `extern RandomGenerator Random;` (RANDOM.h). Reconstructed code that wants the // original zero-arg unit-random (FUN_00408050 -> Scalar in [0,1)) uses RandomUnit(). // Backed by the engine RNG: `RandomGenerator::operator Scalar()` returns a value // in [0,1) (RANDOM.h), exactly matching FUN_00408050's contract. Variadic tail // kept so call sites (dmgtable: roll; theta*TwoPi) bind unchanged; Scalar return // satisfies the dmgtable Scalar consumers. #include // fwd/ -> engine RANDOM.h: extern RandomGenerator Random; template inline Scalar RandomUnit(A&&...) { return (Scalar)Random; } //===========================================================================// // String helpers (decomp inlined strcpy/strcat/stricmp etc.). //===========================================================================// inline void Strcpy(char *d, const char *s) { while ((*d++ = *s++) != 0) {} } inline void Strcat(char *d, const char *s) { while (*d) ++d; while ((*d++ = *s++) != 0) {} } inline int Strcmp(const char *a, const char *b) { return strcmp(a, b); } // Env gate with a DEFAULT: the authentic reconstructed paths (gait cutover/state // machine, collision+gravity, real controls) are ON by default; set the var to // "0" to fall back to the historical bring-up path (e.g. BT_GAIT_SM=0). inline int BTEnvOn(const char *name, int on_by_default) { const char *v = getenv(name); return v ? (v[0] != '0') : on_by_default; } // THE AUTHENTIC GROUND MODEL gate (task #15 -- the 1995 probe/snap/response // ground block, ground-model-decode workflow). ONE function so the Mech ctor // (template lift, mech.cpp), the per-frame ground block and the ProcessCollision // override (mech4.cpp) can never disagree: every TU reads the same env exactly // once. DEFAULT ON (verification passed: grass/arena/dbase census + anti-ladder // soak + slope tracking + render-side screenshots + combat & heapcheck // regressions -- see the task-#15 ground commit). BT_GROUND_REAL=0 falls back // to the bring-up gravity+push-out+clamp baseline. inline int GroundReal() { static const int v = BTEnvOn("BT_GROUND_REAL", 1); return v; } inline int GroundLog() { static const int v = getenv("BT_GROUND_LOG") ? 1 : 0; return v; } inline int Streq (const char *a, const char *b) { return strcmp(a, b) == 0; } //===========================================================================// // Multi-level status/alarm indicator. // // The recovered code names this AlarmIndicator and drives it with a level // count ctor + SetLevel(level). In the WinTesla engine the equivalent is // StateIndicator (SetState / GetState). We provide a thin proxy that exposes // BOTH spellings so the recovered call sites compile unchanged. //===========================================================================// struct ReconAlarm { unsigned level; ReconAlarm() : level(0) {} ReconAlarm(int n) : level(0) { (void)n; } ReconAlarm(unsigned n) : level(0) { (void)n; } ReconAlarm &operator=(const ReconAlarm &) { return *this; } void SetLevel(int n) { level = (unsigned)n; } void SetState(unsigned n){ level = n; } unsigned GetLevel() const { return level; } unsigned GetState() const { return level; } int GetLevelCount() const { return 0; } }; //===========================================================================// // Filtered (running-average) scalar -- decomp uses Initialize(n, value). //===========================================================================// struct ReconFiltered { ReconFiltered() {} template void Initialize(A&&...) {} template void Add(A&&...) {} Scalar Value() const { return Scalar(0); } // static-style accumulator verbs: FilteredScalar::Push(filter, sample) etc. template static void Push(A&&...) {} template static Scalar Sum(A&&...) { return Scalar(0); } template static Scalar Average(A&&...) { return Scalar(0); } }; typedef unsigned int uint; //===========================================================================// // Universal word-cell proxy + word accessor. // // The recovered mech-core bodies address the object as an int[] and assign // raw words by index ( this[0xNN] = ... ). Pointer-subscripting `this` // cannot be made an assignable scalar lvalue, so each `this[0xNN]` is // rewritten to Wword(0xNN), which hands back a distinct, universally // assignable/convertible cell (one per word index). This preserves the // per-offset write structure of the decomp while compiling against the // modern engine; the cell is behaviour-neutral storage for the offline // reconstruction (it is NOT aliased onto the real member at that offset). //===========================================================================// struct BTVal { Scalar max, min, field8; // named sub-fields used by the decomp BTVal() : max(0), min(0), field8(0) {} template BTVal(const T &) : max(0), min(0), field8(0) {} BTVal &operator=(const BTVal &) { return *this; } // real copy-assign template BTVal &operator=(const T &) { return *this; } template operator T() const { return T(); } // member-call passthrough (this[]-proxy + pose-member verbs) template BTVal &Construct(A&&...) { return *this; } template BTVal &LinkTo(A&&...) { return *this; } template BTVal &SetIdentity(A&&...) { return *this; } template BTVal &Mul(A&&...) { return *this; } template BTVal &Copy(A&&...) { return *this; } // arithmetic kept closed over BTVal (avoids operator T() ambiguity) friend BTVal operator+(const BTVal &, const BTVal &) { return BTVal(); } friend BTVal operator-(const BTVal &, const BTVal &) { return BTVal(); } BTVal &operator+=(const BTVal &) { return *this; } template BTVal &operator+=(const T &) { return *this; } // comparisons (exact-match friends beat the templated operator T()) friend bool operator==(const BTVal &, const BTVal &) { return false; } friend bool operator!=(const BTVal &, const BTVal &) { return false; } friend bool operator==(const BTVal &, int) { return false; } friend bool operator!=(const BTVal &, int) { return false; } friend bool operator==(int, const BTVal &) { return false; } friend bool operator!=(int, const BTVal &) { return false; } }; inline BTVal &Wword(int i) { static BTVal bank[0x400]; return bank[i & 0x3ff]; } static Vector3D ZeroVector; // neutral zero vector (decomp ZeroVector datum) //===========================================================================// // Affine-matrix proxy -- adds the SetRotation / FromRotation / FromQuaternion // spellings the decomp uses (the engine AffineMatrix expresses these through // operator= / dedicated helpers). //===========================================================================// struct ReconMatrix { AffineMatrix m; ReconMatrix() {} template ReconMatrix &operator=(const T &) { return *this; } template void FromRotation(A&&...) {} // P3 GAIT CUTOVER: backed by the real engine AffineMatrix (was no-op stubs, so // IntegrateMotion's world-step committed nothing). Identity == BuildIdentity; // FromQuaternion/SetRotation == AffineMatrix::operator=(const Quaternion&) (the // engine's build-rotation-from-quaternion). Signatures match every call site // (mech4.cpp IntegrateMotion + Simulate). static void SetRotation(ReconMatrix *dst, const Quaternion *q) { dst->m = *q; } static void FromQuaternion(ReconMatrix *dst, const Quaternion *q) { dst->m = *q; } static void Identity(ReconMatrix *dst) { dst->m.BuildIdentity(); } operator AffineMatrix &() { return m; } }; //===========================================================================// // SequenceController -- the BT-specific keyframe animation player embedded in the // Mech as legAnimation (@0x65c) / bodyAnimation (@0x6bc). RECONSTRUCTED from the // binary (part_003.c: ctor @00427768, SelectSequence @004277a8, Advance @0042790c, // Reset @004283b8). NOT in the RP411 engine. Plays a gait clip: walks keyframes, // interpolates each joint's rotation, writes it through the engine Joint API // (same as gyro/torso), and returns the root-translation distance advanced. // Modelled as a plain struct (accessed only by name; no Plug base needed). The // keyframeCount/keyframeTimes/keyframeData accessors are preserved for mech3's // LoadLocomotionClips/MeasureClipStride; full spec in btbuild/P3_LOCOMOTION.md. //===========================================================================// class Mech; // owner (forward decl -- mechrecon.hpp precedes the Mech class) struct SequenceController { // rootTranslation entry (binary 0xC/frame); ".stride" == the .z the gait reads. struct Keyframe { Scalar x, y, stride; }; // --- clip data parsed by SelectSequence (keyframe accessors read by mech3) --- int keyframeCount; // frameCount (binary +0x14) int jointCount; // animated joints (binary +0x18) // (AUDIO_FIDELITY F5) the ANI header's footstep CONTACT threshold -- the // clip-authored root height below which the foot is planted. The engine's // own AnimationInstance captures the same word (JMOVER.cpp:1415); the // binary's per-frame perf compares jointlocal.y against it (+0x20). Scalar *footStepThreshold; // &hdr[2] (binary +0x20) int *jointIndices; // keyframe->skeleton map (binary +0x1c) Scalar *keyframeTimes; // frameTimes[frameCount] (binary +0x24) void *keyframeBase; // keyframe pose data base (binary +0x28) void *keyframeCursor; // current pose cursor (binary +0x2c) Keyframe *keyframeData; // rootTranslations[] (binary +0x34) // --- runtime state / links --- int currentFrame; // (binary +0x3c) Scalar currentTime; // clip playback time (binary +0x54) void *jointSubsystem; // resolved owner joint subsystem (binary +0x38) Mech *owner; // (binary +0x40) void *clipResource; // locked clip resource handle (binary +0x44) void *cbCode; // finished-callback code ptr (binary +0x48) unsigned cbArg2, cbArg3; // finished-callback context words (binary +0x4c/0x50) SequenceController() : keyframeCount(0), jointCount(0), footStepThreshold(0), jointIndices(0), keyframeTimes(0), keyframeBase(0), keyframeCursor(0), keyframeData(0), currentFrame(0), currentTime(0.0f), jointSubsystem(0), owner(0), clipResource(0), cbCode(0), cbArg2(0), cbArg3(0) {} void Init(Mech *owner_mech); // @00427768 (ctor logic) void SelectSequence(int clip_id, void *cb, unsigned a2, unsigned a3); // @004277a8 Scalar Advance(Scalar time_slice, int move_joints); // @0042790c void Reset(int loop); // @004283b8 ~SequenceController(); // @004278d4 (release clip) }; typedef SequenceController ReconSeq; //===========================================================================// // Controls motion source -- the controls subsystem the Mech reads its live // commanded ground speed from (this[0x128] -> +0x128). //===========================================================================// struct ReconMotionSource { Scalar commandedSpeed; // +0x128 }; // Gait-state name table ( + state*0x3c ); display data only. static char AnimationNames[0x1e * 0x3c] = { 0 }; //===========================================================================// // MechSubsystem-coupled damage zone view. // // The recovered MechSubsystem code addresses its DamageZone through a small // set of fields (structure level, per-facing armour, structure reference and // the zone's own alarm) that are reconstruction names rather than the modern // engine DamageZone member spellings; this proxy carries them so the bodies // compile. damageZone is stored through it. //===========================================================================// struct ReconDamageZone { Scalar structureLevel; Scalar structureReference; Scalar armour[5]; ReconAlarm alarm; }; //===========================================================================// // Subsystem proxy + critical-subsystem chain (DistributeCriticalHit support). //===========================================================================// struct ReconSub { template Recon GetName(A&&...) { return Recon(); } template void TakeDamage(A&&...) {} }; struct CriticalEntry { ReconSub *sub; ReconSub *Subsystem() { return sub; } }; struct CriticalChain { template CriticalChain(A) {} int CountOfType(int) { return 1; } CriticalEntry *First() { return 0; } CriticalEntry *Next() { return 0; } }; //===========================================================================// // Callable returned by the "base vtable slot 0x18" thunk: (*BaseSlot0x18())(...) //===========================================================================// struct ReconCallable { template Recon operator()(A&&...) { return Recon(); } }; inline ReconCallable *BaseSlot0x18() { static ReconCallable c; return &c; } //===========================================================================// // Resource-table proxies (dmgtable). // // The recovered damage-table classes drive their entry pools through a small // verb set (Init / Insert / Lookup / count) and a forward iterator // (First / Next / Current) that differ from the modern TableOf/TableIterator // API, so the table members are declared with these proxies instead. //===========================================================================// template struct ReconTable { int count; ReconTable() : count(0) {} void Init() { count = 0; } template void Insert(A&&...) {} template void *Lookup(A&&...) const { return 0; } }; struct ReconTableIter { template ReconTableIter(const A &) {} void *First() { return 0; } void *Next() { return 0; } void *Current() { return 0; } }; //===========================================================================// // Generic object stream + notation-line + skeleton proxies (dmgtable parsers). //===========================================================================// struct GenericObjectStream { template void Read(A&&...) {} template void Write(A&&...) {} }; struct NotationLine { NotationLine *Next() { return 0; } const char *Format() { return ""; } const char *Value() { return ""; } int Count() { return 0; } template void Parse(A&&...) {} }; struct Skeleton { int SegmentCount() { return 1; } }; template inline NotationLine *ReconGetPage(A&&...) { static NotationLine l; return &l; } template inline Recon ReconDir(A&&...) { return Recon(); } template inline int ReconSegIndex(A, B) { return 0; } //===========================================================================// // Vector math namespace artifact ( Vector::Add / AddScaled / Scale / Dot ... ). // (The decomp emits free vector ops under a "Vector" qualifier; the engine uses // Vector3D member ops. Behaviour-neutral stand-ins for the reconstruction.) //===========================================================================// namespace Vector { template inline void Add(A&&...) {} template inline void AddScaled(A&&...) {} template inline void Scale(A&&...) {} template inline void Copy(A&&...) {} template inline void Subtract(A&&...) {} template inline Scalar Dot(A&&...) { return Scalar(0); } template inline Scalar Sum(A&&...) { return Scalar(0); } } // // Quaternion free-function artifacts (the decomp writes these as // Quaternion::Identity(dst,&id) / Quaternion::IntegrateInto(...) -- calling the // engine's static const Identity datum as a function; routed to free shims). // // P3 GAIT CUTOVER: backed by the real engine Quaternion (were no-op stubs). // ReconQuatIdentity(q, &id) -> q = Quaternion::Identity // ReconQuatIntegrate(out,in,d) -> out = in composed with an EXACT rotation by // angular delta d (= the real FUN_00409f58). // All callers pass (Quaternion*,Quaternion*) / (Quaternion*,Quaternion*,Vector3D*). inline void ReconQuatIdentity(Quaternion *q, const Quaternion * /*id*/) { *q = Quaternion::Identity; } // EXACT axis-angle quaternion integration -- the real FUN_00409f58 (part_000.c:9359), // verified from the decomp. A prior stand-in wrongly used `out->Add(*in,*delta)` (a // small-angle VECTOR add): correct every-frame on the master (tiny angle) but it // DIVERGES over a long dead-reckon gap -- the spinning-peer heading drifting to ~180deg // then snapping. The binary builds the unit rotation quaternion from the rotation vector // `delta` (angle=|delta|, axis=delta/angle) as { axis*sin(angle/2), cos(angle/2) } and // Hamilton-multiplies it onto `in` (FUN_00409d9c: out = in (X) deltaQuat). inline void ReconQuatIntegrate(Quaternion *out, const Quaternion *in, const Vector3D *delta) { const Scalar ang = delta->Length(); if (ang > 1.0e-6f) { const Scalar half = (Scalar)(fmodf((float)ang, 6.2831853f) * 0.5f); // reduce2pi, then halve const Scalar s = (Scalar)(sinf((float)half) / ang); // sin(angle/2)/angle const Quaternion dq((Scalar)(delta->x * s), (Scalar)(delta->y * s), (Scalar)(delta->z * s), (Scalar)cosf((float)half)); out->Multiply(*in, dq); // in (X) dq } else { *out = *in; } } template inline Recon ReconQuatSlerp(A&&...) { return Recon(); } template inline Recon ComputeImpactDamage(A&&...) { return Recon(); } // // Raw FUN_ helper artifacts referenced in code (vector/matrix/transform glue). // template inline Recon FUN_00408644(A&&...) { return Recon(); } // FUN_00408744 (part_000.c:8331) -- rotate a 3-vector by a matrix's 3x3 rotation: // out[i] = v[0]*M(i,0) + v[1]*M(i,1) + v[2]*M(i,2) (row i . v). // Backed by the engine AffineMatrix column basis: GetFromAxis(j) returns the "from" // axis = column j = (M(0,j),M(1,j),M(2,j)), so out = v.x*colX + v.y*colY + v.z*colZ // reproduces the raw row-dot formula EXACTLY (out[i] = sum_j v[j]*M(i,j)). This is // IntegrateMotion's world-step: worldVelocity = bodyOrientation * localVelocity. The // same GetFromAxis convention the drive facing uses (mech4.cpp), so it is sign-consistent. inline Vector3D *FUN_00408744(Vector3D *out, const Scalar *v, ReconMatrix *m) { Vector3D colX, colY, colZ; m->m.GetFromAxis(X_Axis, &colX); m->m.GetFromAxis(Y_Axis, &colY); m->m.GetFromAxis(Z_Axis, &colZ); out->x = v[0]*colX.x + v[1]*colY.x + v[2]*colZ.x; out->y = v[0]*colX.y + v[1]*colY.y + v[2]*colZ.y; out->z = v[0]*colX.z + v[1]*colY.z + v[2]*colZ.z; return out; } template inline Recon FUN_00408848(A&&...) { return Recon(); } template inline Recon FUN_00433ed4(A&&...) { return Recon(); } template inline Recon FUN_0040e5f0(A&&...) { return Recon(); } template inline Recon FUN_0040e36c(A&&...) { return Recon(); } template inline Recon FUN_00406ff8(A&&...) { return Recon(); } // Free vector op + pose/notify artifacts used unqualified in mech4. template inline Scalar Dot(A&&...) { return Scalar(0); } template inline Recon Build(A&&...) { return Recon(); } template inline void ReconBeginPose(A&&...) {} template inline void ReconEndPose(A&&...) {} template inline int ReconIsDerived(A&&...) { return 0; } // // Damage notification message (DamageMessage msg; DamageMessage::Build(&msg,...)). // struct DamageMessage { template static void Build(A&&...) {} }; // Geometry / pose constants. static Quaternion kIdentityQuat; static Vector3D kZeroVector; static Scalar _DAT_004ac048 = 0; static Scalar _DAT_004ab9c8 = 0; static Scalar _DAT_004ab9d0 = 0; static Scalar _DAT_004ab9d4 = 0; static Recon vtable_0050bb90; //===========================================================================// // Subsystem-roster chain proxy. // // The Mech keeps several ChainOf capability views. The engine // ChainOf has no default ctor / SetOwner / Construct / iterate-in-place verbs // that the recovered ctor uses, so the roster members are declared with this // proxy instead. //===========================================================================// template struct ReconChain { ReconChain() {} ReconChain(void *) {} template void SetOwner(A&&...) {} template void Construct(A&&...) {} template void Reset(A&&...) {} void Add(T) {} T First() { return T(); } T Next() { return T(); } }; //===========================================================================// // Debug-stream artifact ( DebugStream << x << endl; ). //===========================================================================// struct ReconStream { template ReconStream &operator<<(const T &) { return *this; } void Flush() {} void Emit() {} }; //===========================================================================// // Resolve-able connection slot proxy (e.g. the power-bus / death-effect slot). //===========================================================================// struct ReconSlot { template Recon Resolve(A&&...) { return Recon(); } template void LinkTo(A&&...) {} template void Construct(A&&...){} }; static ReconStream DebugStream; static int endl = 0; //===========================================================================// // Type aliases consumed by the recovered headers. Defining the guard here // suppresses the (now superseded) in-header alias blocks. //===========================================================================// #if !defined(BT_RECON_TYPE_ALIASES) # define BT_RECON_TYPE_ALIASES typedef ReconMatrix Matrix34; // orientation matrix proxy typedef ReconAlarm AlarmIndicator; // multi-level status indicator typedef ReconFiltered FilteredScalar; // running-average scalar #endif //===========================================================================// // Decompiler-artifact globals actually referenced by mech-core code. // * .data pointers used in address arithmetic -> char* // * scalar tuning constants -> Scalar // * opaque label/vtable/handle operands -> Recon //===========================================================================// static char *DAT_004efc94 = 0; // TheApp base (App*) static Scalar DAT_005209d0 = 0; // FLT_MAX seed static Scalar DAT_0052140c = 1; // runtime frame time scale (divisor) static char DAT_004e0f8c[1] = { 0 }; // "" empty string literal static Scalar _DAT_004ab9cc = 0; //===========================================================================// // Shared empty handler / attribute sets used to satisfy the 4-arg // Simulation::SharedData ctor for the reconstructed DefaultData objects (the // shipped game passes each class's own generated sets; an empty shared set is // structurally correct for the offline reconstruction). //===========================================================================// static Receiver::MessageHandlerSet ReconMessageHandlers; static Simulation::AttributeIndexSet ReconAttributeIndex; static Recon PTR_FUN_0050cfa8; // Mech vtable static Recon PTR_LAB_0050c0e8; // copy Performance static Recon PTR_LAB_0050c0f4; // master Performance // Animation SelectSequence operand triples (label + 2 floats per gait clip). static Recon PTR_LAB_0050d6f0, DAT_0050d6f4, DAT_0050d6f8; static Recon PTR_LAB_0050d6fc, DAT_0050d700, DAT_0050d704; static Recon PTR_LAB_0050d708, DAT_0050d70c, DAT_0050d710; static Recon PTR_LAB_0050d714, DAT_0050d718, DAT_0050d71c; static Recon PTR_LAB_0050d720, DAT_0050d724, DAT_0050d728; static Recon PTR_LAB_0050d72c, DAT_0050d730, DAT_0050d734; static Recon PTR_LAB_0050d738, DAT_0050d73c, DAT_0050d740; static Recon PTR_LAB_0050d744, DAT_0050d748, DAT_0050d74c; #endif // MECHRECON_HPP