// Copyright 2010 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #pragma once #include #include #include #include #include #include #include "Common/IniFile.h" #include "Common/MathUtil.h" #include "Core/ConfigManager.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/GCPadStatus.h" #define sign(x) ((x) ? (x) < 0 ? -1 : 1 : 0) enum { GROUP_TYPE_OTHER, GROUP_TYPE_STICK, GROUP_TYPE_MIXED_TRIGGERS, GROUP_TYPE_BUTTONS, GROUP_TYPE_FORCE, GROUP_TYPE_EXTENSION, GROUP_TYPE_TILT, GROUP_TYPE_CURSOR, GROUP_TYPE_TRIGGERS, GROUP_TYPE_SLIDER }; enum { SETTING_RADIUS, SETTING_DEADZONE, }; const char* const named_directions[] = {"Up", "Down", "Left", "Right"}; class ControllerEmu { public: class ControlGroup { public: class Control { protected: Control(ControllerInterface::ControlReference* const _ref, const std::string& _name) : control_ref(_ref), name(_name) { } public: virtual ~Control() {} std::unique_ptr const control_ref; const std::string name; }; class Input : public Control { public: Input(const std::string& _name) : Control(new ControllerInterface::InputReference, _name) {} }; class Output : public Control { public: Output(const std::string& _name) : Control(new ControllerInterface::OutputReference, _name) {} }; enum class SettingType { NORMAL, // normal settings are saved to configuration files VIRTUAL, // virtual settings are not saved at all }; class NumericSetting { public: NumericSetting(const std::string& setting_name, const ControlState default_value, const unsigned int low = 0, const unsigned int high = 100, const SettingType setting_type = SettingType::NORMAL) : m_type(setting_type), m_name(setting_name), m_default_value(default_value), m_low(low), m_high(high) { } ControlState GetValue() const { return m_value; } void SetValue(ControlState value) { m_value = value; } const SettingType m_type; const std::string m_name; const ControlState m_default_value; const unsigned int m_low, m_high; ControlState m_value; }; class BooleanSetting { public: BooleanSetting(const std::string& setting_name, const bool default_value, const SettingType setting_type = SettingType::NORMAL) : m_type(setting_type), m_name(setting_name), m_default_value(default_value) { } virtual bool GetValue() const { return m_value; } virtual void SetValue(bool value) { m_value = value; } const SettingType m_type; const std::string m_name; const bool m_default_value; bool m_value; }; class BackgroundInputSetting : public BooleanSetting { public: BackgroundInputSetting(const std::string& setting_name) : BooleanSetting(setting_name, false, SettingType::VIRTUAL) { } bool GetValue() const override { return SConfig::GetInstance().m_BackgroundInput; } void SetValue(bool value) override { m_value = value; SConfig::GetInstance().m_BackgroundInput = value; } }; ControlGroup(const std::string& _name, const unsigned int _type = GROUP_TYPE_OTHER) : name(_name), ui_name(_name), type(_type) { } ControlGroup(const std::string& _name, const std::string& _ui_name, const unsigned int _type = GROUP_TYPE_OTHER) : name(_name), ui_name(_ui_name), type(_type) { } virtual ~ControlGroup() {} virtual void LoadConfig(IniFile::Section* sec, const std::string& defdev = "", const std::string& base = ""); virtual void SaveConfig(IniFile::Section* sec, const std::string& defdev = "", const std::string& base = ""); void SetControlExpression(int index, const std::string& expression); const std::string name; const std::string ui_name; const unsigned int type; std::vector> controls; std::vector> numeric_settings; std::vector> boolean_settings; }; class AnalogStick : public ControlGroup { public: // The GameCube controller and Wiimote attachments have a different default radius AnalogStick(const char* const _name, ControlState default_radius); AnalogStick(const char* const _name, const char* const _ui_name, ControlState default_radius); void GetState(ControlState* const x, ControlState* const y) { ControlState yy = controls[0]->control_ref->State() - controls[1]->control_ref->State(); ControlState xx = controls[3]->control_ref->State() - controls[2]->control_ref->State(); ControlState radius = numeric_settings[SETTING_RADIUS]->GetValue(); ControlState deadzone = numeric_settings[SETTING_DEADZONE]->GetValue(); ControlState m = controls[4]->control_ref->State(); ControlState ang = atan2(yy, xx); ControlState ang_sin = sin(ang); ControlState ang_cos = cos(ang); ControlState dist = sqrt(xx * xx + yy * yy); // dead zone code dist = std::max(0.0, dist - deadzone); dist /= (1 - deadzone); // radius dist *= radius; // The modifier halves the distance by 50%, which is useful // for keyboard controls. if (m) dist *= 0.5; yy = std::max(-1.0, std::min(1.0, ang_sin * dist)); xx = std::max(-1.0, std::min(1.0, ang_cos * dist)); *y = yy; *x = xx; } }; class Buttons : public ControlGroup { public: Buttons(const std::string& _name); Buttons(const std::string& ini_name, const std::string& group_name); template void GetState(C* const buttons, const C* bitmasks) { for (auto& control : controls) { if (control->control_ref->State() > numeric_settings[0]->GetValue()) // threshold *buttons |= *bitmasks; bitmasks++; } } }; class ModifySettingsButton : public Buttons { public: ModifySettingsButton(std::string button_name); void AddInput(std::string button_name, bool toggle = false); void GetState(); const std::vector& isSettingToggled() const { return associated_settings_toggle; } const std::vector& getSettingsModifier() const { return associated_settings; } private: std::vector threshold_exceeded; // internal calculation (if "state" was above threshold) std::vector associated_settings_toggle; // is setting toggled or hold? std::vector associated_settings; // result }; class MixedTriggers : public ControlGroup { public: MixedTriggers(const std::string& _name); void GetState(u16* const digital, const u16* bitmasks, ControlState* analog) { const unsigned int trig_count = ((unsigned int)(controls.size() / 2)); for (unsigned int i = 0; i < trig_count; ++i, ++bitmasks, ++analog) { if (controls[i]->control_ref->State() > numeric_settings[0]->GetValue()) // threshold { *analog = 1.0; *digital |= *bitmasks; } else { *analog = controls[i + trig_count]->control_ref->State(); } } } }; class Triggers : public ControlGroup { public: Triggers(const std::string& _name); void GetState(ControlState* analog) { const unsigned int trig_count = ((unsigned int)(controls.size())); const ControlState deadzone = numeric_settings[0]->GetValue(); for (unsigned int i = 0; i < trig_count; ++i, ++analog) *analog = std::max(controls[i]->control_ref->State() - deadzone, 0.0) / (1 - deadzone); } }; class Slider : public ControlGroup { public: Slider(const std::string& _name); void GetState(ControlState* const slider) { const ControlState deadzone = numeric_settings[0]->GetValue(); const ControlState state = controls[1]->control_ref->State() - controls[0]->control_ref->State(); if (fabs(state) > deadzone) *slider = (state - (deadzone * sign(state))) / (1 - deadzone); else *slider = 0; } }; class Force : public ControlGroup { public: Force(const std::string& _name); void GetState(ControlState* axis) { const ControlState deadzone = numeric_settings[0]->GetValue(); for (unsigned int i = 0; i < 6; i += 2) { ControlState tmpf = 0; const ControlState state = controls[i + 1]->control_ref->State() - controls[i]->control_ref->State(); if (fabs(state) > deadzone) tmpf = ((state - (deadzone * sign(state))) / (1 - deadzone)); ControlState& ax = m_swing[i >> 1]; *axis++ = (tmpf - ax); ax = tmpf; } } private: ControlState m_swing[3]; }; class Tilt : public ControlGroup { public: Tilt(const std::string& _name); void GetState(ControlState* const x, ControlState* const y, const bool step = true) { // this is all a mess ControlState yy = controls[0]->control_ref->State() - controls[1]->control_ref->State(); ControlState xx = controls[3]->control_ref->State() - controls[2]->control_ref->State(); ControlState deadzone = numeric_settings[0]->GetValue(); ControlState circle = numeric_settings[1]->GetValue(); auto const angle = numeric_settings[2]->GetValue() / 1.8; ControlState m = controls[4]->control_ref->State(); // deadzone / circle stick code // this section might be all wrong, but its working good enough, I think ControlState ang = atan2(yy, xx); ControlState ang_sin = sin(ang); ControlState ang_cos = cos(ang); // the amt a full square stick would have at current angle ControlState square_full = std::min(ang_sin ? 1 / fabs(ang_sin) : 2, ang_cos ? 1 / fabs(ang_cos) : 2); // the amt a full stick would have that was (user setting circular) at current angle // I think this is more like a pointed circle rather than a rounded square like it should be ControlState stick_full = (square_full * (1 - circle)) + (circle); ControlState dist = sqrt(xx * xx + yy * yy); // dead zone code dist = std::max(0.0, dist - deadzone * stick_full); dist /= (1 - deadzone); // circle stick code ControlState amt = dist / stick_full; dist += (square_full - 1) * amt * circle; if (m) dist *= 0.5; yy = std::max(-1.0, std::min(1.0, ang_sin * dist)); xx = std::max(-1.0, std::min(1.0, ang_cos * dist)); // this is kinda silly here // gui being open will make this happen 2x as fast, o well // silly if (step) { if (xx > m_tilt[0]) m_tilt[0] = std::min(m_tilt[0] + 0.1, xx); else if (xx < m_tilt[0]) m_tilt[0] = std::max(m_tilt[0] - 0.1, xx); if (yy > m_tilt[1]) m_tilt[1] = std::min(m_tilt[1] + 0.1, yy); else if (yy < m_tilt[1]) m_tilt[1] = std::max(m_tilt[1] - 0.1, yy); } *y = m_tilt[1] * angle; *x = m_tilt[0] * angle; } private: ControlState m_tilt[2]; }; class Cursor : public ControlGroup { public: Cursor(const std::string& _name); void GetState(ControlState* const x, ControlState* const y, ControlState* const z, const bool adjusted = false) { const ControlState zz = controls[4]->control_ref->State() - controls[5]->control_ref->State(); // silly being here if (zz > m_z) m_z = std::min(m_z + 0.1, zz); else if (zz < m_z) m_z = std::max(m_z - 0.1, zz); *z = m_z; // hide if (controls[6]->control_ref->State() > 0.5) { *x = 10000; *y = 0; } else { ControlState yy = controls[0]->control_ref->State() - controls[1]->control_ref->State(); ControlState xx = controls[3]->control_ref->State() - controls[2]->control_ref->State(); // adjust cursor according to settings if (adjusted) { xx *= (numeric_settings[1]->GetValue() * 2); yy *= (numeric_settings[2]->GetValue() * 2); yy += (numeric_settings[0]->GetValue() - 0.5); } // relative input if (boolean_settings[0]->GetValue()) { const ControlState deadzone = numeric_settings[3]->GetValue(); // deadzone to avoid the cursor slowly drifting if (std::abs(xx) > deadzone) m_x = MathUtil::Clamp(m_x + xx * SPEED_MULTIPLIER, -1.0, 1.0); if (std::abs(yy) > deadzone) m_y = MathUtil::Clamp(m_y + yy * SPEED_MULTIPLIER, -1.0, 1.0); // recenter if (controls[7]->control_ref->State() > 0.5) { m_x = 0.0; m_y = 0.0; } } else { m_x = xx; m_y = yy; } *x = m_x; *y = m_y; } } ControlState m_z; private: // This is used to reduce the cursor speed for relative input // to something that makes sense with the default range. static constexpr double SPEED_MULTIPLIER = 0.04; ControlState m_x = 0.0; ControlState m_y = 0.0; }; class Extension : public ControlGroup { public: Extension(const std::string& _name) : ControlGroup(_name, GROUP_TYPE_EXTENSION), switch_extension(0), active_extension(0) { } ~Extension() {} void GetState(u8* const data); bool IsButtonPressed() const; std::vector> attachments; int switch_extension; int active_extension; }; virtual ~ControllerEmu() {} virtual std::string GetName() const = 0; virtual void LoadDefaults(const ControllerInterface& ciface); virtual void LoadConfig(IniFile::Section* sec, const std::string& base = ""); virtual void SaveConfig(IniFile::Section* sec, const std::string& base = ""); void UpdateDefaultDevice(); void UpdateReferences(ControllerInterface& devi); // This returns a lock that should be held before calling State() on any control // references and GetState(), by extension. This prevents a race condition // which happens while handling a hotplug event because a control reference's State() // could be called before we have finished updating the reference. static std::unique_lock GetStateLock(); std::vector> groups; ciface::Core::DeviceQualifier default_device; };