From 7e392e63a869baaf70f17deb8aa081dfe33f52f2 Mon Sep 17 00:00:00 2001 From: Malkierian Date: Sat, 15 Mar 2025 16:11:30 -0700 Subject: [PATCH 01/53] Modern Menu (#5116) * Add menu files, hook menu up to window system. Temporarily rename new menu's UIWidgets to UIWidgets2 to allow both menu systems to coexist temporarily. * Finish implementing new menu. Rename 2ship UIWidgets to UIWidgets2 to complete facilitation of both menus working for now. * More preliminary setup * More prepwork, begin on settings options * Finish settings, add enhancements windows * Fix search function not looking past first columns. * Add dev tool windows * Finish dev tools * Add about window * Fully replace about window * Remove moved menu items from menubar, add more windows to new menu * Implement WindowButtonOptions. Add ability to not embed popout windows when not popped out. Add ability to hide the button for WindowButtons. Fix Entrance Tracker from showing when not enabled. * Fix entrance tracker settings embedded display. Fix entrance tracker settings window original size declaration. * Initial implementation of themed radio button widget. * Move "About" section to second column of general. Fix sidebar sections starting in second column. * Restore Entrance Tracker `Draw()` to allow for custom styling. * Fix combobox positioning formatting. Fix color picker end spacing. Convert everything in check tracker settings to UIWidgets2 (except color pickers and section headers). Make all tracker windows not embed. * Minor cleanup * Fix main volume defaults & mirror jitter fix removal on dev * Improve color picker with RGB/RGBA options. Not finished. * Finish creating CVarColorPicker and implement for Check Tracker background color. Fix tracker and network prefixes. * Finish check tracker settings and convert check tracker. * Port all Cheats menu except for Beta Quest * Port over Beta Quest to new menu * Remove old cheats menu * Port cutscene skips to modern menu * Port Timesaver Enhancements to new menu * Port the Items and Item Count Messages submenu * Port Difficulty Options to new Menu * Removes options that have been ported thus far. * Port "Reduced Clutter" options to new menu * Add forgotten callbacks to Hyper Enemies/Bosses * Copy StateButton to UIWidgets2, and implement custom padding for them in the tracker. * Ports some pause menu related settinga * Change tracker window active title color. Make state buttons smaller in tracker to get more info on screen. * Convert window title active theming to all windows. * Port the rest of Enhancements->Gameplay to new menu * Port the "Graphics" Enhancements to new menu * Ports Fixes over to the Modern Menu * Ported Restorations to Modern Menu * Ported Extra Modes to new menu * Port Autosave and Boot Sequence to modern menu * Cleans up some leftover data for ported buttons * Ports Enhancement Presets to new menu * Port Additional timers to new menu. Removes Enhancements from old menu * Cleans up some unused stuff * Ports Randomizer Enhancements to modern menu * Convert Item Tracker Settings. Something's wrong with the comboboxes in a second column of a table. * Fix combobox alignment and label position calculations. * Convert Entrance Tracker window. * Save Editor Info tab finished. Added `PushStyleInput` and `PushStyleTabs` for Info tab. Fixed some indentation in entrance tracker source. Added font push to tracker windows. * Increase size of all icons in save editor. * Convert flag groups to child windows for automatic sizing and border drawing. * Flags tab completed. Finished inventory tab. * Convert save editor help hover to UIWidgets2. * Various fixes and corrections * Start cosmetics editor, fix theme colors not updating * Cosmetics editor conversion progress * Remove Mac internal resolution restrictions. * Copy over advanced resolution partial and enable most options as a custom widget. * Add size to float sliders, more cosmetics editor progress * Fix incorrect cvar for notifications * Radio button & header color options, more cosmetics editor progress * Finish cosmetics editor conversion * Create and apply THEME_COLOR macro. Resides in SohGui for easy access to mSohMenu. * Move ResolutionEditor to SohGui directory. * Add labels to build info. Fix slider width calculations. * Fix some advanced resolution widget hiding. * Fully implement Advanced Resolution options. Fix graphics settings formatting. Improve slider label position calculations. Implement Clamp options on sliders. * Finish save editor. Convert save editor code to `using namespace UIWidgets2`. Fix search crashing on time splits window. Remove `SetLastItemHoverText` from `UIWidgets2`. * Unify cvar sectioning in time splits. * Add InputString and InputInt widgets, and corresponding CVar Widgets * Adds Widget Type for Inputs, not currently used. * Convert Sail to modern menu * Add Combobox that takes a vector of std::strings * Convert checkbox and combobx to new widgets * Add Tristate checkbox * Convert sliders and tristate checkboxes * Convert top half of Rando window * remove/replace remainder of UIWidgets usage in option.cpp * Converts tricks, locations, and removes old UIWidgets refs * Fix windows build errors * Remove Tri-State checkboxes * Use PushStyleInput instead of PushStyleSlider lol oops didn't realize it was a thing in Ship. * Rebase and address review comments * Convert Crowd Control to modern menu. * Fix build error * Audio editor progress * Re-add CVar SaveOnNextFrame calls to Resolution Editor. Remove old Resolution Editor files. * Convert TimeSplits to new menu. Fix a few enum warnings. * Decrease padding on Arrow Buttons * Audio editor + gameplay stats done * Give Randomizer Menu more screen real-estate * Port plandomizer menu * Fix slider width calculation and allow combobox LabelPosition::None * Fix None labelPos and slider width for inline labels * Fix all slider value label insertions. Convert Collision Viewer. * Minor Collision Viewer enum change. Convert Actor Viewer. * Theme/convert Message Viewer. * Add font to Message Viewer. Theme Value Viewer. * DL Viewer and SohModals themed. * Convert Input Viewer. * Missed some color settings in Input Viewer. Removed UIWidgets references from Controller Config, and restored SoH version. * Remove UIWidgets.hpp include from multiple files. * Completely remove old UIWidgets. Rename UIWidgets2 to UIWidgets. Move Accessibility and Language options to new menu. * Fix Gfx Debugger not showing up. Remove menubar registration. * Fix clearCvars references. * Fix passing std::string to const char* argument. * enum name spacing --------- Co-authored-by: aMannus Co-authored-by: Christopher Leggett --- CMakeLists.txt | 5 +- .../custom/fonts/Inconsolata-Regular.ttf | Bin 0 -> 101752 bytes .../custom/fonts/Montserrat-Regular.ttf | Bin 0 -> 197624 bytes soh/include/variables.h | 6 +- soh/soh/AboutWindow.cpp | 103 - soh/soh/AboutWindow.h | 20 - .../Enhancements/TimeDisplay/TimeDisplay.h | 8 +- soh/soh/Enhancements/audio/AudioEditor.cpp | 239 +- soh/soh/Enhancements/controls/InputViewer.cpp | 253 +- .../controls/SohInputEditorWindow.cpp | 184 +- .../cosmetics/CosmeticsEditor.cpp | 757 +++--- .../Enhancements/cosmetics/CosmeticsEditor.h | 1 - .../Enhancements/debugger/MessageViewer.cpp | 20 +- soh/soh/Enhancements/debugger/actorViewer.cpp | 169 +- soh/soh/Enhancements/debugger/colViewer.cpp | 129 +- .../Enhancements/debugger/debugSaveEditor.cpp | 919 ++++--- soh/soh/Enhancements/debugger/dlViewer.cpp | 14 + soh/soh/Enhancements/debugger/valueViewer.cpp | 25 +- soh/soh/Enhancements/enhancementTypes.h | 26 +- soh/soh/Enhancements/gameplaystats.cpp | 53 +- soh/soh/Enhancements/gameplaystatswindow.h | 1 - soh/soh/Enhancements/mods.cpp | 2 +- soh/soh/Enhancements/presets.cpp | 16 +- .../Enhancements/randomizer/Plandomizer.cpp | 121 +- soh/soh/Enhancements/randomizer/Plandomizer.h | 8 +- soh/soh/Enhancements/randomizer/option.cpp | 120 +- soh/soh/Enhancements/randomizer/option.h | 12 +- .../Enhancements/randomizer/randomizer.cpp | 121 +- .../randomizer/randomizer_check_tracker.cpp | 310 +-- .../randomizer/randomizer_check_tracker.h | 1 + .../randomizer_entrance_tracker.cpp | 134 +- .../randomizer/randomizer_entrance_tracker.h | 2 +- .../randomizer/randomizer_item_tracker.cpp | 195 +- .../randomizer/randomizer_settings_window.h | 2 + soh/soh/Enhancements/randomizer/settings.cpp | 20 +- soh/soh/Enhancements/randomizer/tricks.cpp | 27 +- soh/soh/Enhancements/randomizer/tricks.h | 2 +- .../resolution-editor/ResolutionEditor.cpp | 489 ---- .../resolution-editor/ResolutionEditor.h | 16 - .../Enhancements/timesplits/TimeSplits.cpp | 299 ++- soh/soh/Network/CrowdControl/CrowdControl.cpp | 65 - soh/soh/Network/CrowdControl/CrowdControl.h | 1 - soh/soh/Network/Sail/Sail.cpp | 68 - soh/soh/Network/Sail/Sail.h | 1 - soh/soh/OTRGlobals.cpp | 48 +- soh/soh/OTRGlobals.h | 8 + soh/soh/ShipUtils.cpp | 115 + soh/soh/ShipUtils.h | 31 + soh/soh/SohGui/Menu.cpp | 819 +++++++ soh/soh/SohGui/Menu.h | 65 + soh/soh/SohGui/MenuTypes.h | 280 +++ soh/soh/SohGui/ResolutionEditor.cpp | 528 ++++ soh/soh/SohGui/ResolutionEditor.h | 12 + soh/soh/SohGui/SohGui.cpp | 82 +- soh/soh/SohGui/SohGui.hpp | 13 +- soh/soh/SohGui/SohMenu.cpp | 168 ++ soh/soh/SohGui/SohMenu.h | 227 ++ soh/soh/SohGui/SohMenuBar.cpp | 2104 +--------------- soh/soh/SohGui/SohMenuDevTools.cpp | 170 ++ soh/soh/SohGui/SohMenuEnhancements.cpp | 2146 +++++++++++++++++ soh/soh/SohGui/SohMenuNetwork.cpp | 148 ++ soh/soh/SohGui/SohMenuRandomizer.cpp | 133 + soh/soh/SohGui/SohMenuSettings.cpp | 373 +++ soh/soh/SohGui/SohModals.cpp | 9 +- soh/soh/SohGui/UIWidgets.cpp | 1947 ++++++++------- soh/soh/SohGui/UIWidgets.hpp | 1016 +++++++- soh/soh/config/ConfigMigrators.h | 6 +- soh/soh/cvar_prefixes.h | 10 +- soh/src/code/audio_playback.c | 2 +- soh/src/code/z_frame_advance.c | 5 +- 70 files changed, 9652 insertions(+), 5777 deletions(-) create mode 100644 soh/assets/custom/fonts/Inconsolata-Regular.ttf create mode 100644 soh/assets/custom/fonts/Montserrat-Regular.ttf delete mode 100644 soh/soh/AboutWindow.cpp delete mode 100644 soh/soh/AboutWindow.h delete mode 100644 soh/soh/Enhancements/resolution-editor/ResolutionEditor.cpp delete mode 100644 soh/soh/Enhancements/resolution-editor/ResolutionEditor.h create mode 100644 soh/soh/ShipUtils.cpp create mode 100644 soh/soh/ShipUtils.h create mode 100644 soh/soh/SohGui/Menu.cpp create mode 100644 soh/soh/SohGui/Menu.h create mode 100644 soh/soh/SohGui/MenuTypes.h create mode 100644 soh/soh/SohGui/ResolutionEditor.cpp create mode 100644 soh/soh/SohGui/ResolutionEditor.h create mode 100644 soh/soh/SohGui/SohMenu.cpp create mode 100644 soh/soh/SohGui/SohMenu.h create mode 100644 soh/soh/SohGui/SohMenuDevTools.cpp create mode 100644 soh/soh/SohGui/SohMenuEnhancements.cpp create mode 100644 soh/soh/SohGui/SohMenuNetwork.cpp create mode 100644 soh/soh/SohGui/SohMenuRandomizer.cpp create mode 100644 soh/soh/SohGui/SohMenuSettings.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3437ef3fd..38ac6ce4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,10 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE ) -set(CMAKE_PROJECT_GIT_COMMIT_HASH "${GIT_COMMIT_HASH}" CACHE STRING "Git commit hash" FORCE) +# Get only the first 7 characters of the hash +string(SUBSTRING "${GIT_COMMIT_HASH}" 0 7 SHORT_COMMIT_HASH) + +set(CMAKE_PROJECT_GIT_COMMIT_HASH "${SHORT_COMMIT_HASH}" CACHE STRING "Git commit hash" FORCE) execute_process( COMMAND git describe --tags --abbrev=0 --exact-match HEAD diff --git a/soh/assets/custom/fonts/Inconsolata-Regular.ttf b/soh/assets/custom/fonts/Inconsolata-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d1241516bc2357fadd283371e34055d9b1cc89a8 GIT binary patch literal 101752 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�qnEPT11X=ckn@bDcNSYq1*MY8OvKB(de&^F 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%0A+&hMl60&72-OVAV9J+!ire=9wu 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 -#include -#include "soh/ResourceManagerHelpers.h" - -extern "C" { -#include "variables.h" -} - -AboutWindow::~AboutWindow() { - SPDLOG_TRACE("destruct about window"); -} - -void AboutWindow::InitElement() { - mIsTaggedVersion = gGitCommitTag[0] != 0; - - strncpy(mGitCommitHashTruncated, (char*)gGitCommitHash, 7); - mGitCommitHashTruncated[7] = 0; -} - -void AboutWindow::Draw() { - if (!IsVisible()) { - return; - } - - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoDocking | - ImGuiWindowFlags_NoScrollWithMouse | - ImGuiWindowFlags_NoScrollbar; - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(16 * ImGui::GetIO().FontGlobalScale, 8 * ImGui::GetIO().FontGlobalScale)); - - if (!ImGui::Begin(GetName().c_str(), &mIsVisible, windowFlags)) { - ImGui::End(); - } else { - DrawElement(); - ImGui::End(); - } - - ImGui::PopStyleVar(); - - // Sync up the IsVisible flag if it was changed by ImGui - SyncVisibilityConsoleVariable(); -} - -const char* AboutWindow::GetGameVersionString(uint32_t index) { - uint32_t gameVersion = ResourceMgr_GetGameVersion(index); - switch (gameVersion) { - case OOT_NTSC_US_10: - return "NTSC-U 1.0"; - case OOT_NTSC_US_11: - return "NTSC-U 1.1"; - case OOT_NTSC_US_12: - return "NTSC-U 1.2"; - case OOT_PAL_10: - return "PAL 1.0"; - case OOT_PAL_11: - return "PAL 1.1"; - case OOT_PAL_GC: - return "PAL GC"; - case OOT_PAL_MQ: - return "PAL MQ"; - case OOT_PAL_GC_DBG1: - case OOT_PAL_GC_DBG2: - return "PAL GC-D"; - case OOT_PAL_GC_MQ_DBG: - return "PAL MQ-D"; - case OOT_IQUE_CN: - return "IQUE CN"; - case OOT_IQUE_TW: - return "IQUE TW"; - default: - return "UNKNOWN"; - } -} - -void AboutWindow::DrawElement() { - // The icon is already padded - adjust for that - ImVec2 cursorPos = ImGui::GetCursorScreenPos(); - cursorPos.x -= 16 * ImGui::GetIO().FontGlobalScale; - ImGui::SetCursorScreenPos(cursorPos); - - ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("Game_Icon"), ImVec2(64.0f * ImGui::GetIO().FontGlobalScale, 64.0f * ImGui::GetIO().FontGlobalScale)); - - ImGui::SameLine(); - - ImGui::BeginGroup(); - ImGui::Text("Ship of Harkinian"); - if (mIsTaggedVersion) { - ImGui::Text("%s", gBuildVersion); - } else { - ImGui::Text("%s", gGitBranch); - ImGui::Text("%s", mGitCommitHashTruncated); - } - ImGui::EndGroup(); - - ImGui::Dummy(ImVec2(0, 2 * ImGui::GetIO().FontGlobalScale)); - ImGui::Text("Game Archives:"); - for (uint32_t i = 0; i < ResourceMgr_GetNumGameVersions(); i++) { - ImGui::BulletText(GetGameVersionString(i)); - } -} diff --git a/soh/soh/AboutWindow.h b/soh/soh/AboutWindow.h deleted file mode 100644 index 5a731fdc1..000000000 --- a/soh/soh/AboutWindow.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -class AboutWindow : public Ship::GuiWindow { - public: - using GuiWindow::GuiWindow; - ~AboutWindow(); - - private: - void InitElement() override; - void Draw() override; - void DrawElement() override; - void UpdateElement() override {}; - - const char* GetGameVersionString(uint32_t index); - - bool mIsTaggedVersion; - char mGitCommitHashTruncated[8]; -}; diff --git a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h index 090ac2901..671577631 100644 --- a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h +++ b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h @@ -13,20 +13,20 @@ class TimeDisplayWindow : public Ship::GuiWindow { void TimeDisplayUpdateDisplayOptions(); void TimeDisplayInitSettings(); -typedef enum { +typedef enum TimerDisplay { DISPLAY_IN_GAME_TIMER, DISPLAY_TIME_OF_DAY, DISPLAY_CONDITIONAL_TIMER, DISPLAY_NAVI_TIMER -}; +} TimerDisplay; -typedef enum { +typedef enum NaviTimerValues { NAVI_PREPARE = 600, NAVI_ACTIVE = 3000, NAVI_COOLDOWN = 25800, DAY_BEGINS = 17759, NIGHT_BEGINS = 49155 -}; +} NaviTimerValues; typedef struct { uint32_t timeID; diff --git a/soh/soh/Enhancements/audio/AudioEditor.cpp b/soh/soh/Enhancements/audio/AudioEditor.cpp index d15bc5f25..0149aa148 100644 --- a/soh/soh/Enhancements/audio/AudioEditor.cpp +++ b/soh/soh/Enhancements/audio/AudioEditor.cpp @@ -12,6 +12,7 @@ #include "soh/cvar_prefixes.h" #include #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "AudioCollection.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" @@ -165,13 +166,20 @@ void DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequence const std::string previewButton = ICON_FA_PLAY + hiddenKey; if (CVarGetInteger(CVAR_AUDIO("Playing"), 0) == sequenceId) { - if (ImGui::Button(stopButton.c_str())) { + if (UIWidgets::Button(stopButton.c_str(), UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(10.0f, 6.0f)) + .Tooltip("Stop Preview") + .Color(THEME_COLOR))) { func_800F5C2C(); CVarSetInteger(CVAR_AUDIO("Playing"), 0); } - UIWidgets::Tooltip("Stop Preview"); } else { - if (ImGui::Button(previewButton.c_str())) { + if (UIWidgets::Button(previewButton.c_str(), UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(10.0f, 6.0f)) + .Tooltip("Play Preview") + .Color(THEME_COLOR))) { if (CVarGetInteger(CVAR_AUDIO("Playing"), 0) != 0) { func_800F5C2C(); CVarSetInteger(CVAR_AUDIO("Playing"), 0); @@ -188,11 +196,10 @@ void DrawPreviewButton(uint16_t sequenceId, std::string sfxKey, SeqType sequence } } } - UIWidgets::Tooltip("Play Preview"); } } -void Draw_SfxTab(const std::string& tabId, SeqType type) { +void Draw_SfxTab(const std::string& tabId, SeqType type, const std::string& tabName) { const std::map& map = AudioCollection::Instance->GetAllSequences(); const std::string hiddenTabId = "##" + tabId; @@ -200,7 +207,10 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { const std::string randomizeAllButton = "Randomize All" + hiddenTabId; const std::string lockAllButton = "Lock All" + hiddenTabId; const std::string unlockAllButton = "Unlock All" + hiddenTabId; - if (ImGui::Button(resetAllButton.c_str())) { + + ImGui::SeparatorText(tabName.c_str()); + if (UIWidgets::Button(resetAllButton.c_str(), + UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR))) { auto currentBGM = func_800FA0B4(SEQ_PLAYER_BGM_MAIN); auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); ResetGroup(map, type); @@ -211,7 +221,8 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { } } ImGui::SameLine(); - if (ImGui::Button(randomizeAllButton.c_str())) { + if (UIWidgets::Button(randomizeAllButton.c_str(), + UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR))) { auto currentBGM = func_800FA0B4(SEQ_PLAYER_BGM_MAIN); auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); RandomizeGroup(type); @@ -222,7 +233,8 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { } } ImGui::SameLine(); - if (ImGui::Button(lockAllButton.c_str())) { + if (UIWidgets::Button(lockAllButton.c_str(), + UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR))) { auto currentBGM = func_800FA0B4(SEQ_PLAYER_BGM_MAIN); auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); LockGroup(map, type); @@ -233,7 +245,8 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { } } ImGui::SameLine(); - if (ImGui::Button(unlockAllButton.c_str())) { + if (UIWidgets::Button(unlockAllButton.c_str(), + UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR))) { auto currentBGM = func_800FA0B4(SEQ_PLAYER_BGM_MAIN); auto prevReplacement = AudioCollection::Instance->GetReplacementSequence(currentBGM); UnlockGroup(map, type); @@ -244,10 +257,12 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { } } + // Longest text in Audio Editor + ImVec2 columnSize = ImGui::CalcTextSize("Navi - Look/Hey/Watchout (Target Enemy)"); ImGui::BeginTable(tabId.c_str(), 3, ImGuiTableFlags_SizingFixedFit); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, columnSize.x + 30); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, columnSize.x + 30); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 160.0f); for (const auto& [defaultValue, seqData] : map) { if (~(seqData.category) & type) { continue; @@ -273,6 +288,7 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { ImGui::TableNextColumn(); ImGui::PushItemWidth(-FLT_MIN); const int initialValue = map.contains(currentValue) ? currentValue : defaultValue; + UIWidgets::PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo(hiddenKey.c_str(), map.at(initialValue).label.c_str())) { for (const auto& [value, seqData] : map) { // If excluded as a replacement sequence, don't show in other dropdowns except the effect's own dropdown. @@ -293,22 +309,30 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { ImGui::EndCombo(); } + UIWidgets::PopStyleCombobox(); ImGui::TableNextColumn(); ImGui::PushItemWidth(-FLT_MIN); DrawPreviewButton((type == SEQ_SFX || type == SEQ_VOICE || type == SEQ_INSTRUMENT) ? defaultValue : currentValue, seqData.sfxKey, type); auto locked = CVarGetInteger(cvarLockKey.c_str(), 0) == 1; ImGui::SameLine(); ImGui::PushItemWidth(-FLT_MIN); - if (ImGui::Button(resetButton.c_str())) { + if (UIWidgets::Button(resetButton.c_str(), UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(10.0f, 6.0f)) + .Tooltip("Reset to default") + .Color(THEME_COLOR))) { CVarClear(cvarKey.c_str()); CVarClear(cvarLockKey.c_str()); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); UpdateCurrentBGM(defaultValue, seqData.category); } - UIWidgets::Tooltip("Reset to default"); ImGui::SameLine(); ImGui::PushItemWidth(-FLT_MIN); - if (ImGui::Button(randomizeButton.c_str())) { + if (UIWidgets::Button(randomizeButton.c_str(), UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(10.0f, 6.0f)) + .Tooltip("Randomize this sound") + .Color(THEME_COLOR))) { std::vector validSequences = {}; for (const auto seqInfo : AudioCollection::Instance->GetIncludedSequences()) { if (seqInfo->category & type) { @@ -327,10 +351,14 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { UpdateCurrentBGM(defaultValue, type); } } - UIWidgets::Tooltip("Randomize this sound"); ImGui::SameLine(); ImGui::PushItemWidth(-FLT_MIN); - if (ImGui::Button(locked ? lockedButton.c_str() : unlockedButton.c_str())) { + if (UIWidgets::Button(locked ? lockedButton.c_str() : unlockedButton.c_str(), + UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(10.0f, 6.0f)) + .Tooltip(locked ? "Sound locked" : "Sound unlocked") + .Color(THEME_COLOR))) { if (locked) { CVarClear(cvarLockKey.c_str()); } else { @@ -338,7 +366,6 @@ void Draw_SfxTab(const std::string& tabId, SeqType type) { } Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - UIWidgets::Tooltip(locked ? "Sound locked" : "Sound unlocked"); } ImGui::EndTable(); } @@ -401,10 +428,11 @@ ImVec4 GetSequenceTypeColor(SeqType type) { } } -void DrawTypeChip(SeqType type) { +void DrawTypeChip(SeqType type, std::string sequenceName) { ImGui::BeginDisabled(); ImGui::PushStyleColor(ImGuiCol_Button, GetSequenceTypeColor(type)); - ImGui::SmallButton(GetSequenceTypeName(type).c_str()); + std::string buttonLabel = GetSequenceTypeName(type) + "##" + sequenceName; + ImGui::Button(buttonLabel.c_str()); ImGui::PopStyleColor(); ImGui::EndDisabled(); } @@ -425,56 +453,68 @@ void AudioEditor::InitElement() { void AudioEditor::DrawElement() { AudioCollection::Instance->InitializeShufflePool(); - float buttonSegments = ImGui::GetContentRegionAvail().x / 4; - if (ImGui::Button("Randomize All Groups", ImVec2(buttonSegments, 30.0f))) { + UIWidgets::Separator(); + if (UIWidgets::Button("Randomize All Groups", + UIWidgets::ButtonOptions() + .Size(ImVec2(230.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("Randomizes all unlocked music and sound effects across tab groups"))) { AudioEditor_RandomizeAll(); } - UIWidgets::Tooltip("Randomizes all unlocked music and sound effects across tab groups"); ImGui::SameLine(); - if (ImGui::Button("Reset All Groups", ImVec2(buttonSegments, 30.0f))) { + if (UIWidgets::Button("Reset All Groups", + UIWidgets::ButtonOptions() + .Size(ImVec2(230.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("Resets all unlocked music and sound effects across tab groups"))) { AudioEditor_ResetAll(); } - UIWidgets::Tooltip("Resets all unlocked music and sound effects across tab groups"); ImGui::SameLine(); - if (ImGui::Button("Lock All Groups", ImVec2(buttonSegments, 30.0f))) { + if (UIWidgets::Button("Lock All Groups", UIWidgets::ButtonOptions() + .Size(ImVec2(230.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("Locks all music and sound effects across tab groups"))) { AudioEditor_LockAll(); } - UIWidgets::Tooltip("Locks all music and sound effects across tab groups"); ImGui::SameLine(); - if (ImGui::Button("Unlock All Groups", ImVec2(buttonSegments, 30.0f))) { + if (UIWidgets::Button("Unlock All Groups", + UIWidgets::ButtonOptions() + .Size(ImVec2(230.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("Unlocks all music and sound effects across tab groups"))) { AudioEditor_UnlockAll(); } - UIWidgets::Tooltip("Unlocks all music and sound effects across tab groups"); - + UIWidgets::Separator(); + UIWidgets::PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("SfxContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("Background Music")) { - Draw_SfxTab("backgroundMusic", SEQ_BGM_WORLD); + Draw_SfxTab("backgroundMusic", SEQ_BGM_WORLD, "Background Music"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Fanfares")) { - Draw_SfxTab("fanfares", SEQ_FANFARE); + Draw_SfxTab("fanfares", SEQ_FANFARE, "Fanfares"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Events")) { - Draw_SfxTab("event", SEQ_BGM_EVENT); + Draw_SfxTab("event", SEQ_BGM_EVENT, "Events"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Battle Music")) { - Draw_SfxTab("battleMusic", SEQ_BGM_BATTLE); + Draw_SfxTab("battleMusic", SEQ_BGM_BATTLE, "Battle Music"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Ocarina")) { - Draw_SfxTab("instrument", SEQ_INSTRUMENT); - Draw_SfxTab("ocarina", SEQ_OCARINA); + Draw_SfxTab("instrument", SEQ_INSTRUMENT, "Instruments"); + Draw_SfxTab("ocarina", SEQ_OCARINA, "Ocarina"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Sound Effects")) { - Draw_SfxTab("sfx", SEQ_SFX); + Draw_SfxTab("sfx", SEQ_SFX, "Sound Effects"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Voices")) { - Draw_SfxTab("voice", SEQ_VOICE); + Draw_SfxTab("voice", SEQ_VOICE, "Voices"); ImGui::EndTabItem(); } @@ -486,53 +526,63 @@ void AudioEditor::DrawElement() { ImGui::TableNextRow(); ImGui::TableNextColumn(); if (ImGui::BeginChild("SfxOptions", ImVec2(0, -8))) { - ImGui::PushItemWidth(-FLT_MIN); - UIWidgets::EnhancementCheckbox("Disable Enemy Proximity Music", CVAR_AUDIO("EnemyBGMDisable")); - UIWidgets::InsertHelpHoverText( - "Disables the music change when getting close to enemies. Useful for hearing " - "your custom music for each scene more often."); - UIWidgets::EnhancementCheckbox("Disable Leading Music in Lost Woods", CVAR_AUDIO("LostWoodsConsistentVolume")); - UIWidgets::InsertHelpHoverText( - "Disables the volume shifting in the Lost Woods. Useful for hearing " - "your custom music in the Lost Woods if you don't need the navigation assitance " - "the volume changing provides. If toggling this while in the Lost Woods, reload " - "the area for the effect to kick in." - ); - UIWidgets::EnhancementCheckbox("Display Sequence Name on Overlay", CVAR_AUDIO("SeqNameOverlay")); - UIWidgets::InsertHelpHoverText( - "Displays the name of the current sequence in the corner of the screen whenever a new sequence " - "is loaded to the main sequence player (does not apply to fanfares or enemy BGM)." - ); + UIWidgets::CVarCheckbox( + "Disable Enemy Proximity Music", CVAR_AUDIO("EnemyBGMDisable"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Disables the music change when getting close to enemies. Useful for hearing " + "your custom music for each scene more often.")); + UIWidgets::CVarCheckbox( + "Disable Leading Music in Lost Woods", CVAR_AUDIO("LostWoodsConsistentVolume"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Disables the volume shifting in the Lost Woods. Useful for hearing " + "your custom music in the Lost Woods if you don't need the navigation assitance " + "the volume changing provides. If toggling this while in the Lost Woods, reload " + "the area for the effect to kick in.")); + UIWidgets::CVarCheckbox( + "Display Sequence Name on Overlay", CVAR_AUDIO("SeqNameOverlay"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Displays the name of the current sequence in the corner of the screen whenever a new " + "sequence " + "is loaded to the main sequence player (does not apply to fanfares or enemy BGM).")); + UIWidgets::CVarSliderInt("Overlay Duration: %d seconds", CVAR_AUDIO("SeqNameOverlayDuration"), + UIWidgets::IntSliderOptions() + .Min(1) + .Max(10) + .DefaultValue(5) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + UIWidgets::CVarSliderFloat("Link's voice pitch multiplier", + CVAR_AUDIO("LinkVoiceFreqMultiplier"), + UIWidgets::FloatSliderOptions() + .IsPercentage() + .Min(0.4f) + .Max(2.5f) + .DefaultValue(1.0f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); ImGui::SameLine(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - UIWidgets::EnhancementSliderInt("Overlay Duration: %d seconds", "##SeqNameOverlayDuration", - CVAR_AUDIO("SeqNameOverlayDuration"), 1, 10, "", 5); - ImGui::PopItemWidth(); - ImGui::NewLine(); - ImGui::PopItemWidth(); - UIWidgets::EnhancementSliderFloat("Link's voice pitch multiplier: %.1f %%", "##linkVoiceFreqMultiplier", - CVAR_AUDIO("LinkVoiceFreqMultiplier"), 0.4, 2.5, "", 1.0, true, true); - ImGui::SameLine(); - const std::string resetButton = "Reset##linkVoiceFreqMultiplier"; - if (ImGui::Button(resetButton.c_str())) { + ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 40.f); + if (UIWidgets::Button("Reset##linkVoiceFreqMultiplier", + UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { CVarSetFloat(CVAR_AUDIO("LinkVoiceFreqMultiplier"), 1.0f); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - - ImGui::NewLine(); - UIWidgets::EnhancementCheckbox("Randomize All Music and Sound Effects on New Scene", CVAR_AUDIO("RandomizeAllOnNewScene")); - UIWidgets::Tooltip("Enables randomizing all unlocked music and sound effects when you enter a new scene."); - - ImGui::NewLine(); - ImGui::PushItemWidth(-FLT_MIN); - UIWidgets::PaddedSeparator(); - UIWidgets::PaddedText("The following options are experimental and may cause music\nto sound odd or have other undesireable effects."); - UIWidgets::EnhancementCheckbox("Lower Octaves of Unplayable High Notes", CVAR_AUDIO("ExperimentalOctaveDrop")); - UIWidgets::InsertHelpHoverText("Some custom sequences may have notes that are too high for the game's audio " - "engine to play. Enabling this checkbox will cause these notes to drop a " - "couple of octaves so they can still harmonize with the other notes of the " - "sequence."); - ImGui::PopItemWidth(); + UIWidgets::CVarCheckbox( + "Randomize All Music and Sound Effects on New Scene", CVAR_AUDIO("RandomizeAllOnNewScene"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip( + "Enables randomizing all unlocked music and sound effects when you enter a new scene.")); + UIWidgets::CVarCheckbox( + "Lower Octaves of Unplayable High Notes", CVAR_AUDIO("ExperimentalOctaveDrop"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Some custom sequences may have notes that are too high for the game's audio " + "engine to play. Enabling this checkbox will cause these notes to drop a " + "couple of octaves so they can still harmonize with the other notes of the " + "sequence.")); } ImGui::EndChild(); ImGui::EndTable(); @@ -564,9 +614,12 @@ void AudioEditor::DrawElement() { std::set seqsToExclude = {}; static ImGuiTextFilter sequenceSearch; + UIWidgets::PushStyleInput(THEME_COLOR); sequenceSearch.Draw("Filter (inc,-exc)", 490.0f); + UIWidgets::PopStyleInput(); ImGui::SameLine(); - if (ImGui::Button("Exclude All")) { + if (UIWidgets::Button("Exclude All", + UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR))) { for (auto seqInfo : AudioCollection::Instance->GetIncludedSequences()) { if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { seqsToExclude.insert(seqInfo); @@ -574,7 +627,8 @@ void AudioEditor::DrawElement() { } } ImGui::SameLine(); - if (ImGui::Button("Include All")) { + if (UIWidgets::Button("Include All", + UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR))) { for (auto seqInfo : AudioCollection::Instance->GetExcludedSequences()) { if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { seqsToInclude.insert(seqInfo); @@ -643,13 +697,17 @@ void AudioEditor::DrawElement() { ImGui::BeginChild("ChildIncludedSequences", ImVec2(0, -8)); for (auto seqInfo : AudioCollection::Instance->GetIncludedSequences()) { if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { - if (ImGui::Button(std::string(ICON_FA_TIMES "##" + seqInfo->sfxKey).c_str())) { + if (UIWidgets::Button(std::string(ICON_FA_TIMES "##" + seqInfo->sfxKey).c_str(), + UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(9.0f, 6.0f)) + .Color(THEME_COLOR))) { seqsToExclude.insert(seqInfo); } ImGui::SameLine(); DrawPreviewButton(seqInfo->sequenceId, seqInfo->sfxKey, seqInfo->category); ImGui::SameLine(); - DrawTypeChip(seqInfo->category); + DrawTypeChip(seqInfo->category, seqInfo->label); ImGui::SameLine(); ImGui::Text("%s", seqInfo->label.c_str()); } @@ -667,13 +725,17 @@ void AudioEditor::DrawElement() { ImGui::BeginChild("ChildExcludedSequences", ImVec2(0, -8)); for (auto seqInfo : AudioCollection::Instance->GetExcludedSequences()) { if (sequenceSearch.PassFilter(seqInfo->label.c_str()) && showType[seqInfo->category]) { - if (ImGui::Button(std::string(ICON_FA_PLUS "##" + seqInfo->sfxKey).c_str())) { + if (UIWidgets::Button(std::string(ICON_FA_PLUS "##" + seqInfo->sfxKey).c_str(), + UIWidgets::ButtonOptions() + .Size(UIWidgets::Sizes::Inline) + .Padding(ImVec2(9.0f, 6.0f)) + .Color(THEME_COLOR))) { seqsToInclude.insert(seqInfo); } ImGui::SameLine(); DrawPreviewButton(seqInfo->sequenceId, seqInfo->sfxKey, seqInfo->category); ImGui::SameLine(); - DrawTypeChip(seqInfo->category); + DrawTypeChip(seqInfo->category, seqInfo->sfxKey); ImGui::SameLine(); ImGui::Text("%s", seqInfo->label.c_str()); } @@ -695,6 +757,7 @@ void AudioEditor::DrawElement() { ImGui::EndTabBar(); } + UIWidgets::PopStyleTabs(); } std::vector allTypes = { SEQ_BGM_WORLD, SEQ_BGM_EVENT, SEQ_BGM_BATTLE, SEQ_OCARINA, SEQ_FANFARE, SEQ_INSTRUMENT, SEQ_SFX, SEQ_VOICE }; diff --git a/soh/soh/Enhancements/controls/InputViewer.cpp b/soh/soh/Enhancements/controls/InputViewer.cpp index 5f650a25f..3eb18c0e2 100644 --- a/soh/soh/Enhancements/controls/InputViewer.cpp +++ b/soh/soh/Enhancements/controls/InputViewer.cpp @@ -10,31 +10,25 @@ #include #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" + +using namespace UIWidgets; // Text colors -static ImVec4 textColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); -static ImVec4 range1Color = ImVec4(1.0f, 0.7f, 0, 1.0f); -static ImVec4 range2Color = ImVec4(0, 1.0f, 0, 1.0f); +static Color_RGBA8 textColorDefault = { 255, 255, 255, 255 }; +static Color_RGBA8 range1ColorDefault = { 255, 178, 0, 255 }; +static Color_RGBA8 range2ColorDefault = { 0, 255, 0, 255 }; -static const char* buttonOutlineOptions[4] = { "Always Shown", "Shown Only While Not Pressed", - "Shown Only While Pressed", "Always Hidden" }; -static const char* buttonOutlineOptionsVerbose[4] = { "Outline Always Shown", "Outline Shown Only While Not Pressed", - "Outline Shown Only While Pressed", "Outline Always Hidden" }; +static std::unordered_map buttonOutlineOptions = + {{ BUTTON_OUTLINE_ALWAYS_SHOWN, "Always Shown" }, { BUTTON_OUTLINE_NOT_PRESSED, "Shown Only While Not Pressed" }, + { BUTTON_OUTLINE_PRESSED, "Shown Only While Pressed" }, { BUTTON_OUTLINE_ALWAYS_HIDDEN, "Always Hidden" }}; +static std::unordered_map buttonOutlineOptionsVerbose = + {{ BUTTON_OUTLINE_ALWAYS_SHOWN, "Outline Always Shown" }, { BUTTON_OUTLINE_NOT_PRESSED, "Outline Shown Only While Not Pressed" }, + { BUTTON_OUTLINE_PRESSED, "Outline Shown Only While Pressed" }, { BUTTON_OUTLINE_ALWAYS_HIDDEN, "Outline Always Hidden" }}; -static const char* stickModeOptions[3] = { "Always", "While In Use", "Never" }; - -static Color_RGBA8 vec2Color(ImVec4 vec) { - Color_RGBA8 color; - color.r = vec.x * 255.0; - color.g = vec.y * 255.0; - color.b = vec.z * 255.0; - color.a = vec.w * 255.0; - return color; -} - -static ImVec4 color2Vec(Color_RGBA8 color) { - return ImVec4(color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0); -} +static std::unordered_map stickModeOptions = + {{ STICK_MODE_ALWAYS_SHOWN, "Always" }, { STICK_MODE_HIDDEN_IN_DEADZONE, "While In Use" }, { STICK_MODE_ALWAYS_HIDDEN, "Never" }}; InputViewer::~InputViewer() { SPDLOG_TRACE("destruct input viewer"); @@ -399,17 +393,17 @@ void InputViewer::DrawElement() { (rSquared >= (range1Min * range1Min)) && (rSquared < (range1Max * range1Max))) { ImGui::PushStyleColor( ImGuiCol_Text, - color2Vec(CVarGetColor(CVAR_INPUT_VIEWER("AnalogAngles.Range1.Color"), vec2Color(range1Color)))); + VecFromRGBA8(CVarGetColor(CVAR_INPUT_VIEWER("AnalogAngles.Range1.Color.Value"), range1ColorDefault))); } else if (CVarGetInteger(CVAR_INPUT_VIEWER("AnalogAngles.Range2.Enabled"), 0) && (rSquared >= (range2Min * range2Min)) && (rSquared < (range2Max * range2Max))) { ImGui::PushStyleColor( ImGuiCol_Text, - color2Vec(CVarGetColor(CVAR_INPUT_VIEWER("AnalogAngles.Range2.Color"), vec2Color(range2Color)))); + VecFromRGBA8(CVarGetColor(CVAR_INPUT_VIEWER("AnalogAngles.Range2.Color.Value"), range2ColorDefault))); } else { - ImGui::PushStyleColor(ImGuiCol_Text, color2Vec(CVarGetColor(CVAR_INPUT_VIEWER("AnalogAngles.TextColor"), - vec2Color(textColor)))); + ImGui::PushStyleColor(ImGuiCol_Text, VecFromRGBA8(CVarGetColor(CVAR_INPUT_VIEWER("AnalogAngles.TextColor.Value"), + textColorDefault))); } // Render text @@ -434,250 +428,217 @@ InputViewerSettingsWindow::~InputViewerSettingsWindow() { } void InputViewerSettingsWindow::DrawElement() { - // gInputViewer.Scale - UIWidgets::EnhancementSliderFloat("Input Viewer Scale: %.2f", "##Input", CVAR_INPUT_VIEWER("Scale"), 0.1f, 5.0f, "", - 1.0f, false, true); - UIWidgets::Tooltip("Sets the on screen size of the input viewer"); + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); + // gInputViewer.Scale + CVarSliderFloat("Input Viewer Scale: %.2f", CVAR_INPUT_VIEWER("Scale"), + FloatSliderOptions().Color(THEME_COLOR).DefaultValue(1.0f).Min(0.1f).Max(5.0f).ShowButtons(true).Tooltip("Sets the on screen size of the input viewer")); // gInputViewer.EnableDragging - UIWidgets::EnhancementCheckbox("Enable Dragging", CVAR_INPUT_VIEWER("EnableDragging"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Enable Dragging", CVAR_INPUT_VIEWER("EnableDragging"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); UIWidgets::PaddedSeparator(true, true); // gInputViewer.ShowBackground - UIWidgets::EnhancementCheckbox("Show Background Layer", CVAR_INPUT_VIEWER("ShowBackground"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show Background Layer", CVAR_INPUT_VIEWER("ShowBackground"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); UIWidgets::PaddedSeparator(true, true); + PushStyleHeader(THEME_COLOR); if (ImGui::CollapsingHeader("Buttons")) { // gInputViewer.ButtonOutlineMode - UIWidgets::PaddedText("Button Outlines/Backgrounds", true, false); - UIWidgets::EnhancementCombobox( - CVAR_INPUT_VIEWER("ButtonOutlineMode"), buttonOutlineOptions, BUTTON_OUTLINE_NOT_PRESSED, - !CVarGetInteger(CVAR_INPUT_VIEWER("UseGlobalButtonOutlineMode"), 1), "", - CVarGetInteger(CVAR_INPUT_VIEWER("ButtonOutlineMode"), BUTTON_OUTLINE_NOT_PRESSED)); - UIWidgets::Tooltip( - "Sets the desired visibility behavior for the button outline/background layers. Useful for " - "custom input viewers."); + CVarCombobox("Button Outlines/Backgrounds", CVAR_INPUT_VIEWER("ButtonOutlineMode"), buttonOutlineOptions, + ComboboxOptions({{ .disabled = !CVarGetInteger(CVAR_INPUT_VIEWER("UseGlobalButtonOutlineMode"), 1), .disabledTooltip = "Disabled because Global Button Outline is off" }}) + .Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED) + .Tooltip("Sets the desired visibility behavior for the button outline/background layers. Useful for " + "custom input viewers.")); // gInputViewer.UseGlobalButtonOutlineMode - UIWidgets::EnhancementCheckbox("Use for all buttons", CVAR_INPUT_VIEWER("UseGlobalButtonOutlineMode"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Use for all buttons", CVAR_INPUT_VIEWER("UseGlobalButtonOutlineMode"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); UIWidgets::PaddedSeparator(); bool useIndividualOutlines = !CVarGetInteger(CVAR_INPUT_VIEWER("UseGlobalButtonOutlineMode"), 1); // gInputViewer.ABtn - UIWidgets::EnhancementCheckbox("Show A-Button Layers", CVAR_INPUT_VIEWER("ABtn"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show A-Button Layers", CVAR_INPUT_VIEWER("ABtn"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("ABtn"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("ABtnOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##ABtnOutline", CVAR_INPUT_VIEWER("ABtnOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.BBtn - UIWidgets::EnhancementCheckbox("Show B-Button Layers", CVAR_INPUT_VIEWER("BBtn"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show B-Button Layers", CVAR_INPUT_VIEWER("BBtn"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("BBtn"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("BBtnOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##BBtnOutline", CVAR_INPUT_VIEWER("BBtnOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.CUp - UIWidgets::EnhancementCheckbox("Show C-Up Layers", CVAR_INPUT_VIEWER("CUp"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show C-Up Layers", CVAR_INPUT_VIEWER("CUp"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("CUp"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("CUpOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##CUpOutline", CVAR_INPUT_VIEWER("CUpOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.CRight - UIWidgets::EnhancementCheckbox("Show C-Right Layers", CVAR_INPUT_VIEWER("CRight"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show C-Right Layers", CVAR_INPUT_VIEWER("CRight"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("CRight"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("CRightOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##CRightOutline", CVAR_INPUT_VIEWER("CRightOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.CDown - UIWidgets::EnhancementCheckbox("Show C-Down Layers", CVAR_INPUT_VIEWER("CDown"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show C-Down Layers", CVAR_INPUT_VIEWER("CDown"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("CDown"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("CDownOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##CDownOutline", CVAR_INPUT_VIEWER("CDownOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.CLeft - UIWidgets::EnhancementCheckbox("Show C-Left Layers", CVAR_INPUT_VIEWER("CLeft"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show C-Left Layers", CVAR_INPUT_VIEWER("CLeft"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("CLeft"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("CLeftOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##CLeftOutline", CVAR_INPUT_VIEWER("CLeftOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.LBtn - UIWidgets::EnhancementCheckbox("Show L-Button Layers", CVAR_INPUT_VIEWER("LBtn"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show L-Button Layers", CVAR_INPUT_VIEWER("LBtn"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("LBtn"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("LBtnOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##LBtnOutline", CVAR_INPUT_VIEWER("LBtnOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.RBtn - UIWidgets::EnhancementCheckbox("Show R-Button Layers", CVAR_INPUT_VIEWER("RBtn"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show R-Button Layers", CVAR_INPUT_VIEWER("RBtn"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("RBtn"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("RBtnOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##RBtnOutline", CVAR_INPUT_VIEWER("RBtnOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.ZBtn - UIWidgets::EnhancementCheckbox("Show Z-Button Layers", CVAR_INPUT_VIEWER("ZBtn"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show Z-Button Layers", CVAR_INPUT_VIEWER("ZBtn"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("ZBtn"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("ZBtnOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##ZBtnOutline", CVAR_INPUT_VIEWER("ZBtnOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.StartBtn - UIWidgets::EnhancementCheckbox("Show Start Button Layers", CVAR_INPUT_VIEWER("StartBtn"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, true); + CVarCheckbox("Show Start Button Layers", CVAR_INPUT_VIEWER("StartBtn"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("StartBtn"), 1)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("StartBtnOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##StartBtnOutline", CVAR_INPUT_VIEWER("StartBtnOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.Dpad - UIWidgets::EnhancementCheckbox("Show D-Pad Layers", CVAR_INPUT_VIEWER("Dpad"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, false); + CVarCheckbox("Show D-Pad Layers", CVAR_INPUT_VIEWER("Dpad"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("Dpad"), 0)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("DpadOutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##DpadOutline", CVAR_INPUT_VIEWER("DpadOutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.Mod1 - UIWidgets::EnhancementCheckbox("Show Modifier Button 1 Layers", CVAR_INPUT_VIEWER("Mod1"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, false); + CVarCheckbox("Show Modifier Button 1 Layers", CVAR_INPUT_VIEWER("Mod1"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("Mod1"), 0)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("Mod1OutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##Mmod1Outline", CVAR_INPUT_VIEWER("Mod1OutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } // gInputViewer.Mod2 - UIWidgets::EnhancementCheckbox("Show Modifier Button 2 Layers", CVAR_INPUT_VIEWER("Mod2"), false, "", - UIWidgets::CheckboxGraphics::Checkmark, false); + CVarCheckbox("Show Modifier Button 2 Layers", CVAR_INPUT_VIEWER("Mod2"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); if (useIndividualOutlines && CVarGetInteger(CVAR_INPUT_VIEWER("Mod2"), 0)) { ImGui::Indent(); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("Mod2OutlineMode"), buttonOutlineOptionsVerbose, - BUTTON_OUTLINE_NOT_PRESSED); + CVarCombobox("##Mod2Outline", CVAR_INPUT_VIEWER("Mod2OutlineMode"), buttonOutlineOptionsVerbose, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(BUTTON_OUTLINE_NOT_PRESSED)); ImGui::Unindent(); } - UIWidgets::PaddedSeparator(true, true); + UIWidgets:PaddedSeparator(true, true); } if (ImGui::CollapsingHeader("Analog Stick")) { // gInputViewer.AnalogStick.VisibilityMode - UIWidgets::PaddedText("Analog Stick Visibility", true, false); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("AnalogStick.VisibilityMode"), stickModeOptions, - STICK_MODE_ALWAYS_SHOWN); - UIWidgets::Tooltip( - "Determines the conditions under which the moving layer of the analog stick texture is visible."); + CVarCombobox("Analog Stick Visibility", CVAR_INPUT_VIEWER("AnalogStick.VisibilityMode"), stickModeOptions, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(STICK_MODE_ALWAYS_SHOWN) + .Tooltip("Determines the conditions under which the moving layer of the analog stick texture is visible.")); // gInputViewer.AnalogStick.OutlineMode - UIWidgets::PaddedText("Analog Stick Outline/Background Visibility", true, false); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("AnalogStick.OutlineMode"), stickModeOptions, - STICK_MODE_ALWAYS_SHOWN); - UIWidgets::Tooltip( - "Determines the conditions under which the analog stick outline/background texture is visible."); + CVarCombobox("Analog Stick Outline/Background Visibility", CVAR_INPUT_VIEWER("AnalogStick.OutlineMode"), stickModeOptions, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(STICK_MODE_ALWAYS_SHOWN) + .Tooltip("Determines the conditions under which the analog stick outline/background texture is visible.")); // gInputViewer.AnalogStick.Movement - UIWidgets::EnhancementSliderInt("Analog Stick Movement: %dpx", "##AnalogMovement", - CVAR_INPUT_VIEWER("AnalogStick.Movement"), 0, 200, "", 12, true); - UIWidgets::Tooltip( - "Sets the distance to move the analog stick in the input viewer. Useful for custom input viewers."); + CVarSliderInt("Analog Stick Movement: %dpx", CVAR_INPUT_VIEWER("AnalogStick.Movement"), IntSliderOptions().Color(THEME_COLOR).Min(0).Max(200).DefaultValue(12).ShowButtons(true) + .Tooltip("Sets the distance to move the analog stick in the input viewer. Useful for custom input viewers.")); UIWidgets::PaddedSeparator(true, true); } if (ImGui::CollapsingHeader("Additional (\"Right\") Stick")) { // gInputViewer.RightStick.VisibilityMode - UIWidgets::PaddedText("Right Stick Visibility", true, false); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("RightStick.VisibilityMode"), stickModeOptions, - STICK_MODE_HIDDEN_IN_DEADZONE); - UIWidgets::Tooltip( - "Determines the conditions under which the moving layer of the right stick texture is visible."); + CVarCombobox("Right Stick Visibility", CVAR_INPUT_VIEWER("RightStick.VisibilityMode"), stickModeOptions, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(STICK_MODE_ALWAYS_SHOWN) + .Tooltip("Determines the conditions under which the moving layer of the right stick texture is visible.")); // gInputViewer.RightStick.OutlineMode - UIWidgets::PaddedText("Right Stick Outline/Background Visibility", true, false); - UIWidgets::EnhancementCombobox(CVAR_INPUT_VIEWER("RightStick.OutlineMode"), stickModeOptions, - STICK_MODE_HIDDEN_IN_DEADZONE); - UIWidgets::Tooltip( - "Determines the conditions under which the right stick outline/background texture is visible."); + CVarCombobox("Right Stick Outline/Background Visibility", CVAR_INPUT_VIEWER("RightStick.OutlineMode"), stickModeOptions, + ComboboxOptions().Color(THEME_COLOR).DefaultIndex(STICK_MODE_ALWAYS_SHOWN) + .Tooltip("Determines the conditions under which the right stick outline/background texture is visible.")); // gInputViewer.RightStick.Movement - UIWidgets::EnhancementSliderInt("Right Stick Movement: %dpx", "##RightMovement", - CVAR_INPUT_VIEWER("RightStick.Movement"), 0, 200, "", 7, true); - UIWidgets::Tooltip( - "Sets the distance to move the right stick in the input viewer. Useful for custom input viewers."); + CVarSliderInt("Right Stick Movement: %dpx", CVAR_INPUT_VIEWER("RightStick.Movement"), IntSliderOptions().Color(THEME_COLOR).Min(0).Max(200).DefaultValue(7).ShowButtons(true) + .Tooltip("Sets the distance to move the right stick in the input viewer. Useful for custom input viewers.")); UIWidgets::PaddedSeparator(true, true); } if (ImGui::CollapsingHeader("Analog Angle Values")) { // gAnalogAngles - UIWidgets::EnhancementCheckbox("Show Analog Stick Angle Values", CVAR_INPUT_VIEWER("AnalogAngles.Enabled")); - UIWidgets::Tooltip("Displays analog stick angle values in the input viewer"); + CVarCheckbox("Show Analog Stick Angle Values", CVAR_INPUT_VIEWER("AnalogAngles.Enabled"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Displays analog stick angle values in the input viewer")); if (CVarGetInteger(CVAR_INPUT_VIEWER("AnalogAngles.Enabled"), 0)) { // gInputViewer.AnalogAngles.TextColor - if (ImGui::ColorEdit4("Text Color", (float*)&textColor)) { - CVarSetColor(CVAR_INPUT_VIEWER("AnalogAngles.TextColor"), vec2Color(textColor)); - } + CVarColorPicker("Text Color", CVAR_INPUT_VIEWER("AnalogAngles.TextColor"), textColorDefault, + true, ColorPickerRandomButton | ColorPickerResetButton); // gAnalogAngleScale - UIWidgets::EnhancementSliderFloat("Angle Text Scale: %.2f%%", "##AnalogAngleScale", - CVAR_INPUT_VIEWER("AnalogAngles.Scale"), 0.1f, 5.0f, "", 1.0f, true, true); + CVarSliderFloat("Angle Text Scale: %.2f%%", CVAR_INPUT_VIEWER("AnalogAngles.Scale"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.1f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); // gInputViewer.AnalogAngles.Offset - UIWidgets::EnhancementSliderInt("Angle Text Offset: %dpx", "##AnalogAngleOffset", - CVAR_INPUT_VIEWER("AnalogAngles.Offset"), 0, 400, "", 0, true); + CVarSliderInt("Angle Text Offset: %dpx", CVAR_INPUT_VIEWER("AnalogAngles.Offset"), IntSliderOptions().Color(THEME_COLOR).Min(0).Max(400).DefaultValue(0).ShowButtons(true) + .Tooltip("Sets the distance to move the right stick in the input viewer. Useful for custom input viewers.")); UIWidgets::PaddedSeparator(true, true); // gInputViewer.AnalogAngles.Range1.Enabled - UIWidgets::EnhancementCheckbox("Highlight ESS Position", CVAR_INPUT_VIEWER("AnalogAngles.Range1.Enabled")); - UIWidgets::Tooltip( - "Highlights the angle value text when the analog stick is in ESS position (on flat ground)"); + CVarCheckbox("Highlight ESS Position", CVAR_INPUT_VIEWER("AnalogAngles.Range1.Enabled"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Highlights the angle value text when the analog stick is in ESS position (on flat ground)")); if (CVarGetInteger(CVAR_INPUT_VIEWER("AnalogAngles.Range1.Enabled"), 0)) { // gInputViewer.AnalogAngles.Range1.Color - if (ImGui::ColorEdit4("ESS Color", (float*)&range1Color)) { - CVarSetColor(CVAR_INPUT_VIEWER("AnalogAngles.Range1.Color"), vec2Color(range1Color)); - } + CVarColorPicker("ESS Color", CVAR_INPUT_VIEWER("AnalogAngles.Range1.Color"), range1ColorDefault, + true, ColorPickerRandomButton | ColorPickerResetButton); } UIWidgets::PaddedSeparator(true, true); // gInputViewer.AnalogAngles.Range2.Enabled - UIWidgets::EnhancementCheckbox("Highlight Walking Speed Angles", - CVAR_INPUT_VIEWER("AnalogAngles.Range2.Enabled")); - UIWidgets::Tooltip("Highlights the angle value text when the analog stick is at an angle that would " + CVarCheckbox("Highlight Walking Speed Angles", CVAR_INPUT_VIEWER("AnalogAngles.Range2.Enabled"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Highlights the angle value text when the analog stick is at an angle that would " "produce a walking speed (on flat ground)\n\n" - "Useful for 1.0 Empty Jumpslash Quick Put Away"); + "Useful for 1.0 Empty Jumpslash Quick Put Away")); if (CVarGetInteger(CVAR_INPUT_VIEWER("AnalogAngles.Range2.Enabled"), 0)) { // gInputViewer.AnalogAngles.Range2.Color - if (ImGui::ColorEdit4("Walking Speed Color", (float*)&range2Color)) { - CVarSetColor(CVAR_INPUT_VIEWER("AnalogAngles.Range2.Color"), vec2Color(range2Color)); - } + CVarColorPicker("Walking Speed Color", CVAR_INPUT_VIEWER("AnalogAngles.Range2.Color"), range2ColorDefault, + true, ColorPickerRandomButton | ColorPickerResetButton); } } } + PopStyleHeader(); + ImGui::PopFont(); } diff --git a/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp b/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp index 7e4918799..135cb8a00 100644 --- a/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp +++ b/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp @@ -2,6 +2,7 @@ #include #include "soh/OTRGlobals.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "z64.h" #include "soh/cvar_prefixes.h" #ifndef __WIIU__ @@ -10,6 +11,8 @@ #define SCALE_IMGUI_SIZE(value) ((value / 13.0f) * ImGui::GetFontSize()) +using namespace UIWidgets; + SohInputEditorWindow::~SohInputEditorWindow() { } @@ -1062,17 +1065,16 @@ void SohInputEditorWindow::DrawLEDSection(uint8_t port) { } // todo: clean this up, probably just hardcode to LED_COLOR_SOURCE_GAME and use SoH options only here if (mapping->GetColorSource() == LED_COLOR_SOURCE_GAME) { - static const char* ledSources[] = { + static std::vector ledSources = { "Original Tunic Colors", "Cosmetics Tunic Colors", "Health Colors", "Original Navi Targeting Colors", "Cosmetics Navi Targeting Colors", "Custom" }; - UIWidgets::PaddedText("Source"); - UIWidgets::EnhancementCombobox(CVAR_SETTING("LEDColorSource"), ledSources, LED_SOURCE_TUNIC_ORIGINAL); - UIWidgets::Tooltip("Health\n- Red when health critical (13-20% depending on max health)\n- Yellow when " + CVarCombobox("Source", CVAR_SETTING("LEDColorSource"), ledSources, UIWidgets::ComboboxOptions().Color(THEME_COLOR).DefaultIndex(LED_SOURCE_TUNIC_ORIGINAL) + .Tooltip("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"); + "Custom: single, solid color")); if (CVarGetInteger(CVAR_SETTING("LEDColorSource"), 1) == LED_SOURCE_CUSTOM) { UIWidgets::Spacer(3); auto port1Color = CVarGetColor24(CVAR_SETTING("LEDPort1Color"), { 255, 255, 255 }); @@ -1090,14 +1092,13 @@ void SohInputEditorWindow::DrawLEDSection(uint8_t port) { ImGui::SameLine(); ImGui::Text("Custom Color"); } - UIWidgets::PaddedEnhancementSliderFloat("Brightness: %.1f %%", "##LED_Brightness", CVAR_SETTING("LEDBrightness"), 0.0f, - 1.0f, "", 1.0f, true, true); - UIWidgets::Tooltip("Sets the brightness of controller LEDs. 0% brightness = LEDs off."); - UIWidgets::PaddedEnhancementCheckbox( - "Critical Health Override", CVAR_SETTING("LEDCriticalOverride"), true, true, - CVarGetInteger(CVAR_SETTING("LEDColorSource"), LED_SOURCE_TUNIC_ORIGINAL) == LED_SOURCE_HEALTH, - "Override redundant for health source.", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Shows red color when health is critical, otherwise displays according to color source."); + CVarSliderFloat("Brightness: %.1f %%", CVAR_SETTING("LEDBrightness"), + FloatSliderOptions().IsPercentage().Min(0.0f).Max(1.0f).DefaultValue(1.0f).ShowButtons(true) + .Tooltip("Sets the brightness of controller LEDs. 0% brightness = LEDs off.")); + CVarCheckbox("Critical Health Override", CVAR_SETTING("LEDCriticalOverride"), + CheckboxOptions({{ .disabled = CVarGetInteger(CVAR_SETTING("LEDColorSource"), LED_SOURCE_TUNIC_ORIGINAL) == LED_SOURCE_HEALTH, + .disabledTooltip = "Override redundant for health source."}}).DefaultValue(true) + .Tooltip("Shows red color when health is critical, otherwise displays according to color source.")); } ImGui::TreePop(); } @@ -1281,7 +1282,6 @@ void SohInputEditorWindow::DrawMapping(CustomButtonMap& mapping, float labelWidt preview = "Unknown"; } - UIWidgets::Spacer(0); ImVec2 cursorPos = ImGui::GetCursorPos(); ImVec2 textSize = ImGui::CalcTextSize(mapping.label); ImGui::SetCursorPosY(cursorPos.y + textSize.y / 4); @@ -1303,16 +1303,16 @@ void SohInputEditorWindow::DrawMapping(CustomButtonMap& mapping, float labelWidt } ImGui::EndCombo(); } - UIWidgets::Spacer(0); } void SohInputEditorWindow::DrawOcarinaControlPanel() { ImVec2 cursor = ImGui::GetCursorPos(); ImGui::SetCursorPos(ImVec2(cursor.x, cursor.y + 5)); - UIWidgets::EnhancementCheckbox("Dpad Ocarina Playback", CVAR_SETTING("CustomOcarina.Dpad")); - UIWidgets::EnhancementCheckbox("Right Stick Ocarina Playback", CVAR_SETTING("CustomOcarina.RightStick")); - UIWidgets::EnhancementCheckbox("Customize Ocarina Controls", CVAR_SETTING("CustomOcarina.Enabled")); + CheckboxOptions checkOpt = CheckboxOptions().Color(THEME_COLOR); + CVarCheckbox("Dpad Ocarina Playback", CVAR_SETTING("CustomOcarina.Dpad"), checkOpt); + CVarCheckbox("Right Stick Ocarina Playback", CVAR_SETTING("CustomOcarina.RightStick"), checkOpt); + CVarCheckbox("Customize Ocarina Controls", CVAR_SETTING("CustomOcarina.Enabled"), checkOpt); if (!CVarGetInteger(CVAR_SETTING("CustomOcarina.Enabled"), 0)) { ImGui::BeginDisabled(); @@ -1344,60 +1344,56 @@ void SohInputEditorWindow::DrawCameraControlPanel() { ImVec2 cursor = ImGui::GetCursorPos(); ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); Ship::GuiWindow::BeginGroupPanel("Aiming/First-Person Camera", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("Right Stick Aiming", CVAR_SETTING("Controls.RightStickAim")); - UIWidgets::Tooltip("Allows for aiming with the right stick in:\n-First-Person/C-Up view\n-Weapon Aiming"); + CVarCheckbox("Right Stick Aiming", CVAR_SETTING("Controls.RightStickAim"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Allows for aiming with the right stick in:\n-First-Person/C-Up view\n-Weapon Aiming")); if (CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0)) { - UIWidgets::PaddedEnhancementCheckbox("Allow moving while in first person mode", CVAR_SETTING("MoveInFirstPerson")); - UIWidgets::Tooltip("Changes the left stick to move the player while in first person mode"); + CVarCheckbox("Allow moving while in first person mode", CVAR_SETTING("MoveInFirstPerson"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Changes the left stick to move the player while in first person mode")); } - UIWidgets::PaddedEnhancementCheckbox("Invert Aiming X Axis", CVAR_SETTING("Controls.InvertAimingXAxis")); - UIWidgets::Tooltip("Inverts the Camera X Axis in:\n-First-Person/C-Up view\n-Weapon Aiming"); - UIWidgets::PaddedEnhancementCheckbox("Invert Aiming Y Axis", CVAR_SETTING("Controls.InvertAimingYAxis"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Inverts the Camera Y Axis in:\n-First-Person/C-Up view\n-Weapon Aiming"); - UIWidgets::PaddedEnhancementCheckbox("Invert Shield Aiming X Axis", CVAR_SETTING("Controls.InvertShieldAimingXAxis"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Inverts the Shield Aiming X Axis"); - UIWidgets::PaddedEnhancementCheckbox("Invert Shield Aiming Y Axis", CVAR_SETTING("Controls.InvertShieldAimingYAxis")); - UIWidgets::Tooltip("Inverts the Shield Aiming Y Axis"); - UIWidgets::PaddedEnhancementCheckbox("Invert Z-Weapon Aiming Y Axis", CVAR_SETTING("Controls.InvertZAimingYAxis"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Inverts the Camera Y Axis in:\n-Z-Weapon Aiming"); - UIWidgets::PaddedEnhancementCheckbox("Disable Auto-Centering in First-Person View", CVAR_SETTING("DisableFirstPersonAutoCenterView")); - UIWidgets::Tooltip("Prevents the C-Up view from auto-centering, allowing for Gyro Aiming"); - if (UIWidgets::PaddedEnhancementCheckbox("Enable Custom Aiming/First-Person sensitivity", CVAR_SETTING("FirstPersonCameraSensitivity.Enabled"), true, false)) { + CVarCheckbox("Invert Aiming X Axis", CVAR_SETTING("Controls.InvertAimingXAxis"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Inverts the Camera X Axis in:\n-First-Person/C-Up view\n-Weapon Aiming")); + CVarCheckbox("Invert Aiming Y Axis", CVAR_SETTING("Controls.InvertAimingYAxis"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true) + .Tooltip("Inverts the Camera Y Axis in:\n-First-Person/C-Up view\n-Weapon Aiming")); + CVarCheckbox("Invert Shield Aiming X Axis", CVAR_SETTING("Controls.InvertShieldAimingXAxis"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true) + .Tooltip("Inverts the Shield Aiming X Axis")); + CVarCheckbox("Invert Shield Aiming Y Axis", CVAR_SETTING("Controls.InvertShieldAimingYAxis"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Inverts the Shield Aiming Y Axis")); + CVarCheckbox("Invert Z-Weapon Aiming Y Axis", CVAR_SETTING("Controls.InvertZAimingYAxis"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true) + .Tooltip("Inverts the Camera Y Axis in:\n-Z-Weapon Aiming")); + CVarCheckbox("Disable Auto-Centering in First-Person View", CVAR_SETTING("DisableFirstPersonAutoCenterView"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Prevents the C-Up view from auto-centering, allowing for Gyro Aiming")); + if (CVarCheckbox("Enable Custom Aiming/First-Person sensitivity", CVAR_SETTING("FirstPersonCameraSensitivity.Enabled"), CheckboxOptions().Color(THEME_COLOR))) { if (!CVarGetInteger(CVAR_SETTING("FirstPersonCameraSensitivity.Enabled"), 0)) { CVarClear(CVAR_SETTING("FirstPersonCameraSensitivity.X")); CVarClear(CVAR_SETTING("FirstPersonCameraSensitivity.Y")); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } } if (CVarGetInteger(CVAR_SETTING("FirstPersonCameraSensitivity.Enabled"), 0)) { - UIWidgets::EnhancementSliderFloat("Aiming/First-Person Horizontal Sensitivity: %.0f %%", "##FirstPersonSensitivity Horizontal", - CVAR_SETTING("FirstPersonCameraSensitivity.X"), 0.01f, 5.0f, "", 1.0f, true); - UIWidgets::EnhancementSliderFloat("Aiming/First-Person Vertical Sensitivity: %.0f %%", "##FirstPersonSensitivity Vertical", - CVAR_SETTING("FirstPersonCameraSensitivity.Y"), 0.01f, 5.0f, "", 1.0f, true); + CVarSliderFloat("Aiming/First-Person Horizontal Sensitivity: %.0f %%", CVAR_SETTING("FirstPersonCameraSensitivity.X"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.01f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); + CVarSliderFloat("Aiming/First-Person Vertical Sensitivity: %.0f %%", CVAR_SETTING("FirstPersonCameraSensitivity.Y"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.01f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); } - UIWidgets::Spacer(0); Ship::GuiWindow::EndGroupPanel(0); - UIWidgets::Spacer(0); cursor = ImGui::GetCursorPos(); ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); Ship::GuiWindow::BeginGroupPanel("Third-Person Camera", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("Free Look", CVAR_SETTING("FreeLook.Enabled")); - UIWidgets::Tooltip("Enables free look camera control\nNote: You must remap C buttons off of the right stick in the " - "controller config menu, and map the camera stick to the right stick."); - UIWidgets::PaddedEnhancementCheckbox("Invert Camera X Axis", CVAR_SETTING("FreeLook.InvertXAxis")); - UIWidgets::Tooltip("Inverts the Camera X Axis in:\n-Free look"); - UIWidgets::PaddedEnhancementCheckbox("Invert Camera Y Axis", CVAR_SETTING("FreeLook.InvertYAxis"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Inverts the Camera Y Axis in:\n-Free look"); - UIWidgets::Spacer(0); - UIWidgets::PaddedEnhancementSliderFloat("Third-Person Horizontal Sensitivity: %.0f %%", "##ThirdPersonSensitivity Horizontal", - CVAR_SETTING("FreeLook.CameraSensitivity.X"), 0.01f, 5.0f, "", 1.0f, true, true, false, true); - UIWidgets::PaddedEnhancementSliderFloat("Third-Person Vertical Sensitivity: %.0f %%", "##ThirdPersonSensitivity Vertical", - CVAR_SETTING("FreeLook.CameraSensitivity.Y"), 0.01f, 5.0f, "", 1.0f, true, true, false, true); - UIWidgets::PaddedEnhancementSliderInt("Camera Distance: %d", "##CamDist", - CVAR_SETTING("FreeLook.MaxCameraDistance"), 100, 900, "", 185, true, false, true); - UIWidgets::PaddedEnhancementSliderInt("Camera Transition Speed: %d", "##CamTranSpeed", - CVAR_SETTING("FreeLook.TransitionSpeed"), 0, 900, "", 25, true, false, true); + CVarCheckbox("Free Look", CVAR_SETTING("FreeLook.Enabled"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Enables free look camera control\nNote: You must remap C buttons off of the right stick in the " + "controller config menu, and map the camera stick to the right stick.")); + CVarCheckbox("Invert Camera X Axis", CVAR_SETTING("FreeLook.InvertXAxis"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Inverts the Camera X Axis in:\n-Free look")); + CVarCheckbox("Invert Camera Y Axis", CVAR_SETTING("FreeLook.InvertYAxis"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true) + .Tooltip("Inverts the Camera Y Axis in:\n-Free look")); + CVarSliderFloat("Third-Person Horizontal Sensitivity: %.0f %%", CVAR_SETTING("FreeLook.CameraSensitivity.X"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.01f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); + CVarSliderFloat("Third-Person Vertical Sensitivity: %.0f %%", CVAR_SETTING("FreeLook.CameraSensitivity.Y"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.01f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); + CVarSliderInt("Camera Distance: %d", CVAR_SETTING("FreeLook.MaxCameraDistance"), IntSliderOptions().Color(THEME_COLOR).Min(100).Max(900).DefaultValue(185).ShowButtons(true)); + CVarSliderInt("Camera Transition Speed: %d", CVAR_SETTING("FreeLook.TransitionSpeed"), IntSliderOptions().Color(THEME_COLOR).Min(0).Max(900).DefaultValue(25).ShowButtons(true)); Ship::GuiWindow::EndGroupPanel(0); } @@ -1405,17 +1401,17 @@ void SohInputEditorWindow::DrawDpadControlPanel() { ImVec2 cursor = ImGui::GetCursorPos(); ImGui::SetCursorPos(ImVec2(cursor.x + 5, cursor.y + 5)); Ship::GuiWindow::BeginGroupPanel("D-Pad Options", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("D-pad Support on Pause Screen", CVAR_SETTING("DPadOnPause")); - UIWidgets::Tooltip("Navigate Pause with the D-pad\nIf used with \"D-pad as Equip Items\", you must hold C-Up to equip instead of navigate"); - UIWidgets::PaddedEnhancementCheckbox("D-pad Support in Text Boxes", CVAR_SETTING("DpadInText")); - UIWidgets::Tooltip("Navigate choices in text boxes, shop item selection, and the file select / name entry screens with the D-pad"); + CVarCheckbox("D-pad Support on Pause Screen", CVAR_SETTING("DPadOnPause"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Navigate Pause with the D-pad\nIf used with \"D-pad as Equip Items\", you must hold C-Up to equip instead of navigate")); + CVarCheckbox("D-pad Support in Text Boxes", CVAR_SETTING("DpadInText"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Navigate choices in text boxes, shop item selection, and the file select / name entry screens with the D-pad")); if (!CVarGetInteger(CVAR_SETTING("DPadOnPause"), 0) && !CVarGetInteger(CVAR_SETTING("DpadInText"), 0)) { ImGui::BeginDisabled(); } - UIWidgets::PaddedEnhancementCheckbox("D-pad hold change", CVAR_SETTING("DpadHoldChange"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("The cursor will only move a single space no matter how long a D-pad direction is held"); + CVarCheckbox("D-pad hold change", CVAR_SETTING("DpadHoldChange"), CheckboxOptions().Color(THEME_COLOR).DefaultValue(true) + .Tooltip("The cursor will only move a single space no matter how long a D-pad direction is held")); if (!CVarGetInteger(CVAR_SETTING("DPadOnPause"), 0) && !CVarGetInteger(CVAR_SETTING("DpadInText"), 0)) { ImGui::EndDisabled(); @@ -1536,30 +1532,24 @@ void SohInputEditorWindow::DrawLinkTab() { DrawButtonLine("M2", portIndex, BTN_CUSTOM_MODIFIER2); ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); - UIWidgets::PaddedEnhancementCheckbox("Enable speed modifiers", CVAR_SETTING("WalkModifier.Enabled"), true, false); - UIWidgets::Tooltip("Hold the assigned button to change the maximum walking or swimming speed"); + CVarCheckbox("Enable speed modifiers", CVAR_SETTING("WalkModifier.Enabled"), CheckboxOptions().Color(THEME_COLOR) + .Tooltip("Hold the assigned button to change the maximum walking or swimming speed")); if (CVarGetInteger(CVAR_SETTING("WalkModifier.Enabled"), 0)) { UIWidgets::Spacer(5); Ship::GuiWindow::BeginGroupPanel("Speed Modifier", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("Toggle modifier instead of holding", - CVAR_SETTING("WalkModifier.SpeedToggle"), true, false); + CVarCheckbox("Toggle modifier instead of holding", CVAR_SETTING("WalkModifier.SpeedToggle"), CheckboxOptions().Color(THEME_COLOR)); Ship::GuiWindow::BeginGroupPanel("Walk Modifier", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementCheckbox("Don't affect jump distance/velocity", - CVAR_SETTING("WalkModifier.DoesntChangeJump"), true, false); - UIWidgets::PaddedEnhancementSliderFloat("Walk Modifier 1: %.0f %%", "##WalkMod1", - CVAR_SETTING("WalkModifier.Mapping1"), 0.0f, 5.0f, "", 1.0f, - true, true, false, true); - UIWidgets::PaddedEnhancementSliderFloat("Walk Modifier 2: %.0f %%", "##WalkMod2", - CVAR_SETTING("WalkModifier.Mapping2"), 0.0f, 5.0f, "", 1.0f, - true, true, false, true); + CVarCheckbox("Don't affect jump distance/velocity", CVAR_SETTING("WalkModifier.DoesntChangeJump"), CheckboxOptions().Color(THEME_COLOR)); + CVarSliderFloat("Walk Modifier 1: %.0f %%", CVAR_SETTING("WalkModifier.Mapping1"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.0f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); + CVarSliderFloat("Walk Modifier 2: %.0f %%", CVAR_SETTING("WalkModifier.Mapping2"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.0f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); Ship::GuiWindow::EndGroupPanel(0); Ship::GuiWindow::BeginGroupPanel("Swim Modifier", ImGui::GetContentRegionAvail()); - UIWidgets::PaddedEnhancementSliderFloat("Swim Modifier 1: %.0f %%", "##SwimMod1", - CVAR_SETTING("WalkModifier.SwimMapping1"), 0.0f, 5.0f, "", 1.0f, - true, true, false, true); - UIWidgets::PaddedEnhancementSliderFloat("Swim Modifier 2: %.0f %%", "##SwimMod2", - CVAR_SETTING("WalkModifier.SwimMapping2"), 0.0f, 5.0f, "", 1.0f, - true, true, false, true); + CVarSliderFloat("Swim Modifier 1: %.0f %%", CVAR_SETTING("WalkModifier.SwimMapping1"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.0f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); + CVarSliderFloat("Swim Modifier 2: %.0f %%", CVAR_SETTING("WalkModifier.SwimMapping2"), + FloatSliderOptions().Color(THEME_COLOR).IsPercentage().Min(0.0f).Max(5.0f).DefaultValue(1.0f).ShowButtons(true)); Ship::GuiWindow::EndGroupPanel(0); Ship::GuiWindow::EndGroupPanel(0); } @@ -1659,9 +1649,7 @@ void SohInputEditorWindow::DrawDebugPortTab(uint8_t portIndex, std::string custo UpdateBitmaskToMappingIds(portIndex); UpdateStickDirectionToMappingIds(portIndex); - ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.133f, 0.133f, 0.133f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + PushStyleHeader(THEME_COLOR); if (ImGui::CollapsingHeader("Buttons", NULL, ImGuiTreeNodeFlags_DefaultOpen)) { DrawButtonLine("A", portIndex, BTN_A, CHIP_COLOR_N64_BLUE); @@ -1690,19 +1678,20 @@ void SohInputEditorWindow::DrawDebugPortTab(uint8_t portIndex, std::string custo DrawStickSection(portIndex, Ship::LEFT, 0); } - ImGui::PopStyleColor(); - ImGui::PopStyleColor(); - ImGui::PopStyleColor(); + PopStyleHeader(); ImGui::EndTabItem(); } } void SohInputEditorWindow::DrawClearAllButton(uint8_t portIndex) { + PushStyleButton(THEME_COLOR); if (ImGui::Button("Clear All", ImGui::CalcTextSize("Clear All") * 2)) { ImGui::OpenPopup("Clear All##clearAllPopup"); } + PopStyleButton(); if (ImGui::BeginPopupModal("Clear All##clearAllPopup", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("This will clear all mappings for port %d.\n\nContinue?", portIndex + 1); + PushStyleButton(THEME_COLOR); if (ImGui::Button("Cancel")) { ImGui::CloseCurrentPopup(); } @@ -1710,6 +1699,7 @@ void SohInputEditorWindow::DrawClearAllButton(uint8_t portIndex) { Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(portIndex)->ClearAllMappings(); ImGui::CloseCurrentPopup(); } + PopStyleButton(); ImGui::EndPopup(); } } @@ -1717,22 +1707,23 @@ void SohInputEditorWindow::DrawClearAllButton(uint8_t portIndex) { void SohInputEditorWindow::DrawSetDefaultsButton(uint8_t portIndex) { ImGui::SameLine(); auto popupId = StringHelper::Sprintf("setDefaultsPopup##%d", portIndex); + PushStyleButton(THEME_COLOR); if (ImGui::Button(StringHelper::Sprintf("Set Defaults##%d", portIndex).c_str(), ImVec2(ImGui::CalcTextSize("Set Defaults") * 2))) { ImGui::OpenPopup(popupId.c_str()); } + PopStyleButton(); if (ImGui::BeginPopup(popupId.c_str())) { bool shouldClose = false; - ImGui::PushStyleColor(ImGuiCol_Button, BUTTON_COLOR_KEYBOARD_BEIGE); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, BUTTON_COLOR_KEYBOARD_BEIGE_HOVERED); + PushStyleButton(BUTTON_COLOR_KEYBOARD_BEIGE); if (ImGui::Button(StringHelper::Sprintf("%s Keyboard", ICON_FA_KEYBOARD_O).c_str())) { ImGui::OpenPopup("Set Defaults for Keyboard"); } - ImGui::PopStyleColor(); - ImGui::PopStyleColor(); + PopStyleButton(); if (ImGui::BeginPopupModal("Set Defaults for Keyboard", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("This will clear all existing mappings for\nKeyboard on port %d.\n\nContinue?", portIndex + 1); + PushStyleButton(THEME_COLOR); if (ImGui::Button("Cancel")) { shouldClose = true; ImGui::CloseCurrentPopup(); @@ -1747,21 +1738,21 @@ void SohInputEditorWindow::DrawSetDefaultsButton(uint8_t portIndex) { shouldClose = true; ImGui::CloseCurrentPopup(); } + PopStyleButton(); ImGui::EndPopup(); } auto buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); auto buttonHoveredColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); GetButtonColorsForDeviceType(Ship::PhysicalDeviceType::SDLGamepad, buttonColor, buttonHoveredColor); - ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); + PushStyleButton(buttonColor); if (ImGui::Button(StringHelper::Sprintf("%s %s", ICON_FA_GAMEPAD, "Gamepad (SDL)").c_str())) { ImGui::OpenPopup("Set Defaults for Gamepad (SDL)"); } - ImGui::PopStyleColor(); - ImGui::PopStyleColor(); + PopStyleButton(); if (ImGui::BeginPopupModal("Set Defaults for Gamepad (SDL)", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("This will clear all existing mappings for\nGamepad (SDL) on port %d.\n\nContinue?", portIndex + 1); + PushStyleButton(THEME_COLOR); if (ImGui::Button("Cancel")) { shouldClose = true; ImGui::CloseCurrentPopup(); @@ -1776,12 +1767,15 @@ void SohInputEditorWindow::DrawSetDefaultsButton(uint8_t portIndex) { shouldClose = true; ImGui::CloseCurrentPopup(); } + PopStyleButton(); ImGui::EndPopup(); } + PushStyleButton(THEME_COLOR); if (ImGui::Button("Cancel") || shouldClose) { ImGui::CloseCurrentPopup(); } + PopStyleButton(); ImGui::EndPopup(); } diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp index 16fb2bed4..93c866eb3 100644 --- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp +++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp @@ -11,6 +11,7 @@ #include #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" @@ -92,6 +93,7 @@ std::map groupLabels = { typedef struct { const char* cvar; + const char* valuesCvar; const char* rainbowCvar; const char* lockedCvar; const char* changedCvar; @@ -111,7 +113,8 @@ Color_RGBA8 ColorRGBA8(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { #define COSMETIC_OPTION(id, label, group, defaultColor, supportsAlpha, supportsRainbow, advancedOption) \ { id, { \ - CVAR_COSMETIC(id ".Value"), CVAR_COSMETIC(id ".Rainbow"), CVAR_COSMETIC(id ".Locked"), CVAR_COSMETIC(id ".Changed"), label, group, \ + CVAR_COSMETIC(id), CVAR_COSMETIC(id ".Value"), CVAR_COSMETIC(id ".Rainbow"), CVAR_COSMETIC(id ".Locked"), \ + CVAR_COSMETIC(id ".Changed"), label, group, \ ImVec4(defaultColor.r / 255.0f, defaultColor.g / 255.0f, defaultColor.b / 255.0f, defaultColor.a / 255.0f), defaultColor, \ supportsAlpha, supportsRainbow, advancedOption \ } } @@ -313,7 +316,7 @@ static std::map cosmeticOptions = { #define MESSAGE_COSMETIC_OPTION(id, label, r, g, b) COSMETIC_OPTION("Message." id, label, COSMETICS_GROUP_MESSAGE, ColorRGBA8(r, g, b, 255), false, true, true) MESSAGE_COSMETIC_OPTION("Default.Normal", "Message Default Color", 255, 255, 255), - MESSAGE_COSMETIC_OPTION("Default.NoneNoShadow", "Message Default (None No Shadow) Color", 0, 0, 0), + MESSAGE_COSMETIC_OPTION("Default.NoneNoShadow", "Message Default (None No Shadow)", 0, 0, 0), MESSAGE_COSMETIC_OPTION("Red.Normal", "Message Red Color", 255, 60, 60), MESSAGE_COSMETIC_OPTION("Red.Wooden", "Message Red (Wooden) Color", 255, 120, 0), MESSAGE_COSMETIC_OPTION("Adjustable.Normal", "Message Adjustable Color", 70, 255, 80), @@ -322,7 +325,7 @@ static std::map cosmeticOptions = { MESSAGE_COSMETIC_OPTION("Blue.Wooden", "Message Blue (Wooden) Color", 80, 110, 255), MESSAGE_COSMETIC_OPTION("LightBlue.Normal", "Message Light Blue Color", 100, 180, 255), MESSAGE_COSMETIC_OPTION("LightBlue.Wooden", "Message Light Blue (Wooden) Color", 90, 180, 255), - MESSAGE_COSMETIC_OPTION("LightBlue.LightBlue.NoneNoShadow", "Message Light Blue (None No Shadow) Color", 80, 150, 180), + MESSAGE_COSMETIC_OPTION("LightBlue.LightBlue.NoneNoShadow", "Message Light Blue (None No Shadow)", 80, 150, 180), MESSAGE_COSMETIC_OPTION("Purple.Normal", "Message Purple Color", 255, 150, 180), MESSAGE_COSMETIC_OPTION("Purple.Wooden", "Message Purple (Wooden) Color", 210, 100, 255), MESSAGE_COSMETIC_OPTION("Yellow.Normal", "Message Yellow Color", 255, 255, 50), @@ -469,41 +472,38 @@ static const char* MarginCvarNonAnchor[] { CVAR_COSMETIC("HUD.TitleCard.Boss") }; -ImVec4 GetRandomValue() { -#if !defined(__SWITCH__) && !defined(__WIIU__) - std::random_device rd; - std::mt19937 rng(rd()); -#else - size_t seed = std::hash{}(std::to_string(rand())); - std::mt19937_64 rng(seed); -#endif - std::uniform_int_distribution dist(0, 255 - 1); - - ImVec4 NewColor; - NewColor.x = (float)(dist(rng)) / 255.0f; - NewColor.y = (float)(dist(rng)) / 255.0f; - NewColor.z = (float)(dist(rng)) / 255.0f; - return NewColor; -} - -void SetMarginAll(const char* ButtonName, bool SetActivated) { - if (ImGui::Button(ButtonName)) { - //MarginCvarNonAnchor is an array that list every element that has No anchor by default, because if that the case this function will not touch it with pose type 0. +void SetMarginAll(const char* ButtonName, bool SetActivated, const char* tooltip) { + if (UIWidgets::Button(ButtonName, UIWidgets::ButtonOptions() + .Size(ImVec2(200.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip(tooltip))) { + // MarginCvarNonAnchor is an array that list every element that has No anchor by default, because if that the + // case this function will not touch it with pose type 0. u8 arrayLengthNonMargin = sizeof(MarginCvarNonAnchor) / sizeof(*MarginCvarNonAnchor); for (auto cvarName : MarginCvarList) { std::string cvarPosType = std::string(cvarName).append(".PosType"); std::string cvarNameMargins = std::string(cvarName).append(".UseMargins"); - if (CVarGetInteger(cvarPosType.c_str(),0) <= ANCHOR_RIGHT && SetActivated) { //Our element is not Hidden or Non anchor + if (CVarGetInteger(cvarPosType.c_str(), 0) <= ANCHOR_RIGHT && + SetActivated) { // Our element is not Hidden or Non anchor for (int i = 0; i < arrayLengthNonMargin; i++) { - if ((strcmp(cvarName, MarginCvarNonAnchor[i]) == 0) && (CVarGetInteger(cvarPosType.c_str(), 0) == ORIGINAL_LOCATION)) { //Our element is both in original position and do not have anchor by default so we skip it. - CVarSetInteger(cvarNameMargins.c_str(), false); //force set off - } else if ((strcmp(cvarName, MarginCvarNonAnchor[i]) == 0) && (CVarGetInteger(cvarPosType.c_str(), 0) != ORIGINAL_LOCATION)) { //Our element is not in original position regarless it has no anchor by default since player made it anchored we can toggle margins + if ((strcmp(cvarName, MarginCvarNonAnchor[i]) == 0) && + (CVarGetInteger(cvarPosType.c_str(), 0) == + ORIGINAL_LOCATION)) { // Our element is both in original position and do not have anchor by + // default so we skip it. + CVarSetInteger(cvarNameMargins.c_str(), false); // force set off + } else if ((strcmp(cvarName, MarginCvarNonAnchor[i]) == 0) && + (CVarGetInteger(cvarPosType.c_str(), 0) != + ORIGINAL_LOCATION)) { // Our element is not in original position regarless it has no + // anchor by default since player made it anchored we can toggle + // margins CVarSetInteger(cvarNameMargins.c_str(), SetActivated); - } else if (strcmp(cvarName, MarginCvarNonAnchor[i]) != 0) { //Our elements has an anchor by default so regarless of it's position right now that okay to toggle margins. + } else if (strcmp(cvarName, MarginCvarNonAnchor[i]) != + 0) { // Our elements has an anchor by default so regarless of it's position right now + // that okay to toggle margins. CVarSetInteger(cvarNameMargins.c_str(), SetActivated); } } - } else { //Since the user requested to turn all margin off no need to do any check there. + } else { // Since the user requested to turn all margin off no need to do any check there. CVarSetInteger(cvarNameMargins.c_str(), SetActivated); } } @@ -511,12 +511,16 @@ void SetMarginAll(const char* ButtonName, bool SetActivated) { } void ResetPositionAll() { - if (ImGui::Button("Reset all positions")) { + if (UIWidgets::Button("Reset all positions", + UIWidgets::ButtonOptions() + .Size(ImVec2(200.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("Revert every element to use their original position and no margins"))) { for (auto cvarName : MarginCvarList) { std::string cvarPosType = std::string(cvarName).append(".PosType"); std::string cvarNameMargins = std::string(cvarName).append(".UseMargins"); CVarSetInteger(cvarPosType.c_str(), 0); - CVarSetInteger(cvarNameMargins.c_str(), false); //Turn margin off to everythings as that original position. + CVarSetInteger(cvarNameMargins.c_str(), false); // Turn margin off to everythings as that original position. } } } @@ -545,7 +549,7 @@ void CosmeticsUpdateTick() { cosmeticOption.currentColor.z = newColor.b / 255.0f; cosmeticOption.currentColor.w = newColor.a / 255.0f; - CVarSetColor(cosmeticOption.cvar, newColor); + CVarSetColor(cosmeticOption.valuesCvar, newColor); } // If we don't want the rainbow color on items to be synced, offset the index for each item in the loop. // Technically this would work if you replaced "60" with 1 but the hue would be so close it's @@ -573,21 +577,21 @@ void CosmeticsUpdateTick() { void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& magicFaroresPrimary = cosmeticOptions.at("Magic.FaroresPrimary"); if (manualChange || CVarGetInteger(magicFaroresPrimary.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(magicFaroresPrimary.cvar, magicFaroresPrimary.defaultColor); + Color_RGBA8 color = CVarGetColor(magicFaroresPrimary.valuesCvar, magicFaroresPrimary.defaultColor); PATCH_GFX(sInnerCylinderDL, "Magic_FaroresPrimary1", magicFaroresPrimary.changedCvar, 24, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(sOuterCylinderDL, "Magic_FaroresPrimary2", magicFaroresPrimary.changedCvar, 24, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); } static CosmeticOption& magicFaroresSecondary = cosmeticOptions.at("Magic.FaroresSecondary"); if (manualChange || CVarGetInteger(magicFaroresSecondary.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(magicFaroresSecondary.cvar, magicFaroresSecondary.defaultColor); + Color_RGBA8 color = CVarGetColor(magicFaroresSecondary.valuesCvar, magicFaroresSecondary.defaultColor); PATCH_GFX(sInnerCylinderDL, "Magic_FaroresSecondary1", magicFaroresSecondary.changedCvar, 25, gsDPSetEnvColor(color.r, color.g, color.b, 255)); PATCH_GFX(sOuterCylinderDL, "Magic_FaroresSecondary2", magicFaroresSecondary.changedCvar, 25, gsDPSetEnvColor(color.r, color.g, color.b, 255)); } static CosmeticOption& linkGoronTunic = cosmeticOptions.at("Link.GoronTunic"); if (manualChange || CVarGetInteger(linkGoronTunic.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(linkGoronTunic.cvar, linkGoronTunic.defaultColor); + Color_RGBA8 color = CVarGetColor(linkGoronTunic.valuesCvar, linkGoronTunic.defaultColor); PATCH_GFX(gGiGoronTunicColorDL, "Link_GoronTunic1", linkGoronTunic.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiGoronCollarColorDL, "Link_GoronTunic2", linkGoronTunic.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r / 2, color.g / 2, color.b / 2, 255)); PATCH_GFX(gGiGoronTunicColorDL, "Link_GoronTunic3", linkGoronTunic.changedCvar, 4, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); @@ -596,7 +600,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& linkZoraTunic = cosmeticOptions.at("Link.ZoraTunic"); if (manualChange || CVarGetInteger(linkZoraTunic.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(linkZoraTunic.cvar, linkZoraTunic.defaultColor); + Color_RGBA8 color = CVarGetColor(linkZoraTunic.valuesCvar, linkZoraTunic.defaultColor); PATCH_GFX(gGiZoraTunicColorDL, "Link_ZoraTunic1", linkZoraTunic.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiZoraCollarColorDL, "Link_ZoraTunic2", linkZoraTunic.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r / 2, color.g / 2, color.b / 2, 255)); PATCH_GFX(gGiZoraTunicColorDL, "Link_ZoraTunic3", linkZoraTunic.changedCvar, 4, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); @@ -605,7 +609,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& linkHair = cosmeticOptions.at("Link.Hair"); if (manualChange || CVarGetInteger(linkHair.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(linkHair.cvar, linkHair.defaultColor); + Color_RGBA8 color = CVarGetColor(linkHair.valuesCvar, linkHair.defaultColor); PATCH_GFX(gLinkChildHeadNearDL, "Link_Hair1", linkHair.changedCvar, 10, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkChildHeadFarDL, "Link_Hair2", linkHair.changedCvar, 10, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultHeadNearDL, "Link_Hair3", linkHair.changedCvar, 10, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); @@ -627,7 +631,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& linkLinen = cosmeticOptions.at("Link.Linen"); if (manualChange || CVarGetInteger(linkLinen.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(linkLinen.cvar, linkLinen.defaultColor); + Color_RGBA8 color = CVarGetColor(linkLinen.valuesCvar, linkLinen.defaultColor); PATCH_GFX(gLinkAdultLeftArmNearDL, "Link_Linen1", linkLinen.changedCvar, 30, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultLeftArmNearDL, "Link_Linen2", linkLinen.changedCvar, 83, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultLeftArmOutNearDL, "Link_Linen3", linkLinen.changedCvar, 25, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -668,7 +672,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& linkBoots = cosmeticOptions.at("Link.Boots"); if (manualChange || CVarGetInteger(linkBoots.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(linkBoots.cvar, linkBoots.defaultColor); + Color_RGBA8 color = CVarGetColor(linkBoots.valuesCvar, linkBoots.defaultColor); PATCH_GFX(gLinkChildRightShinNearDL, "Link_Boots1", linkBoots.changedCvar, 10, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkChildRightShinFarDL, "Link_Boots2", linkBoots.changedCvar, 10, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultRightLegNearDL, "Link_Boots3", linkBoots.changedCvar, 10, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); @@ -704,7 +708,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& mirrorShieldBody = cosmeticOptions.at("MirrorShield.Body"); if (manualChange || CVarGetInteger(mirrorShieldBody.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(mirrorShieldBody.cvar, mirrorShieldBody.defaultColor); + Color_RGBA8 color = CVarGetColor(mirrorShieldBody.valuesCvar, mirrorShieldBody.defaultColor); PATCH_GFX(gGiMirrorShieldDL, "MirrorShield_Body1", mirrorShieldBody.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiMirrorShieldDL, "MirrorShield_Body2", mirrorShieldBody.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultMirrorShieldSwordAndSheathNearDL, "MirrorShield_Body3", mirrorShieldBody.changedCvar, 28, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -716,7 +720,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& mirrorShieldMirror = cosmeticOptions.at("MirrorShield.Mirror"); if (manualChange || CVarGetInteger(mirrorShieldMirror.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(mirrorShieldMirror.cvar, mirrorShieldMirror.defaultColor); + Color_RGBA8 color = CVarGetColor(mirrorShieldMirror.valuesCvar, mirrorShieldMirror.defaultColor); PATCH_GFX(gGiMirrorShieldDL, "MirrorShield_Mirror1", mirrorShieldMirror.changedCvar, 47, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiMirrorShieldDL, "MirrorShield_Mirror2", mirrorShieldMirror.changedCvar, 48, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultMirrorShieldSwordAndSheathNearDL, "MirrorShield_Mirror3", mirrorShieldMirror.changedCvar, 17, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -728,7 +732,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& mirrorShieldEmblem = cosmeticOptions.at("MirrorShield.Emblem"); if (manualChange || CVarGetInteger(mirrorShieldEmblem.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(mirrorShieldEmblem.cvar, mirrorShieldEmblem.defaultColor); + Color_RGBA8 color = CVarGetColor(mirrorShieldEmblem.valuesCvar, mirrorShieldEmblem.defaultColor); PATCH_GFX(gGiMirrorShieldSymbolDL, "MirrorShield_Emblem1", mirrorShieldEmblem.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 140)); PATCH_GFX(gGiMirrorShieldSymbolDL, "MirrorShield_Emblem2", mirrorShieldEmblem.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultMirrorShieldSwordAndSheathNearDL, "MirrorShield_Emblem3", mirrorShieldEmblem.changedCvar, 165, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -741,7 +745,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& swordsKokiriBlade = cosmeticOptions.at("Swords.KokiriBlade"); if (manualChange || CVarGetInteger(swordsKokiriBlade.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(swordsKokiriBlade.cvar, swordsKokiriBlade.defaultColor); + Color_RGBA8 color = CVarGetColor(swordsKokiriBlade.valuesCvar, swordsKokiriBlade.defaultColor); PATCH_GFX(gLinkChildLeftFistAndKokiriSwordNearDL, "Swords_KokiriBlade1", swordsKokiriBlade.changedCvar, 79, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gLinkChildLeftFistAndKokiriSwordFarDL, "Swords_KokiriBlade2", swordsKokiriBlade.changedCvar, 75, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiKokiriSwordDL, "Swords_KokiriBlade3", swordsKokiriBlade.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -750,7 +754,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { /* static CosmeticOption& swordsKokiriHilt = cosmeticOptions.at("Swords.KokiriHilt"); if (manualChange || CVarGetInteger(swordsKokiriHilt.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(swordsKokiriHilt.cvar, swordsKokiriHilt.defaultColor); + Color_RGBA8 color = CVarGetColor(swordsKokiriHilt.valuesCvar, swordsKokiriHilt.defaultColor); PATCH_GFX(gLinkChildLeftFistAndKokiriSwordNearDL, "Swords_KokiriHilt1", swordsKokiriHilt.changedCvar, 4, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkChildLeftFistAndKokiriSwordFarDL, "Swords_KokiriHilt2", swordsKokiriHilt.changedCvar, 4, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkChildSwordAndSheathNearDL, "Swords_KokiriHilt3", swordsKokiriHilt.changedCvar, 4, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); @@ -791,7 +795,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { */ static CosmeticOption& swordsMasterBlade = cosmeticOptions.at("Swords.MasterBlade"); if (manualChange || CVarGetInteger(swordsMasterBlade.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(swordsMasterBlade.cvar, swordsMasterBlade.defaultColor); + Color_RGBA8 color = CVarGetColor(swordsMasterBlade.valuesCvar, swordsMasterBlade.defaultColor); PATCH_GFX(gLinkAdultLeftHandHoldingMasterSwordFarDL, "Swords_MasterBlade1", swordsMasterBlade.changedCvar, 60, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultLeftHandHoldingMasterSwordNearDL, "Swords_MasterBlade2", swordsMasterBlade.changedCvar, 17, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(object_toki_objects_DL_001BD0, "Swords_MasterBlade3", swordsMasterBlade.changedCvar, 13, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -802,7 +806,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { /* static CosmeticOption& swordsMasterHilt = cosmeticOptions.at("Swords.MasterHilt"); if (manualChange || CVarGetInteger(swordsMasterHilt.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(swordsMasterHilt.cvar, swordsMasterHilt.defaultColor); + Color_RGBA8 color = CVarGetColor(swordsMasterHilt.valuesCvar, swordsMasterHilt.defaultColor); PATCH_GFX(gLinkAdultLeftHandHoldingMasterSwordNearDL, "Swords_MasterHilt1", swordsMasterHilt.changedCvar, 20, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultLeftHandHoldingMasterSwordFarDL, "Swords_MasterHilt2", swordsMasterHilt.changedCvar, 20, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(object_toki_objects_DL_001BD0, "Swords_MasterHilt3", swordsMasterHilt.changedCvar, 16, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); @@ -849,7 +853,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { */ static CosmeticOption& swordsBiggoronBlade = cosmeticOptions.at("Swords.BiggoronBlade"); if (manualChange || CVarGetInteger(swordsBiggoronBlade.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(swordsBiggoronBlade.cvar, swordsBiggoronBlade.defaultColor); + Color_RGBA8 color = CVarGetColor(swordsBiggoronBlade.valuesCvar, swordsBiggoronBlade.defaultColor); PATCH_GFX(gLinkAdultLeftHandHoldingBgsFarDL, "Swords_BiggoronBlade1", swordsBiggoronBlade.changedCvar, 108, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultLeftHandHoldingBgsNearDL, "Swords_BiggoronBlade2", swordsBiggoronBlade.changedCvar, 63, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBiggoronSwordDL, "Swords_BiggoronBlade3", swordsBiggoronBlade.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -858,7 +862,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { /* static CosmeticOption& swordsBiggoronHilt = cosmeticOptions.at("Swords.BiggoronHilt"); if (manualChange || CVarGetInteger(swordsBiggoronHilt.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(swordsBiggoronHilt.cvar, swordsBiggoronHilt.defaultColor); + Color_RGBA8 color = CVarGetColor(swordsBiggoronHilt.valuesCvar, swordsBiggoronHilt.defaultColor); PATCH_GFX(gLinkAdultLeftHandHoldingBgsNearDL, "Swords_BiggoronHilt1", swordsBiggoronHilt.changedCvar, 20, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gLinkAdultLeftHandHoldingBgsFarDL, "Swords_BiggoronHilt2", swordsBiggoronHilt.changedCvar, 20, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gGiBiggoronSwordDL, "Swords_BiggoronHilt3", swordsBiggoronHilt.changedCvar, 74, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -877,7 +881,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { */ static CosmeticOption& glovesGoronBracelet = cosmeticOptions.at("Gloves.GoronBracelet"); if (manualChange || CVarGetInteger(glovesGoronBracelet.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(glovesGoronBracelet.cvar, glovesGoronBracelet.defaultColor); + Color_RGBA8 color = CVarGetColor(glovesGoronBracelet.valuesCvar, glovesGoronBracelet.defaultColor); PATCH_GFX(gGiGoronBraceletDL, "Gloves_GoronBracelet1", glovesGoronBracelet.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiGoronBraceletDL, "Gloves_GoronBracelet2", glovesGoronBracelet.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkChildGoronBraceletDL, "Gloves_GoronBracelet3", glovesGoronBracelet.changedCvar, 3, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); @@ -889,19 +893,19 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& glovesSilverGauntlets = cosmeticOptions.at("Gloves.SilverGauntlets"); if (manualChange || CVarGetInteger(glovesSilverGauntlets.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(glovesSilverGauntlets.cvar, glovesSilverGauntlets.defaultColor); + Color_RGBA8 color = CVarGetColor(glovesSilverGauntlets.valuesCvar, glovesSilverGauntlets.defaultColor); PATCH_GFX(gGiSilverGauntletsColorDL, "Gloves_SilverGauntlets1", glovesSilverGauntlets.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiSilverGauntletsColorDL, "Gloves_SilverGauntlets2", glovesSilverGauntlets.changedCvar, 4, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); } static CosmeticOption& glovesGoldenGauntlets = cosmeticOptions.at("Gloves.GoldenGauntlets"); if (manualChange || CVarGetInteger(glovesGoldenGauntlets.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(glovesGoldenGauntlets.cvar, glovesGoldenGauntlets.defaultColor); + Color_RGBA8 color = CVarGetColor(glovesGoldenGauntlets.valuesCvar, glovesGoldenGauntlets.defaultColor); PATCH_GFX(gGiGoldenGauntletsColorDL, "Gloves_GoldenGauntlets1", glovesGoldenGauntlets.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiGoldenGauntletsColorDL, "Gloves_GoldenGauntlets2", glovesGoldenGauntlets.changedCvar, 4, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); } static CosmeticOption& glovesGauntletsGem = cosmeticOptions.at("Gloves.GauntletsGem"); if (manualChange || CVarGetInteger(glovesGauntletsGem.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(glovesGauntletsGem.cvar, glovesGauntletsGem.defaultColor); + Color_RGBA8 color = CVarGetColor(glovesGauntletsGem.valuesCvar, glovesGauntletsGem.defaultColor); PATCH_GFX(gGiGauntletsDL, "Gloves_GauntletsGem1", glovesGauntletsGem.changedCvar, 84, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiGauntletsDL, "Gloves_GauntletsGem2", glovesGauntletsGem.changedCvar, 85, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultLeftGauntletPlate2DL, "Gloves_GauntletsGem3", glovesGauntletsGem.changedCvar, 42, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -912,7 +916,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& equipmentBoomerangBody = cosmeticOptions.at("Equipment.BoomerangBody"); if (manualChange || CVarGetInteger(equipmentBoomerangBody.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBoomerangBody.cvar, equipmentBoomerangBody.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBoomerangBody.valuesCvar, equipmentBoomerangBody.defaultColor); PATCH_GFX(gGiBoomerangDL, "Equipment_BoomerangBody1", equipmentBoomerangBody.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBoomerangDL, "Equipment_BoomerangBody2", equipmentBoomerangBody.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkChildLeftFistAndBoomerangNearDL, "Equipment_BoomerangBody3", equipmentBoomerangBody.changedCvar, 34, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -921,7 +925,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& equipmentBoomerangGem = cosmeticOptions.at("Equipment.BoomerangGem"); if (manualChange || CVarGetInteger(equipmentBoomerangGem.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBoomerangGem.cvar, equipmentBoomerangGem.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBoomerangGem.valuesCvar, equipmentBoomerangGem.defaultColor); PATCH_GFX(gGiBoomerangDL, "Equipment_BoomerangGem1", equipmentBoomerangGem.changedCvar, 84, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBoomerangDL, "Equipment_BoomerangGem2", equipmentBoomerangGem.changedCvar, 85, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkChildLeftFistAndBoomerangNearDL, "Equipment_BoomerangGem3", equipmentBoomerangGem.changedCvar, 16, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -932,7 +936,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { /* static CosmeticOption& equipmentSlingshotBody = cosmeticOptions.at("Equipment.SlingshotBody"); if (manualChange || CVarGetInteger(equipmentSlingshotBody.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentSlingshotBody.cvar, equipmentSlingshotBody.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentSlingshotBody.valuesCvar, equipmentSlingshotBody.defaultColor); PATCH_GFX(gGiSlingshotDL, "Equipment_SlingshotBody1", equipmentSlingshotBody.changedCvar, 10, gsDPSetPrimColor(0, 0, MAX(color.r - 100, 0), MAX(color.g - 100, 0), MAX(color.b - 100, 0), 255)); PATCH_GFX(gGiSlingshotDL, "Equipment_SlingshotBody2", equipmentSlingshotBody.changedCvar, 12, gsDPSetEnvColor(MAX(color.r - 100, 0) / 3, MAX(color.g - 100, 0) / 3, MAX(color.b - 100, 0) / 3, 255)); PATCH_GFX(gGiSlingshotDL, "Equipment_SlingshotBody3", equipmentSlingshotBody.changedCvar, 74, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -956,7 +960,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { */ static CosmeticOption& equipmentSlingshotString = cosmeticOptions.at("Equipment.SlingshotString"); if (manualChange || CVarGetInteger(equipmentSlingshotString.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentSlingshotString.cvar, equipmentSlingshotString.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentSlingshotString.valuesCvar, equipmentSlingshotString.defaultColor); PATCH_GFX(gGiSlingshotDL, "Equipment_SlingshotString1",equipmentSlingshotString.changedCvar, 75, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiSlingshotDL, "Equipment_SlingshotString2",equipmentSlingshotString.changedCvar, 76, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); PATCH_GFX(gLinkChildSlingshotStringDL, "Equipment_SlingshotString3",equipmentSlingshotString.changedCvar, 9, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -964,7 +968,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& equipmentBowTips = cosmeticOptions.at("Equipment.BowTips"); if (manualChange || CVarGetInteger(equipmentBowTips.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBowTips.cvar, equipmentBowTips.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBowTips.valuesCvar, equipmentBowTips.defaultColor); PATCH_GFX(gGiBowDL, "Equipment_BowTips1", equipmentBowTips.changedCvar, 86, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBowDL, "Equipment_BowTips2", equipmentBowTips.changedCvar, 87, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultRightHandHoldingBowFirstPersonDL, "Equipment_BowTips3", equipmentBowTips.changedCvar, 34, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -973,14 +977,14 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& equipmentBowString = cosmeticOptions.at("Equipment.BowString"); if (manualChange || CVarGetInteger(equipmentBowString.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBowString.cvar, equipmentBowString.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBowString.valuesCvar, equipmentBowString.defaultColor); PATCH_GFX(gGiBowDL, "Equipment_BowString1", equipmentBowString.changedCvar, 105, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBowDL, "Equipment_BowString2", equipmentBowString.changedCvar, 106, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultBowStringDL, "Equipment_BowString3", equipmentBowString.changedCvar, 9, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); } static CosmeticOption& equipmentBowBody = cosmeticOptions.at("Equipment.BowBody"); if (manualChange || CVarGetInteger(equipmentBowBody.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBowBody.cvar, equipmentBowBody.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBowBody.valuesCvar, equipmentBowBody.defaultColor); PATCH_GFX(gGiBowDL, "Equipment_BowBody1", equipmentBowBody.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBowDL, "Equipment_BowBody2", equipmentBowBody.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultRightHandHoldingBowFirstPersonDL, "Equipment_BowBody3", equipmentBowBody.changedCvar, 42, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -989,7 +993,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& equipmentBowHandle = cosmeticOptions.at("Equipment.BowHandle"); if (manualChange || CVarGetInteger(equipmentBowHandle.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBowHandle.cvar, equipmentBowHandle.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBowHandle.valuesCvar, equipmentBowHandle.defaultColor); PATCH_GFX(gGiBowDL, "Equipment_BowHandle1", equipmentBowHandle.changedCvar, 51, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBowDL, "Equipment_BowHandle2", equipmentBowHandle.changedCvar, 52, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gLinkAdultRightHandHoldingBowFirstPersonDL, "Equipment_BowHandle3", equipmentBowHandle.changedCvar, 18, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -999,7 +1003,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& equipmentHammerHead = cosmeticOptions.at("Equipment.HammerHead"); if (manualChange || CVarGetInteger(equipmentHammerHead.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentHammerHead.cvar, equipmentHammerHead.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentHammerHead.valuesCvar, equipmentHammerHead.defaultColor); PATCH_GFX(gGiHammerDL, "Equipment_HammerHead1", equipmentHammerHead.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiHammerDL, "Equipment_HammerHead2", equipmentHammerHead.changedCvar, 6, gsDPSetEnvColor(color.r / 5, color.g / 5, color.b / 5, 255)); PATCH_GFX(gGiHammerDL, "Equipment_HammerHead3", equipmentHammerHead.changedCvar, 68, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -1009,7 +1013,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& equipmentHammerHandle = cosmeticOptions.at("Equipment.HammerHandle"); if (manualChange || CVarGetInteger(equipmentHammerHandle.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentHammerHandle.cvar, equipmentHammerHandle.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentHammerHandle.valuesCvar, equipmentHammerHandle.defaultColor); PATCH_GFX(gGiHammerDL, "Equipment_HammerHandle1", equipmentHammerHandle.changedCvar, 84, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiHammerDL, "Equipment_HammerHandle2", equipmentHammerHandle.changedCvar, 85, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); PATCH_GFX(gLinkAdultLeftHandHoldingHammerNearDL, "Equipment_HammerHandle5", equipmentHammerHandle.changedCvar, 18, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -1018,13 +1022,13 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& equipmentHookshotChain = cosmeticOptions.at("Equipment.HookshotChain"); if (manualChange || CVarGetInteger(equipmentHookshotChain.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentHookshotChain.cvar, equipmentHookshotChain.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentHookshotChain.valuesCvar, equipmentHookshotChain.defaultColor); PATCH_GFX(gLinkAdultHookshotChainDL, "Equipment_HookshotChain1", equipmentHookshotChain.changedCvar, 17, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); } static CosmeticOption& equipmentChuFace = cosmeticOptions.at("Equipment.ChuFace"); if (manualChange || CVarGetInteger(equipmentChuFace.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentChuFace.cvar, equipmentChuFace.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentChuFace.valuesCvar, equipmentChuFace.defaultColor); PATCH_GFX(gGiBombchuDL, "Equipment_ChuFace1", equipmentChuFace.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBombchuDL, "Equipment_ChuFace2", equipmentChuFace.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gBombchuDL, "Equipment_ChuFace3", equipmentChuFace.changedCvar, 2, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); @@ -1036,7 +1040,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& equipmentChuBody = cosmeticOptions.at("Equipment.ChuBody"); if (manualChange || CVarGetInteger(equipmentChuBody.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentChuBody.cvar, equipmentChuBody.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentChuBody.valuesCvar, equipmentChuBody.defaultColor); PATCH_GFX(gGiBombchuDL, "Equipment_ChuBody1", equipmentChuBody.changedCvar, 39, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBombchuDL, "Equipment_ChuBody2", equipmentChuBody.changedCvar, 40, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gGiBombchuDL, "Equipment_ChuBody3", equipmentChuBody.changedCvar, 60, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -1046,7 +1050,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& equipmentBunnyHood = cosmeticOptions.at("Equipment.BunnyHood"); if (manualChange || CVarGetInteger(equipmentBunnyHood.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(equipmentBunnyHood.cvar, equipmentBunnyHood.defaultColor); + Color_RGBA8 color = CVarGetColor(equipmentBunnyHood.valuesCvar, equipmentBunnyHood.defaultColor); PATCH_GFX(gGiBunnyHoodDL, "Equipment_BunnyHood1", equipmentBunnyHood.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBunnyHoodDL, "Equipment_BunnyHood2", equipmentBunnyHood.changedCvar, 6, gsDPSetEnvColor(color.r / 3, color.g / 3, color.b / 3, 255)); PATCH_GFX(gGiBunnyHoodDL, "Equipment_BunnyHood3", equipmentBunnyHood.changedCvar, 83, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -1065,7 +1069,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& consumableGreenRupee = cosmeticOptions.at("Consumable.GreenRupee"); if (manualChange || CVarGetInteger(consumableGreenRupee.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumableGreenRupee.cvar, consumableGreenRupee.defaultColor); + Color_RGBA8 color = CVarGetColor(consumableGreenRupee.valuesCvar, consumableGreenRupee.defaultColor); PATCH_GFX(gGiGreenRupeeInnerColorDL, "Consumable_GreenRupee1", consumableGreenRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiGreenRupeeInnerColorDL, "Consumable_GreenRupee2", consumableGreenRupee.changedCvar, 4, gsDPSetEnvColor(color.r / 5, color.g / 5, color.b / 5, 255)); PATCH_GFX(gGiGreenRupeeOuterColorDL, "Consumable_GreenRupee3", consumableGreenRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, MIN(color.r + 100, 255), MIN(color.g + 100, 255), MIN(color.b + 100, 255), 255)); @@ -1084,7 +1088,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& consumableBlueRupee = cosmeticOptions.at("Consumable.BlueRupee"); if (manualChange || CVarGetInteger(consumableBlueRupee.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumableBlueRupee.cvar, consumableBlueRupee.defaultColor); + Color_RGBA8 color = CVarGetColor(consumableBlueRupee.valuesCvar, consumableBlueRupee.defaultColor); PATCH_GFX(gGiBlueRupeeInnerColorDL, "Consumable_BlueRupee1", consumableBlueRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiBlueRupeeInnerColorDL, "Consumable_BlueRupee2", consumableBlueRupee.changedCvar, 4, gsDPSetEnvColor(color.r / 5, color.g / 5, color.b / 5, 255)); PATCH_GFX(gGiBlueRupeeOuterColorDL, "Consumable_BlueRupee3", consumableBlueRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, MIN(color.r + 100, 255), MIN(color.g + 100, 255), MIN(color.b + 100, 255), 255)); @@ -1092,7 +1096,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& consumableRedRupee = cosmeticOptions.at("Consumable.RedRupee"); if (manualChange || CVarGetInteger(consumableRedRupee.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumableRedRupee.cvar, consumableRedRupee.defaultColor); + Color_RGBA8 color = CVarGetColor(consumableRedRupee.valuesCvar, consumableRedRupee.defaultColor); PATCH_GFX(gGiRedRupeeInnerColorDL, "Consumable_RedRupee1", consumableRedRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiRedRupeeInnerColorDL, "Consumable_RedRupee2", consumableRedRupee.changedCvar, 4, gsDPSetEnvColor(color.r / 5, color.g / 5, color.b / 5, 255)); PATCH_GFX(gGiRedRupeeOuterColorDL, "Consumable_RedRupee3", consumableRedRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, MIN(color.r + 100, 255), MIN(color.g + 100, 255), MIN(color.b + 100, 255), 255)); @@ -1100,7 +1104,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& consumablePurpleRupee = cosmeticOptions.at("Consumable.PurpleRupee"); if (manualChange || CVarGetInteger(consumablePurpleRupee.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumablePurpleRupee.cvar, consumablePurpleRupee.defaultColor); + Color_RGBA8 color = CVarGetColor(consumablePurpleRupee.valuesCvar, consumablePurpleRupee.defaultColor); PATCH_GFX(gGiPurpleRupeeInnerColorDL, "Consumable_PurpleRupee1", consumablePurpleRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiPurpleRupeeInnerColorDL, "Consumable_PurpleRupee2", consumablePurpleRupee.changedCvar, 4, gsDPSetEnvColor(color.r / 5, color.g / 5, color.b / 5, 255)); PATCH_GFX(gGiPurpleRupeeOuterColorDL, "Consumable_PurpleRupee3", consumablePurpleRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, MIN(color.r + 100, 255), MIN(color.g + 100, 255), MIN(color.b + 100, 255), 255)); @@ -1108,7 +1112,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& consumableGoldRupee = cosmeticOptions.at("Consumable.GoldRupee"); if (manualChange || CVarGetInteger(consumableGoldRupee.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumableGoldRupee.cvar, consumableGoldRupee.defaultColor); + Color_RGBA8 color = CVarGetColor(consumableGoldRupee.valuesCvar, consumableGoldRupee.defaultColor); PATCH_GFX(gGiGoldRupeeInnerColorDL, "Consumable_GoldRupee1", consumableGoldRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiGoldRupeeInnerColorDL, "Consumable_GoldRupee2", consumableGoldRupee.changedCvar, 4, gsDPSetEnvColor(color.r / 5, color.g / 5, color.b / 5, 255)); PATCH_GFX(gGiGoldRupeeOuterColorDL, "Consumable_GoldRupee3", consumableGoldRupee.changedCvar, 3, gsDPSetPrimColor(0, 0, MIN(color.r + 100, 255), MIN(color.g + 100, 255), MIN(color.b + 100, 255), 255)); @@ -1117,7 +1121,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& consumableHearts = cosmeticOptions.at("Consumable.Hearts"); if (manualChange || CVarGetInteger(consumableHearts.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumableHearts.cvar, consumableHearts.defaultColor); + Color_RGBA8 color = CVarGetColor(consumableHearts.valuesCvar, consumableHearts.defaultColor); /* PATCH_GFX(gGiRecoveryHeartDL, "Consumable_Hearts1", consumableHearts.changedCvar, 4, gsDPSetGrayscaleColor(color.r, color.g, color.b, 255)); PATCH_GFX(gGiRecoveryHeartDL, "Consumable_Hearts2", consumableHearts.changedCvar, 26, gsSPGrayscale(true)); @@ -1133,7 +1137,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { } static CosmeticOption& consumableMagic = cosmeticOptions.at("Consumable.Magic"); if (manualChange || CVarGetInteger(consumableMagic.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(consumableMagic.cvar, consumableMagic.defaultColor); + Color_RGBA8 color = CVarGetColor(consumableMagic.valuesCvar, consumableMagic.defaultColor); PATCH_GFX(gGiMagicJarSmallDL, "Consumable_Magic1", consumableMagic.changedCvar, 31, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gGiMagicJarSmallDL, "Consumable_Magic2", consumableMagic.changedCvar, 32, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); PATCH_GFX(gGiMagicJarLargeDL, "Consumable_Magic3", consumableMagic.changedCvar, 31, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -1144,7 +1148,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& npcGoldenSkulltula = cosmeticOptions.at("NPC.GoldenSkulltula"); if (manualChange || CVarGetInteger(npcGoldenSkulltula.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(npcGoldenSkulltula.cvar, npcGoldenSkulltula.defaultColor); + Color_RGBA8 color = CVarGetColor(npcGoldenSkulltula.valuesCvar, npcGoldenSkulltula.defaultColor); PATCH_GFX(gSkulltulaTokenDL, "NPC_GoldenSkulltula1", npcGoldenSkulltula.changedCvar, 5, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); PATCH_GFX(gSkulltulaTokenDL, "NPC_GoldenSkulltula2", npcGoldenSkulltula.changedCvar, 6, gsDPSetEnvColor(color.r / 2, color.g / 2, color.b / 2, 255)); PATCH_GFX(gSkulltulaTokenFlameDL, "NPC_GoldenSkulltula3", npcGoldenSkulltula.changedCvar, 32, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); @@ -1159,7 +1163,7 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& npcGerudo = cosmeticOptions.at("NPC.Gerudo"); if (manualChange || CVarGetInteger(npcGerudo.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(npcGerudo.cvar, npcGerudo.defaultColor); + Color_RGBA8 color = CVarGetColor(npcGerudo.valuesCvar, npcGerudo.defaultColor); PATCH_GFX(gGerudoPurpleTorsoDL, "NPC_Gerudo1", npcGerudo.changedCvar, 139, gsDPSetEnvColor( color.r, color.g, color.b, 255)); PATCH_GFX(gGerudoPurpleRightThighDL, "NPC_Gerudo2", npcGerudo.changedCvar, 11, gsDPSetEnvColor(color.r, color.g, color.b, 255)); PATCH_GFX(gGerudoPurpleLeftThighDL, "NPC_Gerudo3", npcGerudo.changedCvar, 11, gsDPSetEnvColor(color.r, color.g, color.b, 255)); @@ -1172,39 +1176,39 @@ void ApplyOrResetCustomGfxPatches(bool manualChange) { static CosmeticOption& npcMetalTrap = cosmeticOptions.at("NPC.MetalTrap"); if (manualChange || CVarGetInteger(npcMetalTrap.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(npcMetalTrap.cvar, npcMetalTrap.defaultColor); + Color_RGBA8 color = CVarGetColor(npcMetalTrap.valuesCvar, npcMetalTrap.defaultColor); PATCH_GFX(gSlidingBladeTrapDL, "NPC_MetalTrap1", npcMetalTrap.changedCvar, 59, gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); } static CosmeticOption& n64LogoRed = cosmeticOptions.at("Title.N64LogoRed"); if (manualChange || CVarGetInteger(n64LogoRed.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(n64LogoRed.cvar, n64LogoRed.defaultColor); + Color_RGBA8 color = CVarGetColor(n64LogoRed.valuesCvar, n64LogoRed.defaultColor); PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoRed1", n64LogoRed.changedCvar, 17, gsDPSetPrimColor(0, 0, 255, 255, 255, 255)) PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoRed2", n64LogoRed.changedCvar, 18, gsDPSetEnvColor(color.r, color.g, color.b, 128)); } static CosmeticOption& n64LogoBlue = cosmeticOptions.at("Title.N64LogoBlue"); if (manualChange || CVarGetInteger(n64LogoBlue.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(n64LogoBlue.cvar, n64LogoBlue.defaultColor); + Color_RGBA8 color = CVarGetColor(n64LogoBlue.valuesCvar, n64LogoBlue.defaultColor); PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoBlue1", n64LogoBlue.changedCvar, 29, gsDPSetPrimColor(0, 0, 255, 255, 255, 255)) PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoBlue2", n64LogoBlue.changedCvar, 30, gsDPSetEnvColor(color.r, color.g, color.b, 128)); } static CosmeticOption& n64LogoGreen = cosmeticOptions.at("Title.N64LogoGreen"); if (manualChange || CVarGetInteger(n64LogoGreen.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(n64LogoGreen.cvar, n64LogoGreen.defaultColor); + Color_RGBA8 color = CVarGetColor(n64LogoGreen.valuesCvar, n64LogoGreen.defaultColor); PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoGreen1", n64LogoGreen.changedCvar, 56, gsDPSetPrimColor(0, 0, 255, 255, 255, 255)) PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoGreen2", n64LogoGreen.changedCvar, 57, gsDPSetEnvColor(color.r, color.g, color.b, 128)); } static CosmeticOption& n64LogoYellow = cosmeticOptions.at("Title.N64LogoYellow"); if (manualChange || CVarGetInteger(n64LogoYellow.rainbowCvar, 0)) { - Color_RGBA8 color = CVarGetColor(n64LogoYellow.cvar, n64LogoYellow.defaultColor); + Color_RGBA8 color = CVarGetColor(n64LogoYellow.valuesCvar, n64LogoYellow.defaultColor); PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoYellow1", n64LogoYellow.changedCvar, 81, gsDPSetPrimColor(0, 0, 255, 255, 255, 255)) PATCH_GFX(gNintendo64LogoDL, "Title_N64LogoYellow2", n64LogoYellow.changedCvar, 82, gsDPSetEnvColor(color.r, color.g, color.b, 128)); } if (gPlayState != nullptr) { - if (CVarGetInteger(CVAR_COSMETIC("Link.BodyScale.Changed"), 0)) { + if (CVarGetInteger(CVAR_COSMETIC("Link.BodySize.Changed"), 0)) { static Player* player = GET_PLAYER(gPlayState); - float scale = CVarGetFloat(CVAR_COSMETIC("Link.BodyScale.Value"), 0.01f); + float scale = CVarGetFloat(CVAR_COSMETIC("Link.BodySize.Value"), 0.01f); player->actor.scale.x = scale; player->actor.scale.y = scale; player->actor.scale.z = scale; @@ -1235,24 +1239,36 @@ void Table_InitHeader(bool has_header = true) { void DrawUseMarginsSlider(const std::string ElementName, const std::string CvarName){ std::string CvarLabel = CvarName + ".UseMargins"; std::string Label = ElementName + " use margins"; - UIWidgets::EnhancementCheckbox(Label.c_str(), CvarLabel.c_str()); - UIWidgets::Tooltip("Using this allow you move the element with General margins sliders"); + UIWidgets::CVarCheckbox(Label.c_str(), CvarLabel.c_str(), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Using this allow you move the element with General margins sliders")); } -void DrawPositionsRadioBoxes(const std::string CvarName, bool NoAnchorEnabled = true){ +void DrawPositionsRadioBoxes(const std::string CvarName, bool NoAnchorEnabled = true) { std::string CvarLabel = CvarName + ".PosType"; - UIWidgets::EnhancementRadioButton("Original position", CvarLabel.c_str(), 0); - UIWidgets::Tooltip("This will use original intended elements position"); - UIWidgets::EnhancementRadioButton("Anchor to the left", CvarLabel.c_str(), 1); - UIWidgets::Tooltip("This will make your elements follow the left side of your game window"); - UIWidgets::EnhancementRadioButton("Anchor to the right", CvarLabel.c_str(), 2); - UIWidgets::Tooltip("This will make your elements follow the right side of your game window"); + UIWidgets::CVarRadioButton("Original position", CvarLabel.c_str(), 0, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will use original intended elements position")); + UIWidgets::CVarRadioButton("Anchor to the left", CvarLabel.c_str(), 1, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will make your elements follow the left side of your game window")); + UIWidgets::CVarRadioButton("Anchor to the right", CvarLabel.c_str(), 2, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will make your elements follow the right side of your game window")); if (NoAnchorEnabled) { - UIWidgets::EnhancementRadioButton("No anchors", CvarLabel.c_str(), 3); - UIWidgets::Tooltip("This will make your elements to not follow any side\nBetter used for center elements"); + UIWidgets::CVarRadioButton( + "No anchors", CvarLabel.c_str(), 3, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will make your elements to not follow any side\nBetter used for center elements")); } - UIWidgets::EnhancementRadioButton("Hidden", CvarLabel.c_str(), 4); - UIWidgets::Tooltip("This will make your elements hidden"); + UIWidgets::CVarRadioButton( + "Hidden", CvarLabel.c_str(), 4, + UIWidgets::RadioButtonsOptions().Color(THEME_COLOR).Tooltip("This will make your elements hidden")); } void DrawPositionSlider(const std::string CvarName, int MinY, int MaxY, int MinX, int MaxX){ @@ -1260,10 +1276,22 @@ void DrawPositionSlider(const std::string CvarName, int MinY, int MaxY, int MinX std::string PosYCvar = CvarName + ".PosY"; std::string InvisibleLabelX = "##" + PosXCvar; std::string InvisibleLabelY = "##" + PosYCvar; - UIWidgets::EnhancementSliderInt("Up <-> Down : %d", InvisibleLabelY.c_str(), PosYCvar.c_str(), MinY, MaxY, "", 0); - UIWidgets::Tooltip("This slider is used to move Up and Down your elements."); - UIWidgets::EnhancementSliderInt("Left <-> Right : %d", InvisibleLabelX.c_str(), PosXCvar.c_str(), MinX, MaxX, "", 0); - UIWidgets::Tooltip("This slider is used to move Left and Right your elements."); + UIWidgets::CVarSliderInt("Up <-> Down : %d", PosYCvar.c_str(), + UIWidgets::IntSliderOptions() + .Min(MinY) + .Max(MaxY) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("This slider is used to move Up and Down your elements.")); + UIWidgets::CVarSliderInt("Left <-> Right : %d", PosXCvar.c_str(), + UIWidgets::IntSliderOptions() + .Min(MinX) + .Max(MaxX) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("This slider is used to move Left and Right your elements.")); } void DrawScaleSlider(const std::string CvarName, float DefaultValue){ @@ -1274,6 +1302,7 @@ void DrawScaleSlider(const std::string CvarName, float DefaultValue){ } void Draw_Table_Dropdown(const char* Header_Title, const char* Table_ID, const char* Column_Title, const char* Slider_Title, const char* Slider_ID, int MinY, int MaxY, int MinX, int MaxX, float Default_Value) { + UIWidgets::PushStyleHeader(THEME_COLOR); if (ImGui::CollapsingHeader(Header_Title)) { if (ImGui::BeginTable(Table_ID, 1, FlagsTable)) { ImGui::TableSetupColumn(Column_Title, FlagsCell, TablesCellsWidth); @@ -1282,13 +1311,14 @@ void Draw_Table_Dropdown(const char* Header_Title, const char* Table_ID, const c DrawPositionsRadioBoxes(Slider_ID); DrawPositionSlider(Slider_ID, MinY, MaxY, MinX, MaxX); DrawScaleSlider(Slider_ID, Default_Value); - ImGui::NewLine(); ImGui::EndTable(); } } + UIWidgets::PopStyleHeader(); } void C_Button_Dropdown(const char* Header_Title, const char* Table_ID, const char* Column_Title, const char* Slider_Title, const char* Slider_ID, const char* Int_Type, float Slider_Scale_Value) { + UIWidgets::PushStyleHeader(THEME_COLOR); if (ImGui::CollapsingHeader(Header_Title)) { if (ImGui::BeginTable(Table_ID, 1, FlagsTable)) { ImGui::TableSetupColumn(Column_Title, FlagsCell, TablesCellsWidth); @@ -1306,7 +1336,6 @@ void C_Button_Dropdown(const char* Header_Title, const char* Table_ID, const cha } DrawPositionSlider(Slider_ID, 0, static_cast(ImGui::GetWindowViewport()->Size.y / 2), Min_X_CU, Max_X_CU); DrawScaleSlider(Slider_ID, Slider_Scale_Value); - ImGui::NewLine(); ImGui::EndTable(); } std::shared_ptr controller = Ship::Context::GetInstance()->GetControlDeck()->GetControllerByPort(0); @@ -1323,27 +1352,48 @@ void C_Button_Dropdown(const char* Header_Title, const char* Table_ID, const cha controller->GetButton(BTN_CUSTOM_OCARINA_NOTE_D5)->AddButtonMapping(mapping); } } + UIWidgets::PopStyleHeader(); } void Draw_Placements(){ - if (ImGui::BeginTable("tableMargins", 1, FlagsTable)) { - ImGui::TableSetupColumn("General margins settings", FlagsCell, TablesCellsWidth); - Table_InitHeader(); - UIWidgets::EnhancementSliderInt("Top : %dx", "##UIMARGINT", CVAR_COSMETIC("HUD.Margin.T"), static_cast(ImGui::GetWindowViewport()->Size.y / 2) * -1, 25, "", 0); - UIWidgets::EnhancementSliderInt("Left: %dx", "##UIMARGINL", CVAR_COSMETIC("HUD.Margin.L"), -25, static_cast(ImGui::GetWindowViewport()->Size.x), "", 0); - UIWidgets::EnhancementSliderInt("Right: %dx", "##UIMARGINR", CVAR_COSMETIC("HUD.Margin.R"), static_cast(ImGui::GetWindowViewport()->Size.x) * -1, 25, "", 0); - UIWidgets::EnhancementSliderInt("Bottom: %dx", "##UIMARGINB", CVAR_COSMETIC("HUD.Margin.B"), static_cast(ImGui::GetWindowViewport()->Size.y / 2) * -1, 25, "", 0); - SetMarginAll("All margins on",true); - UIWidgets::Tooltip("Set most of the elements to use margins\nSome elements with default position will not be affected\nElements without Anchor or Hidden will not be turned on"); - ImGui::SameLine(); - SetMarginAll("All margins off",false); - UIWidgets::Tooltip("Set all of the elements to not use margins"); - ImGui::SameLine(); - ResetPositionAll(); - UIWidgets::Tooltip("Revert every element to use their original position and no margins"); - ImGui::NewLine(); - ImGui::EndTable(); - } + UIWidgets::PushStyleHeader(THEME_COLOR); + ImGui::SeparatorText("General Margins Settings"); + UIWidgets::CVarSliderInt("Top: %dpx", CVAR_COSMETIC("HUD.Margin.T"), + UIWidgets::IntSliderOptions() + .Min(static_cast(ImGui::GetWindowViewport()->Size.y / 2) * -1) + .Max(25) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + UIWidgets::CVarSliderInt("Left: %dpx", CVAR_COSMETIC("HUD.Margin.L"), + UIWidgets::IntSliderOptions() + .Min(-25) + .Max(static_cast(ImGui::GetWindowViewport()->Size.x)) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + UIWidgets::CVarSliderInt("Right: %dpx", CVAR_COSMETIC("HUD.Margin.R"), + UIWidgets::IntSliderOptions() + .Min(static_cast(ImGui::GetWindowViewport()->Size.x) * -1) + .Max(25) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + UIWidgets::CVarSliderInt("Bottom: %dpx", CVAR_COSMETIC("HUD.Margin.B"), + UIWidgets::IntSliderOptions() + .Min(static_cast(ImGui::GetWindowViewport()->Size.y / 2) * -1) + .Max(25) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + SetMarginAll("All margins on", true, + "Set most of the elements to use margins\nSome elements with default position will not be " + "affected\nElements without Anchor or Hidden will not be turned on"); + ImGui::SameLine(); + SetMarginAll("All margins off", false, "Set all of the elements to not use margins"); + ImGui::SameLine(); + ResetPositionAll(); + UIWidgets::Separator(true, true, 2.0f, 2.0f); if (ImGui::CollapsingHeader("Hearts count position")) { if (ImGui::BeginTable("tableHeartsCounts", 1, FlagsTable)) { ImGui::TableSetupColumn("Hearts counts settings", FlagsCell, TablesCellsWidth); @@ -1352,9 +1402,14 @@ void Draw_Placements(){ DrawPositionsRadioBoxes(CVAR_COSMETIC("HUD.HeartsCount")); DrawPositionSlider(CVAR_COSMETIC("HUD.HeartsCount"), -22, static_cast(ImGui::GetWindowViewport()->Size.y), -125, static_cast(ImGui::GetWindowViewport()->Size.x)); DrawScaleSlider(CVAR_COSMETIC("HUD.HeartsCount"), 0.7f); - UIWidgets::EnhancementSliderInt("Heart line length : %d", "##HeartLineLength", CVAR_COSMETIC("HUD.Hearts.LineLength"), 0, 20, "", 10); - UIWidgets::Tooltip("This will set the length of a row of hearts. Set to 0 for unlimited length."); - ImGui::NewLine(); + UIWidgets::CVarSliderInt("Heart line length : %d", CVAR_COSMETIC("HUD.Hearts.LineLength"), + UIWidgets::IntSliderOptions() + .Min(0) + .Max(20) + .DefaultValue(0) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("This will set the length of a row of hearts. Set to 0 for unlimited length.")); ImGui::EndTable(); } } @@ -1364,11 +1419,13 @@ void Draw_Placements(){ Table_InitHeader(false); DrawUseMarginsSlider("Magic meter", CVAR_COSMETIC("HUD.MagicBar")); DrawPositionsRadioBoxes(CVAR_COSMETIC("HUD.MagicBar")); - UIWidgets::EnhancementRadioButton("Anchor to life bar", CVAR_COSMETIC("HUD.MagicBar.PosType"), 5); - UIWidgets::Tooltip("This will make your elements follow the bottom of the life meter"); + UIWidgets::CVarRadioButton( + "Anchor to life bar", CVAR_COSMETIC("HUD.MagicBar.PosType"), 5, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will make your elements follow the bottom of the life meter")); DrawPositionSlider(CVAR_COSMETIC("HUD.MagicBar"), 0, static_cast(ImGui::GetWindowViewport()->Size.y / 2), -5, static_cast(ImGui::GetWindowViewport()->Size.x / 2)); DrawScaleSlider(CVAR_COSMETIC("HUD.MagicBar"), 1.0f); - ImGui::NewLine(); ImGui::EndTable(); } } @@ -1387,7 +1444,6 @@ void Draw_Placements(){ } DrawPositionSlider(CVAR_COSMETIC("HUD.VisualSoA"), 0, static_cast(ImGui::GetWindowViewport()->Size.y / 2), Min_X_VSOA, Max_X_VSOA); DrawScaleSlider(CVAR_COSMETIC("HUD.VisualSoA"), 1.0f); - ImGui::NewLine(); ImGui::EndTable(); } } @@ -1413,7 +1469,6 @@ void Draw_Placements(){ } DrawPositionSlider(CVAR_COSMETIC("HUD.Dpad"), 0, static_cast(ImGui::GetWindowViewport()->Size.y / 2), Min_X_Dpad, Max_X_Dpad); DrawScaleSlider(CVAR_COSMETIC("HUD.Dpad"), 1.0f); - ImGui::NewLine(); ImGui::EndTable(); } } @@ -1440,66 +1495,94 @@ void Draw_Placements(){ ImGui::TableSetupColumn("Enemy Health Bar settings", FlagsCell, TablesCellsWidth); Table_InitHeader(false); std::string posTypeCVar = CVAR_COSMETIC("HUD.EnemyHealthBar.PosType"); - UIWidgets::EnhancementRadioButton("Anchor to Enemy", posTypeCVar.c_str(), ENEMYHEALTH_ANCHOR_ACTOR); - UIWidgets::Tooltip("This will use enemy on screen position"); - UIWidgets::EnhancementRadioButton("Anchor to the top", posTypeCVar.c_str(), ENEMYHEALTH_ANCHOR_TOP); - UIWidgets::Tooltip("This will make your elements follow the top edge of your game window"); - UIWidgets::EnhancementRadioButton("Anchor to the bottom", posTypeCVar.c_str(), ENEMYHEALTH_ANCHOR_BOTTOM); - UIWidgets::Tooltip("This will make your elements follow the bottom edge of your game window"); + UIWidgets::CVarRadioButton("Anchor to Enemy", CVAR_COSMETIC("HUD.EnemyHealthBar.PosType"), + ENEMYHEALTH_ANCHOR_ACTOR, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will use enemy on screen position")); + UIWidgets::CVarRadioButton( + "Anchor to the top", CVAR_COSMETIC("HUD.EnemyHealthBar.PosType"), ENEMYHEALTH_ANCHOR_TOP, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will make your elements follow the top edge of your game window")); + UIWidgets::CVarRadioButton( + "Anchor to the bottom", CVAR_COSMETIC("HUD.EnemyHealthBar.PosType"), ENEMYHEALTH_ANCHOR_BOTTOM, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("This will make your elements follow the bottom edge of your game window")); DrawPositionSlider(CVAR_COSMETIC("HUD.EnemyHealthBar."), -SCREEN_HEIGHT, SCREEN_HEIGHT, -static_cast(ImGui::GetWindowViewport()->Size.x / 2), static_cast(ImGui::GetWindowViewport()->Size.x / 2)); - if (UIWidgets::EnhancementSliderInt("Health Bar Width: %d", "##EnemyHealthBarWidth", CVAR_COSMETIC("HUD.EnemyHealthBar.Width.Value"), 32, 128, "", 64)) { + if (UIWidgets::CVarSliderInt( + "Health Bar Width: %d", CVAR_COSMETIC("HUD.EnemyHealthBar.Width.Value"), + UIWidgets::IntSliderOptions() + .Min(32) + .Max(128) + .DefaultValue(64) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR) + .Tooltip("This will change the width of the health bar"))) { CVarSetInteger(CVAR_COSMETIC("HUD.EnemyHealthBar.Width.Changed"), 1); } - UIWidgets::Tooltip("This will change the width of the health bar"); ImGui::SameLine(); - if (ImGui::Button("Reset##EnemyHealthBarWidth")) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + if (UIWidgets::Button("Reset##EnemyHealthBarWidth", + UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { CVarClear(CVAR_COSMETIC("HUD.EnemyHealthBar.Width.Value")); CVarClear(CVAR_COSMETIC("HUD.EnemyHealthBar.Width.Changed")); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - ImGui::NewLine(); ImGui::EndTable(); } } + UIWidgets::PopStyleHeader(); } void Reset_Option_Single(const char* Button_Title, const char* name) { ImGui::SameLine(); - if (ImGui::Button(Button_Title)) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + if (UIWidgets::Button(Button_Title, + UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { CVarClear(name); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } } void Reset_Option_Double(const char* Button_Title, const char* name) { ImGui::SameLine(); - if (ImGui::Button(Button_Title)) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + if (UIWidgets::Button(Button_Title, + UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { CVarClear((std::string(name) + ".Value").c_str()); CVarClear((std::string(name) + ".Changed").c_str()); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } } void DrawSillyTab() { ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - if (UIWidgets::EnhancementCheckbox("Let It Snow", CVAR_GENERAL("LetItSnow"))) { - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - UIWidgets::Tooltip("Makes snow fall, changes chest texture colors to red and green, etc, for December holidays.\nWill reset on restart outside of December 23-25."); + UIWidgets::CVarCheckbox("Let It Snow", CVAR_GENERAL("LetItSnow"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Makes snow fall, changes chest texture colors to red and green, etc, for December holidays.\nWill reset on restart outside of December 23-25.")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - if (UIWidgets::EnhancementSliderFloat("Link Body Scale: %.3fx", "##Link_BodyScale", CVAR_COSMETIC("Link.BodyScale.Value"), 0.001f, 0.025f, "", 0.01f, true)) { - CVarSetInteger(CVAR_COSMETIC("Link.BodyScale.Changed"), 1); + if (UIWidgets::CVarSliderFloat("Link Body Size", CVAR_COSMETIC("Link.BodySize.Value"), + UIWidgets::FloatSliderOptions() + .Format("%.3f") + .Min(0.001f) + .Max(0.05f) + .DefaultValue(0.01f) + .Step(0.001f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR))) { + CVarSetInteger(CVAR_COSMETIC("Link.BodySize.Changed"), 1); } ImGui::SameLine(); - if (ImGui::Button("Reset##Link_BodyScale")) { - CVarClear(CVAR_COSMETIC("Link.BodyScale.Value")); - CVarClear(CVAR_COSMETIC("Link.BodyScale.Changed")); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + if (UIWidgets::Button("Reset##Link_BodySize", + UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { + CVarClear(CVAR_COSMETIC("Link.BodySize.Value")); + CVarClear(CVAR_COSMETIC("Link.BodySize.Changed")); if (gPlayState != nullptr) { static Player* player = GET_PLAYER(gPlayState); player->actor.scale.x = 0.01f; @@ -1508,62 +1591,135 @@ void DrawSillyTab() { } } - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); - - if (UIWidgets::EnhancementSliderFloat("Link Head Scale: %.2fx", "##Link_HeadScale", CVAR_COSMETIC("Link.HeadScale.Value"), 0.4f, 4.0f, "", 1.0f, false)) { + UIWidgets::Separator(true, true, 2.0f, 2.0f); + if (UIWidgets::CVarSliderFloat("Link Head Scale", CVAR_COSMETIC("Link.HeadScale.Value"), + UIWidgets::FloatSliderOptions() + .Format("%.1fx") + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f) + .Step(0.1f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR))) { CVarSetInteger(CVAR_COSMETIC("Link.HeadScale.Changed"), 1); } Reset_Option_Double("Reset##Link_HeadScale", CVAR_COSMETIC("Link.HeadScale")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - if (UIWidgets::EnhancementSliderFloat("Link Sword Scale: %f", "##Link_SwordScale", CVAR_COSMETIC("Link.SwordScale.Value"), 1.0f, 2.5f, "", 1.0f, false)) { + if (UIWidgets::CVarSliderFloat("Link Sword Scale", CVAR_COSMETIC("Link.SwordScale.Value"), + UIWidgets::FloatSliderOptions() + .Format("%.1fx") + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f) + .Step(0.1f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR))) { CVarSetInteger(CVAR_COSMETIC("Link.SwordScale.Changed"), 1); } Reset_Option_Double("Reset##Link_SwordScale", CVAR_COSMETIC("Link.SwordScale")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementSliderFloat("Bunny Hood Length: %f", "##BunnyHood_EarLength", CVAR_COSMETIC("BunnyHood.EarLength"), -300.0f, 1000.0f, "", 0.0f, false); + UIWidgets::CVarSliderFloat("Bunny Hood Length", CVAR_COSMETIC("BunnyHood.EarLength"), + UIWidgets::FloatSliderOptions() + .Format("%.0f") + .Min(-300.0f) + .Max(1000.0f) + .DefaultValue(0.0f) + .Step(10.0f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); Reset_Option_Single("Reset##BunnyHood_EarLength", CVAR_COSMETIC("BunnyHood.EarLength")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementSliderFloat("Bunny Hood Spread: %f", "##BunnyHood_EarSpread", CVAR_COSMETIC("BunnyHood.EarSpread"), -300.0f, 500.0f, "", 0.0f, false); + UIWidgets::CVarSliderFloat("Bunny Hood Spread", CVAR_COSMETIC("BunnyHood.EarSpread"), + UIWidgets::FloatSliderOptions() + .Format("%.0f") + .Min(-300.0f) + .Max(500.0f) + .DefaultValue(0.0f) + .Step(10.0f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); Reset_Option_Single("Reset##BunnyHood_EarSpread", CVAR_COSMETIC("BunnyHood.EarSpread")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementSliderFloat("Goron Neck Length: %f", "##Goron_NeckLength", CVAR_COSMETIC("Goron.NeckLength"), 0.0f, 5000.0f, "", 0.0f, false); + UIWidgets::CVarSliderFloat("Goron Neck Length", CVAR_COSMETIC("Goron.NeckLength"), + UIWidgets::FloatSliderOptions() + .Format("%.0f") + .Min(0.0f) + .Max(5000.0f) + .DefaultValue(0.0f) + .Step(10.0f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); Reset_Option_Single("Reset##Goron_NeckLength", CVAR_COSMETIC("Goron.NeckLength")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementCheckbox("Unfix Goron Spin", CVAR_COSMETIC("UnfixGoronSpin")); + UIWidgets::CVarCheckbox("Unfix Goron Spin", CVAR_COSMETIC("UnfixGoronSpin"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR)); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementSliderFloat("Fairies Size: %f", "##Fairies_Size", CVAR_COSMETIC("Fairies.Size"), 0.25f, 5.0f, "", 1.0f, false); + UIWidgets::CVarSliderFloat("Fairies Size", CVAR_COSMETIC("Fairies.Size"), + UIWidgets::FloatSliderOptions() + .Format("%.1fx") + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f) + .Step(0.1f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); Reset_Option_Single("Reset##Fairies_Size", CVAR_COSMETIC("Fairies.Size")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementSliderFloat("N64 Logo Spin Speed: %f", "##N64Logo_SpinSpeed", CVAR_COSMETIC("N64Logo.SpinSpeed"), 0.25f, 5.0f, "", 1.0f, false); + UIWidgets::CVarSliderFloat("N64 Logo Spin Speed", CVAR_COSMETIC("N64Logo.SpinSpeed"), + UIWidgets::FloatSliderOptions() + .Format("%.1fx") + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f) + .Step(0.1f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); Reset_Option_Single("Reset##N64Logo_SpinSpeed", CVAR_COSMETIC("N64Logo.SpinSpeed")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::EnhancementSliderFloat("Moon Size: %f", "##Moon_Size", CVAR_COSMETIC("Moon.Size"), 0.5f, 2.0f, "", 1.0f, false); + UIWidgets::CVarSliderFloat("Moon Size", CVAR_COSMETIC("Moon.Size"), + UIWidgets::FloatSliderOptions() + .Format("%.1fx") + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f) + .Step(0.1f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); Reset_Option_Single("Reset##Moon_Size", CVAR_COSMETIC("Moon.Size")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - if (UIWidgets::EnhancementSliderFloat("Kak Windmill Speed: %f", "##Kak_Windmill_Speed", CVAR_COSMETIC("Kak.Windmill_Speed.Value"), 100.0f, 6000.0f, "", 100.0f, false)) { + if (UIWidgets::CVarSliderFloat("Kak Windmill Speed", CVAR_COSMETIC("Kak.Windmill_Speed.Value"), + UIWidgets::FloatSliderOptions() + .Format("%.0f") + .Min(100.0f) + .Max(6000.0f) + .DefaultValue(100.0f) + .Step(10.0f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR))) { CVarSetInteger(CVAR_COSMETIC("Kak.Windmill_Speed.Changed"), 1); } Reset_Option_Double("Reset##Kak_Windmill_Speed", CVAR_COSMETIC("Kak.Windmill_Speed")); - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); ImGui::EndDisabled(); } @@ -1582,7 +1738,7 @@ void CopyMultipliedColor(CosmeticOption& cosmeticOptionSrc, CosmeticOption& cosm cosmeticOptionTarget.currentColor.z = newColor.b / 255.0f; cosmeticOptionTarget.currentColor.w = newColor.a / 255.0f; - CVarSetColor(cosmeticOptionTarget.cvar, newColor); + CVarSetColor(cosmeticOptionTarget.valuesCvar, newColor); CVarSetInteger((cosmeticOptionTarget.rainbowCvar), 0); CVarSetInteger((cosmeticOptionTarget.changedCvar), 1); } @@ -1642,7 +1798,7 @@ void RandomizeColor(CosmeticOption& cosmeticOption) { cosmeticOption.currentColor.z = newColor.b / 255.0f; cosmeticOption.currentColor.w = newColor.a / 255.0f; - CVarSetColor(cosmeticOption.cvar, newColor); + CVarSetColor(cosmeticOption.valuesCvar, newColor); CVarSetInteger(cosmeticOption.rainbowCvar, 0); CVarSetInteger(cosmeticOption.changedCvar, 1); ApplySideEffects(cosmeticOption); @@ -1658,12 +1814,12 @@ void ResetColor(CosmeticOption& cosmeticOption) { CVarClear(cosmeticOption.changedCvar); CVarClear(cosmeticOption.rainbowCvar); CVarClear(cosmeticOption.lockedCvar); - CVarClear(cosmeticOption.cvar); - CVarClear((std::string(cosmeticOption.cvar) + ".R").c_str()); - CVarClear((std::string(cosmeticOption.cvar) + ".G").c_str()); - CVarClear((std::string(cosmeticOption.cvar) + ".B").c_str()); - CVarClear((std::string(cosmeticOption.cvar) + ".A").c_str()); - CVarClear((std::string(cosmeticOption.cvar) + ".Type").c_str()); + CVarClear(cosmeticOption.valuesCvar); + CVarClear((std::string(cosmeticOption.valuesCvar) + ".R").c_str()); + CVarClear((std::string(cosmeticOption.valuesCvar) + ".G").c_str()); + CVarClear((std::string(cosmeticOption.valuesCvar) + ".B").c_str()); + CVarClear((std::string(cosmeticOption.valuesCvar) + ".A").c_str()); + CVarClear((std::string(cosmeticOption.valuesCvar) + ".Type").c_str()); // This portion should match 1:1 the multiplied colors in `ApplySideEffect()` if (cosmeticOption.label == "Bow Body") { @@ -1709,40 +1865,28 @@ void ResetColor(CosmeticOption& cosmeticOption) { } void DrawCosmeticRow(CosmeticOption& cosmeticOption) { - bool colorChanged; - if (cosmeticOption.supportsAlpha) { - colorChanged = ImGui::ColorEdit4(cosmeticOption.label.c_str(), (float*)&cosmeticOption.currentColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel); - } else { - colorChanged = ImGui::ColorEdit3(cosmeticOption.label.c_str(), (float*)&cosmeticOption.currentColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel); - } - if (colorChanged) { - Color_RGBA8 color; - color.r = static_cast(cosmeticOption.currentColor.x * 255.0f); - color.g = static_cast(cosmeticOption.currentColor.y * 255.0f); - color.b = static_cast(cosmeticOption.currentColor.z * 255.0f); - color.a = static_cast(cosmeticOption.currentColor.w * 255.0f); - - CVarSetColor(cosmeticOption.cvar, color); + if (UIWidgets::CVarColorPicker(cosmeticOption.label.c_str(), cosmeticOption.cvar, + cosmeticOption.defaultColor, + cosmeticOption.supportsAlpha, 0, THEME_COLOR)) { CVarSetInteger((cosmeticOption.rainbowCvar), 0); CVarSetInteger((cosmeticOption.changedCvar), 1); ApplySideEffects(cosmeticOption); ApplyOrResetCustomGfxPatches(); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - ImGui::SameLine(); - ImGui::Text("%s", cosmeticOption.label.c_str()); //the longest option name - ImGui::SameLine((ImGui::CalcTextSize("Message Light Blue (None No Shadow) Color").x * 1.0f) + 60.0f); - if (ImGui::Button(("Random##" + cosmeticOption.label).c_str())) { + ImGui::SameLine((ImGui::CalcTextSize("Message Light Blue (None No Shadow)").x * 1.0f) + 60.0f); + if (UIWidgets::Button( + ("Random##" + cosmeticOption.label).c_str(), + UIWidgets::ButtonOptions().Size(ImVec2(80, 31)).Padding(ImVec2(2.0f, 0.0f)).Color(THEME_COLOR))) { RandomizeColor(cosmeticOption); ApplyOrResetCustomGfxPatches(); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } if (cosmeticOption.supportsRainbow) { ImGui::SameLine(); - bool isRainbow = (bool)CVarGetInteger((cosmeticOption.rainbowCvar), 0); - if (ImGui::Checkbox(("Rainbow##" + cosmeticOption.label).c_str(), &isRainbow)) { - CVarSetInteger((cosmeticOption.rainbowCvar), isRainbow); + if (UIWidgets::CVarCheckbox(("Rainbow##" + cosmeticOption.label).c_str(), cosmeticOption.rainbowCvar, + UIWidgets::CheckboxOptions().Color(THEME_COLOR))) { CVarSetInteger((cosmeticOption.changedCvar), 1); ApplySideEffects(cosmeticOption); ApplyOrResetCustomGfxPatches(); @@ -1750,14 +1894,14 @@ void DrawCosmeticRow(CosmeticOption& cosmeticOption) { } } ImGui::SameLine(); - bool isLocked = (bool)CVarGetInteger((cosmeticOption.lockedCvar), 0); - if (ImGui::Checkbox(("Locked##" + cosmeticOption.label).c_str(), &isLocked)) { - CVarSetInteger((cosmeticOption.lockedCvar), isLocked); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } + + UIWidgets::CVarCheckbox(("Locked##" + cosmeticOption.label).c_str(), cosmeticOption.lockedCvar, + UIWidgets::CheckboxOptions().Color(THEME_COLOR)); + if (CVarGetInteger((cosmeticOption.changedCvar), 0)) { ImGui::SameLine(); - if (ImGui::Button(("Reset##" + cosmeticOption.label).c_str())) { + if (UIWidgets::Button(("Reset##" + cosmeticOption.label).c_str(), + UIWidgets::ButtonOptions().Size(ImVec2(80, 31)).Padding(ImVec2(2.0f, 0.0f)))) { ResetColor(cosmeticOption); ApplyOrResetCustomGfxPatches(); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); @@ -1769,32 +1913,35 @@ void DrawCosmeticGroup(CosmeticGroup cosmeticGroup) { std::string label = groupLabels.at(cosmeticGroup); ImGui::Text("%s", label.c_str()); // the longest option name - ImGui::SameLine((ImGui::CalcTextSize("Message Light Blue (None No Shadow) Color").x * 1.0f) + 60.0f); - if (ImGui::Button(("Random##" + label).c_str())) { + ImGui::SameLine((ImGui::CalcTextSize("Message Light Blue (None No Shadow)").x * 1.0f) + 60.0f); + if (UIWidgets::Button(("Random##" + label).c_str(), + UIWidgets::ButtonOptions().Size(ImVec2(80, 31)).Padding(ImVec2(2.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { - if (cosmeticOption.group == cosmeticGroup && (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) && !CVarGetInteger(cosmeticOption.lockedCvar, 0)) { + if (cosmeticOption.group == cosmeticGroup && + (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) && + !CVarGetInteger(cosmeticOption.lockedCvar, 0)) { RandomizeColor(cosmeticOption); } } ApplyOrResetCustomGfxPatches(); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } ImGui::SameLine(); - if (ImGui::Button(("Reset##" + label).c_str())) { + if (UIWidgets::Button(("Reset##" + label).c_str(), + UIWidgets::ButtonOptions().Size(ImVec2(80, 31)).Padding(ImVec2(2.0f, 0.0f)))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { if (cosmeticOption.group == cosmeticGroup && !CVarGetInteger(cosmeticOption.lockedCvar, 0)) { ResetColor(cosmeticOption); } } ApplyOrResetCustomGfxPatches(); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } + UIWidgets::Spacer(); for (auto& [id, cosmeticOption] : cosmeticOptions) { if (cosmeticOption.group == cosmeticGroup && (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0))) { DrawCosmeticRow(cosmeticOption); } } - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); } static const char* colorSchemes[2] = { @@ -1807,193 +1954,199 @@ void CosmeticsEditorWindow::ApplyDungeonKeyColors() { ResetColor(cosmeticOptions.at("Key.KeyringRing")); // Forest Temple - CVarSetColor(cosmeticOptions["Key.ForestSmallBody"].cvar, { 4, 195, 46, 255 }); + CVarSetColor(cosmeticOptions["Key.ForestSmallBody"].valuesCvar, { 4, 195, 46, 255 }); CVarSetInteger(cosmeticOptions["Key.ForestSmallBody"].changedCvar, 1); cosmeticOptions["Key.ForestSmallBody"].currentColor = { 4 / 255.0f, 195 / 255.0f, 46 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.ForestSmallEmblem")); ResetColor(cosmeticOptions.at("Key.ForestBossBody")); - CVarSetColor(cosmeticOptions["Key.ForestBossGem"].cvar, { 0, 255, 0, 255 }); + CVarSetColor(cosmeticOptions["Key.ForestBossGem"].valuesCvar, { 0, 255, 0, 255 }); CVarSetInteger(cosmeticOptions["Key.ForestBossGem"].changedCvar, 1); cosmeticOptions["Key.ForestBossGem"].currentColor = { 0, 255 / 255.0f, 0, 255 / 255.0f }; // Fire Temple - CVarSetColor(cosmeticOptions["Key.FireSmallBody"].cvar, { 237, 95, 95, 255 }); + CVarSetColor(cosmeticOptions["Key.FireSmallBody"].valuesCvar, { 237, 95, 95, 255 }); CVarSetInteger(cosmeticOptions["Key.FireSmallBody"].changedCvar, 1); cosmeticOptions["Key.FireSmallBody"].currentColor = { 237 / 255.0f, 95 / 255.0f, 95 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.FireSmallEmblem")); ResetColor(cosmeticOptions.at("Key.FireBossBody")); - CVarSetColor(cosmeticOptions["Key.FireBossGem"].cvar, { 255, 30, 0, 255 }); + CVarSetColor(cosmeticOptions["Key.FireBossGem"].valuesCvar, { 255, 30, 0, 255 }); CVarSetInteger(cosmeticOptions["Key.FireBossGem"].changedCvar, 1); cosmeticOptions["Key.FireBossGem"].currentColor = { 255 / 255.0f, 30 / 255.0f, 0, 255 / 255.0f }; // Water Temple - CVarSetColor(cosmeticOptions["Key.WaterSmallBody"].cvar, { 85, 180, 223, 255 }); + CVarSetColor(cosmeticOptions["Key.WaterSmallBody"].valuesCvar, { 85, 180, 223, 255 }); CVarSetInteger(cosmeticOptions["Key.WaterSmallBody"].changedCvar, 1); cosmeticOptions["Key.WaterSmallBody"].currentColor = { 85 / 255.0f, 180 / 255.0f, 223 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.WaterSmallEmblem")); ResetColor(cosmeticOptions.at("Key.WaterBossBody")); - CVarSetColor(cosmeticOptions["Key.WaterBossGem"].cvar, { 0, 137, 255, 255 }); + CVarSetColor(cosmeticOptions["Key.WaterBossGem"].valuesCvar, { 0, 137, 255, 255 }); CVarSetInteger(cosmeticOptions["Key.WaterBossGem"].changedCvar, 1); cosmeticOptions["Key.WaterBossGem"].currentColor = { 0, 137 / 255.0f, 255 / 255.0f, 255 / 255.0f }; // Spirit Temple - CVarSetColor(cosmeticOptions["Key.SpiritSmallBody"].cvar, { 222, 158, 47, 255 }); + CVarSetColor(cosmeticOptions["Key.SpiritSmallBody"].valuesCvar, { 222, 158, 47, 255 }); CVarSetInteger(cosmeticOptions["Key.SpiritSmallBody"].changedCvar, 1); cosmeticOptions["Key.SpiritSmallBody"].currentColor = { 222 / 255.0f, 158 / 255.0f, 47 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.SpiritSmallEmblem")); ResetColor(cosmeticOptions.at("Key.SpiritBossBody")); - CVarSetColor(cosmeticOptions["Key.SpiritBossGem"].cvar, { 255, 85, 0, 255 }); + CVarSetColor(cosmeticOptions["Key.SpiritBossGem"].valuesCvar, { 255, 85, 0, 255 }); CVarSetInteger(cosmeticOptions["Key.SpiritBossGem"].changedCvar, 1); cosmeticOptions["Key.SpiritBossGem"].currentColor = { 255 / 255.0f, 85 / 255.0f, 0, 255 / 255.0f }; // Shadow Temple - CVarSetColor(cosmeticOptions["Key.ShadowSmallBody"].cvar, { 126, 16, 177, 255 }); + CVarSetColor(cosmeticOptions["Key.ShadowSmallBody"].valuesCvar, { 126, 16, 177, 255 }); CVarSetInteger(cosmeticOptions["Key.ShadowSmallBody"].changedCvar, 1); cosmeticOptions["Key.ShadowSmallBody"].currentColor = { 126 / 255.0f, 16 / 255.0f, 177 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.ShadowSmallEmblem")); ResetColor(cosmeticOptions.at("Key.ShadowBossBody")); - CVarSetColor(cosmeticOptions["Key.ShadowBossGem"].cvar, { 153, 0, 255, 255 }); + CVarSetColor(cosmeticOptions["Key.ShadowBossGem"].valuesCvar, { 153, 0, 255, 255 }); CVarSetInteger(cosmeticOptions["Key.ShadowBossGem"].changedCvar, 1); cosmeticOptions["Key.ShadowBossGem"].currentColor = { 153 / 255.0f, 0, 255 / 255.0f, 255 / 255.0f }; // Ganon's Tower - CVarSetColor(cosmeticOptions["Key.GanonsSmallBody"].cvar, { 80, 80, 80, 255 }); + CVarSetColor(cosmeticOptions["Key.GanonsSmallBody"].valuesCvar, { 80, 80, 80, 255 }); CVarSetInteger(cosmeticOptions["Key.GanonsSmallBody"].changedCvar, 1); cosmeticOptions["Key.GanonsSmallBody"].currentColor = { 80 / 255.0f, 80 / 255.0f, 80 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.GanonsSmallEmblem")); - CVarSetColor(cosmeticOptions["Key.GanonsBossBody"].cvar, { 80, 80, 80, 255 }); + CVarSetColor(cosmeticOptions["Key.GanonsBossBody"].valuesCvar, { 80, 80, 80, 255 }); CVarSetInteger(cosmeticOptions["Key.GanonsBossBody"].changedCvar, 1); cosmeticOptions["Key.GanonsBossBody"].currentColor = { 80 / 255.0f, 80 / 255.0f, 80 / 255.0f, 255 / 255.0f }; - CVarSetColor(cosmeticOptions["Key.GanonsBossGem"].cvar, { 255, 0, 0, 255 }); + CVarSetColor(cosmeticOptions["Key.GanonsBossGem"].valuesCvar, { 255, 0, 0, 255 }); CVarSetInteger(cosmeticOptions["Key.GanonsBossGem"].changedCvar, 1); cosmeticOptions["Key.GanonsBossGem"].currentColor = { 255 / 255.0f, 0, 0, 255 / 255.0f }; // Bottom of the Well - CVarSetColor(cosmeticOptions["Key.WellSmallBody"].cvar, { 227, 110, 255, 255 }); + CVarSetColor(cosmeticOptions["Key.WellSmallBody"].valuesCvar, { 227, 110, 255, 255 }); CVarSetInteger(cosmeticOptions["Key.WellSmallBody"].changedCvar, 1); cosmeticOptions["Key.WellSmallBody"].currentColor = { 227 / 255.0f, 110 / 255.0f, 255 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.WellSmallEmblem")); // Gerudo Training Ground - CVarSetColor(cosmeticOptions["Key.GTGSmallBody"].cvar, { 221, 212, 60, 255 }); + CVarSetColor(cosmeticOptions["Key.GTGSmallBody"].valuesCvar, { 221, 212, 60, 255 }); CVarSetInteger(cosmeticOptions["Key.GTGSmallBody"].changedCvar, 1); cosmeticOptions["Key.GTGSmallBody"].currentColor = { 221 / 255.0f, 212 / 255.0f, 60 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.GTGSmallEmblem")); // Gerudo Fortress - CVarSetColor(cosmeticOptions["Key.FortSmallBody"].cvar, { 255, 255, 255, 255 }); + CVarSetColor(cosmeticOptions["Key.FortSmallBody"].valuesCvar, { 255, 255, 255, 255 }); CVarSetInteger(cosmeticOptions["Key.FortSmallBody"].changedCvar, 1); cosmeticOptions["Key.FortSmallBody"].currentColor = { 255 / 255.0f, 255 / 255.0f, 255 / 255.0f, 255 / 255.0f }; ResetColor(cosmeticOptions.at("Key.FortSmallEmblem")); } void CosmeticsEditorWindow::DrawElement() { - ImGui::Text("Color Scheme"); - ImGui::SameLine(); - UIWidgets::EnhancementCombobox(CVAR_COSMETIC("DefaultColorScheme"), colorSchemes, COLORSCHEME_N64); - UIWidgets::EnhancementCheckbox("Advanced Mode", CVAR_COSMETIC("AdvancedMode")); - UIWidgets::InsertHelpHoverText( - "Some cosmetic options may not apply if you have any mods that provide custom models for the cosmetic option.\n\n" - "For example, if you have custom Link model, then the Link's Hair color option will most likely not apply." - ); - + UIWidgets::CVarCombobox("Color Scheme", CVAR_COSMETIC("DefaultColorScheme"), colorSchemes, + UIWidgets::ComboboxOptions() + .DefaultIndex(COLORSCHEME_N64) + .Color(THEME_COLOR) + .LabelPosition(UIWidgets::LabelPosition::Near) + .ComponentAlignment(UIWidgets::ComponentAlignment::Right)); + UIWidgets::CVarCheckbox("Sync Rainbow colors", CVAR_COSMETIC("RainbowSync"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR)); + UIWidgets::CVarSliderFloat("Rainbow Speed", CVAR_COSMETIC("RainbowSpeed"), + UIWidgets::FloatSliderOptions() + .Format("%.2f") + .Min(0.01f) + .Max(1.0f) + .DefaultValue(0.6f) + .Step(0.01f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Randomize All on New Scene", CVAR_COSMETIC("RandomizeAllOnNewScene"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Enables randomizing all unlocked cosmetics when you enter a new scene.")); + UIWidgets::CVarCheckbox( + "Advanced Mode", CVAR_COSMETIC("AdvancedMode"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip( + "Some cosmetic options may not apply if you have any mods that provide custom models for the cosmetic " + "option.\n\n" + "For example, if you have custom Link model, then the Link's Hair color option will most likely not " + "apply.")); if (CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) { - if (ImGui::Button("Lock All Advanced", ImVec2(ImGui::GetContentRegionAvail().x / 2, 30.0f))) { + if (UIWidgets::Button("Lock All Advanced", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { if (cosmeticOption.advancedOption) { CVarSetInteger(cosmeticOption.lockedCvar, 1); } } - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } ImGui::SameLine(); - if (ImGui::Button("Unlock All Advanced", ImVec2(ImGui::GetContentRegionAvail().x, 30.0f))) { + if (UIWidgets::Button("Unlock All Advanced", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { if (cosmeticOption.advancedOption) { CVarSetInteger(cosmeticOption.lockedCvar, 0); } } - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } } - UIWidgets::EnhancementCheckbox("Sync Rainbow colors", CVAR_COSMETIC("RainbowSync")); - UIWidgets::EnhancementSliderFloat("Rainbow Speed: %.3f", "##rainbowSpeed", CVAR_COSMETIC("RainbowSpeed"), 0.03f, 1.0f, "", 0.6f, false, true); - UIWidgets::EnhancementCheckbox("Randomize All on New Scene", CVAR_COSMETIC("RandomizeAllOnNewScene")); - UIWidgets::Tooltip("Enables randomizing all unlocked cosmetics when you enter a new scene."); - - if (ImGui::Button("Randomize All", ImVec2(ImGui::GetContentRegionAvail().x / 2, 30.0f))) { + if (UIWidgets::Button("Randomize All", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { CosmeticsEditor_RandomizeAll(); } ImGui::SameLine(); - if (ImGui::Button("Reset All", ImVec2(ImGui::GetContentRegionAvail().x, 30.0f))) { - for (auto& [id, cosmeticOption] : cosmeticOptions) { - if (!CVarGetInteger(cosmeticOption.lockedCvar, 0)) { - ResetColor(cosmeticOption); - } - } + if (UIWidgets::Button("Reset All", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { + CVarClearBlock("gCosmetics"); ApplyOrResetCustomGfxPatches(); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - - if (ImGui::Button("Lock All", ImVec2(ImGui::GetContentRegionAvail().x / 2, 30.0f))) { + if (UIWidgets::Button("Lock All", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { if (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) { CVarSetInteger(cosmeticOption.lockedCvar, 1); } } - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } ImGui::SameLine(); - if (ImGui::Button("Unlock All", ImVec2(ImGui::GetContentRegionAvail().x, 30.0f))) { + if (UIWidgets::Button("Unlock All", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { if (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) { CVarSetInteger(cosmeticOption.lockedCvar, 0); } } - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - if (ImGui::Button("Rainbow All", ImVec2(ImGui::GetContentRegionAvail().x / 2, 30.0f))) { + if (UIWidgets::Button("Rainbow All", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { - if ( - !CVarGetInteger(cosmeticOption.lockedCvar, 0) && - ( - !cosmeticOption.advancedOption || - CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0) - ) - ) { + if (!CVarGetInteger(cosmeticOption.lockedCvar, 0) && + (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0))) { CVarSetInteger(cosmeticOption.rainbowCvar, 1); CVarSetInteger(cosmeticOption.changedCvar, 1); } } - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } ImGui::SameLine(); - if (ImGui::Button("Un-Rainbow All", ImVec2(ImGui::GetContentRegionAvail().x, 30.0f))) { + if (UIWidgets::Button("Un-Rainbow All", + UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) { for (auto& [id, cosmeticOption] : cosmeticOptions) { - if ( - !CVarGetInteger(cosmeticOption.lockedCvar, 0) && - ( - !cosmeticOption.advancedOption || - CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0) - ) - ) { + if (!CVarGetInteger(cosmeticOption.lockedCvar, 0) && + (!cosmeticOption.advancedOption || CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0))) { CVarSetInteger(cosmeticOption.rainbowCvar, 0); } } - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } + UIWidgets::Spacer(3.0f); + + UIWidgets::PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("CosmeticsContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("Link & Items")) { - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_LINK); DrawCosmeticGroup(COSMETICS_GROUP_GLOVES); @@ -2006,14 +2159,15 @@ void CosmeticsEditorWindow::DrawElement() { if (ImGui::BeginTabItem("Keys")) { - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); - if (ImGui::Button("Give all keys dungeon-specific colors", ImVec2(300.0f, 30.0f))) { + if (UIWidgets::Button( + "Give all keys dungeon-specific colors", + UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { ApplyDungeonKeyColors(); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_KEYRING); DrawCosmeticGroup(COSMETICS_GROUP_SMALL_KEYS); @@ -2024,29 +2178,37 @@ void CosmeticsEditorWindow::DrawElement() { if (ImGui::BeginTabItem("Effects")) { - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_MAGIC); DrawCosmeticGroup(COSMETICS_GROUP_ARROWS); DrawCosmeticGroup(COSMETICS_GROUP_SPIN_ATTACK); DrawCosmeticGroup(COSMETICS_GROUP_TRAILS); - if (UIWidgets::EnhancementSliderInt("Trails Duration: %d", "##Trails_Duration", CVAR_COSMETIC("Trails.Duration.Value"), 2, 20, "", 4)) { + if (UIWidgets::CVarSliderInt("Trails Duration: %d", CVAR_COSMETIC("Trails.Duration.Value"), + UIWidgets::IntSliderOptions() + .Min(2) + .Max(20) + .DefaultValue(4) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR))) { CVarSetInteger(CVAR_COSMETIC("Trails.Duration.Changed"), 1); } ImGui::SameLine(); - if (ImGui::Button("Reset##Trails_Duration")) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + if (UIWidgets::Button("Reset##Trails_Duration", UIWidgets::ButtonOptions() + .Size(ImVec2(80, 36)) + .Padding(ImVec2(5.0f, 0.0f)))) { CVarClear(CVAR_COSMETIC("Trails.Duration.Value")); CVarClear(CVAR_COSMETIC("Trails.Duration.Changed")); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("World & NPCs")) { - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_WORLD); DrawCosmeticGroup(COSMETICS_GROUP_NAVI); @@ -2060,7 +2222,7 @@ void CosmeticsEditorWindow::DrawElement() { } if (ImGui::BeginTabItem("HUD")) { - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_HUD); DrawCosmeticGroup(COSMETICS_GROUP_TITLE); @@ -2074,6 +2236,7 @@ void CosmeticsEditorWindow::DrawElement() { if (CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) { if (ImGui::BeginTabItem("Pause Menu")) { + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_KALEIDO); ImGui::EndTabItem(); } @@ -2081,12 +2244,14 @@ void CosmeticsEditorWindow::DrawElement() { if (CVarGetInteger(CVAR_COSMETIC("AdvancedMode"), 0)) { if (ImGui::BeginTabItem("Message")) { + UIWidgets::Separator(true, true, 2.0f, 2.0f); DrawCosmeticGroup(COSMETICS_GROUP_MESSAGE); ImGui::EndTabItem(); } } ImGui::EndTabBar(); } + UIWidgets::PopStyleTabs(); } void RegisterOnLoadGameHook() { @@ -2113,7 +2278,7 @@ void CosmeticsEditorWindow::InitElement() { // Convert the `current color` into the format that the ImGui color picker expects for (auto& [id, cosmeticOption] : cosmeticOptions) { Color_RGBA8 defaultColor = {cosmeticOption.defaultColor.r, cosmeticOption.defaultColor.g, cosmeticOption.defaultColor.b, cosmeticOption.defaultColor.a}; - Color_RGBA8 cvarColor = CVarGetColor(cosmeticOption.cvar, defaultColor); + Color_RGBA8 cvarColor = CVarGetColor(cosmeticOption.valuesCvar, defaultColor); cosmeticOption.currentColor.x = cvarColor.r / 255.0f; cosmeticOption.currentColor.y = cvarColor.g / 255.0f; diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.h b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.h index c9a28ef29..9c3dd76ba 100644 --- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.h +++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.h @@ -52,7 +52,6 @@ static float TablesCellsWidth = 300.0f; static ImGuiTableColumnFlags FlagsTable = ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV; static ImGuiTableColumnFlags FlagsCell = ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_NoSort; -ImVec4 GetRandomValue(); void CosmeticsEditor_RandomizeAll(); void CosmeticsEditor_RandomizeGroup(CosmeticGroup group); void CosmeticsEditor_ResetAll(); diff --git a/soh/soh/Enhancements/debugger/MessageViewer.cpp b/soh/soh/Enhancements/debugger/MessageViewer.cpp index 589dbaf4a..49764374f 100644 --- a/soh/soh/Enhancements/debugger/MessageViewer.cpp +++ b/soh/soh/Enhancements/debugger/MessageViewer.cpp @@ -1,6 +1,9 @@ #include "MessageViewer.h" -#include +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" +#include "soh/OTRGlobals.h" + #include #include "../custom-message/CustomMessageManager.h" @@ -13,6 +16,8 @@ extern "C" u8 sMessageHasSetSfx; +using namespace UIWidgets; + void MessageViewer::InitElement() { CustomMessageManager::Instance->AddCustomMessageTable(TABLE_ID); mTableIdBuf = static_cast(calloc(MAX_STRING_SIZE, sizeof(char))); @@ -21,8 +26,10 @@ void MessageViewer::InitElement() { } void MessageViewer::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontMonoLargest); ImGui::Text("Table ID"); ImGui::SameLine(); + PushStyleInput(THEME_COLOR); ImGui::InputText("##TableID", mTableIdBuf, MAX_STRING_SIZE, ImGuiInputTextFlags_CallbackCharFilter, UIWidgets::TextFilters::FilterAlphaNum); UIWidgets::InsertHelpHoverText("Leave blank for vanilla table"); ImGui::Text("Text ID"); @@ -38,6 +45,8 @@ void MessageViewer::DrawElement() { UIWidgets::InsertHelpHoverText("Hexadecimal Text ID of the message to load. Hexadecimal digits only (0-9/A-F)."); break; } + PopStyleInput(); + PushStyleCheckbox(THEME_COLOR); if (ImGui::RadioButton("Hexadecimal", &mTextIdBase, HEXADECIMAL)) { memset(mTextIdBuf, 0, sizeof(char) * MAX_STRING_SIZE); } @@ -45,8 +54,10 @@ void MessageViewer::DrawElement() { if (ImGui::RadioButton("Decimal", &mTextIdBase, DECIMAL)) { memset(mTextIdBuf, 0, sizeof(char) * MAX_STRING_SIZE); } + PopStyleCheckbox(); ImGui::Text("Language"); ImGui::SameLine(); + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("##Language", mLanguages[mLanguage])) { // ReSharper disable CppDFAUnreachableCode for (size_t i = 0; i < mLanguages.size(); i++) { @@ -58,7 +69,9 @@ void MessageViewer::DrawElement() { } ImGui::EndCombo(); } + PopStyleCombobox(); UIWidgets::InsertHelpHoverText("Which language to load from the selected text ID"); + PushStyleButton(THEME_COLOR); if (ImGui::Button("Display Message##ExistingMessage")) { mDisplayExistingMessageClicked = true; } @@ -66,11 +79,14 @@ void MessageViewer::DrawElement() { UIWidgets::InsertHelpHoverText("Enter a string using Custom Message Syntax to preview it in-game. " "Any newline (\\n) characters inserted by the Enter key will be stripped " "from the output."); + PushStyleInput(THEME_COLOR); ImGui::InputTextMultiline("##CustomMessage", mCustomMessageBuf, MAX_STRING_SIZE); + PopStyleInput(); if (ImGui::Button("Display Message##CustomMessage")) { mDisplayCustomMessageClicked = true; } - // ReSharper restore CppDFAUnreachableCode + PopStyleButton(); + ImGui::PopFont(); } void MessageViewer::UpdateElement() { diff --git a/soh/soh/Enhancements/debugger/actorViewer.cpp b/soh/soh/Enhancements/debugger/actorViewer.cpp index 8aac60954..a154df65b 100644 --- a/soh/soh/Enhancements/debugger/actorViewer.cpp +++ b/soh/soh/Enhancements/debugger/actorViewer.cpp @@ -1,6 +1,7 @@ #include "actorViewer.h" #include "../../util.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "soh/ActorDB.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/nametag.h" @@ -59,6 +60,8 @@ std::array acMapping = { "Chest" }; +using namespace UIWidgets; + typedef enum { ACTORVIEWER_NAMETAGS_NONE, ACTORVIEWER_NAMETAGS_DESC, @@ -70,29 +73,18 @@ const std::string GetActorDescription(u16 id) { return ActorDB::Instance->RetrieveEntry(id).entry.valid ? ActorDB::Instance->RetrieveEntry(id).entry.desc : "???"; } -template void DrawGroupWithBorder(T&& drawFunc) { +template void DrawGroupWithBorder(T&& drawFunc, std::string section) { // First group encapsulates the inner portion and border - ImGui::BeginGroup(); - - ImVec2 padding = ImGui::GetStyle().FramePadding; - ImVec2 p0 = ImGui::GetCursorScreenPos(); - ImGui::SetCursorScreenPos(ImVec2(p0.x + padding.x, p0.y + padding.y)); + ImGui::BeginChild(std::string("##" + section).c_str(), ImVec2(0, 0), + ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY); // Second group encapsulates just the inner portion ImGui::BeginGroup(); - + ImGui::AlignTextToFramePadding(); drawFunc(); - - ImGui::Dummy(padding); ImGui::EndGroup(); - ImVec2 p1 = ImGui::GetItemRectMax(); - p1.x += padding.x; - ImVec4 borderCol = ImGui::GetStyle().Colors[ImGuiCol_Border]; - ImGui::GetWindowDrawList()->AddRect( - p0, p1, IM_COL32(borderCol.x * 255, borderCol.y * 255, borderCol.z * 255, borderCol.w * 255)); - - ImGui::EndGroup(); + ImGui::EndChild(); } void PopulateActorDropdown(int i, std::vector& data) { @@ -938,10 +930,12 @@ void ActorViewerWindow::DrawElement() { static std::string filler = "Please select"; static std::vector list; static u16 lastSceneId = 0; - static char searchString[64] = ""; + static std::string searchString = ""; static s16 currentSelectedInDropdown; static std::vector actors; + ImGui::PushFont(OTRGlobals::Instance->fontMonoLargest); + if (gPlayState != nullptr) { needs_reset = lastSceneId != gPlayState->sceneNum; if (needs_reset) { @@ -951,13 +945,13 @@ void ActorViewerWindow::DrawElement() { filler = "Please Select"; list.clear(); needs_reset = false; - for (size_t i = 0; i < ARRAY_COUNT(searchString); i += 1) { - searchString[i] = 0; - } + searchString = ""; currentSelectedInDropdown = -1; actors.clear(); } lastSceneId = gPlayState->sceneNum; + + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("Actor Type", acMapping[category])) { for (int i = 0; i < acMapping.size(); i++) { if (ImGui::Selectable(acMapping[i])) { @@ -990,7 +984,9 @@ void ActorViewerWindow::DrawElement() { } ImGui::EndCombo(); } + PopStyleCombobox(); + PushStyleHeader(THEME_COLOR); if (ImGui::TreeNode("Selected Actor")) { DrawGroupWithBorder([&]() { ImGui::Text("Name: %s", ActorDB::Instance->RetrieveEntry(display->id).name.c_str()); @@ -998,46 +994,52 @@ void ActorViewerWindow::DrawElement() { ImGui::Text("Category: %s", acMapping[display->category]); ImGui::Text("ID: %d", display->id); ImGui::Text("Parameters: %d", display->params); - }); - + }, "Selected Actor"); + ImGui::SameLine(); ImGui::PushItemWidth(ImGui::GetFontSize() * 6); DrawGroupWithBorder([&]() { + ImGui::PushItemWidth(ImGui::GetFontSize() * 6); + PushStyleInput(THEME_COLOR); ImGui::Text("Actor Position"); - ImGui::InputScalar("x pos", ImGuiDataType_Float, &display->world.pos.x); - ImGui::SameLine(); - ImGui::InputScalar("y pos", ImGuiDataType_Float, &display->world.pos.y); - ImGui::SameLine(); - ImGui::InputScalar("z pos", ImGuiDataType_Float, &display->world.pos.z); - }); - + ImGui::InputScalar("X##CurPos", ImGuiDataType_Float, &display->world.pos.x); + ImGui::InputScalar("Y##CurPos", ImGuiDataType_Float, &display->world.pos.y); + ImGui::InputScalar("Z##CurPos", ImGuiDataType_Float, &display->world.pos.z); + ImGui::PopItemWidth(); + PopStyleInput(); + }, "Actor Position"); + ImGui::SameLine(); DrawGroupWithBorder([&]() { + PushStyleInput(THEME_COLOR); + ImGui::PushItemWidth(ImGui::GetFontSize() * 6); ImGui::Text("Actor Rotation"); - ImGui::InputScalar("x rot", ImGuiDataType_S16, &display->world.rot.x); - ImGui::SameLine(); - ImGui::InputScalar("y rot", ImGuiDataType_S16, &display->world.rot.y); - ImGui::SameLine(); - ImGui::InputScalar("z rot", ImGuiDataType_S16, &display->world.rot.z); - }); + ImGui::InputScalar("X##CurRot", ImGuiDataType_S16, &display->world.rot.x); + ImGui::InputScalar("Y##CurRot", ImGuiDataType_S16, &display->world.rot.y); + ImGui::InputScalar("Z##CurRot", ImGuiDataType_S16, &display->world.rot.z); + ImGui::PopItemWidth(); + PopStyleInput(); + }, "Actor Rotation"); if (display->category == ACTORCAT_BOSS || display->category == ACTORCAT_ENEMY) { + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Enemy Health", ImGuiDataType_U8, &display->colChkInfo.health); + PopStyleInput(); UIWidgets::InsertHelpHoverText("Some actors might not use this!"); } DrawGroupWithBorder([&]() { ImGui::Text("flags"); UIWidgets::DrawFlagArray32("flags", display->flags); - }); + }, "flags"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("bgCheckFlags"); UIWidgets::DrawFlagArray16("bgCheckFlags", display->bgCheckFlags); - }); + }, "bgCheckFlags"); - if (ImGui::Button("Refresh")) { + if (Button("Refresh", ButtonOptions().Color(THEME_COLOR))) { PopulateActorDropdown(category, list); switch (rm) { case INTERACT: @@ -1053,13 +1055,13 @@ void ActorViewerWindow::DrawElement() { } } - if (ImGui::Button("Go to Actor")) { + if (Button("Go to Actor", ButtonOptions().Color(THEME_COLOR))) { Player* player = GET_PLAYER(gPlayState); Math_Vec3f_Copy(&player->actor.world.pos, &display->world.pos); Math_Vec3f_Copy(&player->actor.home.pos, &player->actor.world.pos); } - if (ImGui::Button("Fetch from Target")) { + if (Button("Fetch from Target", ButtonOptions().Color(THEME_COLOR).Tooltip("Grabs actor with target arrow above it. You might need C-Up for enemies"))) { Player* player = GET_PLAYER(gPlayState); fetch = player->talkActor; if (fetch != NULL) { @@ -1069,8 +1071,7 @@ void ActorViewerWindow::DrawElement() { rm = TARGET; } } - UIWidgets::InsertHelpHoverText("Grabs actor with target arrow above it. You might need C-Up for enemies"); - if (ImGui::Button("Fetch from Held")) { + if (Button("Fetch from Held", ButtonOptions().Color(THEME_COLOR).Tooltip("Grabs actor that Link is holding"))) { Player* player = GET_PLAYER(gPlayState); fetch = player->heldActor; if (fetch != NULL) { @@ -1080,8 +1081,7 @@ void ActorViewerWindow::DrawElement() { rm = HELD; } } - UIWidgets::InsertHelpHoverText("Grabs actor that Link is holding"); - if (ImGui::Button("Fetch from Interaction")) { + if (Button("Fetch from Interaction", ButtonOptions().Color(THEME_COLOR).Tooltip("Grabs actor from \"interaction range\""))) { Player* player = GET_PLAYER(gPlayState); fetch = player->interactRangeActor; if (fetch != NULL) { @@ -1091,21 +1091,21 @@ void ActorViewerWindow::DrawElement() { rm = INTERACT; } } - UIWidgets::InsertHelpHoverText("Grabs actor from \"interaction range\""); ImGui::TreePop(); } if (ImGui::TreeNode("New...")) { - ImGui::PushItemWidth(ImGui::GetFontSize() * 10); + //ImGui::PushItemWidth(ImGui::GetFontSize() * 10); - if (ImGui::InputText("Search Actor", searchString, ARRAY_COUNT(searchString))) { - actors = GetActorsWithDescriptionContainingString(std::string(searchString)); + if (InputString("Search Actor", &searchString, InputOptions().Color(THEME_COLOR))) { + actors = GetActorsWithDescriptionContainingString(searchString); currentSelectedInDropdown = -1; } - if (searchString[0] != 0 && !actors.empty()) { + if (!SohUtils::IsStringEmpty(searchString) && !actors.empty()) { std::string preview = currentSelectedInDropdown == -1 ? "Please Select" : ActorDB::Instance->RetrieveEntry(actors[currentSelectedInDropdown]).desc; + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("Results", preview.c_str())) { for (u8 i = 0; i < actors.size(); i++) { if (ImGui::Selectable( @@ -1118,6 +1118,7 @@ void ActorViewerWindow::DrawElement() { } ImGui::EndCombo(); } + PopStyleCombobox(); } ImGui::Text("%s", GetActorDescription(newActor.id).c_str()); @@ -1125,44 +1126,51 @@ void ActorViewerWindow::DrawElement() { newActor.params = 0; } - UIWidgets::EnhancementCheckbox("Advanced mode", CVAR_DEVELOPER_TOOLS("ActorViewer.AdvancedParams")); - UIWidgets::InsertHelpHoverText("Changes the actor specific param menus with a direct input"); + CVarCheckbox("Advanced mode", CVAR_DEVELOPER_TOOLS("ActorViewer.AdvancedParams"), CheckboxOptions().Tooltip("Changes the actor specific param menus with a direct input")); if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("ActorViewer.AdvancedParams"), 0)) { + PushStyleInput(THEME_COLOR); ImGui::InputScalar("params", ImGuiDataType_S16, &newActor.params, &one); + PopStyleInput(); } else if (std::find(noParamsActors.begin(), noParamsActors.end(), newActor.id) == noParamsActors.end()) { CreateActorSpecificData(); if (actorSpecificData.find(newActor.id) == actorSpecificData.end()) { + PushStyleInput(THEME_COLOR); ImGui::InputScalar("params", ImGuiDataType_S16, &newActor.params, &one); + PopStyleInput(); } else { DrawGroupWithBorder([&]() { ImGui::Text("Actor Specific Data"); newActor.params = actorSpecificData[newActor.id](newActor.params); - }); + }, "Actor Specific Data"); } } ImGui::PushItemWidth(ImGui::GetFontSize() * 6); DrawGroupWithBorder([&]() { + PushStyleInput(THEME_COLOR); ImGui::Text("New Actor Position"); - ImGui::InputScalar("posX", ImGuiDataType_Float, &newActor.pos.x); - ImGui::SameLine(); - ImGui::InputScalar("posY", ImGuiDataType_Float, &newActor.pos.y); - ImGui::SameLine(); - ImGui::InputScalar("posZ", ImGuiDataType_Float, &newActor.pos.z); - }); - + ImGui::PushItemWidth(ImGui::GetFontSize() * 6); + ImGui::InputScalar("X##NewPos", ImGuiDataType_Float, &newActor.pos.x); + ImGui::InputScalar("Y##NewPos", ImGuiDataType_Float, &newActor.pos.y); + ImGui::InputScalar("Z##NewPos", ImGuiDataType_Float, &newActor.pos.z); + ImGui::PopItemWidth(); + PopStyleInput(); + }, "New Actor Position"); + ImGui::SameLine(); DrawGroupWithBorder([&]() { + PushStyleInput(THEME_COLOR); ImGui::Text("New Actor Rotation"); - ImGui::InputScalar("rotX", ImGuiDataType_S16, &newActor.rot.x); - ImGui::SameLine(); - ImGui::InputScalar("rotY", ImGuiDataType_S16, &newActor.rot.y); - ImGui::SameLine(); - ImGui::InputScalar("rotZ", ImGuiDataType_S16, &newActor.rot.z); - }); + ImGui::PushItemWidth(ImGui::GetFontSize() * 6); + ImGui::InputScalar("X##NewRot", ImGuiDataType_S16, &newActor.rot.x); + ImGui::InputScalar("Y##NewRot", ImGuiDataType_S16, &newActor.rot.y); + ImGui::InputScalar("Z##NewRot", ImGuiDataType_S16, &newActor.rot.z); + ImGui::PopItemWidth(); + PopStyleInput(); + }, "New Actor Rotation"); - if (ImGui::Button("Fetch from Link")) { + if (Button("Fetch from Link", ButtonOptions().Color(THEME_COLOR))) { Player* player = GET_PLAYER(gPlayState); Vec3f newPos = player->actor.world.pos; Vec3s newRot = player->actor.world.rot; @@ -1170,7 +1178,7 @@ void ActorViewerWindow::DrawElement() { newActor.rot = newRot; } - if (ImGui::Button("Spawn")) { + if (Button("Spawn", ButtonOptions().Color(THEME_COLOR))) { if (ActorDB::Instance->RetrieveEntry(newActor.id).entry.valid) { Actor_Spawn(&gPlayState->actorCtx, gPlayState, newActor.id, newActor.pos.x, newActor.pos.y, newActor.pos.z, newActor.rot.x, newActor.rot.y, newActor.rot.z, newActor.params, 0); @@ -1179,7 +1187,7 @@ void ActorViewerWindow::DrawElement() { } } - if (ImGui::Button("Spawn as Child")) { + if (Button("Spawn as Child", ButtonOptions().Color(THEME_COLOR))) { Actor* parent = display; if (parent != NULL) { if (newActor.id >= 0 && newActor.id < ACTOR_ID_MAX && @@ -1193,28 +1201,26 @@ void ActorViewerWindow::DrawElement() { } } - if (ImGui::Button("Reset")) { + if (Button("Reset", ButtonOptions().Color(THEME_COLOR))) { newActor = { 0, 0, { 0, 0, 0 }, { 0, 0, 0 } }; } ImGui::TreePop(); } + PopStyleHeader(); - static const char* nameTagOptions[] = { - "None", - "Short Description", - "Actor ID", - "Both" + static std::unordered_map nameTagOptions = { + { 0, "None" }, + { 1, "Short Description" }, + { 2, "Actor ID" }, + { 3, "Both" }, }; - UIWidgets::Spacer(0); - - ImGui::Text("Actor Name Tags"); - if (UIWidgets::EnhancementCombobox(CVAR_DEVELOPER_TOOLS("ActorViewer.NameTags"), nameTagOptions, ACTORVIEWER_NAMETAGS_NONE)) { + if (CVarCombobox("Actor Name Tags", CVAR_DEVELOPER_TOOLS("ActorViewer.NameTags"), nameTagOptions, + ComboboxOptions().Color(THEME_COLOR).Tooltip("Adds \"name tags\" above actors for identification"))) { NameTag_RemoveAllByTag(DEBUG_ACTOR_NAMETAG_TAG); ActorViewer_AddTagForAllActors(); } - UIWidgets::Tooltip("Adds \"name tags\" above actors for identification"); } else { ImGui::Text("Global Context needed for actor info!"); if (needs_reset) { @@ -1223,13 +1229,12 @@ void ActorViewerWindow::DrawElement() { filler = "Please Select"; list.clear(); needs_reset = false; - for (size_t i = 0; i < ARRAY_COUNT(searchString); i += 1) { - searchString[i] = 0; - } + searchString = ""; currentSelectedInDropdown = -1; actors.clear(); } } + ImGui::PopFont(); } void ActorViewerWindow::InitElement() { diff --git a/soh/soh/Enhancements/debugger/colViewer.cpp b/soh/soh/Enhancements/debugger/colViewer.cpp index 493ffdfdc..de2272598 100644 --- a/soh/soh/Enhancements/debugger/colViewer.cpp +++ b/soh/soh/Enhancements/debugger/colViewer.cpp @@ -1,6 +1,7 @@ #include "colViewer.h" #include "../../frame_interpolation.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include #include @@ -19,12 +20,12 @@ extern "C" { extern PlayState* gPlayState; } -enum class ColRenderSetting { Disabled, Solid, Transparent }; +typedef enum ColRenderSetting { ColRenderDisabled, ColRenderSolid, ColRenderTransparent } ColRenderSetting ; -static const char* ColRenderSettingNames[] = { - "Disabled", - "Solid", - "Transparent", +static std::unordered_map ColRenderSettingNames = { + { ColRenderDisabled, "Disabled" }, + { ColRenderSolid, "Solid" }, + { ColRenderTransparent, "Transparent" }, }; ImVec4 scene_col; @@ -53,45 +54,69 @@ static std::vector cylinderVtx; static std::vector sphereGfx; static std::vector sphereVtx; +using namespace UIWidgets; + // Draws the ImGui window for the collision viewer void ColViewerWindow::DrawElement() { - UIWidgets::EnhancementCheckbox("Enabled", CVAR_DEVELOPER_TOOLS("ColViewer.Enabled")); + CheckboxOptions checkOpt = CheckboxOptions().Color(THEME_COLOR); + ComboboxOptions comboOpt = ComboboxOptions().Color(THEME_COLOR); + CVarCheckbox("Enabled", CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), checkOpt); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Scene", CVAR_DEVELOPER_TOOLS("ColViewer.Scene"), ColRenderSettingNames, COLVIEW_DISABLED); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Bg Actors", CVAR_DEVELOPER_TOOLS("ColViewer.BGActors"), ColRenderSettingNames, COLVIEW_DISABLED); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Col Check", CVAR_DEVELOPER_TOOLS("ColViewer.ColCheck"), ColRenderSettingNames, COLVIEW_DISABLED); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Waterbox", CVAR_DEVELOPER_TOOLS("ColViewer.Waterbox"), ColRenderSettingNames, COLVIEW_DISABLED); + CVarCombobox("Scene", CVAR_DEVELOPER_TOOLS("ColViewer.Scene"), ColRenderSettingNames, comboOpt); + CVarCombobox("Bg Actors", CVAR_DEVELOPER_TOOLS("ColViewer.BGActors"), ColRenderSettingNames, comboOpt); + CVarCombobox("Col Check", CVAR_DEVELOPER_TOOLS("ColViewer.ColCheck"), ColRenderSettingNames, comboOpt); + CVarCombobox("Waterbox", CVAR_DEVELOPER_TOOLS("ColViewer.Waterbox"), ColRenderSettingNames, comboOpt); - UIWidgets::EnhancementCheckbox("Apply as decal", CVAR_DEVELOPER_TOOLS("ColViewer.Decal"), false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::InsertHelpHoverText("Applies the collision as a decal display. This can be useful if there is z-fighting occuring " - "with the scene geometry, but can cause other artifacts."); - UIWidgets::EnhancementCheckbox("Shaded", CVAR_DEVELOPER_TOOLS("ColViewer.Shaded")); - UIWidgets::InsertHelpHoverText("Applies the scene's shading to the collision display."); + CVarCheckbox("Apply as decal", CVAR_DEVELOPER_TOOLS("ColViewer.Decal"), + checkOpt.DefaultValue(true).Tooltip("Applies the collision as a decal display. This can be useful if there is z-fighting occuring " + "with the scene geometry, but can cause other artifacts.")); + CVarCheckbox("Shaded", CVAR_DEVELOPER_TOOLS("ColViewer.Shaded"), checkOpt.DefaultValue(false).Tooltip("Applies the scene's shading to the collision display.")); // This has to be duplicated in both code paths due to the nature of ImGui::IsItemHovered() const std::string colorHelpText = "View and change the colors used for collision display."; + PushStyleHeader(THEME_COLOR); if (ImGui::TreeNode("Colors")) { - UIWidgets::InsertHelpHoverText(colorHelpText); + UIWidgets::Tooltip(colorHelpText.c_str()); - UIWidgets::EnhancementColor("Normal", CVAR_DEVELOPER_TOOLS("ColViewer.ColorNormal"), scene_col, ImVec4(255, 255, 255, 255), false); - UIWidgets::EnhancementColor("Hookshot", CVAR_DEVELOPER_TOOLS("ColViewer.ColorHookshot"), hookshot_col, ImVec4(128, 128, 255, 255), - false); - UIWidgets::EnhancementColor("Entrance", CVAR_DEVELOPER_TOOLS("ColViewer.ColorEntrance"), entrance_col, ImVec4(0, 255, 0, 255), false); - UIWidgets::EnhancementColor("Special Surface (Grass/Sand/Etc)", CVAR_DEVELOPER_TOOLS("ColViewer.ColorSpecialSurface"), - specialSurface_col, ImVec4(192, 255, 192, 255), false); - UIWidgets::EnhancementColor("Interactable (Vines/Crawlspace/Etc)", CVAR_DEVELOPER_TOOLS("ColViewer.ColorInteractable"), - interactable_col, ImVec4(192, 0, 192, 255), false); - UIWidgets::EnhancementColor("Slope", CVAR_DEVELOPER_TOOLS("ColViewer.ColorSlope"), slope_col, ImVec4(255, 255, 128, 255), false); - UIWidgets::EnhancementColor("Void", CVAR_DEVELOPER_TOOLS("ColViewer.ColorVoid"), void_col, ImVec4(255, 0, 0, 255), false); - UIWidgets::EnhancementColor("OC", CVAR_DEVELOPER_TOOLS("ColViewer.ColorOC"), oc_col, ImVec4(255, 255, 255, 255), false); - UIWidgets::EnhancementColor("AC", CVAR_DEVELOPER_TOOLS("ColViewer.ColorAC"), ac_col, ImVec4(0, 0, 255, 255), false); - UIWidgets::EnhancementColor("AT", CVAR_DEVELOPER_TOOLS("ColViewer.ColorAT"), at_col, ImVec4(255, 0, 0, 255), false); - UIWidgets::EnhancementColor("Waterbox", CVAR_DEVELOPER_TOOLS("ColViewer.ColorWaterbox"), waterbox_col, ImVec4(0, 0, 255, 255), false); + if (CVarColorPicker("Normal", CVAR_DEVELOPER_TOOLS("ColViewer.ColorNormal"), { 255, 255, 255, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + scene_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorNormal"), { 255, 255, 255, 255 })); + } + if (CVarColorPicker("Hookshot", CVAR_DEVELOPER_TOOLS("ColViewer.ColorHookshot"), { 128, 128, 255, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + hookshot_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorHookshot"), { 128, 128, 255, 255 })); + } + if (CVarColorPicker("Entrance", CVAR_DEVELOPER_TOOLS("ColViewer.ColorEntrance"), { 0, 255, 0, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + entrance_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorEntrance"), { 0, 255, 0, 255 })); + } + if (CVarColorPicker("Special Surface (Grass/Sand/Etc)", CVAR_DEVELOPER_TOOLS("ColViewer.ColorSpecialSurface"), { 192, 255, 192, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + specialSurface_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorSpecialSurface"), { 192, 255, 192, 255 })); + } + if (CVarColorPicker("Interactable (Vines/Crawlspace/Etc)", CVAR_DEVELOPER_TOOLS("ColViewer.ColorInteractable"), { 192, 0, 192, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + interactable_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorInteractable"), { 192, 0, 192, 255 })); + } + if (CVarColorPicker("Slope", CVAR_DEVELOPER_TOOLS("ColViewer.ColorSlope"), { 255, 255, 128, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + slope_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorSlope"), { 255, 255, 128, 255 })); + } + if (CVarColorPicker("Void", CVAR_DEVELOPER_TOOLS("ColViewer.ColorVoid"), { 255, 0, 0, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + void_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorVoid"), { 255, 0, 0, 255 })); + } + if (CVarColorPicker("OC", CVAR_DEVELOPER_TOOLS("ColViewer.ColorOC"), { 255, 255, 255, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + oc_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorOC"), { 255, 255, 255, 255 })); + } + if (CVarColorPicker("AC", CVAR_DEVELOPER_TOOLS("ColViewer.ColorAC"), { 0, 0, 255, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + ac_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorAC"), { 0, 0, 255, 255 })); + } + if (CVarColorPicker("AT", CVAR_DEVELOPER_TOOLS("ColViewer.ColorAT"), { 255, 0, 0, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + at_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorAT"), { 255, 0, 0, 255 })); + } + if (CVarColorPicker("Waterbox", CVAR_DEVELOPER_TOOLS("ColViewer.ColorWaterbox"), { 0, 0, 255, 255 }, false, ColorPickerResetButton | ColorPickerRandomButton, THEME_COLOR)) { + waterbox_col = VecFromRGBA8(CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorWaterbox"), { 0, 0, 255, 255 })); + } ImGui::TreePop(); } else { - UIWidgets::InsertHelpHoverText(colorHelpText); + UIWidgets::Tooltip(colorHelpText.c_str()); } + PopStyleHeader(); } // Calculates the normal for a triangle at the 3 specified points @@ -287,7 +312,7 @@ void InitGfx(std::vector& gfx, ColRenderSetting setting) { uint64_t cm; uint32_t gm; - if (setting == ColRenderSetting::Transparent) { + if (setting == ColRenderTransparent) { rm = Z_CMP | IM_RD | CVG_DST_FULL | FORCE_BL; blc1 = GBL_c1(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA); blc2 = GBL_c2(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA); @@ -301,7 +326,7 @@ void InitGfx(std::vector& gfx, ColRenderSetting setting) { if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Decal"), 1) != 0) { rm |= ZMODE_DEC; - } else if (setting == ColRenderSetting::Transparent) { + } else if (setting == ColRenderTransparent) { rm |= ZMODE_XLU; } else { rm |= ZMODE_OPA; @@ -340,21 +365,21 @@ void DrawDynapoly(std::vector& dl, CollisionHeader* col, int32_t bgId) { CollisionPoly* poly = &col->polyList[i]; if (SurfaceType_IsHookshotSurface(&gPlayState->colCtx, poly, bgId)) { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorHookshot"), { 128, 128, 255, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorHookshot.Value"), { 128, 128, 255, 255 }); } else if (func_80041D94(&gPlayState->colCtx, poly, bgId) > 0x01) { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorInteractable"), {192, 0, 192, 255}); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorInteractable.Value"), {192, 0, 192, 255}); } else if (func_80041E80(&gPlayState->colCtx, poly, bgId) == 0x0C) { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorVoid"), { 255, 0, 0, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorVoid.Value"), { 255, 0, 0, 255 }); } else if (SurfaceType_GetSceneExitIndex(&gPlayState->colCtx, poly, bgId) || func_80041E80(&gPlayState->colCtx, poly, bgId) == 0x05) { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorEntrance"), { 0, 255, 0, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorEntrance.Value"), { 0, 255, 0, 255 }); } else if (func_80041D4C(&gPlayState->colCtx, poly, bgId) != 0 || SurfaceType_IsWallDamage(&gPlayState->colCtx, poly, bgId)) { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorSpecialSurface"), { 192, 255, 192, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorSpecialSurface.Value"), { 192, 255, 192, 255 }); } else if (SurfaceType_GetSlope(&gPlayState->colCtx, poly, bgId) == 0x01) { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorSlope"), { 255, 255, 128, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorSlope.Value"), { 255, 255, 128, 255 }); } else { - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorNormal"), { 255, 255, 255, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorNormal.Value"), { 255, 255, 255, 255 }); } if (color.r != lastColorR || color.g != lastColorG || color.b != lastColorB) { @@ -404,11 +429,11 @@ void DrawDynapoly(std::vector& dl, CollisionHeader* col, int32_t bgId) { void DrawSceneCollision() { ColRenderSetting showSceneColSetting = (ColRenderSetting)CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Scene"), COLVIEW_DISABLED); - if (showSceneColSetting == ColRenderSetting::Disabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { + if (showSceneColSetting == ColRenderDisabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { return; } - std::vector& dl = (showSceneColSetting == ColRenderSetting::Transparent) ? xluDl : opaDl; + std::vector& dl = (showSceneColSetting == ColRenderTransparent) ? xluDl : opaDl; InitGfx(dl, showSceneColSetting); dl.push_back(gsSPMatrix(&gMtxClear, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH)); @@ -418,11 +443,11 @@ void DrawSceneCollision() { // Draws all Bg Actors void DrawBgActorCollision() { ColRenderSetting showBgActorSetting = (ColRenderSetting)CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.BGActors"), COLVIEW_DISABLED); - if (showBgActorSetting == ColRenderSetting::Disabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { + if (showBgActorSetting == ColRenderDisabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { return; } - std::vector& dl = (showBgActorSetting == ColRenderSetting::Transparent) ? xluDl : opaDl; + std::vector& dl = (showBgActorSetting == ColRenderTransparent) ? xluDl : opaDl; InitGfx(dl, showBgActorSetting); dl.push_back(gsSPMatrix(&gMtxClear, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH)); @@ -543,22 +568,22 @@ void DrawColCheckList(std::vector& dl, Collider** objects, int32_t count) { // Draws all Col Check objects void DrawColCheckCollision() { ColRenderSetting showColCheckSetting = (ColRenderSetting)CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.ColCheck"), COLVIEW_DISABLED); - if (showColCheckSetting == ColRenderSetting::Disabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { + if (showColCheckSetting == ColRenderDisabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { return; } - std::vector& dl = (showColCheckSetting == ColRenderSetting::Transparent) ? xluDl : opaDl; + std::vector& dl = (showColCheckSetting == ColRenderTransparent) ? xluDl : opaDl; InitGfx(dl, showColCheckSetting); dl.push_back(gsSPMatrix(&gMtxClear, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH)); CollisionCheckContext& col = gPlayState->colChkCtx; - Color_RGBA8 color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorOC"), { 255, 255, 255, 255 }); + Color_RGBA8 color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorOC.Value"), { 255, 255, 255, 255 }); dl.push_back(gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); DrawColCheckList(dl, col.colOC, col.colOCCount); - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorAC"), { 0, 0, 255, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorAC.Value"), { 0, 0, 255, 255 }); dl.push_back(gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); DrawColCheckList(dl, col.colAC, col.colACCount); - color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorAT"), { 0, 0, 255, 255 }); + color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorAT.Value"), { 0, 0, 255, 255 }); dl.push_back(gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); DrawColCheckList(dl, col.colAT, col.colATCount); @@ -595,15 +620,15 @@ extern "C" f32 zdWaterBoxMinY; // Draws all waterboxes void DrawWaterboxList() { ColRenderSetting showWaterboxSetting = (ColRenderSetting)CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Waterbox"), COLVIEW_DISABLED); - if (showWaterboxSetting == ColRenderSetting::Disabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { + if (showWaterboxSetting == ColRenderDisabled || !CVarGetInteger(CVAR_DEVELOPER_TOOLS("ColViewer.Enabled"), 0)) { return; } - std::vector& dl = (showWaterboxSetting == ColRenderSetting::Transparent) ? xluDl : opaDl; + std::vector& dl = (showWaterboxSetting == ColRenderTransparent) ? xluDl : opaDl; InitGfx(dl, showWaterboxSetting); dl.push_back(gsSPMatrix(&gMtxClear, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH)); - Color_RGBA8 color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorWaterbox"), { 0, 0, 255, 255 }); + Color_RGBA8 color = CVarGetColor(CVAR_DEVELOPER_TOOLS("ColViewer.ColorWaterbox.Value"), { 0, 0, 255, 255 }); dl.push_back(gsDPSetPrimColor(0, 0, color.r, color.g, color.b, 255)); diff --git a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp index b9858aff4..dfc4d9681 100644 --- a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp +++ b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp @@ -3,6 +3,7 @@ #include "soh/SohGui/ImGuiUtils.h" #include "soh/OTRGlobals.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include #include @@ -27,7 +28,7 @@ extern PlayState* gPlayState; } // Maps entries in the GS flag array to the area name it represents -std::vector gsMapping = { +std::vector gsMapping = { "Deku Tree", "Dodongo's Cavern", "Inside Jabu-Jabu's Belly", @@ -56,6 +57,15 @@ extern "C" u8 gAreaGsFlags[]; extern "C" u8 gAmmoItems[]; +#define IMAGE_SIZE 48.0f + +using namespace UIWidgets; + +IntSliderOptions intSliderOptionsBase; +ButtonOptions buttonOptionsBase; +CheckboxOptions checkboxOptionsBase; +ComboboxOptions comboboxOptionsBase; + // Modification of gAmmoItems that replaces ITEM_NONE with the item in inventory slot it represents u8 gAllAmmoItems[] = { ITEM_STICK, ITEM_NUT, ITEM_BOMB, ITEM_BOW, ITEM_ARROW_FIRE, ITEM_DINS_FIRE, @@ -65,28 +75,17 @@ u8 gAllAmmoItems[] = { // Encapsulates what is drawn by the passed-in function within a border template -void DrawGroupWithBorder(T&& drawFunc) { +void DrawGroupWithBorder(T&& drawFunc, std::string section) { // First group encapsulates the inner portion and border + ImGui::BeginChild(std::string("##" + section).c_str(), ImVec2(0, 0), + ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY); + ImGui::BeginGroup(); - - ImVec2 padding = ImGui::GetStyle().FramePadding; - ImVec2 p0 = ImGui::GetCursorScreenPos(); - ImGui::SetCursorScreenPos(ImVec2(p0.x + padding.x, p0.y + padding.y)); - - // Second group encapsulates just the inner portion - ImGui::BeginGroup(); - + ImGui::AlignTextToFramePadding(); drawFunc(); - - ImGui::Dummy(padding); ImGui::EndGroup(); - ImVec2 p1 = ImGui::GetItemRectMax(); - p1.x += padding.x; - ImVec4 borderCol = ImGui::GetStyle().Colors[ImGuiCol_Border]; - ImGui::GetWindowDrawList()->AddRect(p0, p1, IM_COL32(borderCol.x * 255, borderCol.y * 255, borderCol.z * 255, borderCol.w * 255)); - - ImGui::EndGroup(); + ImGui::EndChild(); } char z2ASCII(int code) { @@ -108,6 +107,42 @@ char z2ASCII(int code) { } +typedef enum MagicLevel { + MAGIC_LEVEL_NONE, + MAGIC_LEVEL_SINGLE, + MAGIC_LEVEL_DOUBLE +}; + +std::unordered_map magicLevelMap = { + { MAGIC_LEVEL_NONE, "None" }, + { MAGIC_LEVEL_SINGLE, "Single" }, + { MAGIC_LEVEL_DOUBLE, "Double" }, +}; + +typedef enum AudioOutput { + AUDIO_STEREO, + AUDIO_MONO, + AUDIO_HEADSET, + AUDIO_SURROUND, +}; + +std::unordered_map audioMap = { + { AUDIO_STEREO, "Stereo" }, + { AUDIO_MONO, "Mono" }, + { AUDIO_HEADSET, "Headset" }, + { AUDIO_SURROUND, "Surround" }, +}; + +typedef enum ZTarget { + Z_TARGET_SWITCH, + Z_TARGET_HOLD, +}; + +std::unordered_map zTargetMap = { + { Z_TARGET_SWITCH, "Switch" }, + { Z_TARGET_HOLD, "Hold" }, +}; + void DrawInfoTab() { // TODO Needs a better method for name changing but for now this will work. std::string name; @@ -121,200 +156,147 @@ void DrawInfoTab() { ImGui::PushItemWidth(ImGui::GetFontSize() * 6); ImGui::Text("Name: %s", name.c_str()); - UIWidgets::InsertHelpHoverText("Player Name"); + Tooltip("Player Name"); std::string nameID; for (int i = 0; i < 8; i++) { nameID = z2ASCII(i); if (i % 4 != 0) { ImGui::SameLine(); } + PushStyleInput(THEME_COLOR); ImGui::InputScalar(nameID.c_str(), ImGuiDataType_U8, &gSaveContext.playerName[i], &one, NULL); + PopStyleInput(); } // Use an intermediary to keep the health from updating (and potentially killing the player) // until it is done being edited int16_t healthIntermediary = gSaveContext.healthCapacity; + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Max Health", ImGuiDataType_S16, &healthIntermediary); + PopStyleInput(); if (ImGui::IsItemDeactivated()) { gSaveContext.healthCapacity = healthIntermediary; } - UIWidgets::InsertHelpHoverText("Maximum health. 16 units per full heart"); + Tooltip("Maximum health. 16 units per full heart"); if (gSaveContext.health > gSaveContext.healthCapacity) { gSaveContext.health = gSaveContext.healthCapacity; // Clamp health to new max } - - const uint16_t healthMin = 0; - const uint16_t healthMax = gSaveContext.healthCapacity; - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); - ImGui::SliderScalar("Health", ImGuiDataType_S16, &gSaveContext.health, &healthMin, &healthMax); - UIWidgets::InsertHelpHoverText("Current health. 16 units per full heart"); + int32_t health = (int32_t)gSaveContext.health; + if (SliderInt("Health", &health, intSliderOptionsBase.Tooltip("Current health. 16 units per full heart") + .Min(0).Max(gSaveContext.healthCapacity))) { + gSaveContext.health = (int16_t)health; + } bool isDoubleDefenseAcquired = gSaveContext.isDoubleDefenseAcquired != 0; - if (ImGui::Checkbox("Double Defense", &isDoubleDefenseAcquired)) { + if (Checkbox("Double Defense", &isDoubleDefenseAcquired, checkboxOptionsBase.Tooltip("Is double defense unlocked?"))) { gSaveContext.isDoubleDefenseAcquired = isDoubleDefenseAcquired; gSaveContext.inventory.defenseHearts = gSaveContext.isDoubleDefenseAcquired ? 20 : 0; // Set to get the border drawn in the UI } - UIWidgets::InsertHelpHoverText("Is double defense unlocked?"); - - std::string magicName; - if (gSaveContext.magicLevel == 2) { - magicName = "Double"; - } else if (gSaveContext.magicLevel == 1) { - magicName = "Single"; - } else { - magicName = "None"; + if (Combobox("Magic Level", &gSaveContext.magicLevel, magicLevelMap, comboboxOptionsBase.Tooltip("Current magic level"))) { + gSaveContext.isMagicAcquired = gSaveContext.magicLevel > 0; + gSaveContext.isDoubleMagicAcquired = gSaveContext.magicLevel == 2; } - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6); - if (ImGui::BeginCombo("Magic Level", magicName.c_str())) { - if (ImGui::Selectable("Double")) { - gSaveContext.magicLevel = 2; - gSaveContext.isMagicAcquired = true; - gSaveContext.isDoubleMagicAcquired = true; - } - if (ImGui::Selectable("Single")) { - gSaveContext.magicLevel = 1; - gSaveContext.isMagicAcquired = true; - gSaveContext.isDoubleMagicAcquired = false; - } - if (ImGui::Selectable("None")) { - gSaveContext.magicLevel = 0; - gSaveContext.isMagicAcquired = false; - gSaveContext.isDoubleMagicAcquired = false; - } - - ImGui::EndCombo(); - } - UIWidgets::InsertHelpHoverText("Current magic level"); gSaveContext.magicCapacity = gSaveContext.magicLevel * 0x30; // Set to get the bar drawn in the UI if (gSaveContext.magic > gSaveContext.magicCapacity) { gSaveContext.magic = gSaveContext.magicCapacity; // Clamp magic to new max } - const uint8_t magicMin = 0; - const uint8_t magicMax = gSaveContext.magicCapacity; - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); - ImGui::SliderScalar("Magic", ImGuiDataType_S8, &gSaveContext.magic, &magicMin, &magicMax); - UIWidgets::InsertHelpHoverText("Current magic. 48 units per magic level"); + int32_t magic = (int32_t)gSaveContext.magic; + if (SliderInt("Magic", &magic, intSliderOptionsBase.Min(0).Max(gSaveContext.magicCapacity).Tooltip("Current magic. 48 units per magic level"))) { + gSaveContext.magic = (int8_t)magic; + } + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Rupees", ImGuiDataType_S16, &gSaveContext.rupees); - UIWidgets::InsertHelpHoverText("Current rupees"); + Tooltip("Current rupees"); + PopStyleInput(); - const uint16_t dayTimeMin = 0; - const uint16_t dayTimeMax = 0xFFFF; - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); - ImGui::SliderScalar("Time", ImGuiDataType_U16, &gSaveContext.dayTime, &dayTimeMin, &dayTimeMax); - UIWidgets::InsertHelpHoverText("Time of day"); - if (ImGui::Button("Dawn")) { + SliderInt("Time", (int32_t*) &gSaveContext.dayTime, intSliderOptionsBase.Min(0).Max(0xFFFF).Tooltip("Time of day")); + if (Button("Dawn", buttonOptionsBase)) { gSaveContext.dayTime = 0x4000; } ImGui::SameLine(); - if (ImGui::Button("Noon")) { + if (Button("Noon", buttonOptionsBase)) { gSaveContext.dayTime = 0x8000; } ImGui::SameLine(); - if (ImGui::Button("Sunset")) { + if (Button("Sunset", buttonOptionsBase)) { gSaveContext.dayTime = 0xC001; } ImGui::SameLine(); - if (ImGui::Button("Midnight")) { + if (Button("Midnight", buttonOptionsBase)) { gSaveContext.dayTime = 0; } + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Total Days", ImGuiDataType_S32, &gSaveContext.totalDays); - UIWidgets::InsertHelpHoverText("Total number of days elapsed since the start of the game"); + Tooltip("Total number of days elapsed since the start of the game"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Deaths", ImGuiDataType_U16, &gSaveContext.deaths); - UIWidgets::InsertHelpHoverText("Total number of deaths"); + Tooltip("Total number of deaths"); + PopStyleInput(); - bool bgsFlag = gSaveContext.bgsFlag != 0; - if (ImGui::Checkbox("Has BGS", &bgsFlag)) { - gSaveContext.bgsFlag = bgsFlag; - } - UIWidgets::InsertHelpHoverText("Is Biggoron sword unlocked? Replaces Giant's knife"); + Checkbox("Has BGS", (bool*) &gSaveContext.bgsFlag, checkboxOptionsBase.Tooltip("Is Biggoron sword unlocked? Replaces Giant's knife")); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Sword Health", ImGuiDataType_U16, &gSaveContext.swordHealth); - UIWidgets::InsertHelpHoverText("Giant's knife health. Default is 8. Must be >0 for Biggoron sword to work"); + Tooltip("Giant's knife health. Default is 8. Must be >0 for Biggoron sword to work"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Bgs Day Count", ImGuiDataType_S32, &gSaveContext.bgsDayCount); - UIWidgets::InsertHelpHoverText("Total number of days elapsed since giving Biggoron the claim check"); + Tooltip("Total number of days elapsed since giving Biggoron the claim check"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Entrance Index", ImGuiDataType_S32, &gSaveContext.entranceIndex); - UIWidgets::InsertHelpHoverText("From which entrance did Link arrive?"); + Tooltip("From which entrance did Link arrive?"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Cutscene Index", ImGuiDataType_S32, &gSaveContext.cutsceneIndex); - UIWidgets::InsertHelpHoverText("Which cutscene is this?"); + Tooltip("Which cutscene is this?"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Navi Timer", ImGuiDataType_U16, &gSaveContext.naviTimer); - UIWidgets::InsertHelpHoverText("Navi wants to talk at 600 units, decides not to at 3000."); + Tooltip("Navi wants to talk at 600 units, decides not to at 3000."); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Timer 1 State", ImGuiDataType_S16, &gSaveContext.timer1State); - UIWidgets::InsertHelpHoverText("Heat timer, race timer, etc. Has white font"); + Tooltip("Heat timer, race timer, etc. Has white font"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Timer 1 Value", ImGuiDataType_S16, &gSaveContext.timer1Value, &one, NULL); - UIWidgets::InsertHelpHoverText("Time, in seconds"); + Tooltip("Time, in seconds"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Timer 2 State", ImGuiDataType_S16, &gSaveContext.timer2State); - UIWidgets::InsertHelpHoverText("Trade timer, Ganon collapse timer, etc. Has yellow font"); + Tooltip("Trade timer, Ganon collapse timer, etc. Has yellow font"); + PopStyleInput(); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Timer 2 Value", ImGuiDataType_S16, &gSaveContext.timer2Value, &one, NULL); - UIWidgets::InsertHelpHoverText("Time, in seconds"); - - const char* audioName; - switch (gSaveContext.audioSetting) { - case 0: - audioName = "Stereo"; - break; - case 1: - audioName = "Mono"; - break; - case 2: - audioName = "Headset"; - break; - case 3: - audioName = "Surround"; - break; - default: - audioName = "?"; - } - if (ImGui::BeginCombo("Audio", audioName)) { - if (ImGui::Selectable("Stereo")) { - gSaveContext.audioSetting = 0; - } - if (ImGui::Selectable("Mono")) { - gSaveContext.audioSetting = 1; - } - if (ImGui::Selectable("Headset")) { - gSaveContext.audioSetting = 2; - } - if (ImGui::Selectable("Surround")) { - gSaveContext.audioSetting = 3; - } + Tooltip("Time, in seconds"); + PopStyleInput(); - ImGui::EndCombo(); - } - UIWidgets::InsertHelpHoverText("Sound setting"); + Combobox("Audio", &gSaveContext.audioSetting, audioMap, comboboxOptionsBase.Tooltip("Sound setting")); - bool n64DDFlag = gSaveContext.n64ddFlag != 0; - if (ImGui::Checkbox("64 DD file?", &n64DDFlag)) { - gSaveContext.n64ddFlag = n64DDFlag; - } - UIWidgets::InsertHelpHoverText("WARNING! If you save, your file may be locked! Use caution!"); + Checkbox("64 DD file?", (bool*) &gSaveContext.n64ddFlag, checkboxOptionsBase.Tooltip("WARNING! If you save, your file may be locked! Use caution!")); - if (ImGui::BeginCombo("Z Target Mode", gSaveContext.zTargetSetting ? "Hold" : "Switch")) { - if (ImGui::Selectable("Switch")) { - gSaveContext.zTargetSetting = 0; - } - if (ImGui::Selectable("Hold")) { - gSaveContext.zTargetSetting = 1; - } - ImGui::EndCombo(); - } - UIWidgets::InsertHelpHoverText("Z-Targeting behavior"); + Combobox("Z Target Mode", &gSaveContext.zTargetSetting, zTargetMap, comboboxOptionsBase.Tooltip("Z-Targeting behavior")); if (IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT)) { + PushStyleInput(THEME_COLOR); ImGui::InputScalar("Triforce Pieces", ImGuiDataType_U8, &gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected); - UIWidgets::InsertHelpHoverText("Currently obtained Triforce Pieces. For Triforce Hunt."); + Tooltip("Currently obtained Triforce Pieces. For Triforce Hunt."); + PopStyleInput(); } ImGui::PushItemWidth(ImGui::GetFontSize() * 10); @@ -330,78 +312,79 @@ void DrawInfoTab() { for (int i = 0; i < 7; i++) { if(i == 2 && ImGui::TreeNode("Fishing") ){ //fishing has a few more flags to it u8 fishSize = gSaveContext.highScores[i] & 0x7F; + PushStyleInput(THEME_COLOR); if(ImGui::InputScalar("Child Size Record",ImGuiDataType_U8,&fishSize)){ gSaveContext.highScores[i]&=~0x7F; gSaveContext.highScores[i]|=fishSize & 0x7F; } char fishMsg[64]; std::sprintf(fishMsg,"Weight: %2.0f lbs",((SQ(fishSize)*.0036)+.5)); - UIWidgets::InsertHelpHoverText(fishMsg); + Tooltip(fishMsg); + PopStyleInput(); bool FishBool = gSaveContext.highScores[i]&0x80; - if (ImGui::Checkbox("Cheated as Child", &FishBool)) { + if (Checkbox("Cheated as Child", &FishBool, checkboxOptionsBase.Tooltip("Used the Sinking lure to catch it."))) { gSaveContext.highScores[i] &= ~0x80; gSaveContext.highScores[i] |= (0x80 * FishBool); } - UIWidgets::InsertHelpHoverText("Used the Sinking lure to catch it."); fishSize=(gSaveContext.highScores[i] & 0x7F000000)>>0x18; + PushStyleInput(THEME_COLOR); if(ImGui::InputScalar("Adult Size Record",ImGuiDataType_U8,&fishSize)){ gSaveContext.highScores[i]&=~0x7F000000; gSaveContext.highScores[i]|=(fishSize & 0x7F) << 0x18; } std::sprintf(fishMsg,"Weight: %2.0f lbs",((SQ(fishSize)*.0036)+.5)); - UIWidgets::InsertHelpHoverText(fishMsg); + Tooltip(fishMsg); + PopStyleInput(); FishBool = gSaveContext.highScores[i] & 0x80000000; - if (ImGui::Checkbox("Cheated as Adult", &FishBool)) { + if (Checkbox("Cheated as Adult", &FishBool, checkboxOptionsBase.Tooltip("Used the Sinking lure to catch it."))) { gSaveContext.highScores[i] &= ~0x80000000; gSaveContext.highScores[i] |= (0x80000000 * FishBool); } - UIWidgets::InsertHelpHoverText("Used the Sinking lure to catch it."); FishBool = gSaveContext.highScores[i]&0x100; - if (ImGui::Checkbox("Played as Child", &FishBool)) { + if (Checkbox("Played as Child", &FishBool, checkboxOptionsBase.Tooltip("Played at least one game as a child"))) { gSaveContext.highScores[i] &= ~0x100; gSaveContext.highScores[i] |= (0x100 * FishBool); } - UIWidgets::InsertHelpHoverText("Played at least one game as a child"); FishBool = gSaveContext.highScores[i]&0x200; - if (ImGui::Checkbox("Played as Adult", &FishBool)) { + if (Checkbox("Played as Adult", &FishBool, checkboxOptionsBase.Tooltip("Played at least one game as an adult"))) { gSaveContext.highScores[i] &= ~0x200; gSaveContext.highScores[i] |= (0x200 * FishBool); } - UIWidgets::InsertHelpHoverText("Played at least one game as an adult"); FishBool = gSaveContext.highScores[i]&0x400; - if (ImGui::Checkbox("Got Prize as Child", &FishBool)) { + if (Checkbox("Got Prize as Child", &FishBool, checkboxOptionsBase.Tooltip("Got the prize item (Heart Piece, unless rando.)\nunlocks Sinking Lure for Child Link."))) { gSaveContext.highScores[i] &= ~0x400; gSaveContext.highScores[i] |= (0x400 * FishBool); } - UIWidgets::InsertHelpHoverText("Got the prize item (Heart Piece, unless rando.)\nunlocks Sinking Lure for Child Link."); FishBool = gSaveContext.highScores[i]&0x800; - if (ImGui::Checkbox("Got Prize as Adult", &FishBool)) { + if (Checkbox("Got Prize as Adult", &FishBool, checkboxOptionsBase.Tooltip("Got the prize item (Golden Scale, unless rando.)\nUnlocks Sinking Lure for Adult Link."))) { gSaveContext.highScores[i] &= ~0x800; gSaveContext.highScores[i] |= (0x800 * FishBool); } - UIWidgets::InsertHelpHoverText("Got the prize item (Golden Scale, unless rando.)\nUnlocks Sinking Lure for Adult Link."); FishBool = gSaveContext.highScores[i] & 0x1000; - if (ImGui::Checkbox("Stole Owner's Hat", &FishBool)) { + if (Checkbox("Stole Owner's Hat", &FishBool, checkboxOptionsBase.Tooltip("The owner's now visibly bald when Adult Link."))) { gSaveContext.highScores[i] &= ~0x1000; gSaveContext.highScores[i] |= (0x1000 * FishBool); } - UIWidgets::InsertHelpHoverText("The owner's now visibly bald when Adult Link."); fishSize=(gSaveContext.highScores[i] & 0xFF0000)>>16; + PushStyleInput(THEME_COLOR); if(ImGui::InputScalar("Times Played",ImGuiDataType_U8,&fishSize)){ gSaveContext.highScores[i]&=~0xFF0000; gSaveContext.highScores[i]|=(fishSize) << 16; } - UIWidgets::InsertHelpHoverText("Determines weather and school size during dawn/dusk."); + Tooltip("Determines weather and school size during dawn/dusk."); + PopStyleInput(); ImGui::TreePop(); continue; } - if (i == 5) { //HS_UNK_05 is unused + if (i == 5 || i == 2) { //HS_UNK_05 is unused continue; } std::string minigameLbl = minigameHS[i]; + PushStyleInput(THEME_COLOR); ImGui::InputScalar(minigameLbl.c_str(), ImGuiDataType_S32, &gSaveContext.highScores[i], &one, NULL); + PopStyleInput(); } ImGui::TreePop(); @@ -412,14 +395,14 @@ void DrawInfoTab() { void DrawBGSItemFlag(uint8_t itemID) { const ItemMapEntry& slotEntry = itemMapping[itemID]; - ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), + ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); } void DrawInventoryTab() { static bool restrictToValid = true; - ImGui::Checkbox("Restrict to valid items", &restrictToValid); - UIWidgets::InsertHelpHoverText("Restricts items and ammo to only what is possible to legally acquire in-game"); + Checkbox("Restrict to valid items", &restrictToValid, checkboxOptionsBase.Tooltip("Restricts items and ammo to only what is possible to legally acquire in-game")); for (int32_t y = 0; y < 4; y++) { for (int32_t x = 0; x < 6; x++) { @@ -433,35 +416,33 @@ void DrawInventoryTab() { ImGui::SameLine(); } - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); uint8_t item = gSaveContext.inventory.items[index]; + PushStyleButton(Colors::DarkGray); if (item != ITEM_NONE) { const ItemMapEntry& slotEntry = itemMapping.find(item)->second; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); auto ret = ImGui::ImageButton(slotEntry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(48.0f, 48.0f), ImVec2(0, 0), ImVec2(1, 1)); if (ret) { selectedIndex = index; ImGui::OpenPopup(itemPopupPicker); } } else { - if (ImGui::Button("##itemNone", ImVec2(32.0f, 32.0f))) { + if (ImGui::Button("##itemNone", ImVec2(IMAGE_SIZE, IMAGE_SIZE) + ImGui::GetStyle().FramePadding * 2)) { selectedIndex = index; ImGui::OpenPopup(itemPopupPicker); } } - ImGui::PopStyleVar(); - ImGui::PopStyleColor(); + PopStyleButton(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); if (ImGui::BeginPopup(itemPopupPicker)) { - if (ImGui::Button("##itemNonePicker", ImVec2(32.0f, 32.0f))) { + PushStyleButton(Colors::DarkGray); + if (ImGui::Button("##itemNonePicker", ImVec2(IMAGE_SIZE, IMAGE_SIZE) + ImGui::GetStyle().FramePadding * 2)) { gSaveContext.inventory.items[selectedIndex] = ITEM_NONE; ImGui::CloseCurrentPopup(); } - UIWidgets::SetLastItemHoverText("None"); + PopStyleButton(); + UIWidgets::Tooltip("None"); std::vector possibleItems; if (restrictToValid) { @@ -486,15 +467,15 @@ void DrawInventoryTab() { ImGui::SameLine(); } const ItemMapEntry& slotEntry = possibleItems[pickerIndex]; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + PushStyleButton(Colors::DarkGray); auto ret = ImGui::ImageButton(slotEntry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(IMAGE_SIZE, IMAGE_SIZE), ImVec2(0, 0), ImVec2(1, 1)); + PopStyleButton(); if (ret) { gSaveContext.inventory.items[selectedIndex] = slotEntry.id; ImGui::CloseCurrentPopup(); } - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(slotEntry.id)); + UIWidgets::Tooltip(SohUtils::GetItemName(slotEntry.id).c_str()); } ImGui::EndPopup(); @@ -517,11 +498,13 @@ void DrawInventoryTab() { drawnAmmoItems++; ImGui::PushID(ammoIndex); - ImGui::PushItemWidth(32.0f); + ImGui::PushItemWidth(IMAGE_SIZE); ImGui::BeginGroup(); - ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(itemMapping[item].name), ImVec2(32.0f, 32.0f)); + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(itemMapping[item].name), ImVec2(IMAGE_SIZE, IMAGE_SIZE)); + PushStyleInput(THEME_COLOR); ImGui::InputScalar("##ammoInput", ImGuiDataType_S8, &AMMO(item)); + PopStyleInput(); ImGui::EndGroup(); ImGui::PopItemWidth(); @@ -548,7 +531,10 @@ void DrawFlagTableArray16(const FlagTable& flagTable, uint16_t row, uint16_t& fl ImGui::PushID(flagIndex); bool hasDescription = !!flagTable.flagDescriptions.contains(row * 16 + flagIndex); uint32_t bitMask = 1 << flagIndex; - ImGui::PushStyleColor(ImGuiCol_FrameBg, hasDescription ? ImVec4(0.16f, 0.29f, 0.48f, 0.54f) : ImVec4(0.16f, 0.29f, 0.48f, 0.24f)); + ImVec4 themeColor = ColorValues.at(THEME_COLOR); + ImVec4 colorDark = { themeColor.x * 0.4f, themeColor.y * 0.4f, themeColor.z * 0.4f , themeColor.z }; + PushStyleCheckbox(hasDescription ? themeColor : colorDark); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); bool flag = (flags & bitMask) != 0; if (ImGui::Checkbox("##check", &flag)) { if (flag) { @@ -557,7 +543,8 @@ void DrawFlagTableArray16(const FlagTable& flagTable, uint16_t row, uint16_t& fl flags &= ~bitMask; } } - ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + PopStyleCheckbox(); if (ImGui::IsItemHovered() && hasDescription) { ImGui::BeginTooltip(); ImGui::Text("%s", UIWidgets::WrappedText(flagTable.flagDescriptions.at(row * 16 + flagIndex), 60).c_str()); @@ -575,165 +562,154 @@ void DrawFlagsTab() { DrawGroupWithBorder([&]() { ImGui::Text("stateFlags1"); - UIWidgets::DrawFlagArray32("stateFlags1", player->stateFlags1); - }); + DrawFlagArray32("stateFlags1", player->stateFlags1, THEME_COLOR); + }, "stateFlags1"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("stateFlags2"); - UIWidgets::DrawFlagArray32("stateFlags2", player->stateFlags2); - }); + DrawFlagArray32("stateFlags2", player->stateFlags2, THEME_COLOR); + }, "stateFlags2"); DrawGroupWithBorder([&]() { ImGui::Text("stateFlags3"); - UIWidgets::DrawFlagArray8("stateFlags3", player->stateFlags3); - }); + DrawFlagArray8("stateFlags3", player->stateFlags3, THEME_COLOR); + }, "stateFlags3"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("unk_6AE_rotFlags"); - UIWidgets::DrawFlagArray16("unk_6AE_rotFlags", player->unk_6AE_rotFlags); - }); + DrawFlagArray16("unk_6AE_rotFlags", player->unk_6AE_rotFlags, THEME_COLOR); + }, "unk_6AE_rotFlags"); } ImGui::TreePop(); } if (ImGui::TreeNode("Current Scene")) { if (gPlayState != nullptr) { ActorContext* act = &gPlayState->actorCtx; - DrawGroupWithBorder([&]() { ImGui::Text("Switch"); - UIWidgets::InsertHelpHoverText("Permanently-saved switch flags"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Switch")) { + InsertHelpHoverText("Permanently-saved switch flags"); + if (Button("Set All##Switch", buttonOptionsBase.Tooltip(""))) { act->flags.swch = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Switch")) { + if (Button("Clear All##Switch", buttonOptionsBase.Tooltip(""))) { act->flags.swch = 0; } - UIWidgets::DrawFlagArray32("Switch", act->flags.swch); - }); + DrawFlagArray32("Switch", act->flags.swch, THEME_COLOR); + }, "Switch"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Temp Switch"); - UIWidgets::InsertHelpHoverText("Temporary switch flags. Unset on scene transitions"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Temp Switch")) { + InsertHelpHoverText("Temporary switch flags. Unset on scene transitions"); + if (Button("Set All##Temp Switch", buttonOptionsBase.Tooltip(""))) { act->flags.tempSwch = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Temp Switch")) { + if (Button("Clear All##Temp Switch", buttonOptionsBase.Tooltip(""))) { act->flags.tempSwch = 0; } - UIWidgets::DrawFlagArray32("Temp Switch", act->flags.tempSwch); - }); + DrawFlagArray32("Temp Switch", act->flags.tempSwch, THEME_COLOR); + }, "Temp Switch"); DrawGroupWithBorder([&]() { ImGui::Text("Clear"); - UIWidgets::InsertHelpHoverText("Permanently-saved room-clear flags"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Clear")) { + InsertHelpHoverText("Permanently-saved room-clear flags"); + if (Button("Set All##Clear", buttonOptionsBase.Tooltip(""))) { act->flags.clear = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Clear")) { + if (Button("Clear All##Clear", buttonOptionsBase.Tooltip(""))) { act->flags.clear = 0; } - UIWidgets::DrawFlagArray32("Clear", act->flags.clear); - }); + DrawFlagArray32("Clear", act->flags.clear, THEME_COLOR); + }, "Clear"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Temp Clear"); - UIWidgets::InsertHelpHoverText("Temporary room-clear flags. Unset on scene transitions"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Temp Clear")) { + InsertHelpHoverText("Temporary room-clear flags. Unset on scene transitions"); + if (Button("Set All##Temp Clear", buttonOptionsBase.Tooltip(""))) { act->flags.tempClear = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Temp Clear")) { + if (Button("Clear All##Temp Clear", buttonOptionsBase.Tooltip(""))) { act->flags.tempClear = 0; } - UIWidgets::DrawFlagArray32("Temp Clear", act->flags.tempClear); - }); + DrawFlagArray32("Temp Clear", act->flags.tempClear, THEME_COLOR); + }, "Temp Clear"); DrawGroupWithBorder([&]() { ImGui::Text("Collect"); - UIWidgets::InsertHelpHoverText("Permanently-saved collect flags"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Collect")) { + InsertHelpHoverText("Permanently-saved collect flags"); + if (Button("Set All##Collect", buttonOptionsBase.Tooltip(""))) { act->flags.collect = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Collect")) { + if (Button("Clear All##Collect", buttonOptionsBase.Tooltip(""))) { act->flags.collect = 0; } - UIWidgets::DrawFlagArray32("Collect", act->flags.collect); - }); + DrawFlagArray32("Collect", act->flags.collect, THEME_COLOR); + }, "Collect"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Temp Collect"); - UIWidgets::InsertHelpHoverText("Temporary collect flags. Unset on scene transitions"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Temp Collect")) { + InsertHelpHoverText("Temporary collect flags. Unset on scene transitions"); + if (Button("Set All##Temp Collect", buttonOptionsBase.Tooltip(""))) { act->flags.tempCollect = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Temp Collect")) { + if (Button("Clear All##Temp Collect", buttonOptionsBase.Tooltip(""))) { act->flags.tempCollect = 0; } - UIWidgets::DrawFlagArray32("Temp Collect", act->flags.tempCollect); - }); + DrawFlagArray32("Temp Collect", act->flags.tempCollect, THEME_COLOR); + }, "Temp Collect"); DrawGroupWithBorder([&]() { ImGui::Text("Chest"); - UIWidgets::InsertHelpHoverText("Permanently-saved chest flags"); - ImGui::SameLine(); - if (ImGui::Button("Set All##Chest")) { + InsertHelpHoverText("Permanently-saved chest flags"); + if (Button("Set All##Chest", buttonOptionsBase.Tooltip(""))) { act->flags.chest = UINT32_MAX; } ImGui::SameLine(); - if (ImGui::Button("Clear All##Chest")) { + if (Button("Clear All##Chest", buttonOptionsBase.Tooltip(""))) { act->flags.chest = 0; } - UIWidgets::DrawFlagArray32("Chest", act->flags.chest); - }); + DrawFlagArray32("Chest", act->flags.chest, THEME_COLOR); + }, "Chest"); ImGui::SameLine(); ImGui::BeginGroup(); - if (ImGui::Button("Reload Flags")) { + if (Button("Reload Flags", buttonOptionsBase.Tooltip("Load flags from saved scene flags. Normally happens on scene load"))) { act->flags.swch = gSaveContext.sceneFlags[gPlayState->sceneNum].swch; act->flags.clear = gSaveContext.sceneFlags[gPlayState->sceneNum].clear; act->flags.collect = gSaveContext.sceneFlags[gPlayState->sceneNum].collect; act->flags.chest = gSaveContext.sceneFlags[gPlayState->sceneNum].chest; } - UIWidgets::SetLastItemHoverText("Load flags from saved scene flags. Normally happens on scene load"); - if (ImGui::Button("Save Flags")) { + if (Button("Save Flags", buttonOptionsBase.Tooltip("Save current scene flags. Normally happens on scene exit"))) { gSaveContext.sceneFlags[gPlayState->sceneNum].swch = act->flags.swch; gSaveContext.sceneFlags[gPlayState->sceneNum].clear = act->flags.clear; gSaveContext.sceneFlags[gPlayState->sceneNum].collect = act->flags.collect; gSaveContext.sceneFlags[gPlayState->sceneNum].chest = act->flags.chest; } - UIWidgets::SetLastItemHoverText("Save current scene flags. Normally happens on scene exit"); - if (ImGui::Button("Clear Flags")) { + if (Button("Clear Flags", buttonOptionsBase.Tooltip("Clear current scene flags. Reload scene to see changes"))) { act->flags.swch = 0; act->flags.clear = 0; act->flags.collect = 0; act->flags.chest = 0; } - UIWidgets::SetLastItemHoverText("Clear current scene flags. Reload scene to see changes"); ImGui::EndGroup(); @@ -746,8 +722,10 @@ void DrawFlagsTab() { if (ImGui::TreeNode("Saved Scene Flags")) { static uint32_t selectedSceneFlagMap = 0; + ImGui::AlignTextToFramePadding(); ImGui::Text("Map"); ImGui::SameLine(); + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("##Map", SohUtils::GetSceneName(selectedSceneFlagMap).c_str())) { for (int32_t sceneIndex = 0; sceneIndex < SCENE_ID_MAX; sceneIndex++) { if (ImGui::Selectable(SohUtils::GetSceneName(sceneIndex).c_str())) { @@ -757,77 +735,68 @@ void DrawFlagsTab() { ImGui::EndCombo(); } + PopStyleCombobox(); // Don't show current scene button if there is no current scene if (gPlayState != nullptr) { ImGui::SameLine(); - if (ImGui::Button("Current")) { + if (Button("Current", buttonOptionsBase.Tooltip("Open flags for current scene"))) { selectedSceneFlagMap = gPlayState->sceneNum; } - UIWidgets::SetLastItemHoverText("Open flags for current scene"); } DrawGroupWithBorder([&]() { ImGui::Text("Switch"); - UIWidgets::InsertHelpHoverText("Switch flags"); - UIWidgets::DrawFlagArray32("Switch", gSaveContext.sceneFlags[selectedSceneFlagMap].swch); - }); + InsertHelpHoverText("Switch flags"); + DrawFlagArray32("Switch", gSaveContext.sceneFlags[selectedSceneFlagMap].swch, THEME_COLOR); + }, "Saved Switch"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Clear"); - UIWidgets::InsertHelpHoverText("Room-clear flags"); - UIWidgets::DrawFlagArray32("Clear", gSaveContext.sceneFlags[selectedSceneFlagMap].clear); - }); + InsertHelpHoverText("Room-clear flags"); + DrawFlagArray32("Clear", gSaveContext.sceneFlags[selectedSceneFlagMap].clear, THEME_COLOR); + }, "Saved Clear"); DrawGroupWithBorder([&]() { ImGui::Text("Collect"); - UIWidgets::InsertHelpHoverText("Collect flags"); - UIWidgets::DrawFlagArray32("Collect", gSaveContext.sceneFlags[selectedSceneFlagMap].collect); - }); + InsertHelpHoverText("Collect flags"); + DrawFlagArray32("Collect", gSaveContext.sceneFlags[selectedSceneFlagMap].collect, THEME_COLOR); + }, "Saved Collect"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Chest"); - UIWidgets::InsertHelpHoverText("Chest flags"); - UIWidgets::DrawFlagArray32("Chest", gSaveContext.sceneFlags[selectedSceneFlagMap].chest); - }); + InsertHelpHoverText("Chest flags"); + DrawFlagArray32("Chest", gSaveContext.sceneFlags[selectedSceneFlagMap].chest, THEME_COLOR); + }, "Saved Chest"); DrawGroupWithBorder([&]() { ImGui::Text("Rooms"); - UIWidgets::InsertHelpHoverText("Flags for visted rooms"); - UIWidgets::DrawFlagArray32("Rooms", gSaveContext.sceneFlags[selectedSceneFlagMap].rooms); - }); + InsertHelpHoverText("Flags for visted rooms"); + DrawFlagArray32("Rooms", gSaveContext.sceneFlags[selectedSceneFlagMap].rooms, THEME_COLOR); + }, "Saved Rooms"); ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Floors"); - UIWidgets::InsertHelpHoverText("Flags for visted floors"); - UIWidgets::DrawFlagArray32("Floors", gSaveContext.sceneFlags[selectedSceneFlagMap].floors); - }); + InsertHelpHoverText("Flags for visted floors"); + DrawFlagArray32("Floors", gSaveContext.sceneFlags[selectedSceneFlagMap].floors, THEME_COLOR); + }, "Saved Floors"); ImGui::TreePop(); } DrawGroupWithBorder([&]() { - static uint32_t selectedGsMap = 0; + size_t selectedGsMap = 0; ImGui::Text("Gold Skulltulas"); - ImGui::Text("Map"); - ImGui::SameLine(); - if (ImGui::BeginCombo("##Gold Skulltula Map", gsMapping[selectedGsMap].c_str())) { - for (int32_t gsIndex = 0; gsIndex < gsMapping.size(); gsIndex++) { - if (ImGui::Selectable(gsMapping[gsIndex].c_str())) { - selectedGsMap = gsIndex; - } - } - - ImGui::EndCombo(); - } + Combobox("Map##Gold Skulltulas", &selectedGsMap, gsMapping, comboboxOptionsBase.Tooltip("")); // TODO We should write out descriptions for each one... ugh + ImGui::AlignTextToFramePadding(); ImGui::Text("Flags"); uint32_t currentFlags = GET_GS_FLAGS(selectedGsMap); uint32_t allFlags = gAreaGsFlags[selectedGsMap]; @@ -838,6 +807,8 @@ void DrawFlagsTab() { ImGui::SameLine(); ImGui::PushID(allFlags); + PushStyleCheckbox(THEME_COLOR); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); if (ImGui::Checkbox("##gs", &isThisSet)) { if (isThisSet) { SET_GS_FLAGS(selectedGsMap, setMask); @@ -848,6 +819,8 @@ void DrawFlagsTab() { SET_GS_FLAGS(selectedGsMap, currentFlagsBase & ~setMask); } } + ImGui::PopStyleVar(); + PopStyleCheckbox(); ImGui::PopID(); @@ -860,8 +833,8 @@ void DrawFlagsTab() { // GS Token Count updated, since Gold Skulltulas killed will not correlate to GS Tokens Collected. if (!(IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_TOKENS) != RO_TOKENSANITY_OFF)) { static bool keepGsCountUpdated = true; - ImGui::Checkbox("Keep GS Count Updated", &keepGsCountUpdated); - UIWidgets::InsertHelpHoverText("Automatically adjust the number of gold skulltula tokens acquired based on set flags."); + Checkbox("Keep GS Count Updated", &keepGsCountUpdated, + checkboxOptionsBase.Tooltip("Automatically adjust the number of gold skulltula tokens acquired based on set flags.")); int32_t gsCount = 0; if (keepGsCountUpdated) { for (int32_t gsFlagIndex = 0; gsFlagIndex < 6; gsFlagIndex++) { @@ -870,7 +843,7 @@ void DrawFlagsTab() { gSaveContext.inventory.gsTokens = gsCount; } } - }); + }, "Gold Skulltulas"); for (int i = 0; i < flagTables.size(); i++) { const FlagTable& flagTable = flagTables[i]; @@ -899,7 +872,7 @@ void DrawFlagsTab() { DrawFlagTableArray16(flagTable, j, gSaveContext.ship.randomizerInf[j]); break; } - }); + }, flagTable.name); } // make some buttons to help with fishsanity debugging @@ -941,6 +914,8 @@ void DrawUpgrade(const std::string& categoryName, int32_t categoryId, const std: ImGui::Text("%s", categoryName.c_str()); ImGui::SameLine(); ImGui::PushID(categoryName.c_str()); + PushStyleCombobox(THEME_COLOR); + ImGui::AlignTextToFramePadding(); if (ImGui::BeginCombo("##upgrade", names[CUR_UPG_VALUE(categoryId)].c_str())) { for (int32_t i = 0; i < names.size(); i++) { if (ImGui::Selectable(names[i].c_str())) { @@ -950,8 +925,9 @@ void DrawUpgrade(const std::string& categoryName, int32_t categoryId, const std: ImGui::EndCombo(); } + PopStyleCombobox(); ImGui::PopID(); - UIWidgets::SetLastItemHoverText(categoryName.c_str()); + UIWidgets::Tooltip(categoryName.c_str()); } // Draws a combo that lets you choose and upgrade value from a popup grid of icons @@ -959,59 +935,50 @@ void DrawUpgradeIcon(const std::string& categoryName, int32_t categoryId, const static const char* upgradePopupPicker = "upgradePopupPicker"; ImGui::PushID(categoryName.c_str()); - - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + + PushStyleButton(Colors::DarkGray); uint8_t item = items[CUR_UPG_VALUE(categoryId)]; if (item != ITEM_NONE) { const ItemMapEntry& slotEntry = itemMapping[item]; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - auto ret = ImGui::ImageButton(slotEntry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); - if (ret) { + if (ImGui::ImageButton(slotEntry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), + ImVec2(IMAGE_SIZE, IMAGE_SIZE), ImVec2(0, 0), ImVec2(1, 1))) { ImGui::OpenPopup(upgradePopupPicker); } } else { - if (ImGui::Button("##itemNone", ImVec2(32.0f, 32.0f))) { + if (ImGui::Button("##itemNone", ImVec2(IMAGE_SIZE, IMAGE_SIZE) + ImGui::GetStyle().FramePadding * 2)) { ImGui::OpenPopup(upgradePopupPicker); } } - ImGui::PopStyleVar(); - ImGui::PopStyleColor(); - UIWidgets::SetLastItemHoverText(categoryName.c_str()); + PopStyleButton(); + Tooltip(categoryName.c_str()); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); if (ImGui::BeginPopup(upgradePopupPicker)) { for (int32_t pickerIndex = 0; pickerIndex < items.size(); pickerIndex++) { if ((pickerIndex % 8) != 0) { ImGui::SameLine(); } - + + PushStyleButton(Colors::DarkGray); if (items[pickerIndex] == ITEM_NONE) { - if (ImGui::Button("##upgradePopupPicker", ImVec2(32.0f, 32.0f))) { + if (ImGui::Button("##upgradePopupPicker", ImVec2(IMAGE_SIZE, IMAGE_SIZE) + ImGui::GetStyle().FramePadding * 2)) { Inventory_ChangeUpgrade(categoryId, pickerIndex); ImGui::CloseCurrentPopup(); } - UIWidgets::SetLastItemHoverText("None"); + Tooltip("None"); } else { const ItemMapEntry& slotEntry = itemMapping[items[pickerIndex]]; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); auto ret = ImGui::ImageButton(slotEntry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(slotEntry.name), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(IMAGE_SIZE, IMAGE_SIZE), ImVec2(0, 0), ImVec2(1, 1)); if (ret) { Inventory_ChangeUpgrade(categoryId, pickerIndex); ImGui::CloseCurrentPopup(); } - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(slotEntry.id)); + Tooltip(SohUtils::GetItemName(slotEntry.id).c_str()); } + PopStyleButton(); } - ImGui::EndPopup(); } - ImGui::PopStyleVar(); - ImGui::PopID(); } @@ -1035,11 +1002,9 @@ void DrawEquipmentTab() { uint32_t bitMask = 1 << i; bool hasEquip = (bitMask & gSaveContext.inventory.equipment) != 0; const ItemMapEntry& entry = itemMapping[equipmentValues[i]]; - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + PushStyleButton(Colors::DarkGray); auto ret = ImGui::ImageButton(entry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasEquip ? entry.name : entry.nameFaded), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(IMAGE_SIZE, IMAGE_SIZE), ImVec2(0, 0), ImVec2(1, 1)); if (ret) { if (hasEquip) { gSaveContext.inventory.equipment &= ~bitMask; @@ -1047,9 +1012,9 @@ void DrawEquipmentTab() { gSaveContext.inventory.equipment |= bitMask; } } - ImGui::PopStyleColor(); + PopStyleButton(); + Tooltip(SohUtils::GetItemName(entry.id).c_str()); ImGui::PopID(); - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(entry.id)); } const std::vector bulletBagValues = { @@ -1137,11 +1102,9 @@ void DrawQuestItemButton(uint32_t item) { const QuestMapEntry& entry = questMapping[item]; uint32_t bitMask = 1 << entry.id; bool hasQuestItem = (bitMask & gSaveContext.inventory.questItems) != 0; - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + PushStyleButton(Colors::DarkGray); auto ret = ImGui::ImageButton(entry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasQuestItem ? entry.name : entry.nameFaded), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(IMAGE_SIZE, IMAGE_SIZE), ImVec2(0, 0), ImVec2(1, 1)); if (ret) { if (hasQuestItem) { gSaveContext.inventory.questItems &= ~bitMask; @@ -1149,8 +1112,8 @@ void DrawQuestItemButton(uint32_t item) { gSaveContext.inventory.questItems |= bitMask; } } - ImGui::PopStyleColor(); - UIWidgets::SetLastItemHoverText(SohUtils::GetQuestItemName(entry.id)); + PopStyleButton(); + Tooltip(SohUtils::GetQuestItemName(entry.id).c_str()); } // Draws a toggleable icon for a dungeon item that is faded when disabled @@ -1158,11 +1121,9 @@ void DrawDungeonItemButton(uint32_t item, uint32_t scene) { const ItemMapEntry& entry = itemMapping[item]; uint32_t bitMask = 1 << (entry.id - ITEM_KEY_BOSS); // Bitset starts at ITEM_KEY_BOSS == 0. the rest are sequential bool hasItem = (bitMask & gSaveContext.inventory.dungeonItems[scene]) != 0; - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + PushStyleButton(Colors::DarkGray); auto ret = ImGui::ImageButton(entry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasItem ? entry.name : entry.nameFaded), - ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(IMAGE_SIZE, IMAGE_SIZE), ImVec2(0, 0), ImVec2(1, 1)); if (ret) { if (hasItem) { gSaveContext.inventory.dungeonItems[scene] &= ~bitMask; @@ -1170,12 +1131,11 @@ void DrawDungeonItemButton(uint32_t item, uint32_t scene) { gSaveContext.inventory.dungeonItems[scene] |= bitMask; } } - ImGui::PopStyleColor(); - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(entry.id)); + PopStyleButton(); + Tooltip(SohUtils::GetItemName(entry.id).c_str()); } void DrawQuestStatusTab() { - ImGui::PushItemWidth(ImGui::GetFontSize() * 6); for (int32_t i = QUEST_MEDALLION_FOREST; i < QUEST_MEDALLION_LIGHT + 1; i++) { if (i != QUEST_MEDALLION_FOREST) { @@ -1193,14 +1153,13 @@ void DrawQuestStatusTab() { // Put Stone of Agony and Gerudo Card on the same line with a little space between them ImGui::SameLine(); - ImGui::Dummy(ImVec2(20, 0)); + ImGui::Dummy(ImVec2(IMAGE_SIZE, IMAGE_SIZE) + ImGui::GetStyle().FramePadding * 2); ImGui::SameLine(); DrawQuestItemButton(QUEST_STONE_OF_AGONY); ImGui::SameLine(); DrawQuestItemButton(QUEST_GERUDO_CARD); - for (const auto& [quest, entry] : songMapping) { if ((entry.id != QUEST_SONG_MINUET) && (entry.id != QUEST_SONG_LULLABY)) { ImGui::SameLine(); @@ -1208,11 +1167,9 @@ void DrawQuestStatusTab() { uint32_t bitMask = 1 << entry.id; bool hasQuestItem = (bitMask & gSaveContext.inventory.questItems) != 0; - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + PushStyleButton(Colors::DarkGray); auto ret = ImGui::ImageButton(entry.name.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasQuestItem ? entry.name : entry.nameFaded), - ImVec2(16.0f, 24.0f), ImVec2(0, 0), ImVec2(1, 1)); - ImGui::PopStyleVar(); + ImVec2(32.0f, 48.0f), ImVec2(0, 0), ImVec2(1, 1)); if (ret) { if (hasQuestItem) { gSaveContext.inventory.questItems &= ~bitMask; @@ -1220,25 +1177,28 @@ void DrawQuestStatusTab() { gSaveContext.inventory.questItems |= bitMask; } } - ImGui::PopStyleColor(); - UIWidgets::SetLastItemHoverText(SohUtils::GetQuestItemName(entry.id)); + PopStyleButton(); + Tooltip(SohUtils::GetQuestItemName(entry.id).c_str()); } + PushStyleInput(THEME_COLOR); ImGui::InputScalar("GS Count", ImGuiDataType_S16, &gSaveContext.inventory.gsTokens); - UIWidgets::InsertHelpHoverText("Number of gold skulltula tokens aquired"); + PopStyleInput(); + InsertHelpHoverText("Number of gold skulltula tokens aquired"); uint32_t bitMask = 1 << QUEST_SKULL_TOKEN; bool gsUnlocked = (bitMask & gSaveContext.inventory.questItems) != 0; - if (ImGui::Checkbox("GS unlocked", &gsUnlocked)) { + if (Checkbox("GS unlocked", &gsUnlocked, CheckboxOptions().Color(THEME_COLOR))) { if (gsUnlocked) { gSaveContext.inventory.questItems |= bitMask; } else { gSaveContext.inventory.questItems &= ~bitMask; } } - UIWidgets::InsertHelpHoverText("If unlocked, enables showing the gold skulltula count in the quest status menu"); + InsertHelpHoverText("If unlocked, enables showing the gold skulltula count in the quest status menu"); int32_t pohCount = (gSaveContext.inventory.questItems & 0xF0000000) >> 28; + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("PoH count", std::to_string(pohCount).c_str())) { for (int32_t i = 0; i < 4; i++) { if (ImGui::Selectable(std::to_string(i).c_str(), pohCount == i)) { @@ -1248,13 +1208,14 @@ void DrawQuestStatusTab() { } ImGui::EndCombo(); } - UIWidgets::InsertHelpHoverText("The number of pieces of heart acquired towards the next heart container"); + InsertHelpHoverText("The number of pieces of heart acquired towards the next heart container"); + PopStyleCombobox(); DrawGroupWithBorder([&]() { ImGui::Text("Dungeon Items"); static int32_t dungeonItemsScene = SCENE_DEKU_TREE; - ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f); + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("##DungeonSelect", SohUtils::GetSceneName(dungeonItemsScene).c_str())) { for (int32_t dungeonIndex = SCENE_DEKU_TREE; dungeonIndex < SCENE_JABU_JABU_BOSS + 1; dungeonIndex++) { if (ImGui::Selectable(SohUtils::GetSceneName(dungeonIndex).c_str(), @@ -1265,7 +1226,7 @@ void DrawQuestStatusTab() { ImGui::EndCombo(); } - ImGui::PopItemWidth(); + PopStyleCombobox(); DrawDungeonItemButton(ITEM_KEY_BOSS, dungeonItemsScene); ImGui::SameLine(); @@ -1277,16 +1238,17 @@ void DrawQuestStatusTab() { float lineHeight = ImGui::GetTextLineHeightWithSpacing(); ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(itemMapping[ITEM_KEY_SMALL].name), ImVec2(lineHeight, lineHeight)); ImGui::SameLine(); + PushStyleInput(THEME_COLOR); if (ImGui::InputScalar("##Keys", ImGuiDataType_S8, gSaveContext.inventory.dungeonKeys + dungeonItemsScene)) { gSaveContext.ship.stats.dungeonKeys[dungeonItemsScene] = gSaveContext.inventory.dungeonKeys[dungeonItemsScene]; }; + PopStyleInput(); } else { // dungeonItems is size 20 but dungeonKeys is size 19, so there are no keys for the last scene (Barinade's Lair) ImGui::Text("Barinade's Lair does not have small keys"); } - }); + }, "Dungeon Items"); - ImGui::PopItemWidth(); } void DrawPlayerTab() { @@ -1364,50 +1326,53 @@ void DrawPlayerTab() { } ImGui::PushItemWidth(ImGui::GetFontSize() * 6); + PushStyleInput(THEME_COLOR); DrawGroupWithBorder([&]() { ImGui::Text("Link's Position"); - ImGui::InputScalar("X Pos", ImGuiDataType_Float, &player->actor.world.pos.x); - ImGui::SameLine(); - ImGui::InputScalar("Y Pos", ImGuiDataType_Float, &player->actor.world.pos.y); - ImGui::SameLine(); - ImGui::InputScalar("Z Pos", ImGuiDataType_Float, &player->actor.world.pos.z); - }); - + ImGui::PushItemWidth(ImGui::GetFontSize() * 12); + ImGui::InputScalar("X##Pos", ImGuiDataType_Float, &player->actor.world.pos.x); + ImGui::InputScalar("Y##Pos", ImGuiDataType_Float, &player->actor.world.pos.y); + ImGui::InputScalar("Z##Pos", ImGuiDataType_Float, &player->actor.world.pos.z); + ImGui::PopItemWidth(); + }, "Link's Position"); + ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Link's Rotation"); - UIWidgets::InsertHelpHoverText("For Link's rotation in relation to the world"); - ImGui::InputScalar("X Rot", ImGuiDataType_S16, &player->actor.world.rot.x); - ImGui::SameLine(); - ImGui::InputScalar("Y Rot", ImGuiDataType_S16, &player->actor.world.rot.y); - ImGui::SameLine(); - ImGui::InputScalar("Z Rot", ImGuiDataType_S16, &player->actor.world.rot.z); - }); - + InsertHelpHoverText("For Link's rotation in relation to the world"); + ImGui::PushItemWidth(ImGui::GetFontSize() * 12); + ImGui::InputScalar("X##Rot", ImGuiDataType_S16, &player->actor.world.rot.x); + ImGui::InputScalar("Y##Rot", ImGuiDataType_S16, &player->actor.world.rot.y); + ImGui::InputScalar("Z##Rot", ImGuiDataType_S16, &player->actor.world.rot.z); + ImGui::PopItemWidth(); + }, "Link's Rotation"); + ImGui::SameLine(); DrawGroupWithBorder([&]() { ImGui::Text("Link's Model Rotation"); - UIWidgets::InsertHelpHoverText("For Link's actual model"); - ImGui::InputScalar("X ModRot", ImGuiDataType_S16, &player->actor.shape.rot.x); - ImGui::SameLine(); - ImGui::InputScalar("Y ModRot", ImGuiDataType_S16, &player->actor.shape.rot.y); - ImGui::SameLine(); - ImGui::InputScalar("Z ModRot", ImGuiDataType_S16, &player->actor.shape.rot.z); - }); + InsertHelpHoverText("For Link's actual model"); + ImGui::PushItemWidth(ImGui::GetFontSize() * 12); + ImGui::InputScalar("X##ModRot", ImGuiDataType_S16, &player->actor.shape.rot.x); + ImGui::InputScalar("Y##ModRot", ImGuiDataType_S16, &player->actor.shape.rot.y); + ImGui::InputScalar("Z##ModRot", ImGuiDataType_S16, &player->actor.shape.rot.z); + ImGui::PopItemWidth(); + }, "Link's Model Rotation"); ImGui::InputScalar("Linear Velocity", ImGuiDataType_Float, &player->linearVelocity); - UIWidgets::InsertHelpHoverText("Link's speed along the XZ plane"); + InsertHelpHoverText("Link's speed along the XZ plane"); ImGui::InputScalar("Y Velocity", ImGuiDataType_Float, &player->actor.velocity.y); - UIWidgets::InsertHelpHoverText("Link's speed along the Y plane. Caps at -20"); + InsertHelpHoverText("Link's speed along the Y plane. Caps at -20"); ImGui::InputScalar("Wall Height", ImGuiDataType_Float, &player->yDistToLedge); - UIWidgets::InsertHelpHoverText("Height used to determine whether Link can climb or grab a ledge at the top"); + InsertHelpHoverText("Height used to determine whether Link can climb or grab a ledge at the top"); ImGui::InputScalar("Invincibility Timer", ImGuiDataType_S8, &player->invincibilityTimer); - UIWidgets::InsertHelpHoverText("Can't take damage while this is nonzero"); + InsertHelpHoverText("Can't take damage while this is nonzero"); ImGui::InputScalar("Gravity", ImGuiDataType_Float, &player->actor.gravity); - UIWidgets::InsertHelpHoverText("Rate at which Link falls. Default -4.0f"); + InsertHelpHoverText("Rate at which Link falls. Default -4.0f"); + PopStyleInput(); + PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("Link Age on Load", gPlayState->linkAgeOnLoad == 0 ? "Adult" : "Child")) { if (ImGui::Selectable("Adult")) { gPlayState->linkAgeOnLoad = 0; @@ -1417,132 +1382,140 @@ void DrawPlayerTab() { } ImGui::EndCombo(); } - - UIWidgets::InsertHelpHoverText("This will change Link's age when you load a map"); - + InsertHelpHoverText("This will change Link's age when you load a map"); + PopStyleCombobox(); ImGui::Separator(); - ImGui::Text("Link's Current Equipment"); - ImGui::PushItemWidth(ImGui::GetFontSize() * 15); - if (ImGui::BeginCombo("Sword", curSword)) { - if (ImGui::Selectable("None")) { - player->currentSwordItemId = ITEM_NONE; - gSaveContext.equips.buttonItems[0] = ITEM_NONE; - Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_NONE); - } - if (ImGui::Selectable("Kokiri Sword")) { - player->currentSwordItemId = ITEM_SWORD_KOKIRI; - gSaveContext.equips.buttonItems[0] = ITEM_SWORD_KOKIRI; - Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_KOKIRI); - } - if (ImGui::Selectable("Master Sword")) { - player->currentSwordItemId = ITEM_SWORD_MASTER; - gSaveContext.equips.buttonItems[0] = ITEM_SWORD_MASTER; - Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_MASTER); - } - if (ImGui::Selectable("Biggoron's Sword")) { - if (gSaveContext.bgsFlag) { - if (gSaveContext.swordHealth < 8) { - gSaveContext.swordHealth = 8; - } - player->currentSwordItemId = ITEM_SWORD_BGS; - gSaveContext.equips.buttonItems[0] = ITEM_SWORD_BGS; - } else { - if (gSaveContext.swordHealth < 8) { - gSaveContext.swordHealth = 8; - } - player->currentSwordItemId = ITEM_SWORD_BGS; - gSaveContext.equips.buttonItems[0] = ITEM_SWORD_KNIFE; + DrawGroupWithBorder([&]() { + PushStyleCombobox(THEME_COLOR); + ImGui::Text("Link's Current Equipment"); + ImGui::PushItemWidth(ImGui::GetFontSize() * 12); + if (ImGui::BeginCombo("Sword", curSword)) { + if (ImGui::Selectable("None")) { + player->currentSwordItemId = ITEM_NONE; + gSaveContext.equips.buttonItems[0] = ITEM_NONE; + Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_NONE); } - - Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_BIGGORON); - } - if (ImGui::Selectable("Fishing Pole")) { - player->currentSwordItemId = ITEM_FISHING_POLE; - gSaveContext.equips.buttonItems[0] = ITEM_FISHING_POLE; - Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_MASTER); - } - ImGui::EndCombo(); + if (ImGui::Selectable("Kokiri Sword")) { + player->currentSwordItemId = ITEM_SWORD_KOKIRI; + gSaveContext.equips.buttonItems[0] = ITEM_SWORD_KOKIRI; + Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_KOKIRI); + } + if (ImGui::Selectable("Master Sword")) { + player->currentSwordItemId = ITEM_SWORD_MASTER; + gSaveContext.equips.buttonItems[0] = ITEM_SWORD_MASTER; + Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_MASTER); + } + if (ImGui::Selectable("Biggoron's Sword")) { + if (gSaveContext.bgsFlag) { + if (gSaveContext.swordHealth < 8) { + gSaveContext.swordHealth = 8; + } + player->currentSwordItemId = ITEM_SWORD_BGS; + gSaveContext.equips.buttonItems[0] = ITEM_SWORD_BGS; + } + else { + if (gSaveContext.swordHealth < 8) { + gSaveContext.swordHealth = 8; + } + player->currentSwordItemId = ITEM_SWORD_BGS; + gSaveContext.equips.buttonItems[0] = ITEM_SWORD_KNIFE; + } - } - if (ImGui::BeginCombo("Shield", curShield)) { - if (ImGui::Selectable("None")) { - player->currentShield = PLAYER_SHIELD_NONE; - Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_NONE); - } - if (ImGui::Selectable("Deku Shield")) { - player->currentShield = PLAYER_SHIELD_DEKU; - Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_DEKU); - } - if (ImGui::Selectable("Hylian Shield")) { - player->currentShield = PLAYER_SHIELD_HYLIAN; - Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_HYLIAN); - } - if (ImGui::Selectable("Mirror Shield")) { - player->currentShield = PLAYER_SHIELD_MIRROR; - Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_MIRROR); - } - ImGui::EndCombo(); - } + Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_BIGGORON); + } + if (ImGui::Selectable("Fishing Pole")) { + player->currentSwordItemId = ITEM_FISHING_POLE; + gSaveContext.equips.buttonItems[0] = ITEM_FISHING_POLE; + Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_MASTER); + } + ImGui::EndCombo(); - if (ImGui::BeginCombo("Tunic", curTunic)) { - if (ImGui::Selectable("Kokiri Tunic")) { - player->currentTunic = PLAYER_TUNIC_KOKIRI; - Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_KOKIRI); } - if (ImGui::Selectable("Goron Tunic")) { - player->currentTunic = PLAYER_TUNIC_GORON; - Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_GORON); + if (ImGui::BeginCombo("Shield", curShield)) { + if (ImGui::Selectable("None")) { + player->currentShield = PLAYER_SHIELD_NONE; + Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_NONE); + } + if (ImGui::Selectable("Deku Shield")) { + player->currentShield = PLAYER_SHIELD_DEKU; + Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_DEKU); + } + if (ImGui::Selectable("Hylian Shield")) { + player->currentShield = PLAYER_SHIELD_HYLIAN; + Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_HYLIAN); + } + if (ImGui::Selectable("Mirror Shield")) { + player->currentShield = PLAYER_SHIELD_MIRROR; + Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_MIRROR); + } + ImGui::EndCombo(); } - if (ImGui::Selectable("Zora Tunic")) { - player->currentTunic = PLAYER_TUNIC_ZORA; - Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_ZORA); - } - ImGui::EndCombo(); - } - if (ImGui::BeginCombo("Boots", curBoots)) { - if (ImGui::Selectable("Kokiri Boots")) { - player->currentBoots = PLAYER_BOOTS_KOKIRI; - Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_KOKIRI); + if (ImGui::BeginCombo("Tunic", curTunic)) { + if (ImGui::Selectable("Kokiri Tunic")) { + player->currentTunic = PLAYER_TUNIC_KOKIRI; + Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_KOKIRI); + } + if (ImGui::Selectable("Goron Tunic")) { + player->currentTunic = PLAYER_TUNIC_GORON; + Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_GORON); + } + if (ImGui::Selectable("Zora Tunic")) { + player->currentTunic = PLAYER_TUNIC_ZORA; + Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_ZORA); + } + ImGui::EndCombo(); } - if (ImGui::Selectable("Iron Boots")) { - player->currentBoots = PLAYER_BOOTS_IRON; - Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_IRON); + + if (ImGui::BeginCombo("Boots", curBoots)) { + if (ImGui::Selectable("Kokiri Boots")) { + player->currentBoots = PLAYER_BOOTS_KOKIRI; + Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_KOKIRI); + } + if (ImGui::Selectable("Iron Boots")) { + player->currentBoots = PLAYER_BOOTS_IRON; + Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_IRON); + } + if (ImGui::Selectable("Hover Boots")) { + player->currentBoots = PLAYER_BOOTS_HOVER; + Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_HOVER); + } + ImGui::EndCombo(); } - if (ImGui::Selectable("Hover Boots")) { - player->currentBoots = PLAYER_BOOTS_HOVER; - Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_HOVER); - } - ImGui::EndCombo(); - } + ImGui::PopItemWidth(); + PopStyleCombobox(); + }, "Current Equipment"); + ImGui::SameLine(); ImU16 one = 1; - ImGui::PushItemWidth(ImGui::GetFontSize() * 6); DrawGroupWithBorder([&]() { - ImGui::Text("Current B Item"); + ImGui::PushItemWidth(ImGui::GetFontSize() * 6); + PushStyleInput(THEME_COLOR); + ImGui::Text("Current Items"); ImGui::InputScalar("B Button", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[0], &one, NULL); - ImGui::NewLine(); - - ImGui::Text("Current C Equips"); ImGui::InputScalar("C Left", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[1], &one, NULL); - ImGui::SameLine(); ImGui::InputScalar("C Down", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[2], &one, NULL); - ImGui::SameLine(); ImGui::InputScalar("C Right", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[3], &one, NULL); - - if (CVarGetInteger(CVAR_ENHANCEMENT("DpadEquips"), 0)) { - ImGui::NewLine(); - ImGui::Text("Current D-pad Equips"); - ImGui::InputScalar("D-pad Up ", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[4], &one, NULL); // Two spaces at the end for aligning, not elegant but it's working - ImGui::SameLine(); + PopStyleInput(); + ImGui::PopItemWidth(); + }, "Current Items"); + + if (CVarGetInteger(CVAR_ENHANCEMENT("DpadEquips"), 0)) { + ImGui::SameLine(); + DrawGroupWithBorder([&]() { + ImGui::PushItemWidth(ImGui::GetFontSize() * 6); + PushStyleInput(THEME_COLOR); + ImGui::Text("Current D-pad Items"); + // Two spaces at the end for aligning, not elegant but it's working + ImGui::InputScalar("D-pad Up ", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[4], &one, NULL); ImGui::InputScalar("D-pad Down", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[5], &one, NULL); - // Intentionnal to not put everything on the same line, else it's taking too much for lower resolution. ImGui::InputScalar("D-pad Left", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[6], &one, NULL); - ImGui::SameLine(); ImGui::InputScalar("D-pad Right", ImGuiDataType_U8, &gSaveContext.equips.buttonItems[7], &one, NULL); - } - }); + PopStyleInput(); + ImGui::PopItemWidth(); + }, "Current D-pad Items"); + } ImGui::Text("Player State"); uint8_t bit[32] = {}; @@ -1550,8 +1523,9 @@ void DrawPlayerTab() { std::vector> flag_strs = { state1, state2, state3 }; for (int j = 0; j <= 2; j++) { + std::string label = fmt::format("State Flags {}", j + 1); DrawGroupWithBorder([&]() { - ImGui::Text("State Flags %d", j + 1); + ImGui::Text("%s", label.c_str()); std::vector state = flag_strs[j]; for (int i = 0; i <= 31; i++) { bit[i] = ((flags[j] >> i) & 1); @@ -1559,46 +1533,60 @@ void DrawPlayerTab() { ImGui::Text("%s", state[i].c_str()); } } - }); + }, label.c_str()); ImGui::SameLine(); } DrawGroupWithBorder([&]() { ImGui::Text("Sword"); ImGui::Text(" %d", player->meleeWeaponState); - }); + }, "Sword"); } else { ImGui::Text("Global Context needed for player info!"); } } +void ResetBaseOptions() { + intSliderOptionsBase.Color(THEME_COLOR).Size({320.0f, 0.0f}).Tooltip(""); + buttonOptionsBase.Color(THEME_COLOR).Size(Sizes::Inline).Tooltip(""); + checkboxOptionsBase.Color(THEME_COLOR).Tooltip(""); + comboboxOptionsBase.Color(THEME_COLOR).ComponentAlignment(ComponentAlignment::Left).LabelPosition(LabelPosition::Near).Tooltip(""); +} + void SaveEditorWindow::DrawElement() { + PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("SaveContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { + ResetBaseOptions(); if (ImGui::BeginTabItem("Info")) { DrawInfoTab(); ImGui::EndTabItem(); } + ResetBaseOptions(); if (ImGui::BeginTabItem("Inventory")) { DrawInventoryTab(); ImGui::EndTabItem(); } + ResetBaseOptions(); if (ImGui::BeginTabItem("Flags")) { DrawFlagsTab(); ImGui::EndTabItem(); } + ResetBaseOptions(); if (ImGui::BeginTabItem("Equipment")) { DrawEquipmentTab(); ImGui::EndTabItem(); } + ResetBaseOptions(); if (ImGui::BeginTabItem("Quest Status")) { DrawQuestStatusTab(); ImGui::EndTabItem(); } + ResetBaseOptions(); if (ImGui::BeginTabItem("Player")) { DrawPlayerTab(); ImGui::EndTabItem(); @@ -1606,6 +1594,7 @@ void SaveEditorWindow::DrawElement() { ImGui::EndTabBar(); } + PopStyleTabs(); } void SaveEditorWindow::InitElement() {} diff --git a/soh/soh/Enhancements/debugger/dlViewer.cpp b/soh/soh/Enhancements/debugger/dlViewer.cpp index 4f4d13bff..1459d2809 100644 --- a/soh/soh/Enhancements/debugger/dlViewer.cpp +++ b/soh/soh/Enhancements/debugger/dlViewer.cpp @@ -1,6 +1,7 @@ #include "actorViewer.h" #include "soh/util.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "ResourceManager.h" #include "DisplayList.h" #include "soh/OTRGlobals.h" @@ -90,11 +91,14 @@ void PerformDisplayListSearch() { } void DLViewerWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); // Debounce the search field as listing otr files is expensive + UIWidgets::PushStyleInput(THEME_COLOR); if (ImGui::InputText("Search Display Lists", searchString, ARRAY_COUNT(searchString))) { doSearch = true; searchDebounceFrames = 30; } + UIWidgets::PopStyleInput(); if (doSearch) { if (searchDebounceFrames == 0) { @@ -105,6 +109,7 @@ void DLViewerWindow::DrawElement() { searchDebounceFrames--; } + UIWidgets::PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("Active Display List", activeDisplayList.c_str())) { for (size_t i = 0; i < displayListSearchResults.size(); i++) { if (ImGui::Selectable(displayListSearchResults[i].c_str())) { @@ -114,8 +119,10 @@ void DLViewerWindow::DrawElement() { } ImGui::EndCombo(); } + UIWidgets::PopStyleCombobox(); if (activeDisplayList == "") { + ImGui::PopFont(); return; } @@ -124,6 +131,7 @@ void DLViewerWindow::DrawElement() { if (res->GetInitData()->Type != static_cast(Fast::ResourceType::DisplayList)) { ImGui::Text("Resource type is not a Display List. Please choose another."); + ImGui::PopFont(); return; } @@ -144,6 +152,7 @@ void DLViewerWindow::DrawElement() { ImGui::SameLine(); ImGui::PushItemWidth(175.0f); + UIWidgets::PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo(("CMD" + id).c_str(), cmdLabel.c_str())) { if (ImGui::Selectable("gsDPSetPrimColor") && cmd != G_SETPRIMCOLOR) { *gfx = gsDPSetPrimColor(0, 0, 0, 0, 0, 255); @@ -162,6 +171,7 @@ void DLViewerWindow::DrawElement() { } ImGui::EndCombo(); } + UIWidgets::PopStyleCombobox(); ImGui::PopItemWidth(); @@ -194,9 +204,11 @@ void DLViewerWindow::DrawElement() { if (cmd == G_SETGRAYSCALE) { bool* state = (bool*)&gfx->words.w1; ImGui::SameLine(); + UIWidgets::PushStyleCheckbox(THEME_COLOR); if (ImGui::Checkbox(("state" + id).c_str(), state)) { // } + UIWidgets::PopStyleCheckbox(); } if (cmd == G_SETTILE) { ImGui::SameLine(); @@ -317,8 +329,10 @@ void DLViewerWindow::DrawElement() { } } catch (const std::exception& e) { ImGui::Text("Error displaying DL instructions."); + ImGui::PopFont(); return; } + ImGui::PopFont(); } void DLViewerWindow::InitElement() { diff --git a/soh/soh/Enhancements/debugger/valueViewer.cpp b/soh/soh/Enhancements/debugger/valueViewer.cpp index 7d09dfb63..40ec77f71 100644 --- a/soh/soh/Enhancements/debugger/valueViewer.cpp +++ b/soh/soh/Enhancements/debugger/valueViewer.cpp @@ -1,5 +1,6 @@ #include "valueViewer.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "soh/OTRGlobals.h" #include "soh/ShipInit.hpp" @@ -142,13 +143,15 @@ void RegisterValueViewerHooks() { RegisterShipInitFunc initFunc(RegisterValueViewerHooks, { CVAR_NAME }); void ValueViewerWindow::DrawElement() { - UIWidgets::PaddedEnhancementCheckbox("Enable Printing", CVAR_NAME); + ImGui::PushFont(OTRGlobals::Instance->fontMonoLargest); + UIWidgets::CVarCheckbox("Enable Printing", CVAR_NAME, UIWidgets::CheckboxOptions().Color(THEME_COLOR)); ImGui::BeginGroup(); static int selectedElement = -1; std::string selectedElementText = (selectedElement == -1) ? "Select a value" : ( std::string(valueTable[selectedElement].name) + " (" + std::string(valueTable[selectedElement].path) + ")" ); + UIWidgets::PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("##valueViewerElement", selectedElementText.c_str())) { for (int i = 0; i < valueTable.size(); i++) { if (valueTable[i].isActive) continue; @@ -165,20 +168,28 @@ void ValueViewerWindow::DrawElement() { } ImGui::EndCombo(); } + UIWidgets::PopStyleCombobox(); ImGui::SameLine(); + UIWidgets::PushStyleButton(THEME_COLOR); if (selectedElement != -1 && ImGui::Button("+")) { valueTable[selectedElement].isActive = true; selectedElement = -1; } + UIWidgets::PopStyleButton(); ImGui::EndGroup(); for (int i = 0; i < valueTable.size(); i++) { ValueTableElement& element = valueTable[i]; if (!element.isActive || (gPlayState == NULL && element.requiresPlayState)) continue; - if (ImGui::Button(("x##" + std::string(element.name)).c_str())) { + UIWidgets::PushStyleButton(THEME_COLOR); + UIWidgets::PushStyleCheckbox(THEME_COLOR); + ImGui::AlignTextToFramePadding(); + if (ImGui::Button((ICON_FA_TIMES + std::string("##") + std::string(element.name)).c_str())) { element.isActive = false; element.isPrinted = false; } + UIWidgets::PopStyleCheckbox(); + UIWidgets::PopStyleButton(); ImGui::SameLine(); ImGui::Text("%s:", element.name); ImGui::SameLine(); @@ -212,7 +223,7 @@ void ValueViewerWindow::DrawElement() { break; } ImGui::SameLine(); - + UIWidgets::PushStyleCheckbox(THEME_COLOR); if (element.type <= TYPE_U32) { ImGui::Checkbox(("Hex##" + std::string(element.name)).c_str(), &element.typeFormat); ImGui::SameLine(); @@ -220,23 +231,30 @@ void ValueViewerWindow::DrawElement() { ImGui::Checkbox(("Trim##" + std::string(element.name)).c_str(), &element.typeFormat); ImGui::SameLine(); } + UIWidgets::PopStyleCheckbox(); ImGui::BeginGroup(); if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("ValueViewerEnablePrinting"), 0)) { + UIWidgets::PushStyleCheckbox(THEME_COLOR); ImGui::Checkbox(("Print##" + std::string(element.name)).c_str(), &element.isPrinted); + UIWidgets::PopStyleCheckbox(); if (element.isPrinted) { char* prefix = (char*)element.prefix.c_str(); ImGui::SameLine(); ImGui::SetNextItemWidth(80.0f); + UIWidgets::PushStyleInput(THEME_COLOR); if (ImGui::InputText(("Prefix##" + std::string(element.name)).c_str(), prefix, 10)) { element.prefix = prefix; } + UIWidgets::PopStyleInput(); ImGui::SameLine(); ImGui::ColorEdit3(("##color" + std::string(element.name)).c_str(), (float*)&element.color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel); ImGui::SameLine(); + UIWidgets::PushStyleCheckbox(THEME_COLOR); if (ImGui::Button(("Position##" + std::string(element.name)).c_str())) { ImGui::OpenPopup(("Position Picker##" + std::string(element.name)).c_str()); } + UIWidgets::PopStyleCheckbox(); if (ImGui::BeginPopup(("Position Picker##" + std::string(element.name)).c_str())) { ImGui::DragInt("X", (int*)&element.x, 1.0f, 0, 44); ImGui::DragInt("Y", (int*)&element.y, 1.0f, 0, 29); @@ -246,6 +264,7 @@ void ValueViewerWindow::DrawElement() { } ImGui::EndGroup(); } + ImGui::PopFont(); } void ValueViewerWindow::InitElement() { diff --git a/soh/soh/Enhancements/enhancementTypes.h b/soh/soh/Enhancements/enhancementTypes.h index db96a7700..14228054b 100644 --- a/soh/soh/Enhancements/enhancementTypes.h +++ b/soh/soh/Enhancements/enhancementTypes.h @@ -39,7 +39,7 @@ typedef enum { MIRRORED_WORLD_ALWAYS, MIRRORED_WORLD_RANDOM, MIRRORED_WORLD_RANDOM_SEEDED, - MIRRORED_WORLD_DUNGEONS_All, + MIRRORED_WORLD_DUNGEONS_ALL, MIRRORED_WORLD_DUNGEONS_VANILLA, MIRRORED_WORLD_DUNGEONS_MQ, MIRRORED_WORLD_DUNGEONS_RANDOM, @@ -75,6 +75,18 @@ typedef enum { BONK_DAMAGE_OHKO, } BonkDamage; +typedef enum { + DAMAGE_VANILLA, + DAMAGE_DOUBLE, + DAMAGE_QUADRUPLE, + DAMAGE_OCTUPLE, + DAMAGE_FOOLISH, + DAMAGE_RIDICULOUS, + DAMAGE_MERCILESS, + DAMAGE_TORTURE, + DAMAGE_OHKO +} DamageMultType; + typedef enum { DEKU_STICK_NORMAL, DEKU_STICK_UNBREAKABLE, @@ -87,4 +99,16 @@ typedef enum { SWORD_TOGGLE_BOTH_AGES, } SwordToggleMode; +typedef enum { + TIME_TRAVEL_DISABLED, + TIME_TRAVEL_OOT, + TIME_TRAVEL_ANY +} TimeTravelType; + +typedef enum { + WATERFALL_ALWAYS, + WATERFALL_ONCE, + WATERFALL_NEVER +} SleepingWaterfallType; + #endif diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index aff0d0726..af7807b3a 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -1,11 +1,11 @@ #include "gameplaystats.h" -#include "gameplaystatswindow.h" #include "soh/SaveManager.h" #include "functions.h" #include "macros.h" #include "soh/cvar_prefixes.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "soh/util.h" #include @@ -376,14 +376,18 @@ void SaveStats(SaveContext* saveContext, int sectionID, bool fullSave) { }); } -void GameplayStatsRow(const char* label, const std::string& value, ImVec4 color = COLOR_WHITE) { +void GameplayStatsRow(const char* label, const std::string& value, ImVec4 color = COLOR_WHITE, + const char* tooltip = "") { ImGui::PushStyleColor(ImGuiCol_Text, color); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("%s", label); - ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(value.c_str()).x - 8.0f)); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(value.c_str()).x)); ImGui::Text("%s", value.c_str()); ImGui::PopStyleColor(); + if (tooltip != "" && ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", tooltip); + } } bool compareTimestampInfoByTime(const TimestampInfo& a, const TimestampInfo& b) { @@ -527,8 +531,8 @@ void DrawGameplayStatsCountsTab() { } } } - GameplayStatsRow("Rupees Collected:", formatIntGameplayStat(gSaveContext.ship.stats.count[COUNT_RUPEES_COLLECTED])); - UIWidgets::Tooltip("Includes rupees collected with a full wallet."); + GameplayStatsRow("Rupees Collected:", formatIntGameplayStat(gSaveContext.ship.stats.count[COUNT_RUPEES_COLLECTED]), + COLOR_WHITE, "Includes rupees collected with a full wallet."); GameplayStatsRow("Rupees Spent:", formatIntGameplayStat(gSaveContext.ship.stats.count[COUNT_RUPEES_SPENT])); GameplayStatsRow("Chests Opened:", formatIntGameplayStat(gSaveContext.ship.stats.count[COUNT_CHESTS_OPENED])); GameplayStatsRow("Ammo Used:", formatIntGameplayStat(ammoUsed)); @@ -606,26 +610,34 @@ void DrawGameplayStatsBreakdownTab() { } void DrawGameplayStatsOptionsTab() { - UIWidgets::PaddedEnhancementCheckbox("Show in-game total timer", CVAR_ENHANCEMENT("GameplayStats.ShowIngameTimer"), true, false); - UIWidgets::InsertHelpHoverText("Keep track of the timer as an in-game HUD element. The position of the timer can be changed in the Cosmetics Editor."); - UIWidgets::PaddedEnhancementCheckbox("Show latest timestamps on top", CVAR_ENHANCEMENT("GameplayStats.ReverseTimestamps"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Room Breakdown", CVAR_ENHANCEMENT("GameplayStats.RoomBreakdown"), true, false); - ImGui::SameLine(); - UIWidgets::InsertHelpHoverText("Allows a more in-depth perspective of time spent in a certain map."); - UIWidgets::PaddedEnhancementCheckbox("RTA Timing on new files", CVAR_ENHANCEMENT("GameplayStats.RTATiming"), true, false); - ImGui::SameLine(); - UIWidgets::InsertHelpHoverText( - "Timestamps are relative to starting timestamp rather than in game time, usually necessary for races/speedruns.\n\n" - "Starting timestamp is on first non-c-up input after intro cutscene.\n\n" - "NOTE: THIS NEEDS TO BE SET BEFORE CREATING A FILE TO TAKE EFFECT" - ); - UIWidgets::PaddedEnhancementCheckbox("Show additional detail timers", CVAR_ENHANCEMENT("GameplayStats.ShowAdditionalTimers"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Show Debug Info", CVAR_ENHANCEMENT("GameplayStats.ShowDebugInfo")); + UIWidgets::CVarCheckbox("Show in-game total timer", CVAR_ENHANCEMENT("GameplayStats.ShowIngameTimer"), + UIWidgets::CheckboxOptions() + .Tooltip("Keep track of the timer as an in-game HUD element. The position of the " + "timer can be changed in the Cosmetics Editor.") + .Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Show latest timestamps on top", CVAR_ENHANCEMENT("GameplayStats.ReverseTimestamps"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Room Breakdown", CVAR_ENHANCEMENT("GameplayStats.RoomBreakdown"), + UIWidgets::CheckboxOptions() + .Tooltip("Allows a more in-depth perspective of time spent in a certain map.") + .Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("RTA Timing on new files", CVAR_ENHANCEMENT("GameplayStats.RTATiming"), + UIWidgets::CheckboxOptions() + .Tooltip("Timestamps are relative to starting timestamp rather than in game time, " + "usually necessary for races/speedruns.\n\n" + "Starting timestamp is on first non-c-up input after intro cutscene.\n\n" + "NOTE: THIS NEEDS TO BE SET BEFORE CREATING A FILE TO TAKE EFFECT") + .Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Show additional detail timers", CVAR_ENHANCEMENT("GameplayStats.ShowAdditionalTimers"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Show Debug Info", CVAR_ENHANCEMENT("GameplayStats.ShowDebugInfo"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR)); } void GameplayStatsWindow::DrawElement() { DrawGameplayStatsHeader(); + UIWidgets::PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("Stats", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("Timestamps")) { DrawGameplayStatsTimestampsTab(); @@ -645,6 +657,7 @@ void GameplayStatsWindow::DrawElement() { } ImGui::EndTabBar(); } + UIWidgets::PopStyleTabs(); ImGui::Text("Note: Gameplay stats are saved to the current file and will be\nlost if you quit without saving."); } diff --git a/soh/soh/Enhancements/gameplaystatswindow.h b/soh/soh/Enhancements/gameplaystatswindow.h index 453b24559..446cc1ef4 100644 --- a/soh/soh/Enhancements/gameplaystatswindow.h +++ b/soh/soh/Enhancements/gameplaystatswindow.h @@ -1,5 +1,4 @@ #include -#include "gameplaystats.h" class GameplayStatsWindow : public Ship::GuiWindow { public: diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index cd60f7d8c..cfcb98fa2 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -463,7 +463,7 @@ void UpdateMirrorModeState(int32_t sceneNum) { mirroredMode == MIRRORED_WORLD_ALWAYS || ((mirroredMode == MIRRORED_WORLD_RANDOM || mirroredMode == MIRRORED_WORLD_RANDOM_SEEDED) && randomMirror) || // Dungeon modes - (inDungeon && (mirroredMode == MIRRORED_WORLD_DUNGEONS_All || + (inDungeon && (mirroredMode == MIRRORED_WORLD_DUNGEONS_ALL || (mirroredMode == MIRRORED_WORLD_DUNGEONS_VANILLA && !ResourceMgr_IsSceneMasterQuest(sceneNum)) || (mirroredMode == MIRRORED_WORLD_DUNGEONS_MQ && ResourceMgr_IsSceneMasterQuest(sceneNum)) || ((mirroredMode == MIRRORED_WORLD_DUNGEONS_RANDOM || mirroredMode == MIRRORED_WORLD_DUNGEONS_RANDOM_SEEDED) && randomMirror))) diff --git a/soh/soh/Enhancements/presets.cpp b/soh/soh/Enhancements/presets.cpp index 2b1812d21..240d0fa81 100644 --- a/soh/soh/Enhancements/presets.cpp +++ b/soh/soh/Enhancements/presets.cpp @@ -3,8 +3,10 @@ #include #include #include -#include "soh/SohGui/UIWidgets.hpp" #include +#include "soh/SohGui/MenuTypes.h" +#include "soh/SohGui/SohMenu.h" +#include "soh/SohGui/SohGui.hpp" std::string FormatLocations(std::vector locs) { std::string locString = ""; @@ -30,6 +32,7 @@ void applyPreset(std::vector entries) { CVarSetString(cvar, std::get(value).c_str()); break; } + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } } @@ -47,21 +50,22 @@ void DrawPresetSelector(PresetType presetTypeId) { comboboxTooltip += std::string(iter->second.label) + " - " + std::string(iter->second.description); } - UIWidgets::PaddedText("Presets", false, true); + ImGui::Text("Presets", false, true); + UIWidgets::PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("##PresetsComboBox", selectedPresetDef.label)) { for ( auto iter = presetTypeDef.presets.begin(); iter != presetTypeDef.presets.end(); ++iter ) { if (ImGui::Selectable(iter->second.label, iter->first == selectedPresetId)) { CVarSetInteger(presetTypeCvar.c_str(), iter->first); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } } ImGui::EndCombo(); } + UIWidgets::PopStyleCombobox(); UIWidgets::Tooltip(comboboxTooltip.c_str()); - UIWidgets::Spacer(0); - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f)); + UIWidgets::PushStyleButton(THEME_COLOR); if (ImGui::Button(("Apply Preset##" + presetTypeCvar).c_str())) { for(const char* block : presetTypeDef.blocksToClear) { CVarClearBlock(block); @@ -75,5 +79,5 @@ void DrawPresetSelector(PresetType presetTypeId) { Rando::Settings::GetInstance()->ReloadOptions(); } } - ImGui::PopStyleVar(1); + UIWidgets::PopStyleButton(); } diff --git a/soh/soh/Enhancements/randomizer/Plandomizer.cpp b/soh/soh/Enhancements/randomizer/Plandomizer.cpp index e6c9399b5..4741d9d0c 100644 --- a/soh/soh/Enhancements/randomizer/Plandomizer.cpp +++ b/soh/soh/Enhancements/randomizer/Plandomizer.cpp @@ -1,4 +1,5 @@ #include "Plandomizer.h" +#include #include "soh/SohGui/UIWidgets.hpp" #include "soh/util.h" #include @@ -836,21 +837,9 @@ void PlandomizerDrawItemSlots(uint32_t index) { void PlandomizerDrawShopSlider(uint32_t index) { ImGui::PushID(index); - ImGui::Text("Price:"); - ImGui::SameLine(); - std::string MinusBTNName = " - ##Price"; - if (ImGui::Button(MinusBTNName.c_str()) && plandoLogData[index].shopPrice > 0) { - plandoLogData[index].shopPrice--; - } - ImGui::SameLine(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - 40.0f); - ImGui::SliderInt("", &plandoLogData[index].shopPrice, 0, 999, "%d Rupees"); - ImGui::PopItemWidth(); - ImGui::SameLine(); - std::string PlusBTNName = " + ##Price"; - if (ImGui::Button(PlusBTNName.c_str()) && plandoLogData[index].shopPrice < 999) { - plandoLogData[index].shopPrice++; - } + UIWidgets::SliderInt("Price:", &plandoLogData[index].shopPrice, UIWidgets::IntSliderOptions() + .Color(THEME_COLOR).Format("%d Rupees").Min(0).Max(999).LabelPosition(UIWidgets::LabelPosition::Near) + .ComponentAlignment(UIWidgets::ComponentAlignment::Right).Size(UIWidgets::Sizes::Inline)); ImGui::PopID(); } @@ -883,13 +872,13 @@ void PlandomizerDrawIceTrapSetup(uint32_t index) { ImGui::SameLine(); if (plandoLogData[index].iceTrapModel.GetRandomizerGet() != RG_NONE && plandoLogData[index].iceTrapModel.GetRandomizerGet() != RG_SOLD_OUT) { - if (ImGui::Button(randomizeButton.c_str())) { + if (UIWidgets::Button(randomizeButton.c_str(), UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline).Padding(ImVec2(10.f, 6.f)))) { plandoLogData[index].iceTrapName = GetIceTrapName(plandoLogData[index].iceTrapModel.GetRandomizerGet()).GetForLanguage(CVarGetInteger(CVAR_SETTING("Languages"), 0)).c_str(); } ImGui::SameLine(); } - if (UIWidgets::InputString("##TrapName", &trapTextInput)) { + if (UIWidgets::InputString("##TrapName", &trapTextInput, UIWidgets::InputOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::None))) { plandoLogData[index].iceTrapName = trapTextInput.c_str(); } @@ -900,39 +889,64 @@ void PlandomizerDrawIceTrapSetup(uint32_t index) { ImGui::PopID(); } - +static std::unordered_map rcAreaNameMap = { + { RCAREA_KOKIRI_FOREST, "Kokiri Forest" }, + { RCAREA_LOST_WOODS, "Lost Woods" }, + { RCAREA_SACRED_FOREST_MEADOW, "Sacred Forest Meadow" }, + { RCAREA_HYRULE_FIELD, "Hyrule Field" }, + { RCAREA_LAKE_HYLIA, "Lake Hylia" }, + { RCAREA_GERUDO_VALLEY, "Gerudo Valley" }, + { RCAREA_GERUDO_FORTRESS, "Gerudo Fortress" }, + { RCAREA_WASTELAND, "Haunted Wasteland" }, + { RCAREA_DESERT_COLOSSUS, "Desert Colossus" }, + { RCAREA_MARKET, "Hyrule Market" }, + { RCAREA_HYRULE_CASTLE, "Hyrule Castle" }, + { RCAREA_KAKARIKO_VILLAGE, "Kakariko Village" }, + { RCAREA_GRAVEYARD, "Graveyard" }, + { RCAREA_DEATH_MOUNTAIN_TRAIL, "Death Mountain Trail" }, + { RCAREA_GORON_CITY, "Goron City" }, + { RCAREA_DEATH_MOUNTAIN_CRATER, "Death Mountain Crater" }, + { RCAREA_ZORAS_RIVER, "Zora's River" }, + { RCAREA_ZORAS_DOMAIN, "Zora's Domain" }, + { RCAREA_ZORAS_FOUNTAIN, "Zora's Fountain" }, + { RCAREA_LON_LON_RANCH, "Lon Lon Ranch" }, + { RCAREA_DEKU_TREE, "Deku Tree" }, + { RCAREA_DODONGOS_CAVERN, "Dodongo's Cavern" }, + { RCAREA_JABU_JABUS_BELLY, "Jabu Jabu's Belly" }, + { RCAREA_FOREST_TEMPLE, "Forest Temple" }, + { RCAREA_FIRE_TEMPLE, "Fire Temple" }, + { RCAREA_WATER_TEMPLE, "Water Temple" }, + { RCAREA_SPIRIT_TEMPLE, "Spirit Temple" }, + { RCAREA_SHADOW_TEMPLE, "Shadow Temple" }, + { RCAREA_BOTTOM_OF_THE_WELL, "Bottom of the Well" }, + { RCAREA_ICE_CAVERN, "Ice Cavern" }, + { RCAREA_GERUDO_TRAINING_GROUND, "Gerudo Training Ground" }, + { RCAREA_GANONS_CASTLE, "Ganon's Castle" }, + { RCAREA_INVALID, "All" }, +}; void PlandomizerDrawOptions() { if (ImGui::BeginTable("LoadSpoiler", 2)) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); ImGui::TableNextColumn(); ImGui::SeparatorText("Load/Save Spoiler Log"); PlandomizerPopulateSeedList(); static size_t selectedList = 0; if (existingSeedList.size() != 0) { - if (ImGui::BeginCombo("##JsonFiles", existingSeedList[selectedList].c_str())) { - for (size_t i = 0; i < existingSeedList.size(); i++) { - bool isSelected = (selectedList == i); - if (ImGui::Selectable(existingSeedList[i].c_str(), isSelected)) { - selectedList = i; - } - if (isSelected) { - ImGui::SetItemDefaultFocus(); - } - } - ImGui::EndCombo(); - } + UIWidgets::Combobox("##JsonFiles", &selectedList, existingSeedList, UIWidgets::ComboboxOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::None)); } else { ImGui::Text("No Spoiler Logs found."); } ImGui::BeginDisabled(existingSeedList.empty()); - if (ImGui::Button("Load")) { + if (UIWidgets::Button("Load", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { logTemp = existingSeedList[selectedList].c_str(); PlandomizerLoadSpoilerLog(logTemp.c_str()); } ImGui::EndDisabled(); ImGui::BeginDisabled(spoilerLogData.empty()); ImGui::SameLine(); - if (ImGui::Button("Save")) { + if (UIWidgets::Button("Save", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { PlandomizerSaveSpoilerLog(); } ImGui::EndDisabled(); @@ -1000,44 +1014,19 @@ void PlandomizerDrawOptions() { } if (getTabID == TAB_HINTS) { - if (ImGui::Button("Clear All Hints")) { + if (UIWidgets::Button("Clear All Hints", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { PlandomizerRemoveAllHints(); } ImGui::SameLine(); - if (ImGui::Button("Randomize All Hints")) { + if (UIWidgets::Button("Randomize All Hints", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { PlandomizerRandomizeHint(HINT_ALL, 0); } } if (getTabID == TAB_LOCATIONS) { if (plandoLogData.size() > 0) { - const char* comboLabel = rcAreaNames[selectedArea].c_str(); - if (selectedArea == RCAREA_INVALID) { - comboLabel = "All"; - } - ImGui::Text("Filter by Area:"); + UIWidgets::Combobox("Filter by Area:##AreaFilter", &selectedArea, rcAreaNameMap, UIWidgets::ComboboxOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::Near).ComponentAlignment(UIWidgets::ComponentAlignment::Right)); ImGui::SameLine(); - ImGui::PushItemWidth(300.0f); - if (ImGui::BeginCombo("##AreaFilter", comboLabel)) { - for (const auto& [area, name] : rcAreaNames) { - bool isSelected = (selectedArea == area); - - const char* displayName = name.c_str(); - if (area == RCAREA_INVALID) { - displayName = "All"; - } - if (ImGui::Selectable(displayName, isSelected)) { - selectedArea = area; - } - if (isSelected) { - ImGui::SetItemDefaultFocus(); - } - } - ImGui::EndCombo(); - } - ImGui::PopItemWidth(); - - ImGui::SameLine(); - if (ImGui::Button("Empty All Rewards")) { + if (UIWidgets::Button("Empty All Rewards", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline).Padding(ImVec2(10.f, 6.f)))) { PlandomizerRemoveAllItems(); } } @@ -1067,16 +1056,14 @@ void PlandomizerDrawHintsWindow() { } ImGui::Text("New Hint: "); ImGui::SameLine(); - if (ImGui::Button(randomizeButton.c_str())) { + if (UIWidgets::Button(randomizeButton.c_str(), UIWidgets::ButtonOptions().Color(THEME_COLOR).Padding(ImVec2(10.f, 6.f)).Size(UIWidgets::Sizes::Inline).Tooltip("Randomize Hint"))) { PlandomizerRandomizeHint(HINT_SINGLE, index); } - UIWidgets::Tooltip("Randomize Hint"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 10); - if (UIWidgets::InputString("##HintMessage", &hintInputText)) { + if (UIWidgets::InputString("##HintMessage", &hintInputText, UIWidgets::InputOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::None).Tooltip(plandomizerHintsTooltip().c_str()))) { plandoHintData[index].hintText = hintInputText.c_str(); } - UIWidgets::Tooltip(plandomizerHintsTooltip().c_str()); index++; ImGui::PopID(); } @@ -1133,6 +1120,7 @@ void PlandomizerDrawLocationsWindow(RandomizerCheckArea rcArea) { void PlandomizerDrawSpoilerTable() { ImGui::BeginChild("Main"); + UIWidgets::PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("Check Tabs")) { if (ImGui::BeginTabItem("Gossip Stones")) { getTabID = TAB_HINTS; @@ -1146,12 +1134,13 @@ void PlandomizerDrawSpoilerTable() { } } ImGui::EndTabBar(); + UIWidgets::PopStyleTabs(); ImGui::EndChild(); } void PlandomizerWindow::DrawElement() { PlandomizerDrawOptions(); - UIWidgets::PaddedSeparator(); + UIWidgets::Separator(true, true, 0.f, 0.f); PlandomizerDrawSpoilerTable(); } diff --git a/soh/soh/Enhancements/randomizer/Plandomizer.h b/soh/soh/Enhancements/randomizer/Plandomizer.h index d337e4da6..1b7996ce4 100644 --- a/soh/soh/Enhancements/randomizer/Plandomizer.h +++ b/soh/soh/Enhancements/randomizer/Plandomizer.h @@ -39,14 +39,14 @@ typedef struct { std::string hintText; } SpoilerHintObject; -typedef enum { +typedef enum PlandoTabs { TAB_HINTS, TAB_LOCATIONS -}; +} PlandoTabs; -typedef enum { +typedef enum PlandoHints { HINT_SINGLE, HINT_ALL, -}; +} PlandoHints; #endif \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/option.cpp b/soh/soh/Enhancements/randomizer/option.cpp index e032d1a2d..2c478e34c 100644 --- a/soh/soh/Enhancements/randomizer/option.cpp +++ b/soh/soh/Enhancements/randomizer/option.cpp @@ -2,7 +2,9 @@ #include "libultraship/bridge.h" #include #include +#include "soh/SohGui/SohGui.hpp" #include "soh/SohGui/UIWidgets.hpp" +#include namespace Rando { Option Option::Bool(RandomizerSettingKey key_, std::string name_, std::vector options_, @@ -130,11 +132,10 @@ void Option::Enable() { disabled = false; } -void Option::Disable(std::string text, const UIWidgets::CheckboxGraphics graphic) { - if (!disabled || disabledText != text || disabledGraphic != graphic) { +void Option::Disable(std::string text) { + if (!disabled || disabledText != text) { disabled = true; disabledText = std::move(text); - disabledGraphic = graphic; } } @@ -149,9 +150,6 @@ bool Option::RenderImGui() { case WidgetType::Checkbox: changed = RenderCheckbox(); break; - case WidgetType::TristateCheckbox: - changed = RenderTristateCheckbox(); - break; case WidgetType::Combobox: changed = RenderCombobox(); break; @@ -159,7 +157,6 @@ bool Option::RenderImGui() { changed = RenderSlider(); break; } - UIWidgets::Spacer(0); ImGui::EndGroup(); return changed; } @@ -213,50 +210,19 @@ Option::Option(size_t key_, std::string name_, std::vector options_ bool Option::RenderCheckbox() { bool changed = false; - if (disabled) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } bool val = static_cast(CVarGetInteger(cvarName.c_str(), defaultOption)); - if (CustomCheckbox(name.c_str(), &val, disabled, disabledGraphic)) { + UIWidgets::CheckboxOptions widgetOptions = static_cast(UIWidgets::CheckboxOptions().Color(THEME_COLOR).Tooltip(description.c_str())); + widgetOptions.disabled = disabled; + if (UIWidgets::Checkbox(name.c_str(), &val, widgetOptions)) { CVarSetInteger(cvarName.c_str(), val); changed = true; Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - if (!description.empty()) { - UIWidgets::InsertHelpHoverText(description.c_str()); - } - if (disabled) { - UIWidgets::ReEnableComponent(disabledText.c_str()); - } - return changed; -} - -bool Option::RenderTristateCheckbox() { - bool changed = false; - if (disabled) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - int val = CVarGetInteger(cvarName.c_str(), defaultOption); - if (CustomCheckboxTristate(name.c_str(), &val, disabled, disabledGraphic)) { - CVarSetInteger(cvarName.c_str(), val); - changed = true; - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - if (!description.empty()) { - UIWidgets::InsertHelpHoverText(description.c_str()); - } - if (disabled) { - UIWidgets::ReEnableComponent(disabledText.c_str()); - } return changed; } bool Option::RenderCombobox() { bool changed = false; - if (disabled) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - ImGui::Text("%s", name.c_str()); uint8_t selected = CVarGetInteger(cvarName.c_str(), defaultOption); if (selected >= options.size()) { selected = options.size(); @@ -264,25 +230,15 @@ bool Option::RenderCombobox() { changed = true; Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - if (!description.empty()) { - UIWidgets::InsertHelpHoverText(description.c_str()); + UIWidgets::ComboboxOptions widgetOptions = UIWidgets::ComboboxOptions().Color(THEME_COLOR).Tooltip(description.c_str()); + if (this->GetKey() == RSK_LOGIC_RULES) { + widgetOptions = widgetOptions.LabelPosition(UIWidgets::LabelPosition::None).ComponentAlignment(UIWidgets::ComponentAlignment::Right); } - const std::string comboName = std::string("##") + std::string(cvarName); - if (ImGui::BeginCombo(comboName.c_str(), options[selected].c_str())) { - for (size_t i = 0; i < options.size(); i++) { - if (!options[i].empty()) { - if (ImGui::Selectable(options[i].c_str(), i == selected)) { - CVarSetInteger(cvarName.c_str(), static_cast(i)); - changed = true; - selected = i; - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - } - } - ImGui::EndCombo(); - } - if (disabled) { - UIWidgets::ReEnableComponent(disabledText.c_str()); + widgetOptions.disabled = disabled; + if(UIWidgets::Combobox(name.c_str(), &selected, options, widgetOptions)) { + CVarSetInteger(cvarName.c_str(), static_cast(selected)); + changed = true; + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } return changed; } @@ -295,40 +251,11 @@ bool Option::RenderSlider() { CVarSetInteger(cvarName.c_str(), val); changed = true; } - if (disabled) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - const std::string formatName = name + ": %s"; - ImGui::Text(formatName.c_str(), options[val].c_str()); - if (!description.empty()) { - UIWidgets::InsertHelpHoverText(description.c_str()); - } - UIWidgets::Spacer(0); - ImGui::BeginGroup(); - const std::string MinusBTNName = " - ##" + cvarName; - if (ImGui::Button(MinusBTNName.c_str())) { - val--; + UIWidgets::IntSliderOptions widgetOptions = UIWidgets::IntSliderOptions().Color(THEME_COLOR).Min(0).Max(options.size() - 1).Tooltip(description.c_str()).Format(options[val].c_str()).DefaultValue(defaultOption); + widgetOptions.disabled = disabled; + if (UIWidgets::SliderInt(name.c_str(), &val, widgetOptions)) { changed = true; } - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - ImGui::PushItemWidth(std::min(ImGui::GetContentRegionAvail().x - 30.0f, 260.0f)); - const std::string id = "##Slider" + cvarName; - if (ImGui::SliderInt(id.c_str(), &val, 0, static_cast(options.size()) - 1, "", ImGuiSliderFlags_AlwaysClamp)) { - changed = true; - } - ImGui::PopItemWidth(); - const std::string PlusBTNName = " + ##" + cvarName; - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - if (ImGui::Button(PlusBTNName.c_str())) { - val++; - changed = true; - } - ImGui::EndGroup(); - if (disabled) { - UIWidgets::ReEnableComponent(disabledText.c_str()); - } if (val < 0) { val = 0; changed = true; @@ -465,7 +392,7 @@ bool OptionGroup::RenderImGui() const { // NOLINT(*-no-recursion) ImGui::TableSetColumnIndex(i); ImGui::TableHeader(mSubGroups[i]->GetName().c_str()); if (!mSubGroups[i]->GetDescription().empty()) { - UIWidgets::SetLastItemHoverText(mSubGroups[i]->GetDescription().c_str()); + UIWidgets::Tooltip(mSubGroups[i]->GetDescription().c_str()); } } ImGui::PopItemFlag(); @@ -473,12 +400,10 @@ bool OptionGroup::RenderImGui() const { // NOLINT(*-no-recursion) } } if (mContainerType == WidgetContainerType::SECTION && !mName.empty()) { - UIWidgets::PaddedSeparator(); - ImGui::Text("%s", mName.c_str()); + ImGui::SeparatorText(mName.c_str()); if (!mDescription.empty()) { - UIWidgets::InsertHelpHoverText(mDescription.c_str()); + UIWidgets::Tooltip(mDescription.c_str()); } - UIWidgets::PaddedSeparator(); } if (mContainerType == WidgetContainerType::COLUMN) { ImGui::TableNextColumn(); @@ -507,9 +432,6 @@ bool OptionGroup::RenderImGui() const { // NOLINT(*-no-recursion) if (option->HasFlag(IMFLAG_UNINDENT)) { ImGui::Unindent(); } - if (option->HasFlag(IMFLAG_SEPARATOR_BOTTOM)) { - UIWidgets::PaddedSeparator(false, true); - } } } if (mContainerType == WidgetContainerType::COLUMN) { diff --git a/soh/soh/Enhancements/randomizer/option.h b/soh/soh/Enhancements/randomizer/option.h index d2291463b..518487219 100644 --- a/soh/soh/Enhancements/randomizer/option.h +++ b/soh/soh/Enhancements/randomizer/option.h @@ -1,6 +1,7 @@ #pragma once -#include "soh/SohGui/UIWidgets.hpp" +#ifndef RANDOPTION_H +#define RANDOPTION_H #include #include @@ -35,7 +36,6 @@ enum class OptionCategory { */ enum class WidgetType { Checkbox, /** Default for Bools, not compatible if options.size() > 2. */ - TristateCheckbox, /** Compatible with U8s, not compatible if options.size() != 3. */ Combobox, /** Default for U8s, works with U8s and Bools. */ Slider, /** Compatible with U8s. If constructed with NumOpts, consider using this. Technically can be used for Bool or non-NumOpts options but it would be a bit weird semantically. */ }; @@ -312,7 +312,7 @@ class Option { * @param graphic What graphic to display in a disabled checkbox. Defaults to an * "X" symbol. */ - void Disable(std::string text, UIWidgets::CheckboxGraphics graphic = UIWidgets::CheckboxGraphics::Cross); + void Disable(std::string text); bool IsCategory(OptionCategory category) const; /** @@ -339,7 +339,6 @@ protected: private: bool RenderCheckbox(); - bool RenderTristateCheckbox(); bool RenderCombobox(); bool RenderSlider(); void PopulateTextToNum(); @@ -357,7 +356,6 @@ protected: bool defaultHidden = false; int imFlags = IMFLAG_NONE; bool disabled = false; - UIWidgets::CheckboxGraphics disabledGraphic = UIWidgets::CheckboxGraphics::Cross; std::string disabledText; std::unordered_map optionsTextToVar = {}; }; @@ -546,4 +544,6 @@ class OptionGroup { std::string mDescription; bool mDisabled = false; }; -} // namespace Rando \ No newline at end of file +} // namespace Rando + +#endif //RANDOPTION_H \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 44831c7c0..85ddc6c50 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -11,9 +11,8 @@ #include "3drando/rando_main.hpp" #include "3drando/random.hpp" #include "soh/ResourceManagerHelpers.h" -#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "3drando/custom_messages.hpp" -#include "soh/SohGui/UIWidgets.hpp" #include #include #include "../custom-message/CustomMessageTypes.h" @@ -27,6 +26,7 @@ #include #include #include "draw.h" +#include "soh/SohGui/UIWidgets.hpp" #include "static_data.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include @@ -1924,6 +1924,16 @@ bool GenerateRandomizer(std::string seed /*= ""*/) { return false; } +static const std::unordered_map randomizerPresetList = { + { RANDOMIZER_PRESET_DEFAULT, "Default" }, + { RANDOMIZER_PRESET_SPOCK_RACE, "Spock Race" }, + { RANDOMIZER_PRESET_SPOCK_RACE_NO_LOGIC, "Spock Race (No Logic)" }, + { RANDOMIZER_PRESET_S6, "S6" }, + { RANDOMIZER_PRESET_HELL_MODE, "Hell Mode" }, + { RANDOMIZER_PRESET_BENCHMARK, "Benchmark" } +}; +static int32_t randomizerPresetSelected = RANDOMIZER_PRESET_DEFAULT; + void RandomizerSettingsWindow::DrawElement() { auto ctx = Rando::Context::GetInstance(); if (generated) { @@ -1931,30 +1941,62 @@ void RandomizerSettingsWindow::DrawElement() { randoThread.join(); } bool disableEditingRandoSettings = CVarGetInteger(CVAR_GENERAL("RandoGenerating"), 0) || CVarGetInteger(CVAR_GENERAL("OnFileSelectNameEntry"), 0); - if (disableEditingRandoSettings) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0) || disableEditingRandoSettings); + const PresetTypeDefinition presetTypeDef = presetTypes.at(PRESET_TYPE_RANDOMIZER); + std::string comboboxTooltip = ""; + for (auto iter = presetTypeDef.presets.begin(); iter != presetTypeDef.presets.end(); ++iter) { + if (iter->first != 0) comboboxTooltip += "\n\n"; + comboboxTooltip += std::string(iter->second.label) + " - " + std::string(iter->second.description); + } + const std::string presetTypeCvar = CVAR_GENERAL("SelectedPresets.") + std::to_string(PRESET_TYPE_RANDOMIZER); + randomizerPresetSelected = CVarGetInteger(presetTypeCvar.c_str(), RANDOMIZER_PRESET_DEFAULT); + + if (UIWidgets::Combobox("Randomizer Presets", &randomizerPresetSelected, randomizerPresetList, UIWidgets::ComboboxOptions() + .DefaultIndex(RANDOMIZER_PRESET_DEFAULT) + .Tooltip(comboboxTooltip.c_str()) + .Color(THEME_COLOR)) + ) { + CVarSetInteger(presetTypeCvar.c_str(), randomizerPresetSelected); + } + ImGui::SameLine(); + ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 35.f); + if (UIWidgets::Button("Apply Preset##Randomizer", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline).Padding(ImVec2(10.f, 6.f)))) { + if (randomizerPresetSelected >= presetTypeDef.presets.size()) { + randomizerPresetSelected = 0; + } + const PresetDefinition selectedPresetDef = presetTypeDef.presets.at(randomizerPresetSelected); + for(const char* block : presetTypeDef.blocksToClear) { + CVarClearBlock(block); + } + if (randomizerPresetSelected != 0) { + applyPreset(selectedPresetDef.entries); + } + CVarSetInteger(presetTypeCvar.c_str(), randomizerPresetSelected); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); - DrawPresetSelector(PRESET_TYPE_RANDOMIZER); - ImGui::EndDisabled(); - UIWidgets::Spacer(0); - UIWidgets::EnhancementCheckbox("Manual seed entry", CVAR_RANDOMIZER_SETTING("ManualSeedEntry"), false, ""); + UIWidgets::CVarCheckbox("Manual seed entry", CVAR_RANDOMIZER_SETTING("ManualSeedEntry"), UIWidgets::CheckboxOptions().Color(THEME_COLOR)); if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ManualSeedEntry"), 0)) { - ImGui::Text("Seed"); + UIWidgets::PushStyleInput(THEME_COLOR); ImGui::InputText("##RandomizerSeed", seedString, MAX_SEED_STRING_SIZE, ImGuiInputTextFlags_CallbackCharFilter, UIWidgets::TextFilters::FilterAlphaNum); UIWidgets::Tooltip( "Characters from a-z, A-Z, and 0-9 are supported.\n" "Character limit is 1023, after which the seed will be truncated.\n" ); - ImGui::SameLine(); - if (ImGui::Button("New Seed")) { + if (strnlen(seedString, MAX_SEED_STRING_SIZE) == 0) { + ImGui::SameLine(17.0f); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "Leave blank for random seed"); + } + UIWidgets::PopStyleInput(); + ImGui::SameLine(0.f, 50.f); + if (UIWidgets::Button(ICON_FA_RANDOM, UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR).Padding(ImVec2(10.f, 6.f)).Tooltip( + "Creates a new random seed value to be used when generating a randomizer" + ))) { SohUtils::CopyStringToCharArray(seedString, std::to_string(rand() & 0xFFFFFFFF), MAX_SEED_STRING_SIZE); } - UIWidgets::Tooltip("Creates a new random seed value to be used when generating a randomizer"); ImGui::SameLine(); - if (ImGui::Button("Clear Seed")) { + if (UIWidgets::Button(ICON_FA_ERASER, UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(THEME_COLOR).Padding(ImVec2(10.f, 6.f)))) { memset(seedString, 0, MAX_SEED_STRING_SIZE); } } @@ -1962,13 +2004,13 @@ void RandomizerSettingsWindow::DrawElement() { UIWidgets::Spacer(0); ImGui::BeginDisabled((CVarGetInteger(CVAR_RANDOMIZER_SETTING("DontGenerateSpoiler"), 0) && gSaveContext.gameMode != GAMEMODE_FILE_SELECT) || GameInteractor::IsSaveLoaded()); - if (ImGui::Button("Generate Randomizer")) { + if (UIWidgets::Button("Generate Randomizer", UIWidgets::ButtonOptions().Size(ImVec2(250.f, 0.f)).Color(THEME_COLOR))) { ctx->SetSpoilerLoaded(false); GenerateRandomizer(CVarGetInteger(CVAR_RANDOMIZER_SETTING("ManualSeedEntry"), 0) ? seedString : ""); } ImGui::EndDisabled(); - UIWidgets::Spacer(0); + ImGui::SameLine(); if (!CVarGetInteger(CVAR_RANDOMIZER_SETTING("DontGenerateSpoiler"), 0)) { std::string spoilerfilepath = CVarGetString(CVAR_GENERAL("SpoilerLog"), ""); ImGui::Text("Spoiler File: %s", spoilerfilepath.c_str()); @@ -1978,13 +2020,13 @@ void RandomizerSettingsWindow::DrawElement() { // std::string presetfilepath = CVarGetString(CVAR_RANDOMIZER_SETTING("LoadedPreset"), ""); // ImGui::Text("Settings File: %s", presetfilepath.c_str()); - UIWidgets::PaddedSeparator(); - + UIWidgets::Separator(true, true, 0.f, 0.f); ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); ImGuiWindow* window = ImGui::GetCurrentWindow(); static ImVec2 cellPadding(8.0f, 8.0f); + UIWidgets::PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("Randomizer Settings", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("World")) { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cellPadding); @@ -2048,7 +2090,9 @@ void RandomizerSettingsWindow::DrawElement() { window->DC.CurrLineTextBaseOffset = 0.0f; static ImGuiTextFilter locationSearch; + UIWidgets::PushStyleInput(THEME_COLOR); locationSearch.Draw(); + UIWidgets::PopStyleInput(); ImGui::BeginChild("ChildIncludedLocations", ImVec2(0, -8)); for (auto& [rcArea, locations] : RandomizerCheckObjects::GetAllRCObjectsByArea()) { @@ -2068,7 +2112,7 @@ void RandomizerSettingsWindow::DrawElement() { for (auto& location : locations) { if (ctx->GetItemLocation(location)->IsVisible() && !excludedLocations.count(location) && locationSearch.PassFilter(Rando::StaticData::GetLocation(location)->GetName().c_str())) { - + UIWidgets::PushStyleButton(THEME_COLOR, ImVec2(7.f, 5.f)); if (ImGui::ArrowButton(std::to_string(location).c_str(), ImGuiDir_Right)) { excludedLocations.insert(location); // todo: this efficently when we build out cvar array support @@ -2080,6 +2124,7 @@ void RandomizerSettingsWindow::DrawElement() { CVarSetString(CVAR_RANDOMIZER_SETTING("ExcludedLocations"), excludedLocationString.c_str()); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } + UIWidgets::PopStyleButton(); ImGui::SameLine(); ImGui::Text("%s", Rando::StaticData::GetLocation(location)->GetShortName().c_str()); } @@ -2110,6 +2155,7 @@ void RandomizerSettingsWindow::DrawElement() { for (auto& location : locations) { auto elfound = excludedLocations.find(location); if (ctx->GetItemLocation(location)->IsVisible() && elfound != excludedLocations.end()) { + UIWidgets::PushStyleButton(THEME_COLOR, ImVec2(7.f, 5.f)); if (ImGui::ArrowButton(std::to_string(location).c_str(), ImGuiDir_Left)) { excludedLocations.erase(elfound); // todo: this efficently when we build out cvar array support @@ -2125,6 +2171,7 @@ void RandomizerSettingsWindow::DrawElement() { } Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } + UIWidgets::PopStyleButton(); ImGui::SameLine(); ImGui::Text("%s", Rando::StaticData::GetLocation(location)->GetShortName().c_str()); } @@ -2272,10 +2319,12 @@ void RandomizerSettingsWindow::DrawElement() { //{ Rando::Tricks::Tag::GLITCH, false }, }; static ImGuiTextFilter trickSearch; + UIWidgets::PushStyleInput(THEME_COLOR); trickSearch.Draw("Filter (inc,-exc)", 490.0f); + UIWidgets::PopStyleInput(); if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("LogicRules"), RO_LOGIC_GLITCHLESS) != RO_LOGIC_NO_LOGIC) { ImGui::SameLine(); - if (ImGui::Button("Disable All")) { + if (UIWidgets::Button("Disable All", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(250.f, 0.f)))) { for (int i = 0; i < RT_MAX; i++) { auto etfound = enabledTricks.find(static_cast(i)); if (etfound != enabledTricks.end()) { @@ -2291,7 +2340,7 @@ void RandomizerSettingsWindow::DrawElement() { Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } ImGui::SameLine(); - if (ImGui::Button("Enable All")) { + if (UIWidgets::Button("Enable All", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(250.f, 0.f)))) { for (int i = 0; i < RT_MAX; i++) { if (!enabledTricks.count(static_cast(i))) { enabledTricks.insert(static_cast(i)); @@ -2334,19 +2383,19 @@ void RandomizerSettingsWindow::DrawElement() { ImGui::TableNextColumn(); window->DC.CurrLineTextBaseOffset = 0.0f; - if (ImGui::Button("Collapse All##disabled")) { + if (UIWidgets::Button("Collapse All##disabled", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.f, 0.f)))) { for (int i = 0; i < RA_MAX; i++) { areaTreeDisabled[static_cast(i)] = false; } } ImGui::SameLine(); - if (ImGui::Button("Open All##disabled")) { + if (UIWidgets::Button("Open All##disabled", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.f, 0.f)))) { for (int i = 0; i < RA_MAX; i++) { areaTreeDisabled[static_cast(i)] = true; } } ImGui::SameLine(); - if (ImGui::Button("Enable Visible")) { + if (UIWidgets::Button("Enable Visible", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.f, 0.f)))) { for (int i = 0; i < RT_MAX; i++) { auto option = mSettings->GetTrickOption(static_cast(i)); if (!enabledTricks.count(static_cast(i)) && @@ -2387,6 +2436,7 @@ void RandomizerSettingsWindow::DrawElement() { !enabledTricks.count(rt) && Rando::Tricks::CheckTags(showTag, option.GetTags())) { ImGui::TreeNodeSetOpen(ImGui::GetID((Rando::Tricks::GetAreaName(option.GetArea()) + "##disabled").c_str()), areaTreeDisabled[option.GetArea()]); ImGui::SetNextItemOpen(true, ImGuiCond_Once); + UIWidgets::PushStyleButton(THEME_COLOR, ImVec2(7.f, 5.f)); if (ImGui::ArrowButton(std::to_string(rt).c_str(), ImGuiDir_Right)) { enabledTricks.insert(rt); std::string enabledTrickString = ""; @@ -2397,10 +2447,11 @@ void RandomizerSettingsWindow::DrawElement() { CVarSetString(CVAR_RANDOMIZER_SETTING("EnabledTricks"), enabledTrickString.c_str()); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - Rando::Tricks::DrawTagChips(option.GetTags()); + UIWidgets::PopStyleButton(); + Rando::Tricks::DrawTagChips(option.GetTags(), option.GetName()); ImGui::SameLine(); ImGui::Text("%s", option.GetName().c_str()); - UIWidgets::InsertHelpHoverText(option.GetDescription().c_str()); + UIWidgets::Tooltip(option.GetDescription().c_str()); } } areaTreeDisabled[area] = true; @@ -2416,19 +2467,19 @@ void RandomizerSettingsWindow::DrawElement() { ImGui::TableNextColumn(); window->DC.CurrLineTextBaseOffset = 0.0f; - if (ImGui::Button("Collapse All##enabled")) { + if (UIWidgets::Button("Collapse All##enabled", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.f, 0.f)))) { for (int i = 0; i < RA_MAX; i++) { areaTreeEnabled[static_cast(i)] = false; } } ImGui::SameLine(); - if (ImGui::Button("Open All##enabled")) { + if (UIWidgets::Button("Open All##enabled", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.f, 0.f)))) { for (int i = 0; i < RA_MAX; i++) { areaTreeEnabled[static_cast(i)] = true; } } ImGui::SameLine(); - if (ImGui::Button("Disable Visible")) { + if (UIWidgets::Button("Disable Visible", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.f, 0.f)))) { for (int i = 0; i < RT_MAX; i++) { auto option = mSettings->GetTrickOption(static_cast(i)); if (enabledTricks.count(static_cast(i)) && @@ -2473,6 +2524,7 @@ void RandomizerSettingsWindow::DrawElement() { enabledTricks.count(rt) && Rando::Tricks::CheckTags(showTag, option.GetTags())) { ImGui::TreeNodeSetOpen(ImGui::GetID((Rando::Tricks::GetAreaName(option.GetArea()) + "##enabled").c_str()), areaTreeEnabled[option.GetArea()]); ImGui::SetNextItemOpen(true, ImGuiCond_Once); + UIWidgets::PushStyleButton(THEME_COLOR, ImVec2(7.f, 5.f)); if (ImGui::ArrowButton(std::to_string(rt).c_str(), ImGuiDir_Left)) { enabledTricks.erase(rt); std::string enabledTrickString = ""; @@ -2487,10 +2539,11 @@ void RandomizerSettingsWindow::DrawElement() { } Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } - Rando::Tricks::DrawTagChips(option.GetTags()); + UIWidgets::PopStyleButton(); + Rando::Tricks::DrawTagChips(option.GetTags(), option.GetName()); ImGui::SameLine(); ImGui::Text("%s", option.GetName().c_str()); - UIWidgets::InsertHelpHoverText(option.GetDescription().c_str()); + UIWidgets::Tooltip(option.GetDescription().c_str()); } } areaTreeEnabled[area] = true; @@ -2532,12 +2585,10 @@ void RandomizerSettingsWindow::DrawElement() { ImGui::EndTabBar(); } + UIWidgets::PopStyleTabs(); ImGui::EndDisabled(); - - if (disableEditingRandoSettings) { - UIWidgets::ReEnableComponent(""); - } + ImGui::EndDisabled(); } void RandomizerSettingsWindow::UpdateElement() { diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index e3b07b672..cc139fbfd 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -7,6 +7,7 @@ #include "soh/SaveManager.h" #include "soh/ResourceManagerHelpers.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "dungeon.h" #include "location_access.h" @@ -844,6 +845,39 @@ void SetAreaSpoiled(RandomizerCheckArea rcArea) { } void CheckTrackerWindow::DrawElement() { + Color_Background = CVarGetColor(CVAR_TRACKER_CHECK("BgColor.Value"), Color_Bg_Default); + Color_Area_Incomplete_Main = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.MainColor.Value"), Color_Main_Default); + Color_Area_Incomplete_Extra = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor.Value"), Color_Area_Incomplete_Extra_Default); + Color_Area_Complete_Main = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.MainColor.Value"), Color_Main_Default); + Color_Area_Complete_Extra = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.ExtraColor.Value"), Color_Area_Complete_Extra_Default); + Color_Unchecked_Main = CVarGetColor(CVAR_TRACKER_CHECK("Unchecked.MainColor.Value"), Color_Main_Default); + Color_Unchecked_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Unchecked.ExtraColor.Value"), Color_Unchecked_Extra_Default); + Color_Skipped_Main = CVarGetColor(CVAR_TRACKER_CHECK("Skipped.MainColor.Value"), Color_Main_Default); + Color_Skipped_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Skipped.ExtraColor.Value"), Color_Skipped_Extra_Default); + Color_Seen_Main = CVarGetColor(CVAR_TRACKER_CHECK("Seen.MainColor.Value"), Color_Main_Default); + Color_Seen_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Seen.ExtraColor.Value"), Color_Seen_Extra_Default); + Color_Hinted_Main = CVarGetColor(CVAR_TRACKER_CHECK("Hinted.MainColor.Value"), Color_Main_Default); + Color_Hinted_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Hinted.ExtraColor.Value"), Color_Hinted_Extra_Default); + Color_Collected_Main = CVarGetColor(CVAR_TRACKER_CHECK("Collected.MainColor.Value"), Color_Main_Default); + Color_Collected_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Collected.ExtraColor.Value"), Color_Collected_Extra_Default); + Color_Scummed_Main = CVarGetColor(CVAR_TRACKER_CHECK("Scummed.MainColor.Value"), Color_Main_Default); + Color_Scummed_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Scummed.ExtraColor.Value"), Color_Scummed_Extra_Default); + Color_Saved_Main = CVarGetColor(CVAR_TRACKER_CHECK("Saved.MainColor.Value"), Color_Main_Default); + Color_Saved_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Saved.ExtraColor.Value"), Color_Saved_Extra_Default); + hideUnchecked = CVarGetInteger(CVAR_TRACKER_CHECK("Unchecked.Hide"), 0); + hideScummed = CVarGetInteger(CVAR_TRACKER_CHECK("Scummed.Hide"), 0); + hideSeen = CVarGetInteger(CVAR_TRACKER_CHECK("Seen.Hide"), 0); + hideSkipped = CVarGetInteger(CVAR_TRACKER_CHECK("Skipped.Hide"), 0); + hideSaved = CVarGetInteger(CVAR_TRACKER_CHECK("Saved.Hide"), 0); + hideCollected = CVarGetInteger(CVAR_TRACKER_CHECK("Collected.Hide"), 0); + showHidden = CVarGetInteger(CVAR_TRACKER_CHECK("ShowHidden"), 0); + mystery = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle"), 0); + showLogicTooltip = CVarGetInteger(CVAR_TRACKER_CHECK("ShowLogic"), 0); + + hideShopUnshuffledChecks = CVarGetInteger(CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"), 1); + alwaysShowGS = CVarGetInteger(CVAR_TRACKER_CHECK("AlwaysShowGSLocs"), 0); + + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); if (CVarGetInteger(CVAR_TRACKER_CHECK("WindowType"), TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) { if (CVarGetInteger(CVAR_TRACKER_CHECK("ShowOnlyPaused"), 0) && (gPlayState == nullptr || gPlayState->pauseCtx.state == 0)) { return; @@ -863,11 +897,11 @@ void CheckTrackerWindow::DrawElement() { } ImGui::SetNextWindowSize(ImVec2(400, 540), ImGuiCond_FirstUseEver); - BeginFloatWindows("Check Tracker", mIsVisible, ImGuiWindowFlags_NoScrollbar); if (!GameInteractor::IsSaveLoaded() || !initialized) { ImGui::Text("Waiting for file load..."); //TODO Language + ImGui::PopFont(); EndFloatWindows(); return; } @@ -886,38 +920,40 @@ void CheckTrackerWindow::DrawElement() { ImVec2 size = ImGui::GetContentRegionMax(); size.y -= headerHeight; if (!ImGui::BeginTable("Check Tracker", 1, 0, size)) { + ImGui::PopFont(); EndFloatWindows(); return; } ImGui::TableNextRow(0, headerHeight); ImGui::TableNextColumn(); - UIWidgets::EnhancementCheckbox( - "Show Hidden Items", CVAR_TRACKER_CHECK("ShowHidden"), false, - "When active, items will show hidden checks by default when updated to this state."); + UIWidgets::CVarCheckbox( + "Show Hidden Items", CVAR_TRACKER_CHECK("ShowHidden"), UIWidgets::CheckboxOptions({{ .tooltip = "When active, items will show hidden checks by default when updated to this state." }}) + .Color(THEME_COLOR)); UIWidgets::PaddedSeparator(); - if (ImGui::Button("Expand All")) { + if (UIWidgets::Button("Expand All", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { optCollapseAll = false; optExpandAll = true; doAreaScroll = true; } ImGui::SameLine(); - if (ImGui::Button("Collapse All")) { + if (UIWidgets::Button("Collapse All", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { optExpandAll = false; optCollapseAll = true; } ImGui::SameLine(); - if (ImGui::Button("Clear")) { + if (UIWidgets::Button("Clear", UIWidgets::ButtonOptions({{ .tooltip = "Clear the search field" }}).Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { checkSearch.Clear(); UpdateFilters(); doAreaScroll = true; } - UIWidgets::Tooltip("Clear the search field"); + UIWidgets::PushStyleCombobox(THEME_COLOR); if (checkSearch.Draw()) { UpdateFilters(); } + UIWidgets::PopStyleCombobox(); - UIWidgets::PaddedSeparator(); + ImGui::Separator(); ImGui::Text("Total Checks: %d / %d", totalChecksGotten, totalChecks); @@ -929,6 +965,7 @@ void CheckTrackerWindow::DrawElement() { size = ImGui::GetContentRegionAvail(); if (!ImGui::BeginTable("CheckTracker##Checks", 1, ImGuiTableFlags_ScrollY, size)) { ImGui::EndTable(); + ImGui::PopFont(); EndFloatWindows(); return; } @@ -946,16 +983,17 @@ void CheckTrackerWindow::DrawElement() { bool doingCollapseOrExpand = optExpandAll || optCollapseAll; bool isThisAreaSpoiled; RandomizerCheckArea lastArea = RCAREA_INVALID; - Color_RGBA8 areaCompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.MainColor"), Color_Main_Default); - Color_RGBA8 areaIncompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.MainColor"), Color_Main_Default); - Color_RGBA8 extraCompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.ExtraColor"), Color_Area_Complete_Extra_Default); - Color_RGBA8 extraIncompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor"), Color_Area_Incomplete_Extra_Default); + Color_RGBA8 areaCompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.MainColor.Value"), Color_Main_Default); + Color_RGBA8 areaIncompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.MainColor.Value"), Color_Main_Default); + Color_RGBA8 extraCompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.ExtraColor.Value"), Color_Area_Complete_Extra_Default); + Color_RGBA8 extraIncompleteColor = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor.Value"), Color_Area_Incomplete_Extra_Default); Color_RGBA8 mainColor; Color_RGBA8 extraColor; std::string stemp; bool shouldHideFilteredAreas = CVarGetInteger(CVAR_TRACKER_CHECK("HideFilteredAreas"), 1); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); for (auto& [rcArea, checks] : checksByArea) { RandomizerCheckArea thisArea = currentArea; @@ -1039,9 +1077,11 @@ void CheckTrackerWindow::DrawElement() { } } } + ImGui::PopStyleVar(); ImGui::EndTable(); //Checks Lead-out ImGui::EndTable(); //Quick Options Lead-out + ImGui::PopFont(); EndFloatWindows(); if (doingCollapseOrExpand) { optCollapseAll = false; @@ -1099,7 +1139,7 @@ void BeginFloatWindows(std::string UniqueName, bool& open, ImGuiWindowFlags flag windowFlags |= ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar; - if (!CVarGetInteger(CVAR_TRACKER_CHECK("Draggable"), 0)) { + if (!CVarGetInteger(CVAR_TRACKER_CHECK("Draggable"), 1)) { windowFlags |= ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove; } } @@ -1468,54 +1508,54 @@ void DrawLocation(RandomizerCheck rc) { } mainColor = !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO - ? Color_Collected_Extra_Default + ? Color_Collected_Extra : Color_Collected_Main; - extraColor = Color_Collected_Extra_Default; + extraColor = Color_Collected_Extra; } else if (status == RCSHOW_SAVED) { if (!showHidden && hideSaved) { return; } mainColor = !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO - ? Color_Saved_Extra_Default + ? Color_Saved_Extra : Color_Saved_Main; - extraColor = Color_Saved_Extra_Default; + extraColor = Color_Saved_Extra; } else if (skipped) { if (!showHidden && hideSkipped) { return; } mainColor = !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO - ? Color_Skipped_Extra_Default + ? Color_Skipped_Extra : Color_Skipped_Main; - extraColor = Color_Skipped_Extra_Default; + extraColor = Color_Skipped_Extra; } else if (status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED) { if (!showHidden && hideSeen) { return; } mainColor = !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO - ? Color_Seen_Extra_Default + ? Color_Seen_Extra : Color_Seen_Main; - extraColor = Color_Seen_Extra_Default; + extraColor = Color_Seen_Extra; } else if (status == RCSHOW_SCUMMED) { if (!showHidden && hideScummed) { return; } mainColor = !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO - ? Color_Scummed_Extra_Default + ? Color_Scummed_Extra : Color_Scummed_Main; - extraColor = Color_Scummed_Extra_Default; + extraColor = Color_Scummed_Extra; } else if (status == RCSHOW_UNCHECKED) { if (!showHidden && hideUnchecked) { return; } mainColor = !IsHeartPiece((GetItemID)Rando::StaticData::RetrieveItem(loc->GetVanillaItem()).GetItemID()) && !IS_RANDO - ? Color_Unchecked_Extra_Default + ? Color_Unchecked_Extra : Color_Unchecked_Main; - extraColor = Color_Unchecked_Extra_Default; + extraColor = Color_Unchecked_Extra; } //Main Text @@ -1530,8 +1570,10 @@ void DrawLocation(RandomizerCheck rc) { } // Draw button - for Skipped/Seen/Scummed/Unchecked only + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {4.0f, 3.0f}); + float sz = ImGui::GetFrameHeight(); if (status == RCSHOW_UNCHECKED || status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED || status == RCSHOW_SCUMMED || skipped) { - if (UIWidgets::StateButton(std::to_string(rc).c_str(), skipped ? ICON_FA_PLUS : ICON_FA_TIMES)) { + if (UIWidgets::StateButton(std::to_string(rc).c_str(), skipped ? ICON_FA_PLUS : ICON_FA_TIMES, ImVec2(sz, sz), UIWidgets::ButtonOptions().Color(THEME_COLOR))) { if (skipped) { OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->SetIsSkipped(false); areaChecksGotten[loc->GetArea()]--; @@ -1546,8 +1588,10 @@ void DrawLocation(RandomizerCheck rc) { SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); } } else { - ImGui::Dummy(ImVec2(20.0f, 10.0f)); + ImGui::Dummy(ImVec2(sz, sz)); } + ImGui::PopStyleVar(); + ImGui::SameLine(); //Draw @@ -1621,7 +1665,7 @@ void DrawLocation(RandomizerCheck rc) { if (locationInRegion.GetLocation() == rc) { std::string conditionStr = locationInRegion.GetConditionStr(); if (conditionStr != "true") { - UIWidgets::InsertHelpHoverText(conditionStr); + UIWidgets::Tooltip(conditionStr.c_str()); } return; } @@ -1645,7 +1689,7 @@ int hue = 0; void RainbowTick() { float freqHue = hue * 2 * M_PI / (360 * CVarGetFloat(CVAR_COSMETIC("RainbowSpeed"), 0.6f)); for (auto& cvar : rainbowCVars) { - if (CVarGetInteger((cvar + "RBM").c_str(), 0) == 0) { + if (CVarGetInteger((cvar + ".Rainbow").c_str(), 0) == 0) { continue; } @@ -1655,7 +1699,7 @@ void RainbowTick() { newColor.b = sin(freqHue + (4 * M_PI / 3)) * 127 + 128; newColor.a = 255; - CVarSetColor(cvar.c_str(), newColor); + CVarSetColor((cvar + ".Value").c_str(), newColor); } hue++; @@ -1664,46 +1708,33 @@ void RainbowTick() { void ImGuiDrawTwoColorPickerSection(const char* text, const char* cvarMainName, const char* cvarExtraName, Color_RGBA8& main_color, Color_RGBA8& extra_color, Color_RGBA8& main_default_color, - Color_RGBA8& extra_default_color, const char* cvarHideName, const char* tooltip) { + Color_RGBA8& extra_default_color, const char* cvarHideName, const char* tooltip, UIWidgets::Colors theme) { Color_RGBA8 cvarMainColor = CVarGetColor(cvarMainName, main_default_color); Color_RGBA8 cvarExtraColor = CVarGetColor(cvarExtraName, extra_default_color); main_color = cvarMainColor; extra_color = cvarExtraColor; + UIWidgets::PushStyleCombobox(theme); if (ImGui::CollapsingHeader(text)) { if (*cvarHideName != '\0') { std::string label = cvarHideName; label += "##Hidden"; ImGui::PushID(label.c_str()); - UIWidgets::EnhancementCheckbox("Hidden", cvarHideName, false, - "When active, checks will hide by default when updated to this state. Can " - "be overriden with the \"Show Hidden Items\" option."); + UIWidgets::CVarCheckbox("Hidden", cvarHideName, + UIWidgets::CheckboxOptions({{ .tooltip = "When active, checks will hide by default when updated to this state. Can " + "be overriden with the \"Show Hidden Items\" option." }}).Color(theme)); ImGui::PopID(); } - if (ImGui::BeginTable(text, 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV | ImGuiTableFlags_Hideable)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - if (UIWidgets::EnhancementColor("Check", cvarMainName, - ImVec4(main_color.r, main_color.g, main_color.b, main_color.a), - ImVec4(main_default_color.r, main_default_color.g, main_default_color.b, main_default_color.a))) - { - main_color = CVarGetColor(cvarMainName, main_default_color); - }; - ImGui::PopItemWidth(); + std::string mainLabel = "Name##" + std::string(cvarMainName); + if (UIWidgets::CVarColorPicker(mainLabel.c_str(), cvarMainName, main_default_color, false, + UIWidgets::ColorPickerRandomButton | UIWidgets::ColorPickerResetButton | UIWidgets::ColorPickerRainbowCheck, theme)) { + main_color = CVarGetColor(cvarMainName, main_default_color); + } - ImGui::TableNextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - if (UIWidgets::EnhancementColor("Details", cvarExtraName, - ImVec4(extra_color.r, extra_color.g, extra_color.b, extra_color.a), - ImVec4(extra_default_color.r, extra_default_color.g, extra_default_color.b, extra_default_color.a))) - { - extra_color = CVarGetColor(cvarExtraName, extra_default_color); - } - ImGui::PopItemWidth(); - - ImGui::EndTable(); + std::string extraLabel = "Details##" + std::string(cvarExtraName); + if (UIWidgets::CVarColorPicker(extraLabel.c_str(), cvarExtraName, extra_default_color, false, + UIWidgets::ColorPickerRandomButton | UIWidgets::ColorPickerResetButton | UIWidgets::ColorPickerRainbowCheck, theme)) { + extra_color = CVarGetColor(cvarExtraName, extra_default_color); } } if (tooltip != NULL && strlen(tooltip) != 0) { @@ -1711,6 +1742,7 @@ void ImGuiDrawTwoColorPickerSection(const char* text, const char* cvarMainName, ImGui::Text(" ?"); UIWidgets::Tooltip(tooltip); } + UIWidgets::PopStyleCombobox(); } void CheckTrackerWindow::Draw() { @@ -1722,73 +1754,84 @@ void CheckTrackerWindow::Draw() { SyncVisibilityConsoleVariable(); } -static const char* windowType[] = { "Floating", "Window" }; -static const char* displayType[] = { "Always", "Combo Button Hold" }; -static const char* buttonStrings[] = { "A Button", "B Button", "C-Up", "C-Down", "C-Left", "C-Right", "L Button", - "Z Button", "R Button", "Start", "D-Up", "D-Down", "D-Left", "D-Right" }; +static std::unordered_map windowType = {{ TRACKER_WINDOW_FLOATING, "Floating" }, { TRACKER_WINDOW_WINDOW, "Window" }}; +static std::unordered_map displayType = {{ 0, "Always" }, { 1, "Combo Button Hold" }}; +static std::unordered_map buttonStrings = { + { TRACKER_COMBO_BUTTON_A, "A Button" }, { TRACKER_COMBO_BUTTON_B, "B Button" }, { TRACKER_COMBO_BUTTON_C_UP, "C-Up" }, + { TRACKER_COMBO_BUTTON_C_DOWN, "C-Down" }, { TRACKER_COMBO_BUTTON_C_LEFT, "C-Left" }, { TRACKER_COMBO_BUTTON_C_RIGHT, "C-Right" }, + { TRACKER_COMBO_BUTTON_L, "L Button" }, { TRACKER_COMBO_BUTTON_Z, "Z Button" }, { TRACKER_COMBO_BUTTON_R, "R Button" }, + { TRACKER_COMBO_BUTTON_START, "Start" }, { TRACKER_COMBO_BUTTON_D_UP, "D-Up" }, { TRACKER_COMBO_BUTTON_D_DOWN, "D-Down" }, + { TRACKER_COMBO_BUTTON_D_LEFT, "D-Left" }, { TRACKER_COMBO_BUTTON_D_RIGHT, "D-Right" }}; void CheckTrackerSettingsWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 8.0f, 8.0f }); - ImGui::BeginTable("CheckTrackerSettingsTable", 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV); - ImGui::TableSetupColumn("General settings", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::TableSetupColumn("Section settings", ImGuiTableColumnFlags_WidthStretch, 200.0f); - ImGui::TableHeadersRow(); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - if (UIWidgets::EnhancementColor("BG Color", CVAR_TRACKER_CHECK("BgColor"), - ImVec4(Color_Background.r, Color_Background.g, Color_Background.b, Color_Background.a), - ImVec4(Color_Bg_Default.r, Color_Bg_Default.g, Color_Bg_Default.b, Color_Bg_Default.a), - false, true)) - { - Color_Background = CVarGetColor(CVAR_TRACKER_CHECK("BgColor"), Color_Bg_Default); - } - ImGui::PopItemWidth(); + if (ImGui::BeginTable("CheckTrackerSettingsTable", 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV)) { + ImGui::TableSetupColumn("General settings", ImGuiTableColumnFlags_WidthStretch, 200.0f); + ImGui::TableSetupColumn("Section settings", ImGuiTableColumnFlags_WidthStretch, 200.0f); + ImGui::TableHeadersRow(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + UIWidgets::CVarColorPicker("BG Color", CVAR_TRACKER_CHECK("BgColor"), Color_Bg_Default, true, + UIWidgets::ColorPickerResetButton | UIWidgets::ColorPickerRandomButton, THEME_COLOR); + ImGui::PopItemWidth(); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Window Type", CVAR_TRACKER_CHECK("WindowType"), windowType, TRACKER_WINDOW_WINDOW); - if (CVarGetInteger(CVAR_TRACKER_CHECK("WindowType"), TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) { - UIWidgets::EnhancementCheckbox("Enable Dragging", CVAR_TRACKER_CHECK("Draggable")); - UIWidgets::EnhancementCheckbox("Only enable while paused", CVAR_TRACKER_CHECK("ShowOnlyPaused")); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Display Mode", CVAR_TRACKER_CHECK("DisplayType"), displayType, 0); - if (CVarGetInteger(CVAR_TRACKER_CHECK("DisplayType"), TRACKER_DISPLAY_ALWAYS) == TRACKER_DISPLAY_COMBO_BUTTON) { - UIWidgets::LabeledRightAlignedEnhancementCombobox("Combo Button 1", CVAR_TRACKER_CHECK("ComboButton1"), buttonStrings, TRACKER_COMBO_BUTTON_L); - UIWidgets::LabeledRightAlignedEnhancementCombobox("Combo Button 2", CVAR_TRACKER_CHECK("ComboButton2"), buttonStrings, TRACKER_COMBO_BUTTON_R); + UIWidgets::CVarCombobox("Window Type", CVAR_TRACKER_CHECK("WindowType"), windowType, + UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPosition::Far).ComponentAlignment(UIWidgets::ComponentAlignment::Right) + .Color(THEME_COLOR).DefaultIndex(TRACKER_WINDOW_WINDOW)); + + if (CVarGetInteger(CVAR_TRACKER_CHECK("WindowType"), TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) { + UIWidgets::CVarCheckbox("Enable Dragging", CVAR_TRACKER_CHECK("Draggable"), UIWidgets::CheckboxOptions().Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Only enable while paused", CVAR_TRACKER_CHECK("ShowOnlyPaused"), UIWidgets::CheckboxOptions().Color(THEME_COLOR)); + UIWidgets::CVarCombobox("Display Mode", CVAR_TRACKER_CHECK("DisplayType"), displayType, + UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPosition::Far).ComponentAlignment(UIWidgets::ComponentAlignment::Right) + .Color(THEME_COLOR).DefaultIndex(0)); + if (CVarGetInteger(CVAR_TRACKER_CHECK("DisplayType"), TRACKER_DISPLAY_ALWAYS) == TRACKER_DISPLAY_COMBO_BUTTON) { + UIWidgets::CVarCombobox("Combo Button 1", CVAR_TRACKER_CHECK("ComboButton1"), buttonStrings, + UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPosition::Far).ComponentAlignment(UIWidgets::ComponentAlignment::Right) + .Color(THEME_COLOR).DefaultIndex(TRACKER_COMBO_BUTTON_L)); + UIWidgets::CVarCombobox("Combo Button 2", CVAR_TRACKER_CHECK("ComboButton2"), buttonStrings, + UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPosition::Far).ComponentAlignment(UIWidgets::ComponentAlignment::Right) + .Color(THEME_COLOR).DefaultIndex(TRACKER_COMBO_BUTTON_L)); + } } + UIWidgets::CVarCheckbox("Vanilla/MQ Dungeon Spoilers", CVAR_TRACKER_CHECK("MQSpoilers"), UIWidgets::CheckboxOptions() + .Tooltip("If enabled, Vanilla/MQ dungeons will show on the tracker immediately. Otherwise, Vanilla/MQ dungeon locations must be unlocked.").Color(THEME_COLOR)); + if (UIWidgets::CVarCheckbox("Hide unshuffled shop item checks", CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"), + UIWidgets::CheckboxOptions().Tooltip("If enabled, will prevent the tracker from displaying slots with non-shop-item shuffles.").Color(THEME_COLOR))) { + hideShopUnshuffledChecks = !hideShopUnshuffledChecks; + UpdateFilters(); + } + if (UIWidgets::CVarCheckbox("Always show gold skulltulas", CVAR_TRACKER_CHECK("AlwaysShowGSLocs"), + UIWidgets::CheckboxOptions().Tooltip("If enabled, will show GS locations in the tracker regardless of tokensanity settings.").Color(THEME_COLOR))) { + alwaysShowGS = !alwaysShowGS; + UpdateFilters(); + } + UIWidgets::CVarCheckbox("Show Logic", CVAR_TRACKER_CHECK("ShowLogic"), + UIWidgets::CheckboxOptions().Tooltip("If enabled, will show a check's logic when hovering over it.").Color(THEME_COLOR)); + + // Filtering settings + UIWidgets::PaddedSeparator(); + UIWidgets::CVarCheckbox("Filter Empty Areas", CVAR_TRACKER_CHECK("HideFilteredAreas"), + UIWidgets::CheckboxOptions().Tooltip("If enabled, will hide area headers that have no locations matching filter").Color(THEME_COLOR).DefaultValue(true)); + + ImGui::TableNextColumn(); + + CheckTracker::ImGuiDrawTwoColorPickerSection("Area Incomplete", CVAR_TRACKER_CHECK("AreaIncomplete.MainColor"), CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor"), Color_Area_Incomplete_Main, Color_Area_Incomplete_Extra, Color_Main_Default, Color_Area_Incomplete_Extra_Default, CVAR_TRACKER_CHECK("AreaIncomplete.Hide"), "", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Area Complete", CVAR_TRACKER_CHECK("AreaComplete.MainColor"), CVAR_TRACKER_CHECK("AreaComplete.ExtraColor"), Color_Area_Complete_Main, Color_Area_Complete_Extra, Color_Main_Default, Color_Area_Complete_Extra_Default, CVAR_TRACKER_CHECK("AreaComplete.Hide"), "", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Unchecked", CVAR_TRACKER_CHECK("Unchecked.MainColor"), CVAR_TRACKER_CHECK("Unchecked.ExtraColor"), Color_Unchecked_Main, Color_Unchecked_Extra, Color_Main_Default, Color_Unchecked_Extra_Default, CVAR_TRACKER_CHECK("Unchecked.Hide"), "Checks you have not interacted with at all.", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Skipped", CVAR_TRACKER_CHECK("Skipped.MainColor"), CVAR_TRACKER_CHECK("Skipped.ExtraColor"), Color_Skipped_Main, Color_Skipped_Extra, Color_Main_Default, Color_Skipped_Extra_Default, CVAR_TRACKER_CHECK("Skipped.Hide"), "", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Seen", CVAR_TRACKER_CHECK("Seen.MainColor"), CVAR_TRACKER_CHECK("Seen.ExtraColor"), Color_Seen_Main, Color_Seen_Extra, Color_Main_Default, Color_Seen_Extra_Default, CVAR_TRACKER_CHECK("Seen.Hide"), "Used for shops. Shows item names for shop slots when walking in, and prices when highlighting them in buy mode.", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Scummed", CVAR_TRACKER_CHECK("Scummed.MainColor"), CVAR_TRACKER_CHECK("Scummed.ExtraColor"), Color_Scummed_Main, Color_Scummed_Extra, Color_Main_Default, Color_Scummed_Extra_Default, CVAR_TRACKER_CHECK("Scummed.Hide"), "Checks you collect, but then reload before saving so you no longer have them.", THEME_COLOR); + //CheckTracker::ImGuiDrawTwoColorPickerSection("Hinted (WIP)", CVAR_TRACKER_CHECK("Hinted.MainColor"), CVAR_TRACKER_CHECK("Hinted.ExtraColor"), Color_Hinted_Main, Color_Hinted_Extra, Color_Main_Default, Color_Hinted_Extra_Default, CVAR_TRACKER_CHECK("Hinted.Hide"), "", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Collected", CVAR_TRACKER_CHECK("Collected.MainColor"), CVAR_TRACKER_CHECK("Collected.ExtraColor"), Color_Collected_Main, Color_Collected_Extra, Color_Main_Default, Color_Collected_Extra_Default, CVAR_TRACKER_CHECK("Collected.Hide"), "Checks you have collected without saving or reloading yet.", THEME_COLOR); + CheckTracker::ImGuiDrawTwoColorPickerSection("Saved", CVAR_TRACKER_CHECK("Saved.MainColor"), CVAR_TRACKER_CHECK("Saved.ExtraColor"), Color_Saved_Main, Color_Saved_Extra, Color_Main_Default, Color_Saved_Extra_Default, CVAR_TRACKER_CHECK("Saved.Hide"), "Checks that you saved the game while having collected.", THEME_COLOR); + + ImGui::PopStyleVar(1); } - UIWidgets::EnhancementCheckbox("Vanilla/MQ Dungeon Spoilers", CVAR_TRACKER_CHECK("MQSpoilers")); - UIWidgets::Tooltip("If enabled, Vanilla/MQ dungeons will show on the tracker immediately. Otherwise, Vanilla/MQ dungeon locations must be unlocked."); - if (UIWidgets::EnhancementCheckbox("Hide unshuffled shop item checks", CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"), false, "", UIWidgets::CheckboxGraphics::Cross, false)) { - hideShopUnshuffledChecks = !hideShopUnshuffledChecks; - UpdateFilters(); - } - UIWidgets::Tooltip("If enabled, will prevent the tracker from displaying slots with non-shop-item shuffles."); - if (UIWidgets::EnhancementCheckbox("Always show gold skulltulas", CVAR_TRACKER_CHECK("AlwaysShowGSLocs"), false, "")) { - alwaysShowGS = !alwaysShowGS; - UpdateFilters(); - } - UIWidgets::Tooltip("If enabled, will show GS locations in the tracker regardless of tokensanity settings."); - UIWidgets::EnhancementCheckbox("Show Logic", "gCheckTrackerOptionShowLogic"); - UIWidgets::Tooltip("If enabled, will show a check's logic when hovering over it."); - - // Filtering settings - UIWidgets::PaddedSeparator(); - UIWidgets::EnhancementCheckbox("Filter Empty Areas", CVAR_TRACKER_CHECK("HideFilteredAreas"), false, "", UIWidgets::CheckboxGraphics::Checkmark, true); - UIWidgets::Tooltip("If enabled, will hide area headers that have no locations matching filter"); - - ImGui::TableNextColumn(); - - CheckTracker::ImGuiDrawTwoColorPickerSection("Area Incomplete", CVAR_TRACKER_CHECK("AreaIncomplete.MainColor"), CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor"), Color_Area_Incomplete_Main, Color_Area_Incomplete_Extra, Color_Main_Default, Color_Area_Incomplete_Extra_Default, CVAR_TRACKER_CHECK("AreaIncomplete.Hide"), ""); - CheckTracker::ImGuiDrawTwoColorPickerSection("Area Complete", CVAR_TRACKER_CHECK("AreaComplete.MainColor"), CVAR_TRACKER_CHECK("AreaComplete.ExtraColor"), Color_Area_Complete_Main, Color_Area_Complete_Extra, Color_Main_Default, Color_Area_Complete_Extra_Default, CVAR_TRACKER_CHECK("AreaComplete.Hide"), ""); - CheckTracker::ImGuiDrawTwoColorPickerSection("Unchecked", CVAR_TRACKER_CHECK("Unchecked.MainColor"), CVAR_TRACKER_CHECK("Unchecked.ExtraColor"), Color_Unchecked_Main, Color_Unchecked_Extra, Color_Main_Default, Color_Unchecked_Extra_Default, CVAR_TRACKER_CHECK("Unchecked.Hide"), "Checks you have not interacted with at all."); - CheckTracker::ImGuiDrawTwoColorPickerSection("Skipped", CVAR_TRACKER_CHECK("Skipped.MainColor"), CVAR_TRACKER_CHECK("Skipped.ExtraColor"), Color_Skipped_Main, Color_Skipped_Extra, Color_Main_Default, Color_Skipped_Extra_Default, CVAR_TRACKER_CHECK("Skipped.Hide"), ""); - CheckTracker::ImGuiDrawTwoColorPickerSection("Seen", CVAR_TRACKER_CHECK("Seen.MainColor"), CVAR_TRACKER_CHECK("Seen.ExtraColor"), Color_Seen_Main, Color_Seen_Extra, Color_Main_Default, Color_Seen_Extra_Default, CVAR_TRACKER_CHECK("Seen.Hide"), "Used for shops. Shows item names for shop slots when walking in, and prices when highlighting them in buy mode."); - CheckTracker::ImGuiDrawTwoColorPickerSection("Scummed", CVAR_TRACKER_CHECK("Scummed.MainColor"), CVAR_TRACKER_CHECK("Scummed.ExtraColor"), Color_Scummed_Main, Color_Scummed_Extra, Color_Main_Default, Color_Scummed_Extra_Default, CVAR_TRACKER_CHECK("Scummed.Hide"), "Checks you collect, but then reload before saving so you no longer have them."); - //CheckTracker::ImGuiDrawTwoColorPickerSection("Hinted (WIP)", CVAR_TRACKER_CHECK("Hinted.MainColor"), CVAR_TRACKER_CHECK("Hinted.ExtraColor"), Color_Hinted_Main, Color_Hinted_Extra, Color_Main_Default, Color_Hinted_Extra_Default, CVAR_TRACKER_CHECK("Hinted.Hide"), ""); - CheckTracker::ImGuiDrawTwoColorPickerSection("Collected", CVAR_TRACKER_CHECK("Collected.MainColor"), CVAR_TRACKER_CHECK("Collected.ExtraColor"), Color_Collected_Main, Color_Collected_Extra, Color_Main_Default, Color_Collected_Extra_Default, CVAR_TRACKER_CHECK("Collected.Hide"), "Checks you have collected without saving or reloading yet."); - CheckTracker::ImGuiDrawTwoColorPickerSection("Saved", CVAR_TRACKER_CHECK("Saved.MainColor"), CVAR_TRACKER_CHECK("Saved.ExtraColor"), Color_Saved_Main, Color_Saved_Extra, Color_Main_Default, Color_Saved_Extra_Default, CVAR_TRACKER_CHECK("Saved.Hide"), "Checks that you saved the game while having collected."); - - ImGui::PopStyleVar(1); ImGui::EndTable(); + ImGui::PopFont(); } void CheckTrackerWindow::InitElement() { @@ -1807,36 +1850,5 @@ void CheckTrackerWindow::InitElement() { } void CheckTrackerWindow::UpdateElement() { - Color_Background = CVarGetColor(CVAR_TRACKER_CHECK("BgColor"), Color_Bg_Default); - Color_Area_Incomplete_Main = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.MainColor"), Color_Main_Default); - Color_Area_Incomplete_Extra = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.ExtraColor"), Color_Area_Incomplete_Extra_Default); - Color_Area_Complete_Main = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.MainColor"), Color_Main_Default); - Color_Area_Complete_Extra = CVarGetColor(CVAR_TRACKER_CHECK("AreaComplete.ExtraColor"), Color_Area_Complete_Extra_Default); - Color_Unchecked_Main = CVarGetColor(CVAR_TRACKER_CHECK("Unchecked.MainColor"), Color_Main_Default); - Color_Unchecked_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Unchecked.ExtraColor"), Color_Unchecked_Extra_Default); - Color_Skipped_Main = CVarGetColor(CVAR_TRACKER_CHECK("Skipped.MainColor"), Color_Main_Default); - Color_Skipped_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Skipped.ExtraColor"), Color_Skipped_Extra_Default); - Color_Seen_Main = CVarGetColor(CVAR_TRACKER_CHECK("Seen.MainColor"), Color_Main_Default); - Color_Seen_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Seen.ExtraColor"), Color_Seen_Extra_Default); - Color_Hinted_Main = CVarGetColor(CVAR_TRACKER_CHECK("Hinted.MainColor"), Color_Main_Default); - Color_Hinted_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Hinted.ExtraColor"), Color_Hinted_Extra_Default); - Color_Collected_Main = CVarGetColor(CVAR_TRACKER_CHECK("Collected.MainColor"), Color_Main_Default); - Color_Collected_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Collected.ExtraColor"), Color_Collected_Extra_Default); - Color_Scummed_Main = CVarGetColor(CVAR_TRACKER_CHECK("Scummed.MainColor"), Color_Main_Default); - Color_Scummed_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Scummed.ExtraColor"), Color_Scummed_Extra_Default); - Color_Saved_Main = CVarGetColor(CVAR_TRACKER_CHECK("Saved.MainColor"), Color_Main_Default); - Color_Saved_Extra = CVarGetColor(CVAR_TRACKER_CHECK("Saved.ExtraColor"), Color_Saved_Extra_Default); - hideUnchecked = CVarGetInteger(CVAR_TRACKER_CHECK("Unchecked.Hide"), 0); - hideScummed = CVarGetInteger(CVAR_TRACKER_CHECK("Scummed.Hide"), 0); - hideSeen = CVarGetInteger(CVAR_TRACKER_CHECK("Seen.Hide"), 0); - hideSkipped = CVarGetInteger(CVAR_TRACKER_CHECK("Skipped.Hide"), 0); - hideSaved = CVarGetInteger(CVAR_TRACKER_CHECK("Saved.Hide"), 0); - hideCollected = CVarGetInteger(CVAR_TRACKER_CHECK("Collected.Hide"), 0); - showHidden = CVarGetInteger(CVAR_TRACKER_CHECK("ShowHidden"), 0); - mystery = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle"), 0); - showLogicTooltip = CVarGetInteger("gCheckTrackerOptionShowLogic", 0); - - hideShopUnshuffledChecks = CVarGetInteger(CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"), 1); - alwaysShowGS = CVarGetInteger(CVAR_TRACKER_CHECK("AlwaysShowGSLocs"), 0); } } // namespace CheckTracker diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h index 851910489..c5bcaa074 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h @@ -2,6 +2,7 @@ #include #include "randomizerTypes.h" #include "randomizer_check_objects.h" +#include "soh/SohGui/UIWidgets.hpp" #include diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp index 4c3cb971e..7a151a983 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp @@ -1,7 +1,7 @@ #include "randomizer_entrance_tracker.h" #include "soh/OTRGlobals.h" #include "soh/cvar_prefixes.h" -#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include #include @@ -659,77 +659,70 @@ void InitEntranceTrackingData() { } void EntranceTrackerSettingsWindow::DrawElement() { - if (ImGui::BeginTable("entranceTrackerSettings", 1, ImGuiTableFlags_BordersInnerH)) { + + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); + ImGui::TextWrapped("The entrance tracker will only track shuffled entrances"); + UIWidgets::Spacer(0); + + ImGui::TableNextColumn(); + + if (ImGui::BeginTable("entranceTrackerSubSettings", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("column 1", ImGuiTableColumnFlags_WidthStretch, 150.0f); + ImGui::TableSetupColumn("column 2", ImGuiTableColumnFlags_WidthStretch, 150.0f); ImGui::TableNextColumn(); - UIWidgets::Spacer(0); - ImGui::TextWrapped("The entrance tracker will only track shuffled entrances"); - UIWidgets::Spacer(0); + ImGui::Text("Sort By"); + UIWidgets::CVarRadioButton("To", CVAR_TRACKER_ENTRANCE("SortBy"), 0, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR) + .Tooltip("Sort entrances by the original source entrance")); + UIWidgets::CVarRadioButton("From", CVAR_TRACKER_ENTRANCE("SortBy"), 1, + UIWidgets::RadioButtonsOptions() + .Color(THEME_COLOR).Tooltip("Sort entrances by the overrided destination")); + + ImGui::Text("List Items"); + UIWidgets::CVarCheckbox("Auto scroll", CVAR_TRACKER_ENTRANCE("AutoScroll"), + UIWidgets::CheckboxOptions().Tooltip("Automatically scroll to the first aviable entrance in the current scene").Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Highlight previous", CVAR_TRACKER_ENTRANCE("HighlightPrevious"), + UIWidgets::CheckboxOptions().Tooltip("Highlight the previous entrance that Link came from").Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Highlight available", CVAR_TRACKER_ENTRANCE("HighlightAvailable"), + UIWidgets::CheckboxOptions().Tooltip("Highlight available entrances in the current scene").Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Hide undiscovered", CVAR_TRACKER_ENTRANCE("CollapseUndiscovered"), + UIWidgets::CheckboxOptions().Tooltip("Collapse undiscovered entrances towards the bottom of each group").Color(THEME_COLOR)); + bool disableHideReverseEntrances = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_DECOUPLED_ENTRANCES) == RO_GENERIC_ON; + static const char* disableHideReverseEntrancesText = "This option is disabled because \"Decouple Entrances\" is enabled."; + UIWidgets::CVarCheckbox("Hide reverse", CVAR_TRACKER_ENTRANCE("HideReverseEntrances"), + UIWidgets::CheckboxOptions({ {.disabled = disableHideReverseEntrances, .disabledTooltip = disableHideReverseEntrancesText }}) + .Tooltip("Hide reverse entrance transitions when Decouple Entrances is off").DefaultValue(true).Color(THEME_COLOR)); ImGui::TableNextColumn(); - if (ImGui::BeginTable("entranceTrackerSubSettings", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingStretchProp)) { + ImGui::Text("Group By"); + UIWidgets::CVarRadioButton( + "Area", CVAR_TRACKER_ENTRANCE("GroupBy"), 0, + UIWidgets::RadioButtonsOptions().Color(THEME_COLOR).Tooltip("Group entrances by their area")); + UIWidgets::CVarRadioButton( + "Type", CVAR_TRACKER_ENTRANCE("GroupBy"), 1, + UIWidgets::RadioButtonsOptions().Color(THEME_COLOR).Tooltip("Group entrances by their entrance type")); - ImGui::TableNextColumn(); - - ImGui::Text("Sort By"); - UIWidgets::EnhancementRadioButton("To", CVAR_TRACKER_ENTRANCE("SortBy"), 0); - UIWidgets::Tooltip("Sort entrances by the original source entrance"); - UIWidgets::EnhancementRadioButton("From", CVAR_TRACKER_ENTRANCE("SortBy"), 1); - UIWidgets::Tooltip("Sort entrances by the overrided destination"); - - UIWidgets::Spacer(2.0f); - - ImGui::Text("List Items"); - UIWidgets::PaddedEnhancementCheckbox("Auto scroll", CVAR_TRACKER_ENTRANCE("AutoScroll"), true, false); - UIWidgets::Tooltip("Automatically scroll to the first aviable entrance in the current scene"); - UIWidgets::PaddedEnhancementCheckbox("Highlight previous", CVAR_TRACKER_ENTRANCE("HighlightPrevious"), true, false); - UIWidgets::Tooltip("Highlight the previous entrance that Link came from"); - UIWidgets::PaddedEnhancementCheckbox("Highlight available", CVAR_TRACKER_ENTRANCE("HighlightAvailable"), true, false); - UIWidgets::Tooltip("Highlight available entrances in the current scene"); - UIWidgets::PaddedEnhancementCheckbox("Hide undiscovered", CVAR_TRACKER_ENTRANCE("CollapseUndiscovered"), true, false); - UIWidgets::Tooltip("Collapse undiscovered entrances towards the bottom of each group"); - bool disableHideReverseEntrances = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_DECOUPLED_ENTRANCES) == RO_GENERIC_ON; - static const char* disableHideReverseEntrancesText = "This option is disabled because \"Decouple Entrances\" is enabled."; - UIWidgets::PaddedEnhancementCheckbox("Hide reverse", CVAR_TRACKER_ENTRANCE("HideReverseEntrances"), true, false, - disableHideReverseEntrances, disableHideReverseEntrancesText, UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Hide reverse entrance transitions when Decouple Entrances is off"); - UIWidgets::Spacer(0); - - ImGui::TableNextColumn(); - - ImGui::Text("Group By"); - UIWidgets::EnhancementRadioButton("Area", CVAR_TRACKER_ENTRANCE("GroupBy"), 0); - UIWidgets::Tooltip("Group entrances by their area"); - UIWidgets::EnhancementRadioButton("Type", CVAR_TRACKER_ENTRANCE("GroupBy"), 1); - UIWidgets::Tooltip("Group entrances by their entrance type"); - - UIWidgets::Spacer(2.0f); - - ImGui::Text("Spoiler Reveal"); - UIWidgets::PaddedEnhancementCheckbox("Show Source", CVAR_TRACKER_ENTRANCE("ShowFrom"), true, false); - UIWidgets::Tooltip("Reveal the sourcefor undiscovered entrances"); - UIWidgets::PaddedEnhancementCheckbox("Show Destination", CVAR_TRACKER_ENTRANCE("ShowTo"), true, false); - UIWidgets::Tooltip("Reveal the destination for undiscovered entrances"); - - ImGui::EndTable(); - } - - ImGui::TableNextColumn(); - - ImGui::SetNextItemOpen(false, ImGuiCond_Once); - if (ImGui::TreeNode("Legend")) { - ImGui::TextColored(ImColor(COLOR_ORANGE), "Last Entrance"); - ImGui::TextColored(ImColor(COLOR_GREEN), "Available Entrances"); - ImGui::TextColored(ImColor(COLOR_GRAY), "Undiscovered Entrances"); - ImGui::TreePop(); - } - - UIWidgets::Spacer(0); + ImGui::Text("Spoiler Reveal"); + UIWidgets::CVarCheckbox("Show Source", CVAR_TRACKER_ENTRANCE("ShowFrom"), + UIWidgets::CheckboxOptions().Tooltip("Reveal the sourcefor undiscovered entrances").Color(THEME_COLOR)); + UIWidgets::CVarCheckbox("Show Destination", CVAR_TRACKER_ENTRANCE("ShowTo"), + UIWidgets::CheckboxOptions().Tooltip("Reveal the destination for undiscovered entrances").Color(THEME_COLOR)); ImGui::EndTable(); } + + ImGui::SetNextItemOpen(false, ImGuiCond_Once); + if (ImGui::TreeNode("Legend")) { + ImGui::TextColored(ImColor(COLOR_ORANGE), "Last Entrance"); + ImGui::TextColored(ImColor(COLOR_GREEN), "Available Entrances"); + ImGui::TextColored(ImColor(COLOR_GRAY), "Undiscovered Entrances"); + ImGui::TreePop(); + } + ImGui::PopFont(); } void EntranceTrackerWindow::Draw() { @@ -742,6 +735,7 @@ void EntranceTrackerWindow::Draw() { } void EntranceTrackerWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::SetNextWindowSize(ImVec2(600, 375), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Entrance Tracker", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { @@ -752,24 +746,26 @@ void EntranceTrackerWindow::DrawElement() { static ImGuiTextFilter locationSearch; uint8_t nextTreeState = 0; - if (ImGui::Button("Collapse All")) { + if (UIWidgets::Button("Collapse All", UIWidgets::ButtonOptions({{ .tooltip = "Collapse all entrance groups" }}) + .Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { nextTreeState = 1; } - UIWidgets::Tooltip("Collapse all entrance groups"); ImGui::SameLine(); - if (ImGui::Button("Expand All")) { + if (UIWidgets::Button("Expand All", UIWidgets::ButtonOptions({{ .tooltip = "Expand all entrance groups" }}) + .Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { nextTreeState = 2; } - UIWidgets::Tooltip("Expand all entrance groups"); ImGui::SameLine(); - if (ImGui::Button("Clear")) { + if (UIWidgets::Button("Clear", UIWidgets::ButtonOptions({{ .tooltip = "Clear the search field" }}) + .Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { locationSearch.Clear(); } - UIWidgets::Tooltip("Clear the search field"); + UIWidgets::PushStyleCombobox(THEME_COLOR); if (locationSearch.Draw()) { nextTreeState = 2; } + UIWidgets::PopStyleCombobox(); uint8_t destToggle = CVarGetInteger(CVAR_TRACKER_ENTRANCE("SortBy"), 0); uint8_t groupToggle = CVarGetInteger(CVAR_TRACKER_ENTRANCE("GroupBy"), 0); @@ -940,8 +936,8 @@ void EntranceTrackerWindow::DrawElement() { } } ImGui::EndChild(); - ImGui::End(); + ImGui::PopFont(); } void EntranceTrackerWindow::InitElement() { diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h index ef217b7f0..0e1271e57 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h @@ -100,8 +100,8 @@ class EntranceTrackerSettingsWindow : public Ship::GuiWindow { class EntranceTrackerWindow : public Ship::GuiWindow { public: using GuiWindow::GuiWindow; - void Draw() override; + void InitElement() override; void DrawElement() override; void UpdateElement() override {}; diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index b2343b000..828fcda2d 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -5,6 +5,7 @@ #include "soh/SaveManager.h" #include "soh/ResourceManagerHelpers.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" #include "randomizerTypes.h" #include @@ -35,6 +36,8 @@ void DrawSong(ItemTrackerItem item); int itemTrackerSectionId; +using namespace UIWidgets; + bool shouldUpdateVectors = true; std::vector mainWindowItems = {}; @@ -616,7 +619,7 @@ void DrawEquip(ItemTrackerItem item) { ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasEquip && IsValidSaveFile() ? item.name : item.nameFaded), ImVec2(iconSize, iconSize), ImVec2(0, 0), ImVec2(1, 1)); - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(item.id)); + Tooltip(SohUtils::GetItemName(item.id).c_str()); } void DrawQuest(ItemTrackerItem item) { @@ -632,7 +635,7 @@ void DrawQuest(ItemTrackerItem item) { ImGui::EndGroup(); - UIWidgets::SetLastItemHoverText(SohUtils::GetQuestItemName(item.id)); + Tooltip(SohUtils::GetQuestItemName(item.id).c_str()); }; void DrawItem(ItemTrackerItem item) { @@ -800,7 +803,7 @@ void DrawItem(ItemTrackerItem item) { itemName = SohUtils::GetItemName(item.id); } - UIWidgets::SetLastItemHoverText(itemName); + Tooltip(itemName.c_str()); } void DrawBottle(ItemTrackerItem item) { @@ -815,7 +818,7 @@ void DrawBottle(ItemTrackerItem item) { ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasItem && IsValidSaveFile() ? item.name : item.nameFaded), ImVec2(iconSize, iconSize), ImVec2(0, 0), ImVec2(1, 1)); - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(item.id)); + Tooltip(SohUtils::GetItemName(item.id).c_str()); }; void DrawDungeonItem(ItemTrackerItem item) { @@ -864,7 +867,7 @@ void DrawDungeonItem(ItemTrackerItem item) { } ImGui::EndGroup(); - UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(item.id)); + Tooltip(SohUtils::GetItemName(item.id).c_str()); } void DrawSong(ItemTrackerItem item) { @@ -874,7 +877,7 @@ void DrawSong(ItemTrackerItem item) { ImGui::SetCursorScreenPos(ImVec2(p.x + 6, p.y)); ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasSong && IsValidSaveFile() ? item.name : item.nameFaded), ImVec2(iconSize / 1.5, iconSize), ImVec2(0, 0), ImVec2(1, 1)); - UIWidgets::SetLastItemHoverText(SohUtils::GetQuestItemName(item.id)); + Tooltip(SohUtils::GetQuestItemName(item.id).c_str()); } void DrawNotes(bool resizeable = false) { @@ -931,7 +934,6 @@ void DrawTotalChecks() { } // Windowing stuff -ImVec4 ChromaKeyBackground = { 0, 0, 0, 0 }; // Float value, 1 = 255 in rgb value. void BeginFloatingWindows(std::string UniqueName, ImGuiWindowFlags flags = 0) { ImGuiWindowFlags windowFlags = flags; @@ -947,7 +949,7 @@ void BeginFloatingWindows(std::string UniqueName, ImGuiWindowFlags flags = 0) { windowFlags |= ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove; } } - ImGui::PushStyleColor(ImGuiCol_WindowBg, ChromaKeyBackground); + ImGui::PushStyleColor(ImGuiCol_WindowBg, VecFromRGBA8(CVarGetColor(CVAR_TRACKER_ITEM("BgColor.Value"), {0, 0, 0, 0}))); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); ImGui::Begin(UniqueName.c_str(), nullptr, windowFlags); @@ -1220,6 +1222,7 @@ void ItemTrackerWindow::Draw() { } void ItemTrackerWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); UpdateVectors(); int iconSize = CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36); @@ -1350,19 +1353,29 @@ void ItemTrackerWindow::DrawElement() { EndFloatingWindows(); } } + ImGui::PopFont(); } -static const char* itemTrackerCapacityTrackOptions[5] = { "No Numbers", "Current Capacity", "Current Ammo", "Current Capacity / Max Capacity", "Current Ammo / Current Capacity" }; -static const char* itemTrackerKeyTrackOptions[3] = { "Collected / Max", "Current / Collected / Max", "Current / Max" }; -static const char* itemTrackerTriforcePieceTrackOptions[2] = { "Collected / Required", "Collected / Required / Max" }; -static const char* windowTypes[2] = { "Floating", "Window" }; -static const char* displayModes[2] = { "Always", "Combo Button Hold" }; -static const char* buttons[14] = { "A", "B", "C-Up", "C-Down", "C-Left", "C-Right", "L", "Z", "R", "Start", "D-Up", "D-Down", "D-Left", "D-Right" }; -static const char* displayTypes[3] = { "Hidden", "Main Window", "Separate" }; -static const char* extendedDisplayTypes[4] = { "Hidden", "Main Window", "Misc Window", "Separate" }; -static const char* minimalDisplayTypes[2] = { "Hidden", "Separate" }; +static std::unordered_map itemTrackerCapacityTrackOptions = { + { ITEM_TRACKER_NUMBER_NONE, "No Numbers" }, { ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY, "Current Capacity" }, { ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY, "Current Ammo" }, + { ITEM_TRACKER_NUMBER_CAPACITY, "Current Capacity / Max Capacity" }, { ITEM_TRACKER_NUMBER_AMMO, "Current Ammo / Current Capacity" }}; +static std::unordered_map itemTrackerKeyTrackOptions = { + { KEYS_COLLECTED_MAX, "Collected / Max" }, { KEYS_CURRENT_COLLECTED_MAX, "Current / Collected / Max" }, { KEYS_CURRENT_MAX, "Current / Max" }}; +static std::unordered_map itemTrackerTriforcePieceTrackOptions = { + { TRIFORCE_PIECE_COLLECTED_REQUIRED, "Collected / Required" }, { TRIFORCE_PIECE_COLLECTED_REQUIRED_MAX, "Collected / Required / Max" }}; +static std::unordered_map windowTypes = {{ TRACKER_WINDOW_FLOATING, "Floating" }, { TRACKER_WINDOW_WINDOW, "Window" }}; +static std::unordered_map displayModes = {{ TRACKER_DISPLAY_ALWAYS, "Always" }, { TRACKER_DISPLAY_COMBO_BUTTON, "Combo Button Hold" }}; +static std::unordered_map buttons = {{ TRACKER_COMBO_BUTTON_A, "A" }, { TRACKER_COMBO_BUTTON_B, "B"}, { TRACKER_COMBO_BUTTON_C_UP, "C-Up"}, + { TRACKER_COMBO_BUTTON_C_DOWN, "C-Down" }, { TRACKER_COMBO_BUTTON_C_LEFT, "C-Left" }, { TRACKER_COMBO_BUTTON_C_RIGHT, "C-Right" }, { TRACKER_COMBO_BUTTON_L, "L" }, + { TRACKER_COMBO_BUTTON_Z, "Z" }, { TRACKER_COMBO_BUTTON_R, "R" }, { TRACKER_COMBO_BUTTON_START, "Start" }, { TRACKER_COMBO_BUTTON_D_UP, "D-Up" }, + { TRACKER_COMBO_BUTTON_D_DOWN, "D-Down" }, { TRACKER_COMBO_BUTTON_D_LEFT, "D-Left" }, { TRACKER_COMBO_BUTTON_D_RIGHT, "D-Right" }}; +static std::unordered_map displayTypes = {{ SECTION_DISPLAY_HIDDEN, "Hidden" }, { SECTION_DISPLAY_MAIN_WINDOW, "Main Window" }, { SECTION_DISPLAY_SEPARATE, "Separate" }}; +static std::unordered_map extendedDisplayTypes = {{ SECTION_DISPLAY_EXTENDED_HIDDEN, "Hidden" }, + { SECTION_DISPLAY_EXTENDED_MAIN_WINDOW, "Main Window" }, { SECTION_DISPLAY_EXTENDED_MISC_WINDOW, "Misc Window" }, { SECTION_DISPLAY_EXTENDED_SEPARATE, "Separate" }}; +static std::unordered_map minimalDisplayTypes = {{ SECTION_DISPLAY_MINIMAL_HIDDEN, "Hidden" }, { SECTION_DISPLAY_MINIMAL_SEPARATE, "Separate" }}; void ItemTrackerSettingsWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 8.0f, 8.0f }); ImGui::BeginTable("itemTrackerSettingsTable", 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV); ImGui::TableSetupColumn("General settings", ImGuiTableColumnFlags_WidthStretch, 200.0f); @@ -1370,154 +1383,168 @@ void ItemTrackerSettingsWindow::DrawElement() { ImGui::TableHeadersRow(); ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Text("BG Color"); - ImGui::SameLine(); ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::ColorEdit4("BG Color##gItemTrackerBgColor", (float*)&ChromaKeyBackground, ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_NoLabel)) { - CVarSetFloat(CVAR_TRACKER_ITEM("BgColorR"), ChromaKeyBackground.x); - CVarSetFloat(CVAR_TRACKER_ITEM("BgColorG"), ChromaKeyBackground.y); - CVarSetFloat(CVAR_TRACKER_ITEM("BgColorB"), ChromaKeyBackground.z); - CVarSetFloat(CVAR_TRACKER_ITEM("BgColorA"), ChromaKeyBackground.w); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - ImGui::PopItemWidth(); + CVarColorPicker("Background Color##gItemTrackerBgColor", CVAR_TRACKER_ITEM("BgColor"), { 0, 0, 0, 0 }, true, + ColorPickerRandomButton | ColorPickerResetButton, THEME_COLOR); - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Window Type", CVAR_TRACKER_ITEM("WindowType"), windowTypes, TRACKER_WINDOW_FLOATING)) { + ImGui::PopItemWidth(); + if (CVarCombobox("Window Type", CVAR_TRACKER_ITEM("WindowType"), windowTypes, ComboboxOptions() + .DefaultIndex(TRACKER_WINDOW_FLOATING).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("WindowType"), TRACKER_WINDOW_FLOATING) == TRACKER_WINDOW_FLOATING) { - if (UIWidgets::PaddedEnhancementCheckbox("Enable Dragging", CVAR_TRACKER_ITEM("Draggable"))) { + if (CVarCheckbox("Enable Dragging", CVAR_TRACKER_ITEM("Draggable"), CheckboxOptions().Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::PaddedEnhancementCheckbox("Only enable while paused", CVAR_TRACKER_ITEM("ShowOnlyPaused"))) { + if (CVarCheckbox("Only enable while paused", CVAR_TRACKER_ITEM("ShowOnlyPaused"), CheckboxOptions().Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Display Mode", CVAR_TRACKER_ITEM("DisplayType.Main"), displayModes, TRACKER_DISPLAY_ALWAYS)) { + if (CVarCombobox("Display Mode", CVAR_TRACKER_ITEM("DisplayType.Main"), displayModes, ComboboxOptions() + .DefaultIndex(TRACKER_DISPLAY_ALWAYS).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Main"), TRACKER_DISPLAY_ALWAYS) == TRACKER_DISPLAY_COMBO_BUTTON) { - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Combo Button 1", CVAR_TRACKER_ITEM("ComboButton1"), buttons, TRACKER_COMBO_BUTTON_L)) { + if (CVarCombobox("Combo Button 1", CVAR_TRACKER_ITEM("ComboButton1"), buttons, ComboboxOptions() + .DefaultIndex(TRACKER_COMBO_BUTTON_L).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Combo Button 2", CVAR_TRACKER_ITEM("ComboButton2"), buttons, TRACKER_COMBO_BUTTON_R)) { + if (CVarCombobox("Combo Button 2", CVAR_TRACKER_ITEM("ComboButton2"), buttons, ComboboxOptions() + .DefaultIndex(TRACKER_COMBO_BUTTON_R).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } } } - UIWidgets::PaddedSeparator(); - UIWidgets::EnhancementSliderInt("Icon size : %dpx", "##ITEMTRACKERICONSIZE", CVAR_TRACKER_ITEM("IconSize"), 25, 128, "", 36); - UIWidgets::EnhancementSliderInt("Icon margins : %dpx", "##ITEMTRACKERSPACING", CVAR_TRACKER_ITEM("IconSpacing"), -5, 50, "", 12); - UIWidgets::EnhancementSliderInt("Text size : %dpx", "##ITEMTRACKERTEXTSIZE", CVAR_TRACKER_ITEM("TextSize"), 1, 30, "", 13); + ImGui::Separator(); + CVarSliderInt("Icon size : %dpx", CVAR_TRACKER_ITEM("IconSize"), IntSliderOptions().Min(25).Max(128).DefaultValue(36).Color(THEME_COLOR)); + CVarSliderInt("Icon margins : %dpx", CVAR_TRACKER_ITEM("IconSpacing"), IntSliderOptions().Min(-5).Max(50).DefaultValue(12).Color(THEME_COLOR)); + CVarSliderInt("Text size : %dpx", CVAR_TRACKER_ITEM("TextSize"), IntSliderOptions().Min(1).Max(30).DefaultValue(13).Color(THEME_COLOR)); - UIWidgets::Spacer(0); - - ImGui::Text("Ammo/Capacity Tracking"); - UIWidgets::EnhancementCombobox(CVAR_TRACKER_ITEM("ItemCountType"), itemTrackerCapacityTrackOptions, ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY); - UIWidgets::InsertHelpHoverText("Customize what the numbers under each item are tracking." - "\n\nNote: items without capacity upgrades will track ammo even in capacity mode"); + ImGui::NewLine(); + CVarCombobox("Ammo/Capacity Tracking", CVAR_TRACKER_ITEM("ItemCountType"), itemTrackerCapacityTrackOptions, ComboboxOptions() + .DefaultIndex(ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY).ComponentAlignment(ComponentAlignment::Left) + .LabelPosition(LabelPosition::Above).Color(THEME_COLOR) + .Tooltip("Customize what the numbers under each item are tracking." + "\n\nNote: items without capacity upgrades will track ammo even in capacity mode")); if (CVarGetInteger(CVAR_TRACKER_ITEM("ItemCountType"), ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY) == ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY || CVarGetInteger(CVAR_TRACKER_ITEM("ItemCountType"), ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY) == ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY) { - if (UIWidgets::PaddedEnhancementCheckbox("Align count to left side", CVAR_TRACKER_ITEM("ItemCountAlignLeft"))) { + if (CVarCheckbox("Align count to left side", CVAR_TRACKER_ITEM("ItemCountAlignLeft"), CheckboxOptions().Color(THEME_COLOR))) { shouldUpdateVectors = true; } } - UIWidgets::Spacer(0); + CVarCombobox("Key Count Tracking", CVAR_TRACKER_ITEM("KeyCounts"), itemTrackerKeyTrackOptions, ComboboxOptions() + .DefaultIndex(KEYS_COLLECTED_MAX).ComponentAlignment(ComponentAlignment::Left) + .LabelPosition(LabelPosition::Above).Color(THEME_COLOR) + .Tooltip("Customize what numbers are shown for key tracking.")); - ImGui::Text("Key Count Tracking"); - UIWidgets::EnhancementCombobox(CVAR_TRACKER_ITEM("KeyCounts"), itemTrackerKeyTrackOptions, KEYS_COLLECTED_MAX); - UIWidgets::InsertHelpHoverText("Customize what numbers are shown for key tracking."); - - UIWidgets::Spacer(0); - - ImGui::Text("Triforce Piece Count Tracking"); - UIWidgets::EnhancementCombobox(CVAR_TRACKER_ITEM("TriforcePieceCounts"), itemTrackerTriforcePieceTrackOptions, TRIFORCE_PIECE_COLLECTED_REQUIRED_MAX); - UIWidgets::InsertHelpHoverText("Customize what numbers are shown for triforce piece tracking."); + CVarCombobox("Triforce Piece Count Tracking", CVAR_TRACKER_ITEM("TriforcePieceCounts"), itemTrackerTriforcePieceTrackOptions, ComboboxOptions() + .DefaultIndex(TRIFORCE_PIECE_COLLECTED_REQUIRED_MAX).ComponentAlignment(ComponentAlignment::Left) + .LabelPosition(LabelPosition::Above).Color(THEME_COLOR) + .Tooltip("Customize what numbers are shown for triforce piece tracking.")); ImGui::TableNextColumn(); - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Inventory", CVAR_TRACKER_ITEM("DisplayType.Inventory"), displayTypes, SECTION_DISPLAY_MAIN_WINDOW)) { + if (CVarCombobox("Inventory", CVAR_TRACKER_ITEM("DisplayType.Inventory"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Equipment", CVAR_TRACKER_ITEM("DisplayType.Equipment"), displayTypes, SECTION_DISPLAY_MAIN_WINDOW)) { + if (CVarCombobox("Equipment", CVAR_TRACKER_ITEM("DisplayType.Equipment"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Misc", CVAR_TRACKER_ITEM("DisplayType.Misc"), displayTypes, SECTION_DISPLAY_MAIN_WINDOW)) { + if (CVarCombobox("Misc", CVAR_TRACKER_ITEM("DisplayType.Misc"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Dungeon Rewards", CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), displayTypes, SECTION_DISPLAY_MAIN_WINDOW)) { + if (CVarCombobox("Dungeon Rewards", CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), SECTION_DISPLAY_MAIN_WINDOW) == SECTION_DISPLAY_SEPARATE) { - if (UIWidgets::PaddedEnhancementCheckbox("Circle display", CVAR_TRACKER_ITEM("DungeonRewardsLayout"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, false)) { + if (CVarCheckbox("Circle display", CVAR_TRACKER_ITEM("DungeonRewardsLayout"))) { shouldUpdateVectors = true; } } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Songs", CVAR_TRACKER_ITEM("DisplayType.Songs"), displayTypes, SECTION_DISPLAY_MAIN_WINDOW)) { + if (CVarCombobox("Songs", CVAR_TRACKER_ITEM("DisplayType.Songs"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Dungeon Items", CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), displayTypes, SECTION_DISPLAY_HIDDEN)) { + if (CVarCombobox("Dungeon Items", CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), SECTION_DISPLAY_HIDDEN) != SECTION_DISPLAY_HIDDEN) { if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), SECTION_DISPLAY_HIDDEN) == SECTION_DISPLAY_SEPARATE) { - if (UIWidgets::PaddedEnhancementCheckbox("Horizontal display", CVAR_TRACKER_ITEM("DungeonItems.Layout"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true)) { + if (CVarCheckbox("Horizontal display", CVAR_TRACKER_ITEM("DungeonItems.Layout"), CheckboxOptions().DefaultValue(true).Color(THEME_COLOR))) { shouldUpdateVectors = true; } } - if (UIWidgets::PaddedEnhancementCheckbox("Maps and compasses", CVAR_TRACKER_ITEM("DungeonItems.DisplayMaps"), true, true, false, "", UIWidgets::CheckboxGraphics::Cross, true)) { + if (CVarCheckbox("Maps and compasses", CVAR_TRACKER_ITEM("DungeonItems.DisplayMaps"), CheckboxOptions().DefaultValue(true).Color(THEME_COLOR))) { shouldUpdateVectors = true; } } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Greg", CVAR_TRACKER_ITEM("DisplayType.Greg"), extendedDisplayTypes, SECTION_DISPLAY_EXTENDED_HIDDEN)) { + if (CVarCombobox("Greg", CVAR_TRACKER_ITEM("DisplayType.Greg"), extendedDisplayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_EXTENDED_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Triforce Pieces", CVAR_TRACKER_ITEM("DisplayType.TriforcePieces"), displayTypes, SECTION_DISPLAY_HIDDEN)) { + if (CVarCombobox("Triforce Pieces", CVAR_TRACKER_ITEM("DisplayType.TriforcePieces"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Boss Souls", CVAR_TRACKER_ITEM("DisplayType.BossSouls"), displayTypes, SECTION_DISPLAY_HIDDEN)) { + if (CVarCombobox("Boss Souls", CVAR_TRACKER_ITEM("DisplayType.BossSouls"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Ocarina Buttons", CVAR_TRACKER_ITEM("DisplayType.OcarinaButtons"), displayTypes, SECTION_DISPLAY_HIDDEN)) { + if (CVarCombobox("Ocarina Buttons", CVAR_TRACKER_ITEM("DisplayType.OcarinaButtons"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Fishing Pole", CVAR_TRACKER_ITEM("DisplayType.FishingPole"), extendedDisplayTypes, SECTION_DISPLAY_EXTENDED_HIDDEN)) { + if (CVarCombobox("Fishing Pole", CVAR_TRACKER_ITEM("DisplayType.FishingPole"), extendedDisplayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_EXTENDED_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Total Checks", "gTrackers.ItemTracker.TotalChecks.DisplayType", minimalDisplayTypes, SECTION_DISPLAY_MINIMAL_HIDDEN)) { + if (CVarCombobox("Total Checks", "gTrackers.ItemTracker.TotalChecks.DisplayType", minimalDisplayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_MINIMAL_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Main"), TRACKER_DISPLAY_ALWAYS) == TRACKER_DISPLAY_ALWAYS) { - if (UIWidgets::LabeledRightAlignedEnhancementCombobox("Personal notes", CVAR_TRACKER_ITEM("DisplayType.Notes"), displayTypes, SECTION_DISPLAY_HIDDEN)) { + if (CVarCombobox("Personal notes", CVAR_TRACKER_ITEM("DisplayType.Notes"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } } - UIWidgets::EnhancementCheckbox("Show Hookshot Identifiers", CVAR_TRACKER_ITEM("HookshotIdentifier")); - UIWidgets::InsertHelpHoverText("Shows an 'H' or an 'L' to more easiely distinguish between Hookshot and Longshot."); - - UIWidgets::Spacer(0); + CVarCheckbox("Show Hookshot Identifiers", CVAR_TRACKER_ITEM("HookshotIdentifier"), CheckboxOptions() + .Tooltip("Shows an 'H' or an 'L' to more easiely distinguish between Hookshot and Longshot.").Color(THEME_COLOR)); ImGui::PopStyleVar(1); ImGui::EndTable(); + ImGui::PopFont(); } void ItemTrackerWindow::InitElement() { - float trackerBgR = CVarGetFloat(CVAR_TRACKER_ITEM("BgColorR"), 0); - float trackerBgG = CVarGetFloat(CVAR_TRACKER_ITEM("BgColorG"), 0); - float trackerBgB = CVarGetFloat(CVAR_TRACKER_ITEM("BgColorB"), 0); - float trackerBgA = CVarGetFloat(CVAR_TRACKER_ITEM("BgColorA"), 1); - ChromaKeyBackground = { - trackerBgR, - trackerBgG, - trackerBgB, - trackerBgA - }; // Float value, 1 = 255 in rgb value. // Crashes when the itemTrackerNotes is empty, so add an empty character to it if (itemTrackerNotes.empty()) { itemTrackerNotes.push_back(0); diff --git a/soh/soh/Enhancements/randomizer/randomizer_settings_window.h b/soh/soh/Enhancements/randomizer/randomizer_settings_window.h index 9f297c29d..034101aba 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_settings_window.h +++ b/soh/soh/Enhancements/randomizer/randomizer_settings_window.h @@ -1,3 +1,5 @@ +#pragma once + #include namespace Rando { diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index cf1acdd7f..447da07f9 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -246,15 +246,15 @@ void Settings::CreateOptions() { OPT_U8(RSK_LACS_OPTIONS, "GCBK LACS Reward Options", {"Standard Reward", "Greg as Reward", "Greg as Wildcard"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LacsRewardOptions"), "", WidgetType::Combobox, RO_LACS_STANDARD_REWARD); OPT_U8(RSK_KEYRINGS, "Key Rings", {"Off", "Random", "Count", "Selection"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRings"), mOptionDescriptions[RSK_KEYRINGS], WidgetType::Combobox, RO_KEYRINGS_OFF); OPT_U8(RSK_KEYRINGS_RANDOM_COUNT, "Keyring Dungeon Count", {NumOpts(0, 9)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsRandomCount"), "", WidgetType::Slider, 8); - OPT_U8(RSK_KEYRINGS_GERUDO_FORTRESS, "Gerudo Fortress Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGerudoFortress"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_FOREST_TEMPLE, "Forest Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsForestTemple"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_FIRE_TEMPLE, "Fire Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsFireTemple"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_WATER_TEMPLE, "Water Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsWaterTemple"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_SPIRIT_TEMPLE, "Spirit Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsSpiritTemple"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_SHADOW_TEMPLE, "Shadow Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsShadowTemple"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_BOTTOM_OF_THE_WELL, "Bottom of the Well Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsBottomOfTheWell"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_GTG, "Gerudo Training Ground Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGTG"), "", WidgetType::TristateCheckbox, 0); - OPT_U8(RSK_KEYRINGS_GANONS_CASTLE, "Ganon's Castle Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGanonsCastle"), "", WidgetType::TristateCheckbox, 0); + OPT_U8(RSK_KEYRINGS_GERUDO_FORTRESS, "Gerudo Fortress Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGerudoFortress"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_FOREST_TEMPLE, "Forest Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsForestTemple"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_FIRE_TEMPLE, "Fire Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsFireTemple"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_WATER_TEMPLE, "Water Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsWaterTemple"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_SPIRIT_TEMPLE, "Spirit Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsSpiritTemple"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_SHADOW_TEMPLE, "Shadow Temple Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsShadowTemple"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_BOTTOM_OF_THE_WELL, "Bottom of the Well Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsBottomOfTheWell"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_GTG, "Gerudo Training Ground Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGTG"), "", WidgetType::Combobox, 0); + OPT_U8(RSK_KEYRINGS_GANONS_CASTLE, "Ganon's Castle Keyring", {"No", "Random", "Yes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleKeyRingsGanonsCastle"), "", WidgetType::Combobox, 0); //Dummied out due to redundancy with TimeSavers.SkipChildStealth until such a time that logic needs to consider child stealth e.g. because it's freestanding checks are added to freestanding shuffle. //To undo this dummying, readd this setting to an OptionGroup so it appears in the UI, then edit the timesaver check hooks to look at this, and the timesaver setting to lock itself as needed. OPT_BOOL(RSK_SKIP_CHILD_STEALTH, "Skip Child Stealth", {"Don't Skip", "Skip"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("SkipChildStealth"), mOptionDescriptions[RSK_SKIP_CHILD_STEALTH], WidgetType::Checkbox, RO_GENERIC_DONT_SKIP); @@ -1674,7 +1674,7 @@ void Settings::UpdateOptionProperties() { } // Shuffle 100 GS Reward - Force-Enabled if Ganon's Boss Key is on the 100 GS Reward if (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), RO_GANON_BOSS_KEY_VANILLA) == RO_GANON_BOSS_KEY_KAK_TOKENS) { - mOptions[RSK_SHUFFLE_100_GS_REWARD].Disable("This option is force-enabled because \"Ganon's Boss Key\" is set to \"100 GS Reward.\"", UIWidgets::CheckboxGraphics::Checkmark); + mOptions[RSK_SHUFFLE_100_GS_REWARD].Disable("This option is force-enabled because \"Ganon's Boss Key\" is set to \"100 GS Reward.\""); } else { mOptions[RSK_SHUFFLE_100_GS_REWARD].Enable(); } diff --git a/soh/soh/Enhancements/randomizer/tricks.cpp b/soh/soh/Enhancements/randomizer/tricks.cpp index 6008ba332..69b3db88d 100644 --- a/soh/soh/Enhancements/randomizer/tricks.cpp +++ b/soh/soh/Enhancements/randomizer/tricks.cpp @@ -1,4 +1,5 @@ #include "tricks.h" +#include "soh/SohGui/UIWidgets.hpp" #include namespace Rando { @@ -71,7 +72,6 @@ namespace Rando { const ImVec4 Tricks::GetTextColor(const Tag tag) { switch(tag) { - case Tag::EXPERIMENTAL: case Tag::GLITCH: return { 0.00f, 0.00f, 0.00f, 1.0f }; default: @@ -82,32 +82,33 @@ namespace Rando { const ImVec4 Tricks::GetTagColor(const Tag tag) { switch(tag) { case Tag::NOVICE: - return { 0.09f, 0.55f, 0.37f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::Green); case Tag::INTERMEDIATE: - return { 0.95f, 0.52f, 0.00f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::Orange); case Tag::ADVANCED: - return { 0.00f, 0.29f, 0.71f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::Blue); case Tag::EXPERT: - return { 0.53f, 0.05f, 0.14f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::Red); case Tag::EXTREME: - return { 0.27f, 0.00f, 0.27f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::Purple); case Tag::EXPERIMENTAL: - return { 0.00f, 1.00f, 1.00f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::LightBlue); case Tag::GLITCH: - return { 1.00f, 1.00f, 1.00f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::White); default: assert(false); - return { 0.50f, 0.50f, 0.50f, 1.00f }; + return UIWidgets::ColorValues.at(UIWidgets::Colors::Gray); } } - void Tricks::DrawTagChips(const std::set& rtTags) { + void Tricks::DrawTagChips(const std::set& rtTags, std::string trickName) { for (const Tag rtTag : rtTags) { + std::string tagId = GetTagName(rtTag) + "##" + trickName; ImGui::SameLine(); ImGui::BeginDisabled(); - ImGui::PushStyleColor(ImGuiCol_Button, GetTagColor(rtTag)); - ImGui::SmallButton(GetTagName(rtTag).c_str()); - ImGui::PopStyleColor(); + UIWidgets::PushStyleButton(GetTagColor(rtTag)); + ImGui::SmallButton(tagId.c_str()); + UIWidgets::PopStyleButton(); ImGui::EndDisabled(); } } diff --git a/soh/soh/Enhancements/randomizer/tricks.h b/soh/soh/Enhancements/randomizer/tricks.h index b3b635555..ddaa0f27c 100644 --- a/soh/soh/Enhancements/randomizer/tricks.h +++ b/soh/soh/Enhancements/randomizer/tricks.h @@ -28,7 +28,7 @@ namespace Rando { static const std::string GetTagName(Tag tag); static const ImVec4 GetTextColor(Tag tag); static const ImVec4 GetTagColor(Tag tag); - static void DrawTagChips(const std::set& rtTags); + static void DrawTagChips(const std::set& rtTags, std::string trickName = ""); }; } diff --git a/soh/soh/Enhancements/resolution-editor/ResolutionEditor.cpp b/soh/soh/Enhancements/resolution-editor/ResolutionEditor.cpp deleted file mode 100644 index a18aea8dc..000000000 --- a/soh/soh/Enhancements/resolution-editor/ResolutionEditor.cpp +++ /dev/null @@ -1,489 +0,0 @@ -#include "ResolutionEditor.h" -#include -#include - -#include "soh/SohGui/UIWidgets.hpp" -#include -#include "soh/OTRGlobals.h" -#include "soh/cvar_prefixes.h" - -/* Console Variables are grouped under gAdvancedResolution. (e.g. CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled") - - The following cvars are used in Libultraship and can be edited here: - - Enabled - Turns Advanced Resolution Mode on. - - AspectRatioX, AspectRatioY - Aspect ratio controls. To toggle off, set either to zero. - - VerticalPixelCount, VerticalResolutionToggle - Resolution controls. - - PixelPerfectMode, IntegerScale.Factor - Pixel Perfect Mode a.k.a. integer scaling controls. - - IntegerScale.FitAutomatically - Automatic resizing for Pixel Perfect Mode. - - IntegerScale.NeverExceedBounds - Prevents manual resizing from exceeding screen bounds. - - The following cvars are also implemented in LUS for niche use cases: - - IgnoreAspectCorrection - Stretch framebuffer to fill screen. - This is something of a power-user setting for niche setups that most people won't need or care about, - but may be useful if playing the Switch/Wii U ports on a 4:3 television. - - IntegerScale.ExceedBoundsBy - Offset the max screen bounds, usually by +1. - This isn't that useful at the moment, so it's unused here. -*/ - -namespace AdvancedResolutionSettings { -enum setting { UPDATE_aspectRatioX, UPDATE_aspectRatioY, UPDATE_verticalPixelCount }; - -const char* aspectRatioPresetLabels[] = { - "Off", "Custom", "Original (4:3)", "Widescreen (16:9)", "Nintendo 3DS (5:3)", "16:10 (8:5)", "Ultrawide (21:9)" -}; -const float aspectRatioPresetsX[] = { 0.0f, 16.0f, 4.0f, 16.0f, 5.0f, 16.0f, 21.0f }; -const float aspectRatioPresetsY[] = { 0.0f, 9.0f, 3.0f, 9.0f, 3.0f, 10.0f, 9.0f }; -const int default_aspectRatio = 1; // Default combo list option - -const char* pixelCountPresetLabels[] = { "Custom", "Native N64 (240p)", "2x (480p)", "3x (720p)", "4x (960p)", - "5x (1200p)", "6x (1440p)", "Full HD (1080p)", "4K (2160p)" }; -const int pixelCountPresets[] = { 480, 240, 480, 720, 960, 1200, 1440, 1080, 2160 }; -const int default_pixelCount = 0; // Default combo list option - -// Resolution clamp values as hardcoded in LUS::Gui::ApplyResolutionChanges() -const uint32_t minVerticalPixelCount = SCREEN_HEIGHT; -const uint32_t maxVerticalPixelCount = 4320; // 18x native, or 8K TV resolution - -const unsigned short default_maxIntegerScaleFactor = 6; // Default size of Integer scale factor slider. - -enum messageType { MESSAGE_ERROR, MESSAGE_WARNING, MESSAGE_QUESTION, MESSAGE_INFO, MESSAGE_GRAY_75 }; -const ImVec4 messageColor[]{ - { 0.85f, 0.0f, 0.0f, 1.0f }, // MESSAGE_ERROR - { 0.85f, 0.85f, 0.0f, 1.0f }, // MESSAGE_WARNING - { 0.0f, 0.85f, 0.85f, 1.0f }, // MESSAGE_QUESTION - { 0.0f, 0.85f, 0.55f, 1.0f }, // MESSAGE_INFO - { 0.75f, 0.75f, 0.75f, 1.0f } // MESSAGE_GRAY_75 -}; -const float enhancementSpacerHeight = 19.0f; - -void AdvancedResolutionSettingsWindow::InitElement() { -} - -void AdvancedResolutionSettingsWindow::DrawElement() { - // Initialise update flags. - bool update[3]; - for (uint8_t i = 0; i < sizeof(update); i++) - update[i] = false; - - // Initialise integer scale bounds. - short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get - // overridden depending on viewport res - - short integerScale_maximumBounds = 1; // can change when window is resized - // This is mostly just for UX purposes, as Fit Automatically logic is part of LUS. - if (((float)gfx_current_game_window_viewport.width / gfx_current_game_window_viewport.height) > - ((float)gfx_current_dimensions.width / gfx_current_dimensions.height)) { - // Scale to window height - integerScale_maximumBounds = gfx_current_game_window_viewport.height / gfx_current_dimensions.height; - } else { - // Scale to window width - integerScale_maximumBounds = gfx_current_game_window_viewport.width / gfx_current_dimensions.width; - } - // Lower-clamping maximum bounds value to 1 is no-longer necessary as that's accounted for in LUS. - // Letting it go below 1 in this Editor will even allow for checking if screen bounds are being exceeded. - if (default_maxIntegerScaleFactor < integerScale_maximumBounds) { - max_integerScaleFactor = - integerScale_maximumBounds + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0); - } - - // Combo List defaults - static int item_aspectRatio = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", 3); - static int item_pixelCount = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", default_pixelCount); - // Stored Values for non-UIWidgets elements - static float aspectRatioX = - CVarGetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioPresetsX[item_aspectRatio]); - static float aspectRatioY = - CVarGetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioPresetsY[item_aspectRatio]); - static int verticalPixelCount = - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", pixelCountPresets[item_pixelCount]); - // Additional settings - static bool showHorizontalResField = false; - static int horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; - // Disabling flags - const bool disabled_everything = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0); - const bool disabled_pixelCount = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0); - -#ifdef __APPLE__ - // Display HiDPI warning. (Remove this once we can definitively say it's fixed.) - ImGui::TextColored(messageColor[MESSAGE_INFO], - ICON_FA_INFO_CIRCLE " These settings may behave incorrectly on Retina displays."); - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); -#endif - - if (ImGui::CollapsingHeader("Original Settings", ImGuiTreeNodeFlags_DefaultOpen)) { - // The original resolution slider (for convenience) - const bool disabled_resolutionSlider = (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0) && - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0)) || - CVarGetInteger(CVAR_LOW_RES_MODE, 0); - if (UIWidgets::EnhancementSliderFloat("Internal Resolution: %.1f%%", "##IMul", CVAR_INTERNAL_RESOLUTION, 0.5f, - 2.0f, "", 1.0f, true, true, disabled_resolutionSlider)) { - Ship::Context::GetInstance()->GetWindow()->SetResolutionMultiplier( - CVarGetFloat(CVAR_INTERNAL_RESOLUTION, 1)); - } - UIWidgets::Tooltip("Multiplies your output resolution by the value entered."); - - // The original MSAA slider (also for convenience) -#ifndef __WIIU__ - if (UIWidgets::PaddedEnhancementSliderInt("MSAA: %d", "##IMSAA", CVAR_MSAA_VALUE, 1, 8, "", 1, true, true, - false)) { - Ship::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger(CVAR_MSAA_VALUE, 1)); - }; - UIWidgets::Tooltip( - "Activates multi-sample anti-aliasing when above 1x, up to 8x for 8 samples for every pixel.\n\n" - " " ICON_FA_INFO_CIRCLE - " (Higher MSAA with low resolution can approximate an authentic \"real N64\" look!)"); -#endif - - // N64 Mode toggle (again for convenience) - // UIWidgets::PaddedEnhancementCheckbox("(Enhancements>Graphics) N64 Mode", CVAR_LOW_RES_MODE, false, false, false, "", UIWidgets::CheckboxGraphics::Cross, false); - } - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - // Activator - UIWidgets::PaddedEnhancementCheckbox("Enable advanced settings.", CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", false, false, - false, "", UIWidgets::CheckboxGraphics::Cross, false); - // Error/Warning display - if (!CVarGetInteger(CVAR_LOW_RES_MODE, 0)) { - if (IsDroppingFrames()) { // Significant frame drop warning - ImGui::TextColored(messageColor[MESSAGE_WARNING], - ICON_FA_EXCLAMATION_TRIANGLE " Significant frame rate (FPS) drops may be occuring."); - UIWidgets::Spacer(2); - } else { // No warnings - UIWidgets::Spacer(enhancementSpacerHeight); - } - } else { // N64 Mode warning - ImGui::TextColored(messageColor[MESSAGE_QUESTION], - ICON_FA_QUESTION_CIRCLE " \"N64 Mode\" is overriding these settings."); - ImGui::SameLine(); - if (ImGui::Button("Click to disable")) { - CVarSetInteger(CVAR_LOW_RES_MODE, 0); - CVarSave(); - } - } - // Resolution visualiser - ImGui::Text("Viewport dimensions: %d x %d", gfx_current_game_window_viewport.width, - gfx_current_game_window_viewport.height); - ImGui::Text("Internal resolution: %d x %d", gfx_current_dimensions.width, gfx_current_dimensions.height); - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - if (disabled_everything) { // Hide aspect ratio controls. - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - // Aspect Ratio - ImGui::Text("Force aspect ratio:"); - ImGui::SameLine(); - ImGui::TextColored(messageColor[MESSAGE_GRAY_75], "(Select \"Off\" to disable.)"); - // Presets - if (ImGui::Combo(" ", &item_aspectRatio, aspectRatioPresetLabels, - IM_ARRAYSIZE(aspectRatioPresetLabels)) && - item_aspectRatio != default_aspectRatio) { // don't change anything if "Custom" is selected. - aspectRatioX = aspectRatioPresetsX[item_aspectRatio]; - aspectRatioY = aspectRatioPresetsY[item_aspectRatio]; - - if (showHorizontalResField) { - horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; - } - - CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX); - CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY); - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio); - CVarSave(); - } - // Hide aspect ratio input fields if using one of the presets. - if (item_aspectRatio == default_aspectRatio && !showHorizontalResField) { - // Declare input interaction bools outside of IF statement to prevent Y field from disappearing. - const bool input_X = ImGui::InputFloat("X", &aspectRatioX, 0.1f, 1.0f, "%.3f"); - const bool input_Y = ImGui::InputFloat("Y", &aspectRatioY, 0.1f, 1.0f, "%.3f"); - if (input_X || input_Y) { - item_aspectRatio = default_aspectRatio; - update[UPDATE_aspectRatioX] = true; - update[UPDATE_aspectRatioY] = true; - } - } else if (showHorizontalResField) { // Show calculated aspect ratio - if (item_aspectRatio) { - UIWidgets::Spacer(2); - const float resolvedAspectRatio = (float)gfx_current_dimensions.width / gfx_current_dimensions.height; - ImGui::Text("Aspect ratio: %.2f:1", resolvedAspectRatio); - } else { - UIWidgets::Spacer(enhancementSpacerHeight); - } - } - - if (disabled_everything) { // Hide aspect ratio controls. - UIWidgets::ReEnableComponent("disabledTooltipText"); - } - UIWidgets::Spacer(0); - - // Vertical Resolution - UIWidgets::PaddedEnhancementCheckbox("Set fixed vertical resolution (disables Resolution slider)", - CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", true, false, - disabled_everything, "", UIWidgets::CheckboxGraphics::Cross, false); - UIWidgets::Tooltip( - "Override the resolution scale slider and use the settings below, irrespective of window size."); - if (disabled_pixelCount || disabled_everything) { // Hide pixel count controls. - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - if (ImGui::Combo("Pixel Count Presets", &item_pixelCount, pixelCountPresetLabels, - IM_ARRAYSIZE(pixelCountPresetLabels)) && - item_pixelCount != default_pixelCount) { // don't change anything if "Custom" is selected. - verticalPixelCount = pixelCountPresets[item_pixelCount]; - - if (showHorizontalResField) { - horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; - } - - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount); - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount); - CVarSave(); - } - // Horizontal Resolution, if visibility is enabled for it. - if (showHorizontalResField) { - // Only show the field if Aspect Ratio is being enforced. - if ((aspectRatioX > 0.0f) && (aspectRatioY > 0.0f)) { - // So basically we're "faking" this one by setting aspectRatioX instead. - if (ImGui::InputInt("Horiz. Pixel Count", &horizontalPixelCount, 8, 320)) { - item_aspectRatio = default_aspectRatio; - if (horizontalPixelCount < SCREEN_WIDTH) { - horizontalPixelCount = SCREEN_WIDTH; - } - aspectRatioX = horizontalPixelCount; - aspectRatioY = verticalPixelCount; - update[UPDATE_aspectRatioX] = true; - update[UPDATE_aspectRatioY] = true; - } - } else { // Display a notice instead. - ImGui::TextColored(messageColor[MESSAGE_QUESTION], - ICON_FA_QUESTION_CIRCLE " \"Force aspect ratio\" required."); - // ImGui::Text(" "); - ImGui::SameLine(); - if (ImGui::Button("Click to resolve")) { - item_aspectRatio = default_aspectRatio; // Set it to Custom - aspectRatioX = aspectRatioPresetsX[2]; // but use the 4:3 defaults - aspectRatioY = aspectRatioPresetsY[2]; - update[UPDATE_aspectRatioX] = true; - update[UPDATE_aspectRatioY] = true; - horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; - } - } - } - // Vertical Resolution part 2 - if (ImGui::InputInt("Vertical Pixel Count", &verticalPixelCount, 8, 240)) { - item_pixelCount = default_pixelCount; - update[UPDATE_verticalPixelCount] = true; - - // Account for the natural instinct to enter horizontal first. - // Ignore vertical resolutions that are below the lower clamp constant. - if (showHorizontalResField && !(verticalPixelCount < minVerticalPixelCount)) { - item_aspectRatio = default_aspectRatio; - aspectRatioX = horizontalPixelCount; - aspectRatioY = verticalPixelCount; - update[UPDATE_aspectRatioX] = true; - update[UPDATE_aspectRatioY] = true; - } - } - if (disabled_pixelCount || disabled_everything) { // Hide pixel count controls. - UIWidgets::ReEnableComponent("disabledTooltipText"); - } - - UIWidgets::Spacer(0); - - // Integer scaling settings group (Pixel-perfect Mode) - static const ImGuiTreeNodeFlags IntegerScalingResolvedImGuiFlag = - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) ? ImGuiTreeNodeFlags_DefaultOpen - : ImGuiTreeNodeFlags_None; - if (ImGui::CollapsingHeader("Integer Scaling Settings", IntegerScalingResolvedImGuiFlag)) { - const bool disabled_pixelPerfectMode = - !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || disabled_everything; - // Pixel-perfect Mode - UIWidgets::PaddedEnhancementCheckbox("Pixel-perfect Mode", CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", true, - true, disabled_pixelCount || disabled_everything, "", - UIWidgets::CheckboxGraphics::Cross, false); - UIWidgets::Tooltip("Don't scale image to fill window."); - if (disabled_pixelCount && CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0)) { - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0); - CVarSave(); - } - - // Integer Scaling - UIWidgets::EnhancementSliderInt( - "Integer scale factor: %d", "##ARSIntScale", CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", 1, - max_integerScaleFactor, "%d", 1, true, - disabled_pixelPerfectMode || CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0)); - UIWidgets::Tooltip("Integer scales the image. Only available in pixel-perfect mode."); - // Display warning if size is being clamped or if framebuffer is larger than viewport. - if (!disabled_pixelPerfectMode && - (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", 1) && - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", 1) > integerScale_maximumBounds)) { - ImGui::SameLine(); - ImGui::TextColored(messageColor[MESSAGE_WARNING], ICON_FA_EXCLAMATION_TRIANGLE " Window exceeded."); - } - - UIWidgets::PaddedEnhancementCheckbox( - "Automatically scale image to fit viewport", CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", true, - true, disabled_pixelPerfectMode, "", UIWidgets::CheckboxGraphics::Cross, false); - UIWidgets::Tooltip("Automatically sets scale factor to fit window. Only available in pixel-perfect mode."); - if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0)) { - // This is just here to update the value shown on the slider. - // The function in LUS to handle this setting will ignore IntegerScaleFactor while active. - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", integerScale_maximumBounds); - // CVarSave(); - } - } // End of integer scaling settings - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - - // Collapsible panel for additional settings - if (ImGui::CollapsingHeader("Additional Settings")) { - UIWidgets::Spacer(0); - -#if defined(__SWITCH__) || defined(__WIIU__) - // Disable aspect correction, stretching the framebuffer to fill the viewport. - // This option is only really needed on systems limited to 16:9 TV resolutions, such as consoles. - // The associated cvar is still functional on PC platforms if you want to use it though. - UIWidgets::PaddedEnhancementCheckbox("Disable aspect correction and stretch the output image.\n" - "(Might be useful for 4:3 televisions!)\n" - "Not available in Pixel Perfect Mode.", - CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", false, true, - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || - disabled_everything, - "", UIWidgets::CheckboxGraphics::Cross, false); -#else - if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", 0)) { - // This setting is intentionally not exposed on PC platforms, - // but may be accidentally activated for varying reasons. - // Having this button should hopefully prevent support headaches. - ImGui::TextColored(messageColor[MESSAGE_QUESTION], ICON_FA_QUESTION_CIRCLE - " If the image is stretched and you don't know why, click this."); - if (ImGui::Button("Click to reenable aspect correction.")) { - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", 0); - CVarSave(); - } - UIWidgets::Spacer(2); - } -#endif - - // A requested addition; an alternative way of displaying the resolution field. - if (ImGui::Checkbox("Show a horizontal resolution field, instead of aspect ratio.", &showHorizontalResField)) { - if (!showHorizontalResField && (aspectRatioX > 0.0f)) { // when turning this setting off - // Refresh relevant values - aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount; - horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; - } else { // when turning this setting on - item_aspectRatio = default_aspectRatio; - if (aspectRatioX > 0.0f) { - // Refresh relevant values in the opposite order - horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; - aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount; - } - } - update[UPDATE_aspectRatioX] = true; - } - - // Beginning of Integer Scaling additional settings. - { - // UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - - // Integer Scaling - Never Exceed Bounds. - const bool disabled_neverExceedBounds = - !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0) || disabled_everything; - const bool checkbox_neverExceedBounds = - UIWidgets::PaddedEnhancementCheckbox("Prevent integer scaling from exceeding screen bounds.\n" - "(Makes screen bounds take priority over specified factor.)", - CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", - true, false, disabled_neverExceedBounds, "", - UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip( - "Prevents integer scaling factor from exceeding screen bounds.\n\n" - "Enabled: Will clamp the scaling factor and display a gentle warning in the resolution editor.\n" - "Disabled: Will allow scaling to exceed screen bounds, for users who want to crop overscan.\n\n" - " " ICON_FA_INFO_CIRCLE - " Please note that exceeding screen bounds may show a scroll bar on-screen."); - - // Initialise the (currently unused) "Exceed Bounds By" cvar if it's been changed. - if (checkbox_neverExceedBounds && - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) { - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0); - CVarSave(); - } - - // Integer Scaling - Exceed Bounds By 1x/Offset. - // A popular feature in some retro frontends/upscalers, sometimes called "crop overscan" or "1080p 5x". - /* - UIWidgets::PaddedEnhancementCheckbox("Allow integer scale factor to go +1 above maximum screen bounds.", CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", false, false, !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || disabled_everything, "", UIWidgets::CheckboxGraphics::Cross, false); - */ - // It does actually function as expected, but exceeding the bottom of the screen shows a scroll bar. - // I've ended up commenting this one out because of the scroll bar, and for simplicity. - - // Display an info message about the scroll bar. - if (!CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", 1) || - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) { - if (disabled_neverExceedBounds) { // Dim this help text accordingly - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - ImGui::TextColored(messageColor[MESSAGE_INFO], - " " ICON_FA_INFO_CIRCLE - " A scroll bar may become visible if screen bounds are exceeded."); - if (disabled_neverExceedBounds) { // Dim this help text accordingly - UIWidgets::ReEnableComponent("disabledTooltipText"); - } - - // Another support helper button, to disable the unused "Exceed Bounds By" cvar. - // (Remove this button if uncommenting the checkbox.) - if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) { - if (ImGui::Button("Click to reset a console variable that may be causing this.")) { - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0); - CVarSave(); - } - } - } else { - ImGui::Text(" "); - } - // UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - } // End of Integer Scaling additional settings. - - } // End of additional settings - - // Clamp and update the cvars that don't use UIWidgets - if (update[UPDATE_aspectRatioX] || update[UPDATE_aspectRatioY] || update[UPDATE_verticalPixelCount]) { - if (update[UPDATE_aspectRatioX]) { - if (aspectRatioX < 0.0f) { - aspectRatioX = 0.0f; - } - CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX); - } - if (update[UPDATE_aspectRatioY]) { - if (aspectRatioY < 0.0f) { - aspectRatioY = 0.0f; - } - CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY); - } - if (update[UPDATE_verticalPixelCount]) { - // There's a upper and lower clamp on the Libultraship side too, - // so clamping it here is entirely visual, so the vertical resolution field reflects it. - if (verticalPixelCount < minVerticalPixelCount) { - verticalPixelCount = minVerticalPixelCount; - } - if (verticalPixelCount > maxVerticalPixelCount) { - verticalPixelCount = maxVerticalPixelCount; - } - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount); - } - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio); - CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount); - CVarSave(); - } -} - -void AdvancedResolutionSettingsWindow::UpdateElement() { -} - -bool AdvancedResolutionSettingsWindow::IsDroppingFrames() { - // a rather imprecise way of checking for frame drops. - // but it's mostly there to inform the player of large drops. - const short targetFPS = CVarGetInteger(CVAR_SETTING("InterpolationFPS"), 20); - const float threshold = targetFPS / 20.0f + 4.1f; - return ImGui::GetIO().Framerate < targetFPS - threshold; -} -} // namespace AdvancedResolutionSettings diff --git a/soh/soh/Enhancements/resolution-editor/ResolutionEditor.h b/soh/soh/Enhancements/resolution-editor/ResolutionEditor.h deleted file mode 100644 index f75931451..000000000 --- a/soh/soh/Enhancements/resolution-editor/ResolutionEditor.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once -#include - -namespace AdvancedResolutionSettings { -class AdvancedResolutionSettingsWindow : public Ship::GuiWindow { - private: - bool IsDroppingFrames(); - - public: - using GuiWindow::GuiWindow; - - void InitElement() override; - void DrawElement() override; - void UpdateElement() override; -}; -} // namespace AdvancedResolutionSettings diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index de9e157c3..4340be330 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -1,5 +1,4 @@ #include "TimeSplits.h" -#include "soh/SohGui/UIWidgets.hpp" #include "soh/Enhancements/gameplaystats.h" #include "soh/SaveManager.h" #include "soh/util.h" @@ -14,12 +13,16 @@ #include "soh/Enhancements/debugger/debugSaveEditor.h" #include "soh_assets.h" #include "assets/textures/parameter_static/parameter_static.h" +#include +#include "soh/SohGui/UIWidgets.hpp" extern "C" { extern SaveContext gSaveContext; extern PlayState* gPlayState; } +using namespace UIWidgets; + // ImVec4 Colors #define COLOR_WHITE ImVec4(1.00f, 1.00f, 1.00f, 1.00f) #define COLOR_LIGHT_RED ImVec4(1.0f, 0.05f, 0.0f, 1.0f) @@ -636,59 +639,60 @@ void TimeSplitsDrawSplitsList() { uint32_t dragIndex = 0; ImGui::BeginChild("SplitTable", ImVec2(0.0f, ImGui::GetWindowHeight() - 128.0f)); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4, 0)); - ImGui::BeginTable("Splits", 5, ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable); - ImGui::TableSetupColumn("Item Image", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 34.0f); - ImGui::TableSetupColumn("Item Name"); - ImGui::TableSetupColumn("Current Time"); - ImGui::TableSetupColumn("+/-"); - ImGui::TableSetupColumn("Prev. Best"); - ImGui::TableHeadersRow(); + if (ImGui::BeginTable("Splits", 5, ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable)) { + ImGui::TableSetupColumn("Item Image", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 34.0f); + ImGui::TableSetupColumn("Item Name"); + ImGui::TableSetupColumn("Current Time"); + ImGui::TableSetupColumn("+/-"); + ImGui::TableSetupColumn("Prev. Best"); + ImGui::TableHeadersRow(); - SplitsPushImageButtonStyle(); - for (auto& split : splitList) { - ImGui::TableNextColumn(); - TimeSplitsSplitBestTimeDisplay(split); - - ImGui::PushID(split.splitID); - if (split.splitTimeStatus == SPLIT_STATUS_ACTIVE) { - ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, IM_COL32(47, 79, 90, 255)); + SplitsPushImageButtonStyle(); + for (auto& split : splitList) { + ImGui::TableNextColumn(); + TimeSplitsSplitBestTimeDisplay(split); + + ImGui::PushID(split.splitID); + if (split.splitTimeStatus == SPLIT_STATUS_ACTIVE) { + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, IM_COL32(47, 79, 90, 255)); + } + TimeSplitsGetImageSize(split.splitID); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(imagePadding, imagePadding)); + auto ret = ImGui::ImageButton(split.splitImage.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage), + imageSize, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), split.splitTint); + ImGui::PopStyleVar(); + if (ret) { + TimeSplitsSkipSplit(dragIndex); + } + HandleDragAndDrop(splitList, dragIndex, split.splitName); + ImGui::TableNextColumn(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 5.0f)); + ImGui::AlignTextToFramePadding(); + ImGui::TextWrapped("%s", split.splitName.c_str()); + ImGui::TableNextColumn(); + // Current Time + ImGui::Text("%s", (split.splitTimeStatus == SPLIT_STATUS_ACTIVE) + ? formatTimestampTimeSplit(GAMEPLAYSTAT_TOTAL_TIME).c_str() : (split.splitTimeStatus == SPLIT_STATUS_COLLECTED) + ? formatTimestampTimeSplit(split.splitTimeCurrent).c_str() : "--:--:-"); + ImGui::TableNextColumn(); + // +/- Difference + ImGui::TextColored(splitTimeColor, "%s", formatTimestampTimeSplit(splitBestTimeDisplay).c_str()); + ImGui::TableNextColumn(); + // Previous Best + ImGui::Text("%s", (split.splitTimePreviousBest != 0) ? formatTimestampTimeSplit(split.splitTimePreviousBest).c_str() : "--:--:-"); + ImGui::PopID(); + ImGui::PopStyleVar(1); + + dragIndex++; } - TimeSplitsGetImageSize(split.splitID); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(imagePadding, imagePadding)); - auto ret = ImGui::ImageButton(split.splitImage.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(split.splitImage), - imageSize, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), split.splitTint); - ImGui::PopStyleVar(); - if (ret) { - TimeSplitsSkipSplit(dragIndex); - } - HandleDragAndDrop(splitList, dragIndex, split.splitName); - ImGui::TableNextColumn(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 5.0f)); - ImGui::AlignTextToFramePadding(); - ImGui::TextWrapped("%s", split.splitName.c_str()); - ImGui::TableNextColumn(); - // Current Time - ImGui::Text("%s", (split.splitTimeStatus == SPLIT_STATUS_ACTIVE) - ? formatTimestampTimeSplit(GAMEPLAYSTAT_TOTAL_TIME).c_str() : (split.splitTimeStatus == SPLIT_STATUS_COLLECTED) - ? formatTimestampTimeSplit(split.splitTimeCurrent).c_str() : "--:--:-"); - ImGui::TableNextColumn(); - // +/- Difference - ImGui::TextColored(splitTimeColor, "%s", formatTimestampTimeSplit(splitBestTimeDisplay).c_str()); - ImGui::TableNextColumn(); - // Previous Best - ImGui::Text("%s", (split.splitTimePreviousBest != 0) ? formatTimestampTimeSplit(split.splitTimePreviousBest).c_str() : "--:--:-"); - ImGui::PopID(); - ImGui::PopStyleVar(1); + SplitsPopImageButtonStyle(); - dragIndex++; + TimeSplitsPostDragAndDrop(); + + ImGui::EndTable(); } - SplitsPopImageButtonStyle(); - - TimeSplitsPostDragAndDrop(); - - ImGui::PopStyleVar(1); - ImGui::EndTable(); + ImGui::PopStyleVar(); ImGui::EndChild(); } @@ -769,7 +773,7 @@ void TimeSplitsDrawItemList(uint32_t type) { } void TimeSplitsUpdateWindowSize() { - timeSplitsWindowSize = CVarGetFloat(CVAR_ENHANCEMENT("TimeSplits.WindowSize"), 0); + timeSplitsWindowSize = CVarGetFloat(CVAR_ENHANCEMENT("TimeSplits.WindowScale"), 0); if (timeSplitsWindowSize < 1.0f) { timeSplitsWindowSize = 1.0f; } @@ -777,24 +781,13 @@ void TimeSplitsUpdateWindowSize() { void TimeSplitsDrawOptionsMenu() { ImGui::SeparatorText("Window Options"); - if (ImGui::ColorEdit4("Background Color", (float*)&windowColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) { - Color_RGBA8 color; - color.r = windowColor.x * 255.0; - color.g = windowColor.y * 255.0; - color.b = windowColor.z * 255.0; - color.a = windowColor.w * 255.0; - CVarSetColor("TimeSplits.WindowColor", color); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - ImGui::SameLine(); - if (ImGui::Button("Reset")) { - windowColor = { 0.0f, 0.0f, 0.0f, 1.0f }; - CVarSetColor("TimeSplits.WindowColor", {0, 0, 0, 1}); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + Color_RGBA8 defaultColor = { 0, 0, 0, 255 }; + if (CVarColorPicker("Background Color", CVAR_ENHANCEMENT("TimeSplits.WindowColor"), defaultColor, true, 0, THEME_COLOR)) { + windowColor = VecFromRGBA8(CVarGetColor(CVAR_ENHANCEMENT("TimeSplits.WindowColor.Value"), defaultColor)); } - if (UIWidgets::PaddedEnhancementSliderFloat("Window Size: %.1fx", "##windowSize", - CVAR_ENHANCEMENT("TimeSplits.WindowSize"), 1.0f, 3.0f, "", 1.0f, false, true, true, false)) { + if (CVarSliderFloat("Window Scale", CVAR_ENHANCEMENT("TimeSplits.WindowScale"), + FloatSliderOptions().Min(1.0f).Max(3.0f).DefaultValue(1.0f).Format("%.1fx").Size({300.0f, 0.0f}).Step(0.1f).Color(THEME_COLOR))) { TimeSplitsUpdateWindowSize(); } @@ -802,55 +795,44 @@ void TimeSplitsDrawOptionsMenu() { ImGui::Text("New List Name: "); ImGui::PushItemWidth(150.0f); + PushStyleInput(THEME_COLOR); ImGui::InputText("##listName", listNameBuf, 25); + PopStyleInput(); ImGui::PopItemWidth(); ImGui::SameLine(); - if (ImGui::Button("Create List")) { + if (Button("Create List", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { TimeSplitsFileManagement(SPLIT_ACTION_SAVE, listNameBuf, splitList); } UIWidgets::PaddedSeparator(); TimeSplitsFileManagement(SPLIT_ACTION_COLLECT, "", emptyList); static uint32_t selectedItem = 0; - static std::string listItem = keys[0]; ImGui::Text("Select List to Load: "); ImGui::PushItemWidth(150.0f); - if (ImGui::BeginCombo("##listEntries", keys[selectedItem].c_str())) { - for (int i = 0; i < keys.size(); i++) { - bool isSelected = (selectedItem == i); - if (ImGui::Selectable(keys[i].c_str(), isSelected)) { - selectedItem = i; - listItem = keys[i].c_str(); - } - if (isSelected) { - ImGui::SetItemDefaultFocus(); - } - } - ImGui::EndCombo(); - } + Combobox("", &selectedItem, keys, ComboboxOptions().Color(THEME_COLOR).LabelPosition(LabelPosition::Near)); ImGui::PopItemWidth(); ImGui::SameLine(); - if (ImGui::Button("Load List")) { + if (Button("Load List", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { TimeSplitsFileManagement(SPLIT_ACTION_LOAD, keys[selectedItem].c_str(), emptyList); } ImGui::SameLine(); - if (ImGui::Button("Save List")) { + if (Button("Save List", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { TimeSplitsFileManagement(SPLIT_ACTION_SAVE, keys[selectedItem].c_str(), splitList); } ImGui::SameLine(); - if (ImGui::Button("Delete List")) { + if (Button("Delete List", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { TimeSplitsFileManagement(SPLIT_ACTION_DELETE, keys[selectedItem].c_str(), emptyList); } - UIWidgets::PaddedSeparator(); + UIWidgets::Separator(true, true, ImGui::GetStyle().ItemSpacing.y, ImGui::GetStyle().ItemSpacing.y); - if (ImGui::Button("New Attempt")) { + if (Button("New Attempt", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { for (auto& data : splitList) { data.splitTimeStatus = SPLIT_STATUS_INACTIVE; } splitList[0].splitTimeStatus = SPLIT_STATUS_ACTIVE; } ImGui::SameLine(); - if (ImGui::Button("Update Splits")) { + if (Button("Update Splits", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { TimeSplitsFileManagement(SPLIT_ACTION_UPDATE, keys[selectedItem].c_str(), splitList); } } @@ -864,75 +846,80 @@ void TimeSplitsRemoveSplitEntry(uint32_t index) { void TimeSplitsDrawManageList() { uint32_t index = 0; - ImGui::BeginTable("List Management", 2, ImGuiTableFlags_BordersInnerV); - ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthFixed, 60.0f); - ImGui::TableSetupColumn("Options", ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::BeginChild("SplitTable", ImVec2(0.0f, ImGui::GetWindowHeight() - 128.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4, 0)); + if (ImGui::BeginTable("List Management", 2, ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthFixed, 120.0f); + ImGui::TableSetupColumn("Options", ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f)); - ImGui::TableNextColumn(); - ImGui::BeginTabBar("List Preview"); - if (ImGui::BeginTabItem("Preview")) { - ImGui::BeginChild("PreviewChild"); - for (auto& data : splitList) { - float availableWidth = ImGui::GetContentRegionAvail().x; - float imageWidth = 38.0f; // Width of your image button - float offsetX = (availableWidth - imageWidth) * 0.5f; // Centering offset + ImGui::TableNextColumn(); + ImGui::BeginTabBar("List Preview"); + if (ImGui::BeginTabItem("Preview")) { + ImGui::BeginChild("PreviewChild"); + for (auto& data : splitList) { + float availableWidth = ImGui::GetContentRegionAvail().x; + float imageWidth = 38.0f; // Width of your image button + float offsetX = (availableWidth - imageWidth) * 0.5f; // Centering offset - if (offsetX > 0.0f) { - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX); // Apply the offset to center + if (offsetX > 0.0f) { + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX); // Apply the offset to center + } + TimeSplitsGetImageSize(data.splitID); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(imagePadding, imagePadding)); + auto ret = ImGui::ImageButton(data.splitImage.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(data.splitImage), + imageSize, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), data.splitTint); + ImGui::PopStyleVar(); + if (ret) { + removeIndex = index; + } + HandleDragAndDrop(splitList, index, splitList[index].splitName); + index++; } - TimeSplitsGetImageSize(data.splitID); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(imagePadding, imagePadding)); - auto ret = ImGui::ImageButton(data.splitImage.c_str(), Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(data.splitImage), - imageSize, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), data.splitTint); - ImGui::PopStyleVar(); - if (ret) { - removeIndex = index; - } - HandleDragAndDrop(splitList, index, splitList[index].splitName); - index++; + TimeSplitsRemoveSplitEntry(removeIndex); + ImGui::EndChild(); + ImGui::EndTabItem(); } - TimeSplitsRemoveSplitEntry(removeIndex); - ImGui::EndChild(); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - - ImGui::PopStyleColor(3); - ImGui::TableNextColumn(); - ImGui::BeginTabBar("List Options"); - if (ImGui::BeginTabItem("Equipment")) { - TimeSplitsDrawItemList(SPLIT_TYPE_EQUIPMENT); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Inventory")) { - TimeSplitsDrawItemList(SPLIT_TYPE_ITEM); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Quest")) { - TimeSplitsDrawItemList(SPLIT_TYPE_QUEST); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Entrances")) { - TimeSplitsDrawItemList(SPLIT_TYPE_ENTRANCE); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Bosses")) { - TimeSplitsDrawItemList(SPLIT_TYPE_BOSS); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Miscellaneous")) { - TimeSplitsDrawItemList(SPLIT_TYPE_MISC); - ImGui::EndTabItem(); - } + ImGui::EndTabBar(); - TimeSplitsPostDragAndDrop(); + ImGui::PopStyleColor(3); + ImGui::TableNextColumn(); + ImGui::BeginTabBar("List Options"); + if (ImGui::BeginTabItem("Equipment")) { + TimeSplitsDrawItemList(SPLIT_TYPE_EQUIPMENT); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Inventory")) { + TimeSplitsDrawItemList(SPLIT_TYPE_ITEM); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Quest")) { + TimeSplitsDrawItemList(SPLIT_TYPE_QUEST); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Entrances")) { + TimeSplitsDrawItemList(SPLIT_TYPE_ENTRANCE); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Bosses")) { + TimeSplitsDrawItemList(SPLIT_TYPE_BOSS); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Miscellaneous")) { + TimeSplitsDrawItemList(SPLIT_TYPE_MISC); + ImGui::EndTabItem(); + } - ImGui::EndTabBar(); - ImGui::EndTable(); + TimeSplitsPostDragAndDrop(); + + ImGui::EndTabBar(); + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + ImGui::EndChild(); } void InitializeSplitDataFile() { @@ -954,14 +941,9 @@ static bool initialized = false; void TimeSplitWindow::DrawElement() { ImGui::SetWindowFontScale(timeSplitsWindowSize); - if (!initialized) { - Color_RGBA8 defaultColour = {0, 0, 0, 255}; - Color_RGBA8 color = CVarGetColor("TimeSplits.WindowColor", defaultColour); - windowColor = {(float)color.r / 255.0f, (float)color.g / 255.0f, (float)color.b / 255.0f, (float)color.a / 255.0f}; - InitializeSplitDataFile(); - initialized = true; - } + ImGui::PushFont(OTRGlobals::Instance->fontMonoLargest); + PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("Split Tabs")) { if (ImGui::BeginTabItem("Splits")) { TimeSplitsDrawSplitsList(); @@ -977,6 +959,8 @@ void TimeSplitWindow::DrawElement() { } ImGui::EndTabBar(); } + PopStyleTabs(); + ImGui::PopFont(); } void TimeSplitWindow::InitElement() { @@ -984,6 +968,9 @@ void TimeSplitWindow::InitElement() { Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("SPECIAL_TRIFORCE_PIECE_WHITE", gWTriforcePieceTex, ImVec4(1, 1, 1, 1)); Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture("SPECIAL_SPLIT_ENTRANCE", gSplitEntranceTex, ImVec4(1, 1, 1, 1)); + Color_RGBA8 defaultColour = {0, 0, 0, 255}; + windowColor = VecFromRGBA8(CVarGetColor(CVAR_ENHANCEMENT("TimeSplits.WindowColor.Value"), defaultColour)); + InitializeSplitDataFile(); GameInteractor::Instance->RegisterGameHook([](u8 item) { if (item != ITEM_SKULL_TOKEN) { diff --git a/soh/soh/Network/CrowdControl/CrowdControl.cpp b/soh/soh/Network/CrowdControl/CrowdControl.cpp index 184d6cad2..34532f73b 100644 --- a/soh/soh/Network/CrowdControl/CrowdControl.cpp +++ b/soh/soh/Network/CrowdControl/CrowdControl.cpp @@ -764,69 +764,4 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) { return effect; } - -void CrowdControl::DrawMenu() { - ImGui::PushID("CrowdControl"); - - static std::string host = CVarGetString(CVAR_REMOTE_CROWD_CONTROL("Host"), "127.0.0.1"); - static uint16_t port = CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Port"), 43384); - bool isFormValid = !SohUtils::IsStringEmpty(host) && port > 1024 && port < 65535; - - ImGui::SeparatorText("Crowd Control"); - UIWidgets::Tooltip( - "Crowd Control is a platform that allows viewers to interact " - "with a streamer's game in real time.\n" - "\n" - "Click the question mark to copy the link to the Crowd Control " - "website to your clipboard." - ); - if (ImGui::IsItemClicked()) { - ImGui::SetClipboardText("https://crowdcontrol.live"); - } - - ImGui::BeginDisabled(isEnabled); - ImGui::Text("Host & Port"); - if (UIWidgets::InputString("##Host", &host)) { - CVarSetString(CVAR_REMOTE_CROWD_CONTROL("Host"), host.c_str()); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - - ImGui::SameLine(); - ImGui::PushItemWidth(ImGui::GetFontSize() * 5); - if (ImGui::InputScalar("##Port", ImGuiDataType_U16, &port)) { - CVarSetInteger(CVAR_REMOTE_CROWD_CONTROL("Port"), port); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - ImGui::PopItemWidth(); - ImGui::EndDisabled(); - - ImGui::Spacing(); - - ImGui::BeginDisabled(!isFormValid); - const char* buttonLabel = isEnabled ? "Disable" : "Enable"; - if (ImGui::Button(buttonLabel, ImVec2(-1.0f, 0.0f))) { - if (isEnabled) { - CVarClear(CVAR_REMOTE_CROWD_CONTROL("Enabled")); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - Disable(); - } else { - CVarSetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 1); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - Enable(); - } - } - ImGui::EndDisabled(); - - if (isEnabled) { - ImGui::Spacing(); - if (isConnected) { - ImGui::Text("Connected"); - } else { - ImGui::Text("Connecting..."); - } - } - - ImGui::PopID(); -} - #endif diff --git a/soh/soh/Network/CrowdControl/CrowdControl.h b/soh/soh/Network/CrowdControl/CrowdControl.h index 534197807..8aff1c96e 100644 --- a/soh/soh/Network/CrowdControl/CrowdControl.h +++ b/soh/soh/Network/CrowdControl/CrowdControl.h @@ -84,7 +84,6 @@ class CrowdControl : public Network { void OnIncomingJson(nlohmann::json payload); void OnConnected(); void OnDisconnected(); - void DrawMenu(); }; #endif // __cplusplus diff --git a/soh/soh/Network/Sail/Sail.cpp b/soh/soh/Network/Sail/Sail.cpp index f9ae7b313..272214115 100644 --- a/soh/soh/Network/Sail/Sail.cpp +++ b/soh/soh/Network/Sail/Sail.cpp @@ -501,72 +501,4 @@ void Sail::RegisterHooks() { }); } -void Sail::DrawMenu() { - ImGui::PushID("Sail"); - - static std::string host = CVarGetString(CVAR_REMOTE_SAIL("Host"), "127.0.0.1"); - static uint16_t port = CVarGetInteger(CVAR_REMOTE_SAIL("Port"), 43384); - bool isFormValid = !SohUtils::IsStringEmpty(host) && port > 1024 && port < 65535; - - ImGui::SeparatorText("Sail"); - UIWidgets::Tooltip( - "Sail is a networking protocol designed to facilitate remote " - "control of the Ship of Harkinian client. It is intended to " - "be utilized alongside a Sail server, for which we provide a " - "few straightforward implementations on our GitHub. The current " - "implementations available allow integration with Twitch chat " - "and SAMMI Bot, feel free to contribute your own!\n" - "\n" - "Click the question mark to copy the link to the Sail Github " - "page to your clipboard." - ); - if (ImGui::IsItemClicked()) { - ImGui::SetClipboardText("https://github.com/HarbourMasters/sail"); - } - - ImGui::BeginDisabled(isEnabled); - ImGui::Text("Host & Port"); - if (UIWidgets::InputString("##Host", &host)) { - CVarSetString(CVAR_REMOTE_SAIL("Host"), host.c_str()); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - - ImGui::SameLine(); - ImGui::PushItemWidth(ImGui::GetFontSize() * 5); - if (ImGui::InputScalar("##Port", ImGuiDataType_U16, &port)) { - CVarSetInteger(CVAR_REMOTE_SAIL("Port"), port); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - ImGui::PopItemWidth(); - ImGui::EndDisabled(); - - ImGui::Spacing(); - - ImGui::BeginDisabled(!isFormValid); - const char* buttonLabel = isEnabled ? "Disable" : "Enable"; - if (ImGui::Button(buttonLabel, ImVec2(-1.0f, 0.0f))) { - if (isEnabled) { - CVarClear(CVAR_REMOTE_SAIL("Enabled")); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - Disable(); - } else { - CVarSetInteger(CVAR_REMOTE_SAIL("Enabled"), 1); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - Enable(); - } - } - ImGui::EndDisabled(); - - if (isEnabled) { - ImGui::Spacing(); - if (isConnected) { - ImGui::Text("Connected"); - } else { - ImGui::Text("Connecting..."); - } - } - - ImGui::PopID(); -} - #endif // ENABLE_REMOTE_CONTROL diff --git a/soh/soh/Network/Sail/Sail.h b/soh/soh/Network/Sail/Sail.h index 8c7c9d55e..2a41b8723 100644 --- a/soh/soh/Network/Sail/Sail.h +++ b/soh/soh/Network/Sail/Sail.h @@ -18,7 +18,6 @@ class Sail : public Network { void OnIncomingJson(nlohmann::json payload); void OnConnected(); void OnDisconnected(); - void DrawMenu(); }; #endif // __cplusplus diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 2acc01d7f..0b8d0030a 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -40,6 +40,7 @@ #include "z64.h" #include "macros.h" #include "Fonts.h" +#include "window/gui/resource/Font.h" #include #include "Enhancements/custom-message/CustomMessageManager.h" #include "Enhancements/presets.h" @@ -345,7 +346,7 @@ OTRGlobals::OTRGlobals() { context->InitCrashHandler(); context->InitConsole(); - auto sohInputEditorWindow = std::make_shared(CVAR_WINDOW("ControllerConfiguration"), "Controller Configuration"); + auto sohInputEditorWindow = std::make_shared(CVAR_WINDOW("ControllerConfiguration"), "Configure Controller"); auto sohFast3dWindow = std::make_shared(std::vector>({sohInputEditorWindow})); context->InitWindow(sohFast3dWindow); @@ -399,9 +400,14 @@ OTRGlobals::OTRGlobals() { hasMasterQuest = hasOriginal = false; previousImGuiScale = defaultImGuiScale; - defaultFontSmaller = CreateDefaultFontWithSize(10.0f); - defaultFontLarger = CreateDefaultFontWithSize(16.0f); - defaultFontLargest = CreateDefaultFontWithSize(20.0f); + + fontMono = CreateFontWithSize(16.0f, "fonts/Inconsolata-Regular.ttf"); + fontMonoLarger = CreateFontWithSize(20.0f, "fonts/Inconsolata-Regular.ttf"); + fontMonoLargest = CreateFontWithSize(24.0f, "fonts/Inconsolata-Regular.ttf"); + fontStandard = CreateFontWithSize(16.0f, "fonts/Montserrat-Regular.ttf"); + fontStandardLarger = CreateFontWithSize(20.0f, "fonts/Montserrat-Regular.ttf"); + fontStandardLargest = CreateFontWithSize(24.0f, "fonts/Montserrat-Regular.ttf"); + ImGui::GetIO().FontDefault = fontMono; ScaleImGui(); // Move the camera strings from read only memory onto the heap (writable memory) @@ -1390,9 +1396,12 @@ void RunCommands(Gfx* Commands, const std::vector // Process window events for resize, mouse, keyboard events wnd->HandleEvents(); + UIWidgets::Colors themeColor = static_cast(CVarGetInteger(CVAR_SETTING("Menu.Theme"), UIWidgets::Colors::LightBlue)); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIWidgets::ColorValues.at(themeColor)); for (const auto& m : mtx_replacements) { wnd->DrawAndRunGraphicsCommands(Commands, m); } + ImGui::PopStyleColor(); } // C->C++ Bridge @@ -1559,6 +1568,37 @@ extern "C" SoundFontSample* ReadCustomSample(const char* path) { */ } +ImFont* OTRGlobals::CreateFontWithSize(float size, std::string fontPath) { + auto mImGuiIo = &ImGui::GetIO(); + ImFont* font; + if (fontPath == "") { + ImFontConfig fontCfg = ImFontConfig(); + fontCfg.OversampleH = fontCfg.OversampleV = 1; + fontCfg.PixelSnapH = true; + fontCfg.SizePixels = size; + font = mImGuiIo->Fonts->AddFontDefault(&fontCfg); + } else { + auto initData = std::make_shared(); + initData->Format = RESOURCE_FORMAT_BINARY; + initData->Type = static_cast(RESOURCE_TYPE_FONT); + initData->ResourceVersion = 0; + initData->Path = fontPath; + std::shared_ptr fontData = std::static_pointer_cast( + Ship::Context::GetInstance()->GetResourceManager()->LoadResource(fontPath, false, initData)); + font = mImGuiIo->Fonts->AddFontFromMemoryTTF(fontData->Data, fontData->DataSize, size); + } + // FontAwesome fonts need to have their sizes reduced by 2.0f/3.0f in order to align correctly + float iconFontSize = size * 2.0f / 3.0f; + static const ImWchar sIconsRanges[] = { ICON_MIN_FA, ICON_MAX_16_FA, 0 }; + ImFontConfig iconsConfig; + iconsConfig.MergeMode = true; + iconsConfig.PixelSnapH = true; + iconsConfig.GlyphMinAdvanceX = iconFontSize; + mImGuiIo->Fonts->AddFontFromMemoryCompressedBase85TTF(fontawesome_compressed_data_base85, iconFontSize, + &iconsConfig, sIconsRanges); + return font; +} + std::filesystem::path GetSaveFile(std::shared_ptr Conf) { const std::string fileName = Conf->GetString("Game.SaveName", Ship::Context::GetPathRelativeToAppDirectory("oot_save.sav")); std::filesystem::path saveFile = std::filesystem::absolute(fileName); diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index f056cad5e..0a8d6595e 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -54,6 +54,13 @@ class OTRGlobals { ImFont* defaultFontLarger; ImFont* defaultFontLargest; + ImFont* fontStandard; + ImFont* fontStandardLarger; + ImFont* fontStandardLargest; + ImFont* fontMono; + ImFont* fontMonoLarger; + ImFont* fontMonoLargest; + OTRGlobals(); ~OTRGlobals(); @@ -69,6 +76,7 @@ class OTRGlobals { bool hasMasterQuest; bool hasOriginal; ImFont* CreateDefaultFontWithSize(float size); + ImFont* CreateFontWithSize(float size, std::string fontPath); }; #endif diff --git a/soh/soh/ShipUtils.cpp b/soh/soh/ShipUtils.cpp new file mode 100644 index 000000000..4760dbcd0 --- /dev/null +++ b/soh/soh/ShipUtils.cpp @@ -0,0 +1,115 @@ +#include "ShipUtils.h" +#include + +extern "C" { +#include "z64.h" +#include "functions.h" +#include "macros.h" + +extern float OTRGetAspectRatio(); + +//extern f32 sNESFontWidths[160]; +extern const char* fontTbl[156]; +//extern TexturePtr gItemIcons[131]; +//extern TexturePtr gQuestIcons[14]; +//extern TexturePtr gBombersNotebookPhotos[24]; +} + +constexpr f32 fourByThree = 4.0f / 3.0f; + +// Gets the additional ratio of the screen compared to the original 4:3 ratio, clamping to 1 if smaller +extern "C" f32 Ship_GetExtendedAspectRatioMultiplier() { + f32 currentRatio = OTRGetAspectRatio(); + return MAX(currentRatio / fourByThree, 1.0f); +} + +// Enables Extended Culling options on specific actors by applying an inverse ratio of the draw distance slider +// to the projected Z value of the actor. This tricks distance checks without having to replace hardcoded values. +// Requires that Ship_ExtendedCullingActorRestoreProjectedPos is called within the same function scope. +extern "C" void Ship_ExtendedCullingActorAdjustProjectedZ(Actor* actor) { + s32 multiplier = CVarGetInteger("gEnhancements.Graphics.IncreaseActorDrawDistance", 1); + multiplier = MAX(multiplier, 1); + if (multiplier > 1) { + actor->projectedPos.z /= multiplier; + } +} + +// Enables Extended Culling options on specific actors by applying an inverse ratio of the widescreen aspect ratio +// to the projected X value of the actor. This tricks distance checks without having to replace hardcoded values. +// Requires that Ship_ExtendedCullingActorRestoreProjectedPos is called within the same function scope. +extern "C" void Ship_ExtendedCullingActorAdjustProjectedX(Actor* actor) { + if (CVarGetInteger("gEnhancements.Graphics.ActorCullingAccountsForWidescreen", 0)) { + f32 ratioAdjusted = Ship_GetExtendedAspectRatioMultiplier(); + actor->projectedPos.x /= ratioAdjusted; + } +} + +// Restores the projectedPos values on the actor after modifications from the Extended Culling hacks +//extern "C" void Ship_ExtendedCullingActorRestoreProjectedPos(PlayState* play, Actor* actor) { +// f32 invW = 0.0f; +// Actor_GetProjectedPos(play, &actor->world.pos, &actor->projectedPos, &invW); +//} + +extern "C" bool Ship_IsCStringEmpty(const char* str) { + return str == NULL || str[0] == '\0'; +} + +// Build vertex coordinates for a quad command +// In order of top left, top right, bottom left, then bottom right +// Supports flipping the texture horizontally +extern "C" void Ship_CreateQuadVertexGroup(Vtx* vtxList, s32 xStart, s32 yStart, s32 width, s32 height, u8 flippedH) { + vtxList[0].v.ob[0] = xStart; + vtxList[0].v.ob[1] = yStart; + vtxList[0].v.tc[0] = (flippedH ? width : 0) << 5; + vtxList[0].v.tc[1] = 0 << 5; + + vtxList[1].v.ob[0] = xStart + width; + vtxList[1].v.ob[1] = yStart; + vtxList[1].v.tc[0] = (flippedH ? width * 2 : width) << 5; + vtxList[1].v.tc[1] = 0 << 5; + + vtxList[2].v.ob[0] = xStart; + vtxList[2].v.ob[1] = yStart + height; + vtxList[2].v.tc[0] = (flippedH ? width : 0) << 5; + vtxList[2].v.tc[1] = height << 5; + + vtxList[3].v.ob[0] = xStart + width; + vtxList[3].v.ob[1] = yStart + height; + vtxList[3].v.tc[0] = (flippedH ? width * 2 : width) << 5; + vtxList[3].v.tc[1] = height << 5; +} + +//extern "C" f32 Ship_GetCharFontWidthNES(u8 character) { +// u8 adjustedChar = character - ' '; +// +// if (adjustedChar >= ARRAY_COUNTU(sNESFontWidths)) { +// return 0.0f; +// } +// +// return sNESFontWidths[adjustedChar]; +//} + +//extern "C" TexturePtr Ship_GetCharFontTextureNES(u8 character) { +// u8 adjustedChar = character - ' '; +// +// if (adjustedChar >= ARRAY_COUNTU(sNESFontWidths)) { +// return (TexturePtr)gEmptyTexture; +// } +// +// return (TexturePtr)fontTbl[adjustedChar]; +//} + +//void LoadGuiTextures() { +// for (TexturePtr entry : gItemIcons) { +// const char* path = static_cast(entry); +// Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); +// } +// for (TexturePtr entry : gQuestIcons) { +// const char* path = static_cast(entry); +// Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); +// } +// for (TexturePtr entry : gBombersNotebookPhotos) { +// const char* path = static_cast(entry); +// Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); +// } +//} diff --git a/soh/soh/ShipUtils.h b/soh/soh/ShipUtils.h new file mode 100644 index 000000000..a1ac3081b --- /dev/null +++ b/soh/soh/ShipUtils.h @@ -0,0 +1,31 @@ +#ifndef SHIP_UTILS_H +#define SHIP_UTILS_H + +#include +//#include "PR/ultratypes.h" + +#ifdef __cplusplus + +void LoadGuiTextures(); + +extern "C" { +#endif + +struct PlayState; +struct Actor; + +f32 Ship_GetExtendedAspectRatioMultiplier(); +void Ship_ExtendedCullingActorAdjustProjectedZ(Actor* actor); +void Ship_ExtendedCullingActorAdjustProjectedX(Actor* actor); +void Ship_ExtendedCullingActorRestoreProjectedPos(PlayState* play, Actor* actor); + +bool Ship_IsCStringEmpty(const char* str); +void Ship_CreateQuadVertexGroup(Vtx* vtxList, s32 xStart, s32 yStart, s32 width, s32 height, u8 flippedH); +f32 Ship_GetCharFontWidthNES(u8 character); +//TexturePtr Ship_GetCharFontTextureNES(u8 character); + +#ifdef __cplusplus +} +#endif + +#endif // SHIP_UTILS_H diff --git a/soh/soh/SohGui/Menu.cpp b/soh/soh/SohGui/Menu.cpp new file mode 100644 index 000000000..a10fc4e06 --- /dev/null +++ b/soh/soh/SohGui/Menu.cpp @@ -0,0 +1,819 @@ +#include "Menu.h" +#include "UIWidgets.hpp" +#include "soh/OTRGlobals.h" +#include "soh/Enhancements/controls/SohInputEditorWindow.h" +#include "window/gui/GuiMenuBar.h" +#include "window/gui/GuiElement.h" +#include +#include +#include "variables.h" +#include + +extern "C" { +#include "z64.h" +#include "functions.h" +extern PlayState* gPlayState; +} +std::vector windowTypeSizes = { {} }; + +extern std::unordered_map warpPointSceneList; +extern void Warp(); + +namespace SohGui {} + +namespace Ship { +std::string disabledTempTooltip; +const char* disabledTooltip; +bool disabledValue = false; + +bool operator==(Color_RGB8 const& l, Color_RGB8 const& r) noexcept { + return l.r == r.r && l.g == r.g && l.b == r.b; +} + +bool operator==(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept { + return l.r == r.r && l.g == r.g && l.b == r.b && l.a == r.a; +} + +bool operator<(Color_RGB8 const& l, Color_RGB8 const& r) noexcept { + return (l.r < r.r && l.g <= r.g && l.b <= r.b) || (l.r <= r.r && l.g < r.g && l.b <= r.b) || + (l.r <= r.r && l.g <= r.g && l.b < r.b); +} + +bool operator<(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept { + return (l.r < r.r && l.g <= r.g && l.b <= r.b && l.a <= r.a) || + (l.r <= r.r && l.g < r.g && l.b <= r.b && l.a <= r.a) || + (l.r <= r.r && l.g <= r.g && l.b < r.b && l.a <= r.a) || + (l.r <= r.r && l.g <= r.g && l.b <= r.b && l.a < r.a); +} + +bool operator>(Color_RGB8 const& l, Color_RGB8 const& r) noexcept { + return (l.r > r.r && l.g >= r.g && l.b >= r.b) || (l.r >= r.r && l.g > r.g && l.b >= r.b) || + (l.r >= r.r && l.g >= r.g && l.b > r.b); +} + +bool operator>(Color_RGBA8 const& l, Color_RGBA8 const& r) noexcept { + return (l.r > r.r && l.g >= r.g && l.b >= r.b && l.a >= r.a) || + (l.r >= r.r && l.g > r.g && l.b >= r.b && l.a >= r.a) || + (l.r >= r.r && l.g >= r.g && l.b > r.b && l.a >= r.a) || + (l.r >= r.r && l.g >= r.g && l.b >= r.b && l.a > r.a); +} + +uint32_t GetVectorIndexOf(std::vector& vector, std::string value) { + return std::distance(vector.begin(), std::find(vector.begin(), vector.end(), value)); +} + +void Menu::InsertSidebarSearch() { + menuEntries["Settings"].sidebars.emplace("Search", searchSidebarEntry); + uint32_t curIndex = 0; + if (!Ship_IsCStringEmpty(CVarGetString(menuEntries["Settings"].sidebarCvar, ""))) { + curIndex = GetVectorIndexOf(menuEntries["Settings"].sidebarOrder, + CVarGetString(menuEntries["Settings"].sidebarCvar, "")); + } + menuEntries["Settings"].sidebarOrder.insert(menuEntries["Settings"].sidebarOrder.begin() + searchSidebarIndex, + "Search"); + if (curIndex > searchSidebarIndex) { + CVarSetString(menuEntries["Settings"].sidebarCvar, menuEntries["Settings"].sidebarOrder.at(curIndex).c_str()); + } +} + +void Menu::RemoveSidebarSearch() { + uint32_t curIndex = + GetVectorIndexOf(menuEntries["Settings"].sidebarOrder, CVarGetString(menuEntries["Settings"].sidebarCvar, "General")); + menuEntries["Settings"].sidebars.erase("Search"); + std::erase_if(menuEntries["Settings"].sidebarOrder, [](std::string& name) { return name == "Search"; }); + if (curIndex > searchSidebarIndex) { + curIndex--; + } else if (curIndex >= menuEntries["Settings"].sidebarOrder.size()) { + curIndex = menuEntries["Settings"].sidebarOrder.size() - 1; + } + CVarSetString(menuEntries["Settings"].sidebarCvar, menuEntries["Settings"].sidebarOrder.at(curIndex).c_str()); +} + +void Menu::UpdateWindowBackendObjects() { + Ship::WindowBackend runningWindowBackend = Ship::Context::GetInstance()->GetWindow()->GetWindowBackend(); + int32_t configWindowBackendId = Ship::Context::GetInstance()->GetConfig()->GetInt("Window.Backend.Id", -1); + if (Ship::Context::GetInstance()->GetWindow()->IsAvailableWindowBackend(configWindowBackendId)) { + configWindowBackend = static_cast(configWindowBackendId); + } else { + configWindowBackend = runningWindowBackend; + } + + availableWindowBackends = Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends(); + for (auto& backend : *availableWindowBackends) { + availableWindowBackendsMap[backend] = windowBackendsMap.at(backend); + } +} + +UIWidgets::Colors Menu::GetMenuThemeColor() { + return menuThemeIndex; +} + +Menu::Menu(const std::string& cVar, const std::string& name, uint8_t searchSidebarIndex_, + UIWidgets::Colors defaultThemeIndex_) + : GuiWindow(cVar, name), searchSidebarIndex(searchSidebarIndex_), defaultThemeIndex(defaultThemeIndex_) { +} + +void Menu::InitElement() { + popped = CVarGetInteger(CVAR_SETTING("Menu.Popout"), 0); + poppedSize.x = CVarGetInteger(CVAR_SETTING("Menu.PoppedWidth"), 1280); + poppedSize.y = CVarGetInteger(CVAR_SETTING("Menu.PoppedHeight"), 800); + poppedPos.x = CVarGetInteger(CVAR_SETTING("Menu.PoppedPos.x"), 0); + poppedPos.y = CVarGetInteger(CVAR_SETTING("Menu.PoppedPos.y"), 0); + + UpdateWindowBackendObjects(); +} + +void Menu::UpdateElement() { + menuThemeIndex = static_cast(CVarGetInteger(CVAR_SETTING("Menu.Theme"), defaultThemeIndex)); +} + +bool ModernMenuSidebarEntry(std::string label) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStyle& style = ImGui::GetStyle(); + ImVec2 pos = window->DC.CursorPos; + const ImGuiID sidebarId = window->GetID(std::string(label + "##Sidebar").c_str()); + ImVec2 labelSize = ImGui::CalcTextSize(label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true); + pos.y += style.FramePadding.y; + pos.x = window->WorkRect.GetCenter().x - labelSize.x / 2; + ImRect bb = { pos - style.FramePadding, pos + labelSize + style.FramePadding }; + ImGui::ItemSize(bb, style.FramePadding.y); + ImGui::ItemAdd(bb, sidebarId); + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, sidebarId, &hovered, &held); + if (pressed) { + ImGui::MarkItemEdited(sidebarId); + } + window->DrawList->AddRectFilled(pos - style.FramePadding, pos + labelSize + style.FramePadding, + ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive + : hovered ? ImGuiCol_ButtonHovered + : ImGuiCol_Button), + 3.0f); + UIWidgets::RenderText(pos, label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true); + return pressed; +} + +bool ModernMenuHeaderEntry(std::string label) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStyle& style = ImGui::GetStyle(); + ImVec2 pos = window->DC.CursorPos; + const ImGuiID headerId = window->GetID(std::string(label + "##Header").c_str()); + ImVec2 labelSize = ImGui::CalcTextSize(label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true); + ImRect bb = { pos, pos + labelSize + style.FramePadding * 2 }; + ImGui::ItemSize(bb, style.FramePadding.y); + ImGui::ItemAdd(bb, headerId); + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, headerId, &hovered, &held); + window->DrawList->AddRectFilled(bb.Min, bb.Max, + ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive + : hovered ? ImGuiCol_ButtonHovered + : ImGuiCol_Button), + 3.0f); + pos += style.FramePadding; + UIWidgets::RenderText(pos, label.c_str(), ImGui::FindRenderedTextEnd(label.c_str()), true); + return pressed; +} + +uint32_t Menu::DrawSearchResults(std::string& menuSearchText) { + ImGui::BeginChild("Search Results"); + int searchCount = 0; + for (auto& menuLabel : menuOrder) { + auto& menuEntry = menuEntries.at(menuLabel); + for (auto& sidebarLabel : menuEntry.sidebarOrder) { + auto& sidebar = menuEntry.sidebars[sidebarLabel]; + for (int i = 0; i < sidebar.columnWidgets.size(); i++) { + auto& column = sidebar.columnWidgets.at(i); + for (auto& info : column) { + if (info.type == WIDGET_SEARCH || info.type == WIDGET_SEPARATOR || info.type == WIDGET_SEPARATOR_TEXT || + info.isHidden) { + continue; + } + const char* tooltip = info.options->tooltip; + std::string widgetStr = std::string(info.name) + std::string(tooltip != NULL ? tooltip : ""); + std::transform(menuSearchText.begin(), menuSearchText.end(), menuSearchText.begin(), ::tolower); + menuSearchText.erase(std::remove(menuSearchText.begin(), menuSearchText.end(), ' '), + menuSearchText.end()); + std::transform(widgetStr.begin(), widgetStr.end(), widgetStr.begin(), ::tolower); + widgetStr.erase(std::remove(widgetStr.begin(), widgetStr.end(), ' '), widgetStr.end()); + if (widgetStr.find(menuSearchText) != std::string::npos) { + MenuDrawItem(info, 90 / sidebar.columnCount, menuThemeIndex); + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(UIWidgets::Colors::Gray)); + std::string origin = fmt::format(" ({} -> {}, Col {})", menuEntry.label, sidebarLabel, i + 1); + ImGui::Text("%s", origin.c_str()); + ImGui::PopStyleColor(); + searchCount++; + } + } + } + } + } + return searchCount; +} + +void Menu::AddMenuEntry(std::string entryName, const char* entryCvar) { + menuEntries.emplace(entryName, MainMenuEntry{ entryName, entryCvar }); + menuOrder.push_back(entryName); +} + +std::unordered_map& Menu::GetDisabledMap() { + return disabledMap; +} + +void Menu::MenuDrawItem(WidgetInfo& widget, uint32_t width, UIWidgets::Colors menuThemeIndex) { + disabledTempTooltip = "This setting is disabled because: \n\n"; + disabledValue = false; + disabledTooltip = " "; + + if (widget.preFunc != nullptr) { + widget.ResetDisables(); + widget.preFunc(widget); + if (widget.isHidden) { + return; + } + if (!widget.activeDisables.empty()) { + widget.options->disabled = true; + for (auto option : widget.activeDisables) { + disabledTempTooltip += std::string("- ") + disabledMap.at(option).reason + std::string("\n"); + } + widget.options->disabledTooltip = disabledTempTooltip.c_str(); + } + } + + if (widget.sameLine) { + ImGui::SameLine(); + } + + try { + switch (widget.type) { + case WIDGET_CHECKBOX: { + bool* pointer = std::get(widget.valuePointer); + if (pointer == nullptr) { + SPDLOG_ERROR("Checkbox Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::Checkbox(UIWidgets::WrappedText(widget.name.c_str(), width).c_str(), pointer, + *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_CVAR_CHECKBOX: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::CVarCheckbox(UIWidgets::WrappedText(widget.name.c_str(), width).c_str(), widget.cVar, + *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + }; + } break; + case WIDGET_AUDIO_BACKEND: { + auto currentAudioBackend = Ship::Context::GetInstance()->GetAudio()->GetCurrentAudioBackend(); + UIWidgets::ComboboxOptions options = {}; + options.color = menuThemeIndex; + options.tooltip = "Sets the audio API used by the game. Requires a relaunch to take effect."; + options.disabled = Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1; + options.disabledTooltip = "Only one audio API is available on this platform."; + if (UIWidgets::Combobox("Audio API", ¤tAudioBackend, audioBackendsMap, options)) { + Ship::Context::GetInstance()->GetAudio()->SetCurrentAudioBackend(currentAudioBackend); + } + } break; + case WIDGET_VIDEO_BACKEND: { + UIWidgets::ComboboxOptions options = {}; + options.color = menuThemeIndex; + options.tooltip = "Sets the renderer API used by the game."; + options.disabled = availableWindowBackends->size() <= 1; + options.disabledTooltip = "Only one renderer API is available on this platform."; + if (UIWidgets::Combobox("Renderer API (Needs reload)", &configWindowBackend, availableWindowBackendsMap, + options)) { + Ship::Context::GetInstance()->GetConfig()->SetInt("Window.Backend.Id", + (int32_t)(configWindowBackend)); + Ship::Context::GetInstance()->GetConfig()->SetString("Window.Backend.Name", + windowBackendsMap.at(configWindowBackend)); + Ship::Context::GetInstance()->GetConfig()->Save(); + UpdateWindowBackendObjects(); + } + } break; + case WIDGET_SEPARATOR: { + ImGui::Separator(); + } break; + case WIDGET_SEPARATOR_TEXT: { + if (widget.options->color != UIWidgets::Colors::NoColor) { + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color)); + } + ImGui::SeparatorText(widget.name.c_str()); + if (widget.options->color != UIWidgets::Colors::NoColor) { + ImGui::PopStyleColor(); + } + } break; + case WIDGET_TEXT: { + if (widget.options->color != UIWidgets::Colors::NoColor) { + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color)); + } + ImGui::AlignTextToFramePadding(); + ImGui::TextWrapped("%s", widget.name.c_str()); + if (widget.options->color != UIWidgets::Colors::NoColor) { + ImGui::PopStyleColor(); + } + } break; + case WIDGET_COMBOBOX: { + int32_t* pointer = std::get(widget.valuePointer); + if (pointer == nullptr) { + SPDLOG_ERROR("Combobox Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::Combobox(widget.name.c_str(), pointer, options->comboMap, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + }; + } break; + case WIDGET_CVAR_COMBOBOX: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::CVarCombobox(widget.name.c_str(), widget.cVar, options->comboMap, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_SLIDER_INT: { + int32_t* pointer = std::get(widget.valuePointer); + if (pointer == nullptr) { + SPDLOG_ERROR("int32 Slider Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::SliderInt(widget.name.c_str(), pointer, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + }; + } break; + case WIDGET_CVAR_SLIDER_INT: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::CVarSliderInt(widget.name.c_str(), widget.cVar, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + }; + } break; + case WIDGET_SLIDER_FLOAT: { + float* pointer = std::get(widget.valuePointer); + + if (pointer == nullptr) { + SPDLOG_ERROR("float Slider Widget requires a value pointer, currently nullptr"); + assert(false); + return; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::SliderFloat(widget.name.c_str(), pointer, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_CVAR_SLIDER_FLOAT: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::CVarSliderFloat(widget.name.c_str(), widget.cVar, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_BUTTON: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::Button(widget.name.c_str(), *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; + case WIDGET_CUSTOM: { + if (widget.customFunction != nullptr) { + widget.customFunction(widget); + } + } break; + case WIDGET_WINDOW_BUTTON: { + if (widget.windowName == nullptr || widget.windowName[0] == '\0') { + std::string msg = + fmt::format("Error drawing window contents for {}: windowName not defined", widget.name); + SPDLOG_ERROR(msg.c_str()); + break; + } + auto window = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow(widget.windowName); + if (!window) { + std::string msg = + fmt::format("Error drawing window contents: windowName {} does not exist", widget.windowName); + SPDLOG_ERROR(msg.c_str()); + break; + } + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (options->showButton) { + UIWidgets::WindowButton(widget.name.c_str(), widget.cVar, window, *options); + } + if (!window->IsVisible() && options->embedWindow) { + window->DrawElement(); + } + } break; + case WIDGET_SEARCH: { + UIWidgets::PushStyleButton(menuThemeIndex); + if (ImGui::Button("Clear")) { + menuSearch.Clear(); + } + ImGui::SameLine(); + if (CVarGetInteger(CVAR_SETTING("Menu.SearchAutofocus"), 0) && + ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() && + !ImGui::IsMouseClicked(0)) { + ImGui::SetKeyboardFocusHere(0); + } + UIWidgets::PushStyleCombobox(menuThemeIndex); + ImGui::PushStyleColor(ImGuiCol_Border, UIWidgets::ColorValues.at(menuThemeIndex)); + menuSearch.Draw(); + ImGui::PopStyleColor(); + UIWidgets::PopStyleCombobox(); + UIWidgets::PopStyleButton(); + std::string menuSearchText(menuSearch.InputBuf); + + if (menuSearchText == "") { + ImGui::Text("Start typing to see results."); + return; + } + DrawSearchResults(menuSearchText); + ImGui::EndChild(); + } break; + default: + break; + } + if (widget.postFunc != nullptr) { + widget.postFunc(widget); + } + } catch (const std::bad_variant_access& e) { + SPDLOG_ERROR("Failed to draw menu item \"{}\" due to: {}", widget.name, e.what()); + assert(false); + } +} + +void Menu::Draw() { + if (!IsVisible()) { + return; + } + DrawElement(); + // Sync up the IsVisible flag if it was changed by ImGui + SyncVisibilityConsoleVariable(); +} + +void Menu::DrawElement() { + for (auto& [reason, info] : disabledMap) { + info.active = info.evaluation(info); + } + + windowHeight = ImGui::GetMainViewport()->WorkSize.y; + windowWidth = ImGui::GetMainViewport()->WorkSize.x; + auto windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings; + bool popout = CVarGetInteger(CVAR_SETTING("Menu.Popout"), 0) && allowPopout; + if (popout) { + windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking; + } + if (popout != popped) { + if (popout) { + windowHeight = poppedSize.y; + windowWidth = poppedSize.x; + ImGui::SetNextWindowSize({ static_cast(windowWidth), static_cast(windowHeight) }, + ImGuiCond_Always); + ImGui::SetNextWindowPos(poppedPos, ImGuiCond_Always); + } else if (popped) { + CVarSetFloat(CVAR_SETTING("Menu.PoppedWidth"), poppedSize.x); + CVarSetFloat(CVAR_SETTING("Menu.PoppedHeight"), poppedSize.y); + CVarSave(); + } + } + popped = popout; + auto windowCond = ImGuiCond_Always; + if (!popout) { + ImGui::SetNextWindowSize({ static_cast(windowWidth), static_cast(windowHeight) }, windowCond); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), windowCond, { 0.5f, 0.5f }); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + } + if (!ImGui::Begin("Main Menu", NULL, windowFlags)) { + if (!popout) { + ImGui::PopStyleVar(); + } + ImGui::End(); + return; + } + if (popped != popout) { + if (!popout) { + ImGui::PopStyleVar(); + } + CVarSetInteger(CVAR_SETTING("Menu.Popout"), popped); + CVarSetFloat(CVAR_SETTING("Menu.PoppedWidth"), poppedSize.x); + CVarSetFloat(CVAR_SETTING("Menu.PoppedHeight"), poppedSize.y); + CVarSetFloat(CVAR_SETTING("Menu.PoppedPos.x"), poppedSize.x); + CVarSetFloat(CVAR_SETTING("Menu.PoppedPos.y"), poppedSize.y); + CVarSave(); + ImGui::End(); + return; + } + ImGui::PushFont(OTRGlobals::Instance->fontStandardLargest); + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStyle& style = ImGui::GetStyle(); + windowHeight = window->WorkRect.GetHeight(); + windowWidth = window->WorkRect.GetWidth(); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); + const char* headerCvar = CVAR_SETTING("Menu.ActiveHeader"); + std::string headerIndex = CVarGetString(headerCvar, "Settings"); + ImVec2 pos = window->DC.CursorPos; + float centerX = pos.x + windowWidth / 2 - (style.ItemSpacing.x * (menuEntries.size() + 1)); + std::vector headerSizes; + float headerWidth = style.ItemSpacing.x + 20; + bool headerSearch = !CVarGetInteger(CVAR_SETTING("Menu.SidebarSearch"), 0); + if (headerSearch) { + headerWidth += 200.0f + style.ItemSpacing.x + style.FramePadding.x; + } + for (auto& label : menuOrder) { + ImVec2 size = ImGui::CalcTextSize(label.c_str()); + headerSizes.push_back(size); + headerWidth += size.x + style.FramePadding.x * 2; + if (label == headerIndex) { + headerWidth += style.ItemSpacing.x; + } + } + + // Full screen menu with widths below 1280, heights below 800. + // Up to 100 pixel padding when up to 1700 width, 1050 height. + // Everything above that, fixed size of 1600x950. + ImVec2 menuSize = { std::fminf(1280, windowWidth), std::fminf(800, windowHeight) }; + if (windowWidth > 1380) { + menuSize.x = std::fminf(1600, windowWidth - 100); + } + if (windowHeight > 900) { + menuSize.y = std::fminf(950, windowHeight - 100); + } + + pos += window->WorkRect.GetSize() / 2 - menuSize / 2; + ImGui::SetNextWindowPos(pos); + ImGui::BeginChild("Menu Block", menuSize, + ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar); + + std::unordered_map* sidebar; + float headerHeight = headerSizes.at(0).y + style.FramePadding.y * 2; + ImVec2 buttonSize = ImGui::CalcTextSize(ICON_FA_TIMES_CIRCLE) + style.FramePadding * 2; + bool scrollbar = false; + if (headerWidth > menuSize.x - buttonSize.x * 3 - style.ItemSpacing.x * 3) { + headerHeight += style.ScrollbarSize; + scrollbar = true; + } + UIWidgets::ButtonOptions options = {}; + options.size = UIWidgets::Sizes::Inline; + options.tooltip = "Close Menu (Esc)"; + if (UIWidgets::Button(ICON_FA_TIMES_CIRCLE, options)) { + ToggleVisibility(); + + // Update gamepad navigation after close based on if other menus are still visible + auto mImGuiIo = &ImGui::GetIO(); + if (CVarGetInteger(CVAR_IMGUI_CONTROLLER_NAV, 0) && + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetMenuOrMenubarVisible()) { + mImGuiIo->ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + } else { + mImGuiIo->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad; + } + } + ImGui::SameLine(); + ImGui::SetNextWindowSizeConstraints({ 0, headerHeight }, { headerWidth, headerHeight }); + ImVec2 headerSelSize = { menuSize.x - buttonSize.x * 3 - style.ItemSpacing.x * 3, headerHeight }; + if (scrollbar) { + headerSelSize.y += style.ScrollbarSize; + } + bool autoFocus = CVarGetInteger(CVAR_SETTING("Menu.SearchAutofocus"), 0); + ImGui::BeginChild("Header Selection", headerSelSize, + ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_HorizontalScrollbar); + uint8_t curIndex = 0; + for (auto& label : menuOrder) { + if (curIndex != 0) { + ImGui::SameLine(); + } + auto& entry = menuEntries.at(label); + std::string nextIndex = label; + UIWidgets::PushStyleButton(menuThemeIndex); + if (headerIndex != label) { + ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 }); + } + if (ModernMenuHeaderEntry(entry.label)) { + if (headerSearch) { + menuSearch.Clear(); + } + CVarSetString(headerCvar, label.c_str()); + CVarSave(); + nextIndex = label; + } + if (headerIndex != label) { + ImGui::PopStyleColor(); + } + UIWidgets::PopStyleButton(); + if (headerIndex == label) { + sidebar = &entry.sidebars; + } + if (nextIndex != label) { + headerIndex = nextIndex; + } + curIndex++; + } + std::string menuSearchText = ""; + if (headerSearch) { + ImGui::SameLine(); + if (autoFocus && ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() && + !ImGui::IsMouseClicked(0)) { + ImGui::SetKeyboardFocusHere(0); + } + auto color = UIWidgets::ColorValues.at(menuThemeIndex); + color.w = 0.2f; + ImGui::PushStyleColor(ImGuiCol_FrameBg, color); + menuSearch.Draw("##search", 200.0f); + menuSearchText = menuSearch.InputBuf; + menuSearchText.erase(std::remove(menuSearchText.begin(), menuSearchText.end(), ' '), menuSearchText.end()); + if (menuSearchText.length() < 1) { + ImGui::SameLine(headerWidth - 200.0f + style.ItemSpacing.x); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "Search..."); + } + ImGui::PopStyleColor(); + } + ImGui::EndChild(); + ImGui::SameLine(menuSize.x - (buttonSize.x * 2) - style.ItemSpacing.x); + UIWidgets::ButtonOptions options2 = {}; + options2.color = UIWidgets::Colors::Red; + options2.size = UIWidgets::Sizes::Inline; + options2.tooltip = "Reset" +#ifdef __APPLE__ + " (Command-R)" +#elif !defined(__SWITCH__) && !defined(__WIIU__) + " (Ctrl+R)" +#else + "" +#endif + ; + if (UIWidgets::Button(ICON_FA_UNDO, options2)) { + std::reinterpret_pointer_cast( + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console")) + ->Dispatch("reset"); + } + ImGui::SameLine(); + UIWidgets::ButtonOptions options3 = {}; + options3.color = UIWidgets::Colors::Red; + options3.size = UIWidgets::Sizes::Inline; + options3.tooltip = "Quit SoH"; + if (UIWidgets::Button(ICON_FA_POWER_OFF, options3)) { + if (!popped) { + ToggleVisibility(); + } + Ship::Context::GetInstance()->GetWindow()->Close(); + } + ImGui::PopStyleVar(); + + pos.y += headerHeight + style.ItemSpacing.y; + pos.x = centerX - menuSize.x / 2 + (style.ItemSpacing.x * (menuEntries.size() + 1)); + window->DrawList->AddRectFilled(pos, pos + ImVec2{ menuSize.x, 4 }, ImGui::GetColorU32({ 255, 255, 255, 255 }), + true, style.WindowRounding); + pos.y += style.ItemSpacing.y; + float sectionHeight = menuSize.y - headerHeight - 4 - style.ItemSpacing.y * 2; + float columnHeight = sectionHeight - style.ItemSpacing.y * 4; + ImGui::SetNextWindowPos(pos + style.ItemSpacing * 2); + float sidebarWidth = 200 - style.ItemSpacing.x; + + const char* sidebarCvar = menuEntries.at(headerIndex).sidebarCvar; + + std::string sectionIndex = CVarGetString(sidebarCvar, ""); + if (!sidebar->contains(sectionIndex)) { + sectionIndex = sidebar->begin()->first; + } + float sectionCenterX = pos.x + (sidebarWidth / 2); + float topY = pos.y; + ImGui::SetNextWindowSizeConstraints({ sidebarWidth, 0 }, { sidebarWidth, columnHeight }); + ImGui::BeginChild((menuEntries.at(headerIndex).label + " Section").c_str(), { sidebarWidth, columnHeight * 3 }, + ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize, ImGuiWindowFlags_NoTitleBar); + for (auto& sidebarLabel : menuEntries.at(headerIndex).sidebarOrder) { + std::string nextIndex = ""; + UIWidgets::PushStyleButton(menuThemeIndex); + if (sectionIndex != sidebarLabel) { + ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 }); + } + if (ModernMenuSidebarEntry(sidebarLabel)) { + if (headerSearch) { + menuSearch.Clear(); + } + CVarSetString(sidebarCvar, sidebarLabel.c_str()); + CVarSave(); + nextIndex = sidebarLabel; + } + if (sectionIndex != sidebarLabel) { + ImGui::PopStyleColor(); + } + UIWidgets::PopStyleButton(); + if (nextIndex != "") { + sectionIndex = nextIndex; + } + } + ImGui::EndChild(); + + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); + pos = ImVec2{ sectionCenterX + (sidebarWidth / 2), topY } + style.ItemSpacing * 2; + window->DrawList->AddRectFilled(pos, pos + ImVec2{ 4, sectionHeight - style.FramePadding.y * 2 }, + ImGui::GetColorU32({ 255, 255, 255, 255 }), true, style.WindowRounding); + pos.x += 4 + style.ItemSpacing.x; + ImGui::SetNextWindowPos(pos + style.ItemSpacing); + float sectionWidth = menuSize.x - sidebarWidth - 4 - style.ItemSpacing.x * 4; + std::string sectionMenuId = sectionIndex + " Settings"; + int columns = sidebar->at(sectionIndex).columnCount; + size_t columnFuncs = sidebar->at(sectionIndex).columnWidgets.size(); + if (windowWidth < 800) { + columns = 1; + } + float columnWidth = (sectionWidth - style.ItemSpacing.x * columns) / columns; + bool useColumns = columns > 1; + if (!useColumns || (headerSearch && menuSearchText.length() > 0)) { + ImGui::SameLine(); + ImGui::SetNextWindowSizeConstraints({ sectionWidth, 0 }, { sectionWidth, columnHeight }); + ImGui::BeginChild(sectionMenuId.c_str(), { sectionWidth, windowHeight * 4 }, ImGuiChildFlags_AutoResizeY, + ImGuiWindowFlags_NoTitleBar); + } + if (headerSearch && menuSearchText.length() > 0) { + uint32_t searchCount = DrawSearchResults(menuSearchText); + if (searchCount == 0) { + ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize("No results found").x) / 2); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "No results found"); + } + ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize("Clear Search").x) / 2 - 10.0f); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f); + UIWidgets::ButtonOptions clearBtnOpts = {}; + clearBtnOpts.size = UIWidgets::Sizes::Inline; + if (UIWidgets::Button("Clear Search", clearBtnOpts)) { + menuSearch.Clear(); + } + + ImGui::EndChild(); + } else { + std::string menuLabel = menuEntries.at(headerIndex).label; + if (MenuInit::GetUpdateFuncs().contains(menuLabel)) { + if (MenuInit::GetUpdateFuncs()[menuLabel].contains(sectionIndex)) { + for (auto& updateFunc : MenuInit::GetUpdateFuncs()[menuLabel][sectionIndex]) { + updateFunc(); + } + } + } + for (int i = 0; i < columnFuncs; i++) { + std::string sectionId = fmt::format("{} Column {}", sectionMenuId, i); + if (useColumns) { + ImGui::SetNextWindowSizeConstraints({ columnWidth, 0 }, { columnWidth, columnHeight }); + ImGui::BeginChild(sectionId.c_str(), { columnWidth, windowHeight * 4 }, ImGuiChildFlags_AutoResizeY, + ImGuiWindowFlags_NoTitleBar); + } + // for (auto& entryName : sidebar->at(sectionIndex).sidebarOrder) { + for (auto& entry : sidebar->at(sectionIndex).columnWidgets.at(i)) { + MenuDrawItem(entry, 90 / sidebar->at(sectionIndex).columnCount, menuThemeIndex); + } + //} + if (useColumns) { + ImGui::EndChild(); + } + if (i < columns - 1) { + ImGui::SameLine(); + } + } + } + if (!useColumns || menuSearchText.length() > 0) { + ImGui::EndChild(); + } + ImGui::PopFont(); + ImGui::PopFont(); + + if (!popout) { + ImGui::PopStyleVar(); + } + ImGui::EndChild(); + if (popout) { + poppedSize = ImGui::GetWindowSize(); + poppedPos = ImGui::GetWindowPos(); + } + ImGui::End(); +} +} // namespace Ship diff --git a/soh/soh/SohGui/Menu.h b/soh/soh/SohGui/Menu.h new file mode 100644 index 000000000..d7007bb1e --- /dev/null +++ b/soh/soh/SohGui/Menu.h @@ -0,0 +1,65 @@ +#ifndef MENU_H +#define MENU_H + +#include +#include "graphic/Fast3D/gfx_rendering_api.h" +#include "MenuTypes.h" + +namespace Ship { +uint32_t GetVectorIndexOf(std::vector& vector, std::string value); +class Menu : public GuiWindow { + public: + using GuiWindow::GuiWindow; + + Menu(const std::string& cVar, const std::string& name, uint8_t searchSidebarIndex_ = 0, + UIWidgets::Colors menuThemeIndex_ = UIWidgets::Colors::LightBlue); + + void InitElement() override; + void DrawElement() override; + void UpdateElement() override; + void Draw() override; + void InsertSidebarSearch(); + void RemoveSidebarSearch(); + void UpdateWindowBackendObjects(); + UIWidgets::Colors GetMenuThemeColor(); + + void MenuDrawItem(WidgetInfo& widget, uint32_t width, UIWidgets::Colors menuThemeIndex); + void AddMenuEntry(std::string entryName, const char* entryCvar); + std::unordered_map& GetDisabledMap(); + + protected: + ImVec2 mOriginalSize; + std::string mName; + uint32_t mWindowFlags; + std::unordered_map menuEntries; + std::vector menuOrder; + uint32_t DrawSearchResults(std::string& menuSearchText); + ImGuiTextFilter menuSearch; + uint8_t searchSidebarIndex; + UIWidgets::Colors defaultThemeIndex; + std::shared_ptr> availableWindowBackends; + std::unordered_map availableWindowBackendsMap; + Ship::WindowBackend configWindowBackend; + + std::unordered_map disabledMap; + std::vector disabledVector; + const SidebarEntry searchSidebarEntry = { + .columnCount = 1, + .columnWidgets = { { { .name = "Sidebar Search", + .type = WIDGET_SEARCH, + .options = std::make_shared(UIWidgets::WidgetOptions{}.Tooltip( + "Searches all menus for the given text, including tooltips.")) } } } + }; + + private: + bool allowPopout = true; // PortNote: should be set to false on small screen ports + bool popped; + ImVec2 poppedSize; + ImVec2 poppedPos; + float windowHeight; + float windowWidth; + UIWidgets::Colors menuThemeIndex; +}; +} // namespace Ship + +#endif // MENU_H diff --git a/soh/soh/SohGui/MenuTypes.h b/soh/soh/SohGui/MenuTypes.h new file mode 100644 index 000000000..7b979a824 --- /dev/null +++ b/soh/soh/SohGui/MenuTypes.h @@ -0,0 +1,280 @@ +#ifndef MENUTYPES_H +#define MENUTYPES_H + +#include +#include "UIWidgets.hpp" + +typedef enum { + DISABLE_FOR_NO_VSYNC, + DISABLE_FOR_NO_WINDOWED_FULLSCREEN, + DISABLE_FOR_NO_MULTI_VIEWPORT, + DISABLE_FOR_NOT_DIRECTX, + DISABLE_FOR_DIRECTX, + DISABLE_FOR_MATCH_REFRESH_RATE_ON, + DISABLE_FOR_ADVANCED_RESOLUTION_ON, + DISABLE_FOR_VERTICAL_RES_TOGGLE_ON, + DISABLE_FOR_LOW_RES_MODE_ON, + DISABLE_FOR_NULL_PLAY_STATE, + DISABLE_FOR_DEBUG_MODE_OFF, + DISABLE_FOR_FRAME_ADVANCE_OFF, + DISABLE_FOR_ADVANCED_RESOLUTION_OFF, + DISABLE_FOR_VERTICAL_RESOLUTION_OFF, +} DisableOption; + +struct WidgetInfo; +struct disabledInfo; +using VoidFunc = void (*)(); +using DisableInfoFunc = bool (*)(disabledInfo&); +using DisableVec = std::vector; +using WidgetFunc = void (*)(WidgetInfo&); + +typedef enum { + WIDGET_CHECKBOX, + WIDGET_COMBOBOX, + WIDGET_SLIDER_INT, + WIDGET_SLIDER_FLOAT, + WIDGET_CVAR_CHECKBOX, + WIDGET_CVAR_COMBOBOX, + WIDGET_CVAR_SLIDER_INT, + WIDGET_CVAR_SLIDER_FLOAT, + WIDGET_BUTTON, + WIDGET_INPUT, + WIDGET_CVAR_INPUT, + WIDGET_COLOR_24, // color picker without alpha + WIDGET_COLOR_32, // color picker with alpha + WIDGET_SEARCH, + WIDGET_SEPARATOR, + WIDGET_SEPARATOR_TEXT, + WIDGET_TEXT, + WIDGET_WINDOW_BUTTON, + WIDGET_AUDIO_BACKEND, // needed for special operations that can't be handled easily with the normal combobox widget + WIDGET_VIDEO_BACKEND, // same as above + WIDGET_CUSTOM, +} WidgetType; + +typedef enum { + SECTION_COLUMN_1, + SECTION_COLUMN_2, + SECTION_COLUMN_3, +} SectionColumns; + +typedef enum { + DEBUG_LOG_TRACE, + DEBUG_LOG_DEBUG, + DEBUG_LOG_INFO, + DEBUG_LOG_WARN, + DEBUG_LOG_ERROR, + DEBUG_LOG_CRITICAL, + DEBUG_LOG_OFF, +} DebugLogOption; + +// holds the widget values for a widget, contains all CVar types available from LUS. int32_t is used for boolean +// evaluation +using CVarVariant = std::variant; +using OptionsVariant = + std::variant; + +// All the info needed for display and search of all widgets in the menu. +// `name` is the label displayed, +// `cVar` is the string representation of the CVar used to store the widget value +// `tooltip` is what is displayed when hovering (except when disabled, more on that later) +// `type` is the WidgetType for the widget, which is what determines how the information is used in the draw func +// `options` is a variant that holds the UIWidgetsOptions struct for the widget type +// blank objects need to be initialized with specific typing matching the expected Options struct for the widget +// `callback` is a lambda used for running code on widget change. may need `SohGui::GetMenu()` for specific menu actions +// `preFunc` is a lambda called before drawing code starts. It can be used to determine a widget's status, +// whether disabled or hidden, as well as update pointers for non-CVar widget types. +// `postFunc` is a lambda called after all drawing code is finished, for reacting to states other than +// widgets having been changed, like holding buttons. +// All three lambdas accept a `widgetInfo` reference in case it needs information on the widget for these operations +// `activeDisables` is a vector of DisableOptions for specifying what reasons a widget is disabled, which are displayed +// in the disabledTooltip for the widget. Can display multiple reasons. Handling the reasons is done in `preFunc`. +// It is recommended to utilize `disabledInfo`/`DisableReason` to list out all reasons for disabling and isHidden so +// the info can be shown. +// `windowName` is what is displayed and searched for `windowButton` type and window interactions +// `isHidden` just prevents the widget from being drawn under whatever circumstances you specify in the `preFunc` +// `sameLine` allows for specifying that the widget should be on the same line as the previous widget +struct WidgetInfo { + std::string name; // Used by all widgets + const char* cVar; // Used by all widgets except + WidgetType type; + std::shared_ptr options; + std::variant valuePointer; + WidgetFunc callback = nullptr; + WidgetFunc preFunc = nullptr; + WidgetFunc postFunc = nullptr; + WidgetFunc customFunction = nullptr; + DisableVec activeDisables = {}; + const char* windowName = ""; + bool isHidden = false; + bool sameLine = false; + + WidgetInfo& CVar(const char* cVar_) { + cVar = cVar_; + return *this; + } + WidgetInfo& Options(OptionsVariant options_) { + switch (type) { + case WIDGET_AUDIO_BACKEND: + case WIDGET_VIDEO_BACKEND: + case WIDGET_COMBOBOX: + case WIDGET_CVAR_COMBOBOX: + options = std::make_shared(std::get(options_)); + break; + case WIDGET_CHECKBOX: + case WIDGET_CVAR_CHECKBOX: + options = std::make_shared(std::get(options_)); + break; + case WIDGET_SLIDER_FLOAT: + case WIDGET_CVAR_SLIDER_FLOAT: + options = + std::make_shared(std::get(options_)); + break; + case WIDGET_SLIDER_INT: + case WIDGET_CVAR_SLIDER_INT: + options = + std::make_shared(std::get(options_)); + break; + case WIDGET_BUTTON: + options = std::make_shared(std::get(options_)); + break; + case WIDGET_WINDOW_BUTTON: + options = std::make_shared(std::get(options_)); + break; + case WIDGET_TEXT: + case WIDGET_SEPARATOR_TEXT: + case WIDGET_SEPARATOR: + default: + options = std::make_shared(std::get(options_)); + } + return *this; + } + void ResetDisables() { + isHidden = false; + options->disabled = false; + options->disabledTooltip = ""; + activeDisables.clear(); + } + WidgetInfo& Options(std::shared_ptr options_) { + options = options_; + return *this; + } + WidgetInfo& Callback(WidgetFunc callback_) { + callback = callback_; + return *this; + } + WidgetInfo& PreFunc(WidgetFunc preFunc_) { + preFunc = preFunc_; + return *this; + } + WidgetInfo& PostFunc(WidgetFunc postFunc_) { + postFunc = postFunc_; + return *this; + } + WidgetInfo& WindowName(const char* windowName_) { + windowName = windowName_; + return *this; + } + WidgetInfo& ValuePointer(std::variant valuePointer_) { + valuePointer = valuePointer_; + return *this; + } + WidgetInfo& SameLine(bool sameLine_) { + sameLine = sameLine_; + return *this; + } + WidgetInfo& CustomFunction(WidgetFunc customFunction_) { + customFunction = customFunction_; + return *this; + } +}; + +struct WidgetPath { + std::string sectionName; + std::string sidebarName; + SectionColumns column; +}; + +// `disabledInfo` holds information on reasons for hiding or disabling a widget, as well as an evaluation lambda that +// is run once per frame to update its status (this is done to prevent dozens of redundant CVarGets in each frame loop) +// `evaluation` returns a bool which can be determined by whatever code you want that changes its status +// `reason` is the text displayed in the disabledTooltip when a widget is disabled by a particular DisableReason +// `active` is what's referenced when determining disabled status for a widget that uses this This can also be used to +// hold reasons to hide widgets so that their evaluations are also only run once per frame +struct disabledInfo { + DisableInfoFunc evaluation; + const char* reason; + bool active = false; + int32_t value = 0; +}; + +// Contains the name displayed in the sidebar (label), the number of columns to use in drawing (columnCount; for visual +// separation, 1-3), and nested vectors of the widgets, grouped by column (columnWidgets). The number of widget vectors +// added to the column groups does not need to match the specified columnCount, e.g. you can have one vector added to +// the sidebar, but still separate the window into 3 columns and display only in the first column +struct SidebarEntry { + uint32_t columnCount; + std::vector> columnWidgets; +}; + +// Contains entries for what's listed in the header at the top, including the name displayed on the top bar (label), +// a vector of the SidebarEntries for that header entry, and the name of the cvar used to track what sidebar entry is +// the last viewed for that header. +struct MainMenuEntry { + std::string label; + const char* sidebarCvar; + std::unordered_map sidebars = {}; + std::vector sidebarOrder = {}; +}; + +static const std::unordered_map audioBackendsMap = { + { Ship::AudioBackend::WASAPI, "Windows Audio Session API" }, + { Ship::AudioBackend::SDL, "SDL" }, +}; + +static const std::unordered_map windowBackendsMap = { + { Ship::WindowBackend::FAST3D_DXGI_DX11, "DirectX" }, + { Ship::WindowBackend::FAST3D_SDL_OPENGL, "OpenGL" }, + { Ship::WindowBackend::FAST3D_SDL_METAL, "Metal" }, +}; + +struct MenuInit { + static std::vector>& GetInitFuncs() { + static std::vector> menuInitFuncs; + return menuInitFuncs; + } + + static std::unordered_map>>>& + GetUpdateFuncs() { + static std::unordered_map>>> + menuUpdateFuncs; + return menuUpdateFuncs; + } + + static void InitAll() { + auto& menuInitFuncs = MenuInit::GetInitFuncs(); + for (const auto& initFunc : menuInitFuncs) { + initFunc(); + } + } +}; + +struct RegisterMenuInitFunc { + RegisterMenuInitFunc(std::function initFunc) { + auto& menuInitFuncs = MenuInit::GetInitFuncs(); + + menuInitFuncs.push_back(initFunc); + } +}; + +struct RegisterMenuUpdateFunc { + RegisterMenuUpdateFunc(std::function updateFunc, std::string sectionName, std::string sidebarName) { + auto& menuUpdateFuncs = MenuInit::GetUpdateFuncs(); + + menuUpdateFuncs[sectionName][sidebarName].push_back(updateFunc); + } +}; + +#endif // MENUTYPES_H diff --git a/soh/soh/SohGui/ResolutionEditor.cpp b/soh/soh/SohGui/ResolutionEditor.cpp new file mode 100644 index 000000000..8e850682c --- /dev/null +++ b/soh/soh/SohGui/ResolutionEditor.cpp @@ -0,0 +1,528 @@ +#include "ResolutionEditor.h" +#include +#include + +#include "soh/SohGui/UIWidgets.hpp" +#include +#include "soh/OTRGlobals.h" +#include "soh/SohGui/SohMenu.h" +#include "soh/SohGui/SohGui.hpp" + +/* Console Variables are grouped under gAdvancedResolution. (e.g. CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled") + + The following cvars are used in Libultraship and can be edited here: + - Enabled - Turns Advanced Resolution Mode on. + - AspectRatioX, AspectRatioY - Aspect ratio controls. To toggle off, set either to zero. + - VerticalPixelCount, VerticalResolutionToggle - Resolution controls. + - PixelPerfectMode, IntegerScale.Factor - Pixel Perfect Mode a.k.a. integer scaling controls. + - IntegerScale.FitAutomatically - Automatic resizing for Pixel Perfect Mode. + - IntegerScale.NeverExceedBounds - Prevents manual resizing from exceeding screen bounds. + + The following cvars are also implemented in LUS for niche use cases: + - IgnoreAspectCorrection - Stretch framebuffer to fill screen. + This is something of a power-user setting for niche setups that most people won't need or care about, + but may be useful if playing the Switch/Wii U ports on a 4:3 television. + - IntegerScale.ExceedBoundsBy - Offset the max screen bounds, usually by +1. + This isn't that useful at the moment, so it's unused here. +*/ + +namespace SohGui { +extern std::shared_ptr mSohMenu; +enum setting { UPDATE_aspectRatioX, UPDATE_aspectRatioY, UPDATE_verticalPixelCount }; + +std::unordered_map aspectRatioPresetLabels = { { 0, "Off" }, + { 1, "Custom" }, + { 2, "Original (4:3)" }, + { 3, "Widescreen (16:9)" }, + { 4, "Nintendo 3DS (5:3)" }, + { 5, "16:10 (8:5)" }, + { 6, "Ultrawide (21:9)" } }; +const float aspectRatioPresetsX[] = { 0.0f, 16.0f, 4.0f, 16.0f, 5.0f, 16.0f, 21.0f }; +const float aspectRatioPresetsY[] = { 0.0f, 9.0f, 3.0f, 9.0f, 3.0f, 10.0f, 9.0f }; +const int default_aspectRatio = 1; // Default combo list option + +const char* pixelCountPresetLabels[] = { "Custom", "Native N64 (240p)", "2x (480p)", "3x (720p)", "4x (960p)", + "5x (1200p)", "6x (1440p)", "Full HD (1080p)", "4K (2160p)" }; +const int pixelCountPresets[] = { 480, 240, 480, 720, 960, 1200, 1440, 1080, 2160 }; +const int default_pixelCount = 0; // Default combo list option + +// Resolution clamp values as hardcoded in LUS::Gui::ApplyResolutionChanges() +const uint32_t minVerticalPixelCount = SCREEN_HEIGHT; +const uint32_t maxVerticalPixelCount = 4320; // 18x native, or 8K TV resolution + +const unsigned short default_maxIntegerScaleFactor = 6; // Default size of Integer scale factor slider. + +enum messageType { MESSAGE_ERROR, MESSAGE_WARNING, MESSAGE_QUESTION, MESSAGE_INFO, MESSAGE_GRAY_75 }; +const ImVec4 messageColor[]{ + { 0.85f, 0.0f, 0.0f, 1.0f }, // MESSAGE_ERROR + { 0.85f, 0.85f, 0.0f, 1.0f }, // MESSAGE_WARNING + { 0.0f, 0.85f, 0.85f, 1.0f }, // MESSAGE_QUESTION + { 0.0f, 0.85f, 0.55f, 1.0f }, // MESSAGE_INFO + { 0.75f, 0.75f, 0.75f, 1.0f } // MESSAGE_GRAY_75 +}; +static const float enhancementSpacerHeight = 19.0f; +// Initialise update flags. +static bool update[3]; + +// Initialise integer scale bounds. +static short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get + // overridden depending on viewport res + +static short integerScale_maximumBounds = 1; // can change when window is resized + +// Combo List defaults +static int32_t item_aspectRatio; +static int32_t item_pixelCount; +// Stored Values for non-UIWidgets elements +static float aspectRatioX; +static float aspectRatioY; +static int32_t verticalPixelCount; +// Additional settings +static bool showHorizontalResField; +static int32_t horizontalPixelCount; +// Disabling flags +static bool disabled_everything; +static bool disabled_pixelCount; + +using namespace UIWidgets; + +void ResolutionCustomWidget(WidgetInfo& info) { + ImGui::BeginDisabled(disabled_everything); + // Vertical Resolution + UIWidgets::CVarCheckbox("Set fixed vertical resolution (disables Resolution slider)", CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", + UIWidgets::CheckboxOptions({ {.disabled = disabled_everything} }).Tooltip("Override the resolution scale slider and use the settings below, irrespective of window size.") + .Color(THEME_COLOR)); + //if (disabled_pixelCount || disabled_everything) { // Hide pixel count controls. + // UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); + //} + UIWidgets::PushStyleCombobox(THEME_COLOR); + if (ImGui::Combo("Pixel Count Presets", &item_pixelCount, pixelCountPresetLabels, + IM_ARRAYSIZE(pixelCountPresetLabels)) && + item_pixelCount != default_pixelCount) { // don't change anything if "Custom" is selected. + verticalPixelCount = pixelCountPresets[item_pixelCount]; + + if (showHorizontalResField) { + horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; + } + + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount); + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + UIWidgets::PopStyleCombobox(); + // Horizontal Resolution, if visibility is enabled for it. + if (showHorizontalResField) { + // Only show the field if Aspect Ratio is being enforced. + if ((aspectRatioX > 0.0f) && (aspectRatioY > 0.0f)) { + // So basically we're "faking" this one by setting aspectRatioX instead. + UIWidgets::PushStyleInput(THEME_COLOR); + if (ImGui::InputInt("Horiz. Pixel Count", &horizontalPixelCount, 8, 320)) { + item_aspectRatio = default_aspectRatio; + if (horizontalPixelCount < SCREEN_WIDTH) { + horizontalPixelCount = SCREEN_WIDTH; + } + aspectRatioX = horizontalPixelCount; + aspectRatioY = verticalPixelCount; + update[UPDATE_aspectRatioX] = true; + update[UPDATE_aspectRatioY] = true; + } + UIWidgets::PopStyleInput(); + } else { // Display a notice instead. + ImGui::TextColored(messageColor[MESSAGE_QUESTION], + ICON_FA_QUESTION_CIRCLE " \"Force aspect ratio\" required."); + // ImGui::Text(" "); + ImGui::SameLine(); + if (UIWidgets::Button("Click to resolve", UIWidgets::ButtonOptions().Color(THEME_COLOR))) { + item_aspectRatio = default_aspectRatio; // Set it to Custom + aspectRatioX = aspectRatioPresetsX[2]; // but use the 4:3 defaults + aspectRatioY = aspectRatioPresetsY[2]; + update[UPDATE_aspectRatioX] = true; + update[UPDATE_aspectRatioY] = true; + horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; + } + } + } + // Vertical Resolution part 2 + UIWidgets::PushStyleInput(THEME_COLOR); + if (ImGui::InputInt("Vertical Pixel Count", &verticalPixelCount, 8, 240)) { + item_pixelCount = default_pixelCount; + update[UPDATE_verticalPixelCount] = true; + + // Account for the natural instinct to enter horizontal first. + // Ignore vertical resolutions that are below the lower clamp constant. + if (showHorizontalResField && !(verticalPixelCount < minVerticalPixelCount)) { + item_aspectRatio = default_aspectRatio; + aspectRatioX = horizontalPixelCount; + aspectRatioY = verticalPixelCount; + update[UPDATE_aspectRatioX] = true; + update[UPDATE_aspectRatioY] = true; + } + } + ImGui::EndDisabled(); + UIWidgets::PopStyleInput(); + + // Integer scaling settings group (Pixel-perfect Mode) + static const ImGuiTreeNodeFlags IntegerScalingResolvedImGuiFlag = + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) ? ImGuiTreeNodeFlags_DefaultOpen + : ImGuiTreeNodeFlags_None; + UIWidgets::PushStyleHeader(THEME_COLOR); + if (ImGui::CollapsingHeader("Integer Scaling Settings", IntegerScalingResolvedImGuiFlag)) { + const bool disabled_pixelPerfectMode = + !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || disabled_everything; + // Pixel-perfect Mode + UIWidgets::CVarCheckbox("Pixel-perfect Mode", CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", + UIWidgets::CheckboxOptions({{ .disabled = disabled_pixelCount || disabled_everything }}).Tooltip("Don't scale image to fill window.") + .Color(THEME_COLOR)); + if (disabled_pixelCount && CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0)) { + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + // Integer Scaling + UIWidgets::CVarSliderInt(fmt::format("Integer scale factor: {}", max_integerScaleFactor).c_str(), CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", + UIWidgets::IntSliderOptions({ {.disabled = disabled_pixelPerfectMode || CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0)} }) + .Min(1).Max(max_integerScaleFactor).DefaultValue(1).Tooltip("Integer scales the image. Only available in pixel-perfect mode.").Color(THEME_COLOR)); + // Display warning if size is being clamped or if framebuffer is larger than viewport. + if (!disabled_pixelPerfectMode && + (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", 1) && + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", 1) > + integerScale_maximumBounds)) { + ImGui::SameLine(); + ImGui::TextColored(messageColor[MESSAGE_WARNING], ICON_FA_EXCLAMATION_TRIANGLE " Window exceeded."); + } + + UIWidgets::CVarCheckbox("Automatically scale image to fit viewport", CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", + UIWidgets::CheckboxOptions({ {.disabled = disabled_pixelPerfectMode} }).DefaultValue(true).Color(THEME_COLOR) + .Tooltip("Automatically sets scale factor to fit window. Only available in pixel-perfect mode.")); + if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0)) { + // This is just here to update the value shown on the slider. + // The function in LUS to handle this setting will ignore IntegerScaleFactor while active. + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.Factor", integerScale_maximumBounds); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } // End of integer scaling settings + UIWidgets::PopStyleHeader(); + + // Collapsible panel for additional settings + UIWidgets::PushStyleHeader(THEME_COLOR); + if (ImGui::CollapsingHeader("Additional Settings")) { + #if defined(__SWITCH__) || defined(__WIIU__) + // Disable aspect correction, stretching the framebuffer to fill the viewport. + // This option is only really needed on systems limited to 16:9 TV resolutions, such as consoles. + // The associated cvar is still functional on PC platforms if you want to use it though. + UIWidgets::CVarCheckbox("Disable aspect correction and stretch the output image.\n" + "(Might be useful for 4:3 televisions!)\n" + "Not available in Pixel Perfect Mode.", + CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", + UIWidgets::CheckboxOptions({{ .disabled = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || disabled_everything}}) + .Color(THEME_COLOR)); + #else + if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", 0)) { + // This setting is intentionally not exposed on PC platforms, + // but may be accidentally activated for varying reasons. + // Having this button should hopefully prevent support headaches. + ImGui::TextColored(messageColor[MESSAGE_QUESTION], ICON_FA_QUESTION_CIRCLE + " If the image is stretched and you don't know why, click this."); + if (ImGui::Button("Click to reenable aspect correction.")) { + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IgnoreAspectCorrection", 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + UIWidgets::Spacer(2); + } + #endif + + // A requested addition; an alternative way of displaying the resolution field. + if (UIWidgets::Checkbox("Show a horizontal resolution field, instead of aspect ratio.", + &showHorizontalResField, UIWidgets::CheckboxOptions().Color(THEME_COLOR))) { + if (!showHorizontalResField && (aspectRatioX > 0.0f)) { // when turning this setting off + // Refresh relevant values + aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount; + horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; + } else { // when turning this setting on + item_aspectRatio = default_aspectRatio; + if (aspectRatioX > 0.0f) { + // Refresh relevant values in the opposite order + horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; + aspectRatioX = aspectRatioY * horizontalPixelCount / verticalPixelCount; + } + } + update[UPDATE_aspectRatioX] = true; + } + + // Beginning of Integer Scaling additional settings. + { + // UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); + + // Integer Scaling - Never Exceed Bounds. + const bool disabled_neverExceedBounds = + !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.FitAutomatically", 0) || + disabled_everything; + if (UIWidgets::CVarCheckbox("Prevent integer scaling from exceeding screen bounds.\n" + "(Makes screen bounds take priority over specified factor.)", + CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", UIWidgets::CheckboxOptions({{ .disabled = disabled_neverExceedBounds}}) + .Tooltip( + "Prevents integer scaling factor from exceeding screen bounds.\n\n" + "Enabled: Will clamp the scaling factor and display a gentle warning in the resolution editor.\n" + "Disabled: Will allow scaling to exceed screen bounds, for users who want to crop overscan.\n\n" + " " ICON_FA_INFO_CIRCLE + " Please note that exceeding screen bounds may show a scroll bar on-screen.").Color(THEME_COLOR).DefaultValue(true))) { + + // Initialise the (currently unused) "Exceed Bounds By" cvar if it's been changed. + if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) { + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } + + // Integer Scaling - Exceed Bounds By 1x/Offset. + // A popular feature in some retro frontends/upscalers, sometimes called "crop overscan" or "1080p 5x". + UIWidgets::CVarCheckbox("Allow integer scale factor to go +1 above maximum screen bounds.", + CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", + UIWidgets::CheckboxOptions({{ .disabled = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".PixelPerfectMode", 0) || disabled_everything }}).Color(THEME_COLOR)); + + // It does actually function as expected, but exceeding the bottom of the screen shows a scroll bar. + // I've ended up commenting this one out because of the scroll bar, and for simplicity. + + // Display an info message about the scroll bar. + if (!CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.NeverExceedBounds", 1) || + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) { + ImGui::TextColored(messageColor[MESSAGE_INFO], + " " ICON_FA_INFO_CIRCLE + " A scroll bar may become visible if screen bounds are exceeded."); + + // Another support helper button, to disable the unused "Exceed Bounds By" cvar. + // (Remove this button if uncommenting the checkbox.) + if (CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0)) { + if (UIWidgets::Button("Click to reset a console variable that may be causing this.", UIWidgets::ButtonOptions().Color(THEME_COLOR))) { + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } + } else { + ImGui::Text(" "); + } + } // End of Integer Scaling additional settings. + + } // End of additional settings + UIWidgets::PopStyleHeader(); + + // Clamp and update the cvars that don't use UIWidgets + if (update[UPDATE_aspectRatioX] || update[UPDATE_aspectRatioY] || update[UPDATE_verticalPixelCount]) { + if (update[UPDATE_aspectRatioX]) { + if (aspectRatioX < 0.0f) { + aspectRatioX = 0.0f; + } + CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX); + } + if (update[UPDATE_aspectRatioY]) { + if (aspectRatioY < 0.0f) { + aspectRatioY = 0.0f; + } + CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY); + } + if (update[UPDATE_verticalPixelCount]) { + // There's a upper and lower clamp on the Libultraship side too, + // so clamping it here is entirely visual, so the vertical resolution field reflects it. + if (verticalPixelCount < minVerticalPixelCount) { + verticalPixelCount = minVerticalPixelCount; + } + if (verticalPixelCount > maxVerticalPixelCount) { + verticalPixelCount = maxVerticalPixelCount; + } + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount); + } + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio); + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } +} + +void RegisterResolutionWidgets() { + WidgetPath path = { "Settings", "Graphics", SECTION_COLUMN_2 }; + + // Resolution visualiser + mSohMenu->AddWidget(path, "Viewport dimensions: {} x {}", WIDGET_TEXT).PreFunc([](WidgetInfo& info) { + info.name = fmt::format("Viewport dimensions: {} x {}", gfx_current_game_window_viewport.width, + gfx_current_game_window_viewport.height); + }); + mSohMenu->AddWidget(path, "Internal resolution: {} x {}", WIDGET_TEXT).PreFunc([](WidgetInfo& info) { + info.name = + fmt::format("Internal resolution: {} x {}", gfx_current_dimensions.width, gfx_current_dimensions.height); + }); + + // Activator + mSohMenu->AddWidget(path, "Enable advanced settings.", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled"); + // Error/Warning display + mSohMenu + ->AddWidget(path, ICON_FA_EXCLAMATION_TRIANGLE " Significant frame rate (FPS) drops may be occuring.", + WIDGET_TEXT) + .PreFunc( + [](WidgetInfo& info) { info.isHidden = !(!CVarGetInteger(CVAR_LOW_RES_MODE, 0) && IsDroppingFrames()); }) + .Options(WidgetOptions().Color(Colors::Orange)); + mSohMenu->AddWidget(path, ICON_FA_QUESTION_CIRCLE " \"N64 Mode\" is overriding these settings.", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_LOW_RES_MODE, 0); }) + .Options(WidgetOptions().Color(Colors::LightBlue)); + mSohMenu->AddWidget(path, "Click to disable N64 mode", WIDGET_BUTTON) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_LOW_RES_MODE, 0); }) + .Callback([](WidgetInfo& info) { + CVarSetInteger(CVAR_LOW_RES_MODE, 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + + // Aspect Ratio + mSohMenu->AddWidget(path, "AspectSep", WIDGET_SEPARATOR) + .PreFunc([](WidgetInfo& info) { + if (mSohMenu->GetDisabledMap().at(DISABLE_FOR_ADVANCED_RESOLUTION_OFF).active) { + info.activeDisables.push_back(DISABLE_FOR_ADVANCED_RESOLUTION_OFF); + } + }); + mSohMenu->AddWidget(path, "Force aspect ratio:", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + if (mSohMenu->GetDisabledMap().at(DISABLE_FOR_ADVANCED_RESOLUTION_OFF).active) { + info.activeDisables.push_back(DISABLE_FOR_ADVANCED_RESOLUTION_OFF); + } + }); + mSohMenu->AddWidget(path, "(Select \"Off\" to disable.)", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + if (mSohMenu->GetDisabledMap().at(DISABLE_FOR_ADVANCED_RESOLUTION_OFF).active) { + info.activeDisables.push_back(DISABLE_FOR_ADVANCED_RESOLUTION_OFF); + } + }) + .SameLine(true) + .Options(WidgetOptions().Color(Colors::Gray)); + // Presets + mSohMenu->AddWidget(path, "Aspect Ratio", WIDGET_COMBOBOX) + .ValuePointer(&item_aspectRatio) + .PreFunc([](WidgetInfo& info) { + if (mSohMenu->GetDisabledMap().at(DISABLE_FOR_ADVANCED_RESOLUTION_OFF).active) { + info.activeDisables.push_back(DISABLE_FOR_ADVANCED_RESOLUTION_OFF); + } + }) + .Callback([](WidgetInfo& info) { + if (item_aspectRatio != default_aspectRatio) { // don't change anything if "Custom" is selected. + aspectRatioX = aspectRatioPresetsX[item_aspectRatio]; + aspectRatioY = aspectRatioPresetsY[item_aspectRatio]; + + if (showHorizontalResField) { + horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; + } + + CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX); + CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY); + } + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }) + .Options(ComboboxOptions().ComboMap(aspectRatioPresetLabels)); + mSohMenu->AddWidget(path, "AspectRatioCustom", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) { + // Hide aspect ratio input fields if using one of the presets. + if (item_aspectRatio == default_aspectRatio && !showHorizontalResField) { + // Declare input interaction bools outside of IF statement to prevent Y field from disappearing. + const bool input_X = UIWidgets::SliderFloat("X", &aspectRatioX, + UIWidgets::FloatSliderOptions({{ .disabled = disabled_everything }}).Min(0.1f).Max(32.0f).Step(0.001f).Format("%3f") + .Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::Near).ComponentAlignment(UIWidgets::ComponentAlignment::Right)); + const bool input_Y = UIWidgets::SliderFloat("Y", &aspectRatioY, + UIWidgets::FloatSliderOptions({{ .disabled = disabled_everything }}).Min(0.1f).Max(24.0f).Step(0.001f).Format("%3f") + .Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::Near).ComponentAlignment(UIWidgets::ComponentAlignment::Right)); + if (input_X || input_Y) { + item_aspectRatio = default_aspectRatio; + update[UPDATE_aspectRatioX] = true; + update[UPDATE_aspectRatioY] = true; + } + } else if (showHorizontalResField) { // Show calculated aspect ratio + if (item_aspectRatio) { + ImGui::Dummy({ 0, 2 }); + const float resolvedAspectRatio = (float)gfx_current_dimensions.width / gfx_current_dimensions.height; + ImGui::Text("Aspect ratio: %.2f:1", resolvedAspectRatio); + } + } + }); + mSohMenu->AddWidget(path, "MoreResolutionSettings", WIDGET_CUSTOM).CustomFunction(ResolutionCustomWidget); +} + +void UpdateResolutionVars() { + // Clamp and update the cvars that don't use UIWidgets + if (update[UPDATE_aspectRatioX] || update[UPDATE_aspectRatioY] || update[UPDATE_verticalPixelCount]) { + if (update[UPDATE_aspectRatioX]) { + if (aspectRatioX < 0.0f) { + aspectRatioX = 0.0f; + } + CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioX); + } + if (update[UPDATE_aspectRatioY]) { + if (aspectRatioY < 0.0f) { + aspectRatioY = 0.0f; + } + CVarSetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioY); + } + if (update[UPDATE_verticalPixelCount]) { + // There's a upper and lower clamp on the Libultraship side too, + // so clamping it here is entirely visual, so the vertical resolution field reflects it. + if (verticalPixelCount < minVerticalPixelCount) { + verticalPixelCount = minVerticalPixelCount; + } + if (verticalPixelCount > maxVerticalPixelCount) { + verticalPixelCount = maxVerticalPixelCount; + } + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", verticalPixelCount); + } + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", item_aspectRatio); + CVarSetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", item_pixelCount); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + // Initialise update flags. + for (uint8_t i = 0; i < sizeof(update); i++) { + update[i] = false; + } + + // Initialise integer scale bounds. + short max_integerScaleFactor = default_maxIntegerScaleFactor; // default value, which may or may not get + // overridden depending on viewport res + + short integerScale_maximumBounds = 1; // can change when window is resized + // This is mostly just for UX purposes, as Fit Automatically logic is part of LUS. + if (((float)gfx_current_game_window_viewport.width / gfx_current_game_window_viewport.height) > + ((float)gfx_current_dimensions.width / gfx_current_dimensions.height)) { + // Scale to window height + integerScale_maximumBounds = gfx_current_game_window_viewport.height / gfx_current_dimensions.height; + } else { + // Scale to window width + integerScale_maximumBounds = gfx_current_game_window_viewport.width / gfx_current_dimensions.width; + } + // Lower-clamping maximum bounds value to 1 is no-longer necessary as that's accounted for in LUS. + // Letting it go below 1 in this Editor will even allow for checking if screen bounds are being exceeded. + if (default_maxIntegerScaleFactor < integerScale_maximumBounds) { + max_integerScaleFactor = integerScale_maximumBounds + + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".IntegerScale.ExceedBoundsBy", 0); + } + + // Combo List defaults + item_aspectRatio = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.AspectRatio", 3); + item_pixelCount = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".UIComboItem.PixelCount", default_pixelCount); + // Stored Values for non-UIWidgets elements + aspectRatioX = CVarGetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioX", aspectRatioPresetsX[item_aspectRatio]); + aspectRatioY = CVarGetFloat(CVAR_PREFIX_ADVANCED_RESOLUTION ".AspectRatioY", aspectRatioPresetsY[item_aspectRatio]); + verticalPixelCount = + CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalPixelCount", pixelCountPresets[item_pixelCount]); + // Additional settings + showHorizontalResField = false; + horizontalPixelCount = (verticalPixelCount / aspectRatioY) * aspectRatioX; + // Disabling flags + disabled_everything = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0); + disabled_pixelCount = !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0); +} + +bool IsDroppingFrames() { + // a rather imprecise way of checking for frame drops. + // but it's mostly there to inform the player of large drops. + const short targetFPS = CVarGetInteger("gInterpolationFPS", 20); + const float threshold = targetFPS / 20.0f + 4.1f; + return ImGui::GetIO().Framerate < targetFPS - threshold; +} + + static RegisterMenuUpdateFunc updateFunc(UpdateResolutionVars, "Settings", "Graphics"); + static RegisterMenuInitFunc initFunc(RegisterResolutionWidgets); + +} // namespace BenGui diff --git a/soh/soh/SohGui/ResolutionEditor.h b/soh/soh/SohGui/ResolutionEditor.h new file mode 100644 index 000000000..20d613b3b --- /dev/null +++ b/soh/soh/SohGui/ResolutionEditor.h @@ -0,0 +1,12 @@ +#ifndef RESOLUTIONEDITOR_H +#define RESOLUTIONEDITOR_H + +#include + +namespace SohGui { +bool IsDroppingFrames(); +void RegisterResolutionWidgets(); +void UpdateResolutionVars(); +} // namespace BenGui + +#endif // RESOLUTIONEDITOR_H diff --git a/soh/soh/SohGui/SohGui.cpp b/soh/soh/SohGui/SohGui.cpp index 8d7a4ba45..ef8e40e8c 100644 --- a/soh/soh/SohGui/SohGui.cpp +++ b/soh/soh/SohGui/SohGui.cpp @@ -20,8 +20,7 @@ #ifdef __SWITCH__ #include #endif - -#include "UIWidgets.hpp" +#include "SohMenu.h" #include "include/global.h" #include "include/z64audio.h" #include "soh/SaveManager.h" @@ -32,48 +31,14 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/cosmetics/authenticGfxPatches.h" -#include "soh/Enhancements/resolution-editor/ResolutionEditor.h" #include "soh/Enhancements/debugger/MessageViewer.h" #include "soh/Notification/Notification.h" #include "soh/Enhancements/TimeDisplay/TimeDisplay.h" -bool isBetaQuestEnabled = false; - -extern "C" { - void enableBetaQuest() { isBetaQuestEnabled = true; } - void disableBetaQuest() { isBetaQuestEnabled = false; } -} - - namespace SohGui { // MARK: - Properties - - static const char* chestSizeAndTextureMatchesContentsOptions[4] = { "Disabled", "Both", "Texture Only", "Size Only" }; static const char* bunnyHoodOptions[3] = { "Disabled", "Faster Run & Longer Jump", "Faster Run" }; - static const char* allPowers[9] = { - "Vanilla (1x)", - "Double (2x)", - "Quadruple (4x)", - "Octuple (8x)", - "Foolish (16x)", - "Ridiculous (32x)", - "Merciless (64x)", - "Pure Torture (128x)", - "OHKO (256x)" }; - static const char* subPowers[8] = { allPowers[0], allPowers[1], allPowers[2], allPowers[3], allPowers[4], allPowers[5], allPowers[6], allPowers[7] }; - static const char* subSubPowers[7] = { allPowers[0], allPowers[1], allPowers[2], allPowers[3], allPowers[4], allPowers[5], allPowers[6] }; - static const char* zFightingOptions[3] = { "Disabled", "Consistent Vanish", "No Vanish" }; - static const char* bonkDamageValues[8] = { - "No Damage", - "0.25 Heart", - "0.5 Heart", - "1 Heart", - "2 Hearts", - "4 Hearts", - "8 Hearts", - "OHKO" - }; static const inline std::vector> audioBackends = { #ifdef _WIN32 @@ -98,7 +63,6 @@ namespace SohGui { return buttonText; } - // MARK: - Delegates std::shared_ptr mSohMenuBar; @@ -107,7 +71,8 @@ namespace SohGui { std::shared_ptr mStatsWindow; std::shared_ptr mGfxDebuggerWindow; std::shared_ptr mInputEditorWindow; - + + std::shared_ptr mSohMenu; std::shared_ptr mAudioEditorWindow; std::shared_ptr mInputViewer; std::shared_ptr mInputViewerSettings; @@ -129,25 +94,30 @@ namespace SohGui { std::shared_ptr mTimeSplitWindow; std::shared_ptr mPlandomizerWindow; std::shared_ptr mRandomizerSettingsWindow; - std::shared_ptr mAdvancedResolutionSettingsWindow; std::shared_ptr mModalWindow; std::shared_ptr mNotificationWindow; std::shared_ptr mTimeDisplayWindow; - std::shared_ptr mAboutWindow; + + UIWidgets::Colors GetMenuThemeColor() { + return mSohMenu->GetMenuThemeColor(); + } void SetupGuiElements() { auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui(); - mSohMenuBar = std::make_shared(CVAR_MENU_BAR_OPEN, CVarGetInteger(CVAR_MENU_BAR_OPEN, 0)); + /*mSohMenuBar = std::make_shared(CVAR_MENU_BAR_OPEN, CVarGetInteger(CVAR_MENU_BAR_OPEN, 0)); gui->SetMenuBar(std::reinterpret_pointer_cast(mSohMenuBar)); - if (gui->GetMenuBar() && !gui->GetMenuBar()->IsVisible()) { + if (!gui->GetMenuBar() && !CVarGetInteger("gSettings.DisableMenuShortcutNotify", 0)) { #if defined(__SWITCH__) || defined(__WIIU__) - Notification::Emit({ .message = "Press - to access enhancements menu", .remainingTime = 10.0f }); + gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Press - to access enhancements menu"); #else - Notification::Emit({ .message = "Press F1 to access enhancements menu", .remainingTime = 10.0f }); + gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Press F1 to access enhancements menu"); #endif - } + }*/ + + mSohMenu = std::make_shared(CVAR_WINDOW("Menu"), "Port Menu"); + gui->SetMenu(mSohMenu); mStatsWindow = gui->GetGuiWindow("Stats"); if (mStatsWindow == nullptr) { @@ -159,9 +129,9 @@ namespace SohGui { SPDLOG_ERROR("Could not find console window"); } - mGfxDebuggerWindow = gui->GetGuiWindow("GfxDebuggerWindow"); + mGfxDebuggerWindow = gui->GetGuiWindow("Gfx Debugger"); if (mGfxDebuggerWindow == nullptr) { - SPDLOG_ERROR("Could not find input GfxDebuggerWindow"); + SPDLOG_ERROR("Could not find Gfx Debugger window"); } mInputEditorWindow = gui->GetGuiWindow("Controller Configuration"); @@ -185,7 +155,7 @@ namespace SohGui { gui->AddGuiWindow(mSaveEditorWindow); mHookDebuggerWindow = std::make_shared(CVAR_WINDOW("HookDebugger"), "Hook Debugger", ImVec2(1250, 850)); gui->AddGuiWindow(mHookDebuggerWindow); - mDLViewerWindow = std::make_shared(CVAR_WINDOW("DLViewer"), "Display List Viewer", ImVec2(520, 600)); + mDLViewerWindow = std::make_shared(CVAR_WINDOW("DisplayListViewer"), "Display List Viewer", ImVec2(520, 600)); gui->AddGuiWindow(mDLViewerWindow); mValueViewerWindow = std::make_shared(CVAR_WINDOW("ValueViewer"), "Value Viewer", ImVec2(520, 600)); gui->AddGuiWindow(mValueViewerWindow); @@ -193,26 +163,24 @@ namespace SohGui { gui->AddGuiWindow(mMessageViewerWindow); mGameplayStatsWindow = std::make_shared(CVAR_WINDOW("GameplayStats"), "Gameplay Stats", ImVec2(480, 550)); gui->AddGuiWindow(mGameplayStatsWindow); - mCheckTrackerWindow = std::make_shared(CVAR_WINDOW("CheckTracker"), "Check Tracker"); + mCheckTrackerWindow = std::make_shared(CVAR_WINDOW("CheckTracker"), "Check Tracker", ImVec2(400, 540)); gui->AddGuiWindow(mCheckTrackerWindow); mCheckTrackerSettingsWindow = std::make_shared(CVAR_WINDOW("CheckTrackerSettings"), "Check Tracker Settings", ImVec2(600, 375)); gui->AddGuiWindow(mCheckTrackerSettingsWindow); - mEntranceTrackerWindow = std::make_shared(CVAR_WINDOW("EntranceTracker"), "Entrance Tracker"); + mEntranceTrackerWindow = std::make_shared(CVAR_WINDOW("EntranceTracker"), "Entrance Tracker", ImVec2(500, 750)); gui->AddGuiWindow(mEntranceTrackerWindow); mEntranceTrackerSettingsWindow = std::make_shared(CVAR_WINDOW("EntranceTrackerSettings"), "Entrance Tracker Settings", ImVec2(600, 375)); gui->AddGuiWindow(mEntranceTrackerSettingsWindow); - mItemTrackerWindow = std::make_shared(CVAR_WINDOW("ItemTracker"), "Item Tracker"); + mItemTrackerWindow = std::make_shared(CVAR_WINDOW("ItemTracker"), "Item Tracker", ImVec2(350, 600)); gui->AddGuiWindow(mItemTrackerWindow); mItemTrackerSettingsWindow = std::make_shared(CVAR_WINDOW("ItemTrackerSettings"), "Item Tracker Settings", ImVec2(733, 472)); gui->AddGuiWindow(mItemTrackerSettingsWindow); mRandomizerSettingsWindow = std::make_shared(CVAR_WINDOW("RandomizerSettings"), "Randomizer Settings", ImVec2(920, 600)); gui->AddGuiWindow(mRandomizerSettingsWindow); - mTimeSplitWindow = std::make_shared(CVAR_WINDOW("TimeSplitEnabled"), "Time Splits", ImVec2(450, 660)); + mTimeSplitWindow = std::make_shared(CVAR_WINDOW("TimeSplits"), "Time Splits", ImVec2(450, 660)); gui->AddGuiWindow(mTimeSplitWindow); - mPlandomizerWindow = std::make_shared(CVAR_WINDOW("PlandomizerWindow"), "Plandomizer Editor", ImVec2(850, 760)); + mPlandomizerWindow = std::make_shared(CVAR_WINDOW("PlandomizerEditor"), "Plandomizer Editor", ImVec2(850, 760)); gui->AddGuiWindow(mPlandomizerWindow); - mAdvancedResolutionSettingsWindow = std::make_shared(CVAR_WINDOW("AdvancedResolutionEditor"), "Advanced Resolution Settings", ImVec2(497, 599)); - gui->AddGuiWindow(mAdvancedResolutionSettingsWindow); mModalWindow = std::make_shared(CVAR_WINDOW("ModalWindow"), "Modal Window"); gui->AddGuiWindow(mModalWindow); mModalWindow->Show(); @@ -221,8 +189,6 @@ namespace SohGui { mNotificationWindow->Show(); mTimeDisplayWindow = std::make_shared(CVAR_WINDOW("TimeDisplayEnabled"), "Additional Timers"); gui->AddGuiWindow(mTimeDisplayWindow); - mAboutWindow = std::make_shared(CVAR_WINDOW("AboutWindow"), "About"); - gui->AddGuiWindow(mAboutWindow); } void Destroy() { @@ -231,7 +197,6 @@ namespace SohGui { mNotificationWindow = nullptr; mModalWindow = nullptr; - mAdvancedResolutionSettingsWindow = nullptr; mRandomizerSettingsWindow = nullptr; mItemTrackerWindow = nullptr; mItemTrackerSettingsWindow = nullptr; @@ -259,7 +224,6 @@ namespace SohGui { mTimeSplitWindow = nullptr; mPlandomizerWindow = nullptr; mTimeDisplayWindow = nullptr; - mAboutWindow = nullptr; } void RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function button1callback, std::function button2callback) { diff --git a/soh/soh/SohGui/SohGui.hpp b/soh/soh/SohGui/SohGui.hpp index 4544c13e9..f465d1d1b 100644 --- a/soh/soh/SohGui/SohGui.hpp +++ b/soh/soh/SohGui/SohGui.hpp @@ -26,18 +26,8 @@ #include "soh/Enhancements/randomizer/randomizer_settings_window.h" #include "soh/Enhancements/timesplits/TimeSplits.h" #include "soh/Enhancements/randomizer/Plandomizer.h" -#include "soh/AboutWindow.h" #include "SohModals.h" -#ifdef __cplusplus -extern "C" { -#endif - void enableBetaQuest(); - void disableBetaQuest(); -#ifdef __cplusplus -} -#endif - namespace SohGui { void SetupHooks(); void SetupGuiElements(); @@ -45,6 +35,9 @@ namespace SohGui { void Destroy(); void RegisterPopup(std::string title, std::string message, std::string button1 = "OK", std::string button2 = "", std::function button1callback = nullptr, std::function button2callback = nullptr); void ShowRandomizerSettingsMenu(); + UIWidgets::Colors GetMenuThemeColor(); } +#define THEME_COLOR SohGui::GetMenuThemeColor() + #endif /* SohGui_hpp */ diff --git a/soh/soh/SohGui/SohMenu.cpp b/soh/soh/SohGui/SohMenu.cpp new file mode 100644 index 000000000..d4aee295a --- /dev/null +++ b/soh/soh/SohGui/SohMenu.cpp @@ -0,0 +1,168 @@ +#include "SohMenu.h" +#include "soh/OTRGlobals.h" +#include "soh/Enhancements/controls/SohInputEditorWindow.h" +#include "window/gui/GuiMenuBar.h" +#include "window/gui/GuiElement.h" +#include +#include "StringHelper.h" +#include +#include + +extern std::unordered_map warpPointSceneList; + +namespace SohGui { +extern std::shared_ptr mSohMenu; + +using namespace UIWidgets; + +void SohMenu::AddSidebarEntry(std::string sectionName, std::string sidebarName, uint32_t columnCount) { + assert(!sectionName.empty()); + assert(!sidebarName.empty()); + menuEntries.at(sectionName).sidebars.emplace(sidebarName, SidebarEntry{ .columnCount = columnCount }); + menuEntries.at(sectionName).sidebarOrder.push_back(sidebarName); +} + +WidgetInfo& SohMenu::AddWidget(WidgetPath& pathInfo, std::string widgetName, WidgetType widgetType) { + assert(!widgetName.empty()); // Must be unique + assert(menuEntries.contains(pathInfo.sectionName)); // Section/header must already exist + assert(menuEntries.at(pathInfo.sectionName).sidebars.contains(pathInfo.sidebarName)); // Sidebar must already exist + std::unordered_map& sidebar = menuEntries.at(pathInfo.sectionName).sidebars; + uint8_t column = pathInfo.column; + if (sidebar.contains(pathInfo.sidebarName)) { + while (sidebar.at(pathInfo.sidebarName).columnWidgets.size() < column + 1) { + sidebar.at(pathInfo.sidebarName).columnWidgets.push_back({}); + } + } + SidebarEntry& entry = sidebar.at(pathInfo.sidebarName); + entry.columnWidgets.at(column).push_back({ .name = widgetName, .type = widgetType }); + WidgetInfo& widget = entry.columnWidgets.at(column).back(); + switch (widgetType) { + case WIDGET_CHECKBOX: + case WIDGET_CVAR_CHECKBOX: + widget.options = std::make_shared(); + break; + case WIDGET_SLIDER_FLOAT: + case WIDGET_CVAR_SLIDER_FLOAT: + widget.options = std::make_shared(); + break; + case WIDGET_SLIDER_INT: + case WIDGET_CVAR_SLIDER_INT: + widget.options = std::make_shared(); + break; + case WIDGET_COMBOBOX: + case WIDGET_CVAR_COMBOBOX: + case WIDGET_AUDIO_BACKEND: + case WIDGET_VIDEO_BACKEND: + widget.options = std::make_shared(); + break; + case WIDGET_BUTTON: + widget.options = std::make_shared(); + break; + case WIDGET_WINDOW_BUTTON: + widget.options = std::make_shared(); + break; + case WIDGET_COLOR_24: + case WIDGET_COLOR_32: + break; + case WIDGET_SEARCH: + case WIDGET_SEPARATOR: + case WIDGET_SEPARATOR_TEXT: + case WIDGET_TEXT: + default: + widget.options = std::make_shared(); + } + return widget; +} + +SohMenu::SohMenu(const std::string& consoleVariable, const std::string& name) + : Menu(consoleVariable, name, 0, UIWidgets::Colors::LightBlue) { +} + +void SohMenu::InitElement() { + Ship::Menu::InitElement(); + AddMenuSettings(); + AddMenuEnhancements(); + AddMenuRandomizer(); +#ifdef ENABLE_REMOTE_CONTROL + AddMenuNetwork(); +#endif + AddMenuDevTools(); + + if (CVarGetInteger(CVAR_SETTING("Menu.SidebarSearch"), 0)) { + InsertSidebarSearch(); + } + + for (auto& initFunc : MenuInit::GetInitFuncs()) { + initFunc(); + } + + disabledMap = { + { DISABLE_FOR_NO_VSYNC, + { [](disabledInfo& info) -> bool { + return !Ship::Context::GetInstance()->GetWindow()->CanDisableVerticalSync(); + }, + "Disabling VSync not supported" } }, + { DISABLE_FOR_NO_WINDOWED_FULLSCREEN, + { [](disabledInfo& info) -> bool { + return !Ship::Context::GetInstance()->GetWindow()->SupportsWindowedFullscreen(); + }, + "Windowed Fullscreen not supported" } }, + { DISABLE_FOR_NO_MULTI_VIEWPORT, + { [](disabledInfo& info) -> bool { + return !Ship::Context::GetInstance()->GetWindow()->GetGui()->SupportsViewports(); + }, + "Multi-viewports not supported" } }, + { DISABLE_FOR_NOT_DIRECTX, + { [](disabledInfo& info) -> bool { + return Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() != + Ship::WindowBackend::FAST3D_DXGI_DX11; + }, + "Available Only on DirectX" } }, + { DISABLE_FOR_DIRECTX, + { [](disabledInfo& info) -> bool { + return Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == + Ship::WindowBackend::FAST3D_DXGI_DX11; + }, + "Not Available on DirectX" } }, + { DISABLE_FOR_MATCH_REFRESH_RATE_ON, + { [](disabledInfo& info) -> bool { return CVarGetInteger(CVAR_SETTING("gMatchRefreshRate"), 0); }, + "Match Refresh Rate is Enabled" } }, + { DISABLE_FOR_ADVANCED_RESOLUTION_ON, + { [](disabledInfo& info) -> bool { return CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0); }, + "Advanced Resolution Enabled" } }, + { DISABLE_FOR_VERTICAL_RES_TOGGLE_ON, + { [](disabledInfo& info) -> bool { + return CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0); + }, + "Vertical Resolution Toggle Enabled" } }, + { DISABLE_FOR_LOW_RES_MODE_ON, + { [](disabledInfo& info) -> bool { return CVarGetInteger(CVAR_LOW_RES_MODE, 0); }, "N64 Mode Enabled" } }, + { DISABLE_FOR_NULL_PLAY_STATE, + { [](disabledInfo& info) -> bool { return gPlayState == NULL; }, "Save Not Loaded" } }, + { DISABLE_FOR_DEBUG_MODE_OFF, + { [](disabledInfo& info) -> bool { return !CVarGetInteger("gDeveloperTools.DebugEnabled", 0); }, + "Debug Mode is Disabled" } }, + { DISABLE_FOR_FRAME_ADVANCE_OFF, + { [](disabledInfo& info) -> bool { return !(gPlayState != nullptr && gPlayState->frameAdvCtx.enabled); }, + "Frame Advance is Disabled" } }, + { DISABLE_FOR_ADVANCED_RESOLUTION_OFF, + { [](disabledInfo& info) -> bool { return !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0); }, + "Advanced Resolution is Disabled" } }, + { DISABLE_FOR_VERTICAL_RESOLUTION_OFF, + { [](disabledInfo& info) -> bool { return !CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0); }, + "Vertical Resolution Toggle is Off" } }, + }; +} + +void SohMenu::UpdateElement() { + Ship::Menu::UpdateElement(); +} + +void SohMenu::Draw() { + Ship::Menu::Draw(); +} + +void SohMenu::DrawElement() { + Ship::Menu::DrawElement(); +} +} // namespace SohGui diff --git a/soh/soh/SohGui/SohMenu.h b/soh/soh/SohGui/SohMenu.h new file mode 100644 index 000000000..3e5f7a555 --- /dev/null +++ b/soh/soh/SohGui/SohMenu.h @@ -0,0 +1,227 @@ +#ifndef SOHMENU_H +#define SOHMENU_H + +#include +#include "UIWidgets.hpp" +#include "Menu.h" +#include "graphic/Fast3D/gfx_rendering_api.h" +#include "soh/cvar_prefixes.h" +#include "soh/Enhancements/enhancementTypes.h" +#include "soh/Enhancements/presets.h" + +extern "C" { +#include "z64.h" +#include "functions.h" +#include "variables.h" +#include "macros.h" +extern PlayState* gPlayState; +} + +#ifdef __cplusplus +extern "C" { +#endif + void enableBetaQuest(); + void disableBetaQuest(); +#ifdef __cplusplus +} +#endif + +namespace SohGui { + +static const std::unordered_map menuThemeOptions = { + { UIWidgets::Colors::Red, "Red" }, + { UIWidgets::Colors::DarkRed, "Dark Red" }, + { UIWidgets::Colors::Orange, "Orange" }, + { UIWidgets::Colors::Green, "Green" }, + { UIWidgets::Colors::DarkGreen, "Dark Green" }, + { UIWidgets::Colors::LightBlue, "Light Blue" }, + { UIWidgets::Colors::Blue, "Blue" }, + { UIWidgets::Colors::DarkBlue, "Dark Blue" }, + { UIWidgets::Colors::Indigo, "Indigo" }, + { UIWidgets::Colors::Violet, "Violet" }, + { UIWidgets::Colors::Purple, "Purple" }, + { UIWidgets::Colors::Brown, "Brown" }, + { UIWidgets::Colors::Gray, "Gray" }, + { UIWidgets::Colors::DarkGray, "Dark Gray" }, +}; + +static const std::unordered_map textureFilteringMap = { + { FILTER_THREE_POINT, "Three-Point" }, + { FILTER_LINEAR, "Linear" }, + { FILTER_NONE, "None" }, +}; + +static const std::unordered_map logLevels = { + { DEBUG_LOG_TRACE, "Trace" }, { DEBUG_LOG_DEBUG, "Debug" }, { DEBUG_LOG_INFO, "Info" }, + { DEBUG_LOG_WARN, "Warn" }, { DEBUG_LOG_ERROR, "Error" }, { DEBUG_LOG_CRITICAL, "Critical" }, + { DEBUG_LOG_OFF, "Off" }, +}; + +static const std::unordered_map notificationPosition = { + { 0, "Top Left" }, { 1, "Top Right" }, { 2, "Bottom Left" }, { 3, "Bottom Right" }, { 4, "Hidden" }, +}; + +static const std::unordered_map dekuStickCheat = { + { DEKU_STICK_NORMAL, "Normal" }, + { DEKU_STICK_UNBREAKABLE, "Unbreakable" }, + { DEKU_STICK_UNBREAKABLE_AND_ALWAYS_ON_FIRE, "Unbreakable + Always on Fire" } +}; + +static const std::unordered_map skipGetItemAnimationOptions = { + { SGIA_DISABLED, "Disabled" }, + { SGIA_JUNK, "Junk Items" }, + { SGIA_ALL, "All Items" } +}; + +static const std::unordered_map chestStyleMatchesContentsOptions = { + { CSMC_DISABLED, "Disabled" }, + { CSMC_BOTH, "Both" }, + { CSMC_TEXTURE, "Texture Only" }, + { CSMC_SIZE, "Size Only" } +}; + +static const std::unordered_map timeTravelOptions = { + { TIME_TRAVEL_DISABLED, "Disabled" }, + { TIME_TRAVEL_OOT, "Ocarina of Time" }, + { TIME_TRAVEL_ANY, "Any Ocarina" } +}; + +static const std::unordered_map sleepingWaterfallOptions = { + { WATERFALL_ALWAYS, "Always" }, + { WATERFALL_ONCE, "Once" }, + { WATERFALL_NEVER, "Never" } +}; + +static const std::unordered_map allPowers = { + { DAMAGE_VANILLA, "Vanilla (1x)" }, + { DAMAGE_DOUBLE, "Double (2x)" }, + { DAMAGE_QUADRUPLE, "Quadruple (4x)" }, + { DAMAGE_OCTUPLE, "Octuple (8x)" }, + { DAMAGE_FOOLISH, "Foolish (16x)" }, + { DAMAGE_RIDICULOUS, "Ridiculous (32x)" }, + { DAMAGE_MERCILESS, "Merciless (64x)" }, + { DAMAGE_TORTURE, "Pure Torture (128x)" }, + { DAMAGE_OHKO, "OHKO (256x)" } +}; + +static const std::unordered_map subPowers = { + { DAMAGE_VANILLA, "Vanilla (1x)" }, + { DAMAGE_DOUBLE, "Double (2x)" }, + { DAMAGE_QUADRUPLE, "Quadruple (4x)" }, + { DAMAGE_OCTUPLE, "Octuple (8x)" }, + { DAMAGE_FOOLISH, "Foolish (16x)" }, + { DAMAGE_RIDICULOUS, "Ridiculous (32x)" }, + { DAMAGE_MERCILESS, "Merciless (64x)" }, + { DAMAGE_TORTURE, "Pure Torture (128x)" }, +}; + +static const std::unordered_map subSubPowers = { + { DAMAGE_VANILLA, "Vanilla (1x)" }, + { DAMAGE_DOUBLE, "Double (2x)" }, + { DAMAGE_QUADRUPLE, "Quadruple (4x)" }, + { DAMAGE_OCTUPLE, "Octuple (8x)" }, + { DAMAGE_FOOLISH, "Foolish (16x)" }, + { DAMAGE_RIDICULOUS, "Ridiculous (32x)" }, + { DAMAGE_MERCILESS, "Merciless (64x)" }, +}; + +static const std::unordered_map bonkDamageValues = { + { BONK_DAMAGE_NONE, "No Damage" }, + { BONK_DAMAGE_QUARTER_HEART, "0.25 Hearts" }, + { BONK_DAMAGE_HALF_HEART, "0.5 Hearts" }, + { BONK_DAMAGE_1_HEART, "1 Heart" }, + { BONK_DAMAGE_2_HEARTS, "2 Hearts" }, + { BONK_DAMAGE_4_HEARTS, "4 Hearts" }, + { BONK_DAMAGE_8_HEARTS, "8 Hearts" }, + { BONK_DAMAGE_OHKO, "OHKO" } +}; + +static const std::unordered_map cursorAnywhereValues = { + { PAUSE_ANY_CURSOR_RANDO_ONLY, "Only in Rando" }, + { PAUSE_ANY_CURSOR_ALWAYS_ON, "Always" }, + { PAUSE_ANY_CURSOR_ALWAYS_OFF, "Never" } +}; + +static const std::unordered_map swordToggleModes = { + { SWORD_TOGGLE_NONE, "None" }, + { SWORD_TOGGLE_CHILD, "Child Toggle" }, + { SWORD_TOGGLE_BOTH_AGES, "Both Ages" } +}; + +static const std::unordered_map zFightingOptions = { + { ZFIGHT_FIX_DISABLED, "Disabled" }, + { ZFIGHT_FIX_CONSISTENT_VANISH, "Consistent Vanish" }, + { ZFIGHT_FIX_NO_VANISH, "No Vanish" } +}; + +static const std::unordered_map mirroredWorldModes = { + { MIRRORED_WORLD_OFF, "Disabled" }, + { MIRRORED_WORLD_ALWAYS, "Always" }, + { MIRRORED_WORLD_RANDOM, "Random" }, + { MIRRORED_WORLD_RANDOM_SEEDED, "Random (Seeded)" }, + { MIRRORED_WORLD_DUNGEONS_ALL, "Dungeons" }, + { MIRRORED_WORLD_DUNGEONS_VANILLA, "Dungeons (Vanilla)" }, + { MIRRORED_WORLD_DUNGEONS_MQ, "Dungeons (MQ)" }, + { MIRRORED_WORLD_DUNGEONS_RANDOM, "Dungeons Random" }, + { MIRRORED_WORLD_DUNGEONS_RANDOM_SEEDED, "Dungeons Random (Seeded)"} +}; + +static const std::unordered_map enemyRandomizerModes = { + { ENEMY_RANDOMIZER_OFF, "Disabled" }, + { ENEMY_RANDOMIZER_RANDOM, "Random" }, + { ENEMY_RANDOMIZER_RANDOM_SEEDED, "Random (Seeded)"} +}; + +static const std::unordered_map debugSaveFileModes = { + { 0, "Off" }, { 1, "Vanilla" }, { 2, "Maxed" }, +}; + +static const std::unordered_map bootSequenceLabels = { + { BOOTSEQUENCE_DEFAULT, "Default" }, + { BOOTSEQUENCE_AUTHENTIC, "Authentic" }, + { BOOTSEQUENCE_FILESELECT, "File Select" } +}; + +static const std::unordered_map enhancementPresetList = { + { ENHANCEMENT_PRESET_DEFAULT, "Default" }, + { ENHANCEMENT_PRESET_VANILLA_PLUS, "Vanilla Plus" }, + { ENHANCEMENT_PRESET_ENHANCED, "Enhanced" }, + { ENHANCEMENT_PRESET_RANDOMIZER, "Randomizer" } +}; + +static const char* itemCountMessageCVars[3] = { + CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula"), + CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"), + CVAR_ENHANCEMENT("InjectItemCounts.HeartContainer"), +}; + +static const char* itemCountMessageOptions[ARRAY_COUNT(itemCountMessageCVars)] = { + "Gold Skulltula Tokens", + "Pieces of Heart", + "Heart Containers", +}; + +class SohMenu : public Ship::Menu { + public: + SohMenu(const std::string& consoleVariable, const std::string& name); + + void InitElement() override; + void DrawElement() override; + void UpdateElement() override; + void Draw() override; + + void AddSidebarEntry(std::string sectionName, std::string sidbarName, uint32_t columnCount); + WidgetInfo& AddWidget(WidgetPath& pathInfo, std::string widgetName, WidgetType widgetType); + void AddMenuSettings(); + void AddMenuEnhancements(); + void AddMenuDevTools(); + void AddMenuRandomizer(); + void AddMenuNetwork(); + + private: + char mGitCommitHashTruncated[8]; + bool mIsTaggedVersion; +}; +} // namespace SohGui + +#endif // SOHMENU_H diff --git a/soh/soh/SohGui/SohMenuBar.cpp b/soh/soh/SohGui/SohMenuBar.cpp index 5fd7324fb..18fa8b53d 100644 --- a/soh/soh/SohGui/SohMenuBar.cpp +++ b/soh/soh/SohGui/SohMenuBar.cpp @@ -39,12 +39,10 @@ #include "soh/Enhancements/randomizer/randomizer_entrance_tracker.h" #include "soh/Enhancements/randomizer/randomizer_item_tracker.h" #include "soh/Enhancements/randomizer/randomizer_settings_window.h" -#include "soh/Enhancements/resolution-editor/ResolutionEditor.h" #include "soh/Enhancements/enemyrandomizer.h" #include "soh/Enhancements/timesplits/TimeSplits.h" #include "soh/Enhancements/randomizer/Plandomizer.h" #include "soh/Enhancements/TimeDisplay/TimeDisplay.h" -#include "soh/AboutWindow.h" // FA icons are kind of wonky, if they worked how I expected them to the "+ 2.0f" wouldn't be needed, but // they don't work how I expect them to so I added that because it looked good when I eyeballed it @@ -81,55 +79,6 @@ static const char* imguiScaleOptions[4] = { "Small", "Normal", "Large", "X-Large "Linear", "None" }; - static const char* chestStyleMatchesContentsOptions[4] = { "Disabled", "Both", "Texture Only", "Size Only" }; - static const char* skipGetItemAnimationOptions[3] = { "Disabled", "Junk Items", "All Items" }; - static const char* skipForcedDialogOptions[4] = { "None", "Navi Only", "NPCs Only", "All" }; - static const char* sleepingWaterfallOptions[3] = { "Always", "Once", "Never" }; - static const char* bunnyHoodOptions[3] = { "Disabled", "Faster Run & Longer Jump", "Faster Run" }; - static const char* mirroredWorldModes[9] = { - "Disabled", "Always", "Random", "Random (Seeded)", "Dungeons", - "Dungeons (Vanilla)", "Dungeons (MQ)", "Dungeons Random", "Dungeons Random (Seeded)", - }; - static const char* enemyRandomizerModes[3] = { "Disabled", "Random", "Random (Seeded)" }; - static const char* allPowers[9] = { - "Vanilla (1x)", - "Double (2x)", - "Quadruple (4x)", - "Octuple (8x)", - "Foolish (16x)", - "Ridiculous (32x)", - "Merciless (64x)", - "Pure Torture (128x)", - "OHKO (256x)" }; - static const char* subPowers[8] = { allPowers[0], allPowers[1], allPowers[2], allPowers[3], allPowers[4], allPowers[5], allPowers[6], allPowers[7] }; - static const char* subSubPowers[7] = { allPowers[0], allPowers[1], allPowers[2], allPowers[3], allPowers[4], allPowers[5], allPowers[6] }; - static const char* zFightingOptions[3] = { "Disabled", "Consistent Vanish", "No Vanish" }; - static const char* bootSequenceLabels[3] = { "Default", "Authentic", "File Select" }; - static const char* DebugSaveFileModes[3] = { "Off", "Vanilla", "Maxed" }; - static const char* DekuStickCheat[3] = { "Normal", "Unbreakable", "Unbreakable + Always on Fire" }; - static const char* bonkDamageValues[8] = { - "No Damage", - "0.25 Heart", - "0.5 Heart", - "1 Heart", - "2 Hearts", - "4 Hearts", - "8 Hearts", - "OHKO" - }; - static const char* timeTravelOptions[3] = { "Disabled", "Ocarina of Time", "Any Ocarina" }; - static const char* swordToggleModes[3] = { "Disabled", "Child Toggle", "Both Ages (May lead to unintended behaviour)"}; - static const char* itemCountMessageCVars[3] = { - CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula"), - CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"), - CVAR_ENHANCEMENT("InjectItemCounts.HeartContainer"), - }; - static const char* itemCountMessageOptions[ARRAY_COUNT(itemCountMessageCVars)] = { - "Gold Skulltula Tokens", - "Pieces of Heart", - "Heart Containers", - }; - extern "C" SaveContext gSaveContext; namespace SohGui { @@ -137,2058 +86,43 @@ namespace SohGui { std::unordered_map availableWindowBackendsMap; Ship::WindowBackend configWindowBackend; -void UpdateWindowBackendObjects() { - Ship::WindowBackend runningWindowBackend = Ship::Context::GetInstance()->GetWindow()->GetWindowBackend(); - int32_t configWindowBackendId = Ship::Context::GetInstance()->GetConfig()->GetInt("Window.Backend.Id", -1); - if (Ship::Context::GetInstance()->GetWindow()->IsAvailableWindowBackend(configWindowBackendId)) { - configWindowBackend = static_cast(configWindowBackendId); - } else { - configWindowBackend = runningWindowBackend; - } - - auto availableWindowBackends = Ship::Context::GetInstance()->GetWindow()->GetAvailableWindowBackends(); - for (auto& backend : *availableWindowBackends) { - availableWindowBackendsMap[backend] = windowBackendNames[backend]; - } -} - -void DrawMenuBarIcon() { - static bool gameIconLoaded = false; - if (!gameIconLoaded) { - Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage("Game_Icon", "textures/icons/gIcon.png"); - gameIconLoaded = true; - } - - if (Ship::Context::GetInstance()->GetWindow()->GetGui()->HasTextureByName("Game_Icon")) { -#ifdef __SWITCH__ - ImVec2 iconSize = ImVec2(20.0f, 20.0f); - float posScale = 1.0f; -#elif defined(__WIIU__) - ImVec2 iconSize = ImVec2(16.0f * 2, 16.0f * 2); - float posScale = 2.0f; -#else - ImVec2 iconSize = ImVec2(16.0f, 16.0f); - float posScale = 1.0f; -#endif - ImGui::SetCursorPos(ImVec2(5, 2.5f) * posScale); - ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("Game_Icon"), iconSize); - ImGui::SameLine(); - ImGui::SetCursorPos(ImVec2(25, 0) * posScale); - } -} - -extern std::shared_ptr mAboutWindow; - -void DrawShipMenu() { - if (ImGui::BeginMenu("Ship")) { - if (mAboutWindow) { - if (ImGui::MenuItem("About...")) { - mAboutWindow->Show(); - } - } - - UIWidgets::Spacer(0); - - if (ImGui::MenuItem("Hide Menu Bar", -#if !defined(__SWITCH__) && !defined(__WIIU__) - "F1" -#else - "[-]" -#endif - )) { - Ship::Context::GetInstance()->GetWindow()->GetGui()->GetMenuBar()->ToggleVisibility(); - } - UIWidgets::Spacer(0); -#if !defined(__SWITCH__) && !defined(__WIIU__) - if (ImGui::MenuItem("Toggle Fullscreen", "F11")) { - Ship::Context::GetInstance()->GetWindow()->ToggleFullscreen(); - } - UIWidgets::Spacer(0); -#endif - if (ImGui::MenuItem("Reset", -#ifdef __APPLE__ - "Command-R" -#elif !defined(__SWITCH__) && !defined(__WIIU__) - "Ctrl+R" -#else - "" -#endif - )) { - std::reinterpret_pointer_cast(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch("reset"); - } -#if !defined(__SWITCH__) && !defined(__WIIU__) - UIWidgets::Spacer(0); - if (ImGui::MenuItem("Open App Files Folder")) { - std::string filesPath = Ship::Context::GetInstance()->GetAppDirectoryPath(); - SDL_OpenURL(std::string("file:///" + std::filesystem::absolute(filesPath).string()).c_str()); - } - UIWidgets::Spacer(0); - - if (ImGui::MenuItem("Quit")) { - Ship::Context::GetInstance()->GetWindow()->Close(); - } -#endif - ImGui::EndMenu(); - } -} - -extern std::shared_ptr mInputEditorWindow; extern std::shared_ptr mGfxDebuggerWindow; -extern std::shared_ptr mInputViewer; -extern std::shared_ptr mInputViewerSettings; -extern std::shared_ptr mAdvancedResolutionSettingsWindow; void DrawSettingsMenu() { - if (ImGui::BeginMenu("Settings")) - { - if (ImGui::BeginMenu("Audio")) { - UIWidgets::PaddedEnhancementSliderInt("Master Volume: %d %%", "##Master_Vol", CVAR_SETTING("Volume.Master"), 0, 100, "", 100, true, false, true); - if (UIWidgets::PaddedEnhancementSliderInt("Main Music Volume: %d %%", "##Main_Music_Vol", CVAR_SETTING("Volume.MainMusic"), 0, 100, "", 100, true, false, true)) { - Audio_SetGameVolume(SEQ_PLAYER_BGM_MAIN, ((float)CVarGetInteger(CVAR_SETTING("Volume.MainMusic"), 100) / 100.0f)); - } - if (UIWidgets::PaddedEnhancementSliderInt("Sub Music Volume: %d %%", "##Sub_Music_Vol", CVAR_SETTING("Volume.SubMusic"), 0, 100, "", 100, true, false, true)) { - Audio_SetGameVolume(SEQ_PLAYER_BGM_SUB, ((float)CVarGetInteger(CVAR_SETTING("Volume.SubMusic"), 100) / 100.0f)); - } - if (UIWidgets::PaddedEnhancementSliderInt("Fanfare Volume: %d %%", "##Fanfare_Vol", CVAR_SETTING("Volume.Fanfare"), 0, 100, "", 100, true, false, true)) { - Audio_SetGameVolume(SEQ_PLAYER_FANFARE, ((float)CVarGetInteger(CVAR_SETTING("Volume.Fanfare"), 100) / 100.0f)); - } - if (UIWidgets::PaddedEnhancementSliderInt("Sound Effects Volume: %d %%", "##Sound_Effect_Vol", CVAR_SETTING("Volume.SFX"), 0, 100, "", 100, true, false, true)) { - Audio_SetGameVolume(SEQ_PLAYER_SFX, ((float)CVarGetInteger(CVAR_SETTING("Volume.SFX"), 100) / 100.0f)); - } - - static std::unordered_map audioBackendNames = { - { Ship::AudioBackend::WASAPI, "Windows Audio Session API" }, - { Ship::AudioBackend::SDL, "SDL" } - }; - - ImGui::Text("Audio API (Needs reload)"); - auto currentAudioBackend = Ship::Context::GetInstance()->GetAudio()->GetCurrentAudioBackend(); - - if (Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - if (ImGui::BeginCombo("##AApi", audioBackendNames[currentAudioBackend])) { - for (uint8_t i = 0; i < Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size(); i++) { - auto backend = Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->data()[i]; - if (ImGui::Selectable(audioBackendNames[backend], backend == currentAudioBackend)) { - Ship::Context::GetInstance()->GetAudio()->SetCurrentAudioBackend(backend); - } - } - ImGui::EndCombo(); - } - if (Ship::Context::GetInstance()->GetAudio()->GetAvailableAudioBackends()->size() <= 1) { - UIWidgets::ReEnableComponent(""); - } - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Controller")) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2 (12.0f, 6.0f)); - 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 (mInputEditorWindow) { - if (ImGui::Button(GetWindowButtonText("Controller Mapping", CVarGetInteger(CVAR_WINDOW("ControllerConfiguration"), 0)).c_str(), ImVec2 (-1.0f, 0.0f))) { - mInputEditorWindow->ToggleVisibility(); - } - } - UIWidgets::PaddedSeparator(); - ImGui::PopStyleColor(1); - ImGui::PopStyleVar(3); - #ifndef __SWITCH__ - UIWidgets::EnhancementCheckbox("Menubar Controller Navigation", CVAR_IMGUI_CONTROLLER_NAV); - UIWidgets::Tooltip("Allows controller navigation of the SOH menu bar (Settings, Enhancements,...)\nCAUTION: This will disable game inputs while the menubar is visible.\n\nD-pad to move between items, A to select, and X to grab focus on the menu bar"); - UIWidgets::PaddedSeparator(); - #endif - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2 (12.0f, 6.0f)); - 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 (mInputViewer) { - if (ImGui::Button(GetWindowButtonText("Input Viewer", CVarGetInteger(CVAR_WINDOW("InputViewer"), 0)).c_str(), ImVec2 (-1.0f, 0.0f))) { - mInputViewer->ToggleVisibility(); - } - } - if (mInputViewerSettings) { - if (ImGui::Button(GetWindowButtonText("Input Viewer Settings", CVarGetInteger(CVAR_WINDOW("InputViewerSettings"), 0)).c_str(), ImVec2 (-1.0f, 0.0f))) { - mInputViewerSettings->ToggleVisibility(); - } - } - ImGui::PopStyleColor(1); - ImGui::PopStyleVar(3); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Graphics")) { - #ifndef __APPLE__ - const bool disabled_resolutionSlider = CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".VerticalResolutionToggle", 0) && - CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0); - if (UIWidgets::EnhancementSliderFloat("Internal Resolution: %.1f %%", "##IMul", CVAR_INTERNAL_RESOLUTION, 0.5f, - 2.0f, "", 1.0f, true, true, disabled_resolutionSlider)) { - Ship::Context::GetInstance()->GetWindow()->SetResolutionMultiplier(CVarGetFloat(CVAR_INTERNAL_RESOLUTION, 1)); - } - UIWidgets::Tooltip("Resolution scale. Multiplies output resolution by this value, on each axis relative to window size.\n" - "Lower values may improve performance.\n" - "Values above 100% can be used for super-sampling, as an intensive but highly effective form of anti-aliasing.\n\n" - "Default: 100%"); - - if (mAdvancedResolutionSettingsWindow) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); - 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)); - UIWidgets::Spacer(0); - if (ImGui::Button(GetWindowButtonText("Advanced Resolution", CVarGetInteger(CVAR_WINDOW("AdvancedResolutionEditor"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mAdvancedResolutionSettingsWindow->ToggleVisibility(); - } - ImGui::PopStyleColor(1); - ImGui::PopStyleVar(3); - } - #else - // macOS: Internal resolution is currently disabled in libultraship. - ImGui::BeginGroup(); - ImGui::Text("Internal Resolution: 100.0%%"); - UIWidgets::Spacer(0); - ImGui::Text(" " ICON_FA_INFO_CIRCLE " Not available on this system."); - UIWidgets::Spacer(0); - ImGui::EndGroup(); - #endif - - #ifndef __WIIU__ - if (UIWidgets::PaddedEnhancementSliderInt( - (CVarGetInteger(CVAR_MSAA_VALUE, 1) == 1) ? "Anti-aliasing (MSAA): Off" : "Anti-aliasing (MSAA): %d", - "##IMSAA", CVAR_MSAA_VALUE, 1, 8, "", 1, true, true, false)) { - Ship::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger(CVAR_MSAA_VALUE, 1)); - } - UIWidgets::Tooltip("Activates MSAA (multi-sample anti-aliasing) from 2x up to 8x, to smooth the edges of rendered geometry.\n" - "Higher sample count will result in smoother edges on models, but may reduce performance.\n\n" - "Recommended: 2x or 4x"); - #endif - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - { // FPS Slider - const int minFps = 20; - static int maxFps; - if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { - maxFps = 360; - } else { - maxFps = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); - } - int currentFps = fmax(fmin(OTRGlobals::Instance->GetInterpolationFPS(), maxFps), minFps); - #ifdef __WIIU__ - UIWidgets::Spacer(0); - // only support divisors of 60 on the Wii U - if (currentFps > 60) { - currentFps = 60; - } else { - currentFps = 60 / (60 / currentFps); - } - - int fpsSlider = 1; - if (currentFps == 20) { - ImGui::Text("FPS: Original (20)"); - } else { - ImGui::Text("FPS: %d", currentFps); - if (currentFps == 30) { - fpsSlider = 2; - } else { // currentFps == 60 - fpsSlider = 3; - } - } - if (CVarGetInteger(CVAR_SETTING("MatchRefreshRate"), 0)) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - if (ImGui::Button(" - ##WiiUFPS")) { - fpsSlider--; - } - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - - UIWidgets::Spacer(0); - - ImGui::PushItemWidth(std::min((ImGui::GetContentRegionAvail().x - 60.0f), 260.0f)); - ImGui::SliderInt("##WiiUFPSSlider", &fpsSlider, 1, 3, "", ImGuiSliderFlags_AlwaysClamp); - ImGui::PopItemWidth(); - - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - if (ImGui::Button(" + ##WiiUFPS")) { - fpsSlider++; - } - - if (CVarGetInteger(CVAR_SETTING("MatchRefreshRate"), 0)) { - UIWidgets::ReEnableComponent(""); - } - if (fpsSlider > 3) { - fpsSlider = 3; - } else if (fpsSlider < 1) { - fpsSlider = 1; - } - - if (fpsSlider == 1) { - currentFps = 20; - } else if (fpsSlider == 2) { - currentFps = 30; - } else if (fpsSlider == 3) { - currentFps = 60; - } - CVarSetInteger(CVAR_SETTING("InterpolationFPS"), currentFps); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - #else - bool matchingRefreshRate = - CVarGetInteger(CVAR_SETTING("MatchRefreshRate"), 0) && Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() != Ship::WindowBackend::FAST3D_DXGI_DX11; - UIWidgets::PaddedEnhancementSliderInt( - (currentFps == 20) ? "Frame Rate: Original (20 fps)" : "Frame Rate: %d fps", - "##FPSInterpolation", CVAR_SETTING("InterpolationFPS"), minFps, maxFps, "", 20, true, true, false, matchingRefreshRate); - #endif - if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { - UIWidgets::Tooltip( - "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics.\n" - "This is purely visual and does not impact game logic, execution of glitches etc.\n" - "Higher frame rate settings may impact CPU performance." - "\n\n " ICON_FA_INFO_CIRCLE - " There is no need to set this above your monitor's refresh rate. Doing so will waste resources and may give a worse result."); - } else { - UIWidgets::Tooltip( - "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics.\n" - "This is purely visual and does not impact game logic, execution of glitches etc.\n" - "Higher frame rate settings may impact CPU performance."); - } - } // END FPS Slider - - if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { - UIWidgets::Spacer(0); - if (ImGui::Button("Match Frame Rate to Refresh Rate")) { - int hz = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); - if (hz >= 20 && hz <= 360) { - CVarSetInteger(CVAR_SETTING("InterpolationFPS"), hz); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - } - } else { - UIWidgets::PaddedEnhancementCheckbox("Match Frame Rate to Refresh Rate", CVAR_SETTING("MatchRefreshRate"), true, false); - } - UIWidgets::Tooltip("Matches interpolation value to the game window's current refresh rate."); - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - - ImGui::Text("ImGui Menu Scale"); - ImGui::SameLine(); - ImGui::TextColored({ 0.85f, 0.35f, 0.0f, 1.0f }, "(Experimental)"); - if (UIWidgets::EnhancementCombobox(CVAR_SETTING("ImGuiScale"), imguiScaleOptions, 1)) { - OTRGlobals::Instance->ScaleImGui(); - } - UIWidgets::Tooltip("Changes the scaling of the ImGui menu elements."); - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - - ImGui::Text("Renderer API (Needs reload)"); - - if (availableWindowBackendsMap.size() <= 1) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - if (ImGui::BeginCombo("##RApi", availableWindowBackendsMap[configWindowBackend])) { - for (auto backend : availableWindowBackendsMap) { - if (ImGui::Selectable(backend.second, backend.first == configWindowBackend)) { - Ship::Context::GetInstance()->GetConfig()->SetInt("Window.Backend.Id", static_cast(backend.first)); - Ship::Context::GetInstance()->GetConfig()->SetString("Window.Backend.Name", backend.second); - Ship::Context::GetInstance()->GetConfig()->Save(); - UpdateWindowBackendObjects(); - } - } - ImGui::EndCombo(); - } - if (availableWindowBackendsMap.size() <= 1) { - UIWidgets::ReEnableComponent(""); - } - - if (Ship::Context::GetInstance()->GetWindow()->CanDisableVerticalSync()) { - UIWidgets::PaddedEnhancementCheckbox("Enable Vsync", CVAR_VSYNC_ENABLED, true, false); - UIWidgets::Tooltip("Activate vertical sync, to prevent screen tearing."); - } - - if (Ship::Context::GetInstance()->GetWindow()->SupportsWindowedFullscreen()) { - UIWidgets::PaddedEnhancementCheckbox("Windowed fullscreen", CVAR_SDL_WINDOWED_FULLSCREEN, true, false); - } - - if (Ship::Context::GetInstance()->GetWindow()->GetGui()->SupportsViewports()) { - UIWidgets::PaddedEnhancementCheckbox("Allow multi-windows (Needs reload)", CVAR_ENABLE_MULTI_VIEWPORTS, true, false, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Allows windows to be able to be dragged off of the main game window. Requires a reload to take effect."); - } - - // If more filters are added to LUS, make sure to add them to the filters list here - ImGui::Text("Texture Filtering (Needs reload)"); - UIWidgets::EnhancementCombobox(CVAR_TEXTURE_FILTER, filters, FILTER_THREE_POINT); - UIWidgets::Tooltip("Texture filtering, aka texture smoothing. Requires a reload to take effect.\n\n" - "Three-Point: Replicates real N64 texture filtering.\n" - "Bilinear: If Three-Point causes poor performance, try this.\n" - "Nearest: Disables texture smoothing. (Not recommended)"); - - UIWidgets::PaddedSeparator(true, true, 3.0f, 3.0f); - - // Draw LUS settings menu (such as Overlays Text Font) - Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->DrawSettings(); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Languages")) { - UIWidgets::PaddedEnhancementCheckbox("Translate Title Screen", CVAR_SETTING("TitleScreenTranslation")); - if (UIWidgets::EnhancementRadioButton("English", CVAR_SETTING("Languages"), LANGUAGE_ENG)) { - GameInteractor::Instance->ExecuteHooks(); - } - if (UIWidgets::EnhancementRadioButton("German", CVAR_SETTING("Languages"), LANGUAGE_GER)) { - GameInteractor::Instance->ExecuteHooks(); - } - if (UIWidgets::EnhancementRadioButton("French", CVAR_SETTING("Languages"), LANGUAGE_FRA)) { - GameInteractor::Instance->ExecuteHooks(); - } - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Accessibility")) { - #if defined(_WIN32) || defined(__APPLE__) - UIWidgets::PaddedEnhancementCheckbox("Text to Speech", CVAR_SETTING("A11yTTS")); - UIWidgets::Tooltip("Enables text to speech for in game dialog"); - #endif - UIWidgets::PaddedEnhancementCheckbox("Disable Idle Camera Re-Centering", CVAR_SETTING("A11yDisableIdleCam")); - UIWidgets::Tooltip("Disables the automatic re-centering of the camera when idle."); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Notifications")) { - static const char* notificationPosition[] = { - "Top Left", - "Top Right", - "Bottom Left", - "Bottom Right", - "Hidden", - }; - - ImGui::Text("Position"); - UIWidgets::EnhancementCombobox(CVAR_SETTING("Notifications.Position"), notificationPosition, 0); - UIWidgets::EnhancementSliderFloat("Duration: %.1f seconds", "##NotificationDuration", CVAR_SETTING("Notifications.Duration"), 3.0f, 30.0f, "", 10.0f, false, true, false); - UIWidgets::EnhancementSliderFloat("BG Opacity: %.1f %%", "##NotificaitonBgOpacity", CVAR_SETTING("Notifications.BgOpacity"), 0.0f, 1.0f, "", 0.5f, true, true, false); - UIWidgets::EnhancementSliderFloat("Size: %.1f", "##NotificaitonSize", CVAR_SETTING("Notifications.Size"), 1.0f, 20.0f, "", 1.8f, false, true, false); - - UIWidgets::Spacer(0); - - if (ImGui::Button("Test Notification", ImVec2(-1.0f, 0.0f))) { - Notification::Emit({ - .message = (gPlayState != NULL ? SohUtils::GetSceneName(gPlayState->sceneNum) : "Hyrule") + " looks beautiful today!", - }); - } - - ImGui::EndMenu(); - } - - ImGui::EndMenu(); - } -} - -extern std::shared_ptr mAudioEditorWindow; -extern std::shared_ptr mCosmeticsEditorWindow; -extern std::shared_ptr mGameplayStatsWindow; -extern std::shared_ptr mTimeSplitWindow; -extern std::shared_ptr mTimeDisplayWindow; - -void DrawEnhancementsMenu() { - if (ImGui::BeginMenu("Enhancements")) - { - ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); - - DrawPresetSelector(PRESET_TYPE_ENHANCEMENTS); - - UIWidgets::PaddedSeparator(); - - if (ImGui::BeginMenu("Gameplay")) - { - if (ImGui::BeginMenu("Time Savers")) - { - ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 8.0f); - ImGui::BeginTable("##timeSaversMenu", 2, ImGuiTableFlags_SizingFixedFit); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableNextColumn(); - UIWidgets::Spacer(0); - ImGui::Text("Speed-ups:"); - UIWidgets::PaddedSeparator(); - bool allSkipsChecked = - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), IS_RANDO); - bool someSkipsChecked = - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), IS_RANDO); - - ImGuiContext* g = ImGui::GetCurrentContext(); - ImGuiItemFlags backup_item_flags = g->CurrentItemFlags; - if (!allSkipsChecked && someSkipsChecked) g->CurrentItemFlags |= ImGuiItemFlags_MixedValue; - if (ImGui::Checkbox("All", &allSkipsChecked)) { - int32_t newValue = allSkipsChecked ? 1 : 0; - - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), newValue); - CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), newValue); - - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - g->CurrentItemFlags = backup_item_flags; - UIWidgets::PaddedEnhancementCheckbox("Skip Intro", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip Entrance Cutscenes", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip Story Cutscenes", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip Song Cutscenes", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip Boss Introductions", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Quick Boss Deaths", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip One Point Cutscenes (Chests, Door Unlocks, etc)", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip Owl Interactions", CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Skip Misc Interactions", CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Disable Title Card", CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); - UIWidgets::PaddedEnhancementCheckbox("Exclude Glitch-Aiding Cutscenes", CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, 0); - UIWidgets::Tooltip("Don't skip cutscenes that are associated with useful glitches, currently this is only the Fire Temple Darunia CS, Forest Temple Poe Sisters CS and the Box Skip One Point in Jabu"); - UIWidgets::PaddedEnhancementCheckbox("Skip Child Stealth", CVAR_ENHANCEMENT("TimeSavers.SkipChildStealth"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, false); - UIWidgets::Tooltip("The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards."); - UIWidgets::PaddedEnhancementCheckbox("Skip Tower Escape", CVAR_ENHANCEMENT("TimeSavers.SkipTowerEscape"), false, false, false, "", UIWidgets::CheckboxGraphics::Cross, false); - UIWidgets::Tooltip("Skip the tower escape sequence between Ganondorf and Ganon."); - - UIWidgets::PaddedText("Skip Get Item Animations", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), skipGetItemAnimationOptions, SGIA_DISABLED); - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED) != SGIA_DISABLED) { - UIWidgets::EnhancementSliderFloat("Item Scale: %f", "##ItemScale", CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimationScale"), 5.0f, 15.0f, "", 10.0f, false); - UIWidgets::Tooltip("The size of the item when it is picked up"); - } - - UIWidgets::PaddedText("Skip Forced Dialog", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("TimeSavers.SkipForcedDialog"), skipForcedDialogOptions, FORCED_DIALOG_SKIP_NONE); - UIWidgets::Tooltip("Prevent forced conversations with Navi or other NPCs"); - - UIWidgets::PaddedEnhancementSliderInt("Text Speed: %dx", "##TEXTSPEED", CVAR_ENHANCEMENT("TextSpeed"), 1, 5, "", 1, true, false, true); - UIWidgets::PaddedEnhancementCheckbox("Skip Text", CVAR_ENHANCEMENT("SkipText"), false, true); - UIWidgets::Tooltip("Holding down B skips text"); - UIWidgets::PaddedEnhancementSliderInt("Slow Text Speed: %dx", "##SLOWTEXTSPEED", CVAR_ENHANCEMENT("SlowTextSpeed"), 1, 5, "", 1, true, false, true); - UIWidgets::Tooltip("Changes the speed of sections of text that normally are paced slower than the text surrounding it."); - if (ImGui::Button("Match Normal Text")) { - CVarSetInteger(CVAR_ENHANCEMENT("SlowTextSpeed"), CVarGetInteger(CVAR_ENHANCEMENT("TextSpeed"), 1)); - } - UIWidgets::Tooltip("Makes the speed of slow text match the normal text speed above."); - UIWidgets::PaddedEnhancementSliderFloat("King Zora Speed: %.2fx", "##MWEEPSPEED", CVAR_ENHANCEMENT("MweepSpeed"), 0.1f, 5.0f, "", 1.0f, false, true, false, true); - UIWidgets::PaddedEnhancementSliderInt("Vine/Ladder Climb speed +%d", "##CLIMBSPEED", CVAR_ENHANCEMENT("ClimbSpeed"), 0, 12, "", 0, true, false, true); - UIWidgets::PaddedEnhancementSliderInt("Block pushing speed +%d", "##BLOCKSPEED", CVAR_ENHANCEMENT("FasterBlockPush"), 0, 5, "", 0, true, false, true); - UIWidgets::PaddedEnhancementSliderInt("Crawl speed %dx", "##CRAWLSPEED", CVAR_ENHANCEMENT("CrawlSpeed"), 1, 4, "", 1, true, false, true); - UIWidgets::PaddedEnhancementCheckbox("Faster Heavy Block Lift", CVAR_ENHANCEMENT("FasterHeavyBlockLift"), false, false); - UIWidgets::Tooltip("Speeds up lifting silver rocks and obelisks"); - UIWidgets::PaddedEnhancementCheckbox("Skip Pickup Messages", CVAR_ENHANCEMENT("FastDrops"), true, false); - UIWidgets::Tooltip("Skip pickup messages for new consumable items and bottle swipes"); - UIWidgets::PaddedEnhancementCheckbox("Fast Ocarina Playback", CVAR_ENHANCEMENT("FastOcarinaPlayback"), true, false); - UIWidgets::Tooltip("Skip the part where the Ocarina playback is called when you play a song"); - UIWidgets::PaddedEnhancementCheckbox("Skip Magic Arrow Equip Animation", CVAR_ENHANCEMENT("SkipArrowAnimation"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Skip save confirmation", CVAR_ENHANCEMENT("SkipSaveConfirmation"), true, false); - UIWidgets::Tooltip("Skip the \"Game saved.\" confirmation screen"); - UIWidgets::PaddedEnhancementCheckbox("Faster Farore's Wind", CVAR_ENHANCEMENT("FastFarores"), true, false); - UIWidgets::Tooltip("Greatly decreases cast time of Farore's Wind magic spell."); - - ImGui::TableNextColumn(); - UIWidgets::Spacer(0); - ImGui::Text("Changes:"); - UIWidgets::PaddedSeparator(); - - UIWidgets::PaddedEnhancementSliderInt("Biggoron Forge Time: %d days", "##FORGETIME", CVAR_ENHANCEMENT("ForgeTime"), 0, 3, "", 3, true, false, true); - UIWidgets::Tooltip("Allows you to change the number of days it takes for Biggoron to forge the Biggoron Sword"); - UIWidgets::PaddedEnhancementCheckbox("Remember Save Location", CVAR_ENHANCEMENT("RememberSaveLocation"), false, false); - UIWidgets::Tooltip("When loading a save, places Link at the last entrance he went through.\n" - "This doesn't work if the save was made in grottos/fairy fountains or dungeons."); - UIWidgets::PaddedEnhancementCheckbox("Navi Timer Resets", CVAR_ENHANCEMENT("ResetNaviTimer"), true, false); - UIWidgets::Tooltip("Resets the Navi timer on scene change. If you have already talked to her, she will try and talk to you again, instead of needing a save warp or death. "); - UIWidgets::PaddedEnhancementCheckbox("No Skulltula Freeze", CVAR_ENHANCEMENT("SkulltulaFreeze"), true, false); - UIWidgets::Tooltip("Stops the game from freezing the player when picking up Gold Skulltulas"); - UIWidgets::PaddedEnhancementCheckbox("Nighttime GS Always Spawn", CVAR_ENHANCEMENT("NightGSAlwaysSpawn"), true, false); - UIWidgets::Tooltip("Nighttime Skulltulas will spawn during both day and night."); - UIWidgets::PaddedEnhancementCheckbox("Dampe Appears All Night", CVAR_ENHANCEMENT("DampeAllNight"), true, false); - UIWidgets::Tooltip("Makes Dampe appear anytime during the night, not just his usual working hours."); - UIWidgets::PaddedEnhancementCheckbox("Fast Chests", CVAR_ENHANCEMENT("FastChests"), true, false); - UIWidgets::Tooltip("Kick open every chest"); - UIWidgets::PaddedText("Chest size & texture matches contents", true, false); - if (UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents"), chestStyleMatchesContentsOptions, CSMC_DISABLED)) { - if (CVarGetInteger(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents"), CSMC_DISABLED) == CSMC_DISABLED) { - CVarSetInteger(CVAR_ENHANCEMENT("ChestSizeDependsStoneOfAgony"), 0); - } - } - UIWidgets::Tooltip( - "Chest sizes and textures are changed to help identify the item inside.\n" - " - Major items: Large gold chests\n" - " - Lesser items: Large brown chests\n" - " - Junk items: Small brown chests\n" - " - Small keys: Small silver chest\n" - " - Boss keys: Vanilla size and texture\n" - " - Skulltula Tokens: Small skulltula chest\n" - "\n" - "NOTE: Textures will not apply if you are using a mod pack with a custom chest model." - ); - if (CVarGetInteger(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents"), CSMC_DISABLED) != CSMC_DISABLED) { - UIWidgets::PaddedEnhancementCheckbox("Chests of Agony", CVAR_ENHANCEMENT("ChestSizeDependsStoneOfAgony"), true, false); - UIWidgets::Tooltip("Only change the size/texture of chests if you have the Stone of Agony."); - } - UIWidgets::PaddedEnhancementCheckbox("Ask to Equip New Items", CVAR_ENHANCEMENT("AskToEquip"), true, false); - UIWidgets::Tooltip("Adds a prompt to equip newly-obtained swords, shields and tunics"); - UIWidgets::PaddedEnhancementCheckbox("Better Owl", CVAR_ENHANCEMENT("BetterOwl"), true, false); - UIWidgets::Tooltip("The default response to Kaepora Gaebora is always that you understood what he said"); - UIWidgets::PaddedEnhancementCheckbox("Exit Market at Night", CVAR_ENHANCEMENT("MarketSneak"), true, false); - UIWidgets::Tooltip("Allows exiting Hyrule Castle Market Town to Hyrule Field at night by speaking to the guard next to the gate."); - bool randoLockedOverworldDoors = IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LOCK_OVERWORLD_DOORS); - UIWidgets::PaddedEnhancementCheckbox("Shops and Games Always Open", CVAR_ENHANCEMENT("OpenAllHours"), true, false, randoLockedOverworldDoors, - "This is not compatible with the Locked Overworld Doors Randomizer option", UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Shops and minigames are open both day and night. Requires scene reload to take effect."); - UIWidgets::PaddedEnhancementCheckbox("Link as default file name", CVAR_ENHANCEMENT("LinkDefaultName"), true, false); - UIWidgets::Tooltip("Allows you to have \"Link\" as a premade file name"); - UIWidgets::PaddedEnhancementCheckbox("Quit Fishing At Door", CVAR_ENHANCEMENT("QuitFishingAtDoor"), true, false); - UIWidgets::Tooltip("Fisherman asks if you want to quit at the door when you still have the rod"); - UIWidgets::PaddedText("Time Travel with the Song of Time", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("TimeTravel"), timeTravelOptions, 0); - UIWidgets::Tooltip("Allows Link to freely change age by playing the Song of Time.\n" - "Time Blocks can still be used properly.\n\n" - "Requirements:\n" - "- Obtained the Ocarina of Time (depends on selection)\n" - "- Obtained the Song of Time\n" - "- Obtained the Master Sword\n" - "- Not within range of Time Block\n" - "- Not within range of Ocarina playing spots"); - UIWidgets::PaddedEnhancementCheckbox("Pause Warp", CVAR_ENHANCEMENT("PauseWarp"), true, false); - UIWidgets::Tooltip("Selection of warp song in pause menu initiates warp. Disables song playback."); - UIWidgets::PaddedEnhancementCheckbox("Skip water take breath animation", CVAR_ENHANCEMENT("SkipSwimDeepEndAnim"), true, false); - UIWidgets::Tooltip("Skips Link's taking breath animation after coming up from water. This setting does not interfere with getting items from underwater."); - bool forceSkipScarecrow = IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SKIP_SCARECROWS_SONG); - static const char* forceSkipScarecrowText = "This setting is forcefully enabled because a savefile\nwith \"Skip Scarecrow Song\" is loaded"; - UIWidgets::PaddedEnhancementCheckbox("Skip Scarecrow Song", CVAR_ENHANCEMENT("InstantScarecrow"), true, false, - forceSkipScarecrow, forceSkipScarecrowText, UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Pierre appears when Ocarina is pulled out. Requires learning scarecrow song."); - bool forceSleepingWaterfallEnhancement = - IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SLEEPING_WATERFALL) == RO_WATERFALL_OPEN; - uint8_t forceSleepingWaterfallValue = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SLEEPING_WATERFALL) + 1; - static const char* forceSleepingWaterfallText = - "This setting is forcefully enabled because a randomizer savefile with \"Sleeping Waterfall: Open\" is loaded."; - UIWidgets::PaddedText("Play Zelda's Lullaby to open Sleeping Waterfall", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"), - sleepingWaterfallOptions, 0, forceSleepingWaterfallEnhancement, - forceSleepingWaterfallText, forceSleepingWaterfallValue); - UIWidgets::Tooltip( - "Always: Link must always play Zelda's Lullaby to open " - "the waterfall entrance to Zora's Domain.\n" - "Once: Link only needs to play Zelda's Lullaby once to " - "open the waterfall; after that, it stays open permanently.\n" - "Never: Link never needs to play Zelda's Lullaby to open the " - "waterfall; he only needs to have learned it and have an ocarina." - ); - - ImGui::EndTable(); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Items")) - { - UIWidgets::PaddedEnhancementCheckbox("Equip Items on D-pad", CVAR_ENHANCEMENT("DpadEquips"), true, false); - UIWidgets::Tooltip("Equip items and equipment on the D-pad\nIf used with \"D-pad on Pause Screen\", you " - "must hold C-Up to equip instead of navigate"); - UIWidgets::PaddedEnhancementCheckbox("Instant Putaway", CVAR_ENHANCEMENT("InstantPutaway"), true, false); - UIWidgets::Tooltip("Allow Link to put items away without having to wait around"); - UIWidgets::PaddedEnhancementCheckbox("Instant Boomerang Recall", CVAR_ENHANCEMENT("FastBoomerang"), true, false); - UIWidgets::Tooltip("Instantly return the boomerang to Link by pressing its item button while it's in the air"); - UIWidgets::PaddedEnhancementCheckbox("Prevent Dropped Ocarina Inputs", CVAR_ENHANCEMENT("DpadNoDropOcarinaInput"), true, false); - UIWidgets::Tooltip("Prevent dropping inputs when playing the ocarina quickly"); - UIWidgets::PaddedText("Bunny Hood Effect", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("MMBunnyHood"), bunnyHoodOptions, BUNNY_HOOD_VANILLA); - UIWidgets::Tooltip( - "Wearing the Bunny Hood grants a speed increase like in Majora's Mask. The longer jump option is not accounted for in randomizer logic.\n\n" - "Also disables NPC's reactions to wearing the Bunny Hood." - ); - UIWidgets::PaddedEnhancementCheckbox("Masks Equippable as Adult", CVAR_ENHANCEMENT("AdultMasks"), true, false); - UIWidgets::Tooltip("Allows masks to be equipped normally from the pause menu as adult."); - UIWidgets::PaddedEnhancementCheckbox("Persistent masks", CVAR_ENHANCEMENT("PersistentMasks"), true, false); - UIWidgets::Tooltip( - "Stops masks from automatically unequipping on certain situations:\n" - "- When entering a new scene\n" - "- When not in any C button or the D-Pad\n" - "- When saving and quitting\n" - "- When dying\n" - "- When traveling thru time (if \"Masks Equippable as Adult\" is activated)" - ); - UIWidgets::PaddedEnhancementCheckbox("Mask Select in Inventory", CVAR_ENHANCEMENT("MaskSelect"), true, false); - UIWidgets::Tooltip("After completing the mask trading sub-quest, press A and any direction on the mask slot to change masks"); - UIWidgets::PaddedEnhancementCheckbox("Nuts explode bombs", CVAR_ENHANCEMENT("NutsExplodeBombs"), true, false); - UIWidgets::Tooltip("Makes nuts explode bombs, similar to how they interact with bombchus. This does not affect bombflowers."); - UIWidgets::PaddedEnhancementCheckbox("Equip Multiple Arrows at Once", CVAR_ENHANCEMENT("SeparateArrows"), true, false); - UIWidgets::Tooltip("Allow the bow and magic arrows to be equipped at the same time on different slots. (Note this will disable the behaviour of the 'Equip Dupe' glitch)"); - UIWidgets::PaddedEnhancementCheckbox("Bow as Child/Slingshot as Adult", CVAR_ENHANCEMENT("BowSlingshotAmmoFix"), true, false); - UIWidgets::Tooltip("Allows child to use bow with arrows.\nAllows adult to use slingshot with seeds.\n\nRequires glitches or 'Timeless Equipment' cheat to equip."); - UIWidgets::PaddedEnhancementCheckbox("Better Farore's Wind", CVAR_ENHANCEMENT("BetterFarore"), true, false); - UIWidgets::Tooltip("Helps FW persist between ages, gives child and adult separate FW points, and can be used in more places."); - UIWidgets::PaddedEnhancementCheckbox("Remove Explosive Limit", CVAR_ENHANCEMENT("RemoveExplosiveLimit"), true, false); - UIWidgets::Tooltip("Removes the cap of 3 active explosives being deployed at once."); - UIWidgets::PaddedEnhancementCheckbox("Static Explosion Radius", CVAR_ENHANCEMENT("StaticExplosionRadius"), true, false); - UIWidgets::Tooltip("Explosions are now a static size, like in Majora's Mask and OoT3D. Makes bombchu hovering much easier."); - UIWidgets::PaddedEnhancementCheckbox("Prevent Bombchus Forcing First-Person", CVAR_ENHANCEMENT("DisableFirstPersonChus"), true, false); - UIWidgets::Tooltip("Prevent bombchus from forcing the camera into first-person mode when released."); - UIWidgets::PaddedEnhancementCheckbox("Better Bombchu Shopping", CVAR_ENHANCEMENT("BetterBombchuShopping"), true, false, - IS_RANDO, "This setting is forcefully enabled when you are playing a randomizer.", UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Bombchus do not sell out when bought, and a 10 pack of bombchus costs 99 rupees instead of 100." - "\n" - "Toggling while inside the shop will not change prices or restock any SOLD OUTs"); - UIWidgets::PaddedEnhancementCheckbox("Aiming reticle for the bow/slingshot", CVAR_ENHANCEMENT("BowReticle"), true, false); - UIWidgets::Tooltip("Aiming with a bow or slingshot will display a reticle as with the hookshot when the projectile is ready to fire."); - if (UIWidgets::PaddedEnhancementCheckbox("Aim boomerang in first-person mode", CVAR_ENHANCEMENT("BoomerangFirstPerson"), true, false)) { - if (!CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0)) { - CVarSetInteger(CVAR_ENHANCEMENT("BoomerangReticle"), 0); - } - } - UIWidgets::Tooltip( - "Change aiming for the boomerang from third person to first person to see past Link's head"); - if (CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0)) { - UIWidgets::PaddedEnhancementCheckbox("Aiming reticle for boomerang", CVAR_ENHANCEMENT("BoomerangReticle"), true, false); - UIWidgets::Tooltip("Aiming with the boomerang will display a reticle as with the hookshot"); - } - if (UIWidgets::PaddedEnhancementCheckbox("Allow strength equipment to be toggled", CVAR_ENHANCEMENT("ToggleStrength"), true, false)) { - if (!CVarGetInteger(CVAR_ENHANCEMENT("ToggleStrength"), 0)) { - CVarSetInteger(CVAR_ENHANCEMENT("StrengthDisabled"), 0); - } - } - UIWidgets::Tooltip("Allows strength to be toggled on and off by pressing A on the strength upgrade in the equipment subscreen of the pause menu (This allows performing some glitches that require the player to not have strength)."); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Item Count Messages")) { - int numOptions = ARRAY_COUNT(itemCountMessageCVars); - bool allItemCountsChecked = std::all_of(itemCountMessageCVars, itemCountMessageCVars + numOptions, - [](const char* cvar) { return CVarGetInteger(cvar, 0); }); - bool someItemCountsChecked = std::any_of(itemCountMessageCVars, itemCountMessageCVars + numOptions, - [](const char* cvar) { return CVarGetInteger(cvar, 0); }); - - ImGuiContext* g = ImGui::GetCurrentContext(); - ImGuiItemFlags backup_item_flags = g->CurrentItemFlags; - if (!allItemCountsChecked && someItemCountsChecked) g->CurrentItemFlags |= ImGuiItemFlags_MixedValue; - if (ImGui::Checkbox("All", &allItemCountsChecked)) { - int32_t newValue = allItemCountsChecked ? 1 : 0; - - std::for_each(itemCountMessageCVars, itemCountMessageCVars + numOptions, - [newValue](const char* cvar) { CVarSetInteger(cvar, newValue); }); - - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - g->CurrentItemFlags = backup_item_flags; - - for (int i = 0; i < numOptions; i++) { - UIWidgets::PaddedEnhancementCheckbox(itemCountMessageOptions[i], itemCountMessageCVars[i], true, false); - } - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Difficulty Options")) - { - if (ImGui::BeginMenu("Shooting Gallery")) { - UIWidgets::EnhancementCheckbox("Customize Behavior", CVAR_ENHANCEMENT("CustomizeShootingGallery")); - UIWidgets::Tooltip("Turn on/off changes to the shooting gallery behavior"); - bool disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeShootingGallery"), 0); - static const char* disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off"; - UIWidgets::PaddedEnhancementCheckbox("Instant Win", CVAR_ENHANCEMENT("InstantShootingGalleryWin"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Skips the shooting gallery minigame"); - UIWidgets::PaddedEnhancementCheckbox("No Rupee Randomization", CVAR_ENHANCEMENT("ConstantAdultGallery"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Forces the rupee order to not be randomized as adult, making it the same as chlid"); - UIWidgets::PaddedEnhancementSliderInt("Child Starting Ammunition: %d", "##cShootingGalleryAmmunition", CVAR_ENHANCEMENT("ShootingGalleryAmmoChild"), 10, 30, "", 15, true, true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("The ammunition at the start of the shooting gallery minigame as a child"); - UIWidgets::PaddedEnhancementSliderInt("Adult Starting Ammunition: %d", "##aShootingGalleryAmmunition", CVAR_ENHANCEMENT("ShootingGalleryAmmoAdult"), 10, 30, "", 15, true, true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("The ammunition at the start of the shooting gallery minigame as an adult"); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Bombchu Bowling")) { - UIWidgets::EnhancementCheckbox("Customize Behavior", CVAR_ENHANCEMENT("CustomizeBombchuBowling")); - UIWidgets::Tooltip("Turn on/off changes to the bombchu bowling behavior"); - bool disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeBombchuBowling"), 0) == 0; - static const char* disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off"; - UIWidgets::PaddedEnhancementCheckbox("Remove Small Cucco", CVAR_ENHANCEMENT("BombchuBowlingNoSmallCucco"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Prevents the small cucco from appearing in the bombchu bowling minigame"); - UIWidgets::PaddedEnhancementCheckbox("Remove Big Cucco", CVAR_ENHANCEMENT("BombchuBowlingNoBigCucco"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Prevents the big cucco from appearing in the bombchu bowling minigame"); - UIWidgets::PaddedEnhancementSliderInt("Bombchu Count: %d", "##cBombchuBowlingAmmunition", CVAR_ENHANCEMENT("BombchuBowlingAmmo"), 3, 20, "", 10, true, true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("The number of bombchus available at the start of the bombchu bowling minigame"); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - if (ImGui::BeginMenu("Fishing")) { - UIWidgets::EnhancementCheckbox("Customize Behavior", CVAR_ENHANCEMENT("CustomizeFishing")); - UIWidgets::Tooltip("Turn on/off changes to the fishing behavior"); - bool disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0); - static const char* disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off"; - UIWidgets::PaddedEnhancementCheckbox("Instant Fishing", CVAR_ENHANCEMENT("InstantFishing"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("All fish will be caught instantly"); - UIWidgets::PaddedEnhancementCheckbox("Guarantee Bite", CVAR_ENHANCEMENT("GuaranteeFishingBite"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("When a line is stable, guarantee bite. Otherwise use default logic"); - UIWidgets::PaddedEnhancementCheckbox("Fish Never Escape", CVAR_ENHANCEMENT("FishNeverEscape"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Once a hook has been set, fish will never let go while being reeled in."); - UIWidgets::PaddedEnhancementCheckbox("Loaches Always Appear", CVAR_ENHANCEMENT("LoachesAlwaysAppear"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Loaches will always appear in the fishing pond instead of every four visits."); - UIWidgets::PaddedEnhancementCheckbox("Skip Keep Confirmation", CVAR_ENHANCEMENT("SkipKeepConfirmation"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("The pond owner will not ask to confirm if you want to keep a smaller fish."); - UIWidgets::PaddedEnhancementSliderInt("Child Minimum Weight: %d", "##cMinimumWeight", CVAR_ENHANCEMENT("MinimumFishWeightChild"), 3, 10, "", 10, true, true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("The minimum weight for the unique fishing reward as a child"); - UIWidgets::PaddedEnhancementSliderInt("Adult Minimum Weight: %d", "##aMinimumWeight", CVAR_ENHANCEMENT("MinimumFishWeightAdult"), 6, 13, "", 13, true, true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("The minimum weight for the unique fishing reward as an adult"); - UIWidgets::PaddedEnhancementCheckbox("All fish are Hyrule Loaches", CVAR_ENHANCEMENT("AllHyruleLoaches"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Every fish in the fishing pond will always be a Hyrule Loach\n\nNote: This requires reloading the area"); - ImGui::EndMenu(); - } - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Lost Woods Ocarina Game")) { - UIWidgets::EnhancementCheckbox("Customize Behavior", CVAR_ENHANCEMENT("CustomizeOcarinaGame")); - UIWidgets::Tooltip("Turn on/off changes to the lost woods ocarina game behavior"); - bool disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeOcarinaGame"), 0); - static const char* disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off"; - UIWidgets::PaddedEnhancementCheckbox("Instant Win", CVAR_ENHANCEMENT("InstantOcarinaGameWin"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Skips the lost woods ocarina game"); - UIWidgets::PaddedEnhancementSliderInt("Note Play Speed: %dx", "##OcarinaGameNoteSpeed", CVAR_ENHANCEMENT("OcarinaGame.NoteSpeed"), 1, 5, "", 1, true, true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Adjust the speed that the skull kids play notes"); - UIWidgets::PaddedEnhancementCheckbox("Unlimited Playback Time", CVAR_ENHANCEMENT("OcarinaUnlimitedFailTime"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Removes the timer to play back the song"); - UIWidgets::PaddedEnhancementSliderInt("Number of Starting Notes: %d", "##OcarinaGameStartingNotes", CVAR_ENHANCEMENT("OcarinaGame.StartingNotes"), 1, 8, "", 3, true, true, false, - disabled, disabledTooltip); - UIWidgets::Tooltip("Adjust the number of notes the skull kids play to start the first round"); - int roundMin = CVarGetInteger(CVAR_ENHANCEMENT("OcarinaGame.StartingNotes"), 3); - UIWidgets::PaddedEnhancementSliderInt("Round One Notes: %d", "##OcarinaGameRoundOne", - CVAR_ENHANCEMENT("OcarinaGame.RoundOneNotes"), roundMin, 8, "", 5, true, true, - false, - disabled, disabledTooltip); - UIWidgets::Tooltip("Adjust the number of notes you need to play to end the first round"); - UIWidgets::PaddedEnhancementSliderInt("Round Two Notes: %d", "##OcarinaGameRoundTwoNotes", - CVAR_ENHANCEMENT("OcarinaGame.RoundTwoNotes"), roundMin, 8, "", 6, true, true, - false, - disabled, disabledTooltip); - UIWidgets::Tooltip("Adjust the number of notes you need to play to end the second round"); - UIWidgets::PaddedEnhancementSliderInt("Round Three Notes: %d", "##OcarinaGameRoundThreeNotes", - CVAR_ENHANCEMENT("OcarinaGame.RoundThreeNotes"), roundMin, 8, "", 8, true, true, - false, - disabled, disabledTooltip); - UIWidgets::Tooltip("Adjust the number of notes you need to play to end the third round"); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Frogs Ocarina Game")) { - UIWidgets::EnhancementCheckbox("Customize Behavior", CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame")); - UIWidgets::Tooltip("Turn on/off changes to the frogs ocarina game behavior"); - bool disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame"), 0); - static const char* disabledTooltip = - "This option is disabled because \"Customize Behavior\" is turned off"; - UIWidgets::PaddedEnhancementCheckbox("Instant Win", CVAR_ENHANCEMENT("InstantFrogsGameWin"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Skips the frogs ocarina game"); - UIWidgets::PaddedEnhancementCheckbox("Unlimited Playback Time", CVAR_ENHANCEMENT("FrogsUnlimitedFailTime"), true, false, disabled, disabledTooltip); - UIWidgets::Tooltip("Removes the timer to play back the song"); - bool disabledFrog = 0; - static const char* disabledFrogTooltip = - "This option is disabled because \"Customize Behavior\" is turned off or \"Unlimited Playback Time\" is on"; - if (!CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame"), 0) || CVarGetInteger(CVAR_ENHANCEMENT("FrogsUnlimitedFailTime"), 0)) { - disabledFrog = 1; - } else { - disabledFrog = 0; - } - UIWidgets::PaddedEnhancementSliderInt("Modify note timer: %dx", "##FrogsFailTimer", CVAR_ENHANCEMENT("FrogsModifyFailTime"), 1, 5, "", 1, true, true, false, - disabledFrog, disabledFrogTooltip); - UIWidgets::Tooltip("Adjusts the time allowed for playback before failing"); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - UIWidgets::PaddedEnhancementCheckbox("Delete File On Death", CVAR_ENHANCEMENT("DeleteFileOnDeath"), true, false); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); - UIWidgets::Tooltip("Dying will delete your file\n\n " ICON_FA_EXCLAMATION_TRIANGLE " WARNING " ICON_FA_EXCLAMATION_TRIANGLE "\nTHIS IS NOT REVERSABLE\nUSE AT YOUR OWN RISK!"); - ImGui::PopStyleColor(); - if (UIWidgets::PaddedEnhancementCheckbox("Permanent heart loss", CVAR_ENHANCEMENT("PermanentHeartLoss"), true, false)) { - UpdatePermanentHeartLossState(); - } - UIWidgets::Tooltip("When you lose 4 quarters of a heart you will permanently lose that heart container.\n\nDisabling this after the fact will restore your heart containers."); - ImGui::Text("Damage Multiplier"); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("DamageMult"), allPowers, 0); - UIWidgets::Tooltip( - "Modifies all sources of damage not affected by other sliders\n" - "2x: Can survive all common attacks from the start of the game\n" - "4x: Dies in 1 hit to any substantial attack from the start of the game\n" - "8x: Can only survive trivial damage from the start of the game\n" - "16x: Can survive all common attacks with max health without double defense\n" - "32x: Can survive all common attacks with max health and double defense\n" - "64x: Can survive trivial damage with max health without double defense\n" - "128x: Can survive trivial damage with max health and double defense\n" - "256x: Cannot survive damage" - ); - UIWidgets::PaddedText("Fall Damage Multiplier", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("FallDamageMult"), subPowers, 0); - UIWidgets::Tooltip( - "Modifies all fall damage\n" - "2x: Can survive all fall damage from the start of the game\n" - "4x: Can only survive short fall damage from the start of the game\n" - "8x: Cannot survive any fall damage from the start of the game\n" - "16x: Can survive all fall damage with max health without double defense\n" - "32x: Can survive all fall damage with max health and double defense\n" - "64x: Can survive short fall damage with double defense\n" - "128x: Cannot survive fall damage" - ); - UIWidgets::PaddedText("Void Damage Multiplier", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("VoidDamageMult"), subSubPowers, 0); - UIWidgets::Tooltip( - "Modifies damage taken after falling into a void\n" - "2x: Can survive void damage from the start of the game\n" - "4x: Cannot survive void damage from the start of the game\n" - "8x: Can survive void damage twice with max health without double defense\n" - "16x: Can survive void damage with max health without double defense\n" - "32x: Can survive void damage with max health and double defense\n" - "64x: Cannot survive void damage" - ); - UIWidgets::PaddedText("Bonk Damage Multiplier", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("BonkDamageMult"), bonkDamageValues, BONK_DAMAGE_NONE); - UIWidgets::Tooltip("Modifies damage taken after bonking."); - UIWidgets::PaddedEnhancementCheckbox("Spawn with full health", CVAR_ENHANCEMENT("FullHealthSpawn"), true, false); - UIWidgets::Tooltip("Respawn with full health instead of 3 Hearts"); - UIWidgets::PaddedEnhancementCheckbox("No Random Drops", CVAR_ENHANCEMENT("NoRandomDrops"), true, false); - UIWidgets::Tooltip("Disables random drops, except from the Goron Pot, Dampe, and bosses"); - bool forceEnableBombchuDrops = IS_RANDO && - OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_ENABLE_BOMBCHU_DROPS) == 1; - static const char* forceEnableBombchuDropsText = - "This setting is forcefully enabled because a savefile\nwith \"Enable Bombchu Drops\" is loaded."; - UIWidgets::PaddedEnhancementCheckbox("Enable Bombchu Drops", CVAR_ENHANCEMENT("EnableBombchuDrops"), true, false, - forceEnableBombchuDrops, forceEnableBombchuDropsText, UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Bombchus will sometimes drop in place of bombs"); - UIWidgets::PaddedEnhancementCheckbox("Trees Drop Sticks", CVAR_ENHANCEMENT("TreesDropSticks"), true, false); - UIWidgets::Tooltip("Bonking into trees will have a chance to drop up to 3 sticks. Must already have obtained sticks."); - UIWidgets::PaddedEnhancementCheckbox("No Heart Drops", CVAR_ENHANCEMENT("NoHeartDrops"), true, false); - UIWidgets::Tooltip("Disables heart drops, but not heart placements, like from a Deku Scrub running off\nThis simulates Hero Mode from other games in the series"); - if (UIWidgets::PaddedEnhancementCheckbox("Hyper Bosses", CVAR_ENHANCEMENT("HyperBosses"), true, false)) { - UpdateHyperBossesState(); - } - UIWidgets::Tooltip("All major bosses move and act twice as fast."); - if (UIWidgets::PaddedEnhancementCheckbox("Hyper Enemies", CVAR_ENHANCEMENT("HyperEnemies"), true, false)) { - UpdateHyperEnemiesState(); - } - UIWidgets::Tooltip("All regular enemies and mini-bosses move and act twice as fast."); - UIWidgets::PaddedEnhancementCheckbox("Always Win Goron Pot", CVAR_ENHANCEMENT("GoronPot"), true, false); - UIWidgets::Tooltip("Always get the heart piece/purple rupee from the spinning Goron pot"); - UIWidgets::PaddedEnhancementCheckbox("Always Win Dampe Digging Game", CVAR_ENHANCEMENT("DampeWin"), true, false); - UIWidgets::Tooltip("Always win the heart piece/purple rupee on the first dig in Dampe's grave digging game, just like in rando\nIn a rando file, this is unconditionally enabled"); - UIWidgets::PaddedEnhancementCheckbox("All Dogs are Richard", CVAR_ENHANCEMENT("AllDogsRichard"), true, false); - UIWidgets::Tooltip("All dogs can be traded in and will count as Richard."); - UIWidgets::PaddedEnhancementSliderInt("Cuccos Stay Put Multiplier: %dx", "##CuccoStayDurationMultiplier", CVAR_ENHANCEMENT("CuccoStayDurationMult"), 1, 5, "", 1, true, true, false); - UIWidgets::Tooltip("Cuccos will stay in place longer after putting them down, by a multiple of the value of the slider."); - UIWidgets::PaddedEnhancementSliderInt("Leever Spawn Rate: %d seconds", "##LeeverSpawnRate", CVAR_ENHANCEMENT("LeeverSpawnRate"), 0, 10, "", 0, true, true, false); - UIWidgets::Tooltip("The time between leever groups spawning."); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Reduced Clutter")) - { - UIWidgets::EnhancementCheckbox("Mute Low HP Alarm", CVAR_ENHANCEMENT("LowHpAlarm")); - UIWidgets::Tooltip("Disable the low HP beeping sound"); - UIWidgets::PaddedEnhancementCheckbox("Minimal UI", CVAR_ENHANCEMENT("MinimalUI"), true, false); - UIWidgets::Tooltip("Hides most of the UI when not needed\nNote: Doesn't activate until after loading a new scene"); - UIWidgets::PaddedEnhancementCheckbox("Disable Navi Call Audio", CVAR_ENHANCEMENT("DisableNaviCallAudio"), true, false); - UIWidgets::Tooltip("Disables the voice audio when Navi calls you"); - UIWidgets::PaddedEnhancementCheckbox("Disable Hot/Underwater Warning Text", CVAR_ENHANCEMENT("DisableTunicWarningText"), true, false); - UIWidgets::Tooltip("Disables warning text when you don't have on the Goron/Zora Tunic in Hot/Underwater conditions."); - UIWidgets::PaddedEnhancementCheckbox("Remember Minimap State Between Areas", CVAR_ENHANCEMENT("RememberMapToggleState")); - UIWidgets::Tooltip("Preserves the minimap visibility state when going between areas rather than defaulting it to \"on\" when going through loading zones."); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - UIWidgets::EnhancementCheckbox("Visual Stone of Agony", CVAR_ENHANCEMENT("VisualAgony")); - UIWidgets::Tooltip("Displays an icon and plays a sound when Stone of Agony should be activated, for those without rumble"); - static const char* cursorOnAnySlot[3] = { "Only in Rando", "Always", "Never" }; - UIWidgets::PaddedText("Allow the cursor to be on any slot", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("PauseAnyCursor"), cursorOnAnySlot, PAUSE_ANY_CURSOR_RANDO_ONLY); - UIWidgets::Tooltip("Allows the cursor on the pause menu to be over any slot. Sometimes required in rando to select certain items."); - UIWidgets::PaddedEnhancementCheckbox("Assignable Tunics and Boots", CVAR_ENHANCEMENT("AssignableTunicsAndBoots"), true, false); - UIWidgets::Tooltip("Allows equipping the tunic and boots to c-buttons"); - UIWidgets::PaddedEnhancementCheckbox("Equipment Toggle", CVAR_ENHANCEMENT("EquipmentCanBeRemoved"), true, false); - UIWidgets::Tooltip("Allows equipment to be removed by toggling it off on\nthe equipment subscreen."); - if (CVarGetInteger(CVAR_ENHANCEMENT("EquipmentCanBeRemoved"), 0)) { - UIWidgets::PaddedText("Sword Toggle Options", true, false); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("SwordToggle"), swordToggleModes, SWORD_TOGGLE_NONE); - UIWidgets::Tooltip( - "Introduces Options for unequipping Link's sword\n\n" - "None: Only Biggoron's Sword/Giant's Knife can be toggled. Doing so will equip the Master Sword.\n\n" - "Child Toggle: This will allow for completely unequipping any sword as child link.\n\n" - "Both Ages: Any sword can be unequipped as either age. This may lead to swordless glitches as Adult.\n" - ); - } - - UIWidgets::PaddedEnhancementCheckbox("Link's Cow in Both Time Periods", CVAR_ENHANCEMENT("CowOfTime"), true, false); - UIWidgets::Tooltip("Allows the Lon Lon Ranch obstacle course reward to be shared across time periods"); - UIWidgets::PaddedEnhancementCheckbox("Enable visible guard vision", CVAR_ENHANCEMENT("GuardVision"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Enable passage of time on file select", CVAR_ENHANCEMENT("TimeFlowFileSelect"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Pull grave during the day", CVAR_ENHANCEMENT("DayGravePull"), true, false); - UIWidgets::Tooltip("Allows graves to be pulled when child during the day"); - UIWidgets::PaddedEnhancementCheckbox("Dogs follow you everywhere", CVAR_ENHANCEMENT("DogFollowsEverywhere"), true, false); - UIWidgets::Tooltip("Allows dogs to follow you anywhere you go, even if you leave the market"); - UIWidgets::PaddedEnhancementCheckbox("Don't require input for Credits sequence", CVAR_ENHANCEMENT("NoInputForCredits"), true, false); - UIWidgets::Tooltip("Removes the input requirement on textboxes after defeating Ganon, allowing Credits sequence to continue to progress"); - UIWidgets::PaddedEnhancementCheckbox("Answer Navi Prompt with L Button", CVAR_ENHANCEMENT("NaviOnL"), true, false); - UIWidgets::Tooltip("Speak to Navi with L but enter first-person camera with C-Up"); - - // Blue Fire Arrows - bool forceEnableBlueFireArrows = IS_RANDO && - OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_BLUE_FIRE_ARROWS); - static const char* forceEnableBlueFireArrowsText = - "This setting is forcefully enabled because a savefile\nwith \"Blue Fire Arrows\" is loaded."; - UIWidgets::PaddedEnhancementCheckbox("Blue Fire Arrows", CVAR_ENHANCEMENT("BlueFireArrows"), true, false, - forceEnableBlueFireArrows, forceEnableBlueFireArrowsText, UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Allows Ice Arrows to melt red ice.\nMay require a room reload if toggled during gameplay."); - - // Sunlight Arrows - bool forceEnableSunLightArrows = IS_RANDO && - OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SUNLIGHT_ARROWS); - static const char* forceEnableSunLightArrowsText = - "This setting is forcefully enabled because a savefile\nwith \"Sunlight Arrows\" is loaded."; - UIWidgets::PaddedEnhancementCheckbox("Sunlight Arrows", CVAR_ENHANCEMENT("SunlightArrows"), true, false, - forceEnableSunLightArrows, forceEnableSunLightArrowsText, UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Allows Light Arrows to activate sun switches.\nMay require a room reload if toggled during gameplay."); - - UIWidgets::PaddedEnhancementCheckbox("Disable Crit wiggle", CVAR_ENHANCEMENT("DisableCritWiggle"), true, false); - UIWidgets::Tooltip("Disable random camera wiggle at low health"); - UIWidgets::PaddedEnhancementCheckbox("Enemy Health Bars", CVAR_ENHANCEMENT("EnemyHealthBar"), true, false); - UIWidgets::Tooltip("Renders a health bar for enemies when Z-Targeted"); - - UIWidgets::PaddedEnhancementCheckbox("Targetable Hookshot Reticle", CVAR_ENHANCEMENT("HookshotableReticle"), true, false); - UIWidgets::Tooltip("Use a different color when aiming at hookshotable collision"); - - UIWidgets::PaddedEnhancementCheckbox("Faster Rupee Accumulator", CVAR_ENHANCEMENT("TimeSavers.FasterRupeeAccumulator"), true, false); - UIWidgets::Tooltip("Causes your wallet to fill and empty faster when you gain or lose money."); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Graphics")) - { - if (ImGui::BeginMenu("Mods")) { - UIWidgets::PaddedEnhancementCheckbox("Use Alternate Assets", CVAR_ENHANCEMENT("AltAssets"), false, false); - UIWidgets::Tooltip("Toggle between standard assets and alternate assets. Usually mods will indicate if this setting has to be used or not."); - UIWidgets::PaddedEnhancementCheckbox("Disable Bomb Billboarding", CVAR_ENHANCEMENT("DisableBombBillboarding"), true, false); - UIWidgets::Tooltip("Disables bombs always rotating to face the camera. To be used in conjunction with mods that want to replace bombs with 3D objects."); - UIWidgets::PaddedEnhancementCheckbox("Disable Grotto Fixed Rotation", CVAR_ENHANCEMENT("DisableGrottoRotation"), true, false); - UIWidgets::Tooltip("Disables grottos rotating with the camera. To be used in conjunction with mods that want to replace grottos with 3D objects."); - UIWidgets::PaddedEnhancementCheckbox("Invisible Bunny Hood", CVAR_ENHANCEMENT("HideBunnyHood"), true, false); - UIWidgets::Tooltip("Turns Bunny Hood invisible while still maintaining its effects."); - UIWidgets::PaddedEnhancementCheckbox("Disable HUD Heart animations", CVAR_ENHANCEMENT("NoHUDHeartAnimation"), true, false); - UIWidgets::Tooltip("Disables the beating animation of the hearts on the HUD."); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - UIWidgets::PaddedEnhancementCheckbox("Animated Link in Pause Menu", CVAR_ENHANCEMENT("PauseMenuAnimatedLink"), false, false); - UIWidgets::PaddedEnhancementCheckbox("Disable LOD", CVAR_ENHANCEMENT("DisableLOD"), true, false); - UIWidgets::Tooltip( - "Turns off the Level of Detail setting, making models use their higher-poly variants at any distance"); - if (UIWidgets::EnhancementSliderInt("Increase Actor Draw Distance: %dx", "##IncreaseActorDrawDistance", - CVAR_ENHANCEMENT("DisableDrawDistance"), 1, 5, "", 1, true, false)) { - if (CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) <= 1) { - CVarSetInteger(CVAR_ENHANCEMENT("DisableKokiriDrawDistance"), 0); - } - } - UIWidgets::Tooltip("Increases the range in which actors/objects are drawn"); - if (CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) > 1) { - UIWidgets::PaddedEnhancementCheckbox("Kokiri Draw Distance", - CVAR_ENHANCEMENT("DisableKokiriDrawDistance"), true, false); - UIWidgets::Tooltip("The Kokiri are mystical beings that fade into view when approached\nEnabling this " - "will remove their draw distance"); - } - UIWidgets::PaddedEnhancementCheckbox("Widescreen Actor Culling", CVAR_ENHANCEMENT("WidescreenActorCulling"), - true, false); - UIWidgets::Tooltip("Adjusts the horizontal culling plane to account for widescreen resolutions"); - UIWidgets::PaddedEnhancementCheckbox( - "Cull Glitch Useful Actors", CVAR_ENHANCEMENT("ExtendedCullingExcludeGlitchActors"), true, false, - !CVarGetInteger(CVAR_ENHANCEMENT("WidescreenActorCulling"), 0) && - CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) <= 1, - "Requires Actor Draw Distance to be increased or Widescreen Actor Culling enabled"); - UIWidgets::Tooltip( - "Exclude actors that are useful for glitches from the extended culling ranges.\n" - "Some actors may still draw in the extended ranges, but will not \"update\" so that certain " - "glitches that leverage the original culling requirements will still work.\n" - "\n" - "The following actors are excluded:\n" - "- White clothed Gerudos\n" - "- King Zora\n" - "- Gossip Stones\n" - "- Boulders\n" - "- Blue Warps\n" - "- Darunia\n" - "- Gold Skulltulas"); - if (UIWidgets::PaddedEnhancementCheckbox("Show Age-Dependent Equipment", CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), true, - false)) { - UpdatePatchHand(); - } - UIWidgets::Tooltip("Makes all equipment visible, regardless of Age."); - if (CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0) == 1) { - UIWidgets::PaddedEnhancementCheckbox("Scale Adult Equipment as Child", CVAR_ENHANCEMENT("ScaleAdultEquipmentAsChild"), true, false); - UIWidgets::Tooltip("Scales all of the Adult Equipment, as well and moving some a bit, to fit on Child Link Better. May not work properly with some mods."); - } - UIWidgets::PaddedEnhancementCheckbox("N64 Mode", CVAR_LOW_RES_MODE, true, false); - UIWidgets::Tooltip("Sets aspect ratio to 4:3 and lowers resolution to 240p, the N64's native resolution"); - UIWidgets::PaddedEnhancementCheckbox("Glitch line-up tick", CVAR_ENHANCEMENT("DrawLineupTick"), true, false); - UIWidgets::Tooltip("Displays a tick in the top center of the screen to help with glitch line-ups in SoH, as traditional UI based line-ups do not work outside of 4:3"); - UIWidgets::PaddedEnhancementCheckbox("Enable 3D Dropped items/projectiles", CVAR_ENHANCEMENT("NewDrops"), true, false); - UIWidgets::Tooltip("Change most 2D items and projectiles on the overworld to their 3D versions"); - UIWidgets::PaddedEnhancementCheckbox("Disable Black Bar Letterboxes", CVAR_ENHANCEMENT("DisableBlackBars"), true, false); - UIWidgets::Tooltip("Disables Black Bar Letterboxes during cutscenes and Z-targeting\nNote: there may be minor visual glitches that were covered up by the black bars\nPlease disable this setting before reporting a bug"); - UIWidgets::PaddedEnhancementCheckbox("Dynamic Wallet Icon", CVAR_ENHANCEMENT("DynamicWalletIcon"), true, false); - UIWidgets::Tooltip("Changes the rupee in the wallet icon to match the wallet size you currently have"); - UIWidgets::PaddedEnhancementCheckbox("Always show dungeon entrances", CVAR_ENHANCEMENT("AlwaysShowDungeonMinimapIcon"), true, false); - UIWidgets::Tooltip("Always shows dungeon entrance icons on the minimap"); - UIWidgets::PaddedEnhancementCheckbox("Show Gauntlets in First Person", CVAR_ENHANCEMENT("FirstPersonGauntlets"), true, false); - UIWidgets::Tooltip("Renders Gauntlets when using the Bow and Hookshot like in OOT3D"); - if (UIWidgets::PaddedEnhancementCheckbox("Color Temple of Time's Medallions", CVAR_ENHANCEMENT("ToTMedallionsColors"), true, false)) { - PatchToTMedallions(); - } - UIWidgets::Tooltip("When medallions are collected, the medallion imprints around the Master Sword pedestal in the Temple of Time will become colored"); - UIWidgets::PaddedEnhancementCheckbox("Show locked door chains on both sides of locked doors", CVAR_ENHANCEMENT("ShowDoorLocksOnBothSides"), true, false); - UIWidgets::PaddedText("Fix Vanishing Paths", true, false); - if (UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("SceneSpecificDirtPathFix"), zFightingOptions, ZFIGHT_FIX_DISABLED) && gPlayState != NULL) { - UpdateDirtPathFixState(gPlayState->sceneNum); - } - UIWidgets::Tooltip("Disabled: Paths vanish more the higher the resolution (Z-fighting is based on resolution)\n" - "Consistent: Certain paths vanish the same way in all resolutions\n" - "No Vanish: Paths do not vanish, Link seems to sink in to some paths\n" - "This might affect other decal effects\n"); - UIWidgets::PaddedEnhancementSliderInt("Text Spacing: %d", "##TEXTSPACING", CVAR_ENHANCEMENT("TextSpacing"), 4, 6, "", 6, true, true, true); - UIWidgets::Tooltip("Space between text characters (useful for HD font textures)"); - UIWidgets::PaddedEnhancementCheckbox("More info in file select", CVAR_ENHANCEMENT("FileSelectMoreInfo"), true, false); - UIWidgets::Tooltip("Shows what items you have collected in the file select screen, like in N64 randomizer"); - UIWidgets::PaddedEnhancementCheckbox("Better ammo rendering in pause menu", CVAR_ENHANCEMENT("BetterAmmoRendering"), true, false); - UIWidgets::Tooltip("Ammo counts in the pause menu will work correctly regardless of the position of items in the inventory"); - UIWidgets::PaddedEnhancementCheckbox("Remove spin attack darkness", CVAR_ENHANCEMENT("RemoveSpinAttackDarkness"), true, false); - UIWidgets::Tooltip("Remove the darkness that appears when charging a spin attack"); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Fixes")) - { - UIWidgets::EnhancementCheckbox("Fix L&R Pause menu", CVAR_ENHANCEMENT("FixMenuLR")); - UIWidgets::Tooltip("Makes the L and R buttons in the pause menu the same color"); - UIWidgets::PaddedEnhancementCheckbox("Fix L&Z Page switch in Pause menu", CVAR_ENHANCEMENT("NGCKaleidoSwitcher"), true, false); - UIWidgets::Tooltip("Makes L and R switch pages like on the GameCube\nZ opens the Debug Menu instead"); - UIWidgets::PaddedEnhancementCheckbox("Fix Dungeon entrances", CVAR_ENHANCEMENT("FixDungeonMinimapIcon"), true, false); - UIWidgets::Tooltip("Removes the dungeon entrance icon on the top-left corner of the screen when no dungeon is present on the current map"); - UIWidgets::PaddedEnhancementCheckbox("Fix Two Handed idle animations", CVAR_ENHANCEMENT("TwoHandedIdle"), true, false); - UIWidgets::Tooltip("Re-enables the two-handed idle animation, a seemingly finished animation that was disabled on accident in the original game"); - UIWidgets::PaddedEnhancementCheckbox("Fix the Gravedigging Tour Glitch", CVAR_ENHANCEMENT("GravediggingTourFix"), true, false, SaveManager::Instance->IsRandoFile(), - "This setting is always enabled in randomizer files", UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Fixes a bug where the Gravedigging Tour Heart Piece disappears if the area reloads"); - UIWidgets::PaddedEnhancementCheckbox( - "Fix Deku Nut upgrade", CVAR_ENHANCEMENT("DekuNutUpgradeFix"), true, false, IS_RANDO, - "This setting is forcefully enabled when you are playing a randomizer.", - UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Prevents the Forest Stage Deku Nut upgrade from becoming unobtainable after receiving the Poacher's Saw"); - UIWidgets::PaddedEnhancementCheckbox("Fix Navi text HUD position", CVAR_ENHANCEMENT("NaviTextFix"), true, false); - UIWidgets::Tooltip("Correctly centers the Navi text prompt on the HUD's C-Up button"); - UIWidgets::PaddedEnhancementCheckbox("Fix Anubis fireballs", CVAR_ENHANCEMENT("AnubisFix"), true, false); - UIWidgets::Tooltip("Make Anubis fireballs do fire damage when reflected back at them with the Mirror Shield"); - if (UIWidgets::PaddedEnhancementCheckbox("Fix Megaton Hammer crouch stab", CVAR_ENHANCEMENT("CrouchStabHammerFix"), true, false)) { - if (!CVarGetInteger(CVAR_ENHANCEMENT("CrouchStabHammerFix"), 0)) { - CVarClear(CVAR_ENHANCEMENT("CrouchStabFix")); - } - } - UIWidgets::Tooltip("Make the Megaton Hammer's crouch stab able to destroy rocks without first swinging it normally"); - if (CVarGetInteger(CVAR_ENHANCEMENT("CrouchStabHammerFix"), 0)) { - UIWidgets::PaddedEnhancementCheckbox("Remove power crouch stab", CVAR_ENHANCEMENT("CrouchStabFix"), true, false); - UIWidgets::Tooltip("Make crouch stabbing always do the same damage as a regular slash"); - } - UIWidgets::PaddedEnhancementCheckbox("Fix credits timing", CVAR_ENHANCEMENT("CreditsFix"), true, false); - UIWidgets::Tooltip("Extend certain credits scenes so the music lines up properly with the visuals"); - UIWidgets::PaddedEnhancementCheckbox("Fix Gerudo Warrior's clothing colors", CVAR_ENHANCEMENT("GerudoWarriorClothingFix"), true, false); - UIWidgets::Tooltip("Prevent the Gerudo Warrior's clothes changing color when changing Link's tunic or using bombs in front of her"); - UIWidgets::PaddedEnhancementCheckbox("Fix Camera Drift", CVAR_ENHANCEMENT("FixCameraDrift"), true, false); - UIWidgets::Tooltip("Fixes camera slightly drifting to the left when standing still due to a math error"); - UIWidgets::PaddedEnhancementCheckbox("Fix Camera Swing", CVAR_ENHANCEMENT("FixCameraSwing"), true, false); - UIWidgets::Tooltip("Fixes camera getting stuck on collision when standing still, also fixes slight shift back in camera when stop moving"); - UIWidgets::PaddedEnhancementCheckbox("Fix Hanging Ledge Swing Rate", CVAR_ENHANCEMENT("FixHangingLedgeSwingRate"), true, false); - UIWidgets::Tooltip("Fixes camera swing rate when player falls off a ledge and camera swings around"); - UIWidgets::PaddedEnhancementCheckbox("Fix Missing Jingle after 5 Silver Rupees", CVAR_ENHANCEMENT("SilverRupeeJingleExtend"), true, false); - UIWidgets::Tooltip( - "Adds 5 higher pitches for the Silver Rupee Jingle for the rooms with more than 5 Silver Rupees. " - "Currently only relevant in Master Quest."); - if (UIWidgets::PaddedEnhancementCheckbox("Fix out of bounds textures", CVAR_ENHANCEMENT("FixTexturesOOB"), true, false)) { - ApplyAuthenticGfxPatches(); - } - UIWidgets::Tooltip("Fixes authentic out of bounds texture reads, instead loading textures with the correct size"); - UIWidgets::PaddedEnhancementCheckbox("Fix Poacher's Saw Softlock", CVAR_ENHANCEMENT("FixSawSoftlock"), true, false, CVarGetInteger(CVAR_ENHANCEMENT("SkipText"), 0), - "This is disabled because it is forced on when Skip Text is enabled.", UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Prevents the Poacher's Saw softlock from mashing through the text, or with Skip Text enabled."); - UIWidgets::PaddedEnhancementCheckbox("Fix enemies not spawning near water", CVAR_ENHANCEMENT("EnemySpawnsOverWaterboxes"), true, false); - UIWidgets::Tooltip("Causes respawning enemies, like stalchildren, to appear on land near bodies of water. " - "Fixes an incorrect calculation that acted like water underneath ground was above it."); - UIWidgets::PaddedEnhancementCheckbox("Fix Bush Item Drops", CVAR_ENHANCEMENT("BushDropFix"), true, false); - UIWidgets::Tooltip("Fixes the bushes to drop items correctly rather than spawning undefined items."); - UIWidgets::PaddedEnhancementCheckbox("Fix falling from vine edges", CVAR_ENHANCEMENT("FixVineFall"), true, false); - UIWidgets::Tooltip("Prevents immediately falling off climbable surfaces if climbing on the edges."); - UIWidgets::PaddedEnhancementCheckbox("Fix Link's eyes open while sleeping", CVAR_ENHANCEMENT("FixEyesOpenWhileSleeping"), true, false); - UIWidgets::Tooltip("Fixes Link's eyes being open in the opening cutscene when he is supposed to be sleeping."); - UIWidgets::PaddedEnhancementCheckbox("Fix Darunia dancing too fast", CVAR_ENHANCEMENT("FixDaruniaDanceSpeed"), - true, false, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Fixes Darunia's dancing speed so he dances to the beat of Saria's Song, like in vanilla."); - UIWidgets::PaddedEnhancementCheckbox("Fix raised Floor Switches", CVAR_ENHANCEMENT("FixFloorSwitches"), true, false); - UIWidgets::Tooltip("Fixes the two raised floor switches, the one in Forest Temple Basement and the one at the top of Fire Temple. \n" - "This will lower them, making activating them easier"); - UIWidgets::PaddedEnhancementCheckbox("Fix Zora hint dialogue", CVAR_ENHANCEMENT("FixZoraHintDialogue"), true, false); - UIWidgets::Tooltip("Fixes one Zora's dialogue giving a hint about bringing Ruto's Letter to King Zora to properly occur before moving King Zora rather than after"); - if (UIWidgets::PaddedEnhancementCheckbox("Fix hand holding Hammer", CVAR_ENHANCEMENT("FixHammerHand"), true, false)) { - UpdatePatchHand(); - } - UIWidgets::Tooltip("Fixes Adult Link having a backwards left hand when holding the Megaton Hammer."); - if (UIWidgets::PaddedEnhancementCheckbox( - "Fix Broken Giant's Knife bug", CVAR_ENHANCEMENT("FixBrokenGiantsKnife"), true, false, IS_RANDO, - "This setting is forcefully enabled when you are playing a randomizer.", - UIWidgets::CheckboxGraphics::Checkmark)) { - bool hasGiantsKnife = CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_BIGGORON); - bool hasBrokenKnife = CHECK_OWNED_EQUIP_ALT(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_BROKENGIANTKNIFE); - bool knifeIsBroken = gSaveContext.swordHealth == 0.0f; - - if (hasGiantsKnife && (hasBrokenKnife != knifeIsBroken)) { - func_800849EC(gPlayState); - } - } - UIWidgets::Tooltip("Fixes the Broken Giant's Knife flag not being reset when Medigoron fixes it"); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Restoration")) - { - UIWidgets::PaddedEnhancementCheckbox("Red Ganon blood", CVAR_ENHANCEMENT("RedGanonBlood"), true, false); - UIWidgets::Tooltip("Restore the original red blood from NTSC 1.0/1.1. Disable for green blood"); - UIWidgets::PaddedEnhancementCheckbox("Fish while hovering", CVAR_ENHANCEMENT("HoverFishing"), true, false); - UIWidgets::Tooltip("Restore a bug from NTSC 1.0 that allows casting the Fishing Rod while using the Hover Boots"); - UIWidgets::PaddedEnhancementCheckbox("N64 Weird Frames", CVAR_ENHANCEMENT("N64WeirdFrames"), true, false); - UIWidgets::Tooltip("Restores N64 Weird Frames allowing weirdshots to behave the same as N64"); - UIWidgets::PaddedEnhancementCheckbox("Bombchus out of bounds", CVAR_ENHANCEMENT("BombchusOOB"), true, false); - UIWidgets::Tooltip("Allows bombchus to explode out of bounds\nSimilar to GameCube and Wii VC"); - UIWidgets::PaddedEnhancementCheckbox("Quick Putaway", CVAR_ENHANCEMENT("QuickPutaway"), true, false); - UIWidgets::Tooltip("Restore a bug from NTSC 1.0 that allows putting away an item without an animation and performing Putaway Ocarina Items"); - UIWidgets::PaddedEnhancementCheckbox("Restore old Gold Skulltula cutscene", CVAR_ENHANCEMENT("GSCutscene"), true, false); - UIWidgets::Tooltip("Restore pre-release behavior where defeating a Gold Skulltula will play a cutscene showing it die."); - UIWidgets::PaddedEnhancementCheckbox("Quick Bongo Kill", CVAR_ENHANCEMENT("QuickBongoKill"), true, false); - UIWidgets::Tooltip("Restore a bug from NTSC 1.0 that allows bypassing Bongo Bongo's intro cutscene to quickly kill him"); - UIWidgets::PaddedEnhancementCheckbox("Original RBA Values", CVAR_ENHANCEMENT("RestoreRBAValues"), true, false); - UIWidgets::Tooltip("Restores the original outcomes when performing Reverse Bottle Adventure."); - UIWidgets::PaddedEnhancementCheckbox("Early Eyeball Frog", CVAR_ENHANCEMENT("EarlyEyeballFrog"), true, false); - UIWidgets::Tooltip("Restores a bug from NTSC 1.0/1.1 that allows you to obtain the eyeball frog from King Zora instead of the Zora Tunic by holding shield."); - UIWidgets::PaddedEnhancementCheckbox("Pulsate boss icon", CVAR_ENHANCEMENT("PulsateBossIcon"), true, false); - UIWidgets::Tooltip("Restores an unfinished feature to pulsate the boss room icon when you are in the boss room."); - UIWidgets::PaddedEnhancementSliderInt("Pause Buffer Input Window: %d", "##PauseBufferWindow", CVAR_ENHANCEMENT("PauseBufferWindow"), 0, 40, "", 0, true, true, false); - UIWidgets::PaddedEnhancementCheckbox("Include held inputs at start of Buffer Input Window", CVAR_ENHANCEMENT("IncludeHeldInputsBufferWindow"), true, false); - UIWidgets::Tooltip("Typically, inputs that are held prior to the buffer window are not included in the buffer. This setting changes that behavior to include them. This may cause some inputs to be re-triggered undesireably, for instance Z-Targetting something you might not want to."); - UIWidgets::PaddedEnhancementSliderInt("Simulated Input Lag: %d frames", "##SimulatedInputLag", CVAR_SIMULATED_INPUT_LAG, 0, 6, "", 0, true, true, false); - UIWidgets::Tooltip("Buffers your inputs to be executed a specified amount of frames later"); - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Extra Modes")) { - UIWidgets::PaddedText("Mirrored World", true, false); - if (UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("MirroredWorldMode"), mirroredWorldModes, MIRRORED_WORLD_OFF) && gPlayState != NULL) { - UpdateMirrorModeState(gPlayState->sceneNum); - } - UIWidgets::Tooltip( - "Mirrors the world horizontally\n\n" - "- Always: Always mirror the world\n" - "- Random: Randomly decide to mirror the world on each scene change\n" - "- Random (Seeded): Scenes are mirrored based on the current randomizer seed/file\n" - "- Dungeons: Mirror the world in Dungeons\n" - "- Dungeons (Vanilla): Mirror the world in vanilla Dungeons\n" - "- Dungeons (MQ): Mirror the world in MQ Dungeons\n" - "- Dungeons Random: Randomly decide to mirror the world in Dungeons\n" - "- Dungeons Random (Seeded): Dungeons are mirrored based on the current randomizer seed/file\n" - ); - - UIWidgets::PaddedText("Enemy Randomizer", true, false); - if (UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("RandomizedEnemies"), enemyRandomizerModes, ENEMY_RANDOMIZER_OFF)) { - GetSelectedEnemies(); - } - UIWidgets::Tooltip( - "Replaces fixed enemies throughout the game with a random enemy. Bosses, mini-bosses and a few specific regular enemies are excluded.\n" - "Enemies that need more than Deku Nuts + either Deku Sticks or a sword to kill are excluded from spawning in \"clear enemy\" rooms.\n\n" - "- Random: Enemies are randomized every time you load a room\n" - "- Random (Seeded): Enemies are randomized based on the current randomizer seed/file\n" - ); - if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) == ENEMY_RANDOMIZER_RANDOM) { - ImGui::Separator(); - if (ImGui::BeginMenu("Enemy List")) { - UIWidgets::PaddedEnhancementCheckbox("Select All Enemies", CVAR_ENHANCEMENT("RandomizedEnemyList.All"), true, false); - bool disabledEnemyList = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0); - ImGui::Separator(); - - ImGui::BeginDisabled(CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0)); - - ImGui::BeginTable("Enemy Table", 2); - ImGui::TableNextColumn(); - for (int i = 0; i < RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE; i++) { - if (UIWidgets::PaddedEnhancementCheckbox(enemyNameList[i], enemyCVarList[i], true, false, disabledEnemyList, - "These options are disabled because \"Select All Enemies\" is enabled.", - UIWidgets::CheckboxGraphics::Checkmark)) { - GetSelectedEnemies(); - } - if (i == RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE / 2) { - ImGui::TableNextColumn(); - } - } - ImGui::EndTable(); - ImGui::EndDisabled(); - - ImGui::EndMenu(); - } - }; - ImGui::Separator(); - - UIWidgets::PaddedEnhancementCheckbox("Randomized Enemy Sizes", CVAR_ENHANCEMENT("RandomizedEnemySizes"), true, false); - UIWidgets::Tooltip("Enemies and Bosses spawn with random sizes."); - - if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemySizes"), 0)) { - UIWidgets::EnhancementCheckbox("Scale Health with Size", CVAR_ENHANCEMENT("EnemySizeScalesHealth")); - UIWidgets::Tooltip("Scales normal enemies health with their randomized size. *This will NOT affect bosses*"); - } - - UIWidgets::PaddedEnhancementCheckbox("Ivan the Fairy (Coop Mode)", CVAR_ENHANCEMENT("IvanCoopModeEnabled"), true, false); - UIWidgets::Tooltip("Enables Ivan the Fairy upon the next map change. Player 2 can control Ivan and " - "press the C-Buttons to use items and mess with Player 1!"); - - UIWidgets::PaddedEnhancementCheckbox("Rupee Dash Mode", CVAR_ENHANCEMENT("RupeeDash"), true, false); - UIWidgets::Tooltip("Rupees reduced over time, Link suffers damage when the count hits 0."); - - if (CVarGetInteger(CVAR_ENHANCEMENT("RupeeDash"), 0)) { - UIWidgets::PaddedEnhancementSliderInt( - "Rupee Dash Interval: %d", "##DashInterval", CVAR_ENHANCEMENT("RupeeDashInterval"), 1, 10, "", 5, true, true, false, - !CVarGetInteger(CVAR_ENHANCEMENT("RupeeDash"), 0), - "This option is disabled because \"Rupee Dash Mode\" is turned off"); - UIWidgets::Tooltip("Interval between Rupee reduction in Rupee Dash Mode"); - } - - UIWidgets::PaddedEnhancementCheckbox("Shadow Tag Mode", CVAR_ENHANCEMENT("ShadowTag"), true, false); - UIWidgets::Tooltip("A wallmaster follows Link everywhere, don't get caught!"); - - UIWidgets::PaddedEnhancementCheckbox("Additional Traps", CVAR_ENHANCEMENT("ExtraTraps.Enabled"), true, false); - UIWidgets::Tooltip("Enables additional Trap variants."); - - if (CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0)) { - UIWidgets::PaddedSeparator(); - if (ImGui::BeginMenu("Trap Options")) { - ImGui::Text("Tier 1 Traps:"); - UIWidgets::Spacer(0); - UIWidgets::PaddedEnhancementCheckbox("Freeze Traps", CVAR_ENHANCEMENT("ExtraTraps.Ice"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Burn Traps", CVAR_ENHANCEMENT("ExtraTraps.Burn"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Shock Traps", CVAR_ENHANCEMENT("ExtraTraps.Shock"), true, false); - - UIWidgets::PaddedSeparator(); - ImGui::Text("Tier 2 Traps:"); - UIWidgets::Spacer(0); - UIWidgets::PaddedEnhancementCheckbox("Knockback Traps", CVAR_ENHANCEMENT("ExtraTraps.Knockback"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Speed Traps", CVAR_ENHANCEMENT("ExtraTraps.Speed"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Bomb Traps", CVAR_ENHANCEMENT("ExtraTraps.Bomb"), true, false); - - UIWidgets::PaddedSeparator(); - ImGui::Text("Tier 3 Traps:"); - UIWidgets::Spacer(0); - UIWidgets::PaddedEnhancementCheckbox("Void Traps", CVAR_ENHANCEMENT("ExtraTraps.Void"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Ammo Traps", CVAR_ENHANCEMENT("ExtraTraps.Ammo"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Death Traps", CVAR_ENHANCEMENT("ExtraTraps.Kill"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Teleport Traps", CVAR_ENHANCEMENT("ExtraTraps.Teleport"), true, false); - - ImGui::EndMenu(); - } - } - - UIWidgets::Spacer(0); - if (UIWidgets::PaddedEnhancementCheckbox("Hurt Container Mode", CVAR_ENHANCEMENT("HurtContainer"), true, false)) { - UpdateHurtContainerModeState(CVarGetInteger(CVAR_ENHANCEMENT("HurtContainer"), 0)); - } - UIWidgets::Tooltip("Changes Heart Piece and Heart Container functionality.\n\n" - "- Each Heart Container or full Heart Piece reduces Links hearts by 1.\n" - "- Can be enabled retroactively after a File has already started."); - - ImGui::EndMenu(); - } - - UIWidgets::PaddedSeparator(); - - UIWidgets::EnhancementCheckbox("Autosave", CVAR_ENHANCEMENT("Autosave")); - UIWidgets::Tooltip("Save the game automatically on a 3 minute interval and when soft-resetting the game.\n\n" - "The interval autosave will wait if the game is paused in any way (dialogue, pause screen up, cutscenes)."); - - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); - - UIWidgets::PaddedText("Boot Sequence", false, true); - UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("BootSequence"), bootSequenceLabels, BOOTSEQUENCE_DEFAULT); - UIWidgets::Tooltip("Configure what happens when starting or resetting the game\n\n" - "Default: LUS logo -> N64 logo\n" - "Authentic: N64 logo only\n" - "File Select: Skip to file select menu" - ); - - UIWidgets::PaddedSeparator(true, true, 2.0f, 2.0f); - - ImGui::EndDisabled(); - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - - if (mCosmeticsEditorWindow) { - if (ImGui::Button(GetWindowButtonText("Cosmetics Editor", CVarGetInteger(CVAR_WINDOW("CosmeticsEditor"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mCosmeticsEditorWindow->ToggleVisibility(); - } - } - - if (mAudioEditorWindow) { - if (ImGui::Button(GetWindowButtonText("Audio Editor", CVarGetInteger(CVAR_WINDOW("AudioEditor"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mAudioEditorWindow->ToggleVisibility(); - } - } - - if (mGameplayStatsWindow) { - if (ImGui::Button(GetWindowButtonText("Gameplay Stats", CVarGetInteger(CVAR_WINDOW("GameplayStats"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mGameplayStatsWindow->ToggleVisibility(); - } - } - - if (mTimeSplitWindow) { - if (ImGui::Button(GetWindowButtonText("Time Splits", CVarGetInteger(CVAR_WINDOW("TimeSplitEnabled"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mTimeSplitWindow->ToggleVisibility(); - } - } - - if (mTimeDisplayWindow) { - if (ImGui::Button(GetWindowButtonText("Additional Timers", CVarGetInteger(CVAR_WINDOW("TimeDisplayEnabled"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mTimeDisplayWindow->ToggleVisibility(); - } - } - if (mTimeDisplayWindow->IsVisible()) { - ImGui::SeparatorText("Timer Display Options"); - - if (!gPlayState) { - ImGui::Text("Additional Timer options\n" - "available when a file is\n" - "loaded..."); - } else { - if (UIWidgets::PaddedEnhancementSliderFloat("Font Scale: %.2fx", "##FontScale", CVAR_ENHANCEMENT("TimeDisplay.FontScale"), - 1.0f, 5.0f, "", 1.0f, false, true, false, true)) { - TimeDisplayInitSettings(); - } - if (UIWidgets::PaddedEnhancementCheckbox("Hide Background", CVAR_ENHANCEMENT("TimeDisplay.ShowWindowBG"), - false, false)) { - TimeDisplayInitSettings(); - } - ImGui::Separator(); - for (auto& timer : timeDisplayList) { - if (UIWidgets::PaddedEnhancementCheckbox(timer.timeLabel.c_str(), timer.timeEnable, false, false)) { - TimeDisplayUpdateDisplayOptions(); - } - } - } - } - ImGui::PopStyleVar(3); - ImGui::PopStyleColor(1); - - #ifdef __SWITCH__ - UIWidgets::Spacer(0); - ImGui::Text("Switch performance mode"); - if (UIWidgets::EnhancementCombobox(CVAR_ENHANCEMENT("SwitchPerfMode"), SWITCH_CPU_PROFILES, (int)Ship::SwitchProfiles::STOCK)) { - SPDLOG_INFO("Profile:: %s", SWITCH_CPU_PROFILES[CVarGetInteger(CVAR_ENHANCEMENT("SwitchPerfMode"), (int)Ship::SwitchProfiles::STOCK)]); - Ship::Switch::ApplyOverclock(); - } - #endif - - ImGui::EndMenu(); - } -} - -void DrawCheatsMenu() { - if (ImGui::BeginMenu("Cheats")) - { - ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 8.0f); - ImGui::BeginTable("##cheatsMenu", 2, ImGuiTableFlags_SizingFixedFit); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableNextColumn(); - UIWidgets::Spacer(2.0f); - ImGui::Text("Inventory:"); - UIWidgets::PaddedSeparator(); - - UIWidgets::PaddedEnhancementCheckbox("Super Tunic", CVAR_CHEAT("SuperTunic"), true, false); - UIWidgets::Tooltip("Makes every tunic have the effects of every other tunic"); - UIWidgets::PaddedEnhancementCheckbox("Easy ISG", CVAR_CHEAT("EasyISG"), true, false); - UIWidgets::Tooltip("Passive Infinite Sword Glitch\nIt makes your sword's swing effect and hitbox stay active indefinitely"); - UIWidgets::PaddedEnhancementCheckbox("Easy QPA", CVAR_CHEAT("EasyQPA"), true, false); - UIWidgets::Tooltip("Gives you the glitched damage value of the quick put away glitch."); - UIWidgets::PaddedEnhancementCheckbox("Timeless Equipment", CVAR_CHEAT("TimelessEquipment"), true, false); - UIWidgets::Tooltip("Allows any item to be equipped, regardless of age\nAlso allows Child to use Adult strength upgrades"); - UIWidgets::PaddedEnhancementCheckbox("Unrestricted Items", CVAR_CHEAT("NoRestrictItems"), true, false); - UIWidgets::Tooltip("Allows you to use any item at any location"); - UIWidgets::PaddedEnhancementCheckbox("Fireproof Deku Shield", CVAR_CHEAT("FireproofDekuShield"), true, false); - UIWidgets::Tooltip("Prevents the Deku Shield from burning on contact with fire"); - UIWidgets::PaddedEnhancementCheckbox("Shield with Two-Handed Weapons", CVAR_CHEAT("ShieldTwoHanded"), true, false); - UIWidgets::Tooltip("This allows you to put up your shield with any two-handed weapon in hand except for Deku Sticks"); - UIWidgets::Spacer(2.0f); - ImGui::Text("Deku Sticks:"); - UIWidgets::EnhancementCombobox(CVAR_CHEAT("DekuStick"), DekuStickCheat, DEKU_STICK_NORMAL); - UIWidgets::Spacer(2.0f); - UIWidgets::EnhancementSliderFloat("Bomb Timer Multiplier: %.2fx", "##gBombTimerMultiplier", CVAR_CHEAT("BombTimerMultiplier"), 0.1f, 5.0f, "", 1.0f, false); - UIWidgets::PaddedEnhancementCheckbox("Hookshot Everything", CVAR_CHEAT("HookshotEverything"), true, false); - UIWidgets::Tooltip("Makes every surface in the game hookshot-able"); - UIWidgets::Spacer(0); - UIWidgets::EnhancementSliderFloat("Hookshot Reach Multiplier: %.2fx", "##gCheatHookshotReachMultiplier", CVAR_CHEAT("HookshotReachMultiplier"), 1.0f, 5.0f, "", 1.0f, false); - UIWidgets::Spacer(2.0f); - if (ImGui::Button("Change Age")) { - SwitchAge(); - } - UIWidgets::Tooltip("Switches Link's age and reloads the area."); - UIWidgets::Spacer(2.0f); - if (ImGui::Button("Clear Cutscene Pointer")) { - GameInteractor::RawAction::ClearCutscenePointer(); - } - UIWidgets::Tooltip("Clears the cutscene pointer to a value safe for wrong warps."); - - ImGui::TableNextColumn(); - UIWidgets::Spacer(2.0f); - - if (ImGui::BeginMenu("Infinite...")) { - UIWidgets::EnhancementCheckbox("Money", CVAR_CHEAT("InfiniteMoney")); - UIWidgets::PaddedEnhancementCheckbox("Health", CVAR_CHEAT("InfiniteHealth"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Ammo", CVAR_CHEAT("InfiniteAmmo"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Magic", CVAR_CHEAT("InfiniteMagic"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Nayru's Love", CVAR_CHEAT("InfiniteNayru"), true, false); - UIWidgets::PaddedEnhancementCheckbox("Epona Boost", CVAR_CHEAT("InfiniteEponaBoost"), true, false); - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(0); - - if (ImGui::BeginMenu("Save States")) { - ImGui::TextColored({ 0.85f, 0.85f, 0.0f, 1.0f }, " " ICON_FA_EXCLAMATION_TRIANGLE); - ImGui::SameLine(); - ImGui::TextColored({ 0.85f, 0.35f, 0.0f, 1.0f }, " WARNING!!!! "); - ImGui::SameLine(); - ImGui::TextColored({ 0.85f, 0.85f, 0.0f, 1.0f }, ICON_FA_EXCLAMATION_TRIANGLE); - UIWidgets::PaddedText("These are NOT like emulator states.", true, false); - UIWidgets::PaddedText("They do not save your game progress, and", true, false); - UIWidgets::PaddedText("they WILL break across transitions and", true, false); - UIWidgets::PaddedText("load zones (like doors). Support for", true, false); - UIWidgets::PaddedText("related issues will not be provided.", true, false); - if (UIWidgets::PaddedEnhancementCheckbox("I promise I have read the warning", CVAR_CHEAT("SaveStatePromise"), true, - false)) { - CVarSetInteger(CVAR_CHEAT("SaveStatesEnabled"), 0); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - if (CVarGetInteger(CVAR_CHEAT("SaveStatePromise"), 0) == 1) { - UIWidgets::PaddedEnhancementCheckbox("I understand, enable save states", CVAR_CHEAT("SaveStatesEnabled"), true, - false); - UIWidgets::Tooltip("F5 to save, F6 to change slots, F7 to load"); - } - - ImGui::EndMenu(); - } - - UIWidgets::Spacer(2.0f); - ImGui::Text("Behavior:"); - UIWidgets::PaddedSeparator(); - - UIWidgets::PaddedEnhancementCheckbox("No Clip", CVAR_CHEAT("NoClip"), true, false); - UIWidgets::Tooltip("Allows you to walk through walls"); - UIWidgets::PaddedEnhancementCheckbox("Climb Everything", CVAR_CHEAT("ClimbEverything"), true, false); - UIWidgets::Tooltip("Makes every surface in the game climbable"); - UIWidgets::PaddedEnhancementCheckbox("Moon Jump on L", CVAR_CHEAT("MoonJumpOnL"), true, false); - UIWidgets::Tooltip("Holding L makes you float into the air"); - UIWidgets::PaddedEnhancementCheckbox("New Easy Frame Advancing", CVAR_CHEAT("EasyFrameAdvance"), true, false); - UIWidgets::Tooltip("Continue holding START button when unpausing to only advance a single frame and then re-pause."); - UIWidgets::PaddedEnhancementCheckbox("Drops Don't Despawn", CVAR_CHEAT("DropsDontDie"), true, false); - UIWidgets::Tooltip("Drops from enemies, grass, etc. don't disappear after a set amount of time"); - UIWidgets::PaddedEnhancementCheckbox("Fish Don't despawn", CVAR_CHEAT("NoFishDespawn"), true, false); - UIWidgets::Tooltip("Prevents fish from automatically despawning after a while when dropped"); - UIWidgets::PaddedEnhancementCheckbox("Bugs Don't despawn", CVAR_CHEAT("NoBugsDespawn"), true, false); - UIWidgets::Tooltip("Prevents bugs from automatically despawning after a while when dropped"); - UIWidgets::PaddedEnhancementCheckbox("Freeze Time", CVAR_CHEAT("FreezeTime"), true, false); - UIWidgets::Tooltip("Freezes the time of day"); - UIWidgets::PaddedEnhancementCheckbox("Time Sync", CVAR_CHEAT("TimeSync"), true, false); - UIWidgets::Tooltip("This syncs the ingame time with the real world time"); - UIWidgets::PaddedEnhancementCheckbox("No ReDead/Gibdo Freeze", CVAR_CHEAT("NoRedeadFreeze"), true, false); - UIWidgets::Tooltip("Prevents ReDeads and Gibdos from being able to freeze you with their scream"); - UIWidgets::PaddedEnhancementCheckbox("Keese/Guay don't target you", CVAR_CHEAT("NoKeeseGuayTarget"), true, false); - UIWidgets::Tooltip("Keese and Guay no longer target you and simply ignore you as if you were wearing the skull mask"); - { - static int32_t betaQuestEnabled = CVarGetInteger(CVAR_CHEAT("EnableBetaQuest"), 0); - static int32_t lastBetaQuestEnabled = betaQuestEnabled; - static int32_t betaQuestWorld = CVarGetInteger(CVAR_CHEAT("BetaQuestWorld"), 0xFFEF); - static int32_t lastBetaQuestWorld = betaQuestWorld; - - if (!isBetaQuestEnabled) { - UIWidgets::DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - UIWidgets::PaddedEnhancementCheckbox("Enable Beta Quest", CVAR_CHEAT("EnableBetaQuest"), true, false); - UIWidgets::Tooltip("Turns on OoT Beta Quest. *WARNING* This will reset your game."); - betaQuestEnabled = CVarGetInteger(CVAR_CHEAT("EnableBetaQuest"), 0); - if (betaQuestEnabled) { - if (betaQuestEnabled != lastBetaQuestEnabled) { - betaQuestWorld = 0; - } - - ImGui::Text("Beta Quest World: %d", betaQuestWorld); - - if (ImGui::Button(" - ##BetaQuest")) { - betaQuestWorld--; - } - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - - ImGui::SliderInt("##BetaQuest", &betaQuestWorld, 0, 8, "", ImGuiSliderFlags_AlwaysClamp); - UIWidgets::Tooltip("Set the Beta Quest world to explore. *WARNING* Changing this will reset your game.\nCtrl+Click to type in a value."); - - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - if (ImGui::Button(" + ##BetaQuest")) { - betaQuestWorld++; - } - - if (betaQuestWorld > 8) { - betaQuestWorld = 8; - } - else if (betaQuestWorld < 0) { - betaQuestWorld = 0; - } - } - else { - lastBetaQuestWorld = betaQuestWorld = 0xFFEF; - CVarClear(CVAR_CHEAT("BetaQuestWorld")); - } - if (betaQuestEnabled != lastBetaQuestEnabled || betaQuestWorld != lastBetaQuestWorld) - { - // Reset the game if the beta quest state or world changed because beta quest happens on redirecting the title screen cutscene. - lastBetaQuestEnabled = betaQuestEnabled; - lastBetaQuestWorld = betaQuestWorld; - CVarSetInteger(CVAR_CHEAT("EnableBetaQuest"), betaQuestEnabled); - CVarSetInteger(CVAR_CHEAT("BetaQuestWorld"), betaQuestWorld); - - std::reinterpret_pointer_cast(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch("reset"); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - - if (!isBetaQuestEnabled) { - UIWidgets::ReEnableComponent(""); - } - } - - ImGui::EndTable(); - ImGui::EndDisabled(); - ImGui::EndMenu(); - } -} - -extern std::shared_ptr mStatsWindow; -extern std::shared_ptr mConsoleWindow; -extern std::shared_ptr mSaveEditorWindow; -extern std::shared_ptr mHookDebuggerWindow; -extern std::shared_ptr mColViewerWindow; -extern std::shared_ptr mActorViewerWindow; -extern std::shared_ptr mDLViewerWindow; -extern std::shared_ptr mValueViewerWindow; -extern std::shared_ptr mMessageViewerWindow; - -void DrawDeveloperToolsMenu() { - if (ImGui::BeginMenu("Developer Tools")) { - ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); - - UIWidgets::EnhancementCheckbox("OoT Debug Mode", CVAR_DEVELOPER_TOOLS("DebugEnabled")); - UIWidgets::Tooltip("Enables Debug Mode, allowing you to select maps with L + R + Z, noclip with L + D-pad Right, and open the debug menu with L on the pause screen"); - if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0)) { - UIWidgets::EnhancementCheckbox("OoT Registry Editor", CVAR_DEVELOPER_TOOLS("RegEditEnabled")); - UIWidgets::Tooltip("Enables the registry editor"); - ImGui::Text("Debug Save File Mode:"); - UIWidgets::EnhancementCombobox(CVAR_DEVELOPER_TOOLS("DebugSaveFileMode"), DebugSaveFileModes, 1); - UIWidgets::Tooltip( - "Changes the behaviour of debug file select creation (creating a save file on slot 1 with debug mode on)\n" - "- Off: The debug save file will be a normal savefile\n" - "- Vanilla: The debug save file will be the debug save file from the original game\n" - "- Maxed: The debug save file will be a save file with all of the items & upgrades" - ); - } - UIWidgets::PaddedEnhancementCheckbox("OoT Skulltula Debug", CVAR_DEVELOPER_TOOLS("SkulltulaDebugEnabled"), true, false); - UIWidgets::Tooltip("Enables Skulltula Debug, when moving the cursor in the menu above various map icons (boss key, compass, map screen locations, etc) will set the GS bits in that area.\nUSE WITH CAUTION AS IT DOES NOT UPDATE THE GS COUNT."); - UIWidgets::PaddedEnhancementCheckbox("Better Debug Warp Screen", CVAR_DEVELOPER_TOOLS("BetterDebugWarpScreen"), true, false); - UIWidgets::Tooltip("Optimized debug warp screen, with the added ability to chose entrances and time of day"); - UIWidgets::PaddedEnhancementCheckbox("Debug Warp Screen Translation", CVAR_DEVELOPER_TOOLS("DebugWarpScreenTranslation"), true, false, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip("Translate the Debug Warp Screen based on the game language"); - UIWidgets::PaddedEnhancementCheckbox("Resource logging", CVAR_DEVELOPER_TOOLS("ResourceLogging"), true, false); - UIWidgets::Tooltip("Logs some resources as XML when they're loaded in binary format"); - if (gPlayState != NULL) { - UIWidgets::PaddedSeparator(); - ImGui::Checkbox("Frame Advance##frameAdvance", (bool*)&gPlayState->frameAdvCtx.enabled); - if (gPlayState->frameAdvCtx.enabled) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - if (ImGui::Button("Advance 1", ImVec2(ImGui::GetContentRegionAvail().x / 2.0f, 0.0f))) { - CVarSetInteger(CVAR_GENERAL("FrameAdvance"), 1); - } - ImGui::SameLine(); - ImGui::Button("Advance (Hold)"); - if (ImGui::IsItemActive()) { - CVarSetInteger(CVAR_GENERAL("FrameAdvance"), 1); - } - ImGui::PopStyleVar(3); - ImGui::PopStyleColor(1); - } - } - UIWidgets::PaddedSeparator(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - if (mStatsWindow) { - if (ImGui::Button(GetWindowButtonText("Stats", CVarGetInteger(CVAR_STATS_WINDOW_OPEN, 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mStatsWindow->ToggleVisibility(); - } - UIWidgets::Tooltip("Shows the stats window, with your FPS and frametimes, and the OS you're playing on"); - } - UIWidgets::Spacer(0); - if (mConsoleWindow) { - if (ImGui::Button(GetWindowButtonText("Console", CVarGetInteger(CVAR_CONSOLE_WINDOW_OPEN, 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mConsoleWindow->ToggleVisibility(); - } - UIWidgets::Tooltip("Enables the console window, allowing you to input commands, type help for some examples"); - } - UIWidgets::Spacer(0); - if (mSaveEditorWindow) { - if (ImGui::Button(GetWindowButtonText("Save Editor", CVarGetInteger(CVAR_WINDOW("SaveEditor"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mSaveEditorWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mHookDebuggerWindow) { - if (ImGui::Button(GetWindowButtonText("Hook Debugger", CVarGetInteger(CVAR_WINDOW("HookDebugger"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mHookDebuggerWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mColViewerWindow) { - if (ImGui::Button(GetWindowButtonText("Collision Viewer", CVarGetInteger(CVAR_WINDOW("CollisionViewer"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mColViewerWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mActorViewerWindow) { - if (ImGui::Button(GetWindowButtonText("Actor Viewer", CVarGetInteger(CVAR_WINDOW("ActorViewer"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mActorViewerWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mDLViewerWindow) { - if (ImGui::Button(GetWindowButtonText("Display List Viewer", CVarGetInteger(CVAR_WINDOW("DLViewer"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mDLViewerWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mValueViewerWindow) { - if (ImGui::Button(GetWindowButtonText("Value Viewer", CVarGetInteger(CVAR_WINDOW("ValueViewer"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mValueViewerWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mMessageViewerWindow) { - if (ImGui::Button(GetWindowButtonText("Message Viewer", CVarGetInteger(CVAR_WINDOW("MessageViewer"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mMessageViewerWindow->ToggleVisibility(); - } - } - UIWidgets::Spacer(0); - if (mGfxDebuggerWindow) { - if (ImGui::Button(GetWindowButtonText("Gfx Debugger", CVarGetInteger(CVAR_WINDOW("GfxDebugger"), 0)).c_str(), ImVec2(-1.0f, 0.0f))) { - mGfxDebuggerWindow->ToggleVisibility(); - } - } - - ImGui::PopStyleVar(3); - ImGui::PopStyleColor(1); - - ImGui::EndDisabled(); - - ImGui::EndMenu(); - } -} - -#ifdef ENABLE_REMOTE_CONTROL -void DrawRemoteControlMenu() { - if (ImGui::BeginMenu("Network")) { - Sail::Instance->DrawMenu(); - CrowdControl::Instance->DrawMenu(); - ImGui::EndMenu(); - } -} -#endif - -extern std::shared_ptr mRandomizerSettingsWindow; -extern std::shared_ptr mPlandomizerWindow; -extern std::shared_ptr mItemTrackerWindow; -extern std::shared_ptr mItemTrackerSettingsWindow; -extern std::shared_ptr mEntranceTrackerWindow; -extern std::shared_ptr mEntranceTrackerSettingsWindow; -extern std::shared_ptr mCheckTrackerWindow; -extern std::shared_ptr mCheckTrackerSettingsWindow; -extern "C" u8 Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey); - -void DrawRandomizerMenu() { - if (ImGui::BeginMenu("Randomizer")) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.22f, 0.38f, 0.56f, 1.0f)); - - #ifdef __WIIU__ - static ImVec2 buttonSize(200.0f * 2.0f, 0.0f); - static ImVec2 buttonWithOptionsSize(170.0f * 2.0f, 0.0f); - static ImVec2 optionsButtonSize(25.0f * 2.0f, 0.0f); - static float separationToOptionsButton = 5.0f * 2.0f; - #else - static ImVec2 buttonSize(200.0f, 0.0f); - static ImVec2 buttonWithOptionsSize(170.0f, 0.0f); - static ImVec2 optionsButtonSize(25.0f, 0.0f); - static float separationToOptionsButton = 5.0f; - #endif - - if (mRandomizerSettingsWindow) { - if (ImGui::Button(GetWindowButtonText("Randomizer Settings", CVarGetInteger(CVAR_WINDOW("RandomizerSettings"), 0)).c_str(), buttonSize)) { - mRandomizerSettingsWindow->ToggleVisibility(); - } - } - - UIWidgets::Spacer(0); - - if (mPlandomizerWindow) { - if (ImGui::Button(GetWindowButtonText("Plandomizer Editor", CVarGetInteger(CVAR_WINDOW("PlandomizerWindow"), 0)).c_str(), buttonSize)) { - mPlandomizerWindow->ToggleVisibility(); - } - } - - UIWidgets::Spacer(0); - - if (mItemTrackerWindow) { - if (ImGui::Button(GetWindowButtonText("Item Tracker", CVarGetInteger(CVAR_WINDOW("ItemTracker"), 0)).c_str(), buttonWithOptionsSize)) { - mItemTrackerWindow->ToggleVisibility(); - } - } - - ImGui::SameLine(0, 0); - ImVec2 cursor = ImGui::GetCursorPos(); - ImGui::SetCursorPos(ImVec2(cursor.x + separationToOptionsButton, cursor.y)); - - if (mItemTrackerSettingsWindow) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(FA_ICON_BUTTON_FRAME_PADDING_X(ICON_FA_COG), 6.0f)); - if (ImGui::Button(ICON_FA_COG "##ItemTrackerSettings", optionsButtonSize)) { - mItemTrackerSettingsWindow->ToggleVisibility(); - } - ImGui::PopStyleVar(); - } - - UIWidgets::Spacer(0); - if (mEntranceTrackerWindow) { - if (ImGui::Button(GetWindowButtonText("Entrance Tracker", CVarGetInteger(CVAR_WINDOW("EntranceTracker"), 0)).c_str(), buttonWithOptionsSize)) { - mEntranceTrackerWindow->ToggleVisibility(); - } - } - - ImGui::SameLine(0, 0); - cursor = ImGui::GetCursorPos(); - ImGui::SetCursorPos(ImVec2(cursor.x + separationToOptionsButton, cursor.y)); - - if (mEntranceTrackerSettingsWindow) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(FA_ICON_BUTTON_FRAME_PADDING_X(ICON_FA_COG), 6.0f)); - if (ImGui::Button(ICON_FA_COG "##EntranceTrackerSettings", optionsButtonSize)) { - mEntranceTrackerSettingsWindow->ToggleVisibility(); - } - ImGui::PopStyleVar(); - } - - UIWidgets::Spacer(0); - - if (mCheckTrackerWindow) { - if (ImGui::Button(GetWindowButtonText("Check Tracker", CVarGetInteger(CVAR_WINDOW("CheckTracker"), 0)).c_str(), buttonWithOptionsSize)) { - mCheckTrackerWindow->ToggleVisibility(); - } - } - - ImGui::SameLine(0, 0); - cursor = ImGui::GetCursorPos(); - ImGui::SetCursorPos(ImVec2(cursor.x + separationToOptionsButton, cursor.y)); - - if (mCheckTrackerSettingsWindow) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(FA_ICON_BUTTON_FRAME_PADDING_X(ICON_FA_COG), 6.0f)); - if (ImGui::Button(ICON_FA_COG "##CheckTrackerSettings", optionsButtonSize)) { - mCheckTrackerSettingsWindow->ToggleVisibility(); - } - ImGui::PopStyleVar(); - } - - ImGui::PopStyleVar(3); - ImGui::PopStyleColor(1); - - UIWidgets::PaddedSeparator(); - - if (ImGui::BeginMenu("Rando Enhancements")) - { - UIWidgets::EnhancementCheckbox("Rando-Relevant Navi Hints", CVAR_RANDOMIZER_ENHANCEMENT("RandoRelevantNavi"), false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip( - "Replace Navi's overworld quest hints with rando-related gameplay hints." - ); - UIWidgets::PaddedEnhancementCheckbox("Random Rupee Names", CVAR_RANDOMIZER_ENHANCEMENT("RandomizeRupeeNames"), true, false, false, "", UIWidgets::CheckboxGraphics::Cross, true); - UIWidgets::Tooltip( - "When obtaining rupees, randomize what the rupee is called in the textbox." - ); - - UIWidgets::PaddedEnhancementCheckbox("Use Custom Key Models", CVAR_RANDOMIZER_ENHANCEMENT("CustomKeyModels"), true, false); - UIWidgets::Tooltip("Use Custom graphics for dungeon keys, Big and Small, so that they can be easily told apart"); - - bool disableCompassColors = !DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(RSK_SHUFFLE_MAPANDCOMPASS); - - static const char* disableCompassColorsText = - "This setting is disabled because a savefile is loaded without the compass\n" - "shuffle settings set to \"Any Dungeon\", \"Overworld\" or \"Anywhere\""; - - if (UIWidgets::PaddedEnhancementCheckbox("Compass Colors Match Dungeon", CVAR_RANDOMIZER_ENHANCEMENT("MatchCompassColors"), true, false, - disableCompassColors, disableCompassColorsText, UIWidgets::CheckboxGraphics::Cross, true)) { - PatchCompasses(); - } - UIWidgets::Tooltip( - "Matches the color of compasses to the dungeon they belong to. " - "This helps identify compasses from afar and adds a little bit of flair.\n\nThis only " - "applies to seeds with compasses shuffled to \"Any Dungeon\", \"Overworld\", or \"Anywhere\"."); - - UIWidgets::PaddedEnhancementCheckbox("Quest Item Fanfares", CVAR_RANDOMIZER_ENHANCEMENT("QuestItemFanfares"), true, false); - UIWidgets::Tooltip( - "Play unique fanfares when obtaining quest items " - "(medallions/stones/songs). Note that these fanfares are longer than usual." - ); - UIWidgets::PaddedEnhancementCheckbox("Mysterious Shuffled Items", CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle"), true, false); - UIWidgets::Tooltip( - "Displays a \"Mystery Item\" model in place of any freestanding/GS/shop items that were shuffled, " - "and replaces item names for them and scrubs and merchants, regardless of hint settings, " - "so you never know what you're getting."); - UIWidgets::PaddedEnhancementCheckbox("Simpler Boss Soul Models", - CVAR_RANDOMIZER_ENHANCEMENT("SimplerBossSoulModels"), true, false); - UIWidgets::Tooltip( - "When shuffling boss souls, they'll appear as a simpler model instead of showing the boss' models." - "This might make boss souls more distinguishable from a distance, and can help with performance." - ); - ImGui::EndMenu(); - } - - ImGui::EndMenu(); - } + //if (ImGui::BeginMenu("Settings")) + //{ + // if (ImGui::BeginMenu("Graphics")) { + // ImGui::Text("ImGui Menu Scale"); + // ImGui::SameLine(); + // ImGui::TextColored({ 0.85f, 0.35f, 0.0f, 1.0f }, "(Experimental)"); + // if (UIWidgets::EnhancementCombobox(CVAR_SETTING("ImGuiScale"), imguiScaleOptions, 1)) { + // OTRGlobals::Instance->ScaleImGui(); + // } + // UIWidgets::Tooltip("Changes the scaling of the ImGui menu elements."); + + // // Draw LUS settings menu (such as Overlays Text Font) + // Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->DrawSettings(); + + // ImGui::EndMenu(); + // } + // ImGui::EndMenu(); + //} } void SohMenuBar::InitElement() { - UpdateWindowBackendObjects(); + } void SohMenuBar::DrawElement() { if (ImGui::BeginMenuBar()) { - DrawMenuBarIcon(); - static ImVec2 sWindowPadding(8.0f, 8.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, sWindowPadding); - DrawShipMenu(); - - ImGui::SetCursorPosY(0.0f); - DrawSettingsMenu(); ImGui::SetCursorPosY(0.0f); - DrawEnhancementsMenu(); - - ImGui::SetCursorPosY(0.0f); - - DrawCheatsMenu(); - - ImGui::SetCursorPosY(0.0f); - - DrawDeveloperToolsMenu(); - - ImGui::SetCursorPosY(0.0f); - - #ifdef ENABLE_REMOTE_CONTROL - DrawRemoteControlMenu(); - - ImGui::SetCursorPosY(0.0f); - #endif - - DrawRandomizerMenu(); - ImGui::PopStyleVar(1); ImGui::EndMenuBar(); } diff --git a/soh/soh/SohGui/SohMenuDevTools.cpp b/soh/soh/SohGui/SohMenuDevTools.cpp new file mode 100644 index 000000000..917635f34 --- /dev/null +++ b/soh/soh/SohGui/SohMenuDevTools.cpp @@ -0,0 +1,170 @@ +#include "SohMenu.h" + +namespace SohGui { + +extern std::shared_ptr mSohMenu; +using namespace UIWidgets; + +void SohMenu::AddMenuDevTools() { + // Add Dev Tools Menu + AddMenuEntry("Dev Tools", CVAR_SETTING("Menu.DevToolsSidebarSection")); + + // General + AddSidebarEntry("Dev Tools", "General", 3); + WidgetPath path = { "Dev Tools", "General", SECTION_COLUMN_1 }; + + AddWidget(path, "Popout Menu", WIDGET_CVAR_CHECKBOX) + .CVar("gSettings.Menu.Popout") + .Options(CheckboxOptions().Tooltip("Changes the menu display from overlay to windowed.")); + AddWidget(path, "Debug Mode", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("DebugEnabled")) + .Options(CheckboxOptions().Tooltip("Enables Debug Mode, allowing you to select maps with L + R + Z, noclip " + "with L + D-pad Right, and open the debug menu with L on the pause screen.")); + AddWidget(path, "OoT Registry Editor", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("RegEditEnabled")) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }) + .Options(CheckboxOptions().Tooltip("Enables the registry editor.")); + AddWidget(path, "Debug Save File Mode", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_DEVELOPER_TOOLS("DebugSaveFileMode")) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }) + .Options(ComboboxOptions() + .Tooltip("Changes the behaviour of debug file select creation (creating a save file on slot 1 " + "with debug mode on)\n" + "- Off: The debug save file will be a normal savefile\n" + "- Vanilla: The debug save file will be the debug save file from the original game\n" + "- Maxed: The debug save file will be a save file with all of the items & upgrades") + .ComboMap(debugSaveFileModes)); + AddWidget(path, "OoT Skulltula Debug", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("SkulltulaDebugEnabled")) + .Options(CheckboxOptions().Tooltip("Enables Skulltula Debug, when moving the cursor in the menu above various " + "map icons (boss key, compass, map screen locations, etc) will set the GS " + "bits in that area.\nUSE WITH CAUTION AS IT DOES NOT UPDATE THE GS COUNT.")); + AddWidget(path, "Better Debug Warp Screen", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("BetterDebugWarpScreen")) + .Options(CheckboxOptions().Tooltip( + "Optimized debug warp screen, with the added ability to chose entrances and time of day")); + AddWidget(path, "Debug Warp Screen Translation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("DebugWarpScreenTranslation")) + .Options(CheckboxOptions() + .Tooltip("Translate the Debug Warp Screen based on the game language.") + .DefaultValue(true)); + AddWidget(path, "Resource logging", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_DEVELOPER_TOOLS("ResourceLogging")) + .Options(CheckboxOptions().Tooltip("Logs some resources as XML when they're loaded in binary format")); + + AddWidget(path, "Frame Advance", WIDGET_CHECKBOX) + .Options(CheckboxOptions().Tooltip( + "This allows you to advance through the game one frame at a time on command. " + "To advance a frame, hold Z and tap R on the second controller. Holding Z " + "and R will advance a frame every half second. You can also use the buttons below.")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_NULL_PLAY_STATE).active || + mSohMenu->disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; + if (gPlayState != nullptr) { + info.valuePointer = (bool*)&gPlayState->frameAdvCtx.enabled; + } else { + info.valuePointer = (bool*)nullptr; + } + }); + AddWidget(path, "Advance 1", WIDGET_BUTTON) + .Options(ButtonOptions().Tooltip("Advance 1 frame.").Size(Sizes::Inline)) + .Callback([](WidgetInfo& info) { CVarSetInteger(CVAR_DEVELOPER_TOOLS("FrameAdvanceTick"), 1); }) + .PreFunc([](WidgetInfo& info) { + info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_FRAME_ADVANCE_OFF).active || + mSohMenu->disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; + }); + AddWidget(path, "Advance (Hold)", WIDGET_BUTTON) + .Options(ButtonOptions().Tooltip("Advance frames while the button is held.").Size(Sizes::Inline)) + .PreFunc([](WidgetInfo& info) { + info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_FRAME_ADVANCE_OFF).active || + mSohMenu->disabledMap.at(DISABLE_FOR_DEBUG_MODE_OFF).active; + }) + .PostFunc([](WidgetInfo& info) { + if (ImGui::IsItemActive()) { + CVarSetInteger(CVAR_DEVELOPER_TOOLS("FrameAdvanceTick"), 1); + } + }) + .SameLine(true); + + // Stats + path.sidebarName = "Stats"; + AddSidebarEntry("Dev Tools", path.sidebarName, 1); + AddWidget(path, "Popout Stats Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("Stats")) + .WindowName("Stats") + .Options(WindowButtonOptions().Tooltip("Enables the separate Stats Window.")); + + // Console + path.sidebarName = "Console"; + AddSidebarEntry("Dev Tools", path.sidebarName, 1); + AddWidget(path, "Popout Console", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("Console")) + .WindowName("Console") + .Options(WindowButtonOptions().Tooltip("Enables the separate Console Window.")); + + // Save Editor + path.sidebarName = "Save Editor"; + AddSidebarEntry("Dev Tools", path.sidebarName, 1); + AddWidget(path, "Popout Save Editor", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("SaveEditor")) + .WindowName("Save Editor") + .Options(WindowButtonOptions().Tooltip("Enables the separate Save Editor Window.")); + + // Hook Debugger + path.sidebarName = "Hook Debugger"; + AddSidebarEntry("Dev Tools", path.sidebarName, 1); + AddWidget(path, "Popout Hook Debugger", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("HookDebugger")) + .WindowName("Hook Debugger") + .Options(WindowButtonOptions().Tooltip("Enables the separate Hook Debugger Window.")); + + // Collision Viewer + path.sidebarName = "Collision Viewer"; + AddSidebarEntry("Dev Tools", path.sidebarName, 2); + AddWidget(path, "Popout Collision Viewer", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("CollisionViewer")) + .WindowName("Collision Viewer") + .Options(WindowButtonOptions().Tooltip("Enables the separate Collision Viewer Window.")); + + // Actor Viewer + path.sidebarName = "Actor Viewer"; + AddSidebarEntry("Dev Tools", path.sidebarName, 2); + AddWidget(path, "Popout Actor Viewer", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("ActorViewer")) + .WindowName("Actor Viewer") + .Options(WindowButtonOptions().Tooltip("Enables the separate Actor Viewer Window.")); + + // Display List Viewer + path.sidebarName = "DList Viewer"; + AddSidebarEntry("Dev Tools", path.sidebarName, 2); + AddWidget(path, "Popout Display List Viewer", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("DisplayListViewer")) + .WindowName("Display List Viewer") + .Options(WindowButtonOptions().Tooltip("Enables the separate Display List Viewer Window.")); + + // Value Viewer + path.sidebarName = "Value Viewer"; + AddSidebarEntry("Dev Tools", path.sidebarName, 2); + AddWidget(path, "Popout Value Viewer", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("ValueViewer")) + .WindowName("Value Viewer") + .Options(WindowButtonOptions().Tooltip("Enables the separate Value Viewer Window.")); + + // Message Viewer + path.sidebarName = "Message Viewer"; + AddSidebarEntry("Dev Tools", path.sidebarName, 2); + AddWidget(path, "Popout Message Viewer", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("MessageViewer")) + .WindowName("Message Viewer") + .Options(WindowButtonOptions().Tooltip("Enables the separate Message Viewer Window.")); + + // Gfx Debugger + path.sidebarName = "Gfx Debugger"; + AddSidebarEntry("Dev Tools", path.sidebarName, 1); + AddWidget(path, "Popout Gfx Debugger", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("GfxDebugger")) + .WindowName("GfxDebuggerWindow") + .Options(WindowButtonOptions().Tooltip("Enables the separate Gfx Debugger Window.")); +} + +} // namespace SohGui diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp new file mode 100644 index 000000000..21a90d70b --- /dev/null +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -0,0 +1,2146 @@ +#include "SohMenu.h" +#include +#include +#include +#include +#include +#include +#include + +static std::string comboboxTooltip = ""; +static int32_t enhancementPresetSelected = ENHANCEMENT_PRESET_DEFAULT; +bool isBetaQuestEnabled = false; + +extern "C" { + void enableBetaQuest() { isBetaQuestEnabled = true; } + void disableBetaQuest() { isBetaQuestEnabled = false; } +} + +namespace SohGui { + +extern std::shared_ptr mSohMenu; +using namespace UIWidgets; + +void SohMenu::AddMenuEnhancements() { + // Add Enhancements Menu + AddMenuEntry("Enhancements", CVAR_SETTING("Menu.EnhancementsSidebarSection")); + + // Enhancements + WidgetPath path = { "Enhancements", "Presets", SECTION_COLUMN_1 }; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + + const PresetTypeDefinition presetTypeDef = presetTypes.at(PRESET_TYPE_ENHANCEMENTS); + for (auto iter = presetTypeDef.presets.begin(); iter != presetTypeDef.presets.end(); ++iter) { + if (iter->first != 0) comboboxTooltip += "\n\n"; + comboboxTooltip += std::string(iter->second.label) + " - " + std::string(iter->second.description); + } + AddWidget(path, "Enhancement Presets", WIDGET_COMBOBOX) + .ValuePointer(&enhancementPresetSelected) + .Callback([](WidgetInfo& info) { + const std::string presetTypeCvar = CVAR_GENERAL("SelectedPresets.") + std::to_string(PRESET_TYPE_ENHANCEMENTS); + CVarSetInteger(presetTypeCvar.c_str(), *std::get(info.valuePointer)); + }) + .Options(ComboboxOptions() + .ComboMap(enhancementPresetList) + .DefaultIndex(ENHANCEMENT_PRESET_DEFAULT) + .Tooltip(comboboxTooltip.c_str()) + ); + AddWidget(path, "Apply Preset##Enhancemnts", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + const std::string presetTypeCvar = CVAR_GENERAL("SelectedPresets.") + std::to_string(PRESET_TYPE_ENHANCEMENTS); + const PresetTypeDefinition presetTypeDef = presetTypes.at(PRESET_TYPE_ENHANCEMENTS); + uint16_t selectedPresetId = CVarGetInteger(presetTypeCvar.c_str(), 0); + if(selectedPresetId >= presetTypeDef.presets.size()){ + selectedPresetId = 0; + } + const PresetDefinition selectedPresetDef = presetTypeDef.presets.at(selectedPresetId); + for(const char* block : presetTypeDef.blocksToClear) { + CVarClearBlock(block); + } + if (selectedPresetId != 0) { + applyPreset(selectedPresetDef.entries); + } + CVarSetInteger(presetTypeCvar.c_str(), selectedPresetId); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + + path.sidebarName = "Gameplay"; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + + AddWidget(path, "Boot Sequence", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("BootSequence")) + .Options(ComboboxOptions() + .DefaultIndex(BOOTSEQUENCE_DEFAULT) + .ComboMap(bootSequenceLabels) + .Tooltip( + "Configure what happens when starting or resetting the game.\n\n" + "Default: LUS logo -> N64 logo\n" + "Authentic: N64 logo only\n" + "File Select: Skip to file select menu" + ) + ); + AddWidget(path, "Autosave", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("Autosave")) + .Options(CheckboxOptions().Tooltip( + "Save the game automatically on a 3 minute interval and when soft-resetting the game. The interval " + "autosave will wait if the game is paused in any way (dialogue, pause screen up, cutscenes, " + "etc.).\n\n" + "The soft-reset save will *not* trigger in cutscene maps like the Chamber of Sages!" + )); + + AddWidget(path, "Audio", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Mute Low HP Alarm", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("LowHPAlarm")) + .Options(CheckboxOptions().Tooltip( + "Disable the low HP beeping sound." + )); + AddWidget(path, "Disable Navi Call Audio", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableNaviCallAudio")) + .Options(CheckboxOptions().Tooltip( + "Disables the voice audio when Navi calls you." + )); + + AddWidget(path, "Pause Menu", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Allow the Cursor to be on any slot", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("PauseAnyCursor")) + .Options(ComboboxOptions() + .ComboMap(cursorAnywhereValues) + .DefaultIndex(PAUSE_ANY_CURSOR_RANDO_ONLY) + .Tooltip( + "Allows the cursor on the pause menu to be over any slot. Sometimes required in Randomizer " + "to select certain items." + ) + ); + AddWidget(path, "Assignable Tunics and Boots", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AssignableTunicsAndBoots")) + .Options(CheckboxOptions().Tooltip( + "Allows equipping the Tunics and Boots to C-Buttons/D-Pad." + )); + // TODO: Revist strength toggle, it's currently separate but should probably go here and be locked behind the + // Equipment toggle settings. Also maybe these should all be in Items sidebar? + AddWidget(path, "Equipment Toggle", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EquipmentCanBeRemoved")) + .Options(CheckboxOptions().Tooltip( + "Allows equipment to be removed by toggling it off on\n the equipment subscreen." + )); + AddWidget(path, "Sword Toggle Options", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("SwordToggle")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("EquipmentCanBeRemoved"), 0) == 0; + }) + .Options(ComboboxOptions() + .ComboMap(swordToggleModes) + .DefaultIndex(SWORD_TOGGLE_NONE) + .Tooltip( + "Introduces Options for unequipping Link's sword\n\n" + "None: Only Biggoron's Sword/Giant's Knife can be toggled. Doing so will equip the Master Sword.\n\n" + "Child Toggle: This will allow for completely unequipping any sword as child link.\n\n" + "Both Ages: Any sword can be unequipped as either age. This may lead to swordless glitches as Adult." + ) + ); + + AddWidget(path, "Quality of Life", WIDGET_SEPARATOR_TEXT); + // Maybe should be in Timesavers somewhere? + AddWidget(path, "Link's Cow in Both Time Periods", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CowOfTime")) + .Options(CheckboxOptions().Tooltip( + "Allows the Lon Lon Ranch Obstacle Course reward to be shared across time periods." + )); + // Maybe should be in Difficulty Options somewhere? + AddWidget(path, "Enable Visual Guard Vision", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GuardVision")); + // Maybe should be in TImesavers somewhere? + AddWidget(path, "Pull Grave during the day", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DayGravePull")) + .Options(CheckboxOptions().Tooltip( + "Allows graves to be pulled when child during the day." + )); + AddWidget(path, "Don't Require Input for Credits Sequence", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NoInputForCredits")) + .Options(CheckboxOptions().Tooltip( + "Removes the Input Requirement on Text boxes after defeating Ganon, allowing the Credits " + "Sequence to continue to progress." + )); + AddWidget(path, "Answer Navi Prompt with L Button", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NaviOnL")) + .Options(CheckboxOptions().Tooltip( + "Speak to Navi with L but enter First-Person Camera with C-Up" + )); + AddWidget(path, "Disable Crit Wiggle", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableCritWiggle")) + .Options(CheckboxOptions().Tooltip( + "Disable Random Camera Wiggle at Low Health." + )); + AddWidget(path, "Targetable Hookshot Reticle", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HookshotableReticle")) + .Options(CheckboxOptions().Tooltip( + "Makes the Hookshot Reticle use a different color when aiming at Hookshotable Collision." + )); + AddWidget(path, "Faster Rupee Accumulator", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FasterRupeeAccumulator")) + .Options(CheckboxOptions().Tooltip( + "Causes your Wallet to fill and empty faster when you gain or lose money." + )); + AddWidget(path, "Fun/Aesthetic Options", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Enable Passage of Time on File Select", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeFlowFileSelect")); + AddWidget(path, "Dogs Follow you Everywhere", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DogFollowsEverywhere")) + .Options(CheckboxOptions().Tooltip( + "Allows dogs to follow you anywhere you go, even if you leave the Market." + )); + AddWidget(path, "Enemy Health Bars", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EnemyHealthBar")) + .Options(CheckboxOptions().Tooltip( + "Renders a health bar for Enemies when Z-Targeted." + )); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Authentic Bug Fixes", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Fix L&R Pause Menu", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixMenuLR")) + .Options(CheckboxOptions().Tooltip( + "Makes the L and R buttons in the pause menu the same color" + )); + AddWidget(path, "Fix L&Z Page Switch in Pause Menu", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NGCKaleidoSwitcher")) + .Options(CheckboxOptions().Tooltip( + "Makes L and R switch pages like on the Gamecube. Z opens the Debug Menu instead." + )); + AddWidget(path, "Fix Dungeon Entrances", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixDungeonMinimapIcon")) + .Options(CheckboxOptions().Tooltip( + "Removes the Dungeon Entrance icon on the top-left corner of the screen when no dungeon is present on the " + "current map." + )); + AddWidget(path, "Fix Two-Handled Idle Animations", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TwoHandedIdle")) + .Options(CheckboxOptions().Tooltip( + "Re-Enables the two-handed idle animation, a seemingly finished animation that was disabled on accident " + "in the original game." + )); + AddWidget(path, "Fix the Gravedigging Tour Glitch", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GravediggingTourFix")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); + info.options->disabledTooltip = "This setting is always enabled in randomized save files."; + }) + .Options(CheckboxOptions().Tooltip( + "Fixes a bug where the Gravedigging Tour Heart Piece disappears if the area reloads." + )); + AddWidget(path, "Fix Deku Nut Upgrade", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DekuNutUpgradeFix")) + .Options(CheckboxOptions().Tooltip( + "Prevents the Forest Stage Deku Nut upgrade from becoming unobtainable after receiving the Poacher's Saw." + )); + AddWidget(path, "Fix Navi Text HUD Position", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NaviTextFix")) + .Options(CheckboxOptions().Tooltip( + "Correctly centers the Navi text prompt on the HUD's C-Up button." + )); + AddWidget(path, "Fix Anubis Fireballs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AnubisFix")) + .Options(CheckboxOptions().Tooltip( + "Make Anubis Fireballs do Fire damage when reflected back at them with the Mirror Shield." + )); + AddWidget(path, "Fix Megaton Hammer Crouch Stab", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CrouchStabHammerFix")) + .Callback([](WidgetInfo& info) { + if (!CVarGetInteger(CVAR_ENHANCEMENT("CrouchStabHammerFix"), 0)) { + CVarClear(CVAR_ENHANCEMENT("CrouchStabFix")); + } + }) + .Options(CheckboxOptions().Tooltip( + "Make the Megaton Hammer's crouch stab able to destroy rocks without first swinging it normally." + )); + AddWidget(path, "Remove Power Crouch Stab", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CrouchStabFix")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("CrouchStabHammerFix"), 0) == 0; + }) + .Options(CheckboxOptions().Tooltip( + "Make crouch stabbing always do the same damage as a regular slash." + )); + AddWidget(path, "Fix Credits Timing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CreditsFix")) + .Options(CheckboxOptions().Tooltip( + "Extend certain credits scenes so the music lines up properly with the visuals." + )); + AddWidget(path, "Fix Gerudo Warrior's Clothing Colors", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GerudoWarriorClothingFix")) + .Options(CheckboxOptions().Tooltip( + "Prevent the Gerudo Warrior's clothes changign color when changing Link's tunic or " + "using Bombs in front of her." + )); + AddWidget(path, "Fix Camera Drift", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixCameraDrift")) + .Options(CheckboxOptions().Tooltip( + "Fixes camera slightly drifting to the left when standing still due to a math error." + )); + AddWidget(path, "Fix Camera Swing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixCameraSwing")) + .Options(CheckboxOptions().Tooltip( + "Fixes camera getting stuck on collision when standing still. Also fixes slight shift " + "back in camera when Link stops moving." + )); + AddWidget(path, "Fix Hanging Ledge Swing Rate", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixHangingLedgeSwingRate")) + .Options(CheckboxOptions().Tooltip( + "Fixes camera swing rate when the player falls off a ledge and the camera swings around." + )); + AddWidget(path, "Fix Missing Jingle after 5 Silver Rupees", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SilverRupeeJingleExtend")) + .Options(CheckboxOptions().Tooltip( + "Adds 5 higher pitches for the Silver Rupee Jingle for the rooms with more than 5 Silver Rupees. " + "Only relevant for playthroughs involving Master Quest Dungeons." + )); + AddWidget(path, "Fix Out of Bounds Textures", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixTexturesOOB")) + .Callback([](WidgetInfo& info) { + ApplyAuthenticGfxPatches(); + }) + .Options(CheckboxOptions().Tooltip( + "Fixes authentic out of bounds texture reads, instead loading textures with the correct size." + )); + AddWidget(path, "Fix Poacher's Saw Softlock", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixSawSoftlock")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("SkipText"), 0) == 1; + info.options->disabledTooltip = "This option is forced on when Skip Text is enabled."; + }) + .Options(CheckboxOptions().Tooltip( + "Prevents the Poacher's Saw softlock from mashing through the text, or with Skip Text enabled." + )); + AddWidget(path, "Fix Enemies not Spawning Near Water", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EnemySpawnsOverWaterboxes")) + .Options(CheckboxOptions().Tooltip( + "Causes respanwing enemies, like Stalchildren, to appear on land near bodies of water. " + "Fixes an incorrect calculation that acted like water underneath ground was above it." + )); + AddWidget(path, "Fix Bush Item Drops", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BushDropFix")) + .Options(CheckboxOptions().Tooltip( + "Fixes the bushes to drop items correctly rather than spawning undefined items." + )); + AddWidget(path, "Fix Falling from Vine Edges", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixVineFall")) + .Options(CheckboxOptions().Tooltip( + "Prevents immediately falling off climbable surfaces if climbing on the edges." + )); + AddWidget(path, "Fix Link's Eyes Open while Sleeping", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixEyesOpenWhileSleeping")) + .Options(CheckboxOptions().Tooltip( + "Fixes Link's eyes being open in the openeing cutscene when he is supposed to be sleeping." + )); + AddWidget(path, "Fix Darunia Dancing Too Fast", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixDaruniaDanceSpeed")) + .Options(CheckboxOptions().Tooltip( + "Fixes Darunia's dancing speed so he dances to the beat of Saria's Song, like in the Original Game." + )); + AddWidget(path, "Fix Raised Floor Switches", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixFloorSwitches")) + .Options(CheckboxOptions().Tooltip( + "Fixes the two raised floor switches, the one in Forest Temple Basement and the one at the top of Fire " + "Temple. This will lower them, making activating them easier." + )); + AddWidget(path, "Fix Zora Hint Dialogue", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixZoraHintDialogue")) + .Options(CheckboxOptions().Tooltip( + "Fixes one Zora's dialogue giving a hint about bringing Ruto's Letter to King Zora to properly occur " + "before moving King Zora rather than after." + )); + AddWidget(path, "Fix Hand Holding Hammer", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixHammerHand")) + .Callback([](WidgetInfo& info) { + UpdatePatchHand(); + }) + .Options(CheckboxOptions().Tooltip( + "Fixes Adult Link having a backwards Left hand when holding the Megaton Hammer." + )); + AddWidget(path, "Fix Broken Giant's Knife Bug", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixGrokenGiantsKnife")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); + info.options->disabledTooltip = "This setting is forcefully enabled when you are playing a Randomizer."; + }) + .Callback([](WidgetInfo& info) { + bool hasGiantsKnife = CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_BIGGORON); + bool hasBrokenKnife = CHECK_OWNED_EQUIP_ALT(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_BROKENGIANTKNIFE); + bool knifeIsBroken = gSaveContext.swordHealth == 0.0f; + + if (hasGiantsKnife && (hasBrokenKnife != knifeIsBroken)) { + func_800849EC(gPlayState); + } + }) + .Options(CheckboxOptions().Tooltip( + "Fixes the Broken Giant's Knife flag not being reset when Medigoron fixes it." + )); + + path.column = SECTION_COLUMN_3; + AddWidget(path, "Restorations", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Red Ganon Blood", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RedGanonBlood")) + .Options(CheckboxOptions().Tooltip( + "Restore the original red blood from NTSC 1.0/1.1. Disable for Green blood." + )); + AddWidget(path, "Fish while Hovering", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HoverFishing")) + .Options(CheckboxOptions().Tooltip( + "Restore a bug from NSTC 1.0 that allows casting the Fishing Rod while using the Hover Boots." + )); + AddWidget(path, "N64 Weird Frames", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("N64WeirdFrames")) + .Options(CheckboxOptions().Tooltip( + "Restores N64 Weird Frames allwing weirdshots to behave the same as N64." + )); + AddWidget(path, "Bombchus Out of Bounds", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BombchusOOB")) + .Options(CheckboxOptions().Tooltip( + "Allows Bombchus to explode out of bounds. Similar to Gamecube and Wii VC" + )); + AddWidget(path, "Quick Putaway", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("QuickPutaway")) + .Options(CheckboxOptions().Tooltip( + "Restore a bug from NTSC 1.0 that allows putting away an item without an animation and performing " + "Putaway Ocarina Items." + )); + AddWidget(path, "Restore Old Gold Skulltula Cutscene", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GSCutscene")) + .Options(CheckboxOptions().Tooltip( + "Restore pre-release behavior where defeating a Gold Skulltula will play a cutscene showing it die." + )); + AddWidget(path, "Quick Bongo Kill", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("QuickBongoKill")) + .Options(CheckboxOptions().Tooltip( + "Restore a bug from NTSC 1.0 that allows bypassing Bongo Bongo's intro cutscene to quickly kill him." + )); + AddWidget(path, "Original RBA Values", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RestoreRBAValues")) + .Options(CheckboxOptions().Tooltip( + "Restores the original outcomes when performing Reverse Bottle Adventure." + )); + AddWidget(path, "Early Eyeball Frog", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EarlyEyeballFrog")) + .Options(CheckboxOptions().Tooltip( + "Restores a bug from NTSC 1.0/1.1 that allows you to obtain the eyeball frog from King Zora " + "instead of the Zora Tunic by Holding Shield." + )); + AddWidget(path, "Pulsate Boss Icon", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("Pulsate Boss Icon")) + .Options(CheckboxOptions().Tooltip( + "Restores an unfinished feature to pulsate the boss room icon when you are in the boss room." + )); + AddWidget(path, "Pause Buffer Input Window: %d frames", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("PauseBufferWindow")) + .Options(IntSliderOptions() + .Min(0) + .Max(40) + .DefaultValue(0) + .Format("%d frames") + ); + AddWidget(path, "Include Held Inputs at the Start of Buffer Input Window", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("IncludeHeldInputsBufferWindow")) + .Options(CheckboxOptions().Tooltip( + "Typically, inputs that are held prior to the buffer window are not included in the buffer. This " + "setting changes that behavior to include them. This may cause some inputs to be re-triggered " + "undesireably, for instance Z-Targetting something you might not want to." + )); + AddWidget(path, "Simulated Input Lag: %d frames", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SIMULATED_INPUT_LAG) + .Options(IntSliderOptions() + .Min(0) + .Max(6) + .DefaultValue(0) + .Format("%d frames") + .Tooltip( + "Buffers your inputs to be executed a specified amount of frames later." + ) + ); + + path.sidebarName = "Time Savers"; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Cutscenes", WIDGET_SEPARATOR_TEXT); + bool allSkipsChecked = false; + AddWidget(path, "Skip All", WIDGET_CHECKBOX) + .ValuePointer(&allSkipsChecked) + .PreFunc([](WidgetInfo& info) { + *std::get(info.valuePointer) = + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), IS_RANDO); + }) + .Callback([](WidgetInfo& info) { + int32_t newValue = *std::get(info.valuePointer) ? 1 : 0; + + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), newValue); + CVarSetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), newValue); + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + AddWidget(path, "Skip Intro", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip Entrance Cutscenes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip Story Cutscenes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip Song Cutscenes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip Boss Introductions", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Quick Boss Deaths", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip One Point Cutscenes (Chests, Door Unlocks, etc.)", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip Owl Interactions", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip Misc Interactions", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Disable Title Card", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Exclude Glitch-Aiding Cutscenes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding")) + .Options(CheckboxOptions().Tooltip( + "Don't skip cutscenes that are associated wiht useful glitches. Currently, it is " + "only the Fire Temple Darunia CS, Forest Temple Poe Sisters CS, and the Box Skip One " + "Point in Jabu." + )); + AddWidget(path, "Skip Child Stealth", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipChildStealth")) + .Options(CheckboxOptions().Tooltip( + "The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards." + )); + AddWidget(path, "Skip Tower Escape", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipTowerEscape")) + .Options(CheckboxOptions().Tooltip( + "Skip the tower escape sequence between Ganondorf and Ganon." + )); + AddWidget(path, "Item Scale: %.2f", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimationScale")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED) == SGIA_DISABLED; + }) + .Options(FloatSliderOptions() + .Min(5.0f) + .Max(15.0f) + .Format("%.2f") + .DefaultValue(10.0f) + .Tooltip( + "The size of the item when it is picked up" + )); + + AddWidget(path, "Text", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Skip Forced Dialog", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipForcedDialog")) + .Options(CheckboxOptions().Tooltip( + "Prevent forced conversations with Navi or other NPCs." + )); + AddWidget(path, "Text Speed: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("TextSpeed")) + .Options(IntSliderOptions() + .Min(1) + .Max(5) + .DefaultValue(1) + .Format("%dx") + ); + AddWidget(path, "Skip Text", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipText")) + .Options(CheckboxOptions().Tooltip("Holding down B skips text.")); + AddWidget(path, "Slow Text Speed: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("SlowTextSpeed")) + .Options(IntSliderOptions() + .Min(1) + .Max(5) + .DefaultValue(1) + .Format("%dx") + .Tooltip( + "Changes the speed of sections of text that normally are paced slower than the text surrounding it." + ) + ); + AddWidget(path, "Match Normal Text", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + CVarSetInteger(CVAR_ENHANCEMENT("SlowTextSpeed"), CVarGetInteger(CVAR_ENHANCEMENT("TextSpeed"), 1)); + }) + .Options(ButtonOptions().Tooltip( + "Makes the speed of the slow text match the normal text speed above." + )); + AddWidget(path, "Skip Pickup Messages", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastDrops")) + .Options(CheckboxOptions().Tooltip( + "Skip Pickup Messages for new Consumable Items and Bottle Swipes." + )); + AddWidget(path, "Better Owl", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BetterOwl")) + .Options(CheckboxOptions().Tooltip( + "The default response to Kaepora Gaebora is always that you understood what he said." + )); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Gameplay", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Skip Save Confirmation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipSaveConfirmation")) + .Options(CheckboxOptions().Tooltip("Skip the \"Game Saved\" confirmation screen.")); + AddWidget(path, "Biggoron Forge Time: %d days", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("ForgeTime")) + .Options(IntSliderOptions() + .Min(0) + .Max(3) + .DefaultValue(3) + .Format("%d days") + .Tooltip( + "Allows you to change the number of days it takes for " + "Biggoron to forge the Biggoron's Sword." + ) + ); + AddWidget(path, "Remember Save Location", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RememberSaveLocation")) + .Options(CheckboxOptions().Tooltip( + "When loading a save, places Link at the last entrance he went through.\n" + "This doesn't work if the save was made in grottos, fairy fountains, or dungeons." + )); + AddWidget(path, "Navi Timer Resets", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ResetNaviTimer")) + .Options(CheckboxOptions().Tooltip( + "Resets the Navi timer on scene change. If you have already talked to her, " + "she will try and talk to you again, instead of needing a save warp or death." + )); + AddWidget(path, "No Skulltula Freeze", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkulltulaFreeze")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); + info.options->disabledTooltip = + "This setting is disabled because a randomizer savefile is loaded. Please use the " + "\"Skip Get Item Animation\" option within the randomizer enhancements instead."; + }) + .Options(CheckboxOptions().Tooltip( + "Stops the game from freezing the player when picking up Gold Skulltula Tokens. Does not" + "apply in randomizer savefiles." + )); + AddWidget(path, "Ask to Equip New Items", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AskToEquip")) + .Options(CheckboxOptions().Tooltip( + "Adds a prompt to equip newly-obtained Swords, Shields, and Tunics." + )); + AddWidget(path, "Link as Default File Name", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("LinkDefaultName")) + .Options(CheckboxOptions().Tooltip( + "Allows you to have \"Link\" as a premade file name." + )); + AddWidget(path, "Quit Fishing At Door", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("QuitFishingAtDoor")) + .Options(CheckboxOptions().Tooltip( + "Fisherman asks if you want to quit at the door if you try to leave the Fishing Pond " + "while still holding the Fishing Rod." + )); + AddWidget(path, "Time Travel with Song of Time", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("TimeTravel")) + .Options(ComboboxOptions() + .ComboMap(timeTravelOptions) + .DefaultIndex(0) + .Tooltip( + "Allows Link to freely change age by playing the Song of Time.\n" + "Time Blocks can still be used properly.\n\n" + "Requirements:\n" + " - Obtained the Ocarina of Time (depends on selection)\n" + " - Obtained the Song of Time\n" + " - Obtained the Master Sword\n" + " - Not within range of a Time Block\n" + " - Not within range of Ocarina Playing spots." + ) + ); + AddWidget(path, "Pause Warp", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("PauseWarp")) + .Options(CheckboxOptions().Tooltip( + "Selection of warp song in pause menu initiates a warp. Disables song playback." + )); + AddWidget(path, "Skip Scarecrow's Song", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantScarecrow")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && OTRGlobals::Instance->gRandoContext->GetOption(RSK_SKIP_SCARECROWS_SONG); + info.options->disabledTooltip = "This setting is forcefully enabled because a randomized " + "save file with the option \"Skip Scarecrow Song\" is currently loaded."; + }) + .Options(CheckboxOptions().Tooltip( + "Pierre appears when an Ocarina is pulled out. Requires learning the Scarecrow's Song first." + )); + AddWidget(path, "Time of Day", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Nighttime GS Always Spawn", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NightGSAlwaysSpawn")) + .Options(CheckboxOptions().Tooltip( + "Nighttime Skulltulas will spawn during both day and night." + )); + AddWidget(path, "Dampe Appears All Night", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DampeAllNight")) + .Options(CheckboxOptions().Tooltip( + "Makes Dampe appear anytime during the night, not just his usual working hours." + )); + AddWidget(path, "Exit Market at Night", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("MarketSneak")) + .Options(CheckboxOptions().Tooltip( + "Allows exiting Hyrule Castle Market Town to Hyrule Field at night by speaking to the guard " + "next to the gate." + )); + AddWidget(path, "Shops and Games Always Open", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("OpenAllHours")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && OTRGlobals::Instance->gRandoContext->GetOption(RSK_LOCK_OVERWORLD_DOORS).Is(RO_GENERIC_ON); + }) + .Options(CheckboxOptions().Tooltip( + "Shops and Minigames are open both day and night. Requires a scene reload to take effect." + ).DisabledTooltip( + "This is not compatible with the Locked Overworld Doors Randomizer option." + )); + path.column = SECTION_COLUMN_3; + AddWidget(path, "Animations", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "King Zora Speed: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_ENHANCEMENT("MweepSpeed")) + .Options(FloatSliderOptions() + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f) + .Format("%.2fx") + ); + AddWidget(path, "Vine/Ladder Climb Speed +%d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("Climb Speed")) + .Options(IntSliderOptions() + .Min(0) + .Max(12) + .DefaultValue(0) + .Format("+%d") + ); + AddWidget(path, "Block Pushing Speed +%d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("FasterBlockPush")) + .Options(IntSliderOptions() + .Min(0) + .Max(5) + .DefaultValue(0) + .Format("+%d") + ); + AddWidget(path, "Crawl Speed %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("CrawlSpeed")) + .Options(IntSliderOptions() + .Min(1) + .Max(4) + .DefaultValue(1) + .Format("%dx") + ); + AddWidget(path, "Faster Heavy Block Lift", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FasterHeavyBlockLift")) + .Options(CheckboxOptions().Tooltip( + "Speeds up lifting Silver Rocks and Obelisks." + )); + AddWidget(path, "Fast Ocarina Playback", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastOcarinaPlayback")) + .Options(CheckboxOptions().Tooltip( + "Skip the part where the Ocarina Playback is called when you play a song." + )); + AddWidget(path, "Skip Magic Arrow Equip Animation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipArrowAnimation")); + AddWidget(path, "Faster Farore's Wind", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastFarores")) + .Options(CheckboxOptions().Tooltip("Greatly decreases cast time of Farore's Wind magic spell.")); + AddWidget(path, "Fast Chests", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastChests")) + .Options(CheckboxOptions().Tooltip( + "Makes Link always kick the chest to open it, instead of doing the longer " + "chest opening animation for major items." + )); + AddWidget(path, "Skip Water Take Breath Animation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipSwimDeepEndAnim")) + .Options(CheckboxOptions().Tooltip( + "Skips Link's taking breath animation after coming up from water. " + "This setting does not interfere with getting items from underwater." + )); + AddWidget(path, "Play Zelda's Lullaby to Open Sleeping Waterfall", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && OTRGlobals::Instance->gRandoContext->GetOption(RSK_SLEEPING_WATERFALL).Is(RO_WATERFALL_OPEN); + info.options->disabledTooltip = "This setting is forcefully enabled because a randomizer savefile with \"Sleeping Waterfall: Open\" is loaded."; + }) + .Options(ComboboxOptions() + .ComboMap(sleepingWaterfallOptions) + .DefaultIndex(WATERFALL_ALWAYS) + .Tooltip( + "Always: Link must always play Zelda's Lullaby to open the waterfall entrance to Zora's Domain.\n" + "Once: Link only needs to play Zelda's Lullaby once to open the waterfall; after that, it stays " + "open permanently.\n" + "Never: Link never needs to play Zelda's Lullaby to open the waterfall. He only needs to have " + "learned it and have an Ocarina." + ) + ); + + path.sidebarName = "Graphics"; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Mods", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Use Alternate Assets", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AltAssets")) + .Options(CheckboxOptions().Tooltip( + "Toggle between standard assets and alternate assets. Usually mods will indicate if " + "this setting has to be used or not." + )); + AddWidget(path, "Disable Bomb Billboarding", WIDGET_CVAR_CHECKBOX) + .CVar("DisableBombBillboarding") + .Options(CheckboxOptions().Tooltip( + "Disables bombs always rotating to face the camera. To be used in conjunction with mods that want to " + "replace bombs with 3D objects." + )); + AddWidget(path, "Disable Grotto Fixed Rotation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableGrottoRotation")) + .Options(CheckboxOptions().Tooltip( + "Disables Grottos rotating with the Camera. To be used in conjuction with mods that want to " + "replace grottos with 3D objects." + )); + + + AddWidget(path, "UI", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Minimal UI", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("MinimalUI")) + .Options(CheckboxOptions().Tooltip( + "Hides most of the UI when not needed.\n" + "NOTE: Doesn't activate until scene transition." + )); + AddWidget(path, "Disable Hot/Underwater Warning Text", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableTunicWarningText")) + .Options(CheckboxOptions().Tooltip( + "Disables warning text when you don't have on the Goron/Zora Tunic " + "in Hot/Underwater conditions." + )); + AddWidget(path, "Remember Minimap State Between Areas", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RememberMapToggleState")) + .Options(CheckboxOptions().Tooltip( + "Preverse the minimap visibility state when going between areas rather than default it to \"on\" " + "when going through loading zones." + )); + AddWidget(path, "Visual Stone of Agony", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("VisualAgony")) + .Options(CheckboxOptions().Tooltip( + "Displays an icon and plays a sound when Stone of Agony should be activated, for those without rumble." + )); + AddWidget(path, "Disable HUD Heart Animations", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NoHUDHeartAnimation")) + .Options(CheckboxOptions().Tooltip( + "Disables the Beating Animation of the Hearts on the HUD." + )); + AddWidget(path, "Glitch line-up tick", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DrawLineupTick")) + .Options(CheckboxOptions().Tooltip( + "Displays a tick in the top center of the screen to help with glitch line-ups in SoH, since traditional " + "UI based line-ups do not work outside of 4:3" + )); + AddWidget(path, "Disable Black Bar Letterboxes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableBlackBars")) + .Options(CheckboxOptions().Tooltip( + "Disables Black Bar Letterboxes during cutscenes and Z-Targeting. NOTE: there may be minor visual " + "glitches that were covered up by the black bars. Please disable this setting before reporting a bug." + )); + AddWidget(path, "Dynamic Wallet Icon", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DynamicWalletIcon")) + .Options(CheckboxOptions().Tooltip( + "Changes the Rupee in the Wallet icon to match the wallet size you currently have." + )); + AddWidget(path, "Always Show Dungeon Entrances", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AlwaysShowDungeonMinimapIcon")) + .Options(CheckboxOptions().Tooltip( + "Always shows dungeon entrance icons on the Minimap." + )); + AddWidget(path, "More Info in File Select", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FileSelectMoreInfo")) + .Options(CheckboxOptions().Tooltip( + "Shows what items you have collected in the File Select screen, like in N64 Randomizer." + )); + AddWidget(path, "Better Ammo Rendering in Pause Menu", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BetterAmmoRendering")) + .Options(CheckboxOptions().Tooltip( + "Ammo counts in the pause menu will work correctly regardless of the position of items in the Inventory." + )); + + AddWidget(path, "Models", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Invisible Bunny Hood", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HideBunnyHood")) + .Options(CheckboxOptions().Tooltip( + "Turns Bunny Hood Invisible while still maintaining its effects." + )); + AddWidget(path, "Animated Link in Pause Menu", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("PauseMenuAnimatedLink")) + .Options(CheckboxOptions().Tooltip( + "Turns the Static Image of Link in the Pause Menu's Equipment Subsceen " + "into a model cycling through his idle animations." + )); + AddWidget(path, "Show Age-Dependent Equipment", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EquipmentAlwaysVisible")) + .Callback([](WidgetInfo& info) { + UpdatePatchHand(); + }) + .Options(CheckboxOptions().Tooltip( + "Makes all equipment visible, regardless of Age." + )); + AddWidget(path, "Scale Adult Equipment as Child", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ScaleAdultEquipmentAsChild")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0) == 0; + }) + .Options(CheckboxOptions().Tooltip( + "Scales all of the Adult Equipment, as well as moving some a bit, to fit on Child Link better. May " + "not work properly with some mods." + )); + AddWidget(path, "Enable 3D Dropped Items/Projectiles", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NewDrops")) + .Options(CheckboxOptions().Tooltip( + "Replaces most 2D items and projectiles on the overworld with their equivalent 3D models." + )); + AddWidget(path, "Show Gauntlets in First Person", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FirstPersonGauntlets")) + .Options(CheckboxOptions().Tooltip( + "Renders Guantlets when using the Bow and Hookshot like in OoT3D." + )); + AddWidget(path, "Show Chains on Both Sides of Locked Doors", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ShowDoorLocksOnBothSides")); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Textures", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Chest Size & Texture Matches Contents", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents")) + .Callback([](WidgetInfo& info) { + if (CVarGetInteger(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents"), CSMC_DISABLED) == CSMC_DISABLED) { + CVarSetInteger(CVAR_ENHANCEMENT("ChestSizeDependsStoneOfAgony"), 0); + } + }) + .Options(ComboboxOptions() + .ComboMap(chestStyleMatchesContentsOptions) + .DefaultIndex(CSMC_DISABLED) + .Tooltip( + "Chest sizes and textures are changed to help identify the item inside.\n" + " - Major items: Large gold chests\n" + " - Lesser items: Large brown chests\n" + " - Junk items: Small brown chests\n" + " - Small keys: Small silver chests\n" + " - Boss keys: Vanilla size and texture\n" + " - Skulltula Tokens: Small skulltula chest\n" + "\n" + "NOTE: Textures will not apply if you are using a mod pack with a custom chest model." + ) + ); + AddWidget(path, "Chests of Agony", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ChestSizeDependsStoneOfAgony")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchesContents"), CSMC_DISABLED); + }) + .Options(CheckboxOptions().Tooltip( + "Only change the size/texture of chests if you have the Stone of Agony." + )); + AddWidget(path, "Color Temple of Time's Medallions", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ToTMedallionsColors")) + .Callback([](WidgetInfo& info) { + PatchToTMedallions(); + }) + .Options(CheckboxOptions().Tooltip( + "When Medallions are collected, the Medallion imprints around the Master Sword Pedestal in the Temple " + "of Time will become colored-in." + )); + AddWidget(path, "Fix Vanishing Paths", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("SceneSpecificDirtPathFix")) + .Callback([](WidgetInfo& info) { + if (gPlayState != NULL) { + UpdateDirtPathFixState(gPlayState->sceneNum); + } + }) + .Options(ComboboxOptions() + .ComboMap(zFightingOptions) + .DefaultIndex(ZFIGHT_FIX_DISABLED) + .Tooltip( + "Disabled: Paths vanish more the higher the resolution (Z-fighting is based on resolution)\n" + "Consistent: Certain paths vanish the same way in all resolutions\n" + "No Vanish: Paths do not vanish, Link seems to sink in to some paths\n" + "This might affect other decal effects\n" + ) + ); + AddWidget(path, "Text Spacing: %d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("TextSpacing")) + .Options(IntSliderOptions() + .Min(4) + .Max(6) + .DefaultValue(6) + .Tooltip( + "Space between text characters (useful for HD font textures)." + ) + ); + + AddWidget(path, "Rendering", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Disable LOD", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableLOD")) + .Options(CheckboxOptions().Tooltip( + "Turns off the Level of Detail setting, making models use their Higher-Poly variants at any distance." + )); + AddWidget(path, "Increase Actor Draw Distance: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("DisableDrawDistance")) + .Callback([](WidgetInfo& info) { + if (CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) <= 1) { + CVarSetInteger(CVAR_ENHANCEMENT("DisableKokiriDrawDistance"), 0); + } + }) + .Options(IntSliderOptions() + .Min(1) + .Max(5) + .DefaultValue(1) + .Format("%dx") + .Tooltip( + "Increases the range in which Actors/Objects are drawn." + ) + ); + AddWidget(path, "Kokiri Draw Distance", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableKokiriDrawDistance")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) > 1; + }) + .Options(CheckboxOptions().Tooltip( + "The Kokiri are mystical beings that fade into view when approached. Enabling this will remove their " + "draw distance." + )); + AddWidget(path, "Widescreen Actor Culling", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("WidescreenActorCulling")) + .Options(CheckboxOptions().Tooltip( + "Adjusts the Horizontal Culling Plane to account for Widescreen Resolutions." + )); + AddWidget(path, "Cull Glitch Useful Actors", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtendedCullingExcludeGlitchActors")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("WidescreenActorCulling"), 0) && + CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) <= 1; + info.options->disabledTooltip = "Requires Actor Draw Distance to be increased or Widscreen Actor Culling to be enabled."; + }) + .Options(CheckboxOptions().Tooltip( + "Exclude Actors that are useful for Glitches from the extended culling ranges. Some actors may still draw " + "in the extended ranges, but will not \"update\" so that certain glitches that leverage the original " + "culling requirements will still work.\n\nThe following actors are excluded:\n" + " - White clothed Gerudos\n" + " - King Zora\n" + " - Gossip Stones\n" + " - Boulders\n" + " - Blue Warps\n" + " - Darunia\n" + " - Gold SKulltulas\n" + )); + + // TODO: Find a better home for these. + path.column = SECTION_COLUMN_3; + AddWidget(path, "Misc.", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "N64 Mode", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_LOW_RES_MODE) + .Options(CheckboxOptions().Tooltip( + "Sets the aspect ratio to 4:3 and lowers resolution to 240p, the N64's native resolution." + )); + AddWidget(path, "Remove Spin Attack Darkness", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RemoveSpinAttackDarkness")) + .Options(CheckboxOptions().Tooltip( + "Remove the Darkness that appears when charging a Spin Attack" + )); + + path.sidebarName = "Items"; + AddSidebarEntry("Enhancements", path.sidebarName, 2); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Controls", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Equip Items on Dpad", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DpadEquips")) + .Options(CheckboxOptions().Tooltip( + "Equip items and equipment on the D-Pad. If used with \"D-Pad on Pause Screen\", you must " + "hold C-Up to equip instead of navgiate." + )); + AddWidget(path, "Instant Putaway", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantPutaway")) + .Options(CheckboxOptions().Tooltip( + "Allow Link to put items away without having to wait around." + )); + AddWidget(path, "Instant Boomerang Recall", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastBoomerang")) + .Options(CheckboxOptions().Tooltip( + "Instantly return the boomerang to Link by pressing its item button while " + "it's in the air." + )); + AddWidget(path, "Prevent Dropped Ocarina Inputs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DpadNoDropOcarinaInput")) + .Options(CheckboxOptions().Tooltip( + "Prevent dropping inputs when playing the Ocarina too quickly." + )); + AddWidget(path, "Masks", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Bunny Hood Effect", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("MMBunnyHood")) + .Options(CheckboxOptions().Tooltip( + "Wearing the Bunny Hood grants a speed increase link in Majora's Mask. " + "The longer jump option is not accounted for in Randomizer logic.\n\n" + "Also disables NPC's reactions to wearing the Bunny Hood." + )); + AddWidget(path, "Masks Equippable as Adult", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AdultMasks")) + .Options(CheckboxOptions().Tooltip( + "Allows masks to be equipped normally from the pause menu as adult." + )); + AddWidget(path, "Persistent Masks", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("PersistentMasks")) + .Options(CheckboxOptions().Tooltip( + "Stops masks from automatically unequipping on certain situations:\n" + "- When entering a new scene\n" + "- When not in any C button or the D-Pad\n" + "- When saving and quitting\n" + "- When dying\n" + "- When traveling thru time (if \"Masks Equippable as Adult\" is activated)." + )); + AddWidget(path, "Mask Select in Inventory", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("MaskSelect")) + .Options(CheckboxOptions().Tooltip( + "After completing the mask trading sub-quest, press A and any direction on the mask " + "slog to change masks" + )); + AddWidget(path, "Item Count Messages", WIDGET_SEPARATOR_TEXT); + int numOptions = ARRAY_COUNT(itemCountMessageCVars); + bool allItemCountsChecked = false; + AddWidget(path, "All", WIDGET_CHECKBOX) + .ValuePointer(&allItemCountsChecked) + .PreFunc([](WidgetInfo& info) { + int numOptions = ARRAY_COUNT(itemCountMessageCVars); + *std::get(info.valuePointer) = std::all_of(itemCountMessageCVars, itemCountMessageCVars + numOptions, + [](const char* cvar) { return CVarGetInteger(cvar, 0); }); + }) + .Callback([](WidgetInfo& info) { + int32_t newValue = *std::get(info.valuePointer) ? 1 : 0; + int numOptions = ARRAY_COUNT(itemCountMessageCVars); + std::for_each(itemCountMessageCVars, itemCountMessageCVars + numOptions, + [newValue](const char* cvar) { CVarSetInteger(cvar, newValue); }); + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + for (int i = 0; i < numOptions; i++) { + AddWidget(path, itemCountMessageOptions[i], WIDGET_CVAR_CHECKBOX) + .CVar(itemCountMessageCVars[i]); + } + path.column = SECTION_COLUMN_2; + AddWidget(path, "Equipment", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Deku Nuts Explode Bombs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NutsExplodeBombs")) + .Options(CheckboxOptions().Tooltip( + "Make Deku Nuts explode Bombs, similar to how they interact with Bombchus. " + "This does not affect Bombflowers." + )); + AddWidget(path, "Equip Multiple Arrows at Once", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SeparateArrows")) + .Options(CheckboxOptions().Tooltip( + "Allow the Bow and Magic Arrows to be equipped at the same time on different slots. " + "NOTE: This will disable the behavior of the 'Equip Dupe' glitch." + )); + AddWidget(path, "Bow and Child/Slingshot as Adult", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BowSlingshotAmmoFix")) + .Options(CheckboxOptions().Tooltip( + "Allows Child to use a Bow with Arrows.\n" + "Allows Adult to use a Slingshot with Seeds.\n\n" + "Requires glitches or the 'Timeless Equipment' cheat to equip." + )); + AddWidget(path, "Better Farore's Wind", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BetterFarore")) + .Options(CheckboxOptions().Tooltip( + "Helps FW persist between ages, gives Child and Adult separate FW points, and can " + "be used in more places." + )); + AddWidget(path, "Remove Explosive Limit", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RemoveExplosiveLimit")) + .Options(CheckboxOptions().Tooltip( + "Removes the cap of 3 active explosives being deployed at once." + )); + AddWidget(path, "Static Explosion Radius", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("StaticExplosionRadius")) + .Options(CheckboxOptions().Tooltip( + "Explosions are now a static size, like in Majora's Mask and OoT3D. Makes Bombchu " + "hovering much easier." + )); + AddWidget(path, "Prevent Bombchus Forcing Firs-Person", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableFirstPersonChus")) + .Options(CheckboxOptions().Tooltip( + "Prevent Bombchus from forcing the camera into first-person mode when released." + )); + AddWidget(path, "Better Bombchu Shopping", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BetterBombchuShopping")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); + info.options->disabledTooltip = "This setting is forcefully enabled when you are playing a randomizer."; + }) + .Options(CheckboxOptions().Tooltip( + "Bombchus do not sell out when bought, and a 10 pack of Bombchus costs 99 rupees " + "instead of 100." + )); + AddWidget(path, "Aiming Reticle for the Bow/Slingshot", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BowReticle")) + .Options(CheckboxOptions().Tooltip( + "Aiming with a Bow or Slingshot will display a reticle as with the Hookshot " + "when the projectile is ready to fire." + )); + AddWidget(path, "Aim Boomerang in First-Person Mode", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BoomerangFirstPerson")) + .Callback([](WidgetInfo& info) { + if (!CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0)) { + CVarSetInteger(CVAR_ENHANCEMENT("BoomerangReticle"), 0); + } + }) + .Options(CheckboxOptions().Tooltip( + "Change aiming for the Boomerang from Third-Person to First-Person to see past Link's head." + )); + AddWidget(path, "Aiming Reticle for Boomerang", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BoomerangFirstPerson")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0) != 0; + }) + .Options(CheckboxOptions().Tooltip( + "Aiming with the Boomerang will display a reticle as with the Hookshot." + )); + AddWidget(path, "Allow Strength Equipement to be Toggled", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ToggleStrength")) + .Callback([](WidgetInfo& info) { + if (!CVarGetInteger(CVAR_ENHANCEMENT("ToggleStrength"), 0)) { + CVarSetInteger(CVAR_ENHANCEMENT("StrengthDisabled"), 0); + } + }) + .Options(CheckboxOptions().Tooltip( + "Allows Strength to be toggled on and off by pressing A on the Strength Upgrade " + "in the Equipment Subscreen of the Pause Menu. This allows performing some glitches " + "that require the player to not have Strength." + )); + // TODO: See if a Callback could be registered to avoid the need to reload scenes for the next two options. + AddWidget(path, "Blue Fire Arrows", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BlueFireArrows")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = OTRGlobals::Instance->gRandoContext->GetOption(RSK_BLUE_FIRE_ARROWS).Is(RO_GENERIC_ON); + info.options->disabledTooltip = "This setting is forcefully enabled because a randomized savefile with " + "\"Blue Fire Arrows\" is currently loaded."; + }) + .Options(CheckboxOptions().Tooltip( + "Allows Ice Arrows to melt Red Ice. May require a room reload if toggled during gameplay." + )); + AddWidget(path, "Sunlight Arrows", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SunlightArrows")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = OTRGlobals::Instance->gRandoContext->GetOption(RSK_SUNLIGHT_ARROWS).Is(RO_GENERIC_ON); + info.options->disabledTooltip = "This setting is forcefully enabled because a randomized savefile with " + "\"Sunlight Arrows\" enabled is currently loaded."; + }) + .Options(CheckboxOptions().Tooltip( + "Allows Light Arrows to activate Sun Switches. May require a room reload if toggled during gameplay." + )); + + // Difficulty Options + path.sidebarName = "Difficulty"; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Shooting Gallery", WIDGET_SEPARATOR_TEXT); + auto shootingGalleryDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeShootingGallery"), 0); + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off."; + }; + AddWidget(path, "Customize Behavior##Shooting", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeShootingGallery")) + .Options(CheckboxOptions().Tooltip("Turn on/off changes to the shooting gallery behavior")); + AddWidget(path, "Instant Win", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantShootingGalleryWin")) + .PreFunc(shootingGalleryDisabledFunc) + .Options(CheckboxOptions().Tooltip("Skips the Shooting Gallery minigame")); + AddWidget(path, "No Rupee Randomization", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ConstantAdultGallery")) + .PreFunc(shootingGalleryDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Forces the rupee order to not be randomized as adult, making it the same as child." + )); + AddWidget(path, "Child Starting Ammunition: %d seeds", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("ShootingGalleryAmmoChild")) + .PreFunc(shootingGalleryDisabledFunc) + .Options(IntSliderOptions() + .Min(10) + .Max(30) + .DefaultValue(15) + .Format("%d seeds") + .Tooltip( + "The ammunition at the start of the Shooting Gallery minigame as Child." + ) + ); + AddWidget(path, "Adult Starting Ammunition: %d arrows", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("ShootingGalleryAmmoAdult")) + .PreFunc(shootingGalleryDisabledFunc) + .Options(IntSliderOptions() + .Min(10) + .Max(30) + .DefaultValue(15) + .Format("%d arrows") + .Tooltip( + "The ammunition at the start of the Shooting Gallery minigame as Adult." + ) + ); + + AddWidget(path, "Bombchu Bowling", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Customize Behavior##Bowling", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeBombchuBowling")) + .Options(CheckboxOptions().Tooltip( + "Turn on/off changes to the Bombchu Bowling behavior." + )); + auto bombchuBowlingDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeBombchuBowling"), 0) == 0; + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off"; + }; + AddWidget(path, "Remove Small Cucco", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BombchuBowlingNoSmallCucco")) + .PreFunc(bombchuBowlingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Prevents the small Cucco from appearing in the Bombchu Bowling minigame." + )); + AddWidget(path, "Remove Big Cucco", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BombchuBowlingNoBigCucco")) + .PreFunc(bombchuBowlingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Prevents the big Cucco from appearing in the Bombchu Bowling minigame." + )); + AddWidget(path, "Bombchu Count: %d bombchus", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("BombchuBowlingAmmo")) + .PreFunc(bombchuBowlingDisabledFunc) + .Options(IntSliderOptions() + .Min(3) + .Max(20) + .DefaultValue(10) + .Format("%d bombchus") + .Tooltip("The number of Bombchus available at the start of the Bombchu Bowling minigame.") + ); + + AddWidget(path, "Fishing", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Customize Behavior##Fishing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeFishing")) + .Options(CheckboxOptions().Tooltip( + "Turn on/off changes to the Fishing behavior" + )); + auto fishingDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0) == 0; + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off."; + }; + AddWidget(path, "Instant Fishing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantFishing")) + .PreFunc(fishingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "All fish will be caught instantly." + )); + AddWidget(path, "Guarantee Bite", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GuaranteeFishingBite")) + .PreFunc(fishingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "When a line is stable, guarantee bite. Otherwise use Default logic." + )); + AddWidget(path, "Fish Never Escape", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FishNeverEscape")) + .PreFunc(fishingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Once a hook as been set, Fish will never let go while being reeled in." + )); + AddWidget(path, "Loaches Always Appear", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("LoachesAlwaysAppear")) + .PreFunc(fishingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Loaches will always appear in the fishing pond instead of every four visits." + )); + AddWidget(path, "Skip Keep Confirmation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipKeepConfirmation")) + .PreFunc(fishingDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "The Pond Owner will not ask to confirm if you want to keep a smaller Fish." + )); + AddWidget(path, "Child Minimum Weight: %d lbs.", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("MinimumFishWeightChild")) + .PreFunc(fishingDisabledFunc) + .Options(IntSliderOptions() + .Min(3) + .Max(10) + .DefaultValue(10) + .Format("%d lbs.") + .Tooltip( + "The minimum weight for the unique Fishing Reward as a Child." + ) + ); + AddWidget(path, "Adult Minimum Weight: %d lbs.", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("MinimumFishWeightAdult")) + .PreFunc(fishingDisabledFunc) + .Options(IntSliderOptions() + .Min(6) + .Max(13) + .DefaultValue(13) + .Format("%d lbs.") + .Tooltip( + "The minimum weight for the unique fishing reward as an Adult." + ) + ); + AddWidget(path, "All Fish are Hyrule Loaches", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("AllHyruleLoaches")) + .PreFunc(fishingDisabledFunc) + .Options(IntSliderOptions().Tooltip( + "Every fish in the Fishing Pond will always be a Hyrule Loach.\n\n" + "NOTE: This requires reloading the area." + )); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Lost Woods Ocarina Game", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Customize Behavior##LostWoods", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeOcarinaGame")) + .Options(CheckboxOptions().Tooltip( + "Turn on/off changes to the Lost Woods Ocarina Game behavior." + )); + auto ocarinaMemoryGameDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeOcarinaGame"), 0) == 0; + info.options->disabledTooltip = "This options is disabled because \"Customize Behavior\" is turned off."; + }; + AddWidget(path, "Instant Win##LostWoods", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantOcarinaGameWin")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Skips the Lost Woods Ocarina Memory Game." + )); + AddWidget(path, "Note Play Speed: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.NoteSpeed")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(1) + .Max(5) + .DefaultValue(1) + .Format("%dx") + .Tooltip( + "Adjust the speed that the Skull Kids play the notes." + ) + ); + AddWidget(path, "Unlimited Playback Time##LostWoods", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("OcarinaUnlimitedFailTime")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Removes the timer to play back the song." + )); + AddWidget(path, "Number of Starting Notes: %d notes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.StartingNotes")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(1) + .Max(8) + .DefaultValue(3) + .Format("%d notes") + .Tooltip( + "Adjust the number of notes the Skull Kids play to start the first round." + ) + ); + int roundMin = CVarGetInteger(CVAR_ENHANCEMENT("OcarinaGame.StartingNotes"), 3); + AddWidget(path, "Round One Notes: %d notes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.RoundOneNotes")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(roundMin) + .Max(8) + .DefaultValue(5) + .Format("%d notes") + .Tooltip( + "Adjust the number of notes you need to play to end the first round." + ) + ); + AddWidget(path, "Round Two Notes: %d notes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.RoundTwoNotes")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(roundMin) + .Max(8) + .DefaultValue(6) + .Format("%d notes") + .Tooltip( + "Adjust the number of notes you need to play to end the second round." + ) + ); + AddWidget(path, "Round Three Notes: %d notes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.RoundThreeNotes")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(roundMin) + .Max(8) + .DefaultValue(8) + .Format("%d notes") + .Tooltip( + "Adjust the number of notes you need to play to end the third round." + ) + ); + + AddWidget(path, "Frogs' Ocarina Game", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Customize Behavior##Frogs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame")) + .Options(CheckboxOptions().Tooltip( + "Turn on/off changes to the Frogs' Ocarina Game behavior." + )); + auto frogsOcarinaGameDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame"), 0) == 0; + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off."; + }; + AddWidget(path, "Instant Win##Frogs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantFrogsGameWin")) + .PreFunc(frogsOcarinaGameDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Skips the Frogs' Ocarina Game." + )); + AddWidget(path, "Unlimited Playback Time##Frogs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FrogsUnlimitedFailTime")) + .PreFunc(frogsOcarinaGameDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Removes the timer to play back the song." + )); + AddWidget(path, "Modify note timer: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("FrogsModifyFailTime")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame"), 0) || CVarGetInteger(CVAR_ENHANCEMENT("FrogsUnlimitedFailTime"), 0); + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off or \"Unlimited Playback Time\" is on"; + }) + .Options(IntSliderOptions() + .Min(1) + .Max(5) + .DefaultValue(1) + .Format("%dx") + .Tooltip( + "Adjusts the time allowed for playback before failing." + ) + ); + + AddWidget(path, "Health", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Permanent Heart Loss", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("PermanentHeartLoss")) + .Callback([](WidgetInfo& info) { + UpdatePermanentHeartLossState(); + }) + .Options(CheckboxOptions().Tooltip( + "When you lose 4 quarters of a heart you will permanently lose that Heart Container.\n\n" + "Disabling this after the fact will restore your Heart Containers." + )); + AddWidget(path, "Damage Multiplier", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("DamageMult")) + .Options(ComboboxOptions() + .ComboMap(allPowers) + .DefaultIndex(0) + .Tooltip( + "Modifies all sources of damage not affected by other sliders\n" + "2x: Can survive all common attacks from the start of the game\n" + "4x: Dies in 1 hit to any substantial attack from the start of the game\n" + "8x: Can only survive trivial damage from the start of the game\n" + "16x: Can survive all common attacks with max health without double defense\n" + "32x: Can survive all common attacks with max health and double defense\n" + "64x: Can survive trivial damage with max health without double defense\n" + "128x: Can survive trivial damage with max health and double defense\n" + "256x: Cannot survive damage" + ) + ); + AddWidget(path, "Fall Damage Multiplier", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("FallDamageMult")) + .Options(ComboboxOptions() + .ComboMap(subPowers) + .Tooltip( + "Modifies all fall damage\n" + "2x: Can survive all fall damage from the start of the game\n" + "4x: Can only survive short fall damage from the start of the game\n" + "8x: Cannot survive any fall damage from the start of the game\n" + "16x: Can survive all fall damage with max health without double defense\n" + "32x: Can survive all fall damage with max health and double defense\n" + "64x: Can survive short fall damage with double defense\n" + "128x: Cannot survive fall damage" + ) + ); + AddWidget(path, "Void Damage Multiplier", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("VoidDamageMult")) + .Options(ComboboxOptions() + .ComboMap(subSubPowers) + .DefaultIndex(0) + .Tooltip( + "Modifies damage taken after falling into a void\n" + "2x: Can survive void damage from the start of the game\n" + "4x: Cannot survive void damage from the start of the game\n" + "8x: Can survive void damage twice with max health without double defense\n" + "16x: Can survive void damage with max health without double defense\n" + "32x: Can survive void damage with max health and double defense\n" + "64x: Cannot survive void damage" + ) + ); + AddWidget(path, "Bonk Damage Multiplier", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("BonkDamageMult")) + .Options(ComboboxOptions() + .ComboMap(bonkDamageValues) + .DefaultIndex(BONK_DAMAGE_NONE) + .Tooltip("Modifies Damage taken after Bonking.") + ); + AddWidget(path, "Spawn with Full Health", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FullHealthSpawn")) + .Options(CheckboxOptions().Tooltip( + "Respawn with Full Health instead of 3 hearts." + )); + AddWidget(path, "No Heart Drops", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NoHeartDrops")) + .Options(CheckboxOptions().Tooltip( + "Disables Heart Drops, but not Heart Placements, like from a Deku Scrub running off.\n" + "This simulates Hero Mode from other games in the series." + )); + + path.column = SECTION_COLUMN_3; + AddWidget(path, "Drops", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "No Random Drops", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NoRandomDrops")) + .Options(CheckboxOptions().Tooltip( + "Disables Random Drops, except from the Goron Pot, Dampe, and Bosses." + )); + AddWidget(path, "Enable Bombchu Drops", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EnableBombchuDrops")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = OTRGlobals::Instance->gRandoContext->GetOption(RSK_ENABLE_BOMBCHU_DROPS).Is(RO_GENERIC_ON); + info.options->disabledTooltip = "This setting is forcefully enabled because a randomized savefile with " + "\"Enable Bombchu Drops\" is loaded."; + }) + .Options(CheckboxOptions().Tooltip( + "Bombchus will sometimes drop in place of Bombs." + )); + AddWidget(path, "Trees Drop Sticks", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TreesDropSticks")) + .Options(CheckboxOptions().Tooltip( + "Bonking into Trees will have a chance to drop up to 3 Sticks. Must have obtained sticks previously." + )); + + AddWidget(path, "Miscellaneous", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Delete File on Death", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DeleteFileOnDeath")) + .Options(CheckboxOptions().Tooltip( + "Dying will delete your file.\n\n" + ICON_FA_EXCLAMATION_TRIANGLE " WARNING " ICON_FA_EXCLAMATION_TRIANGLE + "\nTHIS IS NOT REVERSABLE\nUSE AT YOUR OWN RISK!" + )); + AddWidget(path, "Hyper Bosses", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HyperBosses")) + .Callback([](WidgetInfo& info) { + UpdateHyperBossesState(); + }) + .Options(CheckboxOptions().Tooltip( + "All Major Bosses move and act twice as fast." + )); + AddWidget(path, "Hyper Enemies", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HyperEnemies")) + .Callback([](WidgetInfo& info) { + UpdateHyperEnemiesState(); + }) + .Options(CheckboxOptions().Tooltip( + "All Regular Enemies and Mini-Bosses move and act twice as fast." + )); + AddWidget(path, "Always Win Goron Pot", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GoronPot")) + .Options(CheckboxOptions().Tooltip( + "Always get the Heart Piece/Purple Rupee from the Spinning Goron Pot." + )); + AddWidget(path, "Always Win Dampe Digging Game", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DampeWin")) + .Options(CheckboxOptions().Tooltip( + "Always win the Heart Piece/Purple Rupee on the first dig in Dampe's Grave Digging game. " + "In a Randomizer file, this is always enabled." + )); + AddWidget(path, "All Dogs are Richard", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AllDogsRichard")) + .Options(CheckboxOptions().Tooltip( + "All dogs can be traded in and will count as Richard." + )); + AddWidget(path, "Cuccos Stay Put Multiplier: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("CuccoStayDurationMult")) + .Options(IntSliderOptions() + .Min(1) + .Max(5) + .DefaultValue(1) + .Format("%dx") + .Tooltip( + "Cuccos will stay in place longer after putting them down, by a multiple of the value of the slider." + ) + ); + AddWidget(path, "Leever Spawn Rate: %d seconds", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("LeeverSpawnRate")) + .Options(IntSliderOptions() + .Min(0) + .Max(10) + .DefaultValue(0) + .Format("%d seconds") + .Tooltip( + "The time between groups of Leevers spawning." + ) + ); + + path.sidebarName = "Extra Modes"; + AddSidebarEntry("Enhancements", path.sidebarName, 2); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Mirrored World", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("MirroredWorldMode")) + .Callback([](WidgetInfo& info) { + if (gPlayState != NULL) { + UpdateMirrorModeState(gPlayState->sceneNum); + } + }) + .Options(ComboboxOptions() + .DefaultIndex(MIRRORED_WORLD_OFF) + .ComboMap(mirroredWorldModes) + .Tooltip( + "Mirrors the world horizontally\n\n" + " - Always: Always mirror the world\n" + " - Random: Randomly decide to mirror the world on each scene change\n" + " - Random (Seeded): Scenes are mirrored based on the current randomizer seed/file\n" + " - Dungeons: Mirror the world in Dungeons\n" + " - Dungeons (Vanilla): Mirror the world in vanilla Dungeons\n" + " - Dungeons (MQ): Mirror the world in MQ Dungeons\n" + " - Dungeons Random: Randomly decide to mirror the world in Dungeons\n" + " - Dungeons Random (Seeded): Dungeons are mirrored based on the current randomizer seed/file\n" + ) + ); + AddWidget(path, "Randomized Enemy Sizes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RandomizedEnemySizes")) + .Options(CheckboxOptions().Tooltip( + "Enemies and Bosses spawn with random sizes." + )); + AddWidget(path, "Scale Health with Size", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EnemySizeScalesHealth")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemySizes"), 0) == 0; + }) + .Options(CheckboxOptions().Tooltip( + "Scales normal enemies Health with their randomized size. *This will NOT affect bosses*" + )); + AddWidget(path, "Ivan the Fairy (Coop Mode)", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("IvanCoopModeEnabled")) + .Options(CheckboxOptions().Tooltip( + "Enables Ivan the Fairy upon the next map change. Player 2 can control Ivan and press the C-Buttons to " + "use items and mess with Player 1!" + )); + AddWidget(path, "Rupee Dash Mode", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RupeeDash")) + .Options(CheckboxOptions().Tooltip( + "Rupees reduce over time, Link suffers damage when the count hits 0." + )); + AddWidget(path, "Rupee Dash Interval %d seconds", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("RupeeDashInterval")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RupeeDash"), 0) == 0; + }) + .Options(IntSliderOptions() + .Min(1) + .Max(10) + .DefaultValue(5) + .Format("%d seconds") + .Tooltip( + "Interval between Rupee reduction in Rupee Dash Mode." + ) + ); + AddWidget(path, "Shadow Tag Mode", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ShadowTag")) + .Options(CheckboxOptions().Tooltip( + "A Wallmaster follows Link everywhere, don't get caught!" + )); + AddWidget(path, "Hurt Container Mode", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HurtContainer")) + .Callback([](WidgetInfo& info) { + UpdateHurtContainerModeState(CVarGetInteger(CVAR_ENHANCEMENT("HurtContainer"), 0)); + }) + .Options(CheckboxOptions().Tooltip( + "Changes Heart Piece and Heart Container functionality.\n\n" + " - Each Heart Container or full Heart Piece reduces Link's Hearts by 1.\n" + " - Can be enabled retroactively after a File has already started." + )); + AddWidget(path, "Additional Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Enabled")) + .Options(CheckboxOptions().Tooltip( + "Enables additional Trap variants." + )); + AddWidget(path, "Trap Options", WIDGET_SEPARATOR_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Tier 1 Traps:", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Freeze Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Ice")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Burn Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Burn")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Shock Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Shock")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Tier 2 Traps:", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Knockback Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Knockback")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Speed Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Speed")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Bomb Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Bomb")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Tier 3 Traps:", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Void Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Void")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Ammo Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Ammo")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Death Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Kill")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + AddWidget(path, "Teleport Traps", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.Teleport")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; + }); + path.column = SECTION_COLUMN_2; + AddWidget(path, "Enemy Randomizer", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("RandomizedEnemies")) + .Callback([](WidgetInfo& info) { + GetSelectedEnemies(); + }) + .Options(ComboboxOptions() + .DefaultIndex(ENEMY_RANDOMIZER_OFF) + .ComboMap(enemyRandomizerModes) + .Tooltip( + "Replaces fixed enemies throughout the game with a random enemy. Bosses, mini-bosses and a few specific regular enemies are excluded.\n" + "Enemies that need more than Deku Nuts + either Deku Sticks or a sword to kill are excluded from spawning in \"clear enemy\" rooms.\n\n" + "- Random: Enemies are randomized every time you load a room\n" + "- Random (Seeded): Enemies are randomized based on the current randomizer seed/file\n" + ) + ); + AddWidget(path, "Enemy List", WIDGET_SEPARATOR_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) != ENEMY_RANDOMIZER_RANDOM; + }); + AddWidget(path, "Select All Enemies", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RandomizedEnemyList.All")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) != ENEMY_RANDOMIZER_RANDOM; + }); + AddWidget(path, "Enemy List", WIDGET_SEPARATOR); + for (int i = 0; i < RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE; i++) { + AddWidget(path, enemyNameList[i], WIDGET_CVAR_CHECKBOX) + .CVar(enemyCVarList[i]) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) != ENEMY_RANDOMIZER_RANDOM; + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0) == 1; + info.options->disabledTooltip = "These options are disabled because \"Select All Enemies\" is enabled."; + }) + .Callback([](WidgetInfo& info) { + GetSelectedEnemies(); + }); + } + + // Cheats + path.sidebarName = "Cheats"; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Inventory", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Super Tunic", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("SuperTunic")) + .Options(CheckboxOptions().Tooltip( + "Makes every tunic have the effects of every other tunic." + )); + AddWidget(path, "Easy ISG", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("EasyISG")) + .Options(CheckboxOptions().Tooltip( + "Passive Infinite Sword Glitch\n" + "It makes your sword's swing effect and hitbox stay active indefinitely." + )); + AddWidget(path, "Easy QPA", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("EasyQPA")) + .Options(CheckboxOptions().Tooltip( + "Gives you the glitched damage value of the quick put away glitch." + )); + AddWidget(path, "Timeless Equipment", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("TimelessEquipment")) + .Options(CheckboxOptions().Tooltip( + "Allows any item to be equipped, regardless of age.\n" + "Also allows Child to use Adult strength upgrades." + )); + AddWidget(path, "Unrestricted Items", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("Unrestricted Items")) + .Options(CheckboxOptions().Tooltip( + "Allows you to use any item at any location" + )); + AddWidget(path, "Fireproof Deku Shield", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("FireproofDekuShield")) + .Options(CheckboxOptions().Tooltip( + "Prevents the Deku Shield from burning on contact with fire." + )); + AddWidget(path, "Shield with Two-Handed Weapons", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("ShieldTwoHanded")) + .Options(CheckboxOptions().Tooltip( + "This allows you to put up for shield with any two-handed weapon in hand except for Deku Sticks." + )); + AddWidget(path, "Deku Sticks:", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_CHEAT("DekuStick")) + .Options(ComboboxOptions().ComboMap( + dekuStickCheat + ).DefaultIndex(DEKU_STICK_NORMAL)); + AddWidget(path, "Bomb Timer Multiplier: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_CHEAT("BombTimerMultiplier")) + .Options(FloatSliderOptions() + .Format("%.2f") + .Min(0.1f) + .Max(5.0f) + .DefaultValue(1.0f)); + AddWidget(path, "Hookshot Everything", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("HookshotEverything")) + .Options(CheckboxOptions().Tooltip( + "Makes every surface in the game hookshot-able." + )); + AddWidget(path, "Hookshot Reach Multiplier: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_CHEAT("HookshotReachMultiplier")) + .Options(FloatSliderOptions() + .Format("%.2f") + .Min(1.0f) + .Max(5.0f)); + AddWidget(path, "Change Age", WIDGET_BUTTON) + .Options(ButtonOptions().Tooltip("Switches Link's age and reloads the area.")) + .Callback([](WidgetInfo& info){ + SwitchAge(); + }); + AddWidget(path, "Clear Cutscene Pointer", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + GameInteractor::RawAction::ClearCutscenePointer(); + }) + .Options(ButtonOptions().Tooltip( + "Clears the cutscene pointer to a value safe for wrong warps." + )); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Infinite...", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Money", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteMoney")); + AddWidget(path, "Health", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteHealth")); + AddWidget(path, "Ammo", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteAmmo")); + AddWidget(path, "Magic", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteMagic")); + AddWidget(path, "Nayru's Love", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteNayru")); + AddWidget(path, "Epona Boost", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteEponaBoost")); + + AddWidget(path, "Save States", WIDGET_SEPARATOR_TEXT); + AddWidget(path, ICON_FA_EXCLAMATION_TRIANGLE " WARNING!!!! " ICON_FA_EXCLAMATION_TRIANGLE, WIDGET_TEXT) + .Options(WidgetOptions().Color(Colors::Orange)); + AddWidget(path, + "These are NOT like emulator states. They do not save your game progress " + "and they WILL break across transitions and load zones (like doors). " + "Support for related issues will not be provided.", WIDGET_TEXT + ); + AddWidget(path, "I promise I have read the warning", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("SaveStatePromise")) + .Callback([](WidgetInfo& info) { + CVarSetInteger(CVAR_CHEAT("SaveStatesEnabled"), 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + AddWidget(path, "I understand, enable save states", WIDGET_CVAR_CHECKBOX) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_CHEAT("SaveStatePromise"), 0) == 0; + }) + .CVar(CVAR_CHEAT("SaveStatesEnabled")) + .Options(CheckboxOptions().Tooltip( + "F5 to save, F6 to change slots, F7 to load" + )); + + AddWidget(path, "Behavior", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "No Clip", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoClip")) + .Options(CheckboxOptions().Tooltip("Allows you to walk through walls.")); + AddWidget(path, "Climb Everything", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("ClimbEverything")) + .Options(CheckboxOptions().Tooltip("Makes every surface in the game climbable.")); + AddWidget(path, "Moon Jump on L", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("MoonJumpOnL")) + .Options(CheckboxOptions().Tooltip("Holding L makes you float into the air.")); + AddWidget(path, "New Easy Frame Advancing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("EasyFrameAdvance")) + .Options(CheckboxOptions().Tooltip( + "Continue holding START button when unpausing to only advance a single frame and then re-pause." + )); + AddWidget(path, "Drops Don't Despawn", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("DropsDontDie")) + .Options(CheckboxOptions().Tooltip( + "Drops from enemies, grass, etc. don't disappear after a set amount of time." + )); + AddWidget(path, "Fish Don't Despawn", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoFishDespawn")) + .Options(CheckboxOptions().Tooltip( + "Prevents fish from automatically despawning after a while when dropped." + )); + AddWidget(path, "Bugs Don't Despawn", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoBugsDespawn")) + .Options(CheckboxOptions().Tooltip( + "Prevents bugs from automatically despawning after a while when dropped." + )); + AddWidget(path, "Freeze Time", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("FreezeTime")) + .Options(CheckboxOptions().Tooltip("Freezes the time of day")); + AddWidget(path, "Time Sync", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("TimeSync")) + .Options(CheckboxOptions().Tooltip("Syncs the in-game time with the real world time.")); + AddWidget(path, "No ReDead/Gibdo Freeze", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoRedeadFreeze")) + .Options(CheckboxOptions().Tooltip( + "Prevents ReDeads and Gibdos from being able to freeze you with their scream." + )); + AddWidget(path, "Keese/Guay Don't Target You", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoKeeseGuayTarget")) + .Options(CheckboxOptions().Tooltip( + "Keese and Guay no longer target you and simply ignore you as if you were wearing the " + "Skull Mask." + )); + path.column = SECTION_COLUMN_3; + AddWidget(path, "Beta Quest", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Enable Beta Quest", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("EnableBetaQuest")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = !isBetaQuestEnabled; + }) + .Callback([](WidgetInfo& info) { + if (CVarGetInteger(CVAR_CHEAT("EnableBetaQuest"), 0) == 0) { + CVarClear(CVAR_CHEAT("BetaQuestWorld")); + } else { + CVarSetInteger(CVAR_CHEAT("BetaQuestWorld"), 0); + } + std::reinterpret_pointer_cast(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch("reset"); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }) + .Options(CheckboxOptions().Tooltip( + "Turns on OoT Beta Quest. *WARNING* This will reset your game." + )); + AddWidget(path, "Beta Quest World: %d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_CHEAT("BetaQuestWorld")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = info.isHidden = CVarGetInteger(CVAR_CHEAT("EnableBetaQuest"), 0) == 0; + }) + .Callback([](WidgetInfo& info) { + std::reinterpret_pointer_cast(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch("reset"); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }) + .Options(IntSliderOptions() + .DefaultValue(0) + .Min(0) + .Max(8) + .Tooltip( + "Set the Beta Quest world to explore. *WARNING* Changing this will reset your game.\n" + "Ctrl+Click to type in a value." + )); + + // Cosmetics Editor + path.sidebarName = "Cosmetics Editor"; + AddSidebarEntry("Enhancements", path.sidebarName, 1); + path.column = SECTION_COLUMN_1; + AddWidget(path, "Popout Cosmetics Editor Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("CosmeticsEditor")) + .WindowName("Cosmetics Editor") + .Options(WindowButtonOptions().Tooltip("Enables the separate Cosmetics Editor Window.")); + + + // Audio Editor + path.sidebarName = "Audio Editor"; + AddSidebarEntry("Enhancements", path.sidebarName, 1); + AddWidget(path, "Popout Audio Editor Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("AudioEditor")) + .WindowName("Audio Editor") + .Options(WindowButtonOptions().Tooltip("Enables the separate Audio Editor Window.")); + + + // Gameplay Stats + path.sidebarName = "Gameplay Stats"; + AddSidebarEntry("Enhancements", path.sidebarName, 2); + AddWidget(path, "Popout Gameplay Stats Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("GameplayStats")) + .WindowName("Gameplay Stats") + .Options(WindowButtonOptions().Tooltip("Enables the separate Gameplay Stats Window.")); + + + // Time Splits + path.sidebarName = "Time Splits"; + AddSidebarEntry("Enhancements", path.sidebarName, 1); + AddWidget(path, "Popout Time Splits Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("TimeSplits")) + .WindowName("Time Splits") + .Options(WindowButtonOptions().Tooltip("Enables the separate Time Splits Window.")); + + + // Timers + path.sidebarName = "Timers"; + AddSidebarEntry("Enhancements", path.sidebarName, 1); + AddWidget(path, "Toggle Timers Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("TimeDisplayEnabled")) + .WindowName("Additional Timers") + .Options(WindowButtonOptions().Tooltip("Enables the separate Additional Timers Window.")); + AddWidget(path, "Font Scale: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_ENHANCEMENT("TimeDisplay.FontScale")) + .Callback([](WidgetInfo& info) { + TimeDisplayInitSettings(); + }) + .Options(FloatSliderOptions() + .Min(1.0f) + .Max(5.0f) + .DefaultValue(1.0f) + .Format("%.2fx") + ); + AddWidget(path, "Hide Background", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeDisplay.ShowWindowBG")) + .Callback([](WidgetInfo& info) { + TimeDisplayInitSettings(); + }); + for (auto& timer : timeDisplayList) { + AddWidget(path, timer.timeLabel, WIDGET_CVAR_CHECKBOX) + .CVar(timer.timeEnable) + .Callback([](WidgetInfo& info) { + TimeDisplayUpdateDisplayOptions(); + }); + } +} + +} // namespace SohGui diff --git a/soh/soh/SohGui/SohMenuNetwork.cpp b/soh/soh/SohGui/SohMenuNetwork.cpp new file mode 100644 index 000000000..29511a9e9 --- /dev/null +++ b/soh/soh/SohGui/SohMenuNetwork.cpp @@ -0,0 +1,148 @@ +#ifdef ENABLE_REMOTE_CONTROL +#include "SohMenu.h" +#include +#include +#include "SohGui.hpp" +#include +#include + +namespace SohGui { + +extern std::shared_ptr mSohMenu; +using namespace UIWidgets; + +void SohMenu::AddMenuNetwork() { + // Add Network Menu + AddMenuEntry("Network", CVAR_SETTING("Menu.NetworkSidebarSection")); + + // Sail + WidgetPath path = { "Network", "Sail", SECTION_COLUMN_1 }; + AddSidebarEntry("Network", path.sidebarName, 3); + + AddWidget(path, "Sail is a networking protocol designed to facilitate remote " + "control of the Ship of Harkinian client. It is intended to " + "be utilized alongside a Sail server, for which we provide a " + "few straightforward implementations on our GitHub. The current " + "implementations available allow integration with Twitch chat " + "and SAMMI Bot, feel free to contribute your own!\n" + "\n" + "Click this button to copy the link to the Sail Github " + "page to your clipboard.", WIDGET_TEXT); + AddWidget(path, ICON_FA_CLIPBOARD "##Sail", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + ImGui::SetClipboardText("https://github.com/HarbourMasters/sail"); + Notification::Emit({ + .message = "Copied to clipboard", + }); + }) + .Options(ButtonOptions() + .Tooltip("https://github.com/HarbourMasters/sail") + ); + AddWidget(path, "Host & Port", WIDGET_CUSTOM) + .CustomFunction([](WidgetInfo& info) { + ImGui::BeginDisabled(Sail::Instance->isEnabled); + ImGui::Text("%s", info.name.c_str()); + CVarInputString("##HostSail", CVAR_REMOTE_SAIL("Host"), InputOptions().Color(THEME_COLOR).PlaceholderText("127.0.0.1").DefaultValue("127.0.0.1").Size(ImVec2(ImGui::GetFontSize() * 15, 0)).LabelPosition(LabelPosition::None)); + ImGui::SameLine(); + ImGui::Text(":"); + ImGui::SameLine(); + CVarInputInt("##PortSail", CVAR_REMOTE_SAIL("Port"), InputOptions().Color(THEME_COLOR).PlaceholderText("43384").DefaultValue("43384").Size(ImVec2(ImGui::GetFontSize() * 5, 0)).LabelPosition(LabelPosition::None)); + ImGui::EndDisabled(); + }); + AddWidget(path, "Enable##Sail", WIDGET_BUTTON) + .PreFunc([](WidgetInfo& info) { + std::string host = CVarGetString(CVAR_REMOTE_SAIL("Host"), "127.0.0.1"); + uint16_t port = CVarGetInteger(CVAR_REMOTE_SAIL("Port"), 43384); + info.options->disabled = !(!SohUtils::IsStringEmpty(host) && port > 1024 && port < 65535); + if (Sail::Instance->isEnabled) { + info.name = "Disable##Sail"; + } else { + info.name = "Enable##Sail"; + } + }) + .Callback([](WidgetInfo& info) { + if (Sail::Instance->isEnabled) { + CVarClear(CVAR_REMOTE_SAIL("Enabled")); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + Sail::Instance->Disable(); + } else { + CVarSetInteger(CVAR_REMOTE_SAIL("Enabled"), 1); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + Sail::Instance->Enable(); + } + }); + AddWidget(path, "Connecting...##Sail", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = !Sail::Instance->isEnabled; + if (Sail::Instance->isConnected) { + info.name = "Connected##Sail"; + } else { + info.name = "Connecting...##Sail"; + } + }); + + path.sidebarName = "Crowd Control"; + AddSidebarEntry("Network", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Crowd Control is a platform that allows viewers to interact " + "with a streamer's game in real time.\n" + "\n" + "Click the question mark to copy the link to the Crowd Control " + "website to your clipboard.", WIDGET_TEXT); + AddWidget(path, ICON_FA_CLIPBOARD "##CrowdControl", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + ImGui::SetClipboardText("https://crowdcontrol.live"); + Notification::Emit({ + .message = "Copied to clipboard", + }); + }) + .Options(ButtonOptions() + .Tooltip("https://crowdcontrol.live") + ); + AddWidget(path, "Host & Port", WIDGET_CUSTOM) + .CustomFunction([](WidgetInfo& info) { + ImGui::BeginDisabled(CrowdControl::Instance->isEnabled); + ImGui::Text("%s", info.name.c_str()); + CVarInputString("##HostCrowdControl", CVAR_REMOTE_CROWD_CONTROL("Host"), InputOptions().Color(THEME_COLOR).PlaceholderText("127.0.0.1").DefaultValue("127.0.0.1").Size(ImVec2(ImGui::GetFontSize() * 15, 0)).LabelPosition(LabelPosition::None)); + ImGui::SameLine(); + ImGui::Text(":"); + ImGui::SameLine(); + CVarInputInt("##PortCrowdControl", CVAR_REMOTE_CROWD_CONTROL("Port"), InputOptions().Color(THEME_COLOR).PlaceholderText("43384").DefaultValue("43384").Size(ImVec2(ImGui::GetFontSize() * 5, 0)).LabelPosition(LabelPosition::None)); + ImGui::EndDisabled(); + }); + AddWidget(path, "Enable##CrowdControl", WIDGET_BUTTON) + .PreFunc([](WidgetInfo& info) { + std::string host = CVarGetString(CVAR_REMOTE_CROWD_CONTROL("Host"), "127.0.0.1"); + uint16_t port = CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Port"), 43384); + info.options->disabled = !(!SohUtils::IsStringEmpty(host) && port > 1024 && port < 65535); + if (CrowdControl::Instance->isEnabled) { + info.name = "Disable##CrowdControl"; + } else { + info.name = "Enable##CrowdControl"; + } + }) + .Callback([](WidgetInfo& info) { + if (CrowdControl::Instance->isEnabled) { + CVarClear(CVAR_REMOTE_CROWD_CONTROL("Enabled")); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + CrowdControl::Instance->Disable(); + } else { + CVarSetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 1); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + CrowdControl::Instance->Enable(); + } + }); + AddWidget(path, "Connecting...##CrowdControl", WIDGET_TEXT) + .PreFunc([](WidgetInfo& info) { + info.isHidden = !CrowdControl::Instance->isEnabled; + if (CrowdControl::Instance->isConnected) { + info.name = "Connected##CrowdControl"; + } else { + info.name = "Connecting...##CrowdControl"; + } + }); +} + +} // namespace SohGui +#endif \ No newline at end of file diff --git a/soh/soh/SohGui/SohMenuRandomizer.cpp b/soh/soh/SohGui/SohMenuRandomizer.cpp new file mode 100644 index 000000000..e6399baab --- /dev/null +++ b/soh/soh/SohGui/SohMenuRandomizer.cpp @@ -0,0 +1,133 @@ +#include "SohMenu.h" +#include + +namespace SohGui { + +extern std::shared_ptr mSohMenu; +using namespace UIWidgets; + +void SohMenu::AddMenuRandomizer() { + // Add Randomizer Menu + AddMenuEntry("Randomizer", CVAR_SETTING("Menu.RandomizerSidebarSection")); + + // Seed Settings + WidgetPath path = { "Randomizer", "Seed Settings", SECTION_COLUMN_1 }; + AddSidebarEntry("Randomizer", path.sidebarName, 1); + AddWidget(path, "Popout Randomizer Settings Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("RandomizerSettings")) + .WindowName("Randomizer Settings") + .Options(WindowButtonOptions().Tooltip("Enables the separate Randomizer Settings Window.")); + path.sidebarName = "Enhancements"; + AddSidebarEntry("Randomizer", path.sidebarName, 1); + AddWidget(path, "Rando-Relevant Navi Hints", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("RandoRelevantNavi")) + .Options(CheckboxOptions().Tooltip( + "Replace Navi's overworld quest hints with rando-related gameplay hints." + ).DefaultValue(true)); + AddWidget(path, "Random Rupee Names", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("RandomizeRupeeNames")) + .Options(CheckboxOptions().Tooltip( + "When obtaining Rupees, randomize what the Rupee is called in the textbox." + ).DefaultValue(true)); + AddWidget(path, "Use Custom Key Models", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("CustomKeyModels")) + .Options(CheckboxOptions().Tooltip( + "Use Custom graphics for Dungeon Keys, Big and Small, so that they can be easily told apart." + ).DefaultValue(true)); + AddWidget(path, "Compass Colors Match Dungeon", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("MatchCompassColors")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = !( + OTRGlobals::Instance->gRandoContext->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).IsNot(RO_DUNGEON_ITEM_LOC_STARTWITH) && + OTRGlobals::Instance->gRandoContext->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).IsNot(RO_DUNGEON_ITEM_LOC_VANILLA) && + OTRGlobals::Instance->gRandoContext->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).IsNot(RO_DUNGEON_ITEM_LOC_OWN_DUNGEON) + ); + info.options->disabledTooltip = "This setting is disabled because a savefile is loaded without " + "the compass shuffle settings set to Any Dungeon, Overworld, or Anywhere."; + }) + .Options(CheckboxOptions().Tooltip( + "Matches the color of compasses to the dungeon they belong to. " + "This helps identify compasses from afar and adds a little bit of flair.\n\nThis only " + "applies to seeds with compasses shuffled to \"Any Dungeon\", \"Overworld\", or \"Anywhere\"." + ).DefaultValue(true)); + AddWidget(path, "Quest Item Fanfares", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("QuestItemFanfares")) + .Options(CheckboxOptions().Tooltip( + "Play unique fanfares when obtaining quest items (medallions/stones/songs). Note that these " + "fanfares can be longer than usual." + )); + AddWidget(path, "Mysterious Shuffled Items", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle")) + .Options(CheckboxOptions().Tooltip( + "Displays a \"Mystery Item\" model in place of any freestanding/GS/shop items that were shuffled, " + "and replaces item names for them and scrubs and merchants, regardless of hint settings, " + "so you never know what you're getting." + )); + AddWidget(path, "Simpler Boss Soul Models", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("SimplerBossSoulModels")) + .Options(CheckboxOptions().Tooltip( + "When shuffling boss souls, they'll appear as a simpler model instead of showing the boss' models." + "This might make boss souls more distinguishable from a distance, and can help with performance." + )); + AddWidget(path, "Skip Get Item Animations", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation")) + .Options(ComboboxOptions().ComboMap(skipGetItemAnimationOptions).DefaultIndex(SGIA_JUNK)); + + // Plandomizer + path.sidebarName = "Plandomizer"; + AddSidebarEntry("Randomizer", path.sidebarName, 1); + AddWidget(path, "Popout Plandomizer Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("PlandomizerEditor")) + .WindowName("Plandomizer Editor") + .Options(WindowButtonOptions().Tooltip("Enables the separate Randomizer Settings Window.")); + + // Item Tracker + path.sidebarName = "Item Tracker"; + AddSidebarEntry("Randomizer", path.sidebarName, 1); + + AddWidget(path, "Item Tracker", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Toggle Item Tracker", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("ItemTracker")) + .WindowName("Item Tracker") + .Options(WindowButtonOptions().Tooltip("Toggles the Item Tracker.").EmbedWindow(false)); + + AddWidget(path, "Item Tracker Settings", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Popout Item Tracker Settings", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("ItemTrackerSettings")) + .WindowName("Item Tracker Settings") + .Options(WindowButtonOptions().Tooltip("Enables the separate Item Tracker Settings Window.")); + + // Entrance Tracker + path.sidebarName = "Entrance Tracker"; + AddSidebarEntry("Randomizer", path.sidebarName, 1); + + AddWidget(path, "Entrance Tracker", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Toggle Entrance Tracker", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("EntranceTracker")) + .WindowName("Entrance Tracker") + .Options(WindowButtonOptions().Tooltip("Toggles the Entrance Tracker.").EmbedWindow(false)); + + AddWidget(path, "Entrance Tracker Settings", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Popout Entrance Tracker Settings", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("EntranceTrackerSettings")) + .WindowName("Entrance Tracker Settings") + .Options(WindowButtonOptions().Tooltip("Enables the separate Entrance Tracker Settings Window.")); + + // Check Tracker + path.sidebarName = "Check Tracker"; + AddSidebarEntry("Randomizer", path.sidebarName, 1); + + AddWidget(path, "Check Tracker", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Toggle Check Tracker", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("CheckTracker")) + .WindowName("Check Tracker") + .Options(WindowButtonOptions().Tooltip("Toggles the Check Tracker.").EmbedWindow(false)); + + AddWidget(path, "Check Tracker Settings", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Popout Check Tracker Settings", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("CheckTrackerSettings")) + .WindowName("Check Tracker Settings") + .Options(WindowButtonOptions().Tooltip("Enables the separate Check Tracker Settings Window.")); +} + +} // namespace SohGui diff --git a/soh/soh/SohGui/SohMenuSettings.cpp b/soh/soh/SohGui/SohMenuSettings.cpp new file mode 100644 index 000000000..a9103377b --- /dev/null +++ b/soh/soh/SohGui/SohMenuSettings.cpp @@ -0,0 +1,373 @@ +#include "SohMenu.h" +#include "soh/Notification/Notification.h" +#include +#include "soh/ResourceManagerHelpers.h" +#include "UIWidgets.hpp" +#include + +extern "C" { +#include "include/z64audio.h" +#include "variables.h" +} + +namespace SohGui { + +extern std::shared_ptr mSohMenu; +using namespace UIWidgets; +static std::unordered_map languages = {{ LANGUAGE_ENG, "English" }, { LANGUAGE_GER, "German" }, { LANGUAGE_FRA, "French" }}; + +const char* GetGameVersionString(uint32_t index) { + uint32_t gameVersion = ResourceMgr_GetGameVersion(index); + switch (gameVersion) { + case OOT_NTSC_US_10: + return "NTSC-U 1.0"; + case OOT_NTSC_US_11: + return "NTSC-U 1.1"; + case OOT_NTSC_US_12: + return "NTSC-U 1.2"; + case OOT_PAL_10: + return "PAL 1.0"; + case OOT_PAL_11: + return "PAL 1.1"; + case OOT_PAL_GC: + return "PAL GC"; + case OOT_PAL_MQ: + return "PAL MQ"; + case OOT_PAL_GC_DBG1: + case OOT_PAL_GC_DBG2: + return "PAL GC-D"; + case OOT_PAL_GC_MQ_DBG: + return "PAL MQ-D"; + case OOT_IQUE_CN: + return "IQUE CN"; + case OOT_IQUE_TW: + return "IQUE TW"; + default: + return "UNKNOWN"; + } +} + +void SohMenu::AddMenuSettings() { + // Add Settings Menu + AddMenuEntry("Settings", CVAR_SETTING("Menu.SettingsSidebarSection")); + AddSidebarEntry("Settings", "General", 3); + WidgetPath path = { "Settings", "General", SECTION_COLUMN_1 }; + + // General - Settings + AddWidget(path, "Menu Theme", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_SETTING("Menu.Theme")) + .Options(ComboboxOptions() + .Tooltip("Changes the Theme of the Menu Widgets.") + .ComboMap(menuThemeOptions) + .DefaultIndex(Colors::LightBlue)); +#if not defined(__SWITCH__) and not defined(__WIIU__) + AddWidget(path, "Menu Controller Navigation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_IMGUI_CONTROLLER_NAV) + .Options(CheckboxOptions().Tooltip( + "Allows controller navigation of the 2Ship menu (Settings, Enhancements,...)\nCAUTION: " + "This will disable game inputs while the menu is visible.\n\nD-pad to move between " + "items, A to select, B to move up in scope.")); + AddWidget(path, "Cursor Always Visible", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("CursorVisibility")) + .Callback([](WidgetInfo& info) { + Ship::Context::GetInstance()->GetWindow()->SetForceCursorVisibility( + CVarGetInteger(CVAR_SETTING("CursorVisibility"), 0)); + }) + .Options(CheckboxOptions().Tooltip("Makes the cursor always visible, even in full screen.")); +#endif + AddWidget(path, "Search In Sidebar", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("Menu.SidebarSearch")) + .Callback([](WidgetInfo& info) { + if (CVarGetInteger(CVAR_SETTING("Menu.SidebarSearch"), 0)) { + mSohMenu->InsertSidebarSearch(); + } else { + mSohMenu->RemoveSidebarSearch(); + } + }) + .Options(CheckboxOptions().Tooltip( + "Displays the Search menu as a sidebar entry in Settings instead of in the header.")); + AddWidget(path, "Search Input Autofocus", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("Menu.SearchAutofocus")) + .Options(CheckboxOptions().Tooltip( + "Search input box gets autofocus when visible. Does not affect using other widgets.")); + AddWidget(path, "Alt Assets Tab hotkey", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("Mods.AlternateAssetsHotkey")) + .Options( + CheckboxOptions().Tooltip("Allows pressing the Tab key to toggle alternate assets").DefaultValue(true)); + AddWidget(path, "Open App Files Folder", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + std::string filesPath = Ship::Context::GetInstance()->GetAppDirectoryPath(); + SDL_OpenURL(std::string("file:///" + std::filesystem::absolute(filesPath).string()).c_str()); + }) + .Options(ButtonOptions().Tooltip("Opens the folder that contains the save and mods folders, etc.")); + AddWidget(path, "Languages", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Translate Title Screen", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("TitleScreenTranslation")); + AddWidget(path, "Menu Language", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_SETTING("Languages")) + .Options(ComboboxOptions().LabelPosition(LabelPosition::Far).ComponentAlignment(ComponentAlignment::Right).ComboMap(languages).DefaultIndex(LANGUAGE_ENG)); + AddWidget(path, "Accessibility", WIDGET_SEPARATOR_TEXT); + #if defined(_WIN32) || defined(__APPLE__) + AddWidget(path, "Text to Speech", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("A11yTTS")) + .Options(CheckboxOptions().Tooltip("Enables text to speech for in game dialog")); + #endif + AddWidget(path, "Disable Idle Camera Re-Centering", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("A11yDisableIdleCam")) + .Options(CheckboxOptions().Tooltip("Disables the automatic re-centering of the camera when idle.")); + + // General - About + path.column = SECTION_COLUMN_2; + + AddWidget(path, "About", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Ship Of Harkinian", WIDGET_TEXT); + if (gGitCommitTag[0] != 0) { + AddWidget(path, gBuildVersion, WIDGET_TEXT); + } else { + AddWidget(path, ("Branch: " + std::string(gGitBranch)), WIDGET_TEXT); + AddWidget(path, ("Commit: " + std::string(gGitCommitHash)), WIDGET_TEXT); + } + for (uint32_t i = 0; i < ResourceMgr_GetNumGameVersions(); i++) { + AddWidget(path, GetGameVersionString(i), WIDGET_TEXT); + } + + // Audio Settings + path.sidebarName = "Audio"; + path.column = SECTION_COLUMN_1; + AddSidebarEntry("Settings", "Audio", 3); + + AddWidget(path, "Master Volume: %d %%", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SETTING("Volume.Master")) + .Options(IntSliderOptions() + .Min(0) + .Max(100) + .DefaultValue(40) + .ShowButtons(true) + .Format("")); + AddWidget(path, "Main Music Volume: %d %%", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SETTING("Volume.MainMusic")) + .Options(IntSliderOptions() + .Min(0) + .Max(100) + .DefaultValue(100) + .ShowButtons(true) + .Format("")) + .Callback([](WidgetInfo& info) { + Audio_SetGameVolume(SEQ_PLAYER_BGM_MAIN, ((float)CVarGetInteger(CVAR_SETTING("Volume.MainMusic"), 100) / 100.0f)); + }); + AddWidget(path, "Sub Music Volume: %d %%", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SETTING("Volume.SubMusic")) + .Options(IntSliderOptions() + .Min(0) + .Max(100) + .DefaultValue(100) + .ShowButtons(true) + .Format("")) + .Callback([](WidgetInfo& info) { + Audio_SetGameVolume(SEQ_PLAYER_BGM_SUB, ((float)CVarGetInteger(CVAR_SETTING("Volume.SubMusic"), 100) / 100.0f)); + }); + AddWidget(path, "Fanfare Volume: %d %%", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SETTING("Volume.Fanfare")) + .Options(IntSliderOptions() + .Min(0) + .Max(100) + .DefaultValue(100) + .ShowButtons(true) + .Format("")) + .Callback([](WidgetInfo& info) { + Audio_SetGameVolume(SEQ_PLAYER_FANFARE, ((float)CVarGetInteger(CVAR_SETTING("Volume.Fanfare"), 100) / 100.0f)); + }); + AddWidget(path, "Sound Effects Volume: %d %%", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SETTING("Volume.SFX")) + .Options(IntSliderOptions() + .Min(0) + .Max(100) + .DefaultValue(100) + .ShowButtons(true) + .Format("")) + .Callback([](WidgetInfo& info) { + Audio_SetGameVolume(SEQ_PLAYER_SFX, ((float)CVarGetInteger(CVAR_SETTING("Volume.SFX"), 100) / 100.0f)); + }); + AddWidget(path, "Audio API (Needs reload)", WIDGET_AUDIO_BACKEND); + + // Graphics Settings + static int32_t maxFps; + const char* tooltip = ""; + if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { + maxFps = 360; + tooltip = "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. This is " + "purely visual and does not impact game logic, execution of glitches etc.\n\nA higher target " + "FPS than your monitor's refresh rate will waste resources, and might give a worse result."; + } else { + maxFps = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); + tooltip = "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. This is " + "purely visual and does not impact game logic, execution of glitches etc."; + } + path.sidebarName = "Graphics"; + AddSidebarEntry("Settings", "Graphics", 3); + AddWidget(path, "Toggle Fullscreen", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SETTING("Fullscreen")) + .Callback([](WidgetInfo& info) { Ship::Context::GetInstance()->GetWindow()->ToggleFullscreen(); }) + .Options(CheckboxOptions().Tooltip("Toggles Fullscreen On/Off.")); + AddWidget(path, "Internal Resolution", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_INTERNAL_RESOLUTION) + .Callback([](WidgetInfo& info) { + Ship::Context::GetInstance()->GetWindow()->SetResolutionMultiplier( + CVarGetFloat(CVAR_INTERNAL_RESOLUTION, 1)); + }) + .PreFunc([](WidgetInfo& info) { + if (mSohMenu->disabledMap.at(DISABLE_FOR_ADVANCED_RESOLUTION_ON).active && + mSohMenu->disabledMap.at(DISABLE_FOR_VERTICAL_RES_TOGGLE_ON).active) { + info.activeDisables.push_back(DISABLE_FOR_ADVANCED_RESOLUTION_ON); + info.activeDisables.push_back(DISABLE_FOR_VERTICAL_RES_TOGGLE_ON); + } else if (mSohMenu->disabledMap.at(DISABLE_FOR_LOW_RES_MODE_ON).active) { + info.activeDisables.push_back(DISABLE_FOR_LOW_RES_MODE_ON); + } + }) + .Options( + FloatSliderOptions() + .Tooltip("Multiplies your output resolution by the value inputted, as a more intensive but effective " + "form of anti-aliasing.") + .ShowButtons(false) + .IsPercentage() + .Min(0.5f) + .Max(2.0f)); +#ifndef __WIIU__ + AddWidget(path, "Anti-aliasing (MSAA)", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_MSAA_VALUE) + .Callback([](WidgetInfo& info) { + Ship::Context::GetInstance()->GetWindow()->SetMsaaLevel(CVarGetInteger(CVAR_MSAA_VALUE, 1)); + }) + .Options( + IntSliderOptions() + .Tooltip("Activates MSAA (multi-sample anti-aliasing) from 2x up to 8x, to smooth the edges of " + "rendered geometry.\n" + "Higher sample count will result in smoother edges on models, but may reduce performance.") + .Min(1) + .Max(8) + .DefaultValue(1)); +#endif + auto fps = CVarGetInteger(CVAR_SETTING("InterpolationFPS"), 20); + const char* fpsFormat = fps == 20 ? "Original (%d)" : "%d"; + AddWidget(path, "Current FPS", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_SETTING("InterpolationFPS")) + .Callback([](WidgetInfo& info) { + auto options = std::static_pointer_cast(info.options); + int32_t defaultValue = options->defaultValue; + if (CVarGetInteger(info.cVar, defaultValue) == defaultValue) { + options->format = "Original (%d)"; + } + else { + options->format = "%d"; + } + }) + .PreFunc([](WidgetInfo& info) { + if (mSohMenu->disabledMap.at(DISABLE_FOR_MATCH_REFRESH_RATE_ON).active) + info.activeDisables.push_back(DISABLE_FOR_MATCH_REFRESH_RATE_ON); + }) + .Options(IntSliderOptions().Tooltip(tooltip).Min(20).Max(maxFps).DefaultValue(20).Format(fpsFormat)); + AddWidget(path, "Match Refresh Rate", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + int hz = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); + if (hz >= 20 && hz <= 360) { + CVarSetInteger(CVAR_SETTING("InterpolationFPS"), hz); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + }) + .PreFunc([](WidgetInfo& info) { info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_NOT_DIRECTX).active; }) + .Options(ButtonOptions().Tooltip("Matches interpolation value to the current game's window refresh rate.")); + AddWidget(path, "Match Refresh Rate", WIDGET_CVAR_CHECKBOX) + .CVar("gMatchRefreshRate") + .PreFunc([](WidgetInfo& info) { info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_DIRECTX).active; }) + .Options(CheckboxOptions().Tooltip("Matches interpolation value to the current game's window refresh rate.")); + AddWidget(path, "Renderer API (Needs reload)", WIDGET_VIDEO_BACKEND); + AddWidget(path, "Enable Vsync", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_VSYNC_ENABLED) + .PreFunc([](WidgetInfo& info) { info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_NO_VSYNC).active; }) + .Options(CheckboxOptions().Tooltip("Enables Vsync.")); + AddWidget(path, "Windowed Fullscreen", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_SDL_WINDOWED_FULLSCREEN) + .PreFunc([](WidgetInfo& info) { + info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_NO_WINDOWED_FULLSCREEN).active; + }) + .Options(CheckboxOptions().Tooltip("Enables Windowed Fullscreen Mode.")); + AddWidget(path, "Allow multi-windows", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENABLE_MULTI_VIEWPORTS) + .PreFunc( + [](WidgetInfo& info) { info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_NO_MULTI_VIEWPORT).active; }) + .Options(CheckboxOptions().Tooltip( + "Allows multiple windows to be opened at once. Requires a reload to take effect.")); + AddWidget(path, "Texture Filter (Needs reload)", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_TEXTURE_FILTER) + .Options(ComboboxOptions().Tooltip("Sets the applied Texture Filtering.").ComboMap(textureFilteringMap)); + + + // Controls + path.sidebarName = "Controls"; + AddSidebarEntry("Settings", "Controls", 2); + AddWidget(path, "Controller Bindings", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Popout Bindings Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("ControllerConfiguration")) + .WindowName("Configure Controller") + .Options(WindowButtonOptions().Tooltip("Enables the separate Bindings Window.")); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Input Viewer", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Toggle Input Viewer", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("InputViewer")) + .WindowName("Input Viewer") + .Options(WindowButtonOptions().Tooltip("Toggles the Input Viewer.").EmbedWindow(false)); + + AddWidget(path, "Input Viewer Settings", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Popout Input Viewer Settings", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("InputViewerSettings")) + .WindowName("Input Viewer Settings") + .Options(WindowButtonOptions().Tooltip("Enables the separate Input Viewer Settings Window.")); + + // Notifications + path.sidebarName = "Notifications"; + path.column = SECTION_COLUMN_1; + AddSidebarEntry("Settings", "Notifications", 3); + AddWidget(path, "Position", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_SETTING("Notifications.Position")) + .Options(ComboboxOptions() + .Tooltip("Which corner of the screen notifications appear in.") + .ComboMap(notificationPosition) + .DefaultIndex(3)); + AddWidget(path, "Duration: %.0f seconds", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_SETTING("Notifications.Duration")) + .Options(FloatSliderOptions() + .Tooltip("How long notifications are displayed for.") + .Format("%.1f") + .Step(0.1f) + .Min(3.0f) + .Max(30.0f) + .DefaultValue(10.0f)); + AddWidget(path, "Background Opacity: %.0f%%", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_SETTING("Notifications.BgOpacity")) + .Options(FloatSliderOptions() + .Tooltip("How opaque the background of notifications is.") + .DefaultValue(0.5f) + .IsPercentage()); + AddWidget(path, "Size %.1f", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_SETTING("Notifications.Size")) + .Options(FloatSliderOptions() + .Tooltip("How large notifications are.") + .Format("%.1f") + .Step(0.1f) + .Min(1.0f) + .Max(5.0f) + .DefaultValue(1.8f)); + AddWidget(path, "Test Notification", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { + Notification::Emit({ + .itemIcon = "__OTR__textures/icon_item_24_static/gQuestIconGoldSkulltulaTex", + .prefix = "This", + .message = "is a", + .suffix = "test.", + }); + }) + .Options(ButtonOptions().Tooltip("Displays a test notification.")); +} + +} // namespace SohGui diff --git a/soh/soh/SohGui/SohModals.cpp b/soh/soh/SohGui/SohModals.cpp index 642fedf15..c810d52e6 100644 --- a/soh/soh/SohGui/SohModals.cpp +++ b/soh/soh/SohGui/SohModals.cpp @@ -5,6 +5,7 @@ #include #include #include "UIWidgets.hpp" +#include "SohGui.hpp" #include "soh/OTRGlobals.h" #include "z64.h" @@ -29,6 +30,7 @@ void SohModalWindow::Draw() { } void SohModalWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); if (modals.size() > 0) { SohModal curModal = modals.at(0); if (!ImGui::IsPopupOpen(curModal.title_.c_str())) { @@ -36,6 +38,7 @@ void SohModalWindow::DrawElement() { } if (ImGui::BeginPopupModal(curModal.title_.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings)) { ImGui::Text("%s", curModal.message_.c_str()); + UIWidgets::PushStyleButton(THEME_COLOR); if (ImGui::Button(curModal.button1_.c_str())) { if (curModal.button1callback_ != nullptr) { curModal.button1callback_(); @@ -43,8 +46,10 @@ void SohModalWindow::DrawElement() { ImGui::CloseCurrentPopup(); modals.erase(modals.begin()); } - ImGui::SameLine(); + UIWidgets::PopStyleButton(); if (curModal.button2_ != "") { + ImGui::SameLine(); + UIWidgets::PushStyleButton(THEME_COLOR); if (ImGui::Button(curModal.button2_.c_str())) { if (curModal.button2callback_ != nullptr) { curModal.button2callback_(); @@ -52,10 +57,12 @@ void SohModalWindow::DrawElement() { ImGui::CloseCurrentPopup(); modals.erase(modals.begin()); } + UIWidgets::PopStyleButton(); } } ImGui::EndPopup(); } + ImGui::PopFont(); } void SohModalWindow::RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function button1callback, std::function button2callback) { diff --git a/soh/soh/SohGui/UIWidgets.cpp b/soh/soh/SohGui/UIWidgets.cpp index 6d9ca7a65..7fbad6705 100644 --- a/soh/soh/SohGui/UIWidgets.cpp +++ b/soh/soh/SohGui/UIWidgets.cpp @@ -1,836 +1,1139 @@ -// -// UIWidgets.cpp -// soh -// -// Created by David Chavez on 25.08.22. -// - #include "UIWidgets.hpp" - -#include +#define IMGUI_DEFINE_MATH_OPERATORS #include +#include #include - +#include +#include +#include +#include #include -#include "soh/Enhancements/cosmetics/CosmeticsEditor.h" +#include namespace UIWidgets { - // MARK: - Layout Helper - - // Automatically adds newlines to break up text longer than a specified number of characters - // Manually included newlines will still be respected and reset the line length - // If line is midword when it hits the limit, text should break at the last encountered space - std::string WrappedText(const char* text, unsigned int charactersPerLine) { - std::string newText(text); - const size_t tipLength = newText.length(); - int lastSpace = -1; - int currentLineLength = 0; - for (unsigned int currentCharacter = 0; currentCharacter < tipLength; currentCharacter++) { - if (newText[currentCharacter] == '\n') { - currentLineLength = 0; - lastSpace = -1; - continue; - } else if (newText[currentCharacter] == ' ') { - lastSpace = currentCharacter; - } - - if ((currentLineLength >= charactersPerLine) && (lastSpace >= 0)) { - newText[lastSpace] = '\n'; - currentLineLength = currentCharacter - lastSpace - 1; - lastSpace = -1; - } - currentLineLength++; +// Automatically adds newlines to break up text longer than a specified number of characters +// Manually included newlines will still be respected and reset the line length +// If line is midword when it hits the limit, text should break at the last encountered space +std::string WrappedText(const char* text, unsigned int charactersPerLine) { + std::string newText(text); + const size_t tipLength = newText.length(); + int lastSpace = -1; + int currentLineLength = 0; + for (unsigned int currentCharacter = 0; currentCharacter < tipLength; currentCharacter++) { + if (newText[currentCharacter] == '\n') { + currentLineLength = 0; + lastSpace = -1; + continue; + } else if (newText[currentCharacter] == ' ') { + lastSpace = currentCharacter; } - return newText; + if ((currentLineLength >= charactersPerLine) && (lastSpace >= 0)) { + newText[lastSpace] = '\n'; + currentLineLength = currentCharacter - lastSpace - 1; + lastSpace = -1; + } + currentLineLength++; } - std::string WrappedText(const std::string& text, unsigned int charactersPerLine) { - return WrappedText(text.c_str(), charactersPerLine); + return newText; +} + +std::string WrappedText(const std::string& text, unsigned int charactersPerLine) { + return WrappedText(text.c_str(), charactersPerLine); +} + +void PaddedSeparator(bool padTop, bool padBottom, float extraVerticalTopPadding, float extraVerticalBottomPadding) { + if (padTop) { + Spacer(extraVerticalTopPadding); } - - void SetLastItemHoverText(const std::string& text) { - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::Text("%s", WrappedText(text, 60).c_str()); - ImGui::EndTooltip(); - } - } - - void SetLastItemHoverText(const char* text) { - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::Text("%s", WrappedText(text, 60).c_str()); - ImGui::EndTooltip(); - } - } - - // Adds a "?" next to the previous ImGui item with a custom tooltip - void InsertHelpHoverText(const std::string& text) { - ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::Text("%s", WrappedText(text, 60).c_str()); - ImGui::EndTooltip(); - } - } - - void InsertHelpHoverText(const char* text) { - ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::Text("%s", WrappedText(text, 60).c_str()); - ImGui::EndTooltip(); - } - } - - - // MARK: - UI Elements - - void Tooltip(const char* text) { - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s", WrappedText(text).c_str()); - } - } - - void Spacer(float height) { - ImGui::Dummy(ImVec2(0.0f, height)); - } - - void PaddedSeparator(bool padTop, bool padBottom, float extraVerticalTopPadding, float extraVerticalBottomPadding) { - if (padTop) { - Spacer(extraVerticalTopPadding); - } - ImGui::Separator(); - if (padBottom) { - Spacer(extraVerticalBottomPadding); - } - } - - void RenderCross(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz) { - float thickness = ImMax(sz / 5.0f, 1.0f); - sz -= thickness * 0.5f; - pos += ImVec2(thickness * 0.25f, thickness * 0.25f); - - draw_list->PathLineTo(ImVec2(pos.x, pos.y)); - draw_list->PathLineTo(ImVec2(pos.x + sz, pos.y + sz)); - draw_list->PathStroke(col, 0, thickness); - - draw_list->PathLineTo(ImVec2(pos.x + sz, pos.y)); - draw_list->PathLineTo(ImVec2(pos.x, pos.y + sz)); - draw_list->PathStroke(col, 0, thickness); - } - - bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic, bool renderCrossWhenOff) { - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (window->SkipItems) { - return false; - } - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); - - const float square_sz = ImGui::GetFrameHeight(); - const ImVec2 pos = window->DC.CursorPos; - const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); - ImGui::ItemSize(total_bb, style.FramePadding.y); - if (!ImGui::ItemAdd(total_bb, id)) { - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); - return false; - } - - bool hovered, held; - bool pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held); - if (pressed) { - *v = !(*v); - ImGui::MarkItemEdited(id); - } - - const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); - ImGui::RenderNavHighlight(total_bb, id); - ImGui::RenderFrame(check_bb.Min, check_bb.Max, ImGui::GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); - ImU32 check_col = ImGui::GetColorU32(ImGuiCol_CheckMark); - ImU32 cross_col = ImGui::GetColorU32(ImVec4(0.50f, 0.50f, 0.50f, 1.00f)); - bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; - if (mixed_value) { - // Undocumented tristate/mixed/indeterminate checkbox (#2644) - // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox) - ImVec2 pad(ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)), ImMax(1.0f, IM_FLOOR(square_sz / 3.6f))); - window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); - } else if ((!disabled && *v) || (disabled && disabledGraphic == CheckboxGraphics::Checkmark)) { - const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); - ImGui::RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); - } else if ((!disabled && !*v && renderCrossWhenOff) || (disabled && disabledGraphic == CheckboxGraphics::Cross)) { - const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); - //RenderCross(window->DrawList, check_bb.Min + ImVec2(pad, pad), disabled ? cross_col : check_col, square_sz - pad * 2.0f); // Caused confusion as to status - } - - ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); - if (g.LogEnabled) { - ImGui::LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]"); - } - if (label_size.x > 0.0f) { - ImGui::RenderText(label_pos, label); - } - - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); - return pressed; - } - - bool CustomCheckboxTristate(const char* label, int* v, bool disabled, CheckboxGraphics disabledGraphic) { - bool ret; - if (*v == 0) { - bool b = false; - ret = CustomCheckbox(label, &b, disabled, disabledGraphic, true); - if (ret) { - *v = 1; - } - } else if (*v == 1) { - ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, true); - bool b = true; - ret = CustomCheckbox(label, &b, disabled, disabledGraphic, true); - if (ret) { - *v = 2; - } - ImGui::PopItemFlag(); - } else if (*v == 2) { - bool b = true; - ret = CustomCheckbox(label, &b, disabled, disabledGraphic, true); - if (ret) { - *v = 0; - } - } else { - SPDLOG_INFO("Invalid CheckBoxTristate value: {}", *v); - *v = 0; - return false; - } - return ret; - } - - void ReEnableComponent(const char* disabledTooltipText) { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(disabledTooltipText, "") != 0) { - ImGui::SetTooltip("%s", disabledTooltipText); - } - // End of disable region of previous component - ImGui::PopStyleVar(1); - ImGui::PopItemFlag(); - } - - void DisableComponent(const float alpha) { - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); - } - - bool EnhancementCheckbox(const char* text, const char* cvarName, bool disabled, const char* disabledTooltipText, CheckboxGraphics disabledGraphic, bool defaultValue) { - bool changed = false; - if (disabled) { - DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - bool val = (bool)CVarGetInteger(cvarName, defaultValue); - if (CustomCheckbox(text, &val, disabled, disabledGraphic)) { - CVarSetInteger(cvarName, val); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - ShipInit::Init(cvarName); - changed = true; - } - - if (disabled) { - ReEnableComponent(disabledTooltipText); - } - return changed; - } - - bool EnhancementCheckboxTristate(const char* text, const char* cvarName, bool disabled, const char* disabledTooltipText, CheckboxGraphics disabledGraphic, bool defaultValue) { - bool changed = false; - if (disabled) { - DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - int val = CVarGetInteger(cvarName, defaultValue); - if (CustomCheckboxTristate(text, &val, disabled, disabledGraphic)) { - CVarSetInteger(cvarName, val); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - changed = true; - } - - if (disabled) { - ReEnableComponent(disabledTooltipText); - } - return changed; - } - - bool PaddedEnhancementCheckbox(const char* text, const char* cvarName, bool padTop, bool padBottom, bool disabled, const char* disabledTooltipText, CheckboxGraphics disabledGraphic, bool defaultValue) { - ImGui::BeginGroup(); - if (padTop) Spacer(0); - - bool changed = EnhancementCheckbox(text, cvarName, disabled, disabledTooltipText, disabledGraphic, defaultValue); - - if (padBottom) Spacer(0); - ImGui::EndGroup(); - return changed; - } - - bool EnhancementCombobox(const char* cvarName, std::span comboArray, uint8_t defaultIndex, bool disabled, const char* disabledTooltipText, uint8_t disabledValue) { - bool changed = false; - if (defaultIndex <= 0) { - defaultIndex = 0; - } - - if (disabled) { - DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - uint8_t selected = CVarGetInteger(cvarName, defaultIndex); - std::string comboName = std::string("##") + std::string(cvarName); - if (ImGui::BeginCombo(comboName.c_str(), comboArray[selected])) { - for (uint8_t i = 0; i < comboArray.size(); i++) { - if (strlen(comboArray[i]) > 0) { - if (ImGui::Selectable(comboArray[i], i == selected)) { - CVarSetInteger(cvarName, i); - selected = i; - changed = true; - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - ShipInit::Init(cvarName); - } - } - } - ImGui::EndCombo(); - } - - if (disabled) { - ReEnableComponent(disabledTooltipText); - - if (disabledValue >= 0 && selected != disabledValue) { - CVarSetInteger(cvarName, disabledValue); - changed = true; - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - } - - return changed; - } - - bool LabeledRightAlignedEnhancementCombobox(const char* label, const char* cvarName, std::span comboArray, uint8_t defaultIndex, bool disabled, const char* disabledTooltipText, uint8_t disabledValue) { - ImGui::Text("%s", label); - s32 currentValue = CVarGetInteger(cvarName, defaultIndex); - -#ifdef __WIIU__ - ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(comboArray[currentValue]).x + 40.0f)); - ImGui::PushItemWidth(ImGui::CalcTextSize(comboArray[currentValue]).x + 60.0f); -#else - ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(comboArray[currentValue]).x + 20.0f)); - ImGui::PushItemWidth(ImGui::CalcTextSize(comboArray[currentValue]).x + 30.0f); -#endif - - bool changed = EnhancementCombobox(cvarName, comboArray, defaultIndex, disabled, disabledTooltipText, disabledValue); - - ImGui::PopItemWidth(); - return changed; - } - - void PaddedText(const char* text, bool padTop, bool padBottom) { - if (padTop) Spacer(0); - - ImGui::Text("%s", text); - - if (padBottom) Spacer(0); - } - - bool EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue, bool PlusMinusButton, bool disabled, const char* disabledTooltipText) { - bool changed = false; - int val = CVarGetInteger(cvarName, defaultValue); - const int oldVal = val; - - if (disabled) { - DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - ImGui::Text(text, val); - Spacer(0); - - ImGui::BeginGroup(); - if (PlusMinusButton) { - std::string MinusBTNName = " - ##" + std::string(cvarName); - if (ImGui::Button(MinusBTNName.c_str())) { - val--; - changed = true; - } - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - } - - ImGui::PushItemWidth(std::min((ImGui::GetContentRegionAvail().x - (PlusMinusButton ? sliderButtonWidth : 0.0f)), maxSliderWidth)); - if (ImGui::SliderInt(id, &val, min, max, format, ImGuiSliderFlags_AlwaysClamp)) - { - changed = true; - } - ImGui::PopItemWidth(); - - if (PlusMinusButton) { - std::string PlusBTNName = " + ##" + std::string(cvarName); - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - if (ImGui::Button(PlusBTNName.c_str())) { - val++; - changed = true; - } - } - ImGui::EndGroup(); - - if (disabled) { - ReEnableComponent(disabledTooltipText); - } - - if (val < min) { - val = min; - changed = true; - } - - if (val > max) { - val = max; - changed = true; - } - - if (changed && (oldVal != val)) { - CVarSetInteger(cvarName, val); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - ShipInit::Init(cvarName); - } else { - changed = false; - } - - return changed; - } - - bool EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton, bool disabled, const char* disabledTooltipText) { - bool changed = false; - float val = CVarGetFloat(cvarName, defaultValue); - const float oldVal = val; - if (disabled) { - DisableComponent(ImGui::GetStyle().Alpha * 0.5f); - } - - // Calculate how much precision to save based on the given range of the slider, limited to 6 decimal places - // Precision is also used when adding/subtracting using the +/- buttons - const float sliderWidth = std::min((ImGui::GetContentRegionAvail().x - 2.0f * (PlusMinusButton ? sliderButtonWidth : 0.0f)), maxSliderWidth); - const float diff = (max - min) / sliderWidth; - int ticks = 0; - float increment = 1.0f; - if (diff < 1.0f) { - ticks++; - increment = 0.1f; - } - if (diff < 0.1f) { - ticks++; - increment = 0.01f; - } - if (diff < 0.01f) { - ticks++; - increment = 0.001f; - } - if (diff < 0.001f) { - ticks++; - increment = 0.0001f; - } - if (diff < 0.0001f) { - ticks++; - increment = 0.00001f; - } - if (diff < 0.00001f) { - ticks++; - increment = 0.000001f; - } - - if (!isPercentage) { - ImGui::Text(text, val); - } else { - ImGui::Text(text, val * 100.0f); - } - Spacer(0); - - ImGui::BeginGroup(); - if (PlusMinusButton) { - std::string MinusBTNName = " - ##" + std::string(cvarName); - if (ImGui::Button(MinusBTNName.c_str())) { - val -= increment; - changed = true; - } - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - } - - ImGui::PushItemWidth(sliderWidth); - if (ImGui::SliderFloat(id, &val, min, max, format, ImGuiSliderFlags_AlwaysClamp)) { - changed = true; - } - ImGui::PopItemWidth(); - - if (PlusMinusButton) { - std::string PlusBTNName = " + ##" + std::string(cvarName); - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - 7.0f); - if (ImGui::Button(PlusBTNName.c_str())) { - val += increment; - changed = true; - } - } - ImGui::EndGroup(); - - if (disabled) { - ReEnableComponent(disabledTooltipText); - } - - if (val < min) { - val = min; - changed = true; - } - - if (val > max) { - val = max; - changed = true; - } - - if (changed && !(abs(oldVal - val) < 0.000001f)) { - std::stringstream ss; - ss << std::setprecision(ticks + 1) << std::setiosflags(std::ios_base::fixed) << val; - val = std::stof(ss.str()); - CVarSetFloat(cvarName, val); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } else { - changed = false; - } - - return changed; - } - - bool PaddedEnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue, bool PlusMinusButton, bool padTop, bool padBottom, bool disabled, const char* disabledTooltipText) { - bool changed = false; - ImGui::BeginGroup(); - if (padTop) Spacer(0); - - changed = EnhancementSliderInt(text, id, cvarName, min, max, format, defaultValue, PlusMinusButton, disabled, disabledTooltipText); - - if (padBottom) Spacer(0); - ImGui::EndGroup(); - return changed; - } - - bool PaddedEnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton, bool padTop, bool padBottom, bool disabled, const char* disabledTooltipText) { - bool changed = false; - ImGui::BeginGroup(); - if (padTop) Spacer(0); - - changed = EnhancementSliderFloat(text, id, cvarName, min, max, format, defaultValue, isPercentage, PlusMinusButton, disabled, disabledTooltipText); - - if (padBottom) Spacer(0); - ImGui::EndGroup(); - return changed; - } - - bool EnhancementRadioButton(const char* text, const char* cvarName, int id) { - /*Usage : - EnhancementRadioButton("My Visible Name",CVAR_GROUP("MyCVarName"), MyID); - First arg is the visible name of the Radio button - Second is the cvar name where MyID will be saved. - Note: the CVar name should be the same to each Buddies. - Example : - EnhancementRadioButton("English", CVAR_SETTING("Languages"), LANGUAGE_ENG); - EnhancementRadioButton("German", CVAR_SETTING("Languages"), LANGUAGE_GER); - EnhancementRadioButton("French", CVAR_SETTING("Languages"), LANGUAGE_FRA); - */ - std::string make_invisible = "##" + std::string(text) + std::string(cvarName); - - bool ret = false; - int val = CVarGetInteger(cvarName, 0); - if (ImGui::RadioButton(make_invisible.c_str(), id == val)) { - CVarSetInteger(cvarName, id); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - ret = true; - } - ImGui::SameLine(); - ImGui::Text("%s", text); - - return ret; - } - - bool DrawResetColorButton(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha) { - bool changed = false; - std::string Cvar_RBM = std::string(cvarName) + "RBM"; - std::string MakeInvisible = "Reset##" + std::string(cvarName) + "Reset"; - if (ImGui::Button(MakeInvisible.c_str())) { - colors->x = defaultcolors.x; - colors->y = defaultcolors.y; - colors->z = defaultcolors.z; - colors->w = has_alpha ? defaultcolors.w : 255.0f; - - Color_RGBA8 colorsRGBA; - colorsRGBA.r = defaultcolors.x; - colorsRGBA.g = defaultcolors.y; - colorsRGBA.b = defaultcolors.z; - colorsRGBA.a = has_alpha ? defaultcolors.w : 255.0f; - - CVarSetColor(cvarName, colorsRGBA); - CVarSetInteger(Cvar_RBM.c_str(), 0); //On click disable rainbow mode. - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - changed = true; - } - Tooltip("Revert colors to the game's original colors (GameCube version)\nOverwrites previously chosen color"); - return changed; - } - - bool DrawRandomizeColorButton(const char* cvarName, ImVec4* colors) { - bool changed = false; - Color_RGBA8 NewColors = {0,0,0,255}; - std::string Cvar_RBM = std::string(cvarName) + "RBM"; - std::string FullName = "Random##" + std::string(cvarName) + "Random"; - if (ImGui::Button(FullName.c_str())) { -#if defined(__SWITCH__) || defined(__WIIU__) - srand(time(NULL)); -#endif - ImVec4 color = GetRandomValue(); - colors->x = color.x; - colors->y = color.y; - colors->z = color.z; - NewColors.r = fmin(fmax(colors->x * 255, 0), 255); - NewColors.g = fmin(fmax(colors->y * 255, 0), 255); - NewColors.b = fmin(fmax(colors->z * 255, 0), 255); - CVarSetColor(cvarName, NewColors); - CVarSetInteger(Cvar_RBM.c_str(), 0); // On click disable rainbow mode. - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - changed = true; - } - Tooltip("Chooses a random color\nOverwrites previously chosen color"); - return changed; - } - - void DrawLockColorCheckbox(const char* cvarName) { - std::string Cvar_Lock = std::string(cvarName) + "Lock"; - s32 lock = CVarGetInteger(Cvar_Lock.c_str(), 0); - std::string FullName = "Lock##" + Cvar_Lock; - EnhancementCheckbox(FullName.c_str(), Cvar_Lock.c_str()); - Tooltip("Prevents this color from being changed upon selecting \"Randomize all\""); - } - - void RainbowColor(const char* cvarName, ImVec4* colors) { - std::string Cvar_RBM = std::string(cvarName) + "RBM"; - std::string MakeInvisible = "Rainbow##" + std::string(cvarName) + "Rainbow"; - - EnhancementCheckbox(MakeInvisible.c_str(), Cvar_RBM.c_str()); - Tooltip("Cycles through colors on a timer\nOverwrites previously chosen color"); - } - - void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha) { - Color_RGBA8 defaultColors; - defaultColors.r = default_colors.x; - defaultColors.g = default_colors.y; - defaultColors.b = default_colors.z; - defaultColors.a = default_colors.w; - - Color_RGBA8 cvarColor = CVarGetColor(cvarname, defaultColors); - - ColorArray.x = cvarColor.r / 255.0; - ColorArray.y = cvarColor.g / 255.0; - ColorArray.z = cvarColor.b / 255.0; - ColorArray.w = cvarColor.a / 255.0; - } - - bool EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors, bool allow_rainbow, bool has_alpha, bool TitleSameLine) { - bool changed = false; - LoadPickersColors(ColorRGBA, cvarName, default_colors, has_alpha); - - ImGuiColorEditFlags flags = ImGuiColorEditFlags_None; - - if (!TitleSameLine) { - ImGui::Text("%s", text); - flags = ImGuiColorEditFlags_NoLabel; - } - - ImGui::PushID(cvarName); - - if (!has_alpha) { - if (ImGui::ColorEdit3(text, (float*)&ColorRGBA, flags)) - { - Color_RGBA8 colors; - colors.r = ColorRGBA.x * 255.0; - colors.g = ColorRGBA.y * 255.0; - colors.b = ColorRGBA.z * 255.0; - colors.a = 255.0; - - CVarSetColor(cvarName, colors); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - changed = true; - } - } - else - { - flags |= ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreview; - if (ImGui::ColorEdit4(text, (float*)&ColorRGBA, flags)) - { - Color_RGBA8 colors; - colors.r = ColorRGBA.x * 255.0; - colors.g = ColorRGBA.y * 255.0; - colors.b = ColorRGBA.z * 255.0; - colors.a = ColorRGBA.w * 255.0; - - CVarSetColor(cvarName, colors); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - changed = true; - } - } - - ImGui::PopID(); - - //ImGui::SameLine(); // Removing that one to gain some width spacing on the HUD editor - ImGui::PushItemWidth(-FLT_MIN); - if (DrawResetColorButton(cvarName, &ColorRGBA, default_colors, has_alpha)) { - changed = true; - } - ImGui::SameLine(); - if (DrawRandomizeColorButton(cvarName, &ColorRGBA)) { - changed = true; - } - if (allow_rainbow) { - if (ImGui::GetContentRegionAvail().x > 185) { - ImGui::SameLine(); - } - RainbowColor(cvarName, &ColorRGBA); - } - DrawLockColorCheckbox(cvarName); - ImGui::NewLine(); - ImGui::PopItemWidth(); - - return changed; - } - - void DrawFlagArray32(const std::string& name, uint32_t& flags) { - ImGui::PushID(name.c_str()); - for (int32_t flagIndex = 0; flagIndex < 32; flagIndex++) { - if ((flagIndex % 8) != 0) { - ImGui::SameLine(); - } - ImGui::PushID(flagIndex); - uint32_t bitMask = 1 << flagIndex; - bool flag = (flags & bitMask) != 0; - if (ImGui::Checkbox("##check", &flag)) { - if (flag) { - flags |= bitMask; - } else { - flags &= ~bitMask; - } - } - ImGui::PopID(); - } - ImGui::PopID(); - } - - void DrawFlagArray16(const std::string& name, uint16_t& flags) { - ImGui::PushID(name.c_str()); - for (int16_t flagIndex = 0; flagIndex < 16; flagIndex++) { - if ((flagIndex % 8) != 0) { - ImGui::SameLine(); - } - ImGui::PushID(flagIndex); - uint16_t bitMask = 1 << flagIndex; - bool flag = (flags & bitMask) != 0; - if (ImGui::Checkbox("##check", &flag)) { - if (flag) { - flags |= bitMask; - } else { - flags &= ~bitMask; - } - } - ImGui::PopID(); - } - ImGui::PopID(); - } - - void DrawFlagArray8(const std::string& name, uint8_t& flags) { - ImGui::PushID(name.c_str()); - for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) { - if ((flagIndex % 8) != 0) { - ImGui::SameLine(); - } - ImGui::PushID(flagIndex); - uint8_t bitMask = 1 << flagIndex; - bool flag = (flags & bitMask) != 0; - if (ImGui::Checkbox("##check", &flag)) { - if (flag) { - flags |= bitMask; - } else { - flags &= ~bitMask; - } - } - ImGui::PopID(); - } - ImGui::PopID(); - } - - bool StateButtonEx(const char* str_id, const char* label, ImVec2 size, ImGuiButtonFlags flags) { - ImGuiContext& g = *GImGui; - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (window->SkipItems) - return false; - - const ImGuiStyle& style = g.Style; - const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); - - const ImGuiID id = window->GetID(str_id); - const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); - const float default_size = ImGui::GetFrameHeight(); - ImGui::ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f); - if (!ImGui::ItemAdd(bb, id)) - return false; - - if (g.LastItemData.ItemFlags & ImGuiItemFlags_ButtonRepeat) { - ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); - } - - bool hovered, held; - bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags); - - if (g.LastItemData.ItemFlags & ImGuiItemFlags_ButtonRepeat) { - ImGui::PopItemFlag(); // ImGuiItemFlags_ButtonRepeat; - } - - // Render - const ImU32 bg_col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive - : hovered ? ImGuiCol_ButtonHovered - : ImGuiCol_Button); - //const ImU32 text_col = ImGui::GetColorU32(ImGuiCol_Text); - ImGui::RenderNavHighlight(bb, id); - ImGui::RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding); - ImGui::RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, {0.55f, 0.45f}, &bb); - /*ImGui::RenderArrow(window->DrawList, - bb.Min + - ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), - text_col, dir);*/ - - IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); - return pressed; - } - - bool StateButton(const char* str_id, const char* label) { - float sz = ImGui::GetFrameHeight(); - return StateButtonEx(str_id, label, ImVec2(sz, sz), ImGuiButtonFlags_None); - } - - // Reference: imgui-src/misc/cpp/imgui_stdlib.cpp - int InputTextResizeCallback(ImGuiInputTextCallbackData* data) { - std::string* value = (std::string*)data->UserData; - if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) { - value->resize(data->BufTextLen); - data->Buf = (char*)value->c_str(); - } - return 0; - } - - bool InputString(const char* label, std::string* value) { - return ImGui::InputText(label, (char*)value->c_str(), value->capacity() + 1, ImGuiInputTextFlags_CallbackResize, InputTextResizeCallback, value); + ImGui::Separator(); + if (padBottom) { + Spacer(extraVerticalBottomPadding); } } + +void Tooltip(const char* text) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", WrappedText(text).c_str()); + } +} + +void PushStyleMenu(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ColorValues.at(Colors::DarkGray)); + ImGui::PushStyleColor(ImGuiCol_Border, ColorValues.at(Colors::DarkGray)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 15.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 3.0f); +} + +void PushStyleMenu(Colors color) { + PushStyleMenu(ColorValues.at(color)); +} + +void PopStyleMenu() { + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(4); +} + +bool BeginMenu(const char* label, Colors color) { + bool dirty = false; + PushStyleMenu(color); + ImGui::SetNextWindowSizeConstraints(ImVec2(200.0f, 0.0f), ImVec2(FLT_MAX, FLT_MAX)); + if (ImGui::BeginMenu(label)) { + dirty = true; + } + PopStyleMenu(); + return dirty; +} + +void PushStyleMenuItem(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, color); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0f, 15.0f)); +} + +void PushStyleMenuItem(Colors color) { + PushStyleMenuItem(ColorValues.at(color)); +} + +void PopStyleMenuItem() { + ImGui::PopStyleVar(1); + ImGui::PopStyleColor(1); +} + +bool MenuItem(const char* label, const char* shortcut, Colors color) { + bool dirty = false; + PushStyleMenuItem(color); + if (ImGui::MenuItem(label, shortcut)) { + dirty = true; + } + PopStyleMenuItem(); + return dirty; +} + +void PushStyleButton(const ImVec4& color, const ImVec2 padding) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, padding); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f); +} + +void PushStyleButton(Colors color, ImVec2 padding) { + PushStyleButton(ColorValues.at(color), padding); +} + +void PopStyleButton() { + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(4); +} + +void PushStyleInput(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f); +} + +void PushStyleInput(Colors color) { + PushStyleInput(ColorValues.at(color)); +} + +void PopStyleInput() { + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(7); +} + +void PushStyleHeader(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(color.x, color.y, color.z, 0.6f)); +} + +void PushStyleHeader(Colors color) { + PushStyleHeader(ColorValues.at(color)); +} + +void PopStyleHeader() { + ImGui::PopStyleColor(3); +} + +bool Button(const char* label, const ButtonOptions& options) { + ImGui::BeginDisabled(options.disabled); + PushStyleButton(options.color, options.padding); + bool dirty = ImGui::Button(label, options.size); + PopStyleButton(); + ImGui::EndDisabled(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + return dirty; +} + +bool WindowButton(const char* label, const char* cvarName, std::shared_ptr windowPtr, + const WindowButtonOptions& options) { + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); + std::string buttonText = label; + bool dirty = false; + if (CVarGetInteger(cvarName, 0)) { + buttonText = ICON_FA_WINDOW_CLOSE " " + buttonText; + } else { + buttonText = ICON_FA_EXTERNAL_LINK_SQUARE " " + buttonText; + } + if (Button(buttonText.c_str(), {{ options.tooltip, options.disabled, options.disabledTooltip, options.color }, + options.size, options.padding })) { + windowPtr->ToggleVisibility(); + dirty = true; + } + ImGui::PopStyleVar(); + return dirty; +} + +void PushStyleCheckbox(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); + ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.0f, 1.0f, 1.0f, 0.7f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f); +} + +void PushStyleCheckbox(Colors color) { + PushStyleCheckbox(ColorValues.at(color)); +} + +void PopStyleCheckbox() { + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(5); +} + +void Spacer(float height) { + ImGui::Dummy(ImVec2(0.0f, height)); +} + +void Separator(bool padTop, bool padBottom, float extraVerticalTopPadding, float extraVerticalBottomPadding) { + if (padTop) { + Spacer(extraVerticalTopPadding); + } + ImGui::Separator(); + if (padBottom) { + Spacer(extraVerticalBottomPadding); + } +} + +// Adds a "?" next to the previous ImGui item with a custom tooltip +void InsertHelpHoverText(const std::string& text) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("%s", WrappedText(text, 60).c_str()); + ImGui::EndTooltip(); + } +} + +void InsertHelpHoverText(const char* text) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("%s", WrappedText(text, 60).c_str()); + ImGui::EndTooltip(); + } +} + +void RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Hide anything after a '##' string + const char* text_display_end; + if (hide_text_after_hash) { + text_display_end = ImGui::FindRenderedTextEnd(text, text_end); + } else { + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + text_display_end = text_end; + } + + if (text != text_display_end) { + window->DrawList->AddText(g.Font, g.FontSize, pos, ImGui::GetColorU32(ImGuiCol_Text), text, text_display_end); + if (g.LogEnabled) + ImGui::LogRenderedText(&pos, text, text_display_end); + } +} + +bool Checkbox(const char* _label, bool* value, const CheckboxOptions& options) { + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGui::BeginDisabled(options.disabled); + + bool above = options.labelPosition == LabelPosition::Above; + bool lpFar = options.labelPosition == LabelPosition::Far; + bool right = options.alignment == ComponentAlignment::Right; + bool none = options.labelPosition == LabelPosition::None; + + std::string labelStr = (none ? "##" : ""); + labelStr.append(_label); + + const char* label = labelStr.c_str(); + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + const float square_sz = ImGui::GetFrameHeight(); + ImVec2 pos = window->DC.CursorPos; + + if (right) { + float labelOffsetX = (above ? 0 : (style.ItemInnerSpacing.x * 2.0f) + square_sz); + if (!lpFar) { + pos.x += ImGui::GetContentRegionAvail().x - (label_size.x + labelOffsetX); + } + } + float bbAboveX = lpFar ? ImGui::GetContentRegionAvail().x + : (label_size.x + (above ? 0 : (style.ItemInnerSpacing.x * 2.0f) + square_sz)); + float bbAboveY = label_size.y + (above ? square_sz : 0) + (style.FramePadding.y * 2.0f); + const ImRect total_bb(pos, pos + ImVec2(bbAboveX, bbAboveY)); + + ImGui::ItemSize(total_bb, style.FramePadding.y); + if (!ImGui::ItemAdd(total_bb, id)) { + ImGui::EndDisabled(); + return false; + } + bool hovered, held, pressed; + pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) { + *value = !(*value); + ImGui::MarkItemEdited(id); + } + PushStyleCheckbox(options.color); + ImVec2 checkPos = pos; + ImVec2 labelPos = pos; + if (options.labelPosition == LabelPosition::Above) { + checkPos.y += label_size.y + (style.ItemInnerSpacing.y * 2.0f); + } else { + labelPos.y += (square_sz / 2) - (label_size.y / 2); + } + if (options.alignment == ComponentAlignment::Right) { + checkPos.x = total_bb.Max.x - square_sz; + } else { + float labelFarOffset = ImGui::GetContentRegionAvail().x - label_size.x; + float labelOffsetX = above ? 0 : (lpFar ? labelFarOffset : (style.ItemInnerSpacing.x * 2.0f) + square_sz); + labelPos.x += labelOffsetX; + } + const ImRect check_bb(checkPos, checkPos + ImVec2(square_sz, square_sz)); + ImGui::RenderNavHighlight(total_bb, id); + ImGui::RenderFrame(check_bb.Min, check_bb.Max, + ImGui::GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive + : hovered ? ImGuiCol_FrameBgHovered + : ImGuiCol_FrameBg), + true, style.FrameRounding); + ImU32 check_col = ImGui::GetColorU32(ImGuiCol_CheckMark); + bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; + if (mixed_value) { + // Undocumented tristate/mixed/indeterminate checkbox (#2644) + // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all + // widgets (not just checkbox) + ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f))); + window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); + } else if (*value) { + const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); + ImGui::RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); + } + RenderText(labelPos, label, ImGui::FindRenderedTextEnd(label), true); + PopStyleCheckbox(); + ImGui::EndDisabled(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + return pressed; +} + +bool CVarCheckbox(const char* label, const char* cvarName, const CheckboxOptions& options) { + bool dirty = false; + bool value = (bool)CVarGetInteger(cvarName, options.defaultValue); + if (Checkbox(label, &value, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; +} + +bool StateButton(const char* str_id, const char* label, ImVec2 size, ButtonOptions options, ImGuiButtonFlags flags) { + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) { + return false; + } + + const ImGuiStyle& style = g.Style; + const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + + const ImGuiID id = window->GetID(str_id); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + const float default_size = ImGui::GetFrameHeight(); + ImGui::ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f); + if (!ImGui::ItemAdd(bb, id)) + return false; + + if (g.LastItemData.ItemFlags & ImGuiItemFlags_ButtonRepeat) { + ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); + } + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags); + + if (g.LastItemData.ItemFlags & ImGuiItemFlags_ButtonRepeat) { + ImGui::PopItemFlag(); // ImGuiItemFlags_ButtonRepeat; + } + PushStyleButton(options.color); + // Render + const ImU32 bg_col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive + : hovered ? ImGuiCol_ButtonHovered + : ImGuiCol_Button); + //const ImU32 text_col = ImGui::GetColorU32(ImGuiCol_Text); + ImGui::RenderNavHighlight(bb, id); + ImGui::RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding); + ImGui::RenderTextClipped(bb.Min + (style.FramePadding * 0.35f), bb.Max - (style.FramePadding / 4), label, NULL, &label_size, style.ButtonTextAlign, &bb); + PopStyleButton(); + /*ImGui::RenderArrow(window->DrawList, + bb.Min + + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), + text_col, dir);*/ + + IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); + return pressed; +} + +float CalcComboWidth(const char* preview_value, ImGuiComboFlags flags) { + ImGuiContext& g = *GImGui; + + const ImGuiStyle& style = g.Style; + IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together + if (flags & ImGuiComboFlags_WidthFitPreview) + IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0); + + const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : ImGui::GetFrameHeight(); + const float preview_width = ImGui::CalcTextSize(preview_value, NULL, true).x; + float w = arrow_size + preview_width + (style.FramePadding.x * 2.0f); + return w; +} + +void PushStyleCombobox(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); +} + +void PushStyleCombobox(Colors color) { + PushStyleCombobox(ColorValues.at(color)); +} + +void PopStyleCombobox() { + ImGui::PopStyleVar(4); + ImGui::PopStyleColor(9); +} + +void PushStyleTabs(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Tab, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_TabHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_TabActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); +} + +void PushStyleTabs(Colors color) { + PushStyleTabs(ColorValues.at(color)); +} + +void PopStyleTabs() { + ImGui::PopStyleColor(6); + ImGui::PopStyleVar(4); +} + +void PushStyleSlider(Colors color_) { + const ImVec4& color = ColorValues.at(color_); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(1.0, 1.0, 1.0, 0.4f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(1.0, 1.0, 1.0, 0.5f)); + ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); +} + +void PopStyleSlider() { + ImGui::PopStyleVar(4); + ImGui::PopStyleColor(6); +} + +bool SliderInt(const char* label, int32_t* value, const IntSliderOptions& options) { + bool dirty = false; + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleSlider(options.color); + float width = (options.size == ImVec2(0,0)) ? ImGui::GetContentRegionAvail().x : options.size.x; + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + width = width - (ImGui::CalcTextSize(label).x + ImGui::GetStyle().FramePadding.x); + } + ImGui::AlignTextToFramePadding(); + if (options.alignment == ComponentAlignment::Right) { + ImGui::Text(label, *value); + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label, *value); + } + } + if (options.showButtons) { + if (Button("-", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value > options.min) { + *value -= options.step; + if (options.clamp) { + if (*value < options.min) { + *value = options.min; + } + } + dirty = true; + } + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(width - (ImGui::CalcTextSize("+").x + ImGui::GetStyle().FramePadding.x * 2 + 3) * 2); + } else { + ImGui::SetNextItemWidth(width); + } + if (ImGui::SliderScalar(invisibleLabel, ImGuiDataType_S32, value, &options.min, &options.max, options.format, + options.flags)) { + if (options.clamp) { + if (*value < options.min) { + *value = options.min; + } + if (*value > options.max) + *value = options.max; + } + dirty = true; + } + if (options.showButtons) { + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (Button("+", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value < options.max) { + *value += options.step; + if (options.clamp) { + if (*value > options.max) + *value = options.max; + } + dirty = true; + } + } + + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text(label, *value); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x); + ImGui::Text(label, *value); + } + } + PopStyleSlider(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; +} + +bool CVarSliderInt(const char* label, const char* cvarName, const IntSliderOptions& options) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultValue); + if (SliderInt(label, &value, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; +} + +void ClampFloat(float* value, float min, float max, float step) { + int ticks = 0; + float increment = 1.0f; + if (step < 1.0f) { + ticks++; + increment = 0.1f; + } + if (step < 0.1f) { + ticks++; + increment = 0.01f; + } + if (step < 0.01f) { + ticks++; + increment = 0.001f; + } + if (step < 0.001f) { + ticks++; + increment = 0.0001f; + } + if (step < 0.0001f) { + ticks++; + increment = 0.00001f; + } + if (step < 0.00001f) { + ticks++; + increment = 0.000001f; + } + int factor = 1 * std::pow(10, ticks); + if (*value < min) { + *value = min; + } else if (*value > max) { + *value = max; + } else { + int trunc = (int)std::round(*value * factor); + *value = (float)trunc / factor; + } +} + +bool SliderFloat(const char* label, float* value, const FloatSliderOptions& options) { + bool dirty = false; + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + float valueToDisplay = options.isPercentage ? *value * 100.0f : *value; + float maxToDisplay = options.isPercentage ? options.max * 100.0f : options.max; + float minToDisplay = options.isPercentage ? options.min * 100.0f : options.min; + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleSlider(options.color); + float labelSpacing = ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x; + float width = (options.size == ImVec2(0, 0)) ? ImGui::GetContentRegionAvail().x : options.size.x; + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + width = width - (ImGui::CalcTextSize(label).x + ImGui::GetStyle().FramePadding.x); + } + ImGui::AlignTextToFramePadding(); + if (options.alignment == ComponentAlignment::Right) { + ImGui::Text(label, *value); + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + } else if (options.labelPosition == LabelPosition::Near) { + width -= labelSpacing; + ImGui::SameLine(); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + width -= labelSpacing; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label, *value); + } + } + if (options.showButtons) { + if (Button("-", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value > options.min) { + *value -= options.step; + if (options.clamp) { + ClampFloat(value, options.min, options.max, options.step); + } + dirty = true; + } + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(width - (ImGui::CalcTextSize("+").x + ImGui::GetStyle().FramePadding.x * 2 + 3) * 2); + } else { + ImGui::SetNextItemWidth(width); + } + if (ImGui::SliderScalar(invisibleLabel, ImGuiDataType_Float, &valueToDisplay, &minToDisplay, &maxToDisplay, + options.format, options.flags)) { + *value = options.isPercentage ? valueToDisplay / 100.0f : valueToDisplay; + if (options.clamp) { + ClampFloat(value, options.min, options.max, options.step); + } + dirty = true; + } + if (options.showButtons) { + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (Button("+", ButtonOptions{ .color = options.color }.Size(Sizes::Inline)) && *value < options.max) { + *value += options.step; + if (options.clamp) { + ClampFloat(value, options.min, options.max, options.step); + } + dirty = true; + } + } + + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text(label, *value); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - labelSpacing); + ImGui::Text(label, *value); + } + } + PopStyleSlider(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; +} + +bool CVarSliderFloat(const char* label, const char* cvarName, const FloatSliderOptions& options) { + bool dirty = false; + float value = CVarGetFloat(cvarName, options.defaultValue); + if (SliderFloat(label, &value, options)) { + CVarSetFloat(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; +} + +int InputTextResizeCallback(ImGuiInputTextCallbackData* data) { + std::string* value = (std::string*)data->UserData; + if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) { + value->resize(data->BufTextLen); + data->Buf = (char*)value->c_str(); + } + return 0; +} + +bool InputString(const char* label, std::string* value, const InputOptions& options) { + bool dirty = false; + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleInput(options.color); + float width = (options.size == ImVec2(0, 0)) ? ImGui::GetContentRegionAvail().x : options.size.x; + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label, *value->c_str()); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(width - ImGui::CalcTextSize(label).x); + ImGui::Text(label, *value->c_str()); + } + } + ImGui::SetNextItemWidth(width); + if (ImGui::InputText(label, (char*)value->c_str(), value->capacity() + 1, ImGuiInputTextFlags_CallbackResize, InputTextResizeCallback, value)) { + dirty = true; + } + if (value->empty() && !options.placeholder.empty()) { + ImGui::SameLine(17.0f); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "%s", options.placeholder.c_str()); + } + PopStyleInput(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; +} + +bool CVarInputString(const char* label, const char* cvarName, const InputOptions& options) { + bool dirty = false; + std::string value = CVarGetString(cvarName, options.defaultValue.c_str()); + if (InputString(label, &value, options)) { + CVarSetString(cvarName, value.c_str()); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; +} + +bool InputInt(const char* label, int32_t* value, const InputOptions& options) { + bool dirty = false; + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleInput(options.color); + float width = (options.size == ImVec2(0, 0)) ? ImGui::GetContentRegionAvail().x : options.size.x; + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label, *value); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(width - ImGui::CalcTextSize(label).x); + ImGui::Text(label, *value); + } + } + ImGui::SetNextItemWidth(width); + if (ImGui::InputScalar(label, ImGuiDataType_S32, value)) { + dirty = true; + } + if ((ImGui::GetItemStatusFlags() & ImGuiItemStatusFlags_Edited) && !options.placeholder.empty()) { + ImGui::SameLine(17.0f); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "%s", options.placeholder.c_str()); + } + PopStyleInput(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && + !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; +} + +bool CVarInputInt(const char* label, const char* cvarName, const InputOptions& options) { + bool dirty = false; + int32_t defaultValue = std::stoi(options.defaultValue); + int32_t value = CVarGetInteger(cvarName, defaultValue); + if (InputInt(label, &value, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; +} + +bool CVarColorPicker(const char* label, const char* cvarName, Color_RGBA8 defaultColor, bool hasAlpha, uint8_t modifiers, UIWidgets::Colors themeColor) { + std::string valueCVar = std::string(cvarName) + ".Value"; + std::string rainbowCVar = std::string(cvarName) + ".Rainbow"; + std::string lockedCVar = std::string(cvarName) + ".Locked"; + Color_RGBA8 color = CVarGetColor(valueCVar.c_str(), defaultColor); + ImVec4 colorVec = ImVec4(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f); + bool changed = false; + bool showReset = modifiers & ColorPickerResetButton; + bool showRandom = modifiers & ColorPickerRandomButton; + bool showRainbow = modifiers & ColorPickerRainbowCheck; + bool showLock = modifiers & ColorPickerLockCheck; + bool locked = CVarGetInteger(lockedCVar.c_str(), 0); + ImGuiColorEditFlags flags = ImGuiColorEditFlags_NoInputs; + ImGui::BeginDisabled(locked); + PushStyleCombobox(UIWidgets::Colors::DarkGray); + if (hasAlpha) { + changed = ImGui::ColorEdit4(label, (float*)&colorVec, flags | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreview); + } else { + changed = ImGui::ColorEdit3(label, (float*)&colorVec, flags | ImGuiColorEditFlags_NoAlpha); + } + PopStyleCombobox(); + ImGui::AlignTextToFramePadding(); + if (showReset) { + ImGui::SameLine(); + std::string uniqueTag = "Reset##" + std::string(label); + if (UIWidgets::Button(uniqueTag.c_str(), UIWidgets::ButtonOptions({{ .tooltip = "Resets this color to its default value" }} ).Color(themeColor).Size(UIWidgets::Sizes::Inline))) { + CVarClearBlock(valueCVar.c_str()); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } + if (showRandom) { + ImGui::SameLine(); + std::string uniqueTag = "Random##" + std::string(label); + if (UIWidgets::Button(uniqueTag.c_str(), UIWidgets::ButtonOptions({{ .tooltip = "Generates a random color value to use" }}).Color(themeColor).Size(UIWidgets::Sizes::Inline))) { + colorVec = GetRandomValue(); + color.r = fmin(fmax(colorVec.x * 255, 0), 255); + color.g = fmin(fmax(colorVec.y * 255, 0), 255); + color.b = fmin(fmax(colorVec.z * 255, 0), 255); + CVarSetColor(valueCVar.c_str(), color); + CVarSetInteger(rainbowCVar.c_str(), 0); // On click disable rainbow mode. + ShipInit::Init(rainbowCVar.c_str()); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } + if (showRainbow) { + ImGui::SameLine(); + std::string uniqueTag = "Rainbow##" + std::string(cvarName) + "Rainbow"; + + UIWidgets::CVarCheckbox(uniqueTag.c_str(), rainbowCVar.c_str(), UIWidgets::CheckboxOptions({{ .tooltip = "Cycles through colors on a timer\nOverwrites previously chosen color" }}).Color(themeColor)); + } + ImGui::EndDisabled(); + if (showLock) { + ImGui::SameLine(); + std::string uniqueTag = "Lock##" + std::string(cvarName) + "Locked"; + + UIWidgets::CVarCheckbox(uniqueTag.c_str(), lockedCVar.c_str(), UIWidgets::CheckboxOptions({{ .tooltip = "Prevents this color from being changed" }}).Color(themeColor)); + } + if (changed) { + color.r = (uint8_t)(colorVec.x * 255.0f); + color.g = (uint8_t)(colorVec.y * 255.0f); + color.b = (uint8_t)(colorVec.z * 255.0f); + color.a = (uint8_t)(colorVec.w * 255.0f); + CVarSetColor(valueCVar.c_str(), color); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(valueCVar.c_str()); + changed = true; + } + + return changed; +} + +bool RadioButton(const char* label, bool active, const RadioButtonsOptions& options) { + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + + const float square_sz = ImGui::GetFrameHeight(); + const ImVec2 pos = window->DC.CursorPos; + const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); + const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); + ImGui::ItemSize(total_bb, style.FramePadding.y); + if (!ImGui::ItemAdd(total_bb, id)) + return false; + + ImVec2 center = check_bb.GetCenter(); + center.x = IM_ROUND(center.x); + center.y = IM_ROUND(center.y); + const float radius = (square_sz - 1.0f) * 0.5f; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + ImGui::MarkItemEdited(id); + + ImGui::RenderNavCursor(total_bb, id); + const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius); + window->DrawList->AddCircleFilled(center, radius, ImGui::GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment); + if (active) + { + const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); + window->DrawList->AddCircleFilled(center, radius - pad, ImGui::GetColorU32(ImGuiCol_CheckMark)); + } + + if (style.FrameBorderSize > 0.0f) + { + window->DrawList->AddCircle(center + ImVec2(1, 1), radius, ImGui::GetColorU32(ImGuiCol_BorderShadow), num_segment, style.FrameBorderSize); + window->DrawList->AddCircle(center, radius, ImGui::GetColorU32(ImGuiCol_Border), num_segment, style.FrameBorderSize); + } + + ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); + if (g.LogEnabled) + ImGui::LogRenderedText(&label_pos, active ? "(x)" : "( )"); + if (label_size.x > 0.0f) + RenderText(label_pos, label, ImGui::FindRenderedTextEnd(label), true); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + return pressed; +} + +bool CVarRadioButton(const char* text, const char* cvarName, int32_t id, const RadioButtonsOptions& options) { + std::string make_invisible = "##" + std::string(text) + std::string(cvarName); + + bool ret = false; + int val = CVarGetInteger(cvarName, 0); + PushStyleCheckbox(options.color); + if (ImGui::RadioButton(make_invisible.c_str(), id == val)) { + CVarSetInteger(cvarName, id); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ret = true; + } + ImGui::SameLine(); + ImGui::Text("%s", text); + PopStyleCheckbox(); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + + return ret; +} + +void DrawFlagArray32(const std::string& name, uint32_t& flags, Colors color) { + ImGui::PushID(name.c_str()); + for (int32_t flagIndex = 0; flagIndex < 32; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint32_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + PushStyleCheckbox(color); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); + std::string id = fmt::format("##{}{}", name, flagIndex); + if (ImGui::Checkbox(id.c_str(), &flag)) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopStyleVar(); + PopStyleCheckbox(); + ImGui::PopID(); + } + ImGui::PopID(); +} + +void DrawFlagArray16(const std::string& name, uint16_t& flags, Colors color) { + ImGui::PushID(name.c_str()); + for (int16_t flagIndex = 0; flagIndex < 16; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint16_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + PushStyleCheckbox(color); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); + std::string id = fmt::format("##{}{}", name, flagIndex); + if (ImGui::Checkbox(id.c_str(), &flag)) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopStyleVar(); + PopStyleCheckbox(); + ImGui::PopID(); + } + ImGui::PopID(); +} + +void DrawFlagArray8(const std::string& name, uint8_t& flags, Colors color) { + ImGui::PushID(name.c_str()); + for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint8_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + PushStyleCheckbox(color); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); + std::string id = fmt::format("##{}{}", name, flagIndex); + if (ImGui::Checkbox(id.c_str(), &flag)) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopStyleVar(); + PopStyleCheckbox(); + ImGui::PopID(); + } + ImGui::PopID(); +} + +void DrawFlagArray8Mask(const std::string& name, uint8_t& flags, Colors color) { + ImGui::PushID(name.c_str()); + for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint8_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + PushStyleCheckbox(color); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); + std::string id = fmt::format("##{}{}", name, flagIndex); + if (ImGui::Checkbox(id.c_str(), &flag)) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopStyleVar(); + PopStyleCheckbox(); + ImGui::PopID(); + } + ImGui::PopID(); +} +} // namespace UIWidgets + +ImVec4 GetRandomValue() { +#if !defined(__SWITCH__) && !defined(__WIIU__) + std::random_device rd; + std::mt19937 rng(rd()); +#else + size_t seed = std::hash{}(std::to_string(rand())); + std::mt19937_64 rng(seed); +#endif + std::uniform_int_distribution dist(0, 255 - 1); + + ImVec4 NewColor; + NewColor.x = (float)(dist(rng)) / 255.0f; + NewColor.y = (float)(dist(rng)) / 255.0f; + NewColor.z = (float)(dist(rng)) / 255.0f; + return NewColor; +} + +Color_RGBA8 RGBA8FromVec(ImVec4 vec) { + Color_RGBA8 color = { vec.x * 255, vec.y * 255, vec.z * 255, vec.w * 255 }; + return color; +} + +ImVec4 VecFromRGBA8(Color_RGBA8 color) { + ImVec4 vec = { color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f }; + return vec; +} diff --git a/soh/soh/SohGui/UIWidgets.hpp b/soh/soh/SohGui/UIWidgets.hpp index 97a367270..ebf483194 100644 --- a/soh/soh/SohGui/UIWidgets.hpp +++ b/soh/soh/SohGui/UIWidgets.hpp @@ -1,16 +1,21 @@ -#ifndef UIWidgets_hpp -#define UIWidgets_hpp +#ifndef UIWidgets2_hpp +#define UIWidgets2_hpp #include -#include #include #include #include +#define IMGUI_DEFINE_MATH_OPERATORS #include +#include +#include +#include "soh/ShipUtils.h" #include "soh/ShipInit.hpp" namespace UIWidgets { + using SectionFunc = void(*)(); + struct TextFilters { static int FilterNumbers(ImGuiInputTextCallbackData* data) { if (data->EventChar < 256 && strchr("1234567890", (char)data->EventChar)) { @@ -26,74 +31,963 @@ namespace UIWidgets { } return 1; } + }; + + std::string WrappedText(const char* text, unsigned int charactersPerLine = 80); + std::string WrappedText(const std::string& text, unsigned int charactersPerLine = 80); + void PaddedSeparator(bool padTop = true, bool padBottom = true, float extraVerticalTopPadding = 0.0f, float extraVerticalBottomPadding = 0.0f); + void Tooltip(const char* text); + + typedef enum ColorPickerModifiers { + ColorPickerResetButton = 1, + ColorPickerRandomButton = 2, + ColorPickerRainbowCheck = 4, + ColorPickerLockCheck = 8, + } ColorPickerModifiers; + + // mostly in order for colors usable by the menu without custom text color + enum Colors { + Red, + DarkRed, + Orange, + Green, + DarkGreen, + LightBlue, + Blue, + DarkBlue, + Indigo, + Violet, + Purple, + Brown, + Gray, + DarkGray, + // not suitable for menu theme use + Pink, + Yellow, + Cyan, + Black, + LightGray, + White, + NoColor + }; + + enum InputTypes { + String, + Scalar + }; + + const std::unordered_map ColorValues = { + { Colors::Pink, ImVec4(0.87f, 0.3f, 0.87f, 1.0f) }, + { Colors::Red, ImVec4(0.55f, 0.0f, 0.0f, 1.0f) }, + { Colors::DarkRed, ImVec4(0.3f, 0.0f, 0.0f, 1.0f) }, + { Colors::Orange, ImVec4(0.85f, 0.55f, 0.0f, 1.0f) }, + { Colors::Yellow, ImVec4(0.95f, 0.95f, 0.0f, 1.0f) }, + { Colors::Green, ImVec4(0.0f, 0.55f, 0.0f, 1.0f) }, + { Colors::DarkGreen, ImVec4(0.0f, 0.3f, 0.0f, 1.0f) }, + { Colors::Cyan, ImVec4(0.0f, 0.9f, 0.9f, 1.0f) }, + { Colors::LightBlue, ImVec4(0.0f, 0.24f, 0.8f, 1.0f) }, + { Colors::Blue, ImVec4(0.08f, 0.03f, 0.65f, 1.0f) }, + { Colors::DarkBlue, ImVec4(0.03f, 0.0f, 0.5f, 1.0f) }, + { Colors::Indigo, ImVec4(0.35f, 0.0f, 0.87f, 1.0f) }, + { Colors::Violet, ImVec4(0.5f, 0.0f, 0.9f, 1.0f) }, + { Colors::Purple, ImVec4(0.31f, 0.0f, 0.67f, 1.0f) }, + { Colors::Brown, ImVec4(0.37f, 0.18f, 0.0f, 1.0f) }, + { Colors::LightGray, ImVec4(0.75f, 0.75f, 0.75f, 1.0f) }, + { Colors::Gray, ImVec4(0.45f, 0.45f, 0.45f, 1.0f) }, + { Colors::DarkGray, ImVec4(0.15f, 0.15f, 0.15f, 1.0f) }, + { Colors::Black, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)}, + { Colors::White, ImVec4(1.0f, 1.0f, 1.0f, 1.0f) }, + { Colors::NoColor, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)}, + }; + + namespace Sizes { + const ImVec2 Inline = ImVec2(0.0f, 0.0f); + const ImVec2 Fill = ImVec2(-1.0f, 0.0f); + } + + enum LabelPosition { + Near, + Far, + Above, + None, + Within, + }; + + enum ComponentAlignment { + Left, + Right, + }; + + struct WidgetOptions{ + const char* tooltip = ""; + bool disabled = false; + const char* disabledTooltip = ""; + Colors color = Colors::NoColor; + + WidgetOptions& Color(Colors color_) { + color = color = color_; + return *this; + } + WidgetOptions& Tooltip(const char* tooltip_) { + tooltip = tooltip_; + return *this; + } + WidgetOptions& Disabled(bool disabled_) { + disabled = disabled_; + return *this; + } + WidgetOptions& DisabledTooltip(const char* disabledTooltip_) { + disabledTooltip = disabledTooltip_; + return *this; + } + }; + + struct ButtonOptions : WidgetOptions { + ImVec2 size = Sizes::Fill; + ImVec2 padding = ImVec2(10.0f, 8.0f); + Colors color = Colors::Gray; + + ButtonOptions& Size(ImVec2 size_) { + size = size_; + return *this; + } + ButtonOptions& Padding(ImVec2 padding_) { + padding = padding_; + return *this; + } + ButtonOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + ButtonOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + }; + + struct WindowButtonOptions : WidgetOptions { + ImVec2 size = Sizes::Inline; + ImVec2 padding = ImVec2(10.0f, 8.0f); + Colors color = Colors::Gray; + bool showButton = true; + bool embedWindow = true; + + WindowButtonOptions& Size(ImVec2 size_) { + size = size_; + return *this; + } + WindowButtonOptions& Padding(ImVec2 padding_) { + padding = padding_; + return *this; + } + WindowButtonOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + WindowButtonOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + WindowButtonOptions& ShowButton(bool showButton_) { + showButton = showButton_; + return *this; + } + WindowButtonOptions& EmbedWindow(bool embedWindow_) { + embedWindow = embedWindow_; + return *this; + } + }; + + struct CheckboxOptions : WidgetOptions { + bool defaultValue = false; // Only applicable to CVarCheckbox + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Near; + Colors color = WidgetOptions::color = Colors::LightBlue; + + CheckboxOptions& DefaultValue(bool defaultValue_) { + defaultValue = defaultValue_; + return *this; + } + CheckboxOptions& ComponentAlignment(ComponentAlignment alignment_) { + alignment = alignment_; + return *this; + } + CheckboxOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + CheckboxOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + CheckboxOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + CheckboxOptions& DisabledTooltip(const char* disabledTooltip_) { + WidgetOptions::disabledTooltip = disabledTooltip_; + return *this; + } + }; + + struct ComboboxOptions : WidgetOptions { + std::unordered_map comboMap = {}; + uint32_t defaultIndex = 0; // Only applicable to CVarCombobox + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + ImGuiComboFlags flags = 0; + Colors color = Colors::LightBlue; + + ComboboxOptions& ComboMap(std::unordered_map comboMap_) { + comboMap = comboMap_; + return *this; + } + ComboboxOptions& DefaultIndex(uint32_t defaultIndex_) { + defaultIndex = defaultIndex_; + return *this; + } + ComboboxOptions& ComponentAlignment(ComponentAlignment alignment_) { + alignment = alignment_; + return *this; + } + ComboboxOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + ComboboxOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + ComboboxOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + }; + + struct IntSliderOptions : WidgetOptions { + bool showButtons = true; + const char* format = "%d"; + int32_t step = 1; + int32_t min = 1; + int32_t max = 10; + int32_t defaultValue = 1; + bool clamp = true; + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + Colors color = Colors::Gray; + ImGuiSliderFlags flags = 0; + ImVec2 size = {0,0}; + + IntSliderOptions& ShowButtons(bool showButtons_) { + showButtons = showButtons_; + return *this; + } + IntSliderOptions& Format(const char* format_) { + format = format_; + return *this; + } + IntSliderOptions& Step(int32_t step_) { + step = step_; + return *this; + } + IntSliderOptions& Min(int32_t min_) { + min = min_; + return *this; + } + IntSliderOptions& Max(int32_t max_) { + max = max_; + return *this; + } + IntSliderOptions& DefaultValue(int32_t defaultValue_) { + defaultValue = defaultValue_; + return *this; + } + IntSliderOptions& ComponentAlignment(ComponentAlignment alignment_) { + alignment = alignment_; + return *this; + } + IntSliderOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + IntSliderOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + IntSliderOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + IntSliderOptions& Size(ImVec2 size_) { + size = size_; + return *this; + } + IntSliderOptions& Clamp(bool clamp_) { + clamp = clamp_; + return *this; + } + }; + + struct FloatSliderOptions : WidgetOptions { + bool showButtons = true; + const char* format = "%f"; + float step = 0.01f; + float min = 0.01f; + float max = 10.0f; + float defaultValue = 1.0f; + bool clamp = true; + bool isPercentage = false; // Multiplies visual value by 100 + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + Colors color = Colors::Gray; + ImGuiSliderFlags flags = 0; + ImVec2 size = {0,0}; + + FloatSliderOptions& ShowButtons(bool showButtons_) { + showButtons = showButtons_; + return *this; + } + FloatSliderOptions& Format(const char* format_) { + format = format_; + return *this; + } + FloatSliderOptions& Step(float step_) { + step = step_; + return *this; + } + FloatSliderOptions& Min(float min_) { + min = min_; + return *this; + } + FloatSliderOptions& Max(float max_) { + max = max_; + return *this; + } + FloatSliderOptions& DefaultValue(float defaultValue_) { + defaultValue = defaultValue_; + return *this; + } + FloatSliderOptions& ComponentAlignment(ComponentAlignment alignment_) { + alignment = alignment_; + return *this; + } + FloatSliderOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + FloatSliderOptions& IsPercentage(bool isPercentage_ = true) { + isPercentage = isPercentage_; + format = "%.0f%%"; + min = 0.0f; + max = 1.0f; + return *this; + } + FloatSliderOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + FloatSliderOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + FloatSliderOptions& Size(ImVec2 size_) { + size = size_; + return *this; + } + FloatSliderOptions& Clamp(bool clamp_) { + clamp = clamp_; + return *this; + } + }; + + struct RadioButtonsOptions : WidgetOptions { + std::unordered_map buttonMap; + RadioButtonsOptions& ButtonMap(std::unordered_map buttonMap_) { + buttonMap = buttonMap_; + return *this; + } + RadioButtonsOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + RadioButtonsOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } }; - // MARK: - Enums + struct InputOptions : WidgetOptions { + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + Colors color = Colors::Gray; + ImVec2 size = {0,0}; + std::string placeholder = ""; + InputTypes type = InputTypes::String; + std::string defaultValue = ""; - enum class CheckboxGraphics { - Cross, - Checkmark, - None + InputOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + InputOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } + InputOptions& Size(ImVec2 size_) { + size = size_; + return *this; + } + + InputOptions& LabelPosition(LabelPosition labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + + InputOptions& PlaceholderText(std::string&& placeholder_) { + placeholder = std::move(placeholder_); + return *this; + } + + InputOptions& PlaceholderText(std::string& placeholder_) { + placeholder = placeholder_; + return *this; + } + + InputOptions& InputType(InputTypes type_) { + type = type_; + return *this; + } + + InputOptions& DefaultValue(std::string defaultValue_) { + defaultValue = defaultValue_; + return *this; + } }; - constexpr float maxSliderWidth = 260.0f; -#ifdef __SWITCH__ - constexpr float sliderButtonWidth = 42.0f; -#elif defined(__WIIU__) - constexpr float sliderButtonWidth = 60.0f; -#else - constexpr float sliderButtonWidth = 30.0f; -#endif - std::string WrappedText(const char* text, unsigned int charactersPerLine = 60); - std::string WrappedText(const std::string& text, unsigned int charactersPerLine); + void PushStyleMenu(const ImVec4& color); + void PushStyleMenu(Colors color = Colors::LightBlue); + void PopStyleMenu(); + bool BeginMenu(const char* label, Colors color = Colors::LightBlue); - void SetLastItemHoverText(const std::string& text); - void SetLastItemHoverText(const char* text); + void PushStyleMenuItem(const ImVec4& color); + void PushStyleMenuItem(Colors color = Colors::LightBlue); + void PopStyleMenuItem(); + bool MenuItem(const char* label, const char* shortcut = NULL, Colors color = Colors::LightBlue); + + void PushStyleButton(const ImVec4& color, ImVec2 padding = ImVec2(10.0f, 8.0f)); + void PushStyleButton(Colors color = Colors::Gray, ImVec2 padding = ImVec2(10.0f, 8.0f)); + void PopStyleButton(); + bool Button(const char* label, const ButtonOptions& options = {}); + bool WindowButton(const char* label, const char* cvarName, std::shared_ptr windowPtr, const WindowButtonOptions& options = {}); + + void PushStyleCheckbox(const ImVec4& color); + void PushStyleCheckbox(Colors color = Colors::LightBlue); + void PopStyleCheckbox(); + void RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash); + bool Checkbox(const char* label, bool* v, const CheckboxOptions& options = {}); + bool CVarCheckbox(const char* label, const char* cvarName, const CheckboxOptions& options = {}); + + void PushStyleCombobox(const ImVec4& color); + void PushStyleCombobox(Colors color = Colors::LightBlue); + void PopStyleCombobox(); + + void PushStyleTabs(const ImVec4& color); + void PushStyleTabs(Colors color = Colors::LightBlue); + void PopStyleTabs(); + + void PushStyleInput(const ImVec4& color); + void PushStyleInput(Colors color = Colors::LightBlue); + void PopStyleInput(); + + void PushStyleHeader(const ImVec4& color); + void PushStyleHeader(Colors color = Colors::LightBlue); + void PopStyleHeader(); + + void Spacer(float height = 0.0f); + void Separator(bool padTop = true, bool padBottom = true, float extraVerticalTopPadding = 0.0f, + float extraVerticalBottomPadding = 0.0f); + + /*using ComboVariant = std::variant&, const std::vector&>; + + bool Combobox(const char* label, int32_t* value, ComboVariant comboSource, const ComboboxOptions& options = {}) { + bool dirty = false; + float startX = ImGui::GetCursorPosX(); + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text("%s", label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text("%s", label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + float width = ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + } + if (ImGui::BeginCombo(invisibleLabel, comboMap.at(*value), options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (const auto& pair : comboMap) { + if (strlen(pair.second) > 1) { + if (ImGui::Selectable(pair.second, pair.first == *value)) { + *value = pair.first; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text("%s", label); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text("%s", label); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(startX); + ImGui::Text("%s", label); + } + } + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + bool CVarCombobox(const char* label, const char* cvarName, ComboVariant comboSource, const ComboboxOptions& options = {}) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboSource, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; + }*/ + + float CalcComboWidth(const char* preview_value, ImGuiComboFlags flags); + + template + bool Combobox(const char* label, T* value, const std::unordered_map& comboMap, const ComboboxOptions& options = {}) { + bool dirty = false; + float startX = ImGui::GetCursorPosX(); + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + + const char* longest; + int length = 0; + for (auto& [index, string] : comboMap) { + int len = strlen(string); + if (len > length) { + longest = string; + length = len; + } + } + float comboWidth = CalcComboWidth(longest, options.flags); + + ImGui::AlignTextToFramePadding(); + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Right) { + ImGui::Text(label); + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label); + } + } + } + + ImGui::SetNextItemWidth(comboWidth); + if (ImGui::BeginCombo(invisibleLabel, comboMap.at(*value), options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (const auto& pair : comboMap) { + if (strlen(pair.second) > 1) { + if (ImGui::Selectable(pair.second, pair.first == *value)) { + *value = pair.first; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text("%s", label); + } else if (options.labelPosition == LabelPosition::Far) { + float width = ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 2; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::Text("%s", label); + } + } + } + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + template + bool Combobox(const char* label, T* value, const std::vector& comboVector, const ComboboxOptions& options = {}) { + bool dirty = false; + size_t currentValueIndex = static_cast(*value); + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + + const char* longest; + int length = 0; + for (auto& string : comboVector) { + int len = strlen(string); + if (len > length) { + longest = string; + length = len; + } + } + float comboWidth = CalcComboWidth(longest, options.flags); + + ImGui::AlignTextToFramePadding(); + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Right) { + ImGui::Text("%s", label); + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text("%s", label); + } + } + } + + ImGui::SetNextItemWidth(comboWidth); + if (ImGui::BeginCombo(invisibleLabel, comboVector.at(currentValueIndex), options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (size_t i = 0; i < comboVector.size(); ++i) { + auto newValue = static_cast(i); + if (strlen(comboVector.at(i)) > 1) { + if (ImGui::Selectable(comboVector.at(i), newValue == *value)) { + *value = newValue; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text("%s", label); + } else if (options.labelPosition == LabelPosition::Far) { + float width = ImGui::CalcTextSize(comboVector.at(*value)).x + ImGui::GetStyle().FramePadding.x * 2; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::Text("%s", label); + } + } + } + + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + template + bool Combobox(const char* label, T* value, const std::vector& comboVector, const ComboboxOptions& options = {}) { + bool dirty = false; + size_t currentValueIndex = static_cast(*value); + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + + const char* longest; + int length = 0; + for (auto& string : comboVector) { + int len = string.length(); + if (len > length) { + longest = string.c_str(); + length = len; + } + } + float comboWidth = CalcComboWidth(longest, options.flags); + + ImGui::AlignTextToFramePadding(); + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Right) { + ImGui::Text("%s", label); + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text("%s", label); + } + } + } + + ImGui::SetNextItemWidth(comboWidth); + if (ImGui::BeginCombo(invisibleLabel, comboVector.at(currentValueIndex).c_str(), options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (size_t i = 0; i < comboVector.size(); ++i) { + auto newValue = static_cast(i); + if (comboVector.at(i).length() > 1) { + if (ImGui::Selectable(comboVector.at(i).c_str(), newValue == *value)) { + *value = newValue; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text("%s", label); + } else if (options.labelPosition == LabelPosition::Far) { + float width = ImGui::CalcTextSize(comboVector.at(*value).c_str()).x + ImGui::GetStyle().FramePadding.x * 2; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::Text("%s", label); + } + } + } + + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + template + bool Combobox(const char* label, T* value, const char* (&comboArray)[N], const ComboboxOptions& options = {}) { + bool dirty = false; + size_t currentValueIndex = static_cast(*value); + if (currentValueIndex >= N) { + currentValueIndex = 0; + } + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + + const char* longest; + int length = 0; + for (size_t i = 0; i < N; i++) { + int len = strlen(comboArray[i]); + if (len > length) { + longest = comboArray[i]; + length = len; + } + } + float comboWidth = CalcComboWidth(longest, options.flags); + + ImGui::AlignTextToFramePadding(); + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Right) { + ImGui::Text("%s", label); + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text("%s", label); + } + } + } + + ImGui::SetNextItemWidth(comboWidth); + if (ImGui::BeginCombo(invisibleLabel, comboArray[currentValueIndex], options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (size_t i = 0; i < N; ++i) { + auto newValue = static_cast(i); + if (strlen(comboArray[i]) > 1) { + if (ImGui::Selectable(comboArray[i], newValue == *value)) { + *value = newValue; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + + if (options.labelPosition != LabelPosition::None) { + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text("%s", label); + } else if (options.labelPosition == LabelPosition::Far) { + float width = ImGui::CalcTextSize(comboArray[*value]).x + ImGui::GetStyle().FramePadding.x * 2; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::Text("%s", label); + } + } + } + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); + } + ImGui::PopID(); + return dirty; + } + + template + bool CVarCombobox(const char* label, const char* cvarName, const std::unordered_map& comboMap, const ComboboxOptions& options = {}) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboMap, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; + } + + template + bool CVarCombobox(const char* label, const char* cvarName, const std::vector& comboVector, const ComboboxOptions& options = {}) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboVector, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; + } + + template + bool CVarCombobox(const char* label, const char* cvarName, const char* (&comboArray)[N], const ComboboxOptions& options = {}) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboArray, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + return dirty; + } + + void PushStyleSlider(Colors color = Colors::LightBlue); + void PopStyleSlider(); + bool SliderInt(const char* label, int32_t* value, const IntSliderOptions& options = {}); + bool CVarSliderInt(const char* label, const char* cvarName, const IntSliderOptions& options = {}); + bool SliderFloat(const char* label, float* value, const FloatSliderOptions& options = {}); + bool CVarSliderFloat(const char* label, const char* cvarName, const FloatSliderOptions& options = {}); + bool InputString(const char* label, std::string* value, const InputOptions& options = {}); + bool CVarInputString(const char* label, const char* cvarName, const InputOptions& options = {}); + bool InputInt(const char* label, int32_t* value, const InputOptions& options = {}); + bool CVarInputInt(const char* label, const char* cvarName, const InputOptions& options = {}); + bool CVarColorPicker(const char* label, const char* cvarName, Color_RGBA8 defaultColor, bool hasAlpha = false, uint8_t modifiers = 0, UIWidgets::Colors themeColor = UIWidgets::Colors::LightBlue); + bool RadioButton(const char* label, bool active); + bool CVarRadioButton(const char* text, const char* cvarName, int32_t id, const RadioButtonsOptions& options); + bool StateButton(const char* str_id, const char* label, ImVec2 size, UIWidgets::ButtonOptions options, ImGuiButtonFlags flags = ImGuiButtonFlags_None); + void DrawFlagArray32(const std::string& name, uint32_t& flags, Colors color = Colors::LightBlue); + void DrawFlagArray16(const std::string& name, uint16_t& flags, Colors color = Colors::LightBlue); + void DrawFlagArray8(const std::string& name, uint8_t& flags, Colors color = Colors::LightBlue); + void DrawFlagArray8Mask(const std::string& name, uint8_t& flags, Colors color = Colors::LightBlue); void InsertHelpHoverText(const std::string& text); void InsertHelpHoverText(const char* text); - - void Tooltip(const char* text); - void Spacer(float height); - void PaddedSeparator(bool padTop = true, bool padBottom = true, float extraVerticalTopPadding = 0.0f, float extraVerticalBottomPadding = 0.0f); - - void RenderCross(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz); - bool CustomCheckbox(const char* label, bool* v, bool disabled, CheckboxGraphics disabledGraphic, bool renderCrossWhenOff = false); - bool CustomCheckboxTristate(const char* label, int* v, bool disabled, CheckboxGraphics disabledGraphic); - - void ReEnableComponent(const char* disabledTooltipText); - void DisableComponent(const float alpha); - - bool EnhancementCheckbox(const char* text, const char* cvarName, bool disabled = false, const char* disabledTooltipText = "", CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false); - bool EnhancementCheckboxTristate(const char* text, const char* cvarName, bool disabled = false, const char* disabledTooltipText = "", CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false); - bool PaddedEnhancementCheckbox(const char* text, const char* cvarName, bool padTop = true, bool padBottom = true, bool disabled = false, const char* disabledTooltipText = "", CheckboxGraphics disabledGraphic = CheckboxGraphics::Cross, bool defaultValue = false); - - bool EnhancementCombobox(const char* cvarName, std::span comboArray, uint8_t defaultIndex, bool disabled = false, const char* disabledTooltipText = "", uint8_t disabledValue = -1); - bool LabeledRightAlignedEnhancementCombobox(const char* label, const char* cvarName, std::span comboArray, uint8_t defaultIndex, bool disabled = false, const char* disabledTooltipText = "", uint8_t disabledValue = -1); - - void PaddedText(const char* text, bool padTop = true, bool padBottom = true); - - bool EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue = 0, bool PlusMinusButton = true, bool disabled = false, const char* disabledTooltipText = ""); - bool PaddedEnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue = 0, bool PlusMinusButton = true, bool padTop = true, bool padBottom = true, bool disabled = false, const char* disabledTooltipText = ""); - bool EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton = true, bool disabled = false, const char* disabledTooltipText = ""); - bool PaddedEnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton = true, bool padTop = true, bool padBottom = true, bool disabled = false, const char* disabledTooltipText = ""); - - bool EnhancementRadioButton(const char* text, const char* cvarName, int id); - - bool DrawResetColorButton(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha); - bool DrawRandomizeColorButton(const char* cvarName, ImVec4* colors); - void DrawLockColorCheckbox(const char* cvarName); - void RainbowColor(const char* cvarName, ImVec4* colors); - - void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha); - bool EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors, bool allow_rainbow = true, bool has_alpha = false, bool TitleSameLine = false); - - void DrawFlagArray32(const std::string& name, uint32_t& flags); - void DrawFlagArray16(const std::string& name, uint16_t& flags); - void DrawFlagArray8(const std::string& name, uint8_t& flags); - bool StateButton(const char* str_id, const char* label); - bool InputString(const char* label, std::string* value); } +ImVec4 GetRandomValue(); + +Color_RGBA8 RGBA8FromVec(ImVec4 vec); +ImVec4 VecFromRGBA8(Color_RGBA8 color); #endif /* UIWidgets_hpp */ diff --git a/soh/soh/config/ConfigMigrators.h b/soh/soh/config/ConfigMigrators.h index 29cbb0774..008d5692a 100644 --- a/soh/soh/config/ConfigMigrators.h +++ b/soh/soh/config/ConfigMigrators.h @@ -16,7 +16,7 @@ namespace SOH { std::vector version3Migrations = { { MigrationAction::Rename, "gSwitchAge", "gGeneral.SwitchAge" }, - { MigrationAction::Rename, "gFrameAdvance", "gGeneral.FrameAdvance" }, + { MigrationAction::Rename, "gFrameAdvance", "gDeveloperTools.FrameAdvanceTick" }, { MigrationAction::Rename, "gRandoGenerating", "gGeneral.RandoGenerating" }, { MigrationAction::Rename, "gNewSeedGenerated", "gGeneral.NewSeedGenerated" }, { MigrationAction::Rename, "gOnFileSelectNameEntry", "gGeneral.OnFileSelectNameEntry" }, @@ -42,14 +42,12 @@ namespace SOH { { MigrationAction::Rename, "gCheckTrackerSettingsEnabled", "gOpenWindows.CheckTrackerSettings" }, { MigrationAction::Rename, "gCollisionViewerEnabled", "gOpenWindows.CollisionViewer" }, { MigrationAction::Rename, "gCosmeticsEditorEnabled", "gOpenWindows.CosmeticsEditor" }, - { MigrationAction::Rename, "gDLViewerEnabled", "gOpenWindows.DLViewer" }, + { MigrationAction::Rename, "gDLViewerEnabled", "gOpenWindows.DisplayListViewer" }, { MigrationAction::Rename, "gEntranceTrackerEnabled", "gOpenWindows.EntranceTracker" }, { MigrationAction::Rename, "gGameplayStatsEnabled", "gOpenWindows.GameplayStats" }, { MigrationAction::Rename, "gItemTrackerEnabled", "gOpenWindows.ItemTracker" }, { MigrationAction::Rename, "gItemTrackerSettingsEnabled", "gOpenWindows.ItemTrackerSettings" }, { MigrationAction::Rename, "gMessageViewerEnabled", "gOpenWindows.MessageViewer" }, - { MigrationAction::Rename, "gOpenWindows.InputViewer", "gOpenWindows.InputViewer" }, - { MigrationAction::Rename, "gOpenWindows.InputViewerSettings", "gOpenWindows.InputViewerSettings" }, { MigrationAction::Rename, "gRandomizerSettingsEnabled", "gOpenWindows.RandomizerSettings" }, { MigrationAction::Rename, "gSaveEditorEnabled", "gOpenWindows.SaveEditor" }, { MigrationAction::Rename, "gValueViewer.WindowOpen", "gOpenWindows.ValueViewer" }, diff --git a/soh/soh/cvar_prefixes.h b/soh/soh/cvar_prefixes.h index 6ddde97bd..4b8d9f93a 100644 --- a/soh/soh/cvar_prefixes.h +++ b/soh/soh/cvar_prefixes.h @@ -7,11 +7,11 @@ #define CVAR_SETTING(var) CVAR_PREFIX_SETTING "." var #define CVAR_WINDOW(var) CVAR_PREFIX_WINDOW "." var #define CVAR_TRACKER(var) CVAR_PREFIX_TRACKER "." var -#define CVAR_TRACKER_ITEM(var) CVAR_TRACKER(".ItemTracker." var) -#define CVAR_TRACKER_CHECK(var) CVAR_TRACKER(".CheckTracker." var) -#define CVAR_TRACKER_ENTRANCE(var) CVAR_TRACKER(".EntranceTracker." var) +#define CVAR_TRACKER_ITEM(var) CVAR_TRACKER("ItemTracker." var) +#define CVAR_TRACKER_CHECK(var) CVAR_TRACKER("CheckTracker." var) +#define CVAR_TRACKER_ENTRANCE(var) CVAR_TRACKER("EntranceTracker." var) #define CVAR_DEVELOPER_TOOLS(var) CVAR_PREFIX_DEVELOPER_TOOLS "." var #define CVAR_GENERAL(var) CVAR_PREFIX_GENERAL "." var #define CVAR_REMOTE(var) CVAR_PREFIX_REMOTE "." var -#define CVAR_REMOTE_CROWD_CONTROL(var) CVAR_REMOTE(".CrowdControl." var) -#define CVAR_REMOTE_SAIL(var) CVAR_REMOTE(".Sail." var) \ No newline at end of file +#define CVAR_REMOTE_CROWD_CONTROL(var) CVAR_REMOTE("CrowdControl." var) +#define CVAR_REMOTE_SAIL(var) CVAR_REMOTE("Sail." var) \ No newline at end of file diff --git a/soh/src/code/audio_playback.c b/soh/src/code/audio_playback.c index 430c463f8..7e334a919 100644 --- a/soh/src/code/audio_playback.c +++ b/soh/src/code/audio_playback.c @@ -95,7 +95,7 @@ void Audio_InitNoteSub(Note* note, NoteSubEu* sub, NoteSubAttributes* attrs) { vel = 0.0f > vel ? 0.0f : vel; vel = 1.0f < vel ? 1.0f : vel; - float master_vol = (float)CVarGetInteger(CVAR_SETTING("Volume.Master"), 100) / 100.0f; + float master_vol = (float)CVarGetInteger(CVAR_SETTING("Volume.Master"), 40) / 100.0f; sub->targetVolLeft = (s32)((vel * volLeft) * (0x1000 - 0.001f)) * master_vol; sub->targetVolRight = (s32)((vel * volRight) * (0x1000 - 0.001f)) * master_vol; diff --git a/soh/src/code/z_frame_advance.c b/soh/src/code/z_frame_advance.c index 6fcd76ae7..7f9f26fda 100644 --- a/soh/src/code/z_frame_advance.c +++ b/soh/src/code/z_frame_advance.c @@ -18,10 +18,11 @@ s32 FrameAdvance_Update(FrameAdvanceContext* frameAdvCtx, Input* input) { frameAdvCtx->enabled = !frameAdvCtx->enabled; } - if (!frameAdvCtx->enabled || CVarGetInteger(CVAR_GENERAL("FrameAdvance"), 0) || (CHECK_BTN_ALL(input->cur.button, BTN_Z) && + if (!frameAdvCtx->enabled || CVarGetInteger(CVAR_DEVELOPER_TOOLS("FrameAdvanceTick"), 0) || + (CHECK_BTN_ALL(input->cur.button, BTN_Z) && (CHECK_BTN_ALL(input->press.button, BTN_R) || (CHECK_BTN_ALL(input->cur.button, BTN_R) && (++frameAdvCtx->timer >= 9))))) { - CVarClear(CVAR_GENERAL("FrameAdvance")); + CVarClear(CVAR_DEVELOPER_TOOLS("FrameAdvanceTick")); frameAdvCtx->timer = 0; return true; } From 5b622683f4a24cfbdf2b43617dac5de64e9aff5d Mon Sep 17 00:00:00 2001 From: Malkierian Date: Sun, 16 Mar 2025 13:51:52 -0700 Subject: [PATCH 02/53] Clean up Push/Pop font calls in check and item trackers by adding them instead to Begin/EndFloatWindows functions. (#5122) Add missing PopFont call to entrance tracker. --- .../Enhancements/randomizer/randomizer_check_tracker.cpp | 8 ++------ .../randomizer/randomizer_entrance_tracker.cpp | 1 + .../Enhancements/randomizer/randomizer_item_tracker.cpp | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index cc139fbfd..a60865de4 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -876,8 +876,6 @@ void CheckTrackerWindow::DrawElement() { hideShopUnshuffledChecks = CVarGetInteger(CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"), 1); alwaysShowGS = CVarGetInteger(CVAR_TRACKER_CHECK("AlwaysShowGSLocs"), 0); - - ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); if (CVarGetInteger(CVAR_TRACKER_CHECK("WindowType"), TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) { if (CVarGetInteger(CVAR_TRACKER_CHECK("ShowOnlyPaused"), 0) && (gPlayState == nullptr || gPlayState->pauseCtx.state == 0)) { return; @@ -901,7 +899,6 @@ void CheckTrackerWindow::DrawElement() { if (!GameInteractor::IsSaveLoaded() || !initialized) { ImGui::Text("Waiting for file load..."); //TODO Language - ImGui::PopFont(); EndFloatWindows(); return; } @@ -920,7 +917,6 @@ void CheckTrackerWindow::DrawElement() { ImVec2 size = ImGui::GetContentRegionMax(); size.y -= headerHeight; if (!ImGui::BeginTable("Check Tracker", 1, 0, size)) { - ImGui::PopFont(); EndFloatWindows(); return; } @@ -965,7 +961,6 @@ void CheckTrackerWindow::DrawElement() { size = ImGui::GetContentRegionAvail(); if (!ImGui::BeginTable("CheckTracker##Checks", 1, ImGuiTableFlags_ScrollY, size)) { ImGui::EndTable(); - ImGui::PopFont(); EndFloatWindows(); return; } @@ -1081,7 +1076,6 @@ void CheckTrackerWindow::DrawElement() { ImGui::EndTable(); //Checks Lead-out ImGui::EndTable(); //Quick Options Lead-out - ImGui::PopFont(); EndFloatWindows(); if (doingCollapseOrExpand) { optCollapseAll = false; @@ -1147,12 +1141,14 @@ void BeginFloatWindows(std::string UniqueName, bool& open, ImGuiWindowFlags flag Color_Background.b / 255.0f, Color_Background.a / 255.0f)); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::Begin(UniqueName.c_str(), &open, windowFlags); } void EndFloatWindows() { ImGui::PopStyleVar(); ImGui::PopStyleColor(); ImGui::PopStyleColor(); + ImGui::PopFont(); ImGui::End(); } diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp index 7a151a983..49755ff06 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp @@ -740,6 +740,7 @@ void EntranceTrackerWindow::DrawElement() { if (!ImGui::Begin("Entrance Tracker", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { ImGui::End(); + ImGui::PopFont(); return; } diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index 828fcda2d..9bb562b31 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -952,12 +952,14 @@ void BeginFloatingWindows(std::string UniqueName, ImGuiWindowFlags flags = 0) { ImGui::PushStyleColor(ImGuiCol_WindowBg, VecFromRGBA8(CVarGetColor(CVAR_TRACKER_ITEM("BgColor.Value"), {0, 0, 0, 0}))); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::Begin(UniqueName.c_str(), nullptr, windowFlags); } void EndFloatingWindows() { ImGui::PopStyleVar(); ImGui::PopStyleColor(); ImGui::PopStyleColor(); + ImGui::PopFont(); ImGui::End(); } @@ -1222,7 +1224,6 @@ void ItemTrackerWindow::Draw() { } void ItemTrackerWindow::DrawElement() { - ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); UpdateVectors(); int iconSize = CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36); @@ -1353,7 +1354,6 @@ void ItemTrackerWindow::DrawElement() { EndFloatingWindows(); } } - ImGui::PopFont(); } static std::unordered_map itemTrackerCapacityTrackOptions = { From a97f853a140b06cd344a855edaec543f08b46b9c Mon Sep 17 00:00:00 2001 From: Christopher Leggett Date: Sun, 16 Mar 2025 16:55:14 -0400 Subject: [PATCH 03/53] Prevent Big Octo one point cutscene from playing twice. (#5113) --- .../SkipMiscInteractions/SkipChildRutoInteractions.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/SkipChildRutoInteractions.cpp b/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/SkipChildRutoInteractions.cpp index 7d676e6f8..2894cb914 100644 --- a/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/SkipChildRutoInteractions.cpp +++ b/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/SkipChildRutoInteractions.cpp @@ -65,7 +65,12 @@ void SkipChildRutoInteractions_Register() { enRu1->action = 42; Animation_Change(&enRu1->skelAnime, (AnimationHeader*)&gRutoChildWait2Anim, 1.0f, 0, Animation_GetLastFrame((void*)&gRutoChildWait2Anim), ANIMMODE_LOOP, -8.0f); - enRu1->unk_28C->cameraSetting = 1; + // If we aren't skipping one point cutscenes and BgBdan objects has set the camera setting + // to CAM_SET_NORMAL1 (2), don't reset the camera setting to 1. This prevents the One Point + // Cutscene of Ruto getting lifted up from getting queued up twice. + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO) || enRu1->unk_28C->cameraSetting != 2) { + enRu1->unk_28C->cameraSetting = 1; + } Actor* sapphire = func_80AEB124(gPlayState); if (sapphire != NULL) { Actor_Kill(sapphire); From 797fe2d9893ad58b51162b6a16a413ee78d17ef5 Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Sun, 16 Mar 2025 21:58:04 +0100 Subject: [PATCH 04/53] Fix Ganon's Tower entrance logic (#5115) * Fix tower * Update fire_temple.cpp --- .../location_access/dungeons/fire_temple.cpp | 2 +- .../location_access/dungeons/ganons_castle.cpp | 15 +++++++++++---- soh/soh/Enhancements/randomizer/randomizerTypes.h | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp index 1e7214a3d..a17cfc598 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp @@ -357,7 +357,7 @@ void RegionTable_Init_FireTemple() { areaTable[RR_FIRE_TEMPLE_MQ_FIRST_ROOM_UPPER] = Region("Fire Temple MQ First Room Upper", "Fire Temple", {RA_FIRE_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits Entrance(RR_FIRE_TEMPLE_MQ_FIRST_ROOM_LOWER, []{return true;}), - Entrance(RR_FIRE_TEMPLE_NEAR_BOSS_ROOM, []{return logic->HasFireSource();}), + Entrance(RR_FIRE_TEMPLE_MQ_NEAR_BOSS_ROOM, []{return logic->HasFireSource();}), Entrance(RR_FIRE_TEMPLE_MQ_BIG_LAVA_ROOM, []{return Here(RR_FIRE_TEMPLE_MQ_FIRST_ROOM_UPPER, []{return logic->CanUse(RG_MEGATON_HAMMER);});}), }); diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp index 2a7301701..982ac16fc 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp @@ -28,7 +28,7 @@ void RegionTable_Init_GanonsCastle() { Entrance(RR_GANONS_CASTLE_SHADOW_TRIAL, []{return true;}), Entrance(RR_GANONS_CASTLE_SPIRIT_TRIAL, []{return true;}), Entrance(RR_GANONS_CASTLE_LIGHT_TRIAL, []{return logic->CanUse(RG_GOLDEN_GAUNTLETS);}), - Entrance(RR_GANONS_TOWER_FLOOR_1, []{return (logic->ForestTrialClear || ctx->GetTrial(TK_FOREST_TRIAL)->IsSkipped()) && + Entrance(RR_GANONS_TOWER_ENTRYWAY, []{return (logic->ForestTrialClear || ctx->GetTrial(TK_FOREST_TRIAL)->IsSkipped()) && (logic->FireTrialClear || ctx->GetTrial(TK_FIRE_TRIAL)->IsSkipped()) && (logic->WaterTrialClear || ctx->GetTrial(TK_WATER_TRIAL)->IsSkipped()) && (logic->ShadowTrialClear || ctx->GetTrial(TK_SHADOW_TRIAL)->IsSkipped()) && @@ -162,7 +162,7 @@ void RegionTable_Init_GanonsCastle() { Entrance(RR_GANONS_CASTLE_MQ_SPIRIT_TRIAL_CHAIRS_ROOM, []{return true;}), Entrance(RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_DINOLFOS_ROOM, []{return Here(RR_GANONS_CASTLE_MQ_MAIN, []{return logic->CanUse(RG_GOLDEN_GAUNTLETS);});}), //RANDOTODO could we just set these events automatically based on the setting? - Entrance(RR_GANONS_TOWER_FLOOR_1, []{return (logic->ForestTrialClear || ctx->GetTrial(TK_FOREST_TRIAL)->IsSkipped()) && + Entrance(RR_GANONS_TOWER_ENTRYWAY, []{return (logic->ForestTrialClear || ctx->GetTrial(TK_FOREST_TRIAL)->IsSkipped()) && (logic->FireTrialClear || ctx->GetTrial(TK_FIRE_TRIAL)->IsSkipped()) && (logic->WaterTrialClear || ctx->GetTrial(TK_WATER_TRIAL)->IsSkipped()) && (logic->ShadowTrialClear || ctx->GetTrial(TK_SHADOW_TRIAL)->IsSkipped()) && @@ -431,10 +431,17 @@ void RegionTable_Init_GanonsCastle() { #pragma region Tower and Escape + areaTable[RR_GANONS_TOWER_ENTRYWAY] = Region("Ganon's Tower Entryway", "Ganons Castle", {RA_GANONS_CASTLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(RR_GANONS_CASTLE_LOBBY, []{return ctx->GetDungeon(GANONS_CASTLE)->IsVanilla();}), + Entrance(RR_GANONS_CASTLE_MQ_MAIN, []{return ctx->GetDungeon(GANONS_CASTLE)->IsMQ();}), + Entrance(RR_GANONS_TOWER_FLOOR_1, []{return true;}), + }); + areaTable[RR_GANONS_TOWER_FLOOR_1] = Region("Ganon's Tower Floor 1", "Ganons Castle", {RA_GANONS_CASTLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits - Entrance(RR_GANONS_CASTLE_LOBBY, []{return Here(RR_GANONS_TOWER_FLOOR_1, []{return logic->CanKillEnemy(RE_DINOLFOS, ED_CLOSE, true, 2);});}), - Entrance(RR_GANONS_TOWER_FLOOR_2, []{return Here(RR_GANONS_TOWER_FLOOR_1, []{return logic->CanKillEnemy(RE_DINOLFOS, ED_CLOSE, true, 2);});}), + Entrance(RR_GANONS_TOWER_ENTRYWAY, []{return Here(RR_GANONS_TOWER_FLOOR_1, []{return logic->CanKillEnemy(RE_DINOLFOS, ED_CLOSE, true, 2);});}), + Entrance(RR_GANONS_TOWER_FLOOR_2, []{return Here(RR_GANONS_TOWER_FLOOR_1, []{return logic->CanKillEnemy(RE_DINOLFOS, ED_CLOSE, true, 2);});}), }); areaTable[RR_GANONS_TOWER_FLOOR_2] = Region("Ganon's Tower Floor 2", "Ganons Castle", {RA_GANONS_CASTLE}, NO_DAY_NIGHT_CYCLE, {}, { diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index 155b31db1..a418627ef 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -988,7 +988,6 @@ typedef enum { RR_GANONS_CASTLE_SHADOW_TRIAL, RR_GANONS_CASTLE_SPIRIT_TRIAL, RR_GANONS_CASTLE_LIGHT_TRIAL, - RR_GANONS_CASTLE_TOWER, RR_GANONS_CASTLE_MQ_LOBBY, RR_GANONS_CASTLE_MQ_MAIN, @@ -1017,6 +1016,7 @@ typedef enum { RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_BOULDER_ROOM_BACK, RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_FINAL_ROOM, + RR_GANONS_TOWER_ENTRYWAY, RR_GANONS_TOWER_FLOOR_1, RR_GANONS_TOWER_FLOOR_2, RR_GANONS_TOWER_FLOOR_3, From 112fab503ff0d427ee211a04d012bb3beec649fe Mon Sep 17 00:00:00 2001 From: Pepper0ni <93387759+Pepper0ni@users.noreply.github.com> Date: Sun, 16 Mar 2025 21:00:31 +0000 Subject: [PATCH 05/53] Fix oversight in HC storms grotto (#5120) --- .../randomizer/location_access/overworld/castle_grounds.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/randomizer/location_access/overworld/castle_grounds.cpp b/soh/soh/Enhancements/randomizer/location_access/overworld/castle_grounds.cpp index c4dcf4f02..b4b77056b 100644 --- a/soh/soh/Enhancements/randomizer/location_access/overworld/castle_grounds.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/overworld/castle_grounds.cpp @@ -79,7 +79,7 @@ void RegionTable_Init_CastleGrounds() { LOCATION(RC_HC_STORMS_GROTTO_POT_4, logic->CanBreakPots()), }, { //Exits - Entrance(RR_CASTLE_GROUNDS, []{return true;}), + Entrance(RR_HC_STORMS_GROTTO, []{return true;}), }); areaTable[RR_GANONS_CASTLE_GROUNDS] = Region("Ganon's Castle Grounds", "Castle Grounds", {RA_OUTSIDE_GANONS_CASTLE}, NO_DAY_NIGHT_CYCLE, { From 05ab17d3a8fe26600ee4cee032de1c54d2c024f4 Mon Sep 17 00:00:00 2001 From: Malkierian Date: Sun, 16 Mar 2025 15:00:30 -0700 Subject: [PATCH 06/53] Reimplement ImGui Scaling option. (#5124) --- soh/soh/SohGui/SohMenuBar.cpp | 20 -------------------- soh/soh/SohGui/SohMenuSettings.cpp | 10 ++++++++++ 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/soh/soh/SohGui/SohMenuBar.cpp b/soh/soh/SohGui/SohMenuBar.cpp index 18fa8b53d..ed63fea7b 100644 --- a/soh/soh/SohGui/SohMenuBar.cpp +++ b/soh/soh/SohGui/SohMenuBar.cpp @@ -68,8 +68,6 @@ static std::unordered_map windowBackendNames = { Ship::WindowBackend::FAST3D_SDL_METAL, "Metal" }, }; -static const char* imguiScaleOptions[4] = { "Small", "Normal", "Large", "X-Large" }; - static const char* filters[3] = { #ifdef __WIIU__ "", @@ -89,24 +87,6 @@ Ship::WindowBackend configWindowBackend; extern std::shared_ptr mGfxDebuggerWindow; void DrawSettingsMenu() { - //if (ImGui::BeginMenu("Settings")) - //{ - // if (ImGui::BeginMenu("Graphics")) { - // ImGui::Text("ImGui Menu Scale"); - // ImGui::SameLine(); - // ImGui::TextColored({ 0.85f, 0.35f, 0.0f, 1.0f }, "(Experimental)"); - // if (UIWidgets::EnhancementCombobox(CVAR_SETTING("ImGuiScale"), imguiScaleOptions, 1)) { - // OTRGlobals::Instance->ScaleImGui(); - // } - // UIWidgets::Tooltip("Changes the scaling of the ImGui menu elements."); - - // // Draw LUS settings menu (such as Overlays Text Font) - // Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()->DrawSettings(); - - // ImGui::EndMenu(); - // } - // ImGui::EndMenu(); - //} } void SohMenuBar::InitElement() { diff --git a/soh/soh/SohGui/SohMenuSettings.cpp b/soh/soh/SohGui/SohMenuSettings.cpp index a9103377b..5a186b0ae 100644 --- a/soh/soh/SohGui/SohMenuSettings.cpp +++ b/soh/soh/SohGui/SohMenuSettings.cpp @@ -15,6 +15,7 @@ namespace SohGui { extern std::shared_ptr mSohMenu; using namespace UIWidgets; static std::unordered_map languages = {{ LANGUAGE_ENG, "English" }, { LANGUAGE_GER, "German" }, { LANGUAGE_FRA, "French" }}; +static std::unordered_map imguiScaleOptions = {{ 0, "Small" }, { 1, "Normal" }, { 2, "Large" }, { 3, "X-Large" }}; const char* GetGameVersionString(uint32_t index) { uint32_t gameVersion = ResourceMgr_GetGameVersion(index); @@ -115,6 +116,15 @@ void SohMenu::AddMenuSettings() { AddWidget(path, "Disable Idle Camera Re-Centering", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("A11yDisableIdleCam")) .Options(CheckboxOptions().Tooltip("Disables the automatic re-centering of the camera when idle.")); + AddWidget(path, "EXPERIMENTAL", WIDGET_SEPARATOR_TEXT) + .Options(WidgetOptions().Color(Colors::Orange)); + AddWidget(path, "ImGui Menu Scaling", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_SETTING("ImGuiScale")) + .Options(ComboboxOptions().ComboMap(imguiScaleOptions).Tooltip("Changes the scaling of the ImGui menu elements.").DefaultIndex(1) + .ComponentAlignment(ComponentAlignment::Right).LabelPosition(LabelPosition::Far)) + .Callback([](WidgetInfo& info) { + OTRGlobals::Instance->ScaleImGui(); + }); // General - About path.column = SECTION_COLUMN_2; From f3b3e0f5ebfaa5e9160edc23e9ba2c7d80c1cb2c Mon Sep 17 00:00:00 2001 From: aMannus Date: Sun, 16 Mar 2025 23:00:47 +0100 Subject: [PATCH 07/53] Use percentage of screen size as padding (#5123) --- soh/soh/SohGui/Menu.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/soh/soh/SohGui/Menu.cpp b/soh/soh/SohGui/Menu.cpp index a10fc4e06..e130d5b75 100644 --- a/soh/soh/SohGui/Menu.cpp +++ b/soh/soh/SohGui/Menu.cpp @@ -558,14 +558,13 @@ void Menu::DrawElement() { } // Full screen menu with widths below 1280, heights below 800. - // Up to 100 pixel padding when up to 1700 width, 1050 height. - // Everything above that, fixed size of 1600x950. + // 5% of screen width/height padding on both sides above those resolutions. ImVec2 menuSize = { std::fminf(1280, windowWidth), std::fminf(800, windowHeight) }; - if (windowWidth > 1380) { - menuSize.x = std::fminf(1600, windowWidth - 100); + if (windowWidth > 1280) { + menuSize.x = floor(windowWidth * 0.9); } - if (windowHeight > 900) { - menuSize.y = std::fminf(950, windowHeight - 100); + if (windowHeight > 800) { + menuSize.y = floor(windowHeight * 0.9); } pos += window->WorkRect.GetSize() / 2 - menuSize / 2; From a6163ca2e815c98e348f8f316653eaf049e6c0d6 Mon Sep 17 00:00:00 2001 From: Malkierian Date: Sun, 16 Mar 2025 23:36:06 -0700 Subject: [PATCH 08/53] Fixes skip get item prefix in hook handlers and the space in ClimbSpeed CVar. (#5127) Also moves Item Scale option into rando enhancements to match skip get item animation, as it's directly tied. --- soh/soh/Enhancements/randomizer/hook_handlers.cpp | 8 ++++---- soh/soh/SohGui/SohMenuEnhancements.cpp | 15 +-------------- soh/soh/SohGui/SohMenuRandomizer.cpp | 13 +++++++++++++ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index f48b7da5a..aa7bf806c 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -298,9 +298,9 @@ void RandomizerOnPlayerUpdateForRCQueueHandler() { // Always show ItemGet animation for ice traps !(getItemEntry.modIndex == MOD_RANDOMIZER && getItemEntry.getItemId == RG_ICE_TRAP) && ( - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED) == SGIA_ALL || + CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED) == SGIA_ALL || ( - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED) == SGIA_JUNK && + CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED) == SGIA_JUNK && ( getItemEntry.getItemCategory == ITEM_CATEGORY_JUNK || getItemEntry.getItemCategory == ITEM_CATEGORY_SKULLTULA_TOKEN || @@ -411,7 +411,7 @@ void EnExItem_WaitForObjectRandomized(EnExItem* enExItem, PlayState* play) { } void EnItem00_DrawRandomizedItem(EnItem00* enItem00, PlayState* play) { - f32 mtxScale = CVarGetFloat(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimationScale"), 10.0f); + f32 mtxScale = CVarGetFloat(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationScale"), 10.0f); Matrix_Scale(mtxScale, mtxScale, mtxScale, MTXMODE_APPLY); GetItemEntry randoItem = enItem00->itemEntry; if (CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle"), 0) && @@ -783,7 +783,7 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l } case VB_PLAY_SLOW_CHEST_CS: { // We force fast chests if SkipGetItemAnimation is enabled because the camera in the CS looks pretty wonky otherwise - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED)) { + if (CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED)) { *should = false; } break; diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 21a90d70b..5f4961279 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -541,19 +541,6 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip( "Skip the tower escape sequence between Ganondorf and Ganon." )); - AddWidget(path, "Item Scale: %.2f", WIDGET_CVAR_SLIDER_FLOAT) - .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimationScale")) - .PreFunc([](WidgetInfo& info) { - info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED) == SGIA_DISABLED; - }) - .Options(FloatSliderOptions() - .Min(5.0f) - .Max(15.0f) - .Format("%.2f") - .DefaultValue(10.0f) - .Tooltip( - "The size of the item when it is picked up" - )); AddWidget(path, "Text", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Skip Forced Dialog", WIDGET_CVAR_CHECKBOX) @@ -727,7 +714,7 @@ void SohMenu::AddMenuEnhancements() { .Format("%.2fx") ); AddWidget(path, "Vine/Ladder Climb Speed +%d", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("Climb Speed")) + .CVar(CVAR_ENHANCEMENT("ClimbSpeed")) .Options(IntSliderOptions() .Min(0) .Max(12) diff --git a/soh/soh/SohGui/SohMenuRandomizer.cpp b/soh/soh/SohGui/SohMenuRandomizer.cpp index e6399baab..a022d6c40 100644 --- a/soh/soh/SohGui/SohMenuRandomizer.cpp +++ b/soh/soh/SohGui/SohMenuRandomizer.cpp @@ -72,6 +72,19 @@ void SohMenu::AddMenuRandomizer() { AddWidget(path, "Skip Get Item Animations", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation")) .Options(ComboboxOptions().ComboMap(skipGetItemAnimationOptions).DefaultIndex(SGIA_JUNK)); + AddWidget(path, "Item Scale: %.2f", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationScale")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED) == SGIA_DISABLED; + }) + .Options(FloatSliderOptions() + .Min(5.0f) + .Max(15.0f) + .Format("%.2f") + .DefaultValue(10.0f) + .Tooltip( + "The size of the item when it is picked up" + )); // Plandomizer path.sidebarName = "Plandomizer"; From c18d42761926143c278b05bc6306adb6e1d57a91 Mon Sep 17 00:00:00 2001 From: Malkierian Date: Mon, 17 Mar 2025 12:13:35 -0700 Subject: [PATCH 09/53] Setup F11 to toggle the fullscreen CVar to keep it synced with fullscreen state. (#5128) --- soh/soh/OTRGlobals.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 0b8d0030a..86d7d9382 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1369,6 +1369,10 @@ extern "C" void Graph_StartFrame() { break; } #endif + case KbScancode::LUS_KB_F11: { + CVarSetInteger(CVAR_SETTING("Fullscreen"), !CVarGetInteger(CVAR_SETTING("Fullscreen"), 0)); + break; + } case KbScancode::LUS_KB_TAB: { CVarSetInteger(CVAR_ENHANCEMENT("AltAssets"), !CVarGetInteger(CVAR_ENHANCEMENT("AltAssets"), 0)); break; From a95883b6b3c1c64ca9b57dd28ccd70086c3c5167 Mon Sep 17 00:00:00 2001 From: PurpleHato Date: Mon, 17 Mar 2025 20:15:53 +0100 Subject: [PATCH 10/53] Multiple Rando Text related stuff (#5125) * Multiple Rando Text related stuff - Mostly french rework / completion of missing lines - Removal of a duplicate entry for Richard's house overworld key - Correct naming for the English pond - Addition of missing text in English and french * Txeak uncessary spaces --- .../3drando/hint_list/hint_list_item.cpp | 26 +-- soh/soh/Enhancements/randomizer/item_list.cpp | 2 +- .../Enhancements/randomizer/randomizer.cpp | 220 +++++++++--------- 3 files changed, 122 insertions(+), 126 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/3drando/hint_list/hint_list_item.cpp b/soh/soh/Enhancements/randomizer/3drando/hint_list/hint_list_item.cpp index 867da2b2c..c786dd8de 100644 --- a/soh/soh/Enhancements/randomizer/3drando/hint_list/hint_list_item.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/hint_list/hint_list_item.cpp @@ -2070,23 +2070,23 @@ void StaticData::HintTable_Init_Item() { CustomMessage("a rightward tone", /*german*/"ein rechtsseitiger Ton", /*french*/"une tonalité vers la droite")}); // /*spanish*/un tono hacia la derecha - hintTextTable[RHT_FISHING_POLE] = HintText(CustomMessage("a fishing pole", /*german*/"eine Angelrute", /*french*/"canne à pêche"), + hintTextTable[RHT_FISHING_POLE] = HintText(CustomMessage("a fishing pole", /*german*/"eine Angelrute", /*french*/"une canne à pêche"), // /*spanish*/caña de pescar { - CustomMessage("the pond owner's property", /*german*/"der Besitz des Teicheigners", /*french*/"(canne à pêche)") + CustomMessage("the pond owner's property", /*german*/"der Besitz des Teicheigners", /*french*/"(un truc qui appartient au propriétaire de l'étang)") // /*spanish*/(caña de pescar) }, { - CustomMessage("a fish-puller", /*german*/"ein Fischzieher", /*french*/"(canne à pêche)")}); + CustomMessage("a fish-puller", /*german*/"ein Fischzieher", /*french*/"(un aimant à poisson)")}); // /*spanish*/(caña de pescar) - hintTextTable[RHT_BOMBCHU_BAG] = HintText(CustomMessage("Bombchu Bag", /*german*/"!!!", /*french*/"!!!"), + hintTextTable[RHT_BOMBCHU_BAG] = HintText(CustomMessage("Bombchu Bag", /*german*/"!!!", /*french*/"un Sac de Missiles Teigneux"), { CustomMessage("explosives", /*german*/"ein Explosivpaket", /*french*/"un paquet d'explosifs"), // /*spanish*/un montón de explosivos CustomMessage("something that can remove boulders", /*german*/"etwas, das Geröll entfernen kann", /*french*/"une chose qui enlève les rochers") // /*spanish*/algo que pueda quitar rocas }, { - CustomMessage("sack of mice", /*german*/"!!!", /*french*/"!!!")}); + CustomMessage("sack of mice", /*german*/"!!!", /*french*/"un Sac rempli de souris")}); hintTextTable[RHT_SKELETON_KEY] = HintText(CustomMessage("a Skeleton Key", /*german*/ "ein Universalschlüssel", /*french*/ "une Clé Squelette"), // /*spanish*/una Llave Maestra @@ -2096,49 +2096,49 @@ void StaticData::HintTable_Init_Item() { }, { CustomMessage("a master unlocker", /*german*/ "ein Meisterentsperrer", /*french*/ "un Kit de Déverrouillage") }); // /*spanish*/un desbloqueador maestro - hintTextTable[RHT_QUIVER_INF] = HintText(CustomMessage("", /*german*/"!!!", /*french*/"!!!"), + hintTextTable[RHT_QUIVER_INF] = HintText(CustomMessage("an infinite Quiver", /*german*/"!!!", /*french*/"un Carquois Infini"), { CustomMessage("", /*german*/"!!!", /*french*/"!!!"), }, { CustomMessage("", /*german*/"!!!", /*french*/"!!!")}); - hintTextTable[RHT_BOMB_BAG_INF] = HintText(CustomMessage("", /*german*/"!!!", /*french*/"!!!"), + hintTextTable[RHT_BOMB_BAG_INF] = HintText(CustomMessage("an infinite Bomb Bag", /*german*/"!!!", /*french*/"un Sac de Bombe sans fond"), { CustomMessage("", /*german*/"!!!", /*french*/"!!!"), }, { CustomMessage("", /*german*/"!!!", /*french*/"!!!")}); - hintTextTable[RHT_BULLET_BAG_INF] = HintText(CustomMessage("", /*german*/"!!!", /*french*/"!!!"), + hintTextTable[RHT_BULLET_BAG_INF] = HintText(CustomMessage("an infinite Bullet Bag", /*german*/"!!!", /*french*/"un Sac de Graine sans fond"), { CustomMessage("", /*german*/"!!!", /*french*/"!!!"), }, { CustomMessage("", /*german*/"!!!", /*french*/"!!!")}); - hintTextTable[RHT_STICK_UPGRADE_INF] = HintText(CustomMessage("", /*german*/"!!!", /*french*/"!!!"), + hintTextTable[RHT_STICK_UPGRADE_INF] = HintText(CustomMessage("infinite Deku Sticks", /*german*/"!!!", /*french*/" des Bâtons Mojo illimités"), { CustomMessage("", /*german*/"!!!", /*french*/"!!!"), }, { CustomMessage("", /*german*/"!!!", /*french*/"!!!")}); - hintTextTable[RHT_NUT_UPGRADE_INF] = HintText(CustomMessage("", /*german*/"!!!", /*french*/"!!!"), + hintTextTable[RHT_NUT_UPGRADE_INF] = HintText(CustomMessage("infinite Deku Nut", /*german*/"!!!", /*french*/"des Noix Mojo illimitées"), { CustomMessage("", /*german*/"!!!", /*french*/"!!!"), }, { CustomMessage("", /*german*/"!!!", /*french*/"!!!")}); - hintTextTable[RHT_MAGIC_INF] = HintText(CustomMessage("", /*german*/"!!!", /*french*/"!!!"), + hintTextTable[RHT_MAGIC_INF] = HintText(CustomMessage("unlimited Magic", /*german*/"!!!", /*french*/"de la Magie infinie"), { CustomMessage("", /*german*/"!!!", /*french*/"!!!"), }, { CustomMessage("", /*german*/"!!!", /*french*/"!!!")}); - hintTextTable[RHT_BOMBCHU_INF] = HintText(CustomMessage("", /*german*/"!!!", /*french*/"!!!"), + hintTextTable[RHT_BOMBCHU_INF] = HintText(CustomMessage("infinite Bombchus", /*german*/"!!!", /*french*/"des Missiles Teigneux illimités"), { CustomMessage("", /*german*/"!!!", /*french*/"!!!"), }, { CustomMessage("", /*german*/"!!!", /*french*/"!!!")}); - hintTextTable[RHT_WALLET_INF] = HintText(CustomMessage("", /*german*/"!!!", /*french*/"!!!"), + hintTextTable[RHT_WALLET_INF] = HintText(CustomMessage("an infinite Wallet", /*german*/"!!!", /*french*/"une Bourse sans fond"), { CustomMessage("", /*german*/"!!!", /*french*/"!!!"), }, { diff --git a/soh/soh/Enhancements/randomizer/item_list.cpp b/soh/soh/Enhancements/randomizer/item_list.cpp index 0b94aab39..c31a1c4a6 100644 --- a/soh/soh/Enhancements/randomizer/item_list.cpp +++ b/soh/soh/Enhancements/randomizer/item_list.cpp @@ -339,7 +339,7 @@ void Rando::StaticData::InitItemTable() { itemTable[RG_BRONZE_SCALE] = Item(RG_BRONZE_SCALE, Text{ "Bronze Scale", "Écaille de Bronze", "Bronzene Schuppe" }, ITEMTYPE_ITEM, GI_SCALE_SILVER, true, LOGIC_PROGRESSIVE_WALLET, RHT_BRONZE_SCALE, RG_BRONZE_SCALE, OBJECT_GI_SCALE, GID_SCALE_SILVER, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); itemTable[RG_BRONZE_SCALE].SetCustomDrawFunc(Randomizer_DrawBronzeScale); - itemTable[RG_BOMBCHU_BAG] = Item(RG_BOMBCHU_BAG, Text{ "Bombchu Bag", "!!!", "!!!" }, ITEMTYPE_ITEM, RG_BOMBCHU_BAG, true, LOGIC_BOMBCHUS, RHT_BOMBCHU_BAG, RG_BOMBCHU_BAG, OBJECT_GI_BOMB_2, GID_BOMBCHU, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); + itemTable[RG_BOMBCHU_BAG] = Item(RG_BOMBCHU_BAG, Text{ "Bombchu Bag", "!!!", "Sac de Missiles Teigneux" }, ITEMTYPE_ITEM, RG_BOMBCHU_BAG, true, LOGIC_BOMBCHUS, RHT_BOMBCHU_BAG, RG_BOMBCHU_BAG, OBJECT_GI_BOMB_2, GID_BOMBCHU, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); itemTable[RG_BOMBCHU_BAG].SetCustomDrawFunc(Randomizer_DrawBombchuBag); itemTable[RG_QUIVER_INF] = Item(RG_QUIVER_INF, Text{ "Infinite Quiver", "Carquois Infini", "Unendlicher Köcher" }, ITEMTYPE_ITEM, RG_QUIVER_INF, true, LOGIC_PROGRESSIVE_BOW, RHT_QUIVER_INF, RG_QUIVER_INF, OBJECT_GI_ARROWCASE, GID_QUIVER_50, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 85ddc6c50..1cc7363e8 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -3469,251 +3469,247 @@ void Randomizer::CreateCustomMessages() { GIMESSAGE(RG_GUARD_HOUSE_KEY, ITEM_KEY_SMALL, "You found the key to the&%gGuard House%w!", "Du erhältst einen %rkleinen&Schlüssel%w für das %gHaus der Wachen%w!", - "Vous obtenez une %rPetite Clé %w&de la %gMaison des Gardes%w!"), + "Vous obtenez la %rClé %wde la&%gMaison des Gardes%w!"), GIMESSAGE(RG_MARKET_BAZAAR_KEY, ITEM_KEY_SMALL, "You found the key to the&%gMarket Bazaar%w!", "Du erhältst einen %rkleinen&Schlüssel%w für den %gBasar des Marktes%w!", - "Vous obtenez une %rPetite Clé %w&du %gMarché%w!"), + "Vous obtenez la %rClé %wdu %gBazar&de la Place du Marché%w!"), GIMESSAGE(RG_MARKET_POTION_SHOP_KEY, ITEM_KEY_SMALL, "You found the key to the&%gMarket Potion Shop%w!", "Du erhältst einen %rkleinen&Schlüssel%w für den %gMagie-Laden des Marktes%w!", - "Vous obtenez une %rPetite Clé %w&du %gMarché%w!"), + "Vous obtenez la %rClé %wde la&%gPlace du Marché%w!"), GIMESSAGE(RG_MASK_SHOP_KEY, ITEM_KEY_SMALL, "You found the key to the&%gMask Shop%w!", "Du erhältst einen %rkleinen&Schlüssel%w für den %gMaskenladen%w!", - "Vous obtenez une %rPetite Clé %w&du %gMagasin de Masques%w!"), + "Vous obtenez la %rClé %wde la&%gFoire aux Masques%w!"), GIMESSAGE(RG_MARKET_SHOOTING_GALLERY_KEY, ITEM_KEY_SMALL, "You found the key to the&%gMarket Shooting Gallery%w!", "Du erhältst einen %rkleinen&Schlüssel%w für die %gSchießbude des Marktes%w!", - "Vous obtenez une %rPetite Clé %w&du %gStand de Tir%w!"), + "Vous obtenez la %rClé %wdu %gStand de&Tir de la Place du Marché%w!"), GIMESSAGE(RG_BOMBCHU_BOWLING_KEY, ITEM_KEY_SMALL, "You found the key to the&%gBombchu Bowling Alley%w!", "Du erhältst einen %rkleinen&Schlüssel%w für die %gMinenbowlingbahn%w!", - "Vous obtenez une %rPetite Clé %w&du %gBowling Bombchu%w!"), + "Vous obtenez la %rClé %wdu %gBowling&Teigneux%w!"), GIMESSAGE(RG_TREASURE_CHEST_GAME_BUILDING_KEY, ITEM_KEY_SMALL, "You found the key to the&%gTreasure Chest Game Building%w!", "Du erhältst einen %rkleinen&Schlüssel%w für das %gHaus des Schatzkisten-Pokers%w!", - "Vous obtenez une %rPetite Clé %w&du %gJeu de la Chasse au Trésor%w!"), + "Vous obtenez la %rClé %wdu %gJeu de la&Chasse au Trésor%w!"), GIMESSAGE(RG_BOMBCHU_SHOP_KEY, ITEM_KEY_SMALL, "You found the key to the&%gBombchu Shop%w!", "Du erhältst einen %rkleinen&Schlüssel%w für den %gKrabbelminenladen%w!", - "Vous obtenez une %rPetite Clé %w&du %gMagasin de Bombchu%w!"), + "Vous obtenez la %rClé %wdu %gMagasin&de Missiles%w!"), GIMESSAGE(RG_RICHARDS_HOUSE_KEY, ITEM_KEY_SMALL, "You found the key to&%gRichard's House%w!", "Du erhältst einen %rkleinen&Schlüssel%w für das %gHaus von Richard%w!", - "Vous obtenez une %rPetite Clé %w&de la %gMaison de Richard%w!"), - GIMESSAGE(RG_RICHARDS_HOUSE_KEY, ITEM_KEY_SMALL, - "You found the key to&%gRichard's House%w!", - "Du erhältst einen %rkleinen&Schlüssel%w für das %gHaus von Richard%w!", - "Vous obtenez une %rPetite Clé %w&de la %gMaison de Richard%w!"), + "Vous obtenez la %rClé %wde la %gMaison&de Kiki%w!"), GIMESSAGE(RG_ALLEY_HOUSE_KEY, ITEM_KEY_SMALL, "You found the key to&the %gAlley House%w!", "Du erhältst einen %rkleinen&Schlüssel%w für das %gHaus in der Gasse%w!", - "Vous obtenez une %rPetite Clé %w&de la %gMaison de la Ruelle%w!"), + "Vous obtenez la %rClé %wde la %gMaison&de la Ruelle%w!"), GIMESSAGE(RG_KAK_BAZAAR_KEY, ITEM_KEY_SMALL, "You found the key to the&%gKakariko Bazaar%w!", "Du erhältst einen %rkleinen&Schlüssel%w für den %gBasar von Kakariko%w!", - "Vous obtenez une %rPetite Clé %w&du %gMarché de Cocorico%w!"), + "Vous obtenez la %rClé %wdu %gBazar&de Cocorico%w!"), GIMESSAGE(RG_KAK_POTION_SHOP_KEY, ITEM_KEY_SMALL, "You found the key to the&%gKakariko Potion Shop%w!", "Du erhältst einen %rkleinen&Schlüssel%w für den %gMagie-Laden von Kakariko%w!", - "Vous obtenez une %rPetite Clé %w&du %gMagasin de Potions de Cocorico%w!"), + "Vous obtenez la %rClé %wdu %gMagasin de&Potions de Cocorico%w!"), GIMESSAGE(RG_BOSS_HOUSE_KEY, ITEM_KEY_SMALL, "You found the key to the&%gBoss's House%w!", "Du erhältst einen %rkleinen&Schlüssel%w für das %gHaus des Chefs%w!", - "Vous obtenez une %rPetite Clé %w&de la %gMaison du Boss%w!"), + "Vous obtenez la %rClé %wde la %gMaison&du chef des ouvriers%w!"), GIMESSAGE(RG_GRANNYS_POTION_SHOP_KEY, ITEM_KEY_SMALL, "You found the key to&%gGranny's Potion Shop%w!", "Du erhältst einen %rkleinen&Schlüssel%w für %gAsas Hexenladen%w!", - "Vous obtenez une %rPetite Clé %w&du %gMagasin de Potions de Grand-mère%w!"), + "Vous obtenez la %rClé %wde&l'%gApothicaire%w!"), GIMESSAGE(RG_SKULLTULA_HOUSE_KEY, ITEM_KEY_SMALL, "You found the key to the&%gSkulltula House%w!", "Du erhältst einen %rkleinen&Schlüssel%w für das %gSkulltula-Haus%w!", - "Vous obtenez une %rPetite Clé %w&de la %gMaison des Skulltulas%w!"), + "Vous obtenez la %rClé %wde la %gMaison&des Araignées%w!"), GIMESSAGE(RG_IMPAS_HOUSE_KEY, ITEM_KEY_SMALL, "You found the key to&%gImpa's House%w!", "Du erhältst einen %rkleinen&Schlüssel%w für das %gHaus von Impa%w!", - "Vous obtenez une %rPetite Clé %w&de la %gMaison d'Impa%w!"), + "Vous obtenez la %rClé %wde la %gMaison&d'Impa%w!"), GIMESSAGE(RG_WINDMILL_KEY, ITEM_KEY_SMALL, "You found the key to the&%gWindmill%w!", "Du erhältst einen %rkleinen&Schlüssel%w für die %gWindmühle%w!", - "Vous obtenez une %rPetite Clé %w&du %gMoulin à Vent%w!"), + "Vous obtenez la %rClé %w du %gMoulin%w!"), GIMESSAGE(RG_KAK_SHOOTING_GALLERY_KEY, ITEM_KEY_SMALL, "You found the key to the&%gKakariko Shooting Gallery%w!", "Du erhältst einen %rkleinen&Schlüssel%w für die %gSchießbude von Kakariko%w!", - "Vous obtenez une %rPetite Clé %w&du %gStand de Tir de Cocorico%w!"), + "Vous obtenez la %rClé %w du %gStand de&Tir de Cocorico%w!"), GIMESSAGE(RG_DAMPES_HUT_KEY, ITEM_KEY_SMALL, "You found the key to&%gDampe's Hut%w!", "Du erhältst einen %rkleinen&Schlüssel%w für die %gHütte von Boris%w!", - "Vous obtenez une %rPetite Clé %w&du %gChalet de Dampe%w!"), + "Vous obtenez la %rClé %wde la %gCabane&d'Igor%w!"), GIMESSAGE(RG_TALONS_HOUSE_KEY, ITEM_KEY_SMALL, "You found the key to&%gTalon's House%w!", "Du erhältst einen %rkleinen&Schlüssel%w für das %gHaus von Talon%w!", - "Vous obtenez une %rPetite Clé %w&de la %gMaison de Talon%w!"), + "Vous obtenez la %rClé %wde la %gMaison&de Talon%w!"), GIMESSAGE(RG_STABLES_KEY, ITEM_KEY_SMALL, "You found the key to the&%gStables%w!", "Du erhältst einen %rkleinen&Schlüssel%w für die %gStälle%w!", - "Vous obtenez une %rPetite Clé %w&des %gÉcuries%w!"), + "Vous obtenez la %rClé %wdes %gÉcuries%w!"), GIMESSAGE(RG_BACK_TOWER_KEY, ITEM_KEY_SMALL, "You found the key to the&%gBack Tower%w!", "Du erhältst einen %rkleinen&Schlüssel%w für den %ghinteren Turm%w!", - "Vous obtenez une %rPetite Clé %w&du %gTour Arrière%w!"), + "Vous obtenez la %rClé %wdu %gSilo%w!"), GIMESSAGE(RG_HYLIA_LAB_KEY, ITEM_KEY_SMALL, "You found the key to the&%gHylia Laboratory%w!", "Du erhältst einen %rkleinen&Schlüssel%w für das %gHylia-Labor%w!", - "Vous obtenez une %rPetite Clé %w&du %gLaboratoire d'Hylia%w!"), + "Vous obtenez la %rClé %wdu %gLaboratoire&du Lac Hylia%w!"), GIMESSAGE(RG_FISHING_HOLE_KEY, ITEM_KEY_SMALL, - "You found the key to the&%gFishing Hole%w!", + "You found the key to the&%gPond%w!", "Du erhältst einen %rkleinen&Schlüssel%w für den %gFischweiher%w!", - "Vous obtenez une %rPetite Clé %w&du %gTrou de Pêche%w!"), + "Vous obtenez la %rClé %wde l'%gÉtang%w!"), GIMESSAGE(RG_GERUDO_FORTRESS_KEY_RING, ITEM_KEY_SMALL, - "You found a %yThieves Hideout &%wKeyring!", + "You found a %yThieves Hideout&%wKeyring!", "Du erhältst ein %rSchlüsselbund%w&für das %yDiebesversteck%w!", - "Vous obtenez un trousseau de&clés du %yRepaire des Voleurs%w!"), + "Vous obtenez le trousseau de&clés du %yRepaire des Voleurs%w!"), GIMESSAGE(RG_FOREST_TEMPLE_KEY_RING, ITEM_KEY_SMALL, - "You found a %gForest Temple &%wKeyring!", + "You found a %gForest Temple&%wKeyring!", "Du erhältst ein %rSchlüsselbund%w&für den %gWaldtempel%w!", - "Vous obtenez un trousseau de&clés du %gTemple de la Forêt%w!"), + "Vous obtenez le trousseau de&clés du %gTemple de la Forêt%w!"), GIMESSAGE(RG_FIRE_TEMPLE_KEY_RING, ITEM_KEY_SMALL, - "You found a %rFire Temple &%wKeyring!", + "You found a %rFire Temple&%wKeyring!", "Du erhältst ein %rSchlüsselbund%w&für den %rFeuertempel%w!", - "Vous obtenez un trousseau de&clés du %rTemple du Feu%w!"), + "Vous obtenez le trousseau de&clés du %rTemple du Feu%w!"), GIMESSAGE(RG_WATER_TEMPLE_KEY_RING, ITEM_KEY_SMALL, - "You found a %bWater Temple &%wKeyring!", + "You found a %bWater Temple&%wKeyring!", "Du erhältst ein %rSchlüsselbund%w&für den %bWassertempel%w!", - "Vous obtenez un trousseau de&clés du %bTemple de l'Eau%w!"), + "Vous obtenez le trousseau de&clés du %bTemple de l'Eau%w!"), GIMESSAGE(RG_SPIRIT_TEMPLE_KEY_RING, ITEM_KEY_SMALL, - "You found a %ySpirit Temple &%wKeyring!", + "You found a %ySpirit Temple&%wKeyring!", "Du erhältst ein %rSchlüsselbund%w&für den %yGeistertempel%w!", - "Vous obtenez un trousseau de&clés du %yTemple de l'Esprit%w!"), + "Vous obtenez le trousseau de&clés du %yTemple de l'Esprit%w!"), GIMESSAGE(RG_SHADOW_TEMPLE_KEY_RING, ITEM_KEY_SMALL, - "You found a %pShadow Temple &%wKeyring!", + "You found a %pShadow Temple&%wKeyring!", "Du erhältst ein %rSchlüsselbund%w&für den %pSchattentempel%w!", - "Vous obtenez un trousseau de&clés du %pTemple de l'Ombre%w!"), + "Vous obtenez le trousseau de&clés du %pTemple de l'Ombre%w!"), GIMESSAGE(RG_BOTTOM_OF_THE_WELL_KEY_RING, ITEM_KEY_SMALL, - "You found a %pBottom of the &Well %wKeyring!", + "You found a %pBottom of the&Well %wKeyring!", "Du erhältst ein %rSchlüsselbund%w&für den %pGrund des Brunnens%w!", - "Vous obtenez un trousseau de&clés du %pPuits%w!"), + "Vous obtenez le trousseau de&clés du %pPuits%w!"), GIMESSAGE(RG_GERUDO_TRAINING_GROUND_KEY_RING, ITEM_KEY_SMALL, - "You found a %yGerudo Training &Grounds %wKeyring!", + "You found a %yGerudo Training&Grounds %wKeyring!", "Du erhältst ein %rSchlüsselbund%w&für die %yGerudo-Trainingsarena%w!", - "Vous obtenez un trousseau de&clés du %yGymnase Gerudo%w!"), + "Vous obtenez le trousseau de&clés du %yGymnase Gerudo%w!"), GIMESSAGE(RG_GANONS_CASTLE_KEY_RING, ITEM_KEY_SMALL, - "You found a %rGanon's Castle &%wKeyring!", + "You found a %rGanon's Castle&%wKeyring!", "Du erhältst ein %rSchlüsselbund%w&für %rGanons Schloß%w!", - "Vous obtenez un trousseau de&clés du %rChâteau de Ganon%w!"), + "Vous obtenez le trousseau de&clés du %rChâteau de Ganon%w!"), GIMESSAGE(RG_TREASURE_GAME_KEY_RING, ITEM_KEY_SMALL, - "You found a %rTreasure Chest Game &%wKeyring!", + "You found a %rTreasure Chest Game&%wKeyring!", "!!!", - "!!!"), + "Vous obtenez le trousseau de&clés du %rJeu de la Chasse au Trésor%w!"), GIMESSAGE(RG_FOREST_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, - "You found the %gForest Temple &%wBoss Key!", + "You found the %gForest Temple&%wBoss Key!", "Du erhältst den %rMaster-Schlüssel%w&für den %gWaldtempel%w!", "Vous obtenez la %rClé d'or %wdu&%gTemple de la Forêt%w!"), GIMESSAGE(RG_FIRE_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, - "You found the %rFire Temple &%wBoss Key!", + "You found the %rFire Temple&%wBoss Key!", "Du erhältst den %rMaster-Schlüssel%w&für den %rFeuertempel%w!", "Vous obtenez la %rClé d'or %wdu&%rTemple du Feu%w!"), GIMESSAGE(RG_WATER_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, - "You found the %bWater Temple &%wBoss Key!", + "You found the %bWater Temple&%wBoss Key!", "Du erhältst den %rMaster-Schlüssel%w&für den %bWassertempel%w!", "Vous obtenez la %rClé d'or %wdu&%bTemple de l'Eau%w!"), GIMESSAGE(RG_SPIRIT_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, - "You found the %ySpirit Temple &%wBoss Key!", + "You found the %ySpirit Temple&%wBoss Key!", "Du erhältst den %rMaster-Schlüssel%w&für den %yGeistertempel%w!", "Vous obtenez la %rClé d'or %wdu&%yTemple de l'Esprit%w!"), GIMESSAGE(RG_SHADOW_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS, - "You found the %pShadow Temple &%wBoss Key!", + "You found the %pShadow Temple&%wBoss Key!", "Du erhältst den %rMaster-Schlüssel%w&für den %pSchattentempel%w!", "Vous obtenez la %rClé d'or %wdu&%pTemple de l'Ombre%w!"), GIMESSAGE(RG_GANONS_CASTLE_BOSS_KEY, ITEM_KEY_BOSS, - "You found the %rGanon's Castle &%wBoss Key!", + "You found the %rGanon's Castle&%wBoss Key!", "Du erhältst den %rMaster-Schlüssel%w&für %rGanons Schloß%w!", "Vous obtenez la %rClé d'or %wdu&%rChâteau de Ganon%w!"), GIMESSAGE(RG_DEKU_TREE_MAP, ITEM_DUNGEON_MAP, - "You found the %gDeku Tree &%wMap![[typeHint]]", + "You found the %gDeku Tree&%wMap![[typeHint]]", "Du erhältst die %rKarte%w für den&%gDeku-Baum%w![[typeHint]]", "Vous obtenez la %rCarte %wde&l'%gArbre Mojo%w![[typeHint]]"), GIMESSAGE(RG_DODONGOS_CAVERN_MAP, ITEM_DUNGEON_MAP, - "You found the %rDodongo's Cavern &%wMap![[typeHint]]", + "You found the %rDodongo's Cavern&%wMap![[typeHint]]", "Du erhältst die %rKarte%w für&%rDodongos Höhle%w![[typeHint]]", "Vous obtenez la %rCarte %wde la&%rCaverne Dodongo%w![[typeHint]]"), GIMESSAGE(RG_JABU_JABUS_BELLY_MAP, ITEM_DUNGEON_MAP, - "You found the %bJabu Jabu's Belly &%wMap![[typeHint]]", + "You found the %bJabu Jabu's Belly&%wMap![[typeHint]]", "Du erhältst die %rKarte%w für&%bJabu-Jabus Bauch%w![[typeHint]]", - "Vous obtenez la %rCarte %wdu &%bVentre de Jabu-Jabu%w![[typeHint]]"), + "Vous obtenez la %rCarte %wdu&%bVentre de Jabu-Jabu%w![[typeHint]]"), GIMESSAGE(RG_FOREST_TEMPLE_MAP, ITEM_DUNGEON_MAP, - "You found the %gForest Temple &%wMap![[typeHint]]", + "You found the %gForest Temple&%wMap![[typeHint]]", "Du erhältst die %rKarte%w für den&%gWaldtempel%w![[typeHint]]", - "Vous obtenez la %rCarte %wdu &%gTemple de la Forêt%w![[typeHint]]"), + "Vous obtenez la %rCarte %wdu&%gTemple de la Forêt%w![[typeHint]]"), GIMESSAGE(RG_FIRE_TEMPLE_MAP, ITEM_DUNGEON_MAP, - "You found the %rFire Temple &%wMap![[typeHint]]", + "You found the %rFire Temple&%wMap![[typeHint]]", "Du erhältst die %rKarte%w für den&%rFeuertempel%w![[typeHint]]", - "Vous obtenez la %rCarte %wdu &%rTemple du Feu%w![[typeHint]]"), + "Vous obtenez la %rCarte %wdu&%rTemple du Feu%w![[typeHint]]"), GIMESSAGE(RG_WATER_TEMPLE_MAP, ITEM_DUNGEON_MAP, - "You found the %bWater Temple &%wMap![[typeHint]]", + "You found the %bWater Temple&%wMap![[typeHint]]", "Du erhältst die %rKarte%w für den&%bWassertempel%w![[typeHint]]", - "Vous obtenez la %rCarte %wdu &%bTemple de l'Eau%w![[typeHint]]"), + "Vous obtenez la %rCarte %wdu&%bTemple de l'Eau%w![[typeHint]]"), GIMESSAGE(RG_SPIRIT_TEMPLE_MAP, ITEM_DUNGEON_MAP, - "You found the %ySpirit Temple &%wMap![[typeHint]]", + "You found the %ySpirit Temple&%wMap![[typeHint]]", "Du erhältst die %rKarte%w für den&%yGeistertempel%w![[typeHint]]", - "Vous obtenez la %rCarte %wdu &%yTemple de l'Esprit%w![[typeHint]]"), + "Vous obtenez la %rCarte %wdu&%yTemple de l'Esprit%w![[typeHint]]"), GIMESSAGE(RG_SHADOW_TEMPLE_MAP, ITEM_DUNGEON_MAP, - "You found the %pShadow Temple &%wMap![[typeHint]]", + "You found the %pShadow Temple&%wMap![[typeHint]]", "Du erhältst die %rKarte%w für den&%pSchattentempel%w![[typeHint]]", - "Vous obtenez la %rCarte %wdu &%pTemple de l'Ombre%w![[typeHint]]"), + "Vous obtenez la %rCarte %wdu&%pTemple de l'Ombre%w![[typeHint]]"), GIMESSAGE(RG_BOTTOM_OF_THE_WELL_MAP, ITEM_DUNGEON_MAP, - "You found the %pBottom of the &Well %wMap![[typeHint]]", + "You found the %pBottom of the&Well %wMap![[typeHint]]", "Du erhältst die %rKarte%w für den&%pGrund des Brunnens%w![[typeHint]]", - "Vous obtenez la %rCarte %wdu &%pPuits%w![[typeHint]]"), + "Vous obtenez la %rCarte %wdu&%pPuits%w![[typeHint]]"), GIMESSAGE(RG_ICE_CAVERN_MAP, ITEM_DUNGEON_MAP, - "You found the %cIce Cavern &%wMap![[typeHint]]", + "You found the %cIce Cavern&%wMap![[typeHint]]", "Du erhältst die %rKarte%w für die&%cEishöhle%w![[typeHint]]", - "Vous obtenez la %rCarte %wde &la %cCaverne Polaire%w![[typeHint]]"), + "Vous obtenez la %rCarte %wde&la %cCaverne Polaire%w![[typeHint]]"), GIMESSAGE(RG_DEKU_TREE_COMPASS, ITEM_COMPASS, - "You found the %gDeku Tree &%wCompass!", + "You found the %gDeku Tree&%wCompass!", "Du erhältst den %rKompaß%w für den&%gDeku-Baum%w!", "Vous obtenez la %rBoussole %wde&l'%gArbre Mojo%w!"), GIMESSAGE(RG_DODONGOS_CAVERN_COMPASS, ITEM_COMPASS, - "You found the %rDodongo's Cavern &%wCompass!", + "You found the %rDodongo's Cavern&%wCompass!", "Du erhältst den %rKompaß%w für&%rDodongos Höhle%w!", "Vous obtenez la %rBoussole %wde la&%rCaverne Dodongo%w!"), GIMESSAGE(RG_JABU_JABUS_BELLY_COMPASS, ITEM_COMPASS, - "You found the %bJabu Jabu's Belly &%wCompass!", + "You found the %bJabu Jabu's Belly&%wCompass!", "Du erhältst den %rKompaß%w für den&%bJabu-Jabus Bauch%w!", - "Vous obtenez la %rBoussole %wdu &%bVentre de Jabu-Jabu%w!"), + "Vous obtenez la %rBoussole %wdu&%bVentre de Jabu-Jabu%w!"), GIMESSAGE(RG_FOREST_TEMPLE_COMPASS, ITEM_COMPASS, - "You found the %gForest Temple &%wCompass!", + "You found the %gForest Temple&%wCompass!", "Du erhältst den %rKompaß%w für den&%gWaldtempel%w!", - "Vous obtenez la %rBoussole %wdu &%gTemple de la Forêt%w!"), + "Vous obtenez la %rBoussole %wdu&%gTemple de la Forêt%w!"), GIMESSAGE(RG_FIRE_TEMPLE_COMPASS, ITEM_COMPASS, - "You found the %rFire Temple &%wCompass!", + "You found the %rFire Temple&%wCompass!", "Du erhältst den %rKompaß%w für den&%rFeuertempel%w!", - "Vous obtenez la %rBoussole %wdu &%rTemple du Feu%w!"), + "Vous obtenez la %rBoussole %wdu&%rTemple du Feu%w!"), GIMESSAGE(RG_WATER_TEMPLE_COMPASS, ITEM_COMPASS, - "You found the %bWater Temple &%wCompass!", + "You found the %bWater Temple&%wCompass!", "Du erhältst den %rKompaß%w für den&%bWassertempel%w!", - "Vous obtenez la %rBoussole %wdu &%bTemple de l'Eau%w!"), + "Vous obtenez la %rBoussole %wdu&%bTemple de l'Eau%w!"), GIMESSAGE(RG_SPIRIT_TEMPLE_COMPASS, ITEM_COMPASS, - "You found the %ySpirit Temple &%wCompass!", + "You found the %ySpirit Temple&%wCompass!", "Du erhältst den %rKompaß%w für den&%yGeistertempel%w!", - "Vous obtenez la %rBoussole %wdu &%yTemple de l'Esprit%w!"), + "Vous obtenez la %rBoussole %wdu&%yTemple de l'Esprit%w!"), GIMESSAGE(RG_SHADOW_TEMPLE_COMPASS, ITEM_COMPASS, - "You found the %pShadow Temple &%wCompass!", + "You found the %pShadow Temple&%wCompass!", "Du erhältst den %rKompaß%w für den&%pSchattentempel%w!", - "Vous obtenez la %rBoussole %wdu &%pTemple de l'Ombre%w!"), + "Vous obtenez la %rBoussole %wdu&%pTemple de l'Ombre%w!"), GIMESSAGE(RG_BOTTOM_OF_THE_WELL_COMPASS, ITEM_COMPASS, - "You found the %pBottom of the &Well %wCompass!", + "You found the %pBottom of the&Well %wCompass!", "Du erhältst den %rKompaß%w für den&%pGrund des Brunnens%w!", - "Vous obtenez la %rBoussole %wdu &%pPuits%w!"), + "Vous obtenez la %rBoussole %wdu&%pPuits%w!"), GIMESSAGE(RG_ICE_CAVERN_COMPASS, ITEM_COMPASS, - "You found the %cIce Cavern &%wCompass!", + "You found the %cIce Cavern&%wCompass!", "Du erhältst den %rKompaß%w für die&%cEishöhle%w!", - "Vous obtenez la %rBoussole %wde &la %cCaverne Polaire%w!"), + "Vous obtenez la %rBoussole %wde&la %cCaverne Polaire%w!"), GIMESSAGE(RG_MAGIC_BEAN_PACK, ITEM_BEAN, "You got a %rPack of Magic Beans%w!&Find a suitable spot for a garden&and plant them. Then, wait for&something fun to happen!", @@ -3739,7 +3735,7 @@ void Randomizer::CreateCustomMessages() { "Vous obtenez l'âme de %bBarinade%w!"), GIMESSAGE_NO_GERMAN(RG_PHANTOM_GANON_SOUL, ITEM_BIG_POE, "You found the soul for %gPhantom&Ganon%w!", - "Vous obtenez l'âme de %gGanon Spectral%w!"), + "Vous obtenez l'âme de %gGanon&Spectral%w!"), GIMESSAGE_NO_GERMAN(RG_VOLVAGIA_SOUL, ITEM_BIG_POE, "You found the soul for %rVolvagia%w!", "Vous obtenez l'âme de %rVulcania%w!"), @@ -3748,10 +3744,10 @@ void Randomizer::CreateCustomMessages() { "Vous obtenez l'âme de %bMorpha%w!"), GIMESSAGE_NO_GERMAN(RG_BONGO_BONGO_SOUL, ITEM_BIG_POE, "You found the soul for %pBongo&Bongo%w!", - "Vous obtenez l'âme de %pBongo Bongo%w!"), + "Vous obtenez l'âme de %pBongo&Bongo%w!"), GIMESSAGE_NO_GERMAN(RG_TWINROVA_SOUL, ITEM_BIG_POE, "You found the soul for %yTwinrova%w!", - "Vous obtenez l'âme du %yDuo Maléfique%w!"), + "Vous obtenez l'âme du %yDuo&Maléfique%w!"), GIMESSAGE_NO_GERMAN(RG_GANON_SOUL, ITEM_BIG_POE, "You found the soul for %cGanon%w!", "Vous obtenez l'âme de %cGanon%w!"), @@ -3759,66 +3755,66 @@ void Randomizer::CreateCustomMessages() { GIMESSAGE(RG_OCARINA_A_BUTTON, ITEM_OCARINA_TIME, "You got the %b\x9f%r button for the&Ocarina%w! You can now use it&while playing songs!", "Der %b\x9f%r Knopf%w!&Du kannst ihn nun zum Spielen&von Liedern auf der %rOkarina%w&verwenden!", - "Vous trouvez la %rtouche %b\x9f%r de&l'Ocarina%w! Vous pouvez&maintenant l'utiliser lorsque&vous en jouez!"), + "Vous obtenez la %rtouche %b\x9f%r de&l'Ocarina%w! Vous pouvez&maintenant l'utiliser lorsque&vous en jouez!"), GIMESSAGE(RG_OCARINA_C_LEFT_BUTTON, ITEM_OCARINA_TIME, "You got the %y\xa7%r button for the&Ocarina%w! You can now use it&while playing songs!", "Der %y\xa7%r Knopf%w!&Du kannst ihn nun zum Spielen&von Liedern auf der %rOkarina%w&verwenden!", - "Vous trouvez la %rtouche %y\xa7%r de&l'Ocarina%w! Vous pouvez&maintenant l'utiliser lorsque&vous en jouez!"), + "Vous obtenez la %rtouche %y\xa7%r de&l'Ocarina%w! Vous pouvez&maintenant l'utiliser lorsque&vous en jouez!"), GIMESSAGE(RG_OCARINA_C_RIGHT_BUTTON, ITEM_OCARINA_TIME, "You got the %y\xa8%r button for the&Ocarina%w! You can now use it&while playing songs!", "Der %y\xa8%r Knopf%w!&Du kannst ihn nun zum Spielen&von Liedern auf der %rOkarina%w&verwenden!", - "Vous trouvez la %rtouche %y\xa8%r de&l'Ocarina%w! Vous pouvez&maintenant l'utiliser lorsque&vous en jouez!"), + "Vous obtenez la %rtouche %y\xa8%r de&l'Ocarina%w! Vous pouvez&maintenant l'utiliser lorsque&vous en jouez!"), GIMESSAGE(RG_OCARINA_C_UP_BUTTON, ITEM_OCARINA_TIME, "You got the %y\xa5%r button for the&Ocarina%w! You can now use it&while playing songs!", "Der %y\xa5%r Knopf%w!&Du kannst ihn nun zum Spielen&von Liedern auf der %rOkarina%w&verwenden!", - "Vous trouvez la %rtouche %y\xa5%r de&l'Ocarina%w! Vous pouvez&maintenant l'utiliser lorsque&vous en jouez!"), + "Vous obtenez la %rtouche %y\xa5%r de&l'Ocarina%w! Vous pouvez&maintenant l'utiliser lorsque&vous en jouez!"), GIMESSAGE(RG_OCARINA_C_DOWN_BUTTON, ITEM_OCARINA_TIME, "You got the %y\xa6%r button for the&Ocarina%w! You can now use it&while playing songs!", "Der %y\xa6%r Knopf%w!&Du kannst ihn nun zum Spielen&von Liedern auf der %rOkarina%w&verwenden!", - "Vous trouvez la %rtouche %y\xa6%r de&l'Ocarina%w! Vous pouvez&maintenant l'utiliser lorsque&vous en jouez!"), + "Vous obtenez la %rtouche %y\xa6%r de&l'Ocarina%w! Vous pouvez&maintenant l'utiliser lorsque&vous en jouez!"), GIMESSAGE_NO_GERMAN(RG_BRONZE_SCALE, ITEM_SCALE_SILVER, "You got the %rBronze Scale%w!&The power of buoyancy is yours!", - "Vous avez obtenu l'%rÉcaille de Bronze%w!&Le pouvoir de la flottabilité est à vous!"), + "Vous obtenez l'%rÉcaille de Bronze%w!&Le pouvoir de la flottabilité est&à vous!"), GIMESSAGE_NO_GERMAN(RG_FISHING_POLE, ITEM_FISHING_POLE, "You found a lost %rFishing Pole%w!&Time to hit the pond!", - "Vous avez trouvé une %rCanne à pêche%w perdue!&Il est temps d'aller à l'étang!"), + "Vous obtenez une %rCanne à pêche%w&perdue!&Il est temps d'aller à %gl'étang%w!"), GIMESSAGE_NO_GERMAN(RG_BOMBCHU_BAG, ITEM_BOMBCHU, "You found the %rBombchu Bag%w!", - "!!!"), + "Vous obtenez un %rSac de Missiles&Teigneux%w!"), GIMESSAGE_NO_GERMAN(RG_BOMB_BAG_INF, ITEM_BOMB_BAG_40, "You got an %rInfinite Bomb Bag%w!&Now you have %yinfinite bombs%w!", - "Vous avez obtenu un %rSac à bombes à l'infini%w!&Vous avez maintenant des %ybombes à l'infini%w!"), + "Vous obtenez un %rSac de Bombes&sans fond%w!&Vous avez maintenant des %ybombes&en quantité illimitée%w!"), GIMESSAGE_NO_GERMAN(RG_QUIVER_INF, ITEM_QUIVER_50, "You got an %rInfinite Quiver%w!&Now you have %yinfinite arrows%w!", - "Vous avez obtenu un %rCarquois à l'infini%w!&Vous avez maintenant des %yflèches à l'infini%w!"), + "Vous obtenez un %rCarquois Infini%w!&Vous avez maintenant des %yflèches&de manière illimitée%w!"), GIMESSAGE_NO_GERMAN(RG_BULLET_BAG_INF, ITEM_BULLET_BAG_50, "You got an %rInfinite Bullet Bag%w!&Now you have %yinfinite&slingshot seeds%w!", - "Vous avez obtenu un %rSac de Graine à l'infini%w!&Vous avez maintenant des %ygraines de lance-pierres à l'infini%w!"), + "Vous obtenez un %rSac de Graines&sans fond%w!&Vous avez maintenant des %ygraines&de lance-pierres à l'infini%w!"), GIMESSAGE_NO_GERMAN(RG_STICK_UPGRADE_INF, ITEM_STICK, "You now have %yinfinite%w %rDeku Sticks%w!", - "Vous avez maintenant des %yBâtons Mojo à l'infini%w!"), + "Vous avez maintenant des %yBâtons&Mojo de manière illimitée%w!"), GIMESSAGE_NO_GERMAN(RG_NUT_UPGRADE_INF, ITEM_NUT, "You now have %yinfinite%w %rDeku Nuts%w!", - "Vous avez maintenant des %yNoix Mojo à l'infini%w!"), + "Vous avez maintenant des %yNoix&Mojo de manière illimitée%w!"), GIMESSAGE_NO_GERMAN(RG_MAGIC_INF, ITEM_MAGIC_LARGE, "You now have %yinfinite%w %rMagic%w!", - "Vous avez maintenant de la %ymagie à l'infini%w!"), + "Vous avez maintenant une quantité&de %ymagie illimitée%w!"), GIMESSAGE_NO_GERMAN(RG_BOMBCHU_INF, ITEM_BOMBCHU, "You now have %yinfinite%w %rBombchus%w!", - "Vous avez maintenant des %Missiles Teigneux à l'infini%w!"), + "Vous avez maintenant des %yMissiles&Teigneux en quantité illimités%w!"), GIMESSAGE_NO_GERMAN(RG_WALLET_INF, ITEM_WALLET_GIANT, "You now have %yinfinite%w %rmoney%w!", - "Vous avez maintenant de l'%yargent à l'infini%w!"), + "Vous avez maintenant des %yRubis en& quantité illimitée%w!"), GIMESSAGE_NO_GERMAN(RG_SKELETON_KEY, ITEM_KEY_SMALL, "You found the %rSkeleton Key%w!", "Vous avez trouvé la %rClé Squelette%w!"), GIMESSAGE_NO_GERMAN(RG_DEKU_STICK_BAG, ITEM_STICK, "You found the %rDeku Stick Bag%w!&You can now hold deku sticks!", - "Vous avez trouvé le %rSac de Bâtons Mojo%w!&Vous pouvez maintenant porter des Bâtons Mojo!"), + "Vous avez trouvé le %rSac de Bâtons&Mojo%w!&Vous pouvez maintenant porter des&Bâtons Mojo!"), GIMESSAGE_NO_GERMAN(RG_DEKU_NUT_BAG, ITEM_NUT, "You found the %rDeku Nut Bag%w!&You can now hold deku nuts!", - "Vous avez trouvé le %rSac de Noix Mojo%w!&Vous pouvez maintenant porter des Noix Mojo!"), + "Vous avez trouvé le %rSac de Noix& Mojo%w!&Vous pouvez maintenant porter des&Noix Mojo!"), }}; CreateGetItemMessages(&getItemMessages); CreateRupeeMessages(); From 63ae14cc3020de559ff12c56fd5e4ecd2defa205 Mon Sep 17 00:00:00 2001 From: Malkierian Date: Tue, 18 Mar 2025 01:24:32 -0700 Subject: [PATCH 11/53] Menu Header Button Reorder (#5134) * Move close menu button to right side, farthest right, and move Quit SoH to the left of the Reset button. Also remove last reference to 2ship in tooltips. * Change Resolution Editor's use of CVarGet to `GetInterpolationFPS()` in OTRGlobals. --- soh/soh/SohGui/Menu.cpp | 52 ++++++++++++++--------------- soh/soh/SohGui/ResolutionEditor.cpp | 2 +- soh/soh/SohGui/SohMenuSettings.cpp | 2 +- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/soh/soh/SohGui/Menu.cpp b/soh/soh/SohGui/Menu.cpp index e130d5b75..4dd939df8 100644 --- a/soh/soh/SohGui/Menu.cpp +++ b/soh/soh/SohGui/Menu.cpp @@ -581,22 +581,6 @@ void Menu::DrawElement() { headerHeight += style.ScrollbarSize; scrollbar = true; } - UIWidgets::ButtonOptions options = {}; - options.size = UIWidgets::Sizes::Inline; - options.tooltip = "Close Menu (Esc)"; - if (UIWidgets::Button(ICON_FA_TIMES_CIRCLE, options)) { - ToggleVisibility(); - - // Update gamepad navigation after close based on if other menus are still visible - auto mImGuiIo = &ImGui::GetIO(); - if (CVarGetInteger(CVAR_IMGUI_CONTROLLER_NAV, 0) && - Ship::Context::GetInstance()->GetWindow()->GetGui()->GetMenuOrMenubarVisible()) { - mImGuiIo->ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; - } else { - mImGuiIo->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad; - } - } - ImGui::SameLine(); ImGui::SetNextWindowSizeConstraints({ 0, headerHeight }, { headerWidth, headerHeight }); ImVec2 headerSelSize = { menuSize.x - buttonSize.x * 3 - style.ItemSpacing.x * 3, headerHeight }; if (scrollbar) { @@ -657,7 +641,19 @@ void Menu::DrawElement() { ImGui::PopStyleColor(); } ImGui::EndChild(); - ImGui::SameLine(menuSize.x - (buttonSize.x * 2) - style.ItemSpacing.x); + ImGui::SameLine(menuSize.x - (buttonSize.x * 3) - (style.ItemSpacing.x * 2)); + UIWidgets::ButtonOptions options3 = {}; + options3.color = UIWidgets::Colors::Red; + options3.size = UIWidgets::Sizes::Inline; + options3.tooltip = "Quit SoH"; + if (UIWidgets::Button(ICON_FA_POWER_OFF, options3)) { + if (!popped) { + ToggleVisibility(); + } + Ship::Context::GetInstance()->GetWindow()->Close(); + } + ImGui::PopStyleVar(); + ImGui::SameLine(); UIWidgets::ButtonOptions options2 = {}; options2.color = UIWidgets::Colors::Red; options2.size = UIWidgets::Sizes::Inline; @@ -676,17 +672,21 @@ void Menu::DrawElement() { ->Dispatch("reset"); } ImGui::SameLine(); - UIWidgets::ButtonOptions options3 = {}; - options3.color = UIWidgets::Colors::Red; - options3.size = UIWidgets::Sizes::Inline; - options3.tooltip = "Quit SoH"; - if (UIWidgets::Button(ICON_FA_POWER_OFF, options3)) { - if (!popped) { - ToggleVisibility(); + UIWidgets::ButtonOptions options = {}; + options.size = UIWidgets::Sizes::Inline; + options.tooltip = "Close Menu (Esc)"; + if (UIWidgets::Button(ICON_FA_TIMES_CIRCLE, options)) { + ToggleVisibility(); + + // Update gamepad navigation after close based on if other menus are still visible + auto mImGuiIo = &ImGui::GetIO(); + if (CVarGetInteger(CVAR_IMGUI_CONTROLLER_NAV, 0) && + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetMenuOrMenubarVisible()) { + mImGuiIo->ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + } else { + mImGuiIo->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad; } - Ship::Context::GetInstance()->GetWindow()->Close(); } - ImGui::PopStyleVar(); pos.y += headerHeight + style.ItemSpacing.y; pos.x = centerX - menuSize.x / 2 + (style.ItemSpacing.x * (menuEntries.size() + 1)); diff --git a/soh/soh/SohGui/ResolutionEditor.cpp b/soh/soh/SohGui/ResolutionEditor.cpp index 8e850682c..e4f504ccd 100644 --- a/soh/soh/SohGui/ResolutionEditor.cpp +++ b/soh/soh/SohGui/ResolutionEditor.cpp @@ -517,7 +517,7 @@ void UpdateResolutionVars() { bool IsDroppingFrames() { // a rather imprecise way of checking for frame drops. // but it's mostly there to inform the player of large drops. - const short targetFPS = CVarGetInteger("gInterpolationFPS", 20); + const short targetFPS = OTRGlobals::Instance->GetInterpolationFPS(); const float threshold = targetFPS / 20.0f + 4.1f; return ImGui::GetIO().Framerate < targetFPS - threshold; } diff --git a/soh/soh/SohGui/SohMenuSettings.cpp b/soh/soh/SohGui/SohMenuSettings.cpp index 5a186b0ae..81d5b5244 100644 --- a/soh/soh/SohGui/SohMenuSettings.cpp +++ b/soh/soh/SohGui/SohMenuSettings.cpp @@ -65,7 +65,7 @@ void SohMenu::AddMenuSettings() { AddWidget(path, "Menu Controller Navigation", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_IMGUI_CONTROLLER_NAV) .Options(CheckboxOptions().Tooltip( - "Allows controller navigation of the 2Ship menu (Settings, Enhancements,...)\nCAUTION: " + "Allows controller navigation of the port menu (Settings, Enhancements,...)\nCAUTION: " "This will disable game inputs while the menu is visible.\n\nD-pad to move between " "items, A to select, B to move up in scope.")); AddWidget(path, "Cursor Always Visible", WIDGET_CVAR_CHECKBOX) From 546b91510619989de3c7fc751cc647343e556e55 Mon Sep 17 00:00:00 2001 From: Spodi Date: Tue, 18 Mar 2025 09:24:50 +0100 Subject: [PATCH 12/53] Unify FPS and V-Sync controls and behavior between renderer (#5132) * FPS and refresh rate logic changes Changes FPS behavior on all renderer (despite the actual set FPS): - Clamp FPS to refresh rate when V-Sync on (or can't be disabled) - Stick to refresh rate when "MatchRefreshRate" is toggled on * Max 360 FPS for slider on all renderers * "Match Refresh Rate" unification Checkbox for all renderer. Was a button for DirectX. * Fix wrong CVAR for Match Refresh Rate * More descriptive V-Sync tooltip * Fix wrong CVar for DISABLE_FOR_DEBUG_MODE_OFF --- soh/soh/OTRGlobals.cpp | 9 +++----- soh/soh/SohGui/SohMenu.cpp | 4 ++-- soh/soh/SohGui/SohMenuSettings.cpp | 33 +++++++----------------------- 3 files changed, 12 insertions(+), 34 deletions(-) diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 86d7d9382..70d12a78b 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -499,15 +499,12 @@ bool OTRGlobals::HasOriginal() { } uint32_t OTRGlobals::GetInterpolationFPS() { - if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { - return CVarGetInteger(CVAR_SETTING("InterpolationFPS"), 20); - } - if (CVarGetInteger(CVAR_SETTING("MatchRefreshRate"), 0)) { return Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); + } else if (CVarGetInteger(CVAR_VSYNC_ENABLED, 1) || !Ship::Context::GetInstance()->GetWindow()->CanDisableVerticalSync()) { + return std::min(Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(), CVarGetInteger(CVAR_SETTING("InterpolationFPS"), 20)); } - - return std::min(Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(), CVarGetInteger(CVAR_SETTING("InterpolationFPS"), 20)); + return CVarGetInteger(CVAR_SETTING("InterpolationFPS"), 20); } extern "C" void OTRMessage_Init(); diff --git a/soh/soh/SohGui/SohMenu.cpp b/soh/soh/SohGui/SohMenu.cpp index d4aee295a..780aa51f2 100644 --- a/soh/soh/SohGui/SohMenu.cpp +++ b/soh/soh/SohGui/SohMenu.cpp @@ -125,7 +125,7 @@ void SohMenu::InitElement() { }, "Not Available on DirectX" } }, { DISABLE_FOR_MATCH_REFRESH_RATE_ON, - { [](disabledInfo& info) -> bool { return CVarGetInteger(CVAR_SETTING("gMatchRefreshRate"), 0); }, + { [](disabledInfo& info) -> bool { return CVarGetInteger(CVAR_SETTING("MatchRefreshRate"), 0); }, "Match Refresh Rate is Enabled" } }, { DISABLE_FOR_ADVANCED_RESOLUTION_ON, { [](disabledInfo& info) -> bool { return CVarGetInteger(CVAR_PREFIX_ADVANCED_RESOLUTION ".Enabled", 0); }, @@ -140,7 +140,7 @@ void SohMenu::InitElement() { { DISABLE_FOR_NULL_PLAY_STATE, { [](disabledInfo& info) -> bool { return gPlayState == NULL; }, "Save Not Loaded" } }, { DISABLE_FOR_DEBUG_MODE_OFF, - { [](disabledInfo& info) -> bool { return !CVarGetInteger("gDeveloperTools.DebugEnabled", 0); }, + { [](disabledInfo& info) -> bool { return !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }, "Debug Mode is Disabled" } }, { DISABLE_FOR_FRAME_ADVANCE_OFF, { [](disabledInfo& info) -> bool { return !(gPlayState != nullptr && gPlayState->frameAdvCtx.enabled); }, diff --git a/soh/soh/SohGui/SohMenuSettings.cpp b/soh/soh/SohGui/SohMenuSettings.cpp index 81d5b5244..b88065281 100644 --- a/soh/soh/SohGui/SohMenuSettings.cpp +++ b/soh/soh/SohGui/SohMenuSettings.cpp @@ -201,18 +201,10 @@ void SohMenu::AddMenuSettings() { AddWidget(path, "Audio API (Needs reload)", WIDGET_AUDIO_BACKEND); // Graphics Settings - static int32_t maxFps; - const char* tooltip = ""; - if (Ship::Context::GetInstance()->GetWindow()->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { - maxFps = 360; - tooltip = "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. This is " - "purely visual and does not impact game logic, execution of glitches etc.\n\nA higher target " - "FPS than your monitor's refresh rate will waste resources, and might give a worse result."; - } else { - maxFps = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); - tooltip = "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. This is " - "purely visual and does not impact game logic, execution of glitches etc."; - } + static int32_t maxFps = 360; + const char* tooltip = "Uses Matrix Interpolation to create extra frames, resulting in smoother graphics. This is " + "purely visual and does not impact game logic, execution of glitches etc.\n\nA higher target " + "FPS than your monitor's refresh rate will waste resources, and might give a worse result."; path.sidebarName = "Graphics"; AddSidebarEntry("Settings", "Graphics", 3); AddWidget(path, "Toggle Fullscreen", WIDGET_CVAR_CHECKBOX) @@ -276,25 +268,14 @@ void SohMenu::AddMenuSettings() { info.activeDisables.push_back(DISABLE_FOR_MATCH_REFRESH_RATE_ON); }) .Options(IntSliderOptions().Tooltip(tooltip).Min(20).Max(maxFps).DefaultValue(20).Format(fpsFormat)); - AddWidget(path, "Match Refresh Rate", WIDGET_BUTTON) - .Callback([](WidgetInfo& info) { - int hz = Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); - if (hz >= 20 && hz <= 360) { - CVarSetInteger(CVAR_SETTING("InterpolationFPS"), hz); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - }) - .PreFunc([](WidgetInfo& info) { info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_NOT_DIRECTX).active; }) - .Options(ButtonOptions().Tooltip("Matches interpolation value to the current game's window refresh rate.")); AddWidget(path, "Match Refresh Rate", WIDGET_CVAR_CHECKBOX) - .CVar("gMatchRefreshRate") - .PreFunc([](WidgetInfo& info) { info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_DIRECTX).active; }) - .Options(CheckboxOptions().Tooltip("Matches interpolation value to the current game's window refresh rate.")); + .CVar(CVAR_SETTING("MatchRefreshRate")) + .Options(CheckboxOptions().Tooltip("Matches interpolation value to the refresh rate of your display.")); AddWidget(path, "Renderer API (Needs reload)", WIDGET_VIDEO_BACKEND); AddWidget(path, "Enable Vsync", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_VSYNC_ENABLED) .PreFunc([](WidgetInfo& info) { info.isHidden = mSohMenu->disabledMap.at(DISABLE_FOR_NO_VSYNC).active; }) - .Options(CheckboxOptions().Tooltip("Enables Vsync.")); + .Options(CheckboxOptions().Tooltip("Removes tearing, but clamps your max FPS to your displays refresh rate.")); AddWidget(path, "Windowed Fullscreen", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SDL_WINDOWED_FULLSCREEN) .PreFunc([](WidgetInfo& info) { From 9e883ece96bd4cad524bed518e9dd93d1d94502f Mon Sep 17 00:00:00 2001 From: Christopher Leggett Date: Tue, 18 Mar 2025 04:26:09 -0400 Subject: [PATCH 13/53] Fix/modern menu/secret flag (#5131) * Adds ability to set InputString fields as "secret" Meaning it shows the text as dots like a password field. Currently nothing that is merged into the main branch uses this but Anchor will need it. * Adds an `addedFlags` field to InputOptions. --- soh/soh/SohGui/UIWidgets.cpp | 9 +++++++-- soh/soh/SohGui/UIWidgets.hpp | 7 +++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/soh/soh/SohGui/UIWidgets.cpp b/soh/soh/SohGui/UIWidgets.cpp index 7fbad6705..18abb0094 100644 --- a/soh/soh/SohGui/UIWidgets.cpp +++ b/soh/soh/SohGui/UIWidgets.cpp @@ -777,7 +777,12 @@ bool InputString(const char* label, std::string* value, const InputOptions& opti } } ImGui::SetNextItemWidth(width); - if (ImGui::InputText(label, (char*)value->c_str(), value->capacity() + 1, ImGuiInputTextFlags_CallbackResize, InputTextResizeCallback, value)) { + ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackResize; + if (options.secret) { + flags |= ImGuiInputTextFlags_Password; + } + flags |= options.addedFlags; + if (ImGui::InputText(label, (char*)value->c_str(), value->capacity() + 1, flags, InputTextResizeCallback, value)) { dirty = true; } if (value->empty() && !options.placeholder.empty()) { @@ -828,7 +833,7 @@ bool InputInt(const char* label, int32_t* value, const InputOptions& options) { } } ImGui::SetNextItemWidth(width); - if (ImGui::InputScalar(label, ImGuiDataType_S32, value)) { + if (ImGui::InputScalar(label, ImGuiDataType_S32, value, nullptr, nullptr, nullptr, options.addedFlags)) { dirty = true; } if ((ImGui::GetItemStatusFlags() & ImGuiItemStatusFlags_Edited) && !options.placeholder.empty()) { diff --git a/soh/soh/SohGui/UIWidgets.hpp b/soh/soh/SohGui/UIWidgets.hpp index ebf483194..649395b43 100644 --- a/soh/soh/SohGui/UIWidgets.hpp +++ b/soh/soh/SohGui/UIWidgets.hpp @@ -425,6 +425,8 @@ namespace UIWidgets { std::string placeholder = ""; InputTypes type = InputTypes::String; std::string defaultValue = ""; + bool secret = false; + ImGuiInputFlags addedFlags = 0; InputOptions& Tooltip(const char* tooltip_) { WidgetOptions::tooltip = tooltip_; @@ -463,6 +465,11 @@ namespace UIWidgets { defaultValue = defaultValue_; return *this; } + + InputOptions& IsSecret(bool secret_ = false) { + secret = secret_; + return *this; + } }; void PushStyleMenu(const ImVec4& color); From ab5ea0e8ee896cfced2cc88e263890cd0af00cca Mon Sep 17 00:00:00 2001 From: aMannus Date: Tue, 18 Mar 2025 10:12:45 +0100 Subject: [PATCH 14/53] Further Menu Improvements (#5129) * Menu improvements * Update calc for button offsets * Fix enemy rando UI --- soh/soh/Enhancements/controls/InputViewer.cpp | 2 - .../cosmetics/CosmeticsEditor.cpp | 10 +- .../Enhancements/debugger/MessageViewer.cpp | 2 - soh/soh/Enhancements/debugger/actorViewer.cpp | 3 - soh/soh/Enhancements/debugger/dlViewer.cpp | 5 - soh/soh/Enhancements/debugger/valueViewer.cpp | 2 - soh/soh/Enhancements/enemyrandomizer.cpp | 2 +- .../randomizer/randomizer_check_tracker.cpp | 4 - .../randomizer_entrance_tracker.cpp | 5 - .../randomizer/randomizer_item_tracker.cpp | 6 +- .../Enhancements/timesplits/TimeSplits.cpp | 2 - soh/soh/OTRGlobals.cpp | 2 +- soh/soh/SohGui/Menu.cpp | 18 +- soh/soh/SohGui/SohMenuEnhancements.cpp | 158 ++++++++---------- soh/soh/SohGui/SohMenuSettings.cpp | 11 +- soh/soh/SohGui/SohModals.cpp | 2 - soh/soh/SohGui/UIWidgets.hpp | 86 ---------- 17 files changed, 100 insertions(+), 220 deletions(-) diff --git a/soh/soh/Enhancements/controls/InputViewer.cpp b/soh/soh/Enhancements/controls/InputViewer.cpp index 3eb18c0e2..c354e0319 100644 --- a/soh/soh/Enhancements/controls/InputViewer.cpp +++ b/soh/soh/Enhancements/controls/InputViewer.cpp @@ -428,7 +428,6 @@ InputViewerSettingsWindow::~InputViewerSettingsWindow() { } void InputViewerSettingsWindow::DrawElement() { - ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); // gInputViewer.Scale CVarSliderFloat("Input Viewer Scale: %.2f", CVAR_INPUT_VIEWER("Scale"), FloatSliderOptions().Color(THEME_COLOR).DefaultValue(1.0f).Min(0.1f).Max(5.0f).ShowButtons(true).Tooltip("Sets the on screen size of the input viewer")); @@ -640,5 +639,4 @@ void InputViewerSettingsWindow::DrawElement() { } } PopStyleHeader(); - ImGui::PopFont(); } diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp index 93c866eb3..f63c95e30 100644 --- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp +++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp @@ -1523,7 +1523,7 @@ void Draw_Placements(){ CVarSetInteger(CVAR_COSMETIC("HUD.EnemyHealthBar.Width.Changed"), 1); } ImGui::SameLine(); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (ImGui::CalcTextSize("g").y * 2)); if (UIWidgets::Button("Reset##EnemyHealthBarWidth", UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { CVarClear(CVAR_COSMETIC("HUD.EnemyHealthBar.Width.Value")); @@ -1537,7 +1537,7 @@ void Draw_Placements(){ void Reset_Option_Single(const char* Button_Title, const char* name) { ImGui::SameLine(); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (ImGui::CalcTextSize("g").y * 2)); if (UIWidgets::Button(Button_Title, UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { CVarClear(name); @@ -1546,7 +1546,7 @@ void Reset_Option_Single(const char* Button_Title, const char* name) { void Reset_Option_Double(const char* Button_Title, const char* name) { ImGui::SameLine(); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (ImGui::CalcTextSize("g").y * 2)); if (UIWidgets::Button(Button_Title, UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { CVarClear((std::string(name) + ".Value").c_str()); @@ -1578,7 +1578,7 @@ void DrawSillyTab() { CVarSetInteger(CVAR_COSMETIC("Link.BodySize.Changed"), 1); } ImGui::SameLine(); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (ImGui::CalcTextSize("g").y * 2)); if (UIWidgets::Button("Reset##Link_BodySize", UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { CVarClear(CVAR_COSMETIC("Link.BodySize.Value")); @@ -2194,7 +2194,7 @@ void CosmeticsEditorWindow::DrawElement() { CVarSetInteger(CVAR_COSMETIC("Trails.Duration.Changed"), 1); } ImGui::SameLine(); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 24); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (ImGui::CalcTextSize("g").y * 2)); if (UIWidgets::Button("Reset##Trails_Duration", UIWidgets::ButtonOptions() .Size(ImVec2(80, 36)) .Padding(ImVec2(5.0f, 0.0f)))) { diff --git a/soh/soh/Enhancements/debugger/MessageViewer.cpp b/soh/soh/Enhancements/debugger/MessageViewer.cpp index 49764374f..aabe9ad61 100644 --- a/soh/soh/Enhancements/debugger/MessageViewer.cpp +++ b/soh/soh/Enhancements/debugger/MessageViewer.cpp @@ -26,7 +26,6 @@ void MessageViewer::InitElement() { } void MessageViewer::DrawElement() { - ImGui::PushFont(OTRGlobals::Instance->fontMonoLargest); ImGui::Text("Table ID"); ImGui::SameLine(); PushStyleInput(THEME_COLOR); @@ -86,7 +85,6 @@ void MessageViewer::DrawElement() { mDisplayCustomMessageClicked = true; } PopStyleButton(); - ImGui::PopFont(); } void MessageViewer::UpdateElement() { diff --git a/soh/soh/Enhancements/debugger/actorViewer.cpp b/soh/soh/Enhancements/debugger/actorViewer.cpp index a154df65b..767ff2723 100644 --- a/soh/soh/Enhancements/debugger/actorViewer.cpp +++ b/soh/soh/Enhancements/debugger/actorViewer.cpp @@ -934,8 +934,6 @@ void ActorViewerWindow::DrawElement() { static s16 currentSelectedInDropdown; static std::vector actors; - ImGui::PushFont(OTRGlobals::Instance->fontMonoLargest); - if (gPlayState != nullptr) { needs_reset = lastSceneId != gPlayState->sceneNum; if (needs_reset) { @@ -1234,7 +1232,6 @@ void ActorViewerWindow::DrawElement() { actors.clear(); } } - ImGui::PopFont(); } void ActorViewerWindow::InitElement() { diff --git a/soh/soh/Enhancements/debugger/dlViewer.cpp b/soh/soh/Enhancements/debugger/dlViewer.cpp index 1459d2809..a591cf948 100644 --- a/soh/soh/Enhancements/debugger/dlViewer.cpp +++ b/soh/soh/Enhancements/debugger/dlViewer.cpp @@ -91,7 +91,6 @@ void PerformDisplayListSearch() { } void DLViewerWindow::DrawElement() { - ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); // Debounce the search field as listing otr files is expensive UIWidgets::PushStyleInput(THEME_COLOR); if (ImGui::InputText("Search Display Lists", searchString, ARRAY_COUNT(searchString))) { @@ -122,7 +121,6 @@ void DLViewerWindow::DrawElement() { UIWidgets::PopStyleCombobox(); if (activeDisplayList == "") { - ImGui::PopFont(); return; } @@ -131,7 +129,6 @@ void DLViewerWindow::DrawElement() { if (res->GetInitData()->Type != static_cast(Fast::ResourceType::DisplayList)) { ImGui::Text("Resource type is not a Display List. Please choose another."); - ImGui::PopFont(); return; } @@ -329,10 +326,8 @@ void DLViewerWindow::DrawElement() { } } catch (const std::exception& e) { ImGui::Text("Error displaying DL instructions."); - ImGui::PopFont(); return; } - ImGui::PopFont(); } void DLViewerWindow::InitElement() { diff --git a/soh/soh/Enhancements/debugger/valueViewer.cpp b/soh/soh/Enhancements/debugger/valueViewer.cpp index 40ec77f71..23ce4e26c 100644 --- a/soh/soh/Enhancements/debugger/valueViewer.cpp +++ b/soh/soh/Enhancements/debugger/valueViewer.cpp @@ -143,7 +143,6 @@ void RegisterValueViewerHooks() { RegisterShipInitFunc initFunc(RegisterValueViewerHooks, { CVAR_NAME }); void ValueViewerWindow::DrawElement() { - ImGui::PushFont(OTRGlobals::Instance->fontMonoLargest); UIWidgets::CVarCheckbox("Enable Printing", CVAR_NAME, UIWidgets::CheckboxOptions().Color(THEME_COLOR)); ImGui::BeginGroup(); @@ -264,7 +263,6 @@ void ValueViewerWindow::DrawElement() { } ImGui::EndGroup(); } - ImGui::PopFont(); } void ValueViewerWindow::InitElement() { diff --git a/soh/soh/Enhancements/enemyrandomizer.cpp b/soh/soh/Enhancements/enemyrandomizer.cpp index a2b72ca07..e638d04ad 100644 --- a/soh/soh/Enhancements/enemyrandomizer.cpp +++ b/soh/soh/Enhancements/enemyrandomizer.cpp @@ -286,7 +286,7 @@ void GetSelectedEnemies() { for (int i = 0; i < 49; i++) { if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0)) { selectedEnemyList.push_back(randomizedEnemySpawnTable[i]); - } else if (CVarGetInteger(enemyCVarList[i], 0)) { + } else if (CVarGetInteger(enemyCVarList[i], 1)) { selectedEnemyList.push_back(randomizedEnemySpawnTable[i]); } } diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index a60865de4..832276e65 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -1141,14 +1141,12 @@ void BeginFloatWindows(std::string UniqueName, bool& open, ImGuiWindowFlags flag Color_Background.b / 255.0f, Color_Background.a / 255.0f)); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); - ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::Begin(UniqueName.c_str(), &open, windowFlags); } void EndFloatWindows() { ImGui::PopStyleVar(); ImGui::PopStyleColor(); ImGui::PopStyleColor(); - ImGui::PopFont(); ImGui::End(); } @@ -1760,7 +1758,6 @@ static std::unordered_map buttonStrings = { { TRACKER_COMBO_BUTTON_D_LEFT, "D-Left" }, { TRACKER_COMBO_BUTTON_D_RIGHT, "D-Right" }}; void CheckTrackerSettingsWindow::DrawElement() { - ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 8.0f, 8.0f }); if (ImGui::BeginTable("CheckTrackerSettingsTable", 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV)) { ImGui::TableSetupColumn("General settings", ImGuiTableColumnFlags_WidthStretch, 200.0f); @@ -1827,7 +1824,6 @@ void CheckTrackerSettingsWindow::DrawElement() { ImGui::PopStyleVar(1); } ImGui::EndTable(); - ImGui::PopFont(); } void CheckTrackerWindow::InitElement() { diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp index 49755ff06..a6e1c6d07 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp @@ -660,7 +660,6 @@ void InitEntranceTrackingData() { void EntranceTrackerSettingsWindow::DrawElement() { - ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::TextWrapped("The entrance tracker will only track shuffled entrances"); UIWidgets::Spacer(0); @@ -722,7 +721,6 @@ void EntranceTrackerSettingsWindow::DrawElement() { ImGui::TextColored(ImColor(COLOR_GRAY), "Undiscovered Entrances"); ImGui::TreePop(); } - ImGui::PopFont(); } void EntranceTrackerWindow::Draw() { @@ -735,12 +733,10 @@ void EntranceTrackerWindow::Draw() { } void EntranceTrackerWindow::DrawElement() { - ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::SetNextWindowSize(ImVec2(600, 375), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Entrance Tracker", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { ImGui::End(); - ImGui::PopFont(); return; } @@ -938,7 +934,6 @@ void EntranceTrackerWindow::DrawElement() { } ImGui::EndChild(); ImGui::End(); - ImGui::PopFont(); } void EntranceTrackerWindow::InitElement() { diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index 9bb562b31..de214c131 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -469,6 +469,7 @@ void DrawItemCount(ItemTrackerItem item, bool hideMax) { if (!GameInteractor::IsSaveLoaded()) { return; } + ImGui::PushFont(OTRGlobals::Instance->fontMono); int iconSize = CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36); int textSize = CVarGetInteger(CVAR_TRACKER_ITEM("TextSize"), 13); ItemTrackerNumbers currentAndMax = GetItemCurrentAndMax(item); @@ -611,6 +612,7 @@ void DrawItemCount(ItemTrackerItem item, bool hideMax) { ImGui::SetCursorScreenPos(ImVec2(p.x, p.y - 14)); ImGui::Text(""); } + ImGui::PopFont(); } void DrawEquip(ItemTrackerItem item) { @@ -952,14 +954,12 @@ void BeginFloatingWindows(std::string UniqueName, ImGuiWindowFlags flags = 0) { ImGui::PushStyleColor(ImGuiCol_WindowBg, VecFromRGBA8(CVarGetColor(CVAR_TRACKER_ITEM("BgColor.Value"), {0, 0, 0, 0}))); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); - ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::Begin(UniqueName.c_str(), nullptr, windowFlags); } void EndFloatingWindows() { ImGui::PopStyleVar(); ImGui::PopStyleColor(); ImGui::PopStyleColor(); - ImGui::PopFont(); ImGui::End(); } @@ -1375,7 +1375,6 @@ static std::unordered_map extendedDisplayTypes = {{ SECTIO static std::unordered_map minimalDisplayTypes = {{ SECTION_DISPLAY_MINIMAL_HIDDEN, "Hidden" }, { SECTION_DISPLAY_MINIMAL_SEPARATE, "Separate" }}; void ItemTrackerSettingsWindow::DrawElement() { - ImGui::PushFont(OTRGlobals::Instance->fontStandardLarger); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 8.0f, 8.0f }); ImGui::BeginTable("itemTrackerSettingsTable", 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV); ImGui::TableSetupColumn("General settings", ImGuiTableColumnFlags_WidthStretch, 200.0f); @@ -1541,7 +1540,6 @@ void ItemTrackerSettingsWindow::DrawElement() { ImGui::PopStyleVar(1); ImGui::EndTable(); - ImGui::PopFont(); } void ItemTrackerWindow::InitElement() { diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index 4340be330..a16a98f9e 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -941,7 +941,6 @@ static bool initialized = false; void TimeSplitWindow::DrawElement() { ImGui::SetWindowFontScale(timeSplitsWindowSize); - ImGui::PushFont(OTRGlobals::Instance->fontMonoLargest); PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("Split Tabs")) { @@ -960,7 +959,6 @@ void TimeSplitWindow::DrawElement() { ImGui::EndTabBar(); } PopStyleTabs(); - ImGui::PopFont(); } void TimeSplitWindow::InitElement() { diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 70d12a78b..ed5a1169d 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -407,7 +407,7 @@ OTRGlobals::OTRGlobals() { fontStandard = CreateFontWithSize(16.0f, "fonts/Montserrat-Regular.ttf"); fontStandardLarger = CreateFontWithSize(20.0f, "fonts/Montserrat-Regular.ttf"); fontStandardLargest = CreateFontWithSize(24.0f, "fonts/Montserrat-Regular.ttf"); - ImGui::GetIO().FontDefault = fontMono; + ImGui::GetIO().FontDefault = fontStandardLarger; ScaleImGui(); // Move the camera strings from read only memory onto the heap (writable memory) diff --git a/soh/soh/SohGui/Menu.cpp b/soh/soh/SohGui/Menu.cpp index 4dd939df8..be4bbdc89 100644 --- a/soh/soh/SohGui/Menu.cpp +++ b/soh/soh/SohGui/Menu.cpp @@ -530,13 +530,13 @@ void Menu::DrawElement() { ImGui::End(); return; } - ImGui::PushFont(OTRGlobals::Instance->fontStandardLargest); ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiStyle& style = ImGui::GetStyle(); windowHeight = window->WorkRect.GetHeight(); windowWidth = window->WorkRect.GetWidth(); + ImGui::PushFont(OTRGlobals::Instance->fontStandardLargest); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); const char* headerCvar = CVAR_SETTING("Menu.ActiveHeader"); std::string headerIndex = CVarGetString(headerCvar, "Settings"); @@ -559,12 +559,13 @@ void Menu::DrawElement() { // Full screen menu with widths below 1280, heights below 800. // 5% of screen width/height padding on both sides above those resolutions. - ImVec2 menuSize = { std::fminf(1280, windowWidth), std::fminf(800, windowHeight) }; + // Menu width will never exceed a 16:9 aspect ratio. + ImVec2 menuSize = { windowWidth, windowHeight }; if (windowWidth > 1280) { - menuSize.x = floor(windowWidth * 0.9); + menuSize.x = std::fminf(windowWidth * 0.9f, (windowHeight * 1.77f)); } if (windowHeight > 800) { - menuSize.y = floor(windowHeight * 0.9); + menuSize.y = windowHeight * 0.9f; } pos += window->WorkRect.GetSize() / 2 - menuSize / 2; @@ -696,7 +697,12 @@ void Menu::DrawElement() { float sectionHeight = menuSize.y - headerHeight - 4 - style.ItemSpacing.y * 2; float columnHeight = sectionHeight - style.ItemSpacing.y * 4; ImGui::SetNextWindowPos(pos + style.ItemSpacing * 2); + + // Increase sidebar width on larger screens to accomodate people scaling their menus. float sidebarWidth = 200 - style.ItemSpacing.x; + if (menuSize.x > 1600) { + sidebarWidth = menuSize.x * 0.15f; + } const char* sidebarCvar = menuEntries.at(headerIndex).sidebarCvar; @@ -732,8 +738,8 @@ void Menu::DrawElement() { } } ImGui::EndChild(); + ImGui::PopFont(); - ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); pos = ImVec2{ sectionCenterX + (sidebarWidth / 2), topY } + style.ItemSpacing * 2; window->DrawList->AddRectFilled(pos, pos + ImVec2{ 4, sectionHeight - style.FramePadding.y * 2 }, ImGui::GetColorU32({ 255, 255, 255, 255 }), true, style.WindowRounding); @@ -802,8 +808,6 @@ void Menu::DrawElement() { if (!useColumns || menuSearchText.length() > 0) { ImGui::EndChild(); } - ImGui::PopFont(); - ImGui::PopFont(); if (!popout) { ImGui::PopStyleVar(); diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 5f4961279..1adf34f5e 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -34,7 +34,8 @@ void SohMenu::AddMenuEnhancements() { if (iter->first != 0) comboboxTooltip += "\n\n"; comboboxTooltip += std::string(iter->second.label) + " - " + std::string(iter->second.description); } - AddWidget(path, "Enhancement Presets", WIDGET_COMBOBOX) + AddWidget(path, "Enhancement Presets", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Select Preset", WIDGET_COMBOBOX) .ValuePointer(&enhancementPresetSelected) .Callback([](WidgetInfo& info) { const std::string presetTypeCvar = CVAR_GENERAL("SelectedPresets.") + std::to_string(PRESET_TYPE_ENHANCEMENTS); @@ -46,6 +47,7 @@ void SohMenu::AddMenuEnhancements() { .Tooltip(comboboxTooltip.c_str()) ); AddWidget(path, "Apply Preset##Enhancemnts", WIDGET_BUTTON) + .Options(ButtonOptions().Size(UIWidgets::Sizes::Inline)) .Callback([](WidgetInfo& info) { const std::string presetTypeCvar = CVAR_GENERAL("SelectedPresets.") + std::to_string(PRESET_TYPE_ENHANCEMENTS); const PresetTypeDefinition presetTypeDef = presetTypes.at(PRESET_TYPE_ENHANCEMENTS); @@ -1051,7 +1053,7 @@ void SohMenu::AddMenuEnhancements() { )); path.sidebarName = "Items"; - AddSidebarEntry("Enhancements", path.sidebarName, 2); + AddSidebarEntry("Enhancements", path.sidebarName, 3); path.column = SECTION_COLUMN_1; AddWidget(path, "Controls", WIDGET_SEPARATOR_TEXT); @@ -1678,7 +1680,7 @@ void SohMenu::AddMenuEnhancements() { ); path.sidebarName = "Extra Modes"; - AddSidebarEntry("Enhancements", path.sidebarName, 2); + AddSidebarEntry("Enhancements", path.sidebarName, 3); path.column = SECTION_COLUMN_1; AddWidget(path, "Mirrored World", WIDGET_CVAR_COMBOBOX) @@ -1703,19 +1705,6 @@ void SohMenu::AddMenuEnhancements() { " - Dungeons Random (Seeded): Dungeons are mirrored based on the current randomizer seed/file\n" ) ); - AddWidget(path, "Randomized Enemy Sizes", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("RandomizedEnemySizes")) - .Options(CheckboxOptions().Tooltip( - "Enemies and Bosses spawn with random sizes." - )); - AddWidget(path, "Scale Health with Size", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("EnemySizeScalesHealth")) - .PreFunc([](WidgetInfo& info) { - info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemySizes"), 0) == 0; - }) - .Options(CheckboxOptions().Tooltip( - "Scales normal enemies Health with their randomized size. *This will NOT affect bosses*" - )); AddWidget(path, "Ivan the Fairy (Coop Mode)", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("IvanCoopModeEnabled")) .Options(CheckboxOptions().Tooltip( @@ -1827,6 +1816,7 @@ void SohMenu::AddMenuEnhancements() { .PreFunc([](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; }); + path.column = SECTION_COLUMN_2; AddWidget(path, "Enemy Randomizer", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_ENHANCEMENT("RandomizedEnemies")) @@ -1843,22 +1833,34 @@ void SohMenu::AddMenuEnhancements() { "- Random (Seeded): Enemies are randomized based on the current randomizer seed/file\n" ) ); + AddWidget(path, "Randomized Enemy Sizes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RandomizedEnemySizes")) + .Options(CheckboxOptions().Tooltip("Enemies and Bosses spawn with random sizes.")); + AddWidget(path, "Scale Health with Size", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EnemySizeScalesHealth")) + .PreFunc( + [](WidgetInfo& info) { info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemySizes"), 0); }) + .Options(CheckboxOptions().Tooltip( + "Scales normal enemies Health with their randomized size. *This will NOT affect bosses*")); AddWidget(path, "Enemy List", WIDGET_SEPARATOR_TEXT) .PreFunc([](WidgetInfo& info) { - info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) != ENEMY_RANDOMIZER_RANDOM; + info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0); }); AddWidget(path, "Select All Enemies", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("RandomizedEnemyList.All")) .PreFunc([](WidgetInfo& info) { - info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) != ENEMY_RANDOMIZER_RANDOM; + info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0); }); - AddWidget(path, "Enemy List", WIDGET_SEPARATOR); + AddWidget(path, "Enemy List", WIDGET_SEPARATOR).PreFunc([](WidgetInfo& info) { + info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0); + }); for (int i = 0; i < RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE; i++) { AddWidget(path, enemyNameList[i], WIDGET_CVAR_CHECKBOX) .CVar(enemyCVarList[i]) + .Options(CheckboxOptions().DefaultValue(true)) .PreFunc([](WidgetInfo& info) { - info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) != ENEMY_RANDOMIZER_RANDOM; - info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0) == 1; + info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0); + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0); info.options->disabledTooltip = "These options are disabled because \"Select All Enemies\" is enabled."; }) .Callback([](WidgetInfo& info) { @@ -1871,6 +1873,14 @@ void SohMenu::AddMenuEnhancements() { AddSidebarEntry("Enhancements", path.sidebarName, 3); path.column = SECTION_COLUMN_1; + AddWidget(path, "Infinite...", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Money", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteMoney")); + AddWidget(path, "Health", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteHealth")); + AddWidget(path, "Ammo", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteAmmo")); + AddWidget(path, "Magic", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteMagic")); + AddWidget(path, "Nayru's Love", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteNayru")); + AddWidget(path, "Epona Boost", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteEponaBoost")); + AddWidget(path, "Inventory", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Super Tunic", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_CHEAT("SuperTunic")) @@ -1909,73 +1919,8 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip( "This allows you to put up for shield with any two-handed weapon in hand except for Deku Sticks." )); - AddWidget(path, "Deku Sticks:", WIDGET_CVAR_COMBOBOX) - .CVar(CVAR_CHEAT("DekuStick")) - .Options(ComboboxOptions().ComboMap( - dekuStickCheat - ).DefaultIndex(DEKU_STICK_NORMAL)); - AddWidget(path, "Bomb Timer Multiplier: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) - .CVar(CVAR_CHEAT("BombTimerMultiplier")) - .Options(FloatSliderOptions() - .Format("%.2f") - .Min(0.1f) - .Max(5.0f) - .DefaultValue(1.0f)); - AddWidget(path, "Hookshot Everything", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("HookshotEverything")) - .Options(CheckboxOptions().Tooltip( - "Makes every surface in the game hookshot-able." - )); - AddWidget(path, "Hookshot Reach Multiplier: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) - .CVar(CVAR_CHEAT("HookshotReachMultiplier")) - .Options(FloatSliderOptions() - .Format("%.2f") - .Min(1.0f) - .Max(5.0f)); - AddWidget(path, "Change Age", WIDGET_BUTTON) - .Options(ButtonOptions().Tooltip("Switches Link's age and reloads the area.")) - .Callback([](WidgetInfo& info){ - SwitchAge(); - }); - AddWidget(path, "Clear Cutscene Pointer", WIDGET_BUTTON) - .Callback([](WidgetInfo& info) { - GameInteractor::RawAction::ClearCutscenePointer(); - }) - .Options(ButtonOptions().Tooltip( - "Clears the cutscene pointer to a value safe for wrong warps." - )); path.column = SECTION_COLUMN_2; - AddWidget(path, "Infinite...", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Money", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteMoney")); - AddWidget(path, "Health", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteHealth")); - AddWidget(path, "Ammo", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteAmmo")); - AddWidget(path, "Magic", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteMagic")); - AddWidget(path, "Nayru's Love", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteNayru")); - AddWidget(path, "Epona Boost", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteEponaBoost")); - - AddWidget(path, "Save States", WIDGET_SEPARATOR_TEXT); - AddWidget(path, ICON_FA_EXCLAMATION_TRIANGLE " WARNING!!!! " ICON_FA_EXCLAMATION_TRIANGLE, WIDGET_TEXT) - .Options(WidgetOptions().Color(Colors::Orange)); - AddWidget(path, - "These are NOT like emulator states. They do not save your game progress " - "and they WILL break across transitions and load zones (like doors). " - "Support for related issues will not be provided.", WIDGET_TEXT - ); - AddWidget(path, "I promise I have read the warning", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("SaveStatePromise")) - .Callback([](WidgetInfo& info) { - CVarSetInteger(CVAR_CHEAT("SaveStatesEnabled"), 0); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - }); - AddWidget(path, "I understand, enable save states", WIDGET_CVAR_CHECKBOX) - .PreFunc([](WidgetInfo& info) { - info.isHidden = CVarGetInteger(CVAR_CHEAT("SaveStatePromise"), 0) == 0; - }) - .CVar(CVAR_CHEAT("SaveStatesEnabled")) - .Options(CheckboxOptions().Tooltip( - "F5 to save, F6 to change slots, F7 to load" - )); AddWidget(path, "Behavior", WIDGET_SEPARATOR_TEXT); AddWidget(path, "No Clip", WIDGET_CVAR_CHECKBOX) @@ -2024,7 +1969,46 @@ void SohMenu::AddMenuEnhancements() { "Keese and Guay no longer target you and simply ignore you as if you were wearing the " "Skull Mask." )); + + AddWidget(path, "Deku Sticks:", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_CHEAT("DekuStick")) + .Options(ComboboxOptions().ComboMap(dekuStickCheat).DefaultIndex(DEKU_STICK_NORMAL)); + AddWidget(path, "Bomb Timer Multiplier: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_CHEAT("BombTimerMultiplier")) + .Options(FloatSliderOptions().Format("%.2f").Min(0.1f).Max(5.0f).DefaultValue(1.0f)); + AddWidget(path, "Hookshot Everything", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("HookshotEverything")) + .Options(CheckboxOptions().Tooltip("Makes every surface in the game hookshot-able.")); + AddWidget(path, "Hookshot Reach Multiplier: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_CHEAT("HookshotReachMultiplier")) + .Options(FloatSliderOptions().Format("%.2f").Min(1.0f).Max(5.0f)); + AddWidget(path, "Change Age", WIDGET_BUTTON) + .Options(ButtonOptions().Tooltip("Switches Link's age and reloads the area.")) + .Callback([](WidgetInfo& info) { SwitchAge(); }); + AddWidget(path, "Clear Cutscene Pointer", WIDGET_BUTTON) + .Callback([](WidgetInfo& info) { GameInteractor::RawAction::ClearCutscenePointer(); }) + .Options(ButtonOptions().Tooltip("Clears the cutscene pointer to a value safe for wrong warps.")); + path.column = SECTION_COLUMN_3; + AddWidget(path, "Save States", WIDGET_SEPARATOR_TEXT); + AddWidget(path, ICON_FA_EXCLAMATION_TRIANGLE " WARNING!!!! " ICON_FA_EXCLAMATION_TRIANGLE, WIDGET_TEXT) + .Options(WidgetOptions().Color(Colors::Orange)); + AddWidget(path, + "These are NOT like emulator states. They do not save your game progress " + "and they WILL break across transitions and load zones (like doors). " + "Support for related issues will not be provided.", + WIDGET_TEXT); + AddWidget(path, "I promise I have read the warning", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("SaveStatePromise")) + .Callback([](WidgetInfo& info) { + CVarSetInteger(CVAR_CHEAT("SaveStatesEnabled"), 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + AddWidget(path, "I understand, enable save states", WIDGET_CVAR_CHECKBOX) + .PreFunc([](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_CHEAT("SaveStatePromise"), 0) == 0; }) + .CVar(CVAR_CHEAT("SaveStatesEnabled")) + .Options(CheckboxOptions().Tooltip("F5 to save, F6 to change slots, F7 to load")); + AddWidget(path, "Beta Quest", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Enable Beta Quest", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_CHEAT("EnableBetaQuest")) @@ -2100,7 +2084,7 @@ void SohMenu::AddMenuEnhancements() { // Timers path.sidebarName = "Timers"; - AddSidebarEntry("Enhancements", path.sidebarName, 1); + AddSidebarEntry("Enhancements", path.sidebarName, 3); AddWidget(path, "Toggle Timers Window", WIDGET_WINDOW_BUTTON) .CVar(CVAR_WINDOW("TimeDisplayEnabled")) .WindowName("Additional Timers") diff --git a/soh/soh/SohGui/SohMenuSettings.cpp b/soh/soh/SohGui/SohMenuSettings.cpp index b88065281..eec4c8674 100644 --- a/soh/soh/SohGui/SohMenuSettings.cpp +++ b/soh/soh/SohGui/SohMenuSettings.cpp @@ -55,6 +55,7 @@ void SohMenu::AddMenuSettings() { WidgetPath path = { "Settings", "General", SECTION_COLUMN_1 }; // General - Settings + AddWidget(path, "General Settings", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Menu Theme", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_SETTING("Menu.Theme")) .Options(ComboboxOptions() @@ -207,6 +208,7 @@ void SohMenu::AddMenuSettings() { "FPS than your monitor's refresh rate will waste resources, and might give a worse result."; path.sidebarName = "Graphics"; AddSidebarEntry("Settings", "Graphics", 3); + AddWidget(path, "Graphics Options", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Toggle Fullscreen", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("Fullscreen")) .Callback([](WidgetInfo& info) { Ship::Context::GetInstance()->GetWindow()->ToggleFullscreen(); }) @@ -292,9 +294,12 @@ void SohMenu::AddMenuSettings() { .CVar(CVAR_TEXTURE_FILTER) .Options(ComboboxOptions().Tooltip("Sets the applied Texture Filtering.").ComboMap(textureFilteringMap)); + path.column = SECTION_COLUMN_2; + AddWidget(path, "Advanced Graphics Options", WIDGET_SEPARATOR_TEXT); // Controls path.sidebarName = "Controls"; + path.column = SECTION_COLUMN_1; AddSidebarEntry("Settings", "Controls", 2); AddWidget(path, "Controller Bindings", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Popout Bindings Window", WIDGET_WINDOW_BUTTON) @@ -302,7 +307,9 @@ void SohMenu::AddMenuSettings() { .WindowName("Configure Controller") .Options(WindowButtonOptions().Tooltip("Enables the separate Bindings Window.")); - path.column = SECTION_COLUMN_2; + // Input Viewer + path.sidebarName = "Input Viewer"; + AddSidebarEntry("Settings", path.sidebarName, 3); AddWidget(path, "Input Viewer", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Toggle Input Viewer", WIDGET_WINDOW_BUTTON) .CVar(CVAR_WINDOW("InputViewer")) @@ -318,7 +325,7 @@ void SohMenu::AddMenuSettings() { // Notifications path.sidebarName = "Notifications"; path.column = SECTION_COLUMN_1; - AddSidebarEntry("Settings", "Notifications", 3); + AddSidebarEntry("Settings", path.sidebarName, 3); AddWidget(path, "Position", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_SETTING("Notifications.Position")) .Options(ComboboxOptions() diff --git a/soh/soh/SohGui/SohModals.cpp b/soh/soh/SohGui/SohModals.cpp index c810d52e6..b1fe0b43b 100644 --- a/soh/soh/SohGui/SohModals.cpp +++ b/soh/soh/SohGui/SohModals.cpp @@ -30,7 +30,6 @@ void SohModalWindow::Draw() { } void SohModalWindow::DrawElement() { - ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); if (modals.size() > 0) { SohModal curModal = modals.at(0); if (!ImGui::IsPopupOpen(curModal.title_.c_str())) { @@ -62,7 +61,6 @@ void SohModalWindow::DrawElement() { } ImGui::EndPopup(); } - ImGui::PopFont(); } void SohModalWindow::RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function button1callback, std::function button2callback) { diff --git a/soh/soh/SohGui/UIWidgets.hpp b/soh/soh/SohGui/UIWidgets.hpp index 649395b43..b9e69292e 100644 --- a/soh/soh/SohGui/UIWidgets.hpp +++ b/soh/soh/SohGui/UIWidgets.hpp @@ -515,92 +515,6 @@ namespace UIWidgets { void Separator(bool padTop = true, bool padBottom = true, float extraVerticalTopPadding = 0.0f, float extraVerticalBottomPadding = 0.0f); - /*using ComboVariant = std::variant&, const std::vector&>; - - bool Combobox(const char* label, int32_t* value, ComboVariant comboSource, const ComboboxOptions& options = {}) { - bool dirty = false; - float startX = ImGui::GetCursorPosX(); - std::string invisibleLabelStr = "##" + std::string(label); - const char* invisibleLabel = invisibleLabelStr.c_str(); - ImGui::PushID(label); - ImGui::BeginGroup(); - ImGui::BeginDisabled(options.disabled); - PushStyleCombobox(options.color); - if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Above) { - ImGui::Text("%s", label); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - } else if (options.labelPosition == LabelPosition::Near) { - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2); - } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { - ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x); - } - } else if (options.alignment == ComponentAlignment::Right) { - if (options.labelPosition == LabelPosition::Above) { - ImGui::NewLine(); - ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); - ImGui::Text("%s", label); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - } else if (options.labelPosition == LabelPosition::Near) { - ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { - float width = ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 4; - ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - } - } - if (ImGui::BeginCombo(invisibleLabel, comboMap.at(*value), options.flags)) { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); - for (const auto& pair : comboMap) { - if (strlen(pair.second) > 1) { - if (ImGui::Selectable(pair.second, pair.first == *value)) { - *value = pair.first; - dirty = true; - } - } - } - ImGui::PopStyleVar(); - ImGui::EndCombo(); - } - if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Near) { - ImGui::SameLine(); - ImGui::Text("%s", label); - } else if (options.labelPosition == LabelPosition::Far) { - ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); - ImGui::Text("%s", label); - } - } else if (options.alignment == ComponentAlignment::Right) { - if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { - ImGui::SameLine(startX); - ImGui::Text("%s", label); - } - } - PopStyleCombobox(); - ImGui::EndDisabled(); - ImGui::EndGroup(); - if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.disabledTooltip)) { - ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip).c_str()); - } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !Ship_IsCStringEmpty(options.tooltip)) { - ImGui::SetTooltip("%s", WrappedText(options.tooltip).c_str()); - } - ImGui::PopID(); - return dirty; - } - - bool CVarCombobox(const char* label, const char* cvarName, ComboVariant comboSource, const ComboboxOptions& options = {}) { - bool dirty = false; - int32_t value = CVarGetInteger(cvarName, options.defaultIndex); - if (Combobox(label, &value, comboSource, options)) { - CVarSetInteger(cvarName, value); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); - ShipInit::Init(cvarName); - dirty = true; - } - return dirty; - }*/ - float CalcComboWidth(const char* preview_value, ImGuiComboFlags flags); template From 233c07d16aae1bfae44b9b530c67d77f4ff057d2 Mon Sep 17 00:00:00 2001 From: Pepper0ni <93387759+Pepper0ni@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:32:54 +0000 Subject: [PATCH 15/53] Fix entrance oversight in shield grave (#5137) --- .../randomizer/location_access/overworld/graveyard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/randomizer/location_access/overworld/graveyard.cpp b/soh/soh/Enhancements/randomizer/location_access/overworld/graveyard.cpp index 87a2e3b48..28d272711 100644 --- a/soh/soh/Enhancements/randomizer/location_access/overworld/graveyard.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/overworld/graveyard.cpp @@ -50,7 +50,7 @@ void RegionTable_Init_Graveyard() { LOCATION(RC_GRAVEYARD_SHIELD_GRAVE_FAIRY_8, true), }, { //Exits - Entrance(RR_THE_GRAVEYARD, []{return true;}), + Entrance(RR_GRAVEYARD_SHIELD_GRAVE, []{return true;}), }); areaTable[RR_GRAVEYARD_HEART_PIECE_GRAVE] = Region("Graveyard Heart Piece Grave", "Graveyard Heart Piece Grave", {}, NO_DAY_NIGHT_CYCLE, {}, { From c72efd08a97b56f80e87d32f545024b9fd4ee651 Mon Sep 17 00:00:00 2001 From: aMannus Date: Wed, 19 Mar 2025 08:43:14 +0100 Subject: [PATCH 16/53] Menu - Enhancements and cheats re-organisation (#5142) * First pass cheats, extra modes, minigames, difficulty, fixes, graphics * More progress. One tab left * Enhancements & cheats done and fix for item tracker font --- soh/soh/Enhancements/audio/AudioEditor.cpp | 151 +- soh/soh/Enhancements/presets.h | 8 +- .../randomizer/randomizer_item_tracker.cpp | 4 +- soh/soh/SohGui/SohMenuEnhancements.cpp | 2356 +++++++---------- soh/soh/SohGui/SohMenuSettings.cpp | 14 + soh/soh/config/ConfigMigrators.h | 4 +- soh/src/code/z_lifemeter.c | 2 +- soh/src/code/z_parameter.c | 2 +- 8 files changed, 1127 insertions(+), 1414 deletions(-) diff --git a/soh/soh/Enhancements/audio/AudioEditor.cpp b/soh/soh/Enhancements/audio/AudioEditor.cpp index 0149aa148..efbcd5c28 100644 --- a/soh/soh/Enhancements/audio/AudioEditor.cpp +++ b/soh/soh/Enhancements/audio/AudioEditor.cpp @@ -488,6 +488,85 @@ void AudioEditor::DrawElement() { UIWidgets::PushStyleTabs(THEME_COLOR); if (ImGui::BeginTabBar("SfxContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { + + static ImVec2 cellPadding(8.0f, 8.0f); + if (ImGui::BeginTabItem("Audio Options")) { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cellPadding); + ImGui::BeginTable("Audio Options", 1, ImGuiTableFlags_SizingStretchSame); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::BeginChild("SfxOptions", ImVec2(0, -8))) { + UIWidgets::CVarCheckbox( + "Mute Low HP Alarm", CVAR_AUDIO("LowHPAlarm"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR).Tooltip("Disable the low HP beeping sound.")); + UIWidgets::CVarCheckbox("Disable Navi Call Audio", CVAR_AUDIO("DisableNaviCallAudio"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Disables the voice audio when Navi calls you.")); + UIWidgets::CVarCheckbox( + "Disable Enemy Proximity Music", CVAR_AUDIO("EnemyBGMDisable"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Disables the music change when getting close to enemies. Useful for hearing " + "your custom music for each scene more often.")); + UIWidgets::CVarCheckbox( + "Disable Leading Music in Lost Woods", CVAR_AUDIO("LostWoodsConsistentVolume"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Disables the volume shifting in the Lost Woods. Useful for hearing " + "your custom music in the Lost Woods if you don't need the navigation assitance " + "the volume changing provides. If toggling this while in the Lost Woods, reload " + "the area for the effect to kick in.")); + UIWidgets::CVarCheckbox( + "Display Sequence Name on Overlay", CVAR_AUDIO("SeqNameOverlay"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Displays the name of the current sequence in the corner of the screen whenever a new " + "sequence " + "is loaded to the main sequence player (does not apply to fanfares or enemy BGM).")); + UIWidgets::CVarSliderInt("Overlay Duration: %d seconds", CVAR_AUDIO("SeqNameOverlayDuration"), + UIWidgets::IntSliderOptions() + .Min(1) + .Max(10) + .DefaultValue(5) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + UIWidgets::CVarSliderFloat("Link's voice pitch multiplier", CVAR_AUDIO("LinkVoiceFreqMultiplier"), + UIWidgets::FloatSliderOptions() + .IsPercentage() + .Min(0.4f) + .Max(2.5f) + .DefaultValue(1.0f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + ImGui::SameLine(); + ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 40.f); + if (UIWidgets::Button("Reset##linkVoiceFreqMultiplier", + UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { + CVarSetFloat(CVAR_AUDIO("LinkVoiceFreqMultiplier"), 1.0f); + } + UIWidgets::CVarCheckbox( + "Randomize All Music and Sound Effects on New Scene", CVAR_AUDIO("RandomizeAllOnNewScene"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip( + "Enables randomizing all unlocked music and sound effects when you enter a new scene.")); + UIWidgets::CVarCheckbox( + "Lower Octaves of Unplayable High Notes", CVAR_AUDIO("ExperimentalOctaveDrop"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip("Some custom sequences may have notes that are too high for the game's audio " + "engine to play. Enabling this checkbox will cause these notes to drop a " + "couple of octaves so they can still harmonize with the other notes of the " + "sequence.")); + } + ImGui::EndChild(); + ImGui::EndTable(); + ImGui::PopStyleVar(1); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Background Music")) { Draw_SfxTab("backgroundMusic", SEQ_BGM_WORLD, "Background Music"); ImGui::EndTabItem(); @@ -518,78 +597,6 @@ void AudioEditor::DrawElement() { ImGui::EndTabItem(); } - static ImVec2 cellPadding(8.0f, 8.0f); - if (ImGui::BeginTabItem("Options")) { - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cellPadding); - ImGui::BeginTable("Options", 1, ImGuiTableFlags_SizingStretchSame); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::BeginChild("SfxOptions", ImVec2(0, -8))) { - UIWidgets::CVarCheckbox( - "Disable Enemy Proximity Music", CVAR_AUDIO("EnemyBGMDisable"), - UIWidgets::CheckboxOptions() - .Color(THEME_COLOR) - .Tooltip("Disables the music change when getting close to enemies. Useful for hearing " - "your custom music for each scene more often.")); - UIWidgets::CVarCheckbox( - "Disable Leading Music in Lost Woods", CVAR_AUDIO("LostWoodsConsistentVolume"), - UIWidgets::CheckboxOptions() - .Color(THEME_COLOR) - .Tooltip("Disables the volume shifting in the Lost Woods. Useful for hearing " - "your custom music in the Lost Woods if you don't need the navigation assitance " - "the volume changing provides. If toggling this while in the Lost Woods, reload " - "the area for the effect to kick in.")); - UIWidgets::CVarCheckbox( - "Display Sequence Name on Overlay", CVAR_AUDIO("SeqNameOverlay"), - UIWidgets::CheckboxOptions() - .Color(THEME_COLOR) - .Tooltip("Displays the name of the current sequence in the corner of the screen whenever a new " - "sequence " - "is loaded to the main sequence player (does not apply to fanfares or enemy BGM).")); - UIWidgets::CVarSliderInt("Overlay Duration: %d seconds", CVAR_AUDIO("SeqNameOverlayDuration"), - UIWidgets::IntSliderOptions() - .Min(1) - .Max(10) - .DefaultValue(5) - .Size(ImVec2(300.0f, 0.0f)) - .Color(THEME_COLOR)); - UIWidgets::CVarSliderFloat("Link's voice pitch multiplier", - CVAR_AUDIO("LinkVoiceFreqMultiplier"), - UIWidgets::FloatSliderOptions() - .IsPercentage() - .Min(0.4f) - .Max(2.5f) - .DefaultValue(1.0f) - .Size(ImVec2(300.0f, 0.0f)) - .Color(THEME_COLOR)); - ImGui::SameLine(); - ImGui::SetCursorPosY(ImGui::GetCursorPos().y + 40.f); - if (UIWidgets::Button("Reset##linkVoiceFreqMultiplier", - UIWidgets::ButtonOptions().Size(ImVec2(80, 36)).Padding(ImVec2(5.0f, 0.0f)))) { - CVarSetFloat(CVAR_AUDIO("LinkVoiceFreqMultiplier"), 1.0f); - } - UIWidgets::CVarCheckbox( - "Randomize All Music and Sound Effects on New Scene", CVAR_AUDIO("RandomizeAllOnNewScene"), - UIWidgets::CheckboxOptions() - .Color(THEME_COLOR) - .Tooltip( - "Enables randomizing all unlocked music and sound effects when you enter a new scene.")); - UIWidgets::CVarCheckbox( - "Lower Octaves of Unplayable High Notes", CVAR_AUDIO("ExperimentalOctaveDrop"), - UIWidgets::CheckboxOptions() - .Color(THEME_COLOR) - .Tooltip("Some custom sequences may have notes that are too high for the game's audio " - "engine to play. Enabling this checkbox will cause these notes to drop a " - "couple of octaves so they can still harmonize with the other notes of the " - "sequence.")); - } - ImGui::EndChild(); - ImGui::EndTable(); - ImGui::PopStyleVar(1); - ImGui::EndTabItem(); - } - static bool excludeTabOpen = false; if (ImGui::BeginTabItem("Audio Shuffle Pool Management")) { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cellPadding); diff --git a/soh/soh/Enhancements/presets.h b/soh/soh/Enhancements/presets.h index 2922ec589..a7bca4e66 100644 --- a/soh/soh/Enhancements/presets.h +++ b/soh/soh/Enhancements/presets.h @@ -242,7 +242,7 @@ const std::vector enhancedPresetEntries = { PRESET_ENTRY_S32(CVAR_ENHANCEMENT("SeparateArrows"), 1), // Disable Navi Call Audio - PRESET_ENTRY_S32(CVAR_ENHANCEMENT("DisableNaviCallAudio"), 1), + PRESET_ENTRY_S32(CVAR_AUDIO("DisableNaviCallAudio"), 1), // Equipment Toggle PRESET_ENTRY_S32(CVAR_ENHANCEMENT("EquipmentCanBeRemoved"), 1), @@ -373,7 +373,7 @@ const std::vector randomizerPresetEntries = { PRESET_ENTRY_S32(CVAR_ENHANCEMENT("SeparateArrows"), 1), // Disable Navi Call Audio - PRESET_ENTRY_S32(CVAR_ENHANCEMENT("DisableNaviCallAudio"), 1), + PRESET_ENTRY_S32(CVAR_AUDIO("DisableNaviCallAudio"), 1), // Equipment Toggle PRESET_ENTRY_S32(CVAR_ENHANCEMENT("EquipmentCanBeRemoved"), 1), @@ -436,7 +436,7 @@ const std::vector spockRacePresetEntries = { PRESET_ENTRY_S32(CVAR_ENHANCEMENT("FasterBlockPush"), 5), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("FasterHeavyBlockLift"), 1), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("NoForcedNavi"), 1), - PRESET_ENTRY_S32(CVAR_ENHANCEMENT("DisableNaviCallAudio"), 1), + PRESET_ENTRY_S32(CVAR_AUDIO("DisableNaviCallAudio"), 1), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("FastChests"), 1), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("FastDrops"), 1), PRESET_ENTRY_S32(CVAR_SETTING("DpadInText"), 1), @@ -531,7 +531,7 @@ const std::vector spockRaceNoLogicPresetEntries = { PRESET_ENTRY_S32(CVAR_ENHANCEMENT("CustomizeFishing"), 1), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("DampeAllNight"), 1), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("DayGravePull"), 1), - PRESET_ENTRY_S32(CVAR_ENHANCEMENT("DisableNaviCallAudio"), 1), + PRESET_ENTRY_S32(CVAR_AUDIO("DisableNaviCallAudio"), 1), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("DpadNoDropOcarinaInput"), 1), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("EquipmentCanBeRemoved"), 1), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("FastBoomerang"), 1), diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index de214c131..51f161def 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -469,7 +469,6 @@ void DrawItemCount(ItemTrackerItem item, bool hideMax) { if (!GameInteractor::IsSaveLoaded()) { return; } - ImGui::PushFont(OTRGlobals::Instance->fontMono); int iconSize = CVarGetInteger(CVAR_TRACKER_ITEM("IconSize"), 36); int textSize = CVarGetInteger(CVAR_TRACKER_ITEM("TextSize"), 13); ItemTrackerNumbers currentAndMax = GetItemCurrentAndMax(item); @@ -612,7 +611,6 @@ void DrawItemCount(ItemTrackerItem item, bool hideMax) { ImGui::SetCursorScreenPos(ImVec2(p.x, p.y - 14)); ImGui::Text(""); } - ImGui::PopFont(); } void DrawEquip(ItemTrackerItem item) { @@ -1218,9 +1216,11 @@ void ItemTrackerWindow::Draw() { if (!IsVisible()) { return; } + ImGui::PushFont(OTRGlobals::Instance->fontMono); DrawElement(); // Sync up the IsVisible flag if it was changed by ImGui SyncVisibilityConsoleVariable(); + ImGui::PopFont(); } void ItemTrackerWindow::DrawElement() { diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 1adf34f5e..6f0caff61 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -66,400 +66,193 @@ void SohMenu::AddMenuEnhancements() { Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); }); - path.sidebarName = "Gameplay"; + // Quality of Life + path.sidebarName = "Quality of Life"; AddSidebarEntry("Enhancements", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; - AddWidget(path, "Boot Sequence", WIDGET_CVAR_COMBOBOX) - .CVar(CVAR_ENHANCEMENT("BootSequence")) - .Options(ComboboxOptions() - .DefaultIndex(BOOTSEQUENCE_DEFAULT) - .ComboMap(bootSequenceLabels) - .Tooltip( - "Configure what happens when starting or resetting the game.\n\n" - "Default: LUS logo -> N64 logo\n" - "Authentic: N64 logo only\n" - "File Select: Skip to file select menu" - ) - ); + AddWidget(path, "Saving", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Autosave", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("Autosave")) .Options(CheckboxOptions().Tooltip( "Save the game automatically on a 3 minute interval and when soft-resetting the game. The interval " "autosave will wait if the game is paused in any way (dialogue, pause screen up, cutscenes, " "etc.).\n\n" - "The soft-reset save will *not* trigger in cutscene maps like the Chamber of Sages!" - )); + "The soft-reset save will *not* trigger in cutscene maps like the Chamber of Sages!")); + AddWidget(path, "Remember Save Location", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RememberSaveLocation")) + .Options(CheckboxOptions().Tooltip( + "When loading a save, places Link at the last entrance he went through.\n" + "This doesn't work if the save was made in grottos, fairy fountains, or dungeons.")); - AddWidget(path, "Audio", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Mute Low HP Alarm", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("LowHPAlarm")) + AddWidget(path, "Containers Match Contents", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Chest Size & Texture Matches Contents", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents")) + .Callback([](WidgetInfo& info) { + if (CVarGetInteger(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents"), CSMC_DISABLED) == CSMC_DISABLED) { + CVarSetInteger(CVAR_ENHANCEMENT("ChestSizeDependsStoneOfAgony"), 0); + } + }) + .Options(ComboboxOptions() + .ComboMap(chestStyleMatchesContentsOptions) + .DefaultIndex(CSMC_DISABLED) + .Tooltip("Chest sizes and textures are changed to help identify the item inside.\n" + " - Major items: Large gold chests\n" + " - Lesser items: Large brown chests\n" + " - Junk items: Small brown chests\n" + " - Small keys: Small silver chests\n" + " - Boss keys: Vanilla size and texture\n" + " - Skulltula Tokens: Small skulltula chest\n" + "\n" + "NOTE: Textures will not apply if you are using a mod pack with a custom chest model.")); + AddWidget(path, "Chests of Agony", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ChestSizeDependsStoneOfAgony")) + .PreFunc([](WidgetInfo& info) { + info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchesContents"), CSMC_DISABLED); + }) + .Options(CheckboxOptions().Tooltip("Only change the size/texture of chests if you have the Stone of Agony.")); + + AddWidget(path, "Time of Day", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Nighttime GS Always Spawn", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NightGSAlwaysSpawn")) + .Options(CheckboxOptions().Tooltip("Nighttime Skulltulas will spawn during both day and night.")); + AddWidget(path, "Pull Grave during the day", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DayGravePull")) + .Options(CheckboxOptions().Tooltip("Allows graves to be pulled when child during the day.")); + AddWidget(path, "Dampe Appears All Night", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DampeAllNight")) .Options(CheckboxOptions().Tooltip( - "Disable the low HP beeping sound." - )); - AddWidget(path, "Disable Navi Call Audio", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DisableNaviCallAudio")) + "Makes Dampe appear anytime during the night, not just his usual working hours.")); + AddWidget(path, "Exit Market at Night", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("MarketSneak")) .Options(CheckboxOptions().Tooltip( - "Disables the voice audio when Navi calls you." - )); + "Allows exiting Hyrule Castle Market Town to Hyrule Field at night by speaking to the guard " + "next to the gate.")); + AddWidget(path, "Shops and Games Always Open", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("OpenAllHours")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = + IS_RANDO && OTRGlobals::Instance->gRandoContext->GetOption(RSK_LOCK_OVERWORLD_DOORS).Is(RO_GENERIC_ON); + }) + .Options( + CheckboxOptions() + .Tooltip("Shops and Minigames are open both day and night. Requires a scene reload to take effect.") + .DisabledTooltip("This is not compatible with the Locked Overworld Doors Randomizer option.")); AddWidget(path, "Pause Menu", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Allow the Cursor to be on any slot", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_ENHANCEMENT("PauseAnyCursor")) - .Options(ComboboxOptions() - .ComboMap(cursorAnywhereValues) - .DefaultIndex(PAUSE_ANY_CURSOR_RANDO_ONLY) - .Tooltip( - "Allows the cursor on the pause menu to be over any slot. Sometimes required in Randomizer " - "to select certain items." - ) - ); - AddWidget(path, "Assignable Tunics and Boots", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("AssignableTunicsAndBoots")) + .Options( + ComboboxOptions() + .ComboMap(cursorAnywhereValues) + .DefaultIndex(PAUSE_ANY_CURSOR_RANDO_ONLY) + .Tooltip("Allows the cursor on the pause menu to be over any slot. Sometimes required in Randomizer " + "to select certain items.")); + AddWidget(path, "Pause Warp", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("PauseWarp")) .Options(CheckboxOptions().Tooltip( - "Allows equipping the Tunics and Boots to C-Buttons/D-Pad." - )); - // TODO: Revist strength toggle, it's currently separate but should probably go here and be locked behind the - // Equipment toggle settings. Also maybe these should all be in Items sidebar? - AddWidget(path, "Equipment Toggle", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("EquipmentCanBeRemoved")) - .Options(CheckboxOptions().Tooltip( - "Allows equipment to be removed by toggling it off on\n the equipment subscreen." - )); - AddWidget(path, "Sword Toggle Options", WIDGET_CVAR_COMBOBOX) - .CVar(CVAR_ENHANCEMENT("SwordToggle")) - .PreFunc([](WidgetInfo& info) { - info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("EquipmentCanBeRemoved"), 0) == 0; - }) - .Options(ComboboxOptions() - .ComboMap(swordToggleModes) - .DefaultIndex(SWORD_TOGGLE_NONE) - .Tooltip( - "Introduces Options for unequipping Link's sword\n\n" - "None: Only Biggoron's Sword/Giant's Knife can be toggled. Doing so will equip the Master Sword.\n\n" - "Child Toggle: This will allow for completely unequipping any sword as child link.\n\n" - "Both Ages: Any sword can be unequipped as either age. This may lead to swordless glitches as Adult." - ) - ); - - AddWidget(path, "Quality of Life", WIDGET_SEPARATOR_TEXT); - // Maybe should be in Timesavers somewhere? - AddWidget(path, "Link's Cow in Both Time Periods", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("CowOfTime")) - .Options(CheckboxOptions().Tooltip( - "Allows the Lon Lon Ranch Obstacle Course reward to be shared across time periods." - )); - // Maybe should be in Difficulty Options somewhere? - AddWidget(path, "Enable Visual Guard Vision", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("GuardVision")); - // Maybe should be in TImesavers somewhere? - AddWidget(path, "Pull Grave during the day", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DayGravePull")) - .Options(CheckboxOptions().Tooltip( - "Allows graves to be pulled when child during the day." - )); + "Selection of warp song in pause menu initiates a warp. Disables song playback.")); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Controls", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Answer Navi Prompt with L Button", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NaviOnL")) + .Options(CheckboxOptions().Tooltip("Speak to Navi with L but enter First-Person Camera with C-Up")); AddWidget(path, "Don't Require Input for Credits Sequence", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("NoInputForCredits")) .Options(CheckboxOptions().Tooltip( "Removes the Input Requirement on Text boxes after defeating Ganon, allowing the Credits " - "Sequence to continue to progress." - )); - AddWidget(path, "Answer Navi Prompt with L Button", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("NaviOnL")) - .Options(CheckboxOptions().Tooltip( - "Speak to Navi with L but enter First-Person Camera with C-Up" - )); - AddWidget(path, "Disable Crit Wiggle", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DisableCritWiggle")) - .Options(CheckboxOptions().Tooltip( - "Disable Random Camera Wiggle at Low Health." - )); - AddWidget(path, "Targetable Hookshot Reticle", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("HookshotableReticle")) - .Options(CheckboxOptions().Tooltip( - "Makes the Hookshot Reticle use a different color when aiming at Hookshotable Collision." - )); - AddWidget(path, "Faster Rupee Accumulator", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FasterRupeeAccumulator")) - .Options(CheckboxOptions().Tooltip( - "Causes your Wallet to fill and empty faster when you gain or lose money." - )); - AddWidget(path, "Fun/Aesthetic Options", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Enable Passage of Time on File Select", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("TimeFlowFileSelect")); - AddWidget(path, "Dogs Follow you Everywhere", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DogFollowsEverywhere")) - .Options(CheckboxOptions().Tooltip( - "Allows dogs to follow you anywhere you go, even if you leave the Market." - )); - AddWidget(path, "Enemy Health Bars", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("EnemyHealthBar")) - .Options(CheckboxOptions().Tooltip( - "Renders a health bar for Enemies when Z-Targeted." - )); - - path.column = SECTION_COLUMN_2; - AddWidget(path, "Authentic Bug Fixes", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Fix L&R Pause Menu", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixMenuLR")) - .Options(CheckboxOptions().Tooltip( - "Makes the L and R buttons in the pause menu the same color" - )); - AddWidget(path, "Fix L&Z Page Switch in Pause Menu", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("NGCKaleidoSwitcher")) - .Options(CheckboxOptions().Tooltip( - "Makes L and R switch pages like on the Gamecube. Z opens the Debug Menu instead." - )); - AddWidget(path, "Fix Dungeon Entrances", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixDungeonMinimapIcon")) - .Options(CheckboxOptions().Tooltip( - "Removes the Dungeon Entrance icon on the top-left corner of the screen when no dungeon is present on the " - "current map." - )); - AddWidget(path, "Fix Two-Handled Idle Animations", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("TwoHandedIdle")) - .Options(CheckboxOptions().Tooltip( - "Re-Enables the two-handed idle animation, a seemingly finished animation that was disabled on accident " - "in the original game." - )); - AddWidget(path, "Fix the Gravedigging Tour Glitch", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("GravediggingTourFix")) - .PreFunc([](WidgetInfo& info) { - info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); - info.options->disabledTooltip = "This setting is always enabled in randomized save files."; - }) - .Options(CheckboxOptions().Tooltip( - "Fixes a bug where the Gravedigging Tour Heart Piece disappears if the area reloads." - )); - AddWidget(path, "Fix Deku Nut Upgrade", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DekuNutUpgradeFix")) - .Options(CheckboxOptions().Tooltip( - "Prevents the Forest Stage Deku Nut upgrade from becoming unobtainable after receiving the Poacher's Saw." - )); - AddWidget(path, "Fix Navi Text HUD Position", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("NaviTextFix")) - .Options(CheckboxOptions().Tooltip( - "Correctly centers the Navi text prompt on the HUD's C-Up button." - )); - AddWidget(path, "Fix Anubis Fireballs", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("AnubisFix")) - .Options(CheckboxOptions().Tooltip( - "Make Anubis Fireballs do Fire damage when reflected back at them with the Mirror Shield." - )); - AddWidget(path, "Fix Megaton Hammer Crouch Stab", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("CrouchStabHammerFix")) - .Callback([](WidgetInfo& info) { - if (!CVarGetInteger(CVAR_ENHANCEMENT("CrouchStabHammerFix"), 0)) { - CVarClear(CVAR_ENHANCEMENT("CrouchStabFix")); - } - }) - .Options(CheckboxOptions().Tooltip( - "Make the Megaton Hammer's crouch stab able to destroy rocks without first swinging it normally." - )); - AddWidget(path, "Remove Power Crouch Stab", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("CrouchStabFix")) - .PreFunc([](WidgetInfo& info) { - info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("CrouchStabHammerFix"), 0) == 0; - }) - .Options(CheckboxOptions().Tooltip( - "Make crouch stabbing always do the same damage as a regular slash." - )); - AddWidget(path, "Fix Credits Timing", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("CreditsFix")) - .Options(CheckboxOptions().Tooltip( - "Extend certain credits scenes so the music lines up properly with the visuals." - )); - AddWidget(path, "Fix Gerudo Warrior's Clothing Colors", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("GerudoWarriorClothingFix")) - .Options(CheckboxOptions().Tooltip( - "Prevent the Gerudo Warrior's clothes changign color when changing Link's tunic or " - "using Bombs in front of her." - )); - AddWidget(path, "Fix Camera Drift", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixCameraDrift")) - .Options(CheckboxOptions().Tooltip( - "Fixes camera slightly drifting to the left when standing still due to a math error." - )); - AddWidget(path, "Fix Camera Swing", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixCameraSwing")) - .Options(CheckboxOptions().Tooltip( - "Fixes camera getting stuck on collision when standing still. Also fixes slight shift " - "back in camera when Link stops moving." - )); - AddWidget(path, "Fix Hanging Ledge Swing Rate", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixHangingLedgeSwingRate")) - .Options(CheckboxOptions().Tooltip( - "Fixes camera swing rate when the player falls off a ledge and the camera swings around." - )); - AddWidget(path, "Fix Missing Jingle after 5 Silver Rupees", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("SilverRupeeJingleExtend")) - .Options(CheckboxOptions().Tooltip( - "Adds 5 higher pitches for the Silver Rupee Jingle for the rooms with more than 5 Silver Rupees. " - "Only relevant for playthroughs involving Master Quest Dungeons." - )); - AddWidget(path, "Fix Out of Bounds Textures", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixTexturesOOB")) - .Callback([](WidgetInfo& info) { - ApplyAuthenticGfxPatches(); - }) - .Options(CheckboxOptions().Tooltip( - "Fixes authentic out of bounds texture reads, instead loading textures with the correct size." - )); - AddWidget(path, "Fix Poacher's Saw Softlock", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixSawSoftlock")) - .PreFunc([](WidgetInfo& info) { - info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("SkipText"), 0) == 1; - info.options->disabledTooltip = "This option is forced on when Skip Text is enabled."; - }) - .Options(CheckboxOptions().Tooltip( - "Prevents the Poacher's Saw softlock from mashing through the text, or with Skip Text enabled." - )); - AddWidget(path, "Fix Enemies not Spawning Near Water", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("EnemySpawnsOverWaterboxes")) - .Options(CheckboxOptions().Tooltip( - "Causes respanwing enemies, like Stalchildren, to appear on land near bodies of water. " - "Fixes an incorrect calculation that acted like water underneath ground was above it." - )); - AddWidget(path, "Fix Bush Item Drops", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("BushDropFix")) - .Options(CheckboxOptions().Tooltip( - "Fixes the bushes to drop items correctly rather than spawning undefined items." - )); - AddWidget(path, "Fix Falling from Vine Edges", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixVineFall")) - .Options(CheckboxOptions().Tooltip( - "Prevents immediately falling off climbable surfaces if climbing on the edges." - )); - AddWidget(path, "Fix Link's Eyes Open while Sleeping", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixEyesOpenWhileSleeping")) - .Options(CheckboxOptions().Tooltip( - "Fixes Link's eyes being open in the openeing cutscene when he is supposed to be sleeping." - )); - AddWidget(path, "Fix Darunia Dancing Too Fast", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixDaruniaDanceSpeed")) - .Options(CheckboxOptions().Tooltip( - "Fixes Darunia's dancing speed so he dances to the beat of Saria's Song, like in the Original Game." - )); - AddWidget(path, "Fix Raised Floor Switches", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixFloorSwitches")) - .Options(CheckboxOptions().Tooltip( - "Fixes the two raised floor switches, the one in Forest Temple Basement and the one at the top of Fire " - "Temple. This will lower them, making activating them easier." - )); - AddWidget(path, "Fix Zora Hint Dialogue", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixZoraHintDialogue")) - .Options(CheckboxOptions().Tooltip( - "Fixes one Zora's dialogue giving a hint about bringing Ruto's Letter to King Zora to properly occur " - "before moving King Zora rather than after." - )); - AddWidget(path, "Fix Hand Holding Hammer", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixHammerHand")) - .Callback([](WidgetInfo& info) { - UpdatePatchHand(); - }) - .Options(CheckboxOptions().Tooltip( - "Fixes Adult Link having a backwards Left hand when holding the Megaton Hammer." - )); - AddWidget(path, "Fix Broken Giant's Knife Bug", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FixGrokenGiantsKnife")) - .PreFunc([](WidgetInfo& info) { - info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); - info.options->disabledTooltip = "This setting is forcefully enabled when you are playing a Randomizer."; - }) - .Callback([](WidgetInfo& info) { - bool hasGiantsKnife = CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_BIGGORON); - bool hasBrokenKnife = CHECK_OWNED_EQUIP_ALT(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_BROKENGIANTKNIFE); - bool knifeIsBroken = gSaveContext.swordHealth == 0.0f; - - if (hasGiantsKnife && (hasBrokenKnife != knifeIsBroken)) { - func_800849EC(gPlayState); - } - }) - .Options(CheckboxOptions().Tooltip( - "Fixes the Broken Giant's Knife flag not being reset when Medigoron fixes it." - )); - - path.column = SECTION_COLUMN_3; - AddWidget(path, "Restorations", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Red Ganon Blood", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("RedGanonBlood")) - .Options(CheckboxOptions().Tooltip( - "Restore the original red blood from NTSC 1.0/1.1. Disable for Green blood." - )); - AddWidget(path, "Fish while Hovering", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("HoverFishing")) - .Options(CheckboxOptions().Tooltip( - "Restore a bug from NSTC 1.0 that allows casting the Fishing Rod while using the Hover Boots." - )); - AddWidget(path, "N64 Weird Frames", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("N64WeirdFrames")) - .Options(CheckboxOptions().Tooltip( - "Restores N64 Weird Frames allwing weirdshots to behave the same as N64." - )); - AddWidget(path, "Bombchus Out of Bounds", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("BombchusOOB")) - .Options(CheckboxOptions().Tooltip( - "Allows Bombchus to explode out of bounds. Similar to Gamecube and Wii VC" - )); - AddWidget(path, "Quick Putaway", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("QuickPutaway")) - .Options(CheckboxOptions().Tooltip( - "Restore a bug from NTSC 1.0 that allows putting away an item without an animation and performing " - "Putaway Ocarina Items." - )); - AddWidget(path, "Restore Old Gold Skulltula Cutscene", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("GSCutscene")) - .Options(CheckboxOptions().Tooltip( - "Restore pre-release behavior where defeating a Gold Skulltula will play a cutscene showing it die." - )); - AddWidget(path, "Quick Bongo Kill", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("QuickBongoKill")) - .Options(CheckboxOptions().Tooltip( - "Restore a bug from NTSC 1.0 that allows bypassing Bongo Bongo's intro cutscene to quickly kill him." - )); - AddWidget(path, "Original RBA Values", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("RestoreRBAValues")) - .Options(CheckboxOptions().Tooltip( - "Restores the original outcomes when performing Reverse Bottle Adventure." - )); - AddWidget(path, "Early Eyeball Frog", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("EarlyEyeballFrog")) - .Options(CheckboxOptions().Tooltip( - "Restores a bug from NTSC 1.0/1.1 that allows you to obtain the eyeball frog from King Zora " - "instead of the Zora Tunic by Holding Shield." - )); - AddWidget(path, "Pulsate Boss Icon", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("Pulsate Boss Icon")) - .Options(CheckboxOptions().Tooltip( - "Restores an unfinished feature to pulsate the boss room icon when you are in the boss room." - )); - AddWidget(path, "Pause Buffer Input Window: %d frames", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("PauseBufferWindow")) - .Options(IntSliderOptions() - .Min(0) - .Max(40) - .DefaultValue(0) - .Format("%d frames") - ); + "Sequence to continue to progress.")); AddWidget(path, "Include Held Inputs at the Start of Buffer Input Window", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("IncludeHeldInputsBufferWindow")) .Options(CheckboxOptions().Tooltip( "Typically, inputs that are held prior to the buffer window are not included in the buffer. This " "setting changes that behavior to include them. This may cause some inputs to be re-triggered " - "undesireably, for instance Z-Targetting something you might not want to." - )); + "undesireably, for instance Z-Targetting something you might not want to.")); + AddWidget(path, "Pause Buffer Input Window: %d frames", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("PauseBufferWindow")) + .Options(IntSliderOptions().Min(0).Max(40).DefaultValue(0).Format("%d frames")); AddWidget(path, "Simulated Input Lag: %d frames", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR_SIMULATED_INPUT_LAG) .Options(IntSliderOptions() - .Min(0) - .Max(6) - .DefaultValue(0) - .Format("%d frames") - .Tooltip( - "Buffers your inputs to be executed a specified amount of frames later." - ) - ); + .Min(0) + .Max(6) + .DefaultValue(0) + .Format("%d frames") + .Tooltip("Buffers your inputs to be executed a specified amount of frames later.")); - path.sidebarName = "Time Savers"; + AddWidget(path, "Item Count Messages", WIDGET_SEPARATOR_TEXT); + int numOptions = ARRAY_COUNT(itemCountMessageCVars); + bool allItemCountsChecked = false; + AddWidget(path, "All", WIDGET_CHECKBOX) + .ValuePointer(&allItemCountsChecked) + .PreFunc([](WidgetInfo& info) { + int numOptions = ARRAY_COUNT(itemCountMessageCVars); + *std::get(info.valuePointer) = std::all_of(itemCountMessageCVars, itemCountMessageCVars + numOptions, + [](const char* cvar) { return CVarGetInteger(cvar, 0); }); + }) + .Callback([](WidgetInfo& info) { + int32_t newValue = *std::get(info.valuePointer) ? 1 : 0; + int numOptions = ARRAY_COUNT(itemCountMessageCVars); + std::for_each(itemCountMessageCVars, itemCountMessageCVars + numOptions, + [newValue](const char* cvar) { CVarSetInteger(cvar, newValue); }); + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + for (int i = 0; i < numOptions; i++) { + AddWidget(path, itemCountMessageOptions[i], WIDGET_CVAR_CHECKBOX).CVar(itemCountMessageCVars[i]); + } + + path.column = SECTION_COLUMN_3; + AddWidget(path, "Misc", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Disable Crit Wiggle", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableCritWiggle")) + .Options(CheckboxOptions().Tooltip("Disable Random Camera Wiggle at Low Health.")); + AddWidget(path, "Better Owl", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BetterOwl")) + .Options(CheckboxOptions().Tooltip( + "The default response to Kaepora Gaebora is always that you understood what he said.")); + + AddWidget(path, "Convenience", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Quit Fishing At Door", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("QuitFishingAtDoor")) + .Options(CheckboxOptions().Tooltip( + "Fisherman asks if you want to quit at the door if you try to leave the Fishing Pond " + "while still holding the Fishing Rod.")); + AddWidget(path, "Instant Putaway", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantPutaway")) + .Options(CheckboxOptions().Tooltip("Allow Link to put items away without having to wait around.")); + AddWidget(path, "Navi Timer Resets", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ResetNaviTimer")) + .Options( + CheckboxOptions().Tooltip("Resets the Navi timer on scene change. If you have already talked to her, " + "she will try and talk to you again, instead of needing a save warp or death.")); + AddWidget(path, "Link's Cow in Both Time Periods", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CowOfTime")) + .Options(CheckboxOptions().Tooltip( + "Allows the Lon Lon Ranch Obstacle Course reward to be shared across time periods.")); + AddWidget(path, "Play Zelda's Lullaby to Open Sleeping Waterfall", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = + IS_RANDO && + OTRGlobals::Instance->gRandoContext->GetOption(RSK_SLEEPING_WATERFALL).Is(RO_WATERFALL_OPEN); + info.options->disabledTooltip = "This setting is forcefully enabled because a randomizer savefile with " + "\"Sleeping Waterfall: Open\" is loaded."; + }) + .Options( + ComboboxOptions() + .ComboMap(sleepingWaterfallOptions) + .DefaultIndex(WATERFALL_ALWAYS) + .Tooltip( + "Always: Link must always play Zelda's Lullaby to open the waterfall entrance to Zora's Domain.\n" + "Once: Link only needs to play Zelda's Lullaby once to open the waterfall; after that, it stays " + "open permanently.\n" + "Never: Link never needs to play Zelda's Lullaby to open the waterfall. He only needs to have " + "learned it and have an Ocarina.")); + + // Skips & Speed-ups + path.sidebarName = "Skips & Speed-ups"; AddSidebarEntry("Enhancements", path.sidebarName, 3); path.column = SECTION_COLUMN_1; @@ -469,16 +262,16 @@ void SohMenu::AddMenuEnhancements() { .ValuePointer(&allSkipsChecked) .PreFunc([](WidgetInfo& info) { *std::get(info.valuePointer) = - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), IS_RANDO); + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), IS_RANDO); }) .Callback([](WidgetInfo& info) { int32_t newValue = *std::get(info.valuePointer) ? 1 : 0; @@ -531,259 +324,98 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip( "Don't skip cutscenes that are associated wiht useful glitches. Currently, it is " "only the Fire Temple Darunia CS, Forest Temple Poe Sisters CS, and the Box Skip One " - "Point in Jabu." - )); - AddWidget(path, "Skip Child Stealth", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipChildStealth")) - .Options(CheckboxOptions().Tooltip( - "The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards." - )); - AddWidget(path, "Skip Tower Escape", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipTowerEscape")) - .Options(CheckboxOptions().Tooltip( - "Skip the tower escape sequence between Ganondorf and Ganon." - )); - + "Point in Jabu.")); + AddWidget(path, "Text", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Skip Pickup Messages", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastDrops")) + .Options(CheckboxOptions().Tooltip("Skip Pickup Messages for new Consumable Items and Bottle Swipes.")); AddWidget(path, "Skip Forced Dialog", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipForcedDialog")) - .Options(CheckboxOptions().Tooltip( - "Prevent forced conversations with Navi or other NPCs." - )); - AddWidget(path, "Text Speed: %dx", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("TextSpeed")) - .Options(IntSliderOptions() - .Min(1) - .Max(5) - .DefaultValue(1) - .Format("%dx") - ); + .Options(CheckboxOptions().Tooltip("Prevent forced conversations with Navi or other NPCs.")); AddWidget(path, "Skip Text", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("SkipText")) .Options(CheckboxOptions().Tooltip("Holding down B skips text.")); + AddWidget(path, "Text Speed: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("TextSpeed")) + .Options(IntSliderOptions().Min(1).Max(5).DefaultValue(1).Format("%dx")); AddWidget(path, "Slow Text Speed: %dx", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR_ENHANCEMENT("SlowTextSpeed")) - .Options(IntSliderOptions() - .Min(1) - .Max(5) - .DefaultValue(1) - .Format("%dx") - .Tooltip( - "Changes the speed of sections of text that normally are paced slower than the text surrounding it." - ) - ); - AddWidget(path, "Match Normal Text", WIDGET_BUTTON) - .Callback([](WidgetInfo& info) { - CVarSetInteger(CVAR_ENHANCEMENT("SlowTextSpeed"), CVarGetInteger(CVAR_ENHANCEMENT("TextSpeed"), 1)); - }) - .Options(ButtonOptions().Tooltip( - "Makes the speed of the slow text match the normal text speed above." - )); - AddWidget(path, "Skip Pickup Messages", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FastDrops")) - .Options(CheckboxOptions().Tooltip( - "Skip Pickup Messages for new Consumable Items and Bottle Swipes." - )); - AddWidget(path, "Better Owl", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("BetterOwl")) - .Options(CheckboxOptions().Tooltip( - "The default response to Kaepora Gaebora is always that you understood what he said." - )); + .Options(IntSliderOptions().Min(1).Max(5).DefaultValue(1).Format("%dx").Tooltip( + "Changes the speed of sections of text that normally are paced slower than the text surrounding it.")); path.column = SECTION_COLUMN_2; - AddWidget(path, "Gameplay", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Skip Save Confirmation", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("SkipSaveConfirmation")) - .Options(CheckboxOptions().Tooltip("Skip the \"Game Saved\" confirmation screen.")); - AddWidget(path, "Biggoron Forge Time: %d days", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("ForgeTime")) - .Options(IntSliderOptions() - .Min(0) - .Max(3) - .DefaultValue(3) - .Format("%d days") - .Tooltip( - "Allows you to change the number of days it takes for " - "Biggoron to forge the Biggoron's Sword." - ) - ); - AddWidget(path, "Remember Save Location", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("RememberSaveLocation")) + AddWidget(path, "Animations", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Faster Heavy Block Lift", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FasterHeavyBlockLift")) + .Options(CheckboxOptions().Tooltip("Speeds up lifting Silver Rocks and Obelisks.")); + AddWidget(path, "Fast Chests", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastChests")) + .Options(CheckboxOptions().Tooltip("Makes Link always kick the chest to open it, instead of doing the longer " + "chest opening animation for major items.")); + AddWidget(path, "Skip Water Take Breath Animation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipSwimDeepEndAnim")) + .Options(CheckboxOptions().Tooltip("Skips Link's taking breath animation after coming up from water. " + "This setting does not interfere with getting items from underwater.")); + AddWidget(path, "Vine/Ladder Climb Speed +%d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("ClimbSpeed")) + .Options(IntSliderOptions().Min(0).Max(12).DefaultValue(0).Format("+%d")); + AddWidget(path, "Block Pushing Speed +%d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("FasterBlockPush")) + .Options(IntSliderOptions().Min(0).Max(5).DefaultValue(0).Format("+%d")); + AddWidget(path, "Crawl Speed %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("CrawlSpeed")) + .Options(IntSliderOptions().Min(1).Max(4).DefaultValue(1).Format("%dx")); + AddWidget(path, "King Zora Speed: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_ENHANCEMENT("MweepSpeed")) + .Options(FloatSliderOptions().Min(0.1f).Max(5.0f).DefaultValue(1.0f).Format("%.2fx")); + + path.column = SECTION_COLUMN_3; + AddWidget(path, "Misc", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Skip Child Stealth", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipChildStealth")) .Options(CheckboxOptions().Tooltip( - "When loading a save, places Link at the last entrance he went through.\n" - "This doesn't work if the save was made in grottos, fairy fountains, or dungeons." - )); - AddWidget(path, "Navi Timer Resets", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("ResetNaviTimer")) + "The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards.")); + AddWidget(path, "Skip Tower Escape", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipTowerEscape")) + .Options(CheckboxOptions().Tooltip("Skip the tower escape sequence between Ganondorf and Ganon.")); + AddWidget(path, "Skip Scarecrow's Song", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantScarecrow")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = + IS_RANDO && OTRGlobals::Instance->gRandoContext->GetOption(RSK_SKIP_SCARECROWS_SONG); + info.options->disabledTooltip = "This setting is forcefully enabled because a randomized " + "save file with the option \"Skip Scarecrow Song\" is currently loaded."; + }) .Options(CheckboxOptions().Tooltip( - "Resets the Navi timer on scene change. If you have already talked to her, " - "she will try and talk to you again, instead of needing a save warp or death." - )); + "Pierre appears when an Ocarina is pulled out. Requires learning the Scarecrow's Song first.")); + AddWidget(path, "Faster Rupee Accumulator", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FasterRupeeAccumulator")) + .Options(CheckboxOptions().Tooltip("Causes your Wallet to fill and empty faster when you gain or lose money.")); AddWidget(path, "No Skulltula Freeze", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("SkulltulaFreeze")) .PreFunc([](WidgetInfo& info) { info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); - info.options->disabledTooltip = + info.options->disabledTooltip = "This setting is disabled because a randomizer savefile is loaded. Please use the " "\"Skip Get Item Animation\" option within the randomizer enhancements instead."; }) .Options(CheckboxOptions().Tooltip( "Stops the game from freezing the player when picking up Gold Skulltula Tokens. Does not" - "apply in randomizer savefiles." - )); - AddWidget(path, "Ask to Equip New Items", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("AskToEquip")) - .Options(CheckboxOptions().Tooltip( - "Adds a prompt to equip newly-obtained Swords, Shields, and Tunics." - )); + "apply in randomizer savefiles.")); + AddWidget(path, "Skip Save Confirmation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipSaveConfirmation")) + .Options(CheckboxOptions().Tooltip("Skip the \"Game Saved\" confirmation screen.")); AddWidget(path, "Link as Default File Name", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("LinkDefaultName")) - .Options(CheckboxOptions().Tooltip( - "Allows you to have \"Link\" as a premade file name." - )); - AddWidget(path, "Quit Fishing At Door", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("QuitFishingAtDoor")) - .Options(CheckboxOptions().Tooltip( - "Fisherman asks if you want to quit at the door if you try to leave the Fishing Pond " - "while still holding the Fishing Rod." - )); - AddWidget(path, "Time Travel with Song of Time", WIDGET_CVAR_COMBOBOX) - .CVar(CVAR_ENHANCEMENT("TimeTravel")) - .Options(ComboboxOptions() - .ComboMap(timeTravelOptions) - .DefaultIndex(0) - .Tooltip( - "Allows Link to freely change age by playing the Song of Time.\n" - "Time Blocks can still be used properly.\n\n" - "Requirements:\n" - " - Obtained the Ocarina of Time (depends on selection)\n" - " - Obtained the Song of Time\n" - " - Obtained the Master Sword\n" - " - Not within range of a Time Block\n" - " - Not within range of Ocarina Playing spots." - ) - ); - AddWidget(path, "Pause Warp", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("PauseWarp")) - .Options(CheckboxOptions().Tooltip( - "Selection of warp song in pause menu initiates a warp. Disables song playback." - )); - AddWidget(path, "Skip Scarecrow's Song", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("InstantScarecrow")) - .PreFunc([](WidgetInfo& info) { - info.options->disabled = IS_RANDO && OTRGlobals::Instance->gRandoContext->GetOption(RSK_SKIP_SCARECROWS_SONG); - info.options->disabledTooltip = "This setting is forcefully enabled because a randomized " - "save file with the option \"Skip Scarecrow Song\" is currently loaded."; - }) - .Options(CheckboxOptions().Tooltip( - "Pierre appears when an Ocarina is pulled out. Requires learning the Scarecrow's Song first." - )); - AddWidget(path, "Time of Day", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Nighttime GS Always Spawn", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("NightGSAlwaysSpawn")) - .Options(CheckboxOptions().Tooltip( - "Nighttime Skulltulas will spawn during both day and night." - )); - AddWidget(path, "Dampe Appears All Night", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DampeAllNight")) - .Options(CheckboxOptions().Tooltip( - "Makes Dampe appear anytime during the night, not just his usual working hours." - )); - AddWidget(path, "Exit Market at Night", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("MarketSneak")) - .Options(CheckboxOptions().Tooltip( - "Allows exiting Hyrule Castle Market Town to Hyrule Field at night by speaking to the guard " - "next to the gate." - )); - AddWidget(path, "Shops and Games Always Open", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("OpenAllHours")) - .PreFunc([](WidgetInfo& info) { - info.options->disabled = IS_RANDO && OTRGlobals::Instance->gRandoContext->GetOption(RSK_LOCK_OVERWORLD_DOORS).Is(RO_GENERIC_ON); - }) - .Options(CheckboxOptions().Tooltip( - "Shops and Minigames are open both day and night. Requires a scene reload to take effect." - ).DisabledTooltip( - "This is not compatible with the Locked Overworld Doors Randomizer option." - )); - path.column = SECTION_COLUMN_3; - AddWidget(path, "Animations", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "King Zora Speed: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) - .CVar(CVAR_ENHANCEMENT("MweepSpeed")) - .Options(FloatSliderOptions() - .Min(0.1f) - .Max(5.0f) - .DefaultValue(1.0f) - .Format("%.2fx") - ); - AddWidget(path, "Vine/Ladder Climb Speed +%d", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("ClimbSpeed")) - .Options(IntSliderOptions() - .Min(0) - .Max(12) - .DefaultValue(0) - .Format("+%d") - ); - AddWidget(path, "Block Pushing Speed +%d", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("FasterBlockPush")) - .Options(IntSliderOptions() - .Min(0) - .Max(5) - .DefaultValue(0) - .Format("+%d") - ); - AddWidget(path, "Crawl Speed %dx", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("CrawlSpeed")) - .Options(IntSliderOptions() - .Min(1) - .Max(4) - .DefaultValue(1) - .Format("%dx") - ); - AddWidget(path, "Faster Heavy Block Lift", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FasterHeavyBlockLift")) - .Options(CheckboxOptions().Tooltip( - "Speeds up lifting Silver Rocks and Obelisks." - )); - AddWidget(path, "Fast Ocarina Playback", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FastOcarinaPlayback")) - .Options(CheckboxOptions().Tooltip( - "Skip the part where the Ocarina Playback is called when you play a song." - )); - AddWidget(path, "Skip Magic Arrow Equip Animation", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("SkipArrowAnimation")); - AddWidget(path, "Faster Farore's Wind", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FastFarores")) - .Options(CheckboxOptions().Tooltip("Greatly decreases cast time of Farore's Wind magic spell.")); - AddWidget(path, "Fast Chests", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FastChests")) - .Options(CheckboxOptions().Tooltip( - "Makes Link always kick the chest to open it, instead of doing the longer " - "chest opening animation for major items." - )); - AddWidget(path, "Skip Water Take Breath Animation", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("SkipSwimDeepEndAnim")) - .Options(CheckboxOptions().Tooltip( - "Skips Link's taking breath animation after coming up from water. " - "This setting does not interfere with getting items from underwater." - )); - AddWidget(path, "Play Zelda's Lullaby to Open Sleeping Waterfall", WIDGET_CVAR_COMBOBOX) - .CVar(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall")) - .PreFunc([](WidgetInfo& info) { - info.options->disabled = IS_RANDO && OTRGlobals::Instance->gRandoContext->GetOption(RSK_SLEEPING_WATERFALL).Is(RO_WATERFALL_OPEN); - info.options->disabledTooltip = "This setting is forcefully enabled because a randomizer savefile with \"Sleeping Waterfall: Open\" is loaded."; - }) - .Options(ComboboxOptions() - .ComboMap(sleepingWaterfallOptions) - .DefaultIndex(WATERFALL_ALWAYS) - .Tooltip( - "Always: Link must always play Zelda's Lullaby to open the waterfall entrance to Zora's Domain.\n" - "Once: Link only needs to play Zelda's Lullaby once to open the waterfall; after that, it stays " - "open permanently.\n" - "Never: Link never needs to play Zelda's Lullaby to open the waterfall. He only needs to have " - "learned it and have an Ocarina." - ) - ); + .Options(CheckboxOptions().Tooltip("Allows you to have \"Link\" as a premade file name.")); + AddWidget(path, "Biggoron Forge Time: %d days", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("ForgeTime")) + .Options(IntSliderOptions().Min(0).Max(3).DefaultValue(3).Format("%d days").Tooltip( + "Allows you to change the number of days it takes for " + "Biggoron to forge the Biggoron's Sword.")); + // Graphics path.sidebarName = "Graphics"; AddSidebarEntry("Enhancements", path.sidebarName, 3); path.column = SECTION_COLUMN_1; @@ -807,71 +439,23 @@ void SohMenu::AddMenuEnhancements() { "Disables Grottos rotating with the Camera. To be used in conjuction with mods that want to " "replace grottos with 3D objects." )); + AddWidget(path, "Ingame Text Spacing: %d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("TextSpacing")) + .Options(IntSliderOptions().Min(4).Max(6).DefaultValue(6).Tooltip( + "Space between text characters (useful for HD font textures).")); - - AddWidget(path, "UI", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Minimal UI", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("MinimalUI")) + AddWidget(path, "Models & Textures", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Disable LOD", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableLOD")) .Options(CheckboxOptions().Tooltip( - "Hides most of the UI when not needed.\n" - "NOTE: Doesn't activate until scene transition." - )); - AddWidget(path, "Disable Hot/Underwater Warning Text", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DisableTunicWarningText")) + "Turns off the Level of Detail setting, making models use their Higher-Poly variants at any distance.")); + AddWidget(path, "Enemy Health Bars", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EnemyHealthBar")) + .Options(CheckboxOptions().Tooltip("Renders a health bar for Enemies when Z-Targeted.")); + AddWidget(path, "Enable 3D Dropped Items/Projectiles", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NewDrops")) .Options(CheckboxOptions().Tooltip( - "Disables warning text when you don't have on the Goron/Zora Tunic " - "in Hot/Underwater conditions." - )); - AddWidget(path, "Remember Minimap State Between Areas", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("RememberMapToggleState")) - .Options(CheckboxOptions().Tooltip( - "Preverse the minimap visibility state when going between areas rather than default it to \"on\" " - "when going through loading zones." - )); - AddWidget(path, "Visual Stone of Agony", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("VisualAgony")) - .Options(CheckboxOptions().Tooltip( - "Displays an icon and plays a sound when Stone of Agony should be activated, for those without rumble." - )); - AddWidget(path, "Disable HUD Heart Animations", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("NoHUDHeartAnimation")) - .Options(CheckboxOptions().Tooltip( - "Disables the Beating Animation of the Hearts on the HUD." - )); - AddWidget(path, "Glitch line-up tick", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DrawLineupTick")) - .Options(CheckboxOptions().Tooltip( - "Displays a tick in the top center of the screen to help with glitch line-ups in SoH, since traditional " - "UI based line-ups do not work outside of 4:3" - )); - AddWidget(path, "Disable Black Bar Letterboxes", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DisableBlackBars")) - .Options(CheckboxOptions().Tooltip( - "Disables Black Bar Letterboxes during cutscenes and Z-Targeting. NOTE: there may be minor visual " - "glitches that were covered up by the black bars. Please disable this setting before reporting a bug." - )); - AddWidget(path, "Dynamic Wallet Icon", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DynamicWalletIcon")) - .Options(CheckboxOptions().Tooltip( - "Changes the Rupee in the Wallet icon to match the wallet size you currently have." - )); - AddWidget(path, "Always Show Dungeon Entrances", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("AlwaysShowDungeonMinimapIcon")) - .Options(CheckboxOptions().Tooltip( - "Always shows dungeon entrance icons on the Minimap." - )); - AddWidget(path, "More Info in File Select", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FileSelectMoreInfo")) - .Options(CheckboxOptions().Tooltip( - "Shows what items you have collected in the File Select screen, like in N64 Randomizer." - )); - AddWidget(path, "Better Ammo Rendering in Pause Menu", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("BetterAmmoRendering")) - .Options(CheckboxOptions().Tooltip( - "Ammo counts in the pause menu will work correctly regardless of the position of items in the Inventory." - )); - - AddWidget(path, "Models", WIDGET_SEPARATOR_TEXT); + "Replaces most 2D items and projectiles on the overworld with their equivalent 3D models.")); AddWidget(path, "Invisible Bunny Hood", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("HideBunnyHood")) .Options(CheckboxOptions().Tooltip( @@ -900,11 +484,6 @@ void SohMenu::AddMenuEnhancements() { "Scales all of the Adult Equipment, as well as moving some a bit, to fit on Child Link better. May " "not work properly with some mods." )); - AddWidget(path, "Enable 3D Dropped Items/Projectiles", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("NewDrops")) - .Options(CheckboxOptions().Tooltip( - "Replaces most 2D items and projectiles on the overworld with their equivalent 3D models." - )); AddWidget(path, "Show Gauntlets in First Person", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("FirstPersonGauntlets")) .Options(CheckboxOptions().Tooltip( @@ -912,133 +491,63 @@ void SohMenu::AddMenuEnhancements() { )); AddWidget(path, "Show Chains on Both Sides of Locked Doors", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("ShowDoorLocksOnBothSides")); - - path.column = SECTION_COLUMN_2; - AddWidget(path, "Textures", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Chest Size & Texture Matches Contents", WIDGET_CVAR_COMBOBOX) - .CVar(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents")) - .Callback([](WidgetInfo& info) { - if (CVarGetInteger(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchContents"), CSMC_DISABLED) == CSMC_DISABLED) { - CVarSetInteger(CVAR_ENHANCEMENT("ChestSizeDependsStoneOfAgony"), 0); - } - }) - .Options(ComboboxOptions() - .ComboMap(chestStyleMatchesContentsOptions) - .DefaultIndex(CSMC_DISABLED) - .Tooltip( - "Chest sizes and textures are changed to help identify the item inside.\n" - " - Major items: Large gold chests\n" - " - Lesser items: Large brown chests\n" - " - Junk items: Small brown chests\n" - " - Small keys: Small silver chests\n" - " - Boss keys: Vanilla size and texture\n" - " - Skulltula Tokens: Small skulltula chest\n" - "\n" - "NOTE: Textures will not apply if you are using a mod pack with a custom chest model." - ) - ); - AddWidget(path, "Chests of Agony", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("ChestSizeDependsStoneOfAgony")) - .PreFunc([](WidgetInfo& info) { - info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ChestSizeAndTextureMatchesContents"), CSMC_DISABLED); - }) - .Options(CheckboxOptions().Tooltip( - "Only change the size/texture of chests if you have the Stone of Agony." - )); AddWidget(path, "Color Temple of Time's Medallions", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("ToTMedallionsColors")) - .Callback([](WidgetInfo& info) { - PatchToTMedallions(); - }) + .Callback([](WidgetInfo& info) { PatchToTMedallions(); }) .Options(CheckboxOptions().Tooltip( "When Medallions are collected, the Medallion imprints around the Master Sword Pedestal in the Temple " - "of Time will become colored-in." - )); - AddWidget(path, "Fix Vanishing Paths", WIDGET_CVAR_COMBOBOX) - .CVar(CVAR_ENHANCEMENT("SceneSpecificDirtPathFix")) - .Callback([](WidgetInfo& info) { - if (gPlayState != NULL) { - UpdateDirtPathFixState(gPlayState->sceneNum); - } - }) - .Options(ComboboxOptions() - .ComboMap(zFightingOptions) - .DefaultIndex(ZFIGHT_FIX_DISABLED) - .Tooltip( - "Disabled: Paths vanish more the higher the resolution (Z-fighting is based on resolution)\n" - "Consistent: Certain paths vanish the same way in all resolutions\n" - "No Vanish: Paths do not vanish, Link seems to sink in to some paths\n" - "This might affect other decal effects\n" - ) - ); - AddWidget(path, "Text Spacing: %d", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("TextSpacing")) - .Options(IntSliderOptions() - .Min(4) - .Max(6) - .DefaultValue(6) - .Tooltip( - "Space between text characters (useful for HD font textures)." - ) - ); + "of Time will become colored-in.")); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "UI", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Minimal UI", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("MinimalUI")) + .Options(CheckboxOptions().Tooltip("Hides most of the UI when not needed.\n" + "NOTE: Doesn't activate until scene transition.")); + AddWidget(path, "Disable Hot/Underwater Warning Text", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableTunicWarningText")) + .Options(CheckboxOptions().Tooltip("Disables warning text when you don't have on the Goron/Zora Tunic " + "in Hot/Underwater conditions.")); + AddWidget(path, "Remember Minimap State Between Areas", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RememberMapToggleState")) + .Options(CheckboxOptions().Tooltip( + "Preverse the minimap visibility state when going between areas rather than default it to \"on\" " + "when going through loading zones.")); + AddWidget(path, "Visual Stone of Agony", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("VisualAgony")) + .Options(CheckboxOptions().Tooltip( + "Displays an icon and plays a sound when Stone of Agony should be activated, for those without rumble.")); + AddWidget(path, "Disable HUD Heart Animations", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NoHUDHeartAnimation")) + .Options(CheckboxOptions().Tooltip("Disables the Beating Animation of the Hearts on the HUD.")); + AddWidget(path, "Glitch line-up tick", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DrawLineupTick")) + .Options(CheckboxOptions().Tooltip( + "Displays a tick in the top center of the screen to help with glitch line-ups in SoH, since traditional " + "UI based line-ups do not work outside of 4:3")); + AddWidget(path, "Disable Black Bar Letterboxes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableBlackBars")) + .Options(CheckboxOptions().Tooltip( + "Disables Black Bar Letterboxes during cutscenes and Z-Targeting. NOTE: there may be minor visual " + "glitches that were covered up by the black bars. Please disable this setting before reporting a bug.")); + AddWidget(path, "Dynamic Wallet Icon", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DynamicWalletIcon")) + .Options(CheckboxOptions().Tooltip( + "Changes the Rupee in the Wallet icon to match the wallet size you currently have.")); + AddWidget(path, "Always Show Dungeon Entrances", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AlwaysShowDungeonMinimapIcon")) + .Options(CheckboxOptions().Tooltip("Always shows dungeon entrance icons on the Minimap.")); + AddWidget(path, "More Info in File Select", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FileSelectMoreInfo")) + .Options(CheckboxOptions().Tooltip( + "Shows what items you have collected in the File Select screen, like in N64 Randomizer.")); + AddWidget(path, "Better Ammo Rendering in Pause Menu", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BetterAmmoRendering")) + .Options(CheckboxOptions().Tooltip( + "Ammo counts in the pause menu will work correctly regardless of the position of items in the Inventory.")); + AddWidget(path, "Enable Passage of Time on File Select", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TimeFlowFileSelect")); - AddWidget(path, "Rendering", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Disable LOD", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DisableLOD")) - .Options(CheckboxOptions().Tooltip( - "Turns off the Level of Detail setting, making models use their Higher-Poly variants at any distance." - )); - AddWidget(path, "Increase Actor Draw Distance: %dx", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("DisableDrawDistance")) - .Callback([](WidgetInfo& info) { - if (CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) <= 1) { - CVarSetInteger(CVAR_ENHANCEMENT("DisableKokiriDrawDistance"), 0); - } - }) - .Options(IntSliderOptions() - .Min(1) - .Max(5) - .DefaultValue(1) - .Format("%dx") - .Tooltip( - "Increases the range in which Actors/Objects are drawn." - ) - ); - AddWidget(path, "Kokiri Draw Distance", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DisableKokiriDrawDistance")) - .PreFunc([](WidgetInfo& info) { - info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) > 1; - }) - .Options(CheckboxOptions().Tooltip( - "The Kokiri are mystical beings that fade into view when approached. Enabling this will remove their " - "draw distance." - )); - AddWidget(path, "Widescreen Actor Culling", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("WidescreenActorCulling")) - .Options(CheckboxOptions().Tooltip( - "Adjusts the Horizontal Culling Plane to account for Widescreen Resolutions." - )); - AddWidget(path, "Cull Glitch Useful Actors", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("ExtendedCullingExcludeGlitchActors")) - .PreFunc([](WidgetInfo& info) { - info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("WidescreenActorCulling"), 0) && - CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) <= 1; - info.options->disabledTooltip = "Requires Actor Draw Distance to be increased or Widscreen Actor Culling to be enabled."; - }) - .Options(CheckboxOptions().Tooltip( - "Exclude Actors that are useful for Glitches from the extended culling ranges. Some actors may still draw " - "in the extended ranges, but will not \"update\" so that certain glitches that leverage the original " - "culling requirements will still work.\n\nThe following actors are excluded:\n" - " - White clothed Gerudos\n" - " - King Zora\n" - " - Gossip Stones\n" - " - Boulders\n" - " - Blue Warps\n" - " - Darunia\n" - " - Gold SKulltulas\n" - )); - - // TODO: Find a better home for these. path.column = SECTION_COLUMN_3; AddWidget(path, "Misc.", WIDGET_SEPARATOR_TEXT); AddWidget(path, "N64 Mode", WIDGET_CVAR_CHECKBOX) @@ -1051,163 +560,67 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip( "Remove the Darkness that appears when charging a Spin Attack" )); - + AddWidget(path, "Draw Distance", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Increase Actor Draw Distance: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("DisableDrawDistance")) + .Callback([](WidgetInfo& info) { + if (CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) <= 1) { + CVarSetInteger(CVAR_ENHANCEMENT("DisableKokiriDrawDistance"), 0); + } + }) + .Options(IntSliderOptions().Min(1).Max(5).DefaultValue(1).Format("%dx").Tooltip( + "Increases the range in which Actors/Objects are drawn.")); + AddWidget(path, "Kokiri Draw Distance", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableKokiriDrawDistance")) + .PreFunc( + [](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) > 1; }) + .Options(CheckboxOptions().Tooltip( + "The Kokiri are mystical beings that fade into view when approached. Enabling this will remove their " + "draw distance.")); + AddWidget(path, "Widescreen Actor Culling", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("WidescreenActorCulling")) + .Options( + CheckboxOptions().Tooltip("Adjusts the Horizontal Culling Plane to account for Widescreen Resolutions.")); + AddWidget(path, "Cull Glitch Useful Actors", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtendedCullingExcludeGlitchActors")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("WidescreenActorCulling"), 0) && + CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) <= 1; + info.options->disabledTooltip = + "Requires Actor Draw Distance to be increased or Widscreen Actor Culling to be enabled."; + }) + .Options(CheckboxOptions().Tooltip( + "Exclude Actors that are useful for Glitches from the extended culling ranges. Some actors may still draw " + "in the extended ranges, but will not \"update\" so that certain glitches that leverage the original " + "culling requirements will still work.\n\nThe following actors are excluded:\n" + " - White clothed Gerudos\n" + " - King Zora\n" + " - Gossip Stones\n" + " - Boulders\n" + " - Blue Warps\n" + " - Darunia\n" + " - Gold SKulltulas\n")); + path.sidebarName = "Items"; AddSidebarEntry("Enhancements", path.sidebarName, 3); path.column = SECTION_COLUMN_1; - AddWidget(path, "Controls", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Equipment", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Equip Items on Dpad", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("DpadEquips")) .Options(CheckboxOptions().Tooltip( "Equip items and equipment on the D-Pad. If used with \"D-Pad on Pause Screen\", you must " - "hold C-Up to equip instead of navgiate." - )); - AddWidget(path, "Instant Putaway", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("InstantPutaway")) + "hold C-Up to equip instead of navgiate.")); + AddWidget(path, "Assignable Tunics and Boots", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AssignableTunicsAndBoots")) + .Options(CheckboxOptions().Tooltip("Allows equipping the Tunics and Boots to C-Buttons/D-Pad.")); + // TODO: Revist strength toggle, it's currently separate but should probably be locked behind the + // Equipment toggle settings or be absorbed by it completely. + AddWidget(path, "Equipment Toggle", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EquipmentCanBeRemoved")) .Options(CheckboxOptions().Tooltip( - "Allow Link to put items away without having to wait around." - )); - AddWidget(path, "Instant Boomerang Recall", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FastBoomerang")) - .Options(CheckboxOptions().Tooltip( - "Instantly return the boomerang to Link by pressing its item button while " - "it's in the air." - )); - AddWidget(path, "Prevent Dropped Ocarina Inputs", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DpadNoDropOcarinaInput")) - .Options(CheckboxOptions().Tooltip( - "Prevent dropping inputs when playing the Ocarina too quickly." - )); - AddWidget(path, "Masks", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Bunny Hood Effect", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("MMBunnyHood")) - .Options(CheckboxOptions().Tooltip( - "Wearing the Bunny Hood grants a speed increase link in Majora's Mask. " - "The longer jump option is not accounted for in Randomizer logic.\n\n" - "Also disables NPC's reactions to wearing the Bunny Hood." - )); - AddWidget(path, "Masks Equippable as Adult", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("AdultMasks")) - .Options(CheckboxOptions().Tooltip( - "Allows masks to be equipped normally from the pause menu as adult." - )); - AddWidget(path, "Persistent Masks", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("PersistentMasks")) - .Options(CheckboxOptions().Tooltip( - "Stops masks from automatically unequipping on certain situations:\n" - "- When entering a new scene\n" - "- When not in any C button or the D-Pad\n" - "- When saving and quitting\n" - "- When dying\n" - "- When traveling thru time (if \"Masks Equippable as Adult\" is activated)." - )); - AddWidget(path, "Mask Select in Inventory", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("MaskSelect")) - .Options(CheckboxOptions().Tooltip( - "After completing the mask trading sub-quest, press A and any direction on the mask " - "slog to change masks" - )); - AddWidget(path, "Item Count Messages", WIDGET_SEPARATOR_TEXT); - int numOptions = ARRAY_COUNT(itemCountMessageCVars); - bool allItemCountsChecked = false; - AddWidget(path, "All", WIDGET_CHECKBOX) - .ValuePointer(&allItemCountsChecked) - .PreFunc([](WidgetInfo& info) { - int numOptions = ARRAY_COUNT(itemCountMessageCVars); - *std::get(info.valuePointer) = std::all_of(itemCountMessageCVars, itemCountMessageCVars + numOptions, - [](const char* cvar) { return CVarGetInteger(cvar, 0); }); - }) - .Callback([](WidgetInfo& info) { - int32_t newValue = *std::get(info.valuePointer) ? 1 : 0; - int numOptions = ARRAY_COUNT(itemCountMessageCVars); - std::for_each(itemCountMessageCVars, itemCountMessageCVars + numOptions, - [newValue](const char* cvar) { CVarSetInteger(cvar, newValue); }); - - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - }); - for (int i = 0; i < numOptions; i++) { - AddWidget(path, itemCountMessageOptions[i], WIDGET_CVAR_CHECKBOX) - .CVar(itemCountMessageCVars[i]); - } - path.column = SECTION_COLUMN_2; - AddWidget(path, "Equipment", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Deku Nuts Explode Bombs", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("NutsExplodeBombs")) - .Options(CheckboxOptions().Tooltip( - "Make Deku Nuts explode Bombs, similar to how they interact with Bombchus. " - "This does not affect Bombflowers." - )); - AddWidget(path, "Equip Multiple Arrows at Once", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("SeparateArrows")) - .Options(CheckboxOptions().Tooltip( - "Allow the Bow and Magic Arrows to be equipped at the same time on different slots. " - "NOTE: This will disable the behavior of the 'Equip Dupe' glitch." - )); - AddWidget(path, "Bow and Child/Slingshot as Adult", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("BowSlingshotAmmoFix")) - .Options(CheckboxOptions().Tooltip( - "Allows Child to use a Bow with Arrows.\n" - "Allows Adult to use a Slingshot with Seeds.\n\n" - "Requires glitches or the 'Timeless Equipment' cheat to equip." - )); - AddWidget(path, "Better Farore's Wind", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("BetterFarore")) - .Options(CheckboxOptions().Tooltip( - "Helps FW persist between ages, gives Child and Adult separate FW points, and can " - "be used in more places." - )); - AddWidget(path, "Remove Explosive Limit", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("RemoveExplosiveLimit")) - .Options(CheckboxOptions().Tooltip( - "Removes the cap of 3 active explosives being deployed at once." - )); - AddWidget(path, "Static Explosion Radius", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("StaticExplosionRadius")) - .Options(CheckboxOptions().Tooltip( - "Explosions are now a static size, like in Majora's Mask and OoT3D. Makes Bombchu " - "hovering much easier." - )); - AddWidget(path, "Prevent Bombchus Forcing Firs-Person", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("DisableFirstPersonChus")) - .Options(CheckboxOptions().Tooltip( - "Prevent Bombchus from forcing the camera into first-person mode when released." - )); - AddWidget(path, "Better Bombchu Shopping", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("BetterBombchuShopping")) - .PreFunc([](WidgetInfo& info) { - info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); - info.options->disabledTooltip = "This setting is forcefully enabled when you are playing a randomizer."; - }) - .Options(CheckboxOptions().Tooltip( - "Bombchus do not sell out when bought, and a 10 pack of Bombchus costs 99 rupees " - "instead of 100." - )); - AddWidget(path, "Aiming Reticle for the Bow/Slingshot", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("BowReticle")) - .Options(CheckboxOptions().Tooltip( - "Aiming with a Bow or Slingshot will display a reticle as with the Hookshot " - "when the projectile is ready to fire." - )); - AddWidget(path, "Aim Boomerang in First-Person Mode", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("BoomerangFirstPerson")) - .Callback([](WidgetInfo& info) { - if (!CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0)) { - CVarSetInteger(CVAR_ENHANCEMENT("BoomerangReticle"), 0); - } - }) - .Options(CheckboxOptions().Tooltip( - "Change aiming for the Boomerang from Third-Person to First-Person to see past Link's head." - )); - AddWidget(path, "Aiming Reticle for Boomerang", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("BoomerangFirstPerson")) - .PreFunc([](WidgetInfo& info) { - info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0) != 0; - }) - .Options(CheckboxOptions().Tooltip( - "Aiming with the Boomerang will display a reticle as with the Hookshot." - )); - AddWidget(path, "Allow Strength Equipement to be Toggled", WIDGET_CVAR_CHECKBOX) + "Allows equipment to be removed by toggling it off on\n the equipment subscreen.")); + AddWidget(path, "Allow Strength Equipment to be Toggled", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("ToggleStrength")) .Callback([](WidgetInfo& info) { if (!CVarGetInteger(CVAR_ENHANCEMENT("ToggleStrength"), 0)) { @@ -1217,305 +630,398 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip( "Allows Strength to be toggled on and off by pressing A on the Strength Upgrade " "in the Equipment Subscreen of the Pause Menu. This allows performing some glitches " - "that require the player to not have Strength." - )); + "that require the player to not have Strength.")); + AddWidget(path, "Sword Toggle Options", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("SwordToggle")) + .PreFunc( + [](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("EquipmentCanBeRemoved"), 0) == 0; }) + .Options(ComboboxOptions() + .ComboMap(swordToggleModes) + .DefaultIndex(SWORD_TOGGLE_NONE) + .Tooltip("Introduces Options for unequipping Link's sword\n\n" + "None: Only Biggoron's Sword/Giant's Knife can be toggled. Doing so will equip the " + "Master Sword.\n\n" + "Child Toggle: This will allow for completely unequipping any sword as child link.\n\n" + "Both Ages: Any sword can be unequipped as either age. This may lead to swordless " + "glitches as Adult.")); + AddWidget(path, "Ask to Equip New Items", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AskToEquip")) + .Options(CheckboxOptions().Tooltip("Adds a prompt to equip newly-obtained Swords, Shields, and Tunics.")); + + AddWidget(path, "Ocarina", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Prevent Dropped Ocarina Inputs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DpadNoDropOcarinaInput")) + .Options(CheckboxOptions().Tooltip("Prevent dropping inputs when playing the Ocarina too quickly.")); + AddWidget(path, "Fast Ocarina Playback", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastOcarinaPlayback")) + .Options(CheckboxOptions().Tooltip("Skip the part where the Ocarina Playback is called when you play a song.")); + AddWidget(path, "Time Travel with Song of Time", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("TimeTravel")) + .Options(ComboboxOptions() + .ComboMap(timeTravelOptions) + .DefaultIndex(0) + .Tooltip("Allows Link to freely change age by playing the Song of Time.\n" + "Time Blocks can still be used properly.\n\n" + "Requirements:\n" + " - Obtained the Ocarina of Time (depends on selection)\n" + " - Obtained the Song of Time\n" + " - Obtained the Master Sword\n" + " - Not within range of a Time Block\n" + " - Not within range of Ocarina Playing spots.")); + + AddWidget(path, "Masks", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Bunny Hood Effect", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("MMBunnyHood")) + .Options(CheckboxOptions().Tooltip("Wearing the Bunny Hood grants a speed increase link in Majora's Mask. " + "The longer jump option is not accounted for in Randomizer logic.\n\n" + "Also disables NPC's reactions to wearing the Bunny Hood.")); + AddWidget(path, "Masks Equippable as Adult", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AdultMasks")) + .Options(CheckboxOptions().Tooltip("Allows masks to be equipped normally from the pause menu as adult.")); + AddWidget(path, "Persistent Masks", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("PersistentMasks")) + .Options( + CheckboxOptions().Tooltip("Stops masks from automatically unequipping on certain situations:\n" + "- When entering a new scene\n" + "- When not in any C button or the D-Pad\n" + "- When saving and quitting\n" + "- When dying\n" + "- When traveling thru time (if \"Masks Equippable as Adult\" is activated).")); + AddWidget(path, "Mask Select in Inventory", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("MaskSelect")) + .Options(CheckboxOptions().Tooltip( + "After completing the mask trading sub-quest, press A and any direction on the mask " + "slog to change masks")); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Explosives", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Deku Nuts Explode Bombs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NutsExplodeBombs")) + .Options(CheckboxOptions().Tooltip("Make Deku Nuts explode Bombs, similar to how they interact with Bombchus. " + "This does not affect Bombflowers.")); + AddWidget(path, "Remove Explosive Limit", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RemoveExplosiveLimit")) + .Options(CheckboxOptions().Tooltip("Removes the cap of 3 active explosives being deployed at once.")); + AddWidget(path, "Static Explosion Radius", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("StaticExplosionRadius")) + .Options(CheckboxOptions().Tooltip( + "Explosions are now a static size, like in Majora's Mask and OoT3D. Makes Bombchu " + "hovering much easier.")); + AddWidget(path, "Prevent Bombchus Forcing First-Person", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DisableFirstPersonChus")) + .Options(CheckboxOptions().Tooltip( + "Prevent Bombchus from forcing the camera into first-person mode when released.")); + AddWidget(path, "Better Bombchu Shopping", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BetterBombchuShopping")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); + info.options->disabledTooltip = "This setting is forcefully enabled when you are playing a randomizer."; + }) + .Options( + CheckboxOptions().Tooltip("Bombchus do not sell out when bought, and a 10 pack of Bombchus costs 99 rupees " + "instead of 100.")); + + AddWidget(path, "Bow / Slingshot", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Equip Multiple Arrows at Once", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SeparateArrows")) + .Options(CheckboxOptions().Tooltip( + "Allow the Bow and Magic Arrows to be equipped at the same time on different slots. " + "NOTE: This will disable the behavior of the 'Equip Dupe' glitch.")); + AddWidget(path, "Skip Magic Arrow Equip Animation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipArrowAnimation")); // TODO: See if a Callback could be registered to avoid the need to reload scenes for the next two options. AddWidget(path, "Blue Fire Arrows", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("BlueFireArrows")) .PreFunc([](WidgetInfo& info) { - info.options->disabled = OTRGlobals::Instance->gRandoContext->GetOption(RSK_BLUE_FIRE_ARROWS).Is(RO_GENERIC_ON); + info.options->disabled = + OTRGlobals::Instance->gRandoContext->GetOption(RSK_BLUE_FIRE_ARROWS).Is(RO_GENERIC_ON); info.options->disabledTooltip = "This setting is forcefully enabled because a randomized savefile with " - "\"Blue Fire Arrows\" is currently loaded."; + "\"Blue Fire Arrows\" is currently loaded."; }) .Options(CheckboxOptions().Tooltip( - "Allows Ice Arrows to melt Red Ice. May require a room reload if toggled during gameplay." - )); + "Allows Ice Arrows to melt Red Ice. May require a room reload if toggled during gameplay.")); AddWidget(path, "Sunlight Arrows", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("SunlightArrows")) .PreFunc([](WidgetInfo& info) { - info.options->disabled = OTRGlobals::Instance->gRandoContext->GetOption(RSK_SUNLIGHT_ARROWS).Is(RO_GENERIC_ON); + info.options->disabled = + OTRGlobals::Instance->gRandoContext->GetOption(RSK_SUNLIGHT_ARROWS).Is(RO_GENERIC_ON); info.options->disabledTooltip = "This setting is forcefully enabled because a randomized savefile with " - "\"Sunlight Arrows\" enabled is currently loaded."; + "\"Sunlight Arrows\" enabled is currently loaded."; }) .Options(CheckboxOptions().Tooltip( - "Allows Light Arrows to activate Sun Switches. May require a room reload if toggled during gameplay." - )); + "Allows Light Arrows to activate Sun Switches. May require a room reload if toggled during gameplay.")); + AddWidget(path, "Bow and Child/Slingshot as Adult", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BowSlingshotAmmoFix")) + .Options(CheckboxOptions().Tooltip("Allows Child to use a Bow with Arrows.\n" + "Allows Adult to use a Slingshot with Seeds.\n\n" + "Requires glitches or the 'Timeless Equipment' cheat to equip.")); + AddWidget(path, "Aiming Reticle for the Bow/Slingshot", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BowReticle")) + .Options(CheckboxOptions().Tooltip("Aiming with a Bow or Slingshot will display a reticle as with the Hookshot " + "when the projectile is ready to fire.")); + + path.column = SECTION_COLUMN_3; + AddWidget(path, "Hookshot", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Targetable Hookshot Reticle", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HookshotableReticle")) + .Options(CheckboxOptions().Tooltip( + "Makes the Hookshot Reticle use a different color when aiming at Hookshotable Collision.")); + + AddWidget(path, "Boomerang", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Instant Boomerang Recall", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastBoomerang")) + .Options(CheckboxOptions().Tooltip("Instantly return the boomerang to Link by pressing its item button while " + "it's in the air.")); + AddWidget(path, "Aim Boomerang in First-Person Mode", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BoomerangFirstPerson")) + .Callback([](WidgetInfo& info) { + if (!CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0)) { + CVarSetInteger(CVAR_ENHANCEMENT("BoomerangReticle"), 0); + } + }) + .Options(CheckboxOptions().Tooltip( + "Change aiming for the Boomerang from Third-Person to First-Person to see past Link's head.")); + AddWidget(path, "Aiming Reticle for Boomerang", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BoomerangFirstPerson")) + .PreFunc( + [](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("BoomerangFirstPerson"), 0) != 0; }) + .Options(CheckboxOptions().Tooltip("Aiming with the Boomerang will display a reticle as with the Hookshot.")); + + AddWidget(path, "Magic Spells", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Better Farore's Wind", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BetterFarore")) + .Options(CheckboxOptions().Tooltip( + "Helps FW persist between ages, gives Child and Adult separate FW points, and can " + "be used in more places.")); + AddWidget(path, "Faster Farore's Wind", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FastFarores")) + .Options(CheckboxOptions().Tooltip("Greatly decreases cast time of Farore's Wind magic spell.")); + + // Fixes + path.sidebarName = "Fixes"; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; + AddWidget(path, "Gameplay Fixes", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Fix the Gravedigging Tour Glitch", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GravediggingTourFix")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); + info.options->disabledTooltip = "This setting is always enabled in randomized save files."; + }) + .Options(CheckboxOptions().Tooltip( + "Fixes a bug where the Gravedigging Tour Heart Piece disappears if the area reloads.")); + AddWidget(path, "Fix Raised Floor Switches", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixFloorSwitches")) + .Options(CheckboxOptions().Tooltip( + "Fixes the two raised floor switches, the one in Forest Temple Basement and the one at the top of Fire " + "Temple. This will lower them, making activating them easier.")); + AddWidget(path, "Fix Zora Hint Dialogue", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixZoraHintDialogue")) + .Options(CheckboxOptions().Tooltip( + "Fixes one Zora's dialogue giving a hint about bringing Ruto's Letter to King Zora to properly occur " + "before moving King Zora rather than after.")); + AddWidget(path, "Fix Falling from Vine Edges", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixVineFall")) + .Options( + CheckboxOptions().Tooltip("Prevents immediately falling off climbable surfaces if climbing on the edges.")); + AddWidget(path, "Fix Bush Item Drops", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BushDropFix")) + .Options(CheckboxOptions().Tooltip( + "Fixes the bushes to drop items correctly rather than spawning undefined items.")); + AddWidget(path, "Fix Enemies not Spawning Near Water", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EnemySpawnsOverWaterboxes")) + .Options(CheckboxOptions().Tooltip( + "Causes respanwing enemies, like Stalchildren, to appear on land near bodies of water. " + "Fixes an incorrect calculation that acted like water underneath ground was above it.")); + AddWidget(path, "Fix Poacher's Saw Softlock", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixSawSoftlock")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("SkipText"), 0) == 1; + info.options->disabledTooltip = "This option is forced on when Skip Text is enabled."; + }) + .Options(CheckboxOptions().Tooltip( + "Prevents the Poacher's Saw softlock from mashing through the text, or with Skip Text enabled.")); + AddWidget(path, "Fix Anubis Fireballs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("AnubisFix")) + .Options(CheckboxOptions().Tooltip( + "Make Anubis Fireballs do Fire damage when reflected back at them with the Mirror Shield.")); + + AddWidget(path, "Item-related Fixes", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Fix Deku Nut Upgrade", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DekuNutUpgradeFix")) + .Options(CheckboxOptions().Tooltip("Prevents the Forest Stage Deku Nut upgrade from becoming unobtainable " + "after receiving the Poacher's Saw.")); + AddWidget(path, "Fix Megaton Hammer Crouch Stab", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CrouchStabHammerFix")) + .Callback([](WidgetInfo& info) { + if (!CVarGetInteger(CVAR_ENHANCEMENT("CrouchStabHammerFix"), 0)) { + CVarClear(CVAR_ENHANCEMENT("CrouchStabFix")); + } + }) + .Options(CheckboxOptions().Tooltip( + "Make the Megaton Hammer's crouch stab able to destroy rocks without first swinging it normally.")); + AddWidget(path, "Remove Power Crouch Stab", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CrouchStabFix")) + .PreFunc( + [](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("CrouchStabHammerFix"), 0) == 0; }) + .Options(CheckboxOptions().Tooltip("Make crouch stabbing always do the same damage as a regular slash.")); + AddWidget(path, "Fix Broken Giant's Knife Bug", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixGrokenGiantsKnife")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = IS_RANDO && GameInteractor::IsSaveLoaded(true); + info.options->disabledTooltip = "This setting is forcefully enabled when you are playing a Randomizer."; + }) + .Callback([](WidgetInfo& info) { + bool hasGiantsKnife = CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_BIGGORON); + bool hasBrokenKnife = CHECK_OWNED_EQUIP_ALT(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_BROKENGIANTKNIFE); + bool knifeIsBroken = gSaveContext.swordHealth == 0.0f; + + if (hasGiantsKnife && (hasBrokenKnife != knifeIsBroken)) { + func_800849EC(gPlayState); + } + }) + .Options( + CheckboxOptions().Tooltip("Fixes the Broken Giant's Knife flag not being reset when Medigoron fixes it.")); + + AddWidget(path, "Camera Fixes", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Fix Camera Drift", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixCameraDrift")) + .Options(CheckboxOptions().Tooltip( + "Fixes camera slightly drifting to the left when standing still due to a math error.")); + AddWidget(path, "Fix Camera Swing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixCameraSwing")) + .Options(CheckboxOptions().Tooltip( + "Fixes camera getting stuck on collision when standing still. Also fixes slight shift " + "back in camera when Link stops moving.")); + AddWidget(path, "Fix Hanging Ledge Swing Rate", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixHangingLedgeSwingRate")) + .Options(CheckboxOptions().Tooltip( + "Fixes camera swing rate when the player falls off a ledge and the camera swings around.")); + path.column = SECTION_COLUMN_2; + AddWidget(path, "Graphical Fixes", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Fix L&R Pause Menu", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixMenuLR")) + .Options(CheckboxOptions().Tooltip("Makes the L and R buttons in the pause menu the same color")); + AddWidget(path, "Fix Dungeon Entrances", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixDungeonMinimapIcon")) + .Options(CheckboxOptions().Tooltip( + "Removes the Dungeon Entrance icon on the top-left corner of the screen when no dungeon is present on the " + "current map.")); + AddWidget(path, "Fix Two-Handled Idle Animations", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("TwoHandedIdle")) + .Options(CheckboxOptions().Tooltip( + "Re-Enables the two-handed idle animation, a seemingly finished animation that was disabled on accident " + "in the original game.")); + AddWidget(path, "Fix Navi Text HUD Position", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NaviTextFix")) + .Options(CheckboxOptions().Tooltip("Correctly centers the Navi text prompt on the HUD's C-Up button.")); + AddWidget(path, "Fix Gerudo Warrior's Clothing Colors", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GerudoWarriorClothingFix")) + .Options(CheckboxOptions().Tooltip( + "Prevent the Gerudo Warrior's clothes changing color when changing Link's tunic or " + "using Bombs in front of her.")); + AddWidget(path, "Fix Out of Bounds Textures", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixTexturesOOB")) + .Callback([](WidgetInfo& info) { ApplyAuthenticGfxPatches(); }) + .Options(CheckboxOptions().Tooltip( + "Fixes authentic out of bounds texture reads, instead loading textures with the correct size.")); + AddWidget(path, "Fix Link's Eyes Open while Sleeping", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixEyesOpenWhileSleeping")) + .Options(CheckboxOptions().Tooltip( + "Fixes Link's eyes being open in the openeing cutscene when he is supposed to be sleeping.")); + AddWidget(path, "Fix Hand Holding Hammer", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixHammerHand")) + .Callback([](WidgetInfo& info) { UpdatePatchHand(); }) + .Options(CheckboxOptions().Tooltip( + "Fixes Adult Link having a backwards Left hand when holding the Megaton Hammer.")); + AddWidget(path, "Fix Vanishing Paths", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("SceneSpecificDirtPathFix")) + .Callback([](WidgetInfo& info) { + if (gPlayState != NULL) { + UpdateDirtPathFixState(gPlayState->sceneNum); + } + }) + .Options( + ComboboxOptions() + .ComboMap(zFightingOptions) + .DefaultIndex(ZFIGHT_FIX_DISABLED) + .Tooltip("Disabled: Paths vanish more the higher the resolution (Z-fighting is based on resolution)\n" + "Consistent: Certain paths vanish the same way in all resolutions\n" + "No Vanish: Paths do not vanish, Link seems to sink in to some paths\n" + "This might affect other decal effects\n")); + + AddWidget(path, "Audio Fixes", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Fix Missing Jingle after 5 Silver Rupees", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SilverRupeeJingleExtend")) + .Options(CheckboxOptions().Tooltip( + "Adds 5 higher pitches for the Silver Rupee Jingle for the rooms with more than 5 Silver Rupees. " + "Only relevant for playthroughs involving Master Quest Dungeons.")); + + AddWidget(path, "Desync Fixes", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Fix Darunia Dancing Too Fast", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FixDaruniaDanceSpeed")) + .Options(CheckboxOptions().Tooltip( + "Fixes Darunia's dancing speed so he dances to the beat of Saria's Song, like in the Original Game.")); + AddWidget(path, "Fix Credits Timing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CreditsFix")) + .Options(CheckboxOptions().Tooltip( + "Extend certain credits scenes so the music lines up properly with the visuals.")); + + path.column = SECTION_COLUMN_3; + AddWidget(path, "Graphical Restorations", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Red Ganon Blood", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RedGanonBlood")) + .Options( + CheckboxOptions().Tooltip("Restore the original red blood from NTSC 1.0/1.1. Disable for Green blood.")); + AddWidget(path, "Restore Old Gold Skulltula Cutscene", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GSCutscene")) + .Options(CheckboxOptions().Tooltip( + "Restore pre-release behavior where defeating a Gold Skulltula will play a cutscene showing it die.")); + AddWidget(path, "Pulsate Boss Icon", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("Pulsate Boss Icon")) + .Options(CheckboxOptions().Tooltip( + "Restores an unfinished feature to pulsate the boss room icon when you are in the boss room.")); + + AddWidget(path, "Glitch Restorations", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Fish while Hovering", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HoverFishing")) + .Options(CheckboxOptions().Tooltip( + "Restore a bug from NSTC 1.0 that allows casting the Fishing Rod while using the Hover Boots.")); + AddWidget(path, "N64 Weird Frames", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("N64WeirdFrames")) + .Options(CheckboxOptions().Tooltip("Restores N64 Weird Frames allwing weirdshots to behave the same as N64.")); + AddWidget(path, "Bombchus Out of Bounds", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BombchusOOB")) + .Options(CheckboxOptions().Tooltip("Allows Bombchus to explode out of bounds. Similar to Gamecube and Wii VC")); + AddWidget(path, "Quick Putaway", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("QuickPutaway")) + .Options(CheckboxOptions().Tooltip( + "Restore a bug from NTSC 1.0 that allows putting away an item without an animation and performing " + "Putaway Ocarina Items.")); + AddWidget(path, "Quick Bongo Kill", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("QuickBongoKill")) + .Options(CheckboxOptions().Tooltip( + "Restore a bug from NTSC 1.0 that allows bypassing Bongo Bongo's intro cutscene to quickly kill him.")); + AddWidget(path, "Original RBA Values", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("RestoreRBAValues")) + .Options(CheckboxOptions().Tooltip("Restores the original outcomes when performing Reverse Bottle Adventure.")); + AddWidget(path, "Early Eyeball Frog", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("EarlyEyeballFrog")) + .Options(CheckboxOptions().Tooltip( + "Restores a bug from NTSC 1.0/1.1 that allows you to obtain the eyeball frog from King Zora " + "instead of the Zora Tunic by Holding Shield.")); + + AddWidget(path, "Misc Restorations", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Fix L&Z Page Switch in Pause Menu", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("NGCKaleidoSwitcher")) + .Options(CheckboxOptions().Tooltip( + "Makes L and R switch pages like on the Gamecube. Z opens the Debug Menu instead.")); + // Difficulty Options path.sidebarName = "Difficulty"; AddSidebarEntry("Enhancements", path.sidebarName, 3); path.column = SECTION_COLUMN_1; - - AddWidget(path, "Shooting Gallery", WIDGET_SEPARATOR_TEXT); - auto shootingGalleryDisabledFunc = [](WidgetInfo& info) { - info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeShootingGallery"), 0); - info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off."; - }; - AddWidget(path, "Customize Behavior##Shooting", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("CustomizeShootingGallery")) - .Options(CheckboxOptions().Tooltip("Turn on/off changes to the shooting gallery behavior")); - AddWidget(path, "Instant Win", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("InstantShootingGalleryWin")) - .PreFunc(shootingGalleryDisabledFunc) - .Options(CheckboxOptions().Tooltip("Skips the Shooting Gallery minigame")); - AddWidget(path, "No Rupee Randomization", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("ConstantAdultGallery")) - .PreFunc(shootingGalleryDisabledFunc) - .Options(CheckboxOptions().Tooltip( - "Forces the rupee order to not be randomized as adult, making it the same as child." - )); - AddWidget(path, "Child Starting Ammunition: %d seeds", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("ShootingGalleryAmmoChild")) - .PreFunc(shootingGalleryDisabledFunc) - .Options(IntSliderOptions() - .Min(10) - .Max(30) - .DefaultValue(15) - .Format("%d seeds") - .Tooltip( - "The ammunition at the start of the Shooting Gallery minigame as Child." - ) - ); - AddWidget(path, "Adult Starting Ammunition: %d arrows", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("ShootingGalleryAmmoAdult")) - .PreFunc(shootingGalleryDisabledFunc) - .Options(IntSliderOptions() - .Min(10) - .Max(30) - .DefaultValue(15) - .Format("%d arrows") - .Tooltip( - "The ammunition at the start of the Shooting Gallery minigame as Adult." - ) - ); - - AddWidget(path, "Bombchu Bowling", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Customize Behavior##Bowling", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("CustomizeBombchuBowling")) - .Options(CheckboxOptions().Tooltip( - "Turn on/off changes to the Bombchu Bowling behavior." - )); - auto bombchuBowlingDisabledFunc = [](WidgetInfo& info) { - info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeBombchuBowling"), 0) == 0; - info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off"; - }; - AddWidget(path, "Remove Small Cucco", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("BombchuBowlingNoSmallCucco")) - .PreFunc(bombchuBowlingDisabledFunc) - .Options(CheckboxOptions().Tooltip( - "Prevents the small Cucco from appearing in the Bombchu Bowling minigame." - )); - AddWidget(path, "Remove Big Cucco", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("BombchuBowlingNoBigCucco")) - .PreFunc(bombchuBowlingDisabledFunc) - .Options(CheckboxOptions().Tooltip( - "Prevents the big Cucco from appearing in the Bombchu Bowling minigame." - )); - AddWidget(path, "Bombchu Count: %d bombchus", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("BombchuBowlingAmmo")) - .PreFunc(bombchuBowlingDisabledFunc) - .Options(IntSliderOptions() - .Min(3) - .Max(20) - .DefaultValue(10) - .Format("%d bombchus") - .Tooltip("The number of Bombchus available at the start of the Bombchu Bowling minigame.") - ); - - AddWidget(path, "Fishing", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Customize Behavior##Fishing", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("CustomizeFishing")) - .Options(CheckboxOptions().Tooltip( - "Turn on/off changes to the Fishing behavior" - )); - auto fishingDisabledFunc = [](WidgetInfo& info) { - info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0) == 0; - info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off."; - }; - AddWidget(path, "Instant Fishing", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("InstantFishing")) - .PreFunc(fishingDisabledFunc) - .Options(CheckboxOptions().Tooltip( - "All fish will be caught instantly." - )); - AddWidget(path, "Guarantee Bite", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("GuaranteeFishingBite")) - .PreFunc(fishingDisabledFunc) - .Options(CheckboxOptions().Tooltip( - "When a line is stable, guarantee bite. Otherwise use Default logic." - )); - AddWidget(path, "Fish Never Escape", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FishNeverEscape")) - .PreFunc(fishingDisabledFunc) - .Options(CheckboxOptions().Tooltip( - "Once a hook as been set, Fish will never let go while being reeled in." - )); - AddWidget(path, "Loaches Always Appear", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("LoachesAlwaysAppear")) - .PreFunc(fishingDisabledFunc) - .Options(CheckboxOptions().Tooltip( - "Loaches will always appear in the fishing pond instead of every four visits." - )); - AddWidget(path, "Skip Keep Confirmation", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("SkipKeepConfirmation")) - .PreFunc(fishingDisabledFunc) - .Options(CheckboxOptions().Tooltip( - "The Pond Owner will not ask to confirm if you want to keep a smaller Fish." - )); - AddWidget(path, "Child Minimum Weight: %d lbs.", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("MinimumFishWeightChild")) - .PreFunc(fishingDisabledFunc) - .Options(IntSliderOptions() - .Min(3) - .Max(10) - .DefaultValue(10) - .Format("%d lbs.") - .Tooltip( - "The minimum weight for the unique Fishing Reward as a Child." - ) - ); - AddWidget(path, "Adult Minimum Weight: %d lbs.", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("MinimumFishWeightAdult")) - .PreFunc(fishingDisabledFunc) - .Options(IntSliderOptions() - .Min(6) - .Max(13) - .DefaultValue(13) - .Format("%d lbs.") - .Tooltip( - "The minimum weight for the unique fishing reward as an Adult." - ) - ); - AddWidget(path, "All Fish are Hyrule Loaches", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("AllHyruleLoaches")) - .PreFunc(fishingDisabledFunc) - .Options(IntSliderOptions().Tooltip( - "Every fish in the Fishing Pond will always be a Hyrule Loach.\n\n" - "NOTE: This requires reloading the area." - )); - - path.column = SECTION_COLUMN_2; - AddWidget(path, "Lost Woods Ocarina Game", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Customize Behavior##LostWoods", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("CustomizeOcarinaGame")) - .Options(CheckboxOptions().Tooltip( - "Turn on/off changes to the Lost Woods Ocarina Game behavior." - )); - auto ocarinaMemoryGameDisabledFunc = [](WidgetInfo& info) { - info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeOcarinaGame"), 0) == 0; - info.options->disabledTooltip = "This options is disabled because \"Customize Behavior\" is turned off."; - }; - AddWidget(path, "Instant Win##LostWoods", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("InstantOcarinaGameWin")) - .PreFunc(ocarinaMemoryGameDisabledFunc) - .Options(CheckboxOptions().Tooltip( - "Skips the Lost Woods Ocarina Memory Game." - )); - AddWidget(path, "Note Play Speed: %dx", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("OcarinaGame.NoteSpeed")) - .PreFunc(ocarinaMemoryGameDisabledFunc) - .Options(IntSliderOptions() - .Min(1) - .Max(5) - .DefaultValue(1) - .Format("%dx") - .Tooltip( - "Adjust the speed that the Skull Kids play the notes." - ) - ); - AddWidget(path, "Unlimited Playback Time##LostWoods", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("OcarinaUnlimitedFailTime")) - .PreFunc(ocarinaMemoryGameDisabledFunc) - .Options(CheckboxOptions().Tooltip( - "Removes the timer to play back the song." - )); - AddWidget(path, "Number of Starting Notes: %d notes", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("OcarinaGame.StartingNotes")) - .PreFunc(ocarinaMemoryGameDisabledFunc) - .Options(IntSliderOptions() - .Min(1) - .Max(8) - .DefaultValue(3) - .Format("%d notes") - .Tooltip( - "Adjust the number of notes the Skull Kids play to start the first round." - ) - ); - int roundMin = CVarGetInteger(CVAR_ENHANCEMENT("OcarinaGame.StartingNotes"), 3); - AddWidget(path, "Round One Notes: %d notes", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("OcarinaGame.RoundOneNotes")) - .PreFunc(ocarinaMemoryGameDisabledFunc) - .Options(IntSliderOptions() - .Min(roundMin) - .Max(8) - .DefaultValue(5) - .Format("%d notes") - .Tooltip( - "Adjust the number of notes you need to play to end the first round." - ) - ); - AddWidget(path, "Round Two Notes: %d notes", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("OcarinaGame.RoundTwoNotes")) - .PreFunc(ocarinaMemoryGameDisabledFunc) - .Options(IntSliderOptions() - .Min(roundMin) - .Max(8) - .DefaultValue(6) - .Format("%d notes") - .Tooltip( - "Adjust the number of notes you need to play to end the second round." - ) - ); - AddWidget(path, "Round Three Notes: %d notes", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("OcarinaGame.RoundThreeNotes")) - .PreFunc(ocarinaMemoryGameDisabledFunc) - .Options(IntSliderOptions() - .Min(roundMin) - .Max(8) - .DefaultValue(8) - .Format("%d notes") - .Tooltip( - "Adjust the number of notes you need to play to end the third round." - ) - ); - - AddWidget(path, "Frogs' Ocarina Game", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Customize Behavior##Frogs", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame")) - .Options(CheckboxOptions().Tooltip( - "Turn on/off changes to the Frogs' Ocarina Game behavior." - )); - auto frogsOcarinaGameDisabledFunc = [](WidgetInfo& info) { - info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame"), 0) == 0; - info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off."; - }; - AddWidget(path, "Instant Win##Frogs", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("InstantFrogsGameWin")) - .PreFunc(frogsOcarinaGameDisabledFunc) - .Options(CheckboxOptions().Tooltip( - "Skips the Frogs' Ocarina Game." - )); - AddWidget(path, "Unlimited Playback Time##Frogs", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("FrogsUnlimitedFailTime")) - .PreFunc(frogsOcarinaGameDisabledFunc) - .Options(CheckboxOptions().Tooltip( - "Removes the timer to play back the song." - )); - AddWidget(path, "Modify note timer: %dx", WIDGET_CVAR_SLIDER_INT) - .CVar(CVAR_ENHANCEMENT("FrogsModifyFailTime")) - .PreFunc([](WidgetInfo& info) { - info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame"), 0) || CVarGetInteger(CVAR_ENHANCEMENT("FrogsUnlimitedFailTime"), 0); - info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off or \"Unlimited Playback Time\" is on"; - }) - .Options(IntSliderOptions() - .Min(1) - .Max(5) - .DefaultValue(1) - .Format("%dx") - .Tooltip( - "Adjusts the time allowed for playback before failing." - ) - ); AddWidget(path, "Health", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Permanent Heart Loss", WIDGET_CVAR_CHECKBOX) @@ -1593,7 +1099,7 @@ void SohMenu::AddMenuEnhancements() { "This simulates Hero Mode from other games in the series." )); - path.column = SECTION_COLUMN_3; + path.column = SECTION_COLUMN_2; AddWidget(path, "Drops", WIDGET_SEPARATOR_TEXT); AddWidget(path, "No Random Drops", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("NoRandomDrops")) @@ -1603,7 +1109,9 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Enable Bombchu Drops", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("EnableBombchuDrops")) .PreFunc([](WidgetInfo& info) { - info.options->disabled = OTRGlobals::Instance->gRandoContext->GetOption(RSK_ENABLE_BOMBCHU_DROPS).Is(RO_GENERIC_ON); + info.options->disabled = + IS_RANDO && GameInteractor::IsSaveLoaded(true) && + OTRGlobals::Instance->gRandoContext->GetOption(RSK_ENABLE_BOMBCHU_DROPS).Is(RO_GENERIC_ON); info.options->disabledTooltip = "This setting is forcefully enabled because a randomized savefile with " "\"Enable Bombchu Drops\" is loaded."; }) @@ -1624,22 +1132,6 @@ void SohMenu::AddMenuEnhancements() { ICON_FA_EXCLAMATION_TRIANGLE " WARNING " ICON_FA_EXCLAMATION_TRIANGLE "\nTHIS IS NOT REVERSABLE\nUSE AT YOUR OWN RISK!" )); - AddWidget(path, "Hyper Bosses", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("HyperBosses")) - .Callback([](WidgetInfo& info) { - UpdateHyperBossesState(); - }) - .Options(CheckboxOptions().Tooltip( - "All Major Bosses move and act twice as fast." - )); - AddWidget(path, "Hyper Enemies", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("HyperEnemies")) - .Callback([](WidgetInfo& info) { - UpdateHyperEnemiesState(); - }) - .Options(CheckboxOptions().Tooltip( - "All Regular Enemies and Mini-Bosses move and act twice as fast." - )); AddWidget(path, "Always Win Goron Pot", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("GoronPot")) .Options(CheckboxOptions().Tooltip( @@ -1667,18 +1159,228 @@ void SohMenu::AddMenuEnhancements() { "Cuccos will stay in place longer after putting them down, by a multiple of the value of the slider." ) ); + + path.column = SECTION_COLUMN_3; + AddWidget(path, "Enemies", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Hyper Bosses", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HyperBosses")) + .Callback([](WidgetInfo& info) { UpdateHyperBossesState(); }) + .Options(CheckboxOptions().Tooltip("All Major Bosses move and act twice as fast.")); + AddWidget(path, "Hyper Enemies", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HyperEnemies")) + .Callback([](WidgetInfo& info) { UpdateHyperEnemiesState(); }) + .Options(CheckboxOptions().Tooltip("All Regular Enemies and Mini-Bosses move and act twice as fast.")); + AddWidget(path, "Enable Visual Guard Vision", WIDGET_CVAR_CHECKBOX).CVar(CVAR_ENHANCEMENT("GuardVision")); AddWidget(path, "Leever Spawn Rate: %d seconds", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR_ENHANCEMENT("LeeverSpawnRate")) .Options(IntSliderOptions() - .Min(0) - .Max(10) - .DefaultValue(0) - .Format("%d seconds") - .Tooltip( - "The time between groups of Leevers spawning." - ) - ); + .Min(0) + .Max(10) + .DefaultValue(0) + .Format("%d seconds") + .Tooltip("The time between groups of Leevers spawning.")); + // Minigames + path.sidebarName = "Minigames"; + AddSidebarEntry("Enhancements", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Shooting Gallery", WIDGET_SEPARATOR_TEXT); + auto shootingGalleryDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeShootingGallery"), 0); + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off."; + }; + AddWidget(path, "Customize Behavior##Shooting", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeShootingGallery")) + .Options(CheckboxOptions().Tooltip("Turn on/off changes to the shooting gallery behavior")); + AddWidget(path, "Instant Win", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantShootingGalleryWin")) + .PreFunc(shootingGalleryDisabledFunc) + .Options(CheckboxOptions().Tooltip("Skips the Shooting Gallery minigame")); + AddWidget(path, "No Rupee Randomization", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ConstantAdultGallery")) + .PreFunc(shootingGalleryDisabledFunc) + .Options(CheckboxOptions().Tooltip( + "Forces the rupee order to not be randomized as adult, making it the same as child.")); + AddWidget(path, "Child Starting Ammunition: %d seeds", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("ShootingGalleryAmmoChild")) + .PreFunc(shootingGalleryDisabledFunc) + .Options(IntSliderOptions() + .Min(10) + .Max(30) + .DefaultValue(15) + .Format("%d seeds") + .Tooltip("The ammunition at the start of the Shooting Gallery minigame as Child.")); + AddWidget(path, "Adult Starting Ammunition: %d arrows", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("ShootingGalleryAmmoAdult")) + .PreFunc(shootingGalleryDisabledFunc) + .Options(IntSliderOptions() + .Min(10) + .Max(30) + .DefaultValue(15) + .Format("%d arrows") + .Tooltip("The ammunition at the start of the Shooting Gallery minigame as Adult.")); + + AddWidget(path, "Bombchu Bowling", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Customize Behavior##Bowling", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeBombchuBowling")) + .Options(CheckboxOptions().Tooltip("Turn on/off changes to the Bombchu Bowling behavior.")); + auto bombchuBowlingDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeBombchuBowling"), 0) == 0; + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off"; + }; + AddWidget(path, "Remove Small Cucco", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BombchuBowlingNoSmallCucco")) + .PreFunc(bombchuBowlingDisabledFunc) + .Options(CheckboxOptions().Tooltip("Prevents the small Cucco from appearing in the Bombchu Bowling minigame.")); + AddWidget(path, "Remove Big Cucco", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BombchuBowlingNoBigCucco")) + .PreFunc(bombchuBowlingDisabledFunc) + .Options(CheckboxOptions().Tooltip("Prevents the big Cucco from appearing in the Bombchu Bowling minigame.")); + AddWidget(path, "Bombchu Count: %d bombchus", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("BombchuBowlingAmmo")) + .PreFunc(bombchuBowlingDisabledFunc) + .Options(IntSliderOptions() + .Min(3) + .Max(20) + .DefaultValue(10) + .Format("%d bombchus") + .Tooltip("The number of Bombchus available at the start of the Bombchu Bowling minigame.")); + AddWidget(path, "Frogs' Ocarina Game", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Customize Behavior##Frogs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame")) + .Options(CheckboxOptions().Tooltip("Turn on/off changes to the Frogs' Ocarina Game behavior.")); + auto frogsOcarinaGameDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame"), 0) == 0; + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off."; + }; + AddWidget(path, "Instant Win##Frogs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantFrogsGameWin")) + .PreFunc(frogsOcarinaGameDisabledFunc) + .Options(CheckboxOptions().Tooltip("Skips the Frogs' Ocarina Game.")); + AddWidget(path, "Unlimited Playback Time##Frogs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FrogsUnlimitedFailTime")) + .PreFunc(frogsOcarinaGameDisabledFunc) + .Options(CheckboxOptions().Tooltip("Removes the timer to play back the song.")); + AddWidget(path, "Modify note timer: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("FrogsModifyFailTime")) + .PreFunc([](WidgetInfo& info) { + info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame"), 0) || + CVarGetInteger(CVAR_ENHANCEMENT("FrogsUnlimitedFailTime"), 0); + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off or " + "\"Unlimited Playback Time\" is on"; + }) + .Options(IntSliderOptions().Min(1).Max(5).DefaultValue(1).Format("%dx").Tooltip( + "Adjusts the time allowed for playback before failing.")); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Lost Woods Ocarina Game", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Customize Behavior##LostWoods", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeOcarinaGame")) + .Options(CheckboxOptions().Tooltip("Turn on/off changes to the Lost Woods Ocarina Game behavior.")); + auto ocarinaMemoryGameDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeOcarinaGame"), 0) == 0; + info.options->disabledTooltip = "This options is disabled because \"Customize Behavior\" is turned off."; + }; + AddWidget(path, "Instant Win##LostWoods", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantOcarinaGameWin")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(CheckboxOptions().Tooltip("Skips the Lost Woods Ocarina Memory Game.")); + AddWidget(path, "Note Play Speed: %dx", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.NoteSpeed")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions().Min(1).Max(5).DefaultValue(1).Format("%dx").Tooltip( + "Adjust the speed that the Skull Kids play the notes.")); + AddWidget(path, "Unlimited Playback Time##LostWoods", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("OcarinaUnlimitedFailTime")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(CheckboxOptions().Tooltip("Removes the timer to play back the song.")); + AddWidget(path, "Number of Starting Notes: %d notes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.StartingNotes")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(1) + .Max(8) + .DefaultValue(3) + .Format("%d notes") + .Tooltip("Adjust the number of notes the Skull Kids play to start the first round.")); + int roundMin = CVarGetInteger(CVAR_ENHANCEMENT("OcarinaGame.StartingNotes"), 3); + AddWidget(path, "Round One Notes: %d notes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.RoundOneNotes")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(roundMin) + .Max(8) + .DefaultValue(5) + .Format("%d notes") + .Tooltip("Adjust the number of notes you need to play to end the first round.")); + AddWidget(path, "Round Two Notes: %d notes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.RoundTwoNotes")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(roundMin) + .Max(8) + .DefaultValue(6) + .Format("%d notes") + .Tooltip("Adjust the number of notes you need to play to end the second round.")); + AddWidget(path, "Round Three Notes: %d notes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("OcarinaGame.RoundThreeNotes")) + .PreFunc(ocarinaMemoryGameDisabledFunc) + .Options(IntSliderOptions() + .Min(roundMin) + .Max(8) + .DefaultValue(8) + .Format("%d notes") + .Tooltip("Adjust the number of notes you need to play to end the third round.")); + + path.column = SECTION_COLUMN_3; + AddWidget(path, "Fishing", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Customize Behavior##Fishing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("CustomizeFishing")) + .Options(CheckboxOptions().Tooltip("Turn on/off changes to the Fishing behavior")); + auto fishingDisabledFunc = [](WidgetInfo& info) { + info.options->disabled = CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFishing"), 0) == 0; + info.options->disabledTooltip = "This option is disabled because \"Customize Behavior\" is turned off."; + }; + AddWidget(path, "Instant Fishing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("InstantFishing")) + .PreFunc(fishingDisabledFunc) + .Options(CheckboxOptions().Tooltip("All fish will be caught instantly.")); + AddWidget(path, "Guarantee Bite", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("GuaranteeFishingBite")) + .PreFunc(fishingDisabledFunc) + .Options(CheckboxOptions().Tooltip("When a line is stable, guarantee bite. Otherwise use Default logic.")); + AddWidget(path, "Fish Never Escape", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("FishNeverEscape")) + .PreFunc(fishingDisabledFunc) + .Options(CheckboxOptions().Tooltip("Once a hook as been set, Fish will never let go while being reeled in.")); + AddWidget(path, "Loaches Always Appear", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("LoachesAlwaysAppear")) + .PreFunc(fishingDisabledFunc) + .Options( + CheckboxOptions().Tooltip("Loaches will always appear in the fishing pond instead of every four visits.")); + AddWidget(path, "Skip Keep Confirmation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipKeepConfirmation")) + .PreFunc(fishingDisabledFunc) + .Options( + CheckboxOptions().Tooltip("The Pond Owner will not ask to confirm if you want to keep a smaller Fish.")); + AddWidget(path, "Child Minimum Weight: %d lbs.", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("MinimumFishWeightChild")) + .PreFunc(fishingDisabledFunc) + .Options(IntSliderOptions().Min(3).Max(10).DefaultValue(10).Format("%d lbs.").Tooltip( + "The minimum weight for the unique Fishing Reward as a Child.")); + AddWidget(path, "Adult Minimum Weight: %d lbs.", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("MinimumFishWeightAdult")) + .PreFunc(fishingDisabledFunc) + .Options(IntSliderOptions().Min(6).Max(13).DefaultValue(13).Format("%d lbs.").Tooltip( + "The minimum weight for the unique fishing reward as an Adult.")); + AddWidget(path, "All Fish are Hyrule Loaches", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("AllHyruleLoaches")) + .PreFunc(fishingDisabledFunc) + .Options(IntSliderOptions().Tooltip("Every fish in the Fishing Pond will always be a Hyrule Loach.\n\n" + "NOTE: This requires reloading the area.")); + + // Extra Modes path.sidebarName = "Extra Modes"; AddSidebarEntry("Enhancements", path.sidebarName, 3); path.column = SECTION_COLUMN_1; @@ -1711,6 +1413,9 @@ void SohMenu::AddMenuEnhancements() { "Enables Ivan the Fairy upon the next map change. Player 2 can control Ivan and press the C-Buttons to " "use items and mess with Player 1!" )); + AddWidget(path, "Dogs Follow you Everywhere", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DogFollowsEverywhere")) + .Options(CheckboxOptions().Tooltip("Allows dogs to follow you anywhere you go, even if you leave the Market.")); AddWidget(path, "Rupee Dash Mode", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("RupeeDash")) .Options(CheckboxOptions().Tooltip( @@ -1881,95 +1586,24 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Nayru's Love", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteNayru")); AddWidget(path, "Epona Boost", WIDGET_CVAR_CHECKBOX).CVar(CVAR_CHEAT("InfiniteEponaBoost")); - AddWidget(path, "Inventory", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Super Tunic", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("SuperTunic")) - .Options(CheckboxOptions().Tooltip( - "Makes every tunic have the effects of every other tunic." - )); - AddWidget(path, "Easy ISG", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("EasyISG")) - .Options(CheckboxOptions().Tooltip( - "Passive Infinite Sword Glitch\n" - "It makes your sword's swing effect and hitbox stay active indefinitely." - )); - AddWidget(path, "Easy QPA", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("EasyQPA")) - .Options(CheckboxOptions().Tooltip( - "Gives you the glitched damage value of the quick put away glitch." - )); + AddWidget(path, "Items", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Timeless Equipment", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_CHEAT("TimelessEquipment")) - .Options(CheckboxOptions().Tooltip( - "Allows any item to be equipped, regardless of age.\n" - "Also allows Child to use Adult strength upgrades." - )); + .Options(CheckboxOptions().Tooltip("Allows any item to be equipped, regardless of age.\n" + "Also allows Child to use Adult strength upgrades.")); AddWidget(path, "Unrestricted Items", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_CHEAT("Unrestricted Items")) - .Options(CheckboxOptions().Tooltip( - "Allows you to use any item at any location" - )); + .Options(CheckboxOptions().Tooltip("Allows you to use any item at any location")); + AddWidget(path, "Super Tunic", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("SuperTunic")) + .Options(CheckboxOptions().Tooltip("Makes every tunic have the effects of every other tunic.")); AddWidget(path, "Fireproof Deku Shield", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_CHEAT("FireproofDekuShield")) - .Options(CheckboxOptions().Tooltip( - "Prevents the Deku Shield from burning on contact with fire." - )); + .Options(CheckboxOptions().Tooltip("Prevents the Deku Shield from burning on contact with fire.")); AddWidget(path, "Shield with Two-Handed Weapons", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_CHEAT("ShieldTwoHanded")) .Options(CheckboxOptions().Tooltip( - "This allows you to put up for shield with any two-handed weapon in hand except for Deku Sticks." - )); - - path.column = SECTION_COLUMN_2; - - AddWidget(path, "Behavior", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "No Clip", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("NoClip")) - .Options(CheckboxOptions().Tooltip("Allows you to walk through walls.")); - AddWidget(path, "Climb Everything", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("ClimbEverything")) - .Options(CheckboxOptions().Tooltip("Makes every surface in the game climbable.")); - AddWidget(path, "Moon Jump on L", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("MoonJumpOnL")) - .Options(CheckboxOptions().Tooltip("Holding L makes you float into the air.")); - AddWidget(path, "New Easy Frame Advancing", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("EasyFrameAdvance")) - .Options(CheckboxOptions().Tooltip( - "Continue holding START button when unpausing to only advance a single frame and then re-pause." - )); - AddWidget(path, "Drops Don't Despawn", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("DropsDontDie")) - .Options(CheckboxOptions().Tooltip( - "Drops from enemies, grass, etc. don't disappear after a set amount of time." - )); - AddWidget(path, "Fish Don't Despawn", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("NoFishDespawn")) - .Options(CheckboxOptions().Tooltip( - "Prevents fish from automatically despawning after a while when dropped." - )); - AddWidget(path, "Bugs Don't Despawn", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("NoBugsDespawn")) - .Options(CheckboxOptions().Tooltip( - "Prevents bugs from automatically despawning after a while when dropped." - )); - AddWidget(path, "Freeze Time", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("FreezeTime")) - .Options(CheckboxOptions().Tooltip("Freezes the time of day")); - AddWidget(path, "Time Sync", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("TimeSync")) - .Options(CheckboxOptions().Tooltip("Syncs the in-game time with the real world time.")); - AddWidget(path, "No ReDead/Gibdo Freeze", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("NoRedeadFreeze")) - .Options(CheckboxOptions().Tooltip( - "Prevents ReDeads and Gibdos from being able to freeze you with their scream." - )); - AddWidget(path, "Keese/Guay Don't Target You", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("NoKeeseGuayTarget")) - .Options(CheckboxOptions().Tooltip( - "Keese and Guay no longer target you and simply ignore you as if you were wearing the " - "Skull Mask." - )); - + "This allows you to put up for shield with any two-handed weapon in hand except for Deku Sticks.")); AddWidget(path, "Deku Sticks:", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_CHEAT("DekuStick")) .Options(ComboboxOptions().ComboMap(dekuStickCheat).DefaultIndex(DEKU_STICK_NORMAL)); @@ -1982,12 +1616,70 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Hookshot Reach Multiplier: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) .CVar(CVAR_CHEAT("HookshotReachMultiplier")) .Options(FloatSliderOptions().Format("%.2f").Min(1.0f).Max(5.0f)); - AddWidget(path, "Change Age", WIDGET_BUTTON) - .Options(ButtonOptions().Tooltip("Switches Link's age and reloads the area.")) - .Callback([](WidgetInfo& info) { SwitchAge(); }); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Misc", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "No Clip", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoClip")) + .Options(CheckboxOptions().Tooltip("Allows you to walk through walls.")); + AddWidget(path, "Climb Everything", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("ClimbEverything")) + .Options(CheckboxOptions().Tooltip("Makes every surface in the game climbable.")); + AddWidget(path, "Moon Jump on L", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("MoonJumpOnL")) + .Options(CheckboxOptions().Tooltip("Holding L makes you float into the air.")); + AddWidget(path, "No ReDead/Gibdo Freeze", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoRedeadFreeze")) + .Options( + CheckboxOptions().Tooltip("Prevents ReDeads and Gibdos from being able to freeze you with their scream.")); + AddWidget(path, "Keese/Guay Don't Target You", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoKeeseGuayTarget")) + .Options(CheckboxOptions().Tooltip( + "Keese and Guay no longer target you and simply ignore you as if you were wearing the " + "Skull Mask.")); + + AddWidget(path, "Glitch Aids", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Easy Frame Advancing", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("EasyFrameAdvance")) + .Options(CheckboxOptions().Tooltip( + "Continue holding START button when unpausing to only advance a single frame and then re-pause.")); + AddWidget(path, "Easy ISG", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("EasyISG")) + .Options(CheckboxOptions().Tooltip("Passive Infinite Sword Glitch\n" + "It makes your sword's swing effect and hitbox stay active indefinitely.")); + AddWidget(path, "Easy QPA", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("EasyQPA")) + .Options(CheckboxOptions().Tooltip("Gives you the glitched damage value of the quick put away glitch.")); AddWidget(path, "Clear Cutscene Pointer", WIDGET_BUTTON) .Callback([](WidgetInfo& info) { GameInteractor::RawAction::ClearCutscenePointer(); }) - .Options(ButtonOptions().Tooltip("Clears the cutscene pointer to a value safe for wrong warps.")); + .Options(ButtonOptions() + .Tooltip("Clears the cutscene pointer to a value safe for wrong warps.") + .Size(UIWidgets::Sizes::Inline)); + + AddWidget(path, "Despawn Timers", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Drops Don't Despawn", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("DropsDontDie")) + .Options( + CheckboxOptions().Tooltip("Drops from enemies, grass, etc. don't disappear after a set amount of time.")); + AddWidget(path, "Fish Don't Despawn", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoFishDespawn")) + .Options(CheckboxOptions().Tooltip("Prevents fish from automatically despawning after a while when dropped.")); + AddWidget(path, "Bugs Don't Despawn", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("NoBugsDespawn")) + .Options(CheckboxOptions().Tooltip("Prevents bugs from automatically despawning after a while when dropped.")); + + AddWidget(path, "Time of Day", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Freeze Time", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("FreezeTime")) + .Options(CheckboxOptions().Tooltip("Freezes the time of day")); + AddWidget(path, "Time Sync", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_CHEAT("TimeSync")) + .Options(CheckboxOptions().Tooltip("Syncs the in-game time with the real world time.")); + + AddWidget(path, "Instant Age Change", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Change Age", WIDGET_BUTTON) + .Options(ButtonOptions().Tooltip("Switches Link's age and reloads the area.").Size(UIWidgets::Sizes::Inline)) + .Callback([](WidgetInfo& info) { SwitchAge(); }); path.column = SECTION_COLUMN_3; AddWidget(path, "Save States", WIDGET_SEPARATOR_TEXT); diff --git a/soh/soh/SohGui/SohMenuSettings.cpp b/soh/soh/SohGui/SohMenuSettings.cpp index eec4c8674..cd1ba339d 100644 --- a/soh/soh/SohGui/SohMenuSettings.cpp +++ b/soh/soh/SohGui/SohMenuSettings.cpp @@ -102,6 +102,20 @@ void SohMenu::AddMenuSettings() { SDL_OpenURL(std::string("file:///" + std::filesystem::absolute(filesPath).string()).c_str()); }) .Options(ButtonOptions().Tooltip("Opens the folder that contains the save and mods folders, etc.")); + + AddWidget(path, "Boot", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Boot Sequence", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("BootSequence")) + .Options(ComboboxOptions() + .DefaultIndex(BOOTSEQUENCE_DEFAULT) + .LabelPosition(LabelPosition::Far) + .ComponentAlignment(ComponentAlignment::Right) + .ComboMap(bootSequenceLabels) + .Tooltip("Configure what happens when starting or resetting the game.\n\n" + "Default: LUS logo -> N64 logo\n" + "Authentic: N64 logo only\n" + "File Select: Skip to file select menu")); + AddWidget(path, "Languages", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Translate Title Screen", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("TitleScreenTranslation")); diff --git a/soh/soh/config/ConfigMigrators.h b/soh/soh/config/ConfigMigrators.h index 008d5692a..f3c663f66 100644 --- a/soh/soh/config/ConfigMigrators.h +++ b/soh/soh/config/ConfigMigrators.h @@ -186,7 +186,7 @@ namespace SOH { { MigrationAction::Rename, "gDisableGrottoRotation", "gEnhancements.DisableGrottoRotation" }, { MigrationAction::Rename, "gDisableKokiriDrawDistance", "gEnhancements.DisableKokiriDrawDistance" }, { MigrationAction::Rename, "gDisableLOD", "gEnhancements.DisableLOD" }, - { MigrationAction::Rename, "gDisableNaviCallAudio", "gEnhancements.DisableNaviCallAudio" }, + { MigrationAction::Rename, "gDisableNaviCallAudio", "gAudioEditor.DisableNaviCallAudio" }, { MigrationAction::Rename, "gDisableTunicWarningText", "gEnhancements.DisableTunicWarningText" }, { MigrationAction::Rename, "gDogFollowsEverywhere", "gEnhancements.DogFollowsEverywhere" }, { MigrationAction::Rename, "gDpadNoDropOcarinaInput", "gEnhancements.DpadNoDropOcarinaInput" }, @@ -236,7 +236,7 @@ namespace SOH { { MigrationAction::Rename, "gInstantShootingGalleryWin", "gEnhancements.InstantShootingGalleryWin" }, { MigrationAction::Rename, "gIvanCoopModeEnabled", "gEnhancements.IvanCoopModeEnabled" }, { MigrationAction::Rename, "gLinkDefaultName", "gEnhancements.LinkDefaultName" }, - { MigrationAction::Rename, "gLowHpAlarm", "gEnhancements.LowHpAlarm" }, + { MigrationAction::Rename, "gLowHpAlarm", "gAudioEditor.LowHpAlarm" }, { MigrationAction::Rename, "gMMBunnyHood", "gEnhancements.MMBunnyHood" }, { MigrationAction::Rename, "gMarketSneak", "gEnhancements.MarketSneak" }, { MigrationAction::Rename, "gMaskSelect", "gEnhancements.MaskSelect" }, diff --git a/soh/src/code/z_lifemeter.c b/soh/src/code/z_lifemeter.c index 726e8d44d..0deab1a70 100644 --- a/soh/src/code/z_lifemeter.c +++ b/soh/src/code/z_lifemeter.c @@ -651,7 +651,7 @@ void HealthMeter_HandleCriticalAlarm(PlayState* play) { if (interfaceCtx->unk_22A <= 0) { interfaceCtx->unk_22A = 0; interfaceCtx->unk_22C = 0; - if (CVarGetInteger(CVAR_ENHANCEMENT("LowHpAlarm"), 0) == 0 && !Player_InCsMode(play) && (play->pauseCtx.state == 0) && + if (CVarGetInteger(CVAR_AUDIO("LowHpAlarm"), 0) == 0 && !Player_InCsMode(play) && (play->pauseCtx.state == 0) && (play->pauseCtx.debugState == 0) && HealthMeter_IsCritical() && !Play_InCsMode(play)) { Sfx_PlaySfxCentered(NA_SE_SY_HITPOINT_ALARM); } diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index f424b9fcd..8c93b8a57 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -2832,7 +2832,7 @@ void Interface_SetNaviCall(PlayState* play, u16 naviCallState) { if (((naviCallState == 0x1D) || (naviCallState == 0x1E)) && !interfaceCtx->naviCalling && (play->csCtx.state == CS_STATE_IDLE)) { - if (!CVarGetInteger(CVAR_ENHANCEMENT("DisableNaviCallAudio"), 0)) { + if (!CVarGetInteger(CVAR_AUDIO("DisableNaviCallAudio"), 0)) { // clang-format off if (naviCallState == 0x1E) { Audio_PlaySoundGeneral(NA_SE_VO_NAVY_CALL, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); } From 0f81e6d82bae9be04f31fffb083242ee9f02b74c Mon Sep 17 00:00:00 2001 From: xxAtrain223 Date: Wed, 19 Mar 2025 05:11:26 -0500 Subject: [PATCH 17/53] Initialize the RegionTable when loading a file. (#5144) --- soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index 832276e65..6c4177198 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -822,6 +822,7 @@ void LoadFile() { SaveManager::Instance->LoadData("areasSpoiled", areasSpoiled, (uint32_t)0); UpdateAllOrdering(); UpdateAllAreas(); + RegionTable_Init(); } void Teardown() { From 7c227dcc42aa7cd8ea4add6ddf440e02cd316a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Wed, 19 Mar 2025 10:11:47 +0000 Subject: [PATCH 18/53] remove color from UIWidgets::WidgetOptions (#5141) --- soh/soh/SohGui/Menu.cpp | 14 ++++++----- soh/soh/SohGui/MenuTypes.h | 4 +++- soh/soh/SohGui/ResolutionEditor.cpp | 6 ++--- soh/soh/SohGui/SohMenu.cpp | 6 +++-- soh/soh/SohGui/SohMenuEnhancements.cpp | 2 +- soh/soh/SohGui/SohMenuSettings.cpp | 2 +- soh/soh/SohGui/UIWidgets.cpp | 4 ++-- soh/soh/SohGui/UIWidgets.hpp | 33 +++++++++++++++----------- 8 files changed, 41 insertions(+), 30 deletions(-) diff --git a/soh/soh/SohGui/Menu.cpp b/soh/soh/SohGui/Menu.cpp index be4bbdc89..dc7970bc1 100644 --- a/soh/soh/SohGui/Menu.cpp +++ b/soh/soh/SohGui/Menu.cpp @@ -303,21 +303,23 @@ void Menu::MenuDrawItem(WidgetInfo& widget, uint32_t width, UIWidgets::Colors me ImGui::Separator(); } break; case WIDGET_SEPARATOR_TEXT: { - if (widget.options->color != UIWidgets::Colors::NoColor) { - ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color)); + auto options = std::static_pointer_cast(widget.options); + if (options->color != UIWidgets::Colors::NoColor) { + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(options->color)); } ImGui::SeparatorText(widget.name.c_str()); - if (widget.options->color != UIWidgets::Colors::NoColor) { + if (options->color != UIWidgets::Colors::NoColor) { ImGui::PopStyleColor(); } } break; case WIDGET_TEXT: { - if (widget.options->color != UIWidgets::Colors::NoColor) { - ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(widget.options->color)); + auto options = std::static_pointer_cast(widget.options); + if (options->color != UIWidgets::Colors::NoColor) { + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(options->color)); } ImGui::AlignTextToFramePadding(); ImGui::TextWrapped("%s", widget.name.c_str()); - if (widget.options->color != UIWidgets::Colors::NoColor) { + if (options->color != UIWidgets::Colors::NoColor) { ImGui::PopStyleColor(); } } break; diff --git a/soh/soh/SohGui/MenuTypes.h b/soh/soh/SohGui/MenuTypes.h index 7b979a824..28a89820d 100644 --- a/soh/soh/SohGui/MenuTypes.h +++ b/soh/soh/SohGui/MenuTypes.h @@ -73,7 +73,7 @@ typedef enum { using CVarVariant = std::variant; using OptionsVariant = std::variant; // All the info needed for display and search of all widgets in the menu. @@ -145,6 +145,8 @@ struct WidgetInfo { break; case WIDGET_TEXT: case WIDGET_SEPARATOR_TEXT: + options = std::make_shared(std::get(options_)); + break; case WIDGET_SEPARATOR: default: options = std::make_shared(std::get(options_)); diff --git a/soh/soh/SohGui/ResolutionEditor.cpp b/soh/soh/SohGui/ResolutionEditor.cpp index e4f504ccd..c270444fb 100644 --- a/soh/soh/SohGui/ResolutionEditor.cpp +++ b/soh/soh/SohGui/ResolutionEditor.cpp @@ -360,10 +360,10 @@ void RegisterResolutionWidgets() { WIDGET_TEXT) .PreFunc( [](WidgetInfo& info) { info.isHidden = !(!CVarGetInteger(CVAR_LOW_RES_MODE, 0) && IsDroppingFrames()); }) - .Options(WidgetOptions().Color(Colors::Orange)); + .Options(TextOptions().Color(Colors::Orange)); mSohMenu->AddWidget(path, ICON_FA_QUESTION_CIRCLE " \"N64 Mode\" is overriding these settings.", WIDGET_TEXT) .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_LOW_RES_MODE, 0); }) - .Options(WidgetOptions().Color(Colors::LightBlue)); + .Options(TextOptions().Color(Colors::LightBlue)); mSohMenu->AddWidget(path, "Click to disable N64 mode", WIDGET_BUTTON) .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_LOW_RES_MODE, 0); }) .Callback([](WidgetInfo& info) { @@ -391,7 +391,7 @@ void RegisterResolutionWidgets() { } }) .SameLine(true) - .Options(WidgetOptions().Color(Colors::Gray)); + .Options(TextOptions().Color(Colors::Gray)); // Presets mSohMenu->AddWidget(path, "Aspect Ratio", WIDGET_COMBOBOX) .ValuePointer(&item_aspectRatio) diff --git a/soh/soh/SohGui/SohMenu.cpp b/soh/soh/SohGui/SohMenu.cpp index 780aa51f2..26770c986 100644 --- a/soh/soh/SohGui/SohMenu.cpp +++ b/soh/soh/SohGui/SohMenu.cpp @@ -64,10 +64,12 @@ WidgetInfo& SohMenu::AddWidget(WidgetPath& pathInfo, std::string widgetName, Wid case WIDGET_COLOR_24: case WIDGET_COLOR_32: break; - case WIDGET_SEARCH: - case WIDGET_SEPARATOR: case WIDGET_SEPARATOR_TEXT: case WIDGET_TEXT: + widget.options = std::make_shared(); + break; + case WIDGET_SEARCH: + case WIDGET_SEPARATOR: default: widget.options = std::make_shared(); } diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 6f0caff61..1f3066806 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1684,7 +1684,7 @@ void SohMenu::AddMenuEnhancements() { path.column = SECTION_COLUMN_3; AddWidget(path, "Save States", WIDGET_SEPARATOR_TEXT); AddWidget(path, ICON_FA_EXCLAMATION_TRIANGLE " WARNING!!!! " ICON_FA_EXCLAMATION_TRIANGLE, WIDGET_TEXT) - .Options(WidgetOptions().Color(Colors::Orange)); + .Options(TextOptions().Color(Colors::Orange)); AddWidget(path, "These are NOT like emulator states. They do not save your game progress " "and they WILL break across transitions and load zones (like doors). " diff --git a/soh/soh/SohGui/SohMenuSettings.cpp b/soh/soh/SohGui/SohMenuSettings.cpp index cd1ba339d..dc821ecc4 100644 --- a/soh/soh/SohGui/SohMenuSettings.cpp +++ b/soh/soh/SohGui/SohMenuSettings.cpp @@ -132,7 +132,7 @@ void SohMenu::AddMenuSettings() { .CVar(CVAR_SETTING("A11yDisableIdleCam")) .Options(CheckboxOptions().Tooltip("Disables the automatic re-centering of the camera when idle.")); AddWidget(path, "EXPERIMENTAL", WIDGET_SEPARATOR_TEXT) - .Options(WidgetOptions().Color(Colors::Orange)); + .Options(TextOptions().Color(Colors::Orange)); AddWidget(path, "ImGui Menu Scaling", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_SETTING("ImGuiScale")) .Options(ComboboxOptions().ComboMap(imguiScaleOptions).Tooltip("Changes the scaling of the ImGui menu elements.").DefaultIndex(1) diff --git a/soh/soh/SohGui/UIWidgets.cpp b/soh/soh/SohGui/UIWidgets.cpp index 18abb0094..7b6883628 100644 --- a/soh/soh/SohGui/UIWidgets.cpp +++ b/soh/soh/SohGui/UIWidgets.cpp @@ -193,8 +193,8 @@ bool WindowButton(const char* label, const char* cvarName, std::shared_ptrToggleVisibility(); dirty = true; } diff --git a/soh/soh/SohGui/UIWidgets.hpp b/soh/soh/SohGui/UIWidgets.hpp index b9e69292e..4e30de6d1 100644 --- a/soh/soh/SohGui/UIWidgets.hpp +++ b/soh/soh/SohGui/UIWidgets.hpp @@ -122,12 +122,7 @@ namespace UIWidgets { const char* tooltip = ""; bool disabled = false; const char* disabledTooltip = ""; - Colors color = Colors::NoColor; - WidgetOptions& Color(Colors color_) { - color = color = color_; - return *this; - } WidgetOptions& Tooltip(const char* tooltip_) { tooltip = tooltip_; return *this; @@ -142,6 +137,15 @@ namespace UIWidgets { } }; + struct TextOptions : WidgetOptions { + Colors color = Colors::NoColor; + + TextOptions& Color(Colors color_) { + color = color_; + return *this; + } + }; + struct ButtonOptions : WidgetOptions { ImVec2 size = Sizes::Fill; ImVec2 padding = ImVec2(10.0f, 8.0f); @@ -160,7 +164,7 @@ namespace UIWidgets { return *this; } ButtonOptions& Color(Colors color_) { - WidgetOptions::color = color = color_; + color = color_; return *this; } }; @@ -185,7 +189,7 @@ namespace UIWidgets { return *this; } WindowButtonOptions& Color(Colors color_) { - WidgetOptions::color = color = color_; + color = color_; return *this; } WindowButtonOptions& ShowButton(bool showButton_) { @@ -202,7 +206,7 @@ namespace UIWidgets { bool defaultValue = false; // Only applicable to CVarCheckbox ComponentAlignment alignment = ComponentAlignment::Left; LabelPosition labelPosition = LabelPosition::Near; - Colors color = WidgetOptions::color = Colors::LightBlue; + Colors color = Colors::LightBlue; CheckboxOptions& DefaultValue(bool defaultValue_) { defaultValue = defaultValue_; @@ -221,7 +225,7 @@ namespace UIWidgets { return *this; } CheckboxOptions& Color(Colors color_) { - WidgetOptions::color = color = color_; + color = color_; return *this; } CheckboxOptions& DisabledTooltip(const char* disabledTooltip_) { @@ -259,7 +263,7 @@ namespace UIWidgets { return *this; } ComboboxOptions& Color(Colors color_) { - WidgetOptions::color = color = color_; + color = color_; return *this; } }; @@ -315,7 +319,7 @@ namespace UIWidgets { return *this; } IntSliderOptions& Color(Colors color_) { - WidgetOptions::color = color = color_; + color = color_; return *this; } IntSliderOptions& Size(ImVec2 size_) { @@ -387,7 +391,7 @@ namespace UIWidgets { return *this; } FloatSliderOptions& Color(Colors color_) { - WidgetOptions::color = color = color_; + color = color_; return *this; } FloatSliderOptions& Size(ImVec2 size_) { @@ -402,6 +406,7 @@ namespace UIWidgets { struct RadioButtonsOptions : WidgetOptions { std::unordered_map buttonMap; + Colors color = Colors::LightBlue; RadioButtonsOptions& ButtonMap(std::unordered_map buttonMap_) { buttonMap = buttonMap_; @@ -412,7 +417,7 @@ namespace UIWidgets { return *this; } RadioButtonsOptions& Color(Colors color_) { - WidgetOptions::color = color = color_; + color = color_; return *this; } }; @@ -433,7 +438,7 @@ namespace UIWidgets { return *this; } InputOptions& Color(Colors color_) { - WidgetOptions::color = color = color_; + color = color_; return *this; } InputOptions& Size(ImVec2 size_) { From 9795f696c8392696614859a528d74ca3a73574fb Mon Sep 17 00:00:00 2001 From: Archez Date: Wed, 19 Mar 2025 06:14:43 -0400 Subject: [PATCH 19/53] Hook unregister fixes and hook debugger tweaks (#5139) * clang-format before * Hook unregister fixes and hook debugger tweaks --- .../Enhancements/debugger/hookDebugger.cpp | 70 ++--- .../game-interactor/GameInteractor.h | 256 +++++++++++++----- .../game-interactor/GameInteractor_Hooks.cpp | 3 + 3 files changed, 217 insertions(+), 112 deletions(-) diff --git a/soh/soh/Enhancements/debugger/hookDebugger.cpp b/soh/soh/Enhancements/debugger/hookDebugger.cpp index 782cf4f7a..a9d6ca891 100644 --- a/soh/soh/Enhancements/debugger/hookDebugger.cpp +++ b/soh/soh/Enhancements/debugger/hookDebugger.cpp @@ -4,28 +4,29 @@ #include #include -static std::unordered_map*> hookData; +static std::map*> hookData; const ImVec4 grey = ImVec4(0.75, 0.75, 0.75, 1); const ImVec4 yellow = ImVec4(1, 1, 0, 1); const ImVec4 red = ImVec4(1, 0, 0, 1); void DrawHookRegisteringInfos(const char* hookName) { - if ((*hookData[hookName]).size() == 0) { + size_t numHooks = (*hookData[hookName]).size(); + + if (numHooks == 0) { ImGui::TextColored(grey, "No hooks found"); return; } - if (ImGui::BeginTable( - ("Table##" + std::string(hookName)).c_str(), - 4, - ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit - )) { - ImGui::TableSetupColumn("Id"); - ImGui::TableSetupColumn("Type"); - ImGui::TableSetupColumn("Registration Info"); - //ImGui::TableSetupColumn("Stub"); - ImGui::TableSetupColumn("Number of Calls"); + ImGui::Text("Total Registered: %d", numHooks); + + if (ImGui::BeginTable(("Table##" + std::string(hookName)).c_str(), 4, + ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | + ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Registration Info", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("# Calls", ImGuiTableColumnFlags_WidthFixed); ImGui::TableHeadersRow(); for (auto& [id, hookInfo] : (*hookData[hookName])) { ImGui::TableNextRow(); @@ -39,7 +40,7 @@ void DrawHookRegisteringInfos(const char* hookName) { ImGui::Text("Normal"); break; case HOOK_TYPE_ID: - ImGui::Text("Id"); + ImGui::Text("ID"); break; case HOOK_TYPE_PTR: ImGui::Text("Ptr"); @@ -54,27 +55,19 @@ void DrawHookRegisteringInfos(const char* hookName) { ImGui::TableNextColumn(); if (hookInfo.registering.valid) { - ImGui::Text("%s(%d:%d) %s", hookInfo.registering.file, hookInfo.registering.line, hookInfo.registering.column, hookInfo.registering.function); + // Replace the space after the return type of the parent function with a non-breaking space + std::string parentFunction = std::string(hookInfo.registering.function); + size_t pos = parentFunction.find_first_of(" "); + if (pos != std::string::npos) { + parentFunction.replace(pos, 1, "\u00A0"); + } + // Non breaking space to keep the arrow with the parent function + ImGui::TextWrapped("%s(%d:%d) <-\u00A0%s", hookInfo.registering.file, hookInfo.registering.line, + hookInfo.registering.column, parentFunction.c_str()); } else { - ImGui::TextColored(yellow, "[Unavaliable]"); + ImGui::TextColored(yellow, "[Unavailable]"); } - //TODO: not currently possible - /* - ImGui::TableNextColumn(); - - ImGui::BeginDisabled(); - - bool stubButtonPressed = ImGui::Button(("Stub##" + std::to_string(id)).c_str()); - UIWidgets::SetLastItemHoverText("Stub this hook.\nThis is not possible to automatically undo."); - - if (stubButtonPressed) { - //stub - } - - ImGui::EndDisabled(); - */ - ImGui::TableNextColumn(); ImGui::Text("%d", hookInfo.calls); } @@ -84,12 +77,9 @@ void DrawHookRegisteringInfos(const char* hookName) { void HookDebuggerWindow::DrawElement() { #ifndef __cpp_lib_source_location - ImGui::TextColored( - yellow, - "Some features of the Hook Debugger are unavaliable because SoH was compiled " - "without \"\" support " - "(\"__cpp_lib_source_location\" not defined in \"\")." - ); + ImGui::TextColored(yellow, "Some features of the Hook Debugger are unavailable because SoH was compiled " + "without \"\" support " + "(\"__cpp_lib_source_location\" not defined in \"\")."); #endif for (auto& [hookName, _] : hookData) { @@ -101,9 +91,9 @@ void HookDebuggerWindow::DrawElement() { } void HookDebuggerWindow::InitElement() { - #define DEFINE_HOOK(name, _) hookData.insert({#name, GameInteractor::Instance->GetHookData()}); +#define DEFINE_HOOK(name, _) hookData.insert({ #name, GameInteractor::Instance->GetHookData() }); - #include "../game-interactor/GameInteractor_HookTable.h" +#include "../game-interactor/GameInteractor_HookTable.h" - #undef DEFINE_HOOK +#undef DEFINE_HOOK } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 8c6be086f..4d8e6dcb4 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -92,19 +92,20 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state); } #endif - #ifdef __cplusplus #include #include +#include #include #include #include #include + #include #ifdef __cpp_lib_source_location #include #else -#pragma message("Compiling without support, the Hook Debugger will not be avaliable") +#pragma message("Compiling without support, the Hook Debugger will not be available") #endif typedef uint32_t HOOK_ID; @@ -124,24 +125,31 @@ struct HookRegisteringInfo { const char* function; HookType type; - HookRegisteringInfo() : valid(false), file("unknown file"), line(0), column(0), function("unknown function"), type(HOOK_TYPE_NORMAL) {} + HookRegisteringInfo() + : valid(false), file("unknown file"), line(0), column(0), function("unknown function"), type(HOOK_TYPE_NORMAL) { + } - HookRegisteringInfo(const char* _file, std::uint_least32_t _line, std::uint_least32_t _column, const char* _function, HookType _type) : - valid(true), file(_file), line(_line), column(_column), function(_function), type(_type) {} + HookRegisteringInfo(const char* _file, std::uint_least32_t _line, std::uint_least32_t _column, + const char* _function, HookType _type) + : valid(true), file(_file), line(_line), column(_column), function(_function), type(_type) { + // Trim off user parent directories + const char* trimmed = strstr(_file, "soh/soh/"); + if (trimmed != nullptr) { + file = trimmed; + } + } }; struct HookInfo { uint32_t calls; HookRegisteringInfo registering; - - HookInfo() : calls(0), registering(HookRegisteringInfo{}) {} - HookInfo(HookRegisteringInfo _registering) : calls(0), registering(_registering) {} }; #ifdef __cpp_lib_source_location -#define GET_CURRENT_REGISTERING_INFO(type) HookRegisteringInfo{location.file_name(), location.line(), location.column(), location.function_name(), type} +#define GET_CURRENT_REGISTERING_INFO(type) \ + (HookRegisteringInfo{ location.file_name(), location.line(), location.column(), location.function_name(), type }) #else -#define GET_CURRENT_REGISTERING_INFO(type) HookRegisteringInfo{} +#define GET_CURRENT_REGISTERING_INFO(type) (HookRegisteringInfo{}) #endif #define REGISTER_VB_SHOULD(flag, body) \ @@ -171,23 +179,23 @@ struct HookInfo { hookId = GameInteractor::Instance->RegisterGameHookForID(id, body); \ } \ } -#define COND_VB_SHOULD(id, condition, body) \ - { \ - static HOOK_ID hookId = 0; \ +#define COND_VB_SHOULD(id, condition, body) \ + { \ + static HOOK_ID hookId = 0; \ GameInteractor::Instance->UnregisterGameHookForID(hookId); \ - hookId = 0; \ - if (condition) { \ - hookId = REGISTER_VB_SHOULD(id, body); \ - } \ + hookId = 0; \ + if (condition) { \ + hookId = REGISTER_VB_SHOULD(id, body); \ + } \ } class GameInteractor { -public: + public: static GameInteractor* Instance; // Game State class State { - public: + public: static bool NoUIActive; static GILinkSize LinkSize; static bool InvisibleLinkActive; @@ -219,14 +227,15 @@ public: // Game Hooks HOOK_ID nextHookId = 1; + template struct RegisteredGameHooks { inline static std::unordered_map functions; inline static std::unordered_map> functionsForID; inline static std::unordered_map> functionsForPtr; inline static std::unordered_map> functionsForFilter; - //Used for the hook debugger - inline static std::unordered_map hookData; + // Used for the hook debugger + inline static std::map hookData; }; template struct HooksToUnregister { @@ -236,39 +245,43 @@ public: inline static std::vector hooksForFilter; }; - template std::unordered_map* GetHookData() { + template std::map* GetHookData() { return &RegisteredGameHooks::hookData; } // General Hooks - template HOOK_ID RegisterGameHook( - typename H::fn h + template #ifdef __cpp_lib_source_location - , const std::source_location location = std::source_location::current() + HOOK_ID RegisterGameHook(typename H::fn h, const std::source_location location = std::source_location::current()) { +#else + HOOK_ID RegisterGameHook(typename H::fn h) { #endif - ) { - // Ensure hook id is unique and not 0, which is reserved for invalid hooks - if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1; + if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) + this->nextHookId = 1; while (RegisteredGameHooks::functions.find(this->nextHookId) != RegisteredGameHooks::functions.end()) { this->nextHookId++; } RegisteredGameHooks::functions[this->nextHookId] = h; - RegisteredGameHooks::hookData[this->nextHookId] = HookInfo{GET_CURRENT_REGISTERING_INFO(HOOK_TYPE_NORMAL)}; + RegisteredGameHooks::hookData[this->nextHookId] = + HookInfo{ 0, GET_CURRENT_REGISTERING_INFO(HOOK_TYPE_NORMAL) }; return this->nextHookId++; } template void UnregisterGameHook(HOOK_ID hookId) { - if (hookId == 0) return; + if (hookId == 0) + return; HooksToUnregister::hooks.push_back(hookId); } template void ExecuteHooks(Args&&... args) { + // Remove pending hooks for this type for (auto& hookId : HooksToUnregister::hooks) { RegisteredGameHooks::functions.erase(hookId); RegisteredGameHooks::hookData.erase(hookId); } HooksToUnregister::hooks.clear(); + // Execute hooks for (auto& hook : RegisteredGameHooks::functions) { hook.second(std::forward(args)...); RegisteredGameHooks::hookData[hook.first].calls += 1; @@ -276,39 +289,60 @@ public: } // ID based Hooks - template HOOK_ID RegisterGameHookForID( - int32_t id, typename H::fn h + template #ifdef __cpp_lib_source_location - , const std::source_location location = std::source_location::current() + HOOK_ID RegisterGameHookForID(int32_t id, typename H::fn h, + std::source_location location = std::source_location::current()) { +#else + HOOK_ID RegisterGameHookForID(int32_t id, typename H::fn h) { #endif - ) { - if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1; - while (RegisteredGameHooks::functionsForID[id].find(this->nextHookId) != RegisteredGameHooks::functionsForID[id].end()) { + if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) + this->nextHookId = 1; + while (RegisteredGameHooks::functionsForID[id].find(this->nextHookId) != + RegisteredGameHooks::functionsForID[id].end()) { this->nextHookId++; } RegisteredGameHooks::functionsForID[id][this->nextHookId] = h; - RegisteredGameHooks::hookData[this->nextHookId] = HookInfo{GET_CURRENT_REGISTERING_INFO(HOOK_TYPE_ID)}; + RegisteredGameHooks::hookData[this->nextHookId] = HookInfo{ 0, GET_CURRENT_REGISTERING_INFO(HOOK_TYPE_ID) }; return this->nextHookId++; } template void UnregisterGameHookForID(HOOK_ID hookId) { - if (hookId == 0) return; + if (hookId == 0) + return; HooksToUnregister::hooksForID.push_back(hookId); } template void ExecuteHooksForID(int32_t id, Args&&... args) { - for (auto& hookId : HooksToUnregister::hooksForID) { - for (auto it = RegisteredGameHooks::functionsForID[id].begin(); it != RegisteredGameHooks::functionsForID[id].end(); ) { - if (it->first == hookId) { + // Remove pending hooks for this type + for (auto hookIdIt = HooksToUnregister::hooksForID.begin(); + hookIdIt != HooksToUnregister::hooksForID.end();) { + bool remove = false; + + if (RegisteredGameHooks::functionsForID[id].size() == 0) { + break; + } + + for (auto it = RegisteredGameHooks::functionsForID[id].begin(); + it != RegisteredGameHooks::functionsForID[id].end();) { + if (it->first == *hookIdIt) { it = RegisteredGameHooks::functionsForID[id].erase(it); - HooksToUnregister::hooksForID.erase(std::remove(HooksToUnregister::hooksForID.begin(), HooksToUnregister::hooksForID.end(), hookId), HooksToUnregister::hooksForID.end()); - RegisteredGameHooks::hookData.erase(hookId); + RegisteredGameHooks::hookData.erase(*hookIdIt); + remove = true; + break; } else { ++it; } } + + if (remove) { + hookIdIt = HooksToUnregister::hooksForID.erase(hookIdIt); + } else { + ++hookIdIt; + } } + // Execute hooks for (auto& hook : RegisteredGameHooks::functionsForID[id]) { hook.second(std::forward(args)...); RegisteredGameHooks::hookData[hook.first].calls += 1; @@ -316,39 +350,60 @@ public: } // PTR based Hooks - template HOOK_ID RegisterGameHookForPtr( - uintptr_t ptr, typename H::fn h + template #ifdef __cpp_lib_source_location - , const std::source_location location = std::source_location::current() + HOOK_ID RegisterGameHookForPtr(uintptr_t ptr, typename H::fn h, + const std::source_location location = std::source_location::current()) { +#else + HOOK_ID RegisterGameHookForPtr(uintptr_t ptr, typename H::fn h) { #endif - ) { - if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1; - while (RegisteredGameHooks::functionsForPtr[ptr].find(this->nextHookId) != RegisteredGameHooks::functionsForPtr[ptr].end()) { + if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) + this->nextHookId = 1; + while (RegisteredGameHooks::functionsForPtr[ptr].find(this->nextHookId) != + RegisteredGameHooks::functionsForPtr[ptr].end()) { this->nextHookId++; } RegisteredGameHooks::functionsForPtr[ptr][this->nextHookId] = h; - RegisteredGameHooks::hookData[this->nextHookId] = HookInfo{GET_CURRENT_REGISTERING_INFO(HOOK_TYPE_PTR)}; + RegisteredGameHooks::hookData[this->nextHookId] = HookInfo{ 0, GET_CURRENT_REGISTERING_INFO(HOOK_TYPE_PTR) }; return this->nextHookId++; } template void UnregisterGameHookForPtr(HOOK_ID hookId) { - if (hookId == 0) return; + if (hookId == 0) + return; HooksToUnregister::hooksForPtr.push_back(hookId); } template void ExecuteHooksForPtr(uintptr_t ptr, Args&&... args) { - for (auto& hookId : HooksToUnregister::hooksForPtr) { - for (auto it = RegisteredGameHooks::functionsForPtr[ptr].begin(); it != RegisteredGameHooks::functionsForPtr[ptr].end(); ) { - if (it->first == hookId) { + // Remove pending hooks for this type + for (auto hookIdIt = HooksToUnregister::hooksForPtr.begin(); + hookIdIt != HooksToUnregister::hooksForPtr.end();) { + bool remove = false; + + if (RegisteredGameHooks::functionsForPtr[ptr].size() == 0) { + break; + } + + for (auto it = RegisteredGameHooks::functionsForPtr[ptr].begin(); + it != RegisteredGameHooks::functionsForPtr[ptr].end();) { + if (it->first == *hookIdIt) { it = RegisteredGameHooks::functionsForPtr[ptr].erase(it); - HooksToUnregister::hooksForPtr.erase(std::remove(HooksToUnregister::hooksForPtr.begin(), HooksToUnregister::hooksForPtr.end(), hookId), HooksToUnregister::hooksForPtr.end()); - RegisteredGameHooks::hookData.erase(hookId); + RegisteredGameHooks::hookData.erase(*hookIdIt); + remove = true; + break; } else { ++it; } } + + if (remove) { + hookIdIt = HooksToUnregister::hooksForPtr.erase(hookIdIt); + } else { + ++hookIdIt; + } } + // Execute hooks for (auto& hook : RegisteredGameHooks::functionsForPtr[ptr]) { hook.second(std::forward(args)...); RegisteredGameHooks::hookData[hook.first].calls += 1; @@ -356,33 +411,40 @@ public: } // Filter based Hooks - template HOOK_ID RegisterGameHookForFilter( - typename H::filter f, typename H::fn h + template #ifdef __cpp_lib_source_location - , const std::source_location location = std::source_location::current() + HOOK_ID RegisterGameHookForFilter(typename H::filter f, typename H::fn h, + const std::source_location location = std::source_location::current()) { +#else + HOOK_ID RegisterGameHookForFilter(typename H::filter f, typename H::fn h) { #endif - ) { - if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1; - while (RegisteredGameHooks::functionsForFilter.find(this->nextHookId) != RegisteredGameHooks::functionsForFilter.end()) { + if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) + this->nextHookId = 1; + while (RegisteredGameHooks::functionsForFilter.find(this->nextHookId) != + RegisteredGameHooks::functionsForFilter.end()) { this->nextHookId++; } RegisteredGameHooks::functionsForFilter[this->nextHookId] = std::make_pair(f, h); - RegisteredGameHooks::hookData[this->nextHookId] = HookInfo{GET_CURRENT_REGISTERING_INFO(HOOK_TYPE_FILTER)}; + RegisteredGameHooks::hookData[this->nextHookId] = + HookInfo{ 0, GET_CURRENT_REGISTERING_INFO(HOOK_TYPE_FILTER) }; return this->nextHookId++; } template void UnregisterGameHookForFilter(HOOK_ID hookId) { - if (hookId == 0) return; + if (hookId == 0) + return; HooksToUnregister::hooksForFilter.push_back(hookId); } template void ExecuteHooksForFilter(Args&&... args) { + // Remove pending hooks for this type for (auto& hookId : HooksToUnregister::hooksForFilter) { RegisteredGameHooks::functionsForFilter.erase(hookId); RegisteredGameHooks::hookData.erase(hookId); } HooksToUnregister::hooksForFilter.clear(); + // Execute hooks for (auto& hook : RegisteredGameHooks::functionsForFilter) { if (hook.second.first(std::forward(args)...)) { hook.second.second(std::forward(args)...); @@ -391,8 +453,62 @@ public: } } + template void ProcessUnregisteredHooks() { + // Normal + for (auto& hookId : HooksToUnregister::hooks) { + RegisteredGameHooks::functions.erase(hookId); + RegisteredGameHooks::hookData.erase(hookId); + } + HooksToUnregister::hooks.clear(); + + // ID + for (auto& hookId : HooksToUnregister::hooksForID) { + for (auto& idGroup : RegisteredGameHooks::functionsForID) { + for (auto it = idGroup.second.begin(); it != idGroup.second.end();) { + if (it->first == hookId) { + it = idGroup.second.erase(it); + RegisteredGameHooks::hookData.erase(hookId); + } else { + ++it; + } + } + } + } + HooksToUnregister::hooksForID.clear(); + + // Ptr + for (auto& hookId : HooksToUnregister::hooksForPtr) { + for (auto& ptrGroup : RegisteredGameHooks::functionsForPtr) { + for (auto it = ptrGroup.second.begin(); it != ptrGroup.second.end();) { + if (it->first == hookId) { + it = ptrGroup.second.erase(it); + RegisteredGameHooks::hookData.erase(hookId); + } else { + ++it; + } + } + } + } + HooksToUnregister::hooksForPtr.clear(); + + // Filter + for (auto& hookId : HooksToUnregister::hooksForFilter) { + RegisteredGameHooks::functionsForFilter.erase(hookId); + RegisteredGameHooks::hookData.erase(hookId); + } + HooksToUnregister::hooksForFilter.clear(); + } + + void RemoveAllQueuedHooks() { +#define DEFINE_HOOK(name, _) ProcessUnregisteredHooks(); + +#include "GameInteractor_HookTable.h" + +#undef DEFINE_HOOK + } + class HookFilter { - public: + public: static auto ActorNotPlayer(Actor* actor) { return actor->id != ACTOR_PLAYER; } @@ -401,15 +517,11 @@ public: return actor->id != ACTOR_PLAYER; } static auto ActorMatchIdAndParams(int16_t id, int16_t params) { - return [id, params](Actor* actor) { - return actor->id == id && actor->params == params; - }; + return [id, params](Actor* actor) { return actor->id == id && actor->params == params; }; } // For use with Should hooks static auto SActorMatchIdAndParams(int16_t id, int16_t params) { - return [id, params](Actor* actor, bool* result) { - return actor->id == id && actor->params == params; - }; + return [id, params](Actor* actor, bool* result) { return actor->id == id && actor->params == params; }; } }; @@ -430,7 +542,7 @@ public: static bool CanAddOrTakeAmmo(int16_t amount, int16_t item); class RawAction { - public: + public: static void SetSceneFlag(int16_t sceneNum, int16_t flagType, int16_t flag); static void UnsetSceneFlag(int16_t sceneNum, int16_t flagType, int16_t flag); static bool CheckFlag(int16_t flagType, int16_t flag); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index b41030cfa..9c861062b 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -19,6 +19,9 @@ void GameInteractor_ExecuteOnExitGame(int32_t fileNum) { } void GameInteractor_ExecuteOnGameStateMainStart() { + // Cleanup all hooks at the start of each frame + GameInteractor::Instance->RemoveAllQueuedHooks(); + GameInteractor::Instance->ExecuteHooks(); } From 37a2730df241da847279a6fa61e9e51fec10b387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Wed, 19 Mar 2025 10:15:43 +0000 Subject: [PATCH 20/53] remove unused 3drando custom message code (#5138) --- .../randomizer/3drando/custom_messages.cpp | 251 +----------------- .../randomizer/3drando/custom_messages.hpp | 39 --- .../Enhancements/randomizer/3drando/fill.cpp | 2 - .../randomizer/3drando/playthrough.cpp | 2 - .../Enhancements/randomizer/randomizer.cpp | 6 +- 5 files changed, 4 insertions(+), 296 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/3drando/custom_messages.cpp b/soh/soh/Enhancements/randomizer/3drando/custom_messages.cpp index 179987dcc..fcab083b4 100644 --- a/soh/soh/Enhancements/randomizer/3drando/custom_messages.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/custom_messages.cpp @@ -2,258 +2,9 @@ #include "../../custom-message/CustomMessageManager.h" #include "z64item.h" -#include -#include -#include - namespace CustomMessages { - using namespace std::literals::string_literals; -class MessageEntryComp { -public: - bool operator()(const MessageEntry& lhs, const MessageEntry& rhs) const { - return lhs.id < rhs.id; - } -}; - -constexpr std::array EnglishDungeonNames = { - "Deku Tree", - "Dodongo's Cavern", - "Jabu Jabu's Belly", - "Forest Temple", - "Fire Temple", - "Water Temple", - "Spirit Temple", - "Shadow Temple", - "Bottom of the Well", - "Ice Cavern", - "Ganon's Tower", - "Gerudo Training Ground", - "Gerudo Fortress", - "Ganon's Castle", -}; - -constexpr std::array FrenchDungeonNames = { - "Vénérable Arbre Mojo", - "Caverne Dodongo", - "Ventre de Jabu-Jabu", - "Temple de la Forêt", - "Temple du Feu", - "Temple de l'Eau", - "Temple de l'Esprit", - "Temple de l'Ombre", - "Puits", - "Caverne Polaire", - "Tour de Ganon", - "Gymnase Gerudo", - "Repaire des Voleurs", - "Château de Ganon", -}; - -constexpr std::array FrenchDungeonArticles = { - "du ", - "de la ", - "du ", - "du ", - "du ", - "du ", - "du ", - "du ", - "du ", - "de la ", - "", - "du ", - "de la ", - "du ", -}; - -constexpr std::array SpanishDungeonNames = { - "Gran Árbol Deku", - "Cueva de los Dodongos", - "Tripa de Jabu-Jabu", - "Templo del Bosque", - "Templo del Fuego", - "Templo del Agua", - "Templo del Espíritu", - "Templo de las Sombras", - "Fondo del pozo", - "Caverna de hielo", - "Torre de Ganon", - "Centro de Instrucción Gerudo", - "Fortaleza Gerudo", - "Castillo de Ganon", -}; - -constexpr std::array SpanishDungeonArticles = { - "del", - "de la", - "de la", - "del", - "del", - "del", - "del", - "del", - "del", - "de la", - "de la", - "del", - "de la", - "del", -}; - -constexpr std::array DungeonColors = { - QM_GREEN, - QM_RED, - QM_BLUE, - QM_GREEN, - QM_RED, - QM_BLUE, - QM_YELLOW, - QM_PINK, - QM_PINK, - QM_LBLUE, - QM_BLACK, - QM_YELLOW, - QM_YELLOW, - QM_RED, -}; - - std::set messageEntries; - std::stringstream messageData; - - //textBoxType and textBoxPosition are defined here: https://wiki.cloudmodding.com/oot/Text_Format#Message_Id - void CreateMessage(uint32_t textId, uint32_t unk_04, uint32_t textBoxType, uint32_t textBoxPosition, - std::string englishText, std::string frenchText, std::string spanishText) { - MessageEntry newEntry = { textId, unk_04, textBoxType, textBoxPosition, { 0 } }; - - while ((englishText.size() % 4) != 0) { - englishText += "\0"s; - } - messageData.seekg(0, messageData.end); - newEntry.info[ENGLISH_U].offset = (char*)((int)messageData.tellg()); - newEntry.info[ENGLISH_U].length = englishText.size(); - messageData << englishText; - - while ((frenchText.size() % 4) != 0) { - frenchText += "\0"s; - } - messageData.seekg(0, messageData.end); - newEntry.info[FRENCH_U].offset = (char*)((int)messageData.tellg()); - newEntry.info[FRENCH_U].length = frenchText.size(); - messageData << frenchText; - - while ((spanishText.size() % 4) != 0) { - spanishText += "\0"s; - } - messageData.seekg(0, messageData.end); - newEntry.info[SPANISH_U].offset = (char*)((int)messageData.tellg()); - newEntry.info[SPANISH_U].length = spanishText.size(); - messageData << spanishText; - - messageEntries.insert(newEntry); - } - - void CreateMessageFromTextObject(uint32_t textId, uint32_t unk_04, uint32_t textBoxType, uint32_t textBoxPosition, const Text& text) { - CreateMessage(textId, unk_04, textBoxType, textBoxPosition, text.GetEnglish(), text.GetFrench(), text.GetSpanish()); - } - - Text AddColorsAndFormat(Text text, const std::vector& colors /*= {}*/) { - - //for each language - for (std::string* textStr : {&text.english, &text.french, &text.spanish}) { - - //insert playername - size_t atSymbol = textStr->find('@'); - while (atSymbol != std::string::npos) { - textStr->replace(atSymbol, 1, PLAYER_NAME()); - atSymbol = textStr->find('@'); - } - //insert newlines either manually or when encountering a '&' - constexpr size_t lineLength = 44; - size_t lastNewline = 0; - while (lastNewline + lineLength < textStr->length()) { - size_t carrot = textStr->find('^', lastNewline); - size_t ampersand = textStr->find('&', lastNewline); - size_t lastSpace = textStr->rfind(' ', lastNewline + lineLength); - size_t lastPeriod = textStr->rfind('.', lastNewline + lineLength); - //replace '&' first if it's within the newline range - if (ampersand < lastNewline + lineLength) { - textStr->replace(ampersand, 1, NEWLINE()); - lastNewline = ampersand + NEWLINE().length(); - //or move the lastNewline cursor to the next line if a '^' is encountered - } else if (carrot < lastNewline + lineLength) { - lastNewline = carrot + 1; - //some lines need to be split but don't have spaces, look for periods instead - } else if (lastSpace == std::string::npos) { - textStr->replace(lastPeriod, 1, "."+NEWLINE()); - lastNewline = lastPeriod + NEWLINE().length() + 1; - } else { - textStr->replace(lastSpace, 1, NEWLINE()); - lastNewline = lastSpace + NEWLINE().length(); - } - } - //clean up any remaining '&' characters - size_t ampersand = textStr->find('&'); - while (ampersand != std::string::npos) { - textStr->replace(ampersand, 1, NEWLINE()); - ampersand = textStr->find('&'); - } - - //insert box break - size_t carrotSymbol = textStr->find('^'); - while (carrotSymbol != std::string::npos) { - textStr->replace(carrotSymbol, 1, INSTANT_TEXT_OFF()+WAIT_FOR_INPUT()+INSTANT_TEXT_ON()); - carrotSymbol = textStr->find('^'); - } - - //If there's a two-way choice and only 1 newline before it in the same text box, add another one - size_t choice = textStr->find(TWO_WAY_CHOICE()); - if (choice != std::string::npos) { - size_t newLinesCount = 0; - size_t lastBoxBreak = textStr->rfind(WAIT_FOR_INPUT(), choice); - lastNewline = choice; - - if (lastBoxBreak == std::string::npos) { - lastBoxBreak = 0; - } - - while ((lastNewline != std::string::npos)) { - lastNewline = textStr->rfind(NEWLINE(), lastNewline - 1); - if (lastNewline != std::string::npos && lastNewline > lastBoxBreak) { - newLinesCount++; - } else { - break; - } - } - - if (newLinesCount <= 1) { - textStr->replace(choice, TWO_WAY_CHOICE().length(), NEWLINE()+TWO_WAY_CHOICE()); - } - } - - //add colors - for (auto color : colors) { - size_t firstHashtag = textStr->find('#'); - if (firstHashtag != std::string::npos) { - textStr->replace(firstHashtag, 1, COLOR(color)); - size_t secondHashtag = textStr->find('#'); - if (secondHashtag == std::string::npos) { - //CitraPrint("ERROR: Couldn't find second '#' in " + (*textStr)); - } else { - textStr->replace(secondHashtag, 1, COLOR(QM_WHITE)); - } - } - } - } - return Text{"","",""}+UNSKIPPABLE()+INSTANT_TEXT_ON()+text+INSTANT_TEXT_OFF()+MESSAGE_END(); - } - - void ClearMessages() { - messageEntries.clear(); - messageData.str(""); - } - std::string MESSAGE_END() { return "\x7F\x00"s; } std::string WAIT_FOR_INPUT() { return "\x7F\x01"s; } std::string HORIZONTAL_SPACE(uint8_t x) { @@ -280,7 +31,7 @@ constexpr std::array DungeonColors = { std::string SET_SPEED(uint8_t x) { return "\x7F\x10"s + char(x); } - std::string SKULLTULAS_DESTROYED() { return "\x7F\x15"s; } //RANDOTODO just refernce the versions in CustomMessage + std::string SKULLTULAS_DESTROYED() { return "\x7F\x15"s; } std::string CURRENT_TIME() { return "\x7F\x17"s; } std::string UNSKIPPABLE() { return "\x7F\x19"s; } std::string TWO_WAY_CHOICE() { return "\x1B"s; } diff --git a/soh/soh/Enhancements/randomizer/3drando/custom_messages.hpp b/soh/soh/Enhancements/randomizer/3drando/custom_messages.hpp index b9d2a08c6..4daacf51a 100644 --- a/soh/soh/Enhancements/randomizer/3drando/custom_messages.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/custom_messages.hpp @@ -1,50 +1,11 @@ #pragma once #include -#include -#include #include #include "text.hpp" namespace CustomMessages { -typedef struct { - // In the true file format, offset is the offset into the QM file. - // In randomizer, offset will be a pointer to the text in the game's address space. - // Since these pointers will be much larger as u32 than the original script's offsets, - // We will be able to distinguish between original and custom text using their numerical value. - const char* offset; - uint32_t length; -} MessageLanguageInfo; - -typedef enum { - /* 0x00 */ JAPANESE_J, - /* 0x01 */ ENGLISH_U, - /* 0x02 */ ENGLISH_E, - /* 0x03 */ GERMAN_E, - /* 0x04 */ FRENCH_E, - /* 0x05 */ FRENCH_U, - /* 0x06 */ SPANISH_E, - /* 0x07 */ SPANISH_U, - /* 0x08 */ ITALIAN_E, - /* 0x09 */ DUTCH_E, -} MessageLanguage; - -typedef struct { - uint32_t id; - uint32_t unk_04; - uint32_t unk_08; - uint32_t unk_0C; - MessageLanguageInfo info[10]; -} MessageEntry; // size = 0x60 - - void CreateMessage(uint32_t textId, uint32_t unk_04, uint32_t textBoxType, uint32_t textBoxPosition, - std::string englishText, std::string frenchText, std::string spanishText); - void CreateMessageFromTextObject(uint32_t textId, uint32_t unk_04, uint32_t textBoxType, uint32_t textBoxPosition, const Text& text); - - Text AddColorsAndFormat(Text text, const std::vector& colors = {}); - void ClearMessages(); - std::string MESSAGE_END(); std::string WAIT_FOR_INPUT(); std::string HORIZONTAL_SPACE(uint8_t x); diff --git a/soh/soh/Enhancements/randomizer/3drando/fill.cpp b/soh/soh/Enhancements/randomizer/3drando/fill.cpp index 351c57fd2..b0548f5ee 100644 --- a/soh/soh/Enhancements/randomizer/3drando/fill.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/fill.cpp @@ -1,6 +1,5 @@ #include "fill.hpp" -#include "custom_messages.hpp" #include "../dungeon.h" #include "../context.h" #include "item_pool.hpp" @@ -19,7 +18,6 @@ #include #include -using namespace CustomMessages; using namespace Rando; diff --git a/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp b/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp index 144dee80a..d8fd302c1 100644 --- a/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/playthrough.cpp @@ -2,7 +2,6 @@ #include #include -#include "custom_messages.hpp" #include "fill.hpp" #include "../location_access.h" #include "random.hpp" @@ -23,7 +22,6 @@ int Playthrough_Init(uint32_t seed, std::set excludedLocations, auto ctx = Rando::Context::GetInstance(); ctx->overrides.clear(); - CustomMessages::ClearMessages(); ctx->ItemReset(); ctx->HintReset(); ctx->GetLogic()->Reset(); diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 1cc7363e8..95710078c 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -2749,10 +2749,10 @@ CustomMessage Randomizer::GetMapGetItemMessageWithHint(GetItemEntry itemEntry) { } template -void CreateGetItemMessages(const std::array* messageEntries) { +void CreateGetItemMessages(const std::array& messageEntries) { CustomMessageManager* customMessageManager = CustomMessageManager::Instance; customMessageManager->AddCustomMessageTable(Randomizer::getItemMessageTableID); - for (const GetItemMessage& messageEntry : *messageEntries) { + for (const GetItemMessage& messageEntry : messageEntries) { customMessageManager->CreateGetItemMessage( Randomizer::getItemMessageTableID, messageEntry.giid, messageEntry.iid, CustomMessage(messageEntry.english, messageEntry.german, messageEntry.french, TEXTBOX_TYPE_BLUE, @@ -3816,7 +3816,7 @@ void Randomizer::CreateCustomMessages() { "You found the %rDeku Nut Bag%w!&You can now hold deku nuts!", "Vous avez trouvé le %rSac de Noix& Mojo%w!&Vous pouvez maintenant porter des&Noix Mojo!"), }}; - CreateGetItemMessages(&getItemMessages); + CreateGetItemMessages(getItemMessages); CreateRupeeMessages(); CreateTriforcePieceMessages(); CreateNaviRandoMessages(); From dd7dee8f07095d537a1b3717b8d3db688616636c Mon Sep 17 00:00:00 2001 From: Pepper0ni <93387759+Pepper0ni@users.noreply.github.com> Date: Wed, 19 Mar 2025 10:15:55 +0000 Subject: [PATCH 21/53] Fix finite shields getting thier effect applied in SCL (#5130) --- soh/soh/Enhancements/randomizer/logic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/randomizer/logic.cpp b/soh/soh/Enhancements/randomizer/logic.cpp index 408ded72e..9f127c32f 100644 --- a/soh/soh/Enhancements/randomizer/logic.cpp +++ b/soh/soh/Enhancements/randomizer/logic.cpp @@ -1785,7 +1785,7 @@ namespace Rando { case ITEMTYPE_EQUIP: { RandomizerGet itemRG = item.GetRandomizerGet(); - if (itemRG == RG_GIANTS_KNIFE) { + if (itemRG == RG_GIANTS_KNIFE || itemRG == RG_DEKU_SHIELD || itemRG == RG_HYLIAN_SHIELD) { return; } uint32_t equipId = RandoGetToEquipFlag.find(itemRG)->second; From 7a6778c940e6bf148004ddf840202f87e4118200 Mon Sep 17 00:00:00 2001 From: Pepper0ni <93387759+Pepper0ni@users.noreply.github.com> Date: Wed, 19 Mar 2025 10:21:39 +0000 Subject: [PATCH 22/53] exclude maps from junk only GI skip (#5140) --- soh/soh/Enhancements/randomizer/hook_handlers.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index aa7bf806c..05d06dab2 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -302,9 +302,12 @@ void RandomizerOnPlayerUpdateForRCQueueHandler() { ( CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED) == SGIA_JUNK && ( + //crude fix to ensure map hints are readable. Ideally replace with better hint tracking. + !(getItemEntry.getItemId >= RG_DEKU_TREE_MAP && getItemEntry.getItemId <= RG_ICE_CAVERN_MAP) && ( getItemEntry.getItemCategory == ITEM_CATEGORY_JUNK || getItemEntry.getItemCategory == ITEM_CATEGORY_SKULLTULA_TOKEN || - getItemEntry.getItemCategory == ITEM_CATEGORY_LESSER + getItemEntry.getItemCategory == ITEM_CATEGORY_LESSER + ) ) ) ) From d109aa795af2b61c4eb7f0f65871a613a3dee43e Mon Sep 17 00:00:00 2001 From: aMannus Date: Wed, 19 Mar 2025 12:34:01 +0100 Subject: [PATCH 23/53] Remove Shuffle Pots assert (#5145) * Remove assert * Remove exceptions to the assert --- soh/soh/Enhancements/randomizer/ShufflePots.cpp | 3 --- soh/soh/Enhancements/randomizer/randomizer.cpp | 1 - 2 files changed, 4 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/ShufflePots.cpp b/soh/soh/Enhancements/randomizer/ShufflePots.cpp index be12d086b..cbc96cd1d 100644 --- a/soh/soh/Enhancements/randomizer/ShufflePots.cpp +++ b/soh/soh/Enhancements/randomizer/ShufflePots.cpp @@ -54,9 +54,6 @@ void ObjTsubo_RandomizerSpawnCollectible(ObjTsubo* potActor, PlayState* play) { void ObjTsubo_RandomizerInit(void* actorRef) { Actor* actor = static_cast(actorRef); - // Check for Lake Hylia specifically because the game spawns 2 pots out of bounds there for some reason. - if (actor->id != ACTOR_OBJ_TSUBO || gPlayState->sceneNum == SCENE_LAKE_HYLIA || gPlayState->sceneNum == SCENE_HYRULE_CASTLE) return; - ObjTsubo* potActor = static_cast(actorRef); potActor->potIdentity = OTRGlobals::Instance->gRandomizer->IdentifyPot(gPlayState->sceneNum, (s16)actor->world.pos.x, (s16)actor->world.pos.z); diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 95710078c..244afb9c4 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -1807,7 +1807,6 @@ PotIdentity Randomizer::IdentifyPot(s32 sceneNum, s32 posX, s32 posZ) { if (location->GetRandomizerCheck() == RC_UNKNOWN_CHECK) { LUSLOG_WARN("IdentifyPot did not receive a valid RC value (%d).", location->GetRandomizerCheck()); - assert(false); } else { potIdentity.randomizerInf = rcToRandomizerInf[location->GetRandomizerCheck()]; potIdentity.randomizerCheck = location->GetRandomizerCheck(); From 04d79222d413bbe989791c2281158a33ff54806c Mon Sep 17 00:00:00 2001 From: aMannus Date: Wed, 19 Mar 2025 12:34:52 +0100 Subject: [PATCH 24/53] fix crash (#5146) --- soh/soh/Enhancements/RemoveSpinAttackDarkness.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/soh/soh/Enhancements/RemoveSpinAttackDarkness.cpp b/soh/soh/Enhancements/RemoveSpinAttackDarkness.cpp index 20beb193a..37fed1b7d 100644 --- a/soh/soh/Enhancements/RemoveSpinAttackDarkness.cpp +++ b/soh/soh/Enhancements/RemoveSpinAttackDarkness.cpp @@ -10,6 +10,12 @@ void Custom_EnMThunder_Update(Actor* thisx, PlayState* play) { f32 blueRadius; s32 redGreen; + // If thunder effect doesn't exist (aka player doesn't have magic), + // don't do anything. + if (enMThunder->actionFunc == nullptr) { + return; + } + enMThunder->actionFunc(enMThunder, play); // don't call this part, it's what makes the spin attack darkness happen // func_80A9F314(play, this->unk_1BC); From b7b5949bc4b2097b49667489f3fb43d9f30e5e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Wed, 19 Mar 2025 11:42:18 +0000 Subject: [PATCH 25/53] Some location access fixes (#5121) * Some location access fixes Split out a few fixes I've made while working on #5105 * assert false * IC feedback --- .../location_access/dungeons/deku_tree.cpp | 6 +++--- .../location_access/dungeons/dodongos_cavern.cpp | 8 +++++--- .../location_access/dungeons/forest_temple.cpp | 2 +- .../location_access/dungeons/ganons_castle.cpp | 6 +++--- .../dungeons/gerudo_training_ground.cpp | 4 ++-- .../location_access/dungeons/ice_cavern.cpp | 12 ++++++------ .../location_access/dungeons/jabujabus_belly.cpp | 3 +-- .../location_access/dungeons/shadow_temple.cpp | 8 ++++---- soh/soh/Enhancements/randomizer/logic.cpp | 3 +-- soh/soh/Enhancements/randomizer/logic.h | 1 - .../Enhancements/randomizer/randomizer_entrance.c | 4 ++-- 11 files changed, 28 insertions(+), 29 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/deku_tree.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/deku_tree.cpp index 2500e4996..4740a7541 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/deku_tree.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/deku_tree.cpp @@ -71,8 +71,8 @@ void RegionTable_Init_DekuTree() { }, { //Locations LOCATION(RC_DEKU_TREE_BASEMENT_CHEST, true), - LOCATION(RC_DEKU_TREE_GS_BASEMENT_GATE, logic->CanJumpslashExceptHammer() || logic->CanUse(RG_FAIRY_SLINGSHOT) || logic->CanUse(RG_BOOMERANG) || logic->HasExplosives() || logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_DINS_FIRE)), - LOCATION(RC_DEKU_TREE_GS_BASEMENT_VINES, logic->CanUseProjectile() || logic->CanUse(RG_DINS_FIRE) || (ctx->GetTrickOption(RT_DEKU_MQ_COMPASS_GS) && logic->CanJumpslashExceptHammer())), + LOCATION(RC_DEKU_TREE_GS_BASEMENT_GATE, logic->CanKillEnemy(RE_GOLD_SKULLTULA, ED_SHORT_JUMPSLASH)), + LOCATION(RC_DEKU_TREE_GS_BASEMENT_VINES, logic->CanKillEnemy(RE_GOLD_SKULLTULA, ctx->GetTrickOption(RT_DEKU_MQ_COMPASS_GS) ? ED_SHORT_JUMPSLASH : ED_BOMB_THROW)), }, { //Exits Entrance(RR_DEKU_TREE_LOBBY, []{return true;}), @@ -84,7 +84,7 @@ void RegionTable_Init_DekuTree() { areaTable[RR_DEKU_TREE_BASEMENT_SCRUB_ROOM] = Region("Deku Tree Basement Scrub Room", "Deku Tree", {RA_DEKU_TREE}, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits Entrance(RR_DEKU_TREE_BASEMENT_LOWER, []{return true;}), - Entrance(RR_DEKU_TREE_BASEMENT_WATER_ROOM_FRONT, []{return Here(RR_DEKU_TREE_BASEMENT_SCRUB_ROOM, []{return logic->CanUse(RG_FAIRY_SLINGSHOT) || logic->CanUse(RG_FAIRY_BOW);});}), + Entrance(RR_DEKU_TREE_BASEMENT_WATER_ROOM_FRONT, []{return Here(RR_DEKU_TREE_BASEMENT_SCRUB_ROOM, []{return logic->CanHitEyeTargets();});}), }); areaTable[RR_DEKU_TREE_BASEMENT_WATER_ROOM_FRONT] = Region("Deku Tree Basement Water Room Front", "Deku Tree", {RA_DEKU_TREE}, NO_DAY_NIGHT_CYCLE, {}, {}, { diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp index bb5e1754f..4ae2e1e70 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp @@ -154,6 +154,7 @@ void RegionTable_Init_DodongosCavern() { LOCATION(RC_DODONGOS_CAVERN_BLADE_ROOM_HEART, true), }, { //Exits + Entrance(RR_DODONGOS_CAVERN_ARMOS_ROOM, []{return true;}), Entrance(RR_DODONGOS_CAVERN_2F_SIDE_ROOM, []{return Here(RR_DODONGOS_CAVERN_BOMB_ROOM_LOWER, []{return logic->CanBreakMudWalls() || (ctx->GetTrickOption(RT_DC_SCRUB_ROOM) && logic->HasItem(RG_GORONS_BRACELET));});}), Entrance(RR_DODONGOS_CAVERN_FIRST_SLINGSHOT_ROOM, []{return Here(RR_DODONGOS_CAVERN_BOMB_ROOM_LOWER, []{return logic->CanBreakMudWalls() || logic->HasItem(RG_GORONS_BRACELET);});}), Entrance(RR_DODONGOS_CAVERN_BOMB_ROOM_UPPER, []{return (logic->IsAdult && ctx->GetTrickOption(RT_DC_JUMP)) || logic->CanUse(RG_HOVER_BOOTS) || (logic->IsAdult && logic->CanUse(RG_LONGSHOT)) || (ctx->GetTrickOption(RT_DAMAGE_BOOST_SIMPLE) && logic->HasExplosives() && logic->CanJumpslash());}), @@ -304,6 +305,7 @@ void RegionTable_Init_DodongosCavern() { LOCATION(RC_DODONGOS_CAVERN_MQ_STAIRCASE_POT_4, logic->CanBreakPots()), }, { //Exits + Entrance(RR_DODONGOS_CAVERN_MQ_LOBBY, []{return true;}), //This is possible with sticks and shield, igniting a first flower by "touch" then very quickly crouch stabbing in a way that cuts the corner to light the 3rd bomb on the other side, but that's a trick Entrance(RR_DODONGOS_CAVERN_MQ_STAIRS_UPPER, []{return Here(RR_DODONGOS_CAVERN_MQ_STAIRS_LOWER, []{return logic->HasExplosives() || logic->CanUse(RG_DINS_FIRE) || (ctx->GetTrickOption(RT_DC_STAIRCASE) && logic->CanUse(RG_FAIRY_BOW));});}), Entrance(RR_DODONGOS_CAVERN_MQ_STAIRS_PAST_MUD_WALL, []{return Here(RR_DODONGOS_CAVERN_MQ_STAIRS_LOWER, []{return logic->CanBreakMudWalls();});}), @@ -460,9 +462,9 @@ void RegionTable_Init_DodongosCavern() { LOCATION(RC_DODONGOS_CAVERN_MQ_POE_ROOM_POT_4, logic->CanBreakPots()), }, { //Exits - Entrance(RR_DODONGOS_CAVERN_MQ_LOWER_RIGHT_SIDE, []{return Here(RR_DODONGOS_CAVERN_MQ_POES_ROOM, []{return logic->CanDetonateBombFlowers() || logic->HasItem(RG_GORONS_BRACELET);});}), - Entrance(RR_DODONGOS_CAVERN_MQ_LOWER_LIZALFOS, []{return true;}), - Entrance(RR_DODONGOS_CAVERN_MQ_MAD_SCRUB_ROOM, []{return Here(RR_DODONGOS_CAVERN_MQ_POES_ROOM, []{return logic->CanDetonateBombFlowers() || logic->HasItem(RG_GORONS_BRACELET);});}), + Entrance(RR_DODONGOS_CAVERN_MQ_LOBBY, []{return Here(RR_DODONGOS_CAVERN_MQ_POES_ROOM, []{return logic->CanDetonateBombFlowers() || logic->HasItem(RG_GORONS_BRACELET);});}), + Entrance(RR_DODONGOS_CAVERN_MQ_LOWER_LIZALFOS, []{return true;}), + Entrance(RR_DODONGOS_CAVERN_MQ_MAD_SCRUB_ROOM, []{return Here(RR_DODONGOS_CAVERN_MQ_POES_ROOM, []{return logic->CanDetonateBombFlowers() || logic->HasItem(RG_GORONS_BRACELET);});}), }); areaTable[RR_DODONGOS_CAVERN_MQ_MAD_SCRUB_ROOM] = Region("Dodongos Cavern Mad Scrub Room", "Dodongos Cavern", {RA_DODONGOS_CAVERN}, NO_DAY_NIGHT_CYCLE, {}, { diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp index c9f4ebc23..04efd4994 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp @@ -28,7 +28,7 @@ void RegionTable_Init_ForestTemple() { areaTable[RR_FOREST_TEMPLE_SOUTH_CORRIDOR] = Region("Forest Temple South Corridor", "Forest Temple", {RA_FOREST_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits Entrance(RR_FOREST_TEMPLE_FIRST_ROOM, []{return true;}), - Entrance(RR_FOREST_TEMPLE_LOBBY, []{return logic->CanAttack() || logic->CanUse(RG_NUTS);}), + Entrance(RR_FOREST_TEMPLE_LOBBY, []{return logic->CanPassEnemy(RE_BIG_SKULLTULA);}), }); areaTable[RR_FOREST_TEMPLE_LOBBY] = Region("Forest Temple Lobby", "Forest Temple", {RA_FOREST_TEMPLE}, NO_DAY_NIGHT_CYCLE, { diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp index 982ac16fc..c1a846564 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp @@ -61,7 +61,7 @@ void RegionTable_Init_GanonsCastle() { EventAccess(&logic->ForestTrialClear, []{return logic->CanUse(RG_LIGHT_ARROWS) && (logic->CanUse(RG_FIRE_ARROWS) || logic->CanUse(RG_DINS_FIRE));}), }, { //Locations - LOCATION(RC_GANONS_CASTLE_FOREST_TRIAL_CHEST, logic->CanDamage()), + LOCATION(RC_GANONS_CASTLE_FOREST_TRIAL_CHEST, logic->CanKillEnemy(RE_WOLFOS)), LOCATION(RC_GANONS_CASTLE_FOREST_TRIAL_POT_1, logic->CanBreakPots() && (logic->CanUse(RG_FIRE_ARROWS) || logic->CanUse(RG_DINS_FIRE))), LOCATION(RC_GANONS_CASTLE_FOREST_TRIAL_POT_2, logic->CanBreakPots() && (logic->CanUse(RG_FIRE_ARROWS) || logic->CanUse(RG_DINS_FIRE))), }, {}); @@ -78,7 +78,7 @@ void RegionTable_Init_GanonsCastle() { areaTable[RR_GANONS_CASTLE_WATER_TRIAL] = Region("Ganon's Castle Water Trial", "Ganon's Castle", {RA_GANONS_CASTLE}, NO_DAY_NIGHT_CYCLE, { //Events - EventAccess(&logic->BlueFireAccess, []{return logic->BlueFireAccess || logic->HasBottle();}), + EventAccess(&logic->BlueFireAccess, []{return true;}), EventAccess(&logic->FairyPot, []{return logic->FairyPot || (logic->BlueFire() && logic->CanKillEnemy(RE_FREEZARD));}), EventAccess(&logic->WaterTrialClear, []{return logic->BlueFire() && logic->IsAdult && logic->CanUse(RG_MEGATON_HAMMER) && logic->CanUse(RG_LIGHT_ARROWS);}), }, { @@ -112,7 +112,7 @@ void RegionTable_Init_GanonsCastle() { EventAccess(&logic->SpiritTrialClear, []{return logic->CanUse(RG_LIGHT_ARROWS) && (logic->CanUse(RG_MIRROR_SHIELD) || ctx->GetOption(RSK_SUNLIGHT_ARROWS)) && logic->CanUse(RG_BOMBCHU_5) && ((ctx->GetTrickOption(RT_GANON_SPIRIT_TRIAL_HOOKSHOT) && logic->CanJumpslashExceptHammer()) || logic->CanUse(RG_HOOKSHOT));}), }, { //Locations - LOCATION(RC_GANONS_CASTLE_SPIRIT_TRIAL_CRYSTAL_SWITCH_CHEST, (ctx->GetTrickOption(RT_GANON_SPIRIT_TRIAL_HOOKSHOT) || logic->CanUse(RG_HOOKSHOT)) && logic->CanJumpslashExceptHammer()), + LOCATION(RC_GANONS_CASTLE_SPIRIT_TRIAL_CRYSTAL_SWITCH_CHEST, (ctx->GetTrickOption(RT_GANON_SPIRIT_TRIAL_HOOKSHOT) || logic->CanUse(RG_HOOKSHOT)) && (logic->CanJumpslashExceptHammer() || logic->CanUse(RG_BOMBCHU_5))), LOCATION(RC_GANONS_CASTLE_SPIRIT_TRIAL_INVISIBLE_CHEST, (ctx->GetTrickOption(RT_GANON_SPIRIT_TRIAL_HOOKSHOT) || logic->CanUse(RG_HOOKSHOT)) && logic->CanUse(RG_BOMBCHU_5) && (ctx->GetTrickOption(RT_LENS_GANON) || logic->CanUse(RG_LENS_OF_TRUTH))), LOCATION(RC_GANONS_CASTLE_SPIRIT_TRIAL_POT_1, ((ctx->GetTrickOption(RT_GANON_SPIRIT_TRIAL_HOOKSHOT) && logic->CanJumpslashExceptHammer()) || logic->CanUse(RG_HOOKSHOT)) && logic->CanUse(RG_BOMBCHU_5) && logic->CanUse(RG_FAIRY_BOW) && (logic->CanUse(RG_MIRROR_SHIELD) || (ctx->GetOption(RSK_SUNLIGHT_ARROWS) && logic->CanUse(RG_LIGHT_ARROWS)))), LOCATION(RC_GANONS_CASTLE_SPIRIT_TRIAL_POT_2, ((ctx->GetTrickOption(RT_GANON_SPIRIT_TRIAL_HOOKSHOT) && logic->CanJumpslashExceptHammer()) || logic->CanUse(RG_HOOKSHOT)) && logic->CanUse(RG_BOMBCHU_5) && logic->CanUse(RG_FAIRY_BOW) && (logic->CanUse(RG_MIRROR_SHIELD) || (ctx->GetOption(RSK_SUNLIGHT_ARROWS) && logic->CanUse(RG_LIGHT_ARROWS)))), diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/gerudo_training_ground.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/gerudo_training_ground.cpp index f0b9de5c3..0c45d755c 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/gerudo_training_ground.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/gerudo_training_ground.cpp @@ -17,8 +17,8 @@ void RegionTable_Init_GerudoTrainingGround() { areaTable[RR_GERUDO_TRAINING_GROUND_LOBBY] = Region("Gerudo Training Ground Lobby", "Gerudo Training Ground", {RA_GERUDO_TRAINING_GROUND}, NO_DAY_NIGHT_CYCLE, {}, { //Locations - LOCATION(RC_GERUDO_TRAINING_GROUND_LOBBY_LEFT_CHEST, logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_FAIRY_SLINGSHOT)), - LOCATION(RC_GERUDO_TRAINING_GROUND_LOBBY_RIGHT_CHEST, logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_FAIRY_SLINGSHOT)), + LOCATION(RC_GERUDO_TRAINING_GROUND_LOBBY_LEFT_CHEST, logic->CanHitEyeTargets()), + LOCATION(RC_GERUDO_TRAINING_GROUND_LOBBY_RIGHT_CHEST, logic->CanHitEyeTargets()), LOCATION(RC_GERUDO_TRAINING_GROUND_STALFOS_CHEST, logic->CanKillEnemy(RE_STALFOS, ED_CLOSE, true, 2, true)), LOCATION(RC_GERUDO_TRAINING_GROUND_BEAMOS_CHEST, logic->CanKillEnemy(RE_BEAMOS) && logic->CanKillEnemy(RE_DINOLFOS, ED_CLOSE, true, 2, true)), LOCATION(RC_GERUDO_TRAINING_GROUND_ENTRANCE_STORMS_FAIRY, logic->CanUse(RG_SONG_OF_STORMS)), diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp index ff9b171c8..879ac1dce 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp @@ -21,18 +21,18 @@ void RegionTable_Init_IceCavern() { }, { //Exits Entrance(RR_ICE_CAVERN_ENTRYWAY, []{return true;}), - Entrance(RR_ICE_CAVERN_MAIN, []{return Here(RR_ICE_CAVERN_BEGINNING, []{return (logic->CanUse(RG_MASTER_SWORD) || logic->CanUse(RG_BIGGORON_SWORD)) || logic->CanUse(RG_MEGATON_HAMMER) || logic->HasExplosives() || logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_DINS_FIRE);});}), + Entrance(RR_ICE_CAVERN_MAIN, []{return Here(RR_ICE_CAVERN_BEGINNING, []{return logic->CanKillEnemy(RE_FREEZARD, ED_CLOSE, true, 4);});}), }); areaTable[RR_ICE_CAVERN_MAIN] = Region("Ice Cavern", "Ice Cavern", {RA_ICE_CAVERN}, NO_DAY_NIGHT_CYCLE, { //Events - EventAccess(&logic->BlueFireAccess, []{return logic->BlueFireAccess || (logic->IsAdult && logic->HasBottle());}), + EventAccess(&logic->BlueFireAccess, []{return logic->IsAdult;}), }, { //Locations LOCATION(RC_ICE_CAVERN_MAP_CHEST, logic->BlueFire() && logic->IsAdult), LOCATION(RC_ICE_CAVERN_COMPASS_CHEST, logic->BlueFire()), - LOCATION(RC_ICE_CAVERN_IRON_BOOTS_CHEST, logic->BlueFire() && (logic->CanJumpslash() || logic->CanUse(RG_FAIRY_SLINGSHOT) || logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_DINS_FIRE))), - LOCATION(RC_SHEIK_IN_ICE_CAVERN, logic->BlueFire() && (logic->CanJumpslash() || logic->CanUse(RG_FAIRY_SLINGSHOT) || logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_DINS_FIRE)) && logic->IsAdult), + LOCATION(RC_ICE_CAVERN_IRON_BOOTS_CHEST, logic->BlueFire() && logic->CanKillEnemy(RE_WOLFOS)), + LOCATION(RC_SHEIK_IN_ICE_CAVERN, logic->BlueFire() && logic->CanKillEnemy(RE_WOLFOS) && logic->IsAdult), LOCATION(RC_ICE_CAVERN_FREESTANDING_POH, logic->BlueFire()), LOCATION(RC_ICE_CAVERN_GS_SPINNING_SCYTHE_ROOM, logic->HookshotOrBoomerang()), LOCATION(RC_ICE_CAVERN_GS_HEART_PIECE_ROOM, logic->BlueFire() && logic->HookshotOrBoomerang()), @@ -125,8 +125,8 @@ void RegionTable_Init_IceCavern() { LOCATION(RC_SHEIK_IN_ICE_CAVERN, logic->CanKillEnemy(RE_STALFOS)), }, { //Exits - Entrance(RR_ICE_CAVERN_MQ_SCARECROW_ROOM, []{return logic->BlueFire() && Here(RR_ICE_CAVERN_MQ_STALFOS_ROOM, []{return logic->CanKillEnemy(RE_STALFOS);});}), - Entrance(RR_ICE_CAVERN_MQ_BEGINNING, []{return logic->CanUse(RG_IRON_BOOTS) && Here(RR_ICE_CAVERN_MQ_STALFOS_ROOM, []{return logic->CanKillEnemy(RE_STALFOS);});}), + Entrance(RR_ICE_CAVERN_MQ_WEST_CORRIDOR, []{return Here(RR_ICE_CAVERN_MQ_STALFOS_ROOM, []{return logic->CanKillEnemy(RE_STALFOS);});}), + Entrance(RR_ICE_CAVERN_MQ_BEGINNING, []{return logic->CanUse(RG_IRON_BOOTS) && Here(RR_ICE_CAVERN_MQ_STALFOS_ROOM, []{return logic->CanKillEnemy(RE_STALFOS);});}), }); areaTable[RR_ICE_CAVERN_MQ_COMPASS_ROOM] = Region("Ice Cavern MQ Compass Room", "Ice Cavern", {RA_ICE_CAVERN}, NO_DAY_NIGHT_CYCLE, { diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/jabujabus_belly.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/jabujabus_belly.cpp index fa9c7b085..7bfb98a87 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/jabujabus_belly.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/jabujabus_belly.cpp @@ -24,7 +24,6 @@ void RegionTable_Init_JabuJabusBelly() { //Combines Lift room middle and lower, 1F holes room, the forked corridor, and it's side rooms areaTable[RR_JABU_JABUS_BELLY_MAIN] = Region("Jabu Jabus Belly Main", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, { //Events - EventAccess(&logic->JabuRutoInB1, []{return true;}), EventAccess(&logic->JabuWestTentacle, []{return logic->JabuRutoIn1F && logic->CanKillEnemy(RE_TENTACLE, ED_BOOMERANG);}), }, { //Locations @@ -263,7 +262,7 @@ void RegionTable_Init_JabuJabusBelly() { areaTable[RR_JABU_JABUS_BELLY_MQ_WEST_FORKED_ROOMS] = Region("Jabu Jabus Belly MQ West Forked Rooms", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, { //Events - EventAccess(&logic->JabuWestTentacle, []{return logic->CanUse(RG_BOOMERANG);}), + EventAccess(&logic->JabuWestTentacle, []{return logic->CanKillEnemy(RE_TENTACLE, ED_BOOMERANG);}), }, { //Locations LOCATION(RC_JABU_JABUS_BELLY_MQ_GS_TAILPASARAN_ROOM, Here(RR_JABU_JABUS_BELLY_MQ_WEST_FORKED_ROOMS, []{return logic->HasExplosives();}) && logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA, ED_BOOMERANG)), diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp index bdcf8a4fa..8cf70fc3f 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp @@ -79,8 +79,8 @@ void RegionTable_Init_ShadowTemple() { areaTable[RR_SHADOW_TEMPLE_WIND_TUNNEL] = Region("Shadow Temple Wind Tunnel", "Shadow Temple", {RA_SHADOW_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, { //Locations LOCATION(RC_SHADOW_TEMPLE_WIND_HINT_CHEST, true), - LOCATION(RC_SHADOW_TEMPLE_AFTER_WIND_ENEMY_CHEST, logic->CanJumpslashExceptHammer()), - LOCATION(RC_SHADOW_TEMPLE_AFTER_WIND_HIDDEN_CHEST, true), + LOCATION(RC_SHADOW_TEMPLE_AFTER_WIND_ENEMY_CHEST, logic->CanKillEnemy(RE_GIBDO, ED_CLOSE, true, 2)), + LOCATION(RC_SHADOW_TEMPLE_AFTER_WIND_HIDDEN_CHEST, logic->HasExplosives()), LOCATION(RC_SHADOW_TEMPLE_GS_NEAR_SHIP, logic->CanUse(RG_LONGSHOT) && logic->SmallKeys(RR_SHADOW_TEMPLE, 4, 5)), LOCATION(RC_SHADOW_TEMPLE_WIND_HINT_SUN_FAIRY, logic->CanUse(RG_SUNS_SONG)), LOCATION(RC_SHADOW_TEMPLE_AFTER_WIND_POT_1, logic->CanBreakPots()), @@ -96,7 +96,7 @@ void RegionTable_Init_ShadowTemple() { //Locations LOCATION(RC_SHADOW_TEMPLE_SPIKE_WALLS_LEFT_CHEST, logic->CanUse(RG_DINS_FIRE)), LOCATION(RC_SHADOW_TEMPLE_BOSS_KEY_CHEST, logic->CanUse(RG_DINS_FIRE)), - LOCATION(RC_SHADOW_TEMPLE_INVISIBLE_FLOORMASTER_CHEST, logic->CanJumpslashExceptHammer()), + LOCATION(RC_SHADOW_TEMPLE_INVISIBLE_FLOORMASTER_CHEST, logic->CanKillEnemy(RE_FLOORMASTER)), //RANDOTODO check if child can reach the token LOCATION(RC_SHADOW_TEMPLE_GS_TRIPLE_GIANT_POT, logic->IsAdult && logic->CanAttack()), LOCATION(RC_SHADOW_TEMPLE_AFTER_BOAT_POT_1, logic->CanBreakPots()), @@ -181,7 +181,7 @@ void RegionTable_Init_ShadowTemple() { //Room exists for if it's ever possible to go backwards or void warp into the middle of shadow areaTable[RR_SHADOW_TEMPLE_MQ_B2_TO_B3_CORRIDOR] = Region("Shadow Temple MQ B2 to B3 Corridor", "Shadow Temple", {RA_SHADOW_TEMPLE}, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits - Entrance(RR_SHADOW_TEMPLE_MQ_FIRST_BEAMOS, []{return logic->HasExplosives() && logic->SmallKeys(RR_SHADOW_TEMPLE, 2);}), + Entrance(RR_SHADOW_TEMPLE_MQ_FIRST_BEAMOS, []{return logic->SmallKeys(RR_SHADOW_TEMPLE, 2);}), Entrance(RR_SHADOW_TEMPLE_MQ_UPPER_HUGE_PIT, []{return true;}), //bunnyhovers + lens lets you go from the very top of upper pit to the stationary invisible platform below quite easily }); diff --git a/soh/soh/Enhancements/randomizer/logic.cpp b/soh/soh/Enhancements/randomizer/logic.cpp index 9f127c32f..62c14e6ff 100644 --- a/soh/soh/Enhancements/randomizer/logic.cpp +++ b/soh/soh/Enhancements/randomizer/logic.cpp @@ -671,7 +671,7 @@ namespace Rando { } return killed; case RE_BIG_OCTO: - //If chasing octo is annoying but with rolls you can catch him, and you need rang to get into this room without shenanigains anyway. Bunny makes it free + //If chasing octo is annoying but with rolls you can catch him, and you need rang to get into this room without shenanigans anyway. Bunny makes it free return CanUse(RG_KOKIRI_SWORD) || CanUse(RG_STICKS) || CanUse(RG_MASTER_SWORD); case RE_GOHMA: return HasBossSoul(RG_GOHMA_SOUL) && CanJumpslash() && @@ -2338,7 +2338,6 @@ namespace Rando { MQSpiritMapRoomEnemies = false; MQSpirit3SunsEnemies = false; Spirit1FSilverRupees = false; - JabuRutoInB1 = false; JabuRutoIn1F = false; StopPerformanceTimer(PT_LOGIC_RESET); diff --git a/soh/soh/Enhancements/randomizer/logic.h b/soh/soh/Enhancements/randomizer/logic.h index 4286cc5c3..d8fa9864b 100644 --- a/soh/soh/Enhancements/randomizer/logic.h +++ b/soh/soh/Enhancements/randomizer/logic.h @@ -175,7 +175,6 @@ class Logic { bool MQSpiritTimeTravelChest = false; bool MQSpirit3SunsEnemies = false; bool Spirit1FSilverRupees = false; - bool JabuRutoInB1 = false; bool JabuRutoIn1F = false; /* --- END OF HELPERS AND LOCATION ACCESS --- */ diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance.c b/soh/soh/Enhancements/randomizer/randomizer_entrance.c index 3ad26b1f6..26592b0ce 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance.c +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance.c @@ -201,12 +201,12 @@ void Entrance_Init(void) { } // Overwrite grotto related indices - if (originalIndex >= ENTRANCE_GROTTO_EXIT_START) { + if (originalIndex >= ENTRANCE_GROTTO_EXIT_START && originalIndex < ENTRANCE_GROTTO_EXIT_START + NUM_GROTTOS) { Grotto_SetExitOverride(originalIndex, overrideIndex); continue; } - if (originalIndex >= ENTRANCE_GROTTO_LOAD_START && originalIndex < ENTRANCE_GROTTO_EXIT_START) { + if (originalIndex >= ENTRANCE_GROTTO_LOAD_START && originalIndex < ENTRANCE_GROTTO_LOAD_START + NUM_GROTTOS) { Grotto_SetLoadOverride(originalIndex, overrideIndex); continue; } From 5517e22f6eadbc6c5973538f8b65a83483a42be7 Mon Sep 17 00:00:00 2001 From: aMannus Date: Wed, 19 Mar 2025 12:42:56 +0100 Subject: [PATCH 26/53] Exclude skip get item animations outside of rando (#5147) --- soh/soh/Enhancements/randomizer/hook_handlers.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index 05d06dab2..54b658e68 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -297,6 +297,8 @@ void RandomizerOnPlayerUpdateForRCQueueHandler() { rc != RC_MARKET_BOMBCHU_BOWLING_SECOND_PRIZE && // Always show ItemGet animation for ice traps !(getItemEntry.modIndex == MOD_RANDOMIZER && getItemEntry.getItemId == RG_ICE_TRAP) && + // Always show ItemGet animation outside of randomizer to keep behaviour consistent in vanilla + IS_RANDO && ( CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED) == SGIA_ALL || ( From 80850615c3232bcdcafa380d26b6eaa1efba2de8 Mon Sep 17 00:00:00 2001 From: aMannus Date: Wed, 19 Mar 2025 13:49:51 +0100 Subject: [PATCH 27/53] Fix key color issues (#5149) --- soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp | 8 ++++---- soh/soh/Enhancements/randomizer/draw.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp index f63c95e30..ac78353ee 100644 --- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp +++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp @@ -283,13 +283,13 @@ static std::map cosmeticOptions = { COSMETIC_OPTION("Key.GanonsBossGem", "Ganons Boss Key Gem", COSMETICS_GROUP_BOSS_KEYS, ColorRGBA8(255, 0, 0, 255), false, true, false), COSMETIC_OPTION("Key.WellSmallBody", "Well Small Key", COSMETICS_GROUP_SMALL_KEYS, ColorRGBA8(255, 255, 255, 255), false, true, false), - COSMETIC_OPTION("Key.WellSmallEmblem", "Well Small Key Emblem", COSMETICS_GROUP_SMALL_KEYS, ColorRGBA8(227, 110, 255, 255), false, true, true), + COSMETIC_OPTION("Key.WellSmallEmblem", "Well Small Key Emblem", COSMETICS_GROUP_SMALL_KEYS, ColorRGBA8(227, 110, 255, 255), false, true, false), COSMETIC_OPTION("Key.FortSmallBody", "Fortress Small Key", COSMETICS_GROUP_SMALL_KEYS, ColorRGBA8(255, 255, 255, 255), false, true, false), - COSMETIC_OPTION("Key.FortSmallEmblem", "Fortress Small Key Emblem",COSMETICS_GROUP_SMALL_KEYS, ColorRGBA8(255, 255, 255, 255), false, true, true), + COSMETIC_OPTION("Key.FortSmallEmblem", "Fortress Small Key Emblem",COSMETICS_GROUP_SMALL_KEYS, ColorRGBA8(255, 255, 255, 255), false, true, false), COSMETIC_OPTION("Key.GTGSmallBody", "GTG Small Key", COSMETICS_GROUP_SMALL_KEYS, ColorRGBA8(255, 255, 255, 255), false, true, false), - COSMETIC_OPTION("Key.GTGSmallEmblem", "GTG Small Key Emblem", COSMETICS_GROUP_SMALL_KEYS, ColorRGBA8(221, 212, 60, 255), false, true, true), + COSMETIC_OPTION("Key.GTGSmallEmblem", "GTG Small Key Emblem", COSMETICS_GROUP_SMALL_KEYS, ColorRGBA8(221, 212, 60, 255), false, true, false), //COSMETIC_OPTION("Key.ChestGameSmallBody", "Chest Game Key", COSMETICS_GROUP_SMALL_KEYS, ColorRGBA8(255, 255, 255, 255), false, true, false), - //COSMETIC_OPTION("Key.ChestGameEmblem", "Chest Game Key Emblem", COSMETICS_GROUP_SMALL_KEYS, ColorRGBA8(255, 0, 0, 255), false, true, true), + //COSMETIC_OPTION("Key.ChestGameEmblem", "Chest Game Key Emblem", COSMETICS_GROUP_SMALL_KEYS, ColorRGBA8(255, 0, 0, 255), false, true, false), COSMETIC_OPTION("Key.Skeleton", "Skeleton Key", COSMETICS_GROUP_SMALL_KEYS, ColorRGBA8(255, 255, 170, 255), false, true, false), COSMETIC_OPTION("HUD.AButton", "A Button", COSMETICS_GROUP_HUD, ColorRGBA8( 90, 90, 255, 255), false, true, false), diff --git a/soh/soh/Enhancements/randomizer/draw.cpp b/soh/soh/Enhancements/randomizer/draw.cpp index 0a89e4dae..718d91f0f 100644 --- a/soh/soh/Enhancements/randomizer/draw.cpp +++ b/soh/soh/Enhancements/randomizer/draw.cpp @@ -80,7 +80,7 @@ Color_RGB8 SmallEmblemDefaultValue[10] = { extern "C" u8 Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey); extern "C" void Randomizer_DrawSmallKey(PlayState* play, GetItemEntry* getItemEntry) { - s8 isCustomKeysEnabled = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("CustomKeyModels"), 0); + s8 isCustomKeysEnabled = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("CustomKeyModels"), 1); int slot = getItemEntry->drawItemId - RG_FOREST_TEMPLE_SMALL_KEY; Gfx* customIconDLs[] = { @@ -247,7 +247,7 @@ extern "C" void Randomizer_DrawBossKey(PlayState* play, GetItemEntry* getItemEnt } extern "C" void Randomizer_DrawKeyRing(PlayState* play, GetItemEntry* getItemEntry) { - s8 isCustomKeysEnabled = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("CustomKeyModels"), 0); + s8 isCustomKeysEnabled = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("CustomKeyModels"), 1); int slot = getItemEntry->drawItemId - RG_FOREST_TEMPLE_KEY_RING; Gfx* CustomIconDLs[] = { From 7f8e3d43376c0fbf4671ea661171ccc8313685de Mon Sep 17 00:00:00 2001 From: aMannus Date: Wed, 19 Mar 2025 13:50:04 +0100 Subject: [PATCH 28/53] randomizer enhancement menu improvements (#5148) --- soh/soh/SohGui/SohMenuRandomizer.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/soh/soh/SohGui/SohMenuRandomizer.cpp b/soh/soh/SohGui/SohMenuRandomizer.cpp index a022d6c40..a6649e3ea 100644 --- a/soh/soh/SohGui/SohMenuRandomizer.cpp +++ b/soh/soh/SohGui/SohMenuRandomizer.cpp @@ -17,8 +17,11 @@ void SohMenu::AddMenuRandomizer() { .CVar(CVAR_WINDOW("RandomizerSettings")) .WindowName("Randomizer Settings") .Options(WindowButtonOptions().Tooltip("Enables the separate Randomizer Settings Window.")); + + // Enhancements path.sidebarName = "Enhancements"; - AddSidebarEntry("Randomizer", path.sidebarName, 1); + AddSidebarEntry("Randomizer", path.sidebarName, 3); + AddWidget(path, "Randomizer Enhancements", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Rando-Relevant Navi Hints", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_RANDOMIZER_ENHANCEMENT("RandoRelevantNavi")) .Options(CheckboxOptions().Tooltip( @@ -75,7 +78,9 @@ void SohMenu::AddMenuRandomizer() { AddWidget(path, "Item Scale: %.2f", WIDGET_CVAR_SLIDER_FLOAT) .CVar(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationScale")) .PreFunc([](WidgetInfo& info) { - info.isHidden = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED) == SGIA_DISABLED; + info.options->disabled = + !CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_DISABLED); + info.options->disabledTooltip = "This slider only applies when using the \"Skip Get Item Animations\" option."; }) .Options(FloatSliderOptions() .Min(5.0f) From 17b19ddd73a351229c0c987572f8577fc6657129 Mon Sep 17 00:00:00 2001 From: Christopher Leggett Date: Wed, 19 Mar 2025 09:11:56 -0400 Subject: [PATCH 29/53] Remove GBK Triforce Hunt as an option (#5117) Where we were previously checking it, just check the separate triforce hunt option instead. The option under GBK settings was always kind of a dummy option that wasn't meant to actually be selected, and it was set automatically when Triforce Hunt was on. This PR just cuts out the middle man, so to speak. --- soh/soh/Enhancements/randomizer/3drando/item_pool.cpp | 2 +- .../Enhancements/randomizer/3drando/starting_inventory.cpp | 2 +- soh/soh/Enhancements/randomizer/hint.cpp | 7 ++++--- soh/soh/Enhancements/randomizer/randomizerTypes.h | 1 - soh/soh/Enhancements/randomizer/settings.cpp | 6 +----- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp index 45e6221ce..5cbfb9a9e 100644 --- a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp @@ -1297,7 +1297,7 @@ void GenerateItemPool() { if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_KAK_TOKENS)) { ctx->PlaceItemInLocation(RC_KAK_100_GOLD_SKULLTULA_REWARD, RG_GANONS_CASTLE_BOSS_KEY); - } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Get() >= RO_GANON_BOSS_KEY_LACS_VANILLA && ctx->GetOption(RSK_GANONS_BOSS_KEY).IsNot(RO_GANON_BOSS_KEY_TRIFORCE_HUNT)) { + } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Get() >= RO_GANON_BOSS_KEY_LACS_VANILLA) { ctx->PlaceItemInLocation(RC_TOT_LIGHT_ARROWS_CUTSCENE, RG_GANONS_CASTLE_BOSS_KEY); } else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_VANILLA)) { ctx->PlaceItemInLocation(RC_GANONS_TOWER_BOSS_KEY_CHEST, RG_GANONS_CASTLE_BOSS_KEY); diff --git a/soh/soh/Enhancements/randomizer/3drando/starting_inventory.cpp b/soh/soh/Enhancements/randomizer/3drando/starting_inventory.cpp index c72b5f285..04a7e3040 100644 --- a/soh/soh/Enhancements/randomizer/3drando/starting_inventory.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/starting_inventory.cpp @@ -57,7 +57,7 @@ void GenerateStartingInventory() { // Add Ganon's Boss key with Triforce Hunt so the game thinks it's obtainable from the start. // During save init, the boss key isn't actually given and it's instead given when completing the triforce. if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_STARTWITH) || - ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_TRIFORCE_HUNT)) { + ctx->GetOption(RSK_TRIFORCE_HUNT)) { AddItemToInventory(RG_GANONS_CASTLE_BOSS_KEY); } diff --git a/soh/soh/Enhancements/randomizer/hint.cpp b/soh/soh/Enhancements/randomizer/hint.cpp index 29a9a969f..2648c4623 100644 --- a/soh/soh/Enhancements/randomizer/hint.cpp +++ b/soh/soh/Enhancements/randomizer/hint.cpp @@ -587,6 +587,10 @@ CustomMessage Hint::GetGanonBossKeyText() { auto ctx = Rando::Context::GetInstance(); CustomMessage ganonBossKeyMessage; + if (ctx->GetOption(RSK_TRIFORCE_HUNT)) { + return StaticData::hintTextTable[RHT_GANON_BK_TRIFORCE_HINT].GetHintMessage(); + } + if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_STARTWITH)) { return StaticData::hintTextTable[RHT_GANON_BK_START_WITH_HINT].GetHintMessage(); } @@ -631,9 +635,6 @@ CustomMessage Hint::GetGanonBossKeyText() { ganonBossKeyMessage = StaticData::hintTextTable[RHT_LACS_TOKENS_HINT].GetHintMessage(); ganonBossKeyMessage.InsertNumber(ctx->GetOption(RSK_LACS_TOKEN_COUNT).Get()); } - else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_TRIFORCE_HUNT)) { - return StaticData::hintTextTable[RHT_GANON_BK_TRIFORCE_HINT].GetHintMessage(); - } return ganonBossKeyMessage; } diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index a418627ef..e2581a634 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -5425,7 +5425,6 @@ typedef enum { RO_GANON_BOSS_KEY_LACS_DUNGEONS, RO_GANON_BOSS_KEY_LACS_TOKENS, RO_GANON_BOSS_KEY_KAK_TOKENS, - RO_GANON_BOSS_KEY_TRIFORCE_HUNT, } RandoOptionGanonsBossKey; typedef enum { diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 447da07f9..a2bf3660d 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -237,7 +237,7 @@ void Settings::CreateOptions() { OPT_U8(RSK_KEYSANITY, "Small Key Shuffle", {"Start With", "Vanilla", "Own Dungeon", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("Keysanity"), mOptionDescriptions[RSK_KEYSANITY], WidgetType::Combobox, RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); OPT_U8(RSK_GERUDO_KEYS, "Gerudo Fortress Keys", {"Vanilla", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GerudoKeys"), mOptionDescriptions[RSK_GERUDO_KEYS], WidgetType::Combobox, RO_GERUDO_KEYS_VANILLA); OPT_U8(RSK_BOSS_KEYSANITY, "Boss Key Shuffle", {"Start With", "Vanilla", "Own Dungeon", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("BossKeysanity"), mOptionDescriptions[RSK_BOSS_KEYSANITY], WidgetType::Combobox, RO_DUNGEON_ITEM_LOC_OWN_DUNGEON); - OPT_U8(RSK_GANONS_BOSS_KEY, "Ganon's Boss Key", {"Vanilla", "Own Dungeon", "Start With", "Any Dungeon", "Overworld", "Anywhere", "LACS-Vanilla", "LACS-Stones", "LACS-Medallions", "LACS-Rewards", "LACS-Dungeons", "LACS-Tokens", "100 GS Reward", "Triforce Hunt"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), mOptionDescriptions[RSK_GANONS_BOSS_KEY], WidgetType::Combobox, RO_GANON_BOSS_KEY_VANILLA); + OPT_U8(RSK_GANONS_BOSS_KEY, "Ganon's Boss Key", {"Vanilla", "Own Dungeon", "Start With", "Any Dungeon", "Overworld", "Anywhere", "LACS-Vanilla", "LACS-Stones", "LACS-Medallions", "LACS-Rewards", "LACS-Dungeons", "LACS-Tokens", "100 GS Reward"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleGanonBossKey"), mOptionDescriptions[RSK_GANONS_BOSS_KEY], WidgetType::Combobox, RO_GANON_BOSS_KEY_VANILLA); OPT_U8(RSK_LACS_STONE_COUNT, "GCBK Stone Count", {NumOpts(0, 4)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LacsStoneCount"), "", WidgetType::Slider, 3, true); OPT_U8(RSK_LACS_MEDALLION_COUNT, "GCBK Medallion Count", {NumOpts(0, 7)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LacsMedallionCount"), "", WidgetType::Slider, 6, true); OPT_U8(RSK_LACS_REWARD_COUNT, "GCBK Reward Count", {NumOpts(0, 10)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LacsRewardCount"), "", WidgetType::Slider, 9, true); @@ -1856,10 +1856,6 @@ void Context::FinalizeSettings(const std::set& excludedLocation mOptions[RSK_STARTING_AGE].Set(RO_AGE_CHILD); } - if (mOptions[RSK_TRIFORCE_HUNT]) { - mOptions[RSK_GANONS_BOSS_KEY].Set(RO_GANON_BOSS_KEY_TRIFORCE_HUNT); - } - // Force 100 GS Shuffle if that's where Ganon's Boss Key is if (mOptions[RSK_GANONS_BOSS_KEY].Is(RO_GANON_BOSS_KEY_KAK_TOKENS)) { mOptions[RSK_SHUFFLE_100_GS_REWARD].Set(1); From 7dd3c1b2acba6e9635ba32f06dd3f96d6092259e Mon Sep 17 00:00:00 2001 From: aMannus Date: Wed, 19 Mar 2025 15:46:02 +0100 Subject: [PATCH 30/53] update model (#5150) --- .../objects/object_keyring/Hilite_new.rgba16 | Bin 0 -> 604 bytes .../gKeyringIconGerudoFortressDL | 9 +- .../gKeyringIconGerudoFortressDL_tri_0 | 315 ++++++------------ .../gKeyringIconGerudoFortressDL_vtx_0 | 281 +++++++--------- .../gKeyringIconGerudoFortressDL_vtx_cull | 10 + ...ortressDL_f3dlite_IconMetal_GerudoFortress | 14 +- .../custom/objects/object_keyring/model.xml | 10 + 7 files changed, 263 insertions(+), 376 deletions(-) create mode 100644 soh/assets/custom/objects/object_keyring/Hilite_new.rgba16 create mode 100644 soh/assets/custom/objects/object_keyring/gKeyringIconGerudoFortressDL_vtx_cull create mode 100644 soh/assets/custom/objects/object_keyring/model.xml diff --git a/soh/assets/custom/objects/object_keyring/Hilite_new.rgba16 b/soh/assets/custom/objects/object_keyring/Hilite_new.rgba16 new file mode 100644 index 0000000000000000000000000000000000000000..5438695faeb81b84633fe3d1358ffa90efbd7578 GIT binary patch literal 604 zcma)2J!^wd6t(>W?N&mjk|8(*ametdVxWslP&yRFCE_3sAxOyZmO?VR6q79yggzXK zg9PdHx+SBV$yRW33LX3dy)QPTv`al)y?DRQIUL7vkM|G#3;2G%#~&;p{r7VTo!8${ z4?W#+90U|Zg}*|%tgD8)(!#$$zTD8lLE-VFlP1g8LBV=dUD~4@<m@pM4xSz z_PqwX?ONMzHjV{cp4b7~pypBkaV;FNSzHo%z-drOpi*wi`mF6*PHRw{?>eR$s0LF3YJF{`L5;zLF1ELK z!(3n+a5nHF=4x;J@~LdxoRB!wP`{tT7eJ3@^u++D)Sckd|wSfR}>F- p6VO!ABn8klIMVgnXu + + + + - - - - - diff --git a/soh/assets/custom/objects/object_keyring/gKeyringIconGerudoFortressDL_tri_0 b/soh/assets/custom/objects/object_keyring/gKeyringIconGerudoFortressDL_tri_0 index 791defa81..c7b29e9c7 100644 --- a/soh/assets/custom/objects/object_keyring/gKeyringIconGerudoFortressDL_tri_0 +++ b/soh/assets/custom/objects/object_keyring/gKeyringIconGerudoFortressDL_tri_0 @@ -1,213 +1,110 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_keyring/gKeyringIconGerudoFortressDL_vtx_0 b/soh/assets/custom/objects/object_keyring/gKeyringIconGerudoFortressDL_vtx_0 index 1dd0cbb61..5ab0f76ec 100644 --- a/soh/assets/custom/objects/object_keyring/gKeyringIconGerudoFortressDL_vtx_0 +++ b/soh/assets/custom/objects/object_keyring/gKeyringIconGerudoFortressDL_vtx_0 @@ -1,233 +1,210 @@ - + - - - - - - - + + + + + + + - + - + - - - + - - - + + + - - - + + + - - - + - - - + + + + + - - - - + + + + - - - + - - - + - - - + + + - - + + - - - - + + - - - - - - + + + + + + + + - - - + + + + - - - - - - - + + + + + - - - - + + + + - - - + + + - - - + - + - - - + + + + + - + + - - - - + + - - - - + + + - - - - - - - + + + + - + - + - - - + + + - - - - - - - - - - + + + + + + + + + + - - - - - + + + + + - - - - - - + + + - - - + - + - + + - - - - - - + + + + - - - - + + + + + - - - - - - - - - - + + - + - + - + - + - + + + - + - + - + - - + + - + diff --git a/soh/assets/custom/objects/object_keyring/gKeyringIconGerudoFortressDL_vtx_cull b/soh/assets/custom/objects/object_keyring/gKeyringIconGerudoFortressDL_vtx_cull new file mode 100644 index 000000000..6ef0139b8 --- /dev/null +++ b/soh/assets/custom/objects/object_keyring/gKeyringIconGerudoFortressDL_vtx_cull @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_keyring/mat_gKeyringIconGerudoFortressDL_f3dlite_IconMetal_GerudoFortress b/soh/assets/custom/objects/object_keyring/mat_gKeyringIconGerudoFortressDL_f3dlite_IconMetal_GerudoFortress index b5755b4a8..f926de784 100644 --- a/soh/assets/custom/objects/object_keyring/mat_gKeyringIconGerudoFortressDL_f3dlite_IconMetal_GerudoFortress +++ b/soh/assets/custom/objects/object_keyring/mat_gKeyringIconGerudoFortressDL_f3dlite_IconMetal_GerudoFortress @@ -1,21 +1,15 @@ + - - - + - - - - - + + - - diff --git a/soh/assets/custom/objects/object_keyring/model.xml b/soh/assets/custom/objects/object_keyring/model.xml new file mode 100644 index 000000000..6ef0139b8 --- /dev/null +++ b/soh/assets/custom/objects/object_keyring/model.xml @@ -0,0 +1,10 @@ + + + + + + + + + + From 28944cc68fba230deb08eaab649a54e6e14b37fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Wed, 19 Mar 2025 16:30:44 +0000 Subject: [PATCH 31/53] fix some warnings (#5135) * fix some warnings * fix error caught by mac build, fix 3 more warnings * merge fix * feedback * remove color refactor --- soh/soh/Enhancements/controls/InputViewer.cpp | 2 +- .../cosmetics/CosmeticsEditor.cpp | 4 +- soh/soh/Enhancements/debugconsole.cpp | 1 + .../Enhancements/debugger/debugSaveEditor.cpp | 2 +- .../GameInteractor_RawAction.cpp | 2 +- soh/soh/Enhancements/presets.cpp | 2 +- .../Enhancements/randomizer/Plandomizer.cpp | 12 +- soh/soh/Enhancements/randomizer/item.cpp | 2 + soh/soh/Enhancements/randomizer/option.cpp | 2 +- .../randomizer/randomizer_check_tracker.cpp | 8 +- .../randomizer_entrance_tracker.cpp | 3 +- .../randomizer/randomizer_item_tracker.cpp | 80 +++++------ .../Enhancements/timesplits/TimeSplits.cpp | 2 +- soh/soh/SohGui/ResolutionEditor.cpp | 4 +- soh/soh/SohGui/SohMenuNetwork.cpp | 8 +- soh/soh/SohGui/SohMenuSettings.cpp | 8 +- soh/soh/SohGui/UIWidgets.cpp | 90 ++++++------ soh/soh/SohGui/UIWidgets.hpp | 134 +++++++++--------- soh/soh/z_scene_otr.cpp | 4 - 19 files changed, 180 insertions(+), 190 deletions(-) diff --git a/soh/soh/Enhancements/controls/InputViewer.cpp b/soh/soh/Enhancements/controls/InputViewer.cpp index c354e0319..d26de6719 100644 --- a/soh/soh/Enhancements/controls/InputViewer.cpp +++ b/soh/soh/Enhancements/controls/InputViewer.cpp @@ -564,7 +564,7 @@ void InputViewerSettingsWindow::DrawElement() { ImGui::Unindent(); } - UIWidgets:PaddedSeparator(true, true); + UIWidgets::PaddedSeparator(true, true); } if (ImGui::CollapsingHeader("Analog Stick")) { diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp index ac78353ee..ddd36fa56 100644 --- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp +++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp @@ -2045,8 +2045,8 @@ void CosmeticsEditorWindow::DrawElement() { UIWidgets::ComboboxOptions() .DefaultIndex(COLORSCHEME_N64) .Color(THEME_COLOR) - .LabelPosition(UIWidgets::LabelPosition::Near) - .ComponentAlignment(UIWidgets::ComponentAlignment::Right)); + .LabelPosition(UIWidgets::LabelPositions::Near) + .ComponentAlignment(UIWidgets::ComponentAlignments::Right)); UIWidgets::CVarCheckbox("Sync Rainbow colors", CVAR_COSMETIC("RainbowSync"), UIWidgets::CheckboxOptions() .Color(THEME_COLOR)); diff --git a/soh/soh/Enhancements/debugconsole.cpp b/soh/soh/Enhancements/debugconsole.cpp index 67547437d..33eed43f9 100644 --- a/soh/soh/Enhancements/debugconsole.cpp +++ b/soh/soh/Enhancements/debugconsole.cpp @@ -79,6 +79,7 @@ static bool ActorSpawnHandler(std::shared_ptr Console, const std: if (args[8][0] != ',') { spawnPoint.rot.z = std::stoi(args[8]); } + [[fallthrough]]; case 6: if (args[3][0] != ',') { spawnPoint.pos.x = std::stoi(args[3]); diff --git a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp index dfc4d9681..0e9bbc79a 100644 --- a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp +++ b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp @@ -1550,7 +1550,7 @@ void ResetBaseOptions() { intSliderOptionsBase.Color(THEME_COLOR).Size({320.0f, 0.0f}).Tooltip(""); buttonOptionsBase.Color(THEME_COLOR).Size(Sizes::Inline).Tooltip(""); checkboxOptionsBase.Color(THEME_COLOR).Tooltip(""); - comboboxOptionsBase.Color(THEME_COLOR).ComponentAlignment(ComponentAlignment::Left).LabelPosition(LabelPosition::Near).Tooltip(""); + comboboxOptionsBase.Color(THEME_COLOR).ComponentAlignment(ComponentAlignments::Left).LabelPosition(LabelPositions::Near).Tooltip(""); } void SaveEditorWindow::DrawElement() { diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp index aa8c72d00..d0b32018a 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp @@ -564,7 +564,7 @@ void GameInteractor::RawAction::SetRandomWind(bool active) { void GameInteractor::RawAction::SetPlayerInvincibility(bool active) { Player* player = GET_PLAYER(gPlayState); if (active) { - player->invincibilityTimer = 1000; + player->invincibilityTimer = -20; } else { player->invincibilityTimer = 0; } diff --git a/soh/soh/Enhancements/presets.cpp b/soh/soh/Enhancements/presets.cpp index 240d0fa81..c8760caca 100644 --- a/soh/soh/Enhancements/presets.cpp +++ b/soh/soh/Enhancements/presets.cpp @@ -50,7 +50,7 @@ void DrawPresetSelector(PresetType presetTypeId) { comboboxTooltip += std::string(iter->second.label) + " - " + std::string(iter->second.description); } - ImGui::Text("Presets", false, true); + ImGui::Text("Presets"); UIWidgets::PushStyleCombobox(THEME_COLOR); if (ImGui::BeginCombo("##PresetsComboBox", selectedPresetDef.label)) { for ( auto iter = presetTypeDef.presets.begin(); iter != presetTypeDef.presets.end(); ++iter ) { diff --git a/soh/soh/Enhancements/randomizer/Plandomizer.cpp b/soh/soh/Enhancements/randomizer/Plandomizer.cpp index 4741d9d0c..39b20d876 100644 --- a/soh/soh/Enhancements/randomizer/Plandomizer.cpp +++ b/soh/soh/Enhancements/randomizer/Plandomizer.cpp @@ -838,8 +838,8 @@ void PlandomizerDrawItemSlots(uint32_t index) { void PlandomizerDrawShopSlider(uint32_t index) { ImGui::PushID(index); UIWidgets::SliderInt("Price:", &plandoLogData[index].shopPrice, UIWidgets::IntSliderOptions() - .Color(THEME_COLOR).Format("%d Rupees").Min(0).Max(999).LabelPosition(UIWidgets::LabelPosition::Near) - .ComponentAlignment(UIWidgets::ComponentAlignment::Right).Size(UIWidgets::Sizes::Inline)); + .Color(THEME_COLOR).Format("%d Rupees").Min(0).Max(999).LabelPosition(UIWidgets::LabelPositions::Near) + .ComponentAlignment(UIWidgets::ComponentAlignments::Right).Size(UIWidgets::Sizes::Inline)); ImGui::PopID(); } @@ -878,7 +878,7 @@ void PlandomizerDrawIceTrapSetup(uint32_t index) { } ImGui::SameLine(); } - if (UIWidgets::InputString("##TrapName", &trapTextInput, UIWidgets::InputOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::None))) { + if (UIWidgets::InputString("##TrapName", &trapTextInput, UIWidgets::InputOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPositions::None))) { plandoLogData[index].iceTrapName = trapTextInput.c_str(); } @@ -933,7 +933,7 @@ void PlandomizerDrawOptions() { PlandomizerPopulateSeedList(); static size_t selectedList = 0; if (existingSeedList.size() != 0) { - UIWidgets::Combobox("##JsonFiles", &selectedList, existingSeedList, UIWidgets::ComboboxOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::None)); + UIWidgets::Combobox("##JsonFiles", &selectedList, existingSeedList, UIWidgets::ComboboxOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPositions::None)); } else { ImGui::Text("No Spoiler Logs found."); @@ -1024,7 +1024,7 @@ void PlandomizerDrawOptions() { } if (getTabID == TAB_LOCATIONS) { if (plandoLogData.size() > 0) { - UIWidgets::Combobox("Filter by Area:##AreaFilter", &selectedArea, rcAreaNameMap, UIWidgets::ComboboxOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::Near).ComponentAlignment(UIWidgets::ComponentAlignment::Right)); + UIWidgets::Combobox("Filter by Area:##AreaFilter", &selectedArea, rcAreaNameMap, UIWidgets::ComboboxOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPositions::Near).ComponentAlignment(UIWidgets::ComponentAlignments::Right)); ImGui::SameLine(); if (UIWidgets::Button("Empty All Rewards", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline).Padding(ImVec2(10.f, 6.f)))) { PlandomizerRemoveAllItems(); @@ -1061,7 +1061,7 @@ void PlandomizerDrawHintsWindow() { } ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 10); - if (UIWidgets::InputString("##HintMessage", &hintInputText, UIWidgets::InputOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::None).Tooltip(plandomizerHintsTooltip().c_str()))) { + if (UIWidgets::InputString("##HintMessage", &hintInputText, UIWidgets::InputOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPositions::None).Tooltip(plandomizerHintsTooltip().c_str()))) { plandoHintData[index].hintText = hintInputText.c_str(); } index++; diff --git a/soh/soh/Enhancements/randomizer/item.cpp b/soh/soh/Enhancements/randomizer/item.cpp index 6f0309223..1c315b47f 100644 --- a/soh/soh/Enhancements/randomizer/item.cpp +++ b/soh/soh/Enhancements/randomizer/item.cpp @@ -94,6 +94,7 @@ std::shared_ptr Item::GetGIEntry() const { // NOLINT(*-no-recursio actual = RG_DEKU_STICK_BAG; break; } + [[fallthrough]]; case 1: if (infiniteUpgrades == RO_INF_UPGRADES_CONDENSED_PROGRESSIVE) { actual = RG_STICK_UPGRADE_INF; @@ -123,6 +124,7 @@ std::shared_ptr Item::GetGIEntry() const { // NOLINT(*-no-recursio actual = RG_DEKU_NUT_BAG; break; } + [[fallthrough]]; case 1: if (infiniteUpgrades == RO_INF_UPGRADES_CONDENSED_PROGRESSIVE) { actual = RG_NUT_UPGRADE_INF; diff --git a/soh/soh/Enhancements/randomizer/option.cpp b/soh/soh/Enhancements/randomizer/option.cpp index 2c478e34c..64ca107c5 100644 --- a/soh/soh/Enhancements/randomizer/option.cpp +++ b/soh/soh/Enhancements/randomizer/option.cpp @@ -232,7 +232,7 @@ bool Option::RenderCombobox() { } UIWidgets::ComboboxOptions widgetOptions = UIWidgets::ComboboxOptions().Color(THEME_COLOR).Tooltip(description.c_str()); if (this->GetKey() == RSK_LOGIC_RULES) { - widgetOptions = widgetOptions.LabelPosition(UIWidgets::LabelPosition::None).ComponentAlignment(UIWidgets::ComponentAlignment::Right); + widgetOptions = widgetOptions.LabelPosition(UIWidgets::LabelPositions::None).ComponentAlignment(UIWidgets::ComponentAlignments::Right); } widgetOptions.disabled = disabled; if(UIWidgets::Combobox(name.c_str(), &selected, options, widgetOptions)) { diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index 6c4177198..c51650bbd 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -1772,21 +1772,21 @@ void CheckTrackerSettingsWindow::DrawElement() { ImGui::PopItemWidth(); UIWidgets::CVarCombobox("Window Type", CVAR_TRACKER_CHECK("WindowType"), windowType, - UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPosition::Far).ComponentAlignment(UIWidgets::ComponentAlignment::Right) + UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPositions::Far).ComponentAlignment(UIWidgets::ComponentAlignments::Right) .Color(THEME_COLOR).DefaultIndex(TRACKER_WINDOW_WINDOW)); if (CVarGetInteger(CVAR_TRACKER_CHECK("WindowType"), TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) { UIWidgets::CVarCheckbox("Enable Dragging", CVAR_TRACKER_CHECK("Draggable"), UIWidgets::CheckboxOptions().Color(THEME_COLOR)); UIWidgets::CVarCheckbox("Only enable while paused", CVAR_TRACKER_CHECK("ShowOnlyPaused"), UIWidgets::CheckboxOptions().Color(THEME_COLOR)); UIWidgets::CVarCombobox("Display Mode", CVAR_TRACKER_CHECK("DisplayType"), displayType, - UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPosition::Far).ComponentAlignment(UIWidgets::ComponentAlignment::Right) + UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPositions::Far).ComponentAlignment(UIWidgets::ComponentAlignments::Right) .Color(THEME_COLOR).DefaultIndex(0)); if (CVarGetInteger(CVAR_TRACKER_CHECK("DisplayType"), TRACKER_DISPLAY_ALWAYS) == TRACKER_DISPLAY_COMBO_BUTTON) { UIWidgets::CVarCombobox("Combo Button 1", CVAR_TRACKER_CHECK("ComboButton1"), buttonStrings, - UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPosition::Far).ComponentAlignment(UIWidgets::ComponentAlignment::Right) + UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPositions::Far).ComponentAlignment(UIWidgets::ComponentAlignments::Right) .Color(THEME_COLOR).DefaultIndex(TRACKER_COMBO_BUTTON_L)); UIWidgets::CVarCombobox("Combo Button 2", CVAR_TRACKER_CHECK("ComboButton2"), buttonStrings, - UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPosition::Far).ComponentAlignment(UIWidgets::ComponentAlignment::Right) + UIWidgets::ComboboxOptions().LabelPosition(UIWidgets::LabelPositions::Far).ComponentAlignment(UIWidgets::ComponentAlignments::Right) .Color(THEME_COLOR).DefaultIndex(TRACKER_COMBO_BUTTON_L)); } } diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp index a6e1c6d07..fab17acf1 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp @@ -674,8 +674,7 @@ void EntranceTrackerSettingsWindow::DrawElement() { ImGui::Text("Sort By"); UIWidgets::CVarRadioButton("To", CVAR_TRACKER_ENTRANCE("SortBy"), 0, UIWidgets::RadioButtonsOptions() - .Color(THEME_COLOR) - .Tooltip("Sort entrances by the original source entrance")); + .Color(THEME_COLOR).Tooltip("Sort entrances by the original source entrance")); UIWidgets::CVarRadioButton("From", CVAR_TRACKER_ENTRANCE("SortBy"), 1, UIWidgets::RadioButtonsOptions() .Color(THEME_COLOR).Tooltip("Sort entrances by the overrided destination")); diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index 51f161def..92c7491fc 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -1388,8 +1388,8 @@ void ItemTrackerSettingsWindow::DrawElement() { ImGui::PopItemWidth(); if (CVarCombobox("Window Type", CVAR_TRACKER_ITEM("WindowType"), windowTypes, ComboboxOptions() - .DefaultIndex(TRACKER_WINDOW_FLOATING).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(TRACKER_WINDOW_FLOATING).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } @@ -1401,19 +1401,19 @@ void ItemTrackerSettingsWindow::DrawElement() { shouldUpdateVectors = true; } if (CVarCombobox("Display Mode", CVAR_TRACKER_ITEM("DisplayType.Main"), displayModes, ComboboxOptions() - .DefaultIndex(TRACKER_DISPLAY_ALWAYS).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(TRACKER_DISPLAY_ALWAYS).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Main"), TRACKER_DISPLAY_ALWAYS) == TRACKER_DISPLAY_COMBO_BUTTON) { if (CVarCombobox("Combo Button 1", CVAR_TRACKER_ITEM("ComboButton1"), buttons, ComboboxOptions() - .DefaultIndex(TRACKER_COMBO_BUTTON_L).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(TRACKER_COMBO_BUTTON_L).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarCombobox("Combo Button 2", CVAR_TRACKER_ITEM("ComboButton2"), buttons, ComboboxOptions() - .DefaultIndex(TRACKER_COMBO_BUTTON_R).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(TRACKER_COMBO_BUTTON_R).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } } @@ -1425,8 +1425,8 @@ void ItemTrackerSettingsWindow::DrawElement() { ImGui::NewLine(); CVarCombobox("Ammo/Capacity Tracking", CVAR_TRACKER_ITEM("ItemCountType"), itemTrackerCapacityTrackOptions, ComboboxOptions() - .DefaultIndex(ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY).ComponentAlignment(ComponentAlignment::Left) - .LabelPosition(LabelPosition::Above).Color(THEME_COLOR) + .DefaultIndex(ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY).ComponentAlignment(ComponentAlignments::Left) + .LabelPosition(LabelPositions::Above).Color(THEME_COLOR) .Tooltip("Customize what the numbers under each item are tracking." "\n\nNote: items without capacity upgrades will track ammo even in capacity mode")); if (CVarGetInteger(CVAR_TRACKER_ITEM("ItemCountType"), ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY) == ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY || CVarGetInteger(CVAR_TRACKER_ITEM("ItemCountType"), ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY) == ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY) { @@ -1436,35 +1436,35 @@ void ItemTrackerSettingsWindow::DrawElement() { } CVarCombobox("Key Count Tracking", CVAR_TRACKER_ITEM("KeyCounts"), itemTrackerKeyTrackOptions, ComboboxOptions() - .DefaultIndex(KEYS_COLLECTED_MAX).ComponentAlignment(ComponentAlignment::Left) - .LabelPosition(LabelPosition::Above).Color(THEME_COLOR) + .DefaultIndex(KEYS_COLLECTED_MAX).ComponentAlignment(ComponentAlignments::Left) + .LabelPosition(LabelPositions::Above).Color(THEME_COLOR) .Tooltip("Customize what numbers are shown for key tracking.")); CVarCombobox("Triforce Piece Count Tracking", CVAR_TRACKER_ITEM("TriforcePieceCounts"), itemTrackerTriforcePieceTrackOptions, ComboboxOptions() - .DefaultIndex(TRIFORCE_PIECE_COLLECTED_REQUIRED_MAX).ComponentAlignment(ComponentAlignment::Left) - .LabelPosition(LabelPosition::Above).Color(THEME_COLOR) + .DefaultIndex(TRIFORCE_PIECE_COLLECTED_REQUIRED_MAX).ComponentAlignment(ComponentAlignments::Left) + .LabelPosition(LabelPositions::Above).Color(THEME_COLOR) .Tooltip("Customize what numbers are shown for triforce piece tracking.")); ImGui::TableNextColumn(); if (CVarCombobox("Inventory", CVAR_TRACKER_ITEM("DisplayType.Inventory"), displayTypes, ComboboxOptions() - .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarCombobox("Equipment", CVAR_TRACKER_ITEM("DisplayType.Equipment"), displayTypes, ComboboxOptions() - .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarCombobox("Misc", CVAR_TRACKER_ITEM("DisplayType.Misc"), displayTypes, ComboboxOptions() - .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarCombobox("Dungeon Rewards", CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), displayTypes, ComboboxOptions() - .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonRewards"), SECTION_DISPLAY_MAIN_WINDOW) == SECTION_DISPLAY_SEPARATE) { @@ -1473,13 +1473,13 @@ void ItemTrackerSettingsWindow::DrawElement() { } } if (CVarCombobox("Songs", CVAR_TRACKER_ITEM("DisplayType.Songs"), displayTypes, ComboboxOptions() - .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(SECTION_DISPLAY_MAIN_WINDOW).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarCombobox("Dungeon Items", CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), displayTypes, ComboboxOptions() - .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.DungeonItems"), SECTION_DISPLAY_HIDDEN) != SECTION_DISPLAY_HIDDEN) { @@ -1493,45 +1493,45 @@ void ItemTrackerSettingsWindow::DrawElement() { } } if (CVarCombobox("Greg", CVAR_TRACKER_ITEM("DisplayType.Greg"), extendedDisplayTypes, ComboboxOptions() - .DefaultIndex(SECTION_DISPLAY_EXTENDED_HIDDEN).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(SECTION_DISPLAY_EXTENDED_HIDDEN).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarCombobox("Triforce Pieces", CVAR_TRACKER_ITEM("DisplayType.TriforcePieces"), displayTypes, ComboboxOptions() - .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarCombobox("Boss Souls", CVAR_TRACKER_ITEM("DisplayType.BossSouls"), displayTypes, ComboboxOptions() - .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarCombobox("Ocarina Buttons", CVAR_TRACKER_ITEM("DisplayType.OcarinaButtons"), displayTypes, ComboboxOptions() - .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarCombobox("Fishing Pole", CVAR_TRACKER_ITEM("DisplayType.FishingPole"), extendedDisplayTypes, ComboboxOptions() - .DefaultIndex(SECTION_DISPLAY_EXTENDED_HIDDEN).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(SECTION_DISPLAY_EXTENDED_HIDDEN).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarCombobox("Total Checks", "gTrackers.ItemTracker.TotalChecks.DisplayType", minimalDisplayTypes, ComboboxOptions() - .DefaultIndex(SECTION_DISPLAY_MINIMAL_HIDDEN).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(SECTION_DISPLAY_MINIMAL_HIDDEN).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.Main"), TRACKER_DISPLAY_ALWAYS) == TRACKER_DISPLAY_ALWAYS) { if (CVarCombobox("Personal notes", CVAR_TRACKER_ITEM("DisplayType.Notes"), displayTypes, ComboboxOptions() - .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignment::Right) - .LabelPosition(LabelPosition::Far).Color(THEME_COLOR))) { + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { shouldUpdateVectors = true; } } diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index a16a98f9e..046536f45 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -809,7 +809,7 @@ void TimeSplitsDrawOptionsMenu() { static uint32_t selectedItem = 0; ImGui::Text("Select List to Load: "); ImGui::PushItemWidth(150.0f); - Combobox("", &selectedItem, keys, ComboboxOptions().Color(THEME_COLOR).LabelPosition(LabelPosition::Near)); + Combobox("", &selectedItem, keys, ComboboxOptions().Color(THEME_COLOR).LabelPosition(LabelPositions::Near)); ImGui::PopItemWidth(); ImGui::SameLine(); if (Button("Load List", ButtonOptions().Color(THEME_COLOR).Size(Sizes::Inline))) { diff --git a/soh/soh/SohGui/ResolutionEditor.cpp b/soh/soh/SohGui/ResolutionEditor.cpp index c270444fb..6b287fde5 100644 --- a/soh/soh/SohGui/ResolutionEditor.cpp +++ b/soh/soh/SohGui/ResolutionEditor.cpp @@ -422,10 +422,10 @@ void RegisterResolutionWidgets() { // Declare input interaction bools outside of IF statement to prevent Y field from disappearing. const bool input_X = UIWidgets::SliderFloat("X", &aspectRatioX, UIWidgets::FloatSliderOptions({{ .disabled = disabled_everything }}).Min(0.1f).Max(32.0f).Step(0.001f).Format("%3f") - .Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::Near).ComponentAlignment(UIWidgets::ComponentAlignment::Right)); + .Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPositions::Near).ComponentAlignment(UIWidgets::ComponentAlignments::Right)); const bool input_Y = UIWidgets::SliderFloat("Y", &aspectRatioY, UIWidgets::FloatSliderOptions({{ .disabled = disabled_everything }}).Min(0.1f).Max(24.0f).Step(0.001f).Format("%3f") - .Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPosition::Near).ComponentAlignment(UIWidgets::ComponentAlignment::Right)); + .Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPositions::Near).ComponentAlignment(UIWidgets::ComponentAlignments::Right)); if (input_X || input_Y) { item_aspectRatio = default_aspectRatio; update[UPDATE_aspectRatioX] = true; diff --git a/soh/soh/SohGui/SohMenuNetwork.cpp b/soh/soh/SohGui/SohMenuNetwork.cpp index 29511a9e9..135f7e2be 100644 --- a/soh/soh/SohGui/SohMenuNetwork.cpp +++ b/soh/soh/SohGui/SohMenuNetwork.cpp @@ -42,11 +42,11 @@ void SohMenu::AddMenuNetwork() { .CustomFunction([](WidgetInfo& info) { ImGui::BeginDisabled(Sail::Instance->isEnabled); ImGui::Text("%s", info.name.c_str()); - CVarInputString("##HostSail", CVAR_REMOTE_SAIL("Host"), InputOptions().Color(THEME_COLOR).PlaceholderText("127.0.0.1").DefaultValue("127.0.0.1").Size(ImVec2(ImGui::GetFontSize() * 15, 0)).LabelPosition(LabelPosition::None)); + CVarInputString("##HostSail", CVAR_REMOTE_SAIL("Host"), InputOptions().Color(THEME_COLOR).PlaceholderText("127.0.0.1").DefaultValue("127.0.0.1").Size(ImVec2(ImGui::GetFontSize() * 15, 0)).LabelPosition(LabelPositions::None)); ImGui::SameLine(); ImGui::Text(":"); ImGui::SameLine(); - CVarInputInt("##PortSail", CVAR_REMOTE_SAIL("Port"), InputOptions().Color(THEME_COLOR).PlaceholderText("43384").DefaultValue("43384").Size(ImVec2(ImGui::GetFontSize() * 5, 0)).LabelPosition(LabelPosition::None)); + CVarInputInt("##PortSail", CVAR_REMOTE_SAIL("Port"), InputOptions().Color(THEME_COLOR).PlaceholderText("43384").DefaultValue("43384").Size(ImVec2(ImGui::GetFontSize() * 5, 0)).LabelPosition(LabelPositions::None)); ImGui::EndDisabled(); }); AddWidget(path, "Enable##Sail", WIDGET_BUTTON) @@ -104,11 +104,11 @@ void SohMenu::AddMenuNetwork() { .CustomFunction([](WidgetInfo& info) { ImGui::BeginDisabled(CrowdControl::Instance->isEnabled); ImGui::Text("%s", info.name.c_str()); - CVarInputString("##HostCrowdControl", CVAR_REMOTE_CROWD_CONTROL("Host"), InputOptions().Color(THEME_COLOR).PlaceholderText("127.0.0.1").DefaultValue("127.0.0.1").Size(ImVec2(ImGui::GetFontSize() * 15, 0)).LabelPosition(LabelPosition::None)); + CVarInputString("##HostCrowdControl", CVAR_REMOTE_CROWD_CONTROL("Host"), InputOptions().Color(THEME_COLOR).PlaceholderText("127.0.0.1").DefaultValue("127.0.0.1").Size(ImVec2(ImGui::GetFontSize() * 15, 0)).LabelPosition(LabelPositions::None)); ImGui::SameLine(); ImGui::Text(":"); ImGui::SameLine(); - CVarInputInt("##PortCrowdControl", CVAR_REMOTE_CROWD_CONTROL("Port"), InputOptions().Color(THEME_COLOR).PlaceholderText("43384").DefaultValue("43384").Size(ImVec2(ImGui::GetFontSize() * 5, 0)).LabelPosition(LabelPosition::None)); + CVarInputInt("##PortCrowdControl", CVAR_REMOTE_CROWD_CONTROL("Port"), InputOptions().Color(THEME_COLOR).PlaceholderText("43384").DefaultValue("43384").Size(ImVec2(ImGui::GetFontSize() * 5, 0)).LabelPosition(LabelPositions::None)); ImGui::EndDisabled(); }); AddWidget(path, "Enable##CrowdControl", WIDGET_BUTTON) diff --git a/soh/soh/SohGui/SohMenuSettings.cpp b/soh/soh/SohGui/SohMenuSettings.cpp index dc821ecc4..093e98618 100644 --- a/soh/soh/SohGui/SohMenuSettings.cpp +++ b/soh/soh/SohGui/SohMenuSettings.cpp @@ -108,8 +108,8 @@ void SohMenu::AddMenuSettings() { .CVar(CVAR_ENHANCEMENT("BootSequence")) .Options(ComboboxOptions() .DefaultIndex(BOOTSEQUENCE_DEFAULT) - .LabelPosition(LabelPosition::Far) - .ComponentAlignment(ComponentAlignment::Right) + .LabelPosition(LabelPositions::Far) + .ComponentAlignment(ComponentAlignments::Right) .ComboMap(bootSequenceLabels) .Tooltip("Configure what happens when starting or resetting the game.\n\n" "Default: LUS logo -> N64 logo\n" @@ -121,7 +121,7 @@ void SohMenu::AddMenuSettings() { .CVar(CVAR_SETTING("TitleScreenTranslation")); AddWidget(path, "Menu Language", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_SETTING("Languages")) - .Options(ComboboxOptions().LabelPosition(LabelPosition::Far).ComponentAlignment(ComponentAlignment::Right).ComboMap(languages).DefaultIndex(LANGUAGE_ENG)); + .Options(ComboboxOptions().LabelPosition(LabelPositions::Far).ComponentAlignment(ComponentAlignments::Right).ComboMap(languages).DefaultIndex(LANGUAGE_ENG)); AddWidget(path, "Accessibility", WIDGET_SEPARATOR_TEXT); #if defined(_WIN32) || defined(__APPLE__) AddWidget(path, "Text to Speech", WIDGET_CVAR_CHECKBOX) @@ -136,7 +136,7 @@ void SohMenu::AddMenuSettings() { AddWidget(path, "ImGui Menu Scaling", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_SETTING("ImGuiScale")) .Options(ComboboxOptions().ComboMap(imguiScaleOptions).Tooltip("Changes the scaling of the ImGui menu elements.").DefaultIndex(1) - .ComponentAlignment(ComponentAlignment::Right).LabelPosition(LabelPosition::Far)) + .ComponentAlignment(ComponentAlignments::Right).LabelPosition(LabelPositions::Far)) .Callback([](WidgetInfo& info) { OTRGlobals::Instance->ScaleImGui(); }); diff --git a/soh/soh/SohGui/UIWidgets.cpp b/soh/soh/SohGui/UIWidgets.cpp index 7b6883628..9875cf7b9 100644 --- a/soh/soh/SohGui/UIWidgets.cpp +++ b/soh/soh/SohGui/UIWidgets.cpp @@ -285,10 +285,10 @@ bool Checkbox(const char* _label, bool* value, const CheckboxOptions& options) { ImGui::BeginDisabled(options.disabled); - bool above = options.labelPosition == LabelPosition::Above; - bool lpFar = options.labelPosition == LabelPosition::Far; - bool right = options.alignment == ComponentAlignment::Right; - bool none = options.labelPosition == LabelPosition::None; + bool above = options.labelPosition == LabelPositions::Above; + bool lpFar = options.labelPosition == LabelPositions::Far; + bool right = options.alignment == ComponentAlignments::Right; + bool none = options.labelPosition == LabelPositions::None; std::string labelStr = (none ? "##" : ""); labelStr.append(_label); @@ -327,12 +327,12 @@ bool Checkbox(const char* _label, bool* value, const CheckboxOptions& options) { PushStyleCheckbox(options.color); ImVec2 checkPos = pos; ImVec2 labelPos = pos; - if (options.labelPosition == LabelPosition::Above) { + if (options.labelPosition == LabelPositions::Above) { checkPos.y += label_size.y + (style.ItemInnerSpacing.y * 2.0f); } else { labelPos.y += (square_sz / 2) - (label_size.y / 2); } - if (options.alignment == ComponentAlignment::Right) { + if (options.alignment == ComponentAlignments::Right) { checkPos.x = total_bb.Max.x - square_sz; } else { float labelFarOffset = ImGui::GetContentRegionAvail().x - label_size.x; @@ -518,22 +518,22 @@ bool SliderInt(const char* label, int32_t* value, const IntSliderOptions& option ImGui::BeginDisabled(options.disabled); PushStyleSlider(options.color); float width = (options.size == ImVec2(0,0)) ? ImGui::GetContentRegionAvail().x : options.size.x; - if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + if (options.labelPosition == LabelPositions::Near || options.labelPosition == LabelPositions::Far) { width = width - (ImGui::CalcTextSize(label).x + ImGui::GetStyle().FramePadding.x); } ImGui::AlignTextToFramePadding(); - if (options.alignment == ComponentAlignment::Right) { + if (options.alignment == ComponentAlignments::Right) { ImGui::Text(label, *value); - if (options.labelPosition == LabelPosition::Above) { + if (options.labelPosition == LabelPositions::Above) { ImGui::NewLine(); ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); - } else if (options.labelPosition == LabelPosition::Near) { + } else if (options.labelPosition == LabelPositions::Near) { ImGui::SameLine(); - } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + } else if (options.labelPosition == LabelPositions::Far || options.labelPosition == LabelPositions::None) { ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); } - } else if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Above) { + } else if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Above) { ImGui::Text(label, *value); } } @@ -576,11 +576,11 @@ bool SliderInt(const char* label, int32_t* value, const IntSliderOptions& option } } - if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Near) { + if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Near) { ImGui::SameLine(); ImGui::Text(label, *value); - } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + } else if (options.labelPosition == LabelPositions::Far || options.labelPosition == LabelPositions::None) { ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x); ImGui::Text(label, *value); } @@ -611,33 +611,25 @@ bool CVarSliderInt(const char* label, const char* cvarName, const IntSliderOptio } void ClampFloat(float* value, float min, float max, float step) { - int ticks = 0; - float increment = 1.0f; + int factor = 1; if (step < 1.0f) { - ticks++; - increment = 0.1f; + factor *= 10; } if (step < 0.1f) { - ticks++; - increment = 0.01f; + factor *= 10; } if (step < 0.01f) { - ticks++; - increment = 0.001f; + factor *= 10; } if (step < 0.001f) { - ticks++; - increment = 0.0001f; + factor *= 10; } if (step < 0.0001f) { - ticks++; - increment = 0.00001f; + factor *= 10; } if (step < 0.00001f) { - ticks++; - increment = 0.000001f; + factor *= 10; } - int factor = 1 * std::pow(10, ticks); if (*value < min) { *value = min; } else if (*value > max) { @@ -661,24 +653,24 @@ bool SliderFloat(const char* label, float* value, const FloatSliderOptions& opti PushStyleSlider(options.color); float labelSpacing = ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x; float width = (options.size == ImVec2(0, 0)) ? ImGui::GetContentRegionAvail().x : options.size.x; - if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + if (options.labelPosition == LabelPositions::Near || options.labelPosition == LabelPositions::Far) { width = width - (ImGui::CalcTextSize(label).x + ImGui::GetStyle().FramePadding.x); } ImGui::AlignTextToFramePadding(); - if (options.alignment == ComponentAlignment::Right) { + if (options.alignment == ComponentAlignments::Right) { ImGui::Text(label, *value); - if (options.labelPosition == LabelPosition::Above) { + if (options.labelPosition == LabelPositions::Above) { ImGui::NewLine(); ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); - } else if (options.labelPosition == LabelPosition::Near) { + } else if (options.labelPosition == LabelPositions::Near) { width -= labelSpacing; ImGui::SameLine(); - } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + } else if (options.labelPosition == LabelPositions::Far || options.labelPosition == LabelPositions::None) { width -= labelSpacing; ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); } - } else if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Above) { + } else if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Above) { ImGui::Text(label, *value); } } @@ -715,11 +707,11 @@ bool SliderFloat(const char* label, float* value, const FloatSliderOptions& opti } } - if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Near) { + if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Near) { ImGui::SameLine(); ImGui::Text(label, *value); - } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + } else if (options.labelPosition == LabelPositions::Far || options.labelPosition == LabelPositions::None) { ImGui::SameLine(ImGui::GetContentRegionAvail().x - labelSpacing); ImGui::Text(label, *value); } @@ -765,12 +757,12 @@ bool InputString(const char* label, std::string* value, const InputOptions& opti ImGui::BeginDisabled(options.disabled); PushStyleInput(options.color); float width = (options.size == ImVec2(0, 0)) ? ImGui::GetContentRegionAvail().x : options.size.x; - if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Above) { + if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Above) { ImGui::Text(label, *value->c_str()); } - } else if (options.alignment == ComponentAlignment::Right) { - if (options.labelPosition == LabelPosition::Above) { + } else if (options.alignment == ComponentAlignments::Right) { + if (options.labelPosition == LabelPositions::Above) { ImGui::NewLine(); ImGui::SameLine(width - ImGui::CalcTextSize(label).x); ImGui::Text(label, *value->c_str()); @@ -821,12 +813,12 @@ bool InputInt(const char* label, int32_t* value, const InputOptions& options) { ImGui::BeginDisabled(options.disabled); PushStyleInput(options.color); float width = (options.size == ImVec2(0, 0)) ? ImGui::GetContentRegionAvail().x : options.size.x; - if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Above) { + if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Above) { ImGui::Text(label, *value); } - } else if (options.alignment == ComponentAlignment::Right) { - if (options.labelPosition == LabelPosition::Above) { + } else if (options.alignment == ComponentAlignments::Right) { + if (options.labelPosition == LabelPositions::Above) { ImGui::NewLine(); ImGui::SameLine(width - ImGui::CalcTextSize(label).x); ImGui::Text(label, *value); diff --git a/soh/soh/SohGui/UIWidgets.hpp b/soh/soh/SohGui/UIWidgets.hpp index 4e30de6d1..325795023 100644 --- a/soh/soh/SohGui/UIWidgets.hpp +++ b/soh/soh/SohGui/UIWidgets.hpp @@ -105,7 +105,7 @@ namespace UIWidgets { const ImVec2 Fill = ImVec2(-1.0f, 0.0f); } - enum LabelPosition { + enum LabelPositions { Near, Far, Above, @@ -113,7 +113,7 @@ namespace UIWidgets { Within, }; - enum ComponentAlignment { + enum ComponentAlignments { Left, Right, }; @@ -204,19 +204,19 @@ namespace UIWidgets { struct CheckboxOptions : WidgetOptions { bool defaultValue = false; // Only applicable to CVarCheckbox - ComponentAlignment alignment = ComponentAlignment::Left; - LabelPosition labelPosition = LabelPosition::Near; + ComponentAlignments alignment = ComponentAlignments::Left; + LabelPositions labelPosition = LabelPositions::Near; Colors color = Colors::LightBlue; CheckboxOptions& DefaultValue(bool defaultValue_) { defaultValue = defaultValue_; return *this; } - CheckboxOptions& ComponentAlignment(ComponentAlignment alignment_) { + CheckboxOptions& ComponentAlignment(ComponentAlignments alignment_) { alignment = alignment_; return *this; } - CheckboxOptions& LabelPosition(LabelPosition labelPosition_) { + CheckboxOptions& LabelPosition(LabelPositions labelPosition_) { labelPosition = labelPosition_; return *this; } @@ -237,8 +237,8 @@ namespace UIWidgets { struct ComboboxOptions : WidgetOptions { std::unordered_map comboMap = {}; uint32_t defaultIndex = 0; // Only applicable to CVarCombobox - ComponentAlignment alignment = ComponentAlignment::Left; - LabelPosition labelPosition = LabelPosition::Above; + ComponentAlignments alignment = ComponentAlignments::Left; + LabelPositions labelPosition = LabelPositions::Above; ImGuiComboFlags flags = 0; Colors color = Colors::LightBlue; @@ -250,11 +250,11 @@ namespace UIWidgets { defaultIndex = defaultIndex_; return *this; } - ComboboxOptions& ComponentAlignment(ComponentAlignment alignment_) { + ComboboxOptions& ComponentAlignment(ComponentAlignments alignment_) { alignment = alignment_; return *this; } - ComboboxOptions& LabelPosition(LabelPosition labelPosition_) { + ComboboxOptions& LabelPosition(LabelPositions labelPosition_) { labelPosition = labelPosition_; return *this; } @@ -276,8 +276,8 @@ namespace UIWidgets { int32_t max = 10; int32_t defaultValue = 1; bool clamp = true; - ComponentAlignment alignment = ComponentAlignment::Left; - LabelPosition labelPosition = LabelPosition::Above; + ComponentAlignments alignment = ComponentAlignments::Left; + LabelPositions labelPosition = LabelPositions::Above; Colors color = Colors::Gray; ImGuiSliderFlags flags = 0; ImVec2 size = {0,0}; @@ -306,11 +306,11 @@ namespace UIWidgets { defaultValue = defaultValue_; return *this; } - IntSliderOptions& ComponentAlignment(ComponentAlignment alignment_) { + IntSliderOptions& ComponentAlignment(ComponentAlignments alignment_) { alignment = alignment_; return *this; } - IntSliderOptions& LabelPosition(LabelPosition labelPosition_) { + IntSliderOptions& LabelPosition(LabelPositions labelPosition_) { labelPosition = labelPosition_; return *this; } @@ -341,8 +341,8 @@ namespace UIWidgets { float defaultValue = 1.0f; bool clamp = true; bool isPercentage = false; // Multiplies visual value by 100 - ComponentAlignment alignment = ComponentAlignment::Left; - LabelPosition labelPosition = LabelPosition::Above; + ComponentAlignments alignment = ComponentAlignments::Left; + LabelPositions labelPosition = LabelPositions::Above; Colors color = Colors::Gray; ImGuiSliderFlags flags = 0; ImVec2 size = {0,0}; @@ -371,11 +371,11 @@ namespace UIWidgets { defaultValue = defaultValue_; return *this; } - FloatSliderOptions& ComponentAlignment(ComponentAlignment alignment_) { + FloatSliderOptions& ComponentAlignment(ComponentAlignments alignment_) { alignment = alignment_; return *this; } - FloatSliderOptions& LabelPosition(LabelPosition labelPosition_) { + FloatSliderOptions& LabelPosition(LabelPositions labelPosition_) { labelPosition = labelPosition_; return *this; } @@ -423,8 +423,8 @@ namespace UIWidgets { }; struct InputOptions : WidgetOptions { - ComponentAlignment alignment = ComponentAlignment::Left; - LabelPosition labelPosition = LabelPosition::Above; + ComponentAlignments alignment = ComponentAlignments::Left; + LabelPositions labelPosition = LabelPositions::Above; Colors color = Colors::Gray; ImVec2 size = {0,0}; std::string placeholder = ""; @@ -446,7 +446,7 @@ namespace UIWidgets { return *this; } - InputOptions& LabelPosition(LabelPosition labelPosition_) { + InputOptions& LabelPosition(LabelPositions labelPosition_) { labelPosition = labelPosition_; return *this; } @@ -545,20 +545,20 @@ namespace UIWidgets { float comboWidth = CalcComboWidth(longest, options.flags); ImGui::AlignTextToFramePadding(); - if (options.labelPosition != LabelPosition::None) { - if (options.alignment == ComponentAlignment::Right) { - ImGui::Text(label); - if (options.labelPosition == LabelPosition::Above) { + if (options.labelPosition != LabelPositions::None) { + if (options.alignment == ComponentAlignments::Right) { + ImGui::Text("%s", label); + if (options.labelPosition == LabelPositions::Above) { ImGui::NewLine(); ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); - } else if (options.labelPosition == LabelPosition::Near) { + } else if (options.labelPosition == LabelPositions::Near) { ImGui::SameLine(); - } else if (options.labelPosition == LabelPosition::Far) { + } else if (options.labelPosition == LabelPositions::Far) { ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); } - } else if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Above) { - ImGui::Text(label); + } else if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Above) { + ImGui::Text("%s", label); } } } @@ -578,12 +578,12 @@ namespace UIWidgets { ImGui::EndCombo(); } - if (options.labelPosition != LabelPosition::None) { - if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Near) { + if (options.labelPosition != LabelPositions::None) { + if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Near) { ImGui::SameLine(); ImGui::Text("%s", label); - } else if (options.labelPosition == LabelPosition::Far) { + } else if (options.labelPosition == LabelPositions::Far) { float width = ImGui::CalcTextSize(comboMap.at(*value)).x + ImGui::GetStyle().FramePadding.x * 2; ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); ImGui::Text("%s", label); @@ -625,19 +625,19 @@ namespace UIWidgets { float comboWidth = CalcComboWidth(longest, options.flags); ImGui::AlignTextToFramePadding(); - if (options.labelPosition != LabelPosition::None) { - if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition != LabelPositions::None) { + if (options.alignment == ComponentAlignments::Right) { ImGui::Text("%s", label); - if (options.labelPosition == LabelPosition::Above) { + if (options.labelPosition == LabelPositions::Above) { ImGui::NewLine(); ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); - } else if (options.labelPosition == LabelPosition::Near) { + } else if (options.labelPosition == LabelPositions::Near) { ImGui::SameLine(); - } else if (options.labelPosition == LabelPosition::Far) { + } else if (options.labelPosition == LabelPositions::Far) { ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); } - } else if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Above) { + } else if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Above) { ImGui::Text("%s", label); } } @@ -659,12 +659,12 @@ namespace UIWidgets { ImGui::EndCombo(); } - if (options.labelPosition != LabelPosition::None) { - if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Near) { + if (options.labelPosition != LabelPositions::None) { + if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Near) { ImGui::SameLine(); ImGui::Text("%s", label); - } else if (options.labelPosition == LabelPosition::Far) { + } else if (options.labelPosition == LabelPositions::Far) { float width = ImGui::CalcTextSize(comboVector.at(*value)).x + ImGui::GetStyle().FramePadding.x * 2; ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); ImGui::Text("%s", label); @@ -707,19 +707,19 @@ namespace UIWidgets { float comboWidth = CalcComboWidth(longest, options.flags); ImGui::AlignTextToFramePadding(); - if (options.labelPosition != LabelPosition::None) { - if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition != LabelPositions::None) { + if (options.alignment == ComponentAlignments::Right) { ImGui::Text("%s", label); - if (options.labelPosition == LabelPosition::Above) { + if (options.labelPosition == LabelPositions::Above) { ImGui::NewLine(); ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); - } else if (options.labelPosition == LabelPosition::Near) { + } else if (options.labelPosition == LabelPositions::Near) { ImGui::SameLine(); - } else if (options.labelPosition == LabelPosition::Far) { + } else if (options.labelPosition == LabelPositions::Far) { ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); } - } else if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Above) { + } else if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Above) { ImGui::Text("%s", label); } } @@ -741,12 +741,12 @@ namespace UIWidgets { ImGui::EndCombo(); } - if (options.labelPosition != LabelPosition::None) { - if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Near) { + if (options.labelPosition != LabelPositions::None) { + if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Near) { ImGui::SameLine(); ImGui::Text("%s", label); - } else if (options.labelPosition == LabelPosition::Far) { + } else if (options.labelPosition == LabelPositions::Far) { float width = ImGui::CalcTextSize(comboVector.at(*value).c_str()).x + ImGui::GetStyle().FramePadding.x * 2; ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); ImGui::Text("%s", label); @@ -792,19 +792,19 @@ namespace UIWidgets { float comboWidth = CalcComboWidth(longest, options.flags); ImGui::AlignTextToFramePadding(); - if (options.labelPosition != LabelPosition::None) { - if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition != LabelPositions::None) { + if (options.alignment == ComponentAlignments::Right) { ImGui::Text("%s", label); - if (options.labelPosition == LabelPosition::Above) { + if (options.labelPosition == LabelPositions::Above) { ImGui::NewLine(); ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); - } else if (options.labelPosition == LabelPosition::Near) { + } else if (options.labelPosition == LabelPositions::Near) { ImGui::SameLine(); - } else if (options.labelPosition == LabelPosition::Far) { + } else if (options.labelPosition == LabelPositions::Far) { ImGui::SameLine(ImGui::GetContentRegionAvail().x - comboWidth); } - } else if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Above) { + } else if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Above) { ImGui::Text("%s", label); } } @@ -826,12 +826,12 @@ namespace UIWidgets { ImGui::EndCombo(); } - if (options.labelPosition != LabelPosition::None) { - if (options.alignment == ComponentAlignment::Left) { - if (options.labelPosition == LabelPosition::Near) { + if (options.labelPosition != LabelPositions::None) { + if (options.alignment == ComponentAlignments::Left) { + if (options.labelPosition == LabelPositions::Near) { ImGui::SameLine(); ImGui::Text("%s", label); - } else if (options.labelPosition == LabelPosition::Far) { + } else if (options.labelPosition == LabelPositions::Far) { float width = ImGui::CalcTextSize(comboArray[*value]).x + ImGui::GetStyle().FramePadding.x * 2; ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); ImGui::Text("%s", label); diff --git a/soh/soh/z_scene_otr.cpp b/soh/soh/z_scene_otr.cpp index 022aeee37..f03db52ba 100644 --- a/soh/soh/z_scene_otr.cpp +++ b/soh/soh/z_scene_otr.cpp @@ -150,17 +150,13 @@ bool Scene_CommandObjectList(PlayState* play, SOH::ISceneCommand* cmd) { s32 i; s32 j; s32 k; - ObjectStatus* status; ObjectStatus* status2; - ObjectStatus* firstStatus; // s16* objectEntry = SEGMENTED_TO_VIRTUAL(cmd->objectList.segment); s16* objectEntry = (s16*)cmdObj->GetRawPointer(); void* nextPtr; k = 0; i = play->objectCtx.unk_09; - firstStatus = &play->objectCtx.status[0]; - status = &play->objectCtx.status[i]; // Loop until a mismatch in the object lists // Then clear all object ids past that in the context object list and kill actors for those objects From 70fc060381a0561dde5472becf0c74bdd11128f1 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Wed, 19 Mar 2025 15:03:15 -0400 Subject: [PATCH 32/53] Fix Forest Stage upgrades with Skip Misc Interactions (#5052) * Fix Forest Stage upgrades with Skip Misc Interactions * Explicit upgrade checks --- soh/soh/Enhancements/timesaver_hook_handlers.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/soh/soh/Enhancements/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/timesaver_hook_handlers.cpp index eee2a0e9b..7acf51a6d 100644 --- a/soh/soh/Enhancements/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/timesaver_hook_handlers.cpp @@ -1106,12 +1106,18 @@ void TimeSaverOnFlagSetHandler(int16_t flagType, int16_t flag) { break; case FLAG_ITEM_GET_INF: switch (flag) { - case ITEMGETINF_OBTAINED_STICK_UPGRADE_FROM_STAGE: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_DEKU_STICK_CAPACITY_30).GetGIEntry_Copy(); + case ITEMGETINF_OBTAINED_STICK_UPGRADE_FROM_STAGE: { + RandomizerGet stickUpgrade = + CUR_UPG_VALUE(UPG_STICKS) == 2 ? RG_DEKU_STICK_CAPACITY_30 : RG_DEKU_STICK_CAPACITY_20; + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(stickUpgrade).GetGIEntry_Copy(); break; - case ITEMGETINF_OBTAINED_NUT_UPGRADE_FROM_STAGE: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_DEKU_NUT_CAPACITY_40).GetGIEntry_Copy(); + } + case ITEMGETINF_OBTAINED_NUT_UPGRADE_FROM_STAGE: { + RandomizerGet nutUpgrade = + CUR_UPG_VALUE(UPG_NUTS) == 2 ? RG_DEKU_NUT_CAPACITY_40 : RG_DEKU_NUT_CAPACITY_30; + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(nutUpgrade).GetGIEntry_Copy(); break; + } } break; } From a6ff511af5b2e35660bfef704078ece210bc3f60 Mon Sep 17 00:00:00 2001 From: Archez Date: Wed, 19 Mar 2025 16:53:46 -0400 Subject: [PATCH 33/53] Exclude zora sapphire from random enemy size (#5153) --- soh/soh/Enhancements/mods.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index cfcb98fa2..e95c19637 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -810,8 +810,10 @@ void RegisterRandomizedEnemySizes() { Actor* actor = static_cast(refActor); // Exclude wobbly platforms in Jabu because they need to act like platforms. + // Exclude demo effect for Zora sapphire being re-categorized as a "boss". // Exclude Dead Hand hands and Bongo Bongo main body because they make the fights (near) impossible. - uint8_t excludedEnemy = actor->id == ACTOR_EN_BROB || actor->id == ACTOR_EN_DHA || (actor->id == ACTOR_BOSS_SST && actor->params == -1); + uint8_t excludedEnemy = actor->id == ACTOR_EN_BROB || actor->id == ACTOR_EN_DHA || + actor->id == ACTOR_DEMO_EFFECT || (actor->id == ACTOR_BOSS_SST && actor->params == -1); // Dodongo, Volvagia and Dead Hand are always smaller because they're impossible when bigger. uint8_t smallOnlyEnemy = actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD || From 79b53e5920cd6b34d80746c619005e586347c243 Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Wed, 19 Mar 2025 23:52:16 +0100 Subject: [PATCH 34/53] Change `Compass Colors Match Dungeon` to `Map & Compass Colors Match Dungeon` (#5042) * Colored maps & compasses with shipinit * Update SohMenuBar.cpp * Post-merge fixes * Whoops --- .../game-interactor/GameInteractor.h | 2 +- .../game-interactor/GameInteractor_Hooks.h | 1 + .../vanilla-behavior/GIVanillaBehavior.h | 37 +++++++++------- soh/soh/Enhancements/mods.cpp | 22 ---------- soh/soh/Enhancements/mods.h | 3 +- .../randomizer/ColoredMapsAndCompasses.cpp | 43 +++++++++++++++++++ soh/soh/Enhancements/randomizer/draw.cpp | 39 ++++++++++++----- soh/soh/Enhancements/randomizer/draw.h | 1 + soh/soh/Enhancements/randomizer/item_list.cpp | 10 +++++ soh/soh/SohGui/SohMenuRandomizer.cpp | 14 +++--- 10 files changed, 115 insertions(+), 57 deletions(-) create mode 100644 soh/soh/Enhancements/randomizer/ColoredMapsAndCompasses.cpp diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 4d8e6dcb4..db68ad86a 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -4,8 +4,8 @@ #define GameInteractor_h #include "libultraship/libultraship.h" -#include "GameInteractionEffect.h" #include "vanilla-behavior/GIVanillaBehavior.h" +#include "GameInteractionEffect.h" #include "soh/Enhancements/item-tables/ItemTableTypes.h" #include diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index f1b918cef..d20864012 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -1,5 +1,6 @@ #pragma once +#include "vanilla-behavior/GIVanillaBehavior.h" #include "GameInteractor.h" #include diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index a95dabb00..3b3ad4cb4 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -1,3 +1,8 @@ +#pragma once + +#ifndef GI_VANILLA_BEHAVIOR_H +#define GI_VANILLA_BEHAVIOR_H + typedef enum { // #### `result` // ```c @@ -95,10 +100,10 @@ typedef enum { // #### `args` // - `*BgDyYoseizo` VB_BE_ELIGIBLE_FOR_GREAT_FAIRY_REWARD, - + // #### `result` // ```c - // CHECK_QUEST_ITEM(QUEST_MEDALLION_SPIRIT) && + // CHECK_QUEST_ITEM(QUEST_MEDALLION_SPIRIT) && // CHECK_QUEST_ITEM(QUEST_MEDALLION_SHADOW) && // LINK_IS_ADULT && // !Flags_GetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS) && @@ -118,11 +123,11 @@ typedef enum { // #### `result` // ```c - // (gSaveContext.entranceIndex == ENTR_KAKARIKO_VILLAGE_FRONT_GATE) && + // (gSaveContext.entranceIndex == ENTR_KAKARIKO_VILLAGE_FRONT_GATE) && // LINK_IS_ADULT && - // Flags_GetEventChkInf(EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP) && + // Flags_GetEventChkInf(EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP) && // Flags_GetEventChkInf(EVENTCHKINF_USED_FIRE_TEMPLE_BLUE_WARP) && - // Flags_GetEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP) && + // Flags_GetEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP) && // !Flags_GetEventChkInf(EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL) // ``` // #### `args` @@ -473,7 +478,7 @@ typedef enum { // and // ```c // EnGe2_CheckCarpentersFreed() - // ``` + // ``` // #### `args` // - None VB_GERUDOS_BE_FRIENDLY, @@ -1268,7 +1273,7 @@ typedef enum { // #### `result` // Close enough & various cutscene checks // ```c - // (func_80AEC5FC(this, play)) && (!Play_InCsMode(play)) && + // (func_80AEC5FC(this, play)) && (!Play_InCsMode(play)) && // (!(player->stateFlags1 & (PLAYER_STATE1_HANGING_OFF_LEDGE | PLAYER_STATE1_CLIMBING_LEDGE | PLAYER_STATE1_CLIMBING_LADDER))) && // (player->actor.bgCheckFlags & 1) // ``` @@ -1697,25 +1702,25 @@ typedef enum { // #### `args` // ##### In `z_boss_dodongo.c`: // - `*BossDodongo` - // + // // ##### In `z_boss_fd2.c` // - `*BossFd2` - // + // // ##### In `z_boss_ganondrof.c`: // - `*BossGanondrof` - // + // // ##### In `z_boss_goma.c`: // - `*BossGoma` - // + // // ##### In `z_boss_mo.c`: // - `*BossMo` - // + // // ##### In `z_boss_sst.c`: // - `*BossSst` - // + // // ##### In `z_boss_tw.c`: // - `*BossTw` - // + // // ##### In `z_boss_va.c`: // - `*BossVa` VB_SPAWN_BLUE_WARP, @@ -1860,7 +1865,7 @@ typedef enum { // #### `args` // - `*EnMk` VB_USE_EYEDROP_DIALOGUE, - + // #### `result` // ```c // true @@ -1869,3 +1874,5 @@ typedef enum { // - `*EnWonderTalk2` VB_WONDER_TALK, } GIVanillaBehavior; + +#endif diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index e95c19637..38de50575 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -16,7 +16,6 @@ #include "soh/Enhancements/timesaver_hook_handlers.h" #include "soh/Enhancements/TimeSavers/TimeSavers.h" #include "soh/Enhancements/randomizer/hook_handlers.h" -#include "objects/object_gi_compass/object_gi_compass.h" #include "src/overlays/actors/ovl_En_Bb/z_en_bb.h" #include "src/overlays/actors/ovl_En_Dekubaba/z_en_dekubaba.h" @@ -1000,26 +999,6 @@ void RegisterPauseMenuHooks() { }); } -extern "C" u8 Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey); - -void PatchCompasses() { - s8 compassesCanBeOutsideDungeon = IS_RANDO && DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(RSK_SHUFFLE_MAPANDCOMPASS); - s8 isColoredCompassesEnabled = compassesCanBeOutsideDungeon && CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("MatchCompassColors"), 1); - if (isColoredCompassesEnabled) { - ResourceMgr_PatchGfxByName(gGiCompassDL, "Compass_PrimColor", 5, gsDPNoOp()); - ResourceMgr_PatchGfxByName(gGiCompassDL, "Compass_EnvColor", 6, gsDPNoOp()); - } else { - ResourceMgr_UnpatchGfxByName(gGiCompassDL, "Compass_PrimColor"); - ResourceMgr_UnpatchGfxByName(gGiCompassDL, "Compass_EnvColor"); - } -} - -void RegisterRandomizerCompasses() { - GameInteractor::Instance->RegisterGameHook([](int32_t _unused) { - PatchCompasses(); - }); -} - void RegisterCustomSkeletons() { static int8_t previousTunic = -1; @@ -1067,7 +1046,6 @@ void InitMods() { RegisterRandomizedEnemySizes(); RegisterOpenAllHours(); RegisterToTMedallions(); - RegisterRandomizerCompasses(); NameTag_RegisterHooks(); RegisterFloorSwitchesHook(); RegisterPatchHandHandler(); diff --git a/soh/soh/Enhancements/mods.h b/soh/soh/Enhancements/mods.h index 930522250..8ada99f1e 100644 --- a/soh/soh/Enhancements/mods.h +++ b/soh/soh/Enhancements/mods.h @@ -11,12 +11,11 @@ void UpdateDirtPathFixState(int32_t sceneNum); void UpdateMirrorModeState(int32_t sceneNum); void UpdateHurtContainerModeState(bool newState); void PatchToTMedallions(); -void PatchCompasses(); void UpdatePermanentHeartLossState(); void UpdateHyperEnemiesState(); void UpdateHyperBossesState(); void InitMods(); -void UpdatePatchHand(); +void UpdatePatchHand(); void SwitchAge(); #ifdef __cplusplus diff --git a/soh/soh/Enhancements/randomizer/ColoredMapsAndCompasses.cpp b/soh/soh/Enhancements/randomizer/ColoredMapsAndCompasses.cpp new file mode 100644 index 000000000..fcfa13d97 --- /dev/null +++ b/soh/soh/Enhancements/randomizer/ColoredMapsAndCompasses.cpp @@ -0,0 +1,43 @@ +#include +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ResourceManagerHelpers.h" +#include "soh/ShipInit.hpp" +#include "z64save.h" +#include "objects/object_gi_compass/object_gi_compass.h" +#include "objects/object_gi_map/object_gi_map.h" + +extern "C" { + extern SaveContext gSaveContext; + #include "variables.h" + #include "macros.h" + u8 Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey); +} + +#define CVAR_COLORED_MAPS_AND_COMPASSES_NAME CVAR_RANDOMIZER_ENHANCEMENT("ColoredMapsAndCompasses") +#define CVAR_COLORED_MAPS_AND_COMPASSES_DEFAULT 1 +#define CVAR_COLORED_MAPS_AND_COMPASSES_VALUE CVarGetInteger(CVAR_COLORED_MAPS_AND_COMPASSES_NAME, CVAR_COLORED_MAPS_AND_COMPASSES_DEFAULT) + +void OnLoadFileColoredMapsAndCompasses(int32_t _) { + s8 mapsAndCompassesCanBeOutsideDungeon = IS_RANDO && DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(RSK_SHUFFLE_MAPANDCOMPASS); + s8 isColoredMapsAndCompassesEnabled = mapsAndCompassesCanBeOutsideDungeon && CVAR_COLORED_MAPS_AND_COMPASSES_VALUE; + if (isColoredMapsAndCompassesEnabled) { + ResourceMgr_PatchGfxByName(gGiDungeonMapDL, "Map_PrimColor", 5, gsDPNoOp()); + ResourceMgr_PatchGfxByName(gGiDungeonMapDL, "Map_EnvColor", 6, gsDPNoOp()); + ResourceMgr_PatchGfxByName(gGiCompassDL, "Compass_PrimColor", 5, gsDPNoOp()); + ResourceMgr_PatchGfxByName(gGiCompassDL, "Compass_EnvColor", 6, gsDPNoOp()); + } else { + ResourceMgr_UnpatchGfxByName(gGiDungeonMapDL, "Map_PrimColor"); + ResourceMgr_UnpatchGfxByName(gGiDungeonMapDL, "Map_EnvColor"); + ResourceMgr_UnpatchGfxByName(gGiCompassDL, "Compass_PrimColor"); + ResourceMgr_UnpatchGfxByName(gGiCompassDL, "Compass_EnvColor"); + } +} + +void RegisterColoredMapsAndCompasses() { + COND_HOOK(OnLoadFile, CVAR_COLORED_MAPS_AND_COMPASSES_VALUE, OnLoadFileColoredMapsAndCompasses) + + //Also need to call it directly to patch/unpatch on cvar change + OnLoadFileColoredMapsAndCompasses(0); +} + +static RegisterShipInitFunc initFunc(RegisterColoredMapsAndCompasses, { CVAR_COLORED_MAPS_AND_COMPASSES_NAME }); diff --git a/soh/soh/Enhancements/randomizer/draw.cpp b/soh/soh/Enhancements/randomizer/draw.cpp index 718d91f0f..73b4142de 100644 --- a/soh/soh/Enhancements/randomizer/draw.cpp +++ b/soh/soh/Enhancements/randomizer/draw.cpp @@ -11,6 +11,7 @@ #include "objects/object_gi_key/object_gi_key.h" #include "objects/object_gi_bosskey/object_gi_bosskey.h" #include "objects/object_gi_compass/object_gi_compass.h" +#include "objects/object_gi_map/object_gi_map.h" #include "objects/object_gi_hearts/object_gi_hearts.h" #include "objects/object_gi_scale/object_gi_scale.h" #include "objects/object_gi_fire/object_gi_fire.h" @@ -131,15 +132,35 @@ extern "C" void Randomizer_DrawSmallKey(PlayState* play, GetItemEntry* getItemEn CLOSE_DISPS(play->state.gfxCtx); } -extern "C" { - void GetItem_DrawCompass(PlayState* play, s16 drawId); - void ResourceMgr_PatchGfxByName(const char* path, const char* patchName, int index, Gfx instruction); - void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName); +extern "C" void Randomizer_DrawMap(PlayState* play, GetItemEntry* getItemEntry) { + s16 color_slot = getItemEntry->drawItemId - RG_DEKU_TREE_MAP; + s16 colors[12][3] = { + { 4, 100, 46 }, // Deku Tree + { 140, 30, 30 }, // Dodongo's Cavern + { 30, 60, 255 }, // Jabu Jabu's Belly + { 4, 195, 46 }, // Forest Temple + { 237, 95, 95 }, // Fire Temple + { 85, 180, 223 }, // Water Temple + { 222, 158, 47 }, // Spirit Temple + { 126, 16, 177 }, // Shadow Temple + { 227, 110, 255 }, // Bottom of the Well + { 0, 255, 255 }, // Ice Cavern + }; + + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL_25Opa(play->state.gfxCtx); + gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), G_MTX_MODELVIEW | G_MTX_LOAD); + + gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, colors[color_slot][0], colors[color_slot][1], colors[color_slot][2], 255); + + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gGiDungeonMapDL); + + CLOSE_DISPS(play->state.gfxCtx); } extern "C" void Randomizer_DrawCompass(PlayState* play, GetItemEntry* getItemEntry) { - - s16 color_slot = getItemEntry->getItemId - RG_DEKU_TREE_COMPASS; + s16 color_slot = getItemEntry->drawItemId - RG_DEKU_TREE_COMPASS; s16 colors[12][3] = { { 4, 100, 46 }, // Deku Tree { 140, 30, 30 }, // Dodongo's Cavern @@ -158,8 +179,7 @@ extern "C" void Randomizer_DrawCompass(PlayState* play, GetItemEntry* getItemEnt OPEN_DISPS(play->state.gfxCtx); Gfx_SetupDL_25Opa(play->state.gfxCtx); - gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), - G_MTX_MODELVIEW | G_MTX_LOAD); + gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), G_MTX_MODELVIEW | G_MTX_LOAD); gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, colors[color_slot][0], colors[color_slot][1], colors[color_slot][2], 255); gDPSetEnvColor(POLY_OPA_DISP++, colors[color_slot][0] / 2, colors[color_slot][1] / 2, colors[color_slot][2] / 2, 255); @@ -167,8 +187,7 @@ extern "C" void Randomizer_DrawCompass(PlayState* play, GetItemEntry* getItemEnt gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gGiCompassDL); POLY_XLU_DISP = Gfx_SetupDL(POLY_XLU_DISP, 5); - gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), - G_MTX_MODELVIEW | G_MTX_LOAD); + gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), G_MTX_MODELVIEW | G_MTX_LOAD); gSPDisplayList(POLY_XLU_DISP++, (Gfx*)gGiCompassGlassDL); CLOSE_DISPS(play->state.gfxCtx); diff --git a/soh/soh/Enhancements/randomizer/draw.h b/soh/soh/Enhancements/randomizer/draw.h index 3fc5c86b4..aaac71e06 100644 --- a/soh/soh/Enhancements/randomizer/draw.h +++ b/soh/soh/Enhancements/randomizer/draw.h @@ -10,6 +10,7 @@ typedef struct PlayState PlayState; extern "C" { #endif void Randomizer_DrawSmallKey(PlayState* play, GetItemEntry* getItemEntry); +void Randomizer_DrawMap(PlayState* play, GetItemEntry* getItemEntry); void Randomizer_DrawCompass(PlayState* play, GetItemEntry* getItemEntry); void Randomizer_DrawKeyRing(PlayState* play, GetItemEntry* getItemEntry); void Randomizer_DrawBossKey(PlayState* play, GetItemEntry* getItemEntry); diff --git a/soh/soh/Enhancements/randomizer/item_list.cpp b/soh/soh/Enhancements/randomizer/item_list.cpp index c31a1c4a6..45516ef18 100644 --- a/soh/soh/Enhancements/randomizer/item_list.cpp +++ b/soh/soh/Enhancements/randomizer/item_list.cpp @@ -98,15 +98,25 @@ void Rando::StaticData::InitItemTable() { itemTable[RG_PRELUDE_OF_LIGHT] = Item(RG_PRELUDE_OF_LIGHT, Text{ "Prelude of Light", "Prélude de la Lumière", "Kantate des Lichts" }, ITEMTYPE_SONG, 0xC0, true, LOGIC_PRELUDE_OF_LIGHT, RHT_PRELUDE_OF_LIGHT, ITEM_SONG_PRELUDE, OBJECT_GI_MELODY, GID_SONG_PRELUDE, 0x78, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_NONE); // Maps and Compasses itemTable[RG_DEKU_TREE_MAP] = Item(RG_DEKU_TREE_MAP, Text{ "Great Deku Tree Map", "Carte de l'Arbre Mojo", "Karte des Deku-Baums" }, ITEMTYPE_MAP, 0xA5, false, LOGIC_MAP_DEKU_TREE, RHT_DEKU_TREE_MAP, RG_DEKU_TREE_MAP, OBJECT_GI_MAP, GID_DUNGEON_MAP, TEXT_ITEM_DUNGEON_MAP, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); + itemTable[RG_DEKU_TREE_MAP].SetCustomDrawFunc(Randomizer_DrawMap); itemTable[RG_DODONGOS_CAVERN_MAP] = Item(RG_DODONGOS_CAVERN_MAP, Text{ "Dodongo's Cavern Map", "Carte de la Caverne Dodongo", "Karte der Dodongo-Höhle" }, ITEMTYPE_MAP, 0xA6, false, LOGIC_MAP_DODONGOS_CAVERN, RHT_DODONGOS_CAVERN_MAP, RG_DODONGOS_CAVERN_MAP, OBJECT_GI_MAP, GID_DUNGEON_MAP, TEXT_ITEM_DUNGEON_MAP, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); + itemTable[RG_DODONGOS_CAVERN_MAP].SetCustomDrawFunc(Randomizer_DrawMap); itemTable[RG_JABU_JABUS_BELLY_MAP] = Item(RG_JABU_JABUS_BELLY_MAP, Text{ "Jabu-Jabu's Belly Map", "Carte du Ventre de Jabu-Jabu", "Karte des Jabu-Jabu-Bauchs" }, ITEMTYPE_MAP, 0xA7, false, LOGIC_MAP_JABU_JABUS_BELLY, RHT_JABU_JABUS_BELLY_MAP, RG_JABU_JABUS_BELLY_MAP, OBJECT_GI_MAP, GID_DUNGEON_MAP, TEXT_ITEM_DUNGEON_MAP, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); + itemTable[RG_JABU_JABUS_BELLY_MAP].SetCustomDrawFunc(Randomizer_DrawMap); itemTable[RG_FOREST_TEMPLE_MAP] = Item(RG_FOREST_TEMPLE_MAP, Text{ "Forest Temple Map", "Carte du Temple de la Forêt", "Karte des Waldtempels" }, ITEMTYPE_MAP, 0xA8, false, LOGIC_MAP_FOREST_TEMPLE, RHT_FOREST_TEMPLE_MAP, RG_FOREST_TEMPLE_MAP, OBJECT_GI_MAP, GID_DUNGEON_MAP, TEXT_ITEM_DUNGEON_MAP, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); + itemTable[RG_FOREST_TEMPLE_MAP].SetCustomDrawFunc(Randomizer_DrawMap); itemTable[RG_FIRE_TEMPLE_MAP] = Item(RG_FIRE_TEMPLE_MAP, Text{ "Fire Temple Map", "Carte due Temple de Feu", "Karte des Feuertempels" }, ITEMTYPE_MAP, 0xA9, false, LOGIC_MAP_FIRE_TEMPLE, RHT_FIRE_TEMPLE_MAP, RG_FIRE_TEMPLE_MAP, OBJECT_GI_MAP, GID_DUNGEON_MAP, TEXT_ITEM_DUNGEON_MAP, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); + itemTable[RG_FIRE_TEMPLE_MAP].SetCustomDrawFunc(Randomizer_DrawMap); itemTable[RG_WATER_TEMPLE_MAP] = Item(RG_WATER_TEMPLE_MAP, Text{ "Water Temple Map", "Carte du Temple de l'Eau", "Karte des Wassertempels" }, ITEMTYPE_MAP, 0xAA, false, LOGIC_MAP_WATER_TEMPLE, RHT_WATER_TEMPLE_MAP, RG_WATER_TEMPLE_MAP, OBJECT_GI_MAP, GID_DUNGEON_MAP, TEXT_ITEM_DUNGEON_MAP, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); + itemTable[RG_WATER_TEMPLE_MAP].SetCustomDrawFunc(Randomizer_DrawMap); itemTable[RG_SPIRIT_TEMPLE_MAP] = Item(RG_SPIRIT_TEMPLE_MAP, Text{ "Spirit Temple Map", "Carte due Temple de l'Esprit", "Karte des Geistertempels" }, ITEMTYPE_MAP, 0xAB, false, LOGIC_MAP_SPIRIT_TEMPLE, RHT_SPIRIT_TEMPLE_MAP, RG_SPIRIT_TEMPLE_MAP, OBJECT_GI_MAP, GID_DUNGEON_MAP, TEXT_ITEM_DUNGEON_MAP, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); + itemTable[RG_SPIRIT_TEMPLE_MAP].SetCustomDrawFunc(Randomizer_DrawMap); itemTable[RG_SHADOW_TEMPLE_MAP] = Item(RG_SHADOW_TEMPLE_MAP, Text{ "Shadow Temple Map", "Carte du Temple de l'Ombre", "Karte des Schattentempels" }, ITEMTYPE_MAP, 0xAC, false, LOGIC_MAP_SHADOW_TEMPLE, RHT_SHADOW_TEMPLE_MAP, RG_SHADOW_TEMPLE_MAP, OBJECT_GI_MAP, GID_DUNGEON_MAP, TEXT_ITEM_DUNGEON_MAP, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); + itemTable[RG_SHADOW_TEMPLE_MAP].SetCustomDrawFunc(Randomizer_DrawMap); itemTable[RG_BOTTOM_OF_THE_WELL_MAP] = Item(RG_BOTTOM_OF_THE_WELL_MAP, Text{ "Bottom of the Well Map", "Carte du Puits", "Karte des Grund des Brunnens" }, ITEMTYPE_MAP, 0xAD, false, LOGIC_MAP_BOTTOM_OF_THE_WELL, RHT_BOTTOM_OF_THE_WELL_MAP, RG_BOTTOM_OF_THE_WELL_MAP, OBJECT_GI_MAP, GID_DUNGEON_MAP, TEXT_ITEM_DUNGEON_MAP, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); + itemTable[RG_BOTTOM_OF_THE_WELL_MAP].SetCustomDrawFunc(Randomizer_DrawMap); itemTable[RG_ICE_CAVERN_MAP] = Item(RG_ICE_CAVERN_MAP, Text{ "Ice Cavern Map", "Carte de la Caverne Polaire", "Karte der Eishöhle" }, ITEMTYPE_MAP, 0xAE, false, LOGIC_MAP_ICE_CAVERN, RHT_ICE_CAVERN_MAP, RG_ICE_CAVERN_MAP, OBJECT_GI_MAP, GID_DUNGEON_MAP, TEXT_ITEM_DUNGEON_MAP, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); + itemTable[RG_ICE_CAVERN_MAP].SetCustomDrawFunc(Randomizer_DrawMap); itemTable[RG_DEKU_TREE_COMPASS] = Item(RG_DEKU_TREE_COMPASS, Text{ "Great Deku Tree Compass", "Boussole de l'Arbre Mojo", "Kompaß des Deku-Baums" }, ITEMTYPE_COMPASS, 0x9B, false, LOGIC_COMPASS_DEKU_TREE, RHT_DEKU_TREE_COMPASS, RG_DEKU_TREE_COMPASS, OBJECT_GI_COMPASS, GID_COMPASS, TEXT_ITEM_DUNGEON_MAP, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); itemTable[RG_DEKU_TREE_COMPASS].SetCustomDrawFunc(Randomizer_DrawCompass); itemTable[RG_DODONGOS_CAVERN_COMPASS] = Item(RG_DODONGOS_CAVERN_COMPASS, Text{ "Dodongo's Cavern Compass", "Boussole de la Caverne Dodongo", "Kompaß der Dodongo-Höhle" }, ITEMTYPE_COMPASS, 0x9C, false, LOGIC_COMPASS_DODONGOS_CAVERN, RHT_DODONGOS_CAVERN_COMPASS, RG_DODONGOS_CAVERN_COMPASS, OBJECT_GI_COMPASS, GID_COMPASS, TEXT_ITEM_COMPASS, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); diff --git a/soh/soh/SohGui/SohMenuRandomizer.cpp b/soh/soh/SohGui/SohMenuRandomizer.cpp index a6649e3ea..0d3b52b23 100644 --- a/soh/soh/SohGui/SohMenuRandomizer.cpp +++ b/soh/soh/SohGui/SohMenuRandomizer.cpp @@ -37,21 +37,21 @@ void SohMenu::AddMenuRandomizer() { .Options(CheckboxOptions().Tooltip( "Use Custom graphics for Dungeon Keys, Big and Small, so that they can be easily told apart." ).DefaultValue(true)); - AddWidget(path, "Compass Colors Match Dungeon", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_RANDOMIZER_ENHANCEMENT("MatchCompassColors")) + AddWidget(path, "Map & Compass Colors Match Dungeon", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("ColoredMapsAndCompasses")) .PreFunc([](WidgetInfo& info) { info.options->disabled = !( OTRGlobals::Instance->gRandoContext->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).IsNot(RO_DUNGEON_ITEM_LOC_STARTWITH) && OTRGlobals::Instance->gRandoContext->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).IsNot(RO_DUNGEON_ITEM_LOC_VANILLA) && OTRGlobals::Instance->gRandoContext->GetOption(RSK_SHUFFLE_MAPANDCOMPASS).IsNot(RO_DUNGEON_ITEM_LOC_OWN_DUNGEON) ); - info.options->disabledTooltip = "This setting is disabled because a savefile is loaded without " - "the compass shuffle settings set to Any Dungeon, Overworld, or Anywhere."; + info.options->disabledTooltip = "This setting is disabled because a savefile is loaded without the map & compass\n" + "shuffle settings set to \"Any Dungeon\", \"Overworld\" or \"Anywhere\""; }) .Options(CheckboxOptions().Tooltip( - "Matches the color of compasses to the dungeon they belong to. " - "This helps identify compasses from afar and adds a little bit of flair.\n\nThis only " - "applies to seeds with compasses shuffled to \"Any Dungeon\", \"Overworld\", or \"Anywhere\"." + "Matches the color of maps & compasses to the dungeon they belong to. " + "This helps identify maps & compasses from afar and adds a little bit of flair.\n\nThis only " + "applies to seeds with maps & compasses shuffled to \"Any Dungeon\", \"Overworld\", or \"Anywhere\"." ).DefaultValue(true)); AddWidget(path, "Quest Item Fanfares", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_RANDOMIZER_ENHANCEMENT("QuestItemFanfares")) From 4243eb67ae6152c0760209a0d29b488cbe47d92d Mon Sep 17 00:00:00 2001 From: aMannus Date: Wed, 19 Mar 2025 23:52:33 +0100 Subject: [PATCH 35/53] Exclude nabooru from boss souls (#5152) --- soh/soh/Enhancements/randomizer/hook_handlers.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index 54b658e68..a4db9f428 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -2035,11 +2035,13 @@ void RandomizerOnActorInitHandler(void* actorRef) { break; } - //Deletes all actors in the boss category if the soul isn't found. - //Some actors, like Dark Link, Arwings, and Zora's Sapphire...?, are in this category despite not being actual bosses, - //so ignore any "boss" if `currentBossSoulRandInf` doesn't change from RAND_INF_MAX. + // Deletes all actors in the boss category if the soul isn't found. + // Some actors, like Dark Link, Arwings, and Zora's Sapphire...?, are in this category despite not being actual bosses, + // so ignore any "boss" if `currentBossSoulRandInf` doesn't change from RAND_INF_MAX. + // Iron Knuckle (Nabooru) in Twinrova's room is a special exception, so exclude knuckles too. if (currentBossSoulRandInf != RAND_INF_MAX) { - if (!Flags_GetRandomizerInf(currentBossSoulRandInf) && actor->category == ACTORCAT_BOSS) { + if (!Flags_GetRandomizerInf(currentBossSoulRandInf) && actor->category == ACTORCAT_BOSS && + actor->id != ACTOR_EN_IK) { Actor_Delete(&gPlayState->actorCtx, actor, gPlayState); } //Special case for Phantom Ganon's horse (and fake), as they're considered "background actors", From 6ad503e1d2171e8e102d77eff6dbd70bd52f970a Mon Sep 17 00:00:00 2001 From: Malkierian Date: Wed, 19 Mar 2025 15:57:08 -0700 Subject: [PATCH 36/53] Adds a modal registration for pressing F1 and confirming Quit from the menu button. (#5154) Fixes near shortcuts gossip stone name to include "grotto" to signify it's in the grotto. --- .../Enhancements/randomizer/location_list.cpp | 2 +- soh/soh/OTRGlobals.cpp | 5 +++++ soh/soh/SohGui/Menu.cpp | 20 ++++++++++++++----- soh/soh/SohGui/Menu.h | 1 + 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/location_list.cpp b/soh/soh/Enhancements/randomizer/location_list.cpp index d828bad47..116b7d709 100644 --- a/soh/soh/Enhancements/randomizer/location_list.cpp +++ b/soh/soh/Enhancements/randomizer/location_list.cpp @@ -992,7 +992,7 @@ void Rando::StaticData::InitLocationTable() { // locationTable[RC_HF_OPEN_GROTTO_GOSSIP_STONE] = Location::HintStone(RC_HF_OPEN_GROTTO_GOSSIP_STONE, RCQUEST_BOTH, RCAREA_HYRULE_FIELD, SCENE_GROTTOS, -22947, "Open Grotto Gossip Stone"); locationTable[RC_KAK_OPEN_GROTTO_GOSSIP_STONE] = Location::HintStone(RC_KAK_OPEN_GROTTO_GOSSIP_STONE, RCQUEST_BOTH, RCAREA_KAKARIKO_VILLAGE, SCENE_GROTTOS, -22984, "Open Grotto Gossip Stone"); locationTable[RC_ZR_OPEN_GROTTO_GOSSIP_STONE] = Location::HintStone(RC_ZR_OPEN_GROTTO_GOSSIP_STONE, RCQUEST_BOTH, RCAREA_ZORAS_RIVER, SCENE_GROTTOS, -22985, "Open Grotto Gossip Stone"); - locationTable[RC_LW_NEAR_SHORTCUTS_GROTTO_GOSSIP_STONE] = Location::HintStone(RC_LW_NEAR_SHORTCUTS_GROTTO_GOSSIP_STONE, RCQUEST_BOTH, RCAREA_LOST_WOODS, SCENE_GROTTOS, -22964, "Near Shortcuts Gossip Stone"); + locationTable[RC_LW_NEAR_SHORTCUTS_GROTTO_GOSSIP_STONE] = Location::HintStone(RC_LW_NEAR_SHORTCUTS_GROTTO_GOSSIP_STONE, RCQUEST_BOTH, RCAREA_LOST_WOODS, SCENE_GROTTOS, -22964, "Near Shortcuts Grotto Gossip Stone"); locationTable[RC_DMT_STORMS_GROTTO_GOSSIP_STONE] = Location::HintStone(RC_DMT_STORMS_GROTTO_GOSSIP_STONE, RCQUEST_BOTH, RCAREA_DEATH_MOUNTAIN_TRAIL, SCENE_GROTTOS, -23255, "Storms Grotto Gossip Stone"); locationTable[RC_DMC_UPPER_GROTTO_GOSSIP_STONE] = Location::HintStone(RC_DMC_UPPER_GROTTO_GOSSIP_STONE, RCQUEST_BOTH, RCAREA_DEATH_MOUNTAIN_CRATER, SCENE_GROTTOS, -23802, "Upper Grotto Gossip Stone"); diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index ed5a1169d..5d2310e1b 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1293,6 +1293,11 @@ extern "C" void Graph_StartFrame() { OTRGlobals::Instance->context->GetWindow()->SetLastScancode(-1); switch (dwScancode) { + case KbScancode::LUS_KB_F1: { + std::shared_ptr modal = static_pointer_cast(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Modal Window")); + modal->RegisterPopup("Menu Moved", "The menubar, accessed by hitting F1, no longer exists.\nThe new menu can be accessed by hitting the Esc button instead.", "OK"); + break; + } case KbScancode::LUS_KB_F5: { if (CVarGetInteger(CVAR_CHEAT("SaveStatesEnabled"), 0) == 0) { Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGameOverlay()-> diff --git a/soh/soh/SohGui/Menu.cpp b/soh/soh/SohGui/Menu.cpp index dc7970bc1..0e8f8ab51 100644 --- a/soh/soh/SohGui/Menu.cpp +++ b/soh/soh/SohGui/Menu.cpp @@ -4,6 +4,7 @@ #include "soh/Enhancements/controls/SohInputEditorWindow.h" #include "window/gui/GuiMenuBar.h" #include "window/gui/GuiElement.h" +#include "SohModals.h" #include #include #include "variables.h" @@ -19,7 +20,9 @@ std::vector windowTypeSizes = { {} }; extern std::unordered_map warpPointSceneList; extern void Warp(); -namespace SohGui {} +namespace SohGui { +extern std::shared_ptr mModalWindow; +} namespace Ship { std::string disabledTempTooltip; @@ -104,6 +107,10 @@ void Menu::UpdateWindowBackendObjects() { } } +bool Menu::IsMenuPopped() { + return popped; +} + UIWidgets::Colors Menu::GetMenuThemeColor() { return menuThemeIndex; } @@ -650,10 +657,13 @@ void Menu::DrawElement() { options3.size = UIWidgets::Sizes::Inline; options3.tooltip = "Quit SoH"; if (UIWidgets::Button(ICON_FA_POWER_OFF, options3)) { - if (!popped) { - ToggleVisibility(); - } - Ship::Context::GetInstance()->GetWindow()->Close(); + SohGui::mModalWindow->RegisterPopup("Quit SoH", "Are you sure you want to quit SoH?", "Quit", "Cancel", []() { + std::shared_ptr menu = static_pointer_cast(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetMenu()); + if (!menu->IsMenuPopped()) { + menu->ToggleVisibility(); + } + Ship::Context::GetInstance()->GetWindow()->Close(); + }, nullptr); } ImGui::PopStyleVar(); ImGui::SameLine(); diff --git a/soh/soh/SohGui/Menu.h b/soh/soh/SohGui/Menu.h index d7007bb1e..139f90030 100644 --- a/soh/soh/SohGui/Menu.h +++ b/soh/soh/SohGui/Menu.h @@ -21,6 +21,7 @@ class Menu : public GuiWindow { void InsertSidebarSearch(); void RemoveSidebarSearch(); void UpdateWindowBackendObjects(); + bool IsMenuPopped(); UIWidgets::Colors GetMenuThemeColor(); void MenuDrawItem(WidgetInfo& widget, uint32_t width, UIWidgets::Colors menuThemeIndex); From 818c7e463394f86021ce0ae292818cff2b582ee7 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Wed, 19 Mar 2025 19:03:59 -0400 Subject: [PATCH 37/53] Skip carpenter dialogue before Gerudo mini-boss (#5057) * Skip carpenter dialogue before Gerudo mini-boss * Apparently I'd forgotten the include * IS_RANDO default --- soh/soh/Enhancements/timesaver_hook_handlers.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/soh/soh/Enhancements/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/timesaver_hook_handlers.cpp index 7acf51a6d..5aedb90ec 100644 --- a/soh/soh/Enhancements/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/timesaver_hook_handlers.cpp @@ -23,6 +23,7 @@ extern "C" { #include "src/overlays/actors/ovl_Bg_Ddan_Kd/z_bg_ddan_kd.h" #include "src/overlays/actors/ovl_En_Tk/z_en_tk.h" #include "src/overlays/actors/ovl_En_Fu/z_en_fu.h" +#include "src/overlays/actors/ovl_En_Daiku/z_en_daiku.h" #include "src/overlays/actors/ovl_Bg_Spot02_Objects/z_bg_spot02_objects.h" #include "src/overlays/actors/ovl_Bg_Spot03_Taki/z_bg_spot03_taki.h" #include "src/overlays/actors/ovl_Bg_Hidan_Kousi/z_bg_hidan_kousi.h" @@ -354,6 +355,18 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li !(gPlayState->sceneNum == SCENE_ZORAS_RIVER && IS_RANDO && RAND_GET_OPTION(RSK_FROGS_HINT))) { *should = false; } + + // If it's near a jailed carpenter, skip it along with introduction of Gerudo mini-boss + if (gPlayState->sceneNum == SCENE_THIEVES_HIDEOUT && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { + EnWonderTalk2* enWonderTalk = va_arg(args, EnWonderTalk2*); + EnDaiku* enDaiku = + (EnDaiku*)Actor_FindNearby(gPlayState, &enWonderTalk->actor, ACTOR_EN_DAIKU, ACTORCAT_NPC, 999.0f); + if (enDaiku != NULL) { + Flags_SetSwitch(gPlayState, enDaiku->startFightSwitchFlag); + *should = false; + } + } break; } case VB_NAVI_TALK: { From 224c810e39fb6a77b727867db51e74ec540b350c Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Wed, 19 Mar 2025 19:05:20 -0400 Subject: [PATCH 38/53] Link can move around while carpenters escape (#5055) --- .../vanilla-behavior/GIVanillaBehavior.h | 8 ++++++++ soh/soh/Enhancements/timesaver_hook_handlers.cpp | 13 +++++++++++++ soh/src/overlays/actors/ovl_En_Daiku/z_en_daiku.c | 14 ++++++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index 3b3ad4cb4..c8a0f747c 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -1270,6 +1270,14 @@ typedef enum { // - None VB_PLAY_BOLERO_OF_FIRE_CS, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnDaiku` + VB_PLAY_CARPENTER_FREE_CS, + // #### `result` // Close enough & various cutscene checks // ```c diff --git a/soh/soh/Enhancements/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/timesaver_hook_handlers.cpp index 5aedb90ec..a46ebb814 100644 --- a/soh/soh/Enhancements/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/timesaver_hook_handlers.cpp @@ -43,6 +43,8 @@ extern void BgSpot03Taki_ApplyOpeningAlpha(BgSpot03Taki* bgSpot03Taki, s32 buffe extern void EnGo2_CurledUp(EnGo2* enGo2, PlayState* play); extern void EnRu2_SetEncounterSwitchFlag(EnRu2* enRu2, PlayState* play); + +extern void EnDaiku_EscapeSuccess(EnDaiku* enDaiku, PlayState* play); } #define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get() @@ -564,6 +566,17 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li } break; } + case VB_PLAY_CARPENTER_FREE_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO)) { + EnDaiku* enDaiku = va_arg(args, EnDaiku*); + if (enDaiku->subCamActive) { + enDaiku->subCamActive = false; + EnDaiku_EscapeSuccess(enDaiku, gPlayState); + } + *should = false; + } + break; + } case VB_PLAY_GORON_FREE_CS: { if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { *should = false; diff --git a/soh/src/overlays/actors/ovl_En_Daiku/z_en_daiku.c b/soh/src/overlays/actors/ovl_En_Daiku/z_en_daiku.c index 15c1950aa..d40d25499 100644 --- a/soh/src/overlays/actors/ovl_En_Daiku/z_en_daiku.c +++ b/soh/src/overlays/actors/ovl_En_Daiku/z_en_daiku.c @@ -451,6 +451,10 @@ void EnDaiku_InitSubCamera(EnDaiku* this, PlayState* play) { this->subCamActive = true; this->escapeSubCamTimer = sEscapeSubCamParams[this->actor.params & 3].maxFramesActive; + if (!GameInteractor_Should(VB_PLAY_CARPENTER_FREE_CS, true, this)) { + return; + } + eyePosDeltaLocal.x = sEscapeSubCamParams[this->actor.params & 3].eyePosDeltaLocal.x; eyePosDeltaLocal.y = sEscapeSubCamParams[this->actor.params & 3].eyePosDeltaLocal.y; eyePosDeltaLocal.z = sEscapeSubCamParams[this->actor.params & 3].eyePosDeltaLocal.z; @@ -477,6 +481,10 @@ void EnDaiku_InitSubCamera(EnDaiku* this, PlayState* play) { void EnDaiku_UpdateSubCamera(EnDaiku* this, PlayState* play) { s32 pad; + if (!GameInteractor_Should(VB_PLAY_CARPENTER_FREE_CS, true, this)) { + return; + } + this->subCamAtTarget.x = this->actor.world.pos.x; this->subCamAtTarget.y = this->actor.world.pos.y + 60.0f; this->subCamAtTarget.z = this->actor.world.pos.z; @@ -493,8 +501,10 @@ void EnDaiku_EscapeSuccess(EnDaiku* this, PlayState* play) { Actor* gerudoGuard; Vec3f vec; - Play_ClearCamera(play, this->subCamId); - Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_ACTIVE); + if (GameInteractor_Should(VB_PLAY_CARPENTER_FREE_CS, true, this)) { + Play_ClearCamera(play, this->subCamId); + Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_ACTIVE); + } this->subCamActive = false; if (GET_EVENTCHKINF_CARPENTERS_FREE_ALL()) { From 723375a34450087729de4f7bdd8d31f56feb55ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Thu, 20 Mar 2025 02:19:19 +0000 Subject: [PATCH 39/53] fix type signature of Randomizer_DrawMysteryItem (#5156) --- soh/soh/Enhancements/randomizer/3drando/shops.cpp | 3 +++ soh/soh/Enhancements/randomizer/draw.cpp | 2 +- soh/soh/Enhancements/randomizer/draw.h | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/3drando/shops.cpp b/soh/soh/Enhancements/randomizer/3drando/shops.cpp index edaf8aa5d..75120fa41 100644 --- a/soh/soh/Enhancements/randomizer/3drando/shops.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/shops.cpp @@ -275,6 +275,9 @@ int GetShopsanityReplaceAmount() { return 7; } else if (ctx->GetOption(RSK_SHOPSANITY_COUNT).Is(RO_SHOPSANITY_COUNT_EIGHT_ITEMS)) { return 8; //temporarily unreachable due to logic limitations + } else { + assert(false); + return 0; } } else { //Random, get number in [1, 7] return Random(1, 8); diff --git a/soh/soh/Enhancements/randomizer/draw.cpp b/soh/soh/Enhancements/randomizer/draw.cpp index 73b4142de..9c9cfd829 100644 --- a/soh/soh/Enhancements/randomizer/draw.cpp +++ b/soh/soh/Enhancements/randomizer/draw.cpp @@ -504,7 +504,7 @@ extern "C" void Randomizer_DrawTriforcePieceGI(PlayState* play, GetItemEntry get CLOSE_DISPS(play->state.gfxCtx); } -extern "C" void Randomizer_DrawMysteryItem(PlayState* play, GetItemEntry getItemEntry) { +extern "C" void Randomizer_DrawMysteryItem(PlayState* play, GetItemEntry* getItemEntry) { Color_RGB8 color = { 0, 60, 100 }; if (CVarGetInteger(CVAR_COSMETIC("World.MysteryItem.Changed"), 0)) { color = CVarGetColor24(CVAR_COSMETIC("World.MysteryItem.Value"), color); diff --git a/soh/soh/Enhancements/randomizer/draw.h b/soh/soh/Enhancements/randomizer/draw.h index aaac71e06..22d8466c6 100644 --- a/soh/soh/Enhancements/randomizer/draw.h +++ b/soh/soh/Enhancements/randomizer/draw.h @@ -23,7 +23,7 @@ void Randomizer_DrawOcarinaButton(PlayState* play, GetItemEntry* getItemEntry); void Randomizer_DrawBronzeScale(PlayState* play, GetItemEntry* getItemEntry); void Randomizer_DrawFishingPoleGI(PlayState* play, GetItemEntry* getItemEntry); void Randomizer_DrawSkeletonKey(PlayState* play, GetItemEntry* getItemEntry); -void Randomizer_DrawMysteryItem(PlayState* play, GetItemEntry getItemEntry); +void Randomizer_DrawMysteryItem(PlayState* play, GetItemEntry* getItemEntry); void Randomizer_DrawBombchuBagInLogic(PlayState* play, GetItemEntry* getItemEntry); void Randomizer_DrawBombchuBag(PlayState* play, GetItemEntry* getItemEntry); void Randomizer_DrawOverworldKey(PlayState* play, GetItemEntry* getItemEntry); From b3ed24902151115d6a38e58ceebd1ee6567c4611 Mon Sep 17 00:00:00 2001 From: Christopher Leggett Date: Wed, 19 Mar 2025 23:03:42 -0400 Subject: [PATCH 40/53] Fix Pot Shuffle crash caused by removal of id check (#5158) * Fix Pot Shuffle crash caused by removal of id check. * Properly unregister ShufflePots Hook --- soh/soh/Enhancements/randomizer/hook_handlers.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index a4db9f428..f0baa4c35 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -2389,7 +2389,7 @@ void RandomizerRegisterHooks() { GameInteractor::Instance->UnregisterGameHook(fishsanityOnVanillaBehaviorHook); GameInteractor::Instance->UnregisterGameHook(fishsanityOnItemReceiveHook); - GameInteractor::Instance->UnregisterGameHook(shufflePotsOnActorInitHook); + GameInteractor::Instance->UnregisterGameHookForID(shufflePotsOnActorInitHook); GameInteractor::Instance->UnregisterGameHook(shufflePotsOnVanillaBehaviorHook); GameInteractor::Instance->UnregisterGameHook(shuffleFreestandingOnVanillaBehaviorHook); @@ -2465,7 +2465,7 @@ void RandomizerRegisterHooks() { } if (RAND_GET_OPTION(RSK_SHUFFLE_POTS) != RO_SHUFFLE_POTS_OFF) { - shufflePotsOnActorInitHook = GameInteractor::Instance->RegisterGameHook(ObjTsubo_RandomizerInit); + shufflePotsOnActorInitHook = GameInteractor::Instance->RegisterGameHookForID(ACTOR_OBJ_TSUBO, ObjTsubo_RandomizerInit); shufflePotsOnVanillaBehaviorHook = GameInteractor::Instance->RegisterGameHook(ShufflePots_OnVanillaBehaviorHandler); } From 8958db5c2bb32adcf57bf86049c91aa8f5a5fe54 Mon Sep 17 00:00:00 2001 From: Christopher Leggett Date: Thu, 20 Mar 2025 00:21:31 -0400 Subject: [PATCH 41/53] Fix CVar mismatches for skip forced dialog and unrestricted items. (#5159) --- soh/soh/SohGui/SohMenu.h | 7 +++++++ soh/soh/SohGui/SohMenuEnhancements.cpp | 9 ++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/soh/soh/SohGui/SohMenu.h b/soh/soh/SohGui/SohMenu.h index 3e5f7a555..dacb98936 100644 --- a/soh/soh/SohGui/SohMenu.h +++ b/soh/soh/SohGui/SohMenu.h @@ -67,6 +67,13 @@ static const std::unordered_map dekuStickCheat = { { DEKU_STICK_UNBREAKABLE_AND_ALWAYS_ON_FIRE, "Unbreakable + Always on Fire" } }; +static const std::unordered_map skipForcedDialogOptions = { + { FORCED_DIALOG_SKIP_NONE, "None" }, + { FORCED_DIALOG_SKIP_NAVI, "Navi" }, + { FORCED_DIALOG_SKIP_NPC, "NPCs" }, + { FORCED_DIALOG_SKIP_ALL, "All" } +}; + static const std::unordered_map skipGetItemAnimationOptions = { { SGIA_DISABLED, "Disabled" }, { SGIA_JUNK, "Junk Items" }, diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 1f3066806..d8368f299 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -330,9 +330,12 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Skip Pickup Messages", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("FastDrops")) .Options(CheckboxOptions().Tooltip("Skip Pickup Messages for new Consumable Items and Bottle Swipes.")); - AddWidget(path, "Skip Forced Dialog", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Skip Forced Dialog", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipForcedDialog")) - .Options(CheckboxOptions().Tooltip("Prevent forced conversations with Navi or other NPCs.")); + .Options(ComboboxOptions() + .ComboMap(skipForcedDialogOptions) + .DefaultIndex(FORCED_DIALOG_SKIP_NONE) + .Tooltip("Prevent forced conversations with Navi and/or other NPCs.")); AddWidget(path, "Skip Text", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("SkipText")) .Options(CheckboxOptions().Tooltip("Holding down B skips text.")); @@ -1592,7 +1595,7 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip("Allows any item to be equipped, regardless of age.\n" "Also allows Child to use Adult strength upgrades.")); AddWidget(path, "Unrestricted Items", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("Unrestricted Items")) + .CVar(CVAR_CHEAT("NoRestrictItems")) .Options(CheckboxOptions().Tooltip("Allows you to use any item at any location")); AddWidget(path, "Super Tunic", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_CHEAT("SuperTunic")) From f02032aed4e2132173628b1bd648c12745d0b721 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Thu, 20 Mar 2025 07:07:43 -0400 Subject: [PATCH 42/53] Skip dialogue before getting Gerudo membership card (#5058) * Skip dialogue before getting Gerudo membership card * Change to misc interaction * Change to forced NPC dialogue * Rename VB flag --- .../game-interactor/vanilla-behavior/GIVanillaBehavior.h | 8 ++++++++ soh/soh/Enhancements/timesaver_hook_handlers.cpp | 6 ++++++ soh/src/overlays/actors/ovl_En_Ge3/z_en_ge3.c | 3 ++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index c8a0f747c..d6154347c 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -405,6 +405,14 @@ typedef enum { // - `*int16_t` (item id) VB_DRAW_AMMO_COUNT, + // #### `result` + // ```c + // (Message_GetState(&play->msgCtx) == TEXT_STATE_EVENT) && Message_ShouldAdvance(play) + // ``` + // #### `args` + // - None + VB_END_GERUDO_MEMBERSHIP_TALK, + // #### `result` // ```c // !(this->stateFlags3 & PLAYER_STATE3_PAUSE_ACTION_FUNC) diff --git a/soh/soh/Enhancements/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/timesaver_hook_handlers.cpp index a46ebb814..2c84ecac2 100644 --- a/soh/soh/Enhancements/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/timesaver_hook_handlers.cpp @@ -386,6 +386,12 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li } break; } + case VB_END_GERUDO_MEMBERSHIP_TALK: { + if (ForcedDialogIsDisabled(FORCED_DIALOG_SKIP_NPC)) { + *should = true; + } + break; + } case VB_GORON_LINK_BE_SCARED: { if (ForcedDialogIsDisabled(FORCED_DIALOG_SKIP_NPC)) { EnGo2* goronLink = va_arg(args, EnGo2*); diff --git a/soh/src/overlays/actors/ovl_En_Ge3/z_en_ge3.c b/soh/src/overlays/actors/ovl_En_Ge3/z_en_ge3.c index 24129fb2a..fb233cf0d 100644 --- a/soh/src/overlays/actors/ovl_En_Ge3/z_en_ge3.c +++ b/soh/src/overlays/actors/ovl_En_Ge3/z_en_ge3.c @@ -152,7 +152,8 @@ void EnGe3_WaitTillCardGiven(EnGe3* this, PlayState* play) { } void EnGe3_GiveCard(EnGe3* this, PlayState* play) { - if ((Message_GetState(&play->msgCtx) == TEXT_STATE_EVENT) && Message_ShouldAdvance(play)) { + if (GameInteractor_Should(VB_END_GERUDO_MEMBERSHIP_TALK, + (Message_GetState(&play->msgCtx) == TEXT_STATE_EVENT) && Message_ShouldAdvance(play))) { Message_CloseTextbox(play); this->actor.flags &= ~ACTOR_FLAG_TALK_OFFER_AUTO_ACCEPTED; this->actionFunc = EnGe3_WaitTillCardGiven; From 65564e946940bb2535e7176a1c979ae318666bce Mon Sep 17 00:00:00 2001 From: Christopher Leggett Date: Thu, 20 Mar 2025 11:46:52 -0400 Subject: [PATCH 43/53] Fix typo in Mute Low HP Alarm CVar (#5161) --- soh/soh/Enhancements/audio/AudioEditor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/audio/AudioEditor.cpp b/soh/soh/Enhancements/audio/AudioEditor.cpp index efbcd5c28..3daeff01f 100644 --- a/soh/soh/Enhancements/audio/AudioEditor.cpp +++ b/soh/soh/Enhancements/audio/AudioEditor.cpp @@ -498,7 +498,7 @@ void AudioEditor::DrawElement() { ImGui::TableNextColumn(); if (ImGui::BeginChild("SfxOptions", ImVec2(0, -8))) { UIWidgets::CVarCheckbox( - "Mute Low HP Alarm", CVAR_AUDIO("LowHPAlarm"), + "Mute Low HP Alarm", CVAR_AUDIO("LowHpAlarm"), UIWidgets::CheckboxOptions().Color(THEME_COLOR).Tooltip("Disable the low HP beeping sound.")); UIWidgets::CVarCheckbox("Disable Navi Call Audio", CVAR_AUDIO("DisableNaviCallAudio"), UIWidgets::CheckboxOptions() From b6f957b36fa43909360e97fad92b0556268a43c2 Mon Sep 17 00:00:00 2001 From: aMannus Date: Thu, 20 Mar 2025 20:19:16 +0100 Subject: [PATCH 44/53] More Menu Improvements (#5155) * fix mismatch on default value for notification position * Fix checkbox vertical offset for multiline * Moooore changeees too theee menuuuuuu * capitalization again * Fix controller config & remove test var --- .../controls/SohInputEditorWindow.cpp | 15 ++++-- soh/soh/Notification/Notification.cpp | 2 +- soh/soh/SohGui/Menu.cpp | 7 +++ soh/soh/SohGui/SohMenuEnhancements.cpp | 54 +++++++++++-------- soh/soh/SohGui/SohMenuSettings.cpp | 10 +++- soh/soh/SohGui/UIWidgets.cpp | 3 +- 6 files changed, 61 insertions(+), 30 deletions(-) diff --git a/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp b/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp index 135cb8a00..7dbc74a75 100644 --- a/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp +++ b/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp @@ -1501,10 +1501,10 @@ void SohInputEditorWindow::DrawLinkTab() { } if (ImGui::CollapsingHeader("D-Pad", NULL, ImGuiTreeNodeFlags_DefaultOpen)) { - DrawButtonLine(StringHelper::Sprintf("%s", ICON_FA_ARROW_UP).c_str(), portIndex, BTN_DUP); - DrawButtonLine(StringHelper::Sprintf("%s", ICON_FA_ARROW_DOWN).c_str(), portIndex, BTN_DDOWN); - DrawButtonLine(StringHelper::Sprintf("%s", ICON_FA_ARROW_LEFT).c_str(), portIndex, BTN_DLEFT); - DrawButtonLine(StringHelper::Sprintf("%s", ICON_FA_ARROW_RIGHT).c_str(), portIndex, BTN_DRIGHT); + DrawButtonLine(StringHelper::Sprintf("D %s", ICON_FA_ARROW_UP).c_str(), portIndex, BTN_DUP); + DrawButtonLine(StringHelper::Sprintf("D %s", ICON_FA_ARROW_DOWN).c_str(), portIndex, BTN_DDOWN); + DrawButtonLine(StringHelper::Sprintf("D %s", ICON_FA_ARROW_LEFT).c_str(), portIndex, BTN_DLEFT); + DrawButtonLine(StringHelper::Sprintf("D %s", ICON_FA_ARROW_RIGHT).c_str(), portIndex, BTN_DRIGHT); } if (ImGui::CollapsingHeader("Analog Stick", NULL, ImGuiTreeNodeFlags_DefaultOpen)) { @@ -1782,6 +1782,11 @@ void SohInputEditorWindow::DrawSetDefaultsButton(uint8_t portIndex) { } void SohInputEditorWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); + ImVec4 themeColor = ColorValues.at(THEME_COLOR); + ImGui::PushStyleColor(ImGuiCol_Tab, ImVec4(themeColor.x, themeColor.y, themeColor.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_TabHovered, ImVec4(themeColor.x, themeColor.y, themeColor.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_TabActive, ImVec4(themeColor.x, themeColor.y, themeColor.z, 0.6f)); ImGui::BeginTabBar("##ControllerConfigPortTabs"); DrawLinkTab(); DrawIvanTab(); @@ -1790,4 +1795,6 @@ void SohInputEditorWindow::DrawElement() { DrawDebugPortTab(3); } ImGui::EndTabBar(); + ImGui::PopStyleColor(3); + ImGui::PopFont(); } diff --git a/soh/soh/Notification/Notification.cpp b/soh/soh/Notification/Notification.cpp index ebc4e168b..5bc0e8974 100644 --- a/soh/soh/Notification/Notification.cpp +++ b/soh/soh/Notification/Notification.cpp @@ -20,7 +20,7 @@ void Window::Draw() { const float margin = 30.0f; const float padding = 10.0f; - int position = CVarGetInteger(CVAR_SETTING("Notifications.Position"), 0); + int position = CVarGetInteger(CVAR_SETTING("Notifications.Position"), 3); // Top Left ImVec2 basePosition; diff --git a/soh/soh/SohGui/Menu.cpp b/soh/soh/SohGui/Menu.cpp index 0e8f8ab51..955b048d6 100644 --- a/soh/soh/SohGui/Menu.cpp +++ b/soh/soh/SohGui/Menu.cpp @@ -519,6 +519,10 @@ void Menu::DrawElement() { ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), windowCond, { 0.5f, 0.5f }); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); } + + ImGui::PushStyleColor(ImGuiCol_WindowBg, + ImVec4(0, 0, 0, CVarGetFloat(CVAR_SETTING("Menu.BackgroundOpacity"), 0.85f))); + if (!ImGui::Begin("Main Menu", NULL, windowFlags)) { if (!popout) { ImGui::PopStyleVar(); @@ -526,6 +530,9 @@ void Menu::DrawElement() { ImGui::End(); return; } + + ImGui::PopStyleColor(); + if (popped != popout) { if (!popout) { ImGui::PopStyleVar(); diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index d8368f299..2e0cb55af 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -116,7 +116,7 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Nighttime GS Always Spawn", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("NightGSAlwaysSpawn")) .Options(CheckboxOptions().Tooltip("Nighttime Skulltulas will spawn during both day and night.")); - AddWidget(path, "Pull Grave during the day", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Pull Grave During the Day", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("DayGravePull")) .Options(CheckboxOptions().Tooltip("Allows graves to be pulled when child during the day.")); AddWidget(path, "Dampe Appears All Night", WIDGET_CVAR_CHECKBOX) @@ -140,7 +140,7 @@ void SohMenu::AddMenuEnhancements() { .DisabledTooltip("This is not compatible with the Locked Overworld Doors Randomizer option.")); AddWidget(path, "Pause Menu", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Allow the Cursor to be on any slot", WIDGET_CVAR_COMBOBOX) + AddWidget(path, "Allow the Cursor to be on Any Slot", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_ENHANCEMENT("PauseAnyCursor")) .Options( ComboboxOptions() @@ -163,7 +163,7 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip( "Removes the Input Requirement on Text boxes after defeating Ganon, allowing the Credits " "Sequence to continue to progress.")); - AddWidget(path, "Include Held Inputs at the Start of Buffer Input Window", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Include Held Inputs at the Start of Pause Buffer Input Window", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("IncludeHeldInputsBufferWindow")) .Options(CheckboxOptions().Tooltip( "Typically, inputs that are held prior to the buffer window are not included in the buffer. This " @@ -171,7 +171,15 @@ void SohMenu::AddMenuEnhancements() { "undesireably, for instance Z-Targetting something you might not want to.")); AddWidget(path, "Pause Buffer Input Window: %d frames", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR_ENHANCEMENT("PauseBufferWindow")) - .Options(IntSliderOptions().Min(0).Max(40).DefaultValue(0).Format("%d frames")); + .Options(IntSliderOptions() + .Min(0) + .Max(40) + .DefaultValue(0) + .Format("%d frames") + .Tooltip("Adds back in a delay after unpausing before the game resumes playing again, " + "where inputs can be held prematurely to be input immediately after the game resumes. " + "This essentially brings back behaviour from console releases which are lost on default " + "because SoH isn't limited to N64 hardware.")); AddWidget(path, "Simulated Input Lag: %d frames", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR_SIMULATED_INPUT_LAG) .Options(IntSliderOptions() @@ -214,7 +222,7 @@ void SohMenu::AddMenuEnhancements() { "The default response to Kaepora Gaebora is always that you understood what he said.")); AddWidget(path, "Convenience", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Quit Fishing At Door", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Quit Fishing at Door", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("QuitFishingAtDoor")) .Options(CheckboxOptions().Tooltip( "Fisherman asks if you want to quit at the door if you try to leave the Fishing Pond " @@ -222,7 +230,7 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Instant Putaway", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("InstantPutaway")) .Options(CheckboxOptions().Tooltip("Allow Link to put items away without having to wait around.")); - AddWidget(path, "Navi Timer Resets", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Navi Timer Resets on Scene Change", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("ResetNaviTimer")) .Options( CheckboxOptions().Tooltip("Resets the Navi timer on scene change. If you have already talked to her, " @@ -459,11 +467,6 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("NewDrops")) .Options(CheckboxOptions().Tooltip( "Replaces most 2D items and projectiles on the overworld with their equivalent 3D models.")); - AddWidget(path, "Invisible Bunny Hood", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("HideBunnyHood")) - .Options(CheckboxOptions().Tooltip( - "Turns Bunny Hood Invisible while still maintaining its effects." - )); AddWidget(path, "Animated Link in Pause Menu", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("PauseMenuAnimatedLink")) .Options(CheckboxOptions().Tooltip( @@ -523,7 +526,7 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Disable HUD Heart Animations", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("NoHUDHeartAnimation")) .Options(CheckboxOptions().Tooltip("Disables the Beating Animation of the Hearts on the HUD.")); - AddWidget(path, "Glitch line-up tick", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Glitch Line-up Tick", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("DrawLineupTick")) .Options(CheckboxOptions().Tooltip( "Displays a tick in the top center of the screen to help with glitch line-ups in SoH, since traditional " @@ -549,7 +552,9 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip( "Ammo counts in the pause menu will work correctly regardless of the position of items in the Inventory.")); AddWidget(path, "Enable Passage of Time on File Select", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("TimeFlowFileSelect")); + .CVar(CVAR_ENHANCEMENT("TimeFlowFileSelect")) + .Options(CheckboxOptions().Tooltip( + "The skybox in the background of the File Select screen will go through the day and night cycle over time.")); path.column = SECTION_COLUMN_3; AddWidget(path, "Misc.", WIDGET_SEPARATOR_TEXT); @@ -690,6 +695,9 @@ void SohMenu::AddMenuEnhancements() { "- When saving and quitting\n" "- When dying\n" "- When traveling thru time (if \"Masks Equippable as Adult\" is activated).")); + AddWidget(path, "Invisible Bunny Hood", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HideBunnyHood")) + .Options(CheckboxOptions().Tooltip("Turns Bunny Hood Invisible while still maintaining its effects.")); AddWidget(path, "Mask Select in Inventory", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("MaskSelect")) .Options(CheckboxOptions().Tooltip( @@ -962,7 +970,7 @@ void SohMenu::AddMenuEnhancements() { "Only relevant for playthroughs involving Master Quest Dungeons.")); AddWidget(path, "Desync Fixes", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Fix Darunia Dancing Too Fast", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Fix Darunia Dancing too Fast", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("FixDaruniaDanceSpeed")) .Options(CheckboxOptions().Tooltip( "Fixes Darunia's dancing speed so he dances to the beat of Saria's Song, like in the Original Game.")); @@ -1265,7 +1273,7 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("FrogsUnlimitedFailTime")) .PreFunc(frogsOcarinaGameDisabledFunc) .Options(CheckboxOptions().Tooltip("Removes the timer to play back the song.")); - AddWidget(path, "Modify note timer: %dx", WIDGET_CVAR_SLIDER_INT) + AddWidget(path, "Modify Note Timer: %dx", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR_ENHANCEMENT("FrogsModifyFailTime")) .PreFunc([](WidgetInfo& info) { info.options->disabled = !CVarGetInteger(CVAR_ENHANCEMENT("CustomizeFrogsOcarinaGame"), 0) || @@ -1353,11 +1361,11 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("GuaranteeFishingBite")) .PreFunc(fishingDisabledFunc) .Options(CheckboxOptions().Tooltip("When a line is stable, guarantee bite. Otherwise use Default logic.")); - AddWidget(path, "Fish Never Escape", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Fish never Escape", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("FishNeverEscape")) .PreFunc(fishingDisabledFunc) .Options(CheckboxOptions().Tooltip("Once a hook as been set, Fish will never let go while being reeled in.")); - AddWidget(path, "Loaches Always Appear", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Loaches always Appear", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("LoachesAlwaysAppear")) .PreFunc(fishingDisabledFunc) .Options( @@ -1416,7 +1424,7 @@ void SohMenu::AddMenuEnhancements() { "Enables Ivan the Fairy upon the next map change. Player 2 can control Ivan and press the C-Buttons to " "use items and mess with Player 1!" )); - AddWidget(path, "Dogs Follow you Everywhere", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Dogs Follow You Everywhere", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("DogFollowsEverywhere")) .Options(CheckboxOptions().Tooltip("Allows dogs to follow you anywhere you go, even if you leave the Market.")); AddWidget(path, "Rupee Dash Mode", WIDGET_CVAR_CHECKBOX) @@ -1554,7 +1562,7 @@ void SohMenu::AddMenuEnhancements() { .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0); }); - AddWidget(path, "Select All Enemies", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Select all Enemies", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("RandomizedEnemyList.All")) .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0); @@ -1635,14 +1643,14 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_CHEAT("NoRedeadFreeze")) .Options( CheckboxOptions().Tooltip("Prevents ReDeads and Gibdos from being able to freeze you with their scream.")); - AddWidget(path, "Keese/Guay Don't Target You", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Keese/Guay don't Target You", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_CHEAT("NoKeeseGuayTarget")) .Options(CheckboxOptions().Tooltip( "Keese and Guay no longer target you and simply ignore you as if you were wearing the " "Skull Mask.")); AddWidget(path, "Glitch Aids", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Easy Frame Advancing", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Easy Frame Advancing with Pause", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_CHEAT("EasyFrameAdvance")) .Options(CheckboxOptions().Tooltip( "Continue holding START button when unpausing to only advance a single frame and then re-pause.")); @@ -1664,10 +1672,10 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_CHEAT("DropsDontDie")) .Options( CheckboxOptions().Tooltip("Drops from enemies, grass, etc. don't disappear after a set amount of time.")); - AddWidget(path, "Fish Don't Despawn", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Fish don't Despawn", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_CHEAT("NoFishDespawn")) .Options(CheckboxOptions().Tooltip("Prevents fish from automatically despawning after a while when dropped.")); - AddWidget(path, "Bugs Don't Despawn", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Bugs don't Despawn", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_CHEAT("NoBugsDespawn")) .Options(CheckboxOptions().Tooltip("Prevents bugs from automatically despawning after a while when dropped.")); diff --git a/soh/soh/SohGui/SohMenuSettings.cpp b/soh/soh/SohGui/SohMenuSettings.cpp index 093e98618..20ce9ff50 100644 --- a/soh/soh/SohGui/SohMenuSettings.cpp +++ b/soh/soh/SohGui/SohMenuSettings.cpp @@ -55,7 +55,7 @@ void SohMenu::AddMenuSettings() { WidgetPath path = { "Settings", "General", SECTION_COLUMN_1 }; // General - Settings - AddWidget(path, "General Settings", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Menu Settings", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Menu Theme", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_SETTING("Menu.Theme")) .Options(ComboboxOptions() @@ -69,6 +69,14 @@ void SohMenu::AddMenuSettings() { "Allows controller navigation of the port menu (Settings, Enhancements,...)\nCAUTION: " "This will disable game inputs while the menu is visible.\n\nD-pad to move between " "items, A to select, B to move up in scope.")); + AddWidget(path, "Menu Background Opacity", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_SETTING("Menu.BackgroundOpacity")) + .Options(FloatSliderOptions() + .DefaultValue(0.85f) + .IsPercentage() + .Tooltip("Sets the opacity of the background of the port menu.")); + + AddWidget(path, "General Settings", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Cursor Always Visible", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_SETTING("CursorVisibility")) .Callback([](WidgetInfo& info) { diff --git a/soh/soh/SohGui/UIWidgets.cpp b/soh/soh/SohGui/UIWidgets.cpp index 9875cf7b9..e2f7bf2dc 100644 --- a/soh/soh/SohGui/UIWidgets.cpp +++ b/soh/soh/SohGui/UIWidgets.cpp @@ -330,7 +330,8 @@ bool Checkbox(const char* _label, bool* value, const CheckboxOptions& options) { if (options.labelPosition == LabelPositions::Above) { checkPos.y += label_size.y + (style.ItemInnerSpacing.y * 2.0f); } else { - labelPos.y += (square_sz / 2) - (label_size.y / 2); + // Center with checkbox automatically + labelPos.y += ImGui::CalcTextSize("g").y / 8; } if (options.alignment == ComponentAlignments::Right) { checkPos.x = total_bb.Max.x - square_sz; From 985bf919457abeb1672eac1dcb621208ecd6ad2f Mon Sep 17 00:00:00 2001 From: Archez Date: Thu, 20 Mar 2025 15:20:50 -0400 Subject: [PATCH 45/53] prevent some timesavers applying in boss rush (#5163) --- .../TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp | 5 +++++ soh/soh/Enhancements/timesaver_hook_handlers.cpp | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp index c9a999cb1..ae4236218 100644 --- a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp +++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp @@ -20,6 +20,11 @@ static bool sEnteredBlueWarp = false; * should also account for the difference between your first and following visits to the blue warp. */ void SkipBlueWarp_ShouldPlayTransitionCS(GIVanillaBehavior _, bool* should, va_list originalArgs) { + // Do nothing when in a boss rush + if (IS_BOSS_RUSH) { + return; + } + bool overrideBlueWarpDestinations = IS_RANDO && (RAND_GET_OPTION(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF || RAND_GET_OPTION(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF); diff --git a/soh/soh/Enhancements/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/timesaver_hook_handlers.cpp index 2c84ecac2..1db34b4f6 100644 --- a/soh/soh/Enhancements/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/timesaver_hook_handlers.cpp @@ -1066,6 +1066,11 @@ void TimeSaverOnSceneInitHandler(int16_t sceneNum) { static GetItemEntry vanillaQueuedItemEntry = GET_ITEM_NONE; void TimeSaverOnFlagSetHandler(int16_t flagType, int16_t flag) { + // Do nothing when in a boss rush + if (IS_BOSS_RUSH) { + return; + } + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { switch (flagType) { case FLAG_EVENT_CHECK_INF: From 57bc5690e2843dcfd966e6dfcbbd65be956287e1 Mon Sep 17 00:00:00 2001 From: Archez Date: Fri, 21 Mar 2025 07:21:33 -0400 Subject: [PATCH 46/53] Mono dev tools (#5175) * apply mono font to some dev tool windows * soh gui wrappers for gfxdebugger and console to set mono font * it can be just a little larger --- .../Enhancements/debugger/debugSaveEditor.cpp | 4 +++ soh/soh/Enhancements/debugger/dlViewer.cpp | 6 ++++ .../Enhancements/debugger/hookDebugger.cpp | 5 ++++ soh/soh/Enhancements/debugger/sohConsole.cpp | 30 +++++++++++++++++++ soh/soh/Enhancements/debugger/sohConsole.h | 17 +++++++++++ .../Enhancements/debugger/sohGfxDebugger.cpp | 16 ++++++++++ .../Enhancements/debugger/sohGfxDebugger.h | 17 +++++++++++ soh/soh/Enhancements/gameplaystats.cpp | 4 +++ soh/soh/OTRGlobals.cpp | 1 + soh/soh/OTRGlobals.h | 1 + soh/soh/SohGui/SohGui.cpp | 12 +++----- soh/soh/SohGui/SohGui.hpp | 2 ++ soh/soh/SohGui/SohMenuDevTools.cpp | 4 +-- 13 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 soh/soh/Enhancements/debugger/sohConsole.cpp create mode 100644 soh/soh/Enhancements/debugger/sohConsole.h create mode 100644 soh/soh/Enhancements/debugger/sohGfxDebugger.cpp create mode 100644 soh/soh/Enhancements/debugger/sohGfxDebugger.h diff --git a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp index 0e9bbc79a..96163f656 100644 --- a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp +++ b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp @@ -1555,6 +1555,8 @@ void ResetBaseOptions() { void SaveEditorWindow::DrawElement() { PushStyleTabs(THEME_COLOR); + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); + if (ImGui::BeginTabBar("SaveContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { ResetBaseOptions(); if (ImGui::BeginTabItem("Info")) { @@ -1594,6 +1596,8 @@ void SaveEditorWindow::DrawElement() { ImGui::EndTabBar(); } + + ImGui::PopFont(); PopStyleTabs(); } diff --git a/soh/soh/Enhancements/debugger/dlViewer.cpp b/soh/soh/Enhancements/debugger/dlViewer.cpp index a591cf948..27833058a 100644 --- a/soh/soh/Enhancements/debugger/dlViewer.cpp +++ b/soh/soh/Enhancements/debugger/dlViewer.cpp @@ -93,6 +93,8 @@ void PerformDisplayListSearch() { void DLViewerWindow::DrawElement() { // Debounce the search field as listing otr files is expensive UIWidgets::PushStyleInput(THEME_COLOR); + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); + if (ImGui::InputText("Search Display Lists", searchString, ARRAY_COUNT(searchString))) { doSearch = true; searchDebounceFrames = 30; @@ -121,6 +123,7 @@ void DLViewerWindow::DrawElement() { UIWidgets::PopStyleCombobox(); if (activeDisplayList == "") { + ImGui::PopFont(); return; } @@ -326,8 +329,11 @@ void DLViewerWindow::DrawElement() { } } catch (const std::exception& e) { ImGui::Text("Error displaying DL instructions."); + ImGui::PopFont(); return; } + + ImGui::PopFont(); } void DLViewerWindow::InitElement() { diff --git a/soh/soh/Enhancements/debugger/hookDebugger.cpp b/soh/soh/Enhancements/debugger/hookDebugger.cpp index a9d6ca891..113862282 100644 --- a/soh/soh/Enhancements/debugger/hookDebugger.cpp +++ b/soh/soh/Enhancements/debugger/hookDebugger.cpp @@ -1,6 +1,7 @@ #include "hookDebugger.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/SohGui/UIWidgets.hpp" +#include "soh/OTRGlobals.h" #include #include @@ -82,12 +83,16 @@ void HookDebuggerWindow::DrawElement() { "(\"__cpp_lib_source_location\" not defined in \"\")."); #endif + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); + for (auto& [hookName, _] : hookData) { if (ImGui::TreeNode(hookName)) { DrawHookRegisteringInfos(hookName); ImGui::TreePop(); } } + + ImGui::PopFont(); } void HookDebuggerWindow::InitElement() { diff --git a/soh/soh/Enhancements/debugger/sohConsole.cpp b/soh/soh/Enhancements/debugger/sohConsole.cpp new file mode 100644 index 000000000..686152a1f --- /dev/null +++ b/soh/soh/Enhancements/debugger/sohConsole.cpp @@ -0,0 +1,30 @@ +#include "sohConsole.h" +#include "soh/OTRGlobals.h" +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" + +void SohConsoleWindow::InitElement() { + ConsoleWindow::InitElement(); +} + +void SohConsoleWindow::UpdateElement() { + ConsoleWindow::UpdateElement(); +} + +void SohConsoleWindow::DrawElement() { + UIWidgets::PushStyleInput(THEME_COLOR); + // Small font (13) to match hardcoded width values in the LUS window.. set large font after below TODO addressed + ImGui::PushFont(OTRGlobals::Instance->fontMonoSmall); + + // TODO: This can be removed after the LUS console window is designed better without hardcoding widths + ImGui::BeginChild("##Console Wrapper", ImVec2(0, 0), ImGuiChildFlags_None, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse); + + ConsoleWindow::DrawElement(); + + ImGui::EndChild(); + + ImGui::PopFont(); + UIWidgets::PopStyleInput(); +} diff --git a/soh/soh/Enhancements/debugger/sohConsole.h b/soh/soh/Enhancements/debugger/sohConsole.h new file mode 100644 index 000000000..db039625b --- /dev/null +++ b/soh/soh/Enhancements/debugger/sohConsole.h @@ -0,0 +1,17 @@ +#ifndef SOH_CONSOLE_H +#define SOH_CONSOLE_H + +#include "window/gui/GuiWindow.h" +#include "window/gui/ConsoleWindow.h" + +class SohConsoleWindow : public Ship::ConsoleWindow { + public: + using ConsoleWindow::ConsoleWindow; + + protected: + void InitElement() override; + void UpdateElement() override; + void DrawElement() override; +}; + +#endif // SOH_CONSOLE_H diff --git a/soh/soh/Enhancements/debugger/sohGfxDebugger.cpp b/soh/soh/Enhancements/debugger/sohGfxDebugger.cpp new file mode 100644 index 000000000..20b37ca93 --- /dev/null +++ b/soh/soh/Enhancements/debugger/sohGfxDebugger.cpp @@ -0,0 +1,16 @@ +#include "sohGfxDebugger.h" +#include "soh/OTRGlobals.h" + +void SohGfxDebuggerWindow::InitElement() { + GfxDebuggerWindow::InitElement(); +} + +void SohGfxDebuggerWindow::UpdateElement() { + GfxDebuggerWindow::UpdateElement(); +} + +void SohGfxDebuggerWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); + GfxDebuggerWindow::DrawElement(); + ImGui::PopFont(); +} diff --git a/soh/soh/Enhancements/debugger/sohGfxDebugger.h b/soh/soh/Enhancements/debugger/sohGfxDebugger.h new file mode 100644 index 000000000..789173b88 --- /dev/null +++ b/soh/soh/Enhancements/debugger/sohGfxDebugger.h @@ -0,0 +1,17 @@ +#ifndef SOH_GFX_DEBUGGER_H +#define SOH_GFX_DEBUGGER_H + +#include "window/gui/GuiWindow.h" +#include "window/gui/GfxDebuggerWindow.h" + +class SohGfxDebuggerWindow : public LUS::GfxDebuggerWindow { + public: + using GfxDebuggerWindow::GfxDebuggerWindow; + + protected: + void InitElement() override; + void UpdateElement() override; + void DrawElement() override; +}; + +#endif // SOH_GFX_DEBUGGER_H diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index af7807b3a..937e81ede 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -635,9 +635,11 @@ void DrawGameplayStatsOptionsTab() { } void GameplayStatsWindow::DrawElement() { + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); DrawGameplayStatsHeader(); UIWidgets::PushStyleTabs(THEME_COLOR); + if (ImGui::BeginTabBar("Stats", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("Timestamps")) { DrawGameplayStatsTimestampsTab(); @@ -657,7 +659,9 @@ void GameplayStatsWindow::DrawElement() { } ImGui::EndTabBar(); } + UIWidgets::PopStyleTabs(); + ImGui::PopFont(); ImGui::Text("Note: Gameplay stats are saved to the current file and will be\nlost if you quit without saving."); } diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 5d2310e1b..a86c77524 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -401,6 +401,7 @@ OTRGlobals::OTRGlobals() { previousImGuiScale = defaultImGuiScale; + fontMonoSmall = CreateFontWithSize(14.0f, "fonts/Inconsolata-Regular.ttf"); fontMono = CreateFontWithSize(16.0f, "fonts/Inconsolata-Regular.ttf"); fontMonoLarger = CreateFontWithSize(20.0f, "fonts/Inconsolata-Regular.ttf"); fontMonoLargest = CreateFontWithSize(24.0f, "fonts/Inconsolata-Regular.ttf"); diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index 0a8d6595e..2f20b0b15 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -54,6 +54,7 @@ class OTRGlobals { ImFont* defaultFontLarger; ImFont* defaultFontLargest; + ImFont* fontMonoSmall; ImFont* fontStandard; ImFont* fontStandardLarger; ImFont* fontStandardLargest; diff --git a/soh/soh/SohGui/SohGui.cpp b/soh/soh/SohGui/SohGui.cpp index ef8e40e8c..56da5eba0 100644 --- a/soh/soh/SohGui/SohGui.cpp +++ b/soh/soh/SohGui/SohGui.cpp @@ -124,15 +124,11 @@ namespace SohGui { SPDLOG_ERROR("Could not find stats window"); } - mConsoleWindow = gui->GetGuiWindow("Console"); - if (mConsoleWindow == nullptr) { - SPDLOG_ERROR("Could not find console window"); - } + mConsoleWindow = std::make_shared(CVAR_WINDOW("Console"), "Console##SoH", ImVec2(820, 630)); + gui->AddGuiWindow(mConsoleWindow); - mGfxDebuggerWindow = gui->GetGuiWindow("Gfx Debugger"); - if (mGfxDebuggerWindow == nullptr) { - SPDLOG_ERROR("Could not find Gfx Debugger window"); - } + mGfxDebuggerWindow = std::make_shared(CVAR_WINDOW("GfxDebugger"), "GfxDebugger##SoH", ImVec2(820, 630)); + gui->AddGuiWindow(mGfxDebuggerWindow); mInputEditorWindow = gui->GetGuiWindow("Controller Configuration"); if (mInputEditorWindow == nullptr) { diff --git a/soh/soh/SohGui/SohGui.hpp b/soh/soh/SohGui/SohGui.hpp index f465d1d1b..35c93b353 100644 --- a/soh/soh/SohGui/SohGui.hpp +++ b/soh/soh/SohGui/SohGui.hpp @@ -18,6 +18,8 @@ #include "soh/Enhancements/debugger/debugSaveEditor.h" #include "soh/Enhancements/debugger/hookDebugger.h" #include "soh/Enhancements/debugger/dlViewer.h" +#include "soh/Enhancements/debugger/sohConsole.h" +#include "soh/Enhancements/debugger/sohGfxDebugger.h" #include "soh/Enhancements/debugger/valueViewer.h" #include "soh/Enhancements/gameplaystatswindow.h" #include "soh/Enhancements/randomizer/randomizer_check_tracker.h" diff --git a/soh/soh/SohGui/SohMenuDevTools.cpp b/soh/soh/SohGui/SohMenuDevTools.cpp index 917635f34..53d866a8e 100644 --- a/soh/soh/SohGui/SohMenuDevTools.cpp +++ b/soh/soh/SohGui/SohMenuDevTools.cpp @@ -99,7 +99,7 @@ void SohMenu::AddMenuDevTools() { AddSidebarEntry("Dev Tools", path.sidebarName, 1); AddWidget(path, "Popout Console", WIDGET_WINDOW_BUTTON) .CVar(CVAR_WINDOW("Console")) - .WindowName("Console") + .WindowName("Console##SoH") .Options(WindowButtonOptions().Tooltip("Enables the separate Console Window.")); // Save Editor @@ -163,7 +163,7 @@ void SohMenu::AddMenuDevTools() { AddSidebarEntry("Dev Tools", path.sidebarName, 1); AddWidget(path, "Popout Gfx Debugger", WIDGET_WINDOW_BUTTON) .CVar(CVAR_WINDOW("GfxDebugger")) - .WindowName("GfxDebuggerWindow") + .WindowName("GfxDebugger##SoH") .Options(WindowButtonOptions().Tooltip("Enables the separate Gfx Debugger Window.")); } From e21a3e4c0f33c76c5eab7257125ae38d56a2cdc5 Mon Sep 17 00:00:00 2001 From: Malkierian Date: Fri, 21 Mar 2025 04:22:22 -0700 Subject: [PATCH 47/53] Add check for GAMEMODE_END_CREDITS in Audio Editor's OnSceneInit hook (#5172) * Add check for GAMEMODE_END_CREDITS in Audio Editor's OnSceneInit hook to prevent sequence shuffles mid-credits. * Move extern "C" block up. --- soh/soh/Enhancements/audio/AudioEditor.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/audio/AudioEditor.cpp b/soh/soh/Enhancements/audio/AudioEditor.cpp index 3daeff01f..caa40c66f 100644 --- a/soh/soh/Enhancements/audio/AudioEditor.cpp +++ b/soh/soh/Enhancements/audio/AudioEditor.cpp @@ -16,6 +16,11 @@ #include "AudioCollection.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" +extern "C" { + #include "z64save.h" + extern SaveContext gSaveContext; +} + Vec3f pos = { 0.0f, 0.0f, 0.0f }; f32 freqScale = 1.0f; s8 reverbAdd = 0; @@ -440,7 +445,7 @@ void DrawTypeChip(SeqType type, std::string sequenceName) { void AudioEditorRegisterOnSceneInitHook() { GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) { - if (CVarGetInteger(CVAR_AUDIO("RandomizeAllOnNewScene"), 0)) { + if (gSaveContext.gameMode != GAMEMODE_END_CREDITS && CVarGetInteger(CVAR_AUDIO("RandomizeAllOnNewScene"), 0)) { AudioEditor_RandomizeAll(); } }); From 1bcab06fede5330a9d1e623429b42eb0ff442dc4 Mon Sep 17 00:00:00 2001 From: Archez Date: Fri, 21 Mar 2025 07:23:01 -0400 Subject: [PATCH 48/53] Rework CI build caching for stability (#5170) * improve github build caching * add back network build flag * add back sdlnet --- .github/workflows/generate-builds.yml | 222 +++++++++++++++----------- CMake/Packaging-2.cmake | 6 +- CMakeLists.txt | 2 +- 3 files changed, 130 insertions(+), 100 deletions(-) diff --git a/.github/workflows/generate-builds.yml b/.github/workflows/generate-builds.yml index 64c908a37..45cab21d9 100644 --- a/.github/workflows/generate-builds.yml +++ b/.github/workflows/generate-builds.yml @@ -6,42 +6,44 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: + generate-soh-otr: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - name: Git Checkout + uses: actions/checkout@v4 with: submodules: true - - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.14 + - name: Configure ccache + uses: hendrikmuhs/ccache-action@v1.2 with: + save: ${{ github.ref_name == github.event.repository.default_branch }} key: ${{ runner.os }}-otr-ccache-${{ github.ref }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-otr-ccache-${{ github.ref }} - ${{ runner.os }}-otr-ccache- + ${{ runner.os }}-otr-ccache - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y $(cat .github/workflows/apt-deps.txt) libzip-dev zipcmp zipmerge ziptool - - name: Cache build folders - uses: actions/cache@v4 + - name: Restore Cached deps folder + uses: actions/cache/restore@v4 with: - key: ${{ runner.os }}-otr-build-${{ github.ref }}-${{ github.sha }} + key: ${{ runner.os }}-deps-${{ github.ref }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-otr-build-${{ github.ref }} - ${{ runner.os }}-otr-build- - path: | - build-cmake - SDL2-2.30.3 - tinyxml2-10.0.0 + ${{ runner.os }}-deps-${{ github.ref }}- + ${{ runner.os }}-deps- + path: deps + - name: Create deps folder + run: mkdir -p deps - name: Install latest SDL run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - if [ ! -d "SDL2-2.30.3" ]; then - wget https://www.libsdl.org/release/SDL2-2.30.3.tar.gz - tar -xzf SDL2-2.30.3.tar.gz + if [ ! -d "deps/SDL2-2.30.3" ]; then + wget https://github.com/libsdl-org/SDL/releases/download/release-2.30.3/SDL2-2.30.3.tar.gz + tar -xzf SDL2-2.30.3.tar.gz -C deps fi - cd SDL2-2.30.3 + cd deps/SDL2-2.30.3 ./configure --enable-hidapi-libusb make -j 10 sudo make install @@ -50,11 +52,11 @@ jobs: run: | sudo apt-get remove libtinyxml2-dev export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - if [ ! -d "tinyxml2-10.0.0" ]; then + if [ ! -d "deps/tinyxml2-10.0.0" ]; then wget https://github.com/leethomason/tinyxml2/archive/refs/tags/10.0.0.tar.gz - tar -xzf 10.0.0.tar.gz + tar -xzf 10.0.0.tar.gz -C deps fi - cd tinyxml2-10.0.0 + cd deps/tinyxml2-10.0.0 mkdir -p build cd build cmake .. @@ -64,49 +66,57 @@ jobs: run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release - cmake --build build-cmake --config Release --target GenerateSohOtr - - uses: actions/upload-artifact@v4 + cmake --build build-cmake --config Release --target GenerateSohOtr -j3 + - name: Upload soh.otr + uses: actions/upload-artifact@v4 with: name: soh.otr path: soh.otr - retention-days: 1 + retention-days: 3 + build-macos: needs: generate-soh-otr runs-on: macos-14 steps: - - uses: actions/checkout@v4 + - name: Git Checkout + uses: actions/checkout@v4 with: submodules: true - - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.14 + - name: Configure ccache + uses: hendrikmuhs/ccache-action@v1.2 with: create-symlink: true + save: ${{ github.ref_name == github.event.repository.default_branch }} key: ${{ runner.os }}-14-ccache-${{ github.ref }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-14-ccache-${{ github.ref }} - ${{ runner.os }}-14-ccache- + ${{ runner.os }}-14-ccache + # Needed to apply sudo for macports cache restore - name: Install gtar wrapper run: | sudo mv /opt/homebrew/bin/gtar /opt/homebrew/bin/gtar.orig - sudo cp .github/workflows//gtar /opt/homebrew/bin/gtar + sudo cp .github/workflows/gtar /opt/homebrew/bin/gtar sudo chmod +x /opt/homebrew/bin/gtar - - name: Cache MacPorts - id: cache-macports - uses: actions/cache@v4 + - name: Restore Cached MacPorts + id: restore-cache-macports + uses: actions/cache/restore@v4 with: - path: /opt/local/ - key: ${{ runner.os }}-14-macports-${{ hashFiles('.github/workflows/macports-deps.txt') }} + key: ${{ runner.os }}-14-macports-${{ hashFiles('.github/workflows/macports-deps.txt') }}-${{ github.sha }} restore-keys: | + ${{ runner.os }}-14-macports-${{ hashFiles('.github/workflows/macports-deps.txt') }}- ${{ runner.os }}-14-macports- + path: /opt/local/ + # Updated PATH applies to the next step and onwards - name: Install MacPorts (if necessary) run: | - if [ -d /opt/local/ ]; then + if command -v /opt/local/bin/port 2>&1 >/dev/null; then echo "MacPorts already installed" else + echo "Installing MacPorts" wget https://github.com/macports/macports-base/releases/download/v2.9.3/MacPorts-2.9.3-14-Sonoma.pkg sudo installer -pkg ./MacPorts-2.9.3-14-Sonoma.pkg -target / fi - echo "/opt/local/bin:/opt/local/sbin" >> $GITHUB_PATH + echo "/opt/local/bin:/opt/local/sbin" >> "$GITHUB_PATH" - name: Install dependencies run: | brew uninstall --ignore-dependencies libpng @@ -116,11 +126,12 @@ jobs: uses: actions/download-artifact@v4 with: name: soh.otr + path: build-cmake/soh - name: Build SoH run: | + export PATH="/usr/lib/ccache:/opt/homebrew/opt/ccache/libexec:/usr/local/opt/ccache/libexec:$PATH" cmake --no-warn-unused-cli -H. -Bbuild-cmake -GNinja -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DBUILD_REMOTE_CONTROL=1 cmake --build build-cmake --config Release --parallel 10 - mv soh.otr build-cmake/soh (cd build-cmake && cpack) mv _packages/*.dmg SoH.dmg @@ -132,44 +143,52 @@ jobs: path: | SoH.dmg readme.txt + - name: Save Cache MacPorts + if: ${{ github.ref_name == github.event.repository.default_branch }} + uses: actions/cache/save@v4 + with: + key: ${{ steps.restore-cache-macports.outputs.cache-primary-key }} + path: /opt/local/ + build-linux: needs: generate-soh-otr runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - name: Git Checkout + uses: actions/checkout@v4 with: submodules: true - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y $(cat .github/workflows/apt-deps.txt) - - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.14 + - name: Configure ccache + uses: hendrikmuhs/ccache-action@v1.2 with: - key: linux-ccache-${{ github.ref }}-${{ github.sha }} + save: ${{ github.ref_name == github.event.repository.default_branch }} + key: ${{ runner.os }}-ccache-${{ github.ref }}-${{ github.sha }} restore-keys: | - linux-ccache-${{ github.ref }} - linux-ccache- - - name: Cache build folders - uses: actions/cache@v4 + ${{ runner.os }}-ccache-${{ github.ref }} + ${{ runner.os }}-ccache + - name: Restore Cached deps folder + id: restore-cache-deps + uses: actions/cache/restore@v4 with: - key: linux-build-${{ github.ref }}-${{ github.sha }} + key: ${{ runner.os }}-deps-${{ github.ref }}-${{ github.sha }} restore-keys: | - linux-build-${{ github.ref }} - linux-build- - path: | - SDL2-2.30.3 - SDL2_net-2.2.0 - tinyxml2-10.0.0 - libzip-1.10.1 + ${{ runner.os }}-deps-${{ github.ref }}- + ${{ runner.os }}-deps- + path: deps + - name: Create deps folder + run: mkdir -p deps - name: Install latest SDL run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - if [ ! -d "SDL2-2.30.3" ]; then - wget https://www.libsdl.org/release/SDL2-2.30.3.tar.gz - tar -xzf SDL2-2.30.3.tar.gz + if [ ! -d "deps/SDL2-2.30.3" ]; then + wget https://github.com/libsdl-org/SDL/releases/download/release-2.30.3/SDL2-2.30.3.tar.gz + tar -xzf SDL2-2.30.3.tar.gz -C deps fi - cd SDL2-2.30.3 + cd deps/SDL2-2.30.3 ./configure --enable-hidapi-libusb make -j 10 sudo make install @@ -177,11 +196,11 @@ jobs: - name: Install latest SDL_net run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - if [ ! -d "SDL2_net-2.2.0" ]; then + if [ ! -d "deps/SDL2_net-2.2.0" ]; then wget https://www.libsdl.org/projects/SDL_net/release/SDL2_net-2.2.0.tar.gz - tar -xzf SDL2_net-2.2.0.tar.gz + tar -xzf SDL2_net-2.2.0.tar.gz -C deps fi - cd SDL2_net-2.2.0 + cd deps/SDL2_net-2.2.0 ./configure make -j 10 sudo make install @@ -190,11 +209,11 @@ jobs: run: | sudo apt-get remove libtinyxml2-dev export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - if [ ! -d "tinyxml2-10.0.0" ]; then + if [ ! -d "deps/tinyxml2-10.0.0" ]; then wget https://github.com/leethomason/tinyxml2/archive/refs/tags/10.0.0.tar.gz - tar -xzf 10.0.0.tar.gz + tar -xzf 10.0.0.tar.gz -C deps fi - cd tinyxml2-10.0.0 + cd deps/tinyxml2-10.0.0 mkdir -p build cd build cmake .. @@ -203,11 +222,11 @@ jobs: - name: Install libzip without crypto run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - if [ ! -d "libzip-1.10.1" ]; then + if [ ! -d "deps/libzip-1.10.1" ]; then wget https://github.com/nih-at/libzip/releases/download/v1.10.1/libzip-1.10.1.tar.gz - tar -xzf libzip-1.10.1.tar.gz + tar -xzf libzip-1.10.1.tar.gz -C deps fi - cd libzip-1.10.1 + cd deps/libzip-1.10.1 mkdir -p build cd build cmake .. -DENABLE_COMMONCRYPTO=OFF -DENABLE_GNUTLS=OFF -DENABLE_MBEDTLS=OFF -DENABLE_OPENSSL=OFF @@ -218,6 +237,7 @@ jobs: uses: actions/download-artifact@v4 with: name: soh.otr + path: build-cmake/soh - name: Build SoH run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" @@ -237,63 +257,73 @@ jobs: path: | soh.appimage readme.txt + - name: Save Cache deps folder + if: ${{ github.ref_name == github.event.repository.default_branch }} + uses: actions/cache/save@v4 + with: + key: ${{ steps.restore-cache-deps.outputs.cache-primary-key }} + path: deps + build-windows: needs: generate-soh-otr - runs-on: ${{ (vars.WINDOWS_RUNNER && fromJSON(vars.WINDOWS_RUNNER)) || 'windows-latest' }} + runs-on: windows-latest steps: - name: Install dependencies - if: ${{ !vars.WINDOWS_RUNNER }} run: | - choco install ninja + choco install ninja -y Remove-Item -Path "C:\ProgramData\Chocolatey\bin\ccache.exe" -Force -ErrorAction SilentlyContinue - - uses: actions/checkout@v4 + - name: Git Checkout + uses: actions/checkout@v4 with: submodules: true - - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.14 + - name: Configure sccache + uses: hendrikmuhs/ccache-action@v1.2 with: variant: sccache - max-size: "1G" + max-size: "2G" + evict-old-files: job + save: ${{ github.ref_name == github.event.repository.default_branch }} key: ${{ runner.os }}-ccache-${{ github.ref }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-ccache-${{ github.ref }} - ${{ runner.os }}-ccache- - - name: Cache build folder - uses: actions/cache@v4 + ${{ runner.os }}-ccache + - name: Restore Cached VCPKG folder + id: restore-cache-vcpkg + uses: actions/cache/restore@v4 with: - key: ${{ runner.os }}-build-${{ github.ref }}-${{ github.sha }} + key: ${{ runner.os }}-vcpkg-${{ github.ref }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-build-${{ github.ref }} - ${{ runner.os }}-build- - path: | - build-windows - vcpkg + ${{ runner.os }}-vcpkg-${{ github.ref }}- + ${{ runner.os }}-vcpkg- + path: vcpkg - name: Configure Developer Command Prompt uses: ilammy/msvc-dev-cmd@v1 + - name: Download soh.otr + uses: actions/download-artifact@v4 + with: + name: soh.otr + path: build-windows/soh - name: Build SoH - env: + env: VCPKG_ROOT: ${{github.workspace}}/vcpkg run: | set $env:PATH="$env:USERPROFILE/.cargo/bin;$env:PATH" cmake -S . -B build-windows -G Ninja -DCMAKE_MAKE_PROGRAM=ninja -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache -DBUILD_REMOTE_CONTROL=1 cmake --build build-windows --config Release --parallel 10 - mkdir soh-windows - mv ./x64/Release/soh.exe ./soh-windows/soh.exe - mkdir soh-windows/debug - mkdir soh-windows/mods - New-Item soh-windows/mods/custom_otr_files_go_here.txt -type file - mv ./x64/Release/soh.pdb ./soh-windows/debug/soh.pdb - mv ./README.md ./soh-windows/readme.txt - mv ./build-windows/gamecontrollerdb.txt ./soh-windows/gamecontrollerdb.txt - mv ./x64/Release/assets ./soh-windows - - name: Download soh.otr - uses: actions/download-artifact@v4 - with: - name: soh.otr - path: soh-windows + (cd build-windows && cpack) + cd .. + mv _packages/*.zip _packages/soh-windows.zip + - name: Unzip package + run: Expand-Archive -Path _packages/soh-windows.zip -DestinationPath soh-windows - name: Upload build uses: actions/upload-artifact@v4 with: name: soh-windows path: soh-windows + - name: Save Cache VCPKG folder + if: ${{ github.ref_name == github.event.repository.default_branch }} + uses: actions/cache/save@v4 + with: + key: ${{ steps.restore-cache-vcpkg.outputs.cache-primary-key }} + path: vcpkg diff --git a/CMake/Packaging-2.cmake b/CMake/Packaging-2.cmake index a29635eac..dc5da1b7e 100644 --- a/CMake/Packaging-2.cmake +++ b/CMake/Packaging-2.cmake @@ -1,9 +1,9 @@ set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY 0) -set(CPACK_COMPONENTS_ALL "ship" "extractor" "appimage") +set(CPACK_COMPONENTS_ALL "ship") -if (NOT CPACK_GENERATOR STREQUAL "External") - list(REMOVE_ITEM CPACK_COMPONENTS_ALL "appimage") +if (CPACK_GENERATOR STREQUAL "External") + list(APPEND CPACK_COMPONENTS_ALL "extractor" "appimage") endif() if (CPACK_GENERATOR MATCHES "DEB|RPM") diff --git a/CMakeLists.txt b/CMakeLists.txt index 38ac6ce4a..7aa873c62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,7 +174,7 @@ set_property(TARGET soh PROPERTY APPIMAGE_ICON_FILE "${CMAKE_BINARY_DIR}/sohIcon if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") install(PROGRAMS "${CMAKE_BINARY_DIR}/linux/soh.sh" DESTINATION . COMPONENT appimage) -install(FILES "${CMAKE_SOURCE_DIR}/soh.otr" DESTINATION . COMPONENT ship) +install(FILES "${CMAKE_BINARY_DIR}/soh/soh.otr" DESTINATION . COMPONENT ship) install(TARGETS ZAPD DESTINATION ./assets/extractor COMPONENT extractor) install(DIRECTORY "${CMAKE_SOURCE_DIR}/soh/assets/extractor/" DESTINATION ./assets/extractor COMPONENT extractor) install(DIRECTORY "${CMAKE_SOURCE_DIR}/soh/assets/xml/" DESTINATION ./assets/extractor/xmls COMPONENT extractor) From 04458f227410363698e22eee6f5e0a19023150bf Mon Sep 17 00:00:00 2001 From: Malkierian Date: Fri, 21 Mar 2025 04:26:03 -0700 Subject: [PATCH 49/53] Allow F1 to close the menubar modal via new `IsPopupOpen()` and `DismissPopup` functions in `SohModalWindow`. (#5166) --- soh/soh/OTRGlobals.cpp | 6 +++++- soh/soh/SohGui/SohModals.cpp | 15 +++++++++++++++ soh/soh/SohGui/SohModals.h | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index a86c77524..3690e8648 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1296,7 +1296,11 @@ extern "C" void Graph_StartFrame() { switch (dwScancode) { case KbScancode::LUS_KB_F1: { std::shared_ptr modal = static_pointer_cast(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Modal Window")); - modal->RegisterPopup("Menu Moved", "The menubar, accessed by hitting F1, no longer exists.\nThe new menu can be accessed by hitting the Esc button instead.", "OK"); + if (modal->IsPopupOpen("Menu Moved")) { + modal->DismissPopup(); + } else { + modal->RegisterPopup("Menu Moved", "The menubar, accessed by hitting F1, no longer exists.\nThe new menu can be accessed by hitting the Esc button instead.", "OK"); + } break; } case KbScancode::LUS_KB_F5: { diff --git a/soh/soh/SohGui/SohModals.cpp b/soh/soh/SohGui/SohModals.cpp index b1fe0b43b..e2025437f 100644 --- a/soh/soh/SohGui/SohModals.cpp +++ b/soh/soh/SohGui/SohModals.cpp @@ -20,6 +20,8 @@ struct SohModal { }; std::vector modals; +bool closePopup = false; + void SohModalWindow::Draw() { if (!IsVisible()) { return; @@ -35,6 +37,11 @@ void SohModalWindow::DrawElement() { if (!ImGui::IsPopupOpen(curModal.title_.c_str())) { ImGui::OpenPopup(curModal.title_.c_str()); } + if (closePopup) { + ImGui::CloseCurrentPopup(); + modals.erase(modals.begin()); + closePopup = false; + } if (ImGui::BeginPopupModal(curModal.title_.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings)) { ImGui::Text("%s", curModal.message_.c_str()); UIWidgets::PushStyleButton(THEME_COLOR); @@ -66,3 +73,11 @@ void SohModalWindow::DrawElement() { void SohModalWindow::RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, std::function button1callback, std::function button2callback) { modals.push_back({ title, message, button1, button2, button1callback, button2callback }); } + +bool SohModalWindow::IsPopupOpen(std::string title) { + return !modals.empty() && modals.at(0).title_ == title; +} + +void SohModalWindow::DismissPopup() { + closePopup = true; +} diff --git a/soh/soh/SohGui/SohModals.h b/soh/soh/SohGui/SohModals.h index f584b9540..9a1fed80c 100644 --- a/soh/soh/SohGui/SohModals.h +++ b/soh/soh/SohGui/SohModals.h @@ -13,4 +13,6 @@ class SohModalWindow : public Ship::GuiWindow { void DrawElement() override; void UpdateElement() override {}; void RegisterPopup(std::string title, std::string message, std::string button1 = "OK", std::string button2 = "", std::function button1callback = nullptr, std::function button2callback = nullptr); + bool IsPopupOpen(std::string title); + void DismissPopup(); }; \ No newline at end of file From 1e294134af12b1a3d9bace9f71b1f2c8aad93ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Fri, 21 Mar 2025 11:27:53 +0000 Subject: [PATCH 50/53] Remove EnFr_RandomizerCheckFromSongIndex (#5169) * Remove EnFr_RandomizerCheckFromSongIndex Also handle missing return in RawAction::CheckFlag Also adjust jabu jabu shabom timer override to be non-overflowing constant (but same constant) * Update soh/soh/Enhancements/enemyrandomizer.cpp Co-authored-by: Archez * remove shabom room hack from z_obj_roomtimer.c --------- Co-authored-by: Archez --- soh/soh/Enhancements/enemyrandomizer.cpp | 2 +- .../GameInteractor_RawAction.cpp | 3 +++ .../Enhancements/randomizer/hook_handlers.cpp | 21 ------------------- .../ovl_Obj_Roomtimer/z_obj_roomtimer.c | 8 +------ 4 files changed, 5 insertions(+), 29 deletions(-) diff --git a/soh/soh/Enhancements/enemyrandomizer.cpp b/soh/soh/Enhancements/enemyrandomizer.cpp index e638d04ad..326a4a78d 100644 --- a/soh/soh/Enhancements/enemyrandomizer.cpp +++ b/soh/soh/Enhancements/enemyrandomizer.cpp @@ -186,7 +186,7 @@ extern "C" uint8_t GetRandomizedEnemy(PlayState* play, int16_t *actorId, f32 *po // Lengthen timer in non-MQ Jabu Jabu bubble room. if (!isMQ && *actorId == ACTOR_OBJ_ROOMTIMER && *params == 30760 && play->sceneNum == SCENE_JABU_JABU && play->roomCtx.curRoom.num == 12) { - *params = 92280; + *params = (*params & ~0x3FF) | 120; } if (IsEnemyFoundToRandomize(play->sceneNum, play->roomCtx.curRoom.num, *actorId, *params, *posX)) { diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp index d0b32018a..1db2f1e33 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp @@ -220,6 +220,9 @@ bool GameInteractor::RawAction::CheckFlag(int16_t flagType, int16_t flag) { return Flags_GetRandomizerInf(static_cast(flag)); case FlagType::FLAG_GS_TOKEN: return GET_GS_FLAGS((flag & 0x1F00) >> 8); + default: + assert(false); + return false; } } diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index f0baa4c35..9467cb10e 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -547,27 +547,6 @@ u8 EnGm_RandoCanGetMedigoronItem() { !Flags_GetRandomizerInf(RAND_INF_MERCHANTS_MEDIGORON); } -RandomizerCheck EnFr_RandomizerCheckFromSongIndex(u16 songIndex) { - switch (songIndex) { - case FROG_ZL: - return RC_ZR_FROGS_ZELDAS_LULLABY; - case FROG_EPONA: - return RC_ZR_FROGS_EPONAS_SONG; - case FROG_SARIA: - return RC_ZR_FROGS_SARIAS_SONG; - case FROG_SUNS: - return RC_ZR_FROGS_SUNS_SONG; - case FROG_SOT: - return RC_ZR_FROGS_SONG_OF_TIME; - case FROG_STORMS: - return RC_ZR_FROGS_IN_THE_RAIN; - case FROG_CHOIR_SONG: - return RC_ZR_FROGS_OCARINA_GAME; - default: - return RC_UNKNOWN_CHECK; - } -} - void RandomizerSetChestGameRandomizerInf(RandomizerCheck rc) { switch (rc) { case RC_MARKET_TREASURE_CHEST_GAME_ITEM_1: diff --git a/soh/src/overlays/actors/ovl_Obj_Roomtimer/z_obj_roomtimer.c b/soh/src/overlays/actors/ovl_Obj_Roomtimer/z_obj_roomtimer.c index 5bef60ef9..41ababe85 100644 --- a/soh/src/overlays/actors/ovl_Obj_Roomtimer/z_obj_roomtimer.c +++ b/soh/src/overlays/actors/ovl_Obj_Roomtimer/z_obj_roomtimer.c @@ -32,13 +32,7 @@ void ObjRoomtimer_Init(Actor* thisx, PlayState* play) { ObjRoomtimer* this = (ObjRoomtimer*)thisx; s16 params = this->actor.params; - // Shabom room in Jabu Jabu has a lengthened timer in Enemy Randomizer. Flag doesn't match what the game - // expects. Instead set it back to the same flag as what it would be in vanilla. - if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) && play->sceneNum == SCENE_JABU_JABU && play->roomCtx.curRoom.num == 12) { - this->switchFlag = 30; - } else { - this->switchFlag = (params >> 10) & 0x3F; - } + this->switchFlag = (params >> 10) & 0x3F; this->actor.params = params & 0x3FF; params = this->actor.params; From ae5e41f7a06e1decbbe0e3b118d2e257a9021a79 Mon Sep 17 00:00:00 2001 From: Malkierian Date: Fri, 21 Mar 2025 04:32:46 -0700 Subject: [PATCH 51/53] Fix Dungeon Spoiling in Trackers (#5168) * Prevent spoiling dungeons in check and item trackers if MQ is set to Selection, but selection for specific dungeon is Random. * Account for other options using "Set Dungeon Quests". --- .../Enhancements/randomizer/randomizer_check_tracker.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index c51650bbd..62a4793e2 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -483,9 +483,13 @@ void CheckTrackerLoadGame(int32_t fileNum) { for (int i = RCAREA_KOKIRI_FOREST; i < RCAREA_INVALID; i++) { if (!IsAreaSpoiled(static_cast(i)) && (RandomizerCheckObjects::AreaIsOverworld(static_cast(i)) || !IS_RANDO || OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_RANDOM) == RO_MQ_DUNGEONS_NONE || - OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_RANDOM) == RO_MQ_DUNGEONS_SELECTION || + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_RANDOM) == RO_MQ_DUNGEONS_SELECTION && + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(static_cast(RSK_MQ_DEKU_TREE + (i - RCAREA_DEKU_TREE))) != RO_MQ_SET_RANDOM) || + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_SET) == RO_GENERIC_ON && + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(static_cast(RSK_MQ_DEKU_TREE + (i - RCAREA_DEKU_TREE))) != RO_MQ_SET_RANDOM) || (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_RANDOM) == RO_MQ_DUNGEONS_SET_NUMBER && - OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) == 12))) { + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) == 12 || + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) == 0)))) { SetAreaSpoiled(static_cast(i)); } } From 983c075a3328b1071a885110473bbf0352650584 Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Fri, 21 Mar 2025 17:53:17 +0100 Subject: [PATCH 52/53] Overworld Key Tracker (#5164) * Overworld Keys Tracker * Update randomizer_item_tracker.cpp * Fix id collision * Post-merge fixes * Update soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp Co-authored-by: Archez --------- Co-authored-by: Archez --- soh/CMakeLists.txt | 1 + .../Enhancements/randomizer/randomizerTypes.h | 2 + .../randomizer/randomizer_item_tracker.cpp | 224 +++++++++++++++++- soh/soh/util.cpp | 26 ++ 4 files changed, 246 insertions(+), 7 deletions(-) diff --git a/soh/CMakeLists.txt b/soh/CMakeLists.txt index b4ee6ed7f..7277b2dfb 100644 --- a/soh/CMakeLists.txt +++ b/soh/CMakeLists.txt @@ -440,6 +440,7 @@ if(MSVC) /Gy; /W3 > + /bigobj; /sdl-; /permissive-; /MP; diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index e2581a634..23cbb290f 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -3368,6 +3368,8 @@ typedef enum { RG_HOOKSHOT, RG_LONGSHOT, RG_SCARECROW, + + //Overworld keys RG_GUARD_HOUSE_KEY, RG_MARKET_BAZAAR_KEY, RG_MARKET_POTION_SHOP_KEY, diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index 92c7491fc..13046874f 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -107,11 +107,40 @@ std::vector bossSoulItems = { std::vector ocarinaButtonItems = { //Hack for right now, just gonna draw ocarina buttons as ocarinas. //Will replace with other macro once we have a custom texture - ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_A_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem ), - ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_C_UP_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem ), - ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_C_DOWN_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem ), - ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_C_LEFT_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem ), - ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_C_RIGHT_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem ), + ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_A_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_C_UP_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_C_DOWN_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_C_LEFT_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_OCARINA_C_RIGHT_BUTTON, ITEM_OCARINA_TIME, ITEM_OCARINA_TIME, 0, DrawItem), +}; + +std::vector overworldKeyItems = { + // Hack for right now, just gonna overworld keys as dungeon keys. + // Will replace with other macro once we have a custom texture + ITEM_TRACKER_ITEM_CUSTOM(RG_GUARD_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_MARKET_BAZAAR_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_MARKET_POTION_SHOP_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_MASK_SHOP_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_MARKET_SHOOTING_GALLERY_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_BOMBCHU_BOWLING_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_TREASURE_CHEST_GAME_BUILDING_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_BOMBCHU_SHOP_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_RICHARDS_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_ALLEY_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_KAK_BAZAAR_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_KAK_POTION_SHOP_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_BOSS_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_GRANNYS_POTION_SHOP_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_SKULLTULA_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_IMPAS_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_WINDMILL_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_KAK_SHOOTING_GALLERY_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_DAMPES_HUT_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_TALONS_HOUSE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_STABLES_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_BACK_TOWER_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_HYLIA_LAB_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), + ITEM_TRACKER_ITEM_CUSTOM(RG_FISHING_HOLE_KEY, ITEM_KEY_SMALL, ITEM_KEY_SMALL, 0, DrawItem), }; std::vector fishingPoleItems = { @@ -208,6 +237,33 @@ std::map itemTrackerOcarinaButtonShortNames = { { RG_OCARINA_C_RIGHT_BUTTON, "C-R"}, }; +std::map itemTrackerOverworldKeyShortNames = { + { RG_GUARD_HOUSE_KEY, "GUARD" }, + { RG_MARKET_BAZAAR_KEY, "MKBAZ" }, + { RG_MARKET_POTION_SHOP_KEY, "MKPOT" }, + { RG_MASK_SHOP_KEY, "MASK" }, + { RG_MARKET_SHOOTING_GALLERY_KEY, "MKSHO" }, + { RG_BOMBCHU_BOWLING_KEY, "BOWL" }, + { RG_TREASURE_CHEST_GAME_BUILDING_KEY, "TREASU" }, + { RG_BOMBCHU_SHOP_KEY, "CHUSHO" }, + { RG_RICHARDS_HOUSE_KEY, "RICH" }, + { RG_ALLEY_HOUSE_KEY, "ALLEY" }, + { RG_KAK_BAZAAR_KEY, "KAKBAZ" }, + { RG_KAK_POTION_SHOP_KEY, "KAKPO" }, + { RG_BOSS_HOUSE_KEY, "BOSS" }, + { RG_GRANNYS_POTION_SHOP_KEY, "GRANNY" }, + { RG_SKULLTULA_HOUSE_KEY, "SKULL" }, + { RG_IMPAS_HOUSE_KEY, "IMPAS" }, + { RG_WINDMILL_KEY, "WIND" }, + { RG_KAK_SHOOTING_GALLERY_KEY, "KAKSHO" }, + { RG_DAMPES_HUT_KEY, "DAMPES" }, + { RG_TALONS_HOUSE_KEY, "TALONS" }, + { RG_STABLES_KEY, "STABLE" }, + { RG_BACK_TOWER_KEY, "TOWER" }, + { RG_HYLIA_LAB_KEY, "LAB" }, + { RG_FISHING_HOLE_KEY, "FISH" }, +}; + std::vector dungeonItems = {}; std::unordered_map actualItemTrackerItemMap = { @@ -645,7 +701,9 @@ void DrawItem(ItemTrackerItem item) { bool hasItem = actualItemId != ITEM_NONE; std::string itemName = ""; - if (item.id == ITEM_NONE) { + // Hack fix as RG_MARKET_SHOOTING_GALLERY_KEY is RandomizerGet #255 which collides + // with ITEM_NONE (ItemId #255) due to the lack of a modid to separate them + if (item.name != "ITEM_KEY_SMALL" && item.id == ITEM_NONE) { return; } @@ -732,7 +790,6 @@ void DrawItem(ItemTrackerItem item) { actualItemId = item.id; hasItem = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_BOSS_SOULS) == RO_BOSS_SOULS_ON_PLUS_GANON ? Flags_GetRandomizerInf(RAND_INF_GANON_SOUL) : true; - itemName = "Ganon's Soul"; break; @@ -766,6 +823,127 @@ void DrawItem(ItemTrackerItem item) { hasItem = IS_RANDO && Flags_GetRandomizerInf(RAND_INF_FISHING_POLE_FOUND); itemName = "Fishing Pole"; break; + + case RG_GUARD_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_GUARD_HOUSE_KEY_OBTAINED); + itemName = "Guard House Key"; + break; + case RG_MARKET_BAZAAR_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_MARKET_BAZAAR_KEY_OBTAINED); + itemName = "Market Bazaar Key"; + break; + case RG_MARKET_POTION_SHOP_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_MARKET_POTION_SHOP_KEY_OBTAINED); + itemName = "Market Potion Shop Key"; + break; + case RG_MASK_SHOP_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_MASK_SHOP_KEY_OBTAINED); + itemName = "Mask Shop Key"; + break; + case RG_MARKET_SHOOTING_GALLERY_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_MARKET_SHOOTING_GALLERY_KEY_OBTAINED); + itemName = "Market Shooting Gallery Key"; + break; + case RG_BOMBCHU_BOWLING_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_BOMBCHU_BOWLING_KEY_OBTAINED); + itemName = "Bombchu Bowling Key"; + break; + case RG_TREASURE_CHEST_GAME_BUILDING_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_TREASURE_CHEST_GAME_BUILDING_KEY_OBTAINED); + itemName = "Treasure Chest Game Building Key"; + break; + case RG_BOMBCHU_SHOP_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_BOMBCHU_SHOP_KEY_OBTAINED); + itemName = "Bombchu Shop Key"; + break; + case RG_RICHARDS_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_RICHARDS_HOUSE_KEY_OBTAINED); + itemName = "Richards House Key"; + break; + case RG_ALLEY_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_ALLEY_HOUSE_KEY_OBTAINED); + itemName = "Alley House Key"; + break; + case RG_KAK_BAZAAR_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_KAK_BAZAAR_KEY_OBTAINED); + itemName = "Kak Bazaar Key"; + break; + case RG_KAK_POTION_SHOP_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_KAK_POTION_SHOP_KEY_OBTAINED); + itemName = "Kak Potion Shop Key"; + break; + case RG_BOSS_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_BOSS_HOUSE_KEY_OBTAINED); + itemName = "Boss House Key"; + break; + case RG_GRANNYS_POTION_SHOP_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_GRANNYS_POTION_SHOP_KEY_OBTAINED); + itemName = "Granny's Potion Shop Key"; + break; + case RG_SKULLTULA_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_SKULLTULA_HOUSE_KEY_OBTAINED); + itemName = "Skulltula House Key"; + break; + case RG_IMPAS_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_IMPAS_HOUSE_KEY_OBTAINED); + itemName = "Impa's House Key"; + break; + case RG_WINDMILL_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_WINDMILL_KEY_OBTAINED); + itemName = "Windmill Key"; + break; + case RG_KAK_SHOOTING_GALLERY_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_KAK_SHOOTING_GALLERY_KEY_OBTAINED); + itemName = "Kak Shooting Gallery Key"; + break; + case RG_DAMPES_HUT_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_DAMPES_HUT_KEY_OBTAINED); + itemName = "Dampé's Hut Key"; + break; + case RG_TALONS_HOUSE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_TALONS_HOUSE_KEY_OBTAINED); + itemName = "Talon's House Key"; + break; + case RG_STABLES_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_STABLES_KEY_OBTAINED); + itemName = "Stables Key"; + break; + case RG_BACK_TOWER_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_BACK_TOWER_KEY_OBTAINED); + itemName = "Back Tower Key"; + break; + case RG_HYLIA_LAB_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_HYLIA_LAB_KEY_OBTAINED); + itemName = "Hylia Lab Key"; + break; + case RG_FISHING_HOLE_KEY: + actualItemId = item.id; + hasItem = Flags_GetRandomizerInf(RAND_INF_FISHING_HOLE_KEY_OBTAINED); + itemName = "Fishing Hole Key"; + break; } if (GameInteractor::IsSaveLoaded() && (hasItem && item.id != actualItemId && actualItemTrackerItemMap.find(actualItemId) != actualItemTrackerItemMap.end())) { @@ -797,6 +975,15 @@ void DrawItem(ItemTrackerItem item) { ImGui::PopStyleColor(); } + if (item.id >= RG_GUARD_HOUSE_KEY && item.id <= RG_FISHING_HOLE_KEY) { + ImVec2 p = ImGui::GetCursorScreenPos(); + std::string overworldKeyName = itemTrackerOverworldKeyShortNames[item.id]; + ImGui::SetCursorScreenPos(ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize(overworldKeyName.c_str()).x / 2), p.y - (iconSize + 13))); + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL_WHITE); + ImGui::Text("%s", overworldKeyName.c_str()); + ImGui::PopStyleColor(); + } + ImGui::EndGroup(); if (itemName == "") { @@ -1189,6 +1376,17 @@ void UpdateVectors() { mainWindowItems.insert(mainWindowItems.end(), ocarinaButtonItems.begin(), ocarinaButtonItems.end()); } + //If we're adding overworld keys to the main window... + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.OverworldKeys"), SECTION_DISPLAY_HIDDEN) == SECTION_DISPLAY_MAIN_WINDOW) { + //...add empty items on the main window to get the keys on their own row. (Too many to sit with Greg/Triforce pieces/boss souls/ocarina buttons) + while (mainWindowItems.size() % 6) { + mainWindowItems.push_back(ITEM_TRACKER_ITEM(ITEM_NONE, 0, DrawItem)); + } + + //Add overworld keys + mainWindowItems.insert(mainWindowItems.end(), overworldKeyItems.begin(), overworldKeyItems.end()); + } + shouldUpdateVectors = false; } @@ -1333,6 +1531,12 @@ void ItemTrackerWindow::DrawElement() { EndFloatingWindows(); } + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.OverworldKeys"), SECTION_DISPLAY_HIDDEN) == SECTION_DISPLAY_SEPARATE) { + BeginFloatingWindows("Overworld Key Tracker"); + DrawItemsInRows(overworldKeyItems); + EndFloatingWindows(); + } + if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.FishingPole"), SECTION_DISPLAY_EXTENDED_HIDDEN) == SECTION_DISPLAY_EXTENDED_SEPARATE) { BeginFloatingWindows("Fishing Pole Tracker"); DrawItemsInRows(fishingPoleItems); @@ -1516,6 +1720,12 @@ void ItemTrackerSettingsWindow::DrawElement() { shouldUpdateVectors = true; } + if (CVarCombobox("Overworld Keys", CVAR_TRACKER_ITEM("DisplayType.OverworldKeys"), displayTypes, ComboboxOptions() + .DefaultIndex(SECTION_DISPLAY_HIDDEN).ComponentAlignment(ComponentAlignments::Right) + .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { + shouldUpdateVectors = true; + } + if (CVarCombobox("Fishing Pole", CVAR_TRACKER_ITEM("DisplayType.FishingPole"), extendedDisplayTypes, ComboboxOptions() .DefaultIndex(SECTION_DISPLAY_EXTENDED_HIDDEN).ComponentAlignment(ComponentAlignments::Right) .LabelPosition(LabelPositions::Far).Color(THEME_COLOR))) { diff --git a/soh/soh/util.cpp b/soh/soh/util.cpp index 4eeaa16fd..7d3189107 100644 --- a/soh/soh/util.cpp +++ b/soh/soh/util.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "Enhancements/randomizer/randomizerTypes.h" std::vector sceneNames = { @@ -341,18 +343,42 @@ std::array rcareaPrefixes = { }; const std::string& SohUtils::GetSceneName(int32_t scene) { + if (scene > sceneNames.size()) { + SPDLOG_WARN("Passed invalid scene id to SohUtils::GetSceneName: ({})", scene); + assert(false); + return ""; + } + return sceneNames[scene]; } const std::string& SohUtils::GetItemName(int32_t item) { + if (item > itemNames.size()) { + SPDLOG_WARN("Passed invalid item id to SohUtils::GetItemName: ({})", item); + assert(false); + return ""; + } + return itemNames[item]; } const std::string& SohUtils::GetQuestItemName(int32_t item) { + if (item > questItemNames.size()) { + SPDLOG_WARN("Passed invalid quest item id to SohUtils::GetQuestItemName: ({})", item); + assert(false); + return ""; + } + return questItemNames[item]; } const std::string& SohUtils::GetRandomizerCheckAreaPrefix(int32_t rcarea) { + if (rcarea > rcareaPrefixes.size()) { + SPDLOG_WARN("Passed invalid rcarea to SohUtils::GetRandomizerCheckAreaPrefix: ({})", rcarea); + assert(false); + return ""; + } + return rcareaPrefixes[rcarea]; } From 69a32ffba671b1bc55b4bbc9b13040687abe40ec Mon Sep 17 00:00:00 2001 From: Malkierian Date: Fri, 21 Mar 2025 10:42:59 -0700 Subject: [PATCH 53/53] Restore combobox functionality to Bunny Hood Effect (#5167) * Restore combobox functionality to Bunny Hood Effect, and tweak the tooltip. * Tweak the description a bit more. * Final tweak? --- soh/soh/SohGui/SohMenuEnhancements.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 2e0cb55af..ad12c92aa 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -10,6 +10,7 @@ static std::string comboboxTooltip = ""; static int32_t enhancementPresetSelected = ENHANCEMENT_PRESET_DEFAULT; bool isBetaQuestEnabled = false; +static std::unordered_map bunnyHoodEffectMap = {{ BUNNY_HOOD_VANILLA, "Vanilla" }, { BUNNY_HOOD_FAST, "Faster Run" }, { BUNNY_HOOD_FAST_AND_JUMP, "Faster + Longer Jump" }}; extern "C" { void enableBetaQuest() { isBetaQuestEnabled = true; } @@ -678,10 +679,11 @@ void SohMenu::AddMenuEnhancements() { " - Not within range of Ocarina Playing spots.")); AddWidget(path, "Masks", WIDGET_SEPARATOR_TEXT); - AddWidget(path, "Bunny Hood Effect", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "Bunny Hood Effect", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_ENHANCEMENT("MMBunnyHood")) - .Options(CheckboxOptions().Tooltip("Wearing the Bunny Hood grants a speed increase link in Majora's Mask. " - "The longer jump option is not accounted for in Randomizer logic.\n\n" + .Options(ComboboxOptions().ComboMap(bunnyHoodEffectMap).Tooltip("Wearing the Bunny Hood grants a speed and jump boost like in Majora's Mask.\n" + "Can also be limited to only the speed boost.\n" + "The effects of either option are not accounted for in Randomizer logic.\n" "Also disables NPC's reactions to wearing the Bunny Hood.")); AddWidget(path, "Masks Equippable as Adult", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("AdultMasks"))