//===========================================================================// // File: mech.hpp // // Project: BattleTech Brick: Entity Manager // // Contents: Mech -- the BattleMech player/AI entity (declaration) // //---------------------------------------------------------------------------// // 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, BTL4OPT.EXE). // // The Mech class is the heart of the game and its implementation is split // across mech.cpp .. mech4.cpp. This header declares the *whole* class so // the recovered mech.cpp slice compiles; methods whose bodies live in the // sibling translation units are marked [mech2] / [mech3] / [mech4]. // // mech.cpp slice recovered here: 0x004a1232 .. 0x004a5027 // (bounded below by the inter-module gap 0x0049ffc8..0x004a1232 that // separates it from btmssn.cpp, and above by mech2.cpp @0x004a5028.) // // Structural template: Red Planet's player vehicle entity VTV (RP/VTV.h), // which -- like Mech -- derives from JointedMover and hosts a roster of // streamed Subsystem objects. Surviving BT headers MECHDMG.HPP / MECHTECH.HPP // supplied the DamageZone / MechTech shapes. // // Field offsets in comments are BYTE offsets into the shipped object // (sizeof(Mech) == 0x854, see Mech::Make @004a2d48). The decomp addresses // the object as int words, so this[0xNN] == byte offset 0xNN*4. Names are // taken from VTV / the surviving BT headers where an analog exists and are // inferred from usage otherwise (flagged TODO / "best-effort"). // #if !defined(MECH_HPP) # define MECH_HPP #if !defined(JMOVER_HPP) # include // JointedMover -- engine base (MUNGA) #endif #if !defined(CONTROLS_HPP) # include #endif #if !defined(DAMAGE_HPP) # include #endif #if !defined(ALARM_HPP) # include // GaugeAlarm #endif #include // PlatformTool #include // AffineMatrix #include // AverageOf #include // Scalar #include "mechrecon.hpp" // reconstruction shim (proxies, artifact globals, type aliases) //##################### Reconstruction type aliases ##################### // Names inferred by the decompiler mapped to the real engine types. // (The actual aliases are now defined in mechrecon.hpp under the same guard.) #if !defined(BT_RECON_TYPE_ALIASES) # define BT_RECON_TYPE_ALIASES typedef ReconMatrix Matrix34; typedef ReconAlarm AlarmIndicator; typedef ReconFiltered FilteredScalar; #endif //##################### Forward Class Declarations ####################### class Mech; class Subsystem; class MechSubsystem; class MechWeapon; class MechControlsMapper; class MechTech; class Mech__DamageZone; // see MECHDMG.HPP class HitQuery; // weapon sweep query (mech4) class Joint; // engine skeleton node (JOINT.h); ResolveJoint result // // Streamed-subsystem resource blob handed to each ::CreateStreamedSubsystem. // The recovered factory only passes it through, so an opaque alias suffices. // typedef void SubsystemResourceBlob; // // Per-shot impact descriptor consumed by Mech::ResolveWeaponImpact (mech4). // Fields are those the recovered body reads. // struct ShotDescriptor { Vector3D dir; // +0x14 shot direction Scalar damage; // computed impact damage Scalar fieldAt(int) const { return Scalar(0); } // raw +0x240/0x244/0x248 reads }; //########################################################################### //######################### Mech::ModelResource ######################### //########################################################################### // // Parsed by Mech::CreateModelResource @004a2da8 from the "gamedata" notation // block. Layout mirrors the float[] the decomp fills (pfVar3, a 200-byte / // 0x32-word record). Indices in comments are the word index used by the // parser (e.g. pfVar3+0x11 == MaxAcceleration). // struct Mech__ModelResource: public JointedMover::ModelResource { char animationPrefix[12]; // +0x10 "AnimationPrefix" (3-letter) Scalar maxAcceleration; // +0x11 "MaxAcceleration" Scalar superStopAcceleration; // +0x12 "SuperStopAcceleration" Scalar throttleAdjustment; // +0x13 "ThrottleAdjustment" Scalar lookLeftAngle; // +0x14 "LookLeftAngle" Scalar lookRightAngle; // +0x15 "LookRightAngle" Scalar lookFrontAngle; // +0x16 "LookFrontAngle" Scalar lookBackAngle; // +0x17 "LookBackAngle" Scalar walkingTurnRate; // +0x18 "WalkingTurnRate" Scalar runningTurnRate; // +0x19 "RunningTurnRate" char skeletonName[64]; // +0x1a (.skl spec, 0x10 words) Scalar deathEffectResourceID; // +0x1d resolved "DeathEffect" id Scalar deathSplashDamage; // +0x1e "DeathSplashDamage" Scalar deathSplashRadius; // +0x1f "DeathSplashRadius" Scalar maxUnstableAcceleration; // +0x20 "MaxUnstableAcceleration" Scalar unstableAccelerationEffect; // +0x21 "UnstableAccelerationEffect" Scalar unstableGunTheEngineEffect; // +0x22 "UnstableGunTheEngineEffect" Scalar unstableSuperStopEffect; // +0x23 "UnstableSuperStopEffect" Scalar unstableHighVelocityEffect; // +0x24 "UnstableHighVelocityEffect" Scalar unstableStopedTurnEffect; // +0x25 "UnstableStopedTurnEffect" Scalar updatePositionDiffrence; // +0x26 "UpdatePositionDiffrence" Scalar updateTurnVelocityDiffrence;// +0x27 "UpdateTurnVelocityDiffrence" Scalar updateTurnDegreeDiffrence; // +0x28 "UpdateTurnDegreeDiffrence" Scalar timeDelay; // +0x29 "TimeDelay" Vector3D cameraOffset; // +0x2a "CameraOffset" char shadowJointName[20]; // +0x2d "ShadowJointName" (<20 chars) Scalar relativeMechValue; // +0x1c "RelativeMechValue" // --- raw recovered fields read by the ctor (Pass 2, model record) ----- // Offsets are the decomp's record offsets; names are best-effort. char *base; // record base pointer (model->base + 0xa8) int deathEffectField; // resolved death-effect handle source Scalar field48, field4c, field60, field64; int field68, field6c; Scalar field70, field74, field78, field7c; Scalar field80, field84, field88, field8c, field90, field94, field98, field9c; Scalar fieldA0; int fieldA4; int classID; // stamped by CreateModelResourceStub (0xBB9) }; //########################################################################### //######################### Mech::UpdateRecord ########################## //########################################################################### // // The network/replay update packet applied by Mech::ReadUpdateRecord // @004a1232. The switch is keyed on a 16-bit record-type field at +6 of the // message (cases 0,2..8). Field layout is best-effort -- only the offsets // touched by the decomp are named. // struct Mech__UpdateRecord: public JointedMover::UpdateRecord { // case 0 (full pose) : position (+0x10), orientation (+0x74) // case 2/3 (alarm/state) : alarm levels, score/heat state // case 4 (snap+time) : pose + server timestamp re-sync // case 5/6/7 (subsystem) : per-subsystem alarm + flags // case 8 (single scalar) : misc field [0xfd] int recordType; // *(u16*)(msg+6) -- switch selector int sequence; // trailing replication sequence word int field4, field5, field6, field7, field9, fieldA; // per-case payload words Vector3D position; // case 0/4 world position EulerAngles orientation; // case 0/4 net orientation }; //########################################################################### //########################## Mech::MakeMessage ########################## //########################################################################### // // Creation message. Carries the three resource-name strings copied into the // badge/color/insignia slots (this[0x211..0x213]) at +0x7c / +0x90 / +0xa4. // class Mech__MakeMessage: public JointedMover::MakeMessage { public: char resourceNameA[20]; // +0x7c -> this[0x211] (vehicle badge) char resourceNameB[20]; // +0x90 -> this[0x212] (vehicle color) char resourceNameC[20]; // +0xa4 -> this[0x213] (vehicle patch / insignia) // // Construction message, mirrored on VTV__MakeMessage (RP/VTV.h): the // entity_ID form carrying the three resource-name strings copied into // the badge/color/patch slots. Built by BTPlayer::CreatePlayerVehicle. // Mech__MakeMessage( Receiver::MessageID message_ID, size_t length, const EntityID &entity_ID, Entity::ClassID class_ID, const EntityID &owner_ID, ResourceDescription::ResourceID resource_ID, LWord instance_flags, const Origin &origin, const Motion &velocity, const Motion &acceleration, const char *badge, const char *color, const char *patch ): JointedMover::MakeMessage( message_ID, length, entity_ID, class_ID, owner_ID, resource_ID, instance_flags, origin, velocity, acceleration ) { Str_Copy(resourceNameA, badge, sizeof(resourceNameA)); Str_Copy(resourceNameB, color, sizeof(resourceNameB)); Str_Copy(resourceNameC, patch, sizeof(resourceNameC)); } }; //########################################################################### //############################## Mech ############################### //########################################################################### // // The BattleMech entity. vtable @0050cfa8 (16 slots); slot 0 == ~Mech // @004a452c. Engine-base slots (0x41xxxx / 0x42xxxx) are inherited from // JointedMover/Mover/Entity and are NOT redefined here. In-module overrides: // slot 0 (0050cfa8) ~Mech @004a452c [mech.cpp] // slot 6 (0050cfc0) ReadUpdateRecord @004a122c [mech.cpp] // slot 7 (0050cfc4) (pose write/simulate)@004a0c2c [mech.cpp gap] // slot 15 (0050cfe4) (state/print) @004abb40 [mech3/4] // class Mech: public JointedMover { friend class Mech__DamageZone; // per-zone damage code reads the roster / segment table friend struct MechBaseLayoutCheck; // base-region layout locks (mech4.cpp; P3 STEP-6 audit) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // public: static Derivation ClassDerivations; // @0050bdb4 static SharedData DefaultData; // @0050bde4 enum { MechClassID = 0xBB9 }; // stamped by CreateModelResource //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Subsystem roster -- streamed-class registry // // The constructor (@004a1674) walks the model's segment table and // instantiates one Subsystem per segment, dispatching on the segment's // streamed ClassID. ClassIDs confirmed against CLASSMAP.md are named; // the rest are best-effort from the factory address + object size. // public: enum SubsystemClassID { CockpitClassID = 0xBBD, // ctor @004ae568 (size 0x230) SensorClassID = 0xBBE, // ctor @004ae8d0 -> this[0x1f7] CondenserClassID = 0xBC0, // ctor @004af408 (size 0x230) GeneratorClassID = 0xBC1, // ctor @004b225c (CLASSMAP) PoweredSubsystemClassID = 0xBC2, // ctor @004b0f74 (CLASSMAP) MyomersClassID = 0xBC3, // ctor @004b1d18 (CLASSMAP) GyroClassID = 0xBC4, // ctor @004b3778 -> this[0x14a] SinkSourceClassID = 0xBC5, // ctor @004b6b0c -> this[0x10e] ActuatorClassID = 0xBC6, // ctor @004b8fec WeaponEmitterClassID = 0xBC8, // ctor @004bb120 (+weapon count) JumpJetClassID = 0xBCB, // ctor @004bd5c4 MechWeaponClassID = 0xBCD, // ctor @004bc3fc (CLASSMAP) MissileWeaponClassID = 0xBCE, // ctor @004bdcb4 (+weapon count) BallisticWeaponClassID = 0xBD0, // ctor @004bcff0 (+weapon count) MechControlsMapperID = 0xBD3, // ctor @0049bca4 -> this[0x10d] GaussWeaponClassID = 0xBD4, // ctor @004bb888 (+weapon count) MechTechClassID = 0xBD6, // ctor @004b7f94 -> this[0x16d] LegSubsystemClassID = 0xBD8, // ctor @004b84dc HeatableClassID = 0xBDC, // ctor @004ad228 (heat.cpp family; exact class TBD) DisplayClassID = 0xBDE // ctor @004b8718 }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Test Class Support // public: Logical TestInstance() const; // @004a4c7c //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // App / cockpit interface (called by the BT_L4 layer: btl4app/mppr/pb). // Declarations only -- definitions live in mech.cpp (factory pass / bring-up). // public: enum ResetMode { MissionReviewReset = 0 }; // reset-mode selector (Reset arg, value 0) void SetMappingSubsystem(Subsystem *mapper); // btl4app.cpp:567 Logical GetMissionReviewMode(); // reads this+0x414 (btl4mppr.cpp:366) void SetTargetRange(Scalar range); // writes this+0x404 (btl4mppr.cpp:407) void Reset(const Origin &origin, int mode); // btl4pb.cpp:555 (FUN_0049fb74) //~~~ Skeleton joint access (shared by Torso, Gyroscope) ~~~~~~~~~~~~~~~~ // Resolve a named skeleton node to its live Joint* via the PUBLIC segment // path (GetSegment -> segment joint index -> JointSubsystem::GetJoint); // the protected EntitySegment::jointPointer is never read. The Torso ctor // (@004b6b0c) and Gyroscope ctor (@004b3778) inline this exact sequence // (binary FUN_00424b60); hoisted here as the single reuse point. Joint* ResolveJoint(const char *joint_name); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction and Destruction // public: typedef Mech__ModelResource ModelResource; typedef Mech__UpdateRecord UpdateRecord; typedef Mech__MakeMessage MakeMessage; typedef Mech__DamageZone DamageZone; static Mech* Make(MakeMessage *creation_message); // @004a2d48 Mech( MakeMessage *creation_message, SharedData &shared_data = DefaultData ); // @004a1674 ~Mech(); // @004a452c (vtable slot 0) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Resource authoring (offline tools) // public: static ResourceDescription::ResourceID CreateModelResource( // @004a2da8 ResourceFile *resource_file, const char *model_name, NotationFile *model_file, const ResourceDirectories *directories, ModelResource *model = 0 ); static Logical CreateModelResourceStub( // @004a2d78 (best-effort) ModelResource *model ); // stamps model->classID = MechClassID typedef Logical (*FindNameFunction)( const char *control_name, ControlsMapping *mapping); static ResourceDescription::ResourceID CreateControlMappingStream( // @004a3794 const char *mapping_name, NotationFile *mapping_file, FindNameFunction find_name, ResourceFile *resource_file, const char *model_name, NotationFile *model_file, const ResourceDirectories *directories, PlatformTool *current_tool ); static ResourceDescription::ResourceID CreateDamageZoneStream( // @004a474c ResourceFile *resource_file, const char *model_name, NotationFile *model_file, const ResourceDirectories *directories ); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Simulation / replication // protected: void ReadUpdateRecord( // @004a1232 (vtable slot 6) Simulation::UpdateRecord *message ); // WriteUpdateRecord / per-frame Simulate live in the uncaptured // mech.cpp gap (slot 7 @004a0c2c) and mech2.cpp. [mech2] //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Instance-flag helper // protected: void SetInstanceFlags(Word flags); // @004a4c54 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Subsystem roster (host pattern -- cf. VTV / CLASSMAP shared base) // // this[0x47] segmentCount / this[0x48] segment[] (JointedMover base) // this[0x49] subsystemCount / this[0x4a] subsystem[] (Mech-owned table) // The capability sub-rosters below are ChainOf<> views built in the ctor // by IsDerivedFrom() tests against the streamed subsystem classes. // protected: // NOTE: subsystemCount (this[0x49]) and the subsystem table (this[0x4a]) // are the INHERITED Entity base members `subsystemCount` / `subsystemArray` // (Entity reads them in GetSimulation/GetSubsystem). They are NOT // re-declared here -- doing so shadowed the base fields, so the engine's // control-mapping / per-frame code never saw the streamed roster. The // ctor streams into the inherited subsystemArray/subsystemCount directly. // NOTE: damageZoneCount (this[0x47]) and the zone table (this[0x48]) are the // INHERITED Entity base members `damageZoneCount` / `damageZones` -- the engine // Entity ctor (ENTITY.cpp:961-987 == binary FUN_0041ff38) reads the count from // the type-0x14 resource and allocates damageZones[count] (entries left // uninitialised); the Mech ctor populates THAT inherited array. They are NOT // re-declared here -- doing so shadowed the base fields (count read 0xCDCDCDCD, // the populate loop never ran, and the engine damage router read the empty // base array). Same lesson as subsystemCount/subsystemArray above. Use Zone(i) // for typed (Mech__DamageZone*) access to the engine-stored DamageZone* entries. ReconChain controllableSubsystems; // @0x65c this[0x197] (FUN_00427768) ReconChain watchedSubsystems; // @0x6bc this[0x1af] (FUN_00427768) ReconChain heatableSubsystems; // @0x7ac this[0x1eb] (class 0x51155c) ReconChain poweredSubsystems; // @0x7bc this[0x1ef] (class 0x511830) ReconChain damageableSubsystems; // @0x7cc this[499] (class 0x50e4fc) int weaponCount; // @0x448 this[0x112] Subsystem *sensorSubsystem; // @0x7dc this[0x1f7] (0xBBE) Subsystem *gyroSubsystem; // @0x528 this[0x14a] (0xBC4) public: // Cache accessors for the controls mapper (@004afd10 reads mech+0x438/+0x5b4). Subsystem *GetTorsoSubsystem() { return sinkSourceSubsystem; } Subsystem *GetHudSubsystem() { return hudSubsystem; } // Reachable horizontal firing half-arc (radians) the mech's torso can bring // its guns to bear -- the wider torso twist limit, or 0 for a fixed torso. // Defined in mechmppr.cpp (which knows the full Torso type). Used by the // weapon fire path (mech4.cpp) to derive the real, per-mech firing arc. Scalar GetHorizontalFiringReach(); protected: Subsystem *sinkSourceSubsystem; // @0x438 this[0x10e] (0xBC5 -> real Torso) // The @0x5b4 cache holds the HUD, NOT the MechTech: raw factory (part_012.c: // 10155-10164) case 0xbd6 (alloc 0x2a4, HUD ctor @004b7f94) writes param_1[0x16d]; // case 0xbdc (MechTech, alloc 0x104) stores NO cache. Also required by the // controls mapper (@004afd10 writes cockpit+0x28c -- past MechTech's alloc, // inside the HUD's). Was misnamed mechTechSubsystem. Subsystem *hudSubsystem; // @0x5b4 this[0x16d] (0xBD6 -> real HUD) MechControlsMapper *controlsMapper; // @0x434 this[0x10d] (0xBD3) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Pose / kinematics (subset touched by the recovered slice) // protected: Matrix34 orientation; // @0xd0 this[0x34] (FUN_0040ab44) Quaternion bodyRotation; // @0x100 this[0x40] BTVal torsoRotation; // @0x130 this[0x4b] (proxy: .Mul) Vector3D worldPosition; // @0x138 this[0x4e] BTVal turretBase; // @0x1c4 this[0x71] (proxy: .SetIdentity) Quaternion headPitch; // @0x1cc this[0x73] (look) Quaternion torsoTwist; // @0x1dc this[0x77] Vector3D legAngle; // @0x1f4 this[0x7d] Vector3D hipAngle; // @0x200 this[0x80] BTVal torsoAimCurrent; // @0x298 this[0xa6] (proxy: .Mul/.SetIdentity) BTVal torsoAimTarget; // @0x2c8 this[0xb2] (proxy: .SetIdentity) EulerAngles netOrientation; // @0x2d4 this[0xb5] (from update record) // SHADOW JOINT (task #20): the model record's ShadowJointName (record+0xB4, // 'jointshadow' on the BLH) resolved to the live skeleton Joint that tilts // the flat *_tshd shadow proxy to the terrain angle. Binary: part_012.c: // 10285 (record+0xb4 -> CString -> GetSegment -> GetJointIndex -> GetJoint // -> this[0x10b] @0x42c). Accessed BY NAME only; declared after the locked // fields so it does not shift them. public: Joint *shadowJointNode; // binary @0x42c this[0x10b] void UpdateShadowJoint(const Vector3D &ground_normal); // AUTHENTIC GROUND MODEL ctor products (task #15, ground-model-decode; // binary part_012.c:9938-9940 + 9974-9975). By-name access only; declared // after the layout-locked fields so nothing shifts. Scalar standingTemplateMaxY; // binary @0x518 collisionTemplate->maxY at ctor Scalar duckedTemplateMaxY; // binary @0x51c 0.6 x standing (duck preset) Scalar templateBottomLift; // binary @0x4b8 0.05 x (volume maxX-minX) protected: //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Alarms / indicators // protected: AlarmIndicator masterAlarm; // @0x39c this[0xe7] (33 levels) AlarmIndicator heatAlarm; // @0x450 this[0x114] (3 levels) AlarmIndicator stabilityAlarm; // @0x4c4 this[0x131] (2 levels) AlarmIndicator statusAlarm; // @0x714 this[0x1c5] (33 levels) FilteredScalar telemetryFilter[5]; // @0x7e0 this[0x1f8..0x204] (15-sample) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Misc recovered state (names best-effort) // protected: BTVal mechName; // @0x360 this[0xd8] (proxy: .Copy) Scalar maxSpeed; // @0x400 this[0x100] = FLT_MAX int stateFlags; // @0x410 this[0x104] int throttleState; // @0x4a4 this[0x129] = 2 (initial) int heatLevel; // @0x518 this[0x146] int heatCapacity; // @0x51c this[0x147] = 0.6 * heatLevel Time creationTime; // @0x778 this[0x1de] // Cylinder hit-location table (STEP 6) -- DamageLookupTable* cached by the // ctor from the type-0x1d resource (FUN_0049ea48 = the table ctor), read by // Mech::TakeDamageMessageHandler to resolve an unaimed (-1) hit's zone from // its impact point. MUST be a real named member: the old home Wword(0x111) // is the recon ABSORBER bank (BTVal stores nothing, reads 0) -> the cached // table silently vanished and every unaimed hit no-op'd. (Was mislabeled // "ammoExpended"; binary slot this[0x111] is this pointer.) int damageLookupTable; // @0x444 this[0x111] (FUN_0049ea48) int deathHandler; // @0x850 this[0x214] (FUN_0042a984) // Wreck-smoke cadence (port addition, [T3]): re-fires the death/rubble // smoke plume (psfx 1, DDTHSMK) every 10s (its authored emission window) // while the mech is a destroyed wreck, so a dead mech keeps smoking. Scalar wreckSmokeTimer; // Three ref-counted creation-name objects (badge/color/insignia). void *resourceNameA; // @0x844 this[0x211] void *resourceNameB; // @0x848 this[0x212] void *resourceNameC; // @0x84c this[0x213] //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Reconstruction additions -- members & methods whose bodies live in the // mech2 / mech3 / mech4 slices (declared here so the whole class is visible). // Offsets cite the mech2/mech3 offset maps; types inferred from usage. // public: static Receiver::MessageHandlerSet MessageHandlers; // SharedData ctor operand // Mech's OWN handler table (overrides Entity's TakeDamage with the // cylinder hit-location resolver); chained to Entity::GetMessageHandlers(). static const Receiver::HandlerEntry MessageHandlerEntries[]; enum { StateCount = 0x21 }; // // Attribute Support -- the named attributes the cockpit gauges bind to. // The config (content/GAUGE/L4GAUGE.CFG) binds the bare names LinearSpeed / // MaxRunSpeed / DuckState / RadarRange / RadarLinearPosition / // RadarAngularPosition, resolved by Simulation::GetAttributePointer -> // activeAttributeIndex->Find(name). The reconstruction shipped an EMPTY, // unchained Mech::AttributeIndex, so EVERY bare Mech gauge (and even the // inherited attributes) resolved to NullAttribute. // // The full binary id set (0x15..0x38, table VA 0x50be84) is declared so the // published table can be extended without renumbering. The chain starts at // JointedMover::NextAttributeID (0x15); ids MUST stay contiguous, because the // built index is a dense array sized to max(id) and AttributeIndexSet::Find // strcmps EVERY slot -- an unfilled slot holds a garbage entryName -> AV. // enum { MaxAccelerationAttributeID = JointedMover::NextAttributeID, // 0x15 CollisionStateAttributeID, // 0x16 CollisionNormalAttributeID, // 0x17 CollisionSpeedAttributeID, // 0x18 CollisionMaterialTypeAttributeID, // 0x19 CurrentSpeedAttributeID, // 0x1a MaxRunSpeedAttributeID, // 0x1b EyepointRotationAttributeID, // 0x1c TargetReticleAttributeID, // 0x1d FootStepAttributeID, // 0x1e AnimationStateAttributeID, // 0x1f ReplicantAnimationStateAttributeID, // 0x20 LinearSpeedAttributeID, // 0x21 ClimbRateAttributeID, // 0x22 AccelerationLastFrameAttributeID, // 0x23 AngularSpeedAttributeID, // 0x24 ArmorDamageLevelAttributeID, // 0x25 SubsystemDamageLevelAttributeID, // 0x26 MyomerDamageLevelAttributeID, // 0x27 TestButton1AttributeID, // 0x28 TestButton2AttributeID, // 0x29 TestButton3AttributeID, // 0x2a TestButton4AttributeID, // 0x2b TestButton5AttributeID, // 0x2c TestButton6AttributeID, // 0x2d ReduceButtonAttributeID, // 0x2e RadarRangeAttributeID, // 0x2f RadarLinearPositionAttributeID, // 0x30 RadarAngularPositionAttributeID, // 0x31 RearFiringAttributeID, // 0x32 RequestDuckAnimationAttributeID, // 0x33 UnstablePercentageAttributeID, // 0x34 SuperStopAttributeID, // 0x35 IncomingLockAttributeID, // 0x36 DuckStateAttributeID, // 0x37 DistanceToMissileAttributeID, // 0x38 NextAttributeID // 0x39 }; static const IndexEntry AttributePointers[]; static AttributeIndexSet& GetAttributeIndex(); // Cockpit-gauge attribute backing members (APPENDED to Mech's own region -- // never at a locked base/gait offset). attrPad is a shared read-only 0 for // the dense-prefix ids the BLH cockpit does not bind; linearSpeed is the live // forward ground speed the speed readout / arc / radar consume (binary @0x81c). Scalar attrPad; Scalar linearSpeed; // Radar/map gauge attributes (binary @0x404/0x408/0x40c/0x3f8). The map // widget reads position/angle as POINTERS into the mech's live origin, so // radarLinearPosition/radarAngularPosition point at localOrigin.{linear, // angular}Position (set in the ctor); radarRange is the radar scale. Scalar radarRange; // 0x2f RadarRange (scale/max) Point3D *radarLinearPosition; // 0x30 RadarLinearPosition Quaternion *radarAngularPosition; // 0x31 RadarAngularPosition int duckState; // 0x37 DuckState (crouch posture) // --- raw / engine-shim members touched by the ctor slice ------------- void *vtable; // installed Mech vtable ptr Word instanceFlags; // entity instance flags (this+0x18 region) ReconFiltered mechNameFilter; // @0xdb name filter ReconSlot voltageBus; // resolve-able power-bus slot void *controlSource; // @0x128 controls subsystem handle // --- locomotion / gait animation state (mech2/mech3 maps) ------------ Word actionRequestFlags; // @0x18 pending action bits int movementMode; // @0x40 gait/death selector int movementFlags; int motionEventArmed; // @0x5a4 int motionEventPending; int airborneSelect; Scalar forwardCycleRate; // @0x344 Scalar legCycleSpeed; // @0x348 Scalar reverseStrideLength; // @0x34c Scalar gimpStrideLength; // @0x350 AlarmIndicator legStateAlarm; // @0x39c int legAnimationState; // @0x3b0 Scalar gimpSpeedMax; // @0x52c Scalar standSpeed; // @0x530 Scalar walkStrideLength; // @0x534 Scalar reverseSpeedMax; // @0x538 Scalar jumpRunSpeedMax; // @0x53c Scalar jumpWalkSpeedMax; // @0x540 Scalar jumpRunStrideLength; // @0x544 Scalar jumpWalkStrideLength; // @0x548 Scalar groundCycleRate; // @0x5b8 Scalar airborneCycleRate; // @0x5bc // Forward-throttle scale read by the controls mapper (@004afd10: // speedDemand = topSpeed@0x34c * throttle * THIS). No writer found in the // decomp windows (likely model/status-driven); ctor inits 1.0 (neutral). Scalar forwardThrottleScale; // @0x5c0 int clipLoadGuard; // @0x5c4 Scalar globalTimeScale; // @0x5a8 Scalar idleStrideScale; // @0x5ac Scalar gimpCycleRate; // @0x5b0 int jumpCapable; // @0x580 int hasReverseGimpSet; // @0x584 int hasCrashSet; // @0x588 int deathAnimationLatched; // @0x650 int legResetLatch; // @0x654 int bodyResetLatch; // @0x658 ReconSeq legAnimation; // @0x65c channel-A controller ReconSeq bodyAnimation; // @0x6bc channel-B controller AlarmIndicator bodyStateAlarm; // @0x714 int bodyAnimationState; // @0x728 Scalar bodyTargetSpeed; // @0x6b4 Scalar bodyCycleSpeed; // @0x6b8 Scalar reverseSpeedMax2; // @0x7a0 Scalar arrivalTime; Scalar simTime; Scalar spinRate; ResourceDescription::ResourceID animationClips[40]; // @0x5cc gait clips // namedClip is NOT a separate array in the binary: namedClip@0x5e0 == &animationClips[5] // (0x5cc + 5*4). LoadLocomotionClips writes namedClip[i]; SetBodyAnimation/MeasureClipStride // read animationClips[i+5] -- SAME storage. Aliased via this pointer (set in the ctor) so the // array syntax in mech3.cpp keeps working AND the writes land where the state machine reads. ResourceDescription::ResourceID *namedClip; // -> &animationClips[5] ResourceDescription::ResourceID crashClipA, crashClipB, crashClipC; // @0x5d8/0x5d4/0x5dc ResourceDescription::ResourceID gimpBaseClip; // @0x64c CString motionEventName; // @0x598 cleared to "" on fall/reset // --- gait helpers (mech2) ------------------------------------------- void SetLegAnimation(int state); // @004a7fc4 void SetBodyAnimation(int state); // @004a800c void RequestActionFlags(Word bits); // @004a4c54 // P3 STEP-7 body finished-callback (the real PTR_LAB_0050d6fc == FUN_004a6d8c): // called by SequenceController::Advance at end-of-clip -- picks the next gait state // from commanded speed vs the loaded caps, re-arms via SetBodyAnimation, recursively // advances the carryover. Static (the cb is a plain fn ptr, owner Mech is arg1). // BodyTransition = the shared tail (SetBodyAnimation(next) + bodyAnimation.Advance). // LoopBodyClip = bring-up loop for the inline cutover path. static Scalar BodyClipFinished(Mech *m, unsigned a2, Scalar carryover, int move_joints); // FUN_004a6d8c static Scalar LoopBodyClip(Mech *m, unsigned a2, Scalar carryover, int move_joints); Scalar BodyTransition(int next_state, Scalar adv_time, int move_joints); // LEG-channel finished-callback (the real PTR_LAB_0050d6f0 == FUN_004a6928): // same transition machine keyed on legAnimationState@0x3b0, but compares the // LIVE mapper speedDemand (subsystemArray[0]+0x128) and slews legCycleSpeed. static Scalar LegClipFinished(Mech *m, unsigned a2, Scalar carryover, int move_joints); // FUN_004a6928 Scalar LegTransition(int next_state, Scalar adv_time, int move_joints); Scalar AdvanceLegAnimation(Scalar time_slice); // @004a5028 Scalar AdvanceBodyAnimation(Scalar time_slice, int loop); Scalar AdvanceBodyAnimationAirborne(Scalar time_slice, int loop); Scalar AdvanceLegAnimationAirborne(Scalar time_slice); Logical IsDisabled(); // FUN_0049fb54 // --- death sequence (the un-exported master-perf death branch, rebuilt // from its exported consumers + the RP VTV::DeathShutdown analog) --- Logical IsMechDestroyed(); // damage-side death flag: graphicAlarm level >= 9 void UpdateDeathState(Scalar dt); // death freeze + subsystem DeathShutdown + wreck smoke // --- clip loaders (mech3) ------------------------------------------- ResourceDescription::ResourceID * ResolveAnimationClip(const char *prefix, const char *suffix); // @004a7f50 // Body-animation looping callback (bring-up gait). Non-virtual so adding // it does NOT change object layout; passed to AnimationInstance::SetAnimation // (reinterpret_cast to JointedMover::AnimationCallback) so the clip re-arms at // frame 0 when it ends instead of dereferencing a NULL finishedCallback. Scalar OnBodyAnimFinished(ResourceDescription::ResourceID animation_number, Scalar carryover, Logical animate_joints); void MeasureClipStride(int slot, Scalar *total, Scalar *lastKey); // @004a8054 void LoadLocomotionClips(Mech__ModelResource *model); // @004a80d4 void LoadLocomotionClipsExt(Mech__ModelResource *model); // @004a86c8 static int CreateSubsystemStream( int subsystem_index, const char *type_name, int pass, NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResourceBlob *out_blob, NotationFile *subsystem_file, const ResourceDirectories *directories); static ResourceDescription::ResourceID CreateSubsystemsStream( NotationFile *model_file, const char *model_name, NotationFile *model_notation, const ResourceDirectories *directories); static SharedData * SubsystemDefaultData(const char *type_name); // --- simulation / damage (mech4) ------------------------------------ void DeadReckonPose(Scalar fraction); Logical IntegrateMotion(Scalar time_slice, int loop); void Simulate(Scalar time_slice); // --- BRING-UP locomotion (Tier 2: drivable player mech) ------------- // Overrides the engine per-frame Simulation::PerformAndWatch (virtual; // the simulation director calls it every frame on every entity). The // shipped game's per-frame tick (Simulate via activePerformance) is the // uncaptured/reconstructed-but-unsafe chain (raw this+0xNN offset bugs, // stubbed subsystems), so for the drivable bring-up we override // PerformAndWatch directly and integrate the player's localOrigin / // localToWorld from throttle+turn input. localToWorld is the matrix the // render tree + chase camera (btl4vid.cpp) are bound to, so the mech // walks and the camera follows. See mech4.cpp. MUST match the base // signature exactly or it hides instead of overriding. void PerformAndWatch(const Time& till, MemoryStream *update_stream); // THE REAL per-contact collision responder (task #15, ground-model-decode): // binary FUN_004abb40, Mech vtable slot +0x3c -- the override of the engine // protected virtual Mover::ProcessCollision (MOVER.h:359-365, signature // exact). The earlier draft misread this function as a weapon sweep // ("ResolveWeaponImpact") -- it is the mech-vs-world collision policy: // BoxedSolid resolver -> StaticBounce -> owner classification (Mover / // CulturalIcon crush sentinel 0.00123f). Gated: falls through to the // engine base when GroundReal() is off. virtual void ProcessCollision(Scalar time_slice, BoxedSolidCollision &collision, const Point3D &old_position, Damage *damage); // The authentic per-frame ground block (binary FUN_004a9b5c @4aa630-4aab5f): // MoveCollisionVolume -> ground probe/snap -> collisions -> response policy. // old_position = the START-OF-FRAME position (the blocking-hit restore target); // must NOT alias localOrigin.linearPosition. void AuthenticGroundAndCollide(Scalar dt, const Point3D &old_position); void FeedHeatCapacityGauge(); void FeedHeatLevelGauge(); static Logical LookupDamageState(const char *keyword, int *out); void RaiseStatusAlarm(int level); // subsystem -> mech status alarm // --- damage-table support (dmgtable) -------------------------------- Point3D WorldToLocal(const Point3D &world); // FUN_00408bf8 (owner+0xd0) void *TorsoOrientationSource(); // *(this+0x438) // Height reference the cylinder table normalises impact height against -- // the binary's *(mech+0x2ec)+0xc, a stance height set from mech+0x518 // (standing) / +0x51c (ducked). We return the standing value // (collisionTemplate->maxY captured at ctor). Scalar CylinderReferenceHeight(); // Live torso twist (yaw) in radians -- torso+0x1d8, via the roster torso // (sinkSourceSubsystem @0x438). 0 for a fixed-torso mech. Scalar TorsoHeading(); // Mech override of Entity::TakeDamageMessageHandler: resolve an unaimed // (invalidDamageZone) hit's zone via the cylinder table, then base-route. void TakeDamageMessageHandler(TakeDamageMessage *message); // --- damage-routing support (mechdmg / mech4) ----------------------- // Typed access to the inherited Entity::damageZones[] (engine stores DamageZone*; // our entries are Mech__DamageZone, populated by the Mech ctor). Defined in // mech.cpp where Mech__DamageZone is a complete type. Mech__DamageZone *Zone(int i) const; // First VITAL damage zone index (0 if none) -- for concentrated fire that kills. // Defined in mech4.cpp (Mech__DamageZone::vitalDamageZone is protected; Mech has access). int FirstVitalZone() const; int flags; // entity flags word (this+0x28 region) int stance; // posture state int ammoState; // @0x44c 0 none / 1 leaking / 2 dry AlarmIndicator graphicAlarm; // body graphic-state alarm EntityID lastInflictingID; // @0x43c last attacker (read by DamageZone routing) Logical IsAirborne(); // --- gait world-step accumulators (P3 STEP-6 base-region reconciliation) --- // RELOCATED out of the engine base: IntegrateMotion used raw 1995 binary // offsets that, in the 2007 engine layout, stomp live engine fields // (0x260->projectedOrigin, 0x26c->previousOrigin, 0x298->projectedVelocity, // 0x100->localOrigin, 0x12c->updateOrigin). Declared here in the Mech's OWN // region (appended -- shifts no locked offset) and accessed BY NAME. See // btbuild/P3_LOCOMOTION.md "BASE-REGION RECONCILIATION". Quaternion motionDelta; // was raw @0x260 (orientation delta; FromQuat src) Quaternion worldPose; // was raw @0x26c (integrated world pose) Quaternion worldPoseBase; // was raw @0x138 (dead-reckon base pose; DeadReckonPose) Quaternion angularAccum; // was raw @0x298 (per-frame angular accumulator) Vector3D motionSourceA; // was raw @0x100 (motion-event source A) Vector3D motionSourceB; // was raw @0x12c (motion-event source B) Vector3D motionEventVector; // was raw @0x598 (motion-event delta = B - A) Quaternion aimRate; // was raw @0x1dc (aim/torso angular-rate telemetry, // cleared each frame; stomped localAcceleration) // // --- Everything past here is owned by mech2.cpp .. mech4.cpp --- // [mech2] MoveAndCollide / Simulate / WriteUpdateRecord / damage routing // [mech3] PrintState / status reporting / cockpit display feed // [mech4] AI / pathing / scoring hooks // The remaining members up to sizeof==0x854 are declared by those slices. // }; #endif