From f4396d6d2dea413e35eace437162a4696a6e1c46 Mon Sep 17 00:00:00 2001 From: Northfear <11291116+Northfear@users.noreply.github.com> Date: Wed, 28 Apr 2021 18:30:57 +0300 Subject: [PATCH] Controller support --- common/settings.cpp | 3 + common/settings.h | 1 + common/video.h | 2 + common/video_sdl2.cpp | 23 +++- common/wwkeyboard.cpp | 233 ++++++++++++++++++++++++++++++++++++++++ common/wwkeyboard.h | 58 ++++++++++ redalert/scroll.cpp | 8 ++ tiberiandawn/scroll.cpp | 8 ++ 8 files changed, 335 insertions(+), 1 deletion(-) diff --git a/common/settings.cpp b/common/settings.cpp index 89a03a9e..1ec31121 100644 --- a/common/settings.cpp +++ b/common/settings.cpp @@ -11,6 +11,7 @@ SettingsClass::SettingsClass() */ Mouse.RawInput = true; Mouse.Sensitivity = 100; + Mouse.ControllerPointerSpeed = 10; /* ** Video settings @@ -36,6 +37,7 @@ void SettingsClass::Load(INIClass& ini) */ Mouse.RawInput = ini.Get_Bool("Mouse", "RawInput", Mouse.RawInput); Mouse.Sensitivity = ini.Get_Int("Mouse", "Sensitivity", Mouse.Sensitivity); + Mouse.ControllerPointerSpeed = ini.Get_Int("Mouse", "ControllerPointerSpeed", Mouse.ControllerPointerSpeed); /* ** Video settings @@ -72,6 +74,7 @@ void SettingsClass::Save(INIClass& ini) */ ini.Put_Bool("Mouse", "RawInput", Mouse.RawInput); ini.Put_Int("Mouse", "Sensitivity", Mouse.Sensitivity); + ini.Put_Int("Mouse", "ControllerPointerSpeed", Mouse.ControllerPointerSpeed); /* ** Video settings diff --git a/common/settings.h b/common/settings.h index aa4a6c2c..387fac72 100644 --- a/common/settings.h +++ b/common/settings.h @@ -16,6 +16,7 @@ class SettingsClass { bool RawInput; int Sensitivity; + int ControllerPointerSpeed; } Mouse; struct diff --git a/common/video.h b/common/video.h index d9c7f4ae..978ca17b 100644 --- a/common/video.h +++ b/common/video.h @@ -69,6 +69,8 @@ void Toggle_Video_Fullscreen(); void Reset_Video_Mode(); unsigned Get_Free_Video_Memory(); void Wait_Blit(); +void Get_Game_Resolution(int& w, int& h); +void Set_Video_Mouse(int x, int y); /* ** Set desired cursor image in game palette. diff --git a/common/video_sdl2.cpp b/common/video_sdl2.cpp index 59a5460f..fd7b57e3 100644 --- a/common/video_sdl2.cpp +++ b/common/video_sdl2.cpp @@ -47,6 +47,7 @@ #include +extern WWKeyboardClass* Keyboard; static SDL_Window* window; static SDL_Renderer* renderer; static SDL_Palette* palette; @@ -333,6 +334,12 @@ bool Set_Video_Mode(int w, int h, int bits_per_pixel) hwcursor.Y = h / 2; Update_HWCursor_Settings(); + /* + ** Init gamepad. + */ + SDL_Init(SDL_INIT_GAMECONTROLLER); + Keyboard->Open_Controller(); + return true; } @@ -419,7 +426,7 @@ void Move_Video_Mouse(int xrel, int yrel) void Get_Video_Mouse(int& x, int& y) { - if (Settings.Mouse.RawInput && (hwcursor.Clip || !Settings.Video.Windowed)) { + if (Keyboard->Is_Gamepad_Active() || (Settings.Mouse.RawInput && (hwcursor.Clip || !Settings.Video.Windowed))) { x = hwcursor.X; y = hwcursor.Y; } else { @@ -431,6 +438,18 @@ void Get_Video_Mouse(int& x, int& y) } } +void Get_Game_Resolution(int& w, int& h) +{ + w = hwcursor.GameW; + h = hwcursor.GameH; +} + +void Set_Video_Mouse(int x, int y) +{ + hwcursor.X = x; + hwcursor.Y = y; +} + /*********************************************************************************************** * Reset_Video_Mode -- Resets video mode and deletes Direct Draw Object * * * @@ -468,6 +487,8 @@ void Reset_Video_Mode(void) SDL_DestroyWindow(window); window = nullptr; + + Keyboard->Close_Controller(); } static void Update_HWCursor() diff --git a/common/wwkeyboard.cpp b/common/wwkeyboard.cpp index a79b4657..fc936d0d 100644 --- a/common/wwkeyboard.cpp +++ b/common/wwkeyboard.cpp @@ -552,6 +552,13 @@ void WWKeyboardClass::Fill_Buffer_From_System(void) break; case SDL_MOUSEMOTION: Move_Video_Mouse(event.motion.xrel, event.motion.yrel); + if (Is_Gamepad_Active()) { + int mousePosX; + int mousePosY; + Get_Video_Mouse(mousePosX, mousePosY); + EmulatedPointerPosX = mousePosX; + EmulatedPointerPosY = mousePosY; + } break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: { @@ -595,8 +602,32 @@ void WWKeyboardClass::Fill_Buffer_From_System(void) break; } break; + case SDL_CONTROLLERDEVICEREMOVED: + if (GameController != nullptr) { + const SDL_GameController* removedController = SDL_GameControllerFromInstanceID(event.jdevice.which); + if (removedController == GameController) { + SDL_GameControllerClose(GameController); + GameController = nullptr; + } + } + break; + case SDL_CONTROLLERDEVICEADDED: + if (GameController == nullptr) { + GameController = SDL_GameControllerOpen(event.jdevice.which); + } + break; + case SDL_CONTROLLERAXISMOTION: + Handle_Controller_Axis_Event(event.caxis); + break; + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + Handle_Controller_Button_Event(event.cbutton); + break; } } + if (Is_Gamepad_Active()) { + Process_Controller_Axis_Motion(); + } #elif defined(_WIN32) if (!Is_Buffer_Full()) { MSG msg; @@ -611,6 +642,208 @@ void WWKeyboardClass::Fill_Buffer_From_System(void) #endif } +#ifdef SDL2_BUILD +bool WWKeyboardClass::Is_Gamepad_Active() +{ + return GameController != nullptr; +} + +void WWKeyboardClass::Open_Controller() +{ + for (int i = 0; i < SDL_NumJoysticks(); ++i) { + if (SDL_IsGameController(i)) { + GameController = SDL_GameControllerOpen(i); + } + } + + int mousePosX; + int mousePosY; + Get_Video_Mouse(mousePosX, mousePosY); + EmulatedPointerPosX = mousePosX; + EmulatedPointerPosY = mousePosY; +} + +void WWKeyboardClass::Close_Controller() +{ + if (SDL_GameControllerGetAttached(GameController)) { + SDL_GameControllerClose(GameController); + GameController = nullptr; + } +} + +void WWKeyboardClass::Process_Controller_Axis_Motion() +{ + const uint32_t currentTime = SDL_GetTicks(); + const double deltaTime = currentTime - LastControllerTime; + LastControllerTime = currentTime; + + if (ControllerLeftXAxis != 0 || ControllerLeftYAxis != 0) { + const int16_t xSign = (ControllerLeftXAxis > 0) - (ControllerLeftXAxis < 0); + const int16_t ySign = (ControllerLeftYAxis > 0) - (ControllerLeftYAxis < 0); + + EmulatedPointerPosX += pow(std::abs(ControllerLeftXAxis), CONTROLLER_AXIS_SPEEDUP) * xSign * deltaTime + * Settings.Mouse.ControllerPointerSpeed / CONTROLLER_SPEED_MOD * ControllerSpeedBoost; + EmulatedPointerPosY += pow(std::abs(ControllerLeftYAxis), CONTROLLER_AXIS_SPEEDUP) * ySign * deltaTime + * Settings.Mouse.ControllerPointerSpeed / CONTROLLER_SPEED_MOD * ControllerSpeedBoost; + + int width; + int height; + Get_Game_Resolution(width, height); + + if (EmulatedPointerPosX < 0) + EmulatedPointerPosX = 0; + else if (EmulatedPointerPosX >= width) + EmulatedPointerPosX = width - 1; + + if (EmulatedPointerPosY < 0) + EmulatedPointerPosY = 0; + else if (EmulatedPointerPosY >= height) + EmulatedPointerPosY = height - 1; + + Set_Video_Mouse(EmulatedPointerPosX, EmulatedPointerPosY); + } +} + +void WWKeyboardClass::Handle_Controller_Axis_Event(const SDL_ControllerAxisEvent& motion) +{ + AnalogScrollActive = false; + ScrollDirType directionX = SDIR_NONE; + ScrollDirType directionY = SDIR_NONE; + + if (motion.axis == SDL_CONTROLLER_AXIS_LEFTX) { + if (std::abs(motion.value) > CONTROLLER_L_DEADZONE) + ControllerLeftXAxis = motion.value; + else + ControllerLeftXAxis = 0; + } else if (motion.axis == SDL_CONTROLLER_AXIS_LEFTY) { + if (std::abs(motion.value) > CONTROLLER_L_DEADZONE) + ControllerLeftYAxis = motion.value; + else + ControllerLeftYAxis = 0; + } else if (motion.axis == SDL_CONTROLLER_AXIS_RIGHTX) { + if (std::abs(motion.value) > CONTROLLER_R_DEADZONE) + ControllerRightXAxis = motion.value; + else + ControllerRightXAxis = 0; + } else if (motion.axis == SDL_CONTROLLER_AXIS_RIGHTY) { + if (std::abs(motion.value) > CONTROLLER_R_DEADZONE) + ControllerRightYAxis = motion.value; + else + ControllerRightYAxis = 0; + } else if (motion.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { + if (std::abs(motion.value) > CONTROLLER_TRIGGER_R_DEADZONE) + ControllerSpeedBoost = 1 + (static_cast(motion.value) / 32767) * CONTROLLER_TRIGGER_SPEEDUP; + else + ControllerSpeedBoost = 1; + } + + if (ControllerRightXAxis != 0) { + AnalogScrollActive = true; + directionX = ControllerRightXAxis > 0 ? SDIR_E : SDIR_W; + } + if (ControllerRightYAxis != 0) { + AnalogScrollActive = true; + directionY = ControllerRightYAxis > 0 ? SDIR_S : SDIR_N; + } + + if (directionX == SDIR_E && directionY == SDIR_N) + ScrollDirection = SDIR_NE; + else if (directionX == SDIR_E && directionY == SDIR_S) + ScrollDirection = SDIR_SE; + else if (directionX == SDIR_W && directionY == SDIR_N) + ScrollDirection = SDIR_NW; + else if (directionX == SDIR_W && directionY == SDIR_S) + ScrollDirection = SDIR_SW; + else if (directionX == SDIR_E) + ScrollDirection = SDIR_E; + else if (directionX == SDIR_W) + ScrollDirection = SDIR_W; + else if (directionY == SDIR_S) + ScrollDirection = SDIR_S; + else if (directionY == SDIR_N) + ScrollDirection = SDIR_N; +} + +void WWKeyboardClass::Handle_Controller_Button_Event(const SDL_ControllerButtonEvent& button) +{ + bool keyboardPress = false; + bool mousePress = false; + unsigned short key; + SDL_Scancode scancode; + + switch (button.button) { + case SDL_CONTROLLER_BUTTON_A: + mousePress = true; + key = VK_LBUTTON; + break; + case SDL_CONTROLLER_BUTTON_B: + mousePress = true; + key = VK_RBUTTON; + break; + case SDL_CONTROLLER_BUTTON_X: + keyboardPress = true; + scancode = SDL_SCANCODE_G; + break; + case SDL_CONTROLLER_BUTTON_Y: + keyboardPress = true; + scancode = SDL_SCANCODE_F; + break; + case SDL_CONTROLLER_BUTTON_BACK: + keyboardPress = true; + scancode = SDL_SCANCODE_ESCAPE; + break; + case SDL_CONTROLLER_BUTTON_START: + keyboardPress = true; + scancode = SDL_SCANCODE_RETURN; + break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + keyboardPress = true; + scancode = SDL_SCANCODE_LCTRL; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + keyboardPress = true; + scancode = SDL_SCANCODE_LALT; + break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + keyboardPress = true; + scancode = SDL_SCANCODE_1; + break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + keyboardPress = true; + scancode = SDL_SCANCODE_2; + break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + keyboardPress = true; + scancode = SDL_SCANCODE_3; + break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + keyboardPress = true; + scancode = SDL_SCANCODE_4; + break; + default: + break; + } + + if (keyboardPress) { + Put_Key_Message(scancode, button.state == SDL_RELEASED); + } else if (mousePress) { + int x, y; + Get_Video_Mouse(x, y); + Put_Mouse_Message(key, x, y, button.state == SDL_RELEASED); + } +} + +bool WWKeyboardClass::Is_Analog_Scroll_Active() +{ + return AnalogScrollActive; +} + +unsigned char WWKeyboardClass::Get_Scroll_Direction() +{ + return ScrollDirection; +} +#endif + /*********************************************************************************************** * WWKeyboardClass::Clear -- Clears the keyboard buffer. * * * diff --git a/common/wwkeyboard.h b/common/wwkeyboard.h index 3a5b89b9..e59a6191 100644 --- a/common/wwkeyboard.h +++ b/common/wwkeyboard.h @@ -38,6 +38,10 @@ #endif #include +#ifdef SDL2_BUILD +#include +#endif + typedef enum { WWKEY_SHIFT_BIT = 0x100, @@ -709,6 +713,19 @@ typedef enum KeyNumType : unsigned short KN_BUTTON = WWKEY_BTN_BIT, } KeyNumType; +typedef enum ScrollDirType : unsigned char +{ + SDIR_N = 0, + SDIR_NE = 1 << 5, + SDIR_E = 2 << 5, + SDIR_SE = 3 << 5, + SDIR_S = 4 << 5, + SDIR_SW = 5 << 5, + SDIR_W = 6 << 5, + SDIR_NW = 7 << 5, + SDIR_NONE = 100 +} ScrollDirType; + class WWKeyboardClass { public: @@ -723,6 +740,14 @@ class WWKeyboardClass KeyASCIIType To_ASCII(unsigned short num); bool Down(unsigned short key); +#ifdef SDL2_BUILD + bool Is_Gamepad_Active(); + void Open_Controller(); + void Close_Controller(); + bool Is_Analog_Scroll_Active(); + unsigned char Get_Scroll_Direction(); +#endif + #if defined(_WIN32) && !defined(SDL2_BUILD) /* Define the main hook for the message processing loop. */ bool Message_Handler(HWND hwnd, UINT message, UINT wParam, LONG lParam); @@ -770,6 +795,39 @@ class WWKeyboardClass */ uint8_t DownState[0x2000]; // (UINT16_MAX / 8) + 1 int DownSkip; + +#ifdef SDL2_BUILD + void Handle_Controller_Axis_Event(const SDL_ControllerAxisEvent& motion); + void Handle_Controller_Button_Event(const SDL_ControllerButtonEvent& button); + void Handle_Touch_Event(const SDL_TouchFingerEvent& event); + void Process_Controller_Axis_Motion(); + + // used to convert user-friendly pointer speed values into more useable ones + const double CONTROLLER_SPEED_MOD = 2000000.0; + // bigger value correndsponds to faster pointer movement speed with bigger stick axis values + const double CONTROLLER_AXIS_SPEEDUP = 1.03; + // speedup value while the trigger is pressed + const int CONTROLLER_TRIGGER_SPEEDUP = 2; + + enum + { + CONTROLLER_L_DEADZONE = 3000, + CONTROLLER_R_DEADZONE = 6000, + CONTROLLER_TRIGGER_R_DEADZONE = 3000 + }; + + SDL_GameController* GameController = nullptr; + int16_t ControllerLeftXAxis = 0; + int16_t ControllerLeftYAxis = 0; + int16_t ControllerRightXAxis = 0; + int16_t ControllerRightYAxis = 0; + uint32_t LastControllerTime = 0; + float EmulatedPointerPosX = 0; + float EmulatedPointerPosY = 0; + float ControllerSpeedBoost = 1; + bool AnalogScrollActive = false; + ScrollDirType ScrollDirection = SDIR_NONE; +#endif }; #endif diff --git a/redalert/scroll.cpp b/redalert/scroll.cpp index 3be4d6d5..5c063b84 100644 --- a/redalert/scroll.cpp +++ b/redalert/scroll.cpp @@ -98,6 +98,14 @@ void ScrollClass::AI(KeyNumType& input, int x, int y) */ bool noscroll = false; +#ifdef SDL2_BUILD + if (Keyboard->Is_Analog_Scroll_Active()) { + unsigned char scrollDirection = Keyboard->Get_Scroll_Direction(); + int scrollDistance = (7 - Options.ScrollRate) * 20; + Scroll_Map((DirType)scrollDirection, scrollDistance, true); + } +#endif + if (!noscroll) { bool at_screen_edge = (y == 0 || x == 0 || x >= SeenBuff.Get_Width() - 1 || y >= SeenBuff.Get_Height() - 1); diff --git a/tiberiandawn/scroll.cpp b/tiberiandawn/scroll.cpp index 22c352b9..596a8f7f 100644 --- a/tiberiandawn/scroll.cpp +++ b/tiberiandawn/scroll.cpp @@ -102,6 +102,14 @@ void ScrollClass::AI(KeyNumType& input, int x, int y) noscroll = true; } +#ifdef SDL2_BUILD + if (Keyboard->Is_Analog_Scroll_Active()) { + unsigned char scrollDirection = Keyboard->Get_Scroll_Direction(); + int scrollDistance = (7 - Options.ScrollRate) * 20; + Scroll_Map((DirType)scrollDirection, scrollDistance, true); + } +#endif + if (!noscroll) { /*