mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
ddr: add support for analog axis-only pads and handle left+right / up+down input (#659)
## Link to GitHub Issue or related Pull Request, if one exists n/a ## Description of change Some crappy DDR soft mats (either USB-based or console pads with crappy adapters that lack a dedicated "DDR pad mode") use X and Y analog axis for arrows, which means they cannot cleanly report Up+Down or Left+Right input. According to some ancient Stepmania code, a lot of these report a middle value (value between center and right, for example, for left+right). Borrow this hack from Stepmania and implement it in spice as analog input for DDR. Also, for rawinput/xinput automatic bind scenario, update the logic so that face buttons are always detected before analog axis / dpads for consistency. This would help with binding any DDR pads that simultaneously input face buttons / dpad / analog axis, so that we can prefer face buttons when detected. ## Testing I don't have any soft mats that have this, but it was tested with an xbox controller.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
#include "mdxf_poll.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/ddr/ddr.h"
|
||||
#include "games/ddr/io.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
@@ -357,14 +358,17 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
||||
|
||||
// decide on button map
|
||||
const size_t *button_map = nullptr;
|
||||
int player = 0;
|
||||
switch (node) {
|
||||
case 17:
|
||||
case 25:
|
||||
button_map = &buttons_p1[0];
|
||||
player = 1;
|
||||
break;
|
||||
case 18:
|
||||
case 26:
|
||||
button_map = &buttons_p2[0];
|
||||
player = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -374,19 +378,27 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
||||
if (source == EXTERNAL_POLL) {
|
||||
// get buttons
|
||||
auto &buttons = games::ddr::get_buttons();
|
||||
|
||||
// get analogs
|
||||
bool analog_left = false;
|
||||
bool analog_right = false;
|
||||
games::ddr::get_analog_x_axis(player, analog_left, analog_right);
|
||||
bool analog_up = false;
|
||||
bool analog_down = false;
|
||||
games::ddr::get_analog_y_axis(player, analog_up, analog_down);
|
||||
|
||||
uint8_t up_down = 0;
|
||||
uint8_t left_right = 0;
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0])) || analog_up) {
|
||||
up_down |= 0xF0;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1])) || analog_down) {
|
||||
up_down |= 0x0F;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2])) || analog_left) {
|
||||
left_right |= 0xF0;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3])) || analog_right) {
|
||||
left_right |= 0x0F;
|
||||
}
|
||||
current_state = (uint16_t(up_down) << 8) | left_right;
|
||||
|
||||
@@ -291,4 +291,50 @@ namespace games::ddr {
|
||||
// dispose device hook
|
||||
devicehook_dispose();
|
||||
}
|
||||
|
||||
static void get_analog_xy_axis(Analog &analog, bool &less, bool &more) {
|
||||
if (!analog.isSet()) {
|
||||
return;
|
||||
}
|
||||
const auto value = GameAPI::Analogs::getState(RI_MGR, analog);
|
||||
|
||||
// stepmania linux source:
|
||||
// https://github.com/stepmania/stepmania/blob/d55acb1ba26f1c5b5e3048d6d6c0bd116625216f/src/arch/InputHandler/InputHandler_Linux_Event.cpp#L410
|
||||
|
||||
// for example, value cap with range [0, 255]:
|
||||
// 127 = 0.498, neutral
|
||||
// 128 = 0.502, both
|
||||
// apparently some adapters report values like above; this obviously makes a lot of
|
||||
// assumptions about bit width of the HID value cap and how the adapter reports
|
||||
// neutral/both values, but this is good enough for parity with stepmania and its many forks
|
||||
if (0.5001f < value && value < 0.75f) {
|
||||
less |= true;
|
||||
more |= true;
|
||||
|
||||
} else if (value <= 0.25f) {
|
||||
less |= true;
|
||||
} else if (value >= 0.75f) {
|
||||
more |= true;
|
||||
}
|
||||
}
|
||||
|
||||
void get_analog_x_axis(int player, bool &left, bool &right) {
|
||||
if (player != 1 && player != 2) {
|
||||
log_fatal("ddr", "invalid player number: {}, expected 1 or 2", player);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &analog = get_analogs().at(player == 1 ? Analogs::P1_LEFT_RIGHT : Analogs::P2_LEFT_RIGHT);
|
||||
get_analog_xy_axis(analog, left, right);
|
||||
}
|
||||
|
||||
void get_analog_y_axis(int player, bool &up, bool &down) {
|
||||
if (player != 1 && player != 2) {
|
||||
log_fatal("ddr", "invalid player number: {}, expected 1 or 2", player);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &analog = get_analogs().at(player == 1 ? Analogs::P1_UP_DOWN : Analogs::P2_UP_DOWN);
|
||||
get_analog_xy_axis(analog, up, down);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,7 @@ namespace games::ddr {
|
||||
private:
|
||||
void register_codecs();
|
||||
};
|
||||
|
||||
void get_analog_x_axis(int player, bool &left, bool &right);
|
||||
void get_analog_y_axis(int player, bool &up, bool &down);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,49 @@ std::vector<Button> &games::ddr::get_buttons() {
|
||||
return analogs;
|
||||
}
|
||||
|
||||
|
||||
std::string games::ddr::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"For DDR pad arrows, double check that simultaneous Left+Right and Up+Down can be detected.\n"
|
||||
"You should boot the game, enter test menu, and use Foot Panel Check.\n\n"
|
||||
"When mapping arrows, try the following in order:\n\n"
|
||||
" 1. If your controller uses face buttons (A/B/X/Y), bind them here.\n"
|
||||
" 2. If your controller supports XInput, use that.\n"
|
||||
" 3. If your controller uses analog axis for arrows, try Analogs tab.\n"
|
||||
" 4. Otherwise, you will need to use remapping software or get a better adapter."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Analog> &games::ddr::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Dance Dance Revolution");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"P1 Left-Right (Axis Fix)",
|
||||
"P1 Up-Down (Axis Fix)",
|
||||
"P2 Left-Right (Axis Fix)",
|
||||
"P2 Up-Down (Axis Fix)"
|
||||
);
|
||||
}
|
||||
return analogs;
|
||||
}
|
||||
|
||||
std::string games::ddr::get_analogs_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"Only use this if your DDR pad outputs analog axis for arrows.\n\n"
|
||||
"If the pad uses face buttons (A/B/X/Y), use Buttons tab instead.\n\n"
|
||||
"Spice will treat values <=25% as Left, >=75% as Right, ~=50% as neutral,\n"
|
||||
"and value between 50% and 75% as both arrows.\n\n"
|
||||
"This is the classic Stepmania \"Axis Fix\" which may or may not work with\n"
|
||||
"your dance pad or your adapter."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Light> &games::ddr::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
|
||||
@@ -33,6 +33,16 @@ namespace games::ddr {
|
||||
};
|
||||
}
|
||||
|
||||
// all analogs in correct order
|
||||
namespace Analogs {
|
||||
enum {
|
||||
P1_LEFT_RIGHT,
|
||||
P1_UP_DOWN,
|
||||
P2_LEFT_RIGHT,
|
||||
P2_UP_DOWN
|
||||
};
|
||||
}
|
||||
|
||||
// all lights in correct order
|
||||
namespace Lights {
|
||||
enum {
|
||||
@@ -177,5 +187,8 @@ namespace games::ddr {
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::string get_analogs_help();
|
||||
std::vector<Analog> &get_analogs();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
@@ -498,32 +498,80 @@ int games::ddr::DDRP3IOHandle::device_io(
|
||||
|
||||
// shift table
|
||||
static size_t shift_table[] = {
|
||||
30, 28, 29, 8, 9, 10, 11, 12, 24, 25, 14, 15, 16, 17, 18, 19, 20, 26, 27, 22, 23
|
||||
30, 28, 29, // service, test, coin
|
||||
8, // p1 start
|
||||
9, 10, 11, 12, // p1 panel
|
||||
24, 25, 14, 15, // p1 menu
|
||||
16, // p2 start
|
||||
17, 18, 19, 20, // p2 panel
|
||||
26, 27, 22, 23 // p2 menu
|
||||
};
|
||||
static size_t button_table[] = {
|
||||
Buttons::SERVICE,
|
||||
Buttons::TEST,
|
||||
Buttons::COIN_MECH,
|
||||
|
||||
Buttons::P1_START,
|
||||
|
||||
Buttons::P1_PANEL_UP,
|
||||
Buttons::P1_PANEL_DOWN,
|
||||
Buttons::P1_PANEL_LEFT,
|
||||
Buttons::P1_PANEL_RIGHT,
|
||||
|
||||
Buttons::P1_MENU_UP,
|
||||
Buttons::P1_MENU_DOWN,
|
||||
Buttons::P1_MENU_LEFT,
|
||||
Buttons::P1_MENU_RIGHT,
|
||||
|
||||
Buttons::P2_START,
|
||||
|
||||
Buttons::P2_PANEL_UP,
|
||||
Buttons::P2_PANEL_DOWN,
|
||||
Buttons::P2_PANEL_LEFT,
|
||||
Buttons::P2_PANEL_RIGHT,
|
||||
|
||||
Buttons::P2_MENU_UP,
|
||||
Buttons::P2_MENU_DOWN,
|
||||
Buttons::P2_MENU_LEFT,
|
||||
Buttons::P2_MENU_RIGHT,
|
||||
};
|
||||
|
||||
// get analogs
|
||||
struct {
|
||||
bool up;
|
||||
bool down;
|
||||
bool left;
|
||||
bool right;
|
||||
} analog_values[2] = {};
|
||||
games::ddr::get_analog_x_axis(1, analog_values[0].left, analog_values[0].right);
|
||||
games::ddr::get_analog_y_axis(1, analog_values[0].up, analog_values[0].down);
|
||||
games::ddr::get_analog_x_axis(2, analog_values[1].left, analog_values[1].right);
|
||||
games::ddr::get_analog_y_axis(2, analog_values[1].up, analog_values[1].down);
|
||||
if (analog_values[0].up) {
|
||||
controls |= 1 << 9;
|
||||
}
|
||||
if (analog_values[0].down) {
|
||||
controls |= 1 << 10;
|
||||
}
|
||||
if (analog_values[0].left) {
|
||||
controls |= 1 << 11;
|
||||
}
|
||||
if (analog_values[0].right) {
|
||||
controls |= 1 << 12;
|
||||
}
|
||||
if (analog_values[1].up) {
|
||||
controls |= 1 << 17;
|
||||
}
|
||||
if (analog_values[1].down) {
|
||||
controls |= 1 << 18;
|
||||
}
|
||||
if (analog_values[1].left) {
|
||||
controls |= 1 << 19;
|
||||
}
|
||||
if (analog_values[1].right) {
|
||||
controls |= 1 << 20;
|
||||
}
|
||||
|
||||
// update states
|
||||
auto &buttons = get_buttons();
|
||||
size_t count = 0;
|
||||
|
||||
@@ -92,6 +92,9 @@ namespace games {
|
||||
games.push_back(ddr);
|
||||
buttons.insert({ ddr, ddr::get_buttons() });
|
||||
lights.insert({ ddr, ddr::get_lights() });
|
||||
buttons_help.insert({ ddr, ddr::get_buttons_help() });
|
||||
analogs_help.insert({ ddr, ddr::get_analogs_help() });
|
||||
analogs.insert({ ddr, ddr::get_analogs() });
|
||||
file_hints[ddr].push_back({"ddr.dll"});
|
||||
file_hints[ddr].push_back({"mdxja_945.dll"});
|
||||
file_hints[ddr].push_back({"arkmdxp3.dll"});
|
||||
|
||||
@@ -1206,6 +1206,16 @@ namespace overlay::windows {
|
||||
case rawinput::HID: {
|
||||
auto hid = device->hidInfo;
|
||||
|
||||
struct temp_button {
|
||||
std::string device_name;
|
||||
unsigned short vkey;
|
||||
ButtonAnalogType analog_type;
|
||||
};
|
||||
|
||||
// use this to prioritize button binding over value types
|
||||
// (for controllers that output both)
|
||||
std::optional<temp_button> button_to_bind;
|
||||
|
||||
// ignore touchscreen and digitizer button inputs
|
||||
// digitizer has funky stuff like "Touch Valid" "Data Valid" always held high
|
||||
if (!rawinput::touch::is_touchscreen(device) &&
|
||||
@@ -1219,18 +1229,11 @@ namespace overlay::windows {
|
||||
|
||||
// check if button is down
|
||||
if (button_states[i]) {
|
||||
|
||||
// bind key
|
||||
button->setDeviceIdentifier(device->name);
|
||||
button->setVKey(static_cast<unsigned short>(button_index + i));
|
||||
button->setAnalogType(BAT_NONE);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
ImGui::CloseCurrentPopup();
|
||||
buttons_bind_active = false;
|
||||
inc_buttons_many_index(button_it_max);
|
||||
RI_MGR->devices_midi_freeze(false);
|
||||
button_to_bind = {
|
||||
device->name,
|
||||
static_cast<unsigned short>(button_index + i),
|
||||
BAT_NONE
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1239,95 +1242,100 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
// value caps
|
||||
auto value_states = &hid->value_states;
|
||||
auto bind_value_states = &hid->bind_value_states;
|
||||
auto value_names = &hid->value_caps_names;
|
||||
for (size_t i = 0; i < value_states->size(); i++) {
|
||||
auto &state = value_states->at(i);
|
||||
auto &bind_state = bind_value_states->at(i);
|
||||
auto &value_name = value_names->at(i);
|
||||
if (!button_to_bind.has_value()) {
|
||||
auto value_states = &hid->value_states;
|
||||
auto bind_value_states = &hid->bind_value_states;
|
||||
auto value_names = &hid->value_caps_names;
|
||||
for (size_t i = 0; i < value_states->size(); i++) {
|
||||
auto &state = value_states->at(i);
|
||||
auto &bind_state = bind_value_states->at(i);
|
||||
auto &value_name = value_names->at(i);
|
||||
|
||||
// check for valid axis names
|
||||
if (value_name == "X" ||
|
||||
value_name == "Y" ||
|
||||
value_name == "Rx" ||
|
||||
value_name == "Ry" ||
|
||||
value_name == "Z")
|
||||
{
|
||||
// check if axis is in activation area
|
||||
float normalized = (state - 0.5f) * 2.f;
|
||||
float diff = std::fabs(state - bind_state);
|
||||
if (std::fabs(normalized) > 0.9f && diff > 0.1f) {
|
||||
auto bat = normalized > 0 ? BAT_POSITIVE : BAT_NEGATIVE;
|
||||
// check for valid axis names
|
||||
if (value_name == "X" ||
|
||||
value_name == "Y" ||
|
||||
value_name == "Rx" ||
|
||||
value_name == "Ry" ||
|
||||
value_name == "Z")
|
||||
{
|
||||
// check if axis is in activation area
|
||||
float normalized = (state - 0.5f) * 2.f;
|
||||
float diff = std::fabs(state - bind_state);
|
||||
if (std::fabs(normalized) > 0.9f && diff > 0.1f) {
|
||||
auto bat = normalized > 0 ? BAT_POSITIVE : BAT_NEGATIVE;
|
||||
|
||||
// bind value
|
||||
button->setDeviceIdentifier(device->name);
|
||||
button->setVKey(static_cast<unsigned short>(i));
|
||||
button->setAnalogType(bat);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
ImGui::CloseCurrentPopup();
|
||||
buttons_bind_active = false;
|
||||
inc_buttons_many_index(button_it_max);
|
||||
RI_MGR->devices_midi_freeze(false);
|
||||
// bind value
|
||||
button_to_bind = {
|
||||
device->name,
|
||||
static_cast<unsigned short>(i),
|
||||
bat
|
||||
};
|
||||
|
||||
// usually, turntables are X, knobs are X and Y
|
||||
// gamepad triggers are Z, and Rx/Ry are right thumb stick
|
||||
// one day we will label all I/O modules and flag which one are button-as-analog
|
||||
// so that we don't have to do string comparions like below
|
||||
if ((value_name == "X" || value_name == "Y") &&
|
||||
(button->getName().find("Press") == std::string::npos) && // museca
|
||||
(button->getName().find("Slowdown") == std::string::npos) && // bishibashi
|
||||
(button->getName().find("TT+") != std::string::npos ||
|
||||
button->getName().find("TT-") != std::string::npos || // iidx
|
||||
button->getName().find("Knob") != std::string::npos || // gitadora guitar
|
||||
button->getName().find("Disk") != std::string::npos || // museca, bishibashi
|
||||
button->getName().find("VOL-") != std::string::npos)) { // sdvx
|
||||
this->analog_as_button_warning_show_next_frame =
|
||||
std::make_pair(button->getName(), alt_index);
|
||||
// usually, turntables are X, knobs are X and Y
|
||||
// gamepad triggers are Z, and Rx/Ry are right thumb stick
|
||||
// one day we will label all I/O modules and flag which one are button-as-analog
|
||||
// so that we don't have to do string comparions like below
|
||||
if ((value_name == "X" || value_name == "Y") &&
|
||||
(button->getName().find("Press") == std::string::npos) && // museca
|
||||
(button->getName().find("Slowdown") == std::string::npos) && // bishibashi
|
||||
(button->getName().find("TT+") != std::string::npos ||
|
||||
button->getName().find("TT-") != std::string::npos || // iidx
|
||||
button->getName().find("Knob") != std::string::npos || // gitadora guitar
|
||||
button->getName().find("Disk") != std::string::npos || // museca, bishibashi
|
||||
button->getName().find("P1 Panel") != std::string::npos || // ddr
|
||||
button->getName().find("P2 Panel") != std::string::npos || // ddr
|
||||
button->getName().find("VOL-") != std::string::npos)) { // sdvx
|
||||
this->analog_as_button_warning_show_next_frame =
|
||||
std::make_pair(button->getName(), alt_index);
|
||||
}
|
||||
break;
|
||||
|
||||
} else if (diff > 0.3f) {
|
||||
bind_state = state;
|
||||
}
|
||||
break;
|
||||
|
||||
} else if (diff > 0.3f) {
|
||||
bind_state = state;
|
||||
}
|
||||
}
|
||||
|
||||
// hat switch
|
||||
if (value_name == "Hat switch") {
|
||||
// hat switch
|
||||
if (value_name == "Hat switch") {
|
||||
|
||||
// get hat switch values
|
||||
ButtonAnalogType buffer[3], buffer_bind[3];
|
||||
Button::getHatSwitchValues(state, buffer);
|
||||
Button::getHatSwitchValues(bind_state, buffer_bind);
|
||||
// get hat switch values
|
||||
ButtonAnalogType buffer[3], buffer_bind[3];
|
||||
Button::getHatSwitchValues(state, buffer);
|
||||
Button::getHatSwitchValues(bind_state, buffer_bind);
|
||||
|
||||
// check the first entry only
|
||||
if (buffer[0] != BAT_NONE && buffer[0] != buffer_bind[0]) {
|
||||
// check the first entry only
|
||||
if (buffer[0] != BAT_NONE && buffer[0] != buffer_bind[0]) {
|
||||
|
||||
// bind value
|
||||
button->setDeviceIdentifier(device->name);
|
||||
button->setVKey(static_cast<unsigned short>(i));
|
||||
button->setAnalogType(buffer[0]);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
ImGui::CloseCurrentPopup();
|
||||
buttons_bind_active = false;
|
||||
inc_buttons_many_index(button_it_max);
|
||||
RI_MGR->devices_midi_freeze(false);
|
||||
break;
|
||||
// bind value
|
||||
button_to_bind = {
|
||||
device->name,
|
||||
static_cast<unsigned short>(i),
|
||||
buffer[0]
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (button_to_bind.has_value()) {
|
||||
const auto &b = button_to_bind.value();
|
||||
button->setDeviceIdentifier(b.device_name);
|
||||
button->setVKey(b.vkey);
|
||||
button->setAnalogType(b.analog_type);
|
||||
button->setDebounceUp(0.0);
|
||||
button->setDebounceDown(0.0);
|
||||
button->setBatThreshold(0);
|
||||
button->setVelocityThreshold(0);
|
||||
::Config::getInstance().updateBinding(
|
||||
games_list[games_selected], *button,
|
||||
alt_index - 1);
|
||||
ImGui::CloseCurrentPopup();
|
||||
buttons_bind_active = false;
|
||||
inc_buttons_many_index(button_it_max);
|
||||
RI_MGR->devices_midi_freeze(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case rawinput::MIDI: {
|
||||
@@ -2184,9 +2192,9 @@ namespace overlay::windows {
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginTable("AnalogsTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
|
||||
|
||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(220));
|
||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(240));
|
||||
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(100));
|
||||
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(80));
|
||||
|
||||
// check if empty
|
||||
if (!analogs || analogs->empty()) {
|
||||
|
||||
@@ -153,7 +153,7 @@ XInputSetState(
|
||||
float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) {
|
||||
return 0.5f;
|
||||
}
|
||||
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) {
|
||||
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button, XINPUT_GAMEPAD_STATE_NORMALIZED *state_in) {
|
||||
return false;
|
||||
}
|
||||
bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) {
|
||||
@@ -289,59 +289,66 @@ XInputSetState(
|
||||
}
|
||||
}
|
||||
|
||||
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) {
|
||||
XINPUT_GAMEPAD_STATE_NORMALIZED state;
|
||||
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button, XINPUT_GAMEPAD_STATE_NORMALIZED *state_in) {
|
||||
XINPUT_GAMEPAD_STATE_NORMALIZED state_on_stack;
|
||||
XINPUT_GAMEPAD_STATE_NORMALIZED *state;
|
||||
|
||||
get_state(player, state);
|
||||
if (state_in) {
|
||||
state = state_in;
|
||||
} else {
|
||||
state = &state_on_stack;
|
||||
get_state(player, *state);
|
||||
}
|
||||
|
||||
switch (button) {
|
||||
case XInputButtonEnum::DPAD_UP:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
|
||||
case XInputButtonEnum::DPAD_DOWN:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
|
||||
case XInputButtonEnum::DPAD_LEFT:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
|
||||
case XInputButtonEnum::DPAD_RIGHT:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
|
||||
case XInputButtonEnum::START:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_START) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_START) != 0;
|
||||
case XInputButtonEnum::BACK:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_BACK) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_BACK) != 0;
|
||||
case XInputButtonEnum::LEFT_STICK:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
|
||||
case XInputButtonEnum::RIGHT_STICK:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
|
||||
case XInputButtonEnum::LEFT_SHOULDER:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
|
||||
case XInputButtonEnum::RIGHT_SHOULDER:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
|
||||
case XInputButtonEnum::BUTTON_A:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_A) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_A) != 0;
|
||||
case XInputButtonEnum::BUTTON_B:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_B) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_B) != 0;
|
||||
case XInputButtonEnum::BUTTON_X:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_X) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_X) != 0;
|
||||
case XInputButtonEnum::BUTTON_Y:
|
||||
return (state.wButtons & XINPUT_GAMEPAD_Y) != 0;
|
||||
return (state->wButtons & XINPUT_GAMEPAD_Y) != 0;
|
||||
case XInputButtonEnum::LEFT_TRIGGER:
|
||||
return state.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
|
||||
return state->bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
|
||||
case XInputButtonEnum::RIGHT_TRIGGER:
|
||||
return state.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
|
||||
return state->bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
|
||||
case XInputButtonEnum::LEFT_STICK_UP:
|
||||
return state.sThumbLY > 0.6f;
|
||||
return state->sThumbLY > 0.6f;
|
||||
case XInputButtonEnum::LEFT_STICK_DOWN:
|
||||
return state.sThumbLY < 0.4f;
|
||||
return state->sThumbLY < 0.4f;
|
||||
case XInputButtonEnum::LEFT_STICK_LEFT:
|
||||
return state.sThumbLX < 0.4f;
|
||||
return state->sThumbLX < 0.4f;
|
||||
case XInputButtonEnum::LEFT_STICK_RIGHT:
|
||||
return state.sThumbLX > 0.6f;
|
||||
return state->sThumbLX > 0.6f;
|
||||
case XInputButtonEnum::RIGHT_STICK_UP:
|
||||
return state.sThumbRY > 0.6f;
|
||||
return state->sThumbRY > 0.6f;
|
||||
case XInputButtonEnum::RIGHT_STICK_DOWN:
|
||||
return state.sThumbRY < 0.4f;
|
||||
return state->sThumbRY < 0.4f;
|
||||
case XInputButtonEnum::RIGHT_STICK_LEFT:
|
||||
return state.sThumbRX < 0.4f;
|
||||
return state->sThumbRX < 0.4f;
|
||||
case XInputButtonEnum::RIGHT_STICK_RIGHT:
|
||||
return state.sThumbRX > 0.6f;
|
||||
return state->sThumbRX > 0.6f;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -374,11 +381,49 @@ XInputSetState(
|
||||
}
|
||||
|
||||
bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) {
|
||||
|
||||
constexpr std::array<XInputButtonEnum, static_cast<size_t>(XInputButtonEnum::COUNT)> button_priority = {
|
||||
// the ordering here is important; we want to check buttons first, then dpad, then analog
|
||||
// this is to help with cases like DDR pads that output multiple at the same time for arrows
|
||||
|
||||
// actual buttons
|
||||
XInputButtonEnum::BUTTON_A,
|
||||
XInputButtonEnum::BUTTON_B,
|
||||
XInputButtonEnum::BUTTON_X,
|
||||
XInputButtonEnum::BUTTON_Y,
|
||||
XInputButtonEnum::START,
|
||||
XInputButtonEnum::BACK,
|
||||
XInputButtonEnum::LEFT_STICK,
|
||||
XInputButtonEnum::RIGHT_STICK,
|
||||
XInputButtonEnum::LEFT_SHOULDER,
|
||||
XInputButtonEnum::RIGHT_SHOULDER,
|
||||
|
||||
// dpad
|
||||
XInputButtonEnum::DPAD_UP,
|
||||
XInputButtonEnum::DPAD_DOWN,
|
||||
XInputButtonEnum::DPAD_LEFT,
|
||||
XInputButtonEnum::DPAD_RIGHT,
|
||||
|
||||
// analog values that can be used as buttons
|
||||
XInputButtonEnum::LEFT_TRIGGER,
|
||||
XInputButtonEnum::RIGHT_TRIGGER,
|
||||
XInputButtonEnum::LEFT_STICK_UP,
|
||||
XInputButtonEnum::LEFT_STICK_DOWN,
|
||||
XInputButtonEnum::LEFT_STICK_LEFT,
|
||||
XInputButtonEnum::LEFT_STICK_RIGHT,
|
||||
XInputButtonEnum::RIGHT_STICK_UP,
|
||||
XInputButtonEnum::RIGHT_STICK_DOWN,
|
||||
XInputButtonEnum::RIGHT_STICK_LEFT,
|
||||
XInputButtonEnum::RIGHT_STICK_RIGHT,
|
||||
};
|
||||
|
||||
for (uint8_t player = 0; player < XUSER_MAX_COUNT; player++) {
|
||||
XINPUT_GAMEPAD_STATE_NORMALIZED state;
|
||||
get_state(player, state);
|
||||
for (uint16_t b = 0; b < static_cast<uint16_t>(XInputButtonEnum::COUNT); b++) {
|
||||
if (is_button_pressed(player, static_cast<XInputButtonEnum>(b))) {
|
||||
if (is_button_pressed(player, button_priority[b], &state)) {
|
||||
button.player = player;
|
||||
button.button = static_cast<XInputButtonEnum>(b);
|
||||
button.button = button_priority[b];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace xinput {
|
||||
float sThumbRY;
|
||||
};
|
||||
|
||||
// the order of this enum is used for vkey mapping (saved in config file)
|
||||
// therefore it can never be changed
|
||||
enum class XInputButtonEnum : uint16_t {
|
||||
// actual buttons
|
||||
DPAD_UP,
|
||||
@@ -102,7 +104,7 @@ namespace xinput {
|
||||
~XInputManager();
|
||||
void stop();
|
||||
std::vector<uint8_t> get_available_players();
|
||||
bool is_button_pressed(uint8_t player, XInputButtonEnum button);
|
||||
bool is_button_pressed(uint8_t player, XInputButtonEnum button, XINPUT_GAMEPAD_STATE_NORMALIZED *state_in=nullptr);
|
||||
float get_analog_state(uint8_t player, XInputAnalogEnum analog);
|
||||
bool get_any_button_pressed(XINPUT_NEW_BUTTON &button);
|
||||
void set_output_state(uint8_t player, XInputOutputEnum output, float value);
|
||||
|
||||
Reference in New Issue
Block a user