From 8a4249502c7ea8d20b7ed2adadf97e6ad41103b6 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 6 Nov 2025 16:42:22 -0600 Subject: [PATCH 1/8] Entrance Shuffle Proof of Concept --- mm/2s2h/Rando/Logic/EntranceShuffle.cpp | 223 +++++++++++++++++++ mm/2s2h/Rando/Logic/EntranceShuffle.h | 50 +++++ mm/2s2h/Rando/Logic/Logic.cpp | 9 +- mm/2s2h/Rando/Menu.cpp | 8 + mm/2s2h/Rando/MiscBehavior/EntranceHooks.cpp | 39 ++++ mm/2s2h/Rando/MiscBehavior/MiscBehavior.cpp | 2 + mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp | 4 + mm/2s2h/Rando/StaticData/Options.cpp | 1 + mm/2s2h/Rando/Types.h | 8 + 9 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 mm/2s2h/Rando/Logic/EntranceShuffle.cpp create mode 100644 mm/2s2h/Rando/Logic/EntranceShuffle.h create mode 100644 mm/2s2h/Rando/MiscBehavior/EntranceHooks.cpp diff --git a/mm/2s2h/Rando/Logic/EntranceShuffle.cpp b/mm/2s2h/Rando/Logic/EntranceShuffle.cpp new file mode 100644 index 0000000000..1a313425a7 --- /dev/null +++ b/mm/2s2h/Rando/Logic/EntranceShuffle.cpp @@ -0,0 +1,223 @@ +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/ShipInit.hpp" +#include "EntranceShuffle.h" +#include "Logic.h" + +extern "C" { +#include "z64scene.h" +} + +namespace Rando { + +namespace EntranceShuffle { + +// Entrance mapping storage +std::map sEntranceMap; + +// Optimally, these lists are dynamically built from the maps we already have built for logic... + +std::set interiorEntrances = { + // Clock Town + ENTRANCE(ASTRAL_OBSERVATORY, 0), + ENTRANCE(TREASURE_CHEST_SHOP, 0), + ENTRANCE(HONEY_AND_DARLINGS_SHOP, 0), + ENTRANCE(MAYORS_RESIDENCE, 0), + ENTRANCE(TOWN_SHOOTING_GALLERY, 0), + ENTRANCE(STOCK_POT_INN, 0), + ENTRANCE(STOCK_POT_INN, 1), + ENTRANCE(MILK_BAR, 0), + ENTRANCE(CURIOSITY_SHOP, 1), + ENTRANCE(FAIRY_FOUNTAIN, 0), + ENTRANCE(CLOCK_TOWER_INTERIOR, 1), + ENTRANCE(SWORDMANS_SCHOOL, 0), + ENTRANCE(CURIOSITY_SHOP, 0), + ENTRANCE(TRADING_POST, 0), + ENTRANCE(BOMB_SHOP, 0), + ENTRANCE(POST_OFFICE, 0), + ENTRANCE(LOTTERY_SHOP, 0), + // Termina Field & Roads + ENTRANCE(SWAMP_SHOOTING_GALLERY, 0), + ENTRANCE(TOURIST_INFORMATION, 0), + ENTRANCE(MAGIC_HAGS_POTION_SHOP, 0), + // Milk Road + ENTRANCE(CUCCO_SHACK, 0), + ENTRANCE(DOGGY_RACETRACK, 0), + ENTRANCE(RANCH_HOUSE, 0), + ENTRANCE(RANCH_HOUSE, 1), + // Mountain Village + ENTRANCE(GORON_SHOP, 0), + ENTRANCE(MOUNTAIN_SMITHY, 0), + // Great Bay + ENTRANCE(FISHERMANS_HUT, 0), + ENTRANCE(MARINE_RESEARCH_LAB, 0), + ENTRANCE(ZORA_HALL_ROOMS, 0), + ENTRANCE(ZORA_HALL_ROOMS, 1), + ENTRANCE(ZORA_HALL_ROOMS, 2), + ENTRANCE(ZORA_HALL_ROOMS, 3), + ENTRANCE(ZORA_HALL_ROOMS, 5), + ENTRANCE(OCEANSIDE_SPIDER_HOUSE, 0), + // Ikana + ENTRANCE(GHOST_HUT, 0), + ENTRANCE(MUSIC_BOX_HOUSE, 0), + // Great Fairy Fountains + ENTRANCE(FAIRY_FOUNTAIN, 1), + ENTRANCE(FAIRY_FOUNTAIN, 2), + ENTRANCE(FAIRY_FOUNTAIN, 3), + ENTRANCE(FAIRY_FOUNTAIN, 4), + // Other + ENTRANCE(SWAMP_SPIDER_HOUSE, 0), +}; + +std::set dungeonEntrances = { + ENTRANCE(WOODFALL_TEMPLE, 0), + ENTRANCE(SNOWHEAD_TEMPLE, 0), + ENTRANCE(GREAT_BAY_TEMPLE, 0), +}; + +std::vector ConvertSetToEntrancePairs(const std::set& entranceSet) { + std::vector entrancePairs; + for (s32 entrance : entranceSet) { + auto randoRegionId = Rando::Logic::GetRegionIdFromEntrance(entrance); + for (const auto& [exitId, regionExit] : Rando::Logic::Regions[randoRegionId].exits) { + if (regionExit.returnEntrance == entrance) { + entrancePairs.push_back({ entrance, exitId }); + break; + } + } + } + + return entrancePairs; +} + +std::vector GetInteriorEntrances() { + return ConvertSetToEntrancePairs(interiorEntrances); +} + +std::vector GetGrottoEntrances() { + return {}; +} + +std::vector GetDungeonEntrances() { + return ConvertSetToEntrancePairs(dungeonEntrances); +} + +std::vector GetOverworldEntrances() { + return { + { ENTRANCE(EAST_CLOCK_TOWN, 3), ENTRANCE(SOUTH_CLOCK_TOWN, 2) }, + { ENTRANCE(WEST_CLOCK_TOWN, 2), ENTRANCE(SOUTH_CLOCK_TOWN, 3) }, + { ENTRANCE(NORTH_CLOCK_TOWN, 2), ENTRANCE(SOUTH_CLOCK_TOWN, 4) }, + { ENTRANCE(WEST_CLOCK_TOWN, 1), ENTRANCE(SOUTH_CLOCK_TOWN, 5) }, + { ENTRANCE(LAUNDRY_POOL, 0), ENTRANCE(SOUTH_CLOCK_TOWN, 6) }, + { ENTRANCE(EAST_CLOCK_TOWN, 1), ENTRANCE(SOUTH_CLOCK_TOWN, 7) }, + { ENTRANCE(NORTH_CLOCK_TOWN, 1), ENTRANCE(EAST_CLOCK_TOWN, 5) }, + }; +} + +std::vector GetEntrancePool(EntrancePoolType poolType) { + switch (poolType) { + case POOL_INTERIOR: + return GetInteriorEntrances(); + case POOL_GROTTO: + return GetGrottoEntrances(); + case POOL_DUNGEON: + return GetDungeonEntrances(); + case POOL_OVERWORLD: + return GetOverworldEntrances(); + default: + return {}; + } +} + +bool IsEntranceShuffleEnabled() { + return IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_ENTRANCES] != RO_ENTRANCE_SHUFFLE_OFF; +} + +// Called on file load and in file creation prior to logic calculation +void ShuffleEntrances() { + sEntranceMap.clear(); + + if (!IsEntranceShuffleEnabled()) { + return; + } + + Ship_Random_Seed(gSaveContext.save.shipSaveInfo.rando.finalSeed); + + auto shuffleType = RANDO_SAVE_OPTIONS[RO_SHUFFLE_ENTRANCES]; + + std::vector poolsToShuffle; + + switch (shuffleType) { + case RO_ENTRANCE_SHUFFLE_INTERIORS_ONLY: + poolsToShuffle = { POOL_INTERIOR }; + break; + case RO_ENTRANCE_SHUFFLE_DUNGEONS_ONLY: + poolsToShuffle = { POOL_DUNGEON }; + break; + case RO_ENTRANCE_SHUFFLE_FULL: + poolsToShuffle = { POOL_INTERIOR, POOL_DUNGEON, POOL_OVERWORLD }; + break; + default: + return; + } + + // Shuffle each pool independently + for (auto poolType : poolsToShuffle) { + std::vector originalPool = GetEntrancePool(poolType); + auto pool = originalPool; // Make a copy to shuffle + + if (pool.size() < 2) { + continue; + } + + // Shuffle pool + for (size_t i = 0; i < pool.size(); i++) { + size_t j = Ship_Random(0, pool.size() - 1); + std::swap(pool[i], pool[j]); + } + + for (size_t i = 0; i < pool.size(); ++i) { + const auto& from = originalPool[i]; + const auto& to = pool[i]; + + if (from.entrance != to.entrance) { + sEntranceMap[from.entrance] = to.entrance; + } + + // if (from.isReversible && to.isReversible) { + if (to.exit != from.exit) { + sEntranceMap[to.exit] = from.exit; + } + // } + } + } +} + +s32 GetShuffledEntrance(s32 originalEntrance) { + if (!IsEntranceShuffleEnabled()) { + return originalEntrance; + } + + if (sEntranceMap.count(originalEntrance) > 0) { + return sEntranceMap[originalEntrance]; + } + + return originalEntrance; +} + +s32 GetOriginalEntrance(s32 shuffledEntrance) { + if (!IsEntranceShuffleEnabled()) { + return shuffledEntrance; + } + + for (const auto& [orig, mapped] : sEntranceMap) { + if (mapped == shuffledEntrance) { + return orig; + } + } + + return shuffledEntrance; +} + +} // namespace EntranceShuffle + +} // namespace Rando diff --git a/mm/2s2h/Rando/Logic/EntranceShuffle.h b/mm/2s2h/Rando/Logic/EntranceShuffle.h new file mode 100644 index 0000000000..10b5726ae4 --- /dev/null +++ b/mm/2s2h/Rando/Logic/EntranceShuffle.h @@ -0,0 +1,50 @@ +#ifndef RANDO_ENTRANCE_SHUFFLE_H +#define RANDO_ENTRANCE_SHUFFLE_H + +#include "Rando/Rando.h" +#include +#include + +extern "C" { +#include "functions.h" +#include "variables.h" +} + +namespace Rando { + +namespace EntranceShuffle { + +// Structure to represent an entrance pair +struct EntrancePair { + s32 entrance; // the entrance you take to enter Target area from source + s32 exit; // the exit you take to leave Target area to source +}; + +// Groups of entrances that can be shuffled together +enum EntrancePoolType { + POOL_INTERIOR, // Houses, shops, etc. + POOL_GROTTO, // Grottos and caves + POOL_DUNGEON, // Dungeon entrances + POOL_OVERWORLD, // Overworld area connections +}; + +// Generate entrance shuffle mappings for a new seed +void ShuffleEntrances(); + +// Get the shuffled destination for an entrance +s32 GetShuffledEntrance(s32 originalEntrance); + +// Get the original entrance from a shuffled one (for reverse lookups) +s32 GetOriginalEntrance(s32 shuffledEntrance); + +// Check if entrance shuffle is enabled +bool IsEntranceShuffleEnabled(); + +// Get all entrances in a specific pool +std::vector GetEntrancePool(EntrancePoolType poolType); + +} // namespace EntranceShuffle + +} // namespace Rando + +#endif // RANDO_ENTRANCE_SHUFFLE_H diff --git a/mm/2s2h/Rando/Logic/Logic.cpp b/mm/2s2h/Rando/Logic/Logic.cpp index b72dde87a1..c502270bee 100644 --- a/mm/2s2h/Rando/Logic/Logic.cpp +++ b/mm/2s2h/Rando/Logic/Logic.cpp @@ -2,6 +2,7 @@ #include "2s2h/ShipInit.hpp" #include "Logic.h" +#include "EntranceShuffle.h" namespace Rando { @@ -122,7 +123,13 @@ void FindReachableRegions(RandoRegionId currentRegion, std::set& // Explore exits for (auto& [exitId, regionExit] : sourceRegion.exits) { - RandoRegionId connectedRegionId = GetRegionIdFromEntrance(exitId); + s32 lookupExit = exitId; + if (Rando::EntranceShuffle::IsEntranceShuffleEnabled()) { + lookupExit = Rando::EntranceShuffle::GetShuffledEntrance(lookupExit); + } + + RandoRegionId connectedRegionId = GetRegionIdFromEntrance(lookupExit); + // Check if the region is accessible and hasn’t been visited yet if (reachableRegions.count(connectedRegionId) == 0 && regionExit.condition()) { reachableRegions.insert(connectedRegionId); diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp index b0b63dc35e..f9816f6ba0 100644 --- a/mm/2s2h/Rando/Menu.cpp +++ b/mm/2s2h/Rando/Menu.cpp @@ -47,6 +47,13 @@ std::unordered_map trapItemsOptions = { }; // clang-format off +std::unordered_map entranceShuffleOptions = { + { RO_ENTRANCE_SHUFFLE_OFF, "Off" }, + { RO_ENTRANCE_SHUFFLE_INTERIORS_ONLY, "Interiors Only" }, + { RO_ENTRANCE_SHUFFLE_DUNGEONS_ONLY, "Dungeons Only" }, + { RO_ENTRANCE_SHUFFLE_FULL, "Full" }, +}; + std::vector incompatibleWithVanilla = { RO_SHUFFLE_BOSS_SOULS, RO_SHUFFLE_SWIM, @@ -440,6 +447,7 @@ static void DrawShufflesTab() { CVarCheckbox("Shuffle Freestanding Items", Rando::StaticData::Options[RO_SHUFFLE_FREESTANDING_ITEMS].cvar); CVarCheckbox("Shuffle Wonder Items", "gPlaceholderBool", CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } })); + CVarCombobox("Shuffle Entrances", Rando::StaticData::Options[RO_SHUFFLE_ENTRANCES].cvar, &entranceShuffleOptions); ImGui::EndChild(); ImGui::SameLine(); ImGui::BeginChild("randoLocationsColumn3", ImVec2(columnWidth, halfHeight)); diff --git a/mm/2s2h/Rando/MiscBehavior/EntranceHooks.cpp b/mm/2s2h/Rando/MiscBehavior/EntranceHooks.cpp new file mode 100644 index 0000000000..4bd97d8798 --- /dev/null +++ b/mm/2s2h/Rando/MiscBehavior/EntranceHooks.cpp @@ -0,0 +1,39 @@ +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/ShipInit.hpp" +#include "2s2h/Rando/Logic/EntranceShuffle.h" + +extern "C" { +#include "functions.h" +#include "variables.h" +#include "z64scene.h" +} + +namespace Rando { + +namespace EntranceShuffle { + +// Hook into scene transitions to apply entrance shuffling +void OnPlayDestroy() { + if (!IsEntranceShuffleEnabled()) { + return; + } + + // Get the shuffled destination entrance + s32 originalEntrance = gSaveContext.save.entrance; + s32 shuffledEntrance = GetShuffledEntrance(originalEntrance); + + if (shuffledEntrance != originalEntrance) { + gSaveContext.save.entrance = shuffledEntrance; + } +} + +static RegisterShipInitFunc registerHooks( + []() { + // Hook into OnPlayDestroy which is called just before transitioning + GameInteractor::Instance->RegisterGameHook([]() { OnPlayDestroy(); }); + }, + {}); + +} // namespace EntranceShuffle + +} // namespace Rando diff --git a/mm/2s2h/Rando/MiscBehavior/MiscBehavior.cpp b/mm/2s2h/Rando/MiscBehavior/MiscBehavior.cpp index fad880ca4b..18b96850de 100644 --- a/mm/2s2h/Rando/MiscBehavior/MiscBehavior.cpp +++ b/mm/2s2h/Rando/MiscBehavior/MiscBehavior.cpp @@ -1,5 +1,6 @@ #include "MiscBehavior.h" #include "2s2h/Rando/Logic/Logic.h" +#include "2s2h/Rando/Logic/EntranceShuffle.h" extern "C" { #include "variables.h" @@ -17,6 +18,7 @@ void Rando::MiscBehavior::OnFileLoad() { Rando::MiscBehavior::InitKaleidoItemPage(); Rando::MiscBehavior::InitOfferGetItemBehavior(); Rando::MiscBehavior::SariasSongHint(); + Rando::EntranceShuffle::ShuffleEntrances(); COND_HOOK(OnFlagSet, IS_RANDO, Rando::MiscBehavior::OnFlagSet); COND_HOOK(OnSceneFlagSet, IS_RANDO, Rando::MiscBehavior::OnSceneFlagSet); diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp index be39bacd91..e506e78d25 100644 --- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp +++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp @@ -2,6 +2,7 @@ #include "Rando/Rando.h" #include "Rando/Spoiler/Spoiler.h" #include "Rando/Logic/Logic.h" +#include "Rando/Logic/EntranceShuffle.h" #include "2s2h/ShipUtils.h" #include #include "ClockShuffle.h" @@ -143,6 +144,9 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { } } + // Shuffle entrances if enabled + Rando::EntranceShuffle::ShuffleEntrances(); + if (RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_VANILLA) { GiveItem(RI_SWORD_KOKIRI); GiveItem(RI_SHIELD_HERO); diff --git a/mm/2s2h/Rando/StaticData/Options.cpp b/mm/2s2h/Rando/StaticData/Options.cpp index 8317c0eec7..93840a902b 100644 --- a/mm/2s2h/Rando/StaticData/Options.cpp +++ b/mm/2s2h/Rando/StaticData/Options.cpp @@ -44,6 +44,7 @@ std::map Options = { RO(RO_SHUFFLE_CRATE_DROPS, RO_GENERIC_OFF), RO(RO_SHUFFLE_ENEMY_DROPS, RO_GENERIC_OFF), RO(RO_SHUFFLE_ENEMY_SOULS, RO_GENERIC_OFF), + RO(RO_SHUFFLE_ENTRANCES, RO_ENTRANCE_SHUFFLE_OFF), RO(RO_SHUFFLE_FREESTANDING_ITEMS, RO_GENERIC_OFF), RO(RO_SHUFFLE_FROGS, RO_GENERIC_OFF), RO(RO_SHUFFLE_GOLD_SKULLTULAS, RO_GENERIC_OFF), diff --git a/mm/2s2h/Rando/Types.h b/mm/2s2h/Rando/Types.h index 673309a612..050c11cd4e 100644 --- a/mm/2s2h/Rando/Types.h +++ b/mm/2s2h/Rando/Types.h @@ -2977,6 +2977,7 @@ typedef enum { RO_SHUFFLE_CRATE_DROPS, RO_SHUFFLE_ENEMY_DROPS, RO_SHUFFLE_ENEMY_SOULS, + RO_SHUFFLE_ENTRANCES, RO_SHUFFLE_FREESTANDING_ITEMS, RO_SHUFFLE_FROGS, RO_SHUFFLE_GOLD_SKULLTULAS, @@ -3043,6 +3044,13 @@ typedef enum { RO_CLOCK_SHUFFLE_DESCENDING, } RandoClockShuffleOptions; +typedef enum { + RO_ENTRANCE_SHUFFLE_OFF, + RO_ENTRANCE_SHUFFLE_INTERIORS_ONLY, + RO_ENTRANCE_SHUFFLE_DUNGEONS_ONLY, + RO_ENTRANCE_SHUFFLE_FULL, +} RandoOptionEntranceShuffle; + typedef enum { RANDO_INF_PURCHASED_BEANS_FROM_SOUTHERN_SWAMP_SCRUB, RANDO_INF_PURCHASED_BOMB_BAG_FROM_GORON_VILLAGE_SCRUB, From a991a4e0f4255c11dacbbe6ed2c889645c8c7739 Mon Sep 17 00:00:00 2001 From: Phillyjawn Date: Sun, 25 Jan 2026 15:25:52 -0700 Subject: [PATCH 2/8] Add some base tracker functionality - refine later --- mm/2s2h/BenGui/BenGui.cpp | 13 + .../Rando/EntranceTracker/EntranceTracker.cpp | 770 ++++++++++++++++++ .../Rando/EntranceTracker/EntranceTracker.h | 44 + mm/2s2h/Rando/Menu.cpp | 9 + mm/2s2h/Rando/MiscBehavior/EntranceHooks.cpp | 4 + mm/2s2h/Rando/Rando.cpp | 3 + mm/include/z64save.h | 1 + 7 files changed, 844 insertions(+) create mode 100644 mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp create mode 100644 mm/2s2h/Rando/EntranceTracker/EntranceTracker.h diff --git a/mm/2s2h/BenGui/BenGui.cpp b/mm/2s2h/BenGui/BenGui.cpp index f7c9d8d077..ec3d3adfbe 100644 --- a/mm/2s2h/BenGui/BenGui.cpp +++ b/mm/2s2h/BenGui/BenGui.cpp @@ -9,6 +9,7 @@ #include "CosmeticEditor.h" #include "Notification.h" #include "2s2h/Rando/CheckTracker/CheckTracker.h" +#include "2s2h/Rando/EntranceTracker/EntranceTracker.h" #ifdef __APPLE__ #include @@ -59,6 +60,8 @@ std::shared_ptr mBenMenu; std::shared_ptr mNotificationWindow; std::shared_ptr mRandoCheckTrackerWindow; std::shared_ptr mRandoCheckTrackerSettingsWindow; +std::shared_ptr mEntranceTrackerWindow; +std::shared_ptr mEntranceTrackerSettingsWindow; std::shared_ptr mItemTrackerWindow; std::shared_ptr mItemTrackerSettingsWindow; std::shared_ptr mDisplayOverlayWindow; @@ -176,6 +179,14 @@ void SetupGuiElements() { "gWindows.CheckTrackerSettings", "Check Tracker Settings"); gui->AddGuiWindow(mRandoCheckTrackerSettingsWindow); + mEntranceTrackerWindow = std::make_shared( + "gWindows.EntranceTracker", "Entrance Tracker", ImVec2(450, 500)); + gui->AddGuiWindow(mEntranceTrackerWindow); + + mEntranceTrackerSettingsWindow = std::make_shared( + "gWindows.EntranceTrackerSettings", "Entrance Tracker Settings"); + gui->AddGuiWindow(mEntranceTrackerSettingsWindow); + mInputViewer = std::make_shared("gWindows.InputViewer", "Input Viewer"); gui->AddGuiWindow(mInputViewer); mInputViewerSettings = std::make_shared("gWindows.InputViewerSettings", @@ -202,6 +213,8 @@ void Destroy() { mNotificationWindow = nullptr; mRandoCheckTrackerWindow = nullptr; mRandoCheckTrackerSettingsWindow = nullptr; + mEntranceTrackerWindow = nullptr; + mEntranceTrackerSettingsWindow = nullptr; mHookDebuggerWindow = nullptr; mSaveEditorWindow = nullptr; diff --git a/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp new file mode 100644 index 0000000000..627528e654 --- /dev/null +++ b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp @@ -0,0 +1,770 @@ +#include "EntranceTracker.h" +#include "2s2h/Rando/Logic/Logic.h" +#include "2s2h/Rando/Logic/EntranceShuffle.h" +#include "2s2h/ShipUtils.h" +#include "2s2h/BenGui/UIWidgets.hpp" +#include "2s2h/BenPort.h" +#include "2s2h/ShipInit.hpp" +#include +#include +#include + +extern "C" { +#include "z64scene.h" +s16 Play_GetOriginalSceneId(s16 sceneId); +} + +namespace BenGui { +extern std::shared_ptr mEntranceTrackerWindow; +} + +#define WIDGET_COLOR UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)) + +// Scene sorting index (same as CheckTracker) +#define DEFINE_SCENE(_name, enumValue, _textId, _drawConfig, _restrictionFlags, _persistentCycleFlags, \ + _entranceSceneId, betterMapSelectIndex, _humanName) \ + { enumValue, betterMapSelectIndex }, +#define DEFINE_SCENE_UNSET(_enumValue) + +static std::unordered_map betterSceneIndex = { +#include "tables/scene_table.h" +}; + +#undef DEFINE_SCENE +#undef DEFINE_SCENE_UNSET + +// Visibility modes +typedef enum { + ENTRANCE_TRACKER_VISIBILITY_MODE_ALWAYS, + ENTRANCE_TRACKER_VISIBILITY_MODE_ONLY_ON_PAUSE_MENU, + ENTRANCE_TRACKER_VISIBILITY_MODE_BUTTON_TOGGLE, + ENTRANCE_TRACKER_VISIBILITY_MODE_BUTTON_HOLD, +} EntranceTrackerVisibilityMode; + +static std::unordered_map sVisibilityModes = { + { ENTRANCE_TRACKER_VISIBILITY_MODE_ALWAYS, "Always" }, + { ENTRANCE_TRACKER_VISIBILITY_MODE_ONLY_ON_PAUSE_MENU, "Only on Pause Menu" }, + { ENTRANCE_TRACKER_VISIBILITY_MODE_BUTTON_TOGGLE, "Button Toggle" }, + { ENTRANCE_TRACKER_VISIBILITY_MODE_BUTTON_HOLD, "Button Hold" }, +}; + +static bool sEntranceTrackerBtnState = false; + +// CVars +#define CVAR_NAME_SHOW_ENTRANCE_TRACKER "gWindows.EntranceTracker" +#define CVAR_NAME_VISIBILITY_MODE "gRando.EntranceTracker.VisibilityMode" +#define CVAR_NAME_VISIBILITY_BTN "gRando.EntranceTracker.VisibilityBtn" +#define CVAR_NAME_TRACKER_OPACITY "gRando.EntranceTracker.Opacity" +#define CVAR_NAME_TRACKER_SCALE "gRando.EntranceTracker.Scale" +#define CVAR_NAME_SPOILER_MODE "gRando.EntranceTracker.SpoilerMode" +#define CVAR_NAME_ROUTE_DETAIL "gRando.EntranceTracker.RouteDetailMode" +#define CVAR_NAME_SHOW_SEARCH "gRando.EntranceTracker.ShowSearch" + +#define CVAR_SHOW_ENTRANCE_TRACKER CVarGetInteger(CVAR_NAME_SHOW_ENTRANCE_TRACKER, 0) +#define CVAR_VISIBILITY_MODE CVarGetInteger(CVAR_NAME_VISIBILITY_MODE, ENTRANCE_TRACKER_VISIBILITY_MODE_ALWAYS) +#define CVAR_VISIBILITY_BTN CVarGetInteger(CVAR_NAME_VISIBILITY_BTN, BTN_CUSTOM_MODIFIER1) +#define CVAR_TRACKER_OPACITY CVarGetFloat(CVAR_NAME_TRACKER_OPACITY, 0.5f) +#define CVAR_TRACKER_SCALE CVarGetFloat(CVAR_NAME_TRACKER_SCALE, 1.0f) +#define CVAR_SPOILER_MODE CVarGetInteger(CVAR_NAME_SPOILER_MODE, 1) // Default: show all +#define CVAR_ROUTE_DETAIL CVarGetInteger(CVAR_NAME_ROUTE_DETAIL, 0) // Default: compact +#define CVAR_SHOW_SEARCH CVarGetInteger(CVAR_NAME_SHOW_SEARCH, 1) + +static ImGuiTextFilter sEntranceTrackerFilter; +static float trackerScale = 1.0f; +static ImVec4 trackerBG = ImVec4{ 0, 0, 0, 0.5f }; + +// Entrance display data structures +struct EntranceConnection { + s32 originalEntrance; + s32 shuffledEntrance; + SceneId sourceScene; + std::string originalName; + std::string shuffledName; +}; + +static std::map> sEntrancesByArea; +static std::vector sSortedSceneIds; + +// Route finder state +static int sRouteFromIndex = 0; +static int sRouteToIndex = 0; +static std::string sRouteResult = ""; +static std::vector> sRegionList; + +// Entrance name mapping - built from scene names with spawn-specific suffixes +static std::map sEntranceNames; + +// Helper to extract scene ID from entrance +static SceneId GetSceneFromEntrance(s32 entrance) { + return static_cast((entrance >> 9) & 0x7F); +} + +// Helper to extract spawn number from entrance +static s32 GetSpawnFromEntrance(s32 entrance) { + return (entrance >> 4) & 0x1F; +} + +// Build entrance name from scene name and spawn number +static std::string BuildEntranceName(s32 entrance) { + SceneId sceneId = GetSceneFromEntrance(entrance); + s32 spawn = GetSpawnFromEntrance(entrance); + + std::string baseName = Ship_GetSceneName(sceneId); + + // Add spawn suffix for multi-entrance scenes + if (spawn > 0) { + baseName += " (" + std::to_string(spawn + 1) + ")"; + } + + return baseName; +} + +std::string Rando::EntranceTracker::GetEntranceName(s32 entranceId) { + if (sEntranceNames.count(entranceId) > 0) { + return sEntranceNames[entranceId]; + } + return BuildEntranceName(entranceId); +} + +// Discovery tracking - uses save data for persistence +// Maps entrance IDs to compact indices (0-255) for bitfield storage +static std::map sEntranceToIndex; +static std::map sIndexToEntrance; +static u8 sNextEntranceIndex = 0; + +// Get or assign an index for an entrance ID +static u8 GetEntranceIndex(s32 entranceId) { + if (sEntranceToIndex.count(entranceId) == 0) { + if (sNextEntranceIndex >= 255) { + return 255; // Fallback if we run out of indices + } + sEntranceToIndex[entranceId] = sNextEntranceIndex; + sIndexToEntrance[sNextEntranceIndex] = entranceId; + sNextEntranceIndex++; + } + return sEntranceToIndex[entranceId]; +} + +// Build the entrance index mapping from known entrance pools +static void BuildEntranceIndexMap() { + sEntranceToIndex.clear(); + sIndexToEntrance.clear(); + sNextEntranceIndex = 0; + + // Add all entrances from the pools to ensure consistent indexing + std::vector pools = { + Rando::EntranceShuffle::POOL_INTERIOR, + Rando::EntranceShuffle::POOL_DUNGEON, + Rando::EntranceShuffle::POOL_OVERWORLD + }; + + for (auto poolType : pools) { + auto entrancePairs = Rando::EntranceShuffle::GetEntrancePool(poolType); + for (const auto& pair : entrancePairs) { + GetEntranceIndex(pair.entrance); + GetEntranceIndex(pair.exit); + } + } +} + +void Rando::EntranceTracker::SetEntranceDiscovered(s32 entranceId) { + if (!IS_RANDO) { + return; + } + + u8 index = GetEntranceIndex(entranceId); + if (index < 255) { + u8 byteIndex = index / 8; + u8 bitIndex = index % 8; + gSaveContext.save.shipSaveInfo.rando.discoveredEntrances[byteIndex] |= (1 << bitIndex); + } +} + +bool Rando::EntranceTracker::IsEntranceDiscovered(s32 entranceId) { + if (!IS_RANDO) { + return false; + } + + u8 index = GetEntranceIndex(entranceId); + if (index >= 255) { + return false; + } + + u8 byteIndex = index / 8; + u8 bitIndex = index % 8; + return (gSaveContext.save.shipSaveInfo.rando.discoveredEntrances[byteIndex] & (1 << bitIndex)) != 0; +} + +// Build the entrance name mapping with better names for specific entrances +static void BuildEntranceNameMap() { + sEntranceNames.clear(); + + // Add custom names for specific entrances (more descriptive than just scene name) + sEntranceNames[ENTRANCE(STOCK_POT_INN, 0)] = "Stock Pot Inn (Main)"; + sEntranceNames[ENTRANCE(STOCK_POT_INN, 1)] = "Stock Pot Inn (Upstairs)"; + sEntranceNames[ENTRANCE(CURIOSITY_SHOP, 0)] = "Curiosity Shop (Front)"; + sEntranceNames[ENTRANCE(CURIOSITY_SHOP, 1)] = "Curiosity Shop (Back)"; + sEntranceNames[ENTRANCE(RANCH_HOUSE, 0)] = "Romani Ranch House (Main)"; + sEntranceNames[ENTRANCE(RANCH_HOUSE, 1)] = "Romani Ranch House (Barn)"; + sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 0)] = "Zora Hall (Evan's Room)"; + sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 1)] = "Zora Hall (Lulu's Room)"; + sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 2)] = "Zora Hall (Japas' Room)"; + sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 3)] = "Zora Hall (Tijo's Room)"; + sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 5)] = "Zora Hall (Shop)"; + sEntranceNames[ENTRANCE(FAIRY_FOUNTAIN, 0)] = "Great Fairy (Clock Town)"; + sEntranceNames[ENTRANCE(FAIRY_FOUNTAIN, 1)] = "Great Fairy (Woodfall)"; + sEntranceNames[ENTRANCE(FAIRY_FOUNTAIN, 2)] = "Great Fairy (Snowhead)"; + sEntranceNames[ENTRANCE(FAIRY_FOUNTAIN, 3)] = "Great Fairy (Great Bay)"; + sEntranceNames[ENTRANCE(FAIRY_FOUNTAIN, 4)] = "Great Fairy (Ikana)"; + sEntranceNames[ENTRANCE(CLOCK_TOWER_INTERIOR, 1)] = "Clock Tower Interior"; + + // Overworld connections - use more descriptive names + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 3)] = "East Clock Town (from South)"; + sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 2)] = "South Clock Town (from East)"; + sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 2)] = "West Clock Town (from South)"; + sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 3)] = "South Clock Town (from West)"; + sEntranceNames[ENTRANCE(NORTH_CLOCK_TOWN, 2)] = "North Clock Town (from South)"; + sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 4)] = "South Clock Town (from North)"; + sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 1)] = "West Clock Town (Swordsman)"; + sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 5)] = "South Clock Town (Swordsman)"; + sEntranceNames[ENTRANCE(LAUNDRY_POOL, 0)] = "Laundry Pool"; + sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 6)] = "South Clock Town (Laundry)"; + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 1)] = "East Clock Town (Chest Shop)"; + sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 7)] = "South Clock Town (Chest Shop)"; + sEntranceNames[ENTRANCE(NORTH_CLOCK_TOWN, 1)] = "North Clock Town (Fairy)"; + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 5)] = "East Clock Town (Fairy)"; +} + +// Get the source scene for an entrance (where you are when you use it) +static SceneId GetSourceSceneForEntrance(s32 entrance) { + // Use the region data to find where the entrance originates + RandoRegionId regionId = Rando::Logic::GetRegionIdFromEntrance(entrance); + if (regionId != RR_MAX && Rando::Logic::Regions.count(regionId) > 0) { + return Rando::Logic::Regions[regionId].sceneId; + } + // Fallback: the entrance scene itself + return GetSceneFromEntrance(entrance); +} + +// Build entrance data grouped by source area +static void BuildEntranceData() { + sEntrancesByArea.clear(); + sSortedSceneIds.clear(); + + if (!Rando::EntranceShuffle::IsEntranceShuffleEnabled()) { + return; + } + + // Get all entrance pools and process each + std::vector pools = { + Rando::EntranceShuffle::POOL_INTERIOR, + Rando::EntranceShuffle::POOL_DUNGEON, + Rando::EntranceShuffle::POOL_OVERWORLD + }; + + for (auto poolType : pools) { + auto entrancePairs = Rando::EntranceShuffle::GetEntrancePool(poolType); + + for (const auto& pair : entrancePairs) { + s32 original = pair.entrance; + s32 shuffled = Rando::EntranceShuffle::GetShuffledEntrance(original); + + // Get source scene for grouping + SceneId sourceScene = GetSourceSceneForEntrance(original); + + EntranceConnection conn; + conn.originalEntrance = original; + conn.shuffledEntrance = shuffled; + conn.sourceScene = sourceScene; + conn.originalName = Rando::EntranceTracker::GetEntranceName(original); + conn.shuffledName = Rando::EntranceTracker::GetEntranceName(shuffled); + + sEntrancesByArea[sourceScene].push_back(conn); + } + } + + // Build sorted scene list + for (const auto& [sceneId, _] : sEntrancesByArea) { + sSortedSceneIds.push_back(sceneId); + } + + // Sort by betterSceneIndex + std::sort(sSortedSceneIds.begin(), sSortedSceneIds.end(), + [](SceneId a, SceneId b) { return betterSceneIndex[a] < betterSceneIndex[b]; }); +} + +// Build region list for route finder dropdowns +static void BuildRegionList() { + sRegionList.clear(); + + for (const auto& [regionId, region] : Rando::Logic::Regions) { + if (region.name != nullptr && strlen(region.name) > 0) { + sRegionList.push_back({ regionId, region.name }); + } + } + + // Sort alphabetically + std::sort(sRegionList.begin(), sRegionList.end(), + [](const auto& a, const auto& b) { return a.second < b.second; }); +} + +// Route finding using BFS +struct RouteStep { + RandoRegionId region; + s32 entranceUsed; // -1 for connections (same-scene transitions) + std::string description; +}; + +static std::vector FindRoute(RandoRegionId from, RandoRegionId to) { + std::vector result; + + if (from == to) { + return result; // Already there + } + + std::queue> queue; + std::set visited; + + // Start from the source region + queue.push({}); + visited.insert(from); + + RandoRegionId current = from; + + while (!queue.empty()) { + auto path = queue.front(); + queue.pop(); + + // Determine current region from path + current = path.empty() ? from : path.back().region; + + if (Rando::Logic::Regions.count(current) == 0) { + continue; + } + + auto& region = Rando::Logic::Regions[current]; + + // Check exits (using shuffled entrances) + for (const auto& [exitId, regionExit] : region.exits) { + s32 actualExit = Rando::EntranceShuffle::GetShuffledEntrance(exitId); + RandoRegionId targetRegion = Rando::Logic::GetRegionIdFromEntrance(actualExit); + + if (targetRegion == to) { + // Found the destination + result = path; + RouteStep step; + step.region = to; + step.entranceUsed = exitId; + step.description = Rando::EntranceTracker::GetEntranceName(actualExit); + result.push_back(step); + return result; + } + + if (targetRegion != RR_MAX && visited.find(targetRegion) == visited.end()) { + visited.insert(targetRegion); + auto newPath = path; + RouteStep step; + step.region = targetRegion; + step.entranceUsed = exitId; + step.description = Rando::EntranceTracker::GetEntranceName(actualExit); + newPath.push_back(step); + queue.push(newPath); + } + } + + // Check connections (same-scene region transitions) + for (const auto& [connectedRegion, condition] : region.connections) { + if (connectedRegion == to) { + result = path; + RouteStep step; + step.region = to; + step.entranceUsed = -1; + step.description = ""; + result.push_back(step); + return result; + } + + if (visited.find(connectedRegion) == visited.end()) { + visited.insert(connectedRegion); + auto newPath = path; + RouteStep step; + step.region = connectedRegion; + step.entranceUsed = -1; + step.description = ""; + newPath.push_back(step); + queue.push(newPath); + } + } + } + + return result; // Empty if no route found +} + +// Format route for display +static std::string FormatRoute(RandoRegionId from, const std::vector& route, bool detailed) { + if (route.empty()) { + return "No route found"; + } + + std::string result; + + if (detailed) { + // Detailed format + if (Rando::Logic::Regions.count(from) > 0) { + result = "From " + std::string(Rando::Logic::Regions[from].name); + } + + for (size_t i = 0; i < route.size(); i++) { + const auto& step = route[i]; + if (step.entranceUsed != -1) { + result += "\n -> Take '" + step.description + "'"; + } + if (Rando::Logic::Regions.count(step.region) > 0) { + result += "\n => " + std::string(Rando::Logic::Regions[step.region].name); + } + } + } else { + // Compact format + if (Rando::Logic::Regions.count(from) > 0) { + result = Rando::Logic::Regions[from].name; + } + + for (const auto& step : route) { + if (Rando::Logic::Regions.count(step.region) > 0) { + result += " -> " + std::string(Rando::Logic::Regions[step.region].name); + } + } + } + + return result; +} + +namespace Rando { + +namespace EntranceTracker { + +void EntranceTrackerWindow::Draw() { + if (!CVAR_SHOW_ENTRANCE_TRACKER) { + return; + } + + if (CVAR_VISIBILITY_MODE == ENTRANCE_TRACKER_VISIBILITY_MODE_ONLY_ON_PAUSE_MENU && + (!gPlayState || !gPlayState->pauseCtx.state)) { + return; + } + + if ((CVAR_VISIBILITY_MODE == ENTRANCE_TRACKER_VISIBILITY_MODE_BUTTON_TOGGLE || + CVAR_VISIBILITY_MODE == ENTRANCE_TRACKER_VISIBILITY_MODE_BUTTON_HOLD) && + !sEntranceTrackerBtnState) { + return; + } + + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, trackerBG); + ImGui::PushStyleColor(ImGuiCol_TitleBg, trackerBG); + ImGui::PushStyleColor(ImGuiCol_WindowBg, trackerBG); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + + ImGui::SetNextWindowSize(ImVec2(450.0f, 500.0f), ImGuiCond_FirstUseEver); + + ImGui::Begin("Entrance Tracker", nullptr, ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing); + + trackerBG.w = ImGui::IsWindowDocked() ? 1.0f : CVAR_TRACKER_OPACITY; + trackerScale = CVAR_TRACKER_SCALE; + ImGui::SetWindowFontScale(trackerScale); + + if (!gPlayState || !IS_RANDO) { + ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize("No Rando Save Loaded").x) / 2); + ImGui::SetCursorPosY(ImGui::GetWindowHeight() / 2 - 10.0f); + ImGui::TextColored(UIWidgets::ColorValues.at(UIWidgets::Colors::Gray), "No Rando Save Loaded"); + ImGui::End(); + ImGui::PopStyleColor(4); + ImGui::PopStyleVar(1); + return; + } + + if (!Rando::EntranceShuffle::IsEntranceShuffleEnabled()) { + ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize("Entrance Shuffle Not Enabled").x) / 2); + ImGui::SetCursorPosY(ImGui::GetWindowHeight() / 2 - 10.0f); + ImGui::TextColored(UIWidgets::ColorValues.at(UIWidgets::Colors::Gray), "Entrance Shuffle Not Enabled"); + ImGui::End(); + ImGui::PopStyleColor(4); + ImGui::PopStyleVar(1); + return; + } + + // Search bar + if (CVAR_SHOW_SEARCH) { + UIWidgets::PushStyleInput(); + sEntranceTrackerFilter.Draw("##filter", ImGui::GetContentRegionAvail().x - 40.0f); + UIWidgets::PopStyleInput(); + + ImGui::SameLine(); + if (!sEntranceTrackerFilter.IsActive()) { + ImGui::Text("Search"); + } else { + if (UIWidgets::Button(ICON_FA_TIMES, UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) { + sEntranceTrackerFilter.Clear(); + } + } + } + + // Tab bar for Entrances and Route Finder + if (ImGui::BeginTabBar("EntranceTrackerTabs")) { + if (ImGui::BeginTabItem("Entrances")) { + ImGui::BeginChild("EntranceList"); + + bool spoilerMode = CVAR_SPOILER_MODE; + + for (SceneId sceneId : sSortedSceneIds) { + auto& entrances = sEntrancesByArea[sceneId]; + + // Filter entrances + std::vector filtered; + for (const auto& conn : entrances) { + // Check spoiler mode + if (!spoilerMode && !IsEntranceDiscovered(conn.shuffledEntrance)) { + continue; + } + + // Check search filter + std::string searchStr = conn.originalName + " " + conn.shuffledName; + if (!sEntranceTrackerFilter.PassFilter(searchStr.c_str())) { + continue; + } + + filtered.push_back(&conn); + } + + if (filtered.empty()) { + continue; + } + + ImGui::PushID(sceneId); + ImGui::Separator(); + + std::string headerText = Ship_GetSceneName(sceneId); + headerText += " (" + std::to_string(filtered.size()) + ")"; + + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0, 0, 0, 0)); + + if (ImGui::CollapsingHeader(headerText.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Indent(20.0f); + + for (const auto* conn : filtered) { + std::string line = conn->originalName + " -> " + conn->shuffledName; + + // Highlight if this entrance was shuffled (different from original) + if (conn->originalEntrance != conn->shuffledEntrance) { + ImGui::TextColored(UIWidgets::ColorValues.at(UIWidgets::Colors::Yellow), "%s", line.c_str()); + } else { + ImGui::Text("%s", line.c_str()); + } + } + + ImGui::Unindent(20.0f); + } + + ImGui::PopStyleColor(); + ImGui::PopID(); + } + + ImGui::EndChild(); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Route Finder")) { + ImGui::BeginChild("RouteFinder"); + + // Build region list if empty + if (sRegionList.empty()) { + BuildRegionList(); + } + + // From dropdown + ImGui::Text("From:"); + ImGui::SameLine(); + if (ImGui::BeginCombo("##FromRegion", sRouteFromIndex < (int)sRegionList.size() + ? sRegionList[sRouteFromIndex].second.c_str() : "Select...")) { + for (int i = 0; i < (int)sRegionList.size(); i++) { + bool selected = (i == sRouteFromIndex); + if (ImGui::Selectable(sRegionList[i].second.c_str(), selected)) { + sRouteFromIndex = i; + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + + // "Use Current Location" button + ImGui::SameLine(); + if (UIWidgets::Button("Current", UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) { + if (gPlayState) { + RandoRegionId currentRegion = Rando::Logic::GetRegionIdFromEntrance(gSaveContext.save.entrance); + for (int i = 0; i < (int)sRegionList.size(); i++) { + if (sRegionList[i].first == currentRegion) { + sRouteFromIndex = i; + break; + } + } + } + } + UIWidgets::Tooltip("Use current location"); + + // To dropdown + ImGui::Text("To:"); + ImGui::SameLine(); + if (ImGui::BeginCombo("##ToRegion", sRouteToIndex < (int)sRegionList.size() + ? sRegionList[sRouteToIndex].second.c_str() : "Select...")) { + for (int i = 0; i < (int)sRegionList.size(); i++) { + bool selected = (i == sRouteToIndex); + if (ImGui::Selectable(sRegionList[i].second.c_str(), selected)) { + sRouteToIndex = i; + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + + // Find Route button + if (UIWidgets::Button("Find Route", UIWidgets::ButtonOptions().Color(UIWidgets::Colors::Green))) { + if (sRouteFromIndex < (int)sRegionList.size() && sRouteToIndex < (int)sRegionList.size()) { + RandoRegionId from = sRegionList[sRouteFromIndex].first; + RandoRegionId to = sRegionList[sRouteToIndex].first; + auto route = FindRoute(from, to); + sRouteResult = FormatRoute(from, route, CVAR_ROUTE_DETAIL); + } + } + + // Route result + ImGui::Separator(); + ImGui::TextWrapped("%s", sRouteResult.c_str()); + + ImGui::EndChild(); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + + ImGui::End(); + + ImGui::PopStyleColor(4); + ImGui::PopStyleVar(1); +} + +void EntranceTrackerSettingsWindow::DrawElement() { + if (CVarGetInteger("gWindows.EntranceTracker", 0)) { + UIWidgets::WindowButton("Disable Entrance Tracker", "gWindows.EntranceTracker", BenGui::mEntranceTrackerWindow, + { .size = UIWidgets::Sizes::Inline, .color = UIWidgets::Colors::Red }); + } else { + UIWidgets::WindowButton("Enable Entrance Tracker", "gWindows.EntranceTracker", BenGui::mEntranceTrackerWindow, + { .size = UIWidgets::Sizes::Inline, .color = UIWidgets::Colors::Green }); + } + + if (ImGui::BeginTable("Settings Table", 2)) { + ImGui::TableSetupColumn("col1", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("col2", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableNextColumn(); + + ImGui::SeparatorText("Display Settings"); + + UIWidgets::CVarCheckbox("Show All Entrances (Spoilers)", CVAR_NAME_SPOILER_MODE, + UIWidgets::CheckboxOptions().DefaultValue(true)); + UIWidgets::Tooltip("When disabled, only shows entrances you have discovered by using them."); + + UIWidgets::CVarCheckbox("Detailed Route Display", CVAR_NAME_ROUTE_DETAIL); + UIWidgets::Tooltip("When enabled, shows step-by-step instructions instead of compact path."); + + UIWidgets::CVarCheckbox("Show Search", CVAR_NAME_SHOW_SEARCH, UIWidgets::CheckboxOptions().DefaultValue(true)); + + ImGui::TableNextColumn(); + ImGui::SeparatorText("Window Settings"); + + UIWidgets::CVarCombobox("Visibility", CVAR_NAME_VISIBILITY_MODE, &sVisibilityModes, + UIWidgets::ComboboxOptions() + .DefaultIndex(ENTRANCE_TRACKER_VISIBILITY_MODE_ALWAYS) + .ComponentAlignment(UIWidgets::ComponentAlignment::Right) + .LabelPosition(UIWidgets::LabelPosition::Far)); + + if (CVAR_VISIBILITY_MODE == ENTRANCE_TRACKER_VISIBILITY_MODE_BUTTON_TOGGLE || + CVAR_VISIBILITY_MODE == ENTRANCE_TRACKER_VISIBILITY_MODE_BUTTON_HOLD) { + UIWidgets::CVarBtnSelector("Button Combination:", CVAR_NAME_VISIBILITY_BTN, + UIWidgets::BtnSelectorOptions().DefaultValue(BTN_CUSTOM_MODIFIER1)); + } + + if (UIWidgets::CVarSliderFloat("Opacity", CVAR_NAME_TRACKER_OPACITY, + { + .format = "Opacity: %.1f", + .step = 0.10f, + .min = 0.0f, + .max = 1.0f, + .defaultValue = 0.5f, + .labelPosition = UIWidgets::LabelPosition::None, + })) { + trackerBG.w = CVAR_TRACKER_OPACITY; + } + + if (UIWidgets::CVarSliderFloat("Scale", CVAR_NAME_TRACKER_SCALE, + { + .format = "Scale: %.1f", + .step = 0.10f, + .min = 0.7f, + .max = 2.5f, + .defaultValue = 1.0f, + .labelPosition = UIWidgets::LabelPosition::None, + })) { + trackerScale = CVAR_TRACKER_SCALE; + } + + ImGui::EndTable(); + } +} + +void Init() { + BuildEntranceNameMap(); + BuildEntranceIndexMap(); + + trackerBG = { 0, 0, 0, CVAR_TRACKER_OPACITY }; + trackerScale = CVAR_TRACKER_SCALE; +} + +static RegisterShipInitFunc initFunc( + []() { + COND_HOOK(OnGameStateMainStart, CVAR_VISIBILITY_MODE >= ENTRANCE_TRACKER_VISIBILITY_MODE_BUTTON_TOGGLE, []() { + Input* input = CONTROLLER1(gGameState); + + if (CVAR_VISIBILITY_MODE == ENTRANCE_TRACKER_VISIBILITY_MODE_BUTTON_HOLD) { + if (CHECK_BTN_ALL(input->cur.button, CVAR_VISIBILITY_BTN)) { + sEntranceTrackerBtnState = true; + } else { + sEntranceTrackerBtnState = false; + } + } else { + if (CHECK_BTN_ALL(input->cur.button, CVAR_VISIBILITY_BTN) && + CHECK_BTN_ANY(input->press.button, CVAR_VISIBILITY_BTN)) { + sEntranceTrackerBtnState = !sEntranceTrackerBtnState; + } + } + }); + }, + { CVAR_NAME_VISIBILITY_MODE }); + +void OnFileLoad() { + if (!IS_RANDO) { + return; + } + + BuildEntranceIndexMap(); + BuildEntranceData(); + BuildRegionList(); + sRouteResult = ""; +} + +} // namespace EntranceTracker + +} // namespace Rando diff --git a/mm/2s2h/Rando/EntranceTracker/EntranceTracker.h b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.h new file mode 100644 index 0000000000..c1c76640f2 --- /dev/null +++ b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.h @@ -0,0 +1,44 @@ +#ifndef RANDO_ENTRANCE_TRACKER_H +#define RANDO_ENTRANCE_TRACKER_H + +#include "Rando/Rando.h" +#include + +namespace Rando { + +namespace EntranceTracker { + +void Init(); +void OnFileLoad(); + +// Get human-readable name for an entrance +std::string GetEntranceName(s32 entranceId); + +// Discovery tracking +void SetEntranceDiscovered(s32 entranceId); +bool IsEntranceDiscovered(s32 entranceId); + +class EntranceTrackerWindow : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + + void InitElement() override{}; + void DrawElement() override{}; + void Draw() override; + void UpdateElement() override{}; +}; + +class EntranceTrackerSettingsWindow : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + + void InitElement() override{}; + void DrawElement() override; + void UpdateElement() override{}; +}; + +} // namespace EntranceTracker + +} // namespace Rando + +#endif // RANDO_ENTRANCE_TRACKER_H diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp index f9816f6ba0..f448109f29 100644 --- a/mm/2s2h/Rando/Menu.cpp +++ b/mm/2s2h/Rando/Menu.cpp @@ -2,6 +2,7 @@ #include "Rando/Spoiler/Spoiler.h" #include "2s2h/BenGui/UIWidgets.hpp" #include "Rando/CheckTracker/CheckTracker.h" +#include "Rando/EntranceTracker/EntranceTracker.h" #include "Rando/MiscBehavior/ClockShuffle.h" #include "build.h" #include "2s2h/BenGui/BenMenu.h" @@ -70,6 +71,8 @@ bool isExcludedInitialized = false; namespace BenGui { extern std::shared_ptr mRandoCheckTrackerWindow; extern std::shared_ptr mRandoCheckTrackerSettingsWindow; +extern std::shared_ptr mEntranceTrackerWindow; +extern std::shared_ptr mEntranceTrackerSettingsWindow; extern std::shared_ptr mBenMenu; } // namespace BenGui @@ -1090,6 +1093,12 @@ void Rando::RegisterMenu() { mBenMenu->AddWidget(path, "Popout Settings", WIDGET_WINDOW_BUTTON) .CVar("gWindows.CheckTrackerSettings") .WindowName("Check Tracker Settings"); + + mBenMenu->AddSidebarEntry("Rando", "Entrance Tracker", 1); + path.sidebarName = "Entrance Tracker"; + mBenMenu->AddWidget(path, "Popout Settings", WIDGET_WINDOW_BUTTON) + .CVar("gWindows.EntranceTrackerSettings") + .WindowName("Entrance Tracker Settings"); } static RegisterMenuInitFunc initFunc(Rando::RegisterMenu); diff --git a/mm/2s2h/Rando/MiscBehavior/EntranceHooks.cpp b/mm/2s2h/Rando/MiscBehavior/EntranceHooks.cpp index 4bd97d8798..399dfe428c 100644 --- a/mm/2s2h/Rando/MiscBehavior/EntranceHooks.cpp +++ b/mm/2s2h/Rando/MiscBehavior/EntranceHooks.cpp @@ -1,6 +1,7 @@ #include "2s2h/GameInteractor/GameInteractor.h" #include "2s2h/ShipInit.hpp" #include "2s2h/Rando/Logic/EntranceShuffle.h" +#include "2s2h/Rando/EntranceTracker/EntranceTracker.h" extern "C" { #include "functions.h" @@ -22,6 +23,9 @@ void OnPlayDestroy() { s32 originalEntrance = gSaveContext.save.entrance; s32 shuffledEntrance = GetShuffledEntrance(originalEntrance); + // Record discovered entrance for the tracker + EntranceTracker::SetEntranceDiscovered(shuffledEntrance); + if (shuffledEntrance != originalEntrance) { gSaveContext.save.entrance = shuffledEntrance; } diff --git a/mm/2s2h/Rando/Rando.cpp b/mm/2s2h/Rando/Rando.cpp index 09cd90cbc8..49314e4827 100644 --- a/mm/2s2h/Rando/Rando.cpp +++ b/mm/2s2h/Rando/Rando.cpp @@ -5,6 +5,7 @@ #include "Rando/MiscBehavior/ClockShuffle.h" #include "Rando/Spoiler/Spoiler.h" #include "Rando/CheckTracker/CheckTracker.h" +#include "Rando/EntranceTracker/EntranceTracker.h" #include "2s2h/ShipInit.hpp" #include #include @@ -14,6 +15,7 @@ void OnSaveLoadHandler(s16 fileNum) { Rando::MiscBehavior::OnFileLoad(); Rando::ActorBehavior::OnFileLoad(); Rando::CheckTracker::OnFileLoad(); + Rando::EntranceTracker::OnFileLoad(); Rando::ClockShuffle::OnFileLoad(); // Re-initalizes enhancements that are effected by the save being rando or not @@ -26,6 +28,7 @@ void Rando::Init() { Rando::MiscBehavior::Init(); Rando::ActorBehavior::Init(); Rando::CheckTracker::Init(); + Rando::EntranceTracker::Init(); Ship::Context::GetInstance()->GetFileDropMgr()->RegisterDropHandler(Rando::Spoiler::HandleFileDropped); GameInteractor::Instance->RegisterGameHook(OnSaveLoadHandler); diff --git a/mm/include/z64save.h b/mm/include/z64save.h index d5e265e778..cd19e5a647 100644 --- a/mm/include/z64save.h +++ b/mm/include/z64save.h @@ -388,6 +388,7 @@ typedef struct RandoSaveInfo { u16 randoStartingItems[256]; // Max 256 starting items, using u16 in case we add more than 255 items s8 foundDungeonKeys[9]; // Tracks the number of dungeon keys found, opposed to the number of keys in the inventory u16 foundTriforcePieces; + u8 discoveredEntrances[32]; // Bitfield for 256 discovered entrance flags (for entrance tracker) } RandoSaveInfo; // These are values added by 2S2H that we need to be persisted to the save file From fbd1e414a033dd424865a19ad6ae242220aee4b9 Mon Sep 17 00:00:00 2001 From: Phillyjawn Date: Sun, 25 Jan 2026 16:09:14 -0700 Subject: [PATCH 3/8] Its a little better --- .../Rando/EntranceTracker/EntranceTracker.cpp | 132 +++++++++++++++--- 1 file changed, 110 insertions(+), 22 deletions(-) diff --git a/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp index 627528e654..0b184971ab 100644 --- a/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp +++ b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp @@ -195,6 +195,36 @@ bool Rando::EntranceTracker::IsEntranceDiscovered(s32 entranceId) { return (gSaveContext.save.shipSaveInfo.rando.discoveredEntrances[byteIndex] & (1 << bitIndex)) != 0; } +// List of generic region names that need scene context +static const std::set sGenericRegionNames = { + "Entrance", "Main Room", "Maze Room", "Upper", "Lower", "Back", "Front", + "Boss Room", "Passage", "Main Room Upper", "Main Room Lower", + "Night 1 Boss", "Night 2 Boss", "Higher", "Before Great Bay Coast" +}; + +// Get a display name for a region, using scene name as fallback or prefix for generic names +static std::string GetRegionDisplayName(RandoRegionId regionId) { + if (Rando::Logic::Regions.count(regionId) == 0) { + return ""; + } + + const auto& region = Rando::Logic::Regions.at(regionId); + std::string name = region.name; + std::string sceneName = Ship_GetSceneName(region.sceneId); + + // If name is empty, use scene name + if (name.empty()) { + return sceneName; + } + + // If name is generic, prefix with scene name + if (sGenericRegionNames.count(name) > 0) { + return sceneName + " - " + name; + } + + return name; +} + // Build the entrance name mapping with better names for specific entrances static void BuildEntranceNameMap() { sEntranceNames.clear(); @@ -237,12 +267,16 @@ static void BuildEntranceNameMap() { // Get the source scene for an entrance (where you are when you use it) static SceneId GetSourceSceneForEntrance(s32 entrance) { - // Use the region data to find where the entrance originates - RandoRegionId regionId = Rando::Logic::GetRegionIdFromEntrance(entrance); - if (regionId != RR_MAX && Rando::Logic::Regions.count(regionId) > 0) { - return Rando::Logic::Regions[regionId].sceneId; + // Find which region has this entrance as an EXIT (the source region) + // This is the region you're IN when you use this entrance + for (const auto& [regionId, region] : Rando::Logic::Regions) { + for (const auto& [exitId, regionExit] : region.exits) { + if (exitId == entrance) { + return region.sceneId; + } + } } - // Fallback: the entrance scene itself + // Fallback: extract scene from entrance ID (destination scene) return GetSceneFromEntrance(entrance); } @@ -298,8 +332,9 @@ static void BuildRegionList() { sRegionList.clear(); for (const auto& [regionId, region] : Rando::Logic::Regions) { - if (region.name != nullptr && strlen(region.name) > 0) { - sRegionList.push_back({ regionId, region.name }); + std::string displayName = GetRegionDisplayName(regionId); + if (!displayName.empty()) { + sRegionList.push_back({ regionId, displayName }); } } @@ -407,36 +442,66 @@ static std::string FormatRoute(RandoRegionId from, const std::vector& } std::string result; + std::string fromName = GetRegionDisplayName(from); + + // Track last displayed scene to avoid redundant same-scene entries + SceneId lastScene = SCENE_MAX; + if (Rando::Logic::Regions.count(from) > 0) { + lastScene = Rando::Logic::Regions.at(from).sceneId; + } if (detailed) { // Detailed format - if (Rando::Logic::Regions.count(from) > 0) { - result = "From " + std::string(Rando::Logic::Regions[from].name); + if (!fromName.empty()) { + result = "From " + fromName; } for (size_t i = 0; i < route.size(); i++) { const auto& step = route[i]; - if (step.entranceUsed != -1) { + std::string stepName = GetRegionDisplayName(step.region); + + // Get scene for this step + SceneId stepScene = SCENE_MAX; + if (Rando::Logic::Regions.count(step.region) > 0) { + stepScene = Rando::Logic::Regions.at(step.region).sceneId; + } + + // Show entrance used if it's a real exit (not a same-scene connection) + if (step.entranceUsed != -1 && !step.description.empty()) { result += "\n -> Take '" + step.description + "'"; } - if (Rando::Logic::Regions.count(step.region) > 0) { - result += "\n => " + std::string(Rando::Logic::Regions[step.region].name); + + // Only show region if it's a different scene or the final destination + if (!stepName.empty() && (stepScene != lastScene || i == route.size() - 1)) { + result += "\n => " + stepName; + lastScene = stepScene; } } } else { - // Compact format - if (Rando::Logic::Regions.count(from) > 0) { - result = Rando::Logic::Regions[from].name; + // Compact format - skip consecutive same-scene regions + if (!fromName.empty()) { + result = fromName; } - for (const auto& step : route) { + for (size_t i = 0; i < route.size(); i++) { + const auto& step = route[i]; + std::string stepName = GetRegionDisplayName(step.region); + + // Get scene for this step + SceneId stepScene = SCENE_MAX; if (Rando::Logic::Regions.count(step.region) > 0) { - result += " -> " + std::string(Rando::Logic::Regions[step.region].name); + stepScene = Rando::Logic::Regions.at(step.region).sceneId; + } + + // Only show if different scene or final destination + if (!stepName.empty() && (stepScene != lastScene || i == route.size() - 1)) { + result += " -> " + stepName; + lastScene = stepScene; } } } - return result; + return result.empty() ? "No route found" : result; } namespace Rando { @@ -587,6 +652,7 @@ void EntranceTrackerWindow::Draw() { if (ImGui::BeginCombo("##FromRegion", sRouteFromIndex < (int)sRegionList.size() ? sRegionList[sRouteFromIndex].second.c_str() : "Select...")) { for (int i = 0; i < (int)sRegionList.size(); i++) { + ImGui::PushID(i); bool selected = (i == sRouteFromIndex); if (ImGui::Selectable(sRegionList[i].second.c_str(), selected)) { sRouteFromIndex = i; @@ -594,6 +660,7 @@ void EntranceTrackerWindow::Draw() { if (selected) { ImGui::SetItemDefaultFocus(); } + ImGui::PopID(); } ImGui::EndCombo(); } @@ -603,10 +670,29 @@ void EntranceTrackerWindow::Draw() { if (UIWidgets::Button("Current", UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) { if (gPlayState) { RandoRegionId currentRegion = Rando::Logic::GetRegionIdFromEntrance(gSaveContext.save.entrance); - for (int i = 0; i < (int)sRegionList.size(); i++) { - if (sRegionList[i].first == currentRegion) { - sRouteFromIndex = i; - break; + bool found = false; + + // First try: find exact region match + if (currentRegion != RR_MAX) { + for (int i = 0; i < (int)sRegionList.size(); i++) { + if (sRegionList[i].first == currentRegion) { + sRouteFromIndex = i; + found = true; + break; + } + } + } + + // Fallback: find a region matching the current scene + if (!found) { + SceneId currentScene = static_cast(gPlayState->sceneId); + for (int i = 0; i < (int)sRegionList.size(); i++) { + RandoRegionId regionId = sRegionList[i].first; + if (Rando::Logic::Regions.count(regionId) > 0 && + Rando::Logic::Regions.at(regionId).sceneId == currentScene) { + sRouteFromIndex = i; + break; + } } } } @@ -619,6 +705,7 @@ void EntranceTrackerWindow::Draw() { if (ImGui::BeginCombo("##ToRegion", sRouteToIndex < (int)sRegionList.size() ? sRegionList[sRouteToIndex].second.c_str() : "Select...")) { for (int i = 0; i < (int)sRegionList.size(); i++) { + ImGui::PushID(i); bool selected = (i == sRouteToIndex); if (ImGui::Selectable(sRegionList[i].second.c_str(), selected)) { sRouteToIndex = i; @@ -626,6 +713,7 @@ void EntranceTrackerWindow::Draw() { if (selected) { ImGui::SetItemDefaultFocus(); } + ImGui::PopID(); } ImGui::EndCombo(); } From 54f8035f4a4f73f6d72fdb4e04bbcc2a73a63210 Mon Sep 17 00:00:00 2001 From: Phillyjawn Date: Sun, 25 Jan 2026 18:58:04 -0700 Subject: [PATCH 4/8] Organize Tracker by source instead of destination --- .../Rando/EntranceTracker/EntranceTracker.cpp | 347 +++++++++++++----- 1 file changed, 265 insertions(+), 82 deletions(-) diff --git a/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp index 0b184971ab..aac1f0f5d8 100644 --- a/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp +++ b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp @@ -90,6 +90,8 @@ static int sRouteFromIndex = 0; static int sRouteToIndex = 0; static std::string sRouteResult = ""; static std::vector> sRegionList; +static ImGuiTextFilter sRouteFromFilter; +static ImGuiTextFilter sRouteToFilter; // Entrance name mapping - built from scene names with spawn-specific suffixes static std::map sEntranceNames; @@ -230,11 +232,11 @@ static void BuildEntranceNameMap() { sEntranceNames.clear(); // Add custom names for specific entrances (more descriptive than just scene name) - sEntranceNames[ENTRANCE(STOCK_POT_INN, 0)] = "Stock Pot Inn (Main)"; - sEntranceNames[ENTRANCE(STOCK_POT_INN, 1)] = "Stock Pot Inn (Upstairs)"; - sEntranceNames[ENTRANCE(CURIOSITY_SHOP, 0)] = "Curiosity Shop (Front)"; - sEntranceNames[ENTRANCE(CURIOSITY_SHOP, 1)] = "Curiosity Shop (Back)"; - sEntranceNames[ENTRANCE(RANCH_HOUSE, 0)] = "Romani Ranch House (Main)"; + sEntranceNames[ENTRANCE(STOCK_POT_INN, 0)] = "Stock Pot Inn (Main Entrance)"; + sEntranceNames[ENTRANCE(STOCK_POT_INN, 1)] = "Stock Pot Inn (Upper Floor)"; + sEntranceNames[ENTRANCE(CURIOSITY_SHOP, 0)] = "Curiosity Shop (Front Entrance)"; + sEntranceNames[ENTRANCE(CURIOSITY_SHOP, 1)] = "Curiosity Shop (Back Entrance)"; + sEntranceNames[ENTRANCE(RANCH_HOUSE, 0)] = "Romani Ranch House (Main Building)"; sEntranceNames[ENTRANCE(RANCH_HOUSE, 1)] = "Romani Ranch House (Barn)"; sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 0)] = "Zora Hall (Evan's Room)"; sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 1)] = "Zora Hall (Lulu's Room)"; @@ -246,38 +248,135 @@ static void BuildEntranceNameMap() { sEntranceNames[ENTRANCE(FAIRY_FOUNTAIN, 2)] = "Great Fairy (Snowhead)"; sEntranceNames[ENTRANCE(FAIRY_FOUNTAIN, 3)] = "Great Fairy (Great Bay)"; sEntranceNames[ENTRANCE(FAIRY_FOUNTAIN, 4)] = "Great Fairy (Ikana)"; - sEntranceNames[ENTRANCE(CLOCK_TOWER_INTERIOR, 1)] = "Clock Tower Interior"; + sEntranceNames[ENTRANCE(CLOCK_TOWER_INTERIOR, 0)] = "Clock Tower Interior (Main)"; + sEntranceNames[ENTRANCE(CLOCK_TOWER_INTERIOR, 1)] = "Clock Tower Interior (Upper)"; + + // Temple entrances - be specific about which entrance/exit + sEntranceNames[ENTRANCE(GREAT_BAY_TEMPLE, 0)] = "Great Bay Temple (Main Entrance)"; + sEntranceNames[ENTRANCE(GREAT_BAY_TEMPLE, 1)] = "Great Bay Temple (Main Exit)"; + sEntranceNames[ENTRANCE(WOODFALL_TEMPLE, 0)] = "Woodfall Temple (Main Entrance)"; + sEntranceNames[ENTRANCE(WOODFALL_TEMPLE, 1)] = "Woodfall Temple (Main Exit)"; + sEntranceNames[ENTRANCE(SNOWHEAD_TEMPLE, 0)] = "Snowhead Temple (Main Entrance)"; + sEntranceNames[ENTRANCE(SNOWHEAD_TEMPLE, 1)] = "Snowhead Temple (Main Exit)"; + sEntranceNames[ENTRANCE(IKANA_CASTLE, 0)] = "Ikana Castle (Main Entrance)"; + sEntranceNames[ENTRANCE(IKANA_CASTLE, 1)] = "Ikana Castle (Main Exit)"; + sEntranceNames[ENTRANCE(STONE_TOWER_TEMPLE, 0)] = "Stone Tower Temple (Main Entrance)"; + sEntranceNames[ENTRANCE(STONE_TOWER_TEMPLE, 1)] = "Stone Tower Temple (Main Exit)"; // Overworld connections - use more descriptive names + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 0)] = "East Clock Town (to Termina Field)"; + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 1)] = "East Clock Town (Chest Shop)"; + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 2)] = "East Clock Town (Astral Observatory)"; sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 3)] = "East Clock Town (from South)"; + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 4)] = "East Clock Town (Treasure Chest Shop)"; + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 5)] = "East Clock Town (Great Fairy)"; + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 6)] = "East Clock Town (Honey and Darlings)"; + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 7)] = "East Clock Town (Mayor's Residence)"; + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 8)] = "East Clock Town (Shooting Gallery)"; + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 9)] = "East Clock Town (Stock Pot Inn)"; + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 10)] = "East Clock Town (Stock Pot Inn Upstairs)"; + sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 11)] = "East Clock Town (Milk Bar)"; + + sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 0)] = "South Clock Town (Clock Tower Interior)"; sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 2)] = "South Clock Town (from East)"; - sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 2)] = "West Clock Town (from South)"; sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 3)] = "South Clock Town (from West)"; - sEntranceNames[ENTRANCE(NORTH_CLOCK_TOWN, 2)] = "North Clock Town (from South)"; sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 4)] = "South Clock Town (from North)"; - sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 1)] = "West Clock Town (Swordsman)"; sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 5)] = "South Clock Town (Swordsman)"; - sEntranceNames[ENTRANCE(LAUNDRY_POOL, 0)] = "Laundry Pool"; sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 6)] = "South Clock Town (Laundry)"; - sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 1)] = "East Clock Town (Chest Shop)"; sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 7)] = "South Clock Town (Chest Shop)"; - sEntranceNames[ENTRANCE(NORTH_CLOCK_TOWN, 1)] = "North Clock Town (Fairy)"; - sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 5)] = "East Clock Town (Fairy)"; + + sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 0)] = "West Clock Town (to Termina Field)"; + sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 1)] = "West Clock Town (to South Lower)"; + sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 2)] = "West Clock Town (to South Upper)"; + sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 3)] = "West Clock Town (Swordsman)"; + sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 4)] = "West Clock Town (Curiosity Shop)"; + sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 5)] = "West Clock Town (Trading Post)"; + sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 6)] = "West Clock Town (Bomb Shop)"; + sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 7)] = "West Clock Town (Post Office)"; + sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 8)] = "West Clock Town (Lottery Shop)"; + + sEntranceNames[ENTRANCE(NORTH_CLOCK_TOWN, 1)] = "North Clock Town (Great Fairy)"; + sEntranceNames[ENTRANCE(NORTH_CLOCK_TOWN, 2)] = "North Clock Town (from South)"; + sEntranceNames[ENTRANCE(NORTH_CLOCK_TOWN, 3)] = "North Clock Town (Great Fairy Fountain)"; + + sEntranceNames[ENTRANCE(LAUNDRY_POOL, 0)] = "Laundry Pool (Entrance)"; + sEntranceNames[ENTRANCE(LAUNDRY_POOL, 1)] = "Laundry Pool (Back Alley)"; + + // Additional interior shops/buildings from Central.cpp + sEntranceNames[ENTRANCE(SWORDMANS_SCHOOL, 0)] = "Swordsman's School"; + sEntranceNames[ENTRANCE(TREASURE_CHEST_SHOP, 0)] = "Treasure Chest Shop"; + sEntranceNames[ENTRANCE(TOWN_SHOOTING_GALLERY, 0)] = "Shooting Gallery"; + sEntranceNames[ENTRANCE(DEKU_SCRUB_PLAYGROUND, 0)] = "Deku Playground"; + sEntranceNames[ENTRANCE(TRADING_POST, 0)] = "Trading Post"; + sEntranceNames[ENTRANCE(POST_OFFICE, 0)] = "Post Office"; + sEntranceNames[ENTRANCE(MILK_BAR, 0)] = "Milk Bar"; + sEntranceNames[ENTRANCE(LOTTERY_SHOP, 0)] = "Lottery Shop"; + sEntranceNames[ENTRANCE(MAYORS_RESIDENCE, 0)] = "Mayor's Residence"; + sEntranceNames[ENTRANCE(BOMB_SHOP, 0)] = "Bomb Shop"; + sEntranceNames[ENTRANCE(HONEY_AND_DARLINGS_SHOP, 0)] = "Honey and Darling's Shop"; + sEntranceNames[ENTRANCE(ASTRAL_OBSERVATORY, 0)] = "Astral Observatory"; + + // Termina Field & Roads + sEntranceNames[ENTRANCE(TOURIST_INFORMATION, 0)] = "Tourist Information"; + sEntranceNames[ENTRANCE(SWAMP_SHOOTING_GALLERY, 0)] = "Swamp Shooting Gallery"; + sEntranceNames[ENTRANCE(MAGIC_HAGS_POTION_SHOP, 0)] = "Magic Hag's Potion Shop"; + + // Milk Road + sEntranceNames[ENTRANCE(CUCCO_SHACK, 0)] = "Cucco Shack"; + sEntranceNames[ENTRANCE(DOGGY_RACETRACK, 0)] = "Doggy Racetrack"; + sEntranceNames[ENTRANCE(RANCH_HOUSE, 0)] = "Ranch House (Main)"; + sEntranceNames[ENTRANCE(RANCH_HOUSE, 1)] = "Ranch House (Barn)"; + + // Great Bay + sEntranceNames[ENTRANCE(FISHERMANS_HUT, 0)] = "Fisherman's Hut"; + sEntranceNames[ENTRANCE(MARINE_RESEARCH_LAB, 0)] = "Marine Research Lab"; + sEntranceNames[ENTRANCE(OCEANSIDE_SPIDER_HOUSE, 0)] = "Oceanside Spider House"; + sEntranceNames[ENTRANCE(PINNACLE_ROCK, 0)] = "Pinnacle Rock"; + + // Mountain Village + sEntranceNames[ENTRANCE(GORON_SHOP, 0)] = "Goron Shop"; + + // Swamp + sEntranceNames[ENTRANCE(SWAMP_SPIDER_HOUSE, 0)] = "Swamp Spider House"; + + // Stone Tower + sEntranceNames[ENTRANCE(STONE_TOWER, 0)] = "Stone Tower"; + sEntranceNames[ENTRANCE(STONE_TOWER, 1)] = "Stone Tower (Inverted Entrance)"; + sEntranceNames[ENTRANCE(STONE_TOWER, 2)] = "Stone Tower (to Temple)"; + + // Zora Hall + sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 0)] = "Zora Hall (Evan's Room)"; + sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 1)] = "Zora Hall (Lulu's Room)"; + sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 2)] = "Zora Hall (Japas' Room)"; + sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 3)] = "Zora Hall (Tijo's Room)"; + sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 5)] = "Zora Hall (Shop)"; + + // Ghost Hut entrances (from Ikana Canyon) + sEntranceNames[ENTRANCE(GHOST_HUT, 0)] = "Ghost Hut"; + sEntranceNames[ENTRANCE(GHOST_HUT, 1)] = "Ghost Hut (Alternate)"; + sEntranceNames[ENTRANCE(GHOST_HUT, 2)] = "Ghost Hut (Another Route)"; + + // Ikana + sEntranceNames[ENTRANCE(MUSIC_BOX_HOUSE, 0)] = "Music Box House"; } -// Get the source scene for an entrance (where you are when you use it) -static SceneId GetSourceSceneForEntrance(s32 entrance) { - // Find which region has this entrance as an EXIT (the source region) - // This is the region you're IN when you use this entrance +// Get the source scene for an entrance pair (where you are when you use the exit) +static SceneId GetSourceSceneForEntrancePair(s32 returnEntrance) { + // returnEntrance is the entrance ID that you return to when exiting a destination + // We need to find which region has this as a returnEntrance + // The returnEntrance field is in the value of the exits map + for (const auto& [regionId, region] : Rando::Logic::Regions) { - for (const auto& [exitId, regionExit] : region.exits) { - if (exitId == entrance) { + for (const auto& [_, regionExit] : region.exits) { + if (regionExit.returnEntrance == returnEntrance) { + // Found it! This region has this return entrance return region.sceneId; } } } - // Fallback: extract scene from entrance ID (destination scene) - return GetSceneFromEntrance(entrance); + + // Fallback: try to extract scene from the return entrance ID itself + return GetSceneFromEntrance(returnEntrance); } // Build entrance data grouped by source area @@ -302,9 +401,10 @@ static void BuildEntranceData() { for (const auto& pair : entrancePairs) { s32 original = pair.entrance; s32 shuffled = Rando::EntranceShuffle::GetShuffledEntrance(original); + s32 exitId = pair.exit; // This is the exit from the SOURCE region - // Get source scene for grouping - SceneId sourceScene = GetSourceSceneForEntrance(original); + // Get source scene using the exit ID from the source region + SceneId sourceScene = GetSourceSceneForEntrancePair(exitId); EntranceConnection conn; conn.originalEntrance = original; @@ -327,11 +427,39 @@ static void BuildEntranceData() { [](SceneId a, SceneId b) { return betterSceneIndex[a] < betterSceneIndex[b]; }); } -// Build region list for route finder dropdowns +// Build region list for route finder dropdowns - includes shuffled entrance sources and destinations static void BuildRegionList() { sRegionList.clear(); + std::set shuffleRegions; - for (const auto& [regionId, region] : Rando::Logic::Regions) { + // Collect all regions involved in entrance shuffling (both sources and destinations) + std::vector pools = { + Rando::EntranceShuffle::POOL_INTERIOR, + Rando::EntranceShuffle::POOL_DUNGEON, + Rando::EntranceShuffle::POOL_OVERWORLD + }; + + for (auto poolType : pools) { + auto entrancePairs = Rando::EntranceShuffle::GetEntrancePool(poolType); + for (const auto& pair : entrancePairs) { + // Include destination region + s32 destination = pair.entrance; + RandoRegionId destRegion = Rando::Logic::GetRegionIdFromEntrance(destination); + if (destRegion != RR_MAX) { + shuffleRegions.insert(destRegion); + } + + // Include source region (derived from exit entrance) + s32 source = pair.exit; + RandoRegionId srcRandoRegion = Rando::Logic::GetRegionIdFromEntrance(source); + if (srcRandoRegion != RR_MAX) { + shuffleRegions.insert(srcRandoRegion); + } + } + } + + // Build list from all shuffle regions + for (RandoRegionId regionId : shuffleRegions) { std::string displayName = GetRegionDisplayName(regionId); if (!displayName.empty()) { sRegionList.push_back({ regionId, displayName }); @@ -390,7 +518,8 @@ static std::vector FindRoute(RandoRegionId from, RandoRegionId to) { RouteStep step; step.region = to; step.entranceUsed = exitId; - step.description = Rando::EntranceTracker::GetEntranceName(actualExit); + // Use the source entrance name (what you use from current location), not the destination + step.description = Rando::EntranceTracker::GetEntranceName(exitId); result.push_back(step); return result; } @@ -401,7 +530,8 @@ static std::vector FindRoute(RandoRegionId from, RandoRegionId to) { RouteStep step; step.region = targetRegion; step.entranceUsed = exitId; - step.description = Rando::EntranceTracker::GetEntranceName(actualExit); + // Use the source entrance name (what you use from current location), not the destination + step.description = Rando::EntranceTracker::GetEntranceName(exitId); newPath.push_back(step); queue.push(newPath); } @@ -444,14 +574,8 @@ static std::string FormatRoute(RandoRegionId from, const std::vector& std::string result; std::string fromName = GetRegionDisplayName(from); - // Track last displayed scene to avoid redundant same-scene entries - SceneId lastScene = SCENE_MAX; - if (Rando::Logic::Regions.count(from) > 0) { - lastScene = Rando::Logic::Regions.at(from).sceneId; - } - if (detailed) { - // Detailed format + // Detailed format - show all steps with entrance names if (!fromName.empty()) { result = "From " + fromName; } @@ -460,25 +584,18 @@ static std::string FormatRoute(RandoRegionId from, const std::vector& const auto& step = route[i]; std::string stepName = GetRegionDisplayName(step.region); - // Get scene for this step - SceneId stepScene = SCENE_MAX; - if (Rando::Logic::Regions.count(step.region) > 0) { - stepScene = Rando::Logic::Regions.at(step.region).sceneId; - } - // Show entrance used if it's a real exit (not a same-scene connection) if (step.entranceUsed != -1 && !step.description.empty()) { result += "\n -> Take '" + step.description + "'"; } - // Only show region if it's a different scene or the final destination - if (!stepName.empty() && (stepScene != lastScene || i == route.size() - 1)) { + // Always show the destination region + if (!stepName.empty()) { result += "\n => " + stepName; - lastScene = stepScene; } } } else { - // Compact format - skip consecutive same-scene regions + // Compact format - prioritize showing entrance names over region names if (!fromName.empty()) { result = fromName; } @@ -487,16 +604,13 @@ static std::string FormatRoute(RandoRegionId from, const std::vector& const auto& step = route[i]; std::string stepName = GetRegionDisplayName(step.region); - // Get scene for this step - SceneId stepScene = SCENE_MAX; - if (Rando::Logic::Regions.count(step.region) > 0) { - stepScene = Rando::Logic::Regions.at(step.region).sceneId; - } - - // Only show if different scene or final destination - if (!stepName.empty() && (stepScene != lastScene || i == route.size() - 1)) { + // Prefer showing entrance names when available + if (step.entranceUsed != -1 && !step.description.empty()) { + // Show the entrance you take to get there + result += " -> " + step.description; + } else if (!stepName.empty()) { + // Fallback to region name for same-scene connections result += " -> " + stepName; - lastScene = stepScene; } } } @@ -558,27 +672,27 @@ void EntranceTrackerWindow::Draw() { return; } - // Search bar - if (CVAR_SHOW_SEARCH) { - UIWidgets::PushStyleInput(); - sEntranceTrackerFilter.Draw("##filter", ImGui::GetContentRegionAvail().x - 40.0f); - UIWidgets::PopStyleInput(); - - ImGui::SameLine(); - if (!sEntranceTrackerFilter.IsActive()) { - ImGui::Text("Search"); - } else { - if (UIWidgets::Button(ICON_FA_TIMES, UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) { - sEntranceTrackerFilter.Clear(); - } - } - } - // Tab bar for Entrances and Route Finder if (ImGui::BeginTabBar("EntranceTrackerTabs")) { if (ImGui::BeginTabItem("Entrances")) { ImGui::BeginChild("EntranceList"); + // Search bar for entrances tab + if (CVAR_SHOW_SEARCH) { + UIWidgets::PushStyleInput(); + sEntranceTrackerFilter.Draw("##filter", ImGui::GetContentRegionAvail().x - 40.0f); + UIWidgets::PopStyleInput(); + + ImGui::SameLine(); + if (!sEntranceTrackerFilter.IsActive()) { + ImGui::Text("Search"); + } else { + if (UIWidgets::Button(ICON_FA_TIMES, UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) { + sEntranceTrackerFilter.Clear(); + } + } + } + bool spoilerMode = CVAR_SPOILER_MODE; for (SceneId sceneId : sSortedSceneIds) { @@ -618,13 +732,7 @@ void EntranceTrackerWindow::Draw() { for (const auto* conn : filtered) { std::string line = conn->originalName + " -> " + conn->shuffledName; - - // Highlight if this entrance was shuffled (different from original) - if (conn->originalEntrance != conn->shuffledEntrance) { - ImGui::TextColored(UIWidgets::ColorValues.at(UIWidgets::Colors::Yellow), "%s", line.c_str()); - } else { - ImGui::Text("%s", line.c_str()); - } + ImGui::Text("%s", line.c_str()); } ImGui::Unindent(20.0f); @@ -646,12 +754,28 @@ void EntranceTrackerWindow::Draw() { BuildRegionList(); } - // From dropdown + // From section with search ImGui::Text("From:"); ImGui::SameLine(); + + // Calculate available width for combo + float availWidth = ImGui::GetContentRegionAvail().x - 100.0f; + if (ImGui::BeginCombo("##FromRegion", sRouteFromIndex < (int)sRegionList.size() - ? sRegionList[sRouteFromIndex].second.c_str() : "Select...")) { + ? sRegionList[sRouteFromIndex].second.c_str() : "Select...", + ImGuiComboFlags_HeightLarge)) { + + // Search filter for From + sRouteFromFilter.Draw("##FromSearch", availWidth); + ImGui::Separator(); + + int visibleCount = 0; for (int i = 0; i < (int)sRegionList.size(); i++) { + if (!sRouteFromFilter.PassFilter(sRegionList[i].second.c_str())) { + continue; + } + + visibleCount++; ImGui::PushID(i); bool selected = (i == sRouteFromIndex); if (ImGui::Selectable(sRegionList[i].second.c_str(), selected)) { @@ -662,6 +786,11 @@ void EntranceTrackerWindow::Draw() { } ImGui::PopID(); } + + if (visibleCount == 0) { + ImGui::TextDisabled("No matches"); + } + ImGui::EndCombo(); } @@ -699,12 +828,25 @@ void EntranceTrackerWindow::Draw() { } UIWidgets::Tooltip("Use current location"); - // To dropdown + // To section with search ImGui::Text("To:"); ImGui::SameLine(); + if (ImGui::BeginCombo("##ToRegion", sRouteToIndex < (int)sRegionList.size() - ? sRegionList[sRouteToIndex].second.c_str() : "Select...")) { + ? sRegionList[sRouteToIndex].second.c_str() : "Select...", + ImGuiComboFlags_HeightLarge)) { + + // Search filter for To + sRouteToFilter.Draw("##ToSearch", availWidth); + ImGui::Separator(); + + int visibleCount = 0; for (int i = 0; i < (int)sRegionList.size(); i++) { + if (!sRouteToFilter.PassFilter(sRegionList[i].second.c_str())) { + continue; + } + + visibleCount++; ImGui::PushID(i); bool selected = (i == sRouteToIndex); if (ImGui::Selectable(sRegionList[i].second.c_str(), selected)) { @@ -715,16 +857,57 @@ void EntranceTrackerWindow::Draw() { } ImGui::PopID(); } + + if (visibleCount == 0) { + ImGui::TextDisabled("No matches"); + } + ImGui::EndCombo(); } + // Swap From/To button + ImGui::SameLine(); + if (UIWidgets::Button(ICON_FA_EXCHANGE, UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) { + std::swap(sRouteFromIndex, sRouteToIndex); + } + UIWidgets::Tooltip("Swap From and To locations"); + // Find Route button if (UIWidgets::Button("Find Route", UIWidgets::ButtonOptions().Color(UIWidgets::Colors::Green))) { if (sRouteFromIndex < (int)sRegionList.size() && sRouteToIndex < (int)sRegionList.size()) { RandoRegionId from = sRegionList[sRouteFromIndex].first; RandoRegionId to = sRegionList[sRouteToIndex].first; - auto route = FindRoute(from, to); - sRouteResult = FormatRoute(from, route, CVAR_ROUTE_DETAIL); + + // Check if spoilers are off and destination is undiscovered + bool spoilerMode = CVAR_SPOILER_MODE; + bool destinationDiscovered = true; + + if (!spoilerMode && Rando::Logic::Regions.count(to) > 0) { + // Check if any entrance to this region has been discovered + SceneId destScene = Rando::Logic::Regions.at(to).sceneId; + destinationDiscovered = false; + + // Iterate through all regions to find entrances leading to the destination + for (const auto& [regionId, region] : Rando::Logic::Regions) { + for (const auto& [exitId, regionExit] : region.exits) { + if (!regionExit.returnEntrance || regionExit.returnEntrance == ONE_WAY_EXIT) continue; + if (GetSceneFromEntrance(regionExit.returnEntrance) == destScene) { + if (IsEntranceDiscovered(regionExit.returnEntrance)) { + destinationDiscovered = true; + break; + } + } + } + if (destinationDiscovered) break; + } + } + + if (!destinationDiscovered) { + sRouteResult = "No route could be found"; + } else { + auto route = FindRoute(from, to); + sRouteResult = FormatRoute(from, route, CVAR_ROUTE_DETAIL); + } } } From 40d217c19aa9b86fb299faee077e52aea0bcd3d6 Mon Sep 17 00:00:00 2001 From: Phillyjawn Date: Sun, 25 Jan 2026 20:30:05 -0700 Subject: [PATCH 5/8] Remove non-pool logic - it was breaking everything --- .../Rando/EntranceTracker/EntranceTracker.cpp | 187 +++++++++--------- 1 file changed, 88 insertions(+), 99 deletions(-) diff --git a/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp index aac1f0f5d8..d110790cbf 100644 --- a/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp +++ b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp @@ -57,7 +57,6 @@ static bool sEntranceTrackerBtnState = false; #define CVAR_NAME_TRACKER_OPACITY "gRando.EntranceTracker.Opacity" #define CVAR_NAME_TRACKER_SCALE "gRando.EntranceTracker.Scale" #define CVAR_NAME_SPOILER_MODE "gRando.EntranceTracker.SpoilerMode" -#define CVAR_NAME_ROUTE_DETAIL "gRando.EntranceTracker.RouteDetailMode" #define CVAR_NAME_SHOW_SEARCH "gRando.EntranceTracker.ShowSearch" #define CVAR_SHOW_ENTRANCE_TRACKER CVarGetInteger(CVAR_NAME_SHOW_ENTRANCE_TRACKER, 0) @@ -66,7 +65,6 @@ static bool sEntranceTrackerBtnState = false; #define CVAR_TRACKER_OPACITY CVarGetFloat(CVAR_NAME_TRACKER_OPACITY, 0.5f) #define CVAR_TRACKER_SCALE CVarGetFloat(CVAR_NAME_TRACKER_SCALE, 1.0f) #define CVAR_SPOILER_MODE CVarGetInteger(CVAR_NAME_SPOILER_MODE, 1) // Default: show all -#define CVAR_ROUTE_DETAIL CVarGetInteger(CVAR_NAME_ROUTE_DETAIL, 0) // Default: compact #define CVAR_SHOW_SEARCH CVarGetInteger(CVAR_NAME_SHOW_SEARCH, 1) static ImGuiTextFilter sEntranceTrackerFilter; @@ -101,6 +99,34 @@ static SceneId GetSceneFromEntrance(s32 entrance) { return static_cast((entrance >> 9) & 0x7F); } +// Cache of all exits that are in entrance shuffle pools +static std::set sPoolExits; + +// Build the set of exits that are actually in entrance shuffle pools +static void BuildPoolExitSet() { + sPoolExits.clear(); + + std::vector pools = { + Rando::EntranceShuffle::POOL_INTERIOR, + Rando::EntranceShuffle::POOL_DUNGEON, + Rando::EntranceShuffle::POOL_OVERWORLD + }; + + for (auto poolType : pools) { + auto entrancePairs = Rando::EntranceShuffle::GetEntrancePool(poolType); + for (const auto& pair : entrancePairs) { + // Add both the entrance and exit from each pair + sPoolExits.insert(pair.entrance); + sPoolExits.insert(pair.exit); + } + } +} + +// Check if an exit is part of any entrance shuffle pool +static bool IsExitInShufflePool(s32 exitId) { + return sPoolExits.count(exitId) > 0; +} + // Helper to extract spawn number from entrance static s32 GetSpawnFromEntrance(s32 entrance) { return (entrance >> 4) & 0x1F; @@ -344,13 +370,6 @@ static void BuildEntranceNameMap() { sEntranceNames[ENTRANCE(STONE_TOWER, 1)] = "Stone Tower (Inverted Entrance)"; sEntranceNames[ENTRANCE(STONE_TOWER, 2)] = "Stone Tower (to Temple)"; - // Zora Hall - sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 0)] = "Zora Hall (Evan's Room)"; - sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 1)] = "Zora Hall (Lulu's Room)"; - sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 2)] = "Zora Hall (Japas' Room)"; - sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 3)] = "Zora Hall (Tijo's Room)"; - sEntranceNames[ENTRANCE(ZORA_HALL_ROOMS, 5)] = "Zora Hall (Shop)"; - // Ghost Hut entrances (from Ikana Canyon) sEntranceNames[ENTRANCE(GHOST_HUT, 0)] = "Ghost Hut"; sEntranceNames[ENTRANCE(GHOST_HUT, 1)] = "Ghost Hut (Alternate)"; @@ -384,6 +403,9 @@ static void BuildEntranceData() { sEntrancesByArea.clear(); sSortedSceneIds.clear(); + // Build the set of pool exits for route finding + BuildPoolExitSet(); + if (!Rando::EntranceShuffle::IsEntranceShuffleEnabled()) { return; } @@ -427,39 +449,45 @@ static void BuildEntranceData() { [](SceneId a, SceneId b) { return betterSceneIndex[a] < betterSceneIndex[b]; }); } -// Build region list for route finder dropdowns - includes shuffled entrance sources and destinations -static void BuildRegionList() { - sRegionList.clear(); - std::set shuffleRegions; - - // Collect all regions involved in entrance shuffling (both sources and destinations) +// Helper: Find which region a shuffled entrance leads to by checking entrance pools +static RandoRegionId FindRegionFromShuffledEntrance(s32 shuffledEntrance) { + // First try the direct lookup + RandoRegionId direct = Rando::Logic::GetRegionIdFromEntrance(shuffledEntrance); + if (direct != RR_MAX) { + return direct; + } + + // If direct lookup fails, check all entrance pairs to find which original entrance + // this shuffled entrance corresponds to, then look up that region std::vector pools = { Rando::EntranceShuffle::POOL_INTERIOR, Rando::EntranceShuffle::POOL_DUNGEON, Rando::EntranceShuffle::POOL_OVERWORLD }; - + for (auto poolType : pools) { auto entrancePairs = Rando::EntranceShuffle::GetEntrancePool(poolType); for (const auto& pair : entrancePairs) { - // Include destination region - s32 destination = pair.entrance; - RandoRegionId destRegion = Rando::Logic::GetRegionIdFromEntrance(destination); - if (destRegion != RR_MAX) { - shuffleRegions.insert(destRegion); - } - - // Include source region (derived from exit entrance) - s32 source = pair.exit; - RandoRegionId srcRandoRegion = Rando::Logic::GetRegionIdFromEntrance(source); - if (srcRandoRegion != RR_MAX) { - shuffleRegions.insert(srcRandoRegion); + // pair.entrance is the original entrance + s32 shuffled = Rando::EntranceShuffle::GetShuffledEntrance(pair.entrance); + if (shuffled == shuffledEntrance) { + // Found it! The original entrance is pair.entrance + // Now look up that region + RandoRegionId region = Rando::Logic::GetRegionIdFromEntrance(pair.entrance); + if (region != RR_MAX) { + return region; + } } } } + + return RR_MAX; +} +static void BuildRegionList() { + sRegionList.clear(); - // Build list from all shuffle regions - for (RandoRegionId regionId : shuffleRegions) { + // Include all regions from the logic system + for (const auto& [regionId, region] : Rando::Logic::Regions) { std::string displayName = GetRegionDisplayName(regionId); if (!displayName.empty()) { sRegionList.push_back({ regionId, displayName }); @@ -507,10 +535,23 @@ static std::vector FindRoute(RandoRegionId from, RandoRegionId to) { auto& region = Rando::Logic::Regions[current]; - // Check exits (using shuffled entrances) + // Check exits - only traverse exits that are in entrance shuffle pools for (const auto& [exitId, regionExit] : region.exits) { + // Only traverse exits that are part of the entrance shuffle pools + // This prevents the BFS from exploring internal logic connections + if (!IsExitInShufflePool(exitId)) { + continue; + } + s32 actualExit = Rando::EntranceShuffle::GetShuffledEntrance(exitId); - RandoRegionId targetRegion = Rando::Logic::GetRegionIdFromEntrance(actualExit); + + // Find the region this shuffled entrance leads to + RandoRegionId targetRegion = FindRegionFromShuffledEntrance(actualExit); + + // If we can't determine where this exit leads, skip it + if (targetRegion == RR_MAX) { + continue; + } if (targetRegion == to) { // Found the destination @@ -537,36 +578,17 @@ static std::vector FindRoute(RandoRegionId from, RandoRegionId to) { } } - // Check connections (same-scene region transitions) - for (const auto& [connectedRegion, condition] : region.connections) { - if (connectedRegion == to) { - result = path; - RouteStep step; - step.region = to; - step.entranceUsed = -1; - step.description = ""; - result.push_back(step); - return result; - } - - if (visited.find(connectedRegion) == visited.end()) { - visited.insert(connectedRegion); - auto newPath = path; - RouteStep step; - step.region = connectedRegion; - step.entranceUsed = -1; - step.description = ""; - newPath.push_back(step); - queue.push(newPath); - } - } + // Note: We intentionally do NOT traverse region.connections here. + // Connections are same-scene internal transitions used for logic, not actual + // entrances the player uses. Including them causes garbage routes through + // regions like "Moon Deku Trial" that aren't actual navigation steps. } return result; // Empty if no route found } -// Format route for display -static std::string FormatRoute(RandoRegionId from, const std::vector& route, bool detailed) { +// Format route for display (compact format) +static std::string FormatRoute(RandoRegionId from, const std::vector& route) { if (route.empty()) { return "No route found"; } @@ -574,44 +596,14 @@ static std::string FormatRoute(RandoRegionId from, const std::vector& std::string result; std::string fromName = GetRegionDisplayName(from); - if (detailed) { - // Detailed format - show all steps with entrance names - if (!fromName.empty()) { - result = "From " + fromName; - } - - for (size_t i = 0; i < route.size(); i++) { - const auto& step = route[i]; - std::string stepName = GetRegionDisplayName(step.region); - - // Show entrance used if it's a real exit (not a same-scene connection) - if (step.entranceUsed != -1 && !step.description.empty()) { - result += "\n -> Take '" + step.description + "'"; - } - - // Always show the destination region - if (!stepName.empty()) { - result += "\n => " + stepName; - } - } - } else { - // Compact format - prioritize showing entrance names over region names - if (!fromName.empty()) { - result = fromName; - } + if (!fromName.empty()) { + result = fromName; + } - for (size_t i = 0; i < route.size(); i++) { - const auto& step = route[i]; - std::string stepName = GetRegionDisplayName(step.region); - - // Prefer showing entrance names when available - if (step.entranceUsed != -1 && !step.description.empty()) { - // Show the entrance you take to get there - result += " -> " + step.description; - } else if (!stepName.empty()) { - // Fallback to region name for same-scene connections - result += " -> " + stepName; - } + for (const auto& step : route) { + // Show the entrance you take to get there + if (!step.description.empty()) { + result += " -> " + step.description; } } @@ -890,7 +882,7 @@ void EntranceTrackerWindow::Draw() { // Iterate through all regions to find entrances leading to the destination for (const auto& [regionId, region] : Rando::Logic::Regions) { for (const auto& [exitId, regionExit] : region.exits) { - if (!regionExit.returnEntrance || regionExit.returnEntrance == ONE_WAY_EXIT) continue; + if (regionExit.returnEntrance == ONE_WAY_EXIT) continue; if (GetSceneFromEntrance(regionExit.returnEntrance) == destScene) { if (IsEntranceDiscovered(regionExit.returnEntrance)) { destinationDiscovered = true; @@ -906,7 +898,7 @@ void EntranceTrackerWindow::Draw() { sRouteResult = "No route could be found"; } else { auto route = FindRoute(from, to); - sRouteResult = FormatRoute(from, route, CVAR_ROUTE_DETAIL); + sRouteResult = FormatRoute(from, route); } } } @@ -948,9 +940,6 @@ void EntranceTrackerSettingsWindow::DrawElement() { UIWidgets::CheckboxOptions().DefaultValue(true)); UIWidgets::Tooltip("When disabled, only shows entrances you have discovered by using them."); - UIWidgets::CVarCheckbox("Detailed Route Display", CVAR_NAME_ROUTE_DETAIL); - UIWidgets::Tooltip("When enabled, shows step-by-step instructions instead of compact path."); - UIWidgets::CVarCheckbox("Show Search", CVAR_NAME_SHOW_SEARCH, UIWidgets::CheckboxOptions().DefaultValue(true)); ImGui::TableNextColumn(); From 48a564d54f0851c2d3f7f26359ff0bb5bffc0e65 Mon Sep 17 00:00:00 2001 From: Phillyjawn Date: Sun, 25 Jan 2026 20:30:19 -0700 Subject: [PATCH 6/8] Add name for Ranch House, it was freaking out the router --- mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp b/mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp index 9187e49287..a65e66596c 100644 --- a/mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp +++ b/mm/2s2h/Rando/Logic/Regions/MilkRoad.cpp @@ -159,7 +159,8 @@ static RegisterShipInitFunc initFunc([]() { CONNECTION(RR_MILK_ROAD, (RANDO_EVENTS[RE_COWS_FROM_ALIENS] && IS_NIGHT2()) || FINAL_DAY()), }, }; - Regions[RR_RANCH_BARN] = RandoRegion{ .sceneId = SCENE_OMOYA, + Regions[RR_RANCH_BARN] = RandoRegion{ .name = "Mama's House (Barn)", + .sceneId = SCENE_OMOYA, .checks = { CHECK(RC_ROMANI_RANCH_BARN_COW_LEFT, CAN_PLAY_SONG(EPONA) && (BETWEEN(TIME_NIGHT1_PM_06_00, TIME_NIGHT1_AM_02_30) || RANDO_EVENTS[RE_COWS_FROM_ALIENS])), CHECK(RC_ROMANI_RANCH_BARN_COW_MIDDLE, CAN_PLAY_SONG(EPONA) && (BETWEEN(TIME_NIGHT1_PM_06_00, TIME_NIGHT1_AM_02_30) || RANDO_EVENTS[RE_COWS_FROM_ALIENS])), @@ -173,7 +174,8 @@ static RegisterShipInitFunc initFunc([]() { STAY(TIME_NIGHT3_PM_08_00, false), }, }; - Regions[RR_RANCH_HOUSE] = RandoRegion{ .sceneId = SCENE_OMOYA, + Regions[RR_RANCH_HOUSE] = RandoRegion{ .name = "Mama's House (Main)", + .sceneId = SCENE_OMOYA, .exits = { // TO FROM EXIT(ENTRANCE(ROMANI_RANCH, 3), ENTRANCE(RANCH_HOUSE, 1), true), }, From da178e21b1a22140c9fa8da354a3544f4bf8d9af Mon Sep 17 00:00:00 2001 From: Phillyjawn Date: Sun, 25 Jan 2026 21:20:01 -0700 Subject: [PATCH 7/8] Fix experimental routing which allows garbage routes --- .../Rando/EntranceTracker/EntranceTracker.cpp | 109 +++++++++++++++--- 1 file changed, 96 insertions(+), 13 deletions(-) diff --git a/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp index d110790cbf..7390f596ee 100644 --- a/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp +++ b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp @@ -58,6 +58,7 @@ static bool sEntranceTrackerBtnState = false; #define CVAR_NAME_TRACKER_SCALE "gRando.EntranceTracker.Scale" #define CVAR_NAME_SPOILER_MODE "gRando.EntranceTracker.SpoilerMode" #define CVAR_NAME_SHOW_SEARCH "gRando.EntranceTracker.ShowSearch" +#define CVAR_NAME_EXTENDED_ROUTING "gRando.EntranceTracker.ExtendedRouting" #define CVAR_SHOW_ENTRANCE_TRACKER CVarGetInteger(CVAR_NAME_SHOW_ENTRANCE_TRACKER, 0) #define CVAR_VISIBILITY_MODE CVarGetInteger(CVAR_NAME_VISIBILITY_MODE, ENTRANCE_TRACKER_VISIBILITY_MODE_ALWAYS) @@ -66,6 +67,7 @@ static bool sEntranceTrackerBtnState = false; #define CVAR_TRACKER_SCALE CVarGetFloat(CVAR_NAME_TRACKER_SCALE, 1.0f) #define CVAR_SPOILER_MODE CVarGetInteger(CVAR_NAME_SPOILER_MODE, 1) // Default: show all #define CVAR_SHOW_SEARCH CVarGetInteger(CVAR_NAME_SHOW_SEARCH, 1) +#define CVAR_EXTENDED_ROUTING CVarGetInteger(CVAR_NAME_EXTENDED_ROUTING, 0) static ImGuiTextFilter sEntranceTrackerFilter; static float trackerScale = 1.0f; @@ -451,13 +453,13 @@ static void BuildEntranceData() { // Helper: Find which region a shuffled entrance leads to by checking entrance pools static RandoRegionId FindRegionFromShuffledEntrance(s32 shuffledEntrance) { - // First try the direct lookup + // First try the direct lookup (for entrances) RandoRegionId direct = Rando::Logic::GetRegionIdFromEntrance(shuffledEntrance); if (direct != RR_MAX) { return direct; } - // If direct lookup fails, check all entrance pairs to find which original entrance + // If direct lookup fails, check all entrance pairs to find which original entrance/exit // this shuffled entrance corresponds to, then look up that region std::vector pools = { Rando::EntranceShuffle::POOL_INTERIOR, @@ -468,9 +470,9 @@ static RandoRegionId FindRegionFromShuffledEntrance(s32 shuffledEntrance) { for (auto poolType : pools) { auto entrancePairs = Rando::EntranceShuffle::GetEntrancePool(poolType); for (const auto& pair : entrancePairs) { - // pair.entrance is the original entrance - s32 shuffled = Rando::EntranceShuffle::GetShuffledEntrance(pair.entrance); - if (shuffled == shuffledEntrance) { + // Check if shuffled entrance matches shuffled version of the original entrance + s32 shuffledFromEntrance = Rando::EntranceShuffle::GetShuffledEntrance(pair.entrance); + if (shuffledFromEntrance == shuffledEntrance) { // Found it! The original entrance is pair.entrance // Now look up that region RandoRegionId region = Rando::Logic::GetRegionIdFromEntrance(pair.entrance); @@ -478,6 +480,41 @@ static RandoRegionId FindRegionFromShuffledEntrance(s32 shuffledEntrance) { return region; } } + + // Also check if shuffled entrance matches shuffled version of the original exit + s32 shuffledFromExit = Rando::EntranceShuffle::GetShuffledEntrance(pair.exit); + if (shuffledFromExit == shuffledEntrance) { + // This is trickier - we have a shuffled exit. Find which region has this exit. + // The exit points TO a region, so look for the region that has this as an exit + for (const auto& [rid, rdata] : Rando::Logic::Regions) { + for (const auto& [exitId, regionExit] : rdata.exits) { + if (Rando::EntranceShuffle::GetShuffledEntrance(exitId) == shuffledEntrance) { + return rid; + } + } + } + } + } + } + + // Fallback for extended routing: try to find ANY region that has this entrance/exit + // This allows us to trace non-shuffled connections when extended routing is enabled + if (CVAR_EXTENDED_ROUTING) { + // Try looking for a region with this entrance + for (const auto& [rid, rdata] : Rando::Logic::Regions) { + for (const auto& [exitId, regionExit] : rdata.exits) { + if (exitId == shuffledEntrance || Rando::EntranceShuffle::GetShuffledEntrance(exitId) == shuffledEntrance) { + return rid; + } + } + } + + // As a last resort, try by scene ID + SceneId targetScene = GetSceneFromEntrance(shuffledEntrance); + for (const auto& [rid, rdata] : Rando::Logic::Regions) { + if (rdata.sceneId == targetScene) { + return rid; + } } } @@ -535,11 +572,12 @@ static std::vector FindRoute(RandoRegionId from, RandoRegionId to) { auto& region = Rando::Logic::Regions[current]; - // Check exits - only traverse exits that are in entrance shuffle pools + // Check exits - only traverse exits that are in entrance shuffle pools (unless extended routing is enabled) for (const auto& [exitId, regionExit] : region.exits) { // Only traverse exits that are part of the entrance shuffle pools // This prevents the BFS from exploring internal logic connections - if (!IsExitInShufflePool(exitId)) { + // Extended routing bypasses this check for more complete (but messier) routes + if (!CVAR_EXTENDED_ROUTING && !IsExitInShufflePool(exitId)) { continue; } @@ -548,9 +586,25 @@ static std::vector FindRoute(RandoRegionId from, RandoRegionId to) { // Find the region this shuffled entrance leads to RandoRegionId targetRegion = FindRegionFromShuffledEntrance(actualExit); - // If we can't determine where this exit leads, skip it + // If we can't determine where this exit leads: + // - Normal mode: skip it + // - Extended routing: try scene-based lookup as fallback if (targetRegion == RR_MAX) { - continue; + if (CVAR_EXTENDED_ROUTING) { + // Try to find any region in the target scene + SceneId targetScene = GetSceneFromEntrance(actualExit); + for (const auto& [rid, rdata] : Rando::Logic::Regions) { + if (rdata.sceneId == targetScene) { + targetRegion = rid; + break; + } + } + } + + // Still no region found, skip this exit + if (targetRegion == RR_MAX) { + continue; + } } if (targetRegion == to) { @@ -578,10 +632,32 @@ static std::vector FindRoute(RandoRegionId from, RandoRegionId to) { } } - // Note: We intentionally do NOT traverse region.connections here. - // Connections are same-scene internal transitions used for logic, not actual - // entrances the player uses. Including them causes garbage routes through - // regions like "Moon Deku Trial" that aren't actual navigation steps. + // In extended routing mode, also allow traversing connections (same-scene transitions) + // Normal mode skips these to avoid garbage routes through internal regions + if (CVAR_EXTENDED_ROUTING) { + for (const auto& [connectedRegion, condition] : region.connections) { + if (connectedRegion == to) { + result = path; + RouteStep step; + step.region = to; + step.entranceUsed = -1; + step.description = "(internal transition)"; + result.push_back(step); + return result; + } + + if (visited.find(connectedRegion) == visited.end()) { + visited.insert(connectedRegion); + auto newPath = path; + RouteStep step; + step.region = connectedRegion; + step.entranceUsed = -1; + step.description = "(internal transition)"; + newPath.push_back(step); + queue.push(newPath); + } + } + } } return result; // Empty if no route found @@ -942,6 +1018,13 @@ void EntranceTrackerSettingsWindow::DrawElement() { UIWidgets::CVarCheckbox("Show Search", CVAR_NAME_SHOW_SEARCH, UIWidgets::CheckboxOptions().DefaultValue(true)); + UIWidgets::CVarCheckbox("Extended Routing (Experimental)", CVAR_NAME_EXTENDED_ROUTING); + UIWidgets::Tooltip( + "Enables routing through areas not yet in entrance shuffle pools.\n\n" + "Routes may include internal logic regions that don't correspond to " + "actual in-game locations. You'll need to use your knowledge of the " + "vanilla world layout to interpret these steps."); + ImGui::TableNextColumn(); ImGui::SeparatorText("Window Settings"); From 3573ab4f30f34cf9e065544324083de08624b08c Mon Sep 17 00:00:00 2001 From: Phillyjawn Date: Tue, 27 Jan 2026 22:41:28 -0700 Subject: [PATCH 8/8] clang format --- .../Rando/EntranceTracker/EntranceTracker.cpp | 134 +++++++++--------- 1 file changed, 66 insertions(+), 68 deletions(-) diff --git a/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp index 7390f596ee..e518c35b62 100644 --- a/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp +++ b/mm/2s2h/Rando/EntranceTracker/EntranceTracker.cpp @@ -108,11 +108,9 @@ static std::set sPoolExits; static void BuildPoolExitSet() { sPoolExits.clear(); - std::vector pools = { - Rando::EntranceShuffle::POOL_INTERIOR, - Rando::EntranceShuffle::POOL_DUNGEON, - Rando::EntranceShuffle::POOL_OVERWORLD - }; + std::vector pools = { Rando::EntranceShuffle::POOL_INTERIOR, + Rando::EntranceShuffle::POOL_DUNGEON, + Rando::EntranceShuffle::POOL_OVERWORLD }; for (auto poolType : pools) { auto entrancePairs = Rando::EntranceShuffle::GetEntrancePool(poolType); @@ -182,11 +180,9 @@ static void BuildEntranceIndexMap() { sNextEntranceIndex = 0; // Add all entrances from the pools to ensure consistent indexing - std::vector pools = { - Rando::EntranceShuffle::POOL_INTERIOR, - Rando::EntranceShuffle::POOL_DUNGEON, - Rando::EntranceShuffle::POOL_OVERWORLD - }; + std::vector pools = { Rando::EntranceShuffle::POOL_INTERIOR, + Rando::EntranceShuffle::POOL_DUNGEON, + Rando::EntranceShuffle::POOL_OVERWORLD }; for (auto poolType : pools) { auto entrancePairs = Rando::EntranceShuffle::GetEntrancePool(poolType); @@ -227,9 +223,11 @@ bool Rando::EntranceTracker::IsEntranceDiscovered(s32 entranceId) { // List of generic region names that need scene context static const std::set sGenericRegionNames = { - "Entrance", "Main Room", "Maze Room", "Upper", "Lower", "Back", "Front", - "Boss Room", "Passage", "Main Room Upper", "Main Room Lower", - "Night 1 Boss", "Night 2 Boss", "Higher", "Before Great Bay Coast" + "Entrance", "Main Room", "Maze Room", + "Upper", "Lower", "Back", + "Front", "Boss Room", "Passage", + "Main Room Upper", "Main Room Lower", "Night 1 Boss", + "Night 2 Boss", "Higher", "Before Great Bay Coast" }; // Get a display name for a region, using scene name as fallback or prefix for generic names @@ -304,7 +302,7 @@ static void BuildEntranceNameMap() { sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 9)] = "East Clock Town (Stock Pot Inn)"; sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 10)] = "East Clock Town (Stock Pot Inn Upstairs)"; sEntranceNames[ENTRANCE(EAST_CLOCK_TOWN, 11)] = "East Clock Town (Milk Bar)"; - + sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 0)] = "South Clock Town (Clock Tower Interior)"; sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 2)] = "South Clock Town (from East)"; sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 3)] = "South Clock Town (from West)"; @@ -312,7 +310,7 @@ static void BuildEntranceNameMap() { sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 5)] = "South Clock Town (Swordsman)"; sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 6)] = "South Clock Town (Laundry)"; sEntranceNames[ENTRANCE(SOUTH_CLOCK_TOWN, 7)] = "South Clock Town (Chest Shop)"; - + sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 0)] = "West Clock Town (to Termina Field)"; sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 1)] = "West Clock Town (to South Lower)"; sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 2)] = "West Clock Town (to South Upper)"; @@ -322,11 +320,11 @@ static void BuildEntranceNameMap() { sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 6)] = "West Clock Town (Bomb Shop)"; sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 7)] = "West Clock Town (Post Office)"; sEntranceNames[ENTRANCE(WEST_CLOCK_TOWN, 8)] = "West Clock Town (Lottery Shop)"; - + sEntranceNames[ENTRANCE(NORTH_CLOCK_TOWN, 1)] = "North Clock Town (Great Fairy)"; sEntranceNames[ENTRANCE(NORTH_CLOCK_TOWN, 2)] = "North Clock Town (from South)"; sEntranceNames[ENTRANCE(NORTH_CLOCK_TOWN, 3)] = "North Clock Town (Great Fairy Fountain)"; - + sEntranceNames[ENTRANCE(LAUNDRY_POOL, 0)] = "Laundry Pool (Entrance)"; sEntranceNames[ENTRANCE(LAUNDRY_POOL, 1)] = "Laundry Pool (Back Alley)"; @@ -386,7 +384,7 @@ static SceneId GetSourceSceneForEntrancePair(s32 returnEntrance) { // returnEntrance is the entrance ID that you return to when exiting a destination // We need to find which region has this as a returnEntrance // The returnEntrance field is in the value of the exits map - + for (const auto& [regionId, region] : Rando::Logic::Regions) { for (const auto& [_, regionExit] : region.exits) { if (regionExit.returnEntrance == returnEntrance) { @@ -395,7 +393,7 @@ static SceneId GetSourceSceneForEntrancePair(s32 returnEntrance) { } } } - + // Fallback: try to extract scene from the return entrance ID itself return GetSceneFromEntrance(returnEntrance); } @@ -413,11 +411,9 @@ static void BuildEntranceData() { } // Get all entrance pools and process each - std::vector pools = { - Rando::EntranceShuffle::POOL_INTERIOR, - Rando::EntranceShuffle::POOL_DUNGEON, - Rando::EntranceShuffle::POOL_OVERWORLD - }; + std::vector pools = { Rando::EntranceShuffle::POOL_INTERIOR, + Rando::EntranceShuffle::POOL_DUNGEON, + Rando::EntranceShuffle::POOL_OVERWORLD }; for (auto poolType : pools) { auto entrancePairs = Rando::EntranceShuffle::GetEntrancePool(poolType); @@ -425,7 +421,7 @@ static void BuildEntranceData() { for (const auto& pair : entrancePairs) { s32 original = pair.entrance; s32 shuffled = Rando::EntranceShuffle::GetShuffledEntrance(original); - s32 exitId = pair.exit; // This is the exit from the SOURCE region + s32 exitId = pair.exit; // This is the exit from the SOURCE region // Get source scene using the exit ID from the source region SceneId sourceScene = GetSourceSceneForEntrancePair(exitId); @@ -458,15 +454,13 @@ static RandoRegionId FindRegionFromShuffledEntrance(s32 shuffledEntrance) { if (direct != RR_MAX) { return direct; } - + // If direct lookup fails, check all entrance pairs to find which original entrance/exit // this shuffled entrance corresponds to, then look up that region - std::vector pools = { - Rando::EntranceShuffle::POOL_INTERIOR, - Rando::EntranceShuffle::POOL_DUNGEON, - Rando::EntranceShuffle::POOL_OVERWORLD - }; - + std::vector pools = { Rando::EntranceShuffle::POOL_INTERIOR, + Rando::EntranceShuffle::POOL_DUNGEON, + Rando::EntranceShuffle::POOL_OVERWORLD }; + for (auto poolType : pools) { auto entrancePairs = Rando::EntranceShuffle::GetEntrancePool(poolType); for (const auto& pair : entrancePairs) { @@ -480,7 +474,7 @@ static RandoRegionId FindRegionFromShuffledEntrance(s32 shuffledEntrance) { return region; } } - + // Also check if shuffled entrance matches shuffled version of the original exit s32 shuffledFromExit = Rando::EntranceShuffle::GetShuffledEntrance(pair.exit); if (shuffledFromExit == shuffledEntrance) { @@ -496,19 +490,20 @@ static RandoRegionId FindRegionFromShuffledEntrance(s32 shuffledEntrance) { } } } - + // Fallback for extended routing: try to find ANY region that has this entrance/exit // This allows us to trace non-shuffled connections when extended routing is enabled if (CVAR_EXTENDED_ROUTING) { // Try looking for a region with this entrance for (const auto& [rid, rdata] : Rando::Logic::Regions) { for (const auto& [exitId, regionExit] : rdata.exits) { - if (exitId == shuffledEntrance || Rando::EntranceShuffle::GetShuffledEntrance(exitId) == shuffledEntrance) { + if (exitId == shuffledEntrance || + Rando::EntranceShuffle::GetShuffledEntrance(exitId) == shuffledEntrance) { return rid; } } } - + // As a last resort, try by scene ID SceneId targetScene = GetSceneFromEntrance(shuffledEntrance); for (const auto& [rid, rdata] : Rando::Logic::Regions) { @@ -517,7 +512,7 @@ static RandoRegionId FindRegionFromShuffledEntrance(s32 shuffledEntrance) { } } } - + return RR_MAX; } static void BuildRegionList() { @@ -532,8 +527,7 @@ static void BuildRegionList() { } // Sort alphabetically - std::sort(sRegionList.begin(), sRegionList.end(), - [](const auto& a, const auto& b) { return a.second < b.second; }); + std::sort(sRegionList.begin(), sRegionList.end(), [](const auto& a, const auto& b) { return a.second < b.second; }); } // Route finding using BFS @@ -600,7 +594,7 @@ static std::vector FindRoute(RandoRegionId from, RandoRegionId to) { } } } - + // Still no region found, skip this exit if (targetRegion == RR_MAX) { continue; @@ -825,24 +819,26 @@ void EntranceTrackerWindow::Draw() { // From section with search ImGui::Text("From:"); ImGui::SameLine(); - + // Calculate available width for combo float availWidth = ImGui::GetContentRegionAvail().x - 100.0f; - - if (ImGui::BeginCombo("##FromRegion", sRouteFromIndex < (int)sRegionList.size() - ? sRegionList[sRouteFromIndex].second.c_str() : "Select...", + + if (ImGui::BeginCombo("##FromRegion", + sRouteFromIndex < (int)sRegionList.size() + ? sRegionList[sRouteFromIndex].second.c_str() + : "Select...", ImGuiComboFlags_HeightLarge)) { - + // Search filter for From sRouteFromFilter.Draw("##FromSearch", availWidth); ImGui::Separator(); - + int visibleCount = 0; for (int i = 0; i < (int)sRegionList.size(); i++) { if (!sRouteFromFilter.PassFilter(sRegionList[i].second.c_str())) { continue; } - + visibleCount++; ImGui::PushID(i); bool selected = (i == sRouteFromIndex); @@ -854,11 +850,11 @@ void EntranceTrackerWindow::Draw() { } ImGui::PopID(); } - + if (visibleCount == 0) { ImGui::TextDisabled("No matches"); } - + ImGui::EndCombo(); } @@ -899,21 +895,22 @@ void EntranceTrackerWindow::Draw() { // To section with search ImGui::Text("To:"); ImGui::SameLine(); - - if (ImGui::BeginCombo("##ToRegion", sRouteToIndex < (int)sRegionList.size() - ? sRegionList[sRouteToIndex].second.c_str() : "Select...", + + if (ImGui::BeginCombo("##ToRegion", + sRouteToIndex < (int)sRegionList.size() ? sRegionList[sRouteToIndex].second.c_str() + : "Select...", ImGuiComboFlags_HeightLarge)) { - + // Search filter for To sRouteToFilter.Draw("##ToSearch", availWidth); ImGui::Separator(); - + int visibleCount = 0; for (int i = 0; i < (int)sRegionList.size(); i++) { if (!sRouteToFilter.PassFilter(sRegionList[i].second.c_str())) { continue; } - + visibleCount++; ImGui::PushID(i); bool selected = (i == sRouteToIndex); @@ -925,11 +922,11 @@ void EntranceTrackerWindow::Draw() { } ImGui::PopID(); } - + if (visibleCount == 0) { ImGui::TextDisabled("No matches"); } - + ImGui::EndCombo(); } @@ -945,20 +942,21 @@ void EntranceTrackerWindow::Draw() { if (sRouteFromIndex < (int)sRegionList.size() && sRouteToIndex < (int)sRegionList.size()) { RandoRegionId from = sRegionList[sRouteFromIndex].first; RandoRegionId to = sRegionList[sRouteToIndex].first; - + // Check if spoilers are off and destination is undiscovered bool spoilerMode = CVAR_SPOILER_MODE; bool destinationDiscovered = true; - + if (!spoilerMode && Rando::Logic::Regions.count(to) > 0) { // Check if any entrance to this region has been discovered SceneId destScene = Rando::Logic::Regions.at(to).sceneId; destinationDiscovered = false; - + // Iterate through all regions to find entrances leading to the destination for (const auto& [regionId, region] : Rando::Logic::Regions) { for (const auto& [exitId, regionExit] : region.exits) { - if (regionExit.returnEntrance == ONE_WAY_EXIT) continue; + if (regionExit.returnEntrance == ONE_WAY_EXIT) + continue; if (GetSceneFromEntrance(regionExit.returnEntrance) == destScene) { if (IsEntranceDiscovered(regionExit.returnEntrance)) { destinationDiscovered = true; @@ -966,10 +964,11 @@ void EntranceTrackerWindow::Draw() { } } } - if (destinationDiscovered) break; + if (destinationDiscovered) + break; } } - + if (!destinationDiscovered) { sRouteResult = "No route could be found"; } else { @@ -1019,11 +1018,10 @@ void EntranceTrackerSettingsWindow::DrawElement() { UIWidgets::CVarCheckbox("Show Search", CVAR_NAME_SHOW_SEARCH, UIWidgets::CheckboxOptions().DefaultValue(true)); UIWidgets::CVarCheckbox("Extended Routing (Experimental)", CVAR_NAME_EXTENDED_ROUTING); - UIWidgets::Tooltip( - "Enables routing through areas not yet in entrance shuffle pools.\n\n" - "Routes may include internal logic regions that don't correspond to " - "actual in-game locations. You'll need to use your knowledge of the " - "vanilla world layout to interpret these steps."); + UIWidgets::Tooltip("Enables routing through areas not yet in entrance shuffle pools.\n\n" + "Routes may include internal logic regions that don't correspond to " + "actual in-game locations. You'll need to use your knowledge of the " + "vanilla world layout to interpret these steps."); ImGui::TableNextColumn(); ImGui::SeparatorText("Window Settings");