From ff5992e3c48877b097b7740729121069159a57ee Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 1 Apr 2022 20:51:37 +0100 Subject: [PATCH] Rework the entire G_PlayerInputAnalog system. * Previous control checking flow: * Current controller/keyboard (userbound controls). * If on a menu: * Current controller/keyboard (default controls). * All controllers not in use by a player (default controls). * New control checking flow: * Current controller/keyboard (userbound controls). * If player 0 and just checked a controller, check keyboard (userbound controls). * If on a menu: * Check all controllers not in use by a player (userbound controls). * If keys are inaccessible/unbound and keybind is necessary to navigate menus, repeat eveyrhting with default controls. * Instead of duplicated code, control the flow in a finer fashion. * Now able to detect if gamepad inputs are possible to recieve (via checking deviceID), instead of assuming they are. * If a keybind is set but inaccessible by the above metric, make it flash on the Profile Controls screen. * Fix out-of-order key mappings for a given bind being invisible on the Profile Controls menu. --- src/g_game.c | 148 +++++++++++++++++------------------------------ src/g_input.c | 45 ++++++++++++++ src/g_input.h | 9 ++- src/k_menudraw.c | 79 +++++++++++++++++++++---- 4 files changed, 174 insertions(+), 107 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 45682aae9..5f7b0cd73 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -660,70 +660,14 @@ INT16 G_SoftwareClipAimingPitch(INT32 *aiming) return (INT16)((*aiming)>>16); } -static INT32 KeyValue(UINT8 p, INT32 key, UINT8 menuPlayers) -{ - INT32 deviceID; - INT32 i, j; - - if (key <= 0 || key >= NUMINPUTS) - { - return 0; - } - - deviceID = cv_usejoystick[p].value; - - if (menuPlayers > 0) - { - // Try every device that does NOT belong to another player. - for (i = MAXDEVICES-1; i >= 0; i--) - { - if (i == deviceID) - { - // We've tried this one multiple times :V - continue; - } - - if (menuPlayers > 1) - { - for (j = 1; j < menuPlayers; j++) - { - if (i == cv_usejoystick[j].value) - { - break; - } - } - - if (j < menuPlayers) - { - // This one's taken. - continue; - } - } - - if (gamekeydown[i][key] != 0) - { - return gamekeydown[i][key]; - } - } - } - else - { - if (deviceID < 0 || deviceID >= MAXDEVICES) - { - // Device is unset - return 0; - } - - return gamekeydown[deviceID][key]; - } - - return 0; -} - INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) { + INT32 deviceID; INT32 i; INT32 deadzone = 0; + boolean trydefaults = true; + boolean tryingotherID = false; + INT32 *controltable = &(gamecontrol[p][gc][0]); if (p >= MAXSPLITSCREENPLAYERS) { @@ -735,17 +679,24 @@ INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) deadzone = (JOYAXISRANGE * cv_deadzone[p].value) / FRACUNIT; + deviceID = cv_usejoystick[p].value; + +retrygetcontrol: for (i = 0; i < MAXINPUTMAPPING; i++) { - INT32 key = gamecontrol[p][gc][i]; + INT32 key = controltable[i]; INT32 value = 0; - if (key <= 0 || key >= NUMINPUTS) + // Invalid key number. + if (!G_KeyIsAvailable(key, deviceID)) { continue; } - value = KeyValue(p, key, false); + // It's possible to access this control right now, so let's disable the default control backup for later. + trydefaults = false; + + value = gamekeydown[deviceID][key]; if (value >= deadzone) { @@ -753,42 +704,51 @@ INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) } } - if (menuPlayers != 0) + // If you're on controller, try your keyboard-based binds as an immediate backup. + if (p == 0 && deviceID > 0 && !tryingotherID) { - // We don't want menus to become unnavigable if people unbind - // all of their controls, so we do several things in this scenario. + deviceID = 0; + goto retrygetcontrol; + } - // First: check the same device, but with default binds. - for (i = 0; i < MAXINPUTMAPPING; i++) + if (menuPlayers == 0) + { + return 0; + } + + // We don't want menus to become unnavigable if people unbind + // all of their controls, so we do several things in this scenario. + // First: try other controllers. + + if (!tryingotherID) + { + deviceID = MAXDEVICES; + tryingotherID = true; + } +loweringid: + deviceID--; + if (deviceID > 0) + { + for (i = 0; i < menuPlayers; i++) { - INT32 key = gamecontroldefault[gc][i]; - INT32 value = 0; - - if (key <= 0 || key >= NUMINPUTS) - { + if (deviceID != cv_usejoystick[i].value) continue; - } - - value = KeyValue(p, key, false); - - if (value >= deadzone) - { - return value; - } - - if (p == 0 && menuPlayers == 1) - { - // Second: if we're Player 1 and there are no other players, - // then we can use keyboard defaults as a final resort. - - value = KeyValue(p, key, menuPlayers); - - if (value >= deadzone) - { - return value; - } - } + // Controller taken? Try again... + goto loweringid; } + goto retrygetcontrol; + } + + if (trydefaults && G_KeyBindIsNecessary(gc)) + { + // If we still haven't found anything and the keybind is necessary, + // try it all again but with default binds. + trydefaults = false; + controltable = &(gamecontroldefault[gc][0]); + tryingotherID = false; + deviceID = cv_usejoystick[p].value; + goto retrygetcontrol; + } return 0; diff --git a/src/g_input.c b/src/g_input.c index 35e5f7da9..2ab595912 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -417,6 +417,51 @@ static const char *gamecontrolname[num_gamecontrols] = #define NUMKEYNAMES (sizeof (keynames)/sizeof (keyname_t)) +// If keybind is necessary to navigate menus, it's on this list. +boolean G_KeyBindIsNecessary(INT32 gc) +{ + switch (gc) + { + case gc_a: + case gc_b: + case gc_up: + case gc_down: + case gc_left: + case gc_right: + case gc_start: + return true; + default: + return false; + } + return false; +} + +// Returns false if a key is deemed unreachable for this device. +boolean G_KeyIsAvailable(INT32 key, INT32 deviceID) +{ + // Invalid key number. + if (key <= 0 || key >= NUMINPUTS) + { + return false; + } + + // Valid controller-specific virtual key, but no controller attached for player. + if (key >= KEY_JOY1 && key < JOYINPUTEND && deviceID <= 0) + { + return false; + } + + // Valid mouse-specific virtual key, but no mouse attached for player. TODO HOW TO DETECT ACTIVE MOUSE CONNECTION + /* + if (key >= KEY_MOUSE1 && key < MOUSEINPUTEND && ????????) + { + return false; + } + */ + + return true; +} + // // Detach any keys associated to the given game control // - pass the pointer to the gamecontrol table for the player being edited diff --git a/src/g_input.h b/src/g_input.h index f633d4f0f..ae9b130bc 100644 --- a/src/g_input.h +++ b/src/g_input.h @@ -37,13 +37,15 @@ typedef enum KEY_JOY1 = NUMKEYS, KEY_HAT1 = KEY_JOY1 + JOYBUTTONS, KEY_AXIS1 = KEY_HAT1 + JOYHATS*4, + JOYINPUTEND = KEY_AXIS1 + JOYAXISSET*2*2, // 4 sets of 2 axes, each with positive & negative - KEY_MOUSE1 = KEY_AXIS1 + JOYAXISSET*2*2, // 4 sets of 2 axes, each with positive & negative + KEY_MOUSE1 = JOYINPUTEND, KEY_MOUSEMOVE = KEY_MOUSE1 + MOUSEBUTTONS, KEY_MOUSEWHEELUP = KEY_MOUSEMOVE + 4, KEY_MOUSEWHEELDOWN = KEY_MOUSEWHEELUP + 1, + MOUSEINPUTEND = KEY_MOUSEWHEELDOWN + 1, - NUMINPUTS = KEY_MOUSEWHEELDOWN + 1, + NUMINPUTS = MOUSEINPUTEND, } key_input_e; typedef enum @@ -134,6 +136,9 @@ void G_MapEventsToControls(event_t *ev); const char *G_KeynumToString(INT32 keynum); INT32 G_KeyStringtoNum(const char *keystr); +boolean G_KeyBindIsNecessary(INT32 gc); +boolean G_KeyIsAvailable(INT32 key, INT32 deviceID); + // detach any keys associated to the given game control void G_ClearControlKeys(INT32 (*setupcontrols)[MAXINPUTMAPPING], INT32 control); void G_ClearAllControlKeys(void); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 375113bd4..e1b2731b0 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2337,7 +2337,7 @@ void M_DrawEditProfile(void) V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUHINT", PU_CACHE), NULL); if (currentMenu->menuitems[itemOn].tooltip != NULL) { - V_DrawCenteredThinString(BASEVIDWIDTH*2/3, 12, V_ALLOWLOWERCASE|V_6WIDTHSPACE, currentMenu->menuitems[itemOn].tooltip); + V_DrawCenteredThinString(224, 12, V_ALLOWLOWERCASE|V_6WIDTHSPACE, currentMenu->menuitems[itemOn].tooltip); } // Draw the menu options... @@ -2480,18 +2480,76 @@ void M_DrawProfileControls(void) } else if (currentMenu->menuitems[i].status & IT_CONTROL) { - // Draw what the controls are mapped to + UINT32 vflags = V_6WIDTHSPACE; + INT32 gc = currentMenu->menuitems[i].mvar1; + UINT8 available = 0, set = 0; + + // Get userbound controls... for (k = 0; k < MAXINPUTMAPPING; k++) - keys[k] = optionsmenu.profile->controls[currentMenu->menuitems[i].mvar1][k]; + { + keys[k] = optionsmenu.profile->controls[gc][k]; + if (keys[k] == KEY_NULL) + continue; + set++; + if (!G_KeyIsAvailable(keys[k], cv_usejoystick[0].value)) + continue; + available++; + }; buf[0] = '\0'; - if (keys[0] == KEY_NULL) // If the first key's null, so should every other. - strcpy(buf, "\x85NOT BOUND"); + // Can't reach any of them? + if (available == 0) + { + if (((3*optionsmenu.ticker)/(2*TICRATE)) & 1) // 1.5 seconds + { + vflags |= V_ORANGEMAP; +#ifdef SHOWCONTROLDEFAULT + if (G_KeyBindIsNecessary(gc)) + { + // Get the defaults for essential keys. + // Went through all the trouble of making this look cool, + // then realised defaulting should only apply to menus. + // Too much opportunity for confusion if kept. + for (k = 0; k < MAXINPUTMAPPING; k++) + { + keys[k] = gamecontroldefault[gc][k]; + if (keys[k] == KEY_NULL) + continue; + available++; + } + set = available; + } + else if (set) +#else + if (!set) + { + if (!G_KeyBindIsNecessary(gc)) + vflags = V_REDMAP|V_6WIDTHSPACE; + } + else +#endif + { + strcpy(buf, "CURRENTLY UNAVAILABLE"); + } + } + else + { + vflags |= V_REDMAP; + } + } + + if (buf[0]) + ; + else if (!set) + strcpy(buf, "NOT BOUND"); else { - for (k=0; k < MAXINPUTMAPPING && keys[k] != KEY_NULL; k++) + for (k = 0; k < MAXINPUTMAPPING; k++) { + if (keys[k] == KEY_NULL) + continue; + if (k > 0) strcat(buf," / "); @@ -2499,18 +2557,17 @@ void M_DrawProfileControls(void) strcat(buf, "\n"); strcat(buf, G_KeynumToString (keys[k])); - } } // don't shift the text if we didn't draw a patch. - V_DrawThinString(x+ (drawnpatch ? 32 : 0), y+ (drawnpatch? 2 : 12), V_6WIDTHSPACE, buf); + V_DrawThinString(x + (drawnpatch ? 32 : 0), y + (drawnpatch ? 2 : 12), vflags, buf); // controller dest coords: - if (itemOn == i && currentMenu->menuitems[i].mvar1 && currentMenu->menuitems[i].mvar1 <= gc_start) + if (itemOn == i && gc > 0 && gc <= gc_start) { - optionsmenu.tcontx = controlleroffsets[currentMenu->menuitems[i].mvar1][0]; - optionsmenu.tconty = controlleroffsets[currentMenu->menuitems[i].mvar1][1]; + optionsmenu.tcontx = controlleroffsets[gc][0]; + optionsmenu.tconty = controlleroffsets[gc][1]; } }