Operator decision: "make this version devour their custom settings and re-output
the correct binding file with the updates -- I'd like to eliminate competing
code paths." Before this, the preserved-bindings.txt convention meant upgraders
stayed frozen on whatever layout their file was written under, forever: two
player populations, two sets of true documentation, and every future default
improvement reaching only fresh installs.
THE MIGRATION (PadBindingProfile::MigrateBindingsFile, run once at load):
* A file carrying the new "# bindings-board 2" marker is left alone -- the
check is the first thing that runs, so this is a no-op forever after.
* Otherwise: every row matching the UNION OF EVERY DEFAULT SET EVER SHIPPED
(85 canonical rows, harvested from c2ee729..1cda880 and embedded as a
table; comments stripped, whitespace collapsed, uppercased) is dropped --
those rows were never a player's choice, they were just the old board.
* Rows the player actually authored are CARRIED with their original text and
comments, and WIN over the new defaults: the losing default row is emitted
commented out as "# (yours wins) ...", keyed by the control ("KEY T",
"PAD A", "PADAXIS LX") so key rows only displace key rows.
* The joystick wizard's marker-delimited section is preserved VERBATIM (the
same markers L4JOY rewrites between), so HOTAS players migrate without
re-running the wizard.
* The previous file is saved as bindings.old.txt first. Restoring it over
bindings.txt just re-migrates next boot -- deliberate: the old WORLD is not
restorable, only the player's own rows persist. That is the point.
VERIFIED end-to-end with the real exe (three planted scenarios):
A. old-world file (8 old-default rows + 2 authored rows incl. a rate tweak +
a wizard section): both authored rows carried with comments, both
conflicting new defaults stood down with "(yours wins)", wizard section
byte-preserved, backup written, log line states all counts, and the
migrated file parses with ZERO malformed lines.
B. already-migrated file: byte-identical md5 after a full boot, no migration
line, no backup touched. (First attempt at this check clobbered its own
evidence by running case C first -- re-run properly.)
C. no file: fresh defaults now carry the marker as line 1, no migration.
The operator's real bindings.txt was stashed before testing and restored
untouched; it will migrate on their own next launch, as intended.
Docs flipped to the new reality: the README's "your keys DO NOT change"
upgrade promise is replaced by the migration story (customs + stick kept, old
stock keys replaced, bindings.old.txt escape hatch); CONTROLS.md gains the same
note; context/glass-cockpit.md records the mechanism, the 85-row table's
provenance, and why restoring the backup re-migrates.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
190 lines
5.8 KiB
C++
190 lines
5.8 KiB
C++
#pragma once
|
|
|
|
//###########################################################################
|
|
//
|
|
// L4PADBINDINGS -- the PadRIO input-binding profile (BT_GLASS only).
|
|
//
|
|
// Maps PC keyboard keys and XInput pad controls onto the pod's abstract
|
|
// control surface: RIO button addresses (buttonGroup elements), MFD keypad
|
|
// units, and the five analog channels (Throttle, JoystickX/Y, Left/Right
|
|
// Pedal). The profile is read from "bindings.txt" in the working directory
|
|
// (content\); a self-documenting default file is written on first run.
|
|
// Grammar (one binding per line, '#' comments):
|
|
//
|
|
// key <KEYNAME> button <addr>
|
|
// key <KEYNAME> keypad <unit> <key>
|
|
// key <KEYNAME> axis <channel> deflect <+|-> <rate>
|
|
// key <KEYNAME> axis <channel> slew <+|-> <rate>
|
|
// key <KEYNAME> axis <channel> set <value>
|
|
// pad <PADBTN> button <addr>
|
|
// padaxis <PADAXIS> axis <channel> [invert] [slew <rate>]
|
|
// joydev <slot 0-3> [product-name substring...]
|
|
// joyaxis <JOYAXIS> axis <channel> [invert] [slew <rate>] [deadzone <f>]
|
|
// joybutton <n> button <addr>
|
|
// joybutton <n> keypad <unit> <key>
|
|
// joyhat <n> <up|down|left|right> button <addr>
|
|
//
|
|
// deflect = held drives the channel toward +/-1 at <rate>/s, auto-centers
|
|
// on release (the spring-centered stick model).
|
|
// slew = held moves the channel at <rate>/s and it STAYS (the throttle
|
|
// lever model).
|
|
// set = press jumps the channel to <value> (all-stop detent).
|
|
//
|
|
// joy* rows (generic DirectInput joysticks -- flight sticks, HOTAS,
|
|
// pedals; L4JOY.h) attach to the most recent joydev slot (slot 0 if none
|
|
// given). A slot with a name substring binds THAT device; without one it
|
|
// binds the Nth attached non-XInput device. JOYAXIS names follow the
|
|
// DirectInput layout: X Y Z RX RY RZ SL0 SL1 (twist is usually RZ, a
|
|
// HOTAS throttle usually Z or SL0). A direct (non-slew) joyaxis on the
|
|
// Throttle channel maps the full axis travel onto the 0..1 lever (a
|
|
// physical lever OWNS the channel); other channels follow the pad model
|
|
// (outside the deadzone owns the channel). BT_JOYCONFIG=1 generates
|
|
// these rows interactively.
|
|
//
|
|
//###########################################################################
|
|
|
|
class PadBindingProfile
|
|
{
|
|
public:
|
|
enum Channel {
|
|
ChannelThrottle = 0,
|
|
ChannelJoystickX,
|
|
ChannelJoystickY,
|
|
ChannelLeftPedal,
|
|
ChannelRightPedal,
|
|
// issue #25: composite -- a SIGNED axis that decomposes into the
|
|
// pedal pair (positive = right pedal, negative = left pedal) so a
|
|
// gamepad stick can drive the turn ("twin-stick" profile).
|
|
ChannelTurn,
|
|
ChannelCount
|
|
};
|
|
|
|
enum ActionKind {
|
|
ActionButton, // RIO button address (buttonGroup element)
|
|
ActionKeypad, // MFD keypad unit + key
|
|
ActionAxisDeflect,
|
|
ActionAxisSlew,
|
|
ActionAxisSet
|
|
};
|
|
|
|
struct Action
|
|
{
|
|
ActionKind kind;
|
|
int address; // ActionButton: RIO address; ActionKeypad: unit
|
|
int key; // ActionKeypad: key number (0-9=digits, 10+=A..)
|
|
int channel; // ActionAxis*: Channel
|
|
float rate; // deflect/slew: units per second (signed);
|
|
// set: the target value
|
|
};
|
|
|
|
struct KeyBinding
|
|
{
|
|
int virtualKey; // Win32 VK code
|
|
Action action;
|
|
};
|
|
|
|
struct PadButtonBinding
|
|
{
|
|
unsigned buttonMask; // XINPUT_GAMEPAD_* bit
|
|
Action action; // ActionButton / ActionKeypad only
|
|
};
|
|
|
|
enum PadAxis {
|
|
PadAxisLX = 0, PadAxisLY, PadAxisRX, PadAxisRY,
|
|
PadAxisLT, PadAxisRT,
|
|
PadAxisCount
|
|
};
|
|
|
|
struct PadAxisBinding
|
|
{
|
|
int axis; // PadAxis
|
|
int channel; // Channel
|
|
int invert; // flip the sign
|
|
int slew; // 0 = direct absolute, 1 = value drives d/dt
|
|
float slewRate; // full-deflection slew speed (units/s)
|
|
};
|
|
|
|
//
|
|
// Generic-joystick bindings (DirectInput devices -- L4JOY.h). device
|
|
// is a joydev SLOT (0-3), resolved at poll time by the slot's name
|
|
// substring (or ordinal when the slot has none).
|
|
//
|
|
enum { JoyDeviceSlots = 4 };
|
|
|
|
enum JoyAxis {
|
|
JoyAxisX = 0, JoyAxisY, JoyAxisZ,
|
|
JoyAxisRX, JoyAxisRY, JoyAxisRZ,
|
|
JoyAxisSL0, JoyAxisSL1,
|
|
JoyAxisCount
|
|
};
|
|
|
|
struct JoyAxisBinding
|
|
{
|
|
int device; // joydev slot
|
|
int axis; // JoyAxis
|
|
int channel; // Channel
|
|
int invert;
|
|
int slew;
|
|
float slewRate;
|
|
float deadzone; // 0..1 fraction (rescaled past it)
|
|
};
|
|
|
|
struct JoyButtonBinding
|
|
{
|
|
int device; // joydev slot
|
|
int button; // 0-31
|
|
Action action; // ActionButton / ActionKeypad only
|
|
};
|
|
|
|
struct JoyHatBinding
|
|
{
|
|
int device; // joydev slot
|
|
int hat; // 0-3
|
|
int direction; // 0 up / 1 right / 2 down / 3 left
|
|
Action action; // ActionButton only
|
|
};
|
|
|
|
PadBindingProfile();
|
|
~PadBindingProfile();
|
|
|
|
//
|
|
// Load "bindings.txt" from the working directory; if it does not exist,
|
|
// write the default profile there first (self-documenting). Always
|
|
// leaves a usable profile (falls back to built-in defaults on a
|
|
// malformed file, logging each rejected line).
|
|
//
|
|
void
|
|
Load();
|
|
|
|
int keyBindingCount;
|
|
KeyBinding keyBindings[192];
|
|
int padButtonBindingCount;
|
|
PadButtonBinding padButtonBindings[32];
|
|
int padAxisBindingCount;
|
|
PadAxisBinding padAxisBindings[12];
|
|
|
|
int joyAxisBindingCount;
|
|
JoyAxisBinding joyAxisBindings[24];
|
|
int joyButtonBindingCount;
|
|
JoyButtonBinding joyButtonBindings[48];
|
|
int joyHatBindingCount;
|
|
JoyHatBinding joyHatBindings[16];
|
|
char joyDeviceMatch[JoyDeviceSlots][64]; // "" = ordinal slot
|
|
|
|
protected:
|
|
int parseJoyDevice; // joydev slot for subsequent joy rows
|
|
|
|
void
|
|
LoadDefaults();
|
|
void
|
|
WriteDefaultFile(const char *path);
|
|
// One-time migration of a pre-board-2 bindings.txt: player-authored rows
|
|
// and the wizard section are kept (and win over the new defaults), the old
|
|
// default rows are replaced, the original is saved as bindings.old.txt.
|
|
// No-op once the file carries the "# bindings-board 2" marker.
|
|
void
|
|
MigrateBindingsFile(const char *path);
|
|
int
|
|
ParseLine(char *line, int line_number);
|
|
};
|