Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions soh/soh/Enhancements/randomizer/SeedContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ void Context::ParseArchipelago() {
Random_Init(GetSeed());
ParseArchipelagoItemsLocations(apClient.GetScoutedItems());
ParseArchipelagoOptions();
ParseArchipelagoEntrances();
ParseArchipelagoTricks();
ParseArchipelagoExcludedLocations();
ParseArchipelagoHints();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();

Expand Down
1 change: 1 addition & 0 deletions soh/soh/Enhancements/randomizer/SeedContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class Context {

void ParseArchipelago();
void ParseArchipelagoOptions();
void ParseArchipelagoEntrances();
void ParseArchipelagoTricks();
void ParseArchipelagoExcludedLocations();
void ParseArchipelagoItemsLocations(const std::vector<ArchipelagoClient::ApItem>& slot_data);
Expand Down
237 changes: 154 additions & 83 deletions soh/soh/Enhancements/randomizer/entrance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string, Entrance*> 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<std::string, Entrance*> 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 <BlueWarp exit, BossRoom reverse exit>
std::vector<EntrancePair> 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<std::string, Entrance*> 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<std::string, Entrance*> 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 <BlueWarp exit, BossRoom reverse exit>
std::vector<EntrancePair> 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<std::string, RandomizerRegion> 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<RandomizerRegion, Entrance*> 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() {
Expand Down
2 changes: 2 additions & 0 deletions soh/soh/Enhancements/randomizer/entrance.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Entrance*> AssumeEntrancePool(std::vector<Entrance*>& entrancePool);
bool ShuffleOneWayPriorityEntrances(std::map<std::string, PriorityEntrance>& oneWayPriorities,
EntrancePools oneWayEntrancePools, EntrancePools oneWayTargetEntrancePools,
Expand Down
Loading