Files
BT411/engine/MUNGA_L4/L4PADBINDINGS.h
T
arcattackandClaude Opus 4.8 c2ee7299b2 Generic joystick / HOTAS / pedals support: DirectInput 8 layer + capture wizard
Tester ask: configure non-Xbox controllers.  New L4JOY layer (DI8):
enumerates every non-XInput game device (up to 4), DIJOYSTATE2 polling
with range normalization, reacquire-on-loss, 3s hot-plug re-enum.
XInput devices are excluded via the documented RawInput IG_ VID/PID
check (live-verified skipping a real XBOX360 pad -- no double-feed).

bindings.txt grows joydev/joyaxis/joybutton/joyhat rows (device slots
by name substring or ordinal; axes X..RZ/SL0/SL1 with invert/slew/
deadzone; hats as 4-direction buttons with diagonal chords).  PadRIO
runs them through the existing channel/event machinery -- per-binding
edge arrays release automatically on detach (the #24 rule), and a
direct throttle axis maps full lever travel onto the 0..1 channel
while the gait detent still snaps a lever parked in the walk/run dead
band.

BT_JOYCONFIG=1 (joyconfig.bat) runs a console capture wizard: move
each control when prompted (stick/twist/throttle/fire), invert derived
from the move direction, hat look cluster auto-added, output written
between markers in bindings.txt with the rest of the file preserved.

Legacy L4DINPUT DIJoystick (L4CONTROLS=DIJOYSTICK) untouched.
Verified: grammar accept/reject per line, XInput-exclusion live,
30s in-mission no-device polling clean.  Awaiting a tester with real
stick hardware for axis verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 16:36:32 -05:00

184 lines
5.5 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);
int
ParseLine(char *line, int line_number);
};