Files
BT411/game/reconstructed/btl4mppr.cpp
T
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
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>
2026-07-05 21:03:40 -05:00

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();
}