From 71961dae85b232078c7984720bc5c32d0fe68c12 Mon Sep 17 00:00:00 2001 From: Ash Date: Thu, 28 May 2026 08:04:23 +0200 Subject: [PATCH] feat: Make gamepad status lighting optional (#1705) * feat: Status Lighting option * wip: rename to "Enable Controller LED" * wip: setting for all four ports individually * wip: fix detection + code cleanup * wip: increase led speed for dusk preset * wip: use aurora API * bump aurora to 2b07d55 * wip: remove isSupported + fix namespace comment * wip: add value override to BoolButton * Style nit & remove unused * Undo change --------- Co-authored-by: Luke Street --- include/dusk/gamepad_color.h | 5 +- include/dusk/settings.h | 1 + src/dusk/gamepad_color.cpp | 178 +++++++++++++++++------------- src/dusk/settings.cpp | 10 ++ src/dusk/ui/bool_button.cpp | 9 +- src/dusk/ui/bool_button.hpp | 2 + src/dusk/ui/controller_config.cpp | 28 ++++- src/f_ap/f_ap_game.cpp | 2 +- 8 files changed, 153 insertions(+), 82 deletions(-) diff --git a/include/dusk/gamepad_color.h b/include/dusk/gamepad_color.h index c7cfc1e716..0524b0b98d 100644 --- a/include/dusk/gamepad_color.h +++ b/include/dusk/gamepad_color.h @@ -3,6 +3,9 @@ #ifndef GAMEPAD_COLOR_H #define GAMEPAD_COLOR_H -void handleGamepadColor(); +namespace dusk::input { + void handleGamepadColor(); + bool pad_has_led(int port) noexcept; +} #endif \ No newline at end of file diff --git a/include/dusk/settings.h b/include/dusk/settings.h index de6637080b..f48f5862ca 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -209,6 +209,7 @@ struct UserSettings { ConfigVar debugFlyCam; ConfigVar debugFlyCamLockEvents; ConfigVar allowBackgroundInput; + std::array, 4> enableLED; ConfigVar swapDirectSelect; // Cheats diff --git a/src/dusk/gamepad_color.cpp b/src/dusk/gamepad_color.cpp index cba32da632..9fea8fd769 100644 --- a/src/dusk/gamepad_color.cpp +++ b/src/dusk/gamepad_color.cpp @@ -5,102 +5,124 @@ #include #include -cXyz currentGamepadColor = {0, 0, 0}; -cXyz finalGamepadColor = {0, 0, 0}; -cXyz additionalGamepadColor = {0, 0, 0}; +#include "ui/controller_config.hpp" -float lerpSpeed = 0.0f; +#include -const cXyz duskColor = {50, 50, -50}; -const cXyz noColor = {0, 0, 0}; +namespace dusk::input { -cXyz LerpColor(cXyz a, cXyz b, float t) { - return {std::lerp(a.x, b.x, t), std::lerp(a.y, b.y, t), std::lerp(a.z, b.z, t)}; -} +namespace { + cXyz currentColor = {0, 0, 0}; + float lerpSpeed = 0.0f; -void FadeLED(cXyz newColor, float speed) { - finalGamepadColor = newColor; - lerpSpeed = speed / 30.0f; -} + enum ColorVariations : u8 { + NO_COLOR = 0, + DUSK_COLOR = 1, + ZHINT = 2, + LINK_WOLF = 3, + LINK_CASUAL = 4, + LINK_KOKIRI = 5, + LINK_ZORA = 6, + LINK_MAGIC = 7, + LINK_MAGIC_HEAVY = 8, + }; -void SetLED(cXyz newColor) { - currentGamepadColor = newColor; - finalGamepadColor = newColor; -} + struct ColorSetting { + cXyz color; + float speed; + }; -void SetGamepadAdditionalColor(cXyz addColor) { - additionalGamepadColor.x = addColor.x; - additionalGamepadColor.y = addColor.y; - additionalGamepadColor.z = addColor.z; -} + const ColorSetting kColorTable[] = { + /* 0: NO_COLOR */ { {0, 0, 0}, 2.0f }, + /* 1: DUSK_COLOR */ { {50, 50, -50}, 2.0f }, + /* 2: ZHINT */ { {50, 50, 175}, 2.0f }, + /* 3: LINK_WOLF */ { {115, 115, 75}, 5.0f }, + /* 4: LINK_CASUAL */ { {235, 230, 115}, 5.0f }, + /* 5: LINK_KOKIRI */ { {0, 100, 0}, 5.0f }, + /* 6: LINK_ZORA */ { {0, 0, 100}, 5.0f }, + /* 7: LINK_MAGIC */ { {100, 0, 5}, 5.0f }, + /* 8: LINK_MAGIC_HEAVY */ { {5, 100, 100}, 5.0f }, + }; -void handleGamepadColor() { - bool setColor = false; - - fopAc_ac_c* zhint = dComIfGp_att_getZHint(); - if (zhint != NULL) { - FadeLED({50, 50, 175}, 2.0f); - setColor = true; + cXyz LerpColor(const cXyz a, const cXyz b, const float t) { + return {std::lerp(a.x, b.x, t), std::lerp(a.y, b.y, t), std::lerp(a.z, b.z, t)}; } - daPy_py_c* player = daPy_getPlayerActorClass(); - daAlink_c* link = daAlink_getAlinkActorClass(); + void clamp(cXyz& color) { + color.x = std::clamp(color.x, 0.0f, 255.0f); + color.y = std::clamp(color.y, 0.0f, 255.0f); + color.z = std::clamp(color.z, 0.0f, 255.0f); + } + + ColorSetting getColorSetting() { + const fopAc_ac_c* zHint = dComIfGp_att_getZHint(); + if (zHint != nullptr) { + return kColorTable[ZHINT]; + } + + const daAlink_c* link = daAlink_getAlinkActorClass(); + + if (link == nullptr) + return kColorTable[DUSK_COLOR]; - if (link != nullptr && !setColor) { if (link->checkWolf()) { - FadeLED({115, 115, 75}, 5.0f); - setColor = true; - } else { - switch (dComIfGs_getSelectEquipClothes()) { - case dItemNo_WEAR_KOKIRI_e: - FadeLED({0, 100, 0}, 5.0f); - setColor = true; - break; - case dItemNo_WEAR_ZORA_e: - FadeLED({0, 0, 100}, 5.0f); - setColor = true; - break; - case dItemNo_ARMOR_e: - if (link->checkMagicArmorHeavy()) { - FadeLED({5, 100, 100}, 5.0f); - } else { - FadeLED({100, 0, 5}, 5.0f); - } - setColor = true; - break; - case dItemNo_WEAR_CASUAL_e: - FadeLED({235, 230, 115}, 5.0f); - setColor = true; - break; + return kColorTable[LINK_WOLF]; + } + + switch (dComIfGs_getSelectEquipClothes()) { + case dItemNo_WEAR_KOKIRI_e: + return kColorTable[LINK_KOKIRI]; + case dItemNo_WEAR_ZORA_e: + return kColorTable[LINK_ZORA]; + case dItemNo_ARMOR_e: + if (link->checkMagicArmorHeavy()) { + return kColorTable[LINK_MAGIC_HEAVY]; } + + return kColorTable[LINK_MAGIC]; + case dItemNo_WEAR_CASUAL_e: + return kColorTable[LINK_CASUAL]; + default: + return kColorTable[LINK_KOKIRI]; } } - if (dKy_darkworld_check()) { - SetGamepadAdditionalColor(duskColor); - } else { - SetGamepadAdditionalColor(noColor); + cXyz getAdditionalColor() { + if (dKy_darkworld_check()) { + return kColorTable[DUSK_COLOR].color; + } + + return kColorTable[NO_COLOR].color; } - f32 finalRed = finalGamepadColor.x + additionalGamepadColor.x; - f32 finalGreen = finalGamepadColor.y + additionalGamepadColor.y; - f32 finalBlue = finalGamepadColor.z + additionalGamepadColor.z; +} // namespace - if (finalRed > 255) - finalRed = 255; - if (finalRed < 0) - finalRed = 0; +bool pad_has_led(const int port) noexcept { + if (port > PAD_MAX_CONTROLLERS || port < 0) + return false; - if (finalGreen > 255) - finalGreen = 255; - if (finalGreen < 0) - finalGreen = 0; + return PADHasLED(port); +} - if (finalBlue > 255) - finalBlue = 255; - if (finalBlue < 0) - finalBlue = 0; +void handleGamepadColor() { + auto [color, speed] = getColorSetting(); + const cXyz additionalColor = getAdditionalColor(); + cXyz finalColor = color + additionalColor; + lerpSpeed = speed / 30.0f; - currentGamepadColor = LerpColor(currentGamepadColor, cXyz{finalRed, finalGreen, finalBlue}, lerpSpeed); - PADSetColor(PAD_CHAN0, (u8)currentGamepadColor.x, (u8)currentGamepadColor.y, (u8)currentGamepadColor.z); -} \ No newline at end of file + clamp(finalColor); + currentColor = LerpColor(currentColor, finalColor, lerpSpeed); + + for (int i = 0; i < 4; i++) { + if (pad_has_led(i) && getSettings().game.enableLED[i]) { + PADSetColor( + i, + static_cast(currentColor.x), + static_cast(currentColor.y), + static_cast(currentColor.z) + ); + } + } +} + +} // namespace dusk::input \ No newline at end of file diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 464a1ff2b2..3bea017e2c 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -97,6 +97,12 @@ UserSettings g_userSettings = { .debugFlyCam {"game.debugFlyCam", false}, .debugFlyCamLockEvents {"game.debugFlyCamLockEvents", true}, .allowBackgroundInput {"game.allowBackgroundInput", true}, + .enableLED { + ConfigVar{"game.enableLED_port0", true}, + ConfigVar{"game.enableLED_port1", true}, + ConfigVar{"game.enableLED_port2", true}, + ConfigVar{"game.enableLED_port3", true}, + }, .swapDirectSelect {"game.swapDirectSelect", false}, // Cheats @@ -289,6 +295,10 @@ void registerSettings() { Register(g_userSettings.game.debugFlyCam); Register(g_userSettings.game.debugFlyCamLockEvents); Register(g_userSettings.game.allowBackgroundInput); + Register(g_userSettings.game.enableLED[0]); + Register(g_userSettings.game.enableLED[1]); + Register(g_userSettings.game.enableLED[2]); + Register(g_userSettings.game.enableLED[3]); Register(g_userSettings.game.swapDirectSelect); Register(g_userSettings.backend.isoPath); diff --git a/src/dusk/ui/bool_button.cpp b/src/dusk/ui/bool_button.cpp index 2ed088542a..564818b85b 100644 --- a/src/dusk/ui/bool_button.cpp +++ b/src/dusk/ui/bool_button.cpp @@ -12,7 +12,8 @@ BoolButton::BoolButton(Rml::Element* parent, Props props) .icon = std::move(props.icon), }), mGetValue(std::move(props.getValue)), mSetValue(std::move(props.setValue)), - mIsDisabled(std::move(props.isDisabled)), mIsModified(std::move(props.isModified)) {} + mIsDisabled(std::move(props.isDisabled)), mIsModified(std::move(props.isModified)), + mValueOverride(std::move(props.valueOverride)) {} bool BoolButton::modified() const { if (mIsModified) { @@ -29,6 +30,12 @@ bool BoolButton::disabled() const { } Rml::String BoolButton::format_value() { + if (mValueOverride) { + if (std::string value = mValueOverride(); !value.empty()) { + return value; + } + } + return mGetValue() ? "On" : "Off"; } diff --git a/src/dusk/ui/bool_button.hpp b/src/dusk/ui/bool_button.hpp index a6bd133c44..96b0743a16 100644 --- a/src/dusk/ui/bool_button.hpp +++ b/src/dusk/ui/bool_button.hpp @@ -12,6 +12,7 @@ public: std::function setValue; std::function isDisabled; std::function isModified; + std::function valueOverride; }; BoolButton(Rml::Element* parent, Props props); @@ -28,6 +29,7 @@ private: std::function mSetValue; std::function mIsDisabled; std::function mIsModified; + std::function mValueOverride; }; } // namespace dusk::ui diff --git a/src/dusk/ui/controller_config.cpp b/src/dusk/ui/controller_config.cpp index 20c599ecfd..20e9ef62d5 100644 --- a/src/dusk/ui/controller_config.cpp +++ b/src/dusk/ui/controller_config.cpp @@ -17,6 +17,7 @@ #include "dusk/action_bindings.h" #include "dusk/config.hpp" +#include "dusk/gamepad_color.h" namespace dusk::ui { namespace { @@ -254,7 +255,9 @@ ControllerConfigWindow::ControllerConfigWindow(bool prelaunch) { event.StopPropagation(); } }, - true); + true + ); + if (auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr) { if (auto* root = context->GetRootElement()) { mListeners.emplace_back(std::make_unique( @@ -312,6 +315,28 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) { addPageButton(Page::Actions, "Custom Action Bindings", [] {return Rml::String(">"); }, [] { return false; }); leftPane.add_section("Options"); + leftPane.register_control(leftPane.add_child(BoolButton::Props{ + .key = "Enable LED Status", + .getValue = + [port] { + return getSettings().game.enableLED[port].getValue(); + }, + .setValue = + [port](const bool value) { + getSettings().game.enableLED[port].setValue(value); + }, + .isDisabled = [port] { + return !input::pad_has_led(port); + }, + .valueOverride = [port] { + if (!input::pad_has_led(port)) + return "Not Supported"; + + return ""; + }}), + rightPane, [](Pane& pane) { + pane.add_text("Sets the controller's lighting color based on the game's state."); + }); leftPane.register_control(leftPane.add_child(BoolButton::Props{ .key = "Enable Dead Zones", .getValue = @@ -379,6 +404,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) { PADSetKeyboardActive(static_cast(port), FALSE); PADSerializeMappings(); ClearAllActionBindings(port); + refresh_controller_page(); }); pane.add_button({ diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index 0ee76c325b..e2e8a61168 100644 --- a/src/f_ap/f_ap_game.cpp +++ b/src/f_ap/f_ap_game.cpp @@ -743,7 +743,7 @@ static void fapGm_AfterRecord() { BOOL isRecording = false; static void duskExecute() { - handleGamepadColor(); + dusk::input::handleGamepadColor(); updateAutoSave(); if (dusk::getSettings().game.recordingMode) {