mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-08-13 17:03:47 -04:00
c7ccd6dbff
Properly routes SPDLog to the console. Creates an API to be able to send command responses back to the console. Cleans up the console UI, hiding options when not needed. Removes stdout console sink for Windows.
878 lines
52 KiB
C++
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;
|