diff --git a/data/language/en-GB.txt b/data/language/en-GB.txt index 94bb1b8e40..4e142d282b 100644 --- a/data/language/en-GB.txt +++ b/data/language/en-GB.txt @@ -3823,3 +3823,10 @@ STR_6781 :Screenshots STR_6782 :Park notifications STR_6783 :Ride notifications STR_6784 :Guest notifications +STR_6785 :Gamepad +STR_6786 :Deadzone: +STR_6787 :Analogue stick deadzone (minimum movement required) +STR_6788 :Sensitivity: +STR_6789 :Analogue stick sensitivity multiplier +STR_6790 :Deadzone: {COMMA32}% +STR_6791 :Sensitivity: {COMMA32}% diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 92cc41db57..c31944b9b3 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -1,6 +1,7 @@ 0.4.24 (in development) ------------------------------------------------------------------------ - Feature: [#24411] Vanilla scenarios now also have previews in the scenario selection window. +- Feature: [#24616] Add ability to scroll map with gamepad sticks. - Feature: [#24662] Add optional screenshot argument for Z coord. - Improved: [#22684] The limit of 2000 animated tile elements has been removed. - Improved: [#23228] Landscape edge doors now animate opening and closing and play a sound. diff --git a/src/openrct2-ui/input/InputManager.cpp b/src/openrct2-ui/input/InputManager.cpp index ea3bd81305..1cd37b3a64 100644 --- a/src/openrct2-ui/input/InputManager.cpp +++ b/src/openrct2-ui/input/InputManager.cpp @@ -12,6 +12,8 @@ #include "ShortcutIds.h" #include +#include +#include #include #include #include @@ -22,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +41,22 @@ void InputManager::QueueInputEvent(const SDL_Event& e) { switch (e.type) { + case SDL_CONTROLLERAXISMOTION: + { + // Process only the stick axes for scrolling (ignore triggers) + if (e.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX || e.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY + || e.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX || e.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY) + { + InputEvent ie; + ie.DeviceKind = InputDeviceKind::JoyAxis; + ie.Modifiers = SDL_GetModState(); + ie.Button = e.caxis.axis; + ie.State = InputEventState::Down; + ie.AxisValue = e.caxis.value; + QueueInputEvent(std::move(ie)); + } + break; + } case SDL_JOYHATMOTION: { if (e.jhat.value != SDL_HAT_CENTERED) @@ -47,30 +66,44 @@ void InputManager::QueueInputEvent(const SDL_Event& e) ie.Modifiers = SDL_GetModState(); ie.Button = e.jhat.value; ie.State = InputEventState::Down; + ie.AxisValue = 0; QueueInputEvent(std::move(ie)); } break; } + case SDL_CONTROLLERBUTTONDOWN: case SDL_JOYBUTTONDOWN: { InputEvent ie; ie.DeviceKind = InputDeviceKind::JoyButton; ie.Modifiers = SDL_GetModState(); - ie.Button = e.jbutton.button; + ie.Button = e.cbutton.button; ie.State = InputEventState::Down; + ie.AxisValue = 0; QueueInputEvent(std::move(ie)); break; } + case SDL_CONTROLLERBUTTONUP: case SDL_JOYBUTTONUP: { InputEvent ie; ie.DeviceKind = InputDeviceKind::JoyButton; ie.Modifiers = SDL_GetModState(); - ie.Button = e.jbutton.button; + ie.Button = e.cbutton.button; ie.State = InputEventState::Release; + ie.AxisValue = 0; QueueInputEvent(std::move(ie)); break; } + case SDL_CONTROLLERDEVICEADDED: + case SDL_CONTROLLERDEVICEREMOVED: + case SDL_JOYDEVICEADDED: + case SDL_JOYDEVICEREMOVED: + { + // Force joystick refresh on next check + _lastJoystickCheck = 0; + break; + } } } @@ -88,22 +121,86 @@ void InputManager::CheckJoysticks() { _lastJoystickCheck = tick; - _joysticks.clear(); + _gameControllers.clear(); auto numJoysticks = SDL_NumJoysticks(); for (auto i = 0; i < numJoysticks; i++) { - auto joystick = SDL_JoystickOpen(i); - if (joystick != nullptr) + if (SDL_IsGameController(i)) { - _joysticks.push_back(joystick); + auto gameController = SDL_GameControllerOpen(i); + if (gameController != nullptr) + { + _gameControllers.push_back(gameController); + } } } } } +void InputManager::processAnalogueInput() +{ + _analogueScroll.x = 0; + _analogueScroll.y = 0; + + const int32_t deadzone = Config::Get().general.gamepadDeadzone; + const float sensitivity = Config::Get().general.gamepadSensitivity; + + for (auto* gameController : _gameControllers) + { + if (gameController != nullptr) + { + int32_t stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX); + int32_t stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY); + + // Calculate the magnitude of the stick input vector + float magnitude = std::sqrt(static_cast(stickX * stickX + stickY * stickY)); + + if (magnitude > deadzone) + { + // Apply deadzone to the magnitude, creating a more linear response + float adjustedMagnitude = (magnitude - deadzone) / (32767.0f - deadzone); + adjustedMagnitude = std::min(adjustedMagnitude, 1.0f); + + float rawX = (stickX / 32767.0f) * adjustedMagnitude; + float rawY = (stickY / 32767.0f) * adjustedMagnitude; + + // Use a quadratic curve for better fine control at low sensitivities + float sensitivityCurve = sensitivity * sensitivity; + float moveX = rawX * sensitivityCurve * 8.0f; // Reasonable base scale + float moveY = rawY * sensitivityCurve * 8.0f; + + // Accumulate the movement with fractional precision + _analogueScrollAccumX += moveX; + _analogueScrollAccumY += moveY; + + // Extract integer movement for this frame + float intPartX, intPartY; + float fracX = std::modf(_analogueScrollAccumX, &intPartX); + float fracY = std::modf(_analogueScrollAccumY, &intPartY); + + int pixelsX = static_cast(intPartX); + int pixelsY = static_cast(intPartY); + + _analogueScrollAccumX = fracX; + _analogueScrollAccumY = fracY; + + _analogueScroll.x += pixelsX; + _analogueScroll.y += pixelsY; + } + } + } +} + +void InputManager::updateAnalogueScroll() +{ + _viewScroll.x += _analogueScroll.x; + _viewScroll.y += _analogueScroll.y; +} + void InputManager::Process() { CheckJoysticks(); + processAnalogueInput(); HandleModifiers(); ProcessEvents(); ProcessHoldEvents(); @@ -119,13 +216,51 @@ void InputManager::HandleViewScrolling() if (console.IsOpen()) return; - // Shortcut scrolling auto mainWindow = WindowGetMain(); - if (mainWindow != nullptr && (_viewScroll.x != 0 || _viewScroll.y != 0)) + + // Handle gamepad analogue scrolling with cursor-based viewport targeting + if (_analogueScroll.x != 0 || _analogueScroll.y != 0) { - WindowUnfollowSprite(*mainWindow); + // Get cursor position to find target viewport + const CursorState* cursorState = ContextGetCursorState(); + Viewport* targetViewport = ViewportFindFromPoint(cursorState->position); + + WindowBase* targetWindow = nullptr; + if (targetViewport != nullptr) + { + // Find the window that owns this viewport + auto* windowMgr = GetWindowManager(); + targetWindow = windowMgr->GetOwner(targetViewport); + } + + // Fallback to main window if no viewport found under cursor + if (targetWindow == nullptr) + { + targetWindow = mainWindow; + } + + if (targetWindow != nullptr) + { + // Only unfollow sprites for the main window or viewport windows + // Don't unfollow for ride windows that might be following vehicles + if (targetWindow == mainWindow || targetWindow->classification == WindowClass::Viewport) + { + WindowUnfollowSprite(*targetWindow); + } + InputScrollViewportSmooth(_analogueScroll, targetWindow); + } + } + + // Handle keyboard shortcut scrolling with edge-based scrolling (but ignore gamepad input) + ScreenCoordsXY keyboardScroll = { _viewScroll.x - _analogueScroll.x, _viewScroll.y - _analogueScroll.y }; + if (keyboardScroll.x != 0 || keyboardScroll.y != 0) + { + if (mainWindow != nullptr) + { + WindowUnfollowSprite(*mainWindow); + } + InputScrollViewport(keyboardScroll); } - InputScrollViewport(_viewScroll); // Mouse edge scrolling if (Config::Get().general.EdgeScrolling) @@ -337,6 +472,8 @@ void InputManager::ProcessHoldEvents() ProcessViewScrollEvent(ShortcutId::kViewScrollLeft, { -1, 0 }); ProcessViewScrollEvent(ShortcutId::kViewScrollRight, { 1, 0 }); } + + updateAnalogueScroll(); } } @@ -390,9 +527,11 @@ bool InputManager::GetState(const ShortcutInput& shortcut) const } case InputDeviceKind::JoyButton: { - for (auto* joystick : _joysticks) + for (auto* gameController : _gameControllers) { - if (SDL_JoystickGetButton(joystick, shortcut.Button)) + // Get the underlying joystick to maintain compatibility with raw button numbers + auto* joystick = SDL_GameControllerGetJoystick(gameController); + if (joystick && SDL_JoystickGetButton(joystick, shortcut.Button)) { return true; } @@ -401,20 +540,31 @@ bool InputManager::GetState(const ShortcutInput& shortcut) const } case InputDeviceKind::JoyHat: { - for (auto* joystick : _joysticks) + for (auto* gameController : _gameControllers) { - auto numHats = SDL_JoystickNumHats(joystick); - for (int i = 0; i < numHats; i++) + // Get the underlying joystick to maintain compatibility with hat functionality + auto* joystick = SDL_GameControllerGetJoystick(gameController); + if (joystick) { - auto hat = SDL_JoystickGetHat(joystick, i); - if (hat & shortcut.Button) + auto numHats = SDL_JoystickNumHats(joystick); + for (int i = 0; i < numHats; i++) { - return true; + auto hat = SDL_JoystickGetHat(joystick, i); + if (hat & shortcut.Button) + { + return true; + } } } } break; } + case InputDeviceKind::JoyAxis: + { + // analogue axes don't have a simple "pressed" state like buttons + // Return false for shortcuts on analogue axes as they're handled differently + return false; + } } } return false; diff --git a/src/openrct2-ui/input/InputManager.h b/src/openrct2-ui/input/InputManager.h index 93d23c3e82..cbcece0b78 100644 --- a/src/openrct2-ui/input/InputManager.h +++ b/src/openrct2-ui/input/InputManager.h @@ -13,7 +13,7 @@ #include #include -typedef struct _SDL_Joystick SDL_Joystick; +typedef struct _SDL_GameController SDL_GameController; typedef union SDL_Event SDL_Event; namespace OpenRCT2::Ui @@ -27,6 +27,7 @@ namespace OpenRCT2::Ui Keyboard, JoyButton, JoyHat, + JoyAxis, }; enum class InputEventState @@ -41,6 +42,7 @@ namespace OpenRCT2::Ui uint32_t Modifiers; uint32_t Button; InputEventState State; + int16_t AxisValue; // For analogue stick values (-32768 to 32767) }; enum class ModifierKey : uint8_t @@ -56,14 +58,19 @@ namespace OpenRCT2::Ui { private: uint32_t _lastJoystickCheck{}; - std::vector _joysticks; + std::vector _gameControllers; std::queue _events; ScreenCoordsXY _viewScroll; + ScreenCoordsXY _analogueScroll; // analogue stick scroll values + float _analogueScrollAccumX = 0.0f; // Fractional accumulator for X axis + float _analogueScrollAccumY = 0.0f; // Fractional accumulator for Y axis uint32_t _mouseState{}; std::vector _keyboardState; uint8_t _modifierKeyState; void CheckJoysticks(); + void processAnalogueInput(); + void updateAnalogueScroll(); void HandleViewScrolling(); void HandleModifiers(); diff --git a/src/openrct2-ui/input/MouseInput.cpp b/src/openrct2-ui/input/MouseInput.cpp index a35d492be0..3f2f85954f 100644 --- a/src/openrct2-ui/input/MouseInput.cpp +++ b/src/openrct2-ui/input/MouseInput.cpp @@ -980,18 +980,13 @@ namespace OpenRCT2 ScreenCoordsXY newScreenCoords; widgetScrollGetPart(*w, widget, screenCoords, newScreenCoords, &scroll_part, &scrollId); - if (scroll_part != SCROLL_PART_VIEW) - WindowTooltipClose(); - else + if (scroll_part == SCROLL_PART_VIEW) { w->OnScrollMouseOver(scrollId, newScreenCoords); - InputUpdateTooltip(w, widgetIndex, screenCoords); } } - else - { - InputUpdateTooltip(w, widgetIndex, screenCoords); - } + + InputUpdateTooltip(w, widgetIndex, screenCoords); } /** @@ -1695,4 +1690,43 @@ namespace OpenRCT2 gInputFlags.set(InputFlag::viewportScrolling); } } + + void InputScrollViewportSmooth(const ScreenCoordsXY& scrollScreenCoords, WindowBase* targetWindow) + { + if (targetWindow == nullptr) + { + return; + } + + Viewport* viewport = targetWindow->viewport; + if (viewport == nullptr) + { + return; + } + + if (targetWindow->flags & WF_NO_SCROLLING) + { + return; + } + + if (scrollScreenCoords.x == 0 && scrollScreenCoords.y == 0) + return; + + // Apply smooth scrolling similar to mouse drag behavior + // Use zoom-based scaling like mouse dragging does + ScreenCoordsXY differentialCoords = scrollScreenCoords; + + // Apply zoom scaling (same logic as mouse drag) + const bool posX = differentialCoords.x > 0; + const bool posY = differentialCoords.y > 0; + differentialCoords.x = (viewport->zoom + 1).ApplyTo(-std::abs(differentialCoords.x)); + differentialCoords.y = (viewport->zoom + 1).ApplyTo(-std::abs(differentialCoords.y)); + differentialCoords.x = posX ? -differentialCoords.x : differentialCoords.x; + differentialCoords.y = posY ? -differentialCoords.y : differentialCoords.y; + + // Apply the movement (note: we don't invert for gamepad like mouse drag does) + targetWindow->savedViewPos += differentialCoords; + + gInputFlags.set(InputFlag::viewportScrolling); + } } // namespace OpenRCT2 diff --git a/src/openrct2-ui/input/MouseInput.h b/src/openrct2-ui/input/MouseInput.h index 4fe8a5e99b..0e2ffa9dbf 100644 --- a/src/openrct2-ui/input/MouseInput.h +++ b/src/openrct2-ui/input/MouseInput.h @@ -31,4 +31,6 @@ namespace OpenRCT2 void StoreMouseInput(MouseState state, const ScreenCoordsXY& screenCoords); void InputScrollViewport(const ScreenCoordsXY& screenCoords); + void InputScrollViewportSmooth(const ScreenCoordsXY& screenCoords); + void InputScrollViewportSmooth(const ScreenCoordsXY& screenCoords, WindowBase* targetWindow); } // namespace OpenRCT2 diff --git a/src/openrct2-ui/windows/Options.cpp b/src/openrct2-ui/windows/Options.cpp index 359315d9b0..2885a991f6 100644 --- a/src/openrct2-ui/windows/Options.cpp +++ b/src/openrct2-ui/windows/Options.cpp @@ -198,6 +198,13 @@ namespace OpenRCT2::Ui::Windows WIDX_TOUCH_ENHANCEMENTS, WIDX_HOTKEY_DROPDOWN, + // Gamepad + WIDX_GAMEPAD_GROUP, + WIDX_GAMEPAD_DEADZONE_LABEL, + WIDX_GAMEPAD_DEADZONE, + WIDX_GAMEPAD_SENSITIVITY_LABEL, + WIDX_GAMEPAD_SENSITIVITY, + // Misc WIDX_TITLE_SEQUENCE_GROUP = WIDX_PAGE_START, WIDX_TITLE_SEQUENCE, @@ -351,6 +358,7 @@ namespace OpenRCT2::Ui::Windows ); constexpr int32_t kControlsGroupStart = 53; + constexpr int32_t kGamepadGroupStart = kControlsGroupStart + 150; static constexpr auto window_options_controls_widgets = makeWidgets( kMainOptionsWidgets, @@ -362,7 +370,14 @@ namespace OpenRCT2::Ui::Windows makeWidget({ 10, kControlsGroupStart + 75}, {290, 12}, WidgetType::checkbox, WindowColour::tertiary, STR_WINDOW_BUTTONS_ON_THE_LEFT, STR_WINDOW_BUTTONS_ON_THE_LEFT_TIP), // Window buttons on the left makeWidget({ 10, kControlsGroupStart + 90}, {290, 12}, WidgetType::checkbox, WindowColour::tertiary, STR_ENLARGED_UI, STR_ENLARGED_UI_TIP ), makeWidget({ 25, kControlsGroupStart + 105}, {275, 12}, WidgetType::checkbox, WindowColour::tertiary, STR_TOUCH_ENHANCEMENTS, STR_TOUCH_ENHANCEMENTS_TIP ), - makeWidget({155, kControlsGroupStart + 120}, {145, 13}, WidgetType::button, WindowColour::secondary, STR_HOTKEY, STR_HOTKEY_TIP ) // Set hotkeys buttons + makeWidget({155, kControlsGroupStart + 120}, {145, 13}, WidgetType::button, WindowColour::secondary, STR_HOTKEY, STR_HOTKEY_TIP ), // Set hotkeys buttons + + // Gamepad group + makeWidget({ 5, kGamepadGroupStart + 0}, {300, 45}, WidgetType::groupbox, WindowColour::secondary, STR_GAMEPAD_GROUP ), // Gamepad group + makeWidget({ 10, kGamepadGroupStart + 13}, { 90, 12}, WidgetType::label, WindowColour::secondary, STR_GAMEPAD_DEADZONE_LABEL, STR_GAMEPAD_DEADZONE_TIP ), // Deadzone label + makeWidget({105, kGamepadGroupStart + 13}, {190, 13}, WidgetType::scroll, WindowColour::secondary, SCROLL_HORIZONTAL, STR_GAMEPAD_DEADZONE_TOOLTIP_FORMAT), // Deadzone slider + makeWidget({ 10, kGamepadGroupStart + 28}, { 90, 12}, WidgetType::label, WindowColour::secondary, STR_GAMEPAD_SENSITIVITY_LABEL, STR_GAMEPAD_SENSITIVITY_TIP ), // Sensitivity label + makeWidget({105, kGamepadGroupStart + 28}, {190, 13}, WidgetType::scroll, WindowColour::secondary, SCROLL_HORIZONTAL, STR_GAMEPAD_SENSITIVITY_TOOLTIP_FORMAT) // Sensitivity slider ); constexpr int32_t kThemesGroupStart = 53; @@ -638,10 +653,12 @@ namespace OpenRCT2::Ui::Windows case WINDOW_OPTIONS_PAGE_AUDIO: AudioUpdate(); break; + case WINDOW_OPTIONS_PAGE_CONTROLS: + ControlsUpdate(); + break; case WINDOW_OPTIONS_PAGE_DISPLAY: case WINDOW_OPTIONS_PAGE_RENDERING: case WINDOW_OPTIONS_PAGE_CULTURE: - case WINDOW_OPTIONS_PAGE_CONTROLS: case WINDOW_OPTIONS_PAGE_MISC: case WINDOW_OPTIONS_PAGE_ADVANCED: default: @@ -655,10 +672,11 @@ namespace OpenRCT2::Ui::Windows { case WINDOW_OPTIONS_PAGE_AUDIO: return AudioScrollGetSize(scrollIndex); + case WINDOW_OPTIONS_PAGE_CONTROLS: + return ControlsScrollGetSize(scrollIndex); case WINDOW_OPTIONS_PAGE_DISPLAY: case WINDOW_OPTIONS_PAGE_RENDERING: case WINDOW_OPTIONS_PAGE_CULTURE: - case WINDOW_OPTIONS_PAGE_CONTROLS: case WINDOW_OPTIONS_PAGE_MISC: case WINDOW_OPTIONS_PAGE_ADVANCED: default: @@ -671,6 +689,28 @@ namespace OpenRCT2::Ui::Windows if (page == WINDOW_OPTIONS_PAGE_ADVANCED) return AdvancedTooltip(widgetIndex, fallback); + if (page == WINDOW_OPTIONS_PAGE_CONTROLS) + { + if (widgetIndex == WIDX_GAMEPAD_DEADZONE) + { + const int32_t deadzone = Config::Get().general.gamepadDeadzone; + const int32_t deadzonePercent = static_cast((deadzone / 32767.0f) * 100); + + auto ft = Formatter(); + ft.Add(deadzonePercent); + return { STR_GAMEPAD_DEADZONE_TOOLTIP_FORMAT, ft }; + } + else if (widgetIndex == WIDX_GAMEPAD_SENSITIVITY) + { + const float sensitivity = Config::Get().general.gamepadSensitivity; + const int32_t sensitivityDisplay = static_cast(sensitivity * 100); + + auto ft = Formatter(); + ft.Add(sensitivityDisplay); + return { STR_GAMEPAD_SENSITIVITY_TOOLTIP_FORMAT, ft }; + } + } + return WindowBase::OnTooltip(widgetIndex, fallback); } @@ -1538,6 +1578,42 @@ namespace OpenRCT2::Ui::Windows } } + void ControlsUpdate() + { + const auto& deadzoneWidget = widgets[WIDX_GAMEPAD_DEADZONE]; + const auto& deadzoneScroll = scrolls[0]; + uint8_t deadzonePercent = GetScrollPercentage(deadzoneWidget, deadzoneScroll); + int32_t deadzoneValue = static_cast((deadzonePercent / 100.0f) * 32767); + if (deadzoneValue != Config::Get().general.gamepadDeadzone) + { + Config::Get().general.gamepadDeadzone = deadzoneValue; + Config::Save(); + } + + const auto& sensitivityWidget = widgets[WIDX_GAMEPAD_SENSITIVITY]; + const auto& sensitivityScroll = scrolls[1]; + uint8_t sensitivityPercent = GetScrollPercentage(sensitivityWidget, sensitivityScroll); + float sensitivityValue = 0.5f + (sensitivityPercent / 100.0f) * 2.5f; // Map 0-100% to 0.5-3.0 + if (std::abs(sensitivityValue - Config::Get().general.gamepadSensitivity) > 0.01f) + { + Config::Get().general.gamepadSensitivity = sensitivityValue; + Config::Save(); + } + } + + ScreenSize ControlsScrollGetSize(int32_t scrollIndex) + { + switch (scrollIndex) + { + case 0: // Deadzone slider + return { 500, 0 }; // Range 0-500 (same as audio sliders) + case 1: // Sensitivity slider + return { 500, 0 }; // Range 0-500 (same as audio sliders) + default: + return { 0, 0 }; + } + } + ScreenSize AudioScrollGetSize(int32_t scrollIndex) { return { 500, 0 }; @@ -1603,7 +1679,6 @@ namespace OpenRCT2::Ui::Windows #pragma endregion #pragma region Controls tab events - void ControlsMouseUp(WidgetIndex widgetIndex) { auto* windowMgr = Ui::GetWindowManager(); @@ -1670,6 +1745,19 @@ namespace OpenRCT2::Ui::Windows SetCheckboxValue(WIDX_TOUCH_ENHANCEMENTS, Config::Get().interface.TouchEnhancements); widgetSetEnabled(*this, WIDX_TOUCH_ENHANCEMENTS, Config::Get().interface.EnlargedUi); + + // Initialize scroll positions for sliders only on first frame + if (frame_no == 0) + { + // Convert deadzone (0-32767) to percentage (0-100), then to scroll position (0-500) + uint8_t deadzonePercent = static_cast((Config::Get().general.gamepadDeadzone / 32767.0f) * 100); + InitializeScrollPosition(WIDX_GAMEPAD_DEADZONE, 0, deadzonePercent); + + // Convert sensitivity (0.5-3.0) to percentage (0-100), then to scroll position (0-500) + uint8_t sensitivityPercent = static_cast( + ((Config::Get().general.gamepadSensitivity - 0.5f) / 2.5f) * 100); + InitializeScrollPosition(WIDX_GAMEPAD_SENSITIVITY, 1, sensitivityPercent); + } } #pragma endregion diff --git a/src/openrct2/config/Config.cpp b/src/openrct2/config/Config.cpp index fa1611f7bf..3597aa7f55 100644 --- a/src/openrct2/config/Config.cpp +++ b/src/openrct2/config/Config.cpp @@ -222,6 +222,10 @@ namespace OpenRCT2::Config #endif // _DEBUG model->TrapCursor = reader->GetBoolean("trap_cursor", false); model->AutoOpenShops = reader->GetBoolean("auto_open_shops", false); + + // Gamepad settings + model->gamepadDeadzone = reader->GetInt32("gamepad_deadzone", 3600); + model->gamepadSensitivity = reader->GetFloat("gamepad_sensitivity", 1.5f); model->ScenarioUnlockingEnabled = reader->GetBoolean("scenario_unlocking_enabled", true); model->ScenarioHideMegaPark = reader->GetBoolean("scenario_hide_mega_park", true); model->LastSaveGameDirectory = reader->GetString("last_game_directory", ""); @@ -313,6 +317,10 @@ namespace OpenRCT2::Config writer->WriteBoolean("multithreading", model->MultiThreading); writer->WriteBoolean("trap_cursor", model->TrapCursor); writer->WriteBoolean("auto_open_shops", model->AutoOpenShops); + + // Gamepad settings + writer->WriteInt32("gamepad_deadzone", model->gamepadDeadzone); + writer->WriteFloat("gamepad_sensitivity", model->gamepadSensitivity); writer->WriteBoolean("scenario_unlocking_enabled", model->ScenarioUnlockingEnabled); writer->WriteBoolean("scenario_hide_mega_park", model->ScenarioHideMegaPark); writer->WriteString("last_game_directory", model->LastSaveGameDirectory); diff --git a/src/openrct2/config/Config.h b/src/openrct2/config/Config.h index ecc2acf2e2..8e1707b3c7 100644 --- a/src/openrct2/config/Config.h +++ b/src/openrct2/config/Config.h @@ -85,6 +85,10 @@ namespace OpenRCT2::Config bool InvertViewportDrag; bool ZoomToCursor; + // Gamepad + int32_t gamepadDeadzone; + float gamepadSensitivity; + // Miscellaneous bool PlayIntro; int32_t WindowSnapProximity; diff --git a/src/openrct2/interface/Viewport.cpp b/src/openrct2/interface/Viewport.cpp index 0921c40d82..ae03078168 100644 --- a/src/openrct2/interface/Viewport.cpp +++ b/src/openrct2/interface/Viewport.cpp @@ -1880,7 +1880,7 @@ namespace OpenRCT2 GfxSetDirtyBlocks(invalidRect); } - static Viewport* ViewportFindFromPoint(const ScreenCoordsXY& screenCoords) + Viewport* ViewportFindFromPoint(const ScreenCoordsXY& screenCoords) { auto* windowMgr = Ui::GetWindowManager(); WindowBase* w = windowMgr->FindFromPoint(screenCoords); diff --git a/src/openrct2/interface/Viewport.h b/src/openrct2/interface/Viewport.h index f775f43d8a..33ea19d53a 100644 --- a/src/openrct2/interface/Viewport.h +++ b/src/openrct2/interface/Viewport.h @@ -223,6 +223,7 @@ namespace OpenRCT2 std::optional ScreenGetMapXYQuadrantWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* quadrant); std::optional ScreenGetMapXYSide(const ScreenCoordsXY& screenCoords, uint8_t* side); std::optional ScreenGetMapXYSideWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* side); + Viewport* ViewportFindFromPoint(const ScreenCoordsXY& screenCoords); ScreenCoordsXY Translate3DTo2DWithZ(int32_t rotation, const CoordsXYZ& pos); diff --git a/src/openrct2/localisation/StringIds.h b/src/openrct2/localisation/StringIds.h index 47b0a3506b..ae9a3dd9d2 100644 --- a/src/openrct2/localisation/StringIds.h +++ b/src/openrct2/localisation/StringIds.h @@ -1728,6 +1728,15 @@ enum : StringId STR_OBJECT_SELECTION_CLIMATE = 6743, STR_CLIMATE_WEATHER_PERCENT = 6744, + // Gamepad settings + STR_GAMEPAD_GROUP = 6785, + STR_GAMEPAD_DEADZONE_LABEL = 6786, + STR_GAMEPAD_DEADZONE_TIP = 6787, + STR_GAMEPAD_SENSITIVITY_LABEL = 6788, + STR_GAMEPAD_SENSITIVITY_TIP = 6789, + STR_GAMEPAD_DEADZONE_TOOLTIP_FORMAT = 6790, + STR_GAMEPAD_SENSITIVITY_TOOLTIP_FORMAT = 6791, + // Have to include resource strings (from scenarios and objects) for the time being now that language is partially working /* MAX_STR_COUNT = 32768 */ // MAX_STR_COUNT - upper limit for number of strings, not the current count strings };