diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access.cpp index 7eb1436df..e389e440e 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access.cpp @@ -502,7 +502,7 @@ Area* AreaTable(const RandomizerRegion areaKey) { std::vector GetShuffleableEntrances(Rando::EntranceType type, bool onlyPrimary /*= true*/) { std::vector entrancesToShuffle = {}; for (RandomizerRegion area : Areas::GetAllAreas()) { - for (auto& exit: AreaTable(area)->exits) { + for (auto& exit : AreaTable(area)->exits) { if ((exit.GetType() == type || type == Rando::EntranceType::All) && (exit.IsPrimary() || !onlyPrimary) && exit.GetType() != Rando::EntranceType::None) { entrancesToShuffle.push_back(&exit); } diff --git a/soh/soh/Enhancements/randomizer/entrance.cpp b/soh/soh/Enhancements/randomizer/entrance.cpp index ee9df1dc1..2f4b43d26 100644 --- a/soh/soh/Enhancements/randomizer/entrance.cpp +++ b/soh/soh/Enhancements/randomizer/entrance.cpp @@ -10,6 +10,7 @@ EntranceLinkInfo NO_RETURN_ENTRANCE = { EntranceType::None, RR_NONE, RR_NONE, -1 Entrance::Entrance(RandomizerRegion connectedRegion_, std::vector conditions_met_) : connectedRegion(connectedRegion_) { + originalConnectedRegion = connectedRegion_; conditions_met.resize(2); for (size_t i = 0; i < conditions_met_.size(); i++) { conditions_met[i] = conditions_met_[i]; @@ -109,6 +110,10 @@ RandomizerRegion Entrance::GetConnectedRegionKey() const { return connectedRegion; } +RandomizerRegion Entrance::GetOriginalConnectedRegionKey() const { + return originalConnectedRegion; +} + Area* Entrance::GetConnectedRegion() const { return AreaTable(connectedRegion); } @@ -173,14 +178,6 @@ void Entrance::SetIndex(int16_t newIndex) { index = newIndex; } -int16_t Entrance::GetBlueWarp() const { - return blueWarp; -} - -void Entrance::SetBlueWarp(int16_t newBlueWarp) { - blueWarp = newBlueWarp; -} - Entrance* Entrance::GetAssumed() const { return assumed; } @@ -226,7 +223,7 @@ Entrance* Entrance::GetNewTarget() { AreaTable(RR_ROOT)->AddExit(RR_ROOT, connectedRegion, [] { return true; }); Entrance* targetEntrance = AreaTable(RR_ROOT)->GetExit(connectedRegion); targetEntrance->SetReplacement(this); - targetEntrance->SetName(GetParentRegion()->regionName + " -> " + GetConnectedRegion()->regionName); + targetEntrance->SetName(AreaTable(RR_ROOT)->regionName + " -> " + GetConnectedRegion()->regionName); return targetEntrance; } @@ -246,6 +243,11 @@ void EntranceShuffler::SetNoRandomEntrances(bool noRandomEntrances) { mNoRandomEntrances = noRandomEntrances; } +// Construct entrance name from parent and connected region keys +std::string EntranceNameByRegions(RandomizerRegion parentRegion, RandomizerRegion connectedRegion) { + return AreaTable(parentRegion)->regionName + " -> " + AreaTable(connectedRegion)->regionName; +} + void SetAllEntrancesData(std::vector& entranceShuffleTable) { auto ctx = Rando::Context::GetInstance(); for (auto& entrancePair : entranceShuffleTable) { @@ -256,26 +258,22 @@ void SetAllEntrancesData(std::vector& entranceShuffleTable) { // set data Entrance* forwardEntrance = AreaTable(forwardEntry.parentRegion)->GetExit(forwardEntry.connectedRegion); forwardEntrance->SetIndex(forwardEntry.index); - forwardEntrance->SetBlueWarp(forwardEntry.blueWarp); forwardEntrance->SetType(forwardEntry.type); forwardEntrance->SetAsPrimary(); - // When decouple entrances is on, mark it for entrances except boss rooms - if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES) && forwardEntry.type != EntranceType::ChildBoss && - forwardEntry.type != EntranceType::AdultBoss) { + // When decouple entrances is on, mark the forward entrance + if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) { forwardEntrance->SetDecoupled(); } if (returnEntry.parentRegion != RR_NONE) { Entrance* returnEntrance = AreaTable(returnEntry.parentRegion)->GetExit(returnEntry.connectedRegion); returnEntrance->SetIndex(returnEntry.index); - returnEntrance->SetBlueWarp(returnEntry.blueWarp); returnEntrance->SetType(returnEntry.type); forwardEntrance->BindTwoWay(returnEntrance); // Mark reverse entrance as decoupled - if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES) && returnEntry.type != EntranceType::ChildBoss && - returnEntry.type != EntranceType::AdultBoss) { + if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) { returnEntrance->SetDecoupled(); } } @@ -925,24 +923,23 @@ int EntranceShuffler::ShuffleAllEntrances() { mCurNumRandomizedEntrances = 0; std::vector entranceShuffleTable = { - // Parent Region Connected Region index blue warp + // Type Parent Region Connected Region Index { { EntranceType::Dungeon, RR_KF_OUTSIDE_DEKU_TREE, RR_DEKU_TREE_ENTRYWAY, 0x0000 }, - { EntranceType::Dungeon, RR_DEKU_TREE_ENTRYWAY, RR_KF_OUTSIDE_DEKU_TREE, 0x0209, 0x0457 } }, + { EntranceType::Dungeon, RR_DEKU_TREE_ENTRYWAY, RR_KF_OUTSIDE_DEKU_TREE, 0x0209 } }, { { EntranceType::Dungeon, RR_DEATH_MOUNTAIN_TRAIL, RR_DODONGOS_CAVERN_ENTRYWAY, 0x0004 }, - { EntranceType::Dungeon, RR_DODONGOS_CAVERN_ENTRYWAY, RR_DEATH_MOUNTAIN_TRAIL, 0x0242, 0x047A } }, + { EntranceType::Dungeon, RR_DODONGOS_CAVERN_ENTRYWAY, RR_DEATH_MOUNTAIN_TRAIL, 0x0242 } }, { { EntranceType::Dungeon, RR_ZORAS_FOUNTAIN, RR_JABU_JABUS_BELLY_ENTRYWAY, 0x0028 }, - { EntranceType::Dungeon, RR_JABU_JABUS_BELLY_ENTRYWAY, RR_ZORAS_FOUNTAIN, 0x0221, 0x010E } }, + { EntranceType::Dungeon, RR_JABU_JABUS_BELLY_ENTRYWAY, RR_ZORAS_FOUNTAIN, 0x0221 } }, { { EntranceType::Dungeon, RR_SACRED_FOREST_MEADOW, RR_FOREST_TEMPLE_ENTRYWAY, 0x0169 }, - { EntranceType::Dungeon, RR_FOREST_TEMPLE_ENTRYWAY, RR_SACRED_FOREST_MEADOW, 0x0215, 0x0608 } }, + { EntranceType::Dungeon, RR_FOREST_TEMPLE_ENTRYWAY, RR_SACRED_FOREST_MEADOW, 0x0215 } }, { { EntranceType::Dungeon, RR_DMC_CENTRAL_LOCAL, RR_FIRE_TEMPLE_ENTRYWAY, 0x0165 }, - { EntranceType::Dungeon, RR_FIRE_TEMPLE_ENTRYWAY, RR_DMC_CENTRAL_LOCAL, 0x024A, 0x0564 } }, + { EntranceType::Dungeon, RR_FIRE_TEMPLE_ENTRYWAY, RR_DMC_CENTRAL_LOCAL, 0x024A } }, { { EntranceType::Dungeon, RR_LAKE_HYLIA, RR_WATER_TEMPLE_ENTRYWAY, 0x0010 }, - { EntranceType::Dungeon, RR_WATER_TEMPLE_ENTRYWAY, RR_LAKE_HYLIA, 0x021D, 0x060C } }, + { EntranceType::Dungeon, RR_WATER_TEMPLE_ENTRYWAY, RR_LAKE_HYLIA, 0x021D } }, { { EntranceType::Dungeon, RR_DESERT_COLOSSUS, RR_SPIRIT_TEMPLE_ENTRYWAY, 0x0082 }, - { EntranceType::Dungeon, RR_SPIRIT_TEMPLE_ENTRYWAY, RR_DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY, 0x01E1, - 0x0610 } }, + { EntranceType::Dungeon, RR_SPIRIT_TEMPLE_ENTRYWAY, RR_DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY, 0x01E1 } }, { { EntranceType::Dungeon, RR_GRAVEYARD_WARP_PAD_REGION, RR_SHADOW_TEMPLE_ENTRYWAY, 0x0037 }, - { EntranceType::Dungeon, RR_SHADOW_TEMPLE_ENTRYWAY, RR_GRAVEYARD_WARP_PAD_REGION, 0x0205, 0x0580 } }, + { EntranceType::Dungeon, RR_SHADOW_TEMPLE_ENTRYWAY, RR_GRAVEYARD_WARP_PAD_REGION, 0x0205 } }, { { EntranceType::Dungeon, RR_KAKARIKO_VILLAGE, RR_BOTTOM_OF_THE_WELL_ENTRYWAY, 0x0098 }, { EntranceType::Dungeon, RR_BOTTOM_OF_THE_WELL_ENTRYWAY, RR_KAKARIKO_VILLAGE, 0x02A6 } }, { { EntranceType::Dungeon, RR_ZORAS_FOUNTAIN, RR_ICE_CAVERN_ENTRYWAY, 0x0088 }, @@ -1190,22 +1187,32 @@ int EntranceShuffler::ShuffleAllEntrances() { { { EntranceType::WarpSong, RR_PRELUDE_OF_LIGHT_WARP, RR_TEMPLE_OF_TIME, 0x05F4 }, NO_RETURN_ENTRANCE }, { { EntranceType::ChildBoss, RR_DEKU_TREE_BOSS_ENTRYWAY, RR_DEKU_TREE_BOSS_ROOM, 0x040F }, - { EntranceType::ChildBoss, RR_DEKU_TREE_BOSS_ROOM, RR_DEKU_TREE_BOSS_ENTRYWAY, 0x0252, 0x0457 } }, + { EntranceType::ChildBoss, RR_DEKU_TREE_BOSS_ROOM, RR_DEKU_TREE_BOSS_ENTRYWAY, 0x0252 } }, { { EntranceType::ChildBoss, RR_DODONGOS_CAVERN_BOSS_ENTRYWAY, RR_DODONGOS_CAVERN_BOSS_ROOM, 0x040B }, - { EntranceType::ChildBoss, RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_ENTRYWAY, 0x00C5, 0x047A } }, + { EntranceType::ChildBoss, RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_ENTRYWAY, 0x00C5 } }, { { EntranceType::ChildBoss, RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY, RR_JABU_JABUS_BELLY_BOSS_ROOM, 0x0301 }, - { EntranceType::ChildBoss, RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY, 0x0407, - 0x010E } }, + { EntranceType::ChildBoss, RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY, 0x0407 } }, { { EntranceType::AdultBoss, RR_FOREST_TEMPLE_BOSS_ENTRYWAY, RR_FOREST_TEMPLE_BOSS_ROOM, 0x000C }, - { EntranceType::AdultBoss, RR_FOREST_TEMPLE_BOSS_ROOM, RR_FOREST_TEMPLE_BOSS_ENTRYWAY, 0x024E, 0x0608 } }, + { EntranceType::AdultBoss, RR_FOREST_TEMPLE_BOSS_ROOM, RR_FOREST_TEMPLE_BOSS_ENTRYWAY, 0x024E } }, { { EntranceType::AdultBoss, RR_FIRE_TEMPLE_BOSS_ENTRYWAY, RR_FIRE_TEMPLE_BOSS_ROOM, 0x0305 }, - { EntranceType::AdultBoss, RR_FIRE_TEMPLE_BOSS_ROOM, RR_FIRE_TEMPLE_BOSS_ENTRYWAY, 0x0175, 0x0564 } }, + { EntranceType::AdultBoss, RR_FIRE_TEMPLE_BOSS_ROOM, RR_FIRE_TEMPLE_BOSS_ENTRYWAY, 0x0175 } }, { { EntranceType::AdultBoss, RR_WATER_TEMPLE_BOSS_ENTRYWAY, RR_WATER_TEMPLE_BOSS_ROOM, 0x0417 }, - { EntranceType::AdultBoss, RR_WATER_TEMPLE_BOSS_ROOM, RR_WATER_TEMPLE_BOSS_ENTRYWAY, 0x0423, 0x060C } }, + { EntranceType::AdultBoss, RR_WATER_TEMPLE_BOSS_ROOM, RR_WATER_TEMPLE_BOSS_ENTRYWAY, 0x0423 } }, { { EntranceType::AdultBoss, RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY, RR_SPIRIT_TEMPLE_BOSS_ROOM, 0x008D }, - { EntranceType::AdultBoss, RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY, 0x02F5, 0x0610 } }, + { EntranceType::AdultBoss, RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY, 0x02F5 } }, { { EntranceType::AdultBoss, RR_SHADOW_TEMPLE_BOSS_ENTRYWAY, RR_SHADOW_TEMPLE_BOSS_ROOM, 0x0413 }, - { EntranceType::AdultBoss, RR_SHADOW_TEMPLE_BOSS_ROOM, RR_SHADOW_TEMPLE_BOSS_ENTRYWAY, 0x02B2, 0x0580 } }, + { EntranceType::AdultBoss, RR_SHADOW_TEMPLE_BOSS_ROOM, RR_SHADOW_TEMPLE_BOSS_ENTRYWAY, 0x02B2 } }, + + { { EntranceType::BlueWarp, RR_DEKU_TREE_BOSS_ROOM, RR_KF_OUTSIDE_DEKU_TREE, 0x0457 }, NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DEATH_MOUNTAIN_TRAIL, 0x047A }, + NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_ZORAS_FOUNTAIN, 0x010E }, NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_FOREST_TEMPLE_BOSS_ROOM, RR_SACRED_FOREST_MEADOW, 0x0608 }, NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_FIRE_TEMPLE_BOSS_ROOM, RR_DMC_CENTRAL_LOCAL, 0x0564 }, NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_WATER_TEMPLE_BOSS_ROOM, RR_LAKE_HYLIA, 0x060C }, NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_DESERT_COLOSSUS, 0x0610 }, NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_SHADOW_TEMPLE_BOSS_ROOM, RR_GRAVEYARD_WARP_PAD_REGION, 0x0580 }, + NO_RETURN_ENTRANCE }, }; std::map priorityEntranceTable = { @@ -1262,6 +1269,11 @@ int EntranceShuffler::ShuffleAllEntrances() { entrance->GetConnectedRegionKey() == RR_DEKU_TREE_BOSS_ROOM; }); } + if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) { + for (Entrance* entrance : entrancePools[EntranceType::Boss]) { + entrancePools[EntranceType::BossReverse].push_back(entrance->GetReverse()); + } + } } else { entrancePools[EntranceType::ChildBoss] = GetShuffleableEntrances(EntranceType::ChildBoss); entrancePools[EntranceType::AdultBoss] = GetShuffleableEntrances(EntranceType::AdultBoss); @@ -1273,6 +1285,14 @@ int EntranceShuffler::ShuffleAllEntrances() { entrance->GetConnectedRegionKey() == RR_DEKU_TREE_BOSS_ROOM; }); } + if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) { + for (Entrance* entrance : entrancePools[EntranceType::ChildBoss]) { + entrancePools[EntranceType::ChildBossReverse].push_back(entrance->GetReverse()); + } + for (Entrance* entrance : entrancePools[EntranceType::AdultBoss]) { + entrancePools[EntranceType::AdultBossReverse].push_back(entrance->GetReverse()); + } + } } } @@ -1346,11 +1366,13 @@ int EntranceShuffler::ShuffleAllEntrances() { // combine entrance pools if mixing pools. Only continue if more than one pool is selected. int totalMixedPools = - (ctx->GetOption(RSK_MIX_DUNGEON_ENTRANCES) ? 1 : 0) + (ctx->GetOption(RSK_MIX_OVERWORLD_ENTRANCES) ? 1 : 0) + - (ctx->GetOption(RSK_MIX_INTERIOR_ENTRANCES) ? 1 : 0) + (ctx->GetOption(RSK_MIX_GROTTO_ENTRANCES) ? 1 : 0); + (ctx->GetOption(RSK_MIX_DUNGEON_ENTRANCES) ? 1 : 0) + (ctx->GetOption(RSK_MIX_BOSS_ENTRANCES) ? 1 : 0) + + (ctx->GetOption(RSK_MIX_OVERWORLD_ENTRANCES) ? 1 : 0) + (ctx->GetOption(RSK_MIX_INTERIOR_ENTRANCES) ? 1 : 0) + + (ctx->GetOption(RSK_MIX_GROTTO_ENTRANCES) ? 1 : 0); if (totalMixedPools < 2) { ctx->GetOption(RSK_MIXED_ENTRANCE_POOLS).SetSelectedIndex(RO_GENERIC_OFF); ctx->GetOption(RSK_MIX_DUNGEON_ENTRANCES).SetSelectedIndex(RO_GENERIC_OFF); + ctx->GetOption(RSK_MIX_BOSS_ENTRANCES).SetSelectedIndex(RO_GENERIC_OFF); ctx->GetOption(RSK_MIX_OVERWORLD_ENTRANCES).SetSelectedIndex(RO_GENERIC_OFF); ctx->GetOption(RSK_MIX_INTERIOR_ENTRANCES).SetSelectedIndex(RO_GENERIC_OFF); ctx->GetOption(RSK_MIX_GROTTO_ENTRANCES).SetSelectedIndex(RO_GENERIC_OFF); @@ -1364,6 +1386,12 @@ int EntranceShuffler::ShuffleAllEntrances() { poolsToMix.insert(EntranceType::DungeonReverse); } } + if (ctx->GetOption(RSK_MIX_DUNGEON_ENTRANCES)) { + poolsToMix.insert(EntranceType::Boss); + if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) { + poolsToMix.insert(EntranceType::BossReverse); + } + } if (ctx->GetOption(RSK_SHUFFLE_OVERWORLD_ENTRANCES)) { poolsToMix.insert(EntranceType::Overworld); } @@ -1503,6 +1531,94 @@ 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_ENTRYWAY), + GetEntrance(EntranceNameByRegions(RR_DEKU_TREE_ENTRYWAY, RR_KF_OUTSIDE_DEKU_TREE)) }, + { EntranceNameByRegions(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(RR_DODONGOS_CAVERN_ENTRYWAY, RR_DEATH_MOUNTAIN_TRAIL)) }, + { EntranceNameByRegions(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(RR_JABU_JABUS_BELLY_ENTRYWAY, RR_ZORAS_FOUNTAIN)) }, + { EntranceNameByRegions(RR_FOREST_TEMPLE_BOSS_ROOM, RR_FOREST_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(RR_FOREST_TEMPLE_ENTRYWAY, RR_SACRED_FOREST_MEADOW)) }, + { EntranceNameByRegions(RR_FIRE_TEMPLE_BOSS_ROOM, RR_FIRE_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(RR_FIRE_TEMPLE_ENTRYWAY, RR_DMC_CENTRAL_LOCAL)) }, + { EntranceNameByRegions(RR_WATER_TEMPLE_BOSS_ROOM, RR_WATER_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(RR_WATER_TEMPLE_ENTRYWAY, RR_LAKE_HYLIA)) }, + { EntranceNameByRegions(RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(RR_SPIRIT_TEMPLE_ENTRYWAY, RR_DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY)) }, + { EntranceNameByRegions(RR_SHADOW_TEMPLE_BOSS_ROOM, RR_SHADOW_TEMPLE_BOSS_ENTRYWAY), + GetEntrance(EntranceNameByRegions(RR_SHADOW_TEMPLE_ENTRYWAY, RR_GRAVEYARD_WARP_PAD_REGION)) }, + }; + + // 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(EntranceNameByRegions(RR_DEKU_TREE_BOSS_ROOM, RR_KF_OUTSIDE_DEKU_TREE)) }, + { EntranceNameByRegions(RR_DODONGOS_CAVERN_ENTRYWAY, RR_DEATH_MOUNTAIN_TRAIL), + GetEntrance(EntranceNameByRegions(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DEATH_MOUNTAIN_TRAIL)) }, + { EntranceNameByRegions(RR_JABU_JABUS_BELLY_ENTRYWAY, RR_ZORAS_FOUNTAIN), + GetEntrance(EntranceNameByRegions(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_ZORAS_FOUNTAIN)) }, + { EntranceNameByRegions(RR_FOREST_TEMPLE_ENTRYWAY, RR_SACRED_FOREST_MEADOW), + GetEntrance(EntranceNameByRegions(RR_FOREST_TEMPLE_BOSS_ROOM, RR_SACRED_FOREST_MEADOW)) }, + { EntranceNameByRegions(RR_FIRE_TEMPLE_ENTRYWAY, RR_DMC_CENTRAL_LOCAL), + GetEntrance(EntranceNameByRegions(RR_FIRE_TEMPLE_BOSS_ROOM, RR_DMC_CENTRAL_LOCAL)) }, + { EntranceNameByRegions(RR_WATER_TEMPLE_ENTRYWAY, RR_LAKE_HYLIA), + GetEntrance(EntranceNameByRegions(RR_WATER_TEMPLE_BOSS_ROOM, RR_LAKE_HYLIA)) }, + { EntranceNameByRegions(RR_SPIRIT_TEMPLE_ENTRYWAY, RR_DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY), + GetEntrance(EntranceNameByRegions(RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_DESERT_COLOSSUS)) }, + { EntranceNameByRegions(RR_SHADOW_TEMPLE_ENTRYWAY, RR_GRAVEYARD_WARP_PAD_REGION), + GetEntrance(EntranceNameByRegions(RR_SHADOW_TEMPLE_BOSS_ROOM, RR_GRAVEYARD_WARP_PAD_REGION)) }, + }; + + // Pair + std::vector bossRoomExitPairs = { + { GetEntrance(EntranceNameByRegions(RR_DEKU_TREE_BOSS_ROOM, RR_KF_OUTSIDE_DEKU_TREE)), + GetEntrance(EntranceNameByRegions(RR_DEKU_TREE_BOSS_ROOM, RR_DEKU_TREE_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DEATH_MOUNTAIN_TRAIL)), + GetEntrance(EntranceNameByRegions(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_ZORAS_FOUNTAIN)), + GetEntrance(EntranceNameByRegions(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(RR_FOREST_TEMPLE_BOSS_ROOM, RR_SACRED_FOREST_MEADOW)), + GetEntrance(EntranceNameByRegions(RR_FOREST_TEMPLE_BOSS_ROOM, RR_FOREST_TEMPLE_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(RR_FIRE_TEMPLE_BOSS_ROOM, RR_DMC_CENTRAL_LOCAL)), + GetEntrance(EntranceNameByRegions(RR_FIRE_TEMPLE_BOSS_ROOM, RR_FIRE_TEMPLE_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(RR_WATER_TEMPLE_BOSS_ROOM, RR_LAKE_HYLIA)), + GetEntrance(EntranceNameByRegions(RR_WATER_TEMPLE_BOSS_ROOM, RR_WATER_TEMPLE_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_DESERT_COLOSSUS)), + GetEntrance(EntranceNameByRegions(RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY)) }, + { GetEntrance(EntranceNameByRegions(RR_SHADOW_TEMPLE_BOSS_ROOM, RR_GRAVEYARD_WARP_PAD_REGION)), + GetEntrance(EntranceNameByRegions(RR_SHADOW_TEMPLE_BOSS_ROOM, RR_SHADOW_TEMPLE_BOSS_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; + } + + 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; + } + return ENTRANCE_SHUFFLE_SUCCESS; } @@ -1518,16 +1634,21 @@ void EntranceShuffler::CreateEntranceOverrides() { int i = 0; for (Entrance* entrance : allShuffleableEntrances) { + // Include blue warps when dungeons or bosses are shuffled + bool includeBluewarps = + entrance->GetType() == Rando::EntranceType::BlueWarp && + (ctx->GetOption(RSK_SHUFFLE_DUNGEON_ENTRANCES) || ctx->GetOption(RSK_SHUFFLE_BOSS_ENTRANCES)); + // Double-check to make sure the entrance is actually shuffled - if (!entrance->IsShuffled()) { + if (!entrance->IsShuffled() && !includeBluewarps) { continue; } auto message = "Setting " + entrance->to_string() + "\n"; SPDLOG_DEBUG(message); + uint8_t type = (uint8_t)entrance->GetType(); int16_t originalIndex = entrance->GetIndex(); - int16_t originalBlueWarp = entrance->GetBlueWarp(); int16_t replacementIndex = entrance->GetReplacement()->GetIndex(); int16_t destinationIndex = -1; @@ -1541,9 +1662,9 @@ void EntranceShuffler::CreateEntranceOverrides() { } entranceOverrides[i] = { + .type = type, .index = originalIndex, .destination = destinationIndex, - .blueWarp = originalBlueWarp, .override = replacementIndex, .overrideDestination = replacementDestinationIndex, }; @@ -1559,9 +1680,9 @@ void EntranceShuffler::CreateEntranceOverrides() { /// @brief set all the entrances to be 0 to indicate an unshuffled entrance void EntranceShuffler::UnshuffleAllEntrances() { for (auto& entranceOveride : entranceOverrides) { + entranceOveride.type = 0; entranceOveride.index = 0; entranceOveride.destination = 0; - entranceOveride.blueWarp = 0; entranceOveride.override = 0; entranceOveride.overrideDestination = 0; } @@ -1575,12 +1696,12 @@ void EntranceShuffler::ParseJson(nlohmann::json spoilerFileJson) { for (auto it = entrancesJson.begin(); it != entrancesJson.end(); ++it, i++) { nlohmann::json entranceJson = *it; for (auto entranceIt = entranceJson.begin(); entranceIt != entranceJson.end(); ++entranceIt) { - if (entranceIt.key() == "index") { + if (entranceIt.key() == "type") { + entranceOverrides[i].type = entranceIt.value(); + } else if (entranceIt.key() == "index") { entranceOverrides[i].index = entranceIt.value(); } else if (entranceIt.key() == "destination") { entranceOverrides[i].destination = entranceIt.value(); - } else if (entranceIt.key() == "blueWarp") { - entranceOverrides[i].blueWarp = entranceIt.value(); } else if (entranceIt.key() == "override") { entranceOverrides[i].override = entranceIt.value(); } else if (entranceIt.key() == "overrideDestination") { @@ -1593,5 +1714,5 @@ void EntranceShuffler::ParseJson(nlohmann::json spoilerFileJson) { } // namespace Rando extern "C" EntranceOverride* Randomizer_GetEntranceOverrides() { - return Rando::Context::GetInstance()->GetEntranceShuffler()->entranceOverrides.data(); + return Rando::Context::GetInstance()->GetEntranceShuffler()->entranceOverrides.data(); } \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/entrance.h b/soh/soh/Enhancements/randomizer/entrance.h index 0886ddb26..e17687842 100644 --- a/soh/soh/Enhancements/randomizer/entrance.h +++ b/soh/soh/Enhancements/randomizer/entrance.h @@ -15,12 +15,16 @@ enum class EntranceType { OwlDrop, Spawn, WarpSong, + BlueWarp, Dungeon, GanonDungeon, DungeonReverse, Boss, + BossReverse, ChildBoss, + ChildBossReverse, AdultBoss, + AdultBossReverse, Interior, InteriorReverse, SpecialInterior, @@ -45,6 +49,7 @@ class Entrance { uint32_t Getuint32_t() const; bool CheckConditionAtAgeTime(bool& age, bool& time, bool passAnyway = false) const; RandomizerRegion GetConnectedRegionKey() const; + RandomizerRegion GetOriginalConnectedRegionKey() const; Area* GetConnectedRegion() const; void SetParentRegion(RandomizerRegion newParent); RandomizerRegion GetParentRegionKey() const; @@ -61,8 +66,6 @@ class Entrance { void SetDecoupled(); int16_t GetIndex() const; void SetIndex(int16_t newIndex); - int16_t GetBlueWarp() const; - void SetBlueWarp(int16_t newBlueWarp); Entrance* GetAssumed() const; void SetReplacement(Entrance* newReplacement); Entrance* GetReplacement() const; @@ -78,6 +81,7 @@ class Entrance { private: RandomizerRegion parentRegion; RandomizerRegion connectedRegion; + RandomizerRegion originalConnectedRegion; std::vector conditions_met; EntranceType type = EntranceType::None; @@ -86,7 +90,6 @@ class Entrance { Entrance* assumed = nullptr; Entrance* replacement = nullptr; int16_t index = 0xFFFF; - int16_t blueWarp = 0; bool shuffled = false; bool primary = false; bool addedToPool = false; @@ -99,7 +102,6 @@ typedef struct { RandomizerRegion parentRegion; RandomizerRegion connectedRegion; int16_t index; - int16_t blueWarp; } EntranceLinkInfo; typedef struct { diff --git a/soh/soh/Enhancements/randomizer/option_descriptions.cpp b/soh/soh/Enhancements/randomizer/option_descriptions.cpp index 95db75e55..e50846005 100644 --- a/soh/soh/Enhancements/randomizer/option_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/option_descriptions.cpp @@ -170,12 +170,14 @@ void Settings::CreateOptionDescriptions() { "This also adds the one-way entrance from Gerudo Valley to Lake Hylia in the pool of " "overworld entrances when they are shuffled."; mOptionDescriptions[RSK_MIXED_ENTRANCE_POOLS] = - "Shuffle entrances into a mixed pool instead of separate ones.\n" + "Shuffle entrances into a mixed pool instead of separate ones. Has no affect on pools whose " + "entrances aren't shuffled, and \"Shuffle Boss Entrances\" must be set to \"Full\" to include them.\n" "\n" "For example, enabling the settings to shuffle grotto, dungeon, and overworld entrances and " "selecting grotto and dungeon entrances here will allow a dungeon to be inside a grotto or " "vice versa, while overworld entrances are shuffled in their own separate pool and indoors stay vanilla."; mOptionDescriptions[RSK_MIX_DUNGEON_ENTRANCES] = "Dungeon entrances will be part of the mixed pool"; + mOptionDescriptions[RSK_MIX_BOSS_ENTRANCES] = "Boss entrances will be part of the mixed pool"; mOptionDescriptions[RSK_MIX_OVERWORLD_ENTRANCES] = "Overworld entrances will be part of the mixed pool"; mOptionDescriptions[RSK_MIX_INTERIOR_ENTRANCES] = "Interior entrances will be part of the mixed pool"; mOptionDescriptions[RSK_MIX_GROTTO_ENTRANCES] = "Grotto entrances will be part of the mixed pool"; diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index b2d9c7866..179f8144e 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -236,23 +236,6 @@ std::unordered_map getItemIdToItemId = { { GI_CLAIM_CHECK, ITEM_CLAIM_CHECK } }; -std::string sanitize(std::string stringValue) { - // Add backslashes. - for (auto i = stringValue.begin();;) { - auto const pos = std::find_if(i, stringValue.end(), [](char const c) { return '\\' == c || '\'' == c || '"' == c; }); - if (pos == stringValue.end()) { - break; - } - i = std::next(stringValue.insert(pos, '\\'), 2); - } - - // Removes others. - stringValue.erase(std::remove_if(stringValue.begin(), stringValue.end(), [](char const c) { - return '\n' == c || '\r' == c || '\0' == c || '\x1A' == c; }), stringValue.end()); - - return stringValue; -} - #pragma optimize("", off) #pragma GCC push_options #pragma GCC optimize ("O0") diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 3fc4efa86..ac947d4da 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -68,6 +68,7 @@ void Settings::CreateOptions() { mOptions[RSK_SHUFFLE_OVERWORLD_SPAWNS] = Option::Bool("Overworld Spawns", "gRandomizeShuffleOverworldSpanws", mOptionDescriptions[RSK_SHUFFLE_OVERWORLD_SPAWNS]); mOptions[RSK_MIXED_ENTRANCE_POOLS] = Option::Bool("Mixed Entrance Pools", "gRandomizeMixedEntrances", mOptionDescriptions[RSK_MIXED_ENTRANCE_POOLS]); mOptions[RSK_MIX_DUNGEON_ENTRANCES] = Option::Bool("Mix Dungeons", "gRandomizeMixDungeons", mOptionDescriptions[RSK_MIX_DUNGEON_ENTRANCES], IMFLAG_NONE); + mOptions[RSK_MIX_BOSS_ENTRANCES] = Option::Bool("Mix Bosses", "gRandomizeMixBosses", mOptionDescriptions[RSK_MIX_BOSS_ENTRANCES], IMFLAG_NONE); mOptions[RSK_MIX_OVERWORLD_ENTRANCES] = Option::Bool("Mix Overworld", "gRandomizeMixOverworld", mOptionDescriptions[RSK_MIX_OVERWORLD_ENTRANCES], IMFLAG_NONE); mOptions[RSK_MIX_INTERIOR_ENTRANCES] = Option::Bool("Mix Interiors", "gRandomizeMixInteriors", mOptionDescriptions[RSK_MIX_INTERIOR_ENTRANCES], IMFLAG_NONE); mOptions[RSK_MIX_GROTTO_ENTRANCES] = Option::Bool("Mix Grottos", "gRandomizeMixGrottos", mOptionDescriptions[RSK_MIX_GROTTO_ENTRANCES]); @@ -638,6 +639,7 @@ void Settings::CreateOptions() { &mOptions[RSK_DECOUPLED_ENTRANCES], &mOptions[RSK_MIXED_ENTRANCE_POOLS], &mOptions[RSK_MIX_DUNGEON_ENTRANCES], + &mOptions[RSK_MIX_BOSS_ENTRANCES], &mOptions[RSK_MIX_OVERWORLD_ENTRANCES], &mOptions[RSK_MIX_INTERIOR_ENTRANCES], &mOptions[RSK_MIX_GROTTO_ENTRANCES] @@ -828,6 +830,7 @@ void Settings::CreateOptions() { &mOptions[RSK_SHUFFLE_OVERWORLD_SPAWNS], &mOptions[RSK_MIXED_ENTRANCE_POOLS], &mOptions[RSK_MIX_DUNGEON_ENTRANCES], + &mOptions[RSK_MIX_BOSS_ENTRANCES], &mOptions[RSK_MIX_OVERWORLD_ENTRANCES], &mOptions[RSK_MIX_INTERIOR_ENTRANCES], &mOptions[RSK_MIX_GROTTO_ENTRANCES], @@ -1159,6 +1162,7 @@ void Settings::CreateOptions() { { "World Settings:Overworld Spawns", RSK_SHUFFLE_OVERWORLD_SPAWNS }, { "World Settings:Mixed Entrance Pools", RSK_MIXED_ENTRANCE_POOLS }, { "World Settings:Mix Dungeons", RSK_MIX_DUNGEON_ENTRANCES }, + { "World Settings:Mix Bosses", RSK_MIX_BOSS_ENTRANCES }, { "World Settings:Mix Overworld", RSK_MIX_OVERWORLD_ENTRANCES }, { "World Settings:Mix Interiors", RSK_MIX_INTERIOR_ENTRANCES }, { "World Settings:Mix Grottos", RSK_MIX_GROTTO_ENTRANCES }, @@ -1534,12 +1538,14 @@ void Settings::UpdateOptionProperties() { if (CVarGetInteger("gRandomizeMixedEntrances", RO_GENERIC_OFF)) { mOptions[RSK_MIXED_ENTRANCE_POOLS].RemoveFlag(IMFLAG_SEPARATOR_BOTTOM); mOptions[RSK_MIX_DUNGEON_ENTRANCES].Unhide(); + mOptions[RSK_MIX_BOSS_ENTRANCES].Unhide(); mOptions[RSK_MIX_OVERWORLD_ENTRANCES].Unhide(); mOptions[RSK_MIX_INTERIOR_ENTRANCES].Unhide(); mOptions[RSK_MIX_GROTTO_ENTRANCES].Unhide(); } else { mOptions[RSK_MIXED_ENTRANCE_POOLS].AddFlag(IMFLAG_SEPARATOR_BOTTOM); mOptions[RSK_MIX_DUNGEON_ENTRANCES].Hide(); + mOptions[RSK_MIX_BOSS_ENTRANCES].Hide(); mOptions[RSK_MIX_OVERWORLD_ENTRANCES].Hide(); mOptions[RSK_MIX_INTERIOR_ENTRANCES].Hide(); mOptions[RSK_MIX_GROTTO_ENTRANCES].Hide(); @@ -2309,6 +2315,7 @@ void Settings::ParseJson(nlohmann::json spoilerFileJson) { case RSK_SHUFFLE_OVERWORLD_SPAWNS: case RSK_MIXED_ENTRANCE_POOLS: case RSK_MIX_DUNGEON_ENTRANCES: + case RSK_MIX_BOSS_ENTRANCES: case RSK_MIX_OVERWORLD_ENTRANCES: case RSK_MIX_INTERIOR_ENTRANCES: case RSK_MIX_GROTTO_ENTRANCES: diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 0fc3394c9..cb5820204 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -357,9 +357,9 @@ void SaveManager::LoadRandomizerVersion3() { auto entranceCtx = randoContext->GetEntranceShuffler(); SaveManager::Instance->LoadArray("entrances", ARRAY_COUNT(entranceCtx->entranceOverrides), [&](size_t i) { SaveManager::Instance->LoadStruct("", [&]() { + SaveManager::Instance->LoadData("type", entranceCtx->entranceOverrides[i].type); SaveManager::Instance->LoadData("index", entranceCtx->entranceOverrides[i].index); SaveManager::Instance->LoadData("destination", entranceCtx->entranceOverrides[i].destination); - SaveManager::Instance->LoadData("blueWarp", entranceCtx->entranceOverrides[i].blueWarp); SaveManager::Instance->LoadData("override", entranceCtx->entranceOverrides[i].override); SaveManager::Instance->LoadData("overrideDestination", entranceCtx->entranceOverrides[i].overrideDestination);