Rebindable input: vRIO bindings profile ported, full board coverage
PadRIO now loads bindings.txt (vRIO profile grammar: key/pad/padaxis lines with toggle, deflect/rate, invert/deadzone options) written self-documenting with the full default layout on first run. The default is vRIO board-complete map - number and letter rows are the MFD banks as printed on the panel, F-keys the secondary/screen columns, numpad the pilot keypad (0x50-0x5F delivered as arcade RIO KeyEvents, a new PadRIO capability), Space/arrows the joystick column - with the desktop driving keys carved out: WASD stick, Q/E pedals, PgUp/PgDn throttle, B reverse (vRIO gap key; R returns to its bank). Pad bindings unchanged in spirit, plus Panic on LB and config on Start/Back; axis signs are encoded in the profile now, so L4PADFLIP flips on top of it. Default profile parses with zero rejected lines (68 key buttons, 8 key axes, 12 pad buttons, 5 pad axes); single-player cycle and the key-bomb tests stay green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+151
-88
@@ -7,54 +7,12 @@
|
||||
#pragma comment(lib, "xinput9_1_0.lib")
|
||||
|
||||
//########################################################################
|
||||
// Binding tables (vRIO default profile, condensed)
|
||||
// Input helpers; the binding tables live in bindings.txt now
|
||||
// (l4padbindings.cpp writes and parses the vRIO-format profile)
|
||||
//########################################################################
|
||||
|
||||
namespace
|
||||
{
|
||||
struct KeyBinding
|
||||
{
|
||||
int virtualKey;
|
||||
int rioUnit;
|
||||
};
|
||||
|
||||
const KeyBinding keyboardButtonMap[] =
|
||||
{
|
||||
{ VK_SPACE, 0x40 }, // ButtonJoystickTrigger
|
||||
{ 'R', 0x3F }, // ButtonThrottle1 (reverse thrust)
|
||||
{ VK_UP, 0x42 }, // ButtonJoystickHatUp
|
||||
{ VK_DOWN, 0x41 }, // ButtonJoystickHatDown
|
||||
{ VK_RIGHT, 0x43 }, // ButtonJoystickHatRight
|
||||
{ VK_LEFT, 0x44 }, // ButtonJoystickHatLeft
|
||||
{ VK_F1, 0x37 }, // ButtonAuxUpperRight1 (config)
|
||||
{ VK_F2, 0x36 }, // ButtonAuxUpperRight2 (config)
|
||||
};
|
||||
|
||||
struct PadBinding
|
||||
{
|
||||
unsigned short padMask;
|
||||
int rioUnit;
|
||||
};
|
||||
|
||||
const PadBinding padButtonMap[] =
|
||||
{
|
||||
{ XINPUT_GAMEPAD_A, 0x40 }, // trigger
|
||||
{ XINPUT_GAMEPAD_B, 0x3F }, // reverse thrust
|
||||
{ XINPUT_GAMEPAD_X, 0x45 }, // pinky
|
||||
{ XINPUT_GAMEPAD_Y, 0x46 }, // thumb low
|
||||
{ XINPUT_GAMEPAD_LEFT_SHOULDER, 0x46 }, // thumb low
|
||||
{ XINPUT_GAMEPAD_RIGHT_SHOULDER, 0x47 }, // thumb high
|
||||
{ XINPUT_GAMEPAD_DPAD_UP, 0x42 },
|
||||
{ XINPUT_GAMEPAD_DPAD_DOWN, 0x41 },
|
||||
{ XINPUT_GAMEPAD_DPAD_RIGHT, 0x43 },
|
||||
{ XINPUT_GAMEPAD_DPAD_LEFT, 0x44 },
|
||||
{ XINPUT_GAMEPAD_START, 0x37 }, // config 1
|
||||
{ XINPUT_GAMEPAD_BACK, 0x36 }, // config 2
|
||||
};
|
||||
|
||||
// Full throttle sweep takes ~1.3 s at full stick deflection.
|
||||
const Scalar throttleRatePerSecond = 0.75f;
|
||||
|
||||
Scalar StickValue(int raw, int dead_zone)
|
||||
{
|
||||
if (raw > -dead_zone && raw < dead_zone)
|
||||
@@ -125,9 +83,12 @@ PadRIO::PadRIO()
|
||||
sentJoystickX = sentJoystickY = (Scalar) 0;
|
||||
|
||||
memset(buttonDown, 0, sizeof(buttonDown));
|
||||
memset(keypadDown, 0, sizeof(keypadDown));
|
||||
memset(lampState, 0, sizeof(lampState));
|
||||
memset(screenButton, 0, sizeof(screenButton));
|
||||
|
||||
PadBindings_Load(&profile);
|
||||
|
||||
invertX = False;
|
||||
invertY = False;
|
||||
const char *flip = getenv("L4PADFLIP");
|
||||
@@ -208,6 +169,17 @@ void
|
||||
JoystickY = (Scalar) 0;
|
||||
analogRequested = True;
|
||||
memset(lampState, 0, sizeof(lampState));
|
||||
memset(keypadDown, 0, sizeof(keypadDown));
|
||||
for (int i = 0; i < profile.keyButtonCount; ++i)
|
||||
{
|
||||
profile.keyButtons[i].latched = False;
|
||||
profile.keyButtons[i].wasDown = False;
|
||||
}
|
||||
for (int i = 0; i < profile.padButtonCount; ++i)
|
||||
{
|
||||
profile.padButtons[i].latched = False;
|
||||
profile.padButtons[i].wasDown = False;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -295,27 +267,60 @@ void
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Buttons: build the desired state across pad + keyboard, then
|
||||
// diff against what we last reported.
|
||||
// Buttons: build the desired state from the binding profile
|
||||
// (keyboard + pad, with toggle latches), merge the on-screen
|
||||
// cockpit buttons, then diff against what we last reported.
|
||||
// Keypad addresses (0x50-0x6F) collect separately - they become
|
||||
// arcade KeyEvents, not button events.
|
||||
//---------------------------------------------------------------
|
||||
unsigned char desired[buttonUnits];
|
||||
unsigned char keypadDesired[keypadUnits];
|
||||
memset(desired, 0, sizeof(desired));
|
||||
memset(keypadDesired, 0, sizeof(keypadDesired));
|
||||
|
||||
if (pad_live)
|
||||
for (int i = 0; i < profile.keyButtonCount; ++i)
|
||||
{
|
||||
for (int i = 0; i < (int)(sizeof(padButtonMap)/sizeof(padButtonMap[0])); ++i)
|
||||
PadKeyButtonBinding *binding = &profile.keyButtons[i];
|
||||
Logical down = KeyDown(binding->virtualKey);
|
||||
if (binding->toggle && down && !binding->wasDown)
|
||||
{
|
||||
if (pad.Gamepad.wButtons & padButtonMap[i].padMask)
|
||||
binding->latched = !binding->latched;
|
||||
}
|
||||
binding->wasDown = down;
|
||||
|
||||
if (binding->toggle ? binding->latched : down)
|
||||
{
|
||||
if (binding->address < buttonUnits)
|
||||
{
|
||||
desired[padButtonMap[i].rioUnit] = 1;
|
||||
desired[binding->address] = 1;
|
||||
}
|
||||
else if (binding->address >= 0x50 && binding->address < 0x50 + keypadUnits)
|
||||
{
|
||||
keypadDesired[binding->address - 0x50] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < (int)(sizeof(keyboardButtonMap)/sizeof(keyboardButtonMap[0])); ++i)
|
||||
for (int i = 0; i < profile.padButtonCount; ++i)
|
||||
{
|
||||
if (KeyDown(keyboardButtonMap[i].virtualKey))
|
||||
PadPadButtonBinding *binding = &profile.padButtons[i];
|
||||
Logical down = pad_live &&
|
||||
(pad.Gamepad.wButtons & binding->padMask) != 0;
|
||||
if (binding->toggle && down && !binding->wasDown)
|
||||
{
|
||||
desired[keyboardButtonMap[i].rioUnit] = 1;
|
||||
binding->latched = !binding->latched;
|
||||
}
|
||||
binding->wasDown = down;
|
||||
|
||||
if (binding->toggle ? binding->latched : down)
|
||||
{
|
||||
if (binding->address < buttonUnits)
|
||||
{
|
||||
desired[binding->address] = 1;
|
||||
}
|
||||
else if (binding->address >= 0x50 && binding->address < 0x50 + keypadUnits)
|
||||
{
|
||||
keypadDesired[binding->address - 0x50] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < buttonUnits; ++i)
|
||||
@@ -340,53 +345,111 @@ void
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Axes
|
||||
// Keypads: presses become the arcade RIO KeyEvents. Unit 0 is the
|
||||
// pilot's internal keypad (0x50-0x5F), unit 1 the external
|
||||
// operator keypad (0x60-0x6F); the key is the hex digit 0-15.
|
||||
//---------------------------------------------------------------
|
||||
Scalar x = (Scalar) 0, y = (Scalar) 0;
|
||||
Scalar left_pedal = (Scalar) 0, right_pedal = (Scalar) 0;
|
||||
Scalar throttle_rate = (Scalar) 0;
|
||||
|
||||
if (pad_live)
|
||||
for (int pad_key = 0; pad_key < keypadUnits; ++pad_key)
|
||||
{
|
||||
x += StickValue(pad.Gamepad.sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
|
||||
y += StickValue(pad.Gamepad.sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
|
||||
throttle_rate += StickValue(pad.Gamepad.sThumbRY, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
|
||||
|
||||
if (pad.Gamepad.bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
|
||||
if (keypadDesired[pad_key] != keypadDown[pad_key])
|
||||
{
|
||||
left_pedal += (Scalar)(pad.Gamepad.bLeftTrigger) / 255.0f;
|
||||
}
|
||||
if (pad.Gamepad.bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
|
||||
{
|
||||
right_pedal += (Scalar)(pad.Gamepad.bRightTrigger) / 255.0f;
|
||||
keypadDown[pad_key] = keypadDesired[pad_key];
|
||||
if (keypadDesired[pad_key])
|
||||
{
|
||||
RIOEvent an_event;
|
||||
an_event.Type = KeyEvent;
|
||||
an_event.Data.Keyboard.Unit = (pad_key >= 0x10) ? 1 : 0;
|
||||
an_event.Data.Keyboard.Key = pad_key & 0x0F;
|
||||
QueueEvent(an_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// keyboard: WASD stick deflect, Q/E pedals, PgUp/PgDn throttle
|
||||
if (KeyDown('A')) x -= 1.0f;
|
||||
if (KeyDown('D')) x += 1.0f;
|
||||
if (KeyDown('W')) y += 1.0f;
|
||||
if (KeyDown('S')) y -= 1.0f;
|
||||
if (KeyDown('Q')) left_pedal += 1.0f;
|
||||
if (KeyDown('E')) right_pedal += 1.0f;
|
||||
if (KeyDown(VK_PRIOR)) throttle_rate += 1.0f; // PgUp
|
||||
if (KeyDown(VK_NEXT)) throttle_rate -= 1.0f; // PgDn
|
||||
//---------------------------------------------------------------
|
||||
// Axes, from the profile. 'deflect' sources sum into a springy
|
||||
// position; 'rate' sources integrate the throttle (the pod's only
|
||||
// sticky axis) by value per second.
|
||||
//---------------------------------------------------------------
|
||||
Scalar deflect[BindAxisCount];
|
||||
Scalar rate[BindAxisCount];
|
||||
memset(deflect, 0, sizeof(deflect));
|
||||
memset(rate, 0, sizeof(rate));
|
||||
|
||||
for (int i = 0; i < profile.keyAxisCount; ++i)
|
||||
{
|
||||
const PadKeyAxisBinding *binding = &profile.keyAxes[i];
|
||||
if (KeyDown(binding->virtualKey))
|
||||
{
|
||||
if (binding->mode == BindKeyRate)
|
||||
{
|
||||
rate[binding->axis] += binding->value;
|
||||
}
|
||||
else
|
||||
{
|
||||
deflect[binding->axis] += binding->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pad_live)
|
||||
{
|
||||
for (int i = 0; i < profile.padAxisCount; ++i)
|
||||
{
|
||||
const PadPadAxisBinding *binding = &profile.padAxes[i];
|
||||
Scalar value = (Scalar) 0;
|
||||
switch (binding->source)
|
||||
{
|
||||
case BindPadLeftStickX:
|
||||
value = StickValue(pad.Gamepad.sThumbLX, (int)(binding->deadzone * 32767.0f));
|
||||
break;
|
||||
case BindPadLeftStickY:
|
||||
value = StickValue(pad.Gamepad.sThumbLY, (int)(binding->deadzone * 32767.0f));
|
||||
break;
|
||||
case BindPadRightStickX:
|
||||
value = StickValue(pad.Gamepad.sThumbRX, (int)(binding->deadzone * 32767.0f));
|
||||
break;
|
||||
case BindPadRightStickY:
|
||||
value = StickValue(pad.Gamepad.sThumbRY, (int)(binding->deadzone * 32767.0f));
|
||||
break;
|
||||
case BindPadLeftTrigger:
|
||||
value = (Scalar)(pad.Gamepad.bLeftTrigger) / 255.0f;
|
||||
if (value <= binding->deadzone) value = (Scalar) 0;
|
||||
break;
|
||||
case BindPadRightTrigger:
|
||||
value = (Scalar)(pad.Gamepad.bRightTrigger) / 255.0f;
|
||||
if (value <= binding->deadzone) value = (Scalar) 0;
|
||||
break;
|
||||
}
|
||||
if (binding->invert)
|
||||
{
|
||||
value = -value;
|
||||
}
|
||||
if (binding->rate > 0.0f)
|
||||
{
|
||||
rate[binding->axis] += value * binding->rate;
|
||||
}
|
||||
else
|
||||
{
|
||||
deflect[binding->axis] += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throttleAccum = Clamp01(throttleAccum + rate[BindAxisThrottle] * delta_t);
|
||||
|
||||
Scalar x = deflect[BindAxisJoystickX];
|
||||
Scalar y = deflect[BindAxisJoystickY];
|
||||
if (x > 1.0f) x = 1.0f;
|
||||
if (x < -1.0f) x = -1.0f;
|
||||
if (y > 1.0f) y = 1.0f;
|
||||
if (y < -1.0f) y = -1.0f;
|
||||
|
||||
throttleAccum = Clamp01(throttleAccum + throttle_rate * throttleRatePerSecond * delta_t);
|
||||
|
||||
Throttle = throttleAccum;
|
||||
LeftPedal = Clamp01(left_pedal);
|
||||
RightPedal = Clamp01(right_pedal);
|
||||
// The pod's stick convention is opposite the XInput axes on both X and
|
||||
// Y (confirmed in playtest), so the default is negated; L4PADFLIP
|
||||
// flips back per axis.
|
||||
JoystickX = invertX ? x : -x;
|
||||
JoystickY = invertY ? y : -y;
|
||||
Throttle = Clamp01(throttleAccum + deflect[BindAxisThrottle]);
|
||||
LeftPedal = Clamp01(deflect[BindAxisLeftPedal]);
|
||||
RightPedal = Clamp01(deflect[BindAxisRightPedal]);
|
||||
// The profile encodes the pod's stick sign convention; L4PADFLIP
|
||||
// flips on top of it per axis.
|
||||
JoystickX = invertX ? -x : x;
|
||||
JoystickY = invertY ? -y : y;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Emit an analog event when asked to, or when anything moved
|
||||
|
||||
Reference in New Issue
Block a user