diff --git a/soh/soh/Enhancements/randomizer/SeedContext.cpp b/soh/soh/Enhancements/randomizer/SeedContext.cpp index 465bc3fd345..9f8e6c4e8f7 100644 --- a/soh/soh/Enhancements/randomizer/SeedContext.cpp +++ b/soh/soh/Enhancements/randomizer/SeedContext.cpp @@ -461,6 +461,7 @@ void Context::ParseArchipelago() { Random_Init(GetSeed()); ParseArchipelagoItemsLocations(apClient.GetScoutedItems()); ParseArchipelagoOptions(); + ParseArchipelagoEntrances(); ParseArchipelagoTricks(); ParseArchipelagoExcludedLocations(); ParseArchipelagoHints(); @@ -858,8 +859,9 @@ void Context::ParseArchipelagoOptions() { mOptions[RSK_KEYRINGS_BOTTOM_OF_THE_WELL].Set(slotData["bottom_of_the_well_key_ring"]); mOptions[RSK_KEYRINGS_GTG].Set(slotData["gerudo_training_ground_key_ring"]); mOptions[RSK_KEYRINGS_GANONS_CASTLE].Set(slotData["ganons_castle_key_ring"]); - mOptions[RSK_SHUFFLE_ENTRANCES].Set(0); - mOptions[RSK_SHUFFLE_DUNGEON_ENTRANCES].Set(0); + uint8_t shuffleDungeonEntrances = slotData.value("shuffle_dungeon_entrances", 0); + mOptions[RSK_SHUFFLE_ENTRANCES].Set(shuffleDungeonEntrances == 0 ? RO_GENERIC_OFF : RO_GENERIC_ON); + mOptions[RSK_SHUFFLE_DUNGEON_ENTRANCES].Set(shuffleDungeonEntrances); mOptions[RSK_SHUFFLE_OVERWORLD_ENTRANCES].Set(0); mOptions[RSK_SHUFFLE_INTERIOR_ENTRANCES].Set(0); mOptions[RSK_SHUFFLE_THIEVES_HIDEOUT_ENTRANCES].Set(0); @@ -916,6 +918,16 @@ void Context::ParseArchipelagoOptions() { mOptions[RSK_ROCS_FEATHER].Set(slotData["rocs_feather"]); } +void Context::ParseArchipelagoEntrances() { + // Apply the dungeon entrance layout computed by the AP generator (if any). + if (mOptions[RSK_SHUFFLE_DUNGEON_ENTRANCES].IsNot(RO_DUNGEON_ENTRANCE_SHUFFLE_OFF)) { + const nlohmann::json slotData = ArchipelagoClient::GetInstance().GetSlotData(); + if (slotData.contains("dungeon_entrance_layout")) { + mEntranceShuffler->SetEntrancesFromArchipelago(slotData["dungeon_entrance_layout"]); + } + } +} + void Context::ParseArchipelagoTricks() { Context::ResetTrickOptions(); diff --git a/soh/soh/Enhancements/randomizer/SeedContext.h b/soh/soh/Enhancements/randomizer/SeedContext.h index ffe0f3bb6c7..bacf1f846e9 100644 --- a/soh/soh/Enhancements/randomizer/SeedContext.h +++ b/soh/soh/Enhancements/randomizer/SeedContext.h @@ -140,6 +140,7 @@ class Context { void ParseArchipelago(); void ParseArchipelagoOptions(); + void ParseArchipelagoEntrances(); void ParseArchipelagoTricks(); void ParseArchipelagoExcludedLocations(); void ParseArchipelagoItemsLocations(const std::vector& slot_data); diff --git a/soh/soh/Enhancements/randomizer/entrance.cpp b/soh/soh/Enhancements/randomizer/entrance.cpp index 9af7ddba713..b6469ddc685 100644 --- a/soh/soh/Enhancements/randomizer/entrance.cpp +++ b/soh/soh/Enhancements/randomizer/entrance.cpp @@ -1518,101 +1518,172 @@ int EntranceShuffler::ShuffleAllEntrances() { } } - // Determine blue warp targets - // RANDOTODO: add bluewarp shuffle - if (true /* ctx->GetOption(RSK_SHUFFLE_BLUEWARP_ENTRANCES).Is(RO_BLUEWARP_ENTRANCE_SHUFFLE_DUNGEON) */) { - // If a boss room is inside a boss door, make the blue warp go outside the dungeon's entrance - std::map bossExits = { - { EntranceNameByRegions(RR_DEKU_TREE_BOSS_ROOM, RR_DEKU_TREE_BOSS_EXIT), - GetEntrance(RR_DEKU_TREE_ENTRYWAY, RR_KF_OUTSIDE_DEKU_TREE) }, - { EntranceNameByRegions(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_EXIT), - GetEntrance(RR_DODONGOS_CAVERN_ENTRYWAY, RR_DEATH_MOUNTAIN_TRAIL) }, - { EntranceNameByRegions(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_EXIT), - GetEntrance(RR_JABU_JABUS_BELLY_ENTRYWAY, RR_ZORAS_FOUNTAIN) }, - { EntranceNameByRegions(RR_FOREST_TEMPLE_BOSS_ROOM, RR_FOREST_TEMPLE_BOSS_ENTRYWAY), - GetEntrance(RR_FOREST_TEMPLE_ENTRYWAY, RR_SACRED_FOREST_MEADOW) }, - { EntranceNameByRegions(RR_FIRE_TEMPLE_BOSS_ROOM, RR_FIRE_TEMPLE_BOSS_ENTRYWAY), - GetEntrance(RR_FIRE_TEMPLE_ENTRYWAY, RR_DMC_TEMPLE_EXIT) }, - { EntranceNameByRegions(RR_WATER_TEMPLE_BOSS_ROOM, RR_WATER_TEMPLE_BOSS_ENTRYWAY), - GetEntrance(RR_WATER_TEMPLE_ENTRYWAY, RR_LH_FROM_WATER_TEMPLE) }, - { EntranceNameByRegions(RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY), - GetEntrance(RR_SPIRIT_TEMPLE_ENTRYWAY, RR_DESERT_COLOSSUS_OUTSIDE_TEMPLE) }, - { EntranceNameByRegions(RR_SHADOW_TEMPLE_BOSS_ROOM, RR_SHADOW_TEMPLE_BOSS_ENTRYWAY), - GetEntrance(RR_SHADOW_TEMPLE_ENTRYWAY, RR_GRAVEYARD_WARP_PAD_REGION) }, - { EntranceNameByRegions(RR_GANONS_TOWER_STAIRS_1, RR_GANONS_TOWER_ENTRYWAY), - GetEntrance(RR_GANONS_CASTLE_ENTRYWAY, RR_CASTLE_GROUNDS_FROM_GANONS_CASTLE) } - }; + // Determine blue warp targets based on the (now shuffled) dungeon/boss entrances + SetBlueWarps(); - // If a boss room is inside a dungeon entrance (or inside a dungeon which is inside a dungeon entrance), make - // the blue warp go to that dungeon's blue warp target - std::map dungeonExits = { - { EntranceNameByRegions(RR_DEKU_TREE_ENTRYWAY, RR_KF_OUTSIDE_DEKU_TREE), - GetEntrance(RR_DEKU_TREE_BOSS_ROOM, RR_KF_OUTSIDE_DEKU_TREE) }, - { EntranceNameByRegions(RR_DODONGOS_CAVERN_ENTRYWAY, RR_DEATH_MOUNTAIN_TRAIL), - GetEntrance(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DEATH_MOUNTAIN_TRAIL) }, - { EntranceNameByRegions(RR_JABU_JABUS_BELLY_ENTRYWAY, RR_ZORAS_FOUNTAIN), - GetEntrance(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_ZORAS_FOUNTAIN) }, - { EntranceNameByRegions(RR_FOREST_TEMPLE_ENTRYWAY, RR_SACRED_FOREST_MEADOW), - GetEntrance(RR_FOREST_TEMPLE_BOSS_ROOM, RR_SACRED_FOREST_MEADOW) }, - { EntranceNameByRegions(RR_FIRE_TEMPLE_ENTRYWAY, RR_DMC_TEMPLE_EXIT), - GetEntrance(RR_FIRE_TEMPLE_BOSS_ROOM, RR_DMC_PAD_ENTRY) }, - { EntranceNameByRegions(RR_WATER_TEMPLE_ENTRYWAY, RR_LH_FROM_WATER_TEMPLE), - GetEntrance(RR_WATER_TEMPLE_BOSS_ROOM, RR_LAKE_HYLIA) }, - { EntranceNameByRegions(RR_SPIRIT_TEMPLE_ENTRYWAY, RR_DESERT_COLOSSUS_OUTSIDE_TEMPLE), - GetEntrance(RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_DESERT_COLOSSUS) }, - { EntranceNameByRegions(RR_SHADOW_TEMPLE_ENTRYWAY, RR_GRAVEYARD_WARP_PAD_REGION), - GetEntrance(RR_SHADOW_TEMPLE_BOSS_ROOM, RR_GRAVEYARD_WARP_PAD_REGION) }, - { EntranceNameByRegions(RR_GANONS_CASTLE_ENTRYWAY, RR_CASTLE_GROUNDS_FROM_GANONS_CASTLE), - GetEntrance(RR_GANONS_TOWER_STAIRS_1, RR_CASTLE_GROUNDS_FROM_GANONS_CASTLE) } - }; + // Validate the world one last time to ensure all special conditions are still valid + if (!ValidateWorld(nullptr)) { + return ENTRANCE_SHUFFLE_FAILURE; + } - // Pair - std::vector bossRoomExitPairs = { - { GetEntrance(RR_DEKU_TREE_BOSS_ROOM, RR_KF_OUTSIDE_DEKU_TREE), - GetEntrance(RR_DEKU_TREE_BOSS_ROOM, RR_DEKU_TREE_BOSS_EXIT) }, - { GetEntrance(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DEATH_MOUNTAIN_TRAIL), - GetEntrance(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_EXIT) }, - { GetEntrance(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_ZORAS_FOUNTAIN), - GetEntrance(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_EXIT) }, - { GetEntrance(RR_FOREST_TEMPLE_BOSS_ROOM, RR_SACRED_FOREST_MEADOW), - GetEntrance(RR_FOREST_TEMPLE_BOSS_ROOM, RR_FOREST_TEMPLE_BOSS_ENTRYWAY) }, - { GetEntrance(RR_FIRE_TEMPLE_BOSS_ROOM, RR_DMC_PAD_ENTRY), - GetEntrance(RR_FIRE_TEMPLE_BOSS_ROOM, RR_FIRE_TEMPLE_BOSS_ENTRYWAY) }, - { GetEntrance(RR_WATER_TEMPLE_BOSS_ROOM, RR_LAKE_HYLIA), - GetEntrance(RR_WATER_TEMPLE_BOSS_ROOM, RR_WATER_TEMPLE_BOSS_ENTRYWAY) }, - { GetEntrance(RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_DESERT_COLOSSUS), - GetEntrance(RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY) }, - { GetEntrance(RR_SHADOW_TEMPLE_BOSS_ROOM, RR_GRAVEYARD_WARP_PAD_REGION), - GetEntrance(RR_SHADOW_TEMPLE_BOSS_ROOM, RR_SHADOW_TEMPLE_BOSS_ENTRYWAY) }, - { GetEntrance(RR_GANONS_TOWER_STAIRS_1, RR_CASTLE_GROUNDS_FROM_GANONS_CASTLE), - GetEntrance(RR_GANONS_TOWER_STAIRS_1, RR_GANONS_TOWER_ENTRYWAY) } - }; + return ENTRANCE_SHUFFLE_SUCCESS; +} - for (EntrancePair pair : bossRoomExitPairs) { - Entrance* target = pair.second->GetReplacement() != nullptr ? pair.second->GetReplacement() : pair.second; +// Redirect each dungeon boss's blue warp to the exit of whatever dungeon slot the boss +// now lives in, following the shuffled dungeon/boss entrances. Extracted from +// ShuffleAllEntrances so the Archipelago path can reuse the exact same logic. +void EntranceShuffler::SetBlueWarps() { + auto ctx = Rando::Context::GetInstance(); - if (!ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) { - while (bossExits.find(target->GetName()) != bossExits.end()) { - Entrance* next = bossExits.at(target->GetName()); - target = next->GetReplacement() != nullptr ? next->GetReplacement() : next; - } + // If a boss room is inside a boss door, make the blue warp go outside the dungeon's entrance + std::map bossExits = { + { EntranceNameByRegions(RR_DEKU_TREE_BOSS_ROOM, RR_DEKU_TREE_BOSS_EXIT), + GetEntrance(RR_DEKU_TREE_ENTRYWAY, RR_KF_OUTSIDE_DEKU_TREE) }, + { EntranceNameByRegions(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_EXIT), + GetEntrance(RR_DODONGOS_CAVERN_ENTRYWAY, RR_DEATH_MOUNTAIN_TRAIL) }, + { EntranceNameByRegions(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_EXIT), + GetEntrance(RR_JABU_JABUS_BELLY_ENTRYWAY, RR_ZORAS_FOUNTAIN) }, + { EntranceNameByRegions(RR_FOREST_TEMPLE_BOSS_ROOM, RR_FOREST_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(RR_FOREST_TEMPLE_ENTRYWAY, RR_SACRED_FOREST_MEADOW) }, + { EntranceNameByRegions(RR_FIRE_TEMPLE_BOSS_ROOM, RR_FIRE_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(RR_FIRE_TEMPLE_ENTRYWAY, RR_DMC_TEMPLE_EXIT) }, + { EntranceNameByRegions(RR_WATER_TEMPLE_BOSS_ROOM, RR_WATER_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(RR_WATER_TEMPLE_ENTRYWAY, RR_LH_FROM_WATER_TEMPLE) }, + { EntranceNameByRegions(RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(RR_SPIRIT_TEMPLE_ENTRYWAY, RR_DESERT_COLOSSUS_OUTSIDE_TEMPLE) }, + { EntranceNameByRegions(RR_SHADOW_TEMPLE_BOSS_ROOM, RR_SHADOW_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(RR_SHADOW_TEMPLE_ENTRYWAY, RR_GRAVEYARD_WARP_PAD_REGION) }, + { EntranceNameByRegions(RR_GANONS_TOWER_STAIRS_1, RR_GANONS_TOWER_ENTRYWAY), + GetEntrance(RR_GANONS_CASTLE_ENTRYWAY, RR_CASTLE_GROUNDS_FROM_GANONS_CASTLE) } + }; - if (dungeonExits.find(target->GetName()) != dungeonExits.end()) { - target = dungeonExits.at(target->GetName()); - } + // If a boss room is inside a dungeon entrance (or inside a dungeon which is inside a dungeon entrance), make + // the blue warp go to that dungeon's blue warp target + std::map dungeonExits = { + { EntranceNameByRegions(RR_DEKU_TREE_ENTRYWAY, RR_KF_OUTSIDE_DEKU_TREE), + GetEntrance(RR_DEKU_TREE_BOSS_ROOM, RR_KF_OUTSIDE_DEKU_TREE) }, + { EntranceNameByRegions(RR_DODONGOS_CAVERN_ENTRYWAY, RR_DEATH_MOUNTAIN_TRAIL), + GetEntrance(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DEATH_MOUNTAIN_TRAIL) }, + { EntranceNameByRegions(RR_JABU_JABUS_BELLY_ENTRYWAY, RR_ZORAS_FOUNTAIN), + GetEntrance(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_ZORAS_FOUNTAIN) }, + { EntranceNameByRegions(RR_FOREST_TEMPLE_ENTRYWAY, RR_SACRED_FOREST_MEADOW), + GetEntrance(RR_FOREST_TEMPLE_BOSS_ROOM, RR_SACRED_FOREST_MEADOW) }, + { EntranceNameByRegions(RR_FIRE_TEMPLE_ENTRYWAY, RR_DMC_TEMPLE_EXIT), + GetEntrance(RR_FIRE_TEMPLE_BOSS_ROOM, RR_DMC_PAD_ENTRY) }, + { EntranceNameByRegions(RR_WATER_TEMPLE_ENTRYWAY, RR_LH_FROM_WATER_TEMPLE), + GetEntrance(RR_WATER_TEMPLE_BOSS_ROOM, RR_LAKE_HYLIA) }, + { EntranceNameByRegions(RR_SPIRIT_TEMPLE_ENTRYWAY, RR_DESERT_COLOSSUS_OUTSIDE_TEMPLE), + GetEntrance(RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_DESERT_COLOSSUS) }, + { EntranceNameByRegions(RR_SHADOW_TEMPLE_ENTRYWAY, RR_GRAVEYARD_WARP_PAD_REGION), + GetEntrance(RR_SHADOW_TEMPLE_BOSS_ROOM, RR_GRAVEYARD_WARP_PAD_REGION) }, + { EntranceNameByRegions(RR_GANONS_CASTLE_ENTRYWAY, RR_CASTLE_GROUNDS_FROM_GANONS_CASTLE), + GetEntrance(RR_GANONS_TOWER_STAIRS_1, RR_CASTLE_GROUNDS_FROM_GANONS_CASTLE) } + }; + + // Pair + std::vector bossRoomExitPairs = { + { GetEntrance(RR_DEKU_TREE_BOSS_ROOM, RR_KF_OUTSIDE_DEKU_TREE), + GetEntrance(RR_DEKU_TREE_BOSS_ROOM, RR_DEKU_TREE_BOSS_EXIT) }, + { GetEntrance(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DEATH_MOUNTAIN_TRAIL), + GetEntrance(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_EXIT) }, + { GetEntrance(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_ZORAS_FOUNTAIN), + GetEntrance(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_EXIT) }, + { GetEntrance(RR_FOREST_TEMPLE_BOSS_ROOM, RR_SACRED_FOREST_MEADOW), + GetEntrance(RR_FOREST_TEMPLE_BOSS_ROOM, RR_FOREST_TEMPLE_BOSS_ENTRYWAY) }, + { GetEntrance(RR_FIRE_TEMPLE_BOSS_ROOM, RR_DMC_PAD_ENTRY), + GetEntrance(RR_FIRE_TEMPLE_BOSS_ROOM, RR_FIRE_TEMPLE_BOSS_ENTRYWAY) }, + { GetEntrance(RR_WATER_TEMPLE_BOSS_ROOM, RR_LAKE_HYLIA), + GetEntrance(RR_WATER_TEMPLE_BOSS_ROOM, RR_WATER_TEMPLE_BOSS_ENTRYWAY) }, + { GetEntrance(RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_DESERT_COLOSSUS), + GetEntrance(RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY) }, + { GetEntrance(RR_SHADOW_TEMPLE_BOSS_ROOM, RR_GRAVEYARD_WARP_PAD_REGION), + GetEntrance(RR_SHADOW_TEMPLE_BOSS_ROOM, RR_SHADOW_TEMPLE_BOSS_ENTRYWAY) }, + { GetEntrance(RR_GANONS_TOWER_STAIRS_1, RR_CASTLE_GROUNDS_FROM_GANONS_CASTLE), + GetEntrance(RR_GANONS_TOWER_STAIRS_1, RR_GANONS_TOWER_ENTRYWAY) } + }; + + for (EntrancePair pair : bossRoomExitPairs) { + Entrance* target = pair.second->GetReplacement() != nullptr ? pair.second->GetReplacement() : pair.second; + + if (!ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) { + while (bossExits.find(target->GetName()) != bossExits.end()) { + Entrance* next = bossExits.at(target->GetName()); + target = next->GetReplacement() != nullptr ? next->GetReplacement() : next; } - pair.first->Connect(target->GetOriginalConnectedRegionKey()); - pair.first->SetReplacement(target); + if (dungeonExits.find(target->GetName()) != dungeonExits.end()) { + target = dungeonExits.at(target->GetName()); + } } + + pair.first->Connect(target->GetOriginalConnectedRegionKey()); + pair.first->SetReplacement(target); } +} - // Validate the world one last time to ensure all special conditions are still valid - if (!ValidateWorld(nullptr)) { - return ENTRANCE_SHUFFLE_FAILURE; +// Apply a dungeon entrance layout produced by the Archipelago generator instead of +// running the local shuffle. The layout is a JSON array of [originalEntryway, newEntryway] +// region-name pairs describing, for each overworld dungeon door, which dungeon it now +// leads to. We set the corresponding entrance replacements (coupled), reuse the native +// blue-warp logic, then build and apply the runtime overrides exactly like ParseJson. +void EntranceShuffler::SetEntrancesFromArchipelago(nlohmann::json entranceLayout) { + UnshuffleAllEntrances(); + RegionTable_Init(); + SetAllEntrancesData(); + + // region name -> RandomizerRegion (matches the apworld's Regions enum values) + std::unordered_map regionByName; + for (uint32_t i = RR_NONE; i < RR_MAX; i++) { + regionByName[RegionTable((RandomizerRegion)i)->regionName] = (RandomizerRegion)i; } - return ENTRANCE_SHUFFLE_SUCCESS; + // dungeon entryway region -> its forward (overworld-side) dungeon entrance + std::unordered_map forwardByEntryway; + auto dungeonForwards = GetShuffleableEntrances(EntranceType::Dungeon); + for (Entrance* e : GetShuffleableEntrances(EntranceType::GanonDungeon)) { + dungeonForwards.push_back(e); + } + for (Entrance* e : dungeonForwards) { + forwardByEntryway[e->GetOriginalConnectedRegionKey()] = e; + } + + for (const auto& entry : entranceLayout) { + if (!entry.is_array() || entry.size() != 2 || !entry[0].is_string() || !entry[1].is_string()) { + SPDLOG_ERROR("[AP ER] Malformed dungeon_entrance_layout entry: {}", entry.dump()); + continue; + } + std::string origName = entry[0]; + std::string newName = entry[1]; + if (!regionByName.count(origName) || !regionByName.count(newName)) { + SPDLOG_ERROR("[AP ER] Unknown region name in dungeon_entrance_layout: {} -> {}", origName, newName); + continue; + } + Entrance* eOrig = + forwardByEntryway.count(regionByName[origName]) ? forwardByEntryway[regionByName[origName]] : nullptr; + Entrance* eNew = + forwardByEntryway.count(regionByName[newName]) ? forwardByEntryway[regionByName[newName]] : nullptr; + if (eOrig == nullptr || eNew == nullptr) { + SPDLOG_ERROR("[AP ER] Missing dungeon entrance for: {} -> {}", origName, newName); + continue; + } + + // The overworld door at origName's spot now loads newName's dungeon... + eOrig->SetReplacement(eNew); + eOrig->SetAsShuffled(); + // ...and exiting newName's dungeon returns to origName's overworld spot (coupled). + if (eOrig->GetReverse() != nullptr && eNew->GetReverse() != nullptr) { + eNew->GetReverse()->SetReplacement(eOrig->GetReverse()); + eNew->GetReverse()->SetAsShuffled(); + } + } + + SetBlueWarps(); + CreateEntranceOverrides(); + + // Apply to a fresh region graph, mirroring ParseJson's tail. + RegionTable_Init(); + ApplyEntranceOverrides(); + SetAreas(); } void EntranceShuffler::CreateEntranceOverrides() { diff --git a/soh/soh/Enhancements/randomizer/entrance.h b/soh/soh/Enhancements/randomizer/entrance.h index 32fefbb1322..c676eb6499b 100644 --- a/soh/soh/Enhancements/randomizer/entrance.h +++ b/soh/soh/Enhancements/randomizer/entrance.h @@ -137,11 +137,13 @@ class EntranceShuffler { void CreateEntranceOverrides(); void UnshuffleAllEntrances(); void ParseJson(nlohmann::json spoilerFileJson); + void SetEntrancesFromArchipelago(nlohmann::json entranceLayout); void ApplyEntranceOverrides(); static const Entrance* GetEntranceByIndex(int16_t index); private: + void SetBlueWarps(); std::vector AssumeEntrancePool(std::vector& entrancePool); bool ShuffleOneWayPriorityEntrances(std::map& oneWayPriorities, EntrancePools oneWayEntrancePools, EntrancePools oneWayTargetEntrancePools,