summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett Cox <garrettjcox@gmail.com>2026-02-14 11:03:31 -0600
committerGitHub <noreply@github.com>2026-02-14 11:03:31 -0600
commit4f2548d9d6a97e18a371ef01ba6960bc36006e08 (patch)
tree471e83e15cede740eb7af24c368f2ccc38fdaef6
parenteed759261b049e5aaf90a05d5a595c82aa475a15 (diff)
parentf62f5735b90878ad97556e47341b881c9c81f78b (diff)
Merge pull request #1554 from HarbourMasters/develop-keiichi
keiichi -> develop
-rw-r--r--mm/2s2h/Rando/ActorBehavior/EnTimeTag.cpp2
-rw-r--r--mm/2s2h/Rando/ConvertItem.cpp5
-rw-r--r--mm/2s2h/Rando/Logic/Logic.cpp54
-rw-r--r--mm/2s2h/Rando/Logic/Logic.h15
-rw-r--r--mm/2s2h/Rando/Logic/Regions/BeneathTheWell.cpp2
-rw-r--r--mm/2s2h/Rando/Logic/Regions/Central.cpp8
-rw-r--r--mm/2s2h/Rando/Logic/Regions/East.cpp2
-rw-r--r--mm/2s2h/Rando/Logic/Regions/North.cpp7
-rw-r--r--mm/2s2h/Rando/Logic/Regions/South.cpp8
-rw-r--r--mm/2s2h/Rando/Menu.cpp4
-rw-r--r--mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp1
-rw-r--r--mm/2s2h/Rando/Types.h1
12 files changed, 78 insertions, 31 deletions
diff --git a/mm/2s2h/Rando/ActorBehavior/EnTimeTag.cpp b/mm/2s2h/Rando/ActorBehavior/EnTimeTag.cpp
index 17922b5ff..273e6fdb2 100644
--- a/mm/2s2h/Rando/ActorBehavior/EnTimeTag.cpp
+++ b/mm/2s2h/Rando/ActorBehavior/EnTimeTag.cpp
@@ -23,7 +23,7 @@ void Rando::ActorBehavior::InitEnTimeTagBehavior() {
auto entry = CustomMessage::LoadVanillaMessageTableEntry(*textId);
entry.msg = "A note is carved into the stone...\x10What you seek lies in %y{{location}}%w.";
- RandoCheckId randoCheckId = Rando::FindItemPlacement(RI_HOOKSHOT);
+ RandoCheckId randoCheckId = Rando::FindItemPlacement(RI_SONG_SOARING);
CustomMessage::Replace(&entry.msg, "{{location}}",
Ship_GetSceneName(Rando::StaticData::Checks[randoCheckId].sceneId));
CustomMessage::LoadCustomMessageIntoFont(entry);
diff --git a/mm/2s2h/Rando/ConvertItem.cpp b/mm/2s2h/Rando/ConvertItem.cpp
index 5e11a7204..4506f6140 100644
--- a/mm/2s2h/Rando/ConvertItem.cpp
+++ b/mm/2s2h/Rando/ConvertItem.cpp
@@ -590,6 +590,11 @@ bool Rando::IsItemObtainable(RandoItemId randoItemId, RandoCheckId randoCheckId)
return !Flags_GetRandoInf(RANDO_INF_OBTAINED_CLOCK_DAY_1 +
Rando::ClockItems::GetHalfDayIndexFromClockItem(randoItemId));
case RI_TIME_PROGRESSIVE:
+ if (hasObtainedCheck) {
+ return false;
+ } else if (Rando::ClockItems::GetAllOwnedHalfDaysMask() == 0x3F) { // Have all 6 half days
+ return false;
+ }
return true;
case RI_OCARINA_BUTTON_A:
case RI_OCARINA_BUTTON_C_DOWN:
diff --git a/mm/2s2h/Rando/Logic/Logic.cpp b/mm/2s2h/Rando/Logic/Logic.cpp
index b72dde87a..737e1cbcd 100644
--- a/mm/2s2h/Rando/Logic/Logic.cpp
+++ b/mm/2s2h/Rando/Logic/Logic.cpp
@@ -9,8 +9,7 @@ namespace Logic {
std::map<RandoRegionId, RandoRegion> Regions = {};
-// Thread-local storage for current region time during check evaluation
-thread_local uint64_t gCurrentRegionTime = 0;
+uint64_t gCurrentRegionTime = 0;
RandoRegionId GetRegionIdFromEntrance(s32 entrance) {
static std::map<s32, RandoRegionId> entranceToRegionId;
@@ -104,17 +103,29 @@ void FindReachableRegions(RandoRegionId currentRegion, std::set<RandoRegionId>&
sourceTimeState.timeSlices = currentTime;
}
- // Set global time for check evaluation
- gCurrentRegionTime = currentTime;
-
// Explore connections
for (auto& [connectedRegionId, condition] : sourceRegion.connections) {
- if (reachableRegions.count(connectedRegionId) == 0 && condition.first()) {
- reachableRegions.insert(connectedRegionId);
+ // Set global time for check evaluation
+ gCurrentRegionTime = currentTime;
+ if (condition.first()) {
auto& targetRegion = Regions[connectedRegionId];
- regionTimeStates[connectedRegionId] = { .timeSlices = currentTime,
- .canStayOverTime = targetRegion.canStayOverTime };
+ RegionTimeState incomingState = { .timeSlices = currentTime,
+ .canStayOverTime = targetRegion.canStayOverTime };
+
+ auto existingIt = regionTimeStates.find(connectedRegionId);
+ if (existingIt != regionTimeStates.end()) {
+ // Region already visited - check if we have new time to add
+ if (TimeStateCovers(existingIt->second, incomingState)) {
+ continue; // Skip - existing state already covers incoming
+ }
+ // Merge time states for re-exploration
+ existingIt->second = MergeTimeStates(existingIt->second, incomingState);
+ } else {
+ // First visit to this region
+ reachableRegions.insert(connectedRegionId);
+ regionTimeStates[connectedRegionId] = incomingState;
+ }
FindReachableRegions(connectedRegionId, reachableRegions, regionTimeStates);
}
@@ -122,13 +133,28 @@ void FindReachableRegions(RandoRegionId currentRegion, std::set<RandoRegionId>&
// Explore exits
for (auto& [exitId, regionExit] : sourceRegion.exits) {
- RandoRegionId connectedRegionId = GetRegionIdFromEntrance(exitId);
- if (reachableRegions.count(connectedRegionId) == 0 && regionExit.condition()) {
- reachableRegions.insert(connectedRegionId);
+ // Set global time for check evaluation
+ gCurrentRegionTime = currentTime;
+ RandoRegionId connectedRegionId = GetRegionIdFromEntrance(exitId);
+ if (regionExit.condition()) {
auto& targetRegion = Regions[connectedRegionId];
- regionTimeStates[connectedRegionId] = { .timeSlices = currentTime,
- .canStayOverTime = targetRegion.canStayOverTime };
+ RegionTimeState incomingState = { .timeSlices = currentTime,
+ .canStayOverTime = targetRegion.canStayOverTime };
+
+ auto existingIt = regionTimeStates.find(connectedRegionId);
+ if (existingIt != regionTimeStates.end()) {
+ // Region already visited - check if we have new time to add
+ if (TimeStateCovers(existingIt->second, incomingState)) {
+ continue; // Skip - existing state already covers incoming
+ }
+ // Merge time states for re-exploration
+ existingIt->second = MergeTimeStates(existingIt->second, incomingState);
+ } else {
+ // First visit to this region
+ reachableRegions.insert(connectedRegionId);
+ regionTimeStates[connectedRegionId] = incomingState;
+ }
FindReachableRegions(connectedRegionId, reachableRegions, regionTimeStates);
}
diff --git a/mm/2s2h/Rando/Logic/Logic.h b/mm/2s2h/Rando/Logic/Logic.h
index 9f5a7b439..92df13e64 100644
--- a/mm/2s2h/Rando/Logic/Logic.h
+++ b/mm/2s2h/Rando/Logic/Logic.h
@@ -100,8 +100,7 @@ struct RegionTimeState {
bool canStayOverTime;
};
-// Thread-local current region time for check evaluation
-extern thread_local uint64_t gCurrentRegionTime;
+extern uint64_t gCurrentRegionTime;
// Helper: Convert runtime game time to TimeSlice enum
TimeSlice TimeSliceFromGameTime(s32 day, u16 time);
@@ -502,6 +501,16 @@ inline constexpr uint64_t GetHalfDayTimeMask(int halfDayIndex) {
return mask;
}
+// Merge two time states (bitwise OR on time slices)
+inline RegionTimeState MergeTimeStates(const RegionTimeState& a, const RegionTimeState& b) {
+ return { .timeSlices = a.timeSlices | b.timeSlices, .canStayOverTime = a.canStayOverTime || b.canStayOverTime };
+}
+
+// Check if 'a' covers 'b' (a is a superset - adding b gives nothing new)
+inline bool TimeStateCovers(const RegionTimeState& a, const RegionTimeState& b) {
+ return (a.timeSlices | b.timeSlices) == a.timeSlices;
+}
+
// ============================================================================
// CLOCK ITEM MACROS
// ============================================================================
@@ -737,6 +746,8 @@ inline bool CanKillEnemy(ActorId EnemyId) {
CAN_USE_PROJECTILE || CAN_USE_EXPLOSIVE || HAS_ITEM(ITEM_DEKU_NUT));
case ACTOR_EN_DRAGON: // Deep Python
return (CAN_BE_ZORA && HAS_MAGIC);
+ case ACTOR_EN_BIGPO:
+ return HAS_ITEM(ITEM_BOW);
case ACTOR_EN_PO_SISTERS:
// The first three sisters can be damaged with almost anything, but Meg requires ranged attacks. Not using
// CAN_USE_EXPLOSIVE here, as the Blast Mask cannot reach, and the Powder Keg can only be used once.
diff --git a/mm/2s2h/Rando/Logic/Regions/BeneathTheWell.cpp b/mm/2s2h/Rando/Logic/Regions/BeneathTheWell.cpp
index 4ae5d4662..21f459385 100644
--- a/mm/2s2h/Rando/Logic/Regions/BeneathTheWell.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/BeneathTheWell.cpp
@@ -47,7 +47,7 @@ static RegisterShipInitFunc initFunc([]() {
CONNECTION(RR_BENEATH_THE_WELL_RIGHT_FIRE_KEESE, true),
},
.events = {
- EVENT(RE_ACCESS_BIG_POE, HAS_ITEM(ITEM_BOW)),
+ EVENT(RE_ACCESS_BIG_POE, CanKillEnemy(ACTOR_EN_BIGPO)),
}
};
Regions[RR_BENEATH_THE_WELL_COW_ROOM] = RandoRegion{ .name = "Cow Room", .sceneId = SCENE_REDEAD,
diff --git a/mm/2s2h/Rando/Logic/Regions/Central.cpp b/mm/2s2h/Rando/Logic/Regions/Central.cpp
index 0938a4db9..7cba0588a 100644
--- a/mm/2s2h/Rando/Logic/Regions/Central.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/Central.cpp
@@ -463,10 +463,10 @@ static RegisterShipInitFunc initFunc([]() {
};
Regions[RR_TREASURE_SHOP] = RandoRegion{ .sceneId = SCENE_TAKARAYA,
.checks = {
- CHECK(RC_CLOCK_TOWN_EAST_TREASURE_CHEST_GAME_DEKU, CAN_BE_DEKU),
- CHECK(RC_CLOCK_TOWN_EAST_TREASURE_CHEST_GAME_GORON, CAN_BE_GORON),
- CHECK(RC_CLOCK_TOWN_EAST_TREASURE_CHEST_GAME_HUMAN, true), // can be human
- CHECK(RC_CLOCK_TOWN_EAST_TREASURE_CHEST_GAME_ZORA, CAN_BE_ZORA),
+ CHECK(RC_CLOCK_TOWN_EAST_TREASURE_CHEST_GAME_DEKU, CAN_BE_DEKU && BEFORE(TIME_NIGHT3_PM_06_00)),
+ CHECK(RC_CLOCK_TOWN_EAST_TREASURE_CHEST_GAME_GORON, CAN_BE_GORON && BEFORE(TIME_NIGHT3_PM_06_00)),
+ CHECK(RC_CLOCK_TOWN_EAST_TREASURE_CHEST_GAME_HUMAN, BEFORE(TIME_NIGHT3_PM_06_00)), // can be human
+ CHECK(RC_CLOCK_TOWN_EAST_TREASURE_CHEST_GAME_ZORA, CAN_BE_ZORA && BEFORE(TIME_NIGHT3_PM_06_00)),
},
.exits = { // TO FROM
EXIT(ENTRANCE(EAST_CLOCK_TOWN, 4), ENTRANCE(TREASURE_CHEST_SHOP, 0), true),
diff --git a/mm/2s2h/Rando/Logic/Regions/East.cpp b/mm/2s2h/Rando/Logic/Regions/East.cpp
index 514ae967c..bff5e8d3a 100644
--- a/mm/2s2h/Rando/Logic/Regions/East.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/East.cpp
@@ -9,7 +9,7 @@ using namespace Rando::Logic;
static RegisterShipInitFunc initFunc([]() {
Regions[RR_BENEATH_THE_GRAVEYARD_DAMPE] = RandoRegion{ .sceneId = SCENE_DANPEI2TEST,
.checks = {
- CHECK(RC_BENEATH_THE_GRAVEYARD_DAMPE_CHEST, HAS_ITEM(ITEM_BOW)),
+ CHECK(RC_BENEATH_THE_GRAVEYARD_DAMPE_CHEST, CanKillEnemy(ACTOR_EN_BIGPO)),
CHECK(RC_BENEATH_THE_GRAVEYARD_DAMPE_POT_01, true),
CHECK(RC_BENEATH_THE_GRAVEYARD_DAMPE_POT_02, true),
CHECK(RC_BENEATH_THE_GRAVEYARD_DAMPE_POT_03, true),
diff --git a/mm/2s2h/Rando/Logic/Regions/North.cpp b/mm/2s2h/Rando/Logic/Regions/North.cpp
index 95c26d3e3..ad4599860 100644
--- a/mm/2s2h/Rando/Logic/Regions/North.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/North.cpp
@@ -190,12 +190,15 @@ static RegisterShipInitFunc initFunc([]() {
};
Regions[RR_MOUNTAIN_SMITHY] = RandoRegion{ .sceneId = SCENE_KAJIYA,
.checks = {
- CHECK(RC_MOUNTAIN_VILLAGE_SMITHY_RAZOR_SWORD, (RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] || CAN_USE_MAGIC_ARROW(FIRE) || (HAS_BOTTLE && CAN_ACCESS(HOT_SPRING_WATER))) && GET_CUR_UPG_VALUE(UPG_WALLET) >= 1),
- CHECK(RC_MOUNTAIN_VILLAGE_SMITHY_GILDED_SWORD, (RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] || CAN_USE_MAGIC_ARROW(FIRE) || (HAS_BOTTLE && CAN_ACCESS(HOT_SPRING_WATER))) && HAS_BOTTLE && CAN_ACCESS(GOLD_DUST) && (GET_CUR_UPG_VALUE(UPG_WALLET) >= 1)),
+ CHECK(RC_MOUNTAIN_VILLAGE_SMITHY_RAZOR_SWORD, CAN_ACCESS(SMITHY) && GET_CUR_UPG_VALUE(UPG_WALLET) >= 1),
+ CHECK(RC_MOUNTAIN_VILLAGE_SMITHY_GILDED_SWORD, CAN_ACCESS(SMITHY) && HAS_BOTTLE && CAN_ACCESS(GOLD_DUST) && (GET_CUR_UPG_VALUE(UPG_WALLET) >= 1)),
},
.exits = { // TO FROM
EXIT(ENTRANCE(MOUNTAIN_VILLAGE_WINTER, 1), ENTRANCE(MOUNTAIN_SMITHY, 0), true),
},
+ .events = {
+ EVENT(RE_ACCESS_SMITHY, (RANDO_EVENTS[RE_CLEARED_SNOWHEAD_TEMPLE] || CAN_USE_MAGIC_ARROW(FIRE) || (HAS_BOTTLE && CAN_ACCESS(HOT_SPRING_WATER))) && (FIRST_DAY() || SECOND_DAY())),
+ },
};
Regions[RR_MOUNTAIN_VILLAGE_TUNNEL_GROTTO] = RandoRegion{ .name = "Mountain Village Tunnel Grotto", .sceneId = SCENE_KAKUSIANA,
.checks = {
diff --git a/mm/2s2h/Rando/Logic/Regions/South.cpp b/mm/2s2h/Rando/Logic/Regions/South.cpp
index 1c2fbc648..c4916c721 100644
--- a/mm/2s2h/Rando/Logic/Regions/South.cpp
+++ b/mm/2s2h/Rando/Logic/Regions/South.cpp
@@ -502,8 +502,8 @@ static RegisterShipInitFunc initFunc([]() {
Regions[RR_WOODFALL] = RandoRegion{ .sceneId = SCENE_21MITURINMAE,
.checks = {
CHECK(RC_WOODFALL_ENTRANCE_CHEST, CAN_BE_DEKU || (RANDO_EVENTS[RE_CLEARED_WOODFALL_TEMPLE] && (CAN_BE_ZORA || CAN_BE_GORON || CAN_USE_ABILITY(SWIM)))),
- CHECK(RC_WOODFALL_PIECE_OF_HEART_CHEST, CAN_BE_DEKU),
- CHECK(RC_WOODFALL_FREESTANDING_RUPEE, CAN_BE_DEKU),
+ CHECK(RC_WOODFALL_PIECE_OF_HEART_CHEST, CAN_BE_DEKU && (CanKillEnemy(ACTOR_EN_DEKUNUTS) || HAS_ITEM(ITEM_HOOKSHOT))),
+ CHECK(RC_WOODFALL_FREESTANDING_RUPEE, CAN_BE_DEKU && CanKillEnemy(ACTOR_EN_DEKUNUTS)),
CHECK(RC_WOODFALL_GRASS_01, true),
CHECK(RC_WOODFALL_GRASS_02, true),
CHECK(RC_WOODFALL_GRASS_03, true),
@@ -516,11 +516,11 @@ static RegisterShipInitFunc initFunc([]() {
},
.exits = { // TO FROM
EXIT(ENTRANCE(SOUTHERN_SWAMP_POISONED, 2), ENTRANCE(WOODFALL, 0), true),
- EXIT(ENTRANCE(FAIRY_FOUNTAIN, 1), ENTRANCE(WOODFALL, 2), CAN_BE_DEKU),
+ EXIT(ENTRANCE(FAIRY_FOUNTAIN, 1), ENTRANCE(WOODFALL, 2), CAN_BE_DEKU && CanKillEnemy(ACTOR_EN_DEKUNUTS)),
EXIT(ENTRANCE(WOODFALL_TEMPLE, 2), ENTRANCE(WOODFALL, 3), RANDO_EVENTS[RE_CLEARED_WOODFALL_TEMPLE]),
},
.connections = {
- CONNECTION(RR_WOODFALL_OWL_STATUE_PLATFORM, CAN_BE_DEKU),
+ CONNECTION(RR_WOODFALL_OWL_STATUE_PLATFORM, CAN_BE_DEKU && CanKillEnemy(ACTOR_EN_DEKUNUTS)),
},
};
Regions[RR_WOODFALL_OWL_STATUE_PLATFORM] = RandoRegion{ .name = "Owl Statue Platform", .sceneId = SCENE_21MITURINMAE,
diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp
index fdbac9b62..3f434f882 100644
--- a/mm/2s2h/Rando/Menu.cpp
+++ b/mm/2s2h/Rando/Menu.cpp
@@ -583,8 +583,8 @@ static void DrawItemsTab() {
CheckboxOptions({ { .tooltip = "Shuffles the first drop from a non Boss Enemy." } }));
CVarCheckbox(
"Enemy Souls", Rando::StaticData::Options[RO_SHUFFLE_ENEMY_SOULS].cvar,
- CheckboxOptions({ { .tooltip = "Adds the \"souls\" of regular enemies to the item pool. Enemy Souls are items "
- "that must be found in order for their corresponding enemy to spawn.",
+ CheckboxOptions({ { .tooltip = "Adds the \"souls\" of regular enemies to the item pool. An enemy will be "
+ "immune to damage until its corresponding soul has been obtained.",
.disabled = IncompatibleWithLogicSetting(RO_SHUFFLE_ENEMY_SOULS),
.disabledTooltip = "Incompatible with current Logic Setting" } }));
CVarCheckbox("Shuffle Time", Rando::StaticData::Options[RO_CLOCK_SHUFFLE].cvar,
diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp
index 75785b966..f6f1af194 100644
--- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp
+++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp
@@ -53,6 +53,7 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) {
hadInputSeed = false;
}
+ SPDLOG_INFO("Generating new randomizer with seed: {}", inputSeed);
uint32_t finalSeed = Ship_Hash(inputSeed);
Ship_Random_Seed(finalSeed);
diff --git a/mm/2s2h/Rando/Types.h b/mm/2s2h/Rando/Types.h
index 89433292f..36b584f0a 100644
--- a/mm/2s2h/Rando/Types.h
+++ b/mm/2s2h/Rando/Types.h
@@ -3120,6 +3120,7 @@ typedef enum {
RE_ACCESS_PIRATE_PICTURE,
RE_ACCESS_RED_POTION_REFILL,
RE_ACCESS_SEAHORSE,
+ RE_ACCESS_SMITHY,
RE_ACCESS_SPRING_WATER,
RE_ACCESS_STICK_AMMO,
RE_ACCESS_ZORA_EGG,