fix crash when generating decoupled entrances with boss shuffle (#6189)

regression from ganon's tower shuffle
This commit is contained in:
Philip Dubé
2026-02-10 19:22:40 +00:00
committed by GitHub
parent 04eb8d1601
commit 92cb7f1594
10 changed files with 83 additions and 122 deletions

View File

@@ -211,6 +211,8 @@ void ProcessExits(Region* region, GetAccessibleLocationsStruct& gals, Randomizer
// Include bluewarps when unshuffled but dungeon or boss shuffle is on
if ((exit.IsShuffled() ||
(exit.GetType() == Rando::EntranceType::BlueWarp &&
(ctx->GetOption(RSK_SHUFFLE_GANONS_TOWER_ENTRANCE) ||
exit.GetParentRegionKey() != RR_GANONS_TOWER_STAIRS_1) &&
(ctx->GetOption(RSK_SHUFFLE_DUNGEON_ENTRANCES) || ctx->GetOption(RSK_SHUFFLE_BOSS_ENTRANCES)))) &&
!exit.IsAddedToPool() && !ctx->GetEntranceShuffler()->HasNoRandomEntrances()) {
gals.entranceSphere.push_back(&exit);

View File

@@ -8,6 +8,7 @@
#include "pool_functions.hpp"
#include "soh/Enhancements/randomizer/randomizer_entrance_tracker.h"
#include <nlohmann/json.hpp>
#include <spdlog/fmt/fmt.h>
#include <cstdio>
#include <cstdlib>
@@ -223,12 +224,8 @@ static void WriteChosenOptions() {
static void WritePlaythrough() {
auto ctx = Rando::Context::GetInstance();
for (uint32_t i = 0; i < ctx->playthroughLocations.size(); ++i) {
auto sphereNum = std::to_string(i);
std::string sphereString = "sphere ";
if (i < 10)
sphereString += "0";
sphereString += sphereNum;
for (size_t i = 0; i < ctx->playthroughLocations.size(); i++) {
std::string sphereString = fmt::format("sphere {:0>2}", i);
for (const RandomizerCheck key : ctx->playthroughLocations[i]) {
if (!ctx->GetItemLocation(key)->IsHidden()) {
WriteLocation(sphereString, key, true);
@@ -240,12 +237,8 @@ static void WritePlaythrough() {
// Write the randomized entrance playthrough to the spoiler log, if applicable
static void WriteShuffledEntrances() {
auto ctx = Rando::Context::GetInstance();
for (uint32_t i = 0; i < ctx->GetEntranceShuffler()->playthroughEntrances.size(); ++i) {
auto sphereNum = std::to_string(i);
std::string sphereString = "sphere ";
if (i < 10)
sphereString += "0";
sphereString += sphereNum;
for (size_t i = 0; i < ctx->GetEntranceShuffler()->playthroughEntrances.size(); i++) {
std::string sphereString = fmt::format("sphere {:0>2}", i);
for (Entrance* entrance : ctx->GetEntranceShuffler()->playthroughEntrances[i]) {
WriteShuffledEntrance(sphereString, entrance);
}

View File

@@ -589,14 +589,13 @@ void SetAllEntrancesData() {
NO_RETURN_ENTRANCE },
{ { EntranceType::BlueWarp, RR_SHADOW_TEMPLE_BOSS_ROOM, RR_GRAVEYARD_WARP_PAD_REGION, ENTR_GRAVEYARD_SHADOW_TEMPLE_BLUE_WARP },
NO_RETURN_ENTRANCE },
{ { EntranceType::BlueWarp, RR_GANONS_TOWER_GANONDORF_LAIR, RR_GANONS_TOWER_STAIRS_1, ENTR_GANONS_TOWER_0 },
{ { EntranceType::BlueWarp, RR_GANONS_TOWER_STAIRS_1, RR_CASTLE_GROUNDS_FROM_GANONS_CASTLE, ENTR_OUTSIDE_GANONS_CASTLE_1_2 },
NO_RETURN_ENTRANCE },
// clang-format on
};
auto ctx = Rando::Context::GetInstance();
for (auto& entrancePair : entranceShuffleTable) {
auto& forwardEntry = entrancePair.first;
auto& returnEntry = entrancePair.second;
@@ -695,7 +694,6 @@ std::vector<Entrance*> EntranceShuffler::AssumeEntrancePool(std::vector<Entrance
}
static bool AreEntrancesCompatible(Entrance* entrance, Entrance* target, std::vector<EntrancePair>& rollbacks) {
// Entrances shouldn't connect to their own scene, fail in this situation
if (
// allow "special" areas to connect to eachother
@@ -712,21 +710,19 @@ static bool AreEntrancesCompatible(Entrance* entrance, Entrance* target, std::ve
target->GetConnectedRegion()->scene == SCENE_OUTSIDE_GANONS_CASTLE) ||
(entrance->GetParentRegion()->scene == SCENE_OUTSIDE_GANONS_CASTLE &&
target->GetConnectedRegion()->scene == SCENE_HYRULE_CASTLE))) {
auto message = "Entrance " + entrance->GetName() + " attempted to connect with own scene target " +
target->to_string() + ". Connection failed.\n";
SPDLOG_DEBUG(message);
SPDLOG_DEBUG("Entrance {} attempted to connect with own scene target {}. Connection failed.",
entrance->GetName(), target->to_string());
return false;
}
// One way entrances shouldn't lead to the same scene as other already chosen one way entrances
auto type = entrance->GetType();
const std::vector<EntranceType> oneWayTypes = { EntranceType::OwlDrop, EntranceType::Spawn,
EntranceType::WarpSong };
const std::array<EntranceType, 3> oneWayTypes = { EntranceType::OwlDrop, EntranceType::Spawn,
EntranceType::WarpSong };
if (ElementInContainer(type, oneWayTypes)) {
for (auto& rollback : rollbacks) {
if (rollback.first->GetConnectedRegion()->scene == target->GetConnectedRegion()->scene) {
auto message = "A one way entrance already leads to " + target->to_string() + ". Connection failed.\n";
SPDLOG_DEBUG(message);
SPDLOG_DEBUG("A one way entrance already leads to {}. Connection failed.", target->to_string());
return false;
}
}
@@ -738,8 +734,7 @@ static bool AreEntrancesCompatible(Entrance* entrance, Entrance* target, std::ve
// Change connections between an entrance and a target assumed entrance, in order to test the connections afterwards if
// necessary
static void ChangeConnections(Entrance* entrance, Entrance* targetEntrance) {
auto message = "Attempting to connect " + entrance->GetName() + " to " + targetEntrance->to_string() + "\n";
SPDLOG_DEBUG(message);
SPDLOG_DEBUG("Attempting to connect {} to {}", entrance->GetName(), targetEntrance->to_string());
entrance->Connect(targetEntrance->Disconnect());
entrance->SetReplacement(targetEntrance->GetReplacement());
if (entrance->GetReverse() != nullptr && !entrance->IsDecoupled()) {
@@ -791,7 +786,7 @@ static bool EntranceUnreachableAs(Entrance* entrance, uint8_t age, std::vector<E
static bool ValidateWorld(Entrance* entrancePlaced) {
auto ctx = Rando::Context::GetInstance();
SPDLOG_DEBUG("Validating world\n");
SPDLOG_DEBUG("Validating world");
// check certain conditions when certain types of ER are enabled
EntranceType type = EntranceType::None;
@@ -839,13 +834,11 @@ static bool ValidateWorld(Entrance* entrancePlaced) {
if (ElementInContainer(replacementName, childForbidden) &&
!EntranceUnreachableAs(entrance, RO_AGE_CHILD, alreadyChecked)) {
auto message = replacementName + " is replaced by an entrance with a potential child access\n";
SPDLOG_DEBUG(message);
SPDLOG_DEBUG("{} is replaced by an entrance with a potential child access", replacementName);
return false;
} else if (ElementInContainer(replacementName, adultForbidden) &&
!EntranceUnreachableAs(entrance, RO_AGE_ADULT, alreadyChecked)) {
auto message = replacementName + " is replaced by an entrance with a potential adult access\n";
SPDLOG_DEBUG(message);
SPDLOG_DEBUG("{} is replaced by an entrance with a potential adult access", replacementName);
return false;
}
}
@@ -855,13 +848,11 @@ static bool ValidateWorld(Entrance* entrancePlaced) {
if (ElementInContainer(name, childForbidden) &&
!EntranceUnreachableAs(entrance, RO_AGE_CHILD, alreadyChecked)) {
auto message = name + " is potentially accessible as child\n";
SPDLOG_DEBUG(message);
SPDLOG_DEBUG("{} is potentially accessible as child", name);
return false;
} else if (ElementInContainer(name, adultForbidden) &&
!EntranceUnreachableAs(entrance, RO_AGE_ADULT, alreadyChecked)) {
auto message = name + " is potentially accessible as adult\n";
SPDLOG_DEBUG(message);
SPDLOG_DEBUG("{} is potentially accessible as adult");
return false;
}
}
@@ -874,13 +865,13 @@ static bool ValidateWorld(Entrance* entrancePlaced) {
// At least one valid starting region with all basic refills should be reachable without using any items at
// the beginning of the seed
if (!RegionTable(RR_KOKIRI_FOREST)->HasAccess() && !RegionTable(RR_KAKARIKO_VILLAGE)->HasAccess()) {
SPDLOG_DEBUG("Invalid starting area\n");
SPDLOG_DEBUG("Invalid starting area");
return false;
}
// Check that a region where time passes is always reachable as both ages without having collected any items
if (!Regions::HasTimePassAccess(RO_AGE_CHILD) || !Regions::HasTimePassAccess(RO_AGE_ADULT)) {
SPDLOG_DEBUG("Time passing is not guaranteed as both ages\n");
SPDLOG_DEBUG("Time passing is not guaranteed as both ages");
return false;
}
@@ -888,16 +879,16 @@ static bool ValidateWorld(Entrance* entrancePlaced) {
// This is important to ensure that the player never loses access to the pedestal after going through time
if (ctx->GetOption(RSK_SELECTED_STARTING_AGE).Is(RO_AGE_CHILD) &&
!RegionTable(RR_TEMPLE_OF_TIME)->Adult()) {
SPDLOG_DEBUG("Path to Temple of Time as adult is not guaranteed\n");
SPDLOG_DEBUG("Path to Temple of Time as adult is not guaranteed");
return false;
} else if (ctx->GetOption(RSK_SELECTED_STARTING_AGE).Is(RO_AGE_ADULT) &&
!RegionTable(RR_TEMPLE_OF_TIME)->Child()) {
SPDLOG_DEBUG("Path to Temple of Time as child is not guaranteed\n");
SPDLOG_DEBUG("Path to Temple of Time as child is not guaranteed");
return false;
}
}
SPDLOG_DEBUG("All Locations NOT REACHABLE\n");
SPDLOG_DEBUG("All Locations NOT REACHABLE");
return false;
}
return true;
@@ -933,7 +924,6 @@ static void ConfirmReplacement(Entrance* entrance, Entrance* targetEntrance) {
}
bool EntranceShuffler::ReplaceEntrance(Entrance* entrance, Entrance* target, std::vector<EntrancePair>& rollbacks) {
if (!AreEntrancesCompatible(entrance, target, rollbacks)) {
return false;
}
@@ -941,8 +931,7 @@ bool EntranceShuffler::ReplaceEntrance(Entrance* entrance, Entrance* target, std
if (ValidateWorld(entrance)) {
#ifdef ENABLE_DEBUG
std::string ticks = std::to_string(svcGetSystemTick());
auto message = "Dumping World Graph at " + ticks + "\n";
// SPDLOG_DEBUG(message);
// SPDLOG_DEBUG("Dumping World Graph at {}", ticks);
// Regions::DumpWorldGraph(ticks);
#endif
rollbacks.push_back(EntrancePair{ entrance, target });
@@ -951,8 +940,7 @@ bool EntranceShuffler::ReplaceEntrance(Entrance* entrance, Entrance* target, std
} else {
#ifdef ENABLE_DEBUG
std::string ticks = std::to_string(svcGetSystemTick());
auto message = "Dumping World Graph at " + ticks + "\n";
// SPDLOG_DEBUG(message);
// SPDLOG_DEBUG("Dumping World Graph at {}", ticks);
// Regions::DumpWorldGraph(ticks);
#endif
if (entrance->GetConnectedRegionKey() != RR_NONE) {
@@ -1006,7 +994,7 @@ bool EntranceShuffler::PlaceOneWayPriorityEntrance(
}
}
}
SPDLOG_DEBUG("ERROR: Unable to place priority one-way entrance for " + priorityName + "\n");
SPDLOG_DEBUG("ERROR: Unable to place priority one-way entrance for {}", priorityName);
assert(false);
return false;
}
@@ -1044,7 +1032,7 @@ bool EntranceShuffler::ShuffleOneWayPriorityEntrances(std::map<std::string, Prio
if (retryCount <= 0) {
SPDLOG_DEBUG(
"Entrance placement attempt count for one way priorities exceeded. Restarting randomization completely\n");
"Entrance placement attempt count for one way priorities exceeded. Restarting randomization completely");
mEntranceShuffleFailure = true;
return false;
}
@@ -1154,9 +1142,8 @@ void EntranceShuffler::ShuffleEntrancePool(std::vector<Entrance*>& entrancePool,
if (retries != retryCount) {
#ifdef ENABLE_DEBUG
std::string ticks = std::to_string(svcGetSystemTick());
auto message = "Failed to connect entrances. Retrying " + std::to_string(retries) +
" more times.\nDumping World Graph at " + ticks + "\n";
SPDLOG_DEBUG(message);
SPDLOG_DEBUG("Failed to connect entrances. Retrying {} more times.", retries);
SPDLOG_DEBUG("Dumping World Graph at {}", ticks);
// Regions::DumpWorldGraph(ticks);
#endif
}
@@ -1248,23 +1235,23 @@ int EntranceShuffler::ShuffleAllEntrances() {
if (ctx->GetOption(RSK_SHUFFLE_BOSS_ENTRANCES).Is(RO_BOSS_ROOM_ENTRANCE_SHUFFLE_FULL)) {
entrancePools[EntranceType::Boss] = GetShuffleableEntrances(EntranceType::ChildBoss);
AddElementsToPool(entrancePools[EntranceType::Boss], GetShuffleableEntrances(EntranceType::AdultBoss));
if (ctx->GetOption(RSK_SHUFFLE_GANONS_TOWER_ENTRANCE)) {
AddElementsToPool(entrancePools[EntranceType::Boss], GetShuffleableEntrances(EntranceType::GanonTower));
}
if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) {
for (Entrance* entrance : entrancePools[EntranceType::Boss]) {
entrancePools[EntranceType::BossReverse].push_back(entrance->GetReverse());
}
}
if (ctx->GetOption(RSK_SHUFFLE_GANONS_TOWER_ENTRANCE).IsNot(RO_GENERIC_OFF)) {
AddElementsToPool(entrancePools[EntranceType::Boss], GetShuffleableEntrances(EntranceType::GanonTower));
if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) {
for (Entrance* entrance : GetShuffleableEntrances(EntranceType::GanonTower)) {
entrancePools[EntranceType::BossReverse].push_back(entrance->GetReverse());
}
}
}
} else {
entrancePools[EntranceType::ChildBoss] = GetShuffleableEntrances(EntranceType::ChildBoss);
entrancePools[EntranceType::AdultBoss] = GetShuffleableEntrances(EntranceType::AdultBoss);
if (ctx->GetOption(RSK_SHUFFLE_GANONS_TOWER_ENTRANCE)) {
AddElementsToPool(entrancePools[EntranceType::AdultBoss],
GetShuffleableEntrances(EntranceType::GanonTower));
}
if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) {
for (Entrance* entrance : entrancePools[EntranceType::ChildBoss]) {
entrancePools[EntranceType::ChildBossReverse].push_back(entrance->GetReverse());
@@ -1273,16 +1260,6 @@ int EntranceShuffler::ShuffleAllEntrances() {
entrancePools[EntranceType::AdultBossReverse].push_back(entrance->GetReverse());
}
}
if (ctx->GetOption(RSK_SHUFFLE_GANONS_TOWER_ENTRANCE).IsNot(RO_GENERIC_OFF)) {
AddElementsToPool(entrancePools[EntranceType::AdultBoss],
GetShuffleableEntrances(EntranceType::GanonTower));
if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) {
for (Entrance* entrance : GetShuffleableEntrances(EntranceType::GanonTower)) {
entrancePools[EntranceType::AdultBossReverse].push_back(entrance->GetReverse());
}
}
}
}
}
@@ -1376,7 +1353,6 @@ int EntranceShuffler::ShuffleAllEntrances() {
std::set<EntranceType> poolsToMix = {};
if (ctx->GetOption(RSK_MIX_DUNGEON_ENTRANCES)) {
poolsToMix.insert(EntranceType::Dungeon);
// Insert reverse entrances when decoupled entrances is on
if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) {
poolsToMix.insert(EntranceType::DungeonReverse);
}
@@ -1553,8 +1529,8 @@ int EntranceShuffler::ShuffleAllEntrances() {
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_GANONDORF_LAIR, RR_GANONS_TOWER_BEFORE_GANONDORF_LAIR),
GetEntrance(RR_GANONS_TOWER_ENTRYWAY, RR_GANONS_TOWER_STAIRS_1) },
{ EntranceNameByRegions(RR_GANONS_TOWER_STAIRS_1, RR_GANONS_TOWER_ENTRYWAY),
GetEntrance(RR_GANONS_CASTLE_ENTRYWAY, RR_CASTLE_GROUNDS_FROM_GANONS_CASTLE) }
};
// If a boss room is inside a dungeon entrance (or inside a dungeon which is inside a dungeon entrance), make
@@ -1576,8 +1552,8 @@ int EntranceShuffler::ShuffleAllEntrances() {
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_TOWER_ENTRYWAY, RR_GANONS_TOWER_STAIRS_1),
GetEntrance(RR_GANONS_TOWER_GANONDORF_LAIR, RR_GANONS_TOWER_STAIRS_1) },
{ 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>
@@ -1598,8 +1574,8 @@ int EntranceShuffler::ShuffleAllEntrances() {
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_GANONDORF_LAIR, RR_GANONS_TOWER_STAIRS_1),
GetEntrance(RR_GANONS_TOWER_GANONDORF_LAIR, RR_GANONS_TOWER_BEFORE_GANONDORF_LAIR) },
{ 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) {
@@ -1635,7 +1611,7 @@ void EntranceShuffler::CreateEntranceOverrides() {
if (mNoRandomEntrances) {
return;
}
SPDLOG_DEBUG("\nCREATING ENTRANCE OVERRIDES\n");
SPDLOG_DEBUG("CREATING ENTRANCE OVERRIDES");
auto allShuffleableEntrances = GetShuffleableEntrances(EntranceType::All, false);
int i = 0;
@@ -1644,6 +1620,8 @@ void EntranceShuffler::CreateEntranceOverrides() {
// Include blue warps when dungeons or bosses are shuffled
bool includeBluewarps =
entrance->GetType() == Rando::EntranceType::BlueWarp &&
(ctx->GetOption(RSK_SHUFFLE_GANONS_TOWER_ENTRANCE) ||
entrance->GetParentRegionKey() != RR_GANONS_TOWER_STAIRS_1) &&
(ctx->GetOption(RSK_SHUFFLE_DUNGEON_ENTRANCES) || ctx->GetOption(RSK_SHUFFLE_BOSS_ENTRANCES));
// Double-check to make sure the entrance is actually shuffled
@@ -1651,8 +1629,7 @@ void EntranceShuffler::CreateEntranceOverrides() {
continue;
}
auto message = "Setting " + entrance->to_string() + "\n";
SPDLOG_DEBUG(message);
SPDLOG_DEBUG("Setting {}", entrance->to_string());
uint8_t type = (uint8_t)entrance->GetType();
int16_t originalIndex = entrance->GetIndex();
@@ -1661,8 +1638,7 @@ void EntranceShuffler::CreateEntranceOverrides() {
int16_t destinationIndex = -1;
int16_t replacementDestinationIndex = -1;
// Only set destination indices for two way entrances and when decouple entrances
// is off
// Only set destination indices for two way entrances and when decouple entrances is off
if (entrance->GetReverse() != nullptr && !ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) {
replacementDestinationIndex = entrance->GetReplacement()->GetReverse()->GetIndex();
destinationIndex = entrance->GetReverse()->GetIndex();
@@ -1676,10 +1652,8 @@ void EntranceShuffler::CreateEntranceOverrides() {
.overrideDestination = replacementDestinationIndex,
};
message = "\tOriginal: " + std::to_string(originalIndex) + "\n";
SPDLOG_DEBUG(message);
message = "\tReplacement " + std::to_string(replacementIndex) + "\n";
SPDLOG_DEBUG(message);
SPDLOG_DEBUG("\tOriginal {}", originalIndex);
SPDLOG_DEBUG("\tReplacement {}", replacementIndex);
i++;
}
}

View File

@@ -95,7 +95,6 @@ class Entrance {
ConditionFn condition_function;
EntranceType type = EntranceType::None;
Entrance* target = nullptr;
Entrance* reverse = nullptr;
Entrance* assumed = nullptr;
Entrance* replacement = nullptr;
@@ -105,10 +104,10 @@ class Entrance {
bool addedToPool = false;
bool decoupled = false;
std::string name = "";
std::string condition_str = "";
// If this is false, areas only spread to interiors through this entrance if there is no other choice
// Set to false for owl drops, the windmill path between dampe's grave and windmill and blue warps
bool spreadsAreasWithPriority = true;
std::string condition_str = "";
};
typedef struct {

View File

@@ -1188,10 +1188,8 @@ void DumpWorldGraph(std::string str) {
} // namespace Regions
Region* RegionTable(const RandomizerRegion regionKey) {
if (regionKey > RR_MAX) {
printf("\x1b[1;1HERROR: AREAKEY TOO BIG");
}
return &(areaTable[regionKey]);
assert(regionKey < RR_MAX);
return &areaTable[regionKey];
}
// Retrieve all the shuffable entrances of a specific type

View File

@@ -678,6 +678,8 @@ void RegionTable_Init_GanonsCastle() {
//Exits
ENTRANCE(RR_GANONS_TOWER_ENTRYWAY, true),
ENTRANCE(RR_GANONS_TOWER_FLOOR_1, true),
// for imaginary blue warp
ENTRANCE(RR_CASTLE_GROUNDS_FROM_GANONS_CASTLE, false),
});
areaTable[RR_GANONS_TOWER_FLOOR_1] = Region("Ganon's Tower Floor 1", SCENE_GANONS_TOWER, {}, {}, {
@@ -686,7 +688,7 @@ void RegionTable_Init_GanonsCastle() {
ENTRANCE(RR_GANONS_TOWER_STAIRS_2, AnyAgeTime([]{return logic->CanKillEnemy(RE_DINOLFOS, ED_CLOSE, true, 2);})),
});
areaTable[RR_GANONS_TOWER_STAIRS_2] = Region("Ganon's Tower Stairs 1", SCENE_GANONS_TOWER, {}, {}, {
areaTable[RR_GANONS_TOWER_STAIRS_2] = Region("Ganon's Tower Stairs 2", SCENE_GANONS_TOWER, {}, {}, {
//Exits
ENTRANCE(RR_GANONS_TOWER_FLOOR_1, true),
ENTRANCE(RR_GANONS_TOWER_FLOOR_2, true),
@@ -701,7 +703,7 @@ void RegionTable_Init_GanonsCastle() {
ENTRANCE(RR_GANONS_TOWER_STAIRS_3, AnyAgeTime([]{return logic->CanKillEnemy(RE_STALFOS, ED_CLOSE, true, 2);})),
});
areaTable[RR_GANONS_TOWER_STAIRS_3] = Region("Ganon's Tower Stairs 1", SCENE_GANONS_TOWER, {}, {}, {
areaTable[RR_GANONS_TOWER_STAIRS_3] = Region("Ganon's Tower Stairs 3", SCENE_GANONS_TOWER, {}, {}, {
//Exits
ENTRANCE(RR_GANONS_TOWER_FLOOR_2, true),
ENTRANCE(RR_GANONS_TOWER_FLOOR_3, true),
@@ -713,7 +715,7 @@ void RegionTable_Init_GanonsCastle() {
ENTRANCE(RR_GANONS_TOWER_STAIRS_4, AnyAgeTime([]{return logic->CanKillEnemy(RE_IRON_KNUCKLE, ED_CLOSE, true, 2);})),
});
areaTable[RR_GANONS_TOWER_STAIRS_4] = Region("Ganon's Tower Stairs 1", SCENE_GANONS_TOWER, {}, {}, {
areaTable[RR_GANONS_TOWER_STAIRS_4] = Region("Ganon's Tower Stairs 4", SCENE_GANONS_TOWER, {}, {}, {
//Exits
ENTRANCE(RR_GANONS_TOWER_FLOOR_3, true),
ENTRANCE(RR_GANONS_TOWER_BEFORE_GANONDORF_LAIR, true),
@@ -751,8 +753,6 @@ void RegionTable_Init_GanonsCastle() {
}, {
//Exits
ENTRANCE(RR_GANONS_CASTLE_ESCAPE, logic->CanKillEnemy(RE_GANONDORF)),
ENTRANCE(RR_GANONS_TOWER_BEFORE_GANONDORF_LAIR, false),
ENTRANCE(RR_GANONS_TOWER_STAIRS_1, false),
});
areaTable[RR_GANONS_CASTLE_ESCAPE] = Region("Ganon's Castle Escape", SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR, {}, {

View File

@@ -59,23 +59,21 @@ typedef struct {
s16 exit;
s16 bossDoor;
s16 bossDoorReverse;
s16 blueWarp;
s16 scene;
s16 bossScene;
} DungeonEntranceInfo;
static DungeonEntranceInfo dungeons[] = {
// clang-format off
//entryway exit, boss, reverse, bluewarp, dungeon scene, boss scene
{ ENTR_DEKU_TREE_ENTRANCE, ENTR_KOKIRI_FOREST_OUTSIDE_DEKU_TREE, ENTR_DEKU_TREE_BOSS_ENTRANCE, ENTR_DEKU_TREE_BOSS_DOOR, ENTR_KOKIRI_FOREST_DEKU_TREE_BLUE_WARP, SCENE_DEKU_TREE, SCENE_DEKU_TREE_BOSS },
{ ENTR_DODONGOS_CAVERN_ENTRANCE, ENTR_DEATH_MOUNTAIN_TRAIL_OUTSIDE_DODONGOS_CAVERN, ENTR_DODONGOS_CAVERN_BOSS_ENTRANCE, ENTR_DODONGOS_CAVERN_BOSS_DOOR, ENTR_DEATH_MOUNTAIN_TRAIL_DODONGO_BLUE_WARP, SCENE_DODONGOS_CAVERN, SCENE_DODONGOS_CAVERN_BOSS },
{ ENTR_JABU_JABU_ENTRANCE, ENTR_ZORAS_FOUNTAIN_OUTSIDE_JABU_JABU, ENTR_JABU_JABU_BOSS_ENTRANCE, ENTR_JABU_JABU_BOSS_DOOR, ENTR_ZORAS_FOUNTAIN_JABU_JABU_BLUE_WARP, SCENE_JABU_JABU, SCENE_JABU_JABU_BOSS },
{ ENTR_FOREST_TEMPLE_ENTRANCE, ENTR_SACRED_FOREST_MEADOW_OUTSIDE_TEMPLE, ENTR_FOREST_TEMPLE_BOSS_ENTRANCE, ENTR_FOREST_TEMPLE_BOSS_DOOR, ENTR_SACRED_FOREST_MEADOW_FOREST_TEMPLE_BLUE_WARP, SCENE_FOREST_TEMPLE, SCENE_FOREST_TEMPLE_BOSS },
{ ENTR_FIRE_TEMPLE_ENTRANCE, ENTR_DEATH_MOUNTAIN_CRATER_OUTSIDE_TEMPLE, ENTR_FIRE_TEMPLE_BOSS_ENTRANCE, ENTR_FIRE_TEMPLE_BOSS_DOOR, ENTR_DEATH_MOUNTAIN_CRATER_FIRE_TEMPLE_BLUE_WARP, SCENE_FIRE_TEMPLE, SCENE_FIRE_TEMPLE_BOSS },
{ ENTR_WATER_TEMPLE_ENTRANCE, ENTR_LAKE_HYLIA_OUTSIDE_TEMPLE, ENTR_WATER_TEMPLE_BOSS_ENTRANCE, ENTR_WATER_TEMPLE_BOSS_DOOR, ENTR_LAKE_HYLIA_WATER_TEMPLE_BLUE_WARP, SCENE_WATER_TEMPLE, SCENE_WATER_TEMPLE_BOSS },
{ ENTR_SPIRIT_TEMPLE_ENTRANCE, ENTR_DESERT_COLOSSUS_OUTSIDE_TEMPLE, ENTR_SPIRIT_TEMPLE_BOSS_ENTRANCE, ENTR_SPIRIT_TEMPLE_BOSS_DOOR, ENTR_DESERT_COLOSSUS_SPIRIT_TEMPLE_BLUE_WARP, SCENE_SPIRIT_TEMPLE, SCENE_SPIRIT_TEMPLE_BOSS },
{ ENTR_SHADOW_TEMPLE_ENTRANCE, ENTR_GRAVEYARD_OUTSIDE_TEMPLE, ENTR_SHADOW_TEMPLE_BOSS_ENTRANCE, ENTR_SHADOW_TEMPLE_BOSS_DOOR, ENTR_GRAVEYARD_SHADOW_TEMPLE_BLUE_WARP, SCENE_SHADOW_TEMPLE, SCENE_SHADOW_TEMPLE_BOSS },
//entryway exit, boss, reverse, dungeon scene, boss scene
{ ENTR_DEKU_TREE_ENTRANCE, ENTR_KOKIRI_FOREST_OUTSIDE_DEKU_TREE, ENTR_DEKU_TREE_BOSS_ENTRANCE, ENTR_DEKU_TREE_BOSS_DOOR, SCENE_DEKU_TREE, SCENE_DEKU_TREE_BOSS },
{ ENTR_DODONGOS_CAVERN_ENTRANCE, ENTR_DEATH_MOUNTAIN_TRAIL_OUTSIDE_DODONGOS_CAVERN, ENTR_DODONGOS_CAVERN_BOSS_ENTRANCE, ENTR_DODONGOS_CAVERN_BOSS_DOOR, SCENE_DODONGOS_CAVERN, SCENE_DODONGOS_CAVERN_BOSS },
{ ENTR_JABU_JABU_ENTRANCE, ENTR_ZORAS_FOUNTAIN_OUTSIDE_JABU_JABU, ENTR_JABU_JABU_BOSS_ENTRANCE, ENTR_JABU_JABU_BOSS_DOOR, SCENE_JABU_JABU, SCENE_JABU_JABU_BOSS },
{ ENTR_FOREST_TEMPLE_ENTRANCE, ENTR_SACRED_FOREST_MEADOW_OUTSIDE_TEMPLE, ENTR_FOREST_TEMPLE_BOSS_ENTRANCE, ENTR_FOREST_TEMPLE_BOSS_DOOR, SCENE_FOREST_TEMPLE, SCENE_FOREST_TEMPLE_BOSS },
{ ENTR_FIRE_TEMPLE_ENTRANCE, ENTR_DEATH_MOUNTAIN_CRATER_OUTSIDE_TEMPLE, ENTR_FIRE_TEMPLE_BOSS_ENTRANCE, ENTR_FIRE_TEMPLE_BOSS_DOOR, SCENE_FIRE_TEMPLE, SCENE_FIRE_TEMPLE_BOSS },
{ ENTR_WATER_TEMPLE_ENTRANCE, ENTR_LAKE_HYLIA_OUTSIDE_TEMPLE, ENTR_WATER_TEMPLE_BOSS_ENTRANCE, ENTR_WATER_TEMPLE_BOSS_DOOR, SCENE_WATER_TEMPLE, SCENE_WATER_TEMPLE_BOSS },
{ ENTR_SPIRIT_TEMPLE_ENTRANCE, ENTR_DESERT_COLOSSUS_OUTSIDE_TEMPLE, ENTR_SPIRIT_TEMPLE_BOSS_ENTRANCE, ENTR_SPIRIT_TEMPLE_BOSS_DOOR, SCENE_SPIRIT_TEMPLE, SCENE_SPIRIT_TEMPLE_BOSS },
{ ENTR_SHADOW_TEMPLE_ENTRANCE, ENTR_GRAVEYARD_OUTSIDE_TEMPLE, ENTR_SHADOW_TEMPLE_BOSS_ENTRANCE, ENTR_SHADOW_TEMPLE_BOSS_DOOR, SCENE_SHADOW_TEMPLE, SCENE_SHADOW_TEMPLE_BOSS },
// clang-format on
};
@@ -182,7 +180,6 @@ void Entrance_Init(void) {
// Then overwrite the indices which are shuffled
for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) {
if (Entrance_EntranceIsNull(&entranceOverrides[i])) {
break;
}
@@ -265,7 +262,6 @@ void Entrance_Init(void) {
}
s16 Entrance_GetOverride(s16 index) {
// The game sometimes uses special indices from 0x7FF9 -> 0x7FFF for exiting
// grottos and fairy fountains. These aren't handled here since the game
// naturally handles them later.
@@ -273,6 +269,11 @@ s16 Entrance_GetOverride(s16 index) {
return index;
}
// special index for fake ganon's castle blue warp
if (entranceOverrideTable[index] == ENTR_OUTSIDE_GANONS_CASTLE_1_2) {
return ENTR_CASTLE_GROUNDS_RAINBOW_BRIDGE_EXIT;
}
return entranceOverrideTable[index];
}
@@ -325,7 +326,6 @@ u32 Entrance_SceneAndSpawnAre(u8 scene, u8 spawn) {
// Properly respawn the player after a game over, accounting for dungeon entrance randomizer
void Entrance_SetGameOverEntrance(void) {
s16 scene = gPlayState->sceneNum;
// When in a boss room and boss shuffle is on, use the boss scene to find the death warp entrance
@@ -363,15 +363,14 @@ void Entrance_SetGameOverEntrance(void) {
case ENTR_SHADOW_TEMPLE_BOSS_ENTRANCE: // Shadow Temple Boss Room
gSaveContext.entranceIndex = ENTR_SHADOW_TEMPLE_ENTRANCE;
return;
case ENTR_GANONDORF_BOSS_0: // Ganondorf Boss Room
gSaveContext.entranceIndex = ENTR_GANONS_TOWER_0; // Inside Ganon's Castle -> Ganon's Tower Climb
case ENTR_GANONDORF_BOSS_0: // Ganondorf Boss Room
gSaveContext.entranceIndex = ENTR_INSIDE_GANONS_CASTLE_ENTRANCE;
return;
}
}
// Properly savewarp the player accounting for dungeon entrance randomizer.
void Entrance_SetSavewarpEntrance(void) {
s16 scene = gSaveContext.savedSceneNum;
// When in a boss room and boss shuffle is on, use the boss scene to find the savewarp entrance
@@ -723,14 +722,14 @@ void Entrance_OverrideSpawnScene(s32 sceneNum, s32 spawn) {
}
if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF) {
// Repair the authentically bugged entrance when leaving Barniades boss room -> JabuJabu's belly
// Repair the authentically bugged entrance when leaving Barinade's boss room -> Jabu Jabu's belly
// Link's position needs to be adjusted to prevent him from falling through the floor
if (sceneNum == SCENE_JABU_JABU && spawn == 1) {
modifiedLinkActorEntry.pos.z = 0xF7F4;
gPlayState->linkActorEntry = &modifiedLinkActorEntry;
}
// Repair the authentically bugged entrance when leaving Morpha's boass room -> Water Temple
// Repair the authentically bugged entrance when leaving Morpha's boss room -> Water Temple
// Link's position was at the start of the Water Temple entrance
// This updates it to place him in the hallway outside of Morpha's boss room.
if (sceneNum == SCENE_WATER_TEMPLE && spawn == 1) {
@@ -754,13 +753,13 @@ void Entrance_OverrideSpawnScene(s32 sceneNum, s32 spawn) {
s32 Entrance_OverrideSpawnSceneRoom(s32 sceneNum, s32 spawn, s32 roomNum) {
if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF) {
// Repair the authentically bugged scene/spawn info for leaving Barinade's boss room -> JabuJabu's belly
// Repair the authentically bugged scene/spawn info for leaving Barinade's boss room -> Jabu Jabu's belly
// to load the correct room outside Barniade's boss room
if (sceneNum == SCENE_JABU_JABU && spawn == 1) {
return 5;
}
// Repair the authentically bugged scene/spawn info for leaving Morhpa's boss room -> Water Temple
// Repair the authentically bugged scene/spawn info for leaving Morpha's boss room -> Water Temple
// to load the correct room for the hallway before Morpha's boss room
if (sceneNum == SCENE_WATER_TEMPLE && spawn == 1) {
return 11;

View File

@@ -414,8 +414,9 @@ const EntranceData entranceData[] = {
{ ENTR_INSIDE_GANONS_CASTLE_ENTRANCE, ENTR_CASTLE_GROUNDS_RAINBOW_BRIDGE_EXIT, SINGLE_SCENE_INFO(SCENE_OUTSIDE_GANONS_CASTLE), "OGC Rainbow Bridge Exit", "Inside Ganon's Castle Entrance", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_DUNGEON, "outside ganon's castle,gc", 1},
{ ENTR_POTION_SHOP_KAKARIKO_1, ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_OGC_DD, {{ SCENE_GREAT_FAIRYS_FOUNTAIN_MAGIC, 0x02 }}, "OGC Great Fairy Fountain", "OGC Behind Pillar", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_INTERIOR, "outside ganon's castle"},
{ ENTR_CASTLE_GROUNDS_RAINBOW_BRIDGE_EXIT, ENTR_INSIDE_GANONS_CASTLE_ENTRANCE, SINGLE_SCENE_INFO(SCENE_INSIDE_GANONS_CASTLE), "Inside Ganon's Castle Entrance", "OGC Rainbow Bridge Exit", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_DUNGEON, "outside ganon's castle,gc"},
{ ENTR_INSIDE_GANONS_CASTLE_1, ENTR_GANONS_TOWER_0, SINGLE_SCENE_INFO(SCENE_GANONS_TOWER), "Ganon's Tower Entrance", "Inside Ganon's Castle Past Trials", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_DUNGEON, "gc"},
{ ENTR_GANONS_TOWER_0, ENTR_INSIDE_GANONS_CASTLE_1, SINGLE_SCENE_INFO(SCENE_INSIDE_GANONS_CASTLE), "Inside Ganon's Castle Past Trials", "Ganon's Tower Entrance", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_DUNGEON, "gc"},
{ ENTR_INSIDE_GANONS_CASTLE_1, ENTR_GANONS_TOWER_0, SINGLE_SCENE_INFO(SCENE_GANONS_TOWER), "Ganon's Tower Entrance", "Inside Ganon's Castle", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_DUNGEON, "gc"},
{ ENTR_GANONS_TOWER_0, ENTR_INSIDE_GANONS_CASTLE_1, SINGLE_SCENE_INFO(SCENE_INSIDE_GANONS_CASTLE), "Inside Ganon's Castle", "Ganon's Tower Entrance", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_DUNGEON, "gc"},
{ ENTR_OUTSIDE_GANONS_CASTLE_1_2, -1, SINGLE_SCENE_INFO(SCENE_OUTSIDE_GANONS_CASTLE), "Ganon's Blue Warp", "Ganon's Castle Blue Warp", ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_GROUP_HYRULE_CASTLE, ENTRANCE_TYPE_ONE_WAY, "gc,bw", 1},
// clang-format on
};

View File

@@ -186,7 +186,6 @@ s16 Grotto_GetEntranceValueHandlingGrottoRando(s16 nextEntranceIndex) {
// Translates and overrides the passed in entrance index if it corresponds to a
// special grotto entrance (grotto load or return point) and updates player respawn data correctly.
s16 Grotto_OverrideSpecialEntrance(s16 nextEntranceIndex) {
// Don't change anything unless grotto shuffle has been enabled
if (!Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES) &&
!Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_SPAWNS) &&
@@ -233,7 +232,6 @@ s16 Grotto_OverrideSpecialEntrance(s16 nextEntranceIndex) {
lastEntranceType = GROTTO_RETURN;
// Grotto Loads
} else if (nextEntranceIndex >= ENTRANCE_GROTTO_LOAD_START && nextEntranceIndex < ENTRANCE_GROTTO_EXIT_START) {
// Set the respawn data to load the correct grotto
GrottoLoadInfo grotto = grottoLoadTable[grottoId];
gSaveContext.respawn[RESPAWN_MODE_RETURN].data = grotto.content;
@@ -255,7 +253,6 @@ s16 Grotto_OverrideSpecialEntrance(s16 nextEntranceIndex) {
// Override the entrance index when entering into a grotto actor
// thisx - pointer to the grotto actor
void Grotto_OverrideActorEntrance(Actor* thisx) {
// Vanilla Behavior if there's no possibility of ending up in a grotto randomly
if (!Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES) &&
!Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_SPAWNS) &&

View File

@@ -5,8 +5,6 @@
#include "soh/Enhancements/gameconsole.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/debugconsole.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include <overlays/actors/ovl_En_Niw/z_en_niw.h>
#include <overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h>
#include "soh/Enhancements/enhancementTypes.h"