ControllerEmu: Add new "input override" system

This commit is contained in:
JosJuice
2021-03-21 20:27:00 +01:00
parent cb6d476538
commit cb16d20f2d
27 changed files with 355 additions and 74 deletions

View File

@ -4,6 +4,7 @@
#include "InputCommon/ControllerEmu/ControlGroup/AnalogStick.h"
#include <cmath>
#include <optional>
#include "Common/Common.h"
#include "Common/MathUtil.h"
@ -48,6 +49,34 @@ AnalogStick::StateData AnalogStick::GetState() const
return GetReshapableState(true);
}
AnalogStick::StateData AnalogStick::GetState(const InputOverrideFunction& override_func) const
{
bool override_occurred = false;
return GetState(override_func, &override_occurred);
}
AnalogStick::StateData AnalogStick::GetState(const InputOverrideFunction& override_func,
bool* override_occurred) const
{
StateData state = GetState();
if (!override_func)
return state;
if (const std::optional<ControlState> x_override = override_func(name, X_INPUT_OVERRIDE, state.x))
{
state.x = *x_override;
*override_occurred = true;
}
if (const std::optional<ControlState> y_override = override_func(name, Y_INPUT_OVERRIDE, state.y))
{
state.y = *y_override;
*override_occurred = true;
}
return state;
}
ControlState AnalogStick::GetGateRadiusAtAngle(double ang) const
{
return m_stick_gate->GetRadiusAtAngle(ang);

View File

@ -21,6 +21,8 @@ public:
ControlState GetGateRadiusAtAngle(double ang) const override;
StateData GetState() const;
StateData GetState(const InputOverrideFunction& override_func) const;
StateData GetState(const InputOverrideFunction& override_func, bool* override_occurred) const;
private:
Control* GetModifierInput() const override;

View File

@ -3,6 +3,7 @@
#pragma once
#include <cmath>
#include <string>
#include "InputCommon/ControlReference/ControlReference.h"
@ -24,5 +25,21 @@ public:
for (auto& control : controls)
*buttons |= *(bitmasks++) * control->GetState<bool>();
}
template <typename C>
void GetState(C* const buttons, const C* bitmasks,
const InputOverrideFunction& override_func) const
{
if (!override_func)
return GetState(buttons, bitmasks);
for (auto& control : controls)
{
ControlState state = control->GetState();
if (std::optional<ControlState> state_override = override_func(name, control->name, state))
state = *state_override;
*buttons |= *(bitmasks++) * (std::lround(state) > 0);
}
}
};
} // namespace ControllerEmu

View File

@ -5,14 +5,18 @@
#include <algorithm>
#include <cmath>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/IniFile.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"
namespace ControllerEmu
{
@ -27,6 +31,9 @@ class NumericSetting;
template <typename T>
class SettingValue;
using InputOverrideFunction = std::function<std::optional<ControlState>(
const std::string_view group_name, const std::string_view control_name, ControlState state)>;
enum class GroupType
{
Other,

View File

@ -82,15 +82,28 @@ ControlState Cursor::GetGateRadiusAtAngle(double ang) const
Cursor::StateData Cursor::GetState(const bool adjusted)
{
if (!adjusted)
{
const auto raw_input = GetReshapableState(false);
const ReshapeData input = GetReshapableState(adjusted);
const StateData state = adjusted ? UpdateState(input) : StateData{input.x, input.y};
return state;
}
return {raw_input.x, raw_input.y};
}
Cursor::StateData Cursor::GetState(const bool adjusted,
const ControllerEmu::InputOverrideFunction& override_func)
{
StateData state = GetState(adjusted);
if (!override_func)
return state;
const auto input = GetReshapableState(true);
if (const std::optional<ControlState> x_override = override_func(name, X_INPUT_OVERRIDE, state.x))
state.x = *x_override;
if (const std::optional<ControlState> y_override = override_func(name, Y_INPUT_OVERRIDE, state.y))
state.y = *y_override;
return state;
}
Cursor::StateData Cursor::UpdateState(Cursor::ReshapeData input)
{
// TODO: Using system time is ugly.
// Kill this after state is moved into wiimote rather than this class.
const auto now = Clock::now();

View File

@ -29,6 +29,7 @@ public:
// Modifies the state
StateData GetState(bool adjusted);
StateData GetState(bool adjusted, const ControllerEmu::InputOverrideFunction& override_func);
// Yaw movement in radians.
ControlState GetTotalYaw() const;
@ -40,6 +41,8 @@ public:
ControlState GetVerticalOffset() const;
private:
Cursor::StateData UpdateState(Cursor::ReshapeData input);
// This is used to reduce the cursor speed for relative input
// to something that makes sense with the default range.
static constexpr double STEP_PER_SEC = 0.01 * 200;

View File

@ -4,6 +4,7 @@
#include "InputCommon/ControllerEmu/ControlGroup/MixedTriggers.h"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <memory>
#include <string>
@ -63,6 +64,53 @@ void MixedTriggers::GetState(u16* const digital, const u16* bitmasks, ControlSta
}
}
void MixedTriggers::GetState(u16* digital, const u16* bitmasks, ControlState* analog,
const InputOverrideFunction& override_func, bool adjusted) const
{
if (!override_func)
return GetState(digital, bitmasks, analog, adjusted);
const ControlState threshold = GetThreshold();
ControlState deadzone = GetDeadzone();
// Return raw values. (used in UI)
if (!adjusted)
{
deadzone = 0.0;
}
const int trigger_count = int(controls.size() / 2);
for (int i = 0; i != trigger_count; ++i)
{
bool button_bool = false;
const ControlState button_value = ApplyDeadzone(controls[i]->GetState(), deadzone);
ControlState analog_value = ApplyDeadzone(controls[trigger_count + i]->GetState(), deadzone);
// Apply threshold:
if (button_value > threshold)
{
analog_value = 1.0;
button_bool = true;
}
if (const std::optional<ControlState> button_override =
override_func(name, controls[i]->name, static_cast<ControlState>(button_bool)))
{
button_bool = std::lround(*button_override) > 0;
}
if (const std::optional<ControlState> analog_override =
override_func(name, controls[trigger_count + i]->name, analog_value))
{
analog_value = *analog_override;
}
if (button_bool)
*digital |= bitmasks[i];
analog[i] = std::min(analog_value, 1.0);
}
}
ControlState MixedTriggers::GetDeadzone() const
{
return m_deadzone_setting.GetValue() / 100;

View File

@ -17,6 +17,8 @@ public:
void GetState(u16* digital, const u16* bitmasks, ControlState* analog,
bool adjusted = true) const;
void GetState(u16* digital, const u16* bitmasks, ControlState* analog,
const InputOverrideFunction& override_func, bool adjusted = true) const;
ControlState GetDeadzone() const;
ControlState GetThreshold() const;

View File

@ -35,4 +35,21 @@ Slider::StateData Slider::GetState() const
return {std::clamp(ApplyDeadzone(state, deadzone), -1.0, 1.0)};
}
Slider::StateData Slider::GetState(const InputOverrideFunction& override_func) const
{
if (!override_func)
return GetState();
const ControlState deadzone = m_deadzone_setting.GetValue() / 100;
ControlState state = controls[1]->GetState() - controls[0]->GetState();
state = ApplyDeadzone(state, deadzone);
if (std::optional<ControlState> state_override = override_func(name, X_INPUT_OVERRIDE, state))
state = *state_override;
return {std::clamp(state, -1.0, 1.0)};
}
} // namespace ControllerEmu

View File

@ -23,6 +23,9 @@ public:
explicit Slider(const std::string& name_);
StateData GetState() const;
StateData GetState(const InputOverrideFunction& override_func) const;
static constexpr const char* X_INPUT_OVERRIDE = "X";
private:
SettingValue<double> m_deadzone_setting;

View File

@ -6,6 +6,7 @@
#include <algorithm>
#include <cstddef>
#include <memory>
#include <optional>
#include <string>
#include "Common/Common.h"
@ -31,4 +32,25 @@ Triggers::StateData Triggers::GetState() const
return result;
}
Triggers::StateData Triggers::GetState(const InputOverrideFunction& override_func) const
{
if (!override_func)
return GetState();
const size_t trigger_count = controls.size();
const ControlState deadzone = m_deadzone_setting.GetValue() / 100;
StateData result(trigger_count);
for (size_t i = 0; i < trigger_count; ++i)
{
ControlState state = ApplyDeadzone(controls[i]->GetState(), deadzone);
if (std::optional<ControlState> state_override = override_func(name, controls[i]->name, state))
state = *state_override;
result.data[i] = std::min(state, 1.0);
}
return result;
}
} // namespace ControllerEmu

View File

@ -26,6 +26,7 @@ public:
explicit Triggers(const std::string& name);
StateData GetState() const;
StateData GetState(const InputOverrideFunction& override_func) const;
private:
SettingValue<double> m_deadzone_setting;

View File

@ -6,6 +6,7 @@
#include <memory>
#include <mutex>
#include <string>
#include <utility>
#include "Common/IniFile.h"
@ -176,4 +177,15 @@ void EmulatedController::LoadDefaults(const ControllerInterface& ciface)
SetDefaultDevice(default_device_string);
}
}
void EmulatedController::SetInputOverrideFunction(InputOverrideFunction override_func)
{
m_input_override_function = std::move(override_func);
}
void EmulatedController::ClearInputOverrideFunction()
{
m_input_override_function = {};
}
} // namespace ControllerEmu

View File

@ -15,6 +15,7 @@
#include "Common/IniFile.h"
#include "Common/MathUtil.h"
#include "InputCommon/ControlReference/ExpressionParser.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"
class ControllerInterface;
@ -184,6 +185,9 @@ public:
void SetDefaultDevice(const std::string& device);
void SetDefaultDevice(ciface::Core::DeviceQualifier devq);
void SetInputOverrideFunction(InputOverrideFunction override_func);
void ClearInputOverrideFunction();
void UpdateReferences(const ControllerInterface& devi);
void UpdateSingleControlReference(const ControllerInterface& devi, ControlReference* ref);
@ -228,6 +232,8 @@ protected:
// so theirs won't be used (and thus shouldn't even exist).
ciface::ExpressionParser::ControlEnvironment::VariableContainer m_expression_vars;
InputOverrideFunction m_input_override_function;
void UpdateReferences(ciface::ExpressionParser::ControlEnvironment& env);
private:

View File

@ -106,6 +106,10 @@ public:
const ReshapeData& GetCenter() const;
void SetCenter(ReshapeData center);
static constexpr const char* X_INPUT_OVERRIDE = "X";
static constexpr const char* Y_INPUT_OVERRIDE = "Y";
static constexpr const char* Z_INPUT_OVERRIDE = "Z";
protected:
ReshapeData Reshape(ControlState x, ControlState y, ControlState modifier = 0.0,
ControlState clamp = 1.0) const;