From 4c04c7615fbbda07992de88e57283e7d0b3820c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= <159546+serprex@users.noreply.github.com> Date: Tue, 15 Sep 2026 00:59:12 +0000 Subject: [PATCH] Save editor flag improvements (#7198) 1. clicking on flag name toggles checkbox 2. hovering over player state shows flag name 3. add search box for player state, adjust names to what headers call them --- .../Enhancements/debugger/debugSaveEditor.cpp | 93 ++++++++++++++----- .../Enhancements/debugger/debugSaveEditor.h | 26 +++--- 2 files changed, 85 insertions(+), 34 deletions(-) diff --git a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp index 1ae5530838..35d8e5d928 100644 --- a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp +++ b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp @@ -112,6 +112,7 @@ ButtonOptions buttonOptionsBase; CheckboxOptions checkboxOptionsBase; ComboboxOptions comboboxOptionsBase; static std::map flagTableFilters; +static ImGuiTextFilter playerStateFilter; // Modification of gAmmoItems that replaces ITEM_NONE with the item in inventory slot it represents u8 gAllAmmoItems[] = { @@ -1350,6 +1351,35 @@ void DrawFlagArrayWithTooltips(const std::string& name, T& flags, Colors color, ImGui::PopID(); } +// List state flags matching the search, returns whether any matched +template +bool DrawStateSearchResults(const char* label, T& flags, const std::vector& names, + ImGuiTextFilter& filter) { + bool hasMatches = false; + ImGui::PushID(label); + for (size_t flagIndex = 0; flagIndex < names.size() && flagIndex < sizeof(T) * 8; flagIndex++) { + std::string text = spdlog::fmt_lib::format("{} Bit {}: {}", label, flagIndex, names[flagIndex]); + if (!filter.PassFilter(text.c_str())) { + continue; + } + hasMatches = true; + + ImGui::PushID(static_cast(flagIndex)); + uint32_t bitMask = 1u << flagIndex; + bool flag = (flags & bitMask) != 0; + PushStyleCheckbox(THEME_COLOR); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); + if (ImGui::Checkbox(text.c_str(), &flag)) { + flags ^= bitMask; + } + ImGui::PopStyleVar(); + PopStyleCheckbox(); + ImGui::PopID(); + } + ImGui::PopID(); + return hasMatches; +} + static const char* GetFlagDescription(const FlagTable& flagTable, uint16_t index) { if (flagTable.flagTableType == RANDOMIZER_INF) { std::string_view name = EnumToString(static_cast(index)); @@ -1431,7 +1461,7 @@ void DrawActiveStates(const std::string& label, uint32_t states, const std::vect if (!hasAny) active += "None"; ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); - ImGui::Text("%s", active.c_str()); + ImGui::TextWrapped("%s", active.c_str()); ImGui::PopStyleColor(); } @@ -1499,12 +1529,18 @@ static void DrawFlagTableSearchResults(const FlagTable& flagTable, ImGuiTextFilt } ImGui::SameLine(); + ImVec2 labelPos = ImGui::GetCursorScreenPos(); if (hasDescription) { ImGui::TextWrapped("0x%02X: %s", index, desc); } else { ImGui::Text("0x%02X", index); } + ImGui::SetCursorScreenPos(labelPos); + if (ImGui::InvisibleButton("##label", ImGui::GetItemRectSize())) { + flags ^= bitMask; + } + ImGui::PopID(); } } @@ -2603,31 +2639,46 @@ void DrawPlayerTab() { ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Player State"); ImGui::Spacing(); - // State Flags 1 - ImGui::Text("State 1"); - DrawFlagArrayWithTooltips("State1", player->stateFlags1, THEME_COLOR, state1); - - // Show active State 1 flags + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 16); + PushStyleInput(THEME_COLOR); + playerStateFilter.Draw(); + PopStyleInput(); ImGui::Spacing(); - DrawActiveStates("State1", player->stateFlags1, state1); - ImGui::Spacing(); - // State Flags 2 - ImGui::Text("State 2"); - DrawFlagArrayWithTooltips("State2", player->stateFlags2, THEME_COLOR, state2); + if (playerStateFilter.IsActive()) { + bool hasMatches = DrawStateSearchResults("State 1", player->stateFlags1, state1, playerStateFilter); + hasMatches |= DrawStateSearchResults("State 2", player->stateFlags2, state2, playerStateFilter); + hasMatches |= DrawStateSearchResults("State 3", player->stateFlags3, state3, playerStateFilter); + if (!hasMatches) { + ImGui::Text("No flags match the current search."); + } + } else { + // State Flags 1 + ImGui::Text("State 1"); + DrawFlagArrayWithTooltips("State1", player->stateFlags1, THEME_COLOR, state1); - // Show active State 2 flags - ImGui::Spacing(); - DrawActiveStates("State2", player->stateFlags2, state2); + // Show active State 1 flags + ImGui::Spacing(); + DrawActiveStates("State1", player->stateFlags1, state1); - ImGui::Spacing(); - // State Flags 3 - ImGui::Text("State 3"); - DrawFlagArrayWithTooltips("State3", player->stateFlags3, THEME_COLOR, state3); + ImGui::Spacing(); + // State Flags 2 + ImGui::Text("State 2"); + DrawFlagArrayWithTooltips("State2", player->stateFlags2, THEME_COLOR, state2); - // Show active State 3 flags - ImGui::Spacing(); - DrawActiveStates("State3", player->stateFlags3, state3); + // Show active State 2 flags + ImGui::Spacing(); + DrawActiveStates("State2", player->stateFlags2, state2); + + ImGui::Spacing(); + // State Flags 3 + ImGui::Text("State 3"); + DrawFlagArrayWithTooltips("State3", player->stateFlags3, THEME_COLOR, state3); + + // Show active State 3 flags + ImGui::Spacing(); + DrawActiveStates("State3", player->stateFlags3, state3); + } ImGui::Spacing(); // Rotation Flags (unk_6AE_rotFlags) diff --git a/soh/soh/Enhancements/debugger/debugSaveEditor.h b/soh/soh/Enhancements/debugger/debugSaveEditor.h index 3a82149d37..b1692037ff 100644 --- a/soh/soh/Enhancements/debugger/debugSaveEditor.h +++ b/soh/soh/Enhancements/debugger/debugSaveEditor.h @@ -368,33 +368,33 @@ const std::vector state1 = { "Swinging Bottle", "Falling from Hookshot", "Hookshot/Bow in Hand", - "Targeting Enemy", + "Hostile Lock-On", "Input Disabled", - "Text on Screen", + "Talking", "Death", "Starting to put away", "Ready to Fire", "Get Item", - "Item over Head", + "Carrying Actor", "Charging Spin Attack", "Hanging off Ledge", "Climbing Ledge", - "Targeting", - "Target Locked", - "Targeting Nothing", + "Z-Targeting", + "Friendly Actor Focus", + "Parallel Targeting", "Jumping Forward", "In Freefall", "In First-Person View", "Climbing Ladder", "Shielding", "On Horse", - "Boomerang in Hand", + "Using Boomerang", "Boomerang Thrown", "Damaged", "In Water", "In Item Cutscene", "In Cutscene", - "30", // Unknown + "Lock-On Forced to Release", "Floor collision disabled", }; @@ -408,18 +408,18 @@ const std::vector state2 = { "Disabled Rotation", "Grabbed by Enemy", "Grabbing Dynapoly", - "Spawning Dust", + "Force Sand Floor Sound", "Underwater", "Diving", "Stationary on Ladder", - "Switch Targeting", + "Lock-On with Switch Targeting", "Frozen", "Pause Most Updating", "Enter", "Spin Attack w/o Magic", "Crawling", "Hopping", - "Navi Out", + "Navi Active", "Navi Alert", "Down", "Near Ocarina Actor", @@ -427,10 +427,10 @@ const std::vector state2 = { "Playing for Oca. Actor", "Reflection", "Ocarina Playing", - "Idling", + "Idle Fidget", "Disabled draw func", "Sword Lunge", - "Void out", + "Forced Void Out", }; const std::vector state3 = {