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.
This commit is contained in:
toaster 2022-04-01 20:51:37 +01:00
parent e3f9a925d8
commit ff5992e3c4
4 changed files with 174 additions and 107 deletions

View file

@ -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;

View file

@ -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

View file

@ -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);

View file

@ -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];
}
}