Input: bring-up gameplay keys stand down under PadRIO (BT_DEVKEYS)

The hardcoded GetAsyncKeyState gameplay keys in mech4.cpp (WASD drive, 1-4
weapon groups, G config, F5-F9 generators, C valve, M mode, Q/E torso, X
all-stop/recenter) OVERLAP a RIO device's own controls -- with L4CONTROLS=PAD
(or a serial RIO) engaged, every one of them double-fired against the same key
going through the PadRIO bindings.

Gate them behind BT_DEVKEYS: they now stand down whenever a RIO device owns
input (application->GetControlsManager()->rioPointer != 0), so PadRIO's
bindings.txt is the single source of truth. Env override: BT_DEVKEYS=1 forces
them on (hybrid keyboard+pad testing), =0 forces off, unset = auto. Implemented
as a `gfocus` gate (focused AND keys-enabled) replacing `focused` on those
reads; the 'V' cockpit/chase view toggle and the '`' display toggle are
desktop-only conveniences with no RIO equivalent and stay live.

Backward compatible: with no device (keyboard dev mode) gfocus == focused, so
keyboard-only play is unchanged. Boots clean (13 ticks, no faults).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-17 10:37:30 -05:00
co-authored by Claude Opus 4.8
parent 2b238a835c
commit 6a302f3aed
+47 -21
View File
@@ -2566,37 +2566,63 @@ void
if (pAsync)
{
const int dn = 0x8000;
gBTDrive.keyFwd = focused && ((pAsync('W') | pAsync(0x26 /*VK_UP*/)) & dn) ? 1 : 0;
gBTDrive.keyBack = focused && ((pAsync('S') | pAsync(0x28 /*VK_DOWN*/)) & dn) ? 1 : 0;
gBTDrive.keyLeft = focused && ((pAsync('A') | pAsync(0x25 /*VK_LEFT*/)) & dn) ? 1 : 0;
gBTDrive.keyRight = focused && ((pAsync('D') | pAsync(0x27 /*VK_RIGHT*/)) & dn) ? 1 : 0;
// BT_DEVKEYS gate: the bring-up GAMEPLAY keys below (drive,
// weapons, mode, torso, config/gen/valve) OVERLAP a RIO
// device's own controls, so with PadRIO (or serial RIO)
// engaged they double-fire against the device's bindings.
// They stand down whenever a RIO device owns input.
// BT_DEVKEYS unset = AUTO (off under a device, on otherwise)
// BT_DEVKEYS=1 = force ON (hybrid keyboard + pad)
// BT_DEVKEYS=0 = force OFF (device only)
// `gfocus` replaces `focused` for those keys; the 'V'
// cockpit/chase view toggle (a desktop-only convenience with
// no RIO equivalent) stays on `focused` and is always live,
// as is the '`' display toggle (handled in the WndProc).
static int s_devKeysMode = -1; // 0 auto, 1 on, 2 off
if (s_devKeysMode < 0)
{
const char *dk = getenv("BT_DEVKEYS");
s_devKeysMode = (dk == 0) ? 0 : (atoi(dk) != 0 ? 1 : 2);
}
LBE4ControlsManager *pollControls = (application != 0)
? (LBE4ControlsManager *)application->GetControlsManager() : 0;
const int deviceOwnsInput =
(pollControls != 0 && pollControls->rioPointer != 0);
const int gfocus = focused && (
(s_devKeysMode == 1) ? 1 :
(s_devKeysMode == 2) ? 0 :
(deviceOwnsInput ? 0 : 1));
gBTDrive.keyFwd = gfocus && ((pAsync('W') | pAsync(0x26 /*VK_UP*/)) & dn) ? 1 : 0;
gBTDrive.keyBack = gfocus && ((pAsync('S') | pAsync(0x28 /*VK_DOWN*/)) & dn) ? 1 : 0;
gBTDrive.keyLeft = gfocus && ((pAsync('A') | pAsync(0x25 /*VK_LEFT*/)) & dn) ? 1 : 0;
gBTDrive.keyRight = gfocus && ((pAsync('D') | pAsync(0x27 /*VK_RIGHT*/)) & dn) ? 1 : 0;
// WEAPON GROUPS (task #43, KEYBOARD only per user): three fire
// channels like three pod buttons -- 1/SPACE = lasers, 2 = PPCs,
// 3/CTRL = missiles. (Interim; the authentic system is the
// ConfigureMappables/ChooseButton mapper channels.)
gBTLaserKey = focused && ((pAsync('1') | pAsync(0x20 /*VK_SPACE*/)) & dn) ? 1 : 0;
gBTPPCKey = focused && (pAsync('2') & dn) ? 1 : 0;
gBTMissileKey = focused && ((pAsync('3') | pAsync(0x11 /*VK_CONTROL*/)) & dn) ? 1 : 0;
gBTLaserKey = gfocus && ((pAsync('1') | pAsync(0x20 /*VK_SPACE*/)) & dn) ? 1 : 0;
gBTPPCKey = gfocus && (pAsync('2') & dn) ? 1 : 0;
gBTMissileKey = gfocus && ((pAsync('3') | pAsync(0x11 /*VK_CONTROL*/)) & dn) ? 1 : 0;
// The pod's 4TH fire button (Pinky, 0x45) -- previously
// unmapped on desktop, so any weapon the authored groups
// put there (the Avatar's NARC etc.) was unreachable.
gBTPinkyKey = focused && (pAsync('4') & dn) ? 1 : 0;
gBTPinkyKey = gfocus && (pAsync('4') & dn) ? 1 : 0;
// task #6: HOLD 'G' opens the config session on the selected
// weapon (BT_CONFIG_SLOT, default: the first weapon); while
// held, the fire keys TOGGLE its group membership.
gBTConfigKey = focused && (pAsync('G') & dn) ? 1 : 0;
gBTConfigKey = gfocus && (pAsync('G') & dn) ? 1 : 0;
// task #12: F5..F8 assign the selected weapon to Generator
// A..D; F9 toggles Manual/Auto reconnect.
gBTGenSelKey =
(focused && (pAsync(0x74 /*F5*/) & dn)) ? 4
: (focused && (pAsync(0x75 /*F6*/) & dn)) ? 5
: (focused && (pAsync(0x76 /*F7*/) & dn)) ? 6
: (focused && (pAsync(0x77 /*F8*/) & dn)) ? 7
: (focused && (pAsync(0x78 /*F9*/) & dn)) ? 8
(gfocus && (pAsync(0x74 /*F5*/) & dn)) ? 4
: (gfocus && (pAsync(0x75 /*F6*/) & dn)) ? 5
: (gfocus && (pAsync(0x76 /*F7*/) & dn)) ? 6
: (gfocus && (pAsync(0x77 /*F8*/) & dn)) ? 7
: (gfocus && (pAsync(0x78 /*F9*/) & dn)) ? 8
: 0;
// task #13: 'C' cycles the coolant valve (BT_VALVE_SLOT
// picks the condenser roster slot; default = Condenser1).
gBTValveKey = focused && (pAsync('C') & dn) ? 1 : 0;
gBTValveKey = gfocus && (pAsync('C') & dn) ? 1 : 0;
// TORSO CONTROLS (2026-07-13): 'M' cycles the control mode
// (Basic -> Standard -> Veteran -- the pod console button,
// CycleControlModeMessageHandler); Q/E deflect the torso
@@ -2604,15 +2630,15 @@ void
// become the pedals). Ramped like the turn stick.
{
static int sPrevM = 0;
const int mNow = focused && (pAsync('M') & dn) ? 1 : 0;
const int mNow = gfocus && (pAsync('M') & dn) ? 1 : 0;
if (mNow && !sPrevM) gBTModeCycle = 1; // edge -> one cycle
sPrevM = mNow;
// (task #68) 'V' HELD = look behind (the pod's rear-view
// button; releases back to the forward view). Rear-mounted
// weapons (blackhawk/owens back racks) fire only in it.
gBTLookBehind = focused && (pAsync('V') & dn) ? 1 : 0;
const int tw = (focused && (pAsync('E') & dn) ? 1 : 0)
- (focused && (pAsync('Q') & dn) ? 1 : 0);
gBTLookBehind = gfocus && (pAsync('V') & dn) ? 1 : 0;
const int tw = (gfocus && (pAsync('E') & dn) ? 1 : 0)
- (gfocus && (pAsync('Q') & dn) ? 1 : 0);
static float sTwist = 0.0f;
if (tw != 0)
{
@@ -2642,7 +2668,7 @@ void
// edge detector from the drive all-stop below -- both
// fire on the same press.
static int sPrevXT = 0;
const int xtNow = focused && (pAsync('X') & dn) ? 1 : 0;
const int xtNow = gfocus && (pAsync('X') & dn) ? 1 : 0;
if (xtNow && !sPrevXT)
{
sTwist = 0.0f;
@@ -2655,7 +2681,7 @@ void
// damage dispatcher + the beam-visual keepalive)
gBTDrive.fire = (gBTLaserKey || gBTPPCKey || gBTMissileKey || gBTPinkyKey) ? 1 : 0;
static int sPrevX = 0;
const int xNow = focused && (pAsync('X') & dn) ? 1 : 0;
const int xNow = gfocus && (pAsync('X') & dn) ? 1 : 0;
if (xNow && !sPrevX) gBTDrive.allStop = 1; // edge -> one all-stop
sPrevX = xNow;
// V: toggle the view between the authentic COCKPIT eyepoint