diff --git a/Makefile b/Makefile index 2168b7d..ff229bc 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ CFLAGS := -Wall -Wextra -O2 -ffunction-sections\ CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -CXXFLAGS := $(CFLAGS) -std=c++20 +CXXFLAGS := $(CFLAGS) -std=c++23 ASFLAGS := -g $(ARCH) LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) $(WUMSSPECS) diff --git a/source/ButtonComboInfo.h b/source/ButtonComboInfo.h index 192e9a9..5428861 100644 --- a/source/ButtonComboInfo.h +++ b/source/ButtonComboInfo.h @@ -17,7 +17,8 @@ class ButtonComboInfoIF { void *context, bool observer); virtual ~ButtonComboInfoIF(); - virtual void UpdateInput(ButtonComboModule_ControllerTypes controller, std::span pressedButtons) = 0; + // Note: return the index of the sample that activated the combo, or -1. + virtual int UpdateInput(ButtonComboModule_ControllerTypes controller, std::span pressedButtons) = 0; [[nodiscard]] bool isObserver() const; diff --git a/source/ButtonComboInfoDown.cpp b/source/ButtonComboInfoDown.cpp index 275b6e2..53cdf4f 100644 --- a/source/ButtonComboInfoDown.cpp +++ b/source/ButtonComboInfoDown.cpp @@ -1,6 +1,7 @@ #include "ButtonComboInfoDown.h" #include "logger.h" +#include ButtonComboInfoDown::ButtonComboInfoDown( std::string label, @@ -16,23 +17,24 @@ ButtonComboInfoDown::~ButtonComboInfoDown() { DEBUG_FUNCTION_LINE_INFO("Deleted ButtonComboInfoDown: \"%s\", combo: %08X, controllerMask: %08X. Observer %d", mLabel.c_str(), mCombo, mControllerMask, mIsObserver); } -void ButtonComboInfoDown::UpdateInput( +int ButtonComboInfoDown::UpdateInput( const ButtonComboModule_ControllerTypes controller, const std::span pressedButtons) { if ((mControllerMask & controller) == 0) { - return; + return -1; } const auto chanIndex = ControllerTypeToChanIndex(controller); if (chanIndex < 0 || static_cast(chanIndex) >= std::size(mHoldInformation)) { DEBUG_FUNCTION_LINE_WARN("ChanIndex is out of bounds %d", chanIndex); - return; + return -1; } auto &[prevButtonCombo] = mHoldInformation[chanIndex]; DEBUG_FUNCTION_LINE_VERBOSE("[PRESS DOWN] Check button combo %08X on controller %08X (lastItem im pressedButtons (size %d) is %08X) for %s [%08X]", mCombo, controller, pressedButtons.size(), pressedButtons.back(), mLabel.c_str(), getHandle().handle); - for (const auto &pressedButton : pressedButtons) { + int activatedIndex = -1; + for (auto [index, pressedButton] : std::views::enumerate(pressedButtons)) { const bool prevButtonsIncludedCombo = (prevButtonCombo & mCombo) == mCombo; // Make sure the combo can't be triggered on releasing const bool buttonsPressedChanged = prevButtonCombo != pressedButton; // Avoid "holding" the combo const bool buttonsPressedMatchCombo = pressedButton == mCombo; // detect the actual combo @@ -41,12 +43,14 @@ void ButtonComboInfoDown::UpdateInput( if (mCallback != nullptr) { DEBUG_FUNCTION_LINE("Calling callback [%08X](controller: %08X, context: %08X) for \"%s\" [handle: %08X], pressed down %08X", mCallback, controller, mContext, mLabel.c_str(), getHandle().handle, mCombo); mCallback(controller, getHandle(), mContext); + activatedIndex = index; } else { DEBUG_FUNCTION_LINE_WARN("Callback was null for combo %08X", getHandle()); } } prevButtonCombo = pressedButton; } + return activatedIndex; } ButtonComboModule_Error ButtonComboInfoDown::setHoldDuration(uint32_t) { diff --git a/source/ButtonComboInfoDown.h b/source/ButtonComboInfoDown.h index b7f2b4e..1a4541b 100644 --- a/source/ButtonComboInfoDown.h +++ b/source/ButtonComboInfoDown.h @@ -23,7 +23,7 @@ class ButtonComboInfoDown final : public ButtonComboInfoIF { uint32_t prevButtonCombo; } HoldInformation; - void UpdateInput(ButtonComboModule_ControllerTypes controller, std::span pressedButtons) override; + int UpdateInput(ButtonComboModule_ControllerTypes controller, std::span pressedButtons) override; ButtonComboModule_Error setHoldDuration(uint32_t uint32) override; @@ -33,4 +33,4 @@ class ButtonComboInfoDown final : public ButtonComboInfoIF { private: HoldInformation mHoldInformation[9] = {}; // one for each controller -}; \ No newline at end of file +}; diff --git a/source/ButtonComboInfoHold.cpp b/source/ButtonComboInfoHold.cpp index 959fdb0..b7513e7 100644 --- a/source/ButtonComboInfoHold.cpp +++ b/source/ButtonComboInfoHold.cpp @@ -22,16 +22,18 @@ ButtonComboInfoHold::~ButtonComboInfoHold() { DEBUG_FUNCTION_LINE_INFO("Deleted ButtonComboInfoHold: \"%s\", combo: %08X, targetDurationInMs: %d ms, controllerMask: %08X", mLabel.c_str(), mCombo, mTargetDurationInMs, mControllerMask); } -void ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes controller, const std::span pressedButtons) { +int ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes controller, const std::span pressedButtons) { if ((mControllerMask & controller) == 0) { - return; + return -1; } const auto chanIndex = ControllerTypeToChanIndex(controller); if (chanIndex < 0 || static_cast(chanIndex) >= std::size(mHoldInformation)) { DEBUG_FUNCTION_LINE_WARN("ChanIndex is out of bounds %d", chanIndex); - return; + return -1; } + int activatedIndex = -1; + auto &holdInformation = mHoldInformation[chanIndex]; const auto latestButtonPress = pressedButtons.back(); @@ -53,7 +55,7 @@ void ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes co if (mCallback != nullptr) { DEBUG_FUNCTION_LINE("Calling callback [%08X](controller: %08X context: %08X) for \"%s\" [handle: %08X], hold %08X for %d ms", mCallback, controller, mContext, mLabel.c_str(), getHandle().handle, mCombo, intervalInMs); mCallback(controller, getHandle(), mContext); - + activatedIndex = pressedButtons.size() - 1; } else { DEBUG_FUNCTION_LINE_WARN("Callback was null for combo %08X", getHandle()); } @@ -63,6 +65,7 @@ void ButtonComboInfoHold::UpdateInput(const ButtonComboModule_ControllerTypes co holdInformation.callbackTriggered = false; holdInformation.holdStartedAt = 0; } + return activatedIndex; } ButtonComboModule_Error ButtonComboInfoHold::setHoldDuration(const uint32_t holdDurationInMs) { diff --git a/source/ButtonComboInfoHold.h b/source/ButtonComboInfoHold.h index 3c7bdbb..ad843eb 100644 --- a/source/ButtonComboInfoHold.h +++ b/source/ButtonComboInfoHold.h @@ -25,7 +25,7 @@ class ButtonComboInfoHold final : public ButtonComboInfoIF { ~ButtonComboInfoHold() override; private: - void UpdateInput(ButtonComboModule_ControllerTypes controller, std::span pressedButtons) override; + int UpdateInput(ButtonComboModule_ControllerTypes controller, std::span pressedButtons) override; ButtonComboModule_Error setHoldDuration(uint32_t holdDurationInMs) override; diff --git a/source/ButtonComboManager.cpp b/source/ButtonComboManager.cpp index e451e71..264e6eb 100644 --- a/source/ButtonComboManager.cpp +++ b/source/ButtonComboManager.cpp @@ -398,7 +398,7 @@ ButtonComboModule_Error ButtonComboManager::GetButtonComboStatus(const ButtonCom return BUTTON_COMBO_MODULE_ERROR_SUCCESS; } -void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, const VPADStatus *buffer, const uint32_t bufferSize, const VPADReadError *error) { +void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, VPADStatus *buffer, const uint32_t bufferSize, const VPADReadError *error) { if (chan < VPAD_CHAN_0 || chan > VPAD_CHAN_1) { DEBUG_FUNCTION_LINE_ERR("Invalid VPADChan"); return; @@ -435,9 +435,23 @@ void ButtonComboManager::UpdateInputVPAD(const VPADChan chan, const VPADStatus * if (combo->getStatus() != BUTTON_COMBO_MODULE_COMBO_STATUS_VALID) { continue; } - combo->UpdateInput(controller, std::span(mVPADButtonBuffer.data(), usedBufferSize)); + int activated = combo->UpdateInput(controller, std::span(mVPADButtonBuffer.data(), usedBufferSize)); + if (activated >= 0 && !combo->isObserver()) { + // suppress all buttons triggered + uint32_t triggered = buffer[usedBufferSize - activated - 1].trigger; + mVPADSuppressed[chan] |= triggered; + } } } + // hide all suppressed buttons from the game, iterate from oldest sample to newest + for (uint32_t i = bufferSize - 1; i + 1 > 0; --i) { + // released buttons stop being suppressed + mVPADSuppressed[chan] &= ~buffer[i].release; + // hide the suppressed buttons + buffer[i].trigger &= ~mVPADSuppressed[chan]; + buffer[i].hold &= ~mVPADSuppressed[chan]; + buffer[i].release &= ~mVPADSuppressed[chan]; + } } void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data) { @@ -450,42 +464,99 @@ void ButtonComboManager::UpdateInputWPAD(const WPADChan chan, WPADStatus *data) return; } - // Do not check for combos while the combo detection is active - if (mInButtonComboDetection) { - return; - } + auto &coreBtns = mWPADCoreBtns[chan]; + auto &extBtns = mWPADExtBtns[chan]; - const auto controller = convert(chan); - uint32_t pressedButtons = {}; + if (mWPADExtension[chan] != data->extensionType) { + mWPADExtension[chan] = data->extensionType; + extBtns.reset(); + } switch (data->extensionType) { case WPAD_EXT_CORE: case WPAD_EXT_NUNCHUK: case WPAD_EXT_MPLUS: - case WPAD_EXT_MPLUS_NUNCHUK: { - pressedButtons = remapWiiMoteButtons(data->buttons); + case WPAD_EXT_MPLUS_NUNCHUK: + coreBtns.update(data->buttons); + extBtns.reset(); break; - } case WPAD_EXT_CLASSIC: case WPAD_EXT_MPLUS_CLASSIC: { - const auto classic = reinterpret_cast(data); - pressedButtons = remapClassicButtons(classic->buttons); + auto cdata = reinterpret_cast(data); + coreBtns.update(cdata->core.buttons); + extBtns.update(cdata->buttons); break; } case WPAD_EXT_PRO_CONTROLLER: { - const auto proController = reinterpret_cast(data); - pressedButtons = remapProButtons(proController->buttons); + auto pdata = reinterpret_cast(data); + coreBtns.reset(); + extBtns.update(pdata->buttons); break; } - default: + default: // early out when we don't know how to handle extension return; } - { - std::lock_guard lock(mMutex); - for (const auto &combo : mCombos) { - if (combo->getStatus() != BUTTON_COMBO_MODULE_COMBO_STATUS_VALID) { - continue; + + // Do not check for combos while the combo detection is active + if (!mInButtonComboDetection) { + const auto controller = convert(chan); + uint32_t pressedButtons = {}; + switch (data->extensionType) { + case WPAD_EXT_CORE: + case WPAD_EXT_NUNCHUK: + case WPAD_EXT_MPLUS: + case WPAD_EXT_MPLUS_NUNCHUK: { + pressedButtons = remapWiiMoteButtons(data->buttons); + break; + } + case WPAD_EXT_CLASSIC: + case WPAD_EXT_MPLUS_CLASSIC: { + const auto classic = reinterpret_cast(data); + pressedButtons = remapClassicButtons(classic->buttons); + break; } - combo->UpdateInput(controller, std::span(&pressedButtons, 1)); + case WPAD_EXT_PRO_CONTROLLER: { + const auto proController = reinterpret_cast(data); + pressedButtons = remapProButtons(proController->buttons); + break; + } + } + { + std::lock_guard lock(mMutex); + for (const auto &combo : mCombos) { + if (combo->getStatus() != BUTTON_COMBO_MODULE_COMBO_STATUS_VALID) { + continue; + } + int activated = combo->UpdateInput(controller, std::span(&pressedButtons, 1)); + if (activated >= 0 && !combo->isObserver()) { + coreBtns.blockTriggered(); + extBtns.blockTriggered(); + } + } + } + } + + coreBtns.unblockReleased(); + extBtns.unblockReleased(); + + // modify data, to hide all suppressed buttons from the game + switch (data->extensionType) { + case WPAD_EXT_CORE: + case WPAD_EXT_NUNCHUK: + case WPAD_EXT_MPLUS: + case WPAD_EXT_MPLUS_NUNCHUK: + coreBtns.suppressButtons(data->buttons); + break; + case WPAD_EXT_CLASSIC: + case WPAD_EXT_MPLUS_CLASSIC: { + auto cdata = reinterpret_cast(data); + coreBtns.suppressButtons(cdata->core.buttons); + extBtns.suppressButtons(cdata->buttons); + break; + } + case WPAD_EXT_PRO_CONTROLLER: { + auto pdata = reinterpret_cast(data); + extBtns.suppressButtons(pdata->buttons); + break; } } } diff --git a/source/ButtonComboManager.h b/source/ButtonComboManager.h index afead3b..9fae9b7 100644 --- a/source/ButtonComboManager.h +++ b/source/ButtonComboManager.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include +#include "ButtonTracker.h" class ButtonComboManager { public: @@ -21,7 +23,7 @@ class ButtonComboManager { [[nodiscard]] ButtonComboInfoIF *GetComboInfoForHandle(ButtonComboModule_ComboHandle handle) const; - void UpdateInputVPAD(VPADChan chan, const VPADStatus *buffer, uint32_t bufferSize, const VPADReadError *error); + void UpdateInputVPAD(VPADChan chan, VPADStatus *buffer, uint32_t bufferSize, const VPADReadError *error); void UpdateInputWPAD(WPADChan chan, WPADStatus *data); @@ -61,4 +63,9 @@ class ButtonComboManager { std::mutex mMutex; std::mutex mDetectButtonsMutex; bool mInButtonComboDetection = false; -}; \ No newline at end of file + + std::array mVPADSuppressed{}; + std::array, 7> mWPADCoreBtns; + std::array, 7> mWPADExtBtns; + std::array mWPADExtension{}; +}; diff --git a/source/ButtonTracker.h b/source/ButtonTracker.h new file mode 100644 index 0000000..65971ec --- /dev/null +++ b/source/ButtonTracker.h @@ -0,0 +1,39 @@ +#pragma once + +// Simple class to track buttons triggered and released; also perform suppression logic. + +template +struct ButtonTracker { + void reset() noexcept { + hold = 0; + trigger = 0; + release = 0; + suppress = 0; + } + + void update(T buttons) noexcept { + T changed = buttons ^ hold; + hold = buttons; + trigger = changed & buttons; + release = changed & ~buttons; + } + + void blockTriggered() noexcept { + suppress |= trigger; + } + + void unblockReleased() noexcept { + suppress &= ~release; + } + + template + void suppressButtons(U &buttons) { + buttons &= ~suppress; + } + +private: + T hold = 0; + T trigger = 0; + T release = 0; + T suppress = 0; +};