diff options
| author | Garrett Cox <garrettjcox@gmail.com> | 2026-07-26 23:10:11 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-26 23:10:11 -0500 |
| commit | 99ff062849465b9c610faa715438c90758b4c9d7 (patch) | |
| tree | ae28373ca3ae054a0b87b9ff9545aa4bc60c6668 | |
| parent | d66aafc3779f2aab5b69125c2b0bba24e52c6821 (diff) | |
Improvements to excluded checks (#1817)
| -rw-r--r-- | mm/2s2h/PresetManager/PresetManager.cpp | 112 | ||||
| -rw-r--r-- | mm/2s2h/Rando/ExcludedChecks.cpp | 47 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Logic/GeneratePools.cpp | 30 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Menu.cpp | 246 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Rando.h | 3 |
5 files changed, 345 insertions, 93 deletions
diff --git a/mm/2s2h/PresetManager/PresetManager.cpp b/mm/2s2h/PresetManager/PresetManager.cpp index 12b74d8e3..e468168fb 100644 --- a/mm/2s2h/PresetManager/PresetManager.cpp +++ b/mm/2s2h/PresetManager/PresetManager.cpp @@ -7,6 +7,7 @@ #include "2s2h/BenGui/UIWidgets.hpp" #include "2s2h/BenGui/Notification.h" #include <ship/window/FileDropMgr.h> +#include <ship/config/Config.h> std::unordered_map<std::string, std::string> tagMap = { { "gEventLog", "Developer Tools" }, @@ -505,7 +506,53 @@ nlohmann::json voyage3PresetJ = R"( "gRando": { "CSMC": 1, "Enabled": 1, - "ExcludedChecks": "6,20,24,55,57,76,98,99,100,101,102,103,104,105,106,138,139,140,141,142,143,144,146,242,243,244,245,246,247,248,249,258,374,375,376,377,378,379,380,381,382,383,384,385,1195,", + "ExcludedChecks": [ + "RC_BENEATH_THE_GRAVEYARD_DAMPE_CHEST", + "RC_CLOCK_TOWN_EAST_HONEY_DARLING_ALL_DAYS", + "RC_CLOCK_TOWN_EAST_SHOOTING_GALLERY_PERFECT_SCORE", + "RC_DEKU_PLAYGROUND_ALL_DAYS", + "RC_DEKU_SHRINE_MASK_OF_SCENTS", + "RC_GREAT_BAY_COAST_FISHERMAN_MINIGAME", + "RC_GREAT_BAY_TEMPLE_SF_CENTRAL_ROOM_BARREL", + "RC_GREAT_BAY_TEMPLE_SF_CENTRAL_ROOM_UNDERWATER_POT", + "RC_GREAT_BAY_TEMPLE_SF_COMPASS_ROOM_TUNNEL_POT", + "RC_GREAT_BAY_TEMPLE_SF_GREEN_PIPE_3_BARREL", + "RC_GREAT_BAY_TEMPLE_SF_MAP_ROOM_POT", + "RC_GREAT_BAY_TEMPLE_SF_PRE_BOSS_ABOVE_WATER", + "RC_GREAT_BAY_TEMPLE_SF_PRE_BOSS_UNDERWATER", + "RC_GREAT_BAY_TEMPLE_SF_WATER_WHEEL_PLATFORM", + "RC_GREAT_BAY_TEMPLE_SF_WATER_WHEEL_SKULLTULA", + "RC_MOON_FIERCE_DEITY_MASK", + "RC_MOON_TRIAL_DEKU_PIECE_OF_HEART", + "RC_MOON_TRIAL_GORON_PIECE_OF_HEART", + "RC_MOON_TRIAL_LINK_GARO_MASTER_CHEST", + "RC_MOON_TRIAL_LINK_IRON_KNUCKLE_CHEST", + "RC_MOON_TRIAL_LINK_PIECE_OF_HEART", + "RC_MOON_TRIAL_ZORA_PIECE_OF_HEART", + "RC_MOUNTAIN_VILLAGE_FROG_CHOIR", + "RC_SNOWHEAD_TEMPLE_SF_BRIDGE_PILLAR", + "RC_SNOWHEAD_TEMPLE_SF_BRIDGE_UNDER_PLATFORM", + "RC_SNOWHEAD_TEMPLE_SF_COMPASS_ROOM_CRATE", + "RC_SNOWHEAD_TEMPLE_SF_DINOLFOS_01", + "RC_SNOWHEAD_TEMPLE_SF_DINOLFOS_02", + "RC_SNOWHEAD_TEMPLE_SF_DUAL_SWITCHES", + "RC_SNOWHEAD_TEMPLE_SF_MAP_ROOM", + "RC_SNOWHEAD_TEMPLE_SF_SNOW_ROOM", + "RC_STOCK_POT_INN_COUPLES_MASK", + "RC_WOODFALL_TEMPLE_SF_ENTRANCE", + "RC_WOODFALL_TEMPLE_SF_MAIN_BUBBLE", + "RC_WOODFALL_TEMPLE_SF_MAIN_DEKU_BABA", + "RC_WOODFALL_TEMPLE_SF_MAIN_POT", + "RC_WOODFALL_TEMPLE_SF_MAZE_BEEHIVE", + "RC_WOODFALL_TEMPLE_SF_MAZE_BUBBLE", + "RC_WOODFALL_TEMPLE_SF_MAZE_SKULLTULA", + "RC_WOODFALL_TEMPLE_SF_PRE_BOSS_BOTTOM_RIGHT", + "RC_WOODFALL_TEMPLE_SF_PRE_BOSS_LEFT", + "RC_WOODFALL_TEMPLE_SF_PRE_BOSS_PILLAR", + "RC_WOODFALL_TEMPLE_SF_PRE_BOSS_TOP_RIGHT", + "RC_WOODFALL_TEMPLE_SF_WATER_ROOM_BEEHIVE", + "RC_WATERFALL_RAPIDS_BEAVER_RACE_02" + ], "GenerateSpoiler": 0, "JunkItems": 0, "Options": { @@ -635,6 +682,51 @@ void PresetManager_RefreshPresets() { } } +static void EnsureConfigPathExists(const std::string& key) { + auto config = Ship::Context::GetRawInstance()->GetConfig(); + nlohmann::json configJson = config->GetNestedJson(); + nlohmann::json* node = &configJson; + std::string ancestorPath = ""; + size_t start = 0; + + while (start < key.size()) { + size_t nextDot = key.find('.', start); + std::string part = key.substr(start, nextDot == std::string::npos ? key.size() - start : nextDot - start); + start = nextDot == std::string::npos ? key.size() : nextDot + 1; + ancestorPath = ancestorPath.empty() ? part : ancestorPath + "." + part; + + if (!node->is_object() || !node->contains(part) || !(*node)[part].is_object()) { + config->SetBlock(ancestorPath, nlohmann::json::object()); + (*node)[part] = nlohmann::json::object(); + } + + node = &(*node)[part]; + } +} + +// Walks a preset's CVars block, setting each value it finds. This is the descent flatten() used to do for us, but +// flattening also turns a list into "StartingItems.0", "StartingItems.1"... leaves, which write over whatever list the +// user already had one index at a time, leaving the tail of a longer list in place and producing a list that is +// neither theirs nor the preset's. Walking it here is what lets a list be caught one level up and set whole. +static void ApplyPresetCVars(const std::string& path, const nlohmann::json& value) { + if (value.is_object()) { + for (auto& [key, nestedValue] : value.items()) { + ApplyPresetCVars(path.empty() ? key : path + "." + key, nestedValue); + } + } else if (value.is_array()) { + // Lists live in the config rather than in a CVar, same as when the menus write them + std::string key = "CVars." + path; + EnsureConfigPathExists(key.substr(0, key.find_last_of('.'))); + Ship::Context::GetRawInstance()->GetConfig()->SetBlock(key, value); + } else if (value.is_string()) { + CVarSetString(path.c_str(), value.get<std::string>().c_str()); + } else if (value.is_number_integer()) { + CVarSetInteger(path.c_str(), value.get<int>()); + } else if (value.is_number_float()) { + CVarSetFloat(path.c_str(), value.get<float>()); + } +} + void PresetManager_ApplyPreset(nlohmann::json j) { if (!j.contains("type") || j["type"] != "2S2H_PRESET") { throw std::runtime_error("Invalid preset"); @@ -656,23 +748,7 @@ void PresetManager_ApplyPreset(nlohmann::json j) { } if (j.contains("CVars")) { - auto cvars = j["CVars"].flatten(); - - for (auto& [key, value] : cvars.items()) { - // Replace slashes with dots in key, and remove leading dot - std::string path = key; - std::replace(path.begin(), path.end(), '/', '.'); - if (path[0] == '.') { - path.erase(0, 1); - } - if (value.is_string()) { - CVarSetString(path.c_str(), value.get<std::string>().c_str()); - } else if (value.is_number_integer()) { - CVarSetInteger(path.c_str(), value.get<int>()); - } else if (value.is_number_float()) { - CVarSetFloat(path.c_str(), value.get<float>()); - } - } + ApplyPresetCVars("", j["CVars"]); } Ship::Context::GetRawInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); diff --git a/mm/2s2h/Rando/ExcludedChecks.cpp b/mm/2s2h/Rando/ExcludedChecks.cpp new file mode 100644 index 000000000..c359a9f1f --- /dev/null +++ b/mm/2s2h/Rando/ExcludedChecks.cpp @@ -0,0 +1,47 @@ +#include "Rando.h" +#include "2s2h/Rando/StaticData/StaticData.h" +#include <libultraship/libultraship.h> +#include <libultraship/bridge/consolevariablebridge.h> + +namespace Rando { + +std::vector<RandoCheckId> GetExcludedChecksFromConfig() { + auto allConfig = Ship::Context::GetRawInstance()->GetConfig()->GetNestedJson(); + std::vector<RandoCheckId> excludedChecks; + + if (allConfig.find("CVars") != allConfig.end() && allConfig["CVars"].is_object() && + allConfig["CVars"].find("gRando") != allConfig["CVars"].end() && allConfig["CVars"]["gRando"].is_object() && + allConfig["CVars"]["gRando"].find("ExcludedChecks") != allConfig["CVars"]["gRando"].end()) { + + if (allConfig["CVars"]["gRando"]["ExcludedChecks"].is_array()) { + auto excludedChecksStrings = allConfig["CVars"]["gRando"]["ExcludedChecks"].get<std::vector<std::string>>(); + for (auto& checkName : excludedChecksStrings) { + auto randoCheckId = Rando::StaticData::GetCheckIdFromName(checkName.c_str()); + if (randoCheckId > RC_UNKNOWN && randoCheckId < RC_MAX) { + excludedChecks.push_back(randoCheckId); + } + } + } else if (allConfig["CVars"]["gRando"]["ExcludedChecks"].is_string()) { + CVarClear("gRando.ExcludedChecks"); + Ship::Context::GetRawInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } + + std::sort(excludedChecks.begin(), excludedChecks.end()); + return excludedChecks; +} + +void SetExcludedChecksInConfig(std::vector<RandoCheckId>& excludedChecks) { + auto excludedChecksJson = nlohmann::json::array(); + + for (auto& randoCheckId : excludedChecks) { + if (randoCheckId > RC_UNKNOWN && randoCheckId < RC_MAX) { + excludedChecksJson.push_back(Rando::StaticData::Checks[randoCheckId].name); + } + } + + Ship::Context::GetRawInstance()->GetConfig()->SetBlock("CVars.gRando.ExcludedChecks", excludedChecksJson); + Ship::Context::GetRawInstance()->GetConfig()->Save(); +} + +} // namespace Rando diff --git a/mm/2s2h/Rando/Logic/GeneratePools.cpp b/mm/2s2h/Rando/Logic/GeneratePools.cpp index 08dcd6fe5..8a5195a8c 100644 --- a/mm/2s2h/Rando/Logic/GeneratePools.cpp +++ b/mm/2s2h/Rando/Logic/GeneratePools.cpp @@ -1,7 +1,6 @@ #include "Logic.h" #include "Rando/MiscBehavior/ClockShuffle.h" #include <libultraship/bridge/consolevariablebridge.h> -#include <sstream> extern "C" { #include "variables.h" @@ -17,13 +16,7 @@ void GeneratePools(RandoSaveInfo& saveInfo, std::vector<RandoCheckId>& checkPool std::vector<RandoItemId> computedStartingItems = Rando::GetComputedStartingItems(saveInfo); startingItems.insert(startingItems.end(), computedStartingItems.begin(), computedStartingItems.end()); - std::vector<RandoCheckId> excludedChecks; - std::string excludedChecksList = CVarGetString("gRando.ExcludedChecks", ""); - std::string word; - std::istringstream stream(excludedChecksList); - while (std::getline(stream, word, ',')) { - excludedChecks.push_back((RandoCheckId)std::stoi(word)); - } + std::vector<RandoCheckId> excludedChecks = Rando::GetExcludedChecksFromConfig(); // First loop through all regions and add checks/items to the pool for (auto& [randoRegionId, randoRegion] : Rando::Logic::Regions) { @@ -143,17 +136,20 @@ void GeneratePools(RandoSaveInfo& saveInfo, std::vector<RandoCheckId>& checkPool } } - // When a check is skipped, we still want to add it's vanilla item to the pool, but we don't add the check. - // Mark it as skipped and set it to junk. These leaves an inbalance in the pools that will get sorted - // automatically if there is enough space. + // Excluded checks are always left out of the check pool, but what happens to them depends on what their + // vanilla item is worth. A check whose vanilla item is junk (grass, pots, snowballs...) has nothing worth + // preserving, so it stays unshuffled and behaves exactly like it does in vanilla, and its item never enters + // the item pool. Every other check keeps its item in the pool and is marked as skipped with junk in its + // place. That leaves an inbalance in the pools that will get sorted automatically if there is enough space. if (saveInfo.randoSaveOptions[RO_LOGIC] != RO_LOGIC_VANILLA) { - auto it = std::find(excludedChecks.begin(), excludedChecks.end(), randoCheckId); - if (it != excludedChecks.end()) { - itemPool.push_back(randoStaticCheck.randoItemId); + if (std::binary_search(excludedChecks.begin(), excludedChecks.end(), randoCheckId)) { + if (Rando::StaticData::Items[randoStaticCheck.randoItemId].randoItemType != RITYPE_JUNK) { + itemPool.push_back(randoStaticCheck.randoItemId); - saveInfo.randoSaveChecks[randoCheckId].shuffled = true; - saveInfo.randoSaveChecks[randoCheckId].randoItemId = RI_JUNK; - saveInfo.randoSaveChecks[randoCheckId].skipped = true; + saveInfo.randoSaveChecks[randoCheckId].shuffled = true; + saveInfo.randoSaveChecks[randoCheckId].randoItemId = RI_JUNK; + saveInfo.randoSaveChecks[randoCheckId].skipped = true; + } continue; } } diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp index 08b248d16..0b4e969ff 100644 --- a/mm/2s2h/Rando/Menu.cpp +++ b/mm/2s2h/Rando/Menu.cpp @@ -67,7 +67,6 @@ std::vector<int32_t> incompatibleWithVanilla = { // clang-format on std::vector<RandoCheckId> checkExclusionList; -bool isExcludedInitialized = false; namespace BenGui { extern std::shared_ptr<Rando::CheckTracker::CheckTrackerWindow> mRandoCheckTrackerWindow; @@ -150,29 +149,94 @@ void SortExcludedChecks() { } void SaveExcludedChecks() { - std::string excludedString = ""; SortExcludedChecks(); - - for (auto& data : checkExclusionList) { - excludedString += std::to_string(data); - excludedString += ","; - } - CVarSetString("gRando.ExcludedChecks", excludedString.c_str()); + Rando::SetExcludedChecksInConfig(checkExclusionList); Ship::Context::GetRawInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); ShipInit::Init("gRando.ExcludedChecks"); } void LoadExcludedChecks() { - std::string checksList = CVarGetString("gRando.ExcludedChecks", ""); + checkExclusionList = Rando::GetExcludedChecksFromConfig(); +} +static RegisterShipInitFunc loadExcludedChecksInit(LoadExcludedChecks, { "gRando.ExcludedChecks" }); + +struct CuratedCheckGroup { + const char* label; + const char* description; + std::vector<std::pair<RandoCheckId, RandoCheckId>> ranges; +}; + +// clang-format off +std::vector<CuratedCheckGroup> curatedCheckGroups = { + { + "Cow Grotto Grass", + "Every blade of grass in the Great Bay Coast and Termina Field cow grottos.", + { + { RC_GREAT_BAY_COAST_COW_GROTTO_GRASS_01, RC_GREAT_BAY_COAST_COW_GROTTO_GRASS_72 }, + { RC_TERMINA_FIELD_COW_GROTTO_GRASS_01, RC_TERMINA_FIELD_COW_GROTTO_GRASS_72 }, + }, + }, + { + "Termina Field Grass", + "Every blade of grass in Termina Field itself. The grottos within it are unaffected.", + { + { RC_TERMINA_FIELD_GRASS_01, RC_TERMINA_FIELD_GRASS_216 }, + }, + }, + { + "Common Exclusions", + "Minigames and side content that take a lot of time or a lot of luck to finish:", + { + { RC_BENEATH_THE_GRAVEYARD_DAMPE_CHEST, RC_BENEATH_THE_GRAVEYARD_DAMPE_CHEST }, + { RC_CLOCK_TOWN_EAST_HONEY_DARLING_ALL_DAYS, RC_CLOCK_TOWN_EAST_HONEY_DARLING_ALL_DAYS }, + { RC_CLOCK_TOWN_EAST_SHOOTING_GALLERY_PERFECT_SCORE, RC_CLOCK_TOWN_EAST_SHOOTING_GALLERY_PERFECT_SCORE }, + { RC_SWAMP_SHOOTING_GALLERY_PERFECT_SCORE, RC_SWAMP_SHOOTING_GALLERY_PERFECT_SCORE }, + { RC_DEKU_PLAYGROUND_ALL_DAYS, RC_DEKU_PLAYGROUND_ALL_DAYS }, + { RC_DEKU_SHRINE_MASK_OF_SCENTS, RC_DEKU_SHRINE_MASK_OF_SCENTS }, + { RC_GREAT_BAY_COAST_FISHERMAN_MINIGAME, RC_GREAT_BAY_COAST_FISHERMAN_MINIGAME }, + { RC_MOON_FIERCE_DEITY_MASK, RC_MOON_FIERCE_DEITY_MASK }, + { RC_MOUNTAIN_VILLAGE_FROG_CHOIR, RC_MOUNTAIN_VILLAGE_FROG_CHOIR }, + { RC_STOCK_POT_INN_COUPLES_MASK, RC_STOCK_POT_INN_COUPLES_MASK }, + { RC_WATERFALL_RAPIDS_BEAVER_RACE_02, RC_WATERFALL_RAPIDS_BEAVER_RACE_02 }, + { RC_PINNACLE_ROCK_REUNITE_SEAHORSE, RC_PINNACLE_ROCK_REUNITE_SEAHORSE }, + }, + }, +}; +// clang-format on + +const std::vector<std::vector<RandoCheckId>>& GetCuratedGroupChecks() { + static std::vector<std::vector<RandoCheckId>> curatedGroupChecks; + if (curatedGroupChecks.empty()) { + for (auto& curatedCheckGroup : curatedCheckGroups) { + std::vector<RandoCheckId> groupChecks; + for (auto& [firstCheckId, lastCheckId] : curatedCheckGroup.ranges) { + for (int32_t checkId = firstCheckId; checkId <= lastCheckId; checkId++) { + if (Rando::StaticData::Checks.contains((RandoCheckId)checkId)) { + groupChecks.push_back((RandoCheckId)checkId); + } + } + } + std::sort(groupChecks.begin(), groupChecks.end()); + curatedGroupChecks.push_back(groupChecks); + } + } + return curatedGroupChecks; +} - if (checksList != "") { - std::string word; - std::istringstream stream(checksList); - while (std::getline(stream, word, ',')) { - checkExclusionList.push_back((RandoCheckId)std::stoi(word)); +void SetCuratedGroupExcluded(const std::vector<RandoCheckId>& groupChecks, bool excluded) { + if (excluded) { + for (RandoCheckId randoCheckId : groupChecks) { + auto it = std::lower_bound(checkExclusionList.begin(), checkExclusionList.end(), randoCheckId); + if (it == checkExclusionList.end() || *it != randoCheckId) { + checkExclusionList.insert(it, randoCheckId); + } } + } else { + std::erase_if(checkExclusionList, [&](const RandoCheckId& randoCheckId) { + return std::binary_search(groupChecks.begin(), groupChecks.end(), randoCheckId); + }); } - SortExcludedChecks(); + SaveExcludedChecks(); } static int checksInPool = 0; @@ -180,8 +244,10 @@ static int itemsInPool = 0; static int junkInPool = 0; static int balanceStatus = 0; // 0 = Able to balance, 1 = Unlikely to balance, 2 = Unable to balance static std::set<RandoItemId> setOfItemsInPool; +static std::set<RandoCheckId> setOfChecksInPool; void RefreshMetrics() { setOfItemsInPool.clear(); + setOfChecksInPool.clear(); RandoSaveInfo randoSaveInfo; std::vector<RandoCheckId> checkPool; std::vector<RandoItemId> itemPool; @@ -199,6 +265,9 @@ void RefreshMetrics() { checksInPool = checkPool.size(); itemsInPool = itemPool.size(); junkInPool = 0; + for (auto& check : checkPool) { + setOfChecksInPool.insert(check); + } for (auto& item : itemPool) { setOfItemsInPool.insert(item); if (Rando::StaticData::Items[item].randoItemType == RITYPE_JUNK) { @@ -253,6 +322,7 @@ static RegisterShipInitFunc refreshMetricsInit(RefreshMetrics, { "gRando.Options.RO_SHUFFLE_BARREL_DROPS", "gRando.Options.RO_SHUFFLE_BOSS_REMAINS", "gRando.Options.RO_SHUFFLE_BOSS_SOULS", + "gRando.Options.RO_SHUFFLE_BUTTERFLIES", "gRando.Options.RO_SHUFFLE_COWS", "gRando.Options.RO_SHUFFLE_CRATE_DROPS", "gRando.Options.RO_SHUFFLE_ENEMY_DROPS", @@ -275,6 +345,7 @@ static RegisterShipInitFunc refreshMetricsInit(RefreshMetrics, { "gRando.Options.RO_SHUFFLE_SONG_SUN", "gRando.Options.RO_SHUFFLE_SWIM", "gRando.Options.RO_SHUFFLE_TINGLE_SHOPS", + "gRando.Options.RO_SHUFFLE_TREE_DROPS", "gRando.Options.RO_SHUFFLE_TYCOON_WALLET", "gRando.Options.RO_SHUFFLE_TRIFORCE_PIECES", "gRando.Options.RO_SKULLTULA_TOKENS_MAX", @@ -1099,6 +1170,68 @@ static void DrawFilterWithButton(ImGuiTextFilter& filter, const char* placeholde } } +static void DrawCuratedCheckGroups(UIWidgets::Colors menuThemeColor) { + struct CuratedGroupButton { + std::string label; + std::string tooltip; + const std::vector<RandoCheckId>* checks; + bool allExcluded; + bool disabled; + }; + + const auto& curatedGroupChecks = GetCuratedGroupChecks(); + std::vector<CuratedGroupButton> curatedGroupButtons; + for (size_t i = 0; i < curatedCheckGroups.size(); i++) { + const auto& groupChecks = curatedGroupChecks[i]; + bool allExcluded = + !groupChecks.empty() && std::all_of(groupChecks.begin(), groupChecks.end(), [](RandoCheckId randoCheckId) { + return std::binary_search(checkExclusionList.begin(), checkExclusionList.end(), randoCheckId); + }); + bool anyInPool = std::any_of(groupChecks.begin(), groupChecks.end(), [](RandoCheckId randoCheckId) { + return setOfChecksInPool.contains(randoCheckId); + }); + + std::string tooltip = curatedCheckGroups[i].description; + if (groupChecks.size() <= 12) { + for (RandoCheckId randoCheckId : groupChecks) { + tooltip += "\n- " + Rando::StaticData::CheckNames[randoCheckId]; + } + } + tooltip += + allExcluded ? "\n\nClick to stop excluding these checks." : "\n\nClick to exclude all of these checks."; + + curatedGroupButtons.push_back({ + std::string(curatedCheckGroups[i].label) + " (" + std::to_string(groupChecks.size()) + ")", + tooltip, + &groupChecks, + allExcluded, + !anyInPool && !allExcluded, + }); + } + + ImGui::SeparatorText("Curated Groups"); + f32 windowRight = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x; + for (size_t i = 0; i < curatedGroupButtons.size(); i++) { + auto& curatedGroupButton = curatedGroupButtons[i]; + if (Button(curatedGroupButton.label.c_str(), + ButtonOptions({ { .tooltip = curatedGroupButton.tooltip.c_str(), + .disabled = curatedGroupButton.disabled, + .disabledTooltip = "These checks are not shuffled by the current settings" } }) + .Size(Sizes::Inline) + .Color(curatedGroupButton.allExcluded ? Colors::Orange : menuThemeColor))) { + SetCuratedGroupExcluded(*curatedGroupButton.checks, !curatedGroupButton.allExcluded); + } + + if (i + 1 < curatedGroupButtons.size()) { + f32 nextButtonRight = ImGui::GetItemRectMax().x + ImGui::GetStyle().ItemSpacing.x + + CalcButtonWidth(curatedGroupButtons[i + 1].label.c_str()); + if (nextButtonRight < windowRight) { + ImGui::SameLine(); + } + } + } +} + static void DrawCheckFilterTab() { if (CVarGetInteger(Rando::StaticData::Options[RO_LOGIC].cvar, RO_LOGIC_GLITCHLESS) >= RO_LOGIC_VANILLA) { ImGui::TextColored(UIWidgets::ColorValues.at(UIWidgets::Colors::Red), @@ -1111,22 +1244,24 @@ static void DrawCheckFilterTab() { bool excludeFiltered = false; bool removeFiltered = false; bool removeAllChecks = false; - if (!isExcludedInitialized) { - LoadExcludedChecks(); - isExcludedInitialized = true; - } + RandoCheckId checkToExclude = RC_UNKNOWN; + RandoCheckId checkToRestore = RC_UNKNOWN; + + DrawCuratedCheckGroups(menuThemeColor); f32 columnWidth = ImGui::GetContentRegionAvail().x / 2 - (ImGui::GetStyle().ItemSpacing.x * 2); - ImGui::BeginChild("randoIncludedChecks", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y)); - ImGui::SeparatorText("Normal Checks"); + ImGui::BeginChild("randoIncludedChecks", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y), + ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::SeparatorText("Shuffled Checks"); static ImGuiTextFilter includedFilter; - const char* leftButtonLabel = includedFilter.IsActive() ? "Junk Filtered" : "Junk All"; + const char* leftButtonLabel = includedFilter.IsActive() ? "Exclude Filtered" : "Exclude All"; f32 availableWidth = ImGui::GetContentRegionAvail().x; - DrawFilterWithButton(includedFilter, "Normal Search", leftButtonLabel, availableWidth, menuThemeColor, + DrawFilterWithButton(includedFilter, "Shuffled Search", leftButtonLabel, availableWidth, menuThemeColor, excludeFiltered, excludeAllChecks, 30.0f, 24.0f, 6.0f, 18.0f); - if (ImGui::BeginTable("Normal Checks", 1)) { + ImGui::BeginChild("randoIncludedChecksList", ImVec2(0, 0)); + if (ImGui::BeginTable("Shuffled Checks", 1)) { ImGui::TableNextColumn(); for (auto& includedChecks : Rando::StaticData::Checks) { @@ -1134,11 +1269,11 @@ static void DrawCheckFilterTab() { continue; } - if (!includedFilter.PassFilter(Rando::StaticData::CheckNames[includedChecks.first].c_str())) { + if (!setOfChecksInPool.contains(includedChecks.first)) { continue; } - if (std::binary_search(checkExclusionList.begin(), checkExclusionList.end(), includedChecks.first)) { + if (!includedFilter.PassFilter(Rando::StaticData::CheckNames[includedChecks.first].c_str())) { continue; } @@ -1151,74 +1286,68 @@ static void DrawCheckFilterTab() { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, ImGui::IsItemHovered() ? IM_COL32(255, 255, 0, 128) : IM_COL32(255, 255, 255, 0)); if (ImGui::IsItemClicked()) { - auto it = std::lower_bound(checkExclusionList.begin(), checkExclusionList.end(), includedChecks.first); - if (it == checkExclusionList.end() || *it != includedChecks.first) { - checkExclusionList.insert(it, includedChecks.first); - SaveExcludedChecks(); - } + checkToExclude = includedChecks.first; } ImGui::TableNextColumn(); } ImGui::EndTable(); } ImGui::EndChild(); + ImGui::EndChild(); ImGui::SameLine(); - ImGui::BeginChild("randoExcludedChecks", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y)); - ImGui::SeparatorText("Forced Junk Checks"); + ImGui::BeginChild("randoExcludedChecks", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y), + ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::SeparatorText("Excluded Checks"); static ImGuiTextFilter excludedFilter; const char* rightButtonLabel = excludedFilter.IsActive() ? "Remove Filtered" : "Remove All"; f32 rightAvailableWidth = ImGui::GetContentRegionAvail().x; - DrawFilterWithButton(excludedFilter, "Junk Search", rightButtonLabel, rightAvailableWidth, menuThemeColor, + DrawFilterWithButton(excludedFilter, "Excluded Search", rightButtonLabel, rightAvailableWidth, menuThemeColor, removeFiltered, removeAllChecks, 30.0f, 24.0f, 6.0f, 18.0f); - if (ImGui::BeginTable("Forced Junk Checks", 1)) { + ImGui::BeginChild("randoExcludedChecksList", ImVec2(0, 0)); + if (ImGui::BeginTable("Excluded Checks", 1)) { ImGui::TableNextColumn(); - for (auto it = checkExclusionList.begin(); it != checkExclusionList.end();) { - if (!excludedFilter.PassFilter(Rando::StaticData::CheckNames[*it].c_str())) { - ++it; + for (RandoCheckId randoCheckId : checkExclusionList) { + if (!excludedFilter.PassFilter(Rando::StaticData::CheckNames[randoCheckId].c_str())) { continue; } - ImGui::Text("%s", Rando::StaticData::CheckNames[*it].c_str()); + ImGui::Text("%s", Rando::StaticData::CheckNames[randoCheckId].c_str()); ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, ImGui::IsItemHovered() ? IM_COL32(255, 255, 0, 128) : IM_COL32(255, 255, 255, 0)); if (ImGui::IsItemClicked()) { - it = checkExclusionList.erase(it); - SaveExcludedChecks(); - } else { - ++it; + checkToRestore = randoCheckId; } ImGui::TableNextColumn(); } ImGui::EndTable(); } ImGui::EndChild(); + ImGui::EndChild(); - // Junk all checks - if (excludeAllChecks) { - checkExclusionList.clear(); - checkExclusionList.reserve(Rando::StaticData::Checks.size()); - for (auto& includedChecks : Rando::StaticData::Checks) { - if (includedChecks.first != RC_UNKNOWN) { - checkExclusionList.push_back(includedChecks.first); - } + if (checkToExclude != RC_UNKNOWN) { + auto it = std::lower_bound(checkExclusionList.begin(), checkExclusionList.end(), checkToExclude); + if (it == checkExclusionList.end() || *it != checkToExclude) { + checkExclusionList.insert(it, checkToExclude); + SaveExcludedChecks(); } - SortExcludedChecks(); + } + + if (checkToRestore != RC_UNKNOWN) { + std::erase(checkExclusionList, checkToRestore); SaveExcludedChecks(); - includedFilter.Clear(); - excludeAllChecks = false; } - // Junk filtered checks - if (excludeFiltered) { + if (excludeAllChecks || excludeFiltered) { for (auto& includedChecks : Rando::StaticData::Checks) { - if (includedChecks.first == RC_UNKNOWN) { + if (!setOfChecksInPool.contains(includedChecks.first)) { continue; } - if (!includedFilter.PassFilter(Rando::StaticData::CheckNames[includedChecks.first].c_str())) { + if (excludeFiltered && + !includedFilter.PassFilter(Rando::StaticData::CheckNames[includedChecks.first].c_str())) { continue; } @@ -1229,6 +1358,7 @@ static void DrawCheckFilterTab() { } SaveExcludedChecks(); includedFilter.Clear(); + excludeAllChecks = false; excludeFiltered = false; } diff --git a/mm/2s2h/Rando/Rando.h b/mm/2s2h/Rando/Rando.h index 1c7aa6297..80df71535 100644 --- a/mm/2s2h/Rando/Rando.h +++ b/mm/2s2h/Rando/Rando.h @@ -41,6 +41,9 @@ std::vector<RandoItemId> GetSariaPriorityItemsFromConfig(); void SetSariaPriorityItemsInConfig(std::vector<RandoItemId>& priorityItems); std::vector<RandoItemId> GetSariaPriorityItemCandidates(); +std::vector<RandoCheckId> GetExcludedChecksFromConfig(); +void SetExcludedChecksInConfig(std::vector<RandoCheckId>& excludedChecks); + std::vector<RandoCheckId> FindMultiItemPlacement(RandoItemId randoItemId); } // namespace Rando |
