Controller: Add 'Disable Simultaneous Opposing Cardinal Directions' option

pull/3782/head
Stenzek 2 months ago
parent ee21478ad1
commit f7797b57ea
No known key found for this signature in database

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "analog_controller.h"
@ -464,7 +464,8 @@ void AnalogController::Poll()
m_tx_buffer[0] = GetIDByte();
m_tx_buffer[1] = m_status_byte;
const u16 button_state = m_button_state & GetExtraButtonMask();
const u16 button_mask = m_button_state & GetExtraButtonMask();
const u16 button_state = m_disable_socd ? ControllerHelpers::RemoveOpposingDirections(button_mask) : button_mask;
m_tx_buffer[2] = Truncate8(button_state);
m_tx_buffer[3] = Truncate8(button_state >> 8);
@ -813,7 +814,7 @@ static constexpr const char* s_shoulder_settings[] = {
TRANSLATE_NOOP("AnalogController", "Never"), TRANSLATE_NOOP("AnalogController", "Digital Mode Only"),
TRANSLATE_NOOP("AnalogController", "Analog and Digital Modes"), nullptr};
static const SettingInfo s_settings[] = {
static constexpr SettingInfo s_settings[] = {
{SettingInfo::Type::Boolean, "ForceAnalogOnReset",
TRANSLATE_NOOP("AnalogController", "Automatically Enable Analog Mode"),
TRANSLATE_NOOP("AnalogController", "Forces the controller to analog mode when the game is started/restarted."),
@ -824,6 +825,11 @@ static const SettingInfo s_settings[] = {
"AnalogController",
"Allows you to use the left analog stick to control the d-pad in digital mode, as well as the buttons."),
"true", nullptr, nullptr, nullptr, nullptr, nullptr, 0.0f},
{SettingInfo::Type::Boolean, "DisableSOCD",
TRANSLATE_NOOP("AnalogController", "Disable Simultaneous Opposing Cardinal Directions"),
TRANSLATE_NOOP("AnalogController",
"Prevents concurrent left/right or up/down inputs from being presented to the game."),
"false", nullptr, nullptr, nullptr, nullptr, nullptr, 0.0f},
{SettingInfo::Type::IntegerList, "AnalogShoulderButtons",
TRANSLATE_NOOP("AnalogController", "Use Right Analog for Shoulder Buttons"),
TRANSLATE_NOOP(
@ -880,9 +886,9 @@ const Controller::ControllerInfo AnalogController::INFO = {ControllerType::Analo
void AnalogController::LoadSettings(const SettingsInterface& si, const char* section, bool initial)
{
Controller::LoadSettings(si, section, initial);
m_force_analog_on_reset = si.GetBoolValue(section, "ForceAnalogOnReset", true);
m_analog_dpad_in_digital_mode = si.GetBoolValue(section, "AnalogDPadInDigitalMode", true);
m_disable_socd = si.GetBoolValue(section, "DisableSOCD", false);
m_analog_shoulder_buttons = static_cast<u8>(si.GetUIntValue(section, "AnalogShoulderButtons", 0u));
m_analog_trigger_buttons = static_cast<u8>(si.GetUIntValue(section, "AnalogTriggerButtons", 0u));
m_analog_deadzone = std::clamp(si.GetFloatValue(section, "AnalogDeadzone", DEFAULT_STICK_DEADZONE), 0.0f, 1.0f);

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
@ -136,8 +136,9 @@ private:
u8 m_invert_left_stick = 0;
u8 m_invert_right_stick = 0;
bool m_force_analog_on_reset = false;
bool m_analog_dpad_in_digital_mode = false;
bool m_force_analog_on_reset : 1 = false;
bool m_analog_dpad_in_digital_mode : 1 = false;
bool m_disable_socd : 1 = false;
u8 m_analog_shoulder_buttons = 0;
u8 m_analog_trigger_buttons = 0;

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "analog_joystick.h"
@ -273,7 +273,8 @@ bool AnalogJoystick::Transfer(const u8 data_in, u8* data_out)
case TransferState::ButtonsLSB:
{
*data_out = Truncate8(m_button_state);
*data_out =
Truncate8(m_disable_socd ? ControllerHelpers::RemoveOpposingDirections(m_button_state) : m_button_state);
m_transfer_state = TransferState::ButtonsMSB;
return true;
}
@ -381,7 +382,12 @@ static constexpr const char* s_invert_settings[] = {
TRANSLATE_NOOP("AnalogJoystick", "Invert Up/Down"), TRANSLATE_NOOP("AnalogJoystick", "Invert Left/Right + Up/Down"),
nullptr};
static const SettingInfo s_settings[] = {
static constexpr SettingInfo s_settings[] = {
{SettingInfo::Type::Boolean, "DisableSOCD",
TRANSLATE_NOOP("AnalogJoystick", "Disable Simultaneous Opposing Cardinal Directions"),
TRANSLATE_NOOP("AnalogJoystick",
"Prevents concurrent left/right or up/down inputs from being presented to the game."),
"false", nullptr, nullptr, nullptr, nullptr, nullptr, 0.0f},
{SettingInfo::Type::Float, "AnalogDeadzone", TRANSLATE_NOOP("AnalogJoystick", "Analog Deadzone"),
TRANSLATE_NOOP("AnalogJoystick",
"Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored."),
@ -416,4 +422,5 @@ void AnalogJoystick::LoadSettings(const SettingsInterface& si, const char* secti
std::clamp(si.GetFloatValue(section, "AnalogSensitivity", DEFAULT_STICK_SENSITIVITY), 0.01f, 3.0f);
m_invert_left_stick = static_cast<u8>(si.GetIntValue(section, "InvertLeftStick", 0));
m_invert_right_stick = static_cast<u8>(si.GetIntValue(section, "InvertRightStick", 0));
m_disable_socd = si.GetBoolValue(section, "DisableSOCD", false);
}

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
@ -104,6 +104,7 @@ private:
float m_analog_sensitivity = 1.33f;
u8 m_invert_left_stick = 0;
u8 m_invert_right_stick = 0;
bool m_disable_socd = false;
// On original hardware, the mode toggle is a switch rather than a button, so we'll enable Analog Mode by default
bool m_analog_mode = true;

@ -6,6 +6,7 @@
#include "common/types.h"
#include <cmath>
#include <type_traits>
namespace ControllerHelpers {
@ -33,4 +34,21 @@ ALWAYS_INLINE float MergeHalfAxesToFloat(u8 neg_value, u8 pos_value, bool invert
return (invert ? -result : result);
}
/// Removes opposing directions (left/right and up/down), preventing these buttons from being pressed concurrently.
/// Note: Assumes up is bit 4, right is bit 5, down is bit 6, and left is bit 7, which is the case for all PSX pads.
template<typename T>
requires(std::is_integral_v<T> || std::is_enum_v<T>)
ALWAYS_INLINE T RemoveOpposingDirections(T state)
{
using BitsType = std::make_unsigned_t<
typename std::conditional_t<std::is_enum_v<T>, std::underlying_type<T>, std::type_identity<T>>::type>;
const BitsType bits = static_cast<BitsType>(state);
const BitsType conflicts = static_cast<BitsType>(~(bits | (bits >> 2))) & static_cast<BitsType>(0x0030u);
// Active-low, so setting both conflicting bits releases them.
// Prefer Up (bit 4) over Down (bit 6), and Right (bit 5) over Left (bit 7).
return static_cast<T>(bits | (conflicts << 2));
}
} // namespace ControllerHelpers

@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "digital_controller.h"
#include "controller_helpers.h"
#include "system.h"
#include "util/state_wrapper.h"
@ -11,6 +12,7 @@
#include "common/assert.h"
#include "common/bitutils.h"
#include "common/settings_interface.h"
DigitalController::DigitalController(u32 index, u16 button_mask) : Controller(index), m_button_mask(button_mask)
{
@ -127,21 +129,32 @@ bool DigitalController::Transfer(const u8 data_in, u8* data_out)
case TransferState::ButtonsLSB:
{
*data_out = Truncate8(m_button_state & m_button_mask);
*data_out =
Truncate8((m_disable_socd ? ControllerHelpers::RemoveOpposingDirections(m_button_state) : m_button_state) &
m_button_mask);
m_transfer_state = TransferState::ButtonsMSB;
return true;
}
case TransferState::ButtonsMSB:
*data_out = Truncate8((m_button_state & m_button_mask) >> 8);
{
// Can skip opposing directions here since they're all in the LSB.
const u16 buttons_to_send = m_button_state & m_button_mask;
*data_out = Truncate8(buttons_to_send >> 8);
m_transfer_state = TransferState::Idle;
return false;
}
default:
UnreachableCode();
}
}
void DigitalController::LoadSettings(const SettingsInterface& si, const char* section, bool initial)
{
m_disable_socd = si.GetBoolValue(section, "DisableSOCD", false);
}
std::unique_ptr<DigitalController> DigitalController::Create(u32 index, ControllerType type)
{
// popn controller - right/down/left grounded
@ -156,6 +169,14 @@ std::unique_ptr<DigitalController> DigitalController::Create(u32 index, Controll
return std::make_unique<DigitalController>(index, mask);
}
static constexpr SettingInfo s_settings[] = {
{SettingInfo::Type::Boolean, "DisableSOCD",
TRANSLATE_NOOP("DigitalController", "Disable Simultaneous Opposing Cardinal Directions"),
TRANSLATE_NOOP("DigitalController",
"Prevents concurrent left/right or up/down inputs from being presented to the game."),
"false", nullptr, nullptr, nullptr, nullptr, nullptr, 0.0f},
};
static const Controller::ControllerBindingInfo s_binding_info[] = {
#define BUTTON(name, display_name, icon_name, button, genb) \
{name, display_name, icon_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb}
@ -180,13 +201,15 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
#undef BUTTON
};
const Controller::ControllerInfo DigitalController::INFO = {ControllerType::DigitalController,
"DigitalController",
TRANSLATE_NOOP("ControllerType", "Digital Controller"),
ICON_PF_GAMEPAD_ALT,
"images/controllers/digital_controller.svg",
s_binding_info,
{}};
const Controller::ControllerInfo DigitalController::INFO = {
ControllerType::DigitalController,
"DigitalController",
TRANSLATE_NOOP("ControllerType", "Digital Controller"),
ICON_PF_GAMEPAD_ALT,
"images/controllers/digital_controller.svg",
s_binding_info,
s_settings,
};
static const Controller::ControllerBindingInfo s_popn_binding_info[] = {
#define BUTTON(name, display_name, icon_name, button, genb) \
@ -209,10 +232,12 @@ static const Controller::ControllerBindingInfo s_popn_binding_info[] = {
#undef BUTTON
};
const Controller::ControllerInfo DigitalController::INFO_POPN = {ControllerType::PopnController,
"PopnController",
TRANSLATE_NOOP("ControllerType", "Pop'n Controller"),
ICON_PF_POPN_CONTROLLER,
nullptr,
s_popn_binding_info,
{}};
const Controller::ControllerInfo DigitalController::INFO_POPN = {
ControllerType::PopnController,
"PopnController",
TRANSLATE_NOOP("ControllerType", "Pop'n Controller"),
ICON_PF_POPN_CONTROLLER,
nullptr,
s_popn_binding_info,
{},
};

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
@ -52,6 +52,8 @@ public:
void ResetTransferState() override;
bool Transfer(const u8 data_in, u8* data_out) override;
void LoadSettings(const SettingsInterface& si, const char* section, bool initial) override;
private:
enum class TransferState : u8
{
@ -67,4 +69,5 @@ private:
u16 m_button_mask = UINT16_C(0xFFFF);
TransferState m_transfer_state = TransferState::Idle;
bool m_disable_socd = false;
};

@ -564,8 +564,6 @@ bool JogCon::Transfer(const u8 data_in, u8* data_out)
void JogCon::LoadSettings(const SettingsInterface& si, const char* section, bool initial)
{
Controller::LoadSettings(si, section, initial);
m_analog_deadzone = std::clamp(si.GetFloatValue(section, "AnalogDeadzone", DEFAULT_STICK_DEADZONE), 0.0f, 1.0f);
m_analog_sensitivity =
std::clamp(si.GetFloatValue(section, "AnalogSensitivity", DEFAULT_STICK_SENSITIVITY), 0.01f, 3.0f);

@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "negcon.h"
#include "controller_helpers.h"
#include "host.h"
#include "system.h"
@ -208,7 +209,8 @@ bool NeGcon::Transfer(const u8 data_in, u8* data_out)
case TransferState::ButtonsLSB:
{
*data_out = Truncate8(m_button_state);
*data_out =
Truncate8(m_disable_socd ? ControllerHelpers::RemoveOpposingDirections(m_button_state) : m_button_state);
m_transfer_state = TransferState::ButtonsMSB;
return true;
}
@ -292,7 +294,11 @@ static const Controller::ControllerBindingInfo s_binding_info[] = {
#undef BUTTON
};
static const SettingInfo s_settings[] = {
static constexpr SettingInfo s_settings[] = {
{SettingInfo::Type::Boolean, "DisableSOCD",
TRANSLATE_NOOP("NeGcon", "Disable Simultaneous Opposing Cardinal Directions"),
TRANSLATE_NOOP("NeGcon", "Prevents concurrent left/right or up/down inputs from being presented to the game."),
"false", nullptr, nullptr, nullptr, nullptr, nullptr, 0.0f},
{SettingInfo::Type::Float, "SteeringDeadzone", TRANSLATE_NOOP("NeGcon", "Steering Axis Deadzone"),
TRANSLATE_NOOP("NeGcon", "Sets deadzone for steering axis."), "0", "0", "0.99", "0.01", "%.0f%%", nullptr, 100.0f},
{SettingInfo::Type::Float, "SteeringSaturation", TRANSLATE_NOOP("NeGcon", "Steering Axis Saturation"),
@ -337,7 +343,7 @@ const Controller::ControllerInfo NeGcon::INFO = {ControllerType::NeGcon,
void NeGcon::LoadSettings(const SettingsInterface& si, const char* section, bool initial)
{
Controller::LoadSettings(si, section, initial);
m_disable_socd = si.GetBoolValue(section, "DisableSOCD", false);
m_steering_modifier = {
.deadzone = si.GetFloatValue(section, "SteeringDeadzone", DEFAULT_STEERING_MODIFIER.deadzone),
.saturation = si.GetFloatValue(section, "SteeringSaturation", DEFAULT_STEERING_MODIFIER.saturation),

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
@ -125,6 +125,7 @@ private:
u16 m_button_state = UINT16_C(0xFFFF);
TransferState m_transfer_state = TransferState::Idle;
bool m_disable_socd = false;
AxisModifier m_steering_modifier = DEFAULT_STEERING_MODIFIER;
std::array<AxisModifier, 3> m_half_axis_modifiers = {

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "negcon_rumble.h"
@ -62,12 +62,9 @@ void NeGconRumble::Reset()
m_status_byte = 0x5A;
if (m_force_analog_on_reset)
{
// NOTE: Should be NeGconRumble, but we don't want to break it for games that haven't opted in.
if (CanStartInAnalogMode(ControllerType::AnalogController))
SetAnalogMode(true, false);
}
// NOTE: Should be NeGconRumble, but we don't want to break it for games that haven't opted in.
if (CanStartInAnalogMode(ControllerType::AnalogController))
SetAnalogMode(true, false);
}
bool NeGconRumble::DoState(StateWrapper& sw, bool apply_input_state)
@ -293,11 +290,6 @@ float NeGconRumble::GetMotorStrength(u32 motor) const
return (state != 0) ? static_cast<float>(strength / 65535.0) : 0.0f;
}
u8 NeGconRumble::GetExtraButtonMaskLSB() const
{
return 0xFF;
}
void NeGconRumble::ResetRumbleConfig()
{
m_rumble_config.fill(0xFF);
@ -446,7 +438,8 @@ bool NeGconRumble::Transfer(const u8 data_in, u8* data_out)
{
case 2:
{
m_tx_buffer[m_command_step] = Truncate8(m_button_state) & GetExtraButtonMaskLSB();
m_tx_buffer[m_command_step] =
Truncate8(m_disable_socd ? ControllerHelpers::RemoveOpposingDirections(m_button_state) : m_button_state);
if (m_dualshock_enabled)
SetMotorStateForConfigIndex(rumble_index, data_in);
@ -525,7 +518,8 @@ bool NeGconRumble::Transfer(const u8 data_in, u8* data_out)
{
case 2:
{
m_tx_buffer[m_command_step] = Truncate8(m_button_state) & GetExtraButtonMaskLSB();
m_tx_buffer[m_command_step] =
Truncate8(m_disable_socd ? ControllerHelpers::RemoveOpposingDirections(m_button_state) : m_button_state);
}
break;
@ -752,7 +746,11 @@ constinit const Controller::ControllerBindingInfo NeGconRumble::s_binding_info[]
#undef BUTTON
};
static const SettingInfo s_settings[] = {
static constexpr SettingInfo s_settings[] = {
{SettingInfo::Type::Boolean, "DisableSOCD",
TRANSLATE_NOOP("NeGconRumble", "Disable Simultaneous Opposing Cardinal Directions"),
TRANSLATE_NOOP("NeGconRumble", "Prevents concurrent left/right or up/down inputs from being presented to the game."),
"false", nullptr, nullptr, nullptr, nullptr, nullptr, 0.0f},
{SettingInfo::Type::Float, "SteeringDeadzone", TRANSLATE_NOOP("NeGconRumble", "Steering Axis Deadzone"),
TRANSLATE_NOOP("NeGconRumble", "Sets deadzone size for steering axis."), "0", "0", "0.99", "0.01", "%.0f%%", nullptr,
100.0f},
@ -781,9 +779,9 @@ const Controller::ControllerInfo NeGconRumble::INFO = {ControllerType::NeGconRum
void NeGconRumble::LoadSettings(const SettingsInterface& si, const char* section, bool initial)
{
Controller::LoadSettings(si, section, initial);
m_steering_deadzone = si.GetFloatValue(section, "SteeringDeadzone", 0.10f);
m_steering_sensitivity = si.GetFloatValue(section, "SteeringSensitivity", 1.00f);
m_disable_socd = si.GetBoolValue(section, "DisableSOCD", false);
m_vibration_bias[0] = static_cast<s16>(
std::clamp(si.GetIntValue(section, "LargeMotorVibrationBias", DEFAULT_LARGE_MOTOR_VIBRATION_BIAS), -255, 255));
m_vibration_bias[1] = static_cast<s16>(

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
@ -97,7 +97,7 @@ private:
static const Controller::ControllerBindingInfo s_binding_info[];
std::array<s16, NUM_MOTORS> m_vibration_bias{DEFAULT_LARGE_MOTOR_VIBRATION_BIAS, DEFAULT_SMALL_MOTOR_VIBRATION_BIAS};
bool m_force_analog_on_reset = true;
bool m_disable_socd = false;
bool m_analog_mode = false;
bool m_analog_locked = false;
@ -146,7 +146,6 @@ private:
void ProcessAnalogModeToggle();
void SetMotorState(u32 motor, u8 value);
float GetMotorStrength(u32 motor) const;
u8 GetExtraButtonMaskLSB() const;
void ResetRumbleConfig();
void SetMotorStateForConfigIndex(int index, u8 value);

Loading…
Cancel
Save