summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett Cox <garrettjcox@gmail.com>2026-02-22 07:12:25 -0600
committerGitHub <noreply@github.com>2026-02-22 07:12:25 -0600
commitf8bf9bb3b66f467a8a433f8b204a1c575880c6d7 (patch)
tree13522f00a4d2ca248786fd2524a467289d655c94
parent4a7af30687a57e26affe3c36546128d8f61351e0 (diff)
Make scroll to scene & only show current scene work for grottos (#1561)
-rw-r--r--mm/2s2h/Rando/ActorBehavior/DmStk.cpp4
-rw-r--r--mm/2s2h/Rando/ActorBehavior/EnGs.cpp8
-rw-r--r--mm/2s2h/Rando/ActorBehavior/EnKgy.cpp8
-rw-r--r--mm/2s2h/Rando/ActorBehavior/EnTalk.cpp5
-rw-r--r--mm/2s2h/Rando/ActorBehavior/EnTimeTag.cpp4
-rw-r--r--mm/2s2h/Rando/ActorBehavior/EnZow.cpp4
-rw-r--r--mm/2s2h/Rando/CheckTracker/CheckTracker.cpp87
-rw-r--r--mm/2s2h/Rando/Logic/Logic.cpp75
-rw-r--r--mm/2s2h/Rando/Logic/Regions/East.cpp18
-rw-r--r--mm/2s2h/Rando/Logic/Regions/North.cpp28
-rw-r--r--mm/2s2h/Rando/Logic/Regions/South.cpp26
-rw-r--r--mm/2s2h/Rando/Logic/Regions/TerminaField.cpp50
-rw-r--r--mm/2s2h/Rando/Logic/Regions/West.cpp24
-rw-r--r--mm/2s2h/Rando/MiscBehavior/SariasSongHint.cpp2
-rw-r--r--mm/2s2h/Rando/StaticData/Checks.cpp68
-rw-r--r--mm/2s2h/Rando/StaticData/StaticData.h1
16 files changed, 268 insertions, 144 deletions
diff --git a/mm/2s2h/Rando/ActorBehavior/DmStk.cpp b/mm/2s2h/Rando/ActorBehavior/DmStk.cpp
index 18797e4a0..f9ec1b38e 100644
--- a/mm/2s2h/Rando/ActorBehavior/DmStk.cpp
+++ b/mm/2s2h/Rando/ActorBehavior/DmStk.cpp
@@ -28,12 +28,12 @@ void ApplyOathHint(u16* textId, bool* loadFromMessageTable) {
msg = "You think you can defeat me? The Giants are trapped and powerless to stop me. Even if they were free, "
"they couldn't save you.";
} else {
- msg = "I can hear the Giants Melody coming from "
+ msg = "I can hear the Giants Melody echoing "
"%y{{location}}%w. But it's too late! They can't help you now!";
}
RandoCheckId randoCheckId = Rando::FindItemPlacement(RI_SONG_OATH);
- CustomMessage::Replace(&msg, "{{location}}", Ship_GetSceneName(Rando::StaticData::Checks[randoCheckId].sceneId));
+ CustomMessage::Replace(&msg, "{{location}}", Rando::StaticData::GetLocationNameForHint(randoCheckId, false));
CustomMessage::Entry entry = {
.nextMessageID = (u16)0xFFFF,
diff --git a/mm/2s2h/Rando/ActorBehavior/EnGs.cpp b/mm/2s2h/Rando/ActorBehavior/EnGs.cpp
index a3db35aac..3fc96d787 100644
--- a/mm/2s2h/Rando/ActorBehavior/EnGs.cpp
+++ b/mm/2s2h/Rando/ActorBehavior/EnGs.cpp
@@ -92,12 +92,12 @@ void Rando::ActorBehavior::InitEnGsBehavior() {
entry.autoFormat = false;
auto& saveCheck = RANDO_SAVE_CHECKS[randoCheckId];
- entry.msg = "They say %g{{item}}%w is hidden at %y{{location}}%w.";
+ entry.msg = "They say %g{{item}}%w is hidden %y{{location}}%w.";
CustomMessage::Replace(&entry.msg, "{{item}}",
Rando::StaticData::GetItemName(saveCheck.randoItemId, true, randoCheckId));
CustomMessage::Replace(&entry.msg, "{{location}}",
- Ship_GetSceneName(Rando::StaticData::Checks[randoCheckId].sceneId));
+ Rando::StaticData::GetLocationNameForHint(randoCheckId, false));
// Replace colors before line break calculation
CustomMessage::ReplaceColorChars(&entry.msg);
@@ -141,12 +141,12 @@ void Rando::ActorBehavior::InitEnGsBehavior() {
} else {
RandoSaveCheck saveCheck = RANDO_SAVE_CHECKS[randoCheckId];
- entry.msg = "Wise choice... They say %g{{item}}%w is hidden at %y{{location}}%w.";
+ entry.msg = "Wise choice... They say %g{{item}}%w is hidden %y{{location}}%w.";
CustomMessage::Replace(&entry.msg, "{{item}}",
Rando::StaticData::GetItemName(saveCheck.randoItemId, true, randoCheckId));
CustomMessage::Replace(&entry.msg, "{{location}}",
- Ship_GetSceneName(Rando::StaticData::Checks[randoCheckId].sceneId));
+ Rando::StaticData::GetLocationNameForHint(randoCheckId, true));
gSaveContext.rupeeAccumulator -= cost;
cost *= 2;
diff --git a/mm/2s2h/Rando/ActorBehavior/EnKgy.cpp b/mm/2s2h/Rando/ActorBehavior/EnKgy.cpp
index 341c67d48..f62e660c8 100644
--- a/mm/2s2h/Rando/ActorBehavior/EnKgy.cpp
+++ b/mm/2s2h/Rando/ActorBehavior/EnKgy.cpp
@@ -114,10 +114,10 @@ void Rando::ActorBehavior::InitEnKgyBehavior() {
// "Gold dust is the prize for winning the Goron race in spring?"
COND_ID_HOOK(OnOpenText, 0xc49, IS_RANDO, [](u16* textId, bool* loadFromMessageTable) {
auto entry = CustomMessage::LoadVanillaMessageTableEntry(*textId);
- entry.msg = "Huh? You say that gold dust can be found at %r{location}%w?\x19";
+ entry.msg = "Huh? You say that gold dust can be found %r{location}%w?\x19";
RandoCheckId randoCheckId = Rando::FindItemPlacement(RI_BOTTLE_GOLD_DUST);
CustomMessage::Replace(&entry.msg, "{location}",
- Ship_GetSceneName(Rando::StaticData::Checks[randoCheckId].sceneId));
+ Rando::StaticData::GetLocationNameForHint(randoCheckId, false));
CustomMessage::LoadCustomMessageIntoFont(entry);
*loadFromMessageTable = false;
@@ -126,14 +126,14 @@ void Rando::ActorBehavior::InitEnKgyBehavior() {
// "Gold dust happens to be first prize at the racetrack"
COND_ID_HOOK(OnOpenText, 0xc4b, IS_RANDO, [](u16* textId, bool* loadFromMessageTable) {
auto entry = CustomMessage::LoadVanillaMessageTableEntry(*textId);
- entry.msg = "Gold dust can be found at %p{location}%w.\x10";
+ entry.msg = "Gold dust can be found %p{location}%w.\x10";
entry.msg += "Bring me that, and my %r{itemName}%w is all yours.\xE0";
RandoCheckId randoCheckId = Rando::FindItemPlacement(RI_BOTTLE_GOLD_DUST);
CustomMessage::Replace(
&entry.msg, "{itemName}",
Rando::StaticData::Items[RANDO_SAVE_CHECKS[RC_MOUNTAIN_VILLAGE_SMITHY_GILDED_SWORD].randoItemId].name);
CustomMessage::Replace(&entry.msg, "{location}",
- Ship_GetSceneName(Rando::StaticData::Checks[randoCheckId].sceneId));
+ Rando::StaticData::GetLocationNameForHint(randoCheckId, false));
CustomMessage::LoadCustomMessageIntoFont(entry);
*loadFromMessageTable = false;
diff --git a/mm/2s2h/Rando/ActorBehavior/EnTalk.cpp b/mm/2s2h/Rando/ActorBehavior/EnTalk.cpp
index d95069ab6..49a721922 100644
--- a/mm/2s2h/Rando/ActorBehavior/EnTalk.cpp
+++ b/mm/2s2h/Rando/ActorBehavior/EnTalk.cpp
@@ -25,7 +25,7 @@ void ApplyRemainsHint(u16* textId, bool* loadFromMessageTable) {
"for their remains.";
} else {
msg = " %g{{boss}}%w:\n"
- "Last seen in near %y{{location}}%w.";
+ "Last seen %y{{location}}%w.";
switch (remainsHintIndex) {
case 1:
@@ -48,8 +48,7 @@ void ApplyRemainsHint(u16* textId, bool* loadFromMessageTable) {
icon = Rando::StaticData::GetIconForZMessage(randoItemId);
RandoCheckId randoCheckId = Rando::FindItemPlacement(randoItemId);
- CustomMessage::Replace(&msg, "{{location}}",
- Ship_GetSceneName(Rando::StaticData::Checks[randoCheckId].sceneId));
+ CustomMessage::Replace(&msg, "{{location}}", Rando::StaticData::GetLocationNameForHint(randoCheckId, false));
}
CustomMessage::Entry entry = {
diff --git a/mm/2s2h/Rando/ActorBehavior/EnTimeTag.cpp b/mm/2s2h/Rando/ActorBehavior/EnTimeTag.cpp
index 273e6fdb2..dbf5ae238 100644
--- a/mm/2s2h/Rando/ActorBehavior/EnTimeTag.cpp
+++ b/mm/2s2h/Rando/ActorBehavior/EnTimeTag.cpp
@@ -21,11 +21,11 @@ void Rando::ActorBehavior::InitEnTimeTagBehavior() {
if (RANDO_SAVE_OPTIONS[RO_HINTS_SONG_OF_SOARING]) {
auto entry = CustomMessage::LoadVanillaMessageTableEntry(*textId);
- entry.msg = "A note is carved into the stone...\x10What you seek lies in %y{{location}}%w.";
+ entry.msg = "A note is carved into the stone...\x10What you seek lies %y{{location}}%w.";
RandoCheckId randoCheckId = Rando::FindItemPlacement(RI_SONG_SOARING);
CustomMessage::Replace(&entry.msg, "{{location}}",
- Ship_GetSceneName(Rando::StaticData::Checks[randoCheckId].sceneId));
+ Rando::StaticData::GetLocationNameForHint(randoCheckId, false));
CustomMessage::LoadCustomMessageIntoFont(entry);
*loadFromMessageTable = false;
}
diff --git a/mm/2s2h/Rando/ActorBehavior/EnZow.cpp b/mm/2s2h/Rando/ActorBehavior/EnZow.cpp
index 4fa28e7bd..8fbdedc68 100644
--- a/mm/2s2h/Rando/ActorBehavior/EnZow.cpp
+++ b/mm/2s2h/Rando/ActorBehavior/EnZow.cpp
@@ -8,11 +8,11 @@ extern "C" {
}
void ApplyHookshotHint(u16* textId, bool* loadFromMessageTable) {
- std::string msg = "I overheard those Pirates talk about treasure in "
+ std::string msg = "I overheard those Pirates talk about treasure "
"%y{{location}}%w that can help you reach the unreachable!";
RandoCheckId randoCheckId = Rando::FindItemPlacement(RI_HOOKSHOT);
- CustomMessage::Replace(&msg, "{{location}}", Ship_GetSceneName(Rando::StaticData::Checks[randoCheckId].sceneId));
+ CustomMessage::Replace(&msg, "{{location}}", Rando::StaticData::GetLocationNameForHint(randoCheckId, false));
CustomMessage::Entry entry = {
.nextMessageID = (u16)0xFFFF,
diff --git a/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp b/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp
index 6a35728a6..787e2692d 100644
--- a/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp
+++ b/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp
@@ -111,6 +111,7 @@ ImVec4 trackerBG = ImVec4{ 0, 0, 0, 0.5f };
std::map<SceneId, std::set<RandoCheckId>> sceneChecks;
std::vector<SceneId> sortedSceneIds;
std::unordered_map<RandoCheckId, std::string> accessLogicFuncs;
+std::unordered_map<RandoRegionId, SceneId> regionParentSceneMap;
std::set<RandoCheckType> checkTypeFiltersAvailable;
std::set<RandoCheckType> checkTypeFilter;
@@ -170,36 +171,36 @@ void DrawCheckTypeIcon(RandoCheckId randoCheckId) {
checkType == RCTYPE_FREESTANDING ? ImVec4(0.78f, 1, 0.39f, 1) : ImVec4(1, 1, 1, 1), tintColor);
}
+static std::set<SceneId> scenesToCheckParent = {
+ SCENE_KAKUSIANA, SCENE_TAKARAYA, SCENE_BOWLING, SCENE_SONCHONOIE, SCENE_SYATEKI_MIZU, SCENE_YADOYA,
+ SCENE_MILK_BAR, SCENE_AYASHIISHOP, SCENE_YOUSEI_IZUMI, SCENE_DOUJOU, SCENE_8ITEMSHOP, SCENE_BOMYA,
+ SCENE_POSTHOUSE, SCENE_TAKARAKUJI, SCENE_SYATEKI_MORI, SCENE_MAP_SHOP, SCENE_WITCH_SHOP, SCENE_F01C,
+ SCENE_F01_B, SCENE_OMOYA, SCENE_GORONSHOP, SCENE_KAJIYA, SCENE_FISHERMAN, SCENE_LABO,
+ SCENE_BANDROOM, SCENE_TOUGITES, SCENE_MUSICHOUSE,
+};
+
+SceneId GetScrollTargetScene(s32 rawSceneId) {
+ SceneId sceneId = (SceneId)Play_GetOriginalSceneId(rawSceneId);
+ if (scenesToCheckParent.contains(sceneId)) {
+ RandoRegionId regionId = Rando::Logic::GetRegionIdFromEntrance(gSaveContext.save.entrance);
+ if (regionParentSceneMap.contains(regionId)) {
+ return regionParentSceneMap[regionId];
+ }
+ }
+ return sceneId;
+}
+
void initializeSceneChecks() {
sceneChecks.clear();
+ regionParentSceneMap.clear();
checkTypeFiltersAvailable.clear();
checkTypeFiltersAvailable.insert(RCTYPE_UNKNOWN); // Always include the unknown type
- for (auto& [_, randoStaticCheck] : Rando::StaticData::Checks) {
- RandoSaveCheck& randoSaveCheck = RANDO_SAVE_CHECKS[randoStaticCheck.randoCheckId];
- if (!randoSaveCheck.shuffled) {
- continue;
- }
-
- checkTypeFiltersAvailable.insert(randoStaticCheck.randoCheckType);
-
- // Skip Grotto Checks, they will be handled below
- if (randoStaticCheck.sceneId == SCENE_KAKUSIANA) {
- continue;
- }
-
- // Skip Enemy Drops, they will be handled below
- if (randoStaticCheck.randoCheckType == RCTYPE_ENEMY_DROP) {
- continue;
- }
-
- SceneId sceneId = (SceneId)Play_GetOriginalSceneId(randoStaticCheck.sceneId);
- sceneChecks[sceneId].insert(randoStaticCheck.randoCheckId);
- }
-
for (auto& [regionId, staticRegion] : Rando::Logic::Regions) {
- // Handle grottos separately, their checks are assigned to the connected region
- if (staticRegion.sceneId == SCENE_KAKUSIANA) {
+ SceneId sceneId = (SceneId)Play_GetOriginalSceneId(staticRegion.sceneId);
+
+ // For some scenes we want to show them in their parent instead
+ if (scenesToCheckParent.contains(sceneId)) {
RandoRegionId connectedRegionId = RR_MAX;
if (regionId == RR_LONE_PEAK_SHRINE) {
connectedRegionId = RR_LONE_PEAK_SHRINE;
@@ -214,26 +215,17 @@ void initializeSceneChecks() {
continue;
}
}
- SceneId connectedSceneId = Rando::Logic::Regions[connectedRegionId].sceneId;
- for (auto& [randoCheckId, _] : staticRegion.checks) {
- RandoSaveCheck& randoSaveCheck = RANDO_SAVE_CHECKS[randoCheckId];
- if (!randoSaveCheck.shuffled) {
- continue;
- }
- sceneChecks[connectedSceneId].insert(randoCheckId);
- }
- } else {
- // Handle enemy drop seperately, they can be in multiple regions
- for (auto& [randoCheckId, _] : staticRegion.checks) {
- auto& randoStaticCheck = Rando::StaticData::Checks[randoCheckId];
- RandoSaveCheck& randoSaveCheck = RANDO_SAVE_CHECKS[randoCheckId];
- if (!randoSaveCheck.shuffled || randoStaticCheck.randoCheckType != RCTYPE_ENEMY_DROP) {
- continue;
- }
-
- SceneId sceneId = (SceneId)Play_GetOriginalSceneId(staticRegion.sceneId);
- sceneChecks[sceneId].insert(randoCheckId);
+ sceneId = Rando::Logic::Regions[connectedRegionId].sceneId;
+ regionParentSceneMap[regionId] = sceneId;
+ }
+ for (auto& [randoCheckId, _] : staticRegion.checks) {
+ RandoSaveCheck& randoSaveCheck = RANDO_SAVE_CHECKS[randoCheckId];
+ auto& randoStaticCheck = Rando::StaticData::Checks[randoCheckId];
+ if (!randoSaveCheck.shuffled) {
+ continue;
}
+ checkTypeFiltersAvailable.insert(randoStaticCheck.randoCheckType);
+ sceneChecks[sceneId].insert(randoCheckId);
}
}
@@ -356,8 +348,7 @@ void CheckTrackerDrawNonLogicalList() {
continue;
}
- if (CVAR_SHOW_CURRENT_SCENE && Play_GetOriginalSceneId(Rando::StaticData::Checks[checkId].sceneId) !=
- Play_GetOriginalSceneId(gPlayState->sceneId)) {
+ if (CVAR_SHOW_CURRENT_SCENE && sceneId != GetScrollTargetScene(gPlayState->sceneId)) {
continue;
}
@@ -526,7 +517,7 @@ void CheckTrackerWindow::Draw() {
if (!sCheckTrackerFilter.IsActive()) {
if (UIWidgets::Button(ICON_FA_ARROWS_V, UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) {
if (gPlayState) {
- sScrollToTargetScene = Play_GetOriginalSceneId(gPlayState->sceneId);
+ sScrollToTargetScene = GetScrollTargetScene(gPlayState->sceneId);
sScrollToTargetEntrance = gSaveContext.save.entrance;
}
}
@@ -535,7 +526,7 @@ void CheckTrackerWindow::Draw() {
if (UIWidgets::Button(ICON_FA_TIMES, UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) {
sCheckTrackerFilter.Clear();
if (gPlayState) {
- sScrollToTargetScene = Play_GetOriginalSceneId(gPlayState->sceneId);
+ sScrollToTargetScene = GetScrollTargetScene(gPlayState->sceneId);
sScrollToTargetEntrance = gSaveContext.save.entrance;
}
}
@@ -714,7 +705,7 @@ void Init() {
}
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](s8 sceneId, s8 spawnNum) {
if (CVAR_SCROLL_TO_SCENE) {
- sScrollToTargetScene = Play_GetOriginalSceneId(sceneId);
+ sScrollToTargetScene = GetScrollTargetScene(sceneId);
sScrollToTargetEntrance = gSaveContext.save.entrance;
}
});
diff --git a/mm/2s2h/Rando/Logic/Logic.cpp b/mm/2s2h/Rando/Logic/Logic.cpp
index 737e1cbcd..687af5f2d 100644
--- a/mm/2s2h/Rando/Logic/Logic.cpp
+++ b/mm/2s2h/Rando/Logic/Logic.cpp
@@ -11,6 +11,25 @@ std::map<RandoRegionId, RandoRegion> Regions = {};
uint64_t gCurrentRegionTime = 0;
+// GROTTOS
+// grottos are a bit weird, 15 of them share entrances with other grottos, so we have to check an extra field to
+// determine which grotto we're in, and convert the entrance ID to an unused one that we store for logic purposes.
+// ENTRANCE(GROTTOS, 17) = RR_TERMINA_FIELD_COW_GROTTO
+// ENTRANCE(GROTTOS, 18) = RR_TERMINA_FIELD_PILLAR_GROTTO
+// ENTRANCE(GROTTOS, 19) = RR_TERMINA_FIELD_TALL_GRASS_GROTTO
+// ENTRANCE(GROTTOS, 20) = RR_ROAD_TO_SOUTHERN_SWAMP_GROTTO
+// ENTRANCE(GROTTOS, 21) = RR_SOUTHERN_SWAMP_GROTTO
+// ENTRANCE(GROTTOS, 22) = RR_WOODS_OF_MYSTERY_GROTTO
+// ENTRANCE(GROTTOS, 23) = RR_MOUNTAIN_VILLAGE_TUNNEL_GROTTO
+// ENTRANCE(GROTTOS, 24) = RR_PATH_TO_GORON_VILLAGE_RAMP_GROTTO
+// ENTRANCE(GROTTOS, 25) = RR_PATH_TO_SNOWHEAD_GROTTO
+// ENTRANCE(GROTTOS, 26) = RR_GREAT_BAY_COAST_FISHERMAN_GROTTO
+// ENTRANCE(GROTTOS, 27) = RR_GREAT_BAY_COAST_COW_GROTTO
+// ENTRANCE(GROTTOS, 28) = RR_ZORA_CAPE_GROTTO
+// ENTRANCE(GROTTOS, 29) = RR_IKANA_CANYON_GROTTO
+// ENTRANCE(GROTTOS, 30) = RR_IKANA_GRAVEYARD_GROTTO
+// ENTRANCE(GROTTOS, 31) = RR_ROAD_TO_IKANA_GROTTO
+
RandoRegionId GetRegionIdFromEntrance(s32 entrance) {
static std::map<s32, RandoRegionId> entranceToRegionId;
if (entranceToRegionId.empty()) {
@@ -27,8 +46,60 @@ RandoRegionId GetRegionIdFromEntrance(s32 entrance) {
}
}
- if (entranceToRegionId.contains(entrance)) {
- return entranceToRegionId[entrance];
+ s32 modifiedEntrance = entrance;
+ if (entrance == ENTRANCE(GROTTOS, 4)) {
+ switch (gSaveContext.respawn[RESPAWN_MODE_DOWN].entrance) {
+ case ENTRANCE(TERMINA_FIELD, 0):
+ modifiedEntrance = gSaveContext.respawn[RESPAWN_MODE_UNK_3].data == 0x3F ? ENTRANCE(GROTTOS, 19)
+ : ENTRANCE(GROTTOS, 18);
+ break;
+ case ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 0):
+ modifiedEntrance = ENTRANCE(GROTTOS, 20);
+ break;
+ case ENTRANCE(SOUTHERN_SWAMP_POISONED, 0):
+ modifiedEntrance = ENTRANCE(GROTTOS, 21);
+ break;
+ case ENTRANCE(WOODS_OF_MYSTERY, 0):
+ modifiedEntrance = ENTRANCE(GROTTOS, 22);
+ break;
+ case ENTRANCE(MOUNTAIN_VILLAGE_SPRING, 0):
+ modifiedEntrance = ENTRANCE(GROTTOS, 23);
+ break;
+ case ENTRANCE(PATH_TO_GORON_VILLAGE_WINTER, 0):
+ modifiedEntrance = ENTRANCE(GROTTOS, 24);
+ break;
+ case ENTRANCE(PATH_TO_SNOWHEAD, 0):
+ modifiedEntrance = ENTRANCE(GROTTOS, 25);
+ break;
+ case ENTRANCE(GREAT_BAY_COAST, 0):
+ modifiedEntrance = ENTRANCE(GROTTOS, 26);
+ break;
+ case ENTRANCE(ZORA_CAPE, 0):
+ modifiedEntrance = ENTRANCE(GROTTOS, 28);
+ break;
+ case ENTRANCE(IKANA_CANYON, 0):
+ modifiedEntrance = ENTRANCE(GROTTOS, 29);
+ break;
+ case ENTRANCE(IKANA_GRAVEYARD, 0):
+ modifiedEntrance = ENTRANCE(GROTTOS, 30);
+ break;
+ case ENTRANCE(ROAD_TO_IKANA, 0):
+ modifiedEntrance = ENTRANCE(GROTTOS, 31);
+ break;
+ }
+ } else if (entrance == ENTRANCE(GROTTOS, 10)) {
+ switch (gSaveContext.respawn[RESPAWN_MODE_DOWN].entrance) {
+ case ENTRANCE(TERMINA_FIELD, 0):
+ modifiedEntrance = ENTRANCE(GROTTOS, 17);
+ break;
+ case ENTRANCE(GREAT_BAY_COAST, 0):
+ modifiedEntrance = ENTRANCE(GROTTOS, 27);
+ break;
+ }
+ }
+
+ if (entranceToRegionId.contains(modifiedEntrance)) {
+ return entranceToRegionId[modifiedEntrance];
}
return RR_MAX;
diff --git a/mm/2s2h/Rando/Logic/Regions/East.cpp b/mm/2s2h/Rando/Logic/Regions/East.cpp
index f4f975715..98c38e650 100644
--- a/mm/2s2h/Rando/Logic/Regions/East.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/East.cpp
@@ -134,8 +134,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_IKANA_CANYON_GROTTO_GRASS_14, true),
CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)),
},
- .connections = {
- CONNECTION(RR_IKANA_CANYON_LOWER, true), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(IKANA_CANYON, 0), ENTRANCE(GROTTOS, 29), true),
},
};
Regions[RR_IKANA_CANYON_LOWER] = RandoRegion{ .name = "Lower", .sceneId = SCENE_IKANA,
@@ -147,6 +147,7 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ENEMY_DROP_GARO, CanKillEnemy(ACTOR_EN_JSO)),
},
.exits = { // TO FROM
+ EXIT(ENTRANCE(GROTTOS, 29), ENTRANCE(IKANA_CANYON, 0), CAN_USE_ABILITY(SWIM)),
EXIT(ENTRANCE(ROAD_TO_IKANA, 1), ENTRANCE(IKANA_CANYON, 0), true),
/*
TODO : Sakon's Hideout is heavily flag based so we should check for those. Consider what I have in here now to be loose placeholders.
@@ -160,7 +161,6 @@ static RegisterShipInitFunc initFunc([]() {
.connections = {
// Octorok soul not needed; the player can also create ice platforms on the water itself.
CONNECTION(RR_IKANA_CANYON_UPPER, HAS_ITEM(ITEM_HOOKSHOT) && CAN_USE_MAGIC_ARROW(ICE)),
- CONNECTION(RR_IKANA_CANYON_GROTTO, CAN_USE_ABILITY(SWIM)), // TODO: Grotto mapping
},
.events = {
EVENT(RE_ACCESS_BLUE_POTION_REFILL, CUR_UPG_VALUE(UPG_WALLET) >= 1),
@@ -213,8 +213,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_IKANA_GRAVEYARD_GROTTO_GRASS_14, true),
CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)),
},
- .connections = {
- CONNECTION(RR_IKANA_GRAVEYARD_LOWER, true), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(IKANA_GRAVEYARD, 0), ENTRANCE(GROTTOS, 30), true),
},
};
Regions[RR_IKANA_GRAVEYARD_LOWER] = RandoRegion{ .name = "Lower", .sceneId = SCENE_BOTI,
@@ -232,6 +232,7 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ENEMY_DROP_BAD_BAT, CanKillEnemy(ACTOR_EN_BAT) && IS_DAY()), // Day only
},
.exits = { // TO FROM
+ EXIT(ENTRANCE(GROTTOS, 30), ENTRANCE(IKANA_GRAVEYARD, 0), CAN_USE_EXPLOSIVE),
EXIT(ENTRANCE(ROAD_TO_IKANA, 2), ENTRANCE(IKANA_GRAVEYARD, 0), true),
EXIT(ENTRANCE(DAMPES_HOUSE, 0), ONE_WAY_EXIT, HAS_ITEM(ITEM_MASK_CAPTAIN) && IS_NIGHT3()), // Day 3 hole
EXIT(ENTRANCE(BENEATH_THE_GRAVERYARD, 0), ENTRANCE(IKANA_GRAVEYARD, 2), HAS_ITEM(ITEM_MASK_CAPTAIN) && IS_NIGHT2()), // Day 2 hole
@@ -239,7 +240,6 @@ static RegisterShipInitFunc initFunc([]() {
},
.connections = {
CONNECTION(RR_IKANA_GRAVEYARD_UPPER, CAN_PLAY_SONG(SONATA)),
- CONNECTION(RR_IKANA_GRAVEYARD_GROTTO, CAN_USE_EXPLOSIVE), // TODO: Grotto mapping
},
.oneWayEntrances = {
ENTRANCE(IKANA_GRAVEYARD, 4), // Exiting Dampe's house
@@ -306,10 +306,10 @@ static RegisterShipInitFunc initFunc([]() {
},
.exits = { // TO FROM
EXIT(ENTRANCE(TERMINA_FIELD, 4), ENTRANCE(ROAD_TO_IKANA, 0), true),
+ EXIT(ENTRANCE(GROTTOS, 31), ENTRANCE(ROAD_TO_IKANA, 0), CAN_BE_GORON),
},
.connections = {
CONNECTION(RR_ROAD_TO_IKANA_BELOW_LEDGE, CAN_RIDE_EPONA),
- CONNECTION(RR_ROAD_TO_IKANA_GROTTO, CAN_BE_GORON), // TODO: Grotto mapping
},
};
Regions[RR_ROAD_TO_IKANA_GROTTO] = RandoRegion{ .name = "Road to Ikana Grotto", .sceneId = SCENE_KAKUSIANA,
@@ -331,8 +331,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ROAD_TO_IKANA_GROTTO_GRASS_14, true),
CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)),
},
- .connections = {
- CONNECTION(RR_ROAD_TO_IKANA_FIELD_SIDE, true), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(ROAD_TO_IKANA, 0), ENTRANCE(GROTTOS, 31), true),
},
};
Regions[RR_SAKON_HIDEOUT] = RandoRegion{ .sceneId = SCENE_SECOM,
diff --git a/mm/2s2h/Rando/Logic/Regions/North.cpp b/mm/2s2h/Rando/Logic/Regions/North.cpp
index ad4599860..b7e38e41f 100644
--- a/mm/2s2h/Rando/Logic/Regions/North.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/North.cpp
@@ -219,8 +219,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_MOUNTAIN_VILLAGE_TUNNEL_GROTTO_GRASS_14, true),
CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)),
},
- .connections = {
- CONNECTION(RR_MOUNTAIN_VILLAGE, true), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 0), ENTRANCE(GROTTOS, 23), true),
},
};
Regions[RR_MOUNTAIN_VILLAGE] = RandoRegion{ .sceneId = SCENE_10YUKIYAMANOMURA,
@@ -286,6 +286,7 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ENEMY_DROP_WOLFOS, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_WF) && SECOND_DAY()), // Day 2 only
},
.exits = { // TO FROM
+ EXIT(ENTRANCE(GROTTOS, 23), ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 0), RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]),
EXIT(ENTRANCE(MOUNTAIN_SMITHY, 0), ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 1), true),
EXIT(ENTRANCE(PATH_TO_GORON_VILLAGE_WINTER, 0), ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 2), true),
// TODO: When it's spring you need goron mask or zora mask instead?
@@ -293,9 +294,6 @@ static RegisterShipInitFunc initFunc([]() {
EXIT(ENTRANCE(PATH_TO_SNOWHEAD, 0), ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 4), true),
EXIT(ENTRANCE(PATH_TO_MOUNTAIN_VILLAGE, 1), ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 6), true),
},
- .connections = {
- CONNECTION(RR_MOUNTAIN_VILLAGE_TUNNEL_GROTTO, RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE]), // TODO: Grotto mapping
- },
.oneWayEntrances = {
ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 8), // From Song of Soaring
}
@@ -305,7 +303,7 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_TWIN_ISLANDS_FROZEN_GROTTO_CHEST, CAN_USE_EXPLOSIVE),
},
.exits = { // TO FROM
- EXIT(ENTRANCE(PATH_TO_GORON_VILLAGE_WINTER, 0), ENTRANCE(GROTTOS, 5), true), // TODO: Grotto mapping
+ EXIT(ENTRANCE(PATH_TO_GORON_VILLAGE_WINTER, 0), ENTRANCE(GROTTOS, 5), true),
},
.events = {
EVENT(RE_ACCESS_HOT_SPRING_WATER, true),
@@ -330,8 +328,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_TWIN_ISLANDS_RAMP_GROTTO_GRASS_14, true),
CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)),
},
- .connections = {
- CONNECTION(RR_PATH_TO_GORON_VILLAGE, true), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(PATH_TO_GORON_VILLAGE_WINTER, 0), ENTRANCE(GROTTOS, 24), true),
},
};
Regions[RR_PATH_TO_GORON_VILLAGE] = RandoRegion{ .sceneId = SCENE_17SETUGEN,
@@ -389,16 +387,14 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ENEMY_DROP_SNAPPER, CanKillEnemy(ACTOR_OBJ_SNOWBALL) && CanKillEnemy(ACTOR_EN_KAME)),
},
.exits = { // TO FROM
- EXIT(ENTRANCE(GROTTOS, 5), ENTRANCE(PATH_TO_GORON_VILLAGE_WINTER, 0), RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] || CAN_USE_MAGIC_ARROW(FIRE)), // TODO: Grotto mapping Hot spring
+ EXIT(ENTRANCE(GROTTOS, 5), ENTRANCE(PATH_TO_GORON_VILLAGE_WINTER, 0), RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] || CAN_USE_MAGIC_ARROW(FIRE)),
+ // TODO: This can be reached in entrance rando if coming from Goron Racetrack
+ EXIT(ENTRANCE(GROTTOS, 24), ENTRANCE(PATH_TO_GORON_VILLAGE_WINTER, 0), CAN_USE_EXPLOSIVE && (CAN_BE_GORON || CAN_HOOK_SCARECROW)),
EXIT(ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 2), ENTRANCE(PATH_TO_GORON_VILLAGE_WINTER, 0), true),
EXIT(ENTRANCE(GORON_VILLAGE_WINTER, 0), ENTRANCE(PATH_TO_GORON_VILLAGE_WINTER, 1), true),
// This could also be opened by completing Medigoron's test without actually getting the Powder Keg as a item. Not sure what the flag for that is however.
EXIT(ENTRANCE(GORON_RACETRACK, 0), ENTRANCE(PATH_TO_GORON_VILLAGE_WINTER, 2), HAS_ITEM(ITEM_POWDER_KEG) && CAN_BE_GORON),
},
- .connections = {
- // TODO: This can be reached in entrance rando if coming from Goron Racetrack
- CONNECTION(RR_PATH_TO_GORON_VILLAGE_RAMP_GROTTO, CAN_USE_EXPLOSIVE && (CAN_BE_GORON || CAN_HOOK_SCARECROW)), // TODO: Grotto mapping
- },
.events = {
EVENT(RE_ACCESS_PICTOGRAPH_TINGLE, HAS_ITEM(ITEM_PICTOGRAPH_BOX)),
},
@@ -468,8 +464,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_PATH_TO_SNOWHEAD_GROTTO_GRASS_14, true),
CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)),
},
- .connections = {
- CONNECTION(RR_PATH_TO_SNOWHEAD_UPPER, true), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(PATH_TO_SNOWHEAD, 0), ENTRANCE(GROTTOS, 25), true),
},
};
Regions[RR_PATH_TO_SNOWHEAD_LOWER] = RandoRegion{ .sceneId = SCENE_14YUKIDAMANOMITI,
@@ -509,10 +505,10 @@ static RegisterShipInitFunc initFunc([]() {
},
.exits = { // TO FROM
EXIT(ENTRANCE(SNOWHEAD, 0), ENTRANCE(PATH_TO_SNOWHEAD, 1), true),
+ EXIT(ENTRANCE(GROTTOS, 25), ENTRANCE(PATH_TO_SNOWHEAD, 0), CAN_USE_EXPLOSIVE),
},
.connections = {
CONNECTION(RR_PATH_TO_SNOWHEAD_MIDDLE, CAN_BE_GORON && HAS_MAGIC),
- CONNECTION(RR_PATH_TO_SNOWHEAD_GROTTO, CAN_USE_EXPLOSIVE), // TODO: Grotto mapping
},
};
Regions[RR_SNOWHEAD_GREAT_FAIRY_FOUNTAIN] = RandoRegion{ .sceneId = SCENE_YOUSEI_IZUMI,
diff --git a/mm/2s2h/Rando/Logic/Regions/South.cpp b/mm/2s2h/Rando/Logic/Regions/South.cpp
index 5e9a25499..c165e9765 100644
--- a/mm/2s2h/Rando/Logic/Regions/South.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/South.cpp
@@ -73,7 +73,7 @@ static RegisterShipInitFunc initFunc([]() {
// TODO: Bean salesman check
},
.exits = { // TO FROM
- EXIT(ENTRANCE(DEKU_PALACE, 9), ENTRANCE(GROTTOS, 12), true), // TODO: Grotto mapping
+ EXIT(ENTRANCE(DEKU_PALACE, 9), ENTRANCE(GROTTOS, 12), true),
},
};
Regions[RR_DEKU_PALACE_INSIDE_LOWER] = RandoRegion{ .name = "Inside, Lower", .sceneId = SCENE_22DEKUCITY,
@@ -106,7 +106,7 @@ static RegisterShipInitFunc initFunc([]() {
},
.exits = { // TO FROM
EXIT(ENTRANCE(DEKU_KINGS_CHAMBER, 0), ENTRANCE(DEKU_PALACE, 2), true),
- EXIT(ENTRANCE(GROTTOS, 12), ENTRANCE(DEKU_PALACE, 9), true), // TODO: Grotto mapping
+ EXIT(ENTRANCE(GROTTOS, 12), ENTRANCE(DEKU_PALACE, 9), true),
},
.connections = {
CONNECTION(RR_DEKU_PALACE_OUTSIDE, true),
@@ -222,8 +222,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ROAD_TO_SOUTHERN_SWAMP_GROTTO_GRASS_14, true),
CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)),
},
- .connections = {
- CONNECTION(RR_ROAD_TO_SOUTHERN_SWAMP, true), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 0), ENTRANCE(GROTTOS, 20), true),
},
};
Regions[RR_ROAD_TO_SOUTHERN_SWAMP] = RandoRegion{ .sceneId = SCENE_24KEMONOMITI,
@@ -264,12 +264,10 @@ static RegisterShipInitFunc initFunc([]() {
},
.exits = { // TO FROM
EXIT(ENTRANCE(TERMINA_FIELD, 1), ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 0), true),
+ EXIT(ENTRANCE(GROTTOS, 20), ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 0), true),
EXIT(ENTRANCE(SOUTHERN_SWAMP_POISONED, 0), ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 1), true),
EXIT(ENTRANCE(SWAMP_SHOOTING_GALLERY, 0), ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 2), BEFORE(TIME_NIGHT1_PM_10_00) || BETWEEN(TIME_DAY2_AM_06_00, TIME_NIGHT2_PM_10_00) || BETWEEN(TIME_DAY3_AM_06_00, TIME_NIGHT3_PM_10_00)),
},
- .connections = {
- CONNECTION(RR_ROAD_TO_SOUTHERN_SWAMP_GROTTO, true), // TODO: Grotto mapping
- },
.events = {
EVENT(RE_ACCESS_PICTOGRAPH_TINGLE, HAS_ITEM(ITEM_PICTOGRAPH_BOX)),
EVENT(RE_ACCESS_SPRING_WATER, true),
@@ -294,8 +292,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_SOUTHERN_SWAMP_GROTTO_GRASS_14, true),
CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)),
},
- .connections = {
- CONNECTION(RR_SOUTHERN_SWAMP_SOUTH, true), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(SOUTHERN_SWAMP_POISONED, 0), ENTRANCE(GROTTOS, 21), true),
},
};
Regions[RR_SOUTHERN_SWAMP_NORTH] = RandoRegion{ .name = "North Tourist Section", .sceneId = SCENE_20SICHITAI,
@@ -434,13 +432,13 @@ static RegisterShipInitFunc initFunc([]() {
};
Regions[RR_SOUTHERN_SWAMP_SOUTH] = RandoRegion{ .name = "South Section", .sceneId = SCENE_20SICHITAI,
.exits = { // TO FROM
+ EXIT(ENTRANCE(GROTTOS, 21), ENTRANCE(SOUTHERN_SWAMP_POISONED, 0), CAN_BE_DEKU || (RANDO_EVENTS[RE_CLEARED_WOODFALL_TEMPLE] && CAN_TRAVERSE_WAIST_DEEP_WATER)),
EXIT(ENTRANCE(DEKU_PALACE, 0), ENTRANCE(SOUTHERN_SWAMP_POISONED, 3), true),
EXIT(ENTRANCE(SWAMP_SPIDER_HOUSE, 0), ENTRANCE(SOUTHERN_SWAMP_POISONED, 8), CAN_LIGHT_TORCH_NEAR_ANOTHER && (CAN_BE_DEKU || (RANDO_EVENTS[RE_CLEARED_WOODFALL_TEMPLE] && (CAN_USE_ABILITY(SWIM) || CAN_BE_ZORA)))),
},
.connections = {
CONNECTION(RR_SOUTHERN_SWAMP_NORTH, CanGetPastBigOcto()),
CONNECTION(RR_SOUTHERN_SWAMP_NEAR_FLOWERS, CanGetPastBigOctoWithoutBoat()),
- CONNECTION(RR_SOUTHERN_SWAMP_GROTTO, CAN_BE_DEKU || (RANDO_EVENTS[RE_CLEARED_WOODFALL_TEMPLE] && CAN_TRAVERSE_WAIST_DEEP_WATER)), // TODO: Grotto mapping
CONNECTION(RR_SOUTHERN_SWAMP_SOUTH_UPPER, RANDO_EVENTS[RE_CLEARED_WOODFALL_TEMPLE] && CAN_TRAVERSE_WAIST_DEEP_WATER), // Climbable vines, only in cleared swamp
},
.events = {
@@ -557,8 +555,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_WOODS_OF_MYSTERY_GROTTO_GRASS_14, true),
CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)),
},
- .connections = {
- CONNECTION(RR_WOODS_OF_MYSTERY, SECOND_DAY()), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(WOODS_OF_MYSTERY, 0), ENTRANCE(GROTTOS, 22), true),
},
};
Regions[RR_WOODS_OF_MYSTERY] = RandoRegion{ .sceneId = SCENE_26SARUNOMORI,
@@ -589,11 +587,9 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ENEMY_DROP_SNAPPER, CAN_BE_DEKU && CanKillEnemy(ACTOR_EN_KAME)),
},
.exits = { // TO FROM
+ EXIT(ENTRANCE(GROTTOS, 22), ENTRANCE(WOODS_OF_MYSTERY, 0), SECOND_DAY()),
EXIT(ENTRANCE(SOUTHERN_SWAMP_POISONED, 7), ENTRANCE(WOODS_OF_MYSTERY, 0), true),
},
- .connections = {
- CONNECTION(RR_WOODS_OF_MYSTERY_GROTTO, SECOND_DAY()), // TODO: Grotto mapping
- },
.events = {
EVENT(RE_SAVED_KOUME, HAS_BOTTLE && (CAN_ACCESS(RED_POTION_REFILL) || CAN_ACCESS(BLUE_POTION_REFILL))),
},
diff --git a/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp b/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp
index 2f02b917d..62fadf8c7 100644
--- a/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/TerminaField.cpp
@@ -31,7 +31,7 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ENEMY_DROP_BIO_DEKU_BABA, CanKillEnemy(ACTOR_BOSS_05)),
},
.exits = { // TO FROM
- EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 11), true), // TODO: Grotto mapping
+ EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 11), true),
},
};
Regions[RR_TERMINA_FIELD_COW_GROTTO] = RandoRegion{ .name = "Termina Field Cow", .sceneId = SCENE_KAKUSIANA,
@@ -112,8 +112,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_TERMINA_FIELD_COW_GROTTO_GRASS_72, true),
CHECK(RC_ENEMY_DROP_GIANT_BEE, CAN_USE_PROJECTILE && CanKillEnemy(ACTOR_EN_BEE)), // In a beehive
},
- .connections = {
- CONNECTION(RR_TERMINA_FIELD, true), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 17), true),
},
};
Regions[RR_TERMINA_FIELD_DODONGO_GROTTO] = RandoRegion{ .name = "Termina Field Dodongo", .sceneId = SCENE_KAKUSIANA,
@@ -122,7 +122,7 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ENEMY_DROP_DODONGO, CanKillEnemy(ACTOR_EN_DODONGO)),
},
.exits = { // TO FROM
- EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 7), true), // TODO: Grotto mapping
+ EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 7), true),
},
};
Regions[RR_TERMINA_FIELD_GOSSIP_STONE_GROTTO_1] = RandoRegion{ .name = "Termina Field Gossip Stone #1", .sceneId = SCENE_KAKUSIANA,
@@ -131,7 +131,7 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ENEMY_DROP_SKULLWALLTULA, CanKillEnemy(ACTOR_EN_SW)),
},
.exits = { // TO FROM
- EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 1), true), // TODO: Grotto mapping
+ EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 1), true),
},
.events = {
// TODO: gSaveContext.save.saveInfo.unk_EA0 for gossip stone check
@@ -139,7 +139,7 @@ static RegisterShipInitFunc initFunc([]() {
};
Regions[RR_TERMINA_FIELD_GOSSIP_STONE_GROTTO_2] = RandoRegion{ .name = "Termina Field Gossip Stone #2", .sceneId = SCENE_KAKUSIANA,
.exits = { // TO FROM
- EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 3), true), // TODO: Grotto mapping
+ EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 3), true),
},
.events = {
// TODO: gSaveContext.save.saveInfo.unk_EA0 for gossip stone check
@@ -156,7 +156,7 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ENEMY_DROP_GIANT_BEE, CAN_USE_PROJECTILE && CanKillEnemy(ACTOR_EN_BEE)), // In a beehive
},
.exits = { // TO FROM
- EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 0), true), // TODO: Grotto mapping
+ EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 0), true),
},
.events = {
// TODO: gSaveContext.save.saveInfo.unk_EA0 for gossip stone check
@@ -171,7 +171,7 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_TERMINA_FIELD_GOSSIP_STONE_GROTTO_4_GRASS_05, true),
},
.exits = { // TO FROM
- EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 2), true), // TODO: Grotto mapping
+ EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 2), true),
},
.events = {
// TODO: gSaveContext.save.saveInfo.unk_EA0 for gossip stone check
@@ -195,7 +195,7 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ENEMY_DROP_PEAHAT, CanKillEnemy(ACTOR_EN_PEEHAT)),
},
.exits = { // TO FROM
- EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 13), true), // TODO: Grotto mapping
+ EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 13), true),
},
};
Regions[RR_TERMINA_FIELD_PILLAR_GROTTO] = RandoRegion{ .name = "Termina Field Pillar", .sceneId = SCENE_KAKUSIANA,
@@ -217,8 +217,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_TERMINA_FIELD_PILLAR_GROTTO_GRASS_14, true),
CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)),
},
- .connections = {
- CONNECTION(RR_TERMINA_FIELD, true), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 18), true),
},
};
Regions[RR_TERMINA_FIELD_SCRUB_GROTTO] = RandoRegion{ .name = "Termina Field Scrub", .sceneId = SCENE_KAKUSIANA,
@@ -228,7 +228,7 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_TERMINA_FIELD_SCRUB_POT, true),
},
.exits = { // TO FROM
- EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 9), true), // TODO: Grotto mapping
+ EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 9), true),
},
};
Regions[RR_TERMINA_FIELD_TALL_GRASS_GROTTO] = RandoRegion{ .name = "Termina Field Tall Grass", .sceneId = SCENE_KAKUSIANA,
@@ -250,8 +250,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_TERMINA_FIELD_TALL_GRASS_GROTTO_GRASS_14, true),
CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)),
},
- .connections = {
- CONNECTION(RR_TERMINA_FIELD, true), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(TERMINA_FIELD, 0), ENTRANCE(GROTTOS, 19), true),
},
};
Regions[RR_TERMINA_FIELD] = RandoRegion{ .sceneId = SCENE_00KEIKOKU,
@@ -513,14 +513,17 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ENEMY_DROP_TAKKURI, CanKillEnemy(ACTOR_EN_THIEFBIRD)),
},
.exits = { // TO FROM
- EXIT(ENTRANCE(GROTTOS, 0), ENTRANCE(TERMINA_FIELD, 0), CAN_USE_EXPLOSIVE || CAN_BE_GORON), // TODO: Grotto mapping Gossip Stone #3
- EXIT(ENTRANCE(GROTTOS, 1), ENTRANCE(TERMINA_FIELD, 0), true), // TODO: Grotto mapping Gossip Stone #1
- EXIT(ENTRANCE(GROTTOS, 2), ENTRANCE(TERMINA_FIELD, 0), true), // TODO: Grotto mapping Gossip Stone #4
- EXIT(ENTRANCE(GROTTOS, 3), ENTRANCE(TERMINA_FIELD, 0), true), // TODO: Grotto mapping Gossip Stone #2
- EXIT(ENTRANCE(GROTTOS, 7), ENTRANCE(TERMINA_FIELD, 0), true), // TODO: Grotto mapping Dodongo
- EXIT(ENTRANCE(GROTTOS, 9), ENTRANCE(TERMINA_FIELD, 0), true), // TODO: Grotto mapping Scrub
- EXIT(ENTRANCE(GROTTOS, 11), ENTRANCE(TERMINA_FIELD, 0), CAN_USE_EXPLOSIVE || CAN_BE_GORON), // TODO: Grotto mapping bio baba grotto
- EXIT(ENTRANCE(GROTTOS, 13), ENTRANCE(TERMINA_FIELD, 0), true), // TODO: Grotto mapping peahat grotto
+ EXIT(ENTRANCE(GROTTOS, 0), ENTRANCE(TERMINA_FIELD, 0), CAN_USE_EXPLOSIVE || CAN_BE_GORON), // Gossip Stone #3
+ EXIT(ENTRANCE(GROTTOS, 1), ENTRANCE(TERMINA_FIELD, 0), true), // Gossip Stone #1
+ EXIT(ENTRANCE(GROTTOS, 2), ENTRANCE(TERMINA_FIELD, 0), true), // Gossip Stone #4
+ EXIT(ENTRANCE(GROTTOS, 3), ENTRANCE(TERMINA_FIELD, 0), true), // Gossip Stone #2
+ EXIT(ENTRANCE(GROTTOS, 7), ENTRANCE(TERMINA_FIELD, 0), true), // Dodongo
+ EXIT(ENTRANCE(GROTTOS, 9), ENTRANCE(TERMINA_FIELD, 0), true), // Scrub
+ EXIT(ENTRANCE(GROTTOS, 11), ENTRANCE(TERMINA_FIELD, 0), CAN_USE_EXPLOSIVE || CAN_BE_GORON), // bio baba grotto
+ EXIT(ENTRANCE(GROTTOS, 13), ENTRANCE(TERMINA_FIELD, 0), true), // peahat grotto
+ EXIT(ENTRANCE(GROTTOS, 17), ENTRANCE(TERMINA_FIELD, 0), CAN_USE_EXPLOSIVE), // Cow grotto
+ EXIT(ENTRANCE(GROTTOS, 18), ENTRANCE(TERMINA_FIELD, 0), true), // Pillar grotto
+ EXIT(ENTRANCE(GROTTOS, 19), ENTRANCE(TERMINA_FIELD, 0), true), // Tall Grass grotto
EXIT(ENTRANCE(WEST_CLOCK_TOWN, 0), ENTRANCE(TERMINA_FIELD, 0), true),
EXIT(ENTRANCE(ROAD_TO_SOUTHERN_SWAMP, 0), ENTRANCE(TERMINA_FIELD, 1), true),
EXIT(ENTRANCE(ROAD_TO_IKANA, 0), ENTRANCE(TERMINA_FIELD, 4), true),
@@ -533,9 +536,6 @@ static RegisterShipInitFunc initFunc([]() {
CONNECTION(RR_TERMINA_FIELD_BEFORE_PATH_TO_MOUNTAIN_VILLAGE, HAS_ITEM(ITEM_BOW)),
CONNECTION(RR_TERMINA_FIELD_BEFORE_GREAT_BAY_COAST, CAN_PLAY_SONG(EPONA)),
CONNECTION(RR_ASTRAL_OBSERVATORY_OUTSIDE, CAN_BE_DEKU),
- CONNECTION(RR_TERMINA_FIELD_COW_GROTTO, CAN_USE_EXPLOSIVE), // TODO: Grotto mapping
- CONNECTION(RR_TERMINA_FIELD_PILLAR_GROTTO, true), // TODO: Grotto mapping
- CONNECTION(RR_TERMINA_FIELD_TALL_GRASS_GROTTO, true), // TODO: Grotto mapping
},
};
}, {});
diff --git a/mm/2s2h/Rando/Logic/Regions/West.cpp b/mm/2s2h/Rando/Logic/Regions/West.cpp
index a2725f8e8..6ff244133 100644
--- a/mm/2s2h/Rando/Logic/Regions/West.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/West.cpp
@@ -94,9 +94,9 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_GREAT_BAY_COAST_COW_GROTTO_GRASS_72, true),
CHECK(RC_ENEMY_DROP_GIANT_BEE, CAN_USE_PROJECTILE && CanKillEnemy(ACTOR_EN_BEE)), // In a beehive
},
- .connections = {
- CONNECTION(RR_GREAT_BAY_COAST_CLIFFSIDE, true), // TODO: Grotto mapping
- },
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(GREAT_BAY_COAST, 0), ENTRANCE(GROTTOS, 27), true),
+ }
};
Regions[RR_GREAT_BAY_COAST_FISHERMAN_GROTTO] = RandoRegion{ .name = "Great Bay Coast Fisherman Grotto", .sceneId = SCENE_KAKUSIANA,
.checks = {
@@ -117,8 +117,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_GREAT_BAY_COAST_FISHERMAN_GROTTO_GRASS_14, true),
CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)),
},
- .connections = {
- CONNECTION(RR_GREAT_BAY_COAST, true), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(GREAT_BAY_COAST, 0), ENTRANCE(GROTTOS, 26), true),
},
};
Regions[RR_GREAT_BAY_COAST] = RandoRegion{ .sceneId = SCENE_30GYOSON,
@@ -144,10 +144,10 @@ static RegisterShipInitFunc initFunc([]() {
EXIT(ENTRANCE(FISHERMANS_HUT, 0), ENTRANCE(GREAT_BAY_COAST, 4), true),
EXIT(ENTRANCE(PIRATES_FORTRESS_EXTERIOR, 0), ENTRANCE(GREAT_BAY_COAST, 5), CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)),
EXIT(ENTRANCE(OCEANSIDE_SPIDER_HOUSE, 0), ENTRANCE(GREAT_BAY_COAST, 8), true),
+ EXIT(ENTRANCE(GROTTOS, 26), ENTRANCE(GREAT_BAY_COAST, 0), true),
},
.connections = {
- CONNECTION(RR_GREAT_BAY_COAST_CLIFFSIDE, CAN_USE_ABILITY(SWIM)), // TODO: Grotto mapping
- CONNECTION(RR_GREAT_BAY_COAST_FISHERMAN_GROTTO, true), // TODO: Grotto mapping
+ CONNECTION(RR_GREAT_BAY_COAST_CLIFFSIDE, CAN_USE_ABILITY(SWIM)),
CONNECTION(RR_GREAT_BAY_COAST_MARINE_LAB_EXTERIOR, CAN_USE_ABILITY(SWIM)),
CONNECTION(RR_GREAT_BAY_COAST_PIRATE_LEDGE, CAN_USE_ABILITY(SWIM)),
},
@@ -195,8 +195,10 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_GREAT_BAY_COAST_LEDGE_POT_02, HAS_ITEM(ITEM_HOOKSHOT)),
CHECK(RC_GREAT_BAY_COAST_LEDGE_POT_03, HAS_ITEM(ITEM_HOOKSHOT)),
},
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(GROTTOS, 27), ENTRANCE(GREAT_BAY_COAST, 0), CAN_HOOK_SCARECROW && CAN_GROW_BEAN_PLANT),
+ },
.connections = {
- CONNECTION(RR_GREAT_BAY_COAST_COW_GROTTO, CAN_HOOK_SCARECROW && CAN_GROW_BEAN_PLANT), // TODO: Grotto mapping
CONNECTION(RR_GREAT_BAY_COAST, CAN_USE_ABILITY(SWIM)),
CONNECTION(RR_GREAT_BAY_COAST_MINIGAME_PLATFORMS, RANDO_EVENTS[RE_CLEARED_GREAT_BAY_TEMPLE] && (HAS_ITEM(ITEM_HOOKSHOT) || CAN_USE_MAGIC_ARROW(ICE))),
},
@@ -314,8 +316,8 @@ static RegisterShipInitFunc initFunc([]() {
CHECK(RC_ZORA_CAPE_GROTTO_GRASS_14, true),
CHECK(RC_ENEMY_DROP_MINI_BABA, CanKillEnemy(ACTOR_EN_KAREBABA)),
},
- .connections = {
- CONNECTION(RR_ZORA_CAPE, true), // TODO: Grotto mapping
+ .exits = { // TO FROM
+ EXIT(ENTRANCE(ZORA_CAPE, 0), ENTRANCE(GROTTOS, 28), true),
},
};
Regions[RR_ZORA_CAPE_OUTSIDE_FAIRY_FOUNTAIN] = RandoRegion{ .sceneId = SCENE_31MISAKI,
@@ -347,12 +349,12 @@ static RegisterShipInitFunc initFunc([]() {
},
.exits = { // TO FROM
EXIT(ENTRANCE(GREAT_BAY_COAST, 1), ENTRANCE(ZORA_CAPE, 0), true),
+ EXIT(ENTRANCE(GROTTOS, 28), ENTRANCE(ZORA_CAPE, 0), CAN_USE_EXPLOSIVE || CAN_BE_GORON),
EXIT(ENTRANCE(ZORA_HALL, 0), ENTRANCE(ZORA_CAPE, 1), CAN_BE_ZORA && CAN_USE_ABILITY(SWIM)),
EXIT(ENTRANCE(WATERFALL_RAPIDS, 0), ENTRANCE(ZORA_CAPE, 4), HAS_ITEM(ITEM_HOOKSHOT)),
},
.connections = {
CONNECTION(RR_ZORA_CAPE_BEFORE_GREAT_BAY_TEMPLE, CAN_BE_ZORA),
- CONNECTION(RR_ZORA_CAPE_GROTTO, CAN_USE_EXPLOSIVE || CAN_BE_GORON), // TODO: Grotto mapping
CONNECTION(RR_ZORA_CAPE_OUTSIDE_FAIRY_FOUNTAIN, HAS_ITEM(ITEM_HOOKSHOT)),
}
};
diff --git a/mm/2s2h/Rando/MiscBehavior/SariasSongHint.cpp b/mm/2s2h/Rando/MiscBehavior/SariasSongHint.cpp
index c41cfbf3c..a85302580 100644
--- a/mm/2s2h/Rando/MiscBehavior/SariasSongHint.cpp
+++ b/mm/2s2h/Rando/MiscBehavior/SariasSongHint.cpp
@@ -120,7 +120,7 @@ void Rando::MiscBehavior::SariasSongHint() {
"... You need my help?\x10 Alright but just this once. Search %y{{location}}%g, you will "
"find what you need. Hurry now!";
CustomMessage::Replace(&entry.msg, "{{location}}",
- Ship_GetSceneName(Rando::StaticData::Checks[randoCheckId].sceneId));
+ Rando::StaticData::GetLocationNameForHint(randoCheckId, true));
Rando::RemoveItem(RI_SONG_SARIA);
}
diff --git a/mm/2s2h/Rando/StaticData/Checks.cpp b/mm/2s2h/Rando/StaticData/Checks.cpp
index 74d625a89..b4123be1d 100644
--- a/mm/2s2h/Rando/StaticData/Checks.cpp
+++ b/mm/2s2h/Rando/StaticData/Checks.cpp
@@ -1,6 +1,9 @@
#include "StaticData.h"
#include "ShipUtils.h"
+#include "2s2h/CustomMessage/CustomMessage.h"
+#include "2s2h/Rando/Logic/Logic.h"
+
extern "C" {
s16 Play_GetOriginalSceneId(s16 sceneId);
}
@@ -2386,6 +2389,71 @@ void PopulateCheckNames() {
}
}
+static std::set<SceneId> scenesToCheckParent = {
+ SCENE_KAKUSIANA, SCENE_TAKARAYA, SCENE_BOWLING, SCENE_SONCHONOIE, SCENE_SYATEKI_MIZU, SCENE_YADOYA,
+ SCENE_MILK_BAR, SCENE_AYASHIISHOP, SCENE_YOUSEI_IZUMI, SCENE_DOUJOU, SCENE_8ITEMSHOP, SCENE_BOMYA,
+ SCENE_POSTHOUSE, SCENE_TAKARAKUJI, SCENE_SYATEKI_MORI, SCENE_MAP_SHOP, SCENE_WITCH_SHOP, SCENE_F01C,
+ SCENE_F01_B, SCENE_OMOYA, SCENE_GORONSHOP, SCENE_KAJIYA, SCENE_FISHERMAN, SCENE_LABO,
+ SCENE_BANDROOM, SCENE_TOUGITES, SCENE_MUSICHOUSE,
+};
+static std::unordered_map<RandoCheckId, std::string> checkLocationHints;
+
+std::string GetLocationNameForHint(RandoCheckId randoCheckId, bool exact) {
+ if (checkLocationHints.empty()) {
+ for (auto& [regionId, staticRegion] : Rando::Logic::Regions) {
+ SceneId sceneId = (SceneId)Play_GetOriginalSceneId(staticRegion.sceneId);
+
+ // Handle grottos separately, their checks are assigned to the connected region
+ if (scenesToCheckParent.contains(sceneId)) {
+ RandoRegionId connectedRegionId = RR_MAX;
+ if (regionId == RR_LONE_PEAK_SHRINE) {
+ connectedRegionId = RR_LONE_PEAK_SHRINE;
+ } else {
+ for (auto& [entrance, _] : staticRegion.exits) {
+ connectedRegionId = Rando::Logic::GetRegionIdFromEntrance(entrance);
+ }
+ for (auto& [regionId, _] : staticRegion.connections) {
+ connectedRegionId = regionId;
+ }
+ if (connectedRegionId == RR_MAX) {
+ continue;
+ }
+ }
+ sceneId = Rando::Logic::Regions[connectedRegionId].sceneId;
+ }
+ for (auto& [regionRandoCheckId, _] : staticRegion.checks) {
+ checkLocationHints[regionRandoCheckId] = Ship_GetSceneName(sceneId);
+ // Remove some redundant info from the scene names to make hints cleaner
+ CustomMessage::Replace(&checkLocationHints[regionRandoCheckId], " (spring)", "");
+ CustomMessage::Replace(&checkLocationHints[regionRandoCheckId], " (poison)", "");
+ CustomMessage::Replace(&checkLocationHints[regionRandoCheckId], " (winter)", "");
+ CustomMessage::Replace(&checkLocationHints[regionRandoCheckId], " (Clear)", "");
+ CustomMessage::Replace(&checkLocationHints[regionRandoCheckId], " & Grottos", "");
+ }
+ }
+ }
+
+ if (randoCheckId == RC_UNKNOWN) {
+ return "in an Unknown Location";
+ }
+
+ auto& check = Checks[randoCheckId];
+
+ if (exact) {
+ return "at " + CheckNames[randoCheckId];
+ }
+
+ if (check.randoCheckType == RCTYPE_ENEMY_DROP) {
+ return "within a fomidable foe";
+ }
+
+ if (checkLocationHints.contains(randoCheckId)) {
+ return "in " + checkLocationHints[randoCheckId];
+ }
+
+ return "in an Unknown Location";
+}
+
} // namespace StaticData
} // namespace Rando
diff --git a/mm/2s2h/Rando/StaticData/StaticData.h b/mm/2s2h/Rando/StaticData/StaticData.h
index 1bf25dec8..d67d88fb6 100644
--- a/mm/2s2h/Rando/StaticData/StaticData.h
+++ b/mm/2s2h/Rando/StaticData/StaticData.h
@@ -33,6 +33,7 @@ extern std::array<std::string, RC_MAX> CheckNames;
RandoStaticCheck GetCheckFromFlag(FlagType flagType, s32 flag, s16 sceneId = SCENE_MAX);
RandoCheckId GetCheckIdFromName(const char* name);
void PopulateCheckNames();
+std::string GetLocationNameForHint(RandoCheckId randoCheckId, bool exact);
struct RandoStaticItem {
RandoItemId randoItemId;