diff --git a/docs/ddi-socd.md b/docs/ddi-socd.md new file mode 100644 index 000000000..0a2beb937 --- /dev/null +++ b/docs/ddi-socd.md @@ -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 | diff --git a/headers/addons/dualdirectional.h b/headers/addons/dualdirectional.h index 801c34d27..a0d889fc0 100644 --- a/headers/addons/dualdirectional.h +++ b/headers/addons/dualdirectional.h @@ -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 @@ -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; } @@ -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; diff --git a/proto/config.proto b/proto/config.proto index 9b2dcaf56..7a24fa17a 100644 --- a/proto/config.proto +++ b/proto/config.proto @@ -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; } diff --git a/proto/enums.proto b/proto/enums.proto index 27b3b6adf..58d758794 100644 --- a/proto/enums.proto +++ b/proto/enums.proto @@ -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; diff --git a/src/addons/dualdirectional.cpp b/src/addons/dualdirectional.cpp index fdc3c4581..3ab3b7240 100644 --- a/src/addons/dualdirectional.cpp +++ b/src/addons/dualdirectional.cpp @@ -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); @@ -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; - } } /** @@ -117,8 +110,6 @@ 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 @@ -126,35 +117,8 @@ void DualDirectionalInput::preprocess() 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() @@ -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); } } @@ -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; diff --git a/src/config_legacy.cpp b/src/config_legacy.cpp index b317577c2..9d69c8842 100644 --- a/src/config_legacy.cpp +++ b/src/config_legacy.cpp @@ -987,7 +987,8 @@ bool ConfigUtils::fromLegacyStorage(Config& config) { SET_PROPERTY(dualDirectionalOptions, dpadMode, static_cast(legacyAddonOptions.dualDirDpadMode)); } - SET_PROPERTY(dualDirectionalOptions, combineMode, legacyAddonOptions.dualDirCombineMode); + SET_PROPERTY(dualDirectionalOptions, combineMode, + static_cast(legacyAddonOptions.dualDirCombineMode)); ExtraButtonOptions& extraButtonOptions = config.addonOptions.deprecatedExtraButtonOptions; config.addonOptions.has_deprecatedExtraButtonOptions = true; diff --git a/src/config_utils.cpp b/src/config_utils.cpp index 1af6eb2f6..68aafd784 100644 --- a/src/config_utils.cpp +++ b/src/config_utils.cpp @@ -597,7 +597,7 @@ void ConfigUtils::initUnsetPropertiesWithDefaults(Config& config) INIT_UNSET_PROPERTY(config.addonOptions.dualDirectionalOptions, deprecatedLeftPin, (Pin_t)-1); INIT_UNSET_PROPERTY(config.addonOptions.dualDirectionalOptions, deprecatedRightPin, (Pin_t)-1); INIT_UNSET_PROPERTY(config.addonOptions.dualDirectionalOptions, dpadMode, static_cast(DUAL_DIRECTIONAL_STICK_MODE)); - INIT_UNSET_PROPERTY(config.addonOptions.dualDirectionalOptions, combineMode, DUAL_DIRECTIONAL_COMBINE_MODE); + INIT_UNSET_PROPERTY(config.addonOptions.dualDirectionalOptions, combineMode, DualDirectionalCombinationMode::MIXED_MODE); INIT_UNSET_PROPERTY(config.addonOptions.dualDirectionalOptions, fourWayMode, false); // addonOptions.tiltOptions diff --git a/www/src/Addons/DualDirection.tsx b/www/src/Addons/DualDirection.tsx index 086d870c3..a2a7a1485 100644 --- a/www/src/Addons/DualDirection.tsx +++ b/www/src/Addons/DualDirection.tsx @@ -11,8 +11,8 @@ import { DUAL_STICK_MODES } from '../Data/Addons'; const DUAL_COMBINE_MODES = [ { label: 'Mixed', value: 0 }, - { label: 'Gamepad', value: 1 }, - { label: 'Dual Directional', value: 2 }, + { label: 'Gamepad Override', value: 1 }, + { label: 'DDI Override', value: 2 }, { label: 'None', value: 3 }, ];