//===========================================================================// // File: btl4mppr.cpp // // Project: BattleTech Brick: BattleTech LBE Application // // Contents: The L4-application-side Mech controls mappers. L4MechControlsMapper// // is the platform layer over the in-Mech MechControlsMapper; the // // two concrete mappers (MechThrustmasterMapper / MechRIOMapper) // // bind the physical cockpit hardware (throttle/pedal axes, aux // // quadrant buttons, target hotbox, keyboard) to Mech demands, the // // exponential target-range "zoom", and the control/display modes. // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary BTL4OPT.EXE (no header survived). The // class shape follows the Red Planet analog RP_L4\RPL4MPPR.cpp // (L4VTVControlsMapper / VTVThrustmasterMapper / VTVRIOMapper); method bodies // are recovered from the Ghidra pseudo-C for the 0x4d11e8-0x4d27f8 cluster // (all/part_014.c). Names come from the .rdata string pool // ("L4MechControlsMapper" @0051e1eb, "MechThrustmasterMapper" @0051e2b3, // "MechRIOMapper" @0051e2e3) and from the base MechControlsMapper attribute // map already recovered in mechmppr.cpp (NOT re-emitted here). // // Function -> method map: // L4MechControlsMapper ----------------------------- vtable @0051e440 // @004d17ac ctor @004d1814 dtor // @004d196c InterpretControls (target-range ramp + review-mode watch) // @004d1b64 ZoomTargetRangeIn @004d1b9c ZoomTargetRangeOut // @004d1ae4 SetControlMode (mode-mask switch; calls base NotifyOfDisplayMode) // @004d1b24 SetPresetMode (group/item -> preset mode-mask) // @004d1840 CreateTemporaryEventMappings @004d18dc RemoveTemporaryEventMappings // @004d195c NotifyOfControlModeChange (no-op) @004d1964 NotifyOfConfigurationModeChange (no-op) // @004d1bf0 KeypressMessageHandler (the shared L4 keyboard dispatcher) // MechThrustmasterMapper --------------------------- vtable @0051e3f0 // @004d21d0 ctor @004d22b4 dtor @004d22e0 TestInstance // @004d2150 InterpretControls // MechRIOMapper ------------------------------------ vtable @0051e3a0 // @004d266c ctor @004d27cc dtor @004d27f8 TestInstance // @004d25c0 InterpretControls // @004d22fc..@004d24bc Aux{1,2,3}{Quad,Eng1..4} message handlers // @004d24dc ZoomIn @004d24f8 ZoomOut @004d2574 Hotbox (pilot select) // @004d25e8/@004d262c AddOrErase(Receiver*)/AddOrErase(ControlsButton*) // File-scope helpers: // @004d13f0 SendFakeButtonEvent(name, value) (find local-mech subsystem) // @004d133c SendFakeButtonEventPair(mech, name, msgID) (press + release) // @004d141c DispatchDeveloperFakeEvent(this, message) (debug subsystem keys) // @004d168c (best-effort) developer designate message [see note] // @004d11e8 module static cleanup (frees DAT_004f1aac list) [best-effort] // // Engine-helper name mapping: // FUN_004b02f0 MechControlsMapper::MechControlsMapper (base ctor, mechmppr.cpp) // FUN_004b044c MechControlsMapper::~MechControlsMapper // FUN_004b0494 MechControlsMapper::NotifyOfDisplayModeChange // FUN_004b049c MechControlsMapper::UpdateCurrentPilot(page) // FUN_004b04d8 MechControlsMapper::ChooseNearestPilot(self_id) // FUN_004b0600 MechControlsMapper::BuildPilotArray // FUN_004afbe0 MechControlsMapper::CycleControlModeMessageHandler // FUN_004afcac MechControlsMapper::CycleDisplayModeMessageHandler // FUN_004afd10 MechControlsMapper::InterpretControls (the in-Mech master perf) // FUN_0041f98c Entity::FindSubsystem(name) / Mech::FindSubsystemByName // FUN_0041a1a4 IsDerivedFrom(classDerivations) // FUN_004024d8 SharedData/Resource::Release(refcount) // FUN_004022d0 operator delete // FUN_00476fc0 ControlsManager::MakeLinkedLamp(button, modeMask) // FUN_00444e64 L4Lamp::SetAutomaticOperation / SetState // FUN_00476e34/00476e54/00476e8c ControlsManager Begin/End/Center Joystick // FUN_0044e6c0 Application::ProfileReport // FUN_00448654 GaugeRenderer::ProfileReport // FUN_004dcd00 fabsf() FUN_004dce94 powf(2.0, x) // FUN_004dbb24 DEBUG_STREAM << (stream insert) // DAT_004efc94 the global Application* // (app+0x3c controlsManager, app+0x50 modeManager, app+0x4c gaugeRenderer) // // Read-only constants resolved from section_dump.txt: // _DAT_004d1ac0 = 0x3f800000 = 1.0f (full-throttle detent center) // _DAT_004d1ac4 = 0x3d4ccccd = 0.05f (detent snap tolerance) // _DAT_004d1ac8 = 0x437a0000 = 250.0f (target-range scale: range = 250*2^x) // _DAT_004d1b94 = 1.0f zoom-in step _DAT_004d1b98 = 0.0f min exponent // _DAT_004d1bcc = 1.0f zoom-out step _DAT_004d1bd0 = 5.0f max exponent // DAT_0051dbe4[3] = {0x40000,0x80000,0x100000} control-mode mode-masks // DAT_0051dbf0[15] = {clear,set} mode-mask pairs (group*5+item), preset select // DAT_0051dcd0[8] = {0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30} hotbox buttons // // ModeManager (app+0x50): +0x4 currentMode mask, +0x8 savedMode mask. // Mech (mapper owner @this+0xd0): +0x404 targetRange, +0x414 missionReviewMode. // #include #pragma hdrstop #if !defined(BTL4MPPR_HPP) # include #endif #if !defined(BTL4APP_HPP) # include #endif #if !defined(BTL4MODE_HPP) # include #endif #if !defined(L4CTRL_HPP) # include #endif #if !defined(MECH_HPP) # include #endif #if !defined(PLAYER_HPP) # include #endif // // The keyboard message the L4 dispatcher receives (recovered name; it is just // the engine's ReceiverDataMessageOf). // typedef ReceiverDataMessageOf ControlsKeyMessage; //############################################################################# // File-scope helpers //############################################################################# // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // @004d13f0 -- look the named subsystem up on the LOCAL player's Mech and // poke its config slot (vtbl+0x20) with the supplied value. Used by the // developer "fake button" keys below. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // static void SendFakeButtonEvent(const char *subsystem_name, int value) { // Engine drift: the original read the local Mech via Application::localStation // (DAT_004efc94+0x6c). WinTesla exposes it through the mission player's // vehicle instead. Player *player = application->GetMissionPlayer(); if (player == NULL) { return; } Mech *mech = (Mech*)player->GetPlayerVehicle(); if (mech == NULL) { return; } Subsystem *subsystem = mech->FindSubsystem(subsystem_name); // FUN_0041f98c if (subsystem != NULL) { // Engine drift: Subsystem::SetConfigurationValue (vtbl+0x20) is gone in // WinTesla. Translate the developer "poke" into a configuration-style // button dispatch carrying the value. ReceiverDataMessageOf poke(0, sizeof(ReceiverDataMessageOf), (ControlsButton)value); subsystem->Dispatch(&poke); } } // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // @004d133c -- find a subsystem by name on the given Mech and dispatch a // momentary press (1) immediately followed by a release (-1) so the target // behaves as if a real cockpit button were tapped. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // static void SendFakeButtonEventPair( Mech *mech, const char *subsystem_name, Receiver::MessageID message_id ) { Subsystem *subsystem = mech->FindSubsystem(subsystem_name); // FUN_0041f98c if (subsystem == NULL) { DEBUG_STREAM << "SendFakeButtonEvents: subsystem '" // @0051e1b6 << subsystem_name << "' not found\n"; // @0051e1d8 } else { ReceiverDataMessageOf press(message_id, sizeof(ReceiverDataMessageOf), 1); subsystem->Dispatch(&press); // vtbl+0xc ReceiverDataMessageOf release(message_id, sizeof(ReceiverDataMessageOf), -1); subsystem->Dispatch(&release); } } // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // @004d141c -- the "default" keyboard case: a developer/test dispatcher that // maps single key codes to fake button events on individual named subsystems // (GeneratorA..D, Condenser1, the LRM15 launcher and its ammo bin, Avionics, // Hud) plus two special keys. Faithful to the shipped switch. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // static void DispatchDeveloperFakeEvent(L4MechControlsMapper *mapper, ControlsKeyMessage *message) { switch (message->dataContents) { case 0x5c: // '\' -- momentary press straight onto the owner Mech { ReceiverDataMessageOf press(0x19, sizeof(ReceiverDataMessageOf), 1); mapper->GetMech()->Dispatch(&press); // (this+0xd0)->vtbl+0xc } break; case 0x15e: SendFakeButtonEvent("GeneratorA", 0); break; case 0x15f: SendFakeButtonEvent("GeneratorB", 0); break; case 0x160: SendFakeButtonEvent("GeneratorC", 0); break; case 0x161: SendFakeButtonEvent("GeneratorD", 0); break; case 0x162: SendFakeButtonEvent("Condenser1", 0); break; case 0x163: SendFakeButtonEvent("LRM15", 2); break; case 0x164: SendFakeButtonEvent("AmmoBinLRM15", 1); break; case 0x165: SendFakeButtonEvent("LRM15", 0); break; case 0x166: SendFakeButtonEvent("Avionics", 0); break; case 0x167: SendFakeButtonEvent("Hud", 0); break; case 0x168: SendFakeButtonEvent("GeneratorA", 1); break; case 0x169: SendFakeButtonEvent("GeneratorB", 1); break; case 0x16a: SendFakeButtonEvent("GeneratorC", 1); break; case 0x16b: SendFakeButtonEvent("GeneratorD", 1); break; case 0x16c: SendFakeButtonEvent("Condenser1", 1); break; case 0x185: // dump the gauge renderer's profile, if one exists if (application->GetGaugeRenderer() != NULL) // DAT_004efc94+0x4c { application->GetGaugeRenderer()->ProfileReport(); // FUN_00448654 } break; } } //############################################################################# //############################################################################# // L4MechControlsMapper //############################################################################# //############################################################################# //############################################################################# // Shared Data Support // L4MechControlsMapper::SharedData L4MechControlsMapper::DefaultData( L4MechControlsMapper::GetClassDerivations(), L4MechControlsMapper::GetMessageHandlers(), L4MechControlsMapper::GetAttributeIndex(), // inherited from MechControlsMapper L4MechControlsMapper::StateCount ); Derivation* L4MechControlsMapper::GetClassDerivations() { static Derivation classDerivations( MechControlsMapper::GetClassDerivations(), "L4MechControlsMapper" // @0051e1eb ); return &classDerivations; } //############################################################################# // Messaging Support // const L4MechControlsMapper::HandlerEntry L4MechControlsMapper::MessageHandlerEntries[]= { MESSAGE_ENTRY(L4MechControlsMapper, Keypress) // id 0x17/0x19, handler @004d1bf0 }; L4MechControlsMapper::MessageHandlerSet& L4MechControlsMapper::GetMessageHandlers() { static MessageHandlerSet messageHandlers( ELEMENTS(L4MechControlsMapper::MessageHandlerEntries), L4MechControlsMapper::MessageHandlerEntries, MechControlsMapper::GetMessageHandlers() ); return messageHandlers; } // //############################################################################# // L4MechControlsMapper @004d17ac //############################################################################# // // Chains to MechControlsMapper (FUN_004b02f0); stamps vtable &0051e440 and // initialises the target-range zoom (exponent 2.0 == 250*2^2 == 1000m) and the // review-mode watcher. // L4MechControlsMapper::L4MechControlsMapper( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, ClassID class_ID, SharedData &shared_data ): // MechControlsMapper (mechmppr.hpp) takes 4 args -- the ClassID is // stamped by the streamed-resource factory, not the mapper ctor, so it // is consumed here and not forwarded. MechControlsMapper( owner, subsystem_ID, subsystem_resource, shared_data ) { (void)class_ID; previousMissionReviewMode = 0; // this[0x6a] @0x1a8 targetRangeExponentDemand = 2.0f; // this[0x68] @0x1a0 targetRangeExponent = 2.0f; // this[0x69] @0x1a4 Check_Fpu(); } // //############################################################################# // ~L4MechControlsMapper @004d1814 //############################################################################# // L4MechControlsMapper::~L4MechControlsMapper() { // scalar-deleting dtor; reinstalls vtable and chains to ~MechControlsMapper Check_Fpu(); } // //############################################################################# // InterpretControls @004d196c //############################################################################# // // The L4-layer performance, run every frame ahead of the in-Mech mapper: // 1. snap the throttle to a full-throttle detent, // 2. (re)build the pilot roster, // 3. watch the Mech's mission-review flag and toggle the corresponding mode // bit (0x200000) on the application mode manager, // 4. slew the smoothed target-range exponent toward the panel demand and // push the resulting range (250 * 2^exponent) onto the Mech, // 5. delegate to MechControlsMapper::InterpretControls for the actual // locomotion / torso / eyepoint work. // void L4MechControlsMapper::InterpretControls(Scalar time_slice) { Check(this); // // (1) Full-throttle detent. // if (Abs(throttlePosition - 1.0f) <= 0.05f) // _DAT_004d1ac0 / _DAT_004d1ac4 { throttlePosition = 1.0f; // @0x11c } // // (2) Roster. // BuildPilotArray(); // FUN_004b0600 // // (3) Mission-review mode change watcher. // Mech *mech = GetMech(); int review_mode = mech->GetMissionReviewMode(); // (mech)+0x414 if (review_mode != previousMissionReviewMode) // @0x1a8 { previousMissionReviewMode = review_mode; BTL4ModeManager *mode_manager = (BTL4ModeManager*)application->GetModeManager(); // DAT_004efc94+0x50 // ModeManager API: Add/RemoveModeMask save the old mask into // previousModeMask (the recovered "savedMode = currentMode" step). if (review_mode == 0) { mode_manager->RemoveModeMask(0x200000); } else { mode_manager->AddModeMask(0x200000); } } // // (4) Target-range zoom: slew current exponent toward demand, then set // the Mech's target range = 250 * 2^exponent. // if (targetRangeExponentDemand != targetRangeExponent) // @0x1a0 != @0x1a4 { if (targetRangeExponent <= targetRangeExponentDemand) { targetRangeExponent -= time_slice; if (targetRangeExponent < targetRangeExponentDemand) { targetRangeExponent = targetRangeExponentDemand; } } else { targetRangeExponent += time_slice; if (targetRangeExponentDemand < targetRangeExponent) { targetRangeExponent = targetRangeExponentDemand; } } mech->SetTargetRange( // (mech)+0x404 powf(2.0f, targetRangeExponent) * 250.0f); // FUN_004dce94, _DAT_004d1ac8 } // // (5) Hand off to the in-Mech master performance. // MechControlsMapper::InterpretControls(time_slice); // FUN_004afd10 Check_Fpu(); } // //############################################################################# // ZoomTargetRangeIn / ZoomTargetRangeOut @004d1b64 / @004d1b9c //############################################################################# // void L4MechControlsMapper::ZoomTargetRangeIn() { targetRangeExponentDemand -= 1.0f; // _DAT_004d1b94 if (targetRangeExponentDemand < 0.0f) // _DAT_004d1b98 { targetRangeExponentDemand = 0.0f; } } void L4MechControlsMapper::ZoomTargetRangeOut() { targetRangeExponentDemand += 1.0f; // _DAT_004d1bcc if (targetRangeExponentDemand > 5.0f) // _DAT_004d1bd0 { targetRangeExponentDemand = 5.0f; } } // //############################################################################# // SetControlMode @004d1ae4 //############################################################################# // // Swap the application mode-manager's control-mode bits (18..20) for the bit // selected by new_mode, then notify the base of the implied display change. // void L4MechControlsMapper::SetControlMode(int new_mode) { static const ModeMask control_mode_mask[] = // @0051dbe4 { 0x40000, 0x80000, 0x100000 }; BTL4ModeManager *mode_manager = (BTL4ModeManager*)application->GetModeManager(); // DAT_004efc94+0x50 mode_manager->RemoveModeMask(0x001c0000); // clear bits 18..20 (~0xffe3ffff) mode_manager->AddModeMask(control_mode_mask[new_mode]); NotifyOfDisplayModeChange(displayMode); // FUN_004b0494 } // //############################################################################# // SetPresetMode @004d1b24 //############################################################################# // // Each (group,item) selects a preset; the table @0051dbf0 holds {clear,set} // mode-mask pairs that remove the previous preset of that group and enable the // chosen one. // void L4MechControlsMapper::SetPresetMode(int group, int item) { struct PresetMaskPair { ModeMask clear, set; }; static const PresetMaskPair preset_mask[15] = // @0051dbf0 (group*5+item) { {0x1e, 0x01000000}, {0x1f, 0x02000000}, {0x1f, 0x04000000}, {0x1f, 0x08000000}, {0x1f, 0x10000000}, {0x3c0, 0x20000000}, {0x3e0, 0x40000000}, {0x3e0, 0x80000000}, {0x3e0, 0x00010000}, {0x3e0, 0x00020000}, {0x7800,0x00040000}, {0x7c00,0x00080000}, {0x7c00,0x00100000}, {0x7c00,0x00200000}, {0x7c00,0x00400000} }; int index = group * 5 + item; BTL4ModeManager *mode_manager = (BTL4ModeManager*)application->GetModeManager(); mode_manager->RemoveModeMask(preset_mask[index].clear); mode_manager->AddModeMask(preset_mask[index].set); } // //############################################################################# // EnterConfiguration @004d1840 //############################################################################# // // THE CONFIG-MODE SESSION OPEN (task #6; the old reconstruction named this // "CreateTemporaryEventMappings" and carried only the fire-button loop -- // missing the mode flip, so config mode never actually ENGAGED). Called by // MechWeapon handler id 9 (ConfigureMappables, @004b9550) when the pilot // presses-and-HOLDS a weapon's configure button: // 1. StartMappableButtonsConfigure (engine T0, ControlsManager vtbl+0x20): // latches the active weapon (destination/receiver) for the ConfigMap // gauge and flips the GLOBAL mode NonMapping(0x10000) -> Mapping(0x8000) // -- every permanent fire mapping goes dormant for the session. // 2. Re-arm the HELD configure button under 0x8000 so its RELEASE still // reaches the weapon (-> ExitConfiguration). // 3. Temp-map the four mappable fire buttons (0x40 Trigger, 0x45 Pinky, // 0x46 ThumbLow, 0x47 ThumbHigh -- the 0x41..0x44 hat is skipped) to // send ChooseButton (id 10) to the configuring weapon. // void L4MechControlsMapper::EnterConfiguration( int held_element, ControlsButton *destination, Receiver *receiver, Receiver::MessageID choose_message_id, Receiver::MessageID configure_message_id, Receiver::MessageID active_message_id ) { LBE4ControlsManager *controls = (LBE4ControlsManager*)application->GetControlsManager(); // DAT_004efc94+0x3c controls->StartMappableButtonsConfigure( // vtbl+0x20 (CONTROLS.cpp:291) destination, receiver, receiver, active_message_id, /*remove*/ 0x10000 /*ModeNonMapping*/, /*add*/ 0x8000 /*ModeMapping*/); controls->buttonGroup[held_element].Add( // keep the held button live 0x8000, receiver, configure_message_id, receiver); for (int i = LBE4ControlsManager::FirstMappableButton; i <= LBE4ControlsManager::LastMappableButton; ++i) { if (i == LBE4ControlsManager::ButtonJoystickHatDown) // 0x41 -> skip hat { i = LBE4ControlsManager::ButtonJoystickPinky; // 0x45 } controls->buttonGroup[i].Add( 0x8000, receiver, choose_message_id, receiver); } Check_Fpu(); } // //############################################################################# // ExitConfiguration @004d18dc //############################################################################# // // THE SESSION CLOSE (the configure button's RELEASE): restore the mode masks // (Mapping -> NonMapping; the engine also clears the gauge's active* latch), // then strip the 0x8000 temp layer off the held button + the four mappables. // The regrouping itself needs no commit -- the RIO AddOrErase toggled the // permanent 0x10000 mappings directly. // void L4MechControlsMapper::ExitConfiguration(int held_element) { LBE4ControlsManager *controls = (LBE4ControlsManager*)application->GetControlsManager(); controls->StopMappableButtonsConfigure( // vtbl+0x24 (CONTROLS.cpp:319) /*remove*/ 0x8000, /*add*/ 0x10000); controls->buttonGroup[held_element].Remove(0x8000); for (int i = LBE4ControlsManager::FirstMappableButton; i <= LBE4ControlsManager::LastMappableButton; ++i) { if (i == LBE4ControlsManager::ButtonJoystickHatDown) { i = LBE4ControlsManager::ButtonJoystickPinky; } controls->buttonGroup[i].Remove(0x8000); } Check_Fpu(); } // //############################################################################# // AddOrErase (event / direct) -- the L4/Thrustmaster no-ops // @004d195c / @004d1964 //############################################################################# // // task #6 CORRECTION: these two RET stubs occupy the +0x40/+0x44 AddOrErase // vtable slots (section_dump @0051e440/@0051e3f0) -- the old reconstruction // mislabeled them NotifyOfControlModeChange/NotifyOfConfigurationModeChange. // A joystick (Thrustmaster) cockpit inherits them: pressing a fire button // during a config session does NOTHING there. Only the pod RIO variant // (@004d25e8/@004d262c) implements the group toggle. // void L4MechControlsMapper::AddOrErase( unsigned int /*button_ID*/, Receiver * /*receiver*/, Receiver::MessageID /*message_ID*/ ) { } void L4MechControlsMapper::AddOrErase( unsigned int /*button_ID*/, ControlsButton * /*destination*/ ) { } // //############################################################################# // KeypressMessageHandler @004d1bf0 //############################################################################# // // The shared L4 keyboard dispatcher used by BOTH cockpits. It handles the // target-range zoom, the preset / control-group selection (number and letter // keys, via SetPresetMode), pilot-target cycling (UpdateCurrentPilot / // ChooseNearestPilot), control / display-mode cycling, eyepoint look keys, the // joystick service keys, and a couple of fake-button developer shortcuts; // anything else falls through to DispatchDeveloperFakeEvent. // void L4MechControlsMapper::KeypressMessageHandler( ReceiverDataMessageOf *message ) { switch (message->dataContents) { // ---- target range zoom ---- case '+': ZoomTargetRangeIn(); return; // 0x2b case '-': ZoomTargetRangeOut(); return; // 0x2d // ---- group 0 presets (number row) ---- case '1': SetPresetMode(0, 1); return; case '2': SetPresetMode(0, 2); return; case '3': SetPresetMode(0, 3); return; case '4': SetPresetMode(0, 4); return; case '5': SetPresetMode(0, 0); return; // ---- group 1 / group 2 presets (letter keys) ---- case 'a': SetPresetMode(1, 1); return; case 'b': SetPresetMode(2, 0); return; case 'c': SetPresetMode(2, 3); return; case 'd': SetPresetMode(1, 3); return; case 'f': SetPresetMode(1, 4); return; case 'g': SetPresetMode(1, 0); return; case 's': SetPresetMode(1, 2); return; case 'v': SetPresetMode(2, 4); return; case 'x': SetPresetMode(2, 2); return; case 'z': SetPresetMode(2, 1); return; // ---- pilot target selection ---- case 'w': UpdateCurrentPilot(0); return; // FUN_004b049c case 'e': UpdateCurrentPilot(1); return; case 'r': UpdateCurrentPilot(2); return; case 't': UpdateCurrentPilot(3); return; case 'y': UpdateCurrentPilot(4); return; case 'u': UpdateCurrentPilot(5); return; case 'i': UpdateCurrentPilot(6); return; case 'o': UpdateCurrentPilot(7); return; // NOTE (engine drift): the recovered 'p' -> ChooseNearestPilot(0) shortcut // is dropped here because in the WinTesla keymap VK_F1 (PCK_F1) == 0x70 == // 'p', which would collide with the joystick-align case below. The F1/F2 // joystick-service pair is kept; ChooseNearestPilot remains reachable via // the RIO Hotbox (button 8, see HotboxMessageHandler). // ---- fake-button developer shortcuts ---- case 'j': SendFakeButtonEventPair(GetMech(), "LRM10", 0xb); return; // @0051e2e3 case 'l': SendFakeButtonEventPair(GetMech(), "Searchlight", 3); return; // @0051e2d7 // ---- joystick service / mode cycling ---- case PCK_F1: ((LBE4ControlsManager*)application->GetControlsManager()) ->BeginAlignJoystick(); // FUN_00476e34 return; case PCK_F2: ((LBE4ControlsManager*)application->GetControlsManager()) ->EndAlignJoystick(); // FUN_00476e54 return; case 0x13d: // cycle control mode { ReceiverDataMessageOf m(0, sizeof(ReceiverDataMessageOf), 1); CycleControlModeMessageHandler(&m); // FUN_004afbe0 } return; case 0x13e: // cycle display mode { ReceiverDataMessageOf m(0, sizeof(ReceiverDataMessageOf), 1); CycleDisplayModeMessageHandler(&m); // FUN_004afcac } return; // ---- eyepoint look keys ---- case PCK_UP: lookForward = 1; lookLeft = -1; lookRight = -1; lookBehind = -1; lookDown = -1; return; // 0x148 case PCK_LEFT: lookForward =-1; lookLeft = 1; lookRight = -1; lookBehind = -1; lookDown = -1; return; // 0x14b case PCK_RIGHT: lookForward =-1; lookLeft = -1; lookRight = 1; lookBehind = -1; lookDown = -1; return; // 0x14d case PCK_DOWN: lookForward =-1; lookLeft = -1; lookRight = -1; lookBehind = 1; lookDown = -1; return; // 0x150 case PCK_CTL_UP: lookForward =-1; lookLeft = -1; lookRight = -1; lookBehind = -1; lookDown = 1; return; // 0x18d } // // Everything else -> developer fake-event dispatcher. // DispatchDeveloperFakeEvent(this, (ControlsKeyMessage*)message); // FUN_004d141c } //############################################################################# //############################################################################# // MechThrustmasterMapper //############################################################################# //############################################################################# //############################################################################# // Shared Data Support // MechThrustmasterMapper::SharedData MechThrustmasterMapper::DefaultData( MechThrustmasterMapper::GetClassDerivations(), MechThrustmasterMapper::MessageHandlers, MechThrustmasterMapper::GetAttributeIndex(), MechThrustmasterMapper::StateCount ); Derivation* MechThrustmasterMapper::GetClassDerivations() // @0051dc78 { static Derivation classDerivations( L4MechControlsMapper::GetClassDerivations(), "MechThrustmasterMapper" // @0051e2b3 ); return &classDerivations; } //############################################################################# // Messaging Support // const MechThrustmasterMapper::HandlerEntry MechThrustmasterMapper::MessageHandlerEntries[]= { MESSAGE_ENTRY(MechThrustmasterMapper, Keypress) // id 0x17 -> L4 @004d1bf0 }; MechThrustmasterMapper::MessageHandlerSet MechThrustmasterMapper::MessageHandlers( ELEMENTS(MechThrustmasterMapper::MessageHandlerEntries), MechThrustmasterMapper::MessageHandlerEntries, L4MechControlsMapper::GetMessageHandlers() ); // //############################################################################# // MechThrustmasterMapper @004d21d0 //############################################################################# // MechThrustmasterMapper::MechThrustmasterMapper( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, ClassID class_ID, SharedData &shared_data ): L4MechControlsMapper( owner, subsystem_ID, subsystem_resource, class_ID, shared_data ) { SetPerformance(&MechThrustmasterMapper::InterpretControls); // this[7..9] = @004d2150 throttleForward = 0; // this[0x6b] @0x1ac throttleReverse = 0; // this[0x6c] @0x1b0 leftPedal = 0; // this[0x6d] @0x1b4 rightPedal = 0; // this[0x6e] @0x1b8 LBE4ControlsManager *controls = (LBE4ControlsManager*)application->GetControlsManager(); // DAT_004efc94+0x3c // // Analog throttle axis, reverse-throttle button, and the PC keyboard. // (ModeManager::ModeAlwaysActive == -1.) Engine drift: the recovered // "ScalarThrottleReverse" scalar group does not exist in WinTesla; the // reverse throttle is the ButtonThrottle1 button (as in the era's // L4MPPR.CPP), so throttleReverse is staged from that button. // controls->scalarGroup[LBE4ControlsManager::ScalarThrottle] .Add(ModeManager::ModeAlwaysActive, &throttleForward, this); controls->buttonGroup[LBE4ControlsManager::ButtonThrottle1] .Add(ModeManager::ModeAlwaysActive, &throttleReverse, this); controls->keyboardGroup[LBE4ControlsManager::KeyboardPC] .Add(ModeManager::ModeAlwaysActive, this, KeypressMessageID, this); // msg 0x17 Check_Fpu(); } // //############################################################################# // ~MechThrustmasterMapper @004d22b4 //############################################################################# // MechThrustmasterMapper::~MechThrustmasterMapper() { } // //############################################################################# // TestInstance @004d22e0 //############################################################################# // Logical MechThrustmasterMapper::TestInstance() const { return IsDerivedFrom(*GetClassDerivations()); // FUN_0041a1a4(.,0x51dc78) } // //############################################################################# // InterpretControls @004d2150 //############################################################################# // // Reduce the throttle/reverse axes to the Mech's throttlePosition (@0x11c) and // reverseThrust (@0x124), sum the pedals into pedalsPosition (@0x120), then run // the L4 performance. // void MechThrustmasterMapper::InterpretControls(Scalar time_slice) { if (throttleForward < 1) { if (throttleReverse < 1) { reverseThrust = 0; throttlePosition = 0.0f; } else { throttlePosition = 1.0f; reverseThrust = 1; } } else { throttlePosition = 1.0f; reverseThrust = 0; } pedalsPosition = (Scalar)(leftPedal - rightPedal); // @0x120 L4MechControlsMapper::InterpretControls(time_slice); // FUN_004d196c Check_Fpu(); } //############################################################################# //############################################################################# // MechRIOMapper //############################################################################# //############################################################################# //############################################################################# // Shared Data Support // MechRIOMapper::SharedData MechRIOMapper::DefaultData( MechRIOMapper::GetClassDerivations(), MechRIOMapper::MessageHandlers, MechRIOMapper::GetAttributeIndex(), MechRIOMapper::StateCount ); Derivation* MechRIOMapper::GetClassDerivations() // @0051dd00 { static Derivation classDerivations( L4MechControlsMapper::GetClassDerivations(), "MechRIOMapper" // @0051e2e3 ); return &classDerivations; } //############################################################################# // Messaging Support // // The aux quadrant buttons each select a control-group preset via SetPresetMode // (group = quadrant 0..2, item = engine 0..4). ZoomIn / ZoomOut drive the // target-range exponent; Hotbox selects a pilot/target. (Table @0051dd30.) // const MechRIOMapper::HandlerEntry MechRIOMapper::MessageHandlerEntries[]= { MESSAGE_ENTRY(MechRIOMapper, Aux1Quad), MESSAGE_ENTRY(MechRIOMapper, Aux1Eng1), MESSAGE_ENTRY(MechRIOMapper, Aux1Eng2), MESSAGE_ENTRY(MechRIOMapper, Aux1Eng3), MESSAGE_ENTRY(MechRIOMapper, Aux1Eng4), MESSAGE_ENTRY(MechRIOMapper, Aux2Quad), MESSAGE_ENTRY(MechRIOMapper, Aux2Eng1), MESSAGE_ENTRY(MechRIOMapper, Aux2Eng2), MESSAGE_ENTRY(MechRIOMapper, Aux2Eng3), MESSAGE_ENTRY(MechRIOMapper, Aux2Eng4), MESSAGE_ENTRY(MechRIOMapper, Aux3Quad), MESSAGE_ENTRY(MechRIOMapper, Aux3Eng1), MESSAGE_ENTRY(MechRIOMapper, Aux3Eng2), MESSAGE_ENTRY(MechRIOMapper, Aux3Eng3), MESSAGE_ENTRY(MechRIOMapper, Aux3Eng4), MESSAGE_ENTRY(MechRIOMapper, ZoomIn), MESSAGE_ENTRY(MechRIOMapper, ZoomOut), MESSAGE_ENTRY(MechRIOMapper, Hotbox) }; MechRIOMapper::MessageHandlerSet MechRIOMapper::MessageHandlers( ELEMENTS(MechRIOMapper::MessageHandlerEntries), MechRIOMapper::MessageHandlerEntries, L4MechControlsMapper::GetMessageHandlers() ); // //############################################################################# // Aux quadrant handlers @004d22fc .. @004d24bc //############################################################################# // // Fifteen near-identical "press a quadrant button -> select a preset" handlers. // Each fires on press only (dataContents > 0) and calls SetPresetMode(group,item). // #define RIO_AUX_HANDLER(method, grp, itm) \ void \ MechRIOMapper::method##MessageHandler( \ ReceiverDataMessageOf *message) \ { \ if (message->dataContents > 0) SetPresetMode((grp), (itm)); \ } RIO_AUX_HANDLER(Aux1Quad, 0, 0) // @004d22fc RIO_AUX_HANDLER(Aux1Eng1, 0, 1) // @004d231c RIO_AUX_HANDLER(Aux1Eng2, 0, 2) // @004d233c RIO_AUX_HANDLER(Aux1Eng3, 0, 3) // @004d235c RIO_AUX_HANDLER(Aux1Eng4, 0, 4) // @004d237c RIO_AUX_HANDLER(Aux2Quad, 1, 0) // @004d239c RIO_AUX_HANDLER(Aux2Eng1, 1, 1) // @004d23bc RIO_AUX_HANDLER(Aux2Eng2, 1, 2) // @004d23dc RIO_AUX_HANDLER(Aux2Eng3, 1, 3) // @004d23fc RIO_AUX_HANDLER(Aux2Eng4, 1, 4) // @004d241c RIO_AUX_HANDLER(Aux3Quad, 2, 0) // @004d243c RIO_AUX_HANDLER(Aux3Eng1, 2, 1) // @004d245c RIO_AUX_HANDLER(Aux3Eng2, 2, 2) // @004d247c RIO_AUX_HANDLER(Aux3Eng3, 2, 3) // @004d249c RIO_AUX_HANDLER(Aux3Eng4, 2, 4) // @004d24bc #undef RIO_AUX_HANDLER // //############################################################################# // ZoomIn / ZoomOut handlers @004d24dc / @004d24f8 //############################################################################# // void MechRIOMapper::ZoomInMessageHandler( ReceiverDataMessageOf *message) { if (message->dataContents > 0) ZoomTargetRangeIn(); // FUN_004d1b64 } void MechRIOMapper::ZoomOutMessageHandler( ReceiverDataMessageOf *message) { if (message->dataContents > 0) ZoomTargetRangeOut(); // FUN_004d1b9c } // //############################################################################# // HotboxMessageHandler @004d2574 //############################################################################# // // The eight "hotbox" panel buttons select a pilot/target. The pressed button // is matched against the hotbox button table @0051dcd0; its ordinal selects a // roster page (UpdateCurrentPilot), or -- for the last button -- the nearest // living pilot (ChooseNearestPilot). // void MechRIOMapper::HotboxMessageHandler( ReceiverDataMessageOf *message) { static const int hotbox_button[8] = // @0051dcd0 { 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30 }; if (message->dataContents > 0) { for (int i = 0; i < 8; ++i) { if (message->dataContents - 1 == hotbox_button[i]) { if (i < 7) { UpdateCurrentPilot(i + 1); // FUN_004b049c } else { ChooseNearestPilot(0); // FUN_004b04d8 } return; } } } } // //############################################################################# // AddOrErase (event / direct) @004d25e8 / @004d262c //############################################################################# // // THE REGROUP EDIT (task #6): during a config session these TOGGLE the // just-pressed fire button's (1-based ID) permanent mapping under the // ModeNonMapping (0x10000) mask -- the engine ControlsUpdateManager AddOrErase // (CONTROLS.h:431-597) deletes a matching instance or creates one, so the // weapon JOINS the button's fire group if absent and LEAVES it if present. // The toggled 0x10000 instances ARE the commit; there is no save step. // Mirrors RP VTVRIOMapper::AddOrErase. // void MechRIOMapper::AddOrErase( unsigned int button_ID, Receiver *target, Receiver::MessageID message_ID ) { LBE4ControlsManager *controls = (LBE4ControlsManager*)application->GetControlsManager(); controls->buttonGroup[button_ID - 1] .AddOrErase(0x10000, target, message_ID, this); // vtbl+0x1c Check_Fpu(); } void MechRIOMapper::AddOrErase( unsigned int button_ID, ControlsButton *destination ) { LBE4ControlsManager *controls = (LBE4ControlsManager*)application->GetControlsManager(); controls->buttonGroup[button_ID - 1] .AddOrErase(0x10000, destination, this); // vtbl+0x20 if (getenv("BT_FIRE_LOG")) { int state = controls->buttonGroup[button_ID - 1].GetMapState( destination, 0, 0, 0x10000); DEBUG_STREAM << "[rio-map] toggle btn=0x" << std::hex << (button_ID - 1) << std::dec << " dest=" << (void *)destination << " -> " << (((state & 2) != 0) ? "JOINED" : "LEFT") << " (state=" << state << ")" << std::endl; } Check_Fpu(); } // //############################################################################# // MechRIOMapper @004d266c //############################################################################# // MechRIOMapper::MechRIOMapper( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, ClassID class_ID, SharedData &shared_data ): L4MechControlsMapper( owner, subsystem_ID, subsystem_resource, class_ID, shared_data ) { SetPerformance(&MechRIOMapper::InterpretControls); // this[7..9] = @004d25c0 throttleForward = 0; // this[0x6b] @0x1ac throttleReverse = 0; // this[0x6c] @0x1b0 leftPedal = 0; // this[0x6d] @0x1b4 rightPedal = 0; // this[0x6e] @0x1b8 LBE4ControlsManager *controls = (LBE4ControlsManager*)application->GetControlsManager(); // DAT_004efc94+0x3c // // Keyboard, pedal axes, and the panel-wide preset-mode keyboard hook. // Engine drift: the recovered buttonGroup[ButtonLeftPedal/RightPedal] // slots do not exist; the pedals are scalar axes (ScalarLeftPedal / // ScalarRightPedal), as in the era's L4MPPR.CPP. KeyboardPanel maps to // the pilot's internal keypad (KeyboardPilot); 0x200000 is the panel // preset mode mask (the recovered "ModePreset1"). // controls->keyboardGroup[LBE4ControlsManager::KeyboardPC] .Add(ModeManager::ModeAlwaysActive, this, KeypressMessageID, this); // msg 0x19 controls->scalarGroup[LBE4ControlsManager::ScalarLeftPedal] .Add(ModeManager::ModeAlwaysActive, &leftPedal, this); controls->scalarGroup[LBE4ControlsManager::ScalarRightPedal] .Add(ModeManager::ModeAlwaysActive, &rightPedal, this); controls->keyboardGroup[LBE4ControlsManager::KeyboardPilot] .Add(0x200000, owner, KeypressMessageID, this); // mode 0x200000 // configuration-control lamp (button 0x3d == ButtonPanic in this engine) Lamp *config_lamp = controls->MakeLinkedLamp(LBE4ControlsManager::ButtonPanic, 0x200000); // FUN_00476fc0(.,0x3d,0x200000) if (config_lamp != NULL) { config_lamp->SetState(Lamp::LampStateOn); // FUN_00444e64 } // // The eight hotbox / preset buttons -> Hotbox handler, each with a lamp. // static const int hotbox_button[8] = // @0051dcd0 { 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30 }; for (int i = 0; i < 8; ++i) { controls->buttonGroup[hotbox_button[i]] .Add(ModeManager::ModeAlwaysActive, this, HotboxMessageID, this); // msg 0x1a controls->MakeLinkedLamp(hotbox_button[i], ModeManager::ModeAlwaysActive); // FUN_00476fc0(.,btn,-1) } Check_Fpu(); } // //############################################################################# // ~MechRIOMapper @004d27cc //############################################################################# // MechRIOMapper::~MechRIOMapper() { } // //############################################################################# // TestInstance @004d27f8 //############################################################################# // Logical MechRIOMapper::TestInstance() const { return IsDerivedFrom(*GetClassDerivations()); // FUN_0041a1a4(.,0x51dd00) } // //############################################################################# // InterpretControls @004d25c0 //############################################################################# // // The RIO simply sums the pedal buttons into pedalsPosition (@0x120); throttle // is handled elsewhere. Then it runs the L4 performance. // void MechRIOMapper::InterpretControls(Scalar time_slice) { pedalsPosition = (Scalar)(leftPedal - rightPedal); // @0x120 L4MechControlsMapper::InterpretControls(time_slice); // FUN_004d196c Check_Fpu(); }