Ported from BT411, which needed the same thing for its glass cockpit. PadRIO reads XInput, which covers Xbox-class pads and nothing else. A flight stick, a HOTAS throttle, a twist grip, rudder pedals or a wheel arrive through DirectInput instead, and until now the game could not see any of them - the only generic-joystick path left was the 1995 single- device DIJoystick behind L4CONTROLS=DIJOYSTICK, which is untouched here. L4JOY is the reader: up to four devices as normalized state blocks, hot- plug re-enumeration on the same ~3 s cadence PadRIO uses to look for a pad, and a device lost mid-race zeroed rather than left holding whatever was pressed when it went. XInput-class devices are excluded by VID/PID against the RawInput paths carrying the "IG_" marker - without that an Xbox pad arrives through both APIs and every button counts twice. bindings.txt gains four rows in the grammar it already had, using its own vocabulary (deadzone/rate) rather than BT411's: 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> Slots resolve to a live device every poll, by name substring or ordinal, so unplugging and replugging does not rewrite anyone's file. Two things the pod's shape forced that BT411 solved differently: Pedals - a signed composite axis that decomposes into the pod's two pedals, positive right and negative left. The pod has a pedal each side; a twist grip or rudder bar is one signed control, and pressing one or the other but never both is exactly what it wants to say. It is a channel name like any other, so a pad stick can drive the turn too. A joyaxis on Throttle with no rate is a real lever and OWNS the channel - full travel maps onto the 0..1 the pod runs on, instead of nudging the accumulator that a spring-centred pad stick has to use. RP412JOYCONFIG=1 (joyconfig.bat) runs the capture wizard before the console screen: it asks the player to move each control, and derives the sign convention from the DIRECTION of the move. That is the point of it - a stick that reads positive pushed right and one that reads negative are equally common, and no amount of documentation gets a player to work out which they own. It writes only its own section, between marker lines, so hand-edited keyboard and pad rows survive re-running it. The wizard also prints every axis at rest before it starts. A driver that refuses the +-32767 range we ask for reports its own, and an axis then sits hard over instead of near zero; seeing "X +1.00" on an untouched stick is the difference between a five-minute fix and a bug report that says it configured itself. Each capture reports the move it saw for the same reason. Verified on the Logitech Extreme 3D on this machine. Enumeration finds it and excludes the Xbox pad, which still arrives separately through XInput. Every row shape parses - 7 axes, 2 buttons, 4 hat directions - and three deliberately malformed rows (a bad axis name, button 99, a "sideways" hat) are each rejected by line number rather than silently dropped. The wizard lists the device with its axes at rest reading X +0.00 Y -0.01 RZ -0.04 SL0 +1.00, waits on the first prompt without self-triggering, and with a hand on the stick captures X to steering, Y to pitch, RZ to the pedals and SL0 to the throttle, inverting the ones that read backwards. Running the captures through to a written file needs a hand on the stick, so that part is the machine's to confirm, not this build's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
86 lines
3.5 KiB
C
86 lines
3.5 KiB
C
//===========================================================================//
|
|
// 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);
|