dolphin/Source/Core/InputCommon/ControllerEmu.h
JosJuice cbd539eb3d Use official names for GameCube controller sticks
Main Stick is changed to Control Stick and C-Stick is changed to C Stick.

A new ui_name variable is added to ControlGroup so that the UI strings
in DolphinWX can be updated without breaking backwards compatibility
with config INIs and other things that use names as IDs.
2015-09-12 22:45:06 +02:00

455 lines
11 KiB
C++

// Copyright 2010 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <algorithm>
#include <cmath>
#include <memory>
#include <string>
#include <vector>
#include "Common/IniFile.h"
#include "Core/ConfigManager.h"
#include "InputCommon/GCPadStatus.h"
#include "InputCommon/ControllerInterface/ControllerInterface.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<ControllerInterface::ControlReference> 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) {}
};
class Setting
{
public:
Setting(const std::string& _name, const ControlState def_value
, const unsigned int _low = 0, const unsigned int _high = 100)
: name(_name)
, value(def_value)
, default_value(def_value)
, low(_low)
, high(_high)
, is_virtual(false)
, is_iterate(false) {}
virtual ~Setting()
{
}
const std::string name;
ControlState value;
const ControlState default_value;
const unsigned int low, high;
bool is_virtual;
bool is_iterate;
virtual void SetValue(ControlState new_value)
{
value = new_value;
}
virtual ControlState GetValue()
{
return value;
}
};
class BackgroundInputSetting : public Setting
{
public:
BackgroundInputSetting(const std::string &_name) : Setting(_name, false)
{
is_virtual = true;
}
void SetValue(ControlState new_value) override
{
SConfig::GetInstance().m_BackgroundInput = !!new_value;
}
ControlState GetValue() override
{
return SConfig::GetInstance().m_BackgroundInput;
}
};
class IterateUI : public Setting
{
public:
IterateUI(const std::string &_name) : Setting(_name, false)
{
is_iterate = true;
}
};
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 = "" );
const std::string name;
const std::string ui_name;
const unsigned int type;
std::vector<std::unique_ptr<Control>> controls;
std::vector<std::unique_ptr<Setting>> 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 = settings[SETTING_RADIUS]->value;
ControlState deadzone = settings[SETTING_DEADZONE]->value;
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);
template <typename C>
void GetState(C* const buttons, const C* bitmasks)
{
for (auto& control : controls)
{
if (control->control_ref->State() > settings[0]->value) // threshold
*buttons |= *bitmasks;
bitmasks++;
}
}
};
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() > settings[0]->value) //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 = settings[0]->value;
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 = settings[0]->value;
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 = settings[0]->value;
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 = settings[0]->value;
ControlState circle = settings[1]->value;
auto const angle = settings[2]->value / 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 *= (settings[1]->value * 2);
yy *= (settings[2]->value * 2);
yy += (settings[0]->value - 0.5);
}
*x = xx;
*y = yy;
}
}
ControlState m_z;
};
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<std::unique_ptr<ControllerEmu>> 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);
std::vector<std::unique_ptr<ControlGroup>> groups;
ciface::Core::DeviceQualifier default_device;
};