Files
dusklight/src/dusk/imgui/ImGuiMenuRandomizer.cpp
T
2026-06-15 06:03:08 -04:00

838 lines
33 KiB
C++

#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 <mutex>
#include <thread>
#include <filesystem>
#include <numeric>
#include <ranges>
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<std::string> 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<std::string> 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<int, std::pair<int, std::string>> 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<LocationTrackerInfo>& 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<Requirement>(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<Requirement>(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<Item*>(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<int>(req._args[0]);
item = std::get<Item*>(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<int>(req._args[0]);
// ImGui::Text("'Event_" + std::to_string(eventIndex) + "'");
//
// case Type::MACRO:
// macroIndex = std::get<int>(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<int>(req._args[0]);
// return "golden_bugs(" + std::to_string(count) + ")";
case Type::HEARTS:
count = std::get<int>(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<int>(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<std::string> 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<std::string, TrackerAreaGroup>& location) { return location.second.collectedCount; });
m_numCollectedLocations = std::accumulate(counts.begin(), counts.end(), 0);
}
} // namespace dusk