diff --git a/UnleashedRecomp/hid/driver/sdl_hid.cpp b/UnleashedRecomp/hid/driver/sdl_hid.cpp index 762355242..76489cb3a 100644 --- a/UnleashedRecomp/hid/driver/sdl_hid.cpp +++ b/UnleashedRecomp/hid/driver/sdl_hid.cpp @@ -10,6 +10,18 @@ #define TRANSLATE_INPUT(S, X) SDL_GameControllerGetButton(controller, S) << FirstBitLow(X) #define VIBRATION_TIMEOUT_MS 5000 +static bool IsBoostOnRightTriggerActive() +{ + // true if sonic is werehog and right trigger preference is boost + return !App::s_isWerehog && Config::RightTriggerAction == ERightTriggerAction::Boost; +} + +static uint32_t GetBoostCancelDurationMs() +{ + int32_t fps = Config::FPS > 0 ? Config::FPS : 60; + return static_cast(2000 / fps); +} + class Controller { public: @@ -20,6 +32,13 @@ class Controller XAMINPUT_VIBRATION vibration{ 0, 0 }; int index{}; + // for when user sets Config::RightTriggerAction to ERightTriggerAction this + // increases stability to allow square/X to be recognised by the game while + // right trigger is being pressed down especially when the user is moving + // the thumbsticks + bool xWasHeldLastPoll{}; + uint32_t xCancelUntilTick{}; + Controller() = default; explicit Controller(int index) : Controller(SDL_GameControllerOpen(index)) @@ -99,6 +118,33 @@ class Controller pad.bLeftTrigger = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) >> 7; pad.bRightTrigger = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) >> 7; + + + + if (IsBoostOnRightTriggerActive()) + { + bool xHeldPhysically = SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_X) != 0; + bool xRisingEdge = xHeldPhysically && !xWasHeldLastPoll; + bool rtPulled = pad.bRightTrigger >= 30; // TODO: change this to a pressure preference + + // these checks are in place to improve responsiveness of square/X while right trigger is held down + if (xRisingEdge && rtPulled) + xCancelUntilTick = SDL_GetTicks() + GetBoostCancelDurationMs(); + + bool inCancelWindow = SDL_TICKS_PASSED(xCancelUntilTick, SDL_GetTicks()); + + if (inCancelWindow) + pad.wButtons &= ~XAMINPUT_GAMEPAD_X; + else if (xHeldPhysically || rtPulled) + pad.wButtons |= XAMINPUT_GAMEPAD_X; + else + pad.wButtons &= ~XAMINPUT_GAMEPAD_X; + + if (rtPulled) + pad.bRightTrigger = 0; + + xWasHeldLastPoll = xHeldPhysically; + } } void Poll() @@ -129,6 +175,34 @@ class Controller pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_B, XAMINPUT_GAMEPAD_B); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_X, XAMINPUT_GAMEPAD_X); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_Y, XAMINPUT_GAMEPAD_Y); + + // when playing day stages keep the right trigger mirrored onto square/X + // so so the game knows the user is boosting. This will remove the actual + // right trigger from the game so sonic wouldn't drift + if (IsBoostOnRightTriggerActive()) + { + bool xHeldPhysically = (pad.wButtons & XAMINPUT_GAMEPAD_X) != 0; + bool xRisingEdge = xHeldPhysically && !xWasHeldLastPoll; + uint8_t rtRaw = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) >> 7; + bool rtPulled = rtRaw >= 30; // TODO: change this to a pressure preference + + // like in Poll() these checks are in place to improve responsiveness + // of square/X while right trigger is held down + if (xRisingEdge && rtPulled) + xCancelUntilTick = SDL_GetTicks() + GetBoostCancelDurationMs(); + + bool inCancelWindow = SDL_TICKS_PASSED(xCancelUntilTick, SDL_GetTicks()); + + if (inCancelWindow) + pad.wButtons &= ~XAMINPUT_GAMEPAD_X; + else if (rtPulled) + pad.wButtons |= XAMINPUT_GAMEPAD_X; + + if (rtPulled) + pad.bRightTrigger = 0; + + xWasHeldLastPoll = xHeldPhysically; + } } void SetVibration(const XAMINPUT_VIBRATION& vibration) diff --git a/UnleashedRecomp/locale/config_locale.cpp b/UnleashedRecomp/locale/config_locale.cpp index 1242e4eb6..2e03be747 100644 --- a/UnleashedRecomp/locale/config_locale.cpp +++ b/UnleashedRecomp/locale/config_locale.cpp @@ -216,6 +216,66 @@ CONFIG_DEFINE_ENUM_LOCALE(ETimeOfDayTransition) } }; +// Translation required +// Japanese Notes: This localization should include furigana in its description. +CONFIG_DEFINE_LOCALE(RightTriggerAction) +{ + { ELanguage::English, { "Right Trigger Action", "Choose what the right trigger does in day stages." } }, + { ELanguage::Japanese, { "Right Trigger Action", "Choose what the right trigger does in day stages." } }, // Translation required + { ELanguage::German, { "Right Trigger Action", "Choose what the right trigger does in day stages." } }, // Translation required + { ELanguage::French, { "Right Trigger Action", "Choose what the right trigger does in day stages." } }, // Translation required + { ELanguage::Spanish, { "Right Trigger Action", "Choose what the right trigger does in day stages." } }, // Translation required + { ELanguage::Italian, { "Right Trigger Action", "Choose what the right trigger does in day stages." } } // Translation required +}; + +// Translation required +// Japanese Notes: This localization should include furigana in its description. +CONFIG_DEFINE_ENUM_LOCALE(ERightTriggerAction) +{ + { + ELanguage::English, + { + { ERightTriggerAction::Drift, { "DRIFT", "Default: the right trigger acts as drift, matching the original Xbox 360/PS3 controls." } }, + { ERightTriggerAction::Boost, { "BOOST/H.A", "EXPERIMENTAL: The right trigger acts as boost or homing attack. Drift is still available on the left trigger and X/Square still triggers boost too." } } + } + }, + { + ELanguage::Japanese, + { + { ERightTriggerAction::Drift, { "DRIFT", "" } }, + { ERightTriggerAction::Boost, { "BOOST", "" } } + } + }, + { + ELanguage::German, + { + { ERightTriggerAction::Drift, { "DRIFT", "" } }, + { ERightTriggerAction::Boost, { "BOOST", "" } } + } + }, + { + ELanguage::French, + { + { ERightTriggerAction::Drift, { "DRIFT", "" } }, + { ERightTriggerAction::Boost, { "BOOST", "" } } + } + }, + { + ELanguage::Spanish, + { + { ERightTriggerAction::Drift, { "DRIFT", "" } }, + { ERightTriggerAction::Boost, { "BOOST", "" } } + } + }, + { + ELanguage::Italian, + { + { ERightTriggerAction::Drift, { "DRIFT", "" } }, + { ERightTriggerAction::Boost, { "BOOST", "" } } + } + } +}; + // Japanese Notes: This localization should include furigana. CONFIG_DEFINE_LOCALE(ControllerIcons) { diff --git a/UnleashedRecomp/user/config.cpp b/UnleashedRecomp/user/config.cpp index bd622b7f8..1de78e327 100644 --- a/UnleashedRecomp/user/config.cpp +++ b/UnleashedRecomp/user/config.cpp @@ -30,6 +30,12 @@ CONFIG_DEFINE_ENUM_TEMPLATE(ECameraRotationMode) { "Reverse", ECameraRotationMode::Reverse }, }; +CONFIG_DEFINE_ENUM_TEMPLATE(ERightTriggerAction) +{ + { "Drift", ERightTriggerAction::Drift }, + { "Boost", ERightTriggerAction::Boost } +}; + CONFIG_DEFINE_ENUM_TEMPLATE(EControllerIcons) { { "Auto", EControllerIcons::Auto }, diff --git a/UnleashedRecomp/user/config.h b/UnleashedRecomp/user/config.h index 9e87d12e2..ee106d0a3 100644 --- a/UnleashedRecomp/user/config.h +++ b/UnleashedRecomp/user/config.h @@ -52,6 +52,12 @@ enum class ECameraRotationMode : uint32_t Reverse }; +enum class ERightTriggerAction : uint32_t +{ + Drift, + Boost +}; + enum class EControllerIcons : uint32_t { Auto, diff --git a/UnleashedRecomp/user/config_def.h b/UnleashedRecomp/user/config_def.h index 80eb29c93..96e909c5c 100644 --- a/UnleashedRecomp/user/config_def.h +++ b/UnleashedRecomp/user/config_def.h @@ -12,6 +12,7 @@ CONFIG_DEFINE("System", bool, ShowConsole, false); CONFIG_DEFINE_ENUM_LOCALISED("Input", ECameraRotationMode, HorizontalCamera, ECameraRotationMode::Normal); CONFIG_DEFINE_ENUM_LOCALISED("Input", ECameraRotationMode, VerticalCamera, ECameraRotationMode::Normal); +CONFIG_DEFINE_ENUM_LOCALISED("Input", ERightTriggerAction, RightTriggerAction, ERightTriggerAction::Drift); CONFIG_DEFINE_LOCALISED("Input", bool, Vibration, true); CONFIG_DEFINE_LOCALISED("Input", bool, AllowBackgroundInput, false); CONFIG_DEFINE_ENUM_LOCALISED("Input", EControllerIcons, ControllerIcons, EControllerIcons::Auto);