//===========================================================================// // File: mechmppr.hpp // // Project: BattleTech Brick: Entity Manager // // Contents: Mech controls mapper -- maps pilot control inputs and view // // selection onto the Mech's motion / torso / eyepoint demands // //---------------------------------------------------------------------------// // 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, cluster // @004afbe0-@004b08c0) cross-referenced against the Red Planet sibling // RP\VTVMPPR.cpp / VTVMPPR.h (VTVControlsMapper) -- same engine, same // mapper pattern. The class name "MechControlsMapper" is recovered verbatim // from the Derivation name string @0050f173. Attribute and message names are // recovered from the class string pool @0050f180-@0050f367 and the attribute // table @0050efd0; the configuration / event-mapping stub strings come from // @0050f370-@0050f44a. // // Field offsets in comments are the byte offsets observed in the decompiled // object (e.g. "@0x114" == this[0x45]). Names follow VTVControlsMapper where // the role is identical; Mech-specific members (torso, pilot array) are named // from the attribute table and flagged best-effort where uncertain. // // This is the BattleTech analog of Red Planet's VTVControlsMapper. Like that // class it is the active "Performance" of the master, dynamic Mech: each // simulation frame InterpretControls() reads the raw stick/throttle/pedals and // the look/torso buttons and produces speedDemand / turnDemand plus the torso // and eyepoint pose used by the locomotion and view code. // #if !defined(MECHMPPR_HPP) # define MECHMPPR_HPP #if !defined(SUBSYSTM_HPP) # include #endif #if !defined(CONTROLS_HPP) # include // ControlsButton, ControlsJoystick #endif //##################### Forward Class Declarations ####################### class Mech; class Pilot; //########################################################################### //######################### MechControlsMapper ########################## //########################################################################### // // Primary vtable @0050f45c (slot 0 = scalar-deleting destructor @004b044c; // slots 1-13 inherited from Subsystem; slots 18/19 -- vtable+0x48/+0x4c -- // are the NotifyOf* hooks below). A secondary "controls mapper / // configurator" interface vtable @0050f498 supplies the EnterConfiguration / // ExitConfiguration / event-mapping stubs (all "not overridden" by default). // class MechControlsMapper: public Subsystem { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // public: // WinTesla engine refactored the static derivation/handler/attribute // objects behind Get* accessors (avoids static-init ordering bugs). static Derivation *GetClassDerivations(); // @0050ee10 static SharedData DefaultData; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Messaging Support // // Message IDs and handler names recovered from the message table // @0050ee40 (string pool @0050f180). IDs 3..0x13 (the auxiliary-equipment // and zoom buttons) are routed through the shared ConfigureMappable // handler @004afbc4; the last three drive dedicated handlers. // public: enum { Aux1QuadMessageID = Subsystem::NextMessageID, // 3 Aux1Eng1MessageID, Aux1Eng2MessageID, Aux1Eng3MessageID, Aux1Eng4MessageID, Aux2QuadMessageID, Aux2Eng1MessageID, Aux2Eng2MessageID, Aux2Eng3MessageID, Aux2Eng4MessageID, Aux3QuadMessageID, Aux3Eng1MessageID, Aux3Eng2MessageID, Aux3Eng3MessageID, Aux3Eng4MessageID, ZoomInMessageID, // 0x12 ZoomOutMessageID, // 0x13 CycleControlModeMessageID, // 0x14 CycleDisplayModeMessageID, // 0x15 ToggleVoiceAssistMessageID, // 0x16 NextMessageID }; void ConfigureMappableMessageHandler( // @004afbc4 (shared) ReceiverDataMessageOf *message ); void CycleControlModeMessageHandler( // @004afbe0 ReceiverDataMessageOf *message ); // The shared mode-cycle body (the console-button handler above + the // desktop 'M' key both drive it). void CycleControlModeNow(); void CycleDisplayModeMessageHandler( // @004afcac ReceiverDataMessageOf *message ); // The shared display-cycle body (Gitea #6: the pod console button // message above + the desktop 'N' key both drive it) -- cycles the // secondary screen's Damage -> Critical -> Heat schematic via the // NotifyOfDisplayModeChange override (vtbl+0x4C, @004d1ae4). void CycleDisplayModeNow(); void ToggleVoiceAssistMessageHandler( // @004afce8 ReceiverDataMessageOf *message ); private: static const HandlerEntry MessageHandlerEntries[]; // @0050ee40 protected: static MessageHandlerSet& GetMessageHandlers(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Attribute Support // // Attribute IDs / names / member offsets recovered from the attribute // table @0050efd0. (The recorded member offsets carry the engine's // low-bit "scalar" tag, e.g. 0x115 -> &stickPosition @0x114.) // public: enum { // Ids are PINNED to the binary table's numbering (stick=3 ...): // the streamed .CTL mappings carry these ids POSITIONALLY, but our // parent chain ends at Subsystem::NextAttributeID == 2 -- the // binary's reaches 3 (one base attr we don't publish). Chained // (un-pinned) ids landed every streamed record ONE MEMBER LATE -- // live-verified 2026-07-17 via BT_CTRLMAP_LOG: attr 4 (binary // ThrottlePosition) resolved to pedalsPosition; on a real pod the // RIO throttle would drive the pedals. The id-2 gap is padded in // the table (the mech.cpp attrPad idiom, gotcha #11) and locked by // static_asserts in mechmppr.cpp. See docs/GLASS_COCKPIT.md ยง2a. MechControlsMapperPadFirstAttributeID = 2, // == Subsystem::NextAttributeID (locked) StickPositionAttributeID, // 3 ThrottlePositionAttributeID, // 4 PedalsPositionAttributeID, // 5 ReverseThrustAttributeID, // 6 SpeedDemandAttributeID, // 7 TurnDemandAttributeID, // 8 LookForwardAttributeID, // 9 LookLeftAttributeID, // 0xa LookRightAttributeID, // 0xb LookBehindAttributeID, // 0xc LookDownAttributeID, // 0xd TorsoUpAttributeID, // 0xe TorsoDownAttributeID, // 0xf TorsoLeftAttributeID, // 0x10 TorsoRightAttributeID, // 0x11 TorsoCenterAttributeID, // 0x12 ControlModeAttributeID, // 0x13 DisplayModeAttributeID, // 0x14 PilotArrayPageAttributeID, // 0x15 PilotArrayAttributeID, // 0x16 NextAttributeID }; private: static const IndexEntry AttributePointers[]; // @0050efd0 protected: static AttributeIndexSet& GetAttributeIndex(); // // Published attributes. // public: ControlsJoystick stickPosition; // @0x114 (x @0x114, y @0x118) Scalar throttlePosition; // @0x11C Scalar pedalsPosition; // @0x120 ControlsButton reverseThrust; // @0x124 Scalar speedDemand; // @0x128 (output) Scalar turnDemand; // @0x12C (output) ControlsButton lookForward; // @0x130 ControlsButton lookLeft; // @0x134 ControlsButton lookRight; // @0x138 ControlsButton lookBehind; // @0x13C ControlsButton lookDown; // @0x140 ControlsButton torsoUp; // @0x144 ControlsButton torsoDown; // @0x148 ControlsButton torsoLeft; // @0x14C ControlsButton torsoRight; // @0x150 ControlsButton torsoCenter; // @0x154 int pilotArrayPage; // @0x158 (which "page" of pilots) // FIXED 10-SLOT block, the binary's own reservation: 0x15C..0x183 = // (pilotArrayBuilt@0x184 - 0x15C)/4 = 10 pointers ([0]=local, [1..9] // remote; an 8-pod game + the local fits). The old `[1]` "variable // length" declaration made EVERY write past slot 0 stomp the members // declared after it: in MP, FillPilotArray wrote the PEER's Player // pointer over controlMode (the turn shaping dispatched on pointer // garbage -> turnDemand 0 -> "I can't turn"); in solo the zero-loop // wrote 0 there, masked only because 0 == BasicMode. Caught live via // cdb `ba w4` on &controlMode (task #51). enum { PilotArraySlots = 10 }; Pilot *pilotArray[PilotArraySlots]; // @0x15C..0x183 enum ControlMode { BasicMode = 0, StandardMode = 1, VeteranMode = 2 } controlMode; // @0x190 int displayMode; // @0x194 (0..2, cycles) int lookState; // @0x198 eyepoint look selection int previousLookState; // @0x19C protected: Logical pilotArrayBuilt; // @0x184 built-once flag int *pilotIDs; // @0x188 parallel id table int pilotCount; // @0x18C //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Model Support // public: typedef void (MechControlsMapper::*Performance)(Scalar time_slice); void SetPerformance(Performance performance) { Check(this); activePerformance = (Simulation::Performance)performance; } // The active master-Mech performance (vtable default @0050f120). void InterpretControls(Scalar time_slice); // @004afd10 // Owner accessor -- the mapper's owning entity is always a Mech. // (Subsystem::GetEntity() returns the generic Entity*; the decomp // pseudo-C used the owner pointer directly.) Mech* GetMech(); // // Look / torso eyepoint selection. // public: enum LookState { LookNone = 0, LookLeftState = 1, LookRightState = 2, LookBehindState = 3, LookDownState = 4 }; // // Pilot-array (other-player roster) management. Built lazily on first // use from the "Players" group of the application object (@0050f44b). // public: void BuildPilotArray(); // @004b0600 Pilot * GetPilot(int index); // @004b0898 void ChooseNearestPilot(int self_id); // @004b04d8 void ChooseDefaultPilot(); // @004b07f0 void UpdateCurrentPilot(int page); // @004b049c protected: void FillPilotArray(); // @004b06cc // // Configuration / event-mapping interface -- the CONFIG-MODE session // virtuals (task #6, vtable slots verified from the raw tables in // section_dump: base @0050F45C, L4 @0051E440, Thrustmaster @0051E3F0, // RIO @0051E3A0). There is NO "secondary vtable @0050f498" -- that // address is simply slot 15 INSIDE the primary base vtable. Slot map: // +0x38 EnterConfiguration (base Fail stub @004b0280; L4 @004d1840) // +0x3C ExitConfiguration (base Fail stub @004b029c; L4 @004d18dc) // +0x40 AddOrErase(event) (base Fail @004b02b8; L4/TM no-op @004d195c; RIO @004d25e8) // +0x44 AddOrErase(direct) (base Fail @004b02d4; L4/TM no-op @004d1964; RIO @004d262c) // +0x48/+0x4C NotifyOf* pair (base no-ops @004b048c/@004b0494) // The old reconstruction here ("CreateTemporaryEventMappings", a 4-arg // Enter, a 0-arg Exit) was RP-name drift; the binary signatures come from // the ONLY callers -- MechWeapon handlers id 9/10 @004b9550/@004b95b8: // Enter(|v|-1, &weapon.fireImpulse, weapon, /*choose*/10, /*configure*/9, /*active*/0) // Exit(|v|-1) // AddOrErase(v /*1-based*/, &weapon.fireImpulse) // public: virtual void EnterConfiguration( // +0x38 @004b0280 int held_element, ControlsButton *destination, Receiver *receiver, Receiver::MessageID choose_message_id, Receiver::MessageID configure_message_id, Receiver::MessageID active_message_id ); virtual void ExitConfiguration(int held_element); // +0x3C @004b029c virtual void AddOrErase( // +0x40 @004b02b8 (event overload) unsigned int button_ID, Receiver *receiver, Receiver::MessageID message_ID ); virtual void AddOrErase( // +0x44 @004b02d4 (direct overload) unsigned int button_ID, ControlsButton *destination ); virtual void NotifyOfControlModeChange(int new_mode); // @004b048c (vtable+0x48) virtual void NotifyOfDisplayModeChange(int new_mode); // @004b0494 (vtable+0x4c) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction and Destruction // public: MechControlsMapper( // @004b02f0 Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); ~MechControlsMapper(); // @004b044c Logical TestInstance() const; // @004b08c0 }; #endif