summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett Cox <garrettjcox@gmail.com>2026-07-26 23:10:11 -0500
committerGitHub <noreply@github.com>2026-07-26 23:10:11 -0500
commit99ff062849465b9c610faa715438c90758b4c9d7 (patch)
treeae28373ca3ae054a0b87b9ff9545aa4bc60c6668
parentd66aafc3779f2aab5b69125c2b0bba24e52c6821 (diff)
Improvements to excluded checks (#1817)
-rw-r--r--mm/2s2h/PresetManager/PresetManager.cpp112
-rw-r--r--mm/2s2h/Rando/ExcludedChecks.cpp47
-rw-r--r--mm/2s2h/Rando/Logic/GeneratePools.cpp30
-rw-r--r--mm/2s2h/Rando/Menu.cpp246
-rw-r--r--mm/2s2h/Rando/Rando.h3
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