//===========================================================================// // 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 // @004d1acc NotifyOfControlModeChange (+0x48; forwards to base no-op @004b048c) // @004d1ae4 NotifyOfDisplayModeChange (+0x4C; the secondary-view mask swap // -- Gitea #6 relabel; ex-"SetControlMode") // @004d1b24 SetPresetMode (group/item -> preset mode-mask) // @004d1840 EnterConfiguration @004d18dc ExitConfiguration // @004d195c/@004d1964 AddOrErase evt/dir (+0x40/+0x44, RET no-ops) // @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} secondary-VIEW mode-masks // (ModeSecondaryDamage/Critical/Heat, indexed by displayMode) // DAT_0051dbf0[15] = {clear,set} mode-mask pairs (group*5+item), preset select // (little-endian: set = ModeMFD{1,2,3}{Quad,Eng1-4} bits 0-14 // -- Gitea #9; see SetPresetMode below) // 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 #include // placement-new (the '\' fake-event padded message) #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 { // STACK-OVERWRITE FIX (tester "crash" hunt, 2026-07-17): // Entity::Dispatch STAMPS entityID/interestZoneID into the message // at Entity::Message offsets (ENTITY.cpp:236) -- dispatching a bare // ReceiverDataMessageOf at an ENTITY writes past // the stack object and corrupts the frame (the '\' key was an // instant wild-jump crash; the 1995 build survived the identical // overwrite on stack-layout luck). Give the message // Entity::Message-sized backing so the stamp lands in owned // memory; messageID/messageLength semantics are unchanged. double storage[ (sizeof(Entity::Message) + sizeof(ReceiverDataMessageOf)) / sizeof(double) + 1]; ReceiverDataMessageOf *press = new ((void *)storage) ReceiverDataMessageOf( 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 // // TargetRangeExponent -> the live @0x1a4 zoom member (AUDIO_FIDELITY.md F20): // the authored AudioScalarDeltaTrigger blips per zoom step; it bound NULL until // this publication. Chained to MechControlsMapper's dense index. const L4MechControlsMapper::IndexEntry L4MechControlsMapper::AttributePointers[]= { ATTRIBUTE_ENTRY(L4MechControlsMapper, TargetRangeExponent, targetRangeExponent) // @0x1a4 }; L4MechControlsMapper::AttributeIndexSet& L4MechControlsMapper::GetAttributeIndex() { static L4MechControlsMapper::AttributeIndexSet attributeIndex( ELEMENTS(L4MechControlsMapper::AttributePointers), L4MechControlsMapper::AttributePointers, MechControlsMapper::GetAttributeIndex() ); return attributeIndex; } L4MechControlsMapper::SharedData L4MechControlsMapper::DefaultData( L4MechControlsMapper::GetClassDerivations(), L4MechControlsMapper::GetMessageHandlers(), L4MechControlsMapper::GetAttributeIndex(), // own table -> chained to 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. // // NB: the Abs() macro (STYLE.H:118) is unparenthesized -- Abs(throttlePosition // - 1.0f) mis-expands to -(throttlePosition + 1.0f) on the false branch (always, // since throttle <= 1), which is <= 0.05 UNCONDITIONALLY -> the detent used to // snap throttle to full every frame. Diff into a temp so the macro sees one token. const Scalar _thrOff = throttlePosition - 1.0f; if (((_thrOff < 0.0f) ? -_thrOff : _thrOff) <= 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); } } // // (3b) PORT (Gitea #9): the desktop J/K/L per-MFD preset-page cycle // edges (mech4 poll) -- the sender for the pod's per-MFD page-button // banks; the body is the authentic SetPresetMode. // { extern int gBTPresetCycle[3]; for (int mfd_group = 0; mfd_group < 3; ++mfd_group) { if (gBTPresetCycle[mfd_group]) { gBTPresetCycle[mfd_group] = 0; CyclePresetModeNow(mfd_group); } } } // // (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; } } // //############################################################################# // NotifyOfControlModeChange @004d1acc (vtbl+0x48) // NotifyOfDisplayModeChange @004d1ae4 (vtbl+0x4C) //############################################################################# // // Gitea #6 RELABEL (2026-07-19). The old reconstruction carried @004d1ae4 as // a non-virtual "SetControlMode" that nothing ever called, so the desktop's // secondary MFD stayed pinned on the Damage view. The vtable @0051e440 // (section_dump.txt:73033) pins the truth: // // +0x48 = @004d1acc -- dispatched by CycleControlModeMessageHandler // (FUN_004afbe0) with the new controlMode. Capstone disasm: pushes // its two args and forwards to the BASE no-op @004b048c. A // control-mode (BAS/MID/ADV) change never touches the secondary // view -- in the shipped binary OR here. // +0x4C = @004d1ae4 -- dispatched by CycleDisplayModeMessageHandler // (FUN_004afcac) with the new displayMode. THE secondary-view // selector: clears mode bits 18..20 (& 0xffe3ffff = // ~ModeSecondaryAll), sets the mask picked from the table @0051dbe4 // = {ModeSecondaryDamage 0x40000, ModeSecondaryCritical 0x80000, // ModeSecondaryHeat 0x100000}, then chains the base no-op // @004b0494. displayMode 0/1/2 = Damage/Critical/Heat -- the // manual-p13 "'Mech status Info center" schematic cycle (bottom // left of the secondary screen). // void L4MechControlsMapper::NotifyOfControlModeChange(int new_mode) { MechControlsMapper::NotifyOfControlModeChange(new_mode); // @004b048c (no-op) } void L4MechControlsMapper::NotifyOfDisplayModeChange(int new_mode) { static const ModeMask display_mode_mask[] = // @0051dbe4 { BTL4ModeManager::ModeSecondaryDamage, // 0 -> 0x040000 BTL4ModeManager::ModeSecondaryCritical, // 1 -> 0x080000 BTL4ModeManager::ModeSecondaryHeat // 2 -> 0x100000 }; BTL4ModeManager *mode_manager = (BTL4ModeManager*)application->GetModeManager(); // DAT_004efc94+0x50 mode_manager->RemoveModeMask( // clear bits 18..20 BTL4ModeManager::ModeSecondaryAll); // (& 0xffe3ffff) mode_manager->AddModeMask(display_mode_mask[new_mode]); // DIAG (BT_MODE_LOG): the resulting manager mask -- proves the // secondary-view bit actually flipped for the gauge layer gating. if (getenv("BT_MODE_LOG")) DEBUG_STREAM << "[mode] display notify " << new_mode << " -> manager mask 0x" << std::hex << mode_manager->GetModeMask() << std::dec << "\n" << std::flush; MechControlsMapper::NotifyOfDisplayModeChange(new_mode); // @004b0494 (no-op) } // //############################################################################# // SetPresetMode @004d1b24 //############################################################################# // // Each (group,item) selects an upper-MFD PRESET page; the table @0051dbf0 // holds {clear,set} mode-mask pairs that remove the previous preset of that // group and enable the chosen one. Group = the MFD (0/1/2 = Mfd1 lower-left / // Mfd2 upper-center / Mfd3 lower-right); item 0 = the Quad overview page // (four subsystem cluster panels), items 1-4 = the four full-screen // engineering-detail pages (aux screens group*4+1..group*4+4, prepEngr). // // Gitea #9 TABLE FIX (2026-07-19): the set column had been transcribed from // the section dump as BIG-endian dwords ({0x1e,0x01000000} instead of // {0x1e,0x01}, etc.) -- so a preset press set a garbage high bit (and, for // group 1 items 3-4 / group 2 items 0-2, stomped the LIVE ModeNonMapping / // ModeIntercom / ModeSecondary* bits) while the ModeMFD* page bits never // moved: the panels stayed pinned on their Quad pages. Ground truth // (section_dump.txt:72901-72908, little-endian) decodes the table to exactly // the BTL4MODE.HPP [T0] ModeMFD{1,2,3}{Quad,Eng1-4} allocation, bits 0-14: // set = 1 << (group*5 + item); clear = the group's other four page bits // (item 0) or all five (items 1-4). The presets are fully disjoint from the // Mapping/NonMapping (15/16), Intercom (17) and Secondary* (18-20) bits. // void L4MechControlsMapper::SetPresetMode(int group, int item) { struct PresetMaskPair { ModeMask clear, set; }; static const PresetMaskPair preset_mask[15] = // @0051dbf0 (group*5+item) { // group 0 = Mfd1 (lower left) {0x001e, BTL4ModeManager::ModeMFD1Quad}, // 0x0001 {0x001f, BTL4ModeManager::ModeMFD1Eng1}, // 0x0002 {0x001f, BTL4ModeManager::ModeMFD1Eng2}, // 0x0004 {0x001f, BTL4ModeManager::ModeMFD1Eng3}, // 0x0008 {0x001f, BTL4ModeManager::ModeMFD1Eng4}, // 0x0010 // group 1 = Mfd2 (upper center) {0x03c0, BTL4ModeManager::ModeMFD2Quad}, // 0x0020 {0x03e0, BTL4ModeManager::ModeMFD2Eng1}, // 0x0040 {0x03e0, BTL4ModeManager::ModeMFD2Eng2}, // 0x0080 {0x03e0, BTL4ModeManager::ModeMFD2Eng3}, // 0x0100 {0x03e0, BTL4ModeManager::ModeMFD2Eng4}, // 0x0200 // group 2 = Mfd3 (lower right) {0x7800, BTL4ModeManager::ModeMFD3Quad}, // 0x0400 {0x7c00, BTL4ModeManager::ModeMFD3Eng1}, // 0x0800 {0x7c00, BTL4ModeManager::ModeMFD3Eng2}, // 0x1000 {0x7c00, BTL4ModeManager::ModeMFD3Eng3}, // 0x2000 {0x7c00, BTL4ModeManager::ModeMFD3Eng4} // 0x4000 }; if (group < 0 || group > 2 || item < 0 || item > 4) { return; } 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); // DIAG (BT_MODE_LOG): prove the ModeMFD* page bit actually flipped for // the gauge reconfigure/prepEngr layer gating. if (getenv("BT_MODE_LOG")) DEBUG_STREAM << "[mode] preset (" << group << "," << item << ") -> manager mask 0x" << std::hex << mode_manager->GetModeMask() << std::dec << "\n" << std::flush; } // //############################################################################# // CyclePresetModeNow (PORT desktop sender -- Gitea #9, no binary analog) //############################################################################# // // On the pod every MFD has its own 8-button bank (Mfd1 lower-left = RIO // buttons 0x08-0x0F, Mfd2 upper-center = 0x20-0x27, Mfd3 lower-right = // 0x00-0x07) whose meanings are MODE-MASK-gated by the streamed "L4" .CTL // EventMappings: on a Quad page the bank's outer buttons DIRECT-SELECT the // populated Eng pages (mapper messages Aux1Eng1..Aux3Eng4 = 0x4-0x7 / 0x9-0xC // / 0xE-0x11 -> SetPresetMode), on an Eng page one button returns to Quad // (Aux1Quad/Aux2Quad/Aux3Quad = 0x3/0x8/0xD) and the rest drive the shown // subsystem. A desktop keyboard cannot host 24 mode-dependent buttons, so // the port cycles each MFD with one key (J/K/L -> mech4 edge poll -> // gBTPresetCycle -> here); the message BODY is the authentic SetPresetMode. // The cycle visits exactly the pages the pod's streamed buttons could reach: // Quad always, an Eng page only if a roster subsystem is assigned to that // aux screen (screens group*4+1 .. group*4+4; the shipped .CTL carries NO // select button for an unpopulated screen -- e.g. the Blackhawk's screens // 3, 11 and 12 are authored empty). // // The vehicleSubSystems/prepEngr aux-screen bridge (powersub.cpp). extern bool BTGetSubsystemAuxScreen( Subsystem *sub, int *screen, int *placement, char *label64); void L4MechControlsMapper::CyclePresetModeNow(int group) { if (group < 0 || group > 2) { return; } BTL4ModeManager *mode_manager = (BTL4ModeManager*)application->GetModeManager(); // // Current page = the set bit among the group's five ModeMFD* bits. // ModeMask mask = mode_manager->GetModeMask(); int current = 0; // default: Quad for (int i = 0; i < 5; ++i) { if (mask & (1 << (group * 5 + i))) { current = i; break; } } // // Which aux screens are populated on this mech (streamed sub+0x1dc)? // Mech *mech = GetMech(); bool populated[5] = { true, false, false, false, false }; // Quad always if (mech != NULL) { int count = mech->GetSubsystemCount(); for (int i = 1; i < count; ++i) { Subsystem *sub = mech->GetSubsystem(i); if (sub == NULL) { continue; } int screen = 0, placement = -1; if (!BTGetSubsystemAuxScreen(sub, &screen, &placement, NULL)) { continue; } int item = screen - group * 4; // Eng item 1..4 of THIS group? if (item >= 1 && item <= 4) { populated[item] = true; } } } // // Advance to the next reachable page (wraps through Quad). // int next = current; for (int step = 1; step <= 5; ++step) { int candidate = (current + step) % 5; if (populated[candidate]) { next = candidate; break; } } if (next != current) { SetPresetMode(group, next); } } // //############################################################################# // 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 ) { // DIAG (BT_KEY_LOG): trace every key that reaches the cockpit keyboard // dispatcher -- proves delivery + names the key in a crash repro. if (getenv("BT_KEY_LOG")) DEBUG_STREAM << "[keych] 0x" << std::hex << (int)message->dataContents << std::dec << "\n" << std::flush; 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(); }