Improve Controller LED Control (#2864)

* Add brightness control and on/off toggle for tunic LED colors.

* Removed toggle, mentioned brightness of 0% for turning off LEDs.

* Set up grabbing tunic color values from Cosmetics Editor for cosmetics sync.

* Why these stupid differences between Windows and Linux compilers?

* Fix Mac build errors? Also try to move the color fetching back into the switch statements to lessen potential computational load.

* Real fix?

* Move "Customize Game Controls" button under "Controller Configuration" button under Settings -> Controller. Renamed "Controller Configuration" to "Configure Controller" and "Customize Game Controls" to "Customize In-game Controls"

* Added LEDColor and LEDColorSource enums for code clarity.

Moved controller LED brightness to new LED Colors group in "Customize In-game Controls" menu.

Added combobox to choose between vanilla tunics, cosmetics tunics, health, and custom as color sources.

Added critical health override checkbox to allow display of red when health is low even when other sources are selected.

Port color pickers have not been implemented yet, default color is white.

* Moved LED control to OTRControllerCallback and wrapped it in a check to `CanSetLed()`.

* Move settings to Port 1 tab in Customize In-game Controls and limited application of colors in `OTRControllerCallback` to port 1.

* UI clarity updates.

* Removed unnecessary LED color enum.

Added custom color picker to port 1 color settings.

* Changed Critical Health Override default to true.

* Modified logic to not do color fetching and instead default to {0,0,0,0} when brightness is off.

* Fix bad cvar string for custom color.

* Cleaning up some post-merge artifacts.

* Update soh/soh/Enhancements/controls/GameControlEditor.h

Co-authored-by: briaguya <70942617+briaguya-ai@users.noreply.github.com>

* Some name changes.

* idea for cleaning up controller callback stuff

* Rearranged color source checks to make sure criticalOverride is applied regardless of other settings.

---------

Co-authored-by: briaguya <70942617+briaguya-ai@users.noreply.github.com>
Co-authored-by: briaguya <briaguya@alice>
This commit is contained in:
Malkierian 2023-05-28 15:40:53 -07:00 committed by GitHub
parent cebfcd1d88
commit 0c7e80a190
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 119 additions and 55 deletions

View File

@ -2232,6 +2232,13 @@ typedef enum {
/* 0x02 */ PAUSE_ANY_CURSOR_ALWAYS_OFF,
} PauseCursorAnySlotOptions;
typedef enum {
LED_SOURCE_TUNIC_ORIGINAL,
LED_SOURCE_TUNIC_COSMETICS,
LED_SOURCE_HEALTH,
LED_SOURCE_CUSTOM
} LEDColorSource;
#define ROM_FILE(name) \
{ 0, 0, #name }

View File

@ -10,10 +10,11 @@
#include <ImGui/imgui.h>
#include <ImGui/imgui_internal.h>
#include <libultraship/bridge.h>
#include <libultraship/libultra/controller.h>
#include <libultraship/libultraship.h>
#include <Utils/StringHelper.h>
#include <ImGuiImpl.h>
#include "Window.h"
#include "../../UIWidgets.hpp"
namespace GameControlEditor {
@ -88,7 +89,7 @@ namespace GameControlEditor {
void DrawUI(bool&);
void Init() {
LUS::AddWindow("Enhancements", "Game Control Editor", DrawUI, CVarGetInteger("gGameControlEditorEnabled", 0));
LUS::AddWindow("Enhancements", "Additional Controller Options", DrawUI, CVarGetInteger("gControllerOptionsEnabled", 0));
addButtonName(BTN_A, "A");
addButtonName(BTN_B, "B");
@ -325,13 +326,46 @@ namespace GameControlEditor {
UIWidgets::PaddedEnhancementCheckbox("Answer Navi Prompt with L Button", "gNaviOnL");
DrawHelpIcon("Speak to Navi with L but enter first-person camera with C-Up");
LUS::EndGroupPanel();
}
void DrawLEDControlPanel() {
LUS::BeginGroupPanel("LED Colors", ImGui::GetContentRegionAvail());
static const char* ledSources[4] = { "Original Tunic Colors", "Cosmetics Tunic Colors", "Health Colors", "Custom" };
UIWidgets::PaddedText("Source");
UIWidgets::EnhancementCombobox("gLedColorSource", ledSources, LED_SOURCE_TUNIC_ORIGINAL);
DrawHelpIcon("Health\n- Red when health critical (13-20% depending on max health)\n- Yellow when health < 40%. Green otherwise.\n\n" \
"Tunics: colors will mirror currently equipped tunic, whether original or the current values in Cosmetics Editor.\n\n" \
"Custom: single, solid color");
if (CVarGetInteger("gLedColorSource", 1) == 3) {
UIWidgets::Spacer(3);
auto port1Color = CVarGetColor24("gLedPort1Color", { 255, 255, 255 });
ImVec4 colorVec = { port1Color.r / 255.0f, port1Color.g / 255.0f, port1Color.b / 255.0f, 1.0f };
if (ImGui::ColorEdit3("", (float*)&colorVec, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) {
Color_RGB8 color;
color.r = colorVec.x * 255.0;
color.g = colorVec.y * 255.0;
color.b = colorVec.z * 255.0;
CVarSetColor24("gLedPort1Color", color);
LUS::RequestCvarSaveOnNextTick();
}
ImGui::SameLine();
ImGui::Text("Custom Color");
}
UIWidgets::PaddedEnhancementSliderFloat("Brightness: %d%%", "##LED_Brightness", "gLedBrightness",
0.0f, 1.0f, "", 1.0f, true, true);
DrawHelpIcon("Sets the brightness of controller LEDs. 0% brightness = LEDs off.");
UIWidgets::PaddedEnhancementCheckbox("Critical Health Override", "gLedCriticalOverride", true, true,
CVarGetInteger("gLedColorSource", LED_SOURCE_TUNIC_ORIGINAL) == LED_SOURCE_HEALTH, "Override redundant for health source.",
UIWidgets::CheckboxGraphics::Cross, true);
DrawHelpIcon("Shows red color when health is critical, otherwise displays according to color source.");
LUS::EndGroupPanel();
}
void DrawUI(bool& open) {
if (!open) {
if (CVarGetInteger("gGameControlEditorEnabled", 0)) {
CVarClear("gGameControlEditorEnabled");
if (CVarGetInteger("gControllerOptionsEnabled", 0)) {
CVarClear("gControllerOptionsEnabled");
LUS::RequestCvarSaveOnNextTick();
}
return;
@ -361,6 +395,9 @@ namespace GameControlEditor {
DrawMiscControlPanel();
} else {
DrawCustomButtons();
if (CurrentPort == 1 && LUS::Context::GetInstance()->GetControlDeck()->GetDeviceFromPortIndex(0)->CanSetLed()) {
DrawLEDControlPanel();
}
}
}
ImGui::End();

View File

@ -163,7 +163,7 @@ namespace GameMenuBar {
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f));
if (ImGui::Button(GetWindowButtonText("Controller Configuration", CVarGetInteger("gControllerConfigurationEnabled", 0)).c_str(), ImVec2 (-1.0f, 0.0f)))
if (ImGui::Button(GetWindowButtonText("Controller Mapping", CVarGetInteger("gControllerConfigurationEnabled", 0)).c_str(), ImVec2 (-1.0f, 0.0f)))
{
if (CVarGetInteger("gControllerConfigurationEnabled", 0)) {
CVarClear("gControllerConfigurationEnabled");
@ -173,6 +173,16 @@ namespace GameMenuBar {
LUS::RequestCvarSaveOnNextTick();
LUS::ToggleInputEditorWindow(CVarGetInteger("gControllerConfigurationEnabled", 0));
}
if (ImGui::Button(GetWindowButtonText("Additional Controller Options", CVarGetInteger("gControllerOptionsEnabled", 0)).c_str(), ImVec2(-1.0f, 0.0f)))
{
if (CVarGetInteger("gControllerOptionsEnabled", 0)) {
CVarClear("gControllerOptionsEnabled");
} else {
CVarSetInteger("gControllerOptionsEnabled", 1);
}
LUS::RequestCvarSaveOnNextTick();
LUS::EnableWindow("Additional Controller Options", CVarGetInteger("gControllerOptionsEnabled", 0));
}
UIWidgets::PaddedSeparator();
ImGui::PopStyleColor(1);
ImGui::PopStyleVar(3);
@ -186,7 +196,6 @@ namespace GameMenuBar {
UIWidgets::Tooltip("Sets the on screen size of the displayed inputs from the Show Inputs setting");
UIWidgets::PaddedEnhancementSliderInt("Simulated Input Lag: %d frames", "##SimulatedInputLag", "gSimulatedInputLag", 0, 6, "", 0, true, true, false);
UIWidgets::Tooltip("Buffers your inputs to be executed a specified amount of frames later");
ImGui::EndMenu();
}
@ -990,16 +999,6 @@ namespace GameMenuBar {
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f));
if (ImGui::Button(GetWindowButtonText("Customize Game Controls", CVarGetInteger("gGameControlEditorEnabled", 0)).c_str(), ImVec2(-1.0f, 0.0f)))
{
if (CVarGetInteger("gGameControlEditorEnabled", 0)) {
CVarClear("gGameControlEditorEnabled");
} else {
CVarSetInteger("gGameControlEditorEnabled", 1);
}
LUS::RequestCvarSaveOnNextTick();
LUS::EnableWindow("Game Control Editor", CVarGetInteger("gGameControlEditorEnabled", 0));
}
if (ImGui::Button(GetWindowButtonText("Cosmetics Editor", CVarGetInteger("gCosmeticsEditorEnabled", 0)).c_str(), ImVec2(-1.0f, 0.0f)))
{
if (CVarGetInteger("gCosmeticsEditorEnabled", 0)) {

View File

@ -122,6 +122,10 @@ SpeechSynthesizer* SpeechSynthesizer::Instance;
extern "C" char** cameraStrings;
std::vector<std::shared_ptr<std::string>> cameraStdStrings;
Color_RGB8 kokiriColor = { 0x1E, 0x69, 0x1B };
Color_RGB8 goronColor = { 0x64, 0x14, 0x00 };
Color_RGB8 zoraColor = { 0x00, 0xEC, 0x64 };
// OTRTODO: A lot of these left in Japanese are used by the mempak manager. LUS does not currently support mempaks. Ignore unused ones.
const char* constCameraStrings[] = {
"INSUFFICIENT",
@ -1590,28 +1594,63 @@ extern "C" uint32_t OTRGetCurrentHeight() {
return OTRGlobals::Instance->context->GetWindow()->GetCurrentHeight();
}
extern "C" void OTRControllerCallback(uint8_t rumble, uint8_t ledColor) {
auto controlDeck = LUS::Context::GetInstance()->GetControlDeck();
for (int i = 0; i < controlDeck->GetNumConnectedPorts(); ++i) {
auto physicalDevice = controlDeck->GetDeviceFromPortIndex(i);
switch (ledColor) {
case 0:
physicalDevice->SetLedColor(i, {255, 0, 0});
break;
case 1:
physicalDevice->SetLedColor(i, {0x1E, 0x69, 0x1B});
break;
case 2:
physicalDevice->SetLedColor(i, {0x64, 0x14, 0x00});
break;
case 3:
physicalDevice->SetLedColor(i, {0x00, 0x3C, 0x64});
break;
Color_RGB8 GetColorForControllerLED() {
auto brightness = CVarGetFloat("gLedBrightness", 1.0f) / 1.0f;
Color_RGB8 color = { 0, 0, 0 };
if (brightness > 0.0f) {
LEDColorSource source = static_cast<LEDColorSource>(CVarGetInteger("gLedColorSource", LED_SOURCE_TUNIC_ORIGINAL));
bool criticalOverride = CVarGetInteger("gLedCriticalOverride", 1);
if (gPlayState && (source == LED_SOURCE_TUNIC_ORIGINAL || source == LED_SOURCE_TUNIC_COSMETICS)) {
switch (CUR_EQUIP_VALUE(EQUIP_TUNIC) - 1) {
case PLAYER_TUNIC_KOKIRI:
color = source == LED_SOURCE_TUNIC_COSMETICS
? CVarGetColor24("gCosmetics.Link_KokiriTunic.Value", kokiriColor)
: kokiriColor;
break;
case PLAYER_TUNIC_GORON:
color = source == LED_SOURCE_TUNIC_COSMETICS
? CVarGetColor24("gCosmetics.Link_GoronTunic.Value", goronColor)
: goronColor;
break;
case PLAYER_TUNIC_ZORA:
color = source == LED_SOURCE_TUNIC_COSMETICS
? CVarGetColor24("gCosmetics.Link_ZoraTunic.Value", zoraColor)
: zoraColor;
break;
}
}
physicalDevice->SetRumble(i, rumble);
if (source == LED_SOURCE_CUSTOM) {
color = CVarGetColor24("gLedPort1Color", { 255, 255, 255 });
}
if (criticalOverride || source == LED_SOURCE_HEALTH) {
if (HealthMeter_IsCritical()) {
color = { 0xFF, 0, 0 };
} else if (source == LED_SOURCE_HEALTH) {
if (gSaveContext.health / gSaveContext.healthCapacity <= 0.4f) {
color = { 0xFF, 0xFF, 0 };
} else {
color = { 0, 0xFF, 0 };
}
}
}
color.r = color.r * brightness;
color.g = color.g * brightness;
color.b = color.b * brightness;
}
return color;
}
extern "C" void OTRControllerCallback(uint8_t rumble) {
auto physicalDevice = LUS::Context::GetInstance()->GetControlDeck()->GetDeviceFromPortIndex(0);
if (physicalDevice->CanSetLed()) {
// We call this every tick, SDL accounts for this use and prevents driver spam
// https://github.com/libsdl-org/SDL/blob/f17058b562c8a1090c0c996b42982721ace90903/src/joystick/SDL_joystick.c#L1114-L1144
physicalDevice->SetLedColor(0, GetColorForControllerLED());
}
physicalDevice->SetRumble(0, rumble);
}
extern "C" float OTRGetAspectRatio() {

View File

@ -295,25 +295,7 @@ void PadMgr_ProcessInputs(PadMgr* padMgr) {
}
uint8_t rumble = (padMgr->rumbleEnable[0] > 0);
uint8_t ledColor = 1;
if (HealthMeter_IsCritical()) {
ledColor = 0;
} else if (gPlayState) {
switch (CUR_EQUIP_VALUE(EQUIP_TUNIC) - 1) {
case PLAYER_TUNIC_KOKIRI:
ledColor = 1;
break;
case PLAYER_TUNIC_GORON:
ledColor = 2;
break;
case PLAYER_TUNIC_ZORA:
ledColor = 3;
break;
}
}
OTRControllerCallback(rumble, ledColor);
OTRControllerCallback(rumble);
if (CVarGetInteger("gPauseBufferBlockInputFrame", 0)) {
Controller_BlockGameInput();