CommonHostInterface: Implement controller autofire

This commit is contained in:
Connor McLaughlin 2021-05-16 03:25:02 +10:00
parent c7beac5efd
commit 01c869b704
5 changed files with 146 additions and 4 deletions

View file

@ -569,7 +569,7 @@ void AndroidHostInterface::EmulationThreadLoop(JNIEnv* env)
else
System::RunFrame();
UpdateControllerRumble();
UpdateControllerMetaState();
if (m_vibration_enabled)
UpdateVibration();
}

View file

@ -213,7 +213,7 @@ void NoGUIHostInterface::Run()
else
System::RunFrames();
UpdateControllerRumble();
UpdateControllerMetaState();
if (m_frame_step_request)
{
m_frame_step_request = false;

View file

@ -1485,7 +1485,7 @@ void QtHostInterface::threadEntryPoint()
else
System::RunFrames();
UpdateControllerRumble();
UpdateControllerMetaState();
if (m_frame_step_request)
{
m_frame_step_request = false;

View file

@ -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<s32> 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<u8>(
std::clamp<s32>(si.GetIntValue(category, TinyString::FromFormat("AutoFire%uFrequency", turbo_button_index + 1),
DEFAULT_AUTOFIRE_FREQUENCY),
1, std::numeric_limits<decltype(ts.frequency)>::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);
}
}
}

View file

@ -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<ControllerRumbleState> 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<ControllerAutoFireState> m_controller_autofires;
#ifdef WITH_DISCORD_PRESENCE
// discord rich presence
bool m_discord_presence_enabled = false;