#include "imgui.h" #include "ImGuiConsole.hpp" #include "ImGuiMenuRandomizer.hpp" #include "imgui_internal.h" #include "dusk/logging.h" #include "dusk/data.hpp" #include "dusk/map_loader_definitions.h" #include "dusk/ui/rando_config.hpp" #include "dusk/randomizer/generator/logic/search.hpp" #include "dusk/randomizer/generator/utility/string.hpp" #include "dusk/randomizer/game/randomizer_context.hpp" #include "dusk/randomizer/game/tools.h" #include "dusk/randomizer/game/verify_item_functions.h" #include #include #include #include #include namespace dusk { static bool generatingSeed = false; static std::string generationStatusMsg{}; static std::mutex generationStatusMsgMutex{}; static constexpr ImVec4 TRACKER_COLOR_INACCESSIBLE = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); static constexpr ImVec4 TRACKER_COLOR_ACCESSIBLE = ImVec4(0.f, 1.f, 0.f, 1.f); static constexpr ImVec4 TRACKER_COLOR_COLLECTED = ImVec4(0.4f, 0.4f, 0.4f, 1.0f); static constexpr ImVec4 TRACKER_COLOR_COLLECTED_OUT_LOGIC = ImVec4(0.4f, 0.4f, 0.0f, 1.0f); static constexpr ImVec4 TRACKER_COLOR_ACTIVE = ImVec4(1.0f, 1.0f, 0.0f, 1.0f); static void StartSeedGeneration() { if (generatingSeed) { return; } generatingSeed = true; std::lock_guard lock(generationStatusMsgMutex); GenerateAndWriteSeed(generationStatusMsg); generatingSeed = false; DuskLog.debug("{}", generationStatusMsg); } static const char* lookup_map_name(const char* mapFile) { if (!mapFile || mapFile[0] == '\0') return nullptr; for (const auto& region : gameRegions) { for (const auto& map : region.maps) { if (map.mapFile && strcmp(mapFile, map.mapFile) == 0) { return map.mapName; } } } return nullptr; } ImGuiMenuRandomizer::ImGuiMenuRandomizer() {} void ImGuiMenuRandomizer::draw() { if (ImGui::BeginMenu("Randomizer")) { if (ImGui::BeginMenu("Generate")) { if (ImGui::MenuItem("Generate Seed", nullptr, false, !generatingSeed)) { // Put seed generation on a separate thread so it doesn't freeze the game std::thread randoGenerationThread(StartSeedGeneration); randoGenerationThread.detach(); // StartSeedGeneration(); m_showRandoGeneration = true; } ImGui::EndMenu(); } std::string loadSeedText = "Load Seed"; if (!playerIsOnTitleScreen()) { loadSeedText += " (Must be on title screen)"; } if (ImGui::BeginMenu(loadSeedText.c_str(), playerIsOnTitleScreen())) { std::filesystem::path seedDirectory = ui::GetRandomizerSeedsPath(); std::list hashes{}; if (std::filesystem::exists(seedDirectory)) { for (const auto& entry : std::filesystem::directory_iterator(seedDirectory)) { if (entry.is_directory()) { hashes.push_back(entry.path().filename().string()); } } }else { std::filesystem::create_directories(seedDirectory); } if (hashes.empty()) { if (ImGui::MenuItem("No seeds generated")) {} } for (const auto& hash : hashes) { std::string name = hash; if (randomizer_GetContext().mHash == hash) { name += " (Current Seed)"; } if (ImGui::MenuItem(name.c_str())) { if (!randomizer_IsActive()) { randomizer_GetContext() = RandomizerContext(); randomizer_GetContext().LoadFromHash(hash); } } } ImGui::EndMenu(); } if (ImGui::BeginMenu("Delete Seed")) { std::filesystem::path seedDirectory = ui::GetRandomizerSeedsPath(); std::list hashes{}; for (const auto& entry : std::filesystem::directory_iterator(seedDirectory)) { if (entry.is_directory()) { hashes.push_back(entry.path().filename().string()); } } if (hashes.empty()) { if (ImGui::MenuItem("No seeds generated")) {} } for (const auto& hash : hashes) { std::string name = hash; if (randomizer_GetContext().mHash == hash) { name += " (Current Seed)"; } if (ImGui::MenuItem(name.c_str(), nullptr, false, playerIsOnTitleScreen() || randomizer_GetContext().mHash != hash)) { std::filesystem::path hashDirectory = seedDirectory / hash; if (randomizer_GetContext().mHash != hash) { std::filesystem::remove_all(hashDirectory); } else if (!randomizer_IsActive()){ // If the user selected the currently seed, but it's not active, we'll allow the delete std::filesystem::remove_all(hashDirectory); randomizer_GetContext() = RandomizerContext(); } } } ImGui::EndMenu(); } std::string name = "Deactivate Randomizer"; if (!playerIsOnTitleScreen()) { name += " (Must be on title screen)"; } if (ImGui::MenuItem(name.c_str(), nullptr, false, playerIsOnTitleScreen())) { // Reset the main randomizer context only if we're not active if (!randomizer_IsActive()) { randomizer_GetContext() = RandomizerContext(); } } ImGui::Checkbox("Show Rando Stats", &m_showRandoStats); ImGui::Checkbox("Show Rando Tracker", &g_randomizerState.mShowTracker); ImGui::EndMenu(); } } void ImGuiMenuRandomizer::windowRandoGeneration() { if (!m_showRandoGeneration) { return; } ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize; ImGui::SetNextWindowBgAlpha(0.65f); if (!ImGui::Begin("Randomizer Generation", &m_showRandoGeneration, windowFlags)) { ImGui::End(); return; } // Print "Generating..." until the seed finishes generating (at which point it will release // the lock on the mutex) std::string generationText = "Generating Randomizer Seed..."; if (generationStatusMsgMutex.try_lock()) { generationText = generationStatusMsg; generationStatusMsgMutex.unlock(); } ImGui::Text("%s", generationText.c_str()); ImGui::End(); } void ImGuiMenuRandomizer::windowRandoStats() { if (!m_showRandoStats) { return; } ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; ImGui::SetNextWindowBgAlpha(0.65f); if (ImGui::Begin("Rando Stats", nullptr, windowFlags)) { const char* seed = randomizer_GetContext().mHash.empty() ? "None" : randomizer_GetContext().mHash.c_str(); ImGui::Text("Current Seed: %s", seed); ImGui::Text("Status:"); ImGui::SameLine(); if (randomizer_IsActive()) { ImGui::TextColored(ImVec4(0.f, 1.f, 0.f, 1.f), "ACTIVE"); } else { std::string reason{"Unknown"}; if (randomizer_GetContext().mHash.empty()) { reason = "No Seed Selected"; } else if (playerIsOnTitleScreen()) { reason = "On Title Screen"; } ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), "NOT ACTIVE (Reason: %s)", reason.c_str()); } } ImGui::End(); } void ImGuiMenuRandomizer::windowRandoTracker() { if (!g_randomizerState.mShowTracker) { return; } ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; ImGui::SetNextWindowBgAlpha(0.5f); if (!ImGui::Begin("Rando Tracker", nullptr, windowFlags) || !randomizer_IsActive()) { ImGui::End(); return; } auto trackerRando = getTrackerRando(); if (ImGui::Button("Update Tracker") || g_randomizerState.mUpdateTracker) { g_randomizerState.mUpdateTracker = false; // Generate tracker world if it doesn't exist auto contextHash = randomizer_GetContext().mHash; auto trackerHash = trackerRando->GetConfig().GetHash(false); // If no hash, or seeds switched, try to create tracker world from currently active seed if (trackerHash.empty() || (trackerHash != contextHash && !contextHash.empty())) { *trackerRando = randomizer::Randomizer(ui::GetRandomizerPath()); trackerRando->GenerateTrackerWorld(); } if (randomizer_IsActive()) { auto currentItems = getSaveItemPool(trackerRando->GetWorld()); m_currentSearch = randomizer::logic::search::Search::AccessibleNoStartingInventory(&trackerRando->GetWorlds(), currentItems); m_currentSearch.SearchWorlds(); generateLocationInfo(); } } if (trackerRando->GetConfig().GetHash(false).empty()) { ImGui::Text("Load a seed and start a file to activate the tracker"); ImGui::End(); return; } ImGui::Text("Tracker world loaded from seed %s", trackerRando->GetConfig().GetHash().c_str()); const char* curStage = dComIfGp_getStartStageName(); const char* curStageName = lookup_map_name(curStage); ImGui::Text("Current Stage: %s (%s)", curStageName, curStage); auto stageInfo = dComIfGp_getStageStagInfo(); if (stageInfo && dStage_stagInfo_GetSaveTbl(stageInfo) != getStageSaveId(curStage)) { ImGui::TextColored(ImVec4(1.0f, 0.0f,0.0f,1.0f), "Error: Current Stage Save Tbl mismatch!"); } // TODO: move this out of tracker if (ImGui::CollapsingHeader("Current Inventory")) { if (ImGui::BeginChild("ScrollRegionCurInventory", ImVec2(500, 200), true)) { ImGui::SeparatorText("Story Items Count"); ImGui::Text("Fused Shadows: %d/3", numFusedShadows()); ImGui::Text("Mirror Shards: %d/4", numMirrorShards()); static std::unordered_map> keyRegionItemNameMap = { {0x10, {4, "Forest Temple"}}, {0x11, {3, "Goron Mines"}}, {0x12, {3, "Lakebed Temple"}}, {0x13, {5, "Arbiters Grounds"}}, {0x14, {4, "Snowpeak Ruins"}}, {0x15, {3, "Temple of Time"}}, {0x16, {1, "City in the Sky"}}, {0x17, {7, "Palace of Twilight"}}, {0x18, {3, "Hyrule Castle"}}, {0xA, {1, "Gerudo Desert Bulblin Camp"}}, }; for (auto& [stage, keyInfo] : keyRegionItemNameMap) { ImGui::SeparatorText(keyInfo.second.c_str()); int smallKeys = getTempleKeysFound(stage); ImGui::Text("Keys: %d/%d", smallKeys, keyInfo.first); if (stage == 0xA) continue; ImGui::Text("Has:"); if (dComIfGs_isDungeonItemBossKey(stage)) { ImGui::SameLine(); ImGui::Text("Big Key"); } if (dComIfGs_isDungeonItemMap(stage)) { ImGui::SameLine(); ImGui::Text("Map"); } if (dComIfGs_isDungeonItemCompass(stage)) { ImGui::SameLine(); ImGui::Text("Compass"); } if (stage == 0x14) { // Ordon Pumpkin if (haveItem(dItemNo_Randomizer_TOMATO_PUREE_e)) { ImGui::SameLine(); ImGui::Text("Pumpkin"); } // Ordon Cheese if (haveItem(dItemNo_Randomizer_TASTE_e)) { ImGui::SameLine(); ImGui::Text("Cheese"); } } } ImGui::SeparatorText("Overworld Keys"); ImGui::Text("Faron Gate: %s", haveItem(dItemNo_SMALL_KEY2_e) ? "Yes" : "No"); ImGui::Text("Coro Gate: %s", haveItem(dItemNo_KEY_OF_FILONE_e) ? "Yes" : "No"); ImGui::Text("Gate Keys: %s", haveItem(dItemNo_Randomizer_BOSSRIDER_KEY_e) ? "Yes" : "No"); } ImGui::EndChild(); } ImGui::Checkbox("Show Only Accessible Locations", &m_onlyAccessible); ImGui::Checkbox("Hide Area Header", &m_hideAreaHeader); ImGui::Checkbox("Show Location Requirements", &m_showRequirements); ImGui::InputText("Location Filter", m_locationFilter, 100); // Show total number of available locations auto locations = trackerRando->GetWorld()->GetAllLocations(); // TODO: maybe add remaining? ImGui::Text("Locations Available: %d / %d (%d Checked)", m_numAvailableLocations, m_numProgressionLocations, m_numCollectedLocations); ImGui::SetNextWindowBgAlpha(0.5f); if (ImGui::BeginChild("ScrollRegion", ImVec2(500, 500), true)) { std::string filter = m_locationFilter; auto drawLocationsFunc = [this, &filter](std::vector& locations) { for (auto& info : locations) { // Don't show any locations which aren't accessible if only accessible is checked // Don't show any locations which don't meet the filter if ((m_onlyAccessible && !info.accessible) || !randomizer::utility::str::Contains(info.locationName, filter)) { continue; } if (info.collected) { if (info.accessible) { ImGui::TextColored(TRACKER_COLOR_COLLECTED, "%s [%s]", info.locationName.c_str(), info.locationItem.c_str()); }else { ImGui::TextColored(TRACKER_COLOR_COLLECTED_OUT_LOGIC, "%s [%s]", info.locationName.c_str(), info.locationItem.c_str()); } } else { if (ImGui::SmallButton(fmt::format("{}###HideCheckBtn_{}", info.hidden ? "+" : "-", info.locationName.c_str()).c_str())) { if (info.hidden) { std::erase(m_HiddenChecks, info.locationName); } else { m_HiddenChecks.push_back(info.locationName); } info.hidden = !info.hidden; } ImGui::SameLine(); if (info.hidden) { ImGui::TextColored(TRACKER_COLOR_COLLECTED, "%s [Skipped]", info.locationName.c_str()); } else { ImVec4 color; if (info.accessible) { // If the search found this location, change color to green color = TRACKER_COLOR_ACCESSIBLE; } else { color = TRACKER_COLOR_INACCESSIBLE; } ImGui::TextColored(color, "%s", info.locationName.c_str()); } } // Show requirements for the location below it (formatting isn't pretty right now) if (m_showRequirements && ImGui::BeginItemTooltip()) { generateImGuiRequirementTooltip(info.logicReq); ImGui::EndTooltip(); } } }; if (m_hideAreaHeader) { for (auto& region : m_LocationInfo | std::views::values) { drawLocationsFunc(region.unobtainedLocations); } for (auto& region : m_LocationInfo | std::views::values) { drawLocationsFunc(region.obtainedLocations); } }else { // Show all locations. Green for accessible. Red for Unaccessible for (auto& [areaName, region] : m_LocationInfo) { if (m_onlyAccessible && !region.showArea) continue; int areaCount = region.totalCount; bool isCurrentArea = areaName == curStageName; if (isCurrentArea) ImGui::PushStyleColor(ImGuiCol_Text, TRACKER_COLOR_ACTIVE); else if (!region.showArea) ImGui::PushStyleColor(ImGuiCol_Text, TRACKER_COLOR_COLLECTED); bool isShowNode = ImGui::CollapsingHeader(fmt::format("{} ({}/{})###{}_{}", areaName, areaCount - region.collectedCount, areaCount, areaName, areaCount).c_str()); if (!region.showArea || isCurrentArea) ImGui::PopStyleColor(); if (isShowNode) { drawLocationsFunc(region.unobtainedLocations); drawLocationsFunc(region.obtainedLocations); } } } } ImGui::EndChild(); ImGui::End(); } randomizer::Randomizer* ImGuiMenuRandomizer::getTrackerRando() { static randomizer::Randomizer trackerRando{ui::GetRandomizerPath()}; return &trackerRando; } void ImGuiMenuRandomizer::TrackerAreaGroup::addToGroup(LocationTrackerInfo& info) { if (!showArea && info.accessible && !info.collected) { showArea = true; } totalCount++; if (info.collected) collectedCount++; if (info.accessible) accessibleCount++; if (info.collected) { obtainedLocations.push_back(info); }else { unobtainedLocations.push_back(info); } } // Helper function to wrap text for ImGui requirement tooltips static void SameLineOrTextWrap() { ImGui::SameLine(0, 0); if (ImGui::GetCursorPosX() > 600.f) { ImGui::NewLine(); } } void ImGuiMenuRandomizer::generateImGuiRequirementTooltip(const randomizer::logic::requirement::Requirement& req, int nestingLevel /*= 0*/) { using namespace randomizer::logic::requirement; using namespace randomizer::logic::item; std::string reqStr = ""; std::string itemStr = ""; Item* item; Requirement nestedReq; int count; int heartCount; int eventIndex; int macroIndex; if (req._type != Type::AND && nestingLevel == 0) { ImGui::Bullet(); ImGui::SameLine(); } switch (req._type) { case Type::NOTHING: ImGui::TextColored(TRACKER_COLOR_ACCESSIBLE, "Nothing"); ImGui::SameLine(); return; case Type::IMPOSSIBLE: ImGui::TextColored(TRACKER_COLOR_INACCESSIBLE, "Impossible (Please discover an entrance first)"); ImGui::SameLine(); return; case Type::OR: for (int i = 0; i < req._args.size(); ++i) { auto& arg = req._args[i]; nestedReq = std::get(arg); if (nestedReq._type == Type::AND || nestedReq._type == Type::OR) { ImGui::Text("("); ImGui::SameLine(0, 0); generateImGuiRequirementTooltip(nestedReq, nestingLevel + 1); ImGui::Text(")"); SameLineOrTextWrap(); } else { generateImGuiRequirementTooltip(nestedReq, nestingLevel + 1); } if (i < req._args.size() - 1) { ImGui::Text(" or "); SameLineOrTextWrap(); } } return; case Type::AND: for (int i = 0; i < req._args.size(); ++i) { auto& arg = req._args[i]; nestedReq = std::get(arg); if (nestingLevel > 0 && (nestedReq._type == Type::AND || nestedReq._type == Type::OR)) { ImGui::Text("("); ImGui::SameLine(0, 0); generateImGuiRequirementTooltip(nestedReq, nestingLevel + 1); ImGui::Text(")"); SameLineOrTextWrap(); } else { if (nestingLevel == 0) { ImGui::Bullet(); ImGui::SameLine(); } generateImGuiRequirementTooltip(nestedReq, nestingLevel + 1); if (nestingLevel == 0) { ImGui::NewLine(); } } if (nestingLevel > 0 && i < req._args.size() - 1) { ImGui::Text(" and "); SameLineOrTextWrap(); } } return; case Type::ITEM: item = std::get(req._args[0]); switch (item->GetID()) { case dItemNo_Randomizer_FISHING_ROD_1_e: // Progressive Fishing Rod itemStr = "Fishing Rod"; break; case dItemNo_Randomizer_WOOD_STICK_e: // Progressive Sword itemStr = "Sword"; break; case dItemNo_Randomizer_BOW_e: itemStr = "Bow"; break; case dItemNo_Randomizer_HOOKSHOT_e: // Progressive Clawshot itemStr = "Clawshot"; break; case dItemNo_Randomizer_COPY_ROD_e: // Progressive Dominion Rod itemStr = "Dominion Rod"; break; case dItemNo_Randomizer_ENDING_BLOW_e: // Progressive Hidden Skill itemStr = "Ending Blow"; break; case dItemNo_Randomizer_WALLET_LV2_e: // Progressive Wallet itemStr = "Big Wallet"; break; case dItemNo_Randomizer_MIRROR_PIECE_2_e: itemStr = "Mirror Shard"; break; case dItemNo_Randomizer_FUSED_SHADOW_1_e: itemStr = "Fused Shadow"; break; default: break; } if (itemStr.empty()) { itemStr = item->GetName(); } if (m_currentSearch._ownedItems.contains(item)) { ImGui::TextColored(TRACKER_COLOR_ACCESSIBLE, "%s", itemStr.c_str()); ImGui::SameLine(0, 0); } else { ImGui::TextColored(TRACKER_COLOR_INACCESSIBLE, "%s", itemStr.c_str()); ImGui::SameLine(0, 0); } return; case Type::COUNT: count = std::get(req._args[0]); item = std::get(req._args[1]); switch (item->GetID()) { case dItemNo_Randomizer_FISHING_ROD_1_e: // Progressive Fishing Rod if (count == 2) itemStr = "Coral Earring"; break; case dItemNo_Randomizer_WOOD_STICK_e: // Progressive Sword if (count == 2) itemStr = "Ordon Sword"; else if (count == 3) itemStr = "Master Sword"; else if (count == 4) itemStr = "Light Sword"; break; case dItemNo_Randomizer_BOW_e: if (count == 2) itemStr = "Big Quiver"; else if (count == 3) itemStr = "Giant Quiver"; break; case dItemNo_Randomizer_HOOKSHOT_e: // Progressive Clawshot if (count == 2) itemStr = "Double Clawshots"; break; case dItemNo_Randomizer_COPY_ROD_e: // Progressive Dominion Rod if (count == 2) itemStr = "Restored Dominion Rod"; break; case dItemNo_Randomizer_ENDING_BLOW_e: if (count == 2) itemStr = "Shield Attack"; else if (count == 3) itemStr = "Back Slice"; else if (count == 4) itemStr = "Helm Splitter"; else if (count == 5) itemStr = "Mortal Draw"; else if (count == 6) itemStr = "Jump Strike"; else if (count == 7) itemStr = "Great Spin"; break; case dItemNo_Randomizer_WALLET_LV2_e: if (count == 2) itemStr = "Giant Wallet"; break; case dItemNo_Randomizer_MIRROR_PIECE_2_e: itemStr = "Mirror Shard x" + std::to_string(count); break; case dItemNo_Randomizer_FUSED_SHADOW_1_e: itemStr = "Fused Shadow x" + std::to_string(count); break; case dItemNo_Randomizer_ANCIENT_DOCUMENT_e: if (count == 7) itemStr = "Completed Sky Book"; break; case 0x103: // Faron Twilight Tear if (count == 16) itemStr = "Complete Faron Twilight"; break; case 0x104: // Eldin Twilight Tear if (count == 16) itemStr = "Complete Eldin Twilight"; break; case 0x105: // Lanayru Twilight Tear if (count == 16) itemStr = "Complete Lanayru Twilight"; break; default: break; } if (itemStr.empty()) { itemStr = item->GetName() + " x" + std::to_string(count); } if (m_currentSearch._ownedItems.count(item) >= count) { ImGui::TextColored(TRACKER_COLOR_ACCESSIBLE, "%s", itemStr.c_str()); SameLineOrTextWrap(); } else { ImGui::TextColored(TRACKER_COLOR_INACCESSIBLE, "%s", itemStr.c_str()); SameLineOrTextWrap(); } return; // case Type::EVENT: // eventIndex = std::get(req._args[0]); // ImGui::Text("'Event_" + std::to_string(eventIndex) + "'"); // // case Type::MACRO: // macroIndex = std::get(req._args[0]); // return "'Macro_" + std::to_string(macroIndex) + "'"; // // case Type::DAY: // return "Day"; // // case Type::NIGHT: // return "Night"; // // case Type::HUMAN_LINK: // return "Human Link"; // // case Type::WOLF_LINK: // return "Wolf Link"; // // case Type::TWILIGHT: // return "Twilight"; // // case Type::GOLDEN_BUGS: // count = std::get(req._args[0]); // return "golden_bugs(" + std::to_string(count) + ")"; case Type::HEARTS: count = std::get(req._args[0]); heartCount = m_currentSearch._ownedItems.count(getTrackerRando()->GetWorld()->GetItem("Heart Container")) + (m_currentSearch._ownedItems.count(getTrackerRando()->GetWorld()->GetItem("Piece of Heart")) / 5); itemStr = std::to_string(count) + " Hearts"; if (heartCount >= count) { ImGui::TextColored(TRACKER_COLOR_ACCESSIBLE, "%s", itemStr.c_str()); SameLineOrTextWrap(); } else { ImGui::TextColored(TRACKER_COLOR_INACCESSIBLE, "%s", itemStr.c_str()); SameLineOrTextWrap(); } return; case Type::DUNGEONS_COMPLETED: count = std::get(req._args[0]); if (count == 1) { itemStr = "Complete " + std::to_string(count) + " Dungeon"; } else { itemStr = "Complete " + std::to_string(count) + " Dungeons"; } if (EvaluateRequirementAtFormTime(req, &m_currentSearch, FormTime::ALL, getTrackerRando()->GetWorld())) { ImGui::TextColored(TRACKER_COLOR_ACCESSIBLE, "%s", itemStr.c_str()); SameLineOrTextWrap(); } else { ImGui::TextColored(TRACKER_COLOR_INACCESSIBLE, "%s", itemStr.c_str()); SameLineOrTextWrap(); } default: break; } } void ImGuiMenuRandomizer::generateLocationInfo() { auto trackerRando = getTrackerRando(); auto world = trackerRando->GetWorld(); auto locations = world->GetAllLocations(); m_LocationInfo.clear(); m_numProgressionLocations = std::ranges::count_if(locations, [](auto* location) {return location->IsProgression();}); m_numAvailableLocations = std::ranges::count_if(m_currentSearch._visitedLocations, [](auto* location) {return location->IsProgression();}); for (auto location : locations) { // Don't show locations which aren't progression // Don't show any locations which aren't accessible if only accessible is checked if (!location->IsProgression()) { continue; } // Don't show warp portals for now either if (location->HasCategories("Warp Portal")) { continue; } int itemId = getLocationItem(location); LocationTrackerInfo info { .locationName = location->GetName(), .logicReq = location->GetComputedRequirement(), .locationItem = itemId >= 0 ? world->GetItem(itemId, true)->GetName() : "Unknown", .accessible = m_currentSearch._visitedLocations.contains(location) }; info.collected = isLocationObtained(location); info.hidden = std::ranges::find(m_HiddenChecks, info.locationName) != m_HiddenChecks.end(); if (info.accessible && info.collected) m_numAvailableLocations--; // Gather the hint regions this location is in (set avoids duplicates) std::unordered_set regions{}; for (auto access : location->GetAccessList()) { for (const auto& region : access->GetArea()->GetHintRegions()) { regions.insert(region); } } for (const auto& region : regions) { auto& infoGroup = m_LocationInfo[region]; infoGroup.addToGroup(info); } } auto counts = m_LocationInfo | std::views::transform([](const std::pair& location) { return location.second.collectedCount; }); m_numCollectedLocations = std::accumulate(counts.begin(), counts.end(), 0); } } // namespace dusk