1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-22 22:34:33 +01:00

Add ability to scroll the map with gamepad stick

This commit is contained in:
Michał Janiszewski
2025-07-03 22:48:59 +02:00
committed by GitHub
parent 9fcbac56bb
commit 763242b14d
12 changed files with 344 additions and 33 deletions

View File

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

View File

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

View File

@@ -12,6 +12,8 @@
#include "ShortcutIds.h"
#include <SDL.h>
#include <SDL_gamecontroller.h>
#include <cmath>
#include <openrct2-ui/UiContext.h>
#include <openrct2-ui/input/MouseInput.h>
#include <openrct2-ui/input/ShortcutManager.h>
@@ -22,6 +24,7 @@
#include <openrct2/OpenRCT2.h>
#include <openrct2/config/Config.h>
#include <openrct2/interface/Chat.h>
#include <openrct2/interface/Viewport.h>
#include <openrct2/interface/Window.h>
#include <openrct2/paint/VirtualFloor.h>
#include <openrct2/ui/UiContext.h>
@@ -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<float>(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<int>(intPartX);
int pixelsY = static_cast<int>(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;

View File

@@ -13,7 +13,7 @@
#include <queue>
#include <string_view>
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<SDL_Joystick*> _joysticks;
std::vector<SDL_GameController*> _gameControllers;
std::queue<InputEvent> _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<uint8_t> _keyboardState;
uint8_t _modifierKeyState;
void CheckJoysticks();
void processAnalogueInput();
void updateAnalogueScroll();
void HandleViewScrolling();
void HandleModifiers();

View File

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

View File

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

View File

@@ -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<int32_t>((deadzone / 32767.0f) * 100);
auto ft = Formatter();
ft.Add<int32_t>(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<int32_t>(sensitivity * 100);
auto ft = Formatter();
ft.Add<int32_t>(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<int32_t>((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<uint8_t>((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<uint8_t>(
((Config::Get().general.gamepadSensitivity - 0.5f) / 2.5f) * 100);
InitializeScrollPosition(WIDX_GAMEPAD_SENSITIVITY, 1, sensitivityPercent);
}
}
#pragma endregion

View File

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

View File

@@ -85,6 +85,10 @@ namespace OpenRCT2::Config
bool InvertViewportDrag;
bool ZoomToCursor;
// Gamepad
int32_t gamepadDeadzone;
float gamepadSensitivity;
// Miscellaneous
bool PlayIntro;
int32_t WindowSnapProximity;

View File

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

View File

@@ -223,6 +223,7 @@ namespace OpenRCT2
std::optional<CoordsXY> ScreenGetMapXYQuadrantWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* quadrant);
std::optional<CoordsXY> ScreenGetMapXYSide(const ScreenCoordsXY& screenCoords, uint8_t* side);
std::optional<CoordsXY> ScreenGetMapXYSideWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* side);
Viewport* ViewportFindFromPoint(const ScreenCoordsXY& screenCoords);
ScreenCoordsXY Translate3DTo2DWithZ(int32_t rotation, const CoordsXYZ& pos);

View File

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