Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52da65bc0d | ||
|
|
91420b5cb2 |
+1052
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
|||||||
|
//===========================================================================//
|
||||||
|
// File: l4joy.h //
|
||||||
|
// Project: MUNGA Brick: generic joystick reader //
|
||||||
|
// Contents: DirectInput 8 sticks, HOTAS throttles and pedals //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc. //
|
||||||
|
// PROPRIETARY AND CONFIDENTIAL //
|
||||||
|
//===========================================================================//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
//########################################################################
|
||||||
|
//
|
||||||
|
// L4JOY - the generic-joystick reader.
|
||||||
|
//
|
||||||
|
// PadRIO reads XInput, which covers Xbox-class pads and nothing else.
|
||||||
|
// This layer adds every OTHER game device Windows knows - flight sticks,
|
||||||
|
// HOTAS throttles, twist grips, rudder pedals, wheels - through
|
||||||
|
// DirectInput 8, the standard generic-HID game API. It exposes up to
|
||||||
|
// joyMaxDevices attached devices as normalized state blocks; the PadRIO
|
||||||
|
// poll maps them onto the pod's control channels through the joydev /
|
||||||
|
// joyaxis / joybutton / joyhat rows of bindings.txt (L4PADBINDINGS.h),
|
||||||
|
// the same binding machinery the pad and keyboard already use.
|
||||||
|
//
|
||||||
|
// XInput-class devices are EXCLUDED here, or they would double-feed
|
||||||
|
// through both APIs and every input would count twice. A DirectInput
|
||||||
|
// device whose VID/PID also appears in a RawInput device path containing
|
||||||
|
// the "IG_" marker is an XInput device - the documented detection that
|
||||||
|
// does not drag in WMI.
|
||||||
|
//
|
||||||
|
// This is distinct from the legacy L4DINPUT.cpp DIJoystick, the 1995-era
|
||||||
|
// single-device `Joystick` engine interface reachable only through the
|
||||||
|
// old L4CONTROLS=DIJOYSTICK profile. That path is untouched.
|
||||||
|
//
|
||||||
|
// RP412JOYCONFIG=1 runs the interactive setup wizard at boot: it asks
|
||||||
|
// the player to move each control, works out which device and axis moved
|
||||||
|
// and which way, and writes the joystick section of bindings.txt.
|
||||||
|
// RP412JOYLOG=1 logs device attach/detach.
|
||||||
|
//
|
||||||
|
// Ported from BT411, whose glass cockpit needed the same thing.
|
||||||
|
//
|
||||||
|
//########################################################################
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
joyMaxDevices = 4,
|
||||||
|
joyAxisCount = 8, // X Y Z RX RY RZ SL0 SL1 (DIJOYSTATE2 order)
|
||||||
|
joyButtonCount = 32, // buttons exposed to bindings (DI carries 128)
|
||||||
|
joyHatCount = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RPJoyDeviceState
|
||||||
|
{
|
||||||
|
int attached;
|
||||||
|
float axis[joyAxisCount]; // normalized -1..1, raw: deadzones
|
||||||
|
// are the binding layer's business
|
||||||
|
unsigned buttons; // bit n = button n held
|
||||||
|
int hat[joyHatCount]; // POV in centidegrees, -1 = centered
|
||||||
|
char name[64]; // product name ("T.16000M", ...)
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle. Init is lazy-safe (Poll calls it) and returns the attached
|
||||||
|
// non-XInput device count. Re-enumeration for hot-plug happens inside
|
||||||
|
// Poll on a ~3 s cadence whenever nothing is attached.
|
||||||
|
//
|
||||||
|
int RPJoyInit(void);
|
||||||
|
void RPJoyShutdown(void);
|
||||||
|
void RPJoyPoll(void);
|
||||||
|
|
||||||
|
int RPJoyDeviceCount(void);
|
||||||
|
const RPJoyDeviceState *RPJoyDevice(int index); // NULL out of range/detached
|
||||||
|
|
||||||
|
//
|
||||||
|
// Case-insensitive product-name substring match to a device index, -1 for
|
||||||
|
// no match. This is what a named joydev slot resolves through.
|
||||||
|
//
|
||||||
|
int RPJoyFindDevice(const char *name_substring);
|
||||||
|
|
||||||
|
//
|
||||||
|
// The RP412JOYCONFIG capture wizard (console UI; called from RPL4.CPP
|
||||||
|
// before the front end). Returns 0 if it wrote a config, non-zero on
|
||||||
|
// abort or no device.
|
||||||
|
//
|
||||||
|
int RPJoyConfigWizard(void);
|
||||||
+197
-26
@@ -2,6 +2,7 @@
|
|||||||
#pragma hdrstop
|
#pragma hdrstop
|
||||||
|
|
||||||
#include "l4padbindings.h"
|
#include "l4padbindings.h"
|
||||||
|
#include "l4joy.h" // joyButtonCount / joyHatCount, the parse limits
|
||||||
|
|
||||||
#include <XInput.h>
|
#include <XInput.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -79,6 +80,20 @@ namespace
|
|||||||
{ "Throttle", BindAxisThrottle },
|
{ "Throttle", BindAxisThrottle },
|
||||||
{ "LeftPedal", BindAxisLeftPedal }, { "RightPedal", BindAxisRightPedal },
|
{ "LeftPedal", BindAxisLeftPedal }, { "RightPedal", BindAxisRightPedal },
|
||||||
{ "JoystickY", BindAxisJoystickY }, { "JoystickX", BindAxisJoystickX },
|
{ "JoystickY", BindAxisJoystickY }, { "JoystickX", BindAxisJoystickX },
|
||||||
|
{ "Pedals", BindAxisPedals },
|
||||||
|
};
|
||||||
|
|
||||||
|
// DirectInput's axis order, which is what the joy* rows name
|
||||||
|
const NameValue kJoyAxisNames[] =
|
||||||
|
{
|
||||||
|
{ "X", BindJoyAxisX }, { "Y", BindJoyAxisY }, { "Z", BindJoyAxisZ },
|
||||||
|
{ "RX", BindJoyAxisRX }, { "RY", BindJoyAxisRY }, { "RZ", BindJoyAxisRZ },
|
||||||
|
{ "SL0", BindJoyAxisSL0 }, { "SL1", BindJoyAxisSL1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const NameValue kJoyHatNames[] =
|
||||||
|
{
|
||||||
|
{ "up", 0 }, { "right", 1 }, { "down", 2 }, { "left", 3 },
|
||||||
};
|
};
|
||||||
|
|
||||||
Logical NameEquals(const char *a, const char *b)
|
Logical NameEquals(const char *a, const char *b)
|
||||||
@@ -185,10 +200,83 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// One line of the profile grammar
|
// Shared tail of the two axis-source rows: [invert] [deadzone <d>]
|
||||||
|
// [rate <n>], in any order.
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
Logical ParseLine(char *tokens[], int token_count, PadBindingProfile *profile)
|
Logical ParseAxisOptions(
|
||||||
|
char *tokens[], int token_count, int first,
|
||||||
|
Logical *invert, Scalar *deadzone, Scalar *rate)
|
||||||
{
|
{
|
||||||
|
for (int i = first; i < token_count; ++i)
|
||||||
|
{
|
||||||
|
if (NameEquals(tokens[i], "invert"))
|
||||||
|
{
|
||||||
|
*invert = True;
|
||||||
|
}
|
||||||
|
else if (NameEquals(tokens[i], "deadzone") && i + 1 < token_count)
|
||||||
|
{
|
||||||
|
if (!ParseNumber(tokens[++i], deadzone))
|
||||||
|
{
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (NameEquals(tokens[i], "rate") && i + 1 < token_count)
|
||||||
|
{
|
||||||
|
if (!ParseNumber(tokens[++i], rate))
|
||||||
|
{
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return True;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// One line of the profile grammar. joy_slot carries the joydev
|
||||||
|
// state forward from line to line - joy rows attach to the slot
|
||||||
|
// most recently declared.
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
Logical ParseLine(
|
||||||
|
char *tokens[], int token_count, PadBindingProfile *profile,
|
||||||
|
int *joy_slot)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// joydev is the one row that can be two tokens long ("joydev 1"),
|
||||||
|
// so it is answered before the four-token floor below.
|
||||||
|
//
|
||||||
|
if (NameEquals(tokens[0], "joydev") && token_count >= 2)
|
||||||
|
{
|
||||||
|
int slot = -1;
|
||||||
|
if (sscanf(tokens[1], "%d", &slot) != 1 ||
|
||||||
|
slot < 0 || slot >= BindJoyDeviceSlots)
|
||||||
|
{
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
*joy_slot = slot;
|
||||||
|
//
|
||||||
|
// The rest of the line is a product-name substring, rejoined
|
||||||
|
// with single spaces ("Saitek Pro Flight" is four tokens).
|
||||||
|
//
|
||||||
|
profile->joyDeviceMatch[slot][0] = '\0';
|
||||||
|
for (int i = 2; i < token_count; ++i)
|
||||||
|
{
|
||||||
|
if (i > 2)
|
||||||
|
{
|
||||||
|
strncat(profile->joyDeviceMatch[slot], " ",
|
||||||
|
sizeof(profile->joyDeviceMatch[slot]) -
|
||||||
|
strlen(profile->joyDeviceMatch[slot]) - 1);
|
||||||
|
}
|
||||||
|
strncat(profile->joyDeviceMatch[slot], tokens[i],
|
||||||
|
sizeof(profile->joyDeviceMatch[slot]) -
|
||||||
|
strlen(profile->joyDeviceMatch[slot]) - 1);
|
||||||
|
}
|
||||||
|
return True;
|
||||||
|
}
|
||||||
|
|
||||||
if (token_count < 4)
|
if (token_count < 4)
|
||||||
{
|
{
|
||||||
return False;
|
return False;
|
||||||
@@ -271,31 +359,75 @@ namespace
|
|||||||
memset(binding, 0, sizeof(*binding));
|
memset(binding, 0, sizeof(*binding));
|
||||||
binding->source = source;
|
binding->source = source;
|
||||||
binding->axis = axis;
|
binding->axis = axis;
|
||||||
for (int i = 4; i < token_count; ++i)
|
return ParseAxisOptions(tokens, token_count, 4,
|
||||||
|
&binding->invert, &binding->deadzone, &binding->rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Generic joystick rows, all attaching to the current joydev slot
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
if (NameEquals(tokens[0], "joyaxis") && NameEquals(tokens[2], "axis"))
|
||||||
|
{
|
||||||
|
int source = LookupTable(kJoyAxisNames,
|
||||||
|
sizeof(kJoyAxisNames) / sizeof(kJoyAxisNames[0]), tokens[1]);
|
||||||
|
int axis = LookupTable(kRioAxisNames,
|
||||||
|
sizeof(kRioAxisNames) / sizeof(kRioAxisNames[0]), tokens[3]);
|
||||||
|
if (source < 0 || axis < 0 ||
|
||||||
|
profile->joyAxisCount >= PadBindingProfile::maxJoyAxes)
|
||||||
{
|
{
|
||||||
if (NameEquals(tokens[i], "invert"))
|
return False;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
PadJoyAxisBinding *binding = &profile->joyAxes[profile->joyAxisCount++];
|
||||||
|
memset(binding, 0, sizeof(*binding));
|
||||||
|
binding->device = *joy_slot;
|
||||||
|
binding->source = source;
|
||||||
|
binding->axis = axis;
|
||||||
|
return ParseAxisOptions(tokens, token_count, 4,
|
||||||
|
&binding->invert, &binding->deadzone, &binding->rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NameEquals(tokens[0], "joybutton") && NameEquals(tokens[2], "button"))
|
||||||
|
{
|
||||||
|
int button = -1;
|
||||||
|
int address;
|
||||||
|
if (sscanf(tokens[1], "%d", &button) != 1 ||
|
||||||
|
button < 0 || button >= joyButtonCount ||
|
||||||
|
!ParseAddress(tokens[3], &address) ||
|
||||||
|
profile->joyButtonCount >= PadBindingProfile::maxJoyButtons)
|
||||||
|
{
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
Logical toggle = (token_count > 4 && NameEquals(tokens[4], "toggle"));
|
||||||
|
PadJoyButtonBinding *binding =
|
||||||
|
&profile->joyButtons[profile->joyButtonCount++];
|
||||||
|
memset(binding, 0, sizeof(*binding));
|
||||||
|
binding->device = *joy_slot;
|
||||||
|
binding->button = button;
|
||||||
|
binding->address = address;
|
||||||
|
binding->toggle = toggle;
|
||||||
|
return True;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NameEquals(tokens[0], "joyhat") && token_count >= 5 &&
|
||||||
|
NameEquals(tokens[3], "button"))
|
||||||
|
{
|
||||||
|
int hat = -1;
|
||||||
|
int direction = LookupTable(kJoyHatNames,
|
||||||
|
sizeof(kJoyHatNames) / sizeof(kJoyHatNames[0]), tokens[2]);
|
||||||
|
int address;
|
||||||
|
if (sscanf(tokens[1], "%d", &hat) != 1 ||
|
||||||
|
hat < 0 || hat >= joyHatCount || direction < 0 ||
|
||||||
|
!ParseAddress(tokens[4], &address) ||
|
||||||
|
profile->joyHatCount >= PadBindingProfile::maxJoyHats)
|
||||||
|
{
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
PadJoyHatBinding *binding = &profile->joyHats[profile->joyHatCount++];
|
||||||
|
memset(binding, 0, sizeof(*binding));
|
||||||
|
binding->device = *joy_slot;
|
||||||
|
binding->hat = hat;
|
||||||
|
binding->direction = direction;
|
||||||
|
binding->address = address;
|
||||||
return True;
|
return True;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,10 +452,17 @@ namespace
|
|||||||
"# key <name> axis <axis> rate <n-per-second>\n"
|
"# key <name> axis <axis> rate <n-per-second>\n"
|
||||||
"# pad <button> button <addr> [toggle]\n"
|
"# pad <button> button <addr> [toggle]\n"
|
||||||
"# padaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n-per-second>]\n"
|
"# padaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n-per-second>]\n"
|
||||||
|
"# joydev <slot> [product-name substring]\n"
|
||||||
|
"# joyaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n-per-second>]\n"
|
||||||
|
"# joybutton <n> button <addr> [toggle]\n"
|
||||||
|
"# joyhat <n> <up|down|left|right> button <addr>\n"
|
||||||
"#\n"
|
"#\n"
|
||||||
"# <addr> RIO input address: lamp buttons 0x00-0x47, internal keypad\n"
|
"# <addr> RIO input address: lamp buttons 0x00-0x47, internal keypad\n"
|
||||||
"# 0x50-0x5F, external keypad 0x60-0x6F (hex or decimal).\n"
|
"# 0x50-0x5F, external keypad 0x60-0x6F (hex or decimal).\n"
|
||||||
"# <axis> Throttle | LeftPedal | RightPedal | JoystickY | JoystickX\n"
|
"# <axis> Throttle | LeftPedal | RightPedal | JoystickY | JoystickX\n"
|
||||||
|
"# | Pedals - a signed axis that works the pedal PAIR, positive\n"
|
||||||
|
"# for the right pedal and negative for the left, so one rudder\n"
|
||||||
|
"# bar or twist grip drives both.\n"
|
||||||
"# <name> Keys name: A-Z, D0-D9 (digit row), F1-F12, NumPad0-NumPad9,\n"
|
"# <name> Keys name: A-Z, D0-D9 (digit row), F1-F12, NumPad0-NumPad9,\n"
|
||||||
"# Up, Down, Left, Right, Space, Enter, PageUp, PageDown,\n"
|
"# Up, Down, Left, Right, Space, Enter, PageUp, PageDown,\n"
|
||||||
"# OemMinus, Oemplus, Oemcomma, OemPeriod, ...\n"
|
"# OemMinus, Oemplus, Oemcomma, OemPeriod, ...\n"
|
||||||
@@ -336,6 +475,31 @@ namespace
|
|||||||
"# back on release; 'rate' walks the axis by <n> per second and the\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"
|
"# position sticks (the throttle). Every lamp button is also clickable\n"
|
||||||
"# on the on-screen cockpit, so unbound addresses are never stranded.\n"
|
"# on the on-screen cockpit, so unbound addresses are never stranded.\n"
|
||||||
|
"#\n"
|
||||||
|
"# ---- Flight sticks, HOTAS throttles and rudder pedals --------------\n"
|
||||||
|
"#\n"
|
||||||
|
"# EASIEST: run joyconfig.bat once. It asks you to move each control,\n"
|
||||||
|
"# works out which device and axis you moved and which way round it\n"
|
||||||
|
"# reads, and writes the joy* rows below a marker line at the end of\n"
|
||||||
|
"# this file. Everything you have written yourself is kept. Xbox-class\n"
|
||||||
|
"# pads need none of this - they are the pad* rows above.\n"
|
||||||
|
"#\n"
|
||||||
|
"# By hand: joydev picks the device for the rows that follow it - a\n"
|
||||||
|
"# name substring binds that product, a bare slot number binds the Nth\n"
|
||||||
|
"# stick Windows lists. <src> for joyaxis is the DirectInput axis name,\n"
|
||||||
|
"# X Y Z RX RY RZ SL0 SL1: a twist grip is usually RZ and a HOTAS\n"
|
||||||
|
"# throttle usually Z or SL0. A joyaxis on Throttle with no 'rate' is\n"
|
||||||
|
"# treated as a real lever and OWNS the channel - its full travel is\n"
|
||||||
|
"# the full throttle range, rather than nudging the position the way a\n"
|
||||||
|
"# spring-centred pad stick has to.\n"
|
||||||
|
"#\n"
|
||||||
|
"# joydev 0 T.16000M\n"
|
||||||
|
"# joyaxis X axis JoystickX invert deadzone 0.08\n"
|
||||||
|
"# joyaxis Y axis JoystickY invert deadzone 0.08\n"
|
||||||
|
"# joyaxis RZ axis Pedals deadzone 0.08\n"
|
||||||
|
"# joyaxis SL0 axis Throttle deadzone 0\n"
|
||||||
|
"# joybutton 0 button 0x40\n"
|
||||||
|
"# joyhat 0 up button 0x42\n"
|
||||||
"\n"
|
"\n"
|
||||||
"# ---- Flight: number pad + modifiers -------------------------------\n"
|
"# ---- Flight: number pad + modifiers -------------------------------\n"
|
||||||
"# The whole letter board stays free for the MFD banks; flight lives\n"
|
"# The whole letter board stays free for the MFD banks; flight lives\n"
|
||||||
@@ -499,6 +663,7 @@ void
|
|||||||
char line[256];
|
char line[256];
|
||||||
int line_number = 0;
|
int line_number = 0;
|
||||||
int error_count = 0;
|
int error_count = 0;
|
||||||
|
int joy_slot = 0; // joy rows before any joydev belong to slot 0
|
||||||
const char *cursor = source;
|
const char *cursor = source;
|
||||||
while (*cursor != '\0')
|
while (*cursor != '\0')
|
||||||
{
|
{
|
||||||
@@ -523,7 +688,7 @@ void
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!ParseLine(tokens, token_count, profile))
|
if (!ParseLine(tokens, token_count, profile, &joy_slot))
|
||||||
{
|
{
|
||||||
++error_count;
|
++error_count;
|
||||||
DEBUG_STREAM << "PadBindings: " << kBindingsFileName << " line "
|
DEBUG_STREAM << "PadBindings: " << kBindingsFileName << " line "
|
||||||
@@ -542,4 +707,10 @@ void
|
|||||||
<< profile->padAxisCount << " pad axes"
|
<< profile->padAxisCount << " pad axes"
|
||||||
<< (error_count ? " (with rejected lines)" : "")
|
<< (error_count ? " (with rejected lines)" : "")
|
||||||
<< "\n" << std::flush;
|
<< "\n" << std::flush;
|
||||||
|
if (profile->joyAxisCount || profile->joyButtonCount || profile->joyHatCount)
|
||||||
|
{
|
||||||
|
DEBUG_STREAM << "PadBindings: joystick - " << profile->joyAxisCount
|
||||||
|
<< " axes, " << profile->joyButtonCount << " buttons, "
|
||||||
|
<< profile->joyHatCount << " hat directions\n" << std::flush;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,25 @@
|
|||||||
// key <name> axis <axis> deflect <n> | rate <n>
|
// key <name> axis <axis> deflect <n> | rate <n>
|
||||||
// pad <button> button <addr> [toggle]
|
// pad <button> button <addr> [toggle]
|
||||||
// padaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n>]
|
// padaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n>]
|
||||||
|
// joydev <slot> [product-name substring...]
|
||||||
|
// joyaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n>]
|
||||||
|
// joybutton <n> button <addr> [toggle]
|
||||||
|
// joyhat <n> <up|down|left|right> button <addr>
|
||||||
//
|
//
|
||||||
// <addr> is a RIO input address: lamp buttons 0x00-0x47, internal
|
// <addr> is a RIO input address: lamp buttons 0x00-0x47, internal
|
||||||
// keypad 0x50-0x5F, external keypad 0x60-0x6F. Loaded from
|
// keypad 0x50-0x5F, external keypad 0x60-0x6F. Loaded from
|
||||||
// bindings.txt beside the exe; written there (self-documenting, with
|
// bindings.txt beside the exe; written there (self-documenting, with
|
||||||
// the full default profile) on first run. Bad lines are logged and
|
// the full default profile) on first run. Bad lines are logged and
|
||||||
// skipped, good lines always win.
|
// skipped, good lines always win.
|
||||||
|
//
|
||||||
|
// The joy* rows drive generic DirectInput devices - flight sticks,
|
||||||
|
// HOTAS throttles, twist grips, rudder pedals (L4JOY.h). They attach to
|
||||||
|
// the most recent joydev slot, or slot 0 if no joydev came first. A slot
|
||||||
|
// naming a product substring binds THAT device; a bare slot binds the
|
||||||
|
// Nth attached non-XInput device. Source names follow the DirectInput
|
||||||
|
// layout - X Y Z RX RY RZ SL0 SL1 - where a twist grip is usually RZ
|
||||||
|
// and a HOTAS throttle usually Z or SL0. RP412JOYCONFIG=1 writes these
|
||||||
|
// rows for you by asking the player to move each control.
|
||||||
//########################################################################
|
//########################################################################
|
||||||
|
|
||||||
enum PadBindRioAxis
|
enum PadBindRioAxis
|
||||||
@@ -33,6 +46,14 @@ enum PadBindRioAxis
|
|||||||
BindAxisRightPedal,
|
BindAxisRightPedal,
|
||||||
BindAxisJoystickY,
|
BindAxisJoystickY,
|
||||||
BindAxisJoystickX,
|
BindAxisJoystickX,
|
||||||
|
//
|
||||||
|
// A signed composite, not a channel the pod has: positive presses
|
||||||
|
// the right pedal, negative the left. One physical control - a
|
||||||
|
// rudder bar, a twist grip, a stick axis - works the pedal pair the
|
||||||
|
// way a foot never could, one or the other and never both. It
|
||||||
|
// decomposes into the real pair when the poll applies it.
|
||||||
|
//
|
||||||
|
BindAxisPedals,
|
||||||
BindAxisCount
|
BindAxisCount
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -89,6 +110,54 @@ struct PadPadAxisBinding
|
|||||||
Scalar rate; // 0 = direct position, >0 = speed integrate
|
Scalar rate; // 0 = direct position, >0 = speed integrate
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Generic joystick (DirectInput - L4JOY.h). device is a joydev SLOT,
|
||||||
|
// resolved to a live device at poll time by the slot's name substring or
|
||||||
|
// by its ordinal, so unplugging and replugging does not rewrite the file.
|
||||||
|
//
|
||||||
|
enum { BindJoyDeviceSlots = 4 };
|
||||||
|
|
||||||
|
enum PadBindJoyAxis
|
||||||
|
{
|
||||||
|
BindJoyAxisX = 0,
|
||||||
|
BindJoyAxisY,
|
||||||
|
BindJoyAxisZ,
|
||||||
|
BindJoyAxisRX,
|
||||||
|
BindJoyAxisRY,
|
||||||
|
BindJoyAxisRZ,
|
||||||
|
BindJoyAxisSL0,
|
||||||
|
BindJoyAxisSL1,
|
||||||
|
BindJoyAxisCount
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PadJoyAxisBinding
|
||||||
|
{
|
||||||
|
int device; // joydev slot
|
||||||
|
int source; // PadBindJoyAxis
|
||||||
|
int axis; // PadBindRioAxis
|
||||||
|
Logical invert;
|
||||||
|
Scalar deadzone; // normalized 0..1
|
||||||
|
Scalar rate; // 0 = direct position, >0 = speed integrate
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PadJoyButtonBinding
|
||||||
|
{
|
||||||
|
int device; // joydev slot
|
||||||
|
int button; // 0-31
|
||||||
|
int address;
|
||||||
|
Logical toggle;
|
||||||
|
Logical wasDown;
|
||||||
|
Logical latched;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PadJoyHatBinding
|
||||||
|
{
|
||||||
|
int device; // joydev slot
|
||||||
|
int hat; // 0-3
|
||||||
|
int direction; // 0 up, 1 right, 2 down, 3 left
|
||||||
|
int address;
|
||||||
|
};
|
||||||
|
|
||||||
struct PadBindingProfile
|
struct PadBindingProfile
|
||||||
{
|
{
|
||||||
enum
|
enum
|
||||||
@@ -96,7 +165,10 @@ struct PadBindingProfile
|
|||||||
maxKeyButtons = 128,
|
maxKeyButtons = 128,
|
||||||
maxKeyAxes = 32,
|
maxKeyAxes = 32,
|
||||||
maxPadButtons = 32,
|
maxPadButtons = 32,
|
||||||
maxPadAxes = 16
|
maxPadAxes = 16,
|
||||||
|
maxJoyAxes = 24,
|
||||||
|
maxJoyButtons = 48,
|
||||||
|
maxJoyHats = 16
|
||||||
};
|
};
|
||||||
|
|
||||||
PadKeyButtonBinding keyButtons[maxKeyButtons];
|
PadKeyButtonBinding keyButtons[maxKeyButtons];
|
||||||
@@ -107,6 +179,15 @@ struct PadBindingProfile
|
|||||||
int padButtonCount;
|
int padButtonCount;
|
||||||
PadPadAxisBinding padAxes[maxPadAxes];
|
PadPadAxisBinding padAxes[maxPadAxes];
|
||||||
int padAxisCount;
|
int padAxisCount;
|
||||||
|
|
||||||
|
PadJoyAxisBinding joyAxes[maxJoyAxes];
|
||||||
|
int joyAxisCount;
|
||||||
|
PadJoyButtonBinding joyButtons[maxJoyButtons];
|
||||||
|
int joyButtonCount;
|
||||||
|
PadJoyHatBinding joyHats[maxJoyHats];
|
||||||
|
int joyHatCount;
|
||||||
|
// "" = take the slot's ordinal attached device
|
||||||
|
char joyDeviceMatch[BindJoyDeviceSlots][64];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load bindings.txt from the working directory into the profile,
|
// Load bindings.txt from the working directory into the profile,
|
||||||
|
|||||||
+214
-1
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "l4padrio.h"
|
#include "l4padrio.h"
|
||||||
#include "l4keylight.h"
|
#include "l4keylight.h"
|
||||||
|
#include "l4joy.h"
|
||||||
|
|
||||||
#include <XInput.h>
|
#include <XInput.h>
|
||||||
#pragma comment(lib, "xinput9_1_0.lib")
|
#pragma comment(lib, "xinput9_1_0.lib")
|
||||||
@@ -41,6 +42,51 @@ namespace
|
|||||||
return (GetAsyncKeyState(virtual_key) & 0x8000) != 0;
|
return (GetAsyncKeyState(virtual_key) & 0x8000) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// A generic stick axis is already normalized -1..1, so the deadzone
|
||||||
|
// is a plain cut about centre with the remainder rescaled - press
|
||||||
|
// just past the edge and you get just past zero, not a step.
|
||||||
|
//
|
||||||
|
Scalar JoyAxisValue(Scalar raw, Scalar deadzone)
|
||||||
|
{
|
||||||
|
if (deadzone <= 0.0f)
|
||||||
|
{
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
if (raw > -deadzone && raw < deadzone)
|
||||||
|
{
|
||||||
|
return (Scalar) 0;
|
||||||
|
}
|
||||||
|
Scalar value = (raw > 0.0f)
|
||||||
|
? (raw - deadzone) / (1.0f - deadzone)
|
||||||
|
: (raw + deadzone) / (1.0f - deadzone);
|
||||||
|
if (value > 1.0f) value = 1.0f;
|
||||||
|
if (value < -1.0f) value = -1.0f;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// A POV hat reports centidegrees clockwise from up, or -1 centered.
|
||||||
|
// The 45-degree window each way is what makes the diagonals press
|
||||||
|
// both of their neighbours, which is how a four-way hat is read.
|
||||||
|
//
|
||||||
|
Logical JoyHatHeld(int centidegrees, int direction)
|
||||||
|
{
|
||||||
|
if (centidegrees < 0)
|
||||||
|
{
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
int degrees = (centidegrees / 100) % 360;
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case 0: return (degrees >= 315 || degrees <= 45) ? True : False;
|
||||||
|
case 1: return (degrees >= 45 && degrees <= 135) ? True : False;
|
||||||
|
case 2: return (degrees >= 135 && degrees <= 225) ? True : False;
|
||||||
|
case 3: return (degrees >= 225 && degrees <= 315) ? True : False;
|
||||||
|
}
|
||||||
|
return False;
|
||||||
|
}
|
||||||
|
|
||||||
void KeyLightLog(const char *line)
|
void KeyLightLog(const char *line)
|
||||||
{
|
{
|
||||||
DEBUG_STREAM << line << "\n" << std::flush;
|
DEBUG_STREAM << line << "\n" << std::flush;
|
||||||
@@ -166,6 +212,29 @@ PadRIO::PadRIO()
|
|||||||
activeInstance = this;
|
activeInstance = this;
|
||||||
|
|
||||||
DEBUG_STREAM << "PadRIO: virtual RIO active (XInput pad + keyboard)\n" << std::flush;
|
DEBUG_STREAM << "PadRIO: virtual RIO active (XInput pad + keyboard)\n" << std::flush;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Only open DirectInput when the profile actually asks for it. A
|
||||||
|
// player on keyboard and pad should not pay for an enumeration of
|
||||||
|
// every HID on the machine, and joyconfig.bat is what writes the
|
||||||
|
// rows that turn this on.
|
||||||
|
//
|
||||||
|
if (profile.joyAxisCount > 0 || profile.joyButtonCount > 0 ||
|
||||||
|
profile.joyHatCount > 0)
|
||||||
|
{
|
||||||
|
int found = RPJoyInit();
|
||||||
|
DEBUG_STREAM << "PadRIO: joystick bindings present, " << found
|
||||||
|
<< " generic device(s) attached\n" << std::flush;
|
||||||
|
for (int d = 0; d < found; ++d)
|
||||||
|
{
|
||||||
|
const RPJoyDeviceState *state = RPJoyDevice(d);
|
||||||
|
if (state != NULL)
|
||||||
|
{
|
||||||
|
DEBUG_STREAM << "PadRIO: [" << d << "] " << state->name
|
||||||
|
<< "\n" << std::flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PadRIO::~PadRIO()
|
PadRIO::~PadRIO()
|
||||||
@@ -244,6 +313,11 @@ void
|
|||||||
profile.padButtons[i].latched = False;
|
profile.padButtons[i].latched = False;
|
||||||
profile.padButtons[i].wasDown = False;
|
profile.padButtons[i].wasDown = False;
|
||||||
}
|
}
|
||||||
|
for (int i = 0; i < profile.joyButtonCount; ++i)
|
||||||
|
{
|
||||||
|
profile.joyButtons[i].latched = False;
|
||||||
|
profile.joyButtons[i].wasDown = False;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -391,6 +465,81 @@ void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Generic joysticks. The slots are resolved every poll rather than
|
||||||
|
// cached, so a stick unplugged mid-race simply stops answering and
|
||||||
|
// one plugged back in picks up where it left off.
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
int joyDevice[BindJoyDeviceSlots];
|
||||||
|
Logical joyLive = False;
|
||||||
|
for (int slot = 0; slot < BindJoyDeviceSlots; ++slot)
|
||||||
|
{
|
||||||
|
joyDevice[slot] = -1;
|
||||||
|
}
|
||||||
|
if (profile.joyAxisCount > 0 || profile.joyButtonCount > 0 ||
|
||||||
|
profile.joyHatCount > 0)
|
||||||
|
{
|
||||||
|
RPJoyPoll();
|
||||||
|
for (int slot = 0; slot < BindJoyDeviceSlots; ++slot)
|
||||||
|
{
|
||||||
|
joyDevice[slot] = (profile.joyDeviceMatch[slot][0] != '\0')
|
||||||
|
? RPJoyFindDevice(profile.joyDeviceMatch[slot])
|
||||||
|
: ((RPJoyDevice(slot) != NULL) ? slot : -1);
|
||||||
|
if (joyDevice[slot] >= 0)
|
||||||
|
{
|
||||||
|
joyLive = True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < profile.joyButtonCount; ++i)
|
||||||
|
{
|
||||||
|
PadJoyButtonBinding *binding = &profile.joyButtons[i];
|
||||||
|
const RPJoyDeviceState *state =
|
||||||
|
(binding->device >= 0 && binding->device < BindJoyDeviceSlots)
|
||||||
|
? RPJoyDevice(joyDevice[binding->device]) : NULL;
|
||||||
|
Logical down = (state != NULL) &&
|
||||||
|
(state->buttons & (1u << binding->button)) != 0;
|
||||||
|
if (binding->toggle && down && !binding->wasDown)
|
||||||
|
{
|
||||||
|
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 < profile.joyHatCount; ++i)
|
||||||
|
{
|
||||||
|
const PadJoyHatBinding *binding = &profile.joyHats[i];
|
||||||
|
const RPJoyDeviceState *state =
|
||||||
|
(binding->device >= 0 && binding->device < BindJoyDeviceSlots)
|
||||||
|
? RPJoyDevice(joyDevice[binding->device]) : NULL;
|
||||||
|
if (state != NULL &&
|
||||||
|
JoyHatHeld(state->hat[binding->hat], binding->direction))
|
||||||
|
{
|
||||||
|
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)
|
for (int i = 0; i < buttonUnits; ++i)
|
||||||
{
|
{
|
||||||
if (screenButton[i])
|
if (screenButton[i])
|
||||||
@@ -502,6 +651,68 @@ void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Joystick axes. A physical throttle lever is the one source that
|
||||||
|
// does not add into the pile: it has an absolute position, so its
|
||||||
|
// full travel IS the channel and it takes ownership rather than
|
||||||
|
// nudging an accumulator that a spring-centred pad stick has to.
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
Logical throttleLever = False;
|
||||||
|
Scalar throttleLeverValue = (Scalar) 0;
|
||||||
|
|
||||||
|
if (joyLive)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < profile.joyAxisCount; ++i)
|
||||||
|
{
|
||||||
|
const PadJoyAxisBinding *binding = &profile.joyAxes[i];
|
||||||
|
if (binding->device < 0 || binding->device >= BindJoyDeviceSlots)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const RPJoyDeviceState *state = RPJoyDevice(joyDevice[binding->device]);
|
||||||
|
if (state == NULL)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Scalar raw = (Scalar) state->axis[binding->source];
|
||||||
|
if (binding->invert)
|
||||||
|
{
|
||||||
|
raw = -raw;
|
||||||
|
}
|
||||||
|
if (binding->axis == BindAxisThrottle && binding->rate == 0.0f)
|
||||||
|
{
|
||||||
|
// -1..1 of lever travel onto the 0..1 the pod runs on
|
||||||
|
throttleLeverValue = (raw + 1.0f) * 0.5f;
|
||||||
|
throttleLever = True;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Scalar value = JoyAxisValue(raw, binding->deadzone);
|
||||||
|
if (binding->rate > 0.0f)
|
||||||
|
{
|
||||||
|
rate[binding->axis] += value * binding->rate;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
deflect[binding->axis] += value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// The composite pedal axis becomes the pair the pod actually has.
|
||||||
|
// One signed source presses one pedal or the other, never both,
|
||||||
|
// which is what a rudder bar or a twist grip does.
|
||||||
|
//
|
||||||
|
Scalar pedals = deflect[BindAxisPedals];
|
||||||
|
if (pedals > 0.0f)
|
||||||
|
{
|
||||||
|
deflect[BindAxisRightPedal] += pedals;
|
||||||
|
}
|
||||||
|
else if (pedals < 0.0f)
|
||||||
|
{
|
||||||
|
deflect[BindAxisLeftPedal] += -pedals;
|
||||||
|
}
|
||||||
|
|
||||||
throttleAccum = Clamp01(throttleAccum + rate[BindAxisThrottle] * delta_t);
|
throttleAccum = Clamp01(throttleAccum + rate[BindAxisThrottle] * delta_t);
|
||||||
|
|
||||||
Scalar x = deflect[BindAxisJoystickX];
|
Scalar x = deflect[BindAxisJoystickX];
|
||||||
@@ -511,7 +722,9 @@ void
|
|||||||
if (y > 1.0f) y = 1.0f;
|
if (y > 1.0f) y = 1.0f;
|
||||||
if (y < -1.0f) y = -1.0f;
|
if (y < -1.0f) y = -1.0f;
|
||||||
|
|
||||||
Throttle = Clamp01(throttleAccum + deflect[BindAxisThrottle]);
|
Throttle = throttleLever
|
||||||
|
? Clamp01(throttleLeverValue)
|
||||||
|
: Clamp01(throttleAccum + deflect[BindAxisThrottle]);
|
||||||
LeftPedal = Clamp01(deflect[BindAxisLeftPedal]);
|
LeftPedal = Clamp01(deflect[BindAxisLeftPedal]);
|
||||||
RightPedal = Clamp01(deflect[BindAxisRightPedal]);
|
RightPedal = Clamp01(deflect[BindAxisRightPedal]);
|
||||||
// The profile encodes the pod's stick sign convention; L4PADFLIP
|
// The profile encodes the pod's stick sign convention; L4PADFLIP
|
||||||
|
|||||||
@@ -263,6 +263,7 @@
|
|||||||
<ConformanceMode>true</ConformanceMode>
|
<ConformanceMode>true</ConformanceMode>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include=".\L4PADBINDINGS.cpp" />
|
<ClCompile Include=".\L4PADBINDINGS.cpp" />
|
||||||
|
<ClCompile Include=".\L4JOY.cpp" />
|
||||||
<ClCompile Include=".\L4PADRIO.cpp" />
|
<ClCompile Include=".\L4PADRIO.cpp" />
|
||||||
<ClCompile Include=".\L4PCSPAK.cpp" />
|
<ClCompile Include=".\L4PCSPAK.cpp" />
|
||||||
<ClCompile Include=".\L4PLASMA.cpp" />
|
<ClCompile Include=".\L4PLASMA.cpp" />
|
||||||
@@ -454,6 +455,7 @@
|
|||||||
<ClInclude Include=".\L4NETTRANSPORT.h" />
|
<ClInclude Include=".\L4NETTRANSPORT.h" />
|
||||||
<ClInclude Include=".\L4KEYLIGHT.h" />
|
<ClInclude Include=".\L4KEYLIGHT.h" />
|
||||||
<ClInclude Include=".\L4PADBINDINGS.h" />
|
<ClInclude Include=".\L4PADBINDINGS.h" />
|
||||||
|
<ClInclude Include=".\L4JOY.h" />
|
||||||
<ClInclude Include=".\L4STEAMTRANSPORT.h" />
|
<ClInclude Include=".\L4STEAMTRANSPORT.h" />
|
||||||
<ClInclude Include=".\L4PARTICLES.h" />
|
<ClInclude Include=".\L4PARTICLES.h" />
|
||||||
<ClInclude Include=".\L4MFDVIEW.h" />
|
<ClInclude Include=".\L4MFDVIEW.h" />
|
||||||
|
|||||||
@@ -75,6 +75,18 @@ Winners Circle camera is framed off the award stand itself rather than
|
|||||||
off whoever is standing on it, so the shot is the same one for every
|
off whoever is standing on it, so the shot is the same one for every
|
||||||
player at every head count.
|
player at every head count.
|
||||||
|
|
||||||
|
[v4.12.7](https://gitea.mysticmachines.com/VWE/RP412/releases/tag/v4.12.7)
|
||||||
|
is about sticks. Anything that is not an Xbox-class pad — a flight stick,
|
||||||
|
a HOTAS throttle, a twist grip, rudder pedals, a wheel — comes in through
|
||||||
|
DirectInput rather than XInput, and the game could not see any of it.
|
||||||
|
Now it can, and `joyconfig.bat` sets it up: the wizard asks you to move
|
||||||
|
each control in turn, works out which device and axis answered and which
|
||||||
|
way round it reads, and writes the joystick rows of `bindings.txt`,
|
||||||
|
leaving anything you have edited yourself alone. A twist grip or rudder
|
||||||
|
bar drives both pedals through one signed `Pedals` axis, and a real
|
||||||
|
throttle lever owns its channel outright rather than nudging a position
|
||||||
|
the way a spring-centred stick has to. Ported from the sibling BT411.
|
||||||
|
|
||||||
## Playing
|
## Playing
|
||||||
|
|
||||||
Grab the release zip (or run `pack-dist.ps1` on a build). Single player:
|
Grab the release zip (or run `pack-dist.ps1` on a build). Single player:
|
||||||
|
|||||||
+24
-1
@@ -27,6 +27,7 @@
|
|||||||
#include "..\munga_l4\l4steamtransport.h"
|
#include "..\munga_l4\l4steamtransport.h"
|
||||||
#include "..\munga_l4\l4splr.h"
|
#include "..\munga_l4\l4splr.h"
|
||||||
#include "..\munga_l4\l4mfdview.h" // RPWindowLayout_*
|
#include "..\munga_l4\l4mfdview.h" // RPWindowLayout_*
|
||||||
|
#include "..\munga_l4\l4joy.h" // RPJoyConfigWizard
|
||||||
#include "rpl4ver.h"
|
#include "rpl4ver.h"
|
||||||
#include "..\munga\resver.h"
|
#include "..\munga\resver.h"
|
||||||
#include "..\munga\resource.h"
|
#include "..\munga\resource.h"
|
||||||
@@ -187,7 +188,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
fclose(file);
|
fclose(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_STREAM << "Red Planet 4.12.6" << std::endl << std::flush;
|
DEBUG_STREAM << "Red Planet 4.12.7" << std::endl << std::flush;
|
||||||
DEBUG_STREAM << "L4CONTROLS=" << getenv("L4CONTROLS") << std::endl << std::flush;
|
DEBUG_STREAM << "L4CONTROLS=" << getenv("L4CONTROLS") << std::endl << std::flush;
|
||||||
|
|
||||||
#ifdef RP412_STEAM
|
#ifdef RP412_STEAM
|
||||||
@@ -279,6 +280,28 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
RPWindowLayout_Load();
|
RPWindowLayout_Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// RP412JOYCONFIG=1 (joyconfig.bat): the joystick setup wizard, before
|
||||||
|
// anything else claims the screen. It asks the player to move each
|
||||||
|
// control on their stick, HOTAS or pedals and writes the joy* rows of
|
||||||
|
// bindings.txt, then falls through into the game so they can try them
|
||||||
|
// straight away.
|
||||||
|
//
|
||||||
|
// One shot: the variable is cleared from this process so a rebuilt
|
||||||
|
// PadRIO later in the session cannot run the wizard a second time.
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
{
|
||||||
|
const char *joyconfig = getenv("RP412JOYCONFIG");
|
||||||
|
if (joyconfig != NULL && *joyconfig != '\0' && atoi(joyconfig) != 0)
|
||||||
|
{
|
||||||
|
RPJoyConfigWizard();
|
||||||
|
SetEnvironmentVariableA("RP412JOYCONFIG", NULL);
|
||||||
|
_putenv("RP412JOYCONFIG=");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if !_DEBUG
|
#if !_DEBUG
|
||||||
// Arcade pods have no mouse - but desktop/windowed play needs the
|
// Arcade pods have no mouse - but desktop/windowed play needs the
|
||||||
// cursor for the on-screen cockpit buttons, so hide it only when
|
// cursor for the on-screen cockpit buttons, so hide it only when
|
||||||
|
|||||||
+37
-1
@@ -144,6 +144,42 @@ it off.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Flight sticks, HOTAS and rudder pedals
|
||||||
|
|
||||||
|
Anything that is not an Xbox-class pad — a flight stick, a HOTAS
|
||||||
|
throttle, a twist grip, rudder pedals, a wheel — comes in through
|
||||||
|
DirectInput rather than XInput, and needs to be told which axis is
|
||||||
|
which. **Run `joyconfig.bat` once.** It asks you to move each control in
|
||||||
|
turn, works out which device and axis you moved and which way round it
|
||||||
|
reads, and writes the joystick rows of `bindings.txt`. Then it carries
|
||||||
|
on into the setup screen so you can try them straight away.
|
||||||
|
|
||||||
|
It only writes its own section, between two marker lines, so anything
|
||||||
|
you have edited yourself survives re-running it. Xbox-class controllers
|
||||||
|
need none of this — they are the pad rows above and work out of the box.
|
||||||
|
|
||||||
|
By hand, the rows look like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
joydev 0 T.16000M # or a bare slot number
|
||||||
|
joyaxis X axis JoystickX invert deadzone 0.08
|
||||||
|
joyaxis Y axis JoystickY invert deadzone 0.08
|
||||||
|
joyaxis RZ axis Pedals deadzone 0.08 # twist grip works both pedals
|
||||||
|
joyaxis SL0 axis Throttle deadzone 0 # a real lever owns the channel
|
||||||
|
joybutton 0 button 0x40 # trigger
|
||||||
|
joyhat 0 up button 0x42
|
||||||
|
```
|
||||||
|
|
||||||
|
Axis names are DirectInput's — `X Y Z RX RY RZ SL0 SL1` — where a twist
|
||||||
|
grip is usually `RZ` and a HOTAS throttle usually `Z` or `SL0`.
|
||||||
|
|
||||||
|
Two things are worth knowing. `Pedals` is a signed axis that drives the
|
||||||
|
pedal *pair*, positive for the right pedal and negative for the left, so
|
||||||
|
one rudder bar or twist grip works both the way a foot never could. And
|
||||||
|
a `joyaxis` on `Throttle` with no `rate` is treated as a real lever: its
|
||||||
|
full travel *is* the throttle range, rather than nudging a position the
|
||||||
|
way a spring-centred pad stick has to.
|
||||||
|
|
||||||
## Rebinding
|
## Rebinding
|
||||||
|
|
||||||
`bindings.txt` sits beside the exe, written with the full default layout
|
`bindings.txt` sits beside the exe, written with the full default layout
|
||||||
@@ -163,7 +199,7 @@ padaxis LeftStickX axis JoystickX deadzone 0.15 # drop 'invert' to flip X
|
|||||||
|
|
||||||
Addresses: `0x00`–`0x47` are the lamp buttons above, `0x50`–`0x6F` the
|
Addresses: `0x00`–`0x47` are the lamp buttons above, `0x50`–`0x6F` the
|
||||||
keypads. Axis names: `Throttle`, `LeftPedal`, `RightPedal`, `JoystickX`,
|
keypads. Axis names: `Throttle`, `LeftPedal`, `RightPedal`, `JoystickX`,
|
||||||
`JoystickY`. Key names follow the .NET `Keys` naming (`A`–`Z`, `D0`–`D9`
|
`JoystickY`, `Pedals`. Key names follow the .NET `Keys` naming (`A`–`Z`, `D0`–`D9`
|
||||||
for the digit row, `F1`–`F12`, `NumPad0`–`NumPad9`, `Up`, `Down`,
|
for the digit row, `F1`–`F12`, `NumPad0`–`NumPad9`, `Up`, `Down`,
|
||||||
`Left`, `Right`, `Space`, `Return`, `Shift`, `Ctrl`, `Alt`,
|
`Left`, `Right`, `Space`, `Return`, `Shift`, `Ctrl`, `Alt`,
|
||||||
`OemMinus`, `Oemplus`, `Oemcomma`, `OemPeriod`, `OemOpenBrackets`,
|
`OemMinus`, `Oemplus`, `Oemcomma`, `OemPeriod`, `OemOpenBrackets`,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<title>Red Planet 4.12.6 — Controls</title>
|
<title>Red Planet 4.12.7 — Controls</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
@@ -754,7 +754,7 @@
|
|||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
|
|
||||||
<header class="masthead">
|
<header class="masthead">
|
||||||
<p class="eyebrow">Virtual World Entertainment · Tesla pod · RP 4.12.6</p>
|
<p class="eyebrow">Virtual World Entertainment · Tesla pod · RP 4.12.7</p>
|
||||||
<h1>Red Planet<br>Controls Map</h1>
|
<h1>Red Planet<br>Controls Map</h1>
|
||||||
<p class="lede">
|
<p class="lede">
|
||||||
Every input the pod answers to, on the gamepad and the keyboard. The
|
Every input the pod answers to, on the gamepad and the keyboard. The
|
||||||
@@ -1197,7 +1197,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<span>Red Planet 4.12.6</span>
|
<span>Red Planet 4.12.7</span>
|
||||||
<span>gitea.mysticmachines.com/VWE/RP412</span>
|
<span>gitea.mysticmachines.com/VWE/RP412</span>
|
||||||
<span>docs/CONTROLS.md · CONTROLS.txt ships with the game</span>
|
<span>docs/CONTROLS.md · CONTROLS.txt ships with the game</span>
|
||||||
<span>RGB keyboards mirror the pod lamps · RP412KEYLIGHT=0 to disable</span>
|
<span>RGB keyboards mirror the pod lamps · RP412KEYLIGHT=0 to disable</span>
|
||||||
|
|||||||
+24
-2
@@ -378,14 +378,36 @@ cd /d "%~dp0"
|
|||||||
start rpl4opt.exe -fit
|
start rpl4opt.exe -fit
|
||||||
"@
|
"@
|
||||||
|
|
||||||
|
Set-Content -Path "$dist\joyconfig.bat" -Encoding ascii -Value @"
|
||||||
|
@echo off
|
||||||
|
rem Red Planet 4.12 - one-time JOYSTICK / HOTAS / rudder-pedal setup.
|
||||||
|
rem A console wizard asks you to move each control (stick, twist or
|
||||||
|
rem rudder, throttle lever, fire buttons); it works out what you moved
|
||||||
|
rem and which way it reads, and writes the joystick section of
|
||||||
|
rem bindings.txt. When it finishes the game carries on into the setup
|
||||||
|
rem screen so you can try the bindings straight away.
|
||||||
|
rem Xbox-class controllers need NO setup - they work out of the box.
|
||||||
|
rem Re-run this any time to redo the bindings; anything you have edited
|
||||||
|
rem yourself in bindings.txt is kept.
|
||||||
|
cd /d "%~dp0"
|
||||||
|
set RP412JOYCONFIG=1
|
||||||
|
start /wait rpl4opt.exe -windowed -res 1920 1080
|
||||||
|
set RP412JOYCONFIG=
|
||||||
|
"@
|
||||||
|
|
||||||
Set-Content -Path "$dist\README.txt" -Encoding ascii -Value @"
|
Set-Content -Path "$dist\README.txt" -Encoding ascii -Value @"
|
||||||
Red Planet 4.12.6
|
Red Planet 4.12.7
|
||||||
=================
|
=================
|
||||||
|
|
||||||
Run start-fullscreen.bat for borderless over the whole monitor, or
|
Run start-fullscreen.bat for borderless over the whole monitor, or
|
||||||
start-windowed.bat to keep a title bar. No cockpit hardware needed.
|
start-windowed.bat to keep a title bar. No cockpit hardware needed.
|
||||||
If there is no sound, run oalinst.exe once.
|
If there is no sound, run oalinst.exe once.
|
||||||
|
|
||||||
|
Got a flight stick, HOTAS or rudder pedals? Run joyconfig.bat once. It
|
||||||
|
asks you to move each control, works out which axis you moved and which
|
||||||
|
way round it reads, and writes the joystick rows of bindings.txt. Xbox-
|
||||||
|
class controllers need none of that - they work out of the box.
|
||||||
|
|
||||||
Controls (XInput controller and/or keyboard) - EVERY input is
|
Controls (XInput controller and/or keyboard) - EVERY input is
|
||||||
rebindable: edit bindings.txt beside the exe (written with the full
|
rebindable: edit bindings.txt beside the exe (written with the full
|
||||||
documented default layout on first run; delete it to restore).
|
documented default layout on first run; delete it to restore).
|
||||||
@@ -444,7 +466,7 @@ $size = (Get-ChildItem $dist -Recurse | Measure-Object Length -Sum).Sum
|
|||||||
Write-Host ("dist ready: {0:N1} MB" -f ($size / 1MB))
|
Write-Host ("dist ready: {0:N1} MB" -f ($size / 1MB))
|
||||||
|
|
||||||
if ($Zip) {
|
if ($Zip) {
|
||||||
$zipPath = Join-Path $root 'RedPlanet-4.12.6.zip'
|
$zipPath = Join-Path $root 'RedPlanet-4.12.7.zip'
|
||||||
Write-Host "zipping to $zipPath..."
|
Write-Host "zipping to $zipPath..."
|
||||||
|
|
||||||
# Everything lives under a single RP412\ folder inside the zip, so
|
# Everything lives under a single RP412\ folder inside the zip, so
|
||||||
|
|||||||
Reference in New Issue
Block a user