diff options
| author | Garrett Cox <garrettjcox@gmail.com> | 2026-02-14 11:03:31 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-14 11:03:31 -0600 |
| commit | 4f2548d9d6a97e18a371ef01ba6960bc36006e08 (patch) | |
| tree | 471e83e15cede740eb7af24c368f2ccc38fdaef6 | |
| parent | eed759261b049e5aaf90a05d5a595c82aa475a15 (diff) | |
| parent | f62f5735b90878ad97556e47341b881c9c81f78b (diff) | |
Merge pull request #1554 from HarbourMasters/develop-keiichi
keiichi -> develop
| -rw-r--r-- | mm/2s2h/Rando/ActorBehavior/EnTimeTag.cpp | 2 | ||||
| -rw-r--r-- | mm/2s2h/Rando/ConvertItem.cpp | 5 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Logic/Logic.cpp | 54 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Logic/Logic.h | 15 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Logic/Regions/BeneathTheWell.cpp | 2 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Logic/Regions/Central.cpp | 8 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Logic/Regions/East.cpp | 2 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Logic/Regions/North.cpp | 7 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Logic/Regions/South.cpp | 8 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Menu.cpp | 4 | ||||
| -rw-r--r-- | mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp | 1 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Types.h | 1 |
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, |
