mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-24 15:43:13 -04:00
6366ab61d1
For the most part, solo archipelago runs seem to work fine now using the original TP apworld, but im sure there are plenty of weird issues still that need to be handled.
848 lines
34 KiB
C++
848 lines
34 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>
|
|
|
|
#include "dusk/archipelago/archipelago_context.hpp"
|
|
|
|
|
|
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())) {
|
|
if (archi::ArchipelagoContext::IsConnected()) {
|
|
std::filesystem::path workingDir;
|
|
archi::ArchipelagoContext::GetSeedDirectoryPath(workingDir);
|
|
|
|
*trackerRando = randomizer::Randomizer(workingDir);
|
|
trackerRando->GenerateTrackerWorld(false);
|
|
}else {
|
|
*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
|