From b3adacb71deec026633cff4869478716e04f3341 Mon Sep 17 00:00:00 2001 From: rackover Date: Tue, 4 May 2021 15:47:46 +0200 Subject: [PATCH] Gamepad support for menus --- src/Components/Modules/XInput.cpp | 86 ++++++++++++++++++++++-- src/Components/Modules/XInput.hpp | 17 +++++ src/Game/Functions.cpp | 29 ++++++++ src/Game/Functions.hpp | 11 ++++ src/Game/Structs.hpp | 106 ++++++++++++++++++++++++++++++ 5 files changed, 243 insertions(+), 6 deletions(-) diff --git a/src/Components/Modules/XInput.cpp b/src/Components/Modules/XInput.cpp index af7586df..498bf066 100644 --- a/src/Components/Modules/XInput.cpp +++ b/src/Components/Modules/XInput.cpp @@ -8,11 +8,15 @@ namespace Components std::chrono::milliseconds XInput::timeAtFirstHeldMaxLookX = 0ms; // "For how much time in miliseconds has the player been holding a horizontal direction on their stick, fully" (-1.0 or 1.0) bool XInput::isHoldingMaxLookX = false; - float XInput::lockedSensitivityMultiplier = 0.5f; - float XInput::generalXSensitivityMultiplier = 1.6f; + float XInput::lockedSensitivityMultiplier = 0.45f; + float XInput::generalXSensitivityMultiplier = 1.5f; float XInput::generalYSensitivityMultiplier = 0.8f; - std::chrono::milliseconds XInput::msBeforeUnlockingSensitivity = 250ms; + float XInput::lastMenuNavigationDirection = .0f; + std::chrono::milliseconds XInput::lastNavigationTime = 0ms; + std::chrono::milliseconds XInput::msBetweenNavigations = 220ms; + + std::chrono::milliseconds XInput::msBeforeUnlockingSensitivity = 350ms; std::vector mappings = { XInput::ActionMapping(XINPUT_GAMEPAD_A, "gostand"), @@ -31,6 +35,14 @@ namespace Components XInput::ActionMapping(XINPUT_GAMEPAD_DPAD_DOWN, "actionslot 4"), }; + std::vector menuMappings = { + XInput::MenuMapping(XINPUT_GAMEPAD_A, Game::keyNum_t::K_KP_ENTER), + XInput::MenuMapping(XINPUT_GAMEPAD_B, Game::keyNum_t::K_ESCAPE), + XInput::MenuMapping(XINPUT_GAMEPAD_DPAD_RIGHT, Game::keyNum_t::K_KP_RIGHTARROW), + XInput::MenuMapping(XINPUT_GAMEPAD_DPAD_LEFT, Game::keyNum_t::K_KP_LEFTARROW), + XInput::MenuMapping(XINPUT_GAMEPAD_DPAD_UP, Game::keyNum_t::K_KP_UPARROW), + XInput::MenuMapping(XINPUT_GAMEPAD_DPAD_DOWN, Game::keyNum_t::K_KP_DOWNARROW) + }; void XInput::Vibrate(int leftVal, int rightVal) { @@ -108,12 +120,12 @@ namespace Components viewStickX *= XInput::lockedSensitivityMultiplier; } #else - float coeff = std::clamp(hasBeenHoldingLeftXForMs.count()/(float)XInput::msBeforeUnlockingSensitivity.count(), 0.0F, 1.0F); - viewStickX *= XInput::lockedSensitivityMultiplier + coeff * (1.0f -XInput::lockedSensitivityMultiplier); + float coeff = std::clamp(hasBeenHoldingLeftXForMs.count() / (float)XInput::msBeforeUnlockingSensitivity.count(), 0.0F, 1.0F); + viewStickX *= XInput::lockedSensitivityMultiplier + coeff * (1.0f - XInput::lockedSensitivityMultiplier); #endif } } - else{ + else { XInput::isHoldingMaxLookX = false; XInput::timeAtFirstHeldMaxLookX = 0ms; } @@ -281,6 +293,67 @@ namespace Components } } + void XInput::MenuNavigate() { + + Game::menuDef_t* menuDef = Game::Menu_GetFocused(Game::uiContext); + +#define SIGN(d) ((d > 0) - (d < 0)) + + if (menuDef) { + PollXInputDevices(); + + if (XInput::xiPlayerNum != -1) + { + XINPUT_STATE* xiState = &xiStates[xiPlayerNum]; + + // Up/Down + float moveStickX = abs(xiState->Gamepad.sThumbLX) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE ? xiState->Gamepad.sThumbLX / (float)std::numeric_limits().max() : .0f; + float moveStickY = abs(xiState->Gamepad.sThumbLY) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE ? xiState->Gamepad.sThumbLY / (float)std::numeric_limits().max() : .0f; + + 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 (moveStickY > .0f) { + if (canNavigate || SIGN(moveStickY) != SIGN(lastMenuNavigationDirection)) { + Game::Menu_SetPrevCursorItem(Game::uiContext, menuDef, 1); + lastMenuNavigationDirection = moveStickY; + lastNavigationTime = now; + } + } + else if (moveStickY < .0f) { + if (canNavigate || SIGN(moveStickY) != SIGN(lastMenuNavigationDirection)) { + Game::Menu_SetNextCursorItem(Game::uiContext, menuDef, 1); + lastMenuNavigationDirection = moveStickY; + lastNavigationTime = now; + } + } + else { + lastMenuNavigationDirection = .0f; + } + + for (size_t i = 0; i < menuMappings.size(); i++) + { + MenuMapping mapping = menuMappings[i]; + auto action = mapping.keystroke; + + if (mapping.wasPressed) { + if (xiState->Gamepad.wButtons & mapping.input) { + // Button still pressed, do not send info + } + else { + menuMappings[i].wasPressed = false; + } + } + else if(xiState->Gamepad.wButtons & mapping.input){ + Game::UI_KeyEvent(0, mapping.keystroke, 1); + menuMappings[i].wasPressed = true; + } + } + } + } + } + XInput::XInput() { // poll xinput devices every client frame @@ -306,5 +379,6 @@ namespace Components Vibrate(3000, 3000); } + Scheduler::OnFrame(MenuNavigate); } } diff --git a/src/Components/Modules/XInput.hpp b/src/Components/Modules/XInput.hpp index 1d79bd44..36d584b5 100644 --- a/src/Components/Modules/XInput.hpp +++ b/src/Components/Modules/XInput.hpp @@ -23,6 +23,18 @@ 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 XINPUT_STATE xiStates[XUSER_MAX_COUNT]; static int xiPlayerNum; @@ -35,6 +47,10 @@ namespace Components static float generalXSensitivityMultiplier; static float generalYSensitivityMultiplier; + static std::chrono::milliseconds lastNavigationTime; + static std::chrono::milliseconds msBetweenNavigations; + static float lastMenuNavigationDirection; + static void Vibrate(int leftVal = 0, int rightVal = 0); static void CL_FrameStub(); @@ -42,6 +58,7 @@ namespace Components static void CL_CreateCmdStub(); static void CL_GamepadMove(int, Game::usercmd_s*); + static void MenuNavigate(); static void MSG_WriteDeltaUsercmdKeyStub(); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 954f771a..eeffd563 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -175,6 +175,8 @@ namespace Game Menus_FindByName_t Menus_FindByName = Menus_FindByName_t(0x487240); Menu_IsVisible_t Menu_IsVisible = Menu_IsVisible_t(0x4D77D0); Menus_MenuIsInStack_t Menus_MenuIsInStack = Menus_MenuIsInStack_t(0x47ACB0); + Menu_HandleKey_t Menu_HandleKey = Menu_HandleKey_t(0x4C4A00); + Menu_GetFocused_t Menu_GetFocused = Menu_GetFocused_t(0x4AFF10); MSG_Init_t MSG_Init = MSG_Init_t(0x45FCA0); MSG_ReadBit_t MSG_ReadBit = MSG_ReadBit_t(0x476D20); @@ -327,6 +329,7 @@ namespace Game UI_GetContext_t UI_GetContext = UI_GetContext_t(0x4F8940); UI_TextWidth_t UI_TextWidth = UI_TextWidth_t(0x6315C0); UI_DrawText_t UI_DrawText = UI_DrawText_t(0x49C0D0); + UI_KeyEvent_t UI_KeyEvent = UI_KeyEvent_t(0x4970F0); Win_GetLanguage_t Win_GetLanguage = Win_GetLanguage_t(0x45CBA0); @@ -1141,6 +1144,32 @@ namespace Game } } + void Menu_SetNextCursorItem(Game::UiContext* a1, Game::menuDef_t* a2, int unk) + { + __asm + { + push unk + push a2 + mov eax, a1 + mov ebx, 0x639FE0 + call ebx + add esp, 0x8 // 2 args = 2x4 + } + } + + void Menu_SetPrevCursorItem(Game::UiContext* a1, Game::menuDef_t* a2, int unk) + { + __asm + { + push unk + push a2 + mov eax, a1 + mov ebx, 0x639F20 + call ebx + add esp, 0x8 // 2 args = 2x4 + } + } + __declspec(naked) void R_AddDebugLine(float* /*color*/, float* /*v1*/, float* /*v2*/) { __asm diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index a15cc01e..23cb11fb 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -414,6 +414,15 @@ namespace Game typedef bool(__cdecl * Menus_MenuIsInStack_t)(UiContext *dc, menuDef_t *menu); extern Menus_MenuIsInStack_t Menus_MenuIsInStack; + typedef menuDef_t*(__cdecl* Menu_GetFocused_t)(UiContext* ctx); + extern Menu_GetFocused_t Menu_GetFocused; + + 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); + extern UI_KeyEvent_t UI_KeyEvent; + typedef void(__cdecl * MSG_Init_t)(msg_t *buf, char *data, int length); extern MSG_Init_t MSG_Init; @@ -874,6 +883,8 @@ namespace Game XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize); void Menu_FreeItemMemory(Game::itemDef_s* item); + void Menu_SetNextCursorItem(Game::UiContext* ctx, Game::menuDef_t* currentMenu, int unk = 1); + void Menu_SetPrevCursorItem(Game::UiContext* ctx, Game::menuDef_t* currentMenu, int unk = 1); const char* TableLookup(StringTable* stringtable, int row, int column); const char* UI_LocalizeMapName(const char* mapName); const char* UI_LocalizeGameType(const char* gameType); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 3f1a30e7..269717b8 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -173,6 +173,112 @@ namespace Game }; #pragma pack(pop) + enum keyNum_t + { + K_NONE = 0x0, + K_TAB = 0x9, + K_ENTER = 0xD, + K_ESCAPE = 0x1B, + K_SPACE = 0x20, + K_BACKSPACE = 0x7F, + K_ASCII_FIRST = 0x80, + K_ASCII_181 = 0x80, + K_ASCII_191 = 0x81, + K_ASCII_223 = 0x82, + K_ASCII_224 = 0x83, + K_ASCII_225 = 0x84, + K_ASCII_228 = 0x85, + K_ASCII_229 = 0x86, + K_ASCII_230 = 0x87, + K_ASCII_231 = 0x88, + K_ASCII_232 = 0x89, + K_ASCII_233 = 0x8A, + K_ASCII_236 = 0x8B, + K_ASCII_241 = 0x8C, + K_ASCII_242 = 0x8D, + K_ASCII_243 = 0x8E, + K_ASCII_246 = 0x8F, + K_ASCII_248 = 0x90, + K_ASCII_249 = 0x91, + K_ASCII_250 = 0x92, + K_ASCII_252 = 0x93, + K_END_ASCII_CHARS = 0x94, + K_COMMAND = 0x96, + K_CAPSLOCK = 0x97, + K_POWER = 0x98, + K_PAUSE = 0x99, + K_UPARROW = 0x9A, + K_DOWNARROW = 0x9B, + K_LEFTARROW = 0x9C, + K_RIGHTARROW = 0x9D, + K_ALT = 0x9E, + K_CTRL = 0x9F, + K_SHIFT = 0xA0, + K_INS = 0xA1, + K_DEL = 0xA2, + K_PGDN = 0xA3, + K_PGUP = 0xA4, + K_HOME = 0xA5, + K_END = 0xA6, + K_F1 = 0xA7, + K_F2 = 0xA8, + K_F3 = 0xA9, + K_F4 = 0xAA, + K_F5 = 0xAB, + K_F6 = 0xAC, + K_F7 = 0xAD, + K_F8 = 0xAE, + K_F9 = 0xAF, + K_F10 = 0xB0, + K_F11 = 0xB1, + K_F12 = 0xB2, + K_F13 = 0xB3, + K_F14 = 0xB4, + K_F15 = 0xB5, + K_KP_HOME = 0xB6, + K_KP_UPARROW = 0xB7, + K_KP_PGUP = 0xB8, + K_KP_LEFTARROW = 0xB9, + K_KP_5 = 0xBA, + K_KP_RIGHTARROW = 0xBB, + K_KP_END = 0xBC, + K_KP_DOWNARROW = 0xBD, + K_KP_PGDN = 0xBE, + K_KP_ENTER = 0xBF, + K_KP_INS = 0xC0, + K_KP_DEL = 0xC1, + K_KP_SLASH = 0xC2, + K_KP_MINUS = 0xC3, + K_KP_PLUS = 0xC4, + K_KP_NUMLOCK = 0xC5, + K_KP_STAR = 0xC6, + K_KP_EQUALS = 0xC7, + K_MOUSE1 = 0xC8, + K_MOUSE2 = 0xC9, + K_MOUSE3 = 0xCA, + K_MOUSE4 = 0xCB, + K_MOUSE5 = 0xCC, + K_MWHEELDOWN = 0xCD, + K_MWHEELUP = 0xCE, + K_AUX1 = 0xCF, + K_AUX2 = 0xD0, + K_AUX3 = 0xD1, + K_AUX4 = 0xD2, + K_AUX5 = 0xD3, + K_AUX6 = 0xD4, + K_AUX7 = 0xD5, + K_AUX8 = 0xD6, + K_AUX9 = 0xD7, + K_AUX10 = 0xD8, + K_AUX11 = 0xD9, + K_AUX12 = 0xDA, + K_AUX13 = 0xDB, + K_AUX14 = 0xDC, + K_AUX15 = 0xDD, + K_AUX16 = 0xDE, + K_LAST_KEY = 0xDF, + }; + struct __declspec(align(4)) PhysPreset { const char *name;