The joystick wizard works out the shape of your pedals
You are never asked what you own. Two controls cannot simply be watched, so they are asked for differently. Yaw is asked for twice, right then left, and which axis answers is the measurement. The same axis both times is one control covering both directions - a twist grip, a rudder bar, pedals the driver has already mixed - and binds to the signed Pedals axis. Two different axes are two real pedals, one per foot, which is what the pod had, so they bind to the pod's own LeftPedal/RightPedal pair and the game does the mixing: both at once then does what both at once did in the pod. The throttle is zeroed first. A lever sits wherever it was last left, possibly hard against the stop that reads +1, so watching it move says nothing about which end means power. Close it, press SPACE, then open it, and the direction it travels from a known idle is the direction that means throttle. CONTROLS.md, the handbook and the packaged README say all of this, and joyconfig.bat's own header no longer promises "rudder-pedal setup" when racing pedals work too.
This commit is contained in:
+387
-47
@@ -508,13 +508,67 @@ int
|
||||
// common, and no amount of documentation gets a player to work out which
|
||||
// they own.
|
||||
//
|
||||
// The same principle runs deeper than the sign. A player should not have
|
||||
// to know the SHAPE of their own rig either, so the wizard works that
|
||||
// out too, and the two controls that cannot simply be watched are asked
|
||||
// for differently:
|
||||
//
|
||||
// yaw asked for twice, right then left. One axis answering both
|
||||
// is a twist grip or rudder bar - the signed Pedals
|
||||
// composite. Two different axes are two real pedals, one per
|
||||
// foot, the pod's own arrangement, bound to the real pair.
|
||||
//
|
||||
// throttle a lever sits wherever it was left, so no movement of it
|
||||
// says which end is open. The player is asked to put it at
|
||||
// ZERO and say so; the reading is taken there, and the
|
||||
// direction it travels from a known idle means power.
|
||||
//
|
||||
//########################################################################
|
||||
|
||||
#include <conio.h>
|
||||
#include <XInput.h>
|
||||
#include "l4padbindings.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
//
|
||||
// Xbox-class pads are kept out of the capture on purpose: their
|
||||
// layout is fixed and NAMED, so unlike a DirectInput axis there is
|
||||
// nothing to identify by watching, and letting one answer a prompt
|
||||
// would only bind it twice. Invisible is the wrong answer though -
|
||||
// a player whose whole rig is a pad, or a wheel running in XInput
|
||||
// mode, should be told it is already mapped rather than left reading
|
||||
// "no devices found" and wondering what is broken.
|
||||
//
|
||||
int WizardXInputSlot(void)
|
||||
{
|
||||
XINPUT_STATE state;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (XInputGetState((DWORD) i, &state) == ERROR_SUCCESS)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//
|
||||
// What an Xbox-class pad already does, said once and in one place.
|
||||
// The triggers are the interesting half: XInput reports each as its
|
||||
// own 0..255 byte rather than two halves of a shared axis, which is
|
||||
// the pod's two-pedal arrangement exactly, and unipolar already - no
|
||||
// 'lever' to fold, no sign to discover.
|
||||
//
|
||||
void WizardReportXInput(int slot)
|
||||
{
|
||||
printf(" [XInput slot %d] Xbox-class controller - ALREADY MAPPED, and\n"
|
||||
" not part of this setup. Its two triggers are the pod's\n"
|
||||
" left and right pedals, the left stick is the joystick and\n"
|
||||
" the right stick the throttle. Edit the pad rows of\n"
|
||||
" bindings.txt by hand to change any of that.\n", slot);
|
||||
}
|
||||
|
||||
struct WizardCapture
|
||||
{
|
||||
int used;
|
||||
@@ -532,6 +586,39 @@ namespace
|
||||
return (axis >= 0 && axis < joyAxisCount) ? names[axis] : "?";
|
||||
}
|
||||
|
||||
//
|
||||
// A pedal is a ONE-WAY control: its spring holds it at the released
|
||||
// end of its travel, so the direction of the press is the whole
|
||||
// story and the row it writes says 'lever' - the -1..1 axis the
|
||||
// driver reports then folds onto the 0..1 the channel runs on
|
||||
// instead of throwing away the half that reads below zero.
|
||||
//
|
||||
// Where it RESTS is what decides that, and the wizard can see it.
|
||||
// An axis sitting near the MIDDLE is not a pedal at all - a stick
|
||||
// axis pressed into service as one - and already reads zero at
|
||||
// rest, so 'lever' would jam it at half depression for good.
|
||||
//
|
||||
void WizardWritePedal(WizardCapture *capture, int axis, float rest,
|
||||
float delta, const char *channel)
|
||||
{
|
||||
capture->invert = (delta < 0.0f);
|
||||
if (rest > 0.5f || rest < -0.5f)
|
||||
{
|
||||
sprintf(capture->line, "joyaxis %s axis %s%s lever deadzone 0.05",
|
||||
JoyAxisToken(axis), channel,
|
||||
capture->invert ? " invert" : "");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" (%s rests near centre rather than at one end, so it\n"
|
||||
" is bound as a plain axis rather than as a pedal)\n",
|
||||
JoyAxisToken(axis));
|
||||
sprintf(capture->line, "joyaxis %s axis %s%s deadzone 0.08",
|
||||
JoyAxisToken(axis), channel,
|
||||
capture->invert ? " invert" : "");
|
||||
}
|
||||
}
|
||||
|
||||
void WizardBaseline(float baseline[joyMaxDevices][joyAxisCount])
|
||||
{
|
||||
//
|
||||
@@ -746,11 +833,30 @@ int
|
||||
PadBindings_Load(&ensure_default);
|
||||
}
|
||||
|
||||
int xinput_slot = WizardXInputSlot();
|
||||
|
||||
if (RPJoyInit() == 0)
|
||||
{
|
||||
printf("No generic (non-Xbox) game devices found.\n");
|
||||
printf("Plug in the stick, throttle or pedals and run joyconfig again.\n");
|
||||
printf("(Xbox-class controllers already work - no setup needed.)\n\n");
|
||||
if (xinput_slot >= 0)
|
||||
{
|
||||
//
|
||||
// Not a failure, and it should not read like one: the pad IS
|
||||
// the rig, and it is already configured. Say what it does
|
||||
// rather than asking for hardware they have not got.
|
||||
//
|
||||
printf("Nothing here needs configuring.\n\n");
|
||||
WizardReportXInput(xinput_slot);
|
||||
printf("\nThere are no generic (DirectInput) sticks, throttles or\n");
|
||||
printf("pedals attached, and those are the only thing this setup\n");
|
||||
printf("has to work out. Plug one in and run joyconfig again if\n");
|
||||
printf("you add one.\n\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("No generic (non-Xbox) game devices found.\n");
|
||||
printf("Plug in the stick, throttle or pedals and run joyconfig again.\n");
|
||||
printf("(Xbox-class controllers already work - no setup needed.)\n\n");
|
||||
}
|
||||
printf("Press any key to exit.\n");
|
||||
_getch();
|
||||
return 1;
|
||||
@@ -783,48 +889,51 @@ int
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
//
|
||||
// Listed with the rest so a player who squeezes a trigger at a
|
||||
// prompt and sees nothing happen knows why, rather than deciding
|
||||
// the wizard cannot see their pad.
|
||||
//
|
||||
if (xinput_slot >= 0)
|
||||
{
|
||||
WizardReportXInput(xinput_slot);
|
||||
}
|
||||
}
|
||||
printf("\nFor each prompt, MOVE the control you want, or press SPACE to\n"
|
||||
"skip it, ESC to abort. Keep everything else still.\n\n");
|
||||
|
||||
WizardCapture captures[16];
|
||||
memset(captures, 0, sizeof(captures));
|
||||
int capture_count = 0;
|
||||
|
||||
//
|
||||
// The pod's analog channels. wants_negative says the asked-for move
|
||||
// should read NEGATIVE in the pod's sign convention, which is what
|
||||
// decides whether the captured axis gets an invert:
|
||||
// The stick, whose two axes are spring-centred and so give their
|
||||
// sign away the moment they move. wants_negative says the asked-for
|
||||
// move should read NEGATIVE in the pod's sign convention, which is
|
||||
// what decides whether the captured axis gets an invert:
|
||||
//
|
||||
// JoystickX left +1, right -1
|
||||
// JoystickY forward -1, back +1
|
||||
// Pedals right +1, left -1 (the composite that decomposes
|
||||
// into the pod's two pedals)
|
||||
//
|
||||
// Yaw and the throttle are not this simple and are asked for below.
|
||||
//
|
||||
struct AxisStep
|
||||
{
|
||||
const char *prompt;
|
||||
const char *channel;
|
||||
int wants_negative;
|
||||
int lever; // full-travel lever: sign from where
|
||||
// it ENDS, not which way it moved
|
||||
int allow_skip;
|
||||
};
|
||||
static const AxisStep axisSteps[] =
|
||||
{
|
||||
{ "STEER: push the STICK / turn the WHEEL fully RIGHT",
|
||||
"JoystickX", 1, 0, 0 },
|
||||
"JoystickX", 1 },
|
||||
{ "PITCH: push the STICK fully FORWARD\n"
|
||||
" (add or remove the word invert on that line in\n"
|
||||
" bindings.txt to flip it later)",
|
||||
"JoystickY", 1, 0, 0 },
|
||||
{ "PEDALS: twist the stick / press the RIGHT rudder pedal\n"
|
||||
" (SPACE if you have neither)",
|
||||
"Pedals", 0, 0, 1 },
|
||||
{ "THROTTLE: move the throttle lever to FULL (SPACE if none)",
|
||||
"Throttle", 0, 1, 1 }
|
||||
"JoystickY", 1 }
|
||||
};
|
||||
|
||||
printf("\nFor each prompt, MOVE the control you want, or press SPACE to\n"
|
||||
"skip it, ESC to abort. Keep everything else still.\n\n");
|
||||
|
||||
float baseline[joyMaxDevices][joyAxisCount];
|
||||
|
||||
for (int s = 0; s < (int)(sizeof(axisSteps) / sizeof(axisSteps[0])); ++s)
|
||||
@@ -834,7 +943,7 @@ int
|
||||
int device, axis;
|
||||
float delta, final_value;
|
||||
int got = WizardCaptureAxis(baseline, captures, capture_count,
|
||||
axisSteps[s].allow_skip, &device, &axis, &delta, &final_value);
|
||||
0, &device, &axis, &delta, &final_value);
|
||||
if (got < 0)
|
||||
{
|
||||
printf("\nAborted - nothing written.\n");
|
||||
@@ -842,38 +951,18 @@ int
|
||||
_getch();
|
||||
return 1;
|
||||
}
|
||||
if (got == 0)
|
||||
{
|
||||
printf(" skipped.\n\n");
|
||||
continue;
|
||||
}
|
||||
WizardCapture &capture = captures[capture_count++];
|
||||
capture.used = 1;
|
||||
capture.device = device;
|
||||
capture.axis = axis;
|
||||
capture.button = -1;
|
||||
|
||||
if (axisSteps[s].lever)
|
||||
{
|
||||
//
|
||||
// A lever has no rest position to move away from, so the
|
||||
// sign comes from where it finished: full-forward reading
|
||||
// negative means the axis runs backwards for us.
|
||||
//
|
||||
capture.invert = (final_value < 0.0f);
|
||||
sprintf(capture.line, "joyaxis %s axis %s%s deadzone 0",
|
||||
JoyAxisToken(axis), axisSteps[s].channel,
|
||||
capture.invert ? " invert" : "");
|
||||
}
|
||||
else
|
||||
{
|
||||
int went_negative = (delta < 0.0f);
|
||||
capture.invert = axisSteps[s].wants_negative
|
||||
? !went_negative : went_negative;
|
||||
sprintf(capture.line, "joyaxis %s axis %s%s deadzone 0.08",
|
||||
JoyAxisToken(axis), axisSteps[s].channel,
|
||||
capture.invert ? " invert" : "");
|
||||
}
|
||||
int went_negative = (delta < 0.0f);
|
||||
capture.invert = axisSteps[s].wants_negative
|
||||
? !went_negative : went_negative;
|
||||
sprintf(capture.line, "joyaxis %s axis %s%s deadzone 0.08",
|
||||
JoyAxisToken(axis), axisSteps[s].channel,
|
||||
capture.invert ? " invert" : "");
|
||||
//
|
||||
// The move is reported, not just the axis: a capture nobody made
|
||||
// shows up here as a small delta, and a player who wonders why
|
||||
@@ -887,6 +976,257 @@ int
|
||||
Sleep(800); // let the control come back to rest
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Yaw. The pod steered on two foot pedals mixed into the turn, and
|
||||
// hardware answers that in two shapes - but a player should not have
|
||||
// to know which shape they own, and plenty do not. So ask for RIGHT,
|
||||
// then ask for LEFT, and watch WHICH axis answers each time:
|
||||
//
|
||||
// the same axis twice one control covering both directions - a
|
||||
// twist grip, a rudder bar, pedals whose
|
||||
// driver has already mixed them - which is
|
||||
// the signed Pedals composite
|
||||
//
|
||||
// two different axes two real pedals, one per foot, which is
|
||||
// what the pod itself had. They bind to the
|
||||
// pod's own pair and the game does the
|
||||
// mixing, so both at once does what both at
|
||||
// once did in the pod.
|
||||
//
|
||||
// The LEFT capture is deliberately offered the RIGHT axis again -
|
||||
// the usual claimed-axis exclusion would make every rig look like a
|
||||
// pair, since "the same axis answered twice" is the measurement.
|
||||
//---------------------------------------------------------------
|
||||
{
|
||||
printf("YAW RIGHT: press the RIGHT rudder pedal, or twist / push\n"
|
||||
" the stick RIGHT (SPACE if you have no yaw control) ...\n");
|
||||
WizardBaseline(baseline);
|
||||
int right_device, right_axis;
|
||||
float right_delta, right_final;
|
||||
int got = WizardCaptureAxis(baseline, captures, capture_count, 1,
|
||||
&right_device, &right_axis, &right_delta, &right_final);
|
||||
if (got < 0)
|
||||
{
|
||||
printf("\nAborted - nothing written.\n");
|
||||
printf("Press any key to continue into the game.\n");
|
||||
_getch();
|
||||
return 1;
|
||||
}
|
||||
if (got == 0)
|
||||
{
|
||||
printf(" skipped - no yaw control.\n\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
float right_rest = baseline[right_device][right_axis];
|
||||
printf(" -> device %d (%s) axis %s [moved %+.2f]\n",
|
||||
right_device,
|
||||
(RPJoyDevice(right_device) != NULL)
|
||||
? RPJoyDevice(right_device)->name : "?",
|
||||
JoyAxisToken(right_axis), right_delta);
|
||||
Sleep(800); // let it come back to rest before we re-baseline
|
||||
|
||||
printf("YAW LEFT: now the other way - press the LEFT pedal, or\n"
|
||||
" twist / push the stick LEFT ...\n");
|
||||
WizardBaseline(baseline);
|
||||
int left_device, left_axis;
|
||||
float left_delta, left_final;
|
||||
int got_left = WizardCaptureAxis(baseline, captures, capture_count,
|
||||
1, &left_device, &left_axis, &left_delta, &left_final);
|
||||
if (got_left < 0)
|
||||
{
|
||||
printf("\nAborted - nothing written.\n");
|
||||
printf("Press any key to continue into the game.\n");
|
||||
_getch();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int same_axis = (got_left == 0) ||
|
||||
(left_device == right_device && left_axis == right_axis);
|
||||
if (got_left != 0)
|
||||
{
|
||||
printf(" -> device %d (%s) axis %s [moved %+.2f]\n",
|
||||
left_device,
|
||||
(RPJoyDevice(left_device) != NULL)
|
||||
? RPJoyDevice(left_device)->name : "?",
|
||||
JoyAxisToken(left_axis), left_delta);
|
||||
}
|
||||
|
||||
if (same_axis)
|
||||
{
|
||||
//
|
||||
// One axis, both ways: the signed composite, positive
|
||||
// for the right pedal. Signed from the RIGHT answer,
|
||||
// which is the one the convention is written in.
|
||||
//
|
||||
WizardCapture &capture = captures[capture_count++];
|
||||
capture.used = 1;
|
||||
capture.device = right_device;
|
||||
capture.axis = right_axis;
|
||||
capture.button = -1;
|
||||
capture.invert = (right_delta < 0.0f);
|
||||
sprintf(capture.line, "joyaxis %s axis Pedals%s deadzone 0.08",
|
||||
JoyAxisToken(right_axis), capture.invert ? " invert" : "");
|
||||
|
||||
if (got_left == 0)
|
||||
{
|
||||
printf(" left skipped - taking %s as one control that\n"
|
||||
" covers both ways.\n", JoyAxisToken(right_axis));
|
||||
}
|
||||
else if ((left_delta < 0.0f) == (right_delta < 0.0f))
|
||||
{
|
||||
//
|
||||
// Both moves read the same way, which no single
|
||||
// control does. Say so rather than write a row that
|
||||
// turns one way only and let them wonder.
|
||||
//
|
||||
printf(" NOTE: both moves pushed %s the SAME way"
|
||||
" (%+.2f then %+.2f).\n"
|
||||
" Bound as one control anyway - check that line if"
|
||||
" yaw only turns\n one way.\n",
|
||||
JoyAxisToken(right_axis), right_delta, left_delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" ONE axis both ways%s: bound as the pedal PAIR,\n"
|
||||
" a twist grip or rudder bar working both pedals.\n",
|
||||
capture.invert ? " (inverted)" : "");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// Two axes: the pod's own arrangement, one pedal per
|
||||
// foot, so they bind to the real pair rather than to the
|
||||
// composite that stands in for it.
|
||||
//
|
||||
WizardCapture &right_capture = captures[capture_count++];
|
||||
right_capture.used = 1;
|
||||
right_capture.device = right_device;
|
||||
right_capture.axis = right_axis;
|
||||
right_capture.button = -1;
|
||||
WizardWritePedal(&right_capture, right_axis, right_rest,
|
||||
right_delta, "RightPedal");
|
||||
|
||||
WizardCapture &left_capture = captures[capture_count++];
|
||||
left_capture.used = 1;
|
||||
left_capture.device = left_device;
|
||||
left_capture.axis = left_axis;
|
||||
left_capture.button = -1;
|
||||
WizardWritePedal(&left_capture, left_axis,
|
||||
baseline[left_device][left_axis], left_delta, "LeftPedal");
|
||||
|
||||
printf(" TWO axes: %s is the right pedal, %s the left - the\n"
|
||||
" pod's own arrangement, and the game mixes them into"
|
||||
" the turn.\n",
|
||||
JoyAxisToken(right_axis), JoyAxisToken(left_axis));
|
||||
}
|
||||
printf("\n");
|
||||
Sleep(800);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// The throttle, which cannot be read the way everything else is. A
|
||||
// lever sits wherever it was last left - halfway, or hard against
|
||||
// the stop that happens to read +1 - so watching it move says
|
||||
// nothing about which END means power. Nor can the wizard ask the
|
||||
// player which end that is: nobody knows what their driver reports.
|
||||
//
|
||||
// So it asks for the one thing the player DOES know - where zero is
|
||||
// - and takes the reading there. Everything after that follows: the
|
||||
// direction it travels from a known idle is the direction that
|
||||
// means open.
|
||||
//---------------------------------------------------------------
|
||||
{
|
||||
printf("THROTTLE: set the lever to ZERO - idle, fully closed - and\n"
|
||||
" press SPACE. Here SPACE means \"it is at zero now\",\n"
|
||||
" not skip; press S if you have no throttle lever ...\n");
|
||||
|
||||
int have_throttle = 0;
|
||||
for (;;)
|
||||
{
|
||||
int key = _getch();
|
||||
if (key == 27)
|
||||
{
|
||||
printf("\nAborted - nothing written.\n");
|
||||
printf("Press any key to continue into the game.\n");
|
||||
_getch();
|
||||
return 1;
|
||||
}
|
||||
if (key == ' ')
|
||||
{
|
||||
have_throttle = 1;
|
||||
break;
|
||||
}
|
||||
if (key == 's' || key == 'S')
|
||||
{
|
||||
printf(" skipped - no throttle lever.\n\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (have_throttle)
|
||||
{
|
||||
printf(" reading zero ...\n");
|
||||
WizardBaseline(baseline);
|
||||
printf(" now OPEN the throttle to FULL"
|
||||
" (SPACE to skip) ...\n");
|
||||
int device, axis;
|
||||
float delta, final_value;
|
||||
int got = WizardCaptureAxis(baseline, captures, capture_count, 1,
|
||||
&device, &axis, &delta, &final_value);
|
||||
if (got < 0)
|
||||
{
|
||||
printf("\nAborted - nothing written.\n");
|
||||
printf("Press any key to continue into the game.\n");
|
||||
_getch();
|
||||
return 1;
|
||||
}
|
||||
if (got == 0)
|
||||
{
|
||||
printf(" skipped.\n\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
float idle = baseline[device][axis];
|
||||
WizardCapture &capture = captures[capture_count++];
|
||||
capture.used = 1;
|
||||
capture.device = device;
|
||||
capture.axis = axis;
|
||||
capture.button = -1;
|
||||
capture.invert = (delta < 0.0f);
|
||||
sprintf(capture.line, "joyaxis %s axis Throttle%s deadzone 0",
|
||||
JoyAxisToken(axis), capture.invert ? " invert" : "");
|
||||
printf(" -> device %d (%s) axis %s%s"
|
||||
" [zero at %+.2f, opened %+.2f]\n",
|
||||
device,
|
||||
(RPJoyDevice(device) != NULL) ? RPJoyDevice(device)->name : "?",
|
||||
JoyAxisToken(axis), capture.invert ? " (inverted)" : "",
|
||||
idle, delta);
|
||||
if (idle > -0.5f && idle < 0.5f)
|
||||
{
|
||||
//
|
||||
// Zero somewhere in the middle of the travel. The
|
||||
// lever owns the channel outright, so its whole
|
||||
// -1..1 range becomes 0-100% and an idle at the
|
||||
// centre is half power. Worth saying plainly.
|
||||
//
|
||||
printf(" NOTE: your zero reads %+.2f rather than an end"
|
||||
" stop, and a\n"
|
||||
" throttle's FULL travel becomes the pod's 0-100%%"
|
||||
" - so at that\n"
|
||||
" position the pod would sit near half power. Use"
|
||||
" the lever's\n"
|
||||
" real closed stop, or edit that row by hand.\n",
|
||||
idle);
|
||||
}
|
||||
printf("\n");
|
||||
Sleep(800);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// The pod's stick-head buttons, at their RIO addresses.
|
||||
//
|
||||
|
||||
+58
-17
@@ -201,11 +201,15 @@ namespace
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Shared tail of the two axis-source rows: [invert] [deadzone <d>]
|
||||
// [rate <n>], in any order.
|
||||
// [rate <n>], in any order. 'lever' rides along for the rows that
|
||||
// can take it - a NULL lever means the word is not legal here, and
|
||||
// a padaxis row is exactly that: the pad's own triggers already
|
||||
// read 0..1, so there is no half-travel to rescue.
|
||||
//---------------------------------------------------------------
|
||||
Logical ParseAxisOptions(
|
||||
char *tokens[], int token_count, int first,
|
||||
Logical *invert, Scalar *deadzone, Scalar *rate)
|
||||
Logical *invert, Scalar *deadzone, Scalar *rate,
|
||||
Logical *lever = NULL)
|
||||
{
|
||||
for (int i = first; i < token_count; ++i)
|
||||
{
|
||||
@@ -213,6 +217,10 @@ namespace
|
||||
{
|
||||
*invert = True;
|
||||
}
|
||||
else if (NameEquals(tokens[i], "lever") && lever != NULL)
|
||||
{
|
||||
*lever = True;
|
||||
}
|
||||
else if (NameEquals(tokens[i], "deadzone") && i + 1 < token_count)
|
||||
{
|
||||
if (!ParseNumber(tokens[++i], deadzone))
|
||||
@@ -355,12 +363,23 @@ namespace
|
||||
{
|
||||
return False;
|
||||
}
|
||||
PadPadAxisBinding *binding = &profile->padAxes[profile->padAxisCount++];
|
||||
memset(binding, 0, sizeof(*binding));
|
||||
binding->source = source;
|
||||
binding->axis = axis;
|
||||
return ParseAxisOptions(tokens, token_count, 4,
|
||||
&binding->invert, &binding->deadzone, &binding->rate);
|
||||
//
|
||||
// Built aside and only then committed: a row whose options
|
||||
// go bad half way through is a REJECTED row, and taking the
|
||||
// slot first would leave the good half of it bound anyway,
|
||||
// under a log line that says it was skipped.
|
||||
//
|
||||
PadPadAxisBinding candidate;
|
||||
memset(&candidate, 0, sizeof(candidate));
|
||||
candidate.source = source;
|
||||
candidate.axis = axis;
|
||||
if (!ParseAxisOptions(tokens, token_count, 4,
|
||||
&candidate.invert, &candidate.deadzone, &candidate.rate))
|
||||
{
|
||||
return False;
|
||||
}
|
||||
profile->padAxes[profile->padAxisCount++] = candidate;
|
||||
return True;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
@@ -377,13 +396,19 @@ namespace
|
||||
{
|
||||
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);
|
||||
PadJoyAxisBinding candidate;
|
||||
memset(&candidate, 0, sizeof(candidate));
|
||||
candidate.device = *joy_slot;
|
||||
candidate.source = source;
|
||||
candidate.axis = axis;
|
||||
if (!ParseAxisOptions(tokens, token_count, 4,
|
||||
&candidate.invert, &candidate.deadzone, &candidate.rate,
|
||||
&candidate.lever))
|
||||
{
|
||||
return False;
|
||||
}
|
||||
profile->joyAxes[profile->joyAxisCount++] = candidate;
|
||||
return True;
|
||||
}
|
||||
|
||||
if (NameEquals(tokens[0], "joybutton") && NameEquals(tokens[2], "button"))
|
||||
@@ -453,7 +478,7 @@ namespace
|
||||
"# pad <button> button <addr> [toggle]\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"
|
||||
"# joyaxis <src> axis <axis> [invert] [lever] [deadzone <d>] [rate <n>]\n"
|
||||
"# joybutton <n> button <addr> [toggle]\n"
|
||||
"# joyhat <n> <up|down|left|right> button <addr>\n"
|
||||
"#\n"
|
||||
@@ -462,7 +487,10 @@ namespace
|
||||
"# <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"
|
||||
"# bar or twist grip drives both. Two-pedal hardware - racing\n"
|
||||
"# pedals, rudder pedals with an axis per foot - skips the\n"
|
||||
"# composite and binds LeftPedal and RightPedal directly; the\n"
|
||||
"# game mixes the pair into yaw the way the pod always did.\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"
|
||||
@@ -493,6 +521,12 @@ namespace
|
||||
"# the full throttle range, rather than nudging the position the way a\n"
|
||||
"# spring-centred pad stick has to.\n"
|
||||
"#\n"
|
||||
"# 'lever' marks a source that rests at one END of its travel instead of\n"
|
||||
"# in the middle - a floor pedal, a slider. Windows reports it as a full\n"
|
||||
"# -1..1 axis all the same, so without the word half the travel sits\n"
|
||||
"# below zero and the first half of the press does nothing; with it the\n"
|
||||
"# travel maps onto 0..1 and the deadzone measures from the released end.\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"
|
||||
@@ -500,6 +534,13 @@ namespace
|
||||
"# joyaxis SL0 axis Throttle deadzone 0\n"
|
||||
"# joybutton 0 button 0x40\n"
|
||||
"# joyhat 0 up button 0x42\n"
|
||||
"#\n"
|
||||
"# ...and the same stick with racing pedals on a second device, one\n"
|
||||
"# axis per foot instead of the composite:\n"
|
||||
"#\n"
|
||||
"# joydev 1 Pedals\n"
|
||||
"# joyaxis Y axis LeftPedal lever deadzone 0.05\n"
|
||||
"# joyaxis RZ axis RightPedal lever deadzone 0.05\n"
|
||||
"\n"
|
||||
"# ---- Flight: number pad + modifiers -------------------------------\n"
|
||||
"# The whole letter board stays free for the MFD banks; flight lives\n"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// pad <button> button <addr> [toggle]
|
||||
// padaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n>]
|
||||
// joydev <slot> [product-name substring...]
|
||||
// joyaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n>]
|
||||
// joyaxis <src> axis <axis> [invert] [lever] [deadzone <d>] [rate <n>]
|
||||
// joybutton <n> button <addr> [toggle]
|
||||
// joyhat <n> <up|down|left|right> button <addr>
|
||||
//
|
||||
@@ -37,6 +37,13 @@
|
||||
// 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.
|
||||
//
|
||||
// 'lever' says the source is a one-way control that rests at one end of
|
||||
// its travel rather than in the middle - a floor pedal, a throttle
|
||||
// slider. DirectInput reports it as a full -1..1 axis all the same, so
|
||||
// without the keyword half the travel sits below zero and the first
|
||||
// half of the press does nothing. It maps that travel onto 0..1, and
|
||||
// the deadzone then measures from the released end instead of centre.
|
||||
//########################################################################
|
||||
|
||||
enum PadBindRioAxis
|
||||
@@ -136,6 +143,7 @@ struct PadJoyAxisBinding
|
||||
int source; // PadBindJoyAxis
|
||||
int axis; // PadBindRioAxis
|
||||
Logical invert;
|
||||
Logical lever; // rests at one end: -1..1 travel means 0..1
|
||||
Scalar deadzone; // normalized 0..1
|
||||
Scalar rate; // 0 = direct position, >0 = speed integrate
|
||||
};
|
||||
|
||||
+22
-1
@@ -65,6 +65,25 @@ namespace
|
||||
return value;
|
||||
}
|
||||
|
||||
//
|
||||
// A one-way control - a floor pedal, a slider - rests at one END of
|
||||
// its travel, not in the middle, and DirectInput still reports it as
|
||||
// a full -1..1 axis. Fold that travel onto 0..1 so the pedal starts
|
||||
// answering as soon as it moves instead of at half depression, and
|
||||
// measure the deadzone from the released end, where the slack in a
|
||||
// tired return spring actually lives.
|
||||
//
|
||||
Scalar JoyLeverValue(Scalar raw, Scalar deadzone)
|
||||
{
|
||||
Scalar value = (raw + 1.0f) * 0.5f;
|
||||
if (value <= deadzone)
|
||||
{
|
||||
return (Scalar) 0;
|
||||
}
|
||||
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
|
||||
@@ -686,7 +705,9 @@ void
|
||||
throttleLever = True;
|
||||
continue;
|
||||
}
|
||||
Scalar value = JoyAxisValue(raw, binding->deadzone);
|
||||
Scalar value = binding->lever
|
||||
? JoyLeverValue(raw, binding->deadzone)
|
||||
: JoyAxisValue(raw, binding->deadzone);
|
||||
if (binding->rate > 0.0f)
|
||||
{
|
||||
rate[binding->axis] += value * binding->rate;
|
||||
|
||||
+45
-11
@@ -163,15 +163,34 @@ it off.
|
||||
|
||||
---
|
||||
|
||||
## Flight sticks, HOTAS and rudder pedals
|
||||
## Flight sticks, HOTAS and 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.
|
||||
throttle, a twist grip, rudder or racing 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.
|
||||
|
||||
You are never asked what you own. Two of the controls can't simply be
|
||||
watched, though, so they are asked for differently:
|
||||
|
||||
**Yaw is asked for twice** — right, then left — and *which axis answers*
|
||||
is the measurement. The same axis both times is one control covering
|
||||
both directions: a twist grip, a rudder bar, rudder pedals their driver
|
||||
has already mixed, racing pedals in combined mode. Two different axes
|
||||
are two real pedals, one per foot, which is what the pod itself had, and
|
||||
they bind to the pod's own pair so the game does the mixing — both at
|
||||
once then does what both at once did in the pod. Either way you just
|
||||
move the thing twice; the wizard works out the shape.
|
||||
|
||||
**The throttle is set to zero first.** A lever sits wherever it was last
|
||||
left — halfway, or hard against the stop that happens to read `+1` — so
|
||||
watching it move says nothing about which end means power. The wizard
|
||||
asks you to close it, press SPACE to say so, and *then* open it: the
|
||||
direction it travels from a known idle is the direction that means
|
||||
throttle. (Press `S` there if you have no throttle lever.)
|
||||
|
||||
It only writes its own section, between two marker lines, so anything
|
||||
you have edited yourself survives re-running it. Xbox-class controllers
|
||||
@@ -189,15 +208,30 @@ joybutton 0 button 0x40 # trigger
|
||||
joyhat 0 up button 0x42
|
||||
```
|
||||
|
||||
Two separate pedals bind to the pod's real pair instead, usually on a
|
||||
device of their own:
|
||||
|
||||
```
|
||||
joydev 1 Pedals
|
||||
joyaxis Y axis LeftPedal lever deadzone 0.05
|
||||
joyaxis RZ axis RightPedal lever deadzone 0.05
|
||||
```
|
||||
|
||||
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
|
||||
Three 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
|
||||
one rudder bar or twist grip works both the way a foot never could. 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.
|
||||
way a spring-centred pad stick has to. And `lever` says the same thing
|
||||
about any other one-way control — a floor pedal rests at one *end* of
|
||||
its travel, not in the middle, but Windows still reports it as a full
|
||||
−1..+1 axis, so without the word the first half of the press does
|
||||
nothing. With it, the travel maps onto the channel's 0..1 and the
|
||||
deadzone measures from the released end, where a tired return spring's
|
||||
slack actually lives. Add or remove `invert` if a pedal reads backwards.
|
||||
|
||||
## Rebinding
|
||||
|
||||
|
||||
@@ -1183,6 +1183,10 @@
|
||||
<dt>It watches</dt>
|
||||
<dd>Which way<small>the direction you moved sets the sign</small></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>And which</dt>
|
||||
<dd>Yaw twice<small>right then left: one axis or two?</small></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Then</dt>
|
||||
<dd>Straight in<small>the game carries on to the setup screen</small></dd>
|
||||
@@ -1199,6 +1203,23 @@
|
||||
answer off the movement. Skip a prompt with <code>SPACE</code> if you
|
||||
have no such control, <code>ESC</code> to abort without writing.
|
||||
</p>
|
||||
<p>
|
||||
It goes further than the sign: it works out the <b>shape</b> of your
|
||||
rig the same way. <b>Yaw is asked for twice</b>, right and then left,
|
||||
and which axis answers is the measurement — the same axis both times
|
||||
is one control covering both ways (a twist grip, a rudder bar, pedals
|
||||
their driver has already mixed), two different axes are two real
|
||||
pedals, one per foot. You just move the thing twice.
|
||||
</p>
|
||||
<p>
|
||||
The <b>throttle</b> is the one prompt that works backwards, because a
|
||||
lever sits wherever it was left: halfway, or hard against the stop
|
||||
that happens to read <span class="mono">+1</span>. Watching it move
|
||||
cannot tell you which end means power. So close it, press
|
||||
<code>SPACE</code> to say <i>it is at zero now</i>, and then open it —
|
||||
the travel from a known idle is the direction that means throttle.
|
||||
<code>S</code> there if you have no lever at all.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="sub">
|
||||
@@ -1223,7 +1244,7 @@
|
||||
|
||||
<pre><i># grammar</i>
|
||||
<b>joydev</b> <slot> [product-name substring]
|
||||
<b>joyaxis</b> <src> <b>axis</b> <axis> [invert]
|
||||
<b>joyaxis</b> <src> <b>axis</b> <axis> [invert] [lever]
|
||||
[deadzone <d>] [rate <n>]
|
||||
<b>joybutton</b> <n> <b>button</b> <addr> [toggle]
|
||||
<b>joyhat</b> <n> <up|down|left|right>
|
||||
@@ -1235,7 +1256,7 @@
|
||||
|
||||
<div class="tbl-scroll" style="margin-top:22px">
|
||||
<table>
|
||||
<caption>Two rules the pod's shape asks for</caption>
|
||||
<caption>Three rules the pod's shape asks for</caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="mono">Pedals</th>
|
||||
@@ -1246,6 +1267,31 @@
|
||||
that presses one or the other and never both, which is exactly
|
||||
what this says. It is a channel name like any other, so a gamepad
|
||||
stick can drive the turn with it too.
|
||||
<br><br>
|
||||
Two real pedals — racing pedals, or rudder pedals with an axis
|
||||
per foot — bind to
|
||||
<span class="mono">LeftPedal</span> and
|
||||
<span class="mono">RightPedal</span> one axis each instead, and
|
||||
the wizard picks that up on its own from the two yaw prompts
|
||||
answering on two different axes. That is the pod's own
|
||||
arrangement: the game mixes your two feet into the turn rather
|
||||
than taking a turn already mixed for it, and both pedals at once
|
||||
does what both pedals at once did in the pod.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="mono">lever</th>
|
||||
<td>
|
||||
A pedal rests at one <b>end</b> of its travel, not in the middle,
|
||||
and Windows reports it as a full
|
||||
<span class="mono">−1..+1</span> axis regardless — so half the
|
||||
travel reads below zero and the first half of the press would do
|
||||
nothing at all. <span class="mono">lever</span> says the control
|
||||
is one-way: the travel maps onto the channel's
|
||||
<span class="mono">0..1</span>, and the deadzone then measures
|
||||
from the released end, where the slack in a tired return spring
|
||||
actually lives. The wizard writes it for you; add or remove
|
||||
<span class="mono">invert</span> if a pedal reads backwards.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
+21
-10
@@ -220,12 +220,18 @@ 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 Red Planet 4.12 - one-time JOYSTICK / HOTAS / PEDAL setup.
|
||||
rem A console wizard asks you to move each control (stick, yaw, throttle
|
||||
rem lever, fire buttons); it works out what you moved and which way it
|
||||
rem reads, and writes the joystick section of bindings.txt. When it
|
||||
rem finishes the game carries on into the setup screen so you can try
|
||||
rem the bindings straight away.
|
||||
rem You are never asked what you own. Yaw is asked for twice, right then
|
||||
rem left, and which axis answers tells it what you have: one axis both
|
||||
rem ways is a twist grip or rudder bar, two axes are two real pedals,
|
||||
rem one per foot, the way the pod itself was. The throttle asks you to
|
||||
rem close it and press SPACE first, since a lever sits wherever it was
|
||||
rem left and no amount of watching says which end means power.
|
||||
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.
|
||||
@@ -243,10 +249,15 @@ Run start-fullscreen.bat for borderless over the whole monitor, or
|
||||
start-windowed.bat to keep a title bar. No cockpit hardware needed.
|
||||
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.
|
||||
Got a flight stick, HOTAS or 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. It never
|
||||
asks what you own: yaw is asked for twice, right then left, and one
|
||||
axis answering both ways is a twist grip or rudder bar while two axes
|
||||
are two real pedals, one per foot, as the pod itself had. The throttle
|
||||
asks you to close it and press SPACE before you open it, since a lever
|
||||
sits wherever it was left. Xbox-class controllers need none of that -
|
||||
they work out of the box.
|
||||
|
||||
Controls (XInput controller and/or keyboard) - EVERY input is
|
||||
rebindable: edit bindings.txt beside the exe (written with the full
|
||||
|
||||
Reference in New Issue
Block a user