From 01c869b7046b67c012e05ca65e6b58f3b3f9240d Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sun, 16 May 2021 03:25:02 +1000 Subject: [PATCH] CommonHostInterface: Implement controller autofire --- .../app/src/cpp/android_host_interface.cpp | 2 +- .../nogui_host_interface.cpp | 2 +- src/duckstation-qt/qthostinterface.cpp | 2 +- src/frontend-common/common_host_interface.cpp | 121 ++++++++++++++++++ src/frontend-common/common_host_interface.h | 23 +++- 5 files changed, 146 insertions(+), 4 deletions(-) diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index 1928a73d7..cd332604f 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -569,7 +569,7 @@ void AndroidHostInterface::EmulationThreadLoop(JNIEnv* env) else System::RunFrame(); - UpdateControllerRumble(); + UpdateControllerMetaState(); if (m_vibration_enabled) UpdateVibration(); } diff --git a/src/duckstation-nogui/nogui_host_interface.cpp b/src/duckstation-nogui/nogui_host_interface.cpp index 70fd3934c..45cdac42b 100644 --- a/src/duckstation-nogui/nogui_host_interface.cpp +++ b/src/duckstation-nogui/nogui_host_interface.cpp @@ -213,7 +213,7 @@ void NoGUIHostInterface::Run() else System::RunFrames(); - UpdateControllerRumble(); + UpdateControllerMetaState(); if (m_frame_step_request) { m_frame_step_request = false; diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 678e1bf3e..6a659699e 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -1485,7 +1485,7 @@ void QtHostInterface::threadEntryPoint() else System::RunFrames(); - UpdateControllerRumble(); + UpdateControllerMetaState(); if (m_frame_step_request) { m_frame_step_request = false; diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index 8519e7400..ed1f7bedd 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -1427,6 +1427,73 @@ void CommonHostInterface::StopControllerRumble() } } +void CommonHostInterface::SetControllerAutoFireState(u32 controller_index, s32 button_code, bool active) +{ + for (ControllerAutoFireState& ts : m_controller_autofires) + { + if (ts.controller_index != controller_index || ts.button_code != button_code) + continue; + + if (!active) + { + if (ts.state) + { + Controller* controller = System::GetController(ts.controller_index); + if (controller) + controller->SetButtonState(ts.button_code, false); + } + + ts.state = false; + ts.countdown = ts.frequency; + } + + ts.active = active; + return; + } +} + +void CommonHostInterface::UpdateControllerAutoFire() +{ + for (ControllerAutoFireState& ts : m_controller_autofires) + { + if (!ts.active || (--ts.countdown) > 0) + continue; + + ts.countdown = ts.frequency; + ts.state = !ts.state; + + Controller* controller = System::GetController(ts.controller_index); + if (controller) + controller->SetButtonState(ts.button_code, ts.state); + } +} + +void CommonHostInterface::StopControllerAutoFire() +{ + for (ControllerAutoFireState& ts : m_controller_autofires) + { + if (!ts.active) + continue; + + ts.countdown = ts.frequency; + + if (ts.state) + { + Controller* controller = System::GetController(ts.controller_index); + if (controller) + controller->SetButtonState(ts.button_code, false); + + ts.state = false; + } + } +} + +void CommonHostInterface::UpdateControllerMetaState() +{ + UpdateControllerRumble(); + UpdateControllerAutoFire(); +} + static bool SplitBinding(const std::string& binding, std::string_view* device, std::string_view* sub_binding) { const std::string::size_type slash_pos = binding.find('/'); @@ -1443,8 +1510,10 @@ static bool SplitBinding(const std::string& binding, std::string_view* device, s void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si) { + StopControllerAutoFire(); StopControllerRumble(); m_controller_vibration_motors.clear(); + m_controller_autofires.clear(); for (u32 controller_index = 0; controller_index < NUM_CONTROLLER_AND_CARD_PORTS; controller_index++) { @@ -1517,6 +1586,58 @@ void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si) const float deadzone_size = si.GetFloatValue(category, "Deadzone", 0.25f); m_controller_interface->SetControllerDeadzone(controller_index, deadzone_size); } + + for (u32 turbo_button_index = 0; turbo_button_index < NUM_CONTROLLER_AUTOFIRE_BUTTONS; turbo_button_index++) + { + const std::string button_name( + si.GetStringValue(category, TinyString::FromFormat("AutoFire%uButton", turbo_button_index + 1), "")); + if (button_name.empty()) + continue; + + const std::string binding( + si.GetStringValue(category, TinyString::FromFormat("AutoFire%u", turbo_button_index + 1), "")); + +#ifdef __ANDROID__ + // Android doesn't require a binding, since we can trigger it from the touchscreen controller. + if (binding.empty()) + continue; +#endif + + const std::optional button_code = Controller::GetButtonCodeByName(ctype, button_name); + if (!button_code.has_value()) + { + Log_ErrorPrintf("Invalid autofire button binding '%s'", button_name.c_str()); + continue; + } + + ControllerAutoFireState ts; + ts.controller_index = controller_index; + ts.button_code = button_code.value(); + ts.frequency = static_cast( + std::clamp(si.GetIntValue(category, TinyString::FromFormat("AutoFire%uFrequency", turbo_button_index + 1), + DEFAULT_AUTOFIRE_FREQUENCY), + 1, std::numeric_limits::max())); + ts.countdown = ts.frequency; + ts.active = false; + ts.state = false; + + if (!binding.empty()) + { + std::string_view device, button; + if (!SplitBinding(binding, &device, &button) || + !AddButtonToInputMap(binding, device, button, + std::bind(&CommonHostInterface::SetControllerAutoFireState, this, controller_index, + button_code.value(), std::placeholders::_1))) + { + Log_ErrorPrintf("Failed to register binding '%s' for autofire button", binding.c_str()); +#ifndef __ANDROID__ + continue; +#endif + } + } + + m_controller_autofires.push_back(ts); + } } } diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index 8402ef0f9..5ee830f69 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -46,7 +46,9 @@ public: enum : s32 { PER_GAME_SAVE_STATE_SLOTS = 10, - GLOBAL_SAVE_STATE_SLOTS = 10 + GLOBAL_SAVE_STATE_SLOTS = 10, + NUM_CONTROLLER_AUTOFIRE_BUTTONS = 4, + DEFAULT_AUTOFIRE_FREQUENCY = 2 }; using HostKeyCode = s32; @@ -370,10 +372,17 @@ protected: virtual void UpdateInputMap(SettingsInterface& si); void ClearInputMap(); + /// Updates controller metastate, including turbo and rumble. + void UpdateControllerMetaState(); + void AddControllerRumble(u32 controller_index, u32 num_motors, ControllerRumbleCallback callback); void UpdateControllerRumble(); void StopControllerRumble(); + void SetControllerAutoFireState(u32 controller_index, s32 button_code, bool active); + void StopControllerAutoFire(); + void UpdateControllerAutoFire(); + /// Returns the path to a save state file. Specifying an index of -1 is the "resume" save state. std::string GetGameSaveStateFileName(const char* game_code, s32 slot) const; @@ -519,6 +528,18 @@ private: }; std::vector m_controller_vibration_motors; + // controller turbo buttons + struct ControllerAutoFireState + { + u32 controller_index; + s32 button_code; + u8 frequency; + u8 countdown; + bool active; + bool state; + }; + std::vector m_controller_autofires; + #ifdef WITH_DISCORD_PRESENCE // discord rich presence bool m_discord_presence_enabled = false;