From 9b4a48f2c3f1fdff6e93ab9ed06a9d1a8706b086 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 22 Aug 2021 13:44:46 +0200 Subject: [PATCH] Implement menu navigation using gamepad --- src/Components/Modules/Gamepad.cpp | 275 ++++++++++++++++------------- src/Components/Modules/Gamepad.hpp | 23 +-- src/Game/Functions.hpp | 2 +- src/Game/Structs.hpp | 8 + 4 files changed, 172 insertions(+), 136 deletions(-) diff --git a/src/Components/Modules/Gamepad.cpp b/src/Components/Modules/Gamepad.cpp index f725992e..1c80f6d0 100644 --- a/src/Components/Modules/Gamepad.cpp +++ b/src/Components/Modules/Gamepad.cpp @@ -27,16 +27,30 @@ namespace Game {GPAD_RIGHT, K_DPAD_RIGHT} }; - StickToCodeMap_t analogStickList[] + StickToCodeMap_t analogStickList[4] { - {GPAD_LY, GPAD_STICK_POS, K_DPAD_UP}, - {GPAD_LY, GPAD_STICK_NEG, K_DPAD_DOWN}, - {GPAD_LX, GPAD_STICK_POS, K_DPAD_RIGHT}, - {GPAD_LX, GPAD_STICK_NEG, K_DPAD_LEFT}, + {GPAD_LX, K_APAD_RIGHT, K_APAD_LEFT}, + {GPAD_LY, K_APAD_UP, K_APAD_DOWN}, + {GPAD_RX, K_APAD_RIGHT, K_APAD_LEFT}, + {GPAD_RY, K_APAD_UP, K_APAD_DOWN}, + }; + + GamePadStick stickForAxis[GPAD_PHYSAXIS_COUNT] + { + GPAD_RX, + GPAD_RY, + GPAD_LX, + GPAD_LY, + GPAD_INVALID, + GPAD_INVALID }; keyNum_t menuScrollButtonList[] { + K_APAD_UP, + K_APAD_DOWN, + K_APAD_LEFT, + K_APAD_RIGHT, K_DPAD_UP, K_DPAD_DOWN, K_DPAD_LEFT, @@ -106,6 +120,29 @@ namespace Components std::chrono::milliseconds Gamepad::lastNavigationTime = 0ms; std::chrono::milliseconds Gamepad::msBetweenNavigations = 220ms; + struct ControllerMenuKeyMapping + { + Game::keyNum_t controllerKey; + Game::keyNum_t pcKey; + }; + + ControllerMenuKeyMapping controllerMenuKeyMappings[] + { + {Game::K_BUTTON_A, Game::K_KP_ENTER}, + {Game::K_BUTTON_START, Game::K_KP_ENTER}, + {Game::K_BUTTON_B, Game::K_ESCAPE}, + {Game::K_BUTTON_BACK, Game::K_ESCAPE}, + {Game::K_DPAD_UP, Game::K_KP_UPARROW}, + {Game::K_APAD_UP, Game::K_KP_UPARROW}, + {Game::K_DPAD_DOWN, Game::K_KP_DOWNARROW}, + {Game::K_APAD_DOWN, Game::K_KP_DOWNARROW}, + {Game::K_DPAD_LEFT, Game::K_KP_LEFTARROW}, + {Game::K_APAD_LEFT, Game::K_KP_LEFTARROW}, + {Game::K_DPAD_RIGHT, Game::K_KP_RIGHTARROW}, + {Game::K_APAD_RIGHT, Game::K_KP_RIGHTARROW}, + }; + + // This should be read from a text file in the players/ folder, most probably / or from config_mp.cfg std::vector mappings = { Gamepad::ActionMapping(XINPUT_GAMEPAD_A, "gostand"), @@ -125,14 +162,6 @@ namespace Components }; // Same thing - std::vector menuMappings = { - Gamepad::MenuMapping(XINPUT_GAMEPAD_A, Game::keyNum_t::K_KP_ENTER), - Gamepad::MenuMapping(XINPUT_GAMEPAD_B, Game::keyNum_t::K_ESCAPE), - Gamepad::MenuMapping(XINPUT_GAMEPAD_DPAD_RIGHT, Game::keyNum_t::K_KP_RIGHTARROW), - Gamepad::MenuMapping(XINPUT_GAMEPAD_DPAD_LEFT, Game::keyNum_t::K_KP_LEFTARROW), - Gamepad::MenuMapping(XINPUT_GAMEPAD_DPAD_UP, Game::keyNum_t::K_KP_UPARROW), - Gamepad::MenuMapping(XINPUT_GAMEPAD_DPAD_DOWN, Game::keyNum_t::K_KP_DOWNARROW) - }; // void Gamepad::Vibrate(int leftVal, int rightVal) // { @@ -339,65 +368,6 @@ namespace Components } } - void Gamepad::MenuNavigate() - { - auto& gamePad = gamePads[0]; - Game::menuDef_t* menuDef = Game::Menu_GetFocused(Game::uiContext); - - if (menuDef) - { - if (gamePad.enabled) - { - std::chrono::milliseconds now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); - std::chrono::milliseconds timeSinceLastNavigation = now - lastNavigationTime; - bool canNavigate = timeSinceLastNavigation > msBetweenNavigations; - - if (gamePad.stickDown[1][Game::GPAD_STICK_POS]) - { - if (canNavigate) - { - Menu_SetPrevCursorItem(Game::uiContext, menuDef, 1); - lastMenuNavigationDirection = Game::GPAD_STICK_POS; - lastNavigationTime = now; - } - } - else if (gamePad.stickDown[1][Game::GPAD_STICK_NEG]) - { - if (canNavigate) - { - Menu_SetNextCursorItem(Game::uiContext, menuDef, 1); - lastMenuNavigationDirection = Game::GPAD_STICK_NEG; - lastNavigationTime = now; - } - } - else - { - lastMenuNavigationDirection = Game::GPAD_STICK_DIR_COUNT; - } - - for (auto& mapping : menuMappings) - { - if (mapping.wasPressed) - { - if (gamePad.digitals & mapping.input) - { - // Button still pressed, do not send info - } - else - { - mapping.wasPressed = false; - } - } - else if (gamePad.digitals & mapping.input) - { - Game::UI_KeyEvent(0, mapping.keystroke, 1); - mapping.wasPressed = true; - } - } - } - } - } - int Gamepad::unk_CheckKeyHook(int localClientNum, Game::keyNum_t keyCode) { const auto& gamePad = gamePads[0]; @@ -589,20 +559,99 @@ namespace Components } } - void Gamepad::CL_GamepadEvent(int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, const float value) + void Gamepad::CL_GamepadGenerateAPad(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); + + auto& gamePad = gamePads[gamePadIndex]; + + const auto stick = Game::stickForAxis[physicalAxis]; + const auto stickIndex = stick & Game::GPAD_VALUE_MASK; + if(stick != Game::GPAD_INVALID) + { + assert(stickIndex < 4); + const auto& mapping = Game::analogStickList[stickIndex]; + + if(gamePad.stickDown[stickIndex][Game::GPAD_STICK_POS]) + { + const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; + CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, event, time, Game::GPAD_NONE); + } + else if(gamePad.stickDown[stickIndex][Game::GPAD_STICK_NEG]) + { + const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; + CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, event, time, Game::GPAD_NONE); + } + else if(gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS]) + { + CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, Game::GPAD_BUTTON_RELEASED, time, Game::GPAD_NONE); + } + else if(gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG]) + { + CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, Game::GPAD_BUTTON_RELEASED, time, Game::GPAD_NONE); + } + } + } + + void Gamepad::CL_GamepadEvent(int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, const float value, const unsigned time) { assert(gamePadIndex < Game::MAX_GAMEPADS); assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); Game::gaGlobs[gamePadIndex].axesValues[physicalAxis] = value; + CL_GamepadGenerateAPad(gamePadIndex, physicalAxis, time); } - void Gamepad::UI_GamepadKeyEvent(int gamePadIndex, int key, bool down) + void Gamepad::UI_GamepadKeyEvent(const int gamePadIndex, const int key, const bool down) { - + for(const auto& mapping : controllerMenuKeyMappings) + { + if(mapping.controllerKey == key) + { + Game::UI_KeyEvent(gamePadIndex, mapping.pcKey, down); + return; + } + } + + // No point in sending unmapped controller keystrokes to the key event handler since it doesn't know how to use it anyway + // Game::UI_KeyEvent(gamePadIndex, key, down); } - void Gamepad::CL_GamepadButtonEvent(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, unsigned time, Game::GamePadButton button) + bool Gamepad::CL_CheckForIgnoreDueToRepeat(const int gamePadIndex, const int key, const int repeatCount, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + if(Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + { + const int scrollDelayFirst = gpad_menu_scroll_delay_first.get(); + const int scrollDelayRest = gpad_menu_scroll_delay_rest.get(); + + for(const auto menuScrollButton : Game::menuScrollButtonList) + { + if(key == menuScrollButton) + { + if(repeatCount == 1) + { + gamePadGlobal.nextScrollTime = time + scrollDelayFirst; + return false; + } + + if(time > gamePadGlobal.nextScrollTime) + { + gamePadGlobal.nextScrollTime = time + scrollDelayRest; + return false; + } + break; + } + } + } + + return repeatCount > 1; + } + + void Gamepad::CL_GamepadButtonEvent(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time, const Game::GamePadButton button) { assert(gamePadIndex < Game::MAX_GAMEPADS); @@ -612,18 +661,21 @@ namespace Components auto& keyState = Game::playerKeys[gamePadIndex]; keyState.keys[key].down = pressedOrUpdated; - if(pressed) + if(pressedOrUpdated) { if (++keyState.keys[key].repeats == 1) keyState.anyKeyDown++; } - else if(keyState.keys[key].repeats > 0) + else if(buttonEvent == Game::GPAD_BUTTON_RELEASED && keyState.keys[key].repeats > 0) { keyState.keys[key].repeats = 0; if (--keyState.anyKeyDown < 0) keyState.anyKeyDown = 0; } + if (pressedOrUpdated && CL_CheckForIgnoreDueToRepeat(gamePadIndex, key, keyState.keys[key].repeats, time)) + return; + if(Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_LOCATION_SELECTION) && pressedOrUpdated) { if(key == Game::K_BUTTON_B || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+actionslot 4") == 0) @@ -640,10 +692,7 @@ namespace Components keyState.locSelInputState = Game::LOC_SEL_INPUT_NONE; const auto* keyBinding = keyState.keys[key].binding; - - if (!keyBinding) - return; - + char cmd[1024]; if(pressedOrUpdated) { @@ -653,26 +702,29 @@ namespace Components return; } - if(keyBinding[0] == '+') + if (keyBinding) { - float floatValue; - if (button) - floatValue = GPad_GetButton(gamePadIndex, button); - else - floatValue = 0.0f; + if (keyBinding[0] == '+') + { + float floatValue; + if (button) + floatValue = GPad_GetButton(gamePadIndex, button); + else + floatValue = 0.0f; - sprintf_s(cmd, "%s %i %i %0.3f\n", keyBinding, key, time, floatValue); - Game::Cbuf_AddText(gamePadIndex, cmd); - } - else - { - Game::Cbuf_AddText(gamePadIndex, keyBinding); - Game::Cbuf_AddText(gamePadIndex, "\n"); + sprintf_s(cmd, "%s %i %i %0.3f\n", keyBinding, key, time, floatValue); + Game::Cbuf_AddText(gamePadIndex, cmd); + } + else + { + Game::Cbuf_AddText(gamePadIndex, keyBinding); + Game::Cbuf_AddText(gamePadIndex, "\n"); + } } } else { - if (keyBinding[0] == '+') + if (keyBinding && keyBinding[0] == '+') { float floatValue; if (button) @@ -765,19 +817,8 @@ namespace Components if(button & Game::GPAD_DIGITAL_MASK) { const auto buttonValue = button & Game::GPAD_VALUE_MASK; - if (button & 0xF && buttonValue & gamePad.digitals && (gamePad.digitals & 0xF) != (button & 0xF)) - { - down = false; - lastDown = false; - } - else - { - down = (buttonValue & gamePad.digitals) != 0; - if (button & 0xF && buttonValue & gamePad.lastDigitals && (gamePad.lastDigitals & 0xF) != (button & 0xF)) - lastDown = false; - else - lastDown = (buttonValue & gamePad.lastDigitals) != 0; - } + down = (buttonValue & gamePad.digitals) != 0; + lastDown = (buttonValue & gamePad.lastDigitals) != 0; } else if(button & Game::GPAD_ANALOG_MASK) { @@ -796,7 +837,7 @@ namespace Components bool Gamepad::GPad_ButtonRequiresUpdates(const int gamePadIndex, Game::GamePadButton button) { - return button & Game::GPAD_ANALOG_MASK && GPad_GetButton(gamePadIndex, button) > 0.0f; + return (button & Game::GPAD_ANALOG_MASK || button & Game::GPAD_DPAD_MASK) && GPad_GetButton(gamePadIndex, button) > 0.0f; } bool Gamepad::GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button) @@ -985,12 +1026,12 @@ namespace Components const auto leftTrig = GPad_GetButton(gamePadIndex, Game::GPAD_L_TRIG); const auto rightTrig = GPad_GetButton(gamePadIndex, Game::GPAD_R_TRIG); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_X, lx); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_Y, ly); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_X, rx); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_Y, ry); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LTRIGGER, leftTrig); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RTRIGGER, rightTrig); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_X, lx, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_Y, ly, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_X, rx, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_Y, ry, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LTRIGGER, leftTrig, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RTRIGGER, rightTrig, time); for (const auto& buttonMapping : Game::buttonList) { @@ -1109,8 +1150,6 @@ namespace Components //Utils::Hook(0x5A6816, CL_GetMouseMovementStub, HOOK_CALL).install()->quick(); //Utils::Hook(0x5A6829, unk_CheckKeyHook, HOOK_CALL).install()->quick(); - //Scheduler::OnFrame(MenuNavigate); - xpadSensitivity = Dvar::Register("xpad_sensitivity", 1.9f, 0.1f, 10.0f, Game::DVAR_FLAG_SAVED, "View sensitivity for XInput-compatible gamepads"); xpadEarlyTime = Dvar::Register("xpad_early_time", 130, 0, 1000, Game::DVAR_FLAG_SAVED, "Time (in milliseconds) of reduced view sensitivity"); xpadEarlyMultiplier = Dvar::Register("xpad_early_multiplier", 0.25f, 0.01f, 1.0f, Game::DVAR_FLAG_SAVED, diff --git a/src/Components/Modules/Gamepad.hpp b/src/Components/Modules/Gamepad.hpp index 6c0fd096..b33f5ad3 100644 --- a/src/Components/Modules/Gamepad.hpp +++ b/src/Components/Modules/Gamepad.hpp @@ -5,6 +5,7 @@ namespace Game static constexpr auto MAX_GAMEPADS = 1; static constexpr auto GPAD_VALUE_MASK = 0xFFFFFFFu; + static constexpr auto GPAD_DPAD_MASK = XINPUT_GAMEPAD_DPAD_UP | XINPUT_GAMEPAD_DPAD_DOWN | XINPUT_GAMEPAD_DPAD_LEFT | XINPUT_GAMEPAD_DPAD_RIGHT; static constexpr auto GPAD_DIGITAL_MASK = 1u << 28; static constexpr auto GPAD_ANALOG_MASK = 1u << 29; static constexpr auto GPAD_STICK_MASK = 1u << 30; @@ -84,8 +85,8 @@ namespace Game struct StickToCodeMap_t { GamePadStick padStick; - GamePadStickDir padStickDir; - int code; + int posCode; + int negCode; }; struct GamepadVirtualAxisMapping @@ -151,19 +152,6 @@ namespace Components } }; - struct MenuMapping - { - int input; - Game::keyNum_t keystroke; - bool wasPressed = false; - - MenuMapping(int input, Game::keyNum_t keystroke) - { - this->keystroke = keystroke; - this->input = input; - } - }; - private: static GamePad gamePads[Game::MAX_GAMEPADS]; static GamePadGlobals gamePadGlobals[Game::MAX_GAMEPADS]; @@ -210,7 +198,6 @@ namespace Components static void CL_CreateCmdStub(); static void CL_GamepadMove(int, Game::usercmd_s*); - static void MenuNavigate(); static void MSG_WriteDeltaUsercmdKeyStub(); @@ -224,8 +211,10 @@ namespace Components static void GamepadStickTo01(SHORT value, SHORT deadzone, float& output01); static void CL_GamepadResetMenuScrollTime(int gamePadIndex, int key, bool down, unsigned int time); + static bool CL_CheckForIgnoreDueToRepeat(int gamePadIndex, int key, int repeatCount, unsigned int time); static void UI_GamepadKeyEvent(int gamePadIndex, int key, bool down); - static void CL_GamepadEvent(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, float value); + static void CL_GamepadGenerateAPad(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, unsigned time); + static void CL_GamepadEvent(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, float value, unsigned time); static void CL_GamepadButtonEvent(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned time, Game::GamePadButton button); static void CL_GamepadButtonEventForPort(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned int time, Game::GamePadButton button); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 37a243d6..fdeaeeb2 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -426,7 +426,7 @@ namespace Game typedef void(__cdecl* Menu_HandleKey_t)(UiContext* ctx, menuDef_t* menu, Game::keyNum_t key, int down); extern Menu_HandleKey_t Menu_HandleKey; - typedef bool(__cdecl* UI_KeyEvent_t)(int clientNum, Game::keyNum_t key, int down); + typedef bool(__cdecl* UI_KeyEvent_t)(int clientNum, int key, int down); extern UI_KeyEvent_t UI_KeyEvent; typedef void(__cdecl * MSG_Init_t)(msg_t *buf, char *data, int length); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index d99481d2..269ec59d 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -249,6 +249,14 @@ namespace Game K_DPAD_UPDOWN = 0x19, K_LASTGAMEPADBUTTON_RANGE_2 = 0x19, // Last Gamepad 2 K_ESCAPE = 0x1B, + K_FIRSTGAMEPADBUTTON_RANGE_3 = 0x1C, // First Gamepad 3 + K_FIRSTAPAD = 0x1C, // First APad + K_APAD_UP = 0x1C, + K_APAD_DOWN = 0x1D, + K_APAD_LEFT = 0x1E, + K_APAD_RIGHT = 0x1F, + K_LASTAPAD = 0x1F, // Last APad + K_LASTGAMEPADBUTTON_RANGE_3 = 0x1F, // Last Gamepad 3 K_SPACE = 0x20, K_BACKSPACE = 0x7F, K_ASCII_FIRST = 0x80,