Shipwright/soh/soh/Enhancements/randomizer/3drando/entrance.cpp

878 lines
52 KiB
C++

#include "entrance.hpp"
#include "fill.hpp"
#include "settings.hpp"
#include "item_list.hpp"
#include "item_pool.hpp"
#include "item_location.hpp"
#include "debug.hpp"
#include "spoiler_log.hpp"
#include "hints.hpp"
#include "location_access.hpp"
#include <vector>
#include <utility>
#include <set>
#include <map>
#include <Lib/spdlog/include/spdlog/spdlog.h>
std::list<EntranceOverride> entranceOverrides = {};
bool noRandomEntrances = false;
static bool entranceShuffleFailure = false;
static int totalRandomizableEntrances = 0;
static int curNumRandomizedEntrances = 0;
typedef struct {
EntranceType type;
uint32_t parentRegion;
uint32_t connectedRegion;
int16_t index;
int16_t blueWarp;
} EntranceLinkInfo;
//primary, secondary
using EntranceInfoPair = std::pair<EntranceLinkInfo, EntranceLinkInfo>;
using EntrancePair = std::pair<Entrance*, Entrance*>;
//The entrance randomization algorithm used here is a direct copy of
//the algorithm used in the original N64 randomizer (except now in C++ instead
//of python). It may be easier to understand the algorithm by looking at the
//base randomizer's code instead:
// https://github.com/TestRunnerSRL/OoT-Randomizer/blob/Dev/EntranceShuffle.py
// Updates the user on how many entrances are currently shuffled
static void DisplayEntranceProgress() {
std::string dots = ".";
float progress = (float)curNumRandomizedEntrances / (float)totalRandomizableEntrances;
if (progress > 0.33) {
dots += ".";
} else {
dots += " ";
}
if (progress > 0.66) {
dots += ".";
} else {
dots += " ";
}
printf("\x1b[7;29H%s", dots.c_str());
#ifdef ENABLE_DEBUG
if (curNumRandomizedEntrances == totalRandomizableEntrances) {
Areas::DumpWorldGraph("Finish Validation");
}
#endif
}
void SetAllEntrancesData(std::vector<EntranceInfoPair>& entranceShuffleTable) {
for (auto& entrancePair: entranceShuffleTable) {
auto& forwardEntry = entrancePair.first;
auto& returnEntry = entrancePair.second;
//set data
Entrance* forwardEntrance = AreaTable(forwardEntry.parentRegion)->GetExit(forwardEntry.connectedRegion);
forwardEntrance->SetIndex(forwardEntry.index);
forwardEntrance->SetBlueWarp(forwardEntry.blueWarp);
forwardEntrance->SetType(forwardEntry.type);
forwardEntrance->SetAsPrimary();
// if type == 'Grotto':
// forward_entrance.data['index'] = 0x0700 + forward_entrance.data['grotto_id']
if (returnEntry.parentRegion != NONE) {
Entrance* returnEntrance = AreaTable(returnEntry.parentRegion)->GetExit(returnEntry.connectedRegion);
returnEntrance->SetIndex(returnEntry.index);
returnEntrance->SetBlueWarp(returnEntry.blueWarp);
returnEntrance->SetType(returnEntry.type);
forwardEntrance->BindTwoWay(returnEntrance);
// if type == 'Grotto':
// return_entrance.data['index'] = 0x0800 + return_entrance.data['grotto_id']
}
}
}
static std::vector<Entrance*> AssumeEntrancePool(std::vector<Entrance*>& entrancePool) {
std::vector<Entrance*> assumedPool = {};
for (Entrance* entrance : entrancePool) {
Entrance* assumedForward = entrance->AssumeReachable();
if (entrance->GetReverse() != nullptr /*&& entrances are not decoupled*/) {
Entrance* assumedReturn = entrance->GetReverse()->AssumeReachable();
//mixed pool assumption stuff
assumedForward->BindTwoWay(assumedReturn);
}
assumedPool.push_back(assumedForward);
}
return assumedPool;
}
//returns restrictive entrances and soft entrances in an array of size 2 (restrictive vector is index 0, soft is index 1)
static std::array<std::vector<Entrance*>, 2> SplitEntrancesByRequirements(std::vector<Entrance*>& entrancesToSplit, std::vector<Entrance*>& assumedEntrances) {
//First, disconnect all root assumed entrances and save which regions they were originally connected to, so we can reconnect them later
std::map<Entrance*, uint32_t> originalConnectedRegions = {};
std::set<Entrance*> entrancesToDisconnect = {};
for (Entrance* entrance : assumedEntrances) {
entrancesToDisconnect.insert(entrance);
if (entrance->GetReverse() != nullptr) {
entrancesToDisconnect.insert(entrance->GetReverse());
}
}
//disconnect each entrance temporarily to find restrictive vs soft entrances
//soft entrances are ones that can be accessed by both ages (child/adult) at both times of day (day/night)
//restrictive entrances are ones that do not meet this criteria
for (Entrance* entrance : entrancesToDisconnect) {
if (entrance->GetConnectedRegionKey() != NONE) {
originalConnectedRegions[entrance] = entrance->Disconnect();
}
}
std::vector<Entrance*> restrictiveEntrances = {};
std::vector<Entrance*> softEntrances = {};
Logic::LogicReset();
// //Apply the effects of all advancement items to search for entrance accessibility
std::vector<uint32_t> items = FilterFromPool(ItemPool, [](const auto i){ return ItemTable(i).IsAdvancement();});
for (uint32_t unplacedItem : items) {
ItemTable(unplacedItem).ApplyEffect();
}
// run a search to see what's accessible
GetAccessibleLocations({});
for (Entrance* entrance : entrancesToSplit) {
// if an entrance is accessible at all times of day by both ages, it's a soft entrance with no restrictions
if (entrance->ConditionsMet(true)) {
softEntrances.push_back(entrance);
} else {
restrictiveEntrances.push_back(entrance);
}
}
//Reconnect all disconnected entrances
for (Entrance* entrance : entrancesToDisconnect) {
entrance->Connect(originalConnectedRegions[entrance]);
}
return {restrictiveEntrances, softEntrances};
}
static bool AreEntrancesCompatible(Entrance* entrance, Entrance* target, std::vector<EntrancePair>& rollbacks) {
//Entrances shouldn't connect to their own scene, fail in this situation
if (entrance->GetParentRegion()->scene != "" && entrance->GetParentRegion()->scene == target->GetConnectedRegion()->scene) {
auto message = "Entrance " + entrance->GetName() + " attempted to connect with own scene target " + target->to_string() + ". Connection failed.\n";
SPDLOG_DEBUG(message);
return false;
}
//one-way entrance stuff
return true;
}
//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);
entrance->Connect(targetEntrance->Disconnect());
entrance->SetReplacement(targetEntrance->GetReplacement());
if (entrance->GetReverse() != nullptr /*&& entrances aren't decoupled*/) {
targetEntrance->GetReplacement()->GetReverse()->Connect(entrance->GetReverse()->GetAssumed()->Disconnect());
targetEntrance->GetReplacement()->GetReverse()->SetReplacement(entrance->GetReverse());
}
}
static void RestoreConnections(Entrance* entrance, Entrance* targetEntrance) {
targetEntrance->Connect(entrance->Disconnect());
entrance->SetReplacement(nullptr);
if (entrance->GetReverse() != nullptr /*&& entrances are not decoupled*/) {
entrance->GetReverse()->GetAssumed()->Connect(targetEntrance->GetReplacement()->GetReverse()->Disconnect());
targetEntrance->GetReplacement()->GetReverse()->SetReplacement(nullptr);
}
}
static void DeleteTargetEntrance(Entrance* targetEntrance) {
if (targetEntrance->GetConnectedRegionKey() != NONE) {
targetEntrance->Disconnect();
}
if (targetEntrance->GetParentRegionKey() != NONE) {
targetEntrance->GetParentRegion()->RemoveExit(targetEntrance);
targetEntrance->SetParentRegion(NONE);
}
}
static void ConfirmReplacement(Entrance* entrance, Entrance* targetEntrance) {
DeleteTargetEntrance(targetEntrance);
if (entrance->GetReverse() != nullptr /*&& entrances are not decoupled*/) {
auto replacedReverse = targetEntrance->GetReplacement()->GetReverse();
DeleteTargetEntrance(replacedReverse->GetReverse()->GetAssumed());
}
}
// Returns whether or not we can affirm the entrance can never be accessed as the given age
static bool EntranceUnreachableAs(Entrance* entrance, uint8_t age, std::vector<Entrance*>& alreadyChecked) {
if (entrance == nullptr) {
SPDLOG_DEBUG("Entrance is nullptr in EntranceUnreachableAs()");
return true;
}
alreadyChecked.push_back(entrance);
auto type = entrance->GetType();
// The following cases determine when we say an entrance is not safe to affirm unreachable as the given age
if (type == EntranceType::WarpSong || type == EntranceType::Overworld) {
// Note that we consider all overworld entrances as potentially accessible as both ages, to be completely safe
return false;
} else if (type == EntranceType::OwlDrop) {
return age == AGE_ADULT;
} else if (type == EntranceType::Spawn && entrance->GetConnectedRegionKey() == KF_LINKS_HOUSE) {
return age == AGE_ADULT;
} else if (type == EntranceType::Spawn && entrance->GetConnectedRegionKey() == TEMPLE_OF_TIME) {
return age == AGE_CHILD;
}
// Other entrances such as Interior, Dungeon or Grotto are fine unless they have a parent which is one of the above
// cases Recursively check parent entrances to verify that they are also not reachable as the wrong age
auto& parentEntrances = entrance->GetParentRegion()->entrances;
for (Entrance* parentEntrance : parentEntrances) {
// if parentEntrance is in alreadyChecked, then continue
if (ElementInContainer(parentEntrance, alreadyChecked)) {
continue;
}
bool unreachable = EntranceUnreachableAs(parentEntrance, age, alreadyChecked);
if (!unreachable) {
return false;
}
}
return true;
}
static bool ValidateWorld(Entrance* entrancePlaced) {
SPDLOG_DEBUG("Validating world\n");
//check certain conditions when certain types of ER are enabled
EntranceType type = EntranceType::None;
if (entrancePlaced != nullptr) {
type = entrancePlaced->GetType();
}
bool checkPoeCollectorAccess = (Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL)) && (entrancePlaced == nullptr /*|| Settings::MixedEntrancePools.IsNot(MIXEDENTRANCES_OFF)*/ ||
type == EntranceType::Interior || type == EntranceType::SpecialInterior || type == EntranceType::Overworld || type == EntranceType::Spawn || type == EntranceType::WarpSong || type == EntranceType::OwlDrop);
bool checkOtherEntranceAccess = (Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL) /*|| Settings::ShuffleOverworldSpawns*/) && (entrancePlaced == nullptr /*|| Settings::MixedEntrancePools.IsNot(MIXEDENTRANCES_OFF)*/ ||
type == EntranceType::SpecialInterior || type == EntranceType::Overworld || type == EntranceType::Spawn || type == EntranceType::WarpSong || type == EntranceType::OwlDrop);
// Check to make sure all locations are still reachable
Logic::LogicReset();
GetAccessibleLocations({}, SearchMode::ValidateWorld, "", checkPoeCollectorAccess, checkOtherEntranceAccess);
// if not world.decouple_entrances:
// Unless entrances are decoupled, we don't want the player to end up through certain entrances as the wrong age
// This means we need to hard check that none of the relevant entrances are ever reachable as that age
// This is mostly relevant when mixing entrance pools or shuffling special interiors (such as windmill or kak potion shop)
// Warp Songs and Overworld Spawns can also end up inside certain indoors so those need to be handled as well
std::array<std::string, 2> childForbidden = {"OGC Great Fairy Fountain -> Castle Grounds", "GV Carpenter Tent -> GV Fortress Side"};
std::array<std::string, 2> adultForbidden = {"HC Great Fairy Fountain -> Castle Grounds", "HC Storms Grotto -> Castle Grounds"};
auto allShuffleableEntrances = GetShuffleableEntrances(EntranceType::All, false);
for (auto& entrance: allShuffleableEntrances) {
std::vector<Entrance*> alreadyChecked = {};
if (entrance->IsShuffled()) {
if (entrance->GetReplacement() != nullptr) {
auto replacementName = entrance->GetReplacement()->GetName();
alreadyChecked.push_back(entrance->GetReplacement()->GetReverse());
if (ElementInContainer(replacementName, childForbidden) && !EntranceUnreachableAs(entrance, AGE_CHILD, alreadyChecked)) {
auto message = replacementName + " is replaced by an entrance with a potential child access\n";
SPDLOG_DEBUG(message);
return false;
} else if (ElementInContainer(replacementName, adultForbidden) && !EntranceUnreachableAs(entrance, AGE_ADULT, alreadyChecked)) {
auto message = replacementName + " is replaced by an entrance with a potential adult access\n";
SPDLOG_DEBUG(message);
return false;
}
}
} else {
auto name = entrance->GetName();
alreadyChecked.push_back(entrance->GetReverse());
if (ElementInContainer(name, childForbidden) && !EntranceUnreachableAs(entrance, AGE_CHILD, alreadyChecked)) {
auto message = name + " is potentially accessible as child\n";
SPDLOG_DEBUG(message);
return false;
} else if (ElementInContainer(name, adultForbidden) && !EntranceUnreachableAs(entrance, AGE_ADULT, alreadyChecked)) {
auto message = name + " is potentially accessible as adult\n";
SPDLOG_DEBUG(message);
return false;
}
}
}
if (Settings::ShuffleInteriorEntrances.IsNot(SHUFFLEINTERIORS_OFF) && Settings::GossipStoneHints.IsNot(HINTS_NO_HINTS) &&
(entrancePlaced == nullptr || type == EntranceType::Interior || type == EntranceType::SpecialInterior)) {
//When cows are shuffled, ensure both Impa's House entrances are in the same hint area because the cow is reachable from both sides
if (Settings::ShuffleCows) {
auto impasHouseFrontHintRegion = GetHintRegionHintKey(KAK_IMPAS_HOUSE);
auto impasHouseBackHintRegion = GetHintRegionHintKey(KAK_IMPAS_HOUSE_BACK);
if (impasHouseFrontHintRegion != NONE && impasHouseBackHintRegion != NONE && impasHouseBackHintRegion != LINKS_POCKET && impasHouseFrontHintRegion != LINKS_POCKET && impasHouseBackHintRegion != impasHouseFrontHintRegion) {
auto message = "Kak Impas House entrances are not in the same hint area\n";
SPDLOG_DEBUG(message);
return false;
}
}
}
// If all locations aren't reachable, that means that one of the conditions failed when searching
if (!allLocationsReachable) {
if (checkOtherEntranceAccess) {
// At least one valid starting region with all basic refills should be reachable without using any items at the beginning of the seed
if (!AreaTable(KOKIRI_FOREST)->HasAccess() && !AreaTable(KAKARIKO_VILLAGE)->HasAccess()) {
SPDLOG_DEBUG("Invalid starting area\n");
return false;
}
// Check that a region where time passes is always reachable as both ages without having collected any items
if (!Areas::HasTimePassAccess(AGE_CHILD) || !Areas::HasTimePassAccess(AGE_ADULT)) {
SPDLOG_DEBUG("Time passing is not guaranteed as both ages\n");
return false;
}
// The player should be able to get back to ToT after going through time, without having collected any items
// This is important to ensure that the player never loses access to the pedestal after going through time
if (Settings::ResolvedStartingAge == AGE_CHILD && !AreaTable(TEMPLE_OF_TIME)->Adult()) {
SPDLOG_DEBUG("Path to Temple of Time as adult is not guaranteed\n");
return false;
} else if (Settings::ResolvedStartingAge == AGE_ADULT && !AreaTable(TEMPLE_OF_TIME)->Child()) {
SPDLOG_DEBUG("Path to Temple of Time as child is not guaranteed\n");
return false;
}
}
// The Big Poe shop should always be accessible as adult without the need to use any bottles
// This is important to ensure that players can never lock their only bottles by filling them with Big Poes they can't sell
if (checkPoeCollectorAccess) {
if (!AreaTable(MARKET_GUARD_HOUSE)->Adult()) {
SPDLOG_DEBUG("Big Poe Shop access is not guarenteed as adult\n");
return false;
}
}
SPDLOG_DEBUG("All Locations NOT REACHABLE\n");
return false;
}
return true;
}
static bool ReplaceEntrance(Entrance* entrance, Entrance* target, std::vector<EntrancePair>& rollbacks) {
if (!AreEntrancesCompatible(entrance, target, rollbacks)) {
return false;
}
ChangeConnections(entrance, target);
if (ValidateWorld(entrance)) {
#ifdef ENABLE_DEBUG
std::string ticks = std::to_string(svcGetSystemTick());
auto message = "Dumping World Graph at " + ticks + "\n";
//PlacementLog_Msg(message);
//Areas::DumpWorldGraph(ticks);
#endif
rollbacks.push_back(EntrancePair{entrance, target});
curNumRandomizedEntrances++;
DisplayEntranceProgress();
return true;
} else {
#ifdef ENABLE_DEBUG
std::string ticks = std::to_string(svcGetSystemTick());
auto message = "Dumping World Graph at " + ticks + "\n";
//PlacementLog_Msg(message);
//Areas::DumpWorldGraph(ticks);
#endif
if (entrance->GetConnectedRegionKey() != NONE) {
RestoreConnections(entrance, target);
}
}
DisplayEntranceProgress();
return false;
}
// Shuffle entrances by placing them instead of entrances in the provided target entrances list
static bool ShuffleEntrances(std::vector<Entrance*>& entrances, std::vector<Entrance*>& targetEntrances, std::vector<EntrancePair>& rollbacks) {
Shuffle(entrances);
//place all entrances in the pool, validating after every placement
for (Entrance* entrance : entrances) {
if (entrance->GetConnectedRegionKey() != NONE) {
continue;
}
Shuffle(targetEntrances);
for (Entrance* target : targetEntrances) {
if (target->GetConnectedRegionKey() == NONE) {
continue;
}
if (ReplaceEntrance(entrance, target, rollbacks)) {
break;
}
}
if (entrance->GetConnectedRegionKey() == NONE) {
return false;
}
}
//all entrances were validly connected
return true;
}
static void ShuffleEntrancePool(std::vector<Entrance*>& entrancePool, std::vector<Entrance*>& targetEntrances) {
noRandomEntrances = false;
auto splitEntrances = SplitEntrancesByRequirements(entrancePool, targetEntrances);
auto& restrictiveEntrances = splitEntrances[0];
auto& softEntrances = splitEntrances[1];
int retries = 20;
while (retries > 0) {
if (retries != 20) {
#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";
PlacementLog_Msg(message);
Areas::DumpWorldGraph(ticks);
#endif
}
retries--;
std::vector<EntrancePair> rollbacks = {};
//Shuffle Restrictive Entrances first while more regions are available in
//order to heavily reduce the chances of the placement failing
bool success = ShuffleEntrances(restrictiveEntrances, targetEntrances, rollbacks);
if (success) {
success = ShuffleEntrances(softEntrances, targetEntrances, rollbacks);
if(!success) {
for (auto& pair : rollbacks) {
RestoreConnections(pair.first, pair.second);
curNumRandomizedEntrances--;
}
continue;
}
} else {
for (auto& pair : rollbacks) {
RestoreConnections(pair.first, pair.second);
curNumRandomizedEntrances--;
}
continue;
}
//If there are no issues, log the connections and continue
for (auto& pair : rollbacks) {
ConfirmReplacement(pair.first, pair.second);
}
break;
}
if (retries <= 0) {
SPDLOG_DEBUG("Entrance placement attempt count exceeded. Restarting randomization completely");
entranceShuffleFailure = true;
}
}
//Process for setting up the shuffling of all entrances to be shuffled
int ShuffleAllEntrances() {
totalRandomizableEntrances = 0;
curNumRandomizedEntrances = 0;
std::vector<EntranceInfoPair> entranceShuffleTable = {
//Parent Region Connected Region index blue warp
{{EntranceType::Dungeon, KF_OUTSIDE_DEKU_TREE, DEKU_TREE_ENTRYWAY, 0x0000},
{EntranceType::Dungeon, DEKU_TREE_ENTRYWAY, KF_OUTSIDE_DEKU_TREE, 0x0209, 0x0457}},
{{EntranceType::Dungeon, DEATH_MOUNTAIN_TRAIL, DODONGOS_CAVERN_ENTRYWAY, 0x0004},
{EntranceType::Dungeon, DODONGOS_CAVERN_ENTRYWAY, DEATH_MOUNTAIN_TRAIL, 0x0242, 0x047A}},
{{EntranceType::Dungeon, ZORAS_FOUNTAIN, JABU_JABUS_BELLY_ENTRYWAY, 0x0028},
{EntranceType::Dungeon, JABU_JABUS_BELLY_ENTRYWAY, ZORAS_FOUNTAIN, 0x0221, 0x010E}},
{{EntranceType::Dungeon, SACRED_FOREST_MEADOW, FOREST_TEMPLE_ENTRYWAY, 0x0169},
{EntranceType::Dungeon, FOREST_TEMPLE_ENTRYWAY, SACRED_FOREST_MEADOW, 0x0215, 0x0608}},
{{EntranceType::Dungeon, DMC_CENTRAL_LOCAL, FIRE_TEMPLE_ENTRYWAY, 0x0165},
{EntranceType::Dungeon, FIRE_TEMPLE_ENTRYWAY, DMC_CENTRAL_LOCAL, 0x024A, 0x0564}},
{{EntranceType::Dungeon, LAKE_HYLIA, WATER_TEMPLE_ENTRYWAY, 0x0010},
{EntranceType::Dungeon, WATER_TEMPLE_ENTRYWAY, LAKE_HYLIA, 0x021D, 0x060C}},
{{EntranceType::Dungeon, DESERT_COLOSSUS, SPIRIT_TEMPLE_ENTRYWAY, 0x0082},
{EntranceType::Dungeon, SPIRIT_TEMPLE_ENTRYWAY, DESERT_COLOSSUS, 0x01E1, 0x0610}},
{{EntranceType::Dungeon, GRAVEYARD_WARP_PAD_REGION, SHADOW_TEMPLE_ENTRYWAY, 0x0037},
{EntranceType::Dungeon, SHADOW_TEMPLE_ENTRYWAY, GRAVEYARD_WARP_PAD_REGION, 0x0205, 0x0580}},
{{EntranceType::Dungeon, KAKARIKO_VILLAGE, BOTTOM_OF_THE_WELL_ENTRYWAY, 0x0098},
{EntranceType::Dungeon, BOTTOM_OF_THE_WELL_ENTRYWAY, KAKARIKO_VILLAGE, 0x02A6}},
{{EntranceType::Dungeon, ZORAS_FOUNTAIN, ICE_CAVERN_ENTRYWAY, 0x0088},
{EntranceType::Dungeon, ICE_CAVERN_ENTRYWAY, ZORAS_FOUNTAIN, 0x03D4}},
{{EntranceType::Dungeon, GERUDO_FORTRESS, GERUDO_TRAINING_GROUNDS_ENTRYWAY, 0x0008},
{EntranceType::Dungeon, GERUDO_TRAINING_GROUNDS_ENTRYWAY, GERUDO_FORTRESS, 0x03A8}},
{{EntranceType::GanonDungeon, GANONS_CASTLE_GROUNDS, GANONS_CASTLE_ENTRYWAY, 0x0467},
{EntranceType::GanonDungeon, GANONS_CASTLE_ENTRYWAY, GANONS_CASTLE_GROUNDS, 0x023D}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_MIDOS_HOUSE, 0x0433},
{EntranceType::Interior, KF_MIDOS_HOUSE, KOKIRI_FOREST, 0x0443}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_SARIAS_HOUSE, 0x0437},
{EntranceType::Interior, KF_SARIAS_HOUSE, KOKIRI_FOREST, 0x0447}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_HOUSE_OF_TWINS, 0x009C},
{EntranceType::Interior, KF_HOUSE_OF_TWINS, KOKIRI_FOREST, 0x033C}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_KNOW_IT_ALL_HOUSE, 0x00C9},
{EntranceType::Interior, KF_KNOW_IT_ALL_HOUSE, KOKIRI_FOREST, 0x026A}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_KOKIRI_SHOP, 0x00C1},
{EntranceType::Interior, KF_KOKIRI_SHOP, KOKIRI_FOREST, 0x0266}},
{{EntranceType::Interior, LAKE_HYLIA, LH_LAB, 0x0043},
{EntranceType::Interior, LH_LAB, LAKE_HYLIA, 0x03CC}},
{{EntranceType::Interior, LH_FISHING_ISLAND, LH_FISHING_HOLE, 0x045F},
{EntranceType::Interior, LH_FISHING_HOLE, LH_FISHING_ISLAND, 0x0309}},
{{EntranceType::Interior, GV_FORTRESS_SIDE, GV_CARPENTER_TENT, 0x03A0},
{EntranceType::Interior, GV_CARPENTER_TENT, GV_FORTRESS_SIDE, 0x03D0}},
{{EntranceType::Interior, MARKET_ENTRANCE, MARKET_GUARD_HOUSE, 0x007E},
{EntranceType::Interior, MARKET_GUARD_HOUSE, MARKET_ENTRANCE, 0x026E}},
{{EntranceType::Interior, THE_MARKET, MARKET_MASK_SHOP, 0x0530},
{EntranceType::Interior, MARKET_MASK_SHOP, THE_MARKET, 0x01D1}},
{{EntranceType::Interior, THE_MARKET, MARKET_BOMBCHU_BOWLING, 0x0507},
{EntranceType::Interior, MARKET_BOMBCHU_BOWLING, THE_MARKET, 0x03BC}},
{{EntranceType::Interior, THE_MARKET, MARKET_POTION_SHOP, 0x0388},
{EntranceType::Interior, MARKET_POTION_SHOP, THE_MARKET, 0x02A2}},
{{EntranceType::Interior, THE_MARKET, MARKET_TREASURE_CHEST_GAME, 0x0063},
{EntranceType::Interior, MARKET_TREASURE_CHEST_GAME, THE_MARKET, 0x01D5}},
{{EntranceType::Interior, MARKET_BACK_ALLEY, MARKET_BOMBCHU_SHOP, 0x0528},
{EntranceType::Interior, MARKET_BOMBCHU_SHOP, MARKET_BACK_ALLEY, 0x03C0}},
{{EntranceType::Interior, MARKET_BACK_ALLEY, MARKET_MAN_IN_GREEN_HOUSE, 0x043B},
{EntranceType::Interior, MARKET_MAN_IN_GREEN_HOUSE, MARKET_BACK_ALLEY, 0x0067}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_CARPENTER_BOSS_HOUSE, 0x02FD},
{EntranceType::Interior, KAK_CARPENTER_BOSS_HOUSE, KAKARIKO_VILLAGE, 0x0349}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_HOUSE_OF_SKULLTULA, 0x0550},
{EntranceType::Interior, KAK_HOUSE_OF_SKULLTULA, KAKARIKO_VILLAGE, 0x04EE}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_IMPAS_HOUSE, 0x039C},
{EntranceType::Interior, KAK_IMPAS_HOUSE, KAKARIKO_VILLAGE, 0x0345}},
{{EntranceType::Interior, KAK_IMPAS_LEDGE, KAK_IMPAS_HOUSE_BACK, 0x05C8},
{EntranceType::Interior, KAK_IMPAS_HOUSE_BACK, KAK_IMPAS_LEDGE, 0x05DC}},
{{EntranceType::Interior, KAK_BACKYARD, KAK_ODD_POTION_BUILDING, 0x0072},
{EntranceType::Interior, KAK_ODD_POTION_BUILDING, KAK_BACKYARD, 0x034D}},
{{EntranceType::Interior, THE_GRAVEYARD, GRAVEYARD_DAMPES_HOUSE, 0x030D},
{EntranceType::Interior, GRAVEYARD_DAMPES_HOUSE, THE_GRAVEYARD, 0x0355}},
{{EntranceType::Interior, GORON_CITY, GC_SHOP, 0x037C},
{EntranceType::Interior, GC_SHOP, GORON_CITY, 0x03FC}},
{{EntranceType::Interior, ZORAS_DOMAIN, ZD_SHOP, 0x0380},
{EntranceType::Interior, ZD_SHOP, ZORAS_DOMAIN, 0x03C4}},
{{EntranceType::Interior, LON_LON_RANCH, LLR_TALONS_HOUSE, 0x004F},
{EntranceType::Interior, LLR_TALONS_HOUSE, LON_LON_RANCH, 0x0378}},
{{EntranceType::Interior, LON_LON_RANCH, LLR_STABLES, 0x02F9},
{EntranceType::Interior, LLR_STABLES, LON_LON_RANCH, 0x042F}},
{{EntranceType::Interior, LON_LON_RANCH, LLR_TOWER, 0x05D0},
{EntranceType::Interior, LLR_TOWER, LON_LON_RANCH, 0x05D4}},
{{EntranceType::Interior, THE_MARKET, MARKET_BAZAAR, 0x052C},
{EntranceType::Interior, MARKET_BAZAAR, THE_MARKET, 0x03B8}},
{{EntranceType::Interior, THE_MARKET, MARKET_SHOOTING_GALLERY, 0x016D},
{EntranceType::Interior, MARKET_SHOOTING_GALLERY, THE_MARKET, 0x01CD}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_BAZAAR, 0x00B7},
{EntranceType::Interior, KAK_BAZAAR, KAKARIKO_VILLAGE, 0x0201}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_SHOOTING_GALLERY, 0x003B},
{EntranceType::Interior, KAK_SHOOTING_GALLERY, KAKARIKO_VILLAGE, 0x0463}},
{{EntranceType::Interior, DESERT_COLOSSUS, COLOSSUS_GREAT_FAIRY_FOUNTAIN, 0x0588},
{EntranceType::Interior, COLOSSUS_GREAT_FAIRY_FOUNTAIN, DESERT_COLOSSUS, 0x057C}},
{{EntranceType::Interior, HYRULE_CASTLE_GROUNDS, HC_GREAT_FAIRY_FOUNTAIN, 0x0578},
{EntranceType::Interior, HC_GREAT_FAIRY_FOUNTAIN, CASTLE_GROUNDS, 0x0340}},
{{EntranceType::Interior, GANONS_CASTLE_GROUNDS, OGC_GREAT_FAIRY_FOUNTAIN, 0x04C2},
{EntranceType::Interior, OGC_GREAT_FAIRY_FOUNTAIN, CASTLE_GROUNDS, 0x03E8}}, //0x3E8 is an unused entrance index repruposed to differentiate between the HC and OGC fairy fountain exits (normally they both use 0x340)
{{EntranceType::Interior, DMC_LOWER_NEARBY, DMC_GREAT_FAIRY_FOUNTAIN, 0x04BE},
{EntranceType::Interior, DMC_GREAT_FAIRY_FOUNTAIN, DMC_LOWER_LOCAL, 0x0482}},
{{EntranceType::Interior, DEATH_MOUNTAIN_SUMMIT, DMT_GREAT_FAIRY_FOUNTAIN, 0x0315},
{EntranceType::Interior, DMT_GREAT_FAIRY_FOUNTAIN, DEATH_MOUNTAIN_SUMMIT, 0x045B}},
{{EntranceType::Interior, ZORAS_FOUNTAIN, ZF_GREAT_FAIRY_FOUNTAIN, 0x0371},
{EntranceType::Interior, ZF_GREAT_FAIRY_FOUNTAIN, ZORAS_FOUNTAIN, 0x0394}},
{{EntranceType::SpecialInterior, KOKIRI_FOREST, KF_LINKS_HOUSE, 0x0272},
{EntranceType::SpecialInterior, KF_LINKS_HOUSE, KOKIRI_FOREST, 0x0211}},
{{EntranceType::SpecialInterior, TOT_ENTRANCE, TEMPLE_OF_TIME, 0x0053},
{EntranceType::SpecialInterior, TEMPLE_OF_TIME, TOT_ENTRANCE, 0x0472}},
{{EntranceType::SpecialInterior, KAKARIKO_VILLAGE, KAK_WINDMILL, 0x0453},
{EntranceType::SpecialInterior, KAK_WINDMILL, KAKARIKO_VILLAGE, 0x0351}},
{{EntranceType::SpecialInterior, KAKARIKO_VILLAGE, KAK_POTION_SHOP_FRONT, 0x0384},
{EntranceType::SpecialInterior, KAK_POTION_SHOP_FRONT, KAKARIKO_VILLAGE, 0x044B}},
{{EntranceType::SpecialInterior, KAK_BACKYARD, KAK_POTION_SHOP_BACK, 0x03EC},
{EntranceType::SpecialInterior, KAK_POTION_SHOP_BACK, KAK_BACKYARD, 0x04FF}},
// Grotto Loads use an entrance index of 0x0700 + their grotto id. The id is used as index for the
// grottoLoadTable in src/grotto.c
// Grotto Returns use an entrance index of 0x0800 + their grotto id. The id is used as index for the
// grottoReturnTable in src/grotto.c
{{EntranceType::GrottoGrave, DESERT_COLOSSUS, COLOSSUS_GROTTO, 0x0700},
{EntranceType::GrottoGrave, COLOSSUS_GROTTO, DESERT_COLOSSUS, 0x0800}},
{{EntranceType::GrottoGrave, LAKE_HYLIA, LH_GROTTO, 0x0701},
{EntranceType::GrottoGrave, LH_GROTTO, LAKE_HYLIA, 0x0801}},
{{EntranceType::GrottoGrave, ZORAS_RIVER, ZR_STORMS_GROTTO, 0x0702},
{EntranceType::GrottoGrave, ZR_STORMS_GROTTO, ZORAS_RIVER, 0x0802}},
{{EntranceType::GrottoGrave, ZORAS_RIVER, ZR_FAIRY_GROTTO, 0x0703},
{EntranceType::GrottoGrave, ZR_FAIRY_GROTTO, ZORAS_RIVER, 0x0803}},
{{EntranceType::GrottoGrave, ZORAS_RIVER, ZR_OPEN_GROTTO, 0x0704},
{EntranceType::GrottoGrave, ZR_OPEN_GROTTO, ZORAS_RIVER, 0x0804}},
{{EntranceType::GrottoGrave, DMC_LOWER_NEARBY, DMC_HAMMER_GROTTO, 0x0705},
{EntranceType::GrottoGrave, DMC_HAMMER_GROTTO, DMC_LOWER_LOCAL, 0x0805}},
{{EntranceType::GrottoGrave, DMC_UPPER_NEARBY, DMC_UPPER_GROTTO, 0x0706},
{EntranceType::GrottoGrave, DMC_UPPER_GROTTO, DMC_UPPER_LOCAL, 0x0806}},
{{EntranceType::GrottoGrave, GC_GROTTO_PLATFORM, GC_GROTTO, 0x0707},
{EntranceType::GrottoGrave, GC_GROTTO, GC_GROTTO_PLATFORM, 0x0807}},
{{EntranceType::GrottoGrave, DEATH_MOUNTAIN_TRAIL, DMT_STORMS_GROTTO, 0x0708},
{EntranceType::GrottoGrave, DMT_STORMS_GROTTO, DEATH_MOUNTAIN_TRAIL, 0x0808}},
{{EntranceType::GrottoGrave, DEATH_MOUNTAIN_SUMMIT, DMT_COW_GROTTO, 0x0709},
{EntranceType::GrottoGrave, DMT_COW_GROTTO, DEATH_MOUNTAIN_SUMMIT, 0x0809}},
{{EntranceType::GrottoGrave, KAK_BACKYARD, KAK_OPEN_GROTTO, 0x070A},
{EntranceType::GrottoGrave, KAK_OPEN_GROTTO, KAK_BACKYARD, 0x080A}},
{{EntranceType::GrottoGrave, KAKARIKO_VILLAGE, KAK_REDEAD_GROTTO, 0x070B},
{EntranceType::GrottoGrave, KAK_REDEAD_GROTTO, KAKARIKO_VILLAGE, 0x080B}},
{{EntranceType::GrottoGrave, HYRULE_CASTLE_GROUNDS, HC_STORMS_GROTTO, 0x070C},
{EntranceType::GrottoGrave, HC_STORMS_GROTTO, CASTLE_GROUNDS, 0x080C}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_TEKTITE_GROTTO, 0x070D},
{EntranceType::GrottoGrave, HF_TEKTITE_GROTTO, HYRULE_FIELD, 0x080D}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_NEAR_KAK_GROTTO, 0x070E},
{EntranceType::GrottoGrave, HF_NEAR_KAK_GROTTO, HYRULE_FIELD, 0x080E}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_FAIRY_GROTTO, 0x070F},
{EntranceType::GrottoGrave, HF_FAIRY_GROTTO, HYRULE_FIELD, 0x080F}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_NEAR_MARKET_GROTTO, 0x0710},
{EntranceType::GrottoGrave, HF_NEAR_MARKET_GROTTO, HYRULE_FIELD, 0x0810}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_COW_GROTTO, 0x0711},
{EntranceType::GrottoGrave, HF_COW_GROTTO, HYRULE_FIELD, 0x0811}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_INSIDE_FENCE_GROTTO, 0x0712},
{EntranceType::GrottoGrave, HF_INSIDE_FENCE_GROTTO, HYRULE_FIELD, 0x0812}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_OPEN_GROTTO, 0x0713},
{EntranceType::GrottoGrave, HF_OPEN_GROTTO, HYRULE_FIELD, 0x0813}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_SOUTHEAST_GROTTO, 0x0714},
{EntranceType::GrottoGrave, HF_SOUTHEAST_GROTTO, HYRULE_FIELD, 0x0814}},
{{EntranceType::GrottoGrave, LON_LON_RANCH, LLR_GROTTO, 0x0715},
{EntranceType::GrottoGrave, LLR_GROTTO, LON_LON_RANCH, 0x0815}},
{{EntranceType::GrottoGrave, SFM_ENTRYWAY, SFM_WOLFOS_GROTTO, 0x0716},
{EntranceType::GrottoGrave, SFM_WOLFOS_GROTTO, SFM_ENTRYWAY, 0x0816}},
{{EntranceType::GrottoGrave, SACRED_FOREST_MEADOW, SFM_STORMS_GROTTO, 0x0717},
{EntranceType::GrottoGrave, SFM_STORMS_GROTTO, SACRED_FOREST_MEADOW, 0x0817}},
{{EntranceType::GrottoGrave, SACRED_FOREST_MEADOW, SFM_FAIRY_GROTTO, 0x0718},
{EntranceType::GrottoGrave, SFM_FAIRY_GROTTO, SACRED_FOREST_MEADOW, 0x0818}},
{{EntranceType::GrottoGrave, LW_BEYOND_MIDO, LW_SCRUBS_GROTTO, 0x0719},
{EntranceType::GrottoGrave, LW_SCRUBS_GROTTO, LW_BEYOND_MIDO, 0x0819}},
{{EntranceType::GrottoGrave, THE_LOST_WOODS, LW_NEAR_SHORTCUTS_GROTTO, 0x071A},
{EntranceType::GrottoGrave, LW_NEAR_SHORTCUTS_GROTTO, THE_LOST_WOODS, 0x081A}},
{{EntranceType::GrottoGrave, KOKIRI_FOREST, KF_STORMS_GROTTO, 0x071B},
{EntranceType::GrottoGrave, KF_STORMS_GROTTO, KOKIRI_FOREST, 0x081B}},
{{EntranceType::GrottoGrave, ZORAS_DOMAIN, ZD_STORMS_GROTTO, 0x071C},
{EntranceType::GrottoGrave, ZD_STORMS_GROTTO, ZORAS_DOMAIN, 0x081C}},
{{EntranceType::GrottoGrave, GERUDO_FORTRESS, GF_STORMS_GROTTO, 0x071D},
{EntranceType::GrottoGrave, GF_STORMS_GROTTO, GERUDO_FORTRESS, 0x081D}},
{{EntranceType::GrottoGrave, GV_FORTRESS_SIDE, GV_STORMS_GROTTO, 0x071E},
{EntranceType::GrottoGrave, GV_STORMS_GROTTO, GV_FORTRESS_SIDE, 0x081E}},
{{EntranceType::GrottoGrave, GV_GROTTO_LEDGE, GV_OCTOROK_GROTTO, 0x071F},
{EntranceType::GrottoGrave, GV_OCTOROK_GROTTO, GV_GROTTO_LEDGE, 0x081F}},
{{EntranceType::GrottoGrave, LW_BEYOND_MIDO, DEKU_THEATER, 0x0720},
{EntranceType::GrottoGrave, DEKU_THEATER, LW_BEYOND_MIDO, 0x0820}},
// Graves have their own specified entrance indices
{{EntranceType::GrottoGrave, THE_GRAVEYARD, GRAVEYARD_SHIELD_GRAVE, 0x004B},
{EntranceType::GrottoGrave, GRAVEYARD_SHIELD_GRAVE, THE_GRAVEYARD, 0x035D}},
{{EntranceType::GrottoGrave, THE_GRAVEYARD, GRAVEYARD_HEART_PIECE_GRAVE, 0x031C},
{EntranceType::GrottoGrave, GRAVEYARD_HEART_PIECE_GRAVE, THE_GRAVEYARD, 0x0361}},
{{EntranceType::GrottoGrave, THE_GRAVEYARD, GRAVEYARD_COMPOSERS_GRAVE, 0x002D},
{EntranceType::GrottoGrave, GRAVEYARD_COMPOSERS_GRAVE, THE_GRAVEYARD, 0x050B}},
{{EntranceType::GrottoGrave, THE_GRAVEYARD, GRAVEYARD_DAMPES_GRAVE, 0x044F},
{EntranceType::GrottoGrave, GRAVEYARD_DAMPES_GRAVE, THE_GRAVEYARD, 0x0359}},
{{EntranceType::Overworld, KOKIRI_FOREST, LW_BRIDGE_FROM_FOREST, 0x05E0},
{EntranceType::Overworld, LW_BRIDGE, KOKIRI_FOREST, 0x020D}},
{{EntranceType::Overworld, KOKIRI_FOREST, THE_LOST_WOODS, 0x011E},
{EntranceType::Overworld, LW_FOREST_EXIT, KOKIRI_FOREST, 0x0286}},
{{EntranceType::Overworld, THE_LOST_WOODS, GC_WOODS_WARP, 0x04E2},
{EntranceType::Overworld, GC_WOODS_WARP, THE_LOST_WOODS, 0x04D6}},
{{EntranceType::Overworld, THE_LOST_WOODS, ZORAS_RIVER, 0x01DD},
{EntranceType::Overworld, ZORAS_RIVER, THE_LOST_WOODS, 0x04DA}},
{{EntranceType::Overworld, LW_BEYOND_MIDO, SFM_ENTRYWAY, 0x00FC},
{EntranceType::Overworld, SFM_ENTRYWAY, LW_BEYOND_MIDO, 0x01A9}},
{{EntranceType::Overworld, LW_BRIDGE, HYRULE_FIELD, 0x0185},
{EntranceType::Overworld, HYRULE_FIELD, LW_BRIDGE, 0x04DE}},
{{EntranceType::Overworld, HYRULE_FIELD, LAKE_HYLIA, 0x0102},
{EntranceType::Overworld, LAKE_HYLIA, HYRULE_FIELD, 0x0189}},
{{EntranceType::Overworld, HYRULE_FIELD, GERUDO_VALLEY, 0x0117},
{EntranceType::Overworld, GERUDO_VALLEY, HYRULE_FIELD, 0x018D}},
{{EntranceType::Overworld, HYRULE_FIELD, MARKET_ENTRANCE, 0x0276},
{EntranceType::Overworld, MARKET_ENTRANCE, HYRULE_FIELD, 0x01FD}},
{{EntranceType::Overworld, HYRULE_FIELD, KAKARIKO_VILLAGE, 0x00DB},
{EntranceType::Overworld, KAKARIKO_VILLAGE, HYRULE_FIELD, 0x017D}},
{{EntranceType::Overworld, HYRULE_FIELD, ZR_FRONT, 0x00EA},
{EntranceType::Overworld, ZR_FRONT, HYRULE_FIELD, 0x0181}},
{{EntranceType::Overworld, HYRULE_FIELD, LON_LON_RANCH, 0x0157},
{EntranceType::Overworld, LON_LON_RANCH, HYRULE_FIELD, 0x01F9}},
{{EntranceType::Overworld, LAKE_HYLIA, ZORAS_DOMAIN, 0x0328},
{EntranceType::Overworld, ZORAS_DOMAIN, LAKE_HYLIA, 0x0560}},
{{EntranceType::Overworld, GV_FORTRESS_SIDE, GERUDO_FORTRESS, 0x0129},
{EntranceType::Overworld, GERUDO_FORTRESS, GV_FORTRESS_SIDE, 0x022D}},
{{EntranceType::Overworld, GF_OUTSIDE_GATE, WASTELAND_NEAR_FORTRESS, 0x0130},
{EntranceType::Overworld, WASTELAND_NEAR_FORTRESS, GF_OUTSIDE_GATE, 0x03AC}},
{{EntranceType::Overworld, WASTELAND_NEAR_COLOSSUS, DESERT_COLOSSUS, 0x0123},
{EntranceType::Overworld, DESERT_COLOSSUS, WASTELAND_NEAR_COLOSSUS, 0x0365}},
{{EntranceType::Overworld, MARKET_ENTRANCE, THE_MARKET, 0x00B1},
{EntranceType::Overworld, THE_MARKET, MARKET_ENTRANCE, 0x0033}},
{{EntranceType::Overworld, THE_MARKET, CASTLE_GROUNDS, 0x0138},
{EntranceType::Overworld, CASTLE_GROUNDS, THE_MARKET, 0x025A}},
{{EntranceType::Overworld, THE_MARKET, TOT_ENTRANCE, 0x0171},
{EntranceType::Overworld, TOT_ENTRANCE, THE_MARKET, 0x025E}},
{{EntranceType::Overworld, KAKARIKO_VILLAGE, THE_GRAVEYARD, 0x00E4},
{EntranceType::Overworld, THE_GRAVEYARD, KAKARIKO_VILLAGE, 0x0195}},
{{EntranceType::Overworld, KAK_BEHIND_GATE, DEATH_MOUNTAIN_TRAIL, 0x013D},
{EntranceType::Overworld, DEATH_MOUNTAIN_TRAIL, KAK_BEHIND_GATE, 0x0191}},
{{EntranceType::Overworld, DEATH_MOUNTAIN_TRAIL, GORON_CITY, 0x014D},
{EntranceType::Overworld, GORON_CITY, DEATH_MOUNTAIN_TRAIL, 0x01B9}},
{{EntranceType::Overworld, GC_DARUNIAS_CHAMBER, DMC_LOWER_LOCAL, 0x0246},
{EntranceType::Overworld, DMC_LOWER_NEARBY, GC_DARUNIAS_CHAMBER, 0x01C1}},
{{EntranceType::Overworld, DEATH_MOUNTAIN_SUMMIT, DMC_UPPER_LOCAL, 0x0147},
{EntranceType::Overworld, DMC_UPPER_NEARBY, DEATH_MOUNTAIN_SUMMIT, 0x01BD}},
{{EntranceType::Overworld, ZR_BEHIND_WATERFALL, ZORAS_DOMAIN, 0x0108},
{EntranceType::Overworld, ZORAS_DOMAIN, ZR_BEHIND_WATERFALL, 0x019D}},
{{EntranceType::Overworld, ZD_BEHIND_KING_ZORA, ZORAS_FOUNTAIN, 0x0225},
{EntranceType::Overworld, ZORAS_FOUNTAIN, ZD_BEHIND_KING_ZORA, 0x01A1}},
};
entranceShuffleFailure = false;
SetAllEntrancesData(entranceShuffleTable);
// one_way_entrance_pools = OrderedDict()
std::map<EntranceType, std::vector<Entrance*>> entrancePools = {};
// one_way_priorities = {}
//owl drops
//spawns
//warpsongs
//Shuffle Dungeon Entrances
if (Settings::ShuffleDungeonEntrances.IsNot(SHUFFLEDUNGEONS_OFF)) {
entrancePools[EntranceType::Dungeon] = GetShuffleableEntrances(EntranceType::Dungeon);
//Add Ganon's Castle, if set to On + Ganon
if (Settings::ShuffleDungeonEntrances.Is(SHUFFLEDUNGEONS_GANON)) {
AddElementsToPool(entrancePools[EntranceType::Dungeon], GetShuffleableEntrances(EntranceType::GanonDungeon));
}
//If forest is closed don't allow a forest escape via spirit temple hands
if (Settings::OpenForest.Is(OPENFOREST_CLOSED) && !(Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances)) {
FilterAndEraseFromPool(entrancePools[EntranceType::Dungeon], [](const Entrance* entrance){return entrance->GetParentRegionKey() == KF_OUTSIDE_DEKU_TREE &&
entrance->GetConnectedRegionKey() == DEKU_TREE_ENTRYWAY;});
}
//decoupled entrances stuff
}
//interior entrances
if (Settings::ShuffleInteriorEntrances.IsNot(SHUFFLEINTERIORS_OFF)) {
entrancePools[EntranceType::Interior] = GetShuffleableEntrances(EntranceType::Interior);
//special interiors
if (Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL)) {
AddElementsToPool(entrancePools[EntranceType::Interior], GetShuffleableEntrances(EntranceType::SpecialInterior));
}
//decoupled entrance stuff
}
//grotto entrances
if (Settings::ShuffleGrottoEntrances) {
entrancePools[EntranceType::GrottoGrave] = GetShuffleableEntrances(EntranceType::GrottoGrave);
//decoupled entrance stuff
}
//overworld entrances
if (Settings::ShuffleOverworldEntrances) {
bool excludeOverworldReverse = false; //mix_entrance_pools == all && !decouple_entrances
entrancePools[EntranceType::Overworld] = GetShuffleableEntrances(EntranceType::Overworld, excludeOverworldReverse);
// if not worlds[0].decouple_entrances:
// entrance_pools['Overworld'].remove(world.get_entrance('GV Lower Stream -> Lake Hylia'))
if (!excludeOverworldReverse) {
totalRandomizableEntrances -= 26; //Only count each overworld entrance once
}
}
//Set shuffled entrances as such
for (auto& pool : entrancePools) {
for (Entrance* entrance : pool.second) {
entrance->SetAsShuffled();
totalRandomizableEntrances++;
if (entrance->GetReverse() != nullptr) {
entrance->GetReverse()->SetAsShuffled();
}
}
}
//combine entrance pools if mixing pools
//Build target entrance pools and set the assumption for entrances being reachable
//one way entrance stuff
//assume entrance pools for each type
std::map<EntranceType, std::vector<Entrance*>> targetEntrancePools = {};
for (auto& pool : entrancePools) {
targetEntrancePools[pool.first] = AssumeEntrancePool(pool.second);
}
//distribution stuff
//check placed on-way entrances
//remove replaced entrances so we don't place two in one target
//remvoe priority targets if any placed entrances point at their regions
//place priority entrances
//delete all targets that we just placed from one way target pools so multiple one way entrances don't use the same target
//shuffle all entrances among pools to shuffle
for (auto& pool : entrancePools) {
ShuffleEntrancePool(pool.second, targetEntrancePools[pool.first]);
if (entranceShuffleFailure) {
return ENTRANCE_SHUFFLE_FAILURE;
}
}
return ENTRANCE_SHUFFLE_SUCCESS;
}
//Create the set of entrances that will be overridden
void CreateEntranceOverrides() {
entranceOverrides.clear();
if (noRandomEntrances) {
return;
}
SPDLOG_DEBUG("\nCREATING ENTRANCE OVERRIDES\n");
auto allShuffleableEntrances = GetShuffleableEntrances(EntranceType::All, false);
for (Entrance* entrance : allShuffleableEntrances) {
//Double-check to make sure the entrance is actually shuffled
if (!entrance->IsShuffled()) {
continue;
}
auto message = "Setting " + entrance->to_string() + "\n";
SPDLOG_DEBUG(message);
int16_t originalIndex = entrance->GetIndex();
int16_t destinationIndex = entrance->GetReverse()->GetIndex();
int16_t originalBlueWarp = entrance->GetBlueWarp();
int16_t replacementIndex = entrance->GetReplacement()->GetIndex();
int16_t replacementDestinationIndex = entrance->GetReplacement()->GetReverse()->GetIndex();
entranceOverrides.push_back({
.index = originalIndex,
.destination = destinationIndex,
.blueWarp = originalBlueWarp,
.override = replacementIndex,
.overrideDestination = replacementDestinationIndex,
});
message = "\tOriginal: " + std::to_string(originalIndex) + "\n";
SPDLOG_DEBUG(message);
message = "\tReplacement " + std::to_string(replacementIndex) + "\n";
SPDLOG_DEBUG(message);
}
}
std::vector<std::list<Entrance*>> playthroughEntrances;