Ported from RP412: RIOBase split out of the serial RIO (L4RIO.h), rioPointer is RIOBase* (L4CTRL.h), PAD token -> new PadRIO() speaking the RIO surface from an XInput pad + keyboard (L4PADRIO/L4PADBINDINGS, vRIO bindings.txt grammar, hot-plug), KeyLight RGB mirror TU (BT412KEYLIGHT, /std:c++17 per-file). BT-side fixes PadRIO forced into the open: - Both keyboard input bridges (mech4.cpp, mechmppr.cpp BT_KEY_BRIDGE) stand down when a RIO device exists -- they overwrote the engine controls push every frame. M/X conveniences stay live. - Mapper attribute chain OFF BY ONE (latent real-pod bug): the DOS chain below MechControlsMapper carried two base attributes, WinTesla carries one, and AttributeIndexSet::Find is positional -- the .CTL stick mapping wrote throttlePosition. Pad slot + binary-locked enum; gotcha ledgered (reconstruction-gotchas #11). Verified: PAD throttle lever ramps + sticks, stick turns with the authentic speed-vs-turn clamp (61.5 -> 22.0 u/s), mech drives; keyboard fallback intact (BT_FORCE_THROTTLE harness). New diags: BT_CTRLMAP_LOG, BT_STICK_LOG. (Phase 2 of docs/BT412-ROADMAP.md) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
340 lines
12 KiB
C++
340 lines
12 KiB
C++
//===========================================================================//
|
|
// File: mechmppr.hpp //
|
|
// Project: BattleTech Brick: Entity Manager //
|
|
// Contents: Mech controls mapper -- maps pilot control inputs and view //
|
|
// selection onto the Mech's motion / torso / eyepoint demands //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// --/--/95 ?? Initial coding. //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
|
|
// PROPRIETARY AND CONFIDENTIAL //
|
|
//===========================================================================//
|
|
//
|
|
// RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, cluster
|
|
// @004afbe0-@004b08c0) cross-referenced against the Red Planet sibling
|
|
// RP\VTVMPPR.cpp / VTVMPPR.h (VTVControlsMapper) -- same engine, same
|
|
// mapper pattern. The class name "MechControlsMapper" is recovered verbatim
|
|
// from the Derivation name string @0050f173. Attribute and message names are
|
|
// recovered from the class string pool @0050f180-@0050f367 and the attribute
|
|
// table @0050efd0; the configuration / event-mapping stub strings come from
|
|
// @0050f370-@0050f44a.
|
|
//
|
|
// Field offsets in comments are the byte offsets observed in the decompiled
|
|
// object (e.g. "@0x114" == this[0x45]). Names follow VTVControlsMapper where
|
|
// the role is identical; Mech-specific members (torso, pilot array) are named
|
|
// from the attribute table and flagged best-effort where uncertain.
|
|
//
|
|
// This is the BattleTech analog of Red Planet's VTVControlsMapper. Like that
|
|
// class it is the active "Performance" of the master, dynamic Mech: each
|
|
// simulation frame InterpretControls() reads the raw stick/throttle/pedals and
|
|
// the look/torso buttons and produces speedDemand / turnDemand plus the torso
|
|
// and eyepoint pose used by the locomotion and view code.
|
|
//
|
|
|
|
#if !defined(MECHMPPR_HPP)
|
|
# define MECHMPPR_HPP
|
|
|
|
#if !defined(SUBSYSTM_HPP)
|
|
# include <subsystm.hpp>
|
|
#endif
|
|
#if !defined(CONTROLS_HPP)
|
|
# include <controls.hpp> // ControlsButton, ControlsJoystick
|
|
#endif
|
|
|
|
//##################### Forward Class Declarations #######################
|
|
class Mech;
|
|
class Pilot;
|
|
|
|
//###########################################################################
|
|
//######################### MechControlsMapper ##########################
|
|
//###########################################################################
|
|
//
|
|
// Primary vtable @0050f45c (slot 0 = scalar-deleting destructor @004b044c;
|
|
// slots 1-13 inherited from Subsystem; slots 18/19 -- vtable+0x48/+0x4c --
|
|
// are the NotifyOf* hooks below). A secondary "controls mapper /
|
|
// configurator" interface vtable @0050f498 supplies the EnterConfiguration /
|
|
// ExitConfiguration / event-mapping stubs (all "not overridden" by default).
|
|
//
|
|
class MechControlsMapper:
|
|
public Subsystem
|
|
{
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Shared Data Support
|
|
//
|
|
public:
|
|
// WinTesla engine refactored the static derivation/handler/attribute
|
|
// objects behind Get* accessors (avoids static-init ordering bugs).
|
|
static Derivation *GetClassDerivations(); // @0050ee10
|
|
static SharedData DefaultData;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Messaging Support
|
|
//
|
|
// Message IDs and handler names recovered from the message table
|
|
// @0050ee40 (string pool @0050f180). IDs 3..0x13 (the auxiliary-equipment
|
|
// and zoom buttons) are routed through the shared ConfigureMappable
|
|
// handler @004afbc4; the last three drive dedicated handlers.
|
|
//
|
|
public:
|
|
enum {
|
|
Aux1QuadMessageID = Subsystem::NextMessageID, // 3
|
|
Aux1Eng1MessageID, Aux1Eng2MessageID,
|
|
Aux1Eng3MessageID, Aux1Eng4MessageID,
|
|
Aux2QuadMessageID,
|
|
Aux2Eng1MessageID, Aux2Eng2MessageID,
|
|
Aux2Eng3MessageID, Aux2Eng4MessageID,
|
|
Aux3QuadMessageID,
|
|
Aux3Eng1MessageID, Aux3Eng2MessageID,
|
|
Aux3Eng3MessageID, Aux3Eng4MessageID,
|
|
ZoomInMessageID, // 0x12
|
|
ZoomOutMessageID, // 0x13
|
|
CycleControlModeMessageID, // 0x14
|
|
CycleDisplayModeMessageID, // 0x15
|
|
ToggleVoiceAssistMessageID, // 0x16
|
|
NextMessageID
|
|
};
|
|
|
|
void
|
|
ConfigureMappableMessageHandler( // @004afbc4 (shared)
|
|
ReceiverDataMessageOf<ControlsButton> *message
|
|
);
|
|
void
|
|
CycleControlModeMessageHandler( // @004afbe0
|
|
ReceiverDataMessageOf<ControlsButton> *message
|
|
);
|
|
|
|
// The shared mode-cycle body (the console-button handler above + the
|
|
// desktop 'M' key both drive it).
|
|
void
|
|
CycleControlModeNow();
|
|
void
|
|
CycleDisplayModeMessageHandler( // @004afcac
|
|
ReceiverDataMessageOf<ControlsButton> *message
|
|
);
|
|
void
|
|
ToggleVoiceAssistMessageHandler( // @004afce8
|
|
ReceiverDataMessageOf<ControlsButton> *message
|
|
);
|
|
|
|
private:
|
|
static const HandlerEntry MessageHandlerEntries[]; // @0050ee40
|
|
protected:
|
|
static MessageHandlerSet& GetMessageHandlers();
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Attribute Support
|
|
//
|
|
// Attribute IDs / names / member offsets recovered from the attribute
|
|
// table @0050efd0. (The recorded member offsets carry the engine's
|
|
// low-bit "scalar" tag, e.g. 0x115 -> &stickPosition @0x114.)
|
|
//
|
|
// BT412 (databinding, 2026-07-14): the binary's stream IDs put the
|
|
// stick at 3, i.e. the DOS chain below the mapper carried TWO base
|
|
// attributes. The WinTesla Simulation/Subsystem chain carries ONE
|
|
// (SimulationState, Subsystem::NextAttributeID == 2), and the engine's
|
|
// AttributeIndexSet::Find is POSITIONAL (ID N -> slot N-1), so the
|
|
// enum is locked to the binary (+1) and mechmppr.cpp pads the missing
|
|
// base slot -- without it every mapper attribute resolved one entry
|
|
// late (the .CTL stick mapping wrote throttlePosition; found the day
|
|
// PadRIO first drove the mapper from a live device).
|
|
//
|
|
public:
|
|
enum {
|
|
StickPositionAttributeID = Subsystem::NextAttributeID + 1, // 3 (binary-locked)
|
|
ThrottlePositionAttributeID, // 4
|
|
PedalsPositionAttributeID, // 5
|
|
ReverseThrustAttributeID, // 6
|
|
SpeedDemandAttributeID, // 7
|
|
TurnDemandAttributeID, // 8
|
|
LookForwardAttributeID, // 9
|
|
LookLeftAttributeID, // 0xa
|
|
LookRightAttributeID, // 0xb
|
|
LookBehindAttributeID, // 0xc
|
|
LookDownAttributeID, // 0xd
|
|
TorsoUpAttributeID, // 0xe
|
|
TorsoDownAttributeID, // 0xf
|
|
TorsoLeftAttributeID, // 0x10
|
|
TorsoRightAttributeID, // 0x11
|
|
TorsoCenterAttributeID, // 0x12
|
|
ControlModeAttributeID, // 0x13
|
|
DisplayModeAttributeID, // 0x14
|
|
PilotArrayPageAttributeID, // 0x15
|
|
PilotArrayAttributeID, // 0x16
|
|
NextAttributeID
|
|
};
|
|
private:
|
|
static const IndexEntry AttributePointers[]; // @0050efd0
|
|
protected:
|
|
static AttributeIndexSet& GetAttributeIndex();
|
|
|
|
//
|
|
// Published attributes.
|
|
//
|
|
public:
|
|
ControlsJoystick stickPosition; // @0x114 (x @0x114, y @0x118)
|
|
Scalar throttlePosition; // @0x11C
|
|
Scalar pedalsPosition; // @0x120
|
|
ControlsButton reverseThrust; // @0x124
|
|
Scalar speedDemand; // @0x128 (output)
|
|
Scalar turnDemand; // @0x12C (output)
|
|
ControlsButton lookForward; // @0x130
|
|
ControlsButton lookLeft; // @0x134
|
|
ControlsButton lookRight; // @0x138
|
|
ControlsButton lookBehind; // @0x13C
|
|
ControlsButton lookDown; // @0x140
|
|
ControlsButton torsoUp; // @0x144
|
|
ControlsButton torsoDown; // @0x148
|
|
ControlsButton torsoLeft; // @0x14C
|
|
ControlsButton torsoRight; // @0x150
|
|
ControlsButton torsoCenter; // @0x154
|
|
|
|
int pilotArrayPage; // @0x158 (which "page" of pilots)
|
|
// FIXED 10-SLOT block, the binary's own reservation: 0x15C..0x183 =
|
|
// (pilotArrayBuilt@0x184 - 0x15C)/4 = 10 pointers ([0]=local, [1..9]
|
|
// remote; an 8-pod game + the local fits). The old `[1]` "variable
|
|
// length" declaration made EVERY write past slot 0 stomp the members
|
|
// declared after it: in MP, FillPilotArray wrote the PEER's Player
|
|
// pointer over controlMode (the turn shaping dispatched on pointer
|
|
// garbage -> turnDemand 0 -> "I can't turn"); in solo the zero-loop
|
|
// wrote 0 there, masked only because 0 == BasicMode. Caught live via
|
|
// cdb `ba w4` on &controlMode (task #51).
|
|
enum { PilotArraySlots = 10 };
|
|
Pilot *pilotArray[PilotArraySlots]; // @0x15C..0x183
|
|
|
|
enum ControlMode {
|
|
BasicMode = 0,
|
|
StandardMode = 1,
|
|
VeteranMode = 2
|
|
} controlMode; // @0x190
|
|
int displayMode; // @0x194 (0..2, cycles)
|
|
|
|
int lookState; // @0x198 eyepoint look selection
|
|
int previousLookState; // @0x19C
|
|
|
|
protected:
|
|
Logical pilotArrayBuilt; // @0x184 built-once flag
|
|
int *pilotIDs; // @0x188 parallel id table
|
|
int pilotCount; // @0x18C
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Model Support
|
|
//
|
|
public:
|
|
typedef void
|
|
(MechControlsMapper::*Performance)(Scalar time_slice);
|
|
|
|
void
|
|
SetPerformance(Performance performance)
|
|
{
|
|
Check(this);
|
|
activePerformance = (Simulation::Performance)performance;
|
|
}
|
|
|
|
// The active master-Mech performance (vtable default @0050f120).
|
|
void
|
|
InterpretControls(Scalar time_slice); // @004afd10
|
|
|
|
// Owner accessor -- the mapper's owning entity is always a Mech.
|
|
// (Subsystem::GetEntity() returns the generic Entity*; the decomp
|
|
// pseudo-C used the owner pointer directly.)
|
|
Mech*
|
|
GetMech();
|
|
|
|
//
|
|
// Look / torso eyepoint selection.
|
|
//
|
|
public:
|
|
enum LookState {
|
|
LookNone = 0,
|
|
LookLeftState = 1,
|
|
LookRightState = 2,
|
|
LookBehindState = 3,
|
|
LookDownState = 4
|
|
};
|
|
|
|
//
|
|
// Pilot-array (other-player roster) management. Built lazily on first
|
|
// use from the "Players" group of the application object (@0050f44b).
|
|
//
|
|
public:
|
|
void
|
|
BuildPilotArray(); // @004b0600
|
|
Pilot *
|
|
GetPilot(int index); // @004b0898
|
|
void
|
|
ChooseNearestPilot(int self_id); // @004b04d8
|
|
void
|
|
ChooseDefaultPilot(); // @004b07f0
|
|
void
|
|
UpdateCurrentPilot(int page); // @004b049c
|
|
protected:
|
|
void
|
|
FillPilotArray(); // @004b06cc
|
|
|
|
//
|
|
// Configuration / event-mapping interface -- the CONFIG-MODE session
|
|
// virtuals (task #6, vtable slots verified from the raw tables in
|
|
// section_dump: base @0050F45C, L4 @0051E440, Thrustmaster @0051E3F0,
|
|
// RIO @0051E3A0). There is NO "secondary vtable @0050f498" -- that
|
|
// address is simply slot 15 INSIDE the primary base vtable. Slot map:
|
|
// +0x38 EnterConfiguration (base Fail stub @004b0280; L4 @004d1840)
|
|
// +0x3C ExitConfiguration (base Fail stub @004b029c; L4 @004d18dc)
|
|
// +0x40 AddOrErase(event) (base Fail @004b02b8; L4/TM no-op @004d195c; RIO @004d25e8)
|
|
// +0x44 AddOrErase(direct) (base Fail @004b02d4; L4/TM no-op @004d1964; RIO @004d262c)
|
|
// +0x48/+0x4C NotifyOf* pair (base no-ops @004b048c/@004b0494)
|
|
// The old reconstruction here ("CreateTemporaryEventMappings", a 4-arg
|
|
// Enter, a 0-arg Exit) was RP-name drift; the binary signatures come from
|
|
// the ONLY callers -- MechWeapon handlers id 9/10 @004b9550/@004b95b8:
|
|
// Enter(|v|-1, &weapon.fireImpulse, weapon, /*choose*/10, /*configure*/9, /*active*/0)
|
|
// Exit(|v|-1)
|
|
// AddOrErase(v /*1-based*/, &weapon.fireImpulse)
|
|
//
|
|
public:
|
|
virtual void
|
|
EnterConfiguration( // +0x38 @004b0280
|
|
int held_element,
|
|
ControlsButton *destination,
|
|
Receiver *receiver,
|
|
Receiver::MessageID choose_message_id,
|
|
Receiver::MessageID configure_message_id,
|
|
Receiver::MessageID active_message_id
|
|
);
|
|
virtual void
|
|
ExitConfiguration(int held_element); // +0x3C @004b029c
|
|
virtual void
|
|
AddOrErase( // +0x40 @004b02b8 (event overload)
|
|
unsigned int button_ID,
|
|
Receiver *receiver,
|
|
Receiver::MessageID message_ID
|
|
);
|
|
virtual void
|
|
AddOrErase( // +0x44 @004b02d4 (direct overload)
|
|
unsigned int button_ID,
|
|
ControlsButton *destination
|
|
);
|
|
|
|
virtual void
|
|
NotifyOfControlModeChange(int new_mode); // @004b048c (vtable+0x48)
|
|
virtual void
|
|
NotifyOfDisplayModeChange(int new_mode); // @004b0494 (vtable+0x4c)
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Construction and Destruction
|
|
//
|
|
public:
|
|
MechControlsMapper( // @004b02f0
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data = DefaultData
|
|
);
|
|
~MechControlsMapper(); // @004b044c
|
|
|
|
Logical
|
|
TestInstance() const; // @004b08c0
|
|
};
|
|
|
|
#endif
|