From 6a302f3aeda5b90ec65eac37c073875bffe16337 Mon Sep 17 00:00:00 2001 From: Cyd Date: Fri, 17 Jul 2026 10:37:30 -0500 Subject: [PATCH] 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 --- game/reconstructed/mech4.cpp | 68 +++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index f95db34..e35cf3c 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -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