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:
@@ -0,0 +1,547 @@
|
||||
#include "mungal4.h"
|
||||
#pragma hdrstop
|
||||
|
||||
#include "l4padbindings.h"
|
||||
|
||||
#include <XInput.h>
|
||||
#include <stdio.h>
|
||||
|
||||
//########################################################################
|
||||
// Name tables: .NET Keys names (what vRIO uses) to virtual keys, pad
|
||||
// names to XInput masks. Matching is case-insensitive.
|
||||
//########################################################################
|
||||
|
||||
namespace
|
||||
{
|
||||
struct NameValue
|
||||
{
|
||||
const char *name;
|
||||
int value;
|
||||
};
|
||||
|
||||
const NameValue kKeyNames[] =
|
||||
{
|
||||
// letters and digits are generated in LookupKey
|
||||
{ "F1", VK_F1 }, { "F2", VK_F2 }, { "F3", VK_F3 }, { "F4", VK_F4 },
|
||||
{ "F5", VK_F5 }, { "F6", VK_F6 }, { "F7", VK_F7 }, { "F8", VK_F8 },
|
||||
{ "F9", VK_F9 }, { "F10", VK_F10 }, { "F11", VK_F11 }, { "F12", VK_F12 },
|
||||
{ "NumPad0", VK_NUMPAD0 }, { "NumPad1", VK_NUMPAD1 },
|
||||
{ "NumPad2", VK_NUMPAD2 }, { "NumPad3", VK_NUMPAD3 },
|
||||
{ "NumPad4", VK_NUMPAD4 }, { "NumPad5", VK_NUMPAD5 },
|
||||
{ "NumPad6", VK_NUMPAD6 }, { "NumPad7", VK_NUMPAD7 },
|
||||
{ "NumPad8", VK_NUMPAD8 }, { "NumPad9", VK_NUMPAD9 },
|
||||
{ "Divide", VK_DIVIDE }, { "Multiply", VK_MULTIPLY },
|
||||
{ "Subtract", VK_SUBTRACT }, { "Add", VK_ADD },
|
||||
{ "Decimal", VK_DECIMAL },
|
||||
{ "Up", VK_UP }, { "Down", VK_DOWN },
|
||||
{ "Left", VK_LEFT }, { "Right", VK_RIGHT },
|
||||
{ "Space", VK_SPACE }, { "Return", VK_RETURN }, { "Enter", VK_RETURN },
|
||||
{ "Escape", VK_ESCAPE }, { "Tab", VK_TAB }, { "Back", VK_BACK },
|
||||
{ "Home", VK_HOME }, { "End", VK_END },
|
||||
{ "PageUp", VK_PRIOR }, { "PageDown", VK_NEXT },
|
||||
{ "Prior", VK_PRIOR }, { "Next", VK_NEXT },
|
||||
{ "Insert", VK_INSERT }, { "Delete", VK_DELETE },
|
||||
{ "ShiftKey", VK_SHIFT }, { "ControlKey", VK_CONTROL },
|
||||
{ "OemMinus", VK_OEM_MINUS }, { "Oemplus", VK_OEM_PLUS },
|
||||
{ "Oemcomma", VK_OEM_COMMA }, { "OemPeriod", VK_OEM_PERIOD },
|
||||
{ "OemOpenBrackets", VK_OEM_4 }, { "OemCloseBrackets", VK_OEM_6 },
|
||||
{ "OemSemicolon", VK_OEM_1 }, { "OemQuotes", VK_OEM_7 },
|
||||
{ "OemQuestion", VK_OEM_2 }, { "OemPipe", VK_OEM_5 },
|
||||
{ "Oemtilde", VK_OEM_3 },
|
||||
};
|
||||
|
||||
const NameValue kPadButtonNames[] =
|
||||
{
|
||||
{ "A", XINPUT_GAMEPAD_A }, { "B", XINPUT_GAMEPAD_B },
|
||||
{ "X", XINPUT_GAMEPAD_X }, { "Y", XINPUT_GAMEPAD_Y },
|
||||
{ "DPadUp", XINPUT_GAMEPAD_DPAD_UP },
|
||||
{ "DPadDown", XINPUT_GAMEPAD_DPAD_DOWN },
|
||||
{ "DPadLeft", XINPUT_GAMEPAD_DPAD_LEFT },
|
||||
{ "DPadRight", XINPUT_GAMEPAD_DPAD_RIGHT },
|
||||
{ "Start", XINPUT_GAMEPAD_START }, { "Back", XINPUT_GAMEPAD_BACK },
|
||||
{ "LeftShoulder", XINPUT_GAMEPAD_LEFT_SHOULDER },
|
||||
{ "RightShoulder", XINPUT_GAMEPAD_RIGHT_SHOULDER },
|
||||
{ "LeftThumb", XINPUT_GAMEPAD_LEFT_THUMB },
|
||||
{ "RightThumb", XINPUT_GAMEPAD_RIGHT_THUMB },
|
||||
};
|
||||
|
||||
const NameValue kPadAxisNames[] =
|
||||
{
|
||||
{ "LeftStickX", BindPadLeftStickX }, { "LeftStickY", BindPadLeftStickY },
|
||||
{ "RightStickX", BindPadRightStickX }, { "RightStickY", BindPadRightStickY },
|
||||
{ "LeftTrigger", BindPadLeftTrigger }, { "RightTrigger", BindPadRightTrigger },
|
||||
};
|
||||
|
||||
const NameValue kRioAxisNames[] =
|
||||
{
|
||||
{ "Throttle", BindAxisThrottle },
|
||||
{ "LeftPedal", BindAxisLeftPedal }, { "RightPedal", BindAxisRightPedal },
|
||||
{ "JoystickY", BindAxisJoystickY }, { "JoystickX", BindAxisJoystickX },
|
||||
};
|
||||
|
||||
Logical NameEquals(const char *a, const char *b)
|
||||
{
|
||||
return _stricmp(a, b) == 0;
|
||||
}
|
||||
|
||||
int LookupTable(const NameValue *table, int count, const char *name)
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
if (NameEquals(table[i].name, name))
|
||||
{
|
||||
return table[i].value;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int LookupKey(const char *name)
|
||||
{
|
||||
// single letter A-Z (VK == uppercase ASCII)
|
||||
if (name[1] == '\0' &&
|
||||
((name[0] >= 'A' && name[0] <= 'Z') || (name[0] >= 'a' && name[0] <= 'z')))
|
||||
{
|
||||
return toupper(name[0]);
|
||||
}
|
||||
// digit row: D0-D9 (VK == '0'-'9')
|
||||
if ((name[0] == 'D' || name[0] == 'd') &&
|
||||
name[1] >= '0' && name[1] <= '9' && name[2] == '\0')
|
||||
{
|
||||
return name[1];
|
||||
}
|
||||
return LookupTable(kKeyNames, sizeof(kKeyNames) / sizeof(kKeyNames[0]), name);
|
||||
}
|
||||
|
||||
Logical ParseAddress(const char *text, int *address)
|
||||
{
|
||||
int value = -1;
|
||||
if (text[0] == '0' && (text[1] == 'x' || text[1] == 'X'))
|
||||
{
|
||||
if (sscanf(text + 2, "%x", &value) != 1)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
}
|
||||
else if (sscanf(text, "%d", &value) != 1)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
if ((value >= 0x00 && value <= 0x47) || (value >= 0x50 && value <= 0x6F))
|
||||
{
|
||||
*address = value;
|
||||
return True;
|
||||
}
|
||||
return False;
|
||||
}
|
||||
|
||||
Logical ParseNumber(const char *text, Scalar *value)
|
||||
{
|
||||
float parsed;
|
||||
if (sscanf(text, "%f", &parsed) != 1)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
*value = (Scalar) parsed;
|
||||
return True;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Line tokenizer: whitespace separated, '#' starts a comment
|
||||
//---------------------------------------------------------------
|
||||
int Tokenize(char *line, char *tokens[], int max_tokens)
|
||||
{
|
||||
char *hash = strchr(line, '#');
|
||||
if (hash != NULL)
|
||||
{
|
||||
*hash = '\0';
|
||||
}
|
||||
int count = 0;
|
||||
char *cursor = line;
|
||||
while (count < max_tokens)
|
||||
{
|
||||
while (*cursor == ' ' || *cursor == '\t' || *cursor == '\r' || *cursor == '\n')
|
||||
{
|
||||
++cursor;
|
||||
}
|
||||
if (*cursor == '\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
tokens[count++] = cursor;
|
||||
while (*cursor != '\0' && *cursor != ' ' && *cursor != '\t' &&
|
||||
*cursor != '\r' && *cursor != '\n')
|
||||
{
|
||||
++cursor;
|
||||
}
|
||||
if (*cursor != '\0')
|
||||
{
|
||||
*cursor++ = '\0';
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// One line of the profile grammar
|
||||
//---------------------------------------------------------------
|
||||
Logical ParseLine(char *tokens[], int token_count, PadBindingProfile *profile)
|
||||
{
|
||||
if (token_count < 4)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
|
||||
if (NameEquals(tokens[0], "key") && NameEquals(tokens[2], "button"))
|
||||
{
|
||||
int key = LookupKey(tokens[1]);
|
||||
int address;
|
||||
if (key < 0 || !ParseAddress(tokens[3], &address) ||
|
||||
profile->keyButtonCount >= PadBindingProfile::maxKeyButtons)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
Logical toggle = (token_count > 4 && NameEquals(tokens[4], "toggle"));
|
||||
PadKeyButtonBinding *binding = &profile->keyButtons[profile->keyButtonCount++];
|
||||
memset(binding, 0, sizeof(*binding));
|
||||
binding->virtualKey = key;
|
||||
binding->address = address;
|
||||
binding->toggle = toggle;
|
||||
return True;
|
||||
}
|
||||
|
||||
if (NameEquals(tokens[0], "key") && NameEquals(tokens[2], "axis"))
|
||||
{
|
||||
if (token_count < 6)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
int key = LookupKey(tokens[1]);
|
||||
int axis = LookupTable(kRioAxisNames,
|
||||
sizeof(kRioAxisNames) / sizeof(kRioAxisNames[0]), tokens[3]);
|
||||
int mode = NameEquals(tokens[4], "deflect") ? BindKeyDeflect
|
||||
: NameEquals(tokens[4], "rate") ? BindKeyRate : -1;
|
||||
Scalar value;
|
||||
if (key < 0 || axis < 0 || mode < 0 || !ParseNumber(tokens[5], &value) ||
|
||||
profile->keyAxisCount >= PadBindingProfile::maxKeyAxes)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
PadKeyAxisBinding *binding = &profile->keyAxes[profile->keyAxisCount++];
|
||||
binding->virtualKey = key;
|
||||
binding->axis = axis;
|
||||
binding->mode = mode;
|
||||
binding->value = value;
|
||||
return True;
|
||||
}
|
||||
|
||||
if (NameEquals(tokens[0], "pad") && NameEquals(tokens[2], "button"))
|
||||
{
|
||||
int mask = LookupTable(kPadButtonNames,
|
||||
sizeof(kPadButtonNames) / sizeof(kPadButtonNames[0]), tokens[1]);
|
||||
int address;
|
||||
if (mask < 0 || !ParseAddress(tokens[3], &address) ||
|
||||
profile->padButtonCount >= PadBindingProfile::maxPadButtons)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
Logical toggle = (token_count > 4 && NameEquals(tokens[4], "toggle"));
|
||||
PadPadButtonBinding *binding = &profile->padButtons[profile->padButtonCount++];
|
||||
memset(binding, 0, sizeof(*binding));
|
||||
binding->padMask = (unsigned short) mask;
|
||||
binding->address = address;
|
||||
binding->toggle = toggle;
|
||||
return True;
|
||||
}
|
||||
|
||||
if (NameEquals(tokens[0], "padaxis") && NameEquals(tokens[2], "axis"))
|
||||
{
|
||||
int source = LookupTable(kPadAxisNames,
|
||||
sizeof(kPadAxisNames) / sizeof(kPadAxisNames[0]), tokens[1]);
|
||||
int axis = LookupTable(kRioAxisNames,
|
||||
sizeof(kRioAxisNames) / sizeof(kRioAxisNames[0]), tokens[3]);
|
||||
if (source < 0 || axis < 0 ||
|
||||
profile->padAxisCount >= PadBindingProfile::maxPadAxes)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
PadPadAxisBinding *binding = &profile->padAxes[profile->padAxisCount++];
|
||||
memset(binding, 0, sizeof(*binding));
|
||||
binding->source = source;
|
||||
binding->axis = axis;
|
||||
for (int i = 4; i < token_count; ++i)
|
||||
{
|
||||
if (NameEquals(tokens[i], "invert"))
|
||||
{
|
||||
binding->invert = True;
|
||||
}
|
||||
else if (NameEquals(tokens[i], "deadzone") && i + 1 < token_count)
|
||||
{
|
||||
if (!ParseNumber(tokens[++i], &binding->deadzone))
|
||||
{
|
||||
return False;
|
||||
}
|
||||
}
|
||||
else if (NameEquals(tokens[i], "rate") && i + 1 < token_count)
|
||||
{
|
||||
if (!ParseNumber(tokens[++i], &binding->rate))
|
||||
{
|
||||
return False;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return False;
|
||||
}
|
||||
}
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// The default profile: vRIO's board-complete layout, adapted for
|
||||
// the desktop game - the driving keys (WASD/QE/PgUp/PgDn) stay
|
||||
// axes, so six bank buttons lose their keyboard keys (they are
|
||||
// still clickable on the on-screen cockpit, and rebindable here).
|
||||
//---------------------------------------------------------------
|
||||
const char kDefaultProfile[] =
|
||||
"# RP412 input bindings - keyboard and Xbox (XInput) controller.\n"
|
||||
"#\n"
|
||||
"# One binding per line, '#' starts a comment, keywords are case-\n"
|
||||
"# insensitive. Loaded at game start; delete this file to restore\n"
|
||||
"# the defaults.\n"
|
||||
"#\n"
|
||||
"# key <name> button <addr> [toggle]\n"
|
||||
"# key <name> axis <axis> deflect <n>\n"
|
||||
"# key <name> axis <axis> rate <n-per-second>\n"
|
||||
"# pad <button> button <addr> [toggle]\n"
|
||||
"# padaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n-per-second>]\n"
|
||||
"#\n"
|
||||
"# <addr> RIO input address: lamp buttons 0x00-0x47, internal keypad\n"
|
||||
"# 0x50-0x5F, external keypad 0x60-0x6F (hex or decimal).\n"
|
||||
"# <axis> Throttle | LeftPedal | RightPedal | JoystickY | JoystickX\n"
|
||||
"# <name> Keys name: A-Z, D0-D9 (digit row), F1-F12, NumPad0-NumPad9,\n"
|
||||
"# Up, Down, Left, Right, Space, Enter, PageUp, PageDown,\n"
|
||||
"# OemMinus, Oemplus, Oemcomma, OemPeriod, ...\n"
|
||||
"# <button> A B X Y DPadUp DPadDown DPadLeft DPadRight Start Back\n"
|
||||
"# LeftShoulder RightShoulder LeftThumb RightThumb\n"
|
||||
"# <src> LeftStickX LeftStickY RightStickX RightStickY\n"
|
||||
"# LeftTrigger RightTrigger\n"
|
||||
"#\n"
|
||||
"# 'deflect' holds the axis there while the key is down and springs\n"
|
||||
"# back on release; 'rate' walks the axis by <n> per second and the\n"
|
||||
"# position sticks (the throttle). Every lamp button is also clickable\n"
|
||||
"# on the on-screen cockpit, so unbound addresses are never stranded.\n"
|
||||
"\n"
|
||||
"# ---- Driving: keyboard --------------------------------------------\n"
|
||||
"key W axis JoystickY deflect -1\n"
|
||||
"key S axis JoystickY deflect 1\n"
|
||||
"key A axis JoystickX deflect 1\n"
|
||||
"key D axis JoystickX deflect -1\n"
|
||||
"key Q axis LeftPedal deflect 1\n"
|
||||
"key E axis RightPedal deflect 1\n"
|
||||
"key PageUp axis Throttle rate 0.75\n"
|
||||
"key PageDown axis Throttle rate -0.75\n"
|
||||
"\n"
|
||||
"# ---- Driving: Xbox controller (signs match the pod convention) ----\n"
|
||||
"padaxis LeftStickX axis JoystickX invert deadzone 0.24\n"
|
||||
"padaxis LeftStickY axis JoystickY invert deadzone 0.24\n"
|
||||
"padaxis RightStickY axis Throttle deadzone 0.24 rate 0.75\n"
|
||||
"padaxis LeftTrigger axis LeftPedal deadzone 0.12\n"
|
||||
"padaxis RightTrigger axis RightPedal deadzone 0.12\n"
|
||||
"\n"
|
||||
"# ---- Joystick column: hat on the arrows, Main on Space, reverse\n"
|
||||
"# ---- thrust on B (vRIO's gap key - the pod had it on the throttle)\n"
|
||||
"key Space button 0x40 # Main trigger\n"
|
||||
"key B button 0x3F # Throttle button (reverse thrust)\n"
|
||||
"key Up button 0x42 # Hat Up\n"
|
||||
"key Down button 0x41 # Hat Back\n"
|
||||
"key Left button 0x44 # Hat Left\n"
|
||||
"key Right button 0x43 # Hat Right\n"
|
||||
"\n"
|
||||
"# ---- Xbox controller: buttons -------------------------------------\n"
|
||||
"pad A button 0x40 # Main\n"
|
||||
"pad B button 0x46 # Middle\n"
|
||||
"pad X button 0x45 # Pinky\n"
|
||||
"pad Y button 0x47 # Upper\n"
|
||||
"pad DPadUp button 0x42 # Hat Up\n"
|
||||
"pad DPadDown button 0x41 # Hat Back\n"
|
||||
"pad DPadLeft button 0x44 # Hat Left\n"
|
||||
"pad DPadRight button 0x43 # Hat Right\n"
|
||||
"pad LeftShoulder button 0x3D # Panic\n"
|
||||
"pad RightShoulder button 0x3F # Throttle button (reverse)\n"
|
||||
"pad Start button 0x37 # config 1\n"
|
||||
"pad Back button 0x36 # config 2\n"
|
||||
"\n"
|
||||
"# ---- Upper MFD bank: the number row is the top MFD row and the\n"
|
||||
"# ---- QWERTY row is the row under it, left to right across the\n"
|
||||
"# ---- Left / Middle / Right MFDs. (W/A/S/D/Q/E drive; their bank\n"
|
||||
"# ---- buttons live on the on-screen cockpit.)\n"
|
||||
"key D1 button 0x2F\n"
|
||||
"key D2 button 0x2E\n"
|
||||
"key D3 button 0x2D\n"
|
||||
"key D4 button 0x2C\n"
|
||||
"key D5 button 0x27\n"
|
||||
"key D6 button 0x26\n"
|
||||
"key D7 button 0x25\n"
|
||||
"key D8 button 0x24\n"
|
||||
"key D9 button 0x37\n"
|
||||
"key D0 button 0x36\n"
|
||||
"key OemMinus button 0x35\n"
|
||||
"key Oemplus button 0x34\n"
|
||||
"key R button 0x28\n"
|
||||
"key T button 0x23\n"
|
||||
"key Y button 0x22\n"
|
||||
"key U button 0x21\n"
|
||||
"key I button 0x20\n"
|
||||
"key O button 0x33\n"
|
||||
"key P button 0x32\n"
|
||||
"key OemOpenBrackets button 0x31\n"
|
||||
"key OemCloseBrackets button 0x30\n"
|
||||
"\n"
|
||||
"# ---- Lower MFD bank: home row and the row below it, two 4-key\n"
|
||||
"# ---- blocks split by an unbound gap key (G), mirroring the keypad\n"
|
||||
"# ---- gap between the Lower Left and Lower Right MFDs.\n"
|
||||
"key F button 0x0C\n"
|
||||
"key H button 0x07\n"
|
||||
"key J button 0x06\n"
|
||||
"key K button 0x05\n"
|
||||
"key L button 0x04\n"
|
||||
"key Z button 0x0B\n"
|
||||
"key X button 0x0A\n"
|
||||
"key C button 0x09\n"
|
||||
"key V button 0x08\n"
|
||||
"key N button 0x03\n"
|
||||
"key M button 0x02\n"
|
||||
"key Oemcomma button 0x01\n"
|
||||
"key OemPeriod button 0x00\n"
|
||||
"\n"
|
||||
"# ---- Secondary / Screen columns on the function keys, top to\n"
|
||||
"# ---- bottom (0x16/0x17 and 0x1E/0x1F intentionally unmapped).\n"
|
||||
"key F1 button 0x10\n"
|
||||
"key F2 button 0x11\n"
|
||||
"key F3 button 0x12\n"
|
||||
"key F4 button 0x13\n"
|
||||
"key F5 button 0x14\n"
|
||||
"key F6 button 0x15\n"
|
||||
"key F7 button 0x18\n"
|
||||
"key F8 button 0x19\n"
|
||||
"key F9 button 0x1A\n"
|
||||
"key F10 button 0x1B\n"
|
||||
"key F11 button 0x1C\n"
|
||||
"key F12 button 0x1D\n"
|
||||
"\n"
|
||||
"# ---- Internal keypad on the numpad (hex keys on the operators) ----\n"
|
||||
"key NumPad0 button 0x50\n"
|
||||
"key NumPad1 button 0x51\n"
|
||||
"key NumPad2 button 0x52\n"
|
||||
"key NumPad3 button 0x53\n"
|
||||
"key NumPad4 button 0x54\n"
|
||||
"key NumPad5 button 0x55\n"
|
||||
"key NumPad6 button 0x56\n"
|
||||
"key NumPad7 button 0x57\n"
|
||||
"key NumPad8 button 0x58\n"
|
||||
"key NumPad9 button 0x59\n"
|
||||
"key Divide button 0x5A # keypad A (/)\n"
|
||||
"key Multiply button 0x5B # keypad B (*)\n"
|
||||
"key Subtract button 0x5C # keypad C (-)\n"
|
||||
"key Add button 0x5D # keypad D (+)\n"
|
||||
"key Decimal button 0x5E # keypad E (.)\n"
|
||||
"key Return button 0x5F # keypad F (Enter)\n";
|
||||
|
||||
const char kBindingsFileName[] = "bindings.txt";
|
||||
}
|
||||
|
||||
//########################################################################
|
||||
// Loader
|
||||
//########################################################################
|
||||
|
||||
void
|
||||
PadBindings_Load(PadBindingProfile *profile)
|
||||
{
|
||||
memset(profile, 0, sizeof(*profile));
|
||||
|
||||
//
|
||||
// First run: write the default profile so the player has a
|
||||
// self-documenting file to edit
|
||||
//
|
||||
FILE *file = fopen(kBindingsFileName, "rb");
|
||||
if (file == NULL)
|
||||
{
|
||||
FILE *out = fopen(kBindingsFileName, "wb");
|
||||
if (out != NULL)
|
||||
{
|
||||
fwrite(kDefaultProfile, 1, strlen(kDefaultProfile), out);
|
||||
fclose(out);
|
||||
DEBUG_STREAM << "PadBindings: wrote default " << kBindingsFileName
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
file = fopen(kBindingsFileName, "rb");
|
||||
}
|
||||
|
||||
//
|
||||
// Parse: file if we have one, the built-in default if not
|
||||
//
|
||||
char *text = NULL;
|
||||
long size = 0;
|
||||
if (file != NULL)
|
||||
{
|
||||
fseek(file, 0, SEEK_END);
|
||||
size = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
text = new char[size + 1];
|
||||
fread(text, 1, size, file);
|
||||
text[size] = '\0';
|
||||
fclose(file);
|
||||
}
|
||||
const char *source = (text != NULL) ? text : kDefaultProfile;
|
||||
|
||||
char line[256];
|
||||
int line_number = 0;
|
||||
int error_count = 0;
|
||||
const char *cursor = source;
|
||||
while (*cursor != '\0')
|
||||
{
|
||||
int length = 0;
|
||||
while (cursor[length] != '\0' && cursor[length] != '\n' &&
|
||||
length < (int) sizeof(line) - 1)
|
||||
{
|
||||
line[length] = cursor[length];
|
||||
++length;
|
||||
}
|
||||
line[length] = '\0';
|
||||
cursor += length;
|
||||
if (*cursor == '\n')
|
||||
{
|
||||
++cursor;
|
||||
}
|
||||
++line_number;
|
||||
|
||||
char *tokens[12];
|
||||
int token_count = Tokenize(line, tokens, 12);
|
||||
if (token_count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!ParseLine(tokens, token_count, profile))
|
||||
{
|
||||
++error_count;
|
||||
DEBUG_STREAM << "PadBindings: " << kBindingsFileName << " line "
|
||||
<< line_number << " rejected\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
if (text != NULL)
|
||||
{
|
||||
delete[] text;
|
||||
}
|
||||
|
||||
DEBUG_STREAM << "PadBindings: " << profile->keyButtonCount << " key buttons, "
|
||||
<< profile->keyAxisCount << " key axes, "
|
||||
<< profile->padButtonCount << " pad buttons, "
|
||||
<< profile->padAxisCount << " pad axes"
|
||||
<< (error_count ? " (with rejected lines)" : "")
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
//===========================================================================//
|
||||
// File: l4padbindings.h //
|
||||
// Project: MUNGA Brick: PadRIO binding profiles //
|
||||
// Contents: The rebindable input profile (vRIO bindings format) //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc. //
|
||||
// PROPRIETARY AND CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "..\munga\style.h"
|
||||
|
||||
//########################################################################
|
||||
// The bindings file (vRIO's format, shared grammar):
|
||||
//
|
||||
// key <name> button <addr> [toggle]
|
||||
// key <name> axis <axis> deflect <n> | rate <n>
|
||||
// pad <button> button <addr> [toggle]
|
||||
// padaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n>]
|
||||
//
|
||||
// <addr> is a RIO input address: lamp buttons 0x00-0x47, internal
|
||||
// keypad 0x50-0x5F, external keypad 0x60-0x6F. Loaded from
|
||||
// bindings.txt beside the exe; written there (self-documenting, with
|
||||
// the full default profile) on first run. Bad lines are logged and
|
||||
// skipped, good lines always win.
|
||||
//########################################################################
|
||||
|
||||
enum PadBindRioAxis
|
||||
{
|
||||
BindAxisThrottle = 0,
|
||||
BindAxisLeftPedal,
|
||||
BindAxisRightPedal,
|
||||
BindAxisJoystickY,
|
||||
BindAxisJoystickX,
|
||||
BindAxisCount
|
||||
};
|
||||
|
||||
enum PadBindKeyMode
|
||||
{
|
||||
BindKeyDeflect = 0, // hold at value while down, spring back
|
||||
BindKeyRate // walk by value/second while down, position holds
|
||||
};
|
||||
|
||||
enum PadBindPadAxis
|
||||
{
|
||||
BindPadLeftStickX = 0,
|
||||
BindPadLeftStickY,
|
||||
BindPadRightStickX,
|
||||
BindPadRightStickY,
|
||||
BindPadLeftTrigger,
|
||||
BindPadRightTrigger,
|
||||
BindPadAxisCount
|
||||
};
|
||||
|
||||
struct PadKeyButtonBinding
|
||||
{
|
||||
int virtualKey;
|
||||
int address; // 0x00-0x47 button, 0x50-0x6F keypad
|
||||
Logical toggle;
|
||||
// runtime edge/latch state
|
||||
Logical wasDown;
|
||||
Logical latched;
|
||||
};
|
||||
|
||||
struct PadKeyAxisBinding
|
||||
{
|
||||
int virtualKey;
|
||||
int axis; // PadBindRioAxis
|
||||
int mode; // PadBindKeyMode
|
||||
Scalar value;
|
||||
};
|
||||
|
||||
struct PadPadButtonBinding
|
||||
{
|
||||
unsigned short padMask; // XINPUT_GAMEPAD_*
|
||||
int address;
|
||||
Logical toggle;
|
||||
Logical wasDown;
|
||||
Logical latched;
|
||||
};
|
||||
|
||||
struct PadPadAxisBinding
|
||||
{
|
||||
int source; // PadBindPadAxis
|
||||
int axis; // PadBindRioAxis
|
||||
Logical invert;
|
||||
Scalar deadzone; // normalized 0..1
|
||||
Scalar rate; // 0 = direct position, >0 = speed integrate
|
||||
};
|
||||
|
||||
struct PadBindingProfile
|
||||
{
|
||||
enum
|
||||
{
|
||||
maxKeyButtons = 128,
|
||||
maxKeyAxes = 32,
|
||||
maxPadButtons = 32,
|
||||
maxPadAxes = 16
|
||||
};
|
||||
|
||||
PadKeyButtonBinding keyButtons[maxKeyButtons];
|
||||
int keyButtonCount;
|
||||
PadKeyAxisBinding keyAxes[maxKeyAxes];
|
||||
int keyAxisCount;
|
||||
PadPadButtonBinding padButtons[maxPadButtons];
|
||||
int padButtonCount;
|
||||
PadPadAxisBinding padAxes[maxPadAxes];
|
||||
int padAxisCount;
|
||||
};
|
||||
|
||||
// Load bindings.txt from the working directory into the profile,
|
||||
// writing the default profile file first when absent. Errors go to
|
||||
// the debug log with line numbers.
|
||||
void
|
||||
PadBindings_Load(PadBindingProfile *profile);
|
||||
+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
|
||||
|
||||
+20
-14
@@ -1,29 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "l4rio.h"
|
||||
#include "l4padbindings.h"
|
||||
|
||||
//########################################################################
|
||||
//############################### PadRIO #################################
|
||||
//########################################################################
|
||||
//
|
||||
// A cockpit-less RIO: synthesizes the RIOBase control surface from an
|
||||
// XInput controller and the PC keyboard, following vRIO's default
|
||||
// binding profile. Selected with L4CONTROLS=PAD.
|
||||
// XInput controller and the PC keyboard. Selected with L4CONTROLS=PAD.
|
||||
//
|
||||
// Left stick -> joystick X/Y [-1..1]
|
||||
// Left/right trigger-> left/right pedal [0..1]
|
||||
// Right stick Y -> throttle rate (position holds) [0..1]
|
||||
// A / B -> joystick trigger / reverse thrust
|
||||
// X / Y / LB / RB -> pinky / thumb-low / thumb-low / thumb-high
|
||||
// DPad -> joystick hat
|
||||
// Back / Start -> config buttons (AuxUpperRight2 / 1)
|
||||
//
|
||||
// Keyboard: WASD = stick deflect (spring-back), PgUp/PgDn = throttle,
|
||||
// Q/E = pedals, Space = trigger, R = reverse, arrows = hat, F1/F2 =
|
||||
// config buttons.
|
||||
// Every input is REBINDABLE: bindings.txt beside the exe (vRIO's
|
||||
// profile format, written with the full default layout on first run)
|
||||
// maps keys and pad controls to RIO button addresses (0x00-0x47), the
|
||||
// pilot/external keypads (0x50-0x6F, delivered as arcade KeyEvents),
|
||||
// and the five axes with deflect/rate semantics. Defaults follow
|
||||
// vRIO's board-complete layout with the driving keys carved out:
|
||||
// WASD stick, Q/E pedals, PgUp/PgDn throttle, Space trigger, B
|
||||
// reverse, arrows hat, letter rows = MFD banks, F-keys = secondary
|
||||
// columns, numpad = pilot keypad.
|
||||
//
|
||||
// Set L4PADFLIP to a string containing 'X' and/or 'Y' to invert the
|
||||
// stick axes.
|
||||
// stick axes on top of whatever the profile produces.
|
||||
//
|
||||
class PadRIO :
|
||||
public RIOBase
|
||||
@@ -102,6 +100,14 @@ protected:
|
||||
unsigned char
|
||||
buttonDown[buttonUnits];
|
||||
|
||||
// keypad pressed-state (0x50-0x6F), for KeyEvent edges
|
||||
enum { keypadUnits = 0x20 };
|
||||
unsigned char
|
||||
keypadDown[keypadUnits];
|
||||
|
||||
PadBindingProfile
|
||||
profile;
|
||||
|
||||
Scalar
|
||||
throttleAccum;
|
||||
Logical
|
||||
|
||||
@@ -254,6 +254,7 @@
|
||||
<ClCompile Include=".\L4STEAMTRANSPORT.cpp" />
|
||||
<ClCompile Include=".\L4PARTICLES.cpp" />
|
||||
<ClCompile Include=".\L4MFDVIEW.cpp" />
|
||||
<ClCompile Include=".\L4PADBINDINGS.cpp" />
|
||||
<ClCompile Include=".\L4PADRIO.cpp" />
|
||||
<ClCompile Include=".\L4PCSPAK.cpp" />
|
||||
<ClCompile Include=".\L4PLASMA.cpp" />
|
||||
@@ -443,6 +444,7 @@
|
||||
<ClInclude Include=".\L4MPPR.h" />
|
||||
<ClInclude Include=".\L4NET.H" />
|
||||
<ClInclude Include=".\L4NETTRANSPORT.h" />
|
||||
<ClInclude Include=".\L4PADBINDINGS.h" />
|
||||
<ClInclude Include=".\L4STEAMTRANSPORT.h" />
|
||||
<ClInclude Include=".\L4PARTICLES.h" />
|
||||
<ClInclude Include=".\L4MFDVIEW.h" />
|
||||
|
||||
+8
-3
@@ -99,15 +99,20 @@ Red Planet 4.12 - desktop prototype
|
||||
Run start-windowed.bat (or: rpl4opt.exe -windowed -res 800 600 -egg TEST.EGG).
|
||||
No cockpit hardware needed. If there is no sound, run oalinst.exe once.
|
||||
|
||||
Controls (XInput controller and/or keyboard):
|
||||
Controls (XInput controller and/or keyboard) - EVERY input is
|
||||
rebindable: edit bindings.txt beside the exe (written with the full
|
||||
documented default layout on first run; delete it to restore).
|
||||
|
||||
Left stick / WASD joystick
|
||||
LT / RT or Q / E left / right pedal
|
||||
Right stick Y, PgUp/PgDn throttle (holds position)
|
||||
A / Space joystick trigger
|
||||
B / R reverse thrust
|
||||
RB / B key reverse thrust
|
||||
DPad / arrow keys joystick hat (look)
|
||||
Start,Back / F1,F2 config buttons
|
||||
Start,Back / 9,0 config buttons
|
||||
Number+letter rows MFD bank buttons (as printed on the panel)
|
||||
F1-F12 secondary / screen columns
|
||||
Numpad pilot keypad (0-9, /*-+. = A-E, Enter = F)
|
||||
Alt+Q abort the mission (score banked)
|
||||
|
||||
The full pod cockpit comes up in a single window: three green MFDs
|
||||
|
||||
Reference in New Issue
Block a user