Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.
Layout:
engine/ MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
models) + image codec; the minimal rp/ headers the audio HAL needs
game/ reconstructed BT logic + surviving-original BT source + fwd shims
+ WinMain launcher
content/ full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
docs/ format specs + reconstruction ledgers
reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
tools/ MP console emulator + map/resource scanners
One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1110 lines
40 KiB
C++
1110 lines
40 KiB
C++
//===========================================================================//
|
|
// 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 <bt.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(BTL4MPPR_HPP)
|
|
# include <btl4mppr.hpp>
|
|
#endif
|
|
#if !defined(BTL4APP_HPP)
|
|
# include <btl4app.hpp>
|
|
#endif
|
|
#if !defined(BTL4MODE_HPP)
|
|
# include <btl4mode.hpp>
|
|
#endif
|
|
#if !defined(L4CTRL_HPP)
|
|
# include <l4ctrl.hpp>
|
|
#endif
|
|
#if !defined(MECH_HPP)
|
|
# include <mech.hpp>
|
|
#endif
|
|
#if !defined(PLAYER_HPP)
|
|
# include <player.hpp>
|
|
#endif
|
|
|
|
//
|
|
// The keyboard message the L4 dispatcher receives (recovered name; it is just
|
|
// the engine's ReceiverDataMessageOf<ControlsKey>).
|
|
//
|
|
typedef ReceiverDataMessageOf<ControlsKey> 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<ControlsButton>
|
|
poke(0, sizeof(ReceiverDataMessageOf<ControlsButton>),
|
|
(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<ControlsButton>
|
|
press(message_id, sizeof(ReceiverDataMessageOf<ControlsButton>), 1);
|
|
subsystem->Dispatch(&press); // vtbl+0xc
|
|
|
|
ReceiverDataMessageOf<ControlsButton>
|
|
release(message_id, sizeof(ReceiverDataMessageOf<ControlsButton>), -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<ControlsButton>
|
|
press(0x19, sizeof(ReceiverDataMessageOf<ControlsButton>), 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);
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// CreateTemporaryEventMappings @004d1840
|
|
//#############################################################################
|
|
//
|
|
// During configuration, route every mappable button (the 0x40..0x47 block,
|
|
// skipping the joystick hat 0x41..0x44) at the configuration receiver so the
|
|
// pilot can press a button to bind it. Mode mask 0x8000 == ModeConfigReady.
|
|
//
|
|
void
|
|
L4MechControlsMapper::CreateTemporaryEventMappings(
|
|
Receiver *receiver,
|
|
Receiver::MessageID config_message_id
|
|
)
|
|
{
|
|
LBE4ControlsManager *controls =
|
|
(LBE4ControlsManager*)application->GetControlsManager(); // DAT_004efc94+0x3c
|
|
|
|
// Route every mappable button (0x40..0x47, skipping the joystick hat
|
|
// 0x41..0x44) at the configuration receiver; mode 0x8000 == ModeConfigReady.
|
|
// (Loop bounds mirror the RP twin L4VTVControlsMapper.)
|
|
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, config_message_id, receiver);
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// RemoveTemporaryEventMappings @004d18dc
|
|
//#############################################################################
|
|
//
|
|
void
|
|
L4MechControlsMapper::RemoveTemporaryEventMappings()
|
|
{
|
|
LBE4ControlsManager *controls =
|
|
(LBE4ControlsManager*)application->GetControlsManager();
|
|
|
|
for (int i = LBE4ControlsManager::FirstMappableButton;
|
|
i <= LBE4ControlsManager::LastMappableButton; ++i)
|
|
{
|
|
if (i == LBE4ControlsManager::ButtonJoystickHatDown)
|
|
{
|
|
i = LBE4ControlsManager::ButtonJoystickPinky;
|
|
}
|
|
controls->buttonGroup[i].Remove(0x8000);
|
|
}
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// NotifyOfControlModeChange / NotifyOfConfigurationModeChange
|
|
// @004d195c / @004d1964
|
|
//#############################################################################
|
|
//
|
|
// No-ops at the L4 layer (the gauges are driven directly off the mode masks),
|
|
// exactly like RP's L4VTVControlsMapper::NotifyOf* pair.
|
|
//
|
|
void
|
|
L4MechControlsMapper::NotifyOfControlModeChange(int /*new_mode*/)
|
|
{
|
|
}
|
|
|
|
void
|
|
L4MechControlsMapper::NotifyOfConfigurationModeChange(Logical /*new_state*/)
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// 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<ControlsKey> *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<ControlsButton>
|
|
m(0, sizeof(ReceiverDataMessageOf<ControlsButton>), 1);
|
|
CycleControlModeMessageHandler(&m); // FUN_004afbe0
|
|
}
|
|
return;
|
|
case 0x13e: // cycle display mode
|
|
{
|
|
ReceiverDataMessageOf<ControlsButton>
|
|
m(0, sizeof(ReceiverDataMessageOf<ControlsButton>), 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<ControlsButton> *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<ControlsButton> *message)
|
|
{
|
|
if (message->dataContents > 0) ZoomTargetRangeIn(); // FUN_004d1b64
|
|
}
|
|
|
|
void
|
|
MechRIOMapper::ZoomOutMessageHandler(
|
|
ReceiverDataMessageOf<ControlsButton> *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<ControlsButton> *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
|
|
//#############################################################################
|
|
//
|
|
// During configuration these bind the just-pressed button (1-based ID) either
|
|
// to a receiver+message or to a direct ControlsButton sink, under the base
|
|
// (ModeBasic, 0x10000) mode mask. 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
|
|
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();
|
|
}
|