Skip to content

Commit

Permalink
DDI cleanups and bugfixes (#909)
Browse files Browse the repository at this point in the history
* make a proper protobuf enum for DDI combination mode

* remove unused DDI variables from removed debouncer

* do some tab vs. space cleanups for readability

* remove support for Gamepad and Dual Directional DDI combination modes

Gamepad is buggy, and both modes don't seem to have use cases. we are
soft deprecating these for the moment, only making them work like Mixed,
rather than actually migrating them to a new mode, for now

* move first/last wins SOCD cleaning out of DDI preprocess

I think this is just a vestige of how things used to work, it makes no
discernable difference in my testing.

in order to start showing my work, I also added a simple piece of
documentation to describe how the modes work

* simplify the DDI process block; documented behavior remains

* reimplement DDI override mode

this replaces the gamepad output with the DDI output (no combination or
re-cleaning), which is interesting for situations like gamepad holding
one axis and DDI pressing the other, letting you essentially toggle
between the two axis with a press/release

Fixes #823

* break out Neutral and Up Prio results for the DDI override docs

* dual mode is supported again, name it properly in protobuf

* reimplement Gamepad Override DDI mode

since DDI Override was pretty easy to bring back with the new code, we
might as well just bring Gamepad Override back since it's the same
logic, just flipped. added more docs while I was at it.

* tiny tabs/spaces fix

* revert the default to Mixed like it used to be
  • Loading branch information
bsstephan committed Apr 1, 2024
1 parent 2f14b1d commit dea263d
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 102 deletions.
119 changes: 119 additions & 0 deletions docs/ddi-socd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Dual Directional Input + SOCD Modes

Documenting results for future reference. Below examples are holding the buttons sequentially, unless otherwise noted by
"xx", which means the button is released.

## Basic SOCD Example

I only documented Left/Right tests here, because Up/Down either follows the same behavior (Neutral/First/Last) on the
other axis, or it has its own far more explicit Up priority that isn't worth distinguishing in this doc.

| # | Input | Up Priority/Neutral | First Input Wins | Last Input Wins |
| - | --------------- | ------------------- | ---------------- | --------------- |
| 1 | Core Left | Left | Left | Left |
| 2 | Core Right | Neutral | Left | Right |
| 3 | xx Core Left | Right | Right | Right |
| 4 | Core Left | Neutral | Right | Left |
| 5 | xx Core Right | Left | Left | Left |

## Combination Mode Mixed

All Combination Mode Mixed examples apply equally to Combination Mode None when the DDI output is the same LS/DP/RS
option as the core gamepad's.

I only documented Left/Right tests here, because Up/Down either follows the same behavior (Neutral/First/Last) on the
other axis, or it has its own far more explicit Up priority that isn't worth distinguishing in this doc.

This mode seems to be useful for having two sets of inputs contribute to any one of LS/DP/RS at the same time, but is
distinguished from two sets of core gamepad mappings by the fact that this allows for two layers of SOCD cleaning to be
applied, essentially `SOCD( SOCD(gamepad) | SOCD(DDI) )` which can allow for some interesting inputs.

| # | Input | Up Priority/Neutral | First Input Wins | Last Input Wins |
| - | --------------- | ------------------- | ---------------- | --------------- |
| 1 | Core Left | Left | Left | Left |
| 2 | DDI Right | Neutral | Left | Right |
| 3 | DDI Left | Left | Left | Left |
| 4 | xx DDI Left | Neutral | Left | Right |
| 5 | Core Right | Right | Left | Right |
| 6 | DDI Left | Neutral | Left | Left |

| # | Input | Up Priority/Neutral | First Input Wins | Last Input Wins |
| - | --------------- | ------------------- | ---------------- | --------------- |
| 1 | Core Left | Left | Left | Left |
| 2 | Core Right | Neutral | Left | Right |
| 3 | DDI Left | Left | Left | Left |
| 4 | xx DDI Left | Neutral | Left | Right |
| 5 | DDI Left | Left | Left | Left |
| 6 | DDI Right | Neutral | Left | Right |

| # | Input | Up Priority/Neutral | First Input Wins | Last Input Wins |
| - | --------------- | ------------------- | ---------------- | --------------- |
| 1 | Core Right | Right | Right | Right |
| 2 | Core Left | Neutral | Right | Left |
| 3 | DDI Right | Right | Right | Right |
| 4 | xx DDI Right | Neutral | Right | Left |
| 5 | DDI Right | Right | Right | Right |
| 6 | DDI Left | Neutral | Right | Left |

| # | Input | Up Priority/Neutral | First Input Wins | Last Input Wins |
| - | --------------- | ------------------- | ---------------- | --------------- |
| 1 | DDI Left | Left | Left | Left |
| 2 | DDI Right | Neutral | Left | Right |
| 3 | Core Left | Left | Left | Left |
| 4 | xx Core Left | Neutral | Left | Right |
| 5 | Core Left | Left | Left | Left |
| 6 | Core Right | Neutral | Left | Right |

## Combination Mode Gamepad Override

This ignores the DDI output when the core gamepad has any output. SOCD history is maintained. Cross-axis
behavior is interesting here, allowing some down-to-left/right tricks.

This makes Tekken Shadow Step easy, apparently.

| # | Input | Neutral | Up Prio | First Input Wins | Last Input Wins |
| - | --------------- | --------- | --------- | ---------------- | --------------- |
| 1 | DDI Down | Down | Down | Down | Down |
| 2 | Core Left | Left | Left | Left | Left |
| 3 | Core Down | Down Left | Down Left | Down Left | Down Left |
| 4 | xx DDI Down | Down Left | Down Left | Down Left | Down Left |
| 5 | xx Core Down | Left | Left | Left | Left |
| 6 | xx Core Left | Neutral | Neutral | Neutral | Neutral |
| 7 | DDI Down | Down | Down | Down | Down |
| 8 | DDI Left | Down Left | Down Left | Down Left | Down Left |
| 9 | Core Up | Up | Up | Up | Up |

| # | Input | Neutral | Up Prio | First Input Wins | Last Input Wins |
| - | --------------- | ------- | --------- | ---------------- | --------------- |
| 1 | Core Left | Left | Left | Left | Left |
| 2 | DDI Down | Left | Left | Left | Left |
| 3 | DDI Up | Left | Left | Left | Left |
| 4 | xx DDI Up | Left | Left | Left | Left |
| 5 | xx DDI Down | Left | Left | Left | Left |
| 6 | Core Right | Neutral | Neutral | Left | Right |

| # | Input | Neutral | Up Prio | First Input Wins | Last Input Wins |
| - | --------------- | ------- | --------- | ---------------- | --------------- |
| 1 | DDI Left | Left | Left | Left | Left |
| 2 | DDI Right | Neutral | Neutral | Left | Right |
| 3 | Core Down | Down | Down | Down | Down |
| 4 | Core Up | Neutral | Up | Down | Up |
| 5 | xx Core Up | Down | Down | Down | Down |
| 6 | xx Core Down | Neutral | Neutral | Left | Right |
| 7 | xx DDI Right | Left | Left | Left | Left |

## Combination Mode DDI Override

This replaces whatever the gamepad has for output with a non-zero DDI output. SOCD history is maintained. Cross-axis
behavior is interesting here, allowing some down-to-left/right tricks. (It's the same idea as Gamepad Override, just
swapping which method overrides which.)

| # | Input | Neutral | Up Prio | First Input Wins | Last Input Wins |
| - | --------------- | ------- | --------- | ---------------- | --------------- |
| 1 | Core Left | Left | Left | Left | Left |
| 2 | Core Right | Neutral | Neutral | Left | Right |
| 3 | DDI Down | Down | Down | Down | Down |
| 4 | DDI Up | Neutral | Up | Down | Up |
| 5 | xx DDI Up | Down | Down | Down | Down |
| 6 | xx DDI Down | Neutral | Neutral | Left | Right |
| 7 | xx Core Right | Left | Left | Left | Left |
22 changes: 3 additions & 19 deletions headers/addons/dualdirectional.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@

#include "GamepadEnums.h"

// The available combinational methods
enum DualDirectionalCombinationMode
{
DUAL_COMBINE_MODE_MIXED = 0,
DUAL_COMBINE_MODE_GAMEPAD,
DUAL_COMBINE_MODE_DUAL,
DUAL_COMBINE_MODE_NONE
};

#ifndef DUAL_DIRECTIONAL_ENABLED
#define DUAL_DIRECTIONAL_ENABLED 0
#endif
Expand All @@ -22,18 +13,14 @@ enum DualDirectionalCombinationMode
#define DUAL_DIRECTIONAL_STICK_MODE DPAD_MODE_DIGITAL
#endif

#ifndef DUAL_DIRECTIONAL_COMBINE_MODE
#define DUAL_DIRECTIONAL_COMBINE_MODE DUAL_COMBINE_MODE_MIXED
#endif

// Dual Directional Module Name
#define DualDirectionalName "DualDirectional"

class DualDirectionalInput : public GPAddon {
public:
virtual bool available();
virtual void setup(); // Dual Directional Setup
virtual void process(); // Dual Directional Process
virtual void setup(); // Dual Directional Setup
virtual void process(); // Dual Directional Process
virtual void reinit();
virtual void preprocess(); // Dual Directional Pre-Process (Cheat)
virtual std::string name() { return DualDirectionalName; }
Expand All @@ -46,14 +33,11 @@ class DualDirectionalInput : public GPAddon {
uint8_t SOCDGamepadClean(uint8_t, bool isLastWin);
void OverrideGamepad(Gamepad *, DpadMode, uint8_t);
const SOCDMode getSOCDMode(const GamepadOptions&);
uint8_t dDebState; // Debounce State (stored)
uint8_t dualState; // Dual Directional State
DpadDirection lastGPUD; // Gamepad Last Up-Down
DpadDirection lastGPLR; // Gamepad Last Left-Right
DpadDirection lastGPLR; // Gamepad Last Left-Right
DpadDirection lastDualUD; // Dual Last Up-Down
DpadDirection lastDualLR; // Gamepad Last Left-Right
uint32_t dpadTime[4];
uint8_t combineMode;
DpadMode dpadMode;
GamepadButtonMapping *mapDpadUp;
GamepadButtonMapping *mapDpadDown;
Expand Down
2 changes: 1 addition & 1 deletion proto/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ message DualDirectionalOptions
optional int32 deprecatedRightPin = 5 [deprecated = true];

optional DpadMode dpadMode = 6;
optional uint32 combineMode = 7;
optional DualDirectionalCombinationMode combineMode = 7;
optional bool fourWayMode = 8;
}

Expand Down
10 changes: 10 additions & 0 deletions proto/enums.proto
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,16 @@ enum ForcedSetupMode
FORCED_SETUP_MODE_LOCK_BOTH = 3;
};

enum DualDirectionalCombinationMode
{
option (nanopb_enumopt).long_names = false;

MIXED_MODE = 0;
GAMEPAD_MODE = 1;
DUAL_MODE = 2;
NONE_MODE = 3;
}

enum PS4ControllerType
{
option (nanopb_enumopt).long_names = false;
Expand Down
110 changes: 32 additions & 78 deletions src/addons/dualdirectional.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ bool DualDirectionalInput::available() {

void DualDirectionalInput::setup() {
const DualDirectionalOptions& options = Storage::getInstance().getAddonOptions().dualDirectionalOptions;
combineMode = options.combineMode;
dpadMode = options.dpadMode;

mapDpadUp = new GamepadButtonMapping(GAMEPAD_MASK_UP);
Expand All @@ -30,19 +29,13 @@ void DualDirectionalInput::setup() {
}
}

dDebState = 0;
dualState = 0;

lastGPUD = DIRECTION_NONE;
lastGPLR = DIRECTION_NONE;
lastGPLR = DIRECTION_NONE;

lastDualUD = DIRECTION_NONE;
lastDualLR = DIRECTION_NONE;

uint32_t now = getMillis();
for(int i = 0; i < 4; i++) {
dpadTime[i] = now;
}
}

/**
Expand Down Expand Up @@ -117,44 +110,15 @@ void DualDirectionalInput::preprocess()
| ((values & mapDpadLeft->pinMask) ? mapDpadLeft->buttonMask : 0)
| ((values & mapDpadRight->pinMask) ? mapDpadRight->buttonMask : 0);

// Convert gamepad from process() output to uint8 value
uint8_t gamepadState = gamepad->state.dpad;
const SOCDMode socdMode = getSOCDMode(gamepad->getOptions());

// 4-way before SOCD, might have better history without losing any coherent functionality
if (options.fourWayMode) {
dualState = filterToFourWayModeDDI(dualState);
}

// Combined Mode
if ( combineMode == DUAL_COMBINE_MODE_MIXED ) {
SOCDDualClean(socdMode); // Clean up Dual SOCD based on the mode

// Second Input (Last Input Priority) needs to happen before we MPG clean
if ( socdMode == SOCD_MODE_SECOND_INPUT_PRIORITY ||
socdMode == SOCD_MODE_FIRST_INPUT_PRIORITY ) {
gamepadState = SOCDGamepadClean(gamepadState, socdMode == SOCD_MODE_SECOND_INPUT_PRIORITY) | dualState;
}
}
// None Mode (no combination, no overwrite)
else if ( combineMode == DUAL_COMBINE_MODE_NONE ) {
// just SOCD clean the dual inputs based on the desired mode
SOCDDualClean(socdMode);
}
// Gamepad Overwrite Mode
else if ( combineMode == DUAL_COMBINE_MODE_GAMEPAD ) {
if ( gamepadState != 0 && (gamepadState != dualState)) {
dualState = gamepadState;
}
}
// Dual Overwrite Mode
else if ( combineMode == DUAL_COMBINE_MODE_DUAL ) {
if ( dualState != 0 && gamepadState != dualState) {
gamepadState = dualState;
}
}

gamepad->state.dpad = gamepadState;
// SOCD clean the dual inputs based on the mode in the gamepad config
SOCDDualClean(socdMode);
}

void DualDirectionalInput::process()
Expand All @@ -163,46 +127,36 @@ void DualDirectionalInput::process()
Gamepad * gamepad = Storage::getInstance().GetGamepad();
uint8_t dualOut = dualState;
const SOCDMode socdMode = getSOCDMode(gamepad->getOptions());

// If we're in mixed mode
if (combineMode == DUAL_COMBINE_MODE_MIXED) {
uint8_t gamepadDpad = gpadToBinary(gamepad->getOptions().dpadMode, gamepad->state);
// Up-Win or Neutral Modify AFTER SOCD(gamepad), Last-Win Modifies BEFORE SOCD(gamepad)
if ( socdMode == SOCD_MODE_UP_PRIORITY ||
socdMode == SOCD_MODE_NEUTRAL ) {

// Up-Win or Neutral: SOCD(gamepad) *already done* | SOCD(dual) *done in preprocess()*
uint8_t gamepadDpad = gpadToBinary(gamepad->getOptions().dpadMode, gamepad->state);

// in mixed mode, we need to combine/re-clean the gamepad and DDI outputs to create a coherent behavior
// reminder that combination mode none with the DDI output set to the same thing as the gamepad
// output is, in practice, the same behavior as mixed mode, so it also is addressed here
if (options.combineMode == DualDirectionalCombinationMode::MIXED_MODE ||
(options.combineMode == DualDirectionalCombinationMode::NONE_MODE &&
gamepad->getOptions().dpadMode == options.dpadMode)) {
if ( socdMode == SOCD_MODE_UP_PRIORITY || socdMode == SOCD_MODE_NEUTRAL ) {
// neutral/up priority SOCD cleaning are pretty simple, they just need to be re-neutralized
dualOut = SOCDCombine(socdMode, gamepadDpad);

// Modify Gamepad if we're in mixed Up-Win or Neutral and dual != gamepad
if ( dualOut != gamepadDpad ) {
OverrideGamepad(gamepad, gamepad->getOptions().dpadMode, dualOut);
}
} else if (socdMode == SOCD_MODE_BYPASS) {
OverrideGamepad(gamepad, gamepad->getOptions().dpadMode, dualOut | gamepad->state.dpad);
} else if ( socdMode != SOCD_MODE_BYPASS ) {
// else if not bypass, what's left is first/last input wins SOCD, which need a complicated re-clean
dualOut = SOCDGamepadClean(dualOut | gamepadDpad, socdMode == SOCD_MODE_SECOND_INPUT_PRIORITY);
} else {
// this is bypass SOCD, just OR them together
dualOut |= gamepadDpad;
}
} else { // We are not mixed mode, don't change dual output
if ( combineMode == DUAL_COMBINE_MODE_GAMEPAD ) {
// Set Dual Directional Output
OverrideGamepad(gamepad, dpadMode, dualOut);
}
else if (combineMode == DUAL_COMBINE_MODE_NONE) {
// Set the configured directional mode to the value of the dual output
// if configured dual mode is the same mode as the gamepad mode, they
// need to be SOCD cleaned first
// this also avoids accidentally masking gamepad inputs with the lack of dual inputs
if (gamepad->getOptions().dpadMode == options.dpadMode) {
uint8_t gamepadDpad = gpadToBinary(gamepad->getOptions().dpadMode, gamepad->state);
if ( socdMode == SOCD_MODE_NEUTRAL ) {
dualOut = SOCDCombine(socdMode, gamepadDpad);
} else if ( socdMode != SOCD_MODE_BYPASS ) {
dualOut = SOCDGamepadClean(dualOut | gamepadDpad, socdMode == SOCD_MODE_SECOND_INPUT_PRIORITY);
} else {
dualOut |= gamepadDpad;
}
}
OverrideGamepad(gamepad, options.dpadMode, dualOut);
OverrideGamepad(gamepad, gamepad->getOptions().dpadMode, dualOut);
} else if (options.combineMode != DualDirectionalCombinationMode::NONE_MODE) {
// this is either of the override modes, which we will treat the same way --- they replace
// the gamepad entirely in certain conditions: DDI Override if it has any data,
// Gamepad Override if gamepad doesn't have any data
if ((options.combineMode == DualDirectionalCombinationMode::DUAL_MODE && dualOut != 0) ||
(options.combineMode == DualDirectionalCombinationMode::GAMEPAD_MODE && gamepadDpad == 0)) {
OverrideGamepad(gamepad, gamepad->getOptions().dpadMode, dualOut);
}
} else {
// the DDI and gamepad outputs don't need to be mixed, so just apply DDI output to the gamepad
OverrideGamepad(gamepad, options.dpadMode, dualOut);
}
}

Expand Down Expand Up @@ -303,13 +257,13 @@ void DualDirectionalInput::SOCDDualClean(SOCDMode socdMode) {
case (GAMEPAD_MASK_UP | GAMEPAD_MASK_DOWN): // If last state was Up or Down, exclude it from our gamepad
if ( socdMode == SOCD_MODE_UP_PRIORITY ) {
dualState ^= GAMEPAD_MASK_DOWN; // Remove Down
lastDualUD = DIRECTION_UP; // We're in UP mode
lastDualUD = DIRECTION_UP; // We're in UP mode
} else if ( socdMode == SOCD_MODE_SECOND_INPUT_PRIORITY && lastDualUD != DIRECTION_NONE ) {
dualState ^= (lastDualUD == DIRECTION_UP) ? GAMEPAD_MASK_UP : GAMEPAD_MASK_DOWN;
} else if ( socdMode == SOCD_MODE_FIRST_INPUT_PRIORITY && lastDualUD != DIRECTION_NONE ) {
dualState ^= (lastDualUD == DIRECTION_UP) ? GAMEPAD_MASK_DOWN : GAMEPAD_MASK_UP;
} else {
dualState ^= (GAMEPAD_MASK_UP | GAMEPAD_MASK_DOWN); // Remove UP and Down in Neutral
dualState ^= (GAMEPAD_MASK_UP | GAMEPAD_MASK_DOWN); // Remove UP and Down in Neutral
lastDualUD = DIRECTION_NONE;
}
break;
Expand Down
3 changes: 2 additions & 1 deletion src/config_legacy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,8 @@ bool ConfigUtils::fromLegacyStorage(Config& config)
{
SET_PROPERTY(dualDirectionalOptions, dpadMode, static_cast<DpadMode>(legacyAddonOptions.dualDirDpadMode));
}
SET_PROPERTY(dualDirectionalOptions, combineMode, legacyAddonOptions.dualDirCombineMode);
SET_PROPERTY(dualDirectionalOptions, combineMode,
static_cast<DualDirectionalCombinationMode>(legacyAddonOptions.dualDirCombineMode));

ExtraButtonOptions& extraButtonOptions = config.addonOptions.deprecatedExtraButtonOptions;
config.addonOptions.has_deprecatedExtraButtonOptions = true;
Expand Down
Loading

0 comments on commit dea263d

Please sign in to comment.