From d9f3844b2db80f74fc233391164981cf0b59f1f7 Mon Sep 17 00:00:00 2001 From: Adam Bird Date: Tue, 6 Dec 2022 18:37:50 -0500 Subject: [PATCH] [Feature] Entrance Rando v2 (#2071) --- soh/include/z64save.h | 6 + .../custom-message/CustomMessageTypes.h | 7 + soh/soh/Enhancements/presets.h | 3 +- .../randomizer/3drando/entrance.cpp | 1062 +++++++++++------ .../randomizer/3drando/entrance.hpp | 9 + .../Enhancements/randomizer/3drando/fill.cpp | 2 +- .../Enhancements/randomizer/3drando/hints.cpp | 69 ++ .../Enhancements/randomizer/3drando/hints.hpp | 7 + .../Enhancements/randomizer/3drando/keys.hpp | 15 +- .../randomizer/3drando/location_access.cpp | 82 +- .../location_access/locacc_castle_town.cpp | 30 +- .../location_access/locacc_death_mountain.cpp | 18 +- .../location_access/locacc_gerudo_valley.cpp | 21 +- .../location_access/locacc_hyrule_field.cpp | 30 +- .../location_access/locacc_kakariko.cpp | 49 +- .../location_access/locacc_lost_woods.cpp | 26 +- .../location_access/locacc_spirit_temple.cpp | 6 +- .../location_access/locacc_zoras_domain.cpp | 12 +- .../3drando/setting_descriptions.cpp | 46 + .../3drando/setting_descriptions.hpp | 14 + .../randomizer/3drando/settings.cpp | 111 +- .../randomizer/3drando/settings.hpp | 18 + .../randomizer/3drando/spoiler_log.cpp | 70 +- .../Enhancements/randomizer/randomizer.cpp | 199 ++- soh/soh/Enhancements/randomizer/randomizer.h | 1 + .../randomizer/randomizer_entrance.c | 64 +- .../randomizer/randomizer_entrance.h | 1 + .../randomizer/randomizer_grotto.c | 78 +- .../randomizer/randomizer_grotto.h | 2 + soh/soh/OTRGlobals.cpp | 3 + soh/soh/SaveManager.cpp | 24 + soh/src/code/z_sram.c | 19 +- .../actors/ovl_Demo_Kankyo/z_demo_kankyo.c | 5 + soh/src/overlays/actors/ovl_En_Owl/z_en_owl.c | 12 +- .../actors/ovl_player_actor/z_player.c | 5 + .../ovl_file_choose/z_file_choose.c | 9 + 36 files changed, 1599 insertions(+), 536 deletions(-) diff --git a/soh/include/z64save.h b/soh/include/z64save.h index 52c7318a6..9be4c8fee 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -261,6 +261,12 @@ typedef struct { /* */ char adultAltarText[750]; /* */ char ganonHintText[150]; /* */ char ganonText[250]; + /* */ char warpMinuetText[100]; + /* */ char warpBoleroText[100]; + /* */ char warpSerenadeText[100]; + /* */ char warpRequiemText[100]; + /* */ char warpNocturneText[100]; + /* */ char warpPreludeText[100]; /* */ u8 seedIcons[5]; /* */ u16 randomizerInf[9]; /* */ u16 adultTradeItems; diff --git a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h index 8856d7e5a..6c736f8b0 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h +++ b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h @@ -28,6 +28,13 @@ typedef enum { TEXT_SCRUB_RANDOM_FREE = 0x9001, TEXT_SHOP_ITEM_RANDOM = 0x9100, TEXT_SHOP_ITEM_RANDOM_CONFIRM = 0x9101, + TEXT_WARP_MINUET_OF_FOREST = 0x88D, + TEXT_WARP_BOLERO_OF_FIRE = 0x88E, + TEXT_WARP_SERENADE_OF_WATER = 0x88F, + TEXT_WARP_REQUIEM_OF_SPIRIT = 0x890, + TEXT_WARP_NOCTURNE_OF_SHADOW = 0x891, + TEXT_WARP_PRELUDE_OF_LIGHT = 0x892, + TEXT_WARP_RANDOM_REPLACED_TEXT = 0x9200, } TextIDs; #ifdef __cplusplus diff --git a/soh/soh/Enhancements/presets.h b/soh/soh/Enhancements/presets.h index b875e9411..e836fd46b 100644 --- a/soh/soh/Enhancements/presets.h +++ b/soh/soh/Enhancements/presets.h @@ -570,6 +570,7 @@ const std::vector s6PresetEntries = { PRESET_ENTRY_S32("gRandomizeShuffleDungeonReward", RO_DUNGEON_REWARDS_END_OF_DUNGEON), PRESET_ENTRY_S32("gRandomizeShuffleGanonBossKey", RO_GANON_BOSS_KEY_STARTWITH), PRESET_ENTRY_S32("gRandomizeShuffleKokiriSword", 1), + PRESET_ENTRY_S32("gRandomizeShuffleOverworldSpawns", RO_GENERIC_ON), PRESET_ENTRY_S32("gRandomizeSkipChildStealth", 1), PRESET_ENTRY_S32("gRandomizeSkipChildZelda", 1), PRESET_ENTRY_S32("gRandomizeSkipEponaRace", 1), @@ -674,7 +675,7 @@ const std::map presetTypes = { { RANDOMIZER_PRESET_S6, { "S6 Tournament (Adapted)", "Matches OOTR S6 tournament settings as close as we can get with the options available in SoH. The following differences are notable:\n" \ - "- Child overworld spawn not randomized\n" \ + "- Both child and adult overworld spawns are randomized" \ "- Dungeon rewards are shuffled at the end of dungeons, rather than at the end of their own dungeon\n" \ "- Full adult trade sequence is shuffled instead of the selected 4\n" \ "- Hint distribution no \"tournament\" mode, falling back to balanced", diff --git a/soh/soh/Enhancements/randomizer/3drando/entrance.cpp b/soh/soh/Enhancements/randomizer/3drando/entrance.cpp index 549624760..442d07f08 100644 --- a/soh/soh/Enhancements/randomizer/3drando/entrance.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/entrance.cpp @@ -24,14 +24,22 @@ static int curNumRandomizedEntrances = 0; typedef struct { EntranceType type; - uint32_t parentRegion; - uint32_t connectedRegion; + AreaKey parentRegion; + AreaKey connectedRegion; int16_t index; int16_t blueWarp; } EntranceLinkInfo; + +EntranceLinkInfo NO_RETURN_ENTRANCE = {EntranceType::None, NONE, NONE, -1}; + +typedef struct { + std::list targetRegions; + std::list allowedTypes; +} PriorityEntrance; //primary, secondary using EntranceInfoPair = std::pair; using EntrancePair = std::pair; +using EntrancePools = std::map>; //The entrance randomization algorithm used here is a direct copy of //the algorithm used in the original N64 randomizer (except now in C++ instead @@ -73,16 +81,12 @@ void SetAllEntrancesData(std::vector& entranceShuffleTable) { 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'] } } } @@ -90,10 +94,18 @@ void SetAllEntrancesData(std::vector& entranceShuffleTable) { static std::vector AssumeEntrancePool(std::vector& entrancePool) { std::vector assumedPool = {}; for (Entrance* entrance : entrancePool) { + totalRandomizableEntrances++; Entrance* assumedForward = entrance->AssumeReachable(); - if (entrance->GetReverse() != nullptr /*&& entrances are not decoupled*/) { + if (entrance->GetReverse() != nullptr && !Settings::DecoupleEntrances) { Entrance* assumedReturn = entrance->GetReverse()->AssumeReachable(); - //mixed pool assumption stuff + if (!(Settings::MixedEntrancePools && (Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL)))) { + auto type = entrance->GetType(); + if (((type == EntranceType::Dungeon || type == EntranceType::GrottoGrave) && entrance->GetReverse()->GetName() != "Spirit Temple Entryway -> Desert Colossus From Spirit Entryway") || + (type == EntranceType::Interior && Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL))) { + // In most cases, Dungeon, Grotto/Grave and Simple Interior exits shouldn't be assumed able to give access to their parent region + assumedReturn->SetCondition([]{return false;}); + } + } assumedForward->BindTwoWay(assumedReturn); } assumedPool.push_back(assumedForward); @@ -101,10 +113,34 @@ static std::vector AssumeEntrancePool(std::vector& entranc return assumedPool; } +static std::vector BuildOneWayTargets(std::vector typesToInclude, std::vector> exclude = {}/*, target_region_names*/) { + std::vector oneWayEntrances = {}; + // Get all entrances of the specified type + for (EntranceType poolType : typesToInclude) { + AddElementsToPool(oneWayEntrances, GetShuffleableEntrances(poolType, false)); + } + // Filter out any that are passed in the exclusion list + FilterAndEraseFromPool(oneWayEntrances, [&exclude](Entrance* entrance){ + std::pair entranceBeingChecked (entrance->GetParentRegionKey(), entrance->GetConnectedRegionKey()); + return ElementInContainer(entranceBeingChecked, exclude); + }); + + // The code below is part of the function in ootr, but no use of the function ever provides target_region_names + // if target_region_names: + // return [entrance.get_new_target() for entrance in valid_one_way_entrances + // if entrance.connected_region.name in target_region_names] + + std::vector newTargets = {}; + for (Entrance* entrance : oneWayEntrances) { + newTargets.push_back(entrance->GetNewTarget()); + } + return newTargets; +} + //returns restrictive entrances and soft entrances in an array of size 2 (restrictive vector is index 0, soft is index 1) static std::array, 2> SplitEntrancesByRequirements(std::vector& entrancesToSplit, std::vector& assumedEntrances) { //First, disconnect all root assumed entrances and save which regions they were originally connected to, so we can reconnect them later - std::map originalConnectedRegions = {}; + std::map originalConnectedRegions = {}; std::set entrancesToDisconnect = {}; for (Entrance* entrance : assumedEntrances) { entrancesToDisconnect.insert(entrance); @@ -126,9 +162,9 @@ static std::array, 2> SplitEntrancesByRequirements(std::v std::vector softEntrances = {}; Logic::LogicReset(); - // //Apply the effects of all advancement items to search for entrance accessibility - std::vector items = FilterFromPool(ItemPool, [](const auto i){ return ItemTable(i).IsAdvancement();}); - for (uint32_t unplacedItem : items) { + // Apply the effects of all advancement items to search for entrance accessibility + std::vector items = FilterFromPool(ItemPool, [](const ItemKey i){ return ItemTable(i).IsAdvancement();}); + for (ItemKey unplacedItem : items) { ItemTable(unplacedItem).ApplyEffect(); } // run a search to see what's accessible @@ -160,7 +196,18 @@ static bool AreEntrancesCompatible(Entrance* entrance, Entrance* target, std::ve return false; } - //one-way entrance stuff + // One way entrances shouldn't lead to the same scene as other already chosen one way entrances + auto type = entrance->GetType(); + const std::vector 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); + return false; + } + } + } return true; } @@ -168,19 +215,21 @@ 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(message); entrance->Connect(targetEntrance->Disconnect()); entrance->SetReplacement(targetEntrance->GetReplacement()); - if (entrance->GetReverse() != nullptr /*&& entrances aren't decoupled*/) { + if (entrance->GetReverse() != nullptr && !Settings::DecoupleEntrances) { targetEntrance->GetReplacement()->GetReverse()->Connect(entrance->GetReverse()->GetAssumed()->Disconnect()); targetEntrance->GetReplacement()->GetReverse()->SetReplacement(entrance->GetReverse()); } } +// In the event that we need to retry shuffling an entire group we can restore the +// original connections to reset the entrance and target entrance. static void RestoreConnections(Entrance* entrance, Entrance* targetEntrance) { targetEntrance->Connect(entrance->Disconnect()); entrance->SetReplacement(nullptr); - if (entrance->GetReverse() != nullptr /*&& entrances are not decoupled*/) { + if (entrance->GetReverse() != nullptr && !Settings::DecoupleEntrances) { entrance->GetReverse()->GetAssumed()->Connect(targetEntrance->GetReplacement()->GetReverse()->Disconnect()); targetEntrance->GetReplacement()->GetReverse()->SetReplacement(nullptr); } @@ -198,7 +247,7 @@ static void DeleteTargetEntrance(Entrance* targetEntrance) { static void ConfirmReplacement(Entrance* entrance, Entrance* targetEntrance) { DeleteTargetEntrance(targetEntrance); - if (entrance->GetReverse() != nullptr /*&& entrances are not decoupled*/) { + if (entrance->GetReverse() != nullptr && !Settings::DecoupleEntrances) { auto replacedReverse = targetEntrance->GetReplacement()->GetReverse(); DeleteTargetEntrance(replacedReverse->GetReverse()->GetAssumed()); } @@ -207,47 +256,47 @@ static void ConfirmReplacement(Entrance* entrance, Entrance* targetEntrance) { // 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& 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; - } - } - + 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"); + SPDLOG_DEBUG("Validating world\n"); //check certain conditions when certain types of ER are enabled EntranceType type = EntranceType::None; @@ -255,16 +304,18 @@ static bool ValidateWorld(Entrance* entrancePlaced) { type = entrancePlaced->GetType(); } - bool checkPoeCollectorAccess = (Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL)) && (entrancePlaced == nullptr /*|| Settings::MixedEntrancePools.IsNot(MIXEDENTRANCES_OFF)*/ || + bool checkPoeCollectorAccess = (Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL)) && (entrancePlaced == nullptr || Settings::MixedEntrancePools || 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)*/ || + bool checkOtherEntranceAccess = (Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL) || Settings::ShuffleOverworldSpawns) && (entrancePlaced == nullptr || Settings::MixedEntrancePools || type == EntranceType::SpecialInterior || type == EntranceType::Overworld || type == EntranceType::Spawn || type == EntranceType::WarpSong || type == EntranceType::OwlDrop); - // Check to make sure all locations are still reachable + // Search the world to verify that all necessary conditions are still being held + // Conditions will be checked during the search and any that fail will be figured out + // afterwards Logic::LogicReset(); GetAccessibleLocations({}, SearchMode::ValidateWorld, "", checkPoeCollectorAccess, checkOtherEntranceAccess); - // if not world.decouple_entrances: + if (!Settings::DecoupleEntrances) { // 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) @@ -285,11 +336,11 @@ static bool ValidateWorld(Entrance* entrancePlaced) { 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); + 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); + SPDLOG_DEBUG(message); return false; } } @@ -299,15 +350,16 @@ static bool ValidateWorld(Entrance* entrancePlaced) { if (ElementInContainer(name, childForbidden) && !EntranceUnreachableAs(entrance, AGE_CHILD, alreadyChecked)) { auto message = name + " is potentially accessible as child\n"; - SPDLOG_DEBUG(message); + 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); + 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)) { @@ -317,7 +369,7 @@ static bool ValidateWorld(Entrance* entrancePlaced) { 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); + SPDLOG_DEBUG(message); return false; } } @@ -328,23 +380,23 @@ static bool ValidateWorld(Entrance* entrancePlaced) { 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"); + 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"); + 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"); + 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"); + SPDLOG_DEBUG("Path to Temple of Time as child is not guaranteed\n"); return false; } } @@ -353,7 +405,7 @@ static bool ValidateWorld(Entrance* entrancePlaced) { // 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"); + SPDLOG_DEBUG("Big Poe Shop access is not guarenteed as adult\n"); return false; } } @@ -373,7 +425,7 @@ static bool ReplaceEntrance(Entrance* entrance, Entrance* target, std::vectorGetConnectedRegionKey() != NONE) { @@ -395,6 +447,84 @@ static bool ReplaceEntrance(Entrance* entrance, Entrance* target, std::vector& allowedRegions, std::list& allowedTypes, std::vector& rollbacks, EntrancePools oneWayEntrancePools, EntrancePools oneWayTargetEntrancePools) { + // Combine the entrances for allowed types in one list. + // Shuffle this list. + // Pick the first one not already set, not adult spawn, that has a valid target entrance. + // Assemble then clear entrances from the pool and target pools as appropriate. + std::vector availPool = {}; + for (auto& pool : oneWayEntrancePools) { + auto entranceType = pool.first; + if (ElementInContainer(entranceType, allowedTypes)) { + AddElementsToPool(availPool, pool.second); + } + } + Shuffle(availPool); + + for (Entrance* entrance : availPool) { + if (entrance->GetReplacement() != nullptr) { + continue; + } + // Only allow Adult Spawn as sole Nocturne access if hints != mask. + // Otherwise, child access is required here (adult access assumed or guaranteed later). + if (entrance->GetParentRegionKey() == ADULT_SPAWN) { + if (priorityName != "Nocturne" || Settings::GossipStoneHints.Is(HINTS_MASK_OF_TRUTH)) { + continue; + } + } + // If not shuffling dungeons, Nocturne requires adult access + if (!Settings::ShuffleDungeonEntrances && priorityName == "Nocturne") { + if (entrance->GetType() != EntranceType::WarpSong && entrance->GetParentRegionKey() != ADULT_SPAWN) { + continue; + } + } + for (Entrance* target : oneWayTargetEntrancePools[entrance->GetType()]) { + AreaKey targetRegionKey = target->GetConnectedRegionKey(); + if (targetRegionKey != NONE && ElementInContainer(targetRegionKey, allowedRegions)) { + if (ReplaceEntrance(entrance, target, rollbacks)) { + // Return once the entrance has been replaced + return true; + } + } + } + } + #ifdef ENABLE_DEBUG + auto message = "ERROR: Unable to place priority one-way entrance for " + priorityName + "\n"; + SPDLOG_DEBUG(message); + PlacementLog_Write(); + #endif + return false; +} + +// Once the first entrance to Impas House has been placed, try to place the next one immediately to reduce chances of failure. +static bool PlaceOtherImpasHouseEntrance(std::vector entrances, std::vector targetEntrances, std::vector& rollbacks) { + // Get the other impas house entrance + auto otherImpaTargets = FilterFromPool(targetEntrances, [](const Entrance* target){return (target->GetConnectedRegionKey() == KAK_IMPAS_HOUSE || target->GetConnectedRegionKey() == KAK_IMPAS_HOUSE_BACK);}); + if (otherImpaTargets.empty()) { + return true; + } + + Entrance* otherImpaTarget = otherImpaTargets[0]; + auto m = "Now Placing Other Impa Target: " + otherImpaTarget->GetName() + "\n"; + SPDLOG_DEBUG(m); + AreaKey otherImpaRegion = otherImpaTarget->GetConnectedRegionKey() != KAK_IMPAS_HOUSE_BACK ? KAK_IMPAS_HOUSE_BACK : KAK_IMPAS_HOUSE; + for (Entrance* entrance : entrances) { + // If the entrance is already connected or it doesn't have the same hint region as the already placed impas house entrance, then don't try to use it + if (entrance->GetConnectedRegionKey() != NONE || (GetHintRegionHintKey(otherImpaRegion) != GetHintRegionHintKey(entrance->GetParentRegionKey()))) { + continue; + } + // If the placement succeeds, we return true + if (ReplaceEntrance(entrance, otherImpaTarget, rollbacks)) { + return true; + } + } + SPDLOG_DEBUG("No available entrances for placing other impa region.\n"); + return false; +} + // Shuffle entrances by placing them instead of entrances in the provided target entrances list static bool ShuffleEntrances(std::vector& entrances, std::vector& targetEntrances, std::vector& rollbacks) { @@ -412,7 +542,17 @@ static bool ShuffleEntrances(std::vector& entrances, std::vectorGetConnectedRegionKey() == KAK_IMPAS_HOUSE || target->GetConnectedRegionKey() == KAK_IMPAS_HOUSE_BACK); + if (ReplaceEntrance(entrance, target, rollbacks)) { + // If shuffle cows is enabled and the last entrance was one to Impas House, + // then immediately attempt to place the other entrance to Impas House + if (Settings::ShuffleCows && attemptedImpasHousePlacement) { + if (!PlaceOtherImpasHouseEntrance(entrances, targetEntrances, rollbacks)) { + return false; + } + } break; } } @@ -426,7 +566,43 @@ static bool ShuffleEntrances(std::vector& entrances, std::vector& entrancePool, std::vector& targetEntrances) { +static bool ShuffleOneWayPriorityEntrances(std::map& oneWayPriorities, EntrancePools oneWayEntrancePools, EntrancePools oneWayTargetEntrancePools, int retryCount = 2) { + while (retryCount > 0) { + retryCount--; + std::vector rollbacks = {}; + + bool success = true; + for (auto& priority : oneWayPriorities) { + std::string key = priority.first; + auto& regions = priority.second.targetRegions; + auto& types = priority.second.allowedTypes; + success = PlaceOneWayPriorityEntrance(key, regions, types, rollbacks, oneWayEntrancePools, oneWayTargetEntrancePools); + if (!success) { + for (auto& pair : rollbacks) { + RestoreConnections(pair.first, pair.second); + } + break; + } + } + if (!success) { + continue; + } + // If there are no issues, log the connections and continue + for (auto& pair : rollbacks) { + ConfirmReplacement(pair.first, pair.second); + } + break; + } + + if (retryCount <= 0) { + SPDLOG_DEBUG("Entrance placement attempt count for one way priorities exceeded. Restarting randomization completely\n"); + entranceShuffleFailure = true; + return false; + } + return true; +} + +static void ShuffleEntrancePool(std::vector& entrancePool, std::vector& targetEntrances, int retryCount = 20) { noRandomEntrances = false; auto splitEntrances = SplitEntrancesByRequirements(entrancePool, targetEntrances); @@ -434,14 +610,14 @@ static void ShuffleEntrancePool(std::vector& entrancePool, std::vecto auto& restrictiveEntrances = splitEntrances[0]; auto& softEntrances = splitEntrances[1]; - int retries = 20; + int retries = retryCount; while (retries > 0) { - if (retries != 20) { + 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"; - PlacementLog_Msg(message); - Areas::DumpWorldGraph(ticks); + SPDLOG_DEBUG(message); + //Areas::DumpWorldGraph(ticks); #endif } retries--; @@ -476,11 +652,22 @@ static void ShuffleEntrancePool(std::vector& entrancePool, std::vecto } if (retries <= 0) { - SPDLOG_DEBUG("Entrance placement attempt count exceeded. Restarting randomization completely"); + SPDLOG_DEBUG("Entrance placement attempt count exceeded. Restarting randomization completely"); entranceShuffleFailure = true; } } +static void SetShuffledEntrances(EntrancePools entrancePools) { + for (auto& pool : entrancePools) { + for (Entrance* entrance : pool.second) { + entrance->SetAsShuffled(); + if (entrance->GetReverse() != nullptr) { + entrance->GetReverse()->SetAsShuffled(); + } + } + } +} + //Process for setting up the shuffling of all entrances to be shuffled int ShuffleAllEntrances() { @@ -488,263 +675,301 @@ int ShuffleAllEntrances() { curNumRandomizedEntrances = 0; std::vector 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}}, + //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_FROM_SPIRIT_ENTRYWAY, 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::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}}, + {{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}}, + {{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::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}}, + {{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}}, + + {{EntranceType::Overworld, GV_LOWER_STREAM, LAKE_HYLIA, 0x0219}, NO_RETURN_ENTRANCE}, + + {{EntranceType::OwlDrop, LH_OWL_FLIGHT, HYRULE_FIELD, 0x027E}, NO_RETURN_ENTRANCE}, + {{EntranceType::OwlDrop, DMT_OWL_FLIGHT, KAK_IMPAS_ROOFTOP, 0x0554}, NO_RETURN_ENTRANCE}, + + {{EntranceType::Spawn, CHILD_SPAWN, KF_LINKS_HOUSE, 0x00BB}, NO_RETURN_ENTRANCE}, + {{EntranceType::Spawn, ADULT_SPAWN, TEMPLE_OF_TIME, 0x0282}, NO_RETURN_ENTRANCE}, // 0x282 is an unused entrance index repurposed to differentiate between + // Adult Spawn and prelude of light (normally they both use 0x5F4) + {{EntranceType::WarpSong, MINUET_OF_FOREST_WARP, SACRED_FOREST_MEADOW, 0x0600}, NO_RETURN_ENTRANCE}, + {{EntranceType::WarpSong, BOLERO_OF_FIRE_WARP, DMC_CENTRAL_LOCAL, 0x04F6}, NO_RETURN_ENTRANCE}, + {{EntranceType::WarpSong, SERENADE_OF_WATER_WARP, LAKE_HYLIA, 0x0604}, NO_RETURN_ENTRANCE}, + {{EntranceType::WarpSong, REQUIEM_OF_SPIRIT_WARP, DESERT_COLOSSUS, 0x01F1}, NO_RETURN_ENTRANCE}, + {{EntranceType::WarpSong, NOCTURNE_OF_SHADOW_WARP, GRAVEYARD_WARP_PAD_REGION, 0x0568}, NO_RETURN_ENTRANCE}, + {{EntranceType::WarpSong, PRELUDE_OF_LIGHT_WARP, TEMPLE_OF_TIME, 0x05F4}, NO_RETURN_ENTRANCE}, + }; + + std::map priorityEntranceTable = { + {"Bolero", {{DMC_CENTRAL_LOCAL}, {EntranceType::OwlDrop, EntranceType::WarpSong}}}, + {"Nocturne", {{GRAVEYARD_WARP_PAD_REGION}, {EntranceType::OwlDrop, EntranceType::Spawn, EntranceType::WarpSong}}}, + {"Requiem", {{DESERT_COLOSSUS, DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY}, {EntranceType::OwlDrop, EntranceType::Spawn, EntranceType::WarpSong}}}, }; entranceShuffleFailure = false; SetAllEntrancesData(entranceShuffleTable); - // one_way_entrance_pools = OrderedDict() - std::map> entrancePools = {}; - // one_way_priorities = {} + EntrancePools oneWayEntrancePools = {}; + EntrancePools entrancePools = {}; + std::map oneWayPriorities = {}; - //owl drops + // Owl Drops + if (Settings::ShuffleOwlDrops) { + oneWayEntrancePools[EntranceType::OwlDrop] = GetShuffleableEntrances(EntranceType::OwlDrop); + } - //spawns + // Spawns + if (Settings::ShuffleOverworldSpawns) { + oneWayEntrancePools[EntranceType::Spawn] = GetShuffleableEntrances(EntranceType::Spawn); + } - //warpsongs + // Warpsongs + if (Settings::ShuffleWarpSongs) { + oneWayEntrancePools[EntranceType::WarpSong] = GetShuffleableEntrances(EntranceType::WarpSong); + // In Glitchless, there aren't any other ways to access these areas + if (Settings::Logic.Is(LOGIC_GLITCHLESS)) { + oneWayPriorities["Bolero"] = priorityEntranceTable["Bolero"]; + oneWayPriorities["Nocturne"] = priorityEntranceTable["Nocturne"]; + if (!Settings::ShuffleDungeonEntrances && !Settings::ShuffleOverworldEntrances) { + oneWayPriorities["Requiem"] = priorityEntranceTable["Requiem"]; + } + } + } //Shuffle Dungeon Entrances if (Settings::ShuffleDungeonEntrances.IsNot(SHUFFLEDUNGEONS_OFF)) { @@ -758,57 +983,135 @@ int ShuffleAllEntrances() { FilterAndEraseFromPool(entrancePools[EntranceType::Dungeon], [](const Entrance* entrance){return entrance->GetParentRegionKey() == KF_OUTSIDE_DEKU_TREE && entrance->GetConnectedRegionKey() == DEKU_TREE_ENTRYWAY;}); } - - //decoupled entrances stuff + if (Settings::DecoupleEntrances) { + for (Entrance* entrance : entrancePools[EntranceType::Dungeon]) { + entrancePools[EntranceType::DungeonReverse].push_back(entrance->GetReverse()); + } + } } - //interior entrances + // Interior entrances if (Settings::ShuffleInteriorEntrances.IsNot(SHUFFLEINTERIORS_OFF)) { entrancePools[EntranceType::Interior] = GetShuffleableEntrances(EntranceType::Interior); - //special interiors + // Special interiors if (Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL)) { AddElementsToPool(entrancePools[EntranceType::Interior], GetShuffleableEntrances(EntranceType::SpecialInterior)); } - - //decoupled entrance stuff + if (Settings::DecoupleEntrances) { + for (Entrance* entrance : entrancePools[EntranceType::Interior]) { + entrancePools[EntranceType::InteriorReverse].push_back(entrance->GetReverse()); + } + } } //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(); + if (Settings::DecoupleEntrances) { + for (Entrance* entrance : entrancePools[EntranceType::GrottoGrave]) { + entrancePools[EntranceType::GrottoGraveReverse].push_back(entrance->GetReverse()); } } } - //combine entrance pools if mixing pools + //overworld entrances + if (Settings::ShuffleOverworldEntrances) { + bool excludeOverworldReverse = Settings::MixOverworld && !Settings::DecoupleEntrances; + entrancePools[EntranceType::Overworld] = GetShuffleableEntrances(EntranceType::Overworld, excludeOverworldReverse); + // Only shuffle GV Lower Stream -> Lake Hylia if decoupled entrances are on + if (!Settings::DecoupleEntrances) { + FilterAndEraseFromPool(entrancePools[EntranceType::Overworld], [](const Entrance* entrance){return entrance->GetParentRegionKey() == GV_LOWER_STREAM && + entrance->GetConnectedRegionKey() == LAKE_HYLIA;}); + } + } - //Build target entrance pools and set the assumption for entrances being reachable - //one way entrance stuff + // Set shuffled entrances as such + SetShuffledEntrances(entrancePools); + SetShuffledEntrances(oneWayEntrancePools); - //assume entrance pools for each type - std::map> targetEntrancePools = {}; + //combine entrance pools if mixing pools. Only continue if more than one pool is selected. + int totalMixedPools = (Settings::MixDungeons ? 1 : 0) + (Settings::MixOverworld ? 1 : 0) + (Settings::MixInteriors ? 1 : 0) + (Settings::MixGrottos ? 1 : 0); + if (totalMixedPools < 2) { + Settings::MixedEntrancePools.SetSelectedIndex(OFF); + Settings::MixDungeons.SetSelectedIndex(OFF); + Settings::MixOverworld.SetSelectedIndex(OFF); + Settings::MixInteriors.SetSelectedIndex(OFF); + Settings::MixGrottos.SetSelectedIndex(OFF); + } + if (Settings::MixedEntrancePools) { + std::set poolsToMix = {}; + if (Settings::MixDungeons) { + poolsToMix.insert(EntranceType::Dungeon); + // Insert reverse entrances when decoupled entrances is on + if (Settings::DecoupleEntrances) { + poolsToMix.insert(EntranceType::DungeonReverse); + } + } + if (Settings::MixOverworld) { + poolsToMix.insert(EntranceType::Overworld); + } + if (Settings::MixInteriors) { + poolsToMix.insert(EntranceType::Interior); + if (Settings::DecoupleEntrances) { + poolsToMix.insert(EntranceType::InteriorReverse); + } + } + if (Settings::MixGrottos) { + poolsToMix.insert(EntranceType::GrottoGrave); + if (Settings::DecoupleEntrances) { + poolsToMix.insert(EntranceType::GrottoGraveReverse); + } + } + + for (auto& pool : entrancePools) { + + auto type = pool.first; + + if (poolsToMix.count(type) > 0) { + AddElementsToPool(entrancePools[EntranceType::Mixed], pool.second); + entrancePools[type].clear(); + } + } + } + + // Build target entrance pools and set the assumption for entrances being reachable + EntrancePools oneWayTargetEntrancePools = {}; + for (auto& pool : oneWayEntrancePools) { + + std::vector validTargetTypes = {}; + EntranceType poolType = pool.first; + + if (poolType == EntranceType::OwlDrop) { + validTargetTypes = {EntranceType::WarpSong, EntranceType::OwlDrop, EntranceType::Overworld, EntranceType::Extra}; + oneWayTargetEntrancePools[poolType] = BuildOneWayTargets(validTargetTypes, {std::make_pair(PRELUDE_OF_LIGHT_WARP, TEMPLE_OF_TIME)}); + // Owl Drops are only accessible as child, so targets should reflect that + for (Entrance* target : oneWayTargetEntrancePools[poolType]) { + target->SetCondition([]{return Logic::IsChild;}); + } + + } else if (poolType == EntranceType::Spawn) { + validTargetTypes = {EntranceType::Spawn, EntranceType::WarpSong, EntranceType::OwlDrop, EntranceType::Overworld, EntranceType::Interior, EntranceType::SpecialInterior, EntranceType::GrottoGrave, EntranceType::Extra}; + oneWayTargetEntrancePools[poolType] = BuildOneWayTargets(validTargetTypes); + + } else if (poolType == EntranceType::WarpSong) { + validTargetTypes = {EntranceType::Spawn, EntranceType::WarpSong, EntranceType::OwlDrop, EntranceType::Overworld, EntranceType::Interior, EntranceType::SpecialInterior, EntranceType::GrottoGrave, EntranceType::Extra}; + oneWayTargetEntrancePools[poolType] = BuildOneWayTargets(validTargetTypes); + } + // for target in one_way_target_entrance_pools[pool_type]: + // target.add_rule((lambda entrances=entrance_pool: (lambda state, **kwargs: any(entrance.connected_region == None for entrance in entrances)))()) + } + + // Disconnect all one way entrances at this point (they need to be connected during all of the above process) + for (auto& pool : oneWayEntrancePools) { + for (Entrance* entrance : pool.second) { + totalRandomizableEntrances++; + entrance->Disconnect(); + } + } + + // Assume entrance pools for each type + EntrancePools targetEntrancePools = {}; for (auto& pool : entrancePools) { targetEntrancePools[pool.first] = AssumeEntrancePool(pool.second); } @@ -819,9 +1122,56 @@ int ShuffleAllEntrances() { //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 + // Place priority entrances + ShuffleOneWayPriorityEntrances(oneWayPriorities, oneWayEntrancePools, oneWayTargetEntrancePools); + if (entranceShuffleFailure) { + return ENTRANCE_SHUFFLE_FAILURE; + } - //delete all targets that we just placed from one way target pools so multiple one way entrances don't use the same target + // Delete all targets that we just placed from one way target pools so + // multiple one way entrances don't use the same target + std::vector replacedEntrances = {}; + for (auto& pool : oneWayEntrancePools) { + for (Entrance* entrance : pool.second) { + if (entrance->GetReplacement() != nullptr) { + replacedEntrances.push_back(entrance); + } + } + } + for (auto& pool : oneWayTargetEntrancePools) { + for (Entrance* remainingTarget : pool.second) { + auto replacement = remainingTarget->GetReplacement(); + if (ElementInContainer(replacement, replacedEntrances)) { + DeleteTargetEntrance(remainingTarget); + } + } + } + + // Shuffle all one way entrances among pools to shuffle + for (auto& pool : oneWayEntrancePools) { + ShuffleEntrancePool(pool.second, oneWayTargetEntrancePools[pool.first], 5); + if (entranceShuffleFailure) { + return ENTRANCE_SHUFFLE_FAILURE; + } + // Delete all targets that we just placed from other one way target pools so + // multiple one way entrances don't use the same target + replacedEntrances = FilterFromPool(pool.second, [](Entrance* entrance){ + return entrance->GetReplacement() != nullptr; + }); + for (auto& targetPool : oneWayTargetEntrancePools) { + for (Entrance* remainingTarget : targetPool.second) { + auto replacement = remainingTarget->GetReplacement(); + if (ElementInContainer(replacement, replacedEntrances)) { + DeleteTargetEntrance(remainingTarget); + } + } + } + // Delete all unused extra targets after placing a one way pool, since the + // unused targets won't ever be replaced + for (Entrance* unusedTarget : oneWayTargetEntrancePools[pool.first]) { + DeleteTargetEntrance(unusedTarget); + } + } //shuffle all entrances among pools to shuffle for (auto& pool : entrancePools) { @@ -834,7 +1184,7 @@ int ShuffleAllEntrances() { return ENTRANCE_SHUFFLE_SUCCESS; } -//Create the set of entrances that will be overridden +// Save the set of shuffled entrances that will be sent to the patch void CreateEntranceOverrides() { entranceOverrides.clear(); if (noRandomEntrances) { @@ -854,10 +1204,18 @@ void CreateEntranceOverrides() { 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(); + + int16_t destinationIndex = -1; + int16_t replacementDestinationIndex = -1; + + // Only set destination indices for two way entrances and when decouple entrances + // is off + if (entrance->GetReverse() != nullptr && !Settings::DecoupleEntrances) { + replacementDestinationIndex = entrance->GetReplacement()->GetReverse()->GetIndex(); + destinationIndex = entrance->GetReverse()->GetIndex(); + } entranceOverrides.push_back({ .index = originalIndex, diff --git a/soh/soh/Enhancements/randomizer/3drando/entrance.hpp b/soh/soh/Enhancements/randomizer/3drando/entrance.hpp index 013f31dbe..42ef1cb50 100644 --- a/soh/soh/Enhancements/randomizer/3drando/entrance.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/entrance.hpp @@ -21,11 +21,15 @@ enum class EntranceType { WarpSong, Dungeon, GanonDungeon, + DungeonReverse, Interior, + InteriorReverse, SpecialInterior, GrottoGrave, + GrottoGraveReverse, Overworld, Extra, + Mixed, All, }; @@ -40,6 +44,11 @@ public: } } + // Resets the glitchless condition for the entrance + void SetCondition(ConditionFn newCondition) { + conditions_met[0] = newCondition; + } + bool GetConditionsMet() const { if (Settings::Logic.Is(LOGIC_NONE) || Settings::Logic.Is(LOGIC_VANILLA)) { return true; diff --git a/soh/soh/Enhancements/randomizer/3drando/fill.cpp b/soh/soh/Enhancements/randomizer/3drando/fill.cpp index 380d761c4..af5d54e0e 100644 --- a/soh/soh/Enhancements/randomizer/3drando/fill.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/fill.cpp @@ -296,7 +296,7 @@ std::vector GetAccessibleLocations(const std::vector& allowe entranceSphere.push_back(&exit); exit.AddToPool(); // Don't list a coupled entrance from both directions - if (exit.GetReplacement()->GetReverse() != nullptr /*&& !DecoupleEntrances*/) { + if (exit.GetReplacement()->GetReverse() != nullptr && !Settings::DecoupleEntrances) { exit.GetReplacement()->GetReverse()->AddToPool(); } } diff --git a/soh/soh/Enhancements/randomizer/3drando/hints.cpp b/soh/soh/Enhancements/randomizer/3drando/hints.cpp index 2b700d4a8..785ad5cc9 100644 --- a/soh/soh/Enhancements/randomizer/3drando/hints.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/hints.cpp @@ -116,6 +116,12 @@ Text childAltarText; Text adultAltarText; Text ganonText; Text ganonHintText; +Text warpMinuetText; +Text warpBoleroText; +Text warpSerenadeText; +Text warpRequiemText; +Text warpNocturneText; +Text warpPreludeText; Text& GetChildAltarText() { return childAltarText; @@ -133,6 +139,30 @@ Text& GetGanonHintText() { return ganonHintText; } +Text& GetWarpMinuetText() { + return warpMinuetText; +} + +Text& GetWarpBoleroText() { + return warpBoleroText; +} + +Text& GetWarpSerenadeText() { + return warpSerenadeText; +} + +Text& GetWarpRequiemText() { + return warpRequiemText; +} + +Text& GetWarpNocturneText() { + return warpNocturneText; +} + +Text& GetWarpPreludeText() { + return warpPreludeText; +} + static Area* GetHintRegion(const uint32_t area) { std::vector alreadyChecked = {}; @@ -707,10 +737,49 @@ void CreateMerchantsHints() { CreateMessageFromTextObject(0x6078, 0, 2, 3, AddColorsAndFormat(carpetSalesmanTextTwo, {QM_RED, QM_YELLOW, QM_RED})); } +void CreateWarpSongTexts() { + auto warpSongEntrances = GetShuffleableEntrances(EntranceType::WarpSong, false); + + for (auto entrance : warpSongEntrances) { + Text resolvedHint; + // Start with entrance location text + auto region = entrance->GetConnectedRegion()->regionName; + resolvedHint = Text{"","",""} + region; + + auto destination = entrance->GetConnectedRegion()->GetHint().GetText(); + // Prefer hint location when available + if (destination.GetEnglish() != "No Hint") { + resolvedHint = destination; + } + + switch (entrance->GetIndex()) { + case 0x0600: // minuet + warpMinuetText = resolvedHint; + break; + case 0x04F6: // bolero + warpBoleroText = resolvedHint; + break; + case 0x0604: // serenade + warpSerenadeText = resolvedHint; + break; + case 0x01F1: // requiem + warpRequiemText = resolvedHint; + break; + case 0x0568: // nocturne + warpNocturneText = resolvedHint; + break; + case 0x05F4: // prelude + warpPreludeText = resolvedHint; + break; + } + } +} + void CreateAllHints() { CreateGanonText(); CreateAltarText(); + CreateWarpSongTexts(); SPDLOG_DEBUG("\nNOW CREATING HINTS\n"); const HintSetting& hintSetting = hintSettingTable[Settings::HintDistribution.Value()]; diff --git a/soh/soh/Enhancements/randomizer/3drando/hints.hpp b/soh/soh/Enhancements/randomizer/3drando/hints.hpp index e1c017284..9f184f762 100644 --- a/soh/soh/Enhancements/randomizer/3drando/hints.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/hints.hpp @@ -225,3 +225,10 @@ Text& GetChildAltarText(); Text& GetAdultAltarText(); Text& GetGanonText(); Text& GetGanonHintText(); + +Text& GetWarpMinuetText(); +Text& GetWarpBoleroText(); +Text& GetWarpSerenadeText(); +Text& GetWarpRequiemText(); +Text& GetWarpNocturneText(); +Text& GetWarpPreludeText(); diff --git a/soh/soh/Enhancements/randomizer/3drando/keys.hpp b/soh/soh/Enhancements/randomizer/3drando/keys.hpp index 07714145b..46486299a 100644 --- a/soh/soh/Enhancements/randomizer/3drando/keys.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/keys.hpp @@ -2,7 +2,10 @@ #include -using AreaKey = uint32_t; +using HintKey = uint32_t; +using ItemKey = uint32_t; +using LocationKey = uint32_t; +using AreaKey = uint32_t; typedef enum { NONE, @@ -1082,6 +1085,14 @@ typedef enum { //AREAS ROOT, ROOT_EXITS, + CHILD_SPAWN, + ADULT_SPAWN, + MINUET_OF_FOREST_WARP, + BOLERO_OF_FIRE_WARP, + SERENADE_OF_WATER_WARP, + REQUIEM_OF_SPIRIT_WARP, + NOCTURNE_OF_SHADOW_WARP, + PRELUDE_OF_LIGHT_WARP, KOKIRI_FOREST, KF_LINKS_HOUSE, KF_MIDOS_HOUSE, @@ -1135,6 +1146,7 @@ typedef enum { HAUNTED_WASTELAND, WASTELAND_NEAR_COLOSSUS, DESERT_COLOSSUS, + DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY, COLOSSUS_GREAT_FAIRY_FOUNTAIN, COLOSSUS_GROTTO, MARKET_ENTRANCE, @@ -1173,6 +1185,7 @@ typedef enum { KAK_POTION_SHOP_FRONT, KAK_POTION_SHOP_BACK, KAK_ROOFTOP, + KAK_IMPAS_ROOFTOP, KAK_BEHIND_GATE, KAK_BACKYARD, KAK_ODD_POTION_BUILDING, diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access.cpp index 0b6efde9b..e77d7943c 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access.cpp @@ -264,25 +264,66 @@ void AreaTable_Init() { areaTable[ROOT_EXITS] = Area("Root Exits", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits - Entrance(KF_LINKS_HOUSE, {[]{return IsChild;}}), - Entrance(TEMPLE_OF_TIME, {[]{return (CanPlay(PreludeOfLight) && CanLeaveForest) || IsAdult;}, - /*Glitched*/[]{return (((IsChild && (ChildCanAccess(KOKIRI_FOREST) || ChildCanAccess(HYRULE_FIELD) || ChildCanAccess(LAKE_HYLIA))) || (IsAdult && (AdultCanAccess(KOKIRI_FOREST) || AdultCanAccess(HYRULE_FIELD) || AdultCanAccess(LAKE_HYLIA)))) && (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::NOVICE) || - ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || ((Bugs || Fish) && (IsAdult || KokiriSword || Sticks || Bombs || HasBombchus || Boomerang || Slingshot || CanUse(MEGATON_HAMMER)) && CanShield && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::NOVICE)))) && PreludeOfLight && CanLeaveForest;}}), - Entrance(SACRED_FOREST_MEADOW, {[]{return CanPlay(MinuetOfForest);}, - /*Glitched*/[]{return (((IsChild && (ChildCanAccess(KOKIRI_FOREST) || ChildCanAccess(HYRULE_FIELD) || ChildCanAccess(LAKE_HYLIA))) || (IsAdult && (AdultCanAccess(KOKIRI_FOREST) || AdultCanAccess(HYRULE_FIELD) || AdultCanAccess(LAKE_HYLIA)))) && (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::NOVICE) || + Entrance(CHILD_SPAWN, {[]{return IsChild;}}), + Entrance(ADULT_SPAWN, {[]{return IsAdult;}}), + Entrance(MINUET_OF_FOREST_WARP, {[]{return CanPlay(MinuetOfForest);}, + /*Glitched*/[]{return (((IsChild && (ChildCanAccess(KOKIRI_FOREST) || ChildCanAccess(HYRULE_FIELD) || ChildCanAccess(LAKE_HYLIA))) || (IsAdult && (AdultCanAccess(KOKIRI_FOREST) || AdultCanAccess(HYRULE_FIELD) || AdultCanAccess(LAKE_HYLIA)))) && (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::NOVICE) || ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || ((Bugs || Fish) && (IsAdult || KokiriSword || Sticks || Bombs || HasBombchus || Boomerang || Slingshot || CanUse(MEGATON_HAMMER)) && CanShield && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::NOVICE)))) && MinuetOfForest;}}), - Entrance(DMC_CENTRAL_LOCAL, {[]{return CanPlay(BoleroOfFire) && CanLeaveForest;}, - /*Glitched*/[]{return (((IsChild && (ChildCanAccess(KOKIRI_FOREST) || ChildCanAccess(HYRULE_FIELD) || ChildCanAccess(LAKE_HYLIA))) || (IsAdult && (AdultCanAccess(KOKIRI_FOREST) || AdultCanAccess(HYRULE_FIELD) || AdultCanAccess(LAKE_HYLIA)))) && (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::NOVICE) || + Entrance(BOLERO_OF_FIRE_WARP, {[]{return CanPlay(BoleroOfFire) && CanLeaveForest;}, + /*Glitched*/[]{return (((IsChild && (ChildCanAccess(KOKIRI_FOREST) || ChildCanAccess(HYRULE_FIELD) || ChildCanAccess(LAKE_HYLIA))) || (IsAdult && (AdultCanAccess(KOKIRI_FOREST) || AdultCanAccess(HYRULE_FIELD) || AdultCanAccess(LAKE_HYLIA)))) && (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::NOVICE) || ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || ((Bugs || Fish) && (IsAdult || KokiriSword || Sticks || Bombs || HasBombchus || Boomerang || Slingshot || CanUse(MEGATON_HAMMER)) && CanShield && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::NOVICE)))) && BoleroOfFire && CanLeaveForest;}}), - Entrance(LAKE_HYLIA, {[]{return CanPlay(SerenadeOfWater) && CanLeaveForest;}, - /*Glitched*/[]{return (((IsChild && (ChildCanAccess(KOKIRI_FOREST) || ChildCanAccess(HYRULE_FIELD) || ChildCanAccess(LAKE_HYLIA))) || (IsAdult && (AdultCanAccess(KOKIRI_FOREST) || AdultCanAccess(HYRULE_FIELD) || AdultCanAccess(LAKE_HYLIA)))) && (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::NOVICE) || + Entrance(SERENADE_OF_WATER_WARP, {[]{return CanPlay(SerenadeOfWater) && CanLeaveForest;}, + /*Glitched*/[]{return (((IsChild && (ChildCanAccess(KOKIRI_FOREST) || ChildCanAccess(HYRULE_FIELD) || ChildCanAccess(LAKE_HYLIA))) || (IsAdult && (AdultCanAccess(KOKIRI_FOREST) || AdultCanAccess(HYRULE_FIELD) || AdultCanAccess(LAKE_HYLIA)))) && (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::NOVICE) || ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || ((Bugs || Fish) && (IsAdult || KokiriSword || Sticks || Bombs || HasBombchus || Boomerang || Slingshot || CanUse(MEGATON_HAMMER)) && CanShield && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::NOVICE)))) && SerenadeOfWater && CanLeaveForest;}}), - Entrance(GRAVEYARD_WARP_PAD_REGION, {[]{return CanPlay(NocturneOfShadow) && CanLeaveForest;}, - /*Glitched*/[]{return (((IsChild && (ChildCanAccess(KOKIRI_FOREST) || ChildCanAccess(HYRULE_FIELD) || ChildCanAccess(LAKE_HYLIA))) || (IsAdult && (AdultCanAccess(KOKIRI_FOREST) || AdultCanAccess(HYRULE_FIELD) || AdultCanAccess(LAKE_HYLIA)))) && (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::NOVICE) || + Entrance(NOCTURNE_OF_SHADOW_WARP, {[]{return CanPlay(NocturneOfShadow) && CanLeaveForest;}, + /*Glitched*/[]{return (((IsChild && (ChildCanAccess(KOKIRI_FOREST) || ChildCanAccess(HYRULE_FIELD) || ChildCanAccess(LAKE_HYLIA))) || (IsAdult && (AdultCanAccess(KOKIRI_FOREST) || AdultCanAccess(HYRULE_FIELD) || AdultCanAccess(LAKE_HYLIA)))) && (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::NOVICE) || ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || ((Bugs || Fish) && (IsAdult || KokiriSword || Sticks || Bombs || HasBombchus || Boomerang || Slingshot || CanUse(MEGATON_HAMMER)) && CanShield && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::NOVICE)))) && NocturneOfShadow && CanLeaveForest;}}), - Entrance(DESERT_COLOSSUS, {[]{return CanPlay(RequiemOfSpirit) && CanLeaveForest;}, - /*Glitched*/[]{return (((IsChild && (ChildCanAccess(KOKIRI_FOREST) || ChildCanAccess(HYRULE_FIELD) || ChildCanAccess(LAKE_HYLIA))) || (IsAdult && (AdultCanAccess(KOKIRI_FOREST) || AdultCanAccess(HYRULE_FIELD) || AdultCanAccess(LAKE_HYLIA)))) && (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::NOVICE) || + Entrance(REQUIEM_OF_SPIRIT_WARP, {[]{return CanPlay(RequiemOfSpirit) && CanLeaveForest;}, + /*Glitched*/[]{return (((IsChild && (ChildCanAccess(KOKIRI_FOREST) || ChildCanAccess(HYRULE_FIELD) || ChildCanAccess(LAKE_HYLIA))) || (IsAdult && (AdultCanAccess(KOKIRI_FOREST) || AdultCanAccess(HYRULE_FIELD) || AdultCanAccess(LAKE_HYLIA)))) && (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::NOVICE) || ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || ((Bugs || Fish) && (IsAdult || KokiriSword || Sticks || Bombs || HasBombchus || Boomerang || Slingshot || CanUse(MEGATON_HAMMER)) && CanShield && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::NOVICE)))) && RequiemOfSpirit && CanLeaveForest;}}), + Entrance(PRELUDE_OF_LIGHT_WARP, {[]{return CanPlay(PreludeOfLight) && CanLeaveForest;}, + /*Glitched*/[]{return (((IsChild && (ChildCanAccess(KOKIRI_FOREST) || ChildCanAccess(HYRULE_FIELD) || ChildCanAccess(LAKE_HYLIA))) || (IsAdult && (AdultCanAccess(KOKIRI_FOREST) || AdultCanAccess(HYRULE_FIELD) || AdultCanAccess(LAKE_HYLIA)))) && (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::NOVICE) || + ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || ((Bugs || Fish) && (IsAdult || KokiriSword || Sticks || Bombs || HasBombchus || Boomerang || Slingshot || CanUse(MEGATON_HAMMER)) && CanShield && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::NOVICE)))) && PreludeOfLight && CanLeaveForest;}}), + }); + + areaTable[CHILD_SPAWN] = Area("Child Spawn", "", TEMPLE_OF_TIME, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(KF_LINKS_HOUSE, {[]{return true;}}), + }); + + areaTable[ADULT_SPAWN] = Area("Adult Spawn", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(TEMPLE_OF_TIME, {[]{return true;}}), + }); + + areaTable[MINUET_OF_FOREST_WARP] = Area("Minuet of Forest Warp", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(SACRED_FOREST_MEADOW, {[]{return true;}}), + }); + + areaTable[BOLERO_OF_FIRE_WARP] = Area("Bolero of Fire Warp", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(DMC_CENTRAL_LOCAL, {[]{return true;}}), + }); + + areaTable[SERENADE_OF_WATER_WARP] = Area("Serenade of Water Warp", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(LAKE_HYLIA, {[]{return true;}}), + }); + + areaTable[REQUIEM_OF_SPIRIT_WARP] = Area("Requiem of Spirit Warp", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(DESERT_COLOSSUS, {[]{return true;}}), + }); + + areaTable[NOCTURNE_OF_SHADOW_WARP] = Area("Nocturne of Shadow Warp", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(GRAVEYARD_WARP_PAD_REGION, {[]{return true;}}), + }); + + areaTable[PRELUDE_OF_LIGHT_WARP] = Area("Prelude of Light Warp", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + //Exits + Entrance(TEMPLE_OF_TIME, {[]{return true;}}), }); // Overworld @@ -330,10 +371,19 @@ void AreaTable_Init() { namespace Areas { - static std::array allAreas = { + static std::array allAreas = { ROOT, ROOT_EXITS, + CHILD_SPAWN, + ADULT_SPAWN, + MINUET_OF_FOREST_WARP, + BOLERO_OF_FIRE_WARP, + SERENADE_OF_WATER_WARP, + REQUIEM_OF_SPIRIT_WARP, + NOCTURNE_OF_SHADOW_WARP, + PRELUDE_OF_LIGHT_WARP, + KOKIRI_FOREST, KF_LINKS_HOUSE, KF_MIDOS_HOUSE, @@ -373,6 +423,7 @@ namespace Areas { KAK_POTION_SHOP_FRONT, KAK_POTION_SHOP_BACK, KAK_ROOFTOP, + KAK_IMPAS_ROOFTOP, KAK_BEHIND_GATE, KAK_BACKYARD, KAK_ODD_POTION_BUILDING, @@ -473,6 +524,7 @@ namespace Areas { HAUNTED_WASTELAND, WASTELAND_NEAR_COLOSSUS, DESERT_COLOSSUS, + DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY, COLOSSUS_GREAT_FAIRY_FOUNTAIN, COLOSSUS_GROTTO, SPIRIT_TEMPLE_ENTRYWAY, diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_castle_town.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_castle_town.cpp index 172cca45d..aeb2ddbbf 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_castle_town.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_castle_town.cpp @@ -50,7 +50,7 @@ void AreaTable_Init_CastleTown() { Entrance(TEMPLE_OF_TIME, {[]{return true;}}), }); - areaTable[TEMPLE_OF_TIME] = Area("Temple of Time", "", TEMPLE_OF_TIME, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[TEMPLE_OF_TIME] = Area("Temple of Time", "Temple of Time", TEMPLE_OF_TIME, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(TOT_LIGHT_ARROWS_CUTSCENE, {[]{return IsAdult && CanTriggerLACS;}}), }, { @@ -61,7 +61,7 @@ void AreaTable_Init_CastleTown() { ((Bugs || Fish) && Bombs && (CanSurviveDamage || (Fairy && NumBottles >= 2)) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED) && CanDoGlitch(GlitchType::RestrictedItems, GlitchDifficulty::NOVICE)));}}), }); - areaTable[TOT_BEYOND_DOOR_OF_TIME] = Area("Beyond Door of Time", "", TEMPLE_OF_TIME, NO_DAY_NIGHT_CYCLE, { + areaTable[TOT_BEYOND_DOOR_OF_TIME] = Area("Beyond Door of Time", "Beyond Door of Time", TEMPLE_OF_TIME, NO_DAY_NIGHT_CYCLE, { //Events //EventAccess(&TimeTravel, {[]{return true;}}), }, { @@ -111,7 +111,7 @@ void AreaTable_Init_CastleTown() { Entrance(HYRULE_CASTLE_GROUNDS, {[]{return true;}}), }); - areaTable[HC_GREAT_FAIRY_FOUNTAIN] = Area("HC Great Fairy Fountain", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[HC_GREAT_FAIRY_FOUNTAIN] = Area("HC Great Fairy Fountain", "HC Great Fairy Fountain", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(HC_GREAT_FAIRY_REWARD, {[]{return CanPlay(ZeldasLullaby);}, /*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::INTERMEDIATE) || ((Bugs || Fish) && CanShield && Bombs && (CanSurviveDamage || (Fairy && NumBottles >= 2)) && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || ((Bugs || Fish) && HasBombchus && CanShield && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::ADVANCED))) && ZeldasLullaby;}}), @@ -120,7 +120,7 @@ void AreaTable_Init_CastleTown() { Entrance(CASTLE_GROUNDS, {[]{return true;}}), }); - areaTable[HC_STORMS_GROTTO] = Area("HC Storms Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, { + areaTable[HC_STORMS_GROTTO] = Area("HC Storms Grotto", "HC Storms Grotto", NONE, NO_DAY_NIGHT_CYCLE, { //Events EventAccess(&NutPot, {[]{return NutPot || CanBlastOrSmash;}}), EventAccess(&GossipStoneFairy, {[]{return GossipStoneFairy || (CanBlastOrSmash && CanSummonGossipFairy);}}), @@ -146,7 +146,7 @@ void AreaTable_Init_CastleTown() { /*Glitched*/[]{return (HasBombchus && CanDoGlitch(GlitchType::BombHover, GlitchDifficulty::NOVICE)) || CanDoGlitch(GlitchType::HoverBoost, GlitchDifficulty::ADVANCED) || (HoverBoots && CanShield && Bombs && CanDoGlitch(GlitchType::SuperSlide, GlitchDifficulty::EXPERT)) || (HoverBoots && CanDoGlitch(GlitchType::Megaflip, GlitchDifficulty::ADVANCED));}}), }); - areaTable[OGC_GREAT_FAIRY_FOUNTAIN] = Area("OGC Great Fairy Fountain", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[OGC_GREAT_FAIRY_FOUNTAIN] = Area("OGC Great Fairy Fountain", "OGC Great Fairy Fountain", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(OGC_GREAT_FAIRY_REWARD, {[]{return CanPlay(ZeldasLullaby);}, /*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::INTERMEDIATE) || ((Bugs || Fish) && CanShield && Bombs && (CanSurviveDamage || (Fairy && NumBottles >= 2)) && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || ((Bugs || Fish) && HasBombchus && CanShield && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::ADVANCED))) && ZeldasLullaby;}}), @@ -155,7 +155,7 @@ void AreaTable_Init_CastleTown() { Entrance(CASTLE_GROUNDS, {[]{return true;}}), }); - areaTable[MARKET_GUARD_HOUSE] = Area("Market Guard House", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[MARKET_GUARD_HOUSE] = Area("Market Guard House", "Market Guard House", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(MARKET_10_BIG_POES, {[]{return IsAdult && BigPoeKill;}}), LocationAccess(MARKET_GS_GUARD_HOUSE, {[]{return IsChild;}}), @@ -164,7 +164,7 @@ void AreaTable_Init_CastleTown() { Entrance(MARKET_ENTRANCE, {[]{return true;}}), }); - areaTable[MARKET_BAZAAR] = Area("Market Bazaar", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[MARKET_BAZAAR] = Area("Market Bazaar", "Market Bazaar", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(MARKET_BAZAAR_ITEM_1, {[]{return true;}}), LocationAccess(MARKET_BAZAAR_ITEM_2, {[]{return true;}}), @@ -179,7 +179,7 @@ void AreaTable_Init_CastleTown() { Entrance(THE_MARKET, {[]{return true;}}), }); - areaTable[MARKET_MASK_SHOP] = Area("Market Mask Shop", "", NONE, NO_DAY_NIGHT_CYCLE, { + areaTable[MARKET_MASK_SHOP] = Area("Market Mask Shop", "Market Mask Shop", NONE, NO_DAY_NIGHT_CYCLE, { //Events EventAccess(&SkullMask, {[]{return SkullMask || (ZeldasLetter && (CompleteMaskQuest || ChildCanAccess(KAKARIKO_VILLAGE)));}}), EventAccess(&MaskOfTruth, {[]{return MaskOfTruth || (SkullMask && (CompleteMaskQuest || (ChildCanAccess(THE_LOST_WOODS) && CanPlay(SariasSong) && AreaTable(THE_GRAVEYARD)->childDay && ChildCanAccess(HYRULE_FIELD) && HasAllStones)));}}), @@ -188,7 +188,7 @@ void AreaTable_Init_CastleTown() { Entrance(THE_MARKET, {[]{return true;}}), }); - areaTable[MARKET_SHOOTING_GALLERY] = Area("Market Shooting Gallery", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[MARKET_SHOOTING_GALLERY] = Area("Market Shooting Gallery", "Market Shooting Gallery", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(MARKET_SHOOTING_GALLERY_REWARD, {[]{return IsChild;}}), }, { @@ -196,7 +196,7 @@ void AreaTable_Init_CastleTown() { Entrance(THE_MARKET, {[]{return true;}}), }); - areaTable[MARKET_BOMBCHU_BOWLING] = Area("Market Bombchu Bowling", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[MARKET_BOMBCHU_BOWLING] = Area("Market Bombchu Bowling", "Market Bombchu Bowling", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(MARKET_BOMBCHU_BOWLING_FIRST_PRIZE, {[]{return CanPlayBowling;}}), LocationAccess(MARKET_BOMBCHU_BOWLING_SECOND_PRIZE, {[]{return CanPlayBowling;}}), @@ -206,7 +206,7 @@ void AreaTable_Init_CastleTown() { Entrance(THE_MARKET, {[]{return true;}}), }); - areaTable[MARKET_POTION_SHOP] = Area("Market Potion Shop", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[MARKET_POTION_SHOP] = Area("Market Potion Shop", "Market Potion Shop", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(MARKET_POTION_SHOP_ITEM_1, {[]{return true;}}), LocationAccess(MARKET_POTION_SHOP_ITEM_2, {[]{return true;}}), @@ -221,7 +221,7 @@ void AreaTable_Init_CastleTown() { Entrance(THE_MARKET, {[]{return true;}}), }); - areaTable[MARKET_TREASURE_CHEST_GAME] = Area("Market Treasure Chest Game", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[MARKET_TREASURE_CHEST_GAME] = Area("Market Treasure Chest Game", "Market Treasure Chest Game", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(MARKET_TREASURE_CHEST_GAME_REWARD, {[]{return (CanUse(LENS_OF_TRUTH) && !ShuffleChestMinigame) || (ShuffleChestMinigame.Is(SHUFFLECHESTMINIGAME_SINGLE_KEYS) && SmallKeys(MARKET_TREASURE_CHEST_GAME, 6)) || (ShuffleChestMinigame.Is(SHUFFLECHESTMINIGAME_PACK) && SmallKeys(MARKET_TREASURE_CHEST_GAME, 1));}, /*Glitched*/[]{return !ShuffleChestMinigame && (CanUse(FARORES_WIND) && (HasBottle || WeirdEgg) && CanDoGlitch(GlitchType::RestrictedItems, GlitchDifficulty::NOVICE));}}), @@ -235,7 +235,7 @@ void AreaTable_Init_CastleTown() { Entrance(THE_MARKET, {[]{return true;}}), }); - areaTable[MARKET_BOMBCHU_SHOP] = Area("Market Bombchu Shop", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[MARKET_BOMBCHU_SHOP] = Area("Market Bombchu Shop", "Market Bombchu Shop", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(MARKET_BOMBCHU_SHOP_ITEM_1, {[]{return true;}}), LocationAccess(MARKET_BOMBCHU_SHOP_ITEM_2, {[]{return true;}}), @@ -250,7 +250,7 @@ void AreaTable_Init_CastleTown() { Entrance(MARKET_BACK_ALLEY, {[]{return true;}}), }); - areaTable[MARKET_DOG_LADY_HOUSE] = Area("Market Dog Lady House", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[MARKET_DOG_LADY_HOUSE] = Area("Market Dog Lady House", "Market Dog Lady House", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(MARKET_LOST_DOG, {[]{return IsChild && AtNight;}}), }, { @@ -258,7 +258,7 @@ void AreaTable_Init_CastleTown() { Entrance(MARKET_BACK_ALLEY, {[]{return true;}}), }); - areaTable[MARKET_MAN_IN_GREEN_HOUSE] = Area("Market Man in Green House", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + areaTable[MARKET_MAN_IN_GREEN_HOUSE] = Area("Market Man in Green House", "Market Man in Green House", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits Entrance(MARKET_BACK_ALLEY, {[]{return true;}}), }); diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp index 6633c567b..aac8199ed 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_death_mountain.cpp @@ -55,10 +55,10 @@ void AreaTable_Init_DeathMountain() { areaTable[DMT_OWL_FLIGHT] = Area("DMT Owl Flight", "Death Mountain", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits - Entrance(KAK_IMPAS_LEDGE, {[]{return true;}}), + Entrance(KAK_IMPAS_ROOFTOP, {[]{return true;}}), }); - areaTable[DMT_COW_GROTTO] = Area("DMT Cow Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[DMT_COW_GROTTO] = Area("DMT Cow Grotto", "DMT Cow Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(DMT_COW_GROTTO_COW, {[]{return CanPlay(EponasSong);}, /*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::INTERMEDIATE) || ((Bugs || Fish) && CanShield && (CanSurviveDamage || (Fairy && NumBottles >= 2)) && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || @@ -69,7 +69,7 @@ void AreaTable_Init_DeathMountain() { }); - areaTable[DMT_STORMS_GROTTO] = Area("DMT Storms Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { + areaTable[DMT_STORMS_GROTTO] = Area("DMT Storms Grotto", "DMT Storms Grotto", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { //Locations LocationAccess(DMT_STORMS_GROTTO_CHEST, {[]{return true;}}), LocationAccess(DMT_STORMS_GROTTO_GOSSIP_STONE, {[]{return true;}}), @@ -78,7 +78,7 @@ void AreaTable_Init_DeathMountain() { Entrance(DEATH_MOUNTAIN_TRAIL, {[]{return true;}}), }); - areaTable[DMT_GREAT_FAIRY_FOUNTAIN] = Area("DMT Great Fairy Fountain", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[DMT_GREAT_FAIRY_FOUNTAIN] = Area("DMT Great Fairy Fountain", "DMT Great Fairy Fountain", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(DMT_GREAT_FAIRY_REWARD, {[]{return CanPlay(ZeldasLullaby);}, /*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::INTERMEDIATE) || ((Bugs || Fish) && CanShield && (CanSurviveDamage || (Fairy && NumBottles >= 2)) && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || @@ -156,7 +156,7 @@ void AreaTable_Init_DeathMountain() { Entrance(GORON_CITY, {[]{return EffectiveHealth > 2 || CanUse(GORON_TUNIC) || CanUse(NAYRUS_LOVE) || ((IsChild || CanPlay(SongOfTime)) && CanUse(LONGSHOT));}}), }); - areaTable[GC_SHOP] = Area("GC Shop", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[GC_SHOP] = Area("GC Shop", "GC Shop", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(GC_SHOP_ITEM_1, {[]{return true;}}), LocationAccess(GC_SHOP_ITEM_2, {[]{return true;}}), @@ -171,7 +171,7 @@ void AreaTable_Init_DeathMountain() { Entrance(GORON_CITY, {[]{return true;}}), }); - areaTable[GC_GROTTO] = Area("GC Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[GC_GROTTO] = Area("GC Grotto", "GC Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(GC_DEKU_SCRUB_GROTTO_LEFT, {[]{return CanStunDeku;}}), LocationAccess(GC_DEKU_SCRUB_GROTTO_RIGHT, {[]{return CanStunDeku;}}), @@ -262,7 +262,7 @@ void AreaTable_Init_DeathMountain() { /*Glitched*/[]{return CanDoGlitch(GlitchType::ASlide, GlitchDifficulty::INTERMEDIATE) && FireTimer >= 48;}}), }); - areaTable[DMC_GREAT_FAIRY_FOUNTAIN] = Area("DMC Great Fairy Fountain", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[DMC_GREAT_FAIRY_FOUNTAIN] = Area("DMC Great Fairy Fountain", "DMC Great Fairy Fountain", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(DMC_GREAT_FAIRY_REWARD, {[]{return CanPlay(ZeldasLullaby);}, /*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::INTERMEDIATE) || ((Bugs || Fish) && CanShield && (CanTakeDamage || (Fairy && NumBottles >= 2)) && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || @@ -272,7 +272,7 @@ void AreaTable_Init_DeathMountain() { Entrance(DMC_LOWER_LOCAL, {[]{return true;}}), }); - areaTable[DMC_UPPER_GROTTO] = Area("DMC Upper Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { + areaTable[DMC_UPPER_GROTTO] = Area("DMC Upper Grotto", "DMC Upper Grotto", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { //Locations LocationAccess(DMC_UPPER_GROTTO_CHEST, {[]{return true;}}), LocationAccess(DMC_UPPER_GROTTO_GOSSIP_STONE, {[]{return true;}}), @@ -281,7 +281,7 @@ void AreaTable_Init_DeathMountain() { Entrance(DMC_UPPER_LOCAL, {[]{return true;}}), }); - areaTable[DMC_HAMMER_GROTTO] = Area("DMC Hammer Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[DMC_HAMMER_GROTTO] = Area("DMC Hammer Grotto", "DMC Hammer Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(DMC_DEKU_SCRUB_GROTTO_LEFT, {[]{return CanStunDeku;}}), LocationAccess(DMC_DEKU_SCRUB_GROTTO_RIGHT, {[]{return CanStunDeku;}}), diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_gerudo_valley.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_gerudo_valley.cpp index 7f99b05ff..1617c295d 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_gerudo_valley.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_gerudo_valley.cpp @@ -84,17 +84,17 @@ void AreaTable_Init_GerudoValley() { /*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::INTERMEDIATE) || ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || ((Bugs || Fish) && HasBombchus && CanShield && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::ADVANCED))) && IsAdult && SongOfStorms && (ShardOfAgony || LogicGrottosWithoutAgony);}}), }); - areaTable[GV_CARPENTER_TENT] = Area("GV Carpenter Tent", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + areaTable[GV_CARPENTER_TENT] = Area("GV Carpenter Tent", "GV Carpenter Tent", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits Entrance(GV_FORTRESS_SIDE, {[]{return true;}}), }); - areaTable[GV_OCTOROK_GROTTO] = Area("GV Octorok Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + areaTable[GV_OCTOROK_GROTTO] = Area("GV Octorok Grotto", "GV Octorok Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits Entrance(GV_GROTTO_LEDGE, {[]{return true;}}), }); - areaTable[GV_STORMS_GROTTO] = Area("GV Storms Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[GV_STORMS_GROTTO] = Area("GV Storms Grotto", "GV Storms Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(GV_DEKU_SCRUB_GROTTO_REAR, {[]{return CanStunDeku;}}), LocationAccess(GV_DEKU_SCRUB_GROTTO_FRONT, {[]{return CanStunDeku;}}), @@ -148,7 +148,7 @@ void AreaTable_Init_GerudoValley() { Entrance(WASTELAND_NEAR_FORTRESS, {[]{return true;}}), }); - areaTable[GF_STORMS_GROTTO] = Area("GF Storms Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, { + areaTable[GF_STORMS_GROTTO] = Area("GF Storms Grotto", "GF Storms Grotto", NONE, NO_DAY_NIGHT_CYCLE, { //Events EventAccess(&FreeFairies, {[]{return true;}}), }, {}, { @@ -200,7 +200,6 @@ void AreaTable_Init_GerudoValley() { LocationAccess(COLOSSUS_FREESTANDING_POH, {[]{return IsAdult && CanPlantBean(DESERT_COLOSSUS);}, /*Glitched*/[]{return (HoverBoots && CanDoGlitch(GlitchType::HookshotJump_Boots, GlitchDifficulty::ADVANCED)) || (((IsChild && (ChildCanAccess(SPIRIT_TEMPLE_OUTDOOR_HANDS) || ChildCanAccess(SPIRIT_TEMPLE_MQ_SILVER_GAUNTLETS_HAND) || ChildCanAccess(SPIRIT_TEMPLE_MQ_MIRROR_SHIELD_HAND))) || (IsAdult && (AdultCanAccess(SPIRIT_TEMPLE_OUTDOOR_HANDS) || AdultCanAccess(SPIRIT_TEMPLE_MQ_SILVER_GAUNTLETS_HAND) || AdultCanAccess(SPIRIT_TEMPLE_MQ_MIRROR_SHIELD_HAND)))) && (CanDoGlitch(GlitchType::Megaflip, GlitchDifficulty::ADVANCED) || CanDoGlitch(GlitchType::HoverBoost, GlitchDifficulty::INTERMEDIATE)));}}), - LocationAccess(SHEIK_AT_COLOSSUS, {[]{return true;}}), LocationAccess(COLOSSUS_GS_BEAN_PATCH, {[]{return CanPlantBugs && CanChildAttack;}}), LocationAccess(COLOSSUS_GS_TREE, {[]{return IsAdult && HookshotOrBoomerang && AtNight && CanGetNightTimeGS;}}), LocationAccess(COLOSSUS_GS_HILL, {[]{return IsAdult && AtNight && (CanPlantBean(DESERT_COLOSSUS) || CanUse(LONGSHOT) || (LogicColossusGS && CanUse(HOOKSHOT))) && CanGetNightTimeGS;}, @@ -214,7 +213,15 @@ void AreaTable_Init_GerudoValley() { Entrance(COLOSSUS_GROTTO, {[]{return CanUse(SILVER_GAUNTLETS);}}), }); - areaTable[COLOSSUS_GREAT_FAIRY_FOUNTAIN] = Area("Colossus Great Fairy Fountain", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY] = Area("Desert Colossus From Spirit Entryway", "Desert Colossus", DESERT_COLOSSUS, NO_DAY_NIGHT_CYCLE, {}, { + //Locations + LocationAccess(SHEIK_AT_COLOSSUS, {[]{return true;}}), + }, { + //Exist + Entrance(DESERT_COLOSSUS, {[]{return true;}}), + }); + + areaTable[COLOSSUS_GREAT_FAIRY_FOUNTAIN] = Area("Colossus Great Fairy Fountain", "Colossus Great Fairy Fountain", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(COLOSSUS_GREAT_FAIRY_REWARD, {[]{return CanPlay(ZeldasLullaby);}, /*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::INTERMEDIATE) || ((Bugs || Fish) && CanShield && Bombs && CanTakeDamage && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || ((Bugs || Fish) && HasBombchus && CanShield && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::ADVANCED))) && ZeldasLullaby;}}), @@ -223,7 +230,7 @@ void AreaTable_Init_GerudoValley() { Entrance(DESERT_COLOSSUS, {[]{return true;}}), }); - areaTable[COLOSSUS_GROTTO] = Area("Colossus Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[COLOSSUS_GROTTO] = Area("Colossus Grotto", "Colossus Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(COLOSSUS_DEKU_SCRUB_GROTTO_REAR, {[]{return CanStunDeku;}}), LocationAccess(COLOSSUS_DEKU_SCRUB_GROTTO_FRONT, {[]{return CanStunDeku;}}), diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_hyrule_field.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_hyrule_field.cpp index c1ac29d54..75d201b4e 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_hyrule_field.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_hyrule_field.cpp @@ -39,7 +39,7 @@ void AreaTable_Init_HyruleField() { /*Glitched*/[]{return Sticks && IsChild && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED);}}), }); - areaTable[HF_SOUTHEAST_GROTTO] = Area("HF Southeast Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { + areaTable[HF_SOUTHEAST_GROTTO] = Area("HF Southeast Grotto", "HF Southeast Grotto", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { //Locations LocationAccess(HF_SOUTHEAST_GROTTO_CHEST, {[]{return true;}}), LocationAccess(HF_SOUTHEAST_GROTTO_GOSSIP_STONE, {[]{return true;}}), @@ -48,7 +48,7 @@ void AreaTable_Init_HyruleField() { Entrance(HYRULE_FIELD, {[]{return true;}}), }); - areaTable[HF_OPEN_GROTTO] = Area("HF Open Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { + areaTable[HF_OPEN_GROTTO] = Area("HF Open Grotto", "HF Open Grotto", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { //Locations LocationAccess(HF_OPEN_GROTTO_CHEST, {[]{return true;}}), LocationAccess(HF_OPEN_GROTTO_GOSSIP_STONE, {[]{return true;}}), @@ -57,7 +57,7 @@ void AreaTable_Init_HyruleField() { Entrance(HYRULE_FIELD, {[]{return true;}}), }); - areaTable[HF_INSIDE_FENCE_GROTTO] = Area("HF Inside Fence Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[HF_INSIDE_FENCE_GROTTO] = Area("HF Inside Fence Grotto", "HF Inside Fence Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(HF_DEKU_SCRUB_GROTTO, {[]{return CanStunDeku;}}), }, { @@ -65,7 +65,7 @@ void AreaTable_Init_HyruleField() { Entrance(HYRULE_FIELD, {[]{return true;}}), }); - areaTable[HF_COW_GROTTO] = Area("HF Cow Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { + areaTable[HF_COW_GROTTO] = Area("HF Cow Grotto", "HF Cow Grotto", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { //Locations LocationAccess(HF_GS_COW_GROTTO, {[]{return HasFireSource && HookshotOrBoomerang;}, /*Glitched*/[]{return (CanUse(STICKS) && Bombs && CanSurviveDamage && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::NOVICE)) && HookshotOrBoomerang;}}), @@ -79,7 +79,7 @@ void AreaTable_Init_HyruleField() { Entrance(HYRULE_FIELD, {[]{return true;}}), }); - areaTable[HF_NEAR_MARKET_GROTTO] = Area("HF Near Market Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { + areaTable[HF_NEAR_MARKET_GROTTO] = Area("HF Near Market Grotto", "HF Near Market Grotto", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { //Locations LocationAccess(HF_NEAR_MARKET_GROTTO_CHEST, {[]{return true;}}), LocationAccess(HF_NEAR_MARKET_GROTTO_GOSSIP_STONE, {[]{return true;}}), @@ -88,7 +88,7 @@ void AreaTable_Init_HyruleField() { Entrance(HYRULE_FIELD, {[]{return true;}}), }); - areaTable[HF_FAIRY_GROTTO] = Area("HF Fairy Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, { + areaTable[HF_FAIRY_GROTTO] = Area("HF Fairy Grotto", "HF Fairy Grotto", NONE, NO_DAY_NIGHT_CYCLE, { //Events EventAccess(&FreeFairies, {[]{return true;}}), }, {}, { @@ -96,7 +96,7 @@ void AreaTable_Init_HyruleField() { Entrance(HYRULE_FIELD, {[]{return true;}}), }); - areaTable[HF_NEAR_KAK_GROTTO] = Area("HF Near Kak Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[HF_NEAR_KAK_GROTTO] = Area("HF Near Kak Grotto", "HF Near Kak Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(HF_GS_NEAR_KAK_GROTTO, {[]{return HookshotOrBoomerang;}}), }, { @@ -104,7 +104,7 @@ void AreaTable_Init_HyruleField() { Entrance(HYRULE_FIELD, {[]{return true;}}), }); - areaTable[HF_TEKTITE_GROTTO] = Area("HF Tektite Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[HF_TEKTITE_GROTTO] = Area("HF Tektite Grotto", "HF Tektite Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(HF_TEKTITE_GROTTO_FREESTANDING_POH, {[]{return ProgressiveScale >= 2 || CanUse(IRON_BOOTS);}}), }, { @@ -163,7 +163,7 @@ void AreaTable_Init_HyruleField() { Entrance(HYRULE_FIELD, {[]{return true;}}), }); - areaTable[LH_LAB] = Area("LH Lab", "", NONE, NO_DAY_NIGHT_CYCLE, { + areaTable[LH_LAB] = Area("LH Lab", "LH Lab", NONE, NO_DAY_NIGHT_CYCLE, { //Events EventAccess(&EyedropsAccess, {[]{return EyedropsAccess || (IsAdult && (EyeballFrogAccess || (EyeballFrog && DisableTradeRevert)));}}), }, { @@ -182,7 +182,7 @@ void AreaTable_Init_HyruleField() { Entrance(LAKE_HYLIA, {[]{return true;}}), }); - areaTable[LH_FISHING_HOLE] = Area("LH Fishing Hole", "", NONE, DAY_NIGHT_CYCLE, {}, { + areaTable[LH_FISHING_HOLE] = Area("LH Fishing Hole", "LH Fishing Hole", NONE, DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(LH_CHILD_FISHING, {[]{return IsChild;}}), LocationAccess(LH_ADULT_FISHING, {[]{return IsAdult;}}), @@ -191,7 +191,7 @@ void AreaTable_Init_HyruleField() { Entrance(LH_FISHING_ISLAND, {[]{return true;}}), }); - areaTable[LH_GROTTO] = Area("LH Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[LH_GROTTO] = Area("LH Grotto", "LH Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(LH_DEKU_SCRUB_GROTTO_LEFT, {[]{return CanStunDeku;}}), LocationAccess(LH_DEKU_SCRUB_GROTTO_RIGHT, {[]{return CanStunDeku;}}), @@ -224,7 +224,7 @@ void AreaTable_Init_HyruleField() { Entrance(LLR_GROTTO, {[]{return IsChild;}}), }); - areaTable[LLR_TALONS_HOUSE] = Area("LLR Talons House", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[LLR_TALONS_HOUSE] = Area("LLR Talons House", "LLR Talons House", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(LLR_TALONS_CHICKENS, {[]{return IsChild && AtDay && ZeldasLetter;}}), }, { @@ -232,7 +232,7 @@ void AreaTable_Init_HyruleField() { Entrance(LON_LON_RANCH, {[]{return true;}}), }); - areaTable[LLR_STABLES] = Area("LLR Stables", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[LLR_STABLES] = Area("LLR Stables", "LLR Stables", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(LLR_STABLES_LEFT_COW, {[]{return CanPlay(EponasSong);}, /*Glitched*/[]{return (CanDoGlitch(GlitchType::IndoorBombOI, GlitchDifficulty::EXPERT) || ((Bugs || Fish) && CanShield && (CanSurviveDamage || (Fairy && NumBottles >= 2)) && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED) && CanDoGlitch(GlitchType::RestrictedItems, GlitchDifficulty::NOVICE))) && EponasSong;}}), @@ -243,7 +243,7 @@ void AreaTable_Init_HyruleField() { Entrance(LON_LON_RANCH, {[]{return true;}}), }); - areaTable[LLR_TOWER] = Area("LLR Tower", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[LLR_TOWER] = Area("LLR Tower", "LLR Tower", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(LLR_FREESTANDING_POH, {[]{return IsChild;}}), LocationAccess(LLR_TOWER_LEFT_COW, {[]{return CanPlay(EponasSong);}, @@ -255,7 +255,7 @@ void AreaTable_Init_HyruleField() { Entrance(LON_LON_RANCH, {[]{return true;}}), }); - areaTable[LLR_GROTTO] = Area("LLR Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[LLR_GROTTO] = Area("LLR Grotto", "LLR Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(LLR_DEKU_SCRUB_GROTTO_LEFT, {[]{return CanStunDeku;}}), LocationAccess(LLR_DEKU_SCRUB_GROTTO_RIGHT, {[]{return CanStunDeku;}}), diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_kakariko.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_kakariko.cpp index 87523ef3b..fbd4adff9 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_kakariko.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_kakariko.cpp @@ -23,8 +23,6 @@ void AreaTable_Init_Kakariko() { LocationAccess(KAK_GS_TREE, {[]{return IsChild && AtNight && CanGetNightTimeGS;}}), LocationAccess(KAK_GS_WATCHTOWER, {[]{return IsChild && (Slingshot || HasBombchus || CanUse(BOW) || CanUse(LONGSHOT)) && AtNight && CanGetNightTimeGS;}, /*Glitched*/[]{return IsChild && AtNight && CanGetNightTimeGS && (CanDoGlitch(GlitchType::ISG, GlitchDifficulty::NOVICE) || CanDoGlitch(GlitchType::SuperStab, GlitchDifficulty::NOVICE) || (Sticks && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::INTERMEDIATE)));}}), - LocationAccess(KAK_GS_ABOVE_IMPAS_HOUSE, {[]{return IsAdult && CanUse(HOOKSHOT) && AtNight && CanGetNightTimeGS;}, - /*Glitched*/[]{return IsAdult && AtNight && CanGetNightTimeGS && ((HoverBoots && CanDoGlitch(GlitchType::Megaflip, GlitchDifficulty::INTERMEDIATE)) || (Bombs && HasBombchus && CanDoGlitch(GlitchType::BombHover, GlitchDifficulty::INTERMEDIATE)) || CanDoGlitch(GlitchType::HoverBoost, GlitchDifficulty::INTERMEDIATE));}}), }, { //Exits Entrance(HYRULE_FIELD, {[]{return true;}}), @@ -45,6 +43,8 @@ void AreaTable_Init_Kakariko() { /*Glitched*/[]{return IsAdult && ((HoverBoots && CanDoGlitch(GlitchType::Megaflip, GlitchDifficulty::INTERMEDIATE)) || (Bombs && HasBombchus && CanDoGlitch(GlitchType::BombHover, GlitchDifficulty::INTERMEDIATE)) || CanDoGlitch(GlitchType::HoverBoost, GlitchDifficulty::INTERMEDIATE));}}), Entrance(KAK_ROOFTOP, {[]{return CanUse(HOOKSHOT) || (LogicManOnRoof && (IsAdult || AtDay || Slingshot || HasBombchus || CanUse(BOW) || CanUse(LONGSHOT)));}, /*Glitched*/[]{return LogicManOnRoof && CanDoGlitch(GlitchType::ISG, GlitchDifficulty::NOVICE);}}), + Entrance(KAK_IMPAS_ROOFTOP, {[]{return CanUse(HOOKSHOT);}, + /*Glitched*/[]{return (HoverBoots && CanDoGlitch(GlitchType::Megaflip, GlitchDifficulty::INTERMEDIATE)) || (Bombs && HasBombchus && CanDoGlitch(GlitchType::BombHover, GlitchDifficulty::INTERMEDIATE)) || CanDoGlitch(GlitchType::HoverBoost, GlitchDifficulty::INTERMEDIATE);}}), Entrance(THE_GRAVEYARD, {[]{return true;}}), Entrance(KAK_BEHIND_GATE, {[]{return IsAdult || (KakarikoVillageGateOpen);}, /*Glitched*/[]{return CanDoGlitch(GlitchType::BombHover, GlitchDifficulty::INTERMEDIATE);}}), @@ -56,6 +56,15 @@ void AreaTable_Init_Kakariko() { Entrance(KAKARIKO_VILLAGE, {[]{return true;}}), }); + areaTable[KAK_IMPAS_ROOFTOP] = Area("Kak Impas Rooftop", "Kakariko Village", KAKARIKO_VILLAGE, NO_DAY_NIGHT_CYCLE, {}, { + //Locations + LocationAccess(KAK_GS_ABOVE_IMPAS_HOUSE, {[]{return IsAdult && AtNight && CanGetNightTimeGS && (CanJumpslash || CanUseProjectile);}}), + }, { + //Exits + Entrance(KAK_IMPAS_LEDGE, {[]{return true;}}), + Entrance(KAKARIKO_VILLAGE, {[]{return true;}}), + }); + areaTable[KAK_ROOFTOP] = Area("Kak Rooftop", "Kakariko Village", KAKARIKO_VILLAGE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(KAK_MAN_ON_ROOF, {[]{return true;}}), @@ -73,7 +82,7 @@ void AreaTable_Init_Kakariko() { /*Glitched*/[]{return CanDoGlitch(GlitchType::TripleSlashClip, GlitchDifficulty::NOVICE);}}), }); - areaTable[KAK_CARPENTER_BOSS_HOUSE] = Area("Kak Carpenter Boss House", "", NONE, NO_DAY_NIGHT_CYCLE, { + areaTable[KAK_CARPENTER_BOSS_HOUSE] = Area("Kak Carpenter Boss House", "Kak Carpenter Boss House", NONE, NO_DAY_NIGHT_CYCLE, { //Events EventAccess(&WakeUpAdultTalon, {[]{return WakeUpAdultTalon || (IsAdult && PocketEgg);}}), }, {}, { @@ -81,7 +90,7 @@ void AreaTable_Init_Kakariko() { Entrance(KAKARIKO_VILLAGE, {[]{return true;}}), }); - areaTable[KAK_HOUSE_OF_SKULLTULA] = Area("Kak House of Skulltula", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[KAK_HOUSE_OF_SKULLTULA] = Area("Kak House of Skulltula", "Kak House of Skulltula", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(KAK_10_GOLD_SKULLTULA_REWARD, {[]{return GoldSkulltulaTokens >= 10;}}), LocationAccess(KAK_20_GOLD_SKULLTULA_REWARD, {[]{return GoldSkulltulaTokens >= 20;}}), @@ -93,13 +102,13 @@ void AreaTable_Init_Kakariko() { Entrance(KAKARIKO_VILLAGE, {[]{return true;}}), }); - areaTable[KAK_IMPAS_HOUSE] = Area("Kak Impas House", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + areaTable[KAK_IMPAS_HOUSE] = Area("Kak Impas House", "Kak Impas House", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits Entrance(KAK_IMPAS_HOUSE_NEAR_COW, {[]{return true;}}), Entrance(KAKARIKO_VILLAGE, {[]{return true;}}), }); - areaTable[KAK_IMPAS_HOUSE_BACK] = Area("Kak Impas House Back", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[KAK_IMPAS_HOUSE_BACK] = Area("Kak Impas House Back", "Kak Impas House", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(KAK_IMPAS_HOUSE_FREESTANDING_POH, {[]{return true;}}), }, { @@ -108,13 +117,13 @@ void AreaTable_Init_Kakariko() { Entrance(KAK_IMPAS_HOUSE_NEAR_COW, {[]{return true;}}), }); - areaTable[KAK_IMPAS_HOUSE_NEAR_COW] = Area("Kak Impas House Near Cow", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[KAK_IMPAS_HOUSE_NEAR_COW] = Area("Kak Impas House Near Cow", "Kak Impas House", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(KAK_IMPAS_HOUSE_COW, {[]{return CanPlay(EponasSong);}, /*Glitched*/[]{return (CanDoGlitch(GlitchType::IndoorBombOI, GlitchDifficulty::ADVANCED) || ((Bugs || Fish) && CanShield && (CanSurviveDamage || (Fairy && NumBottles >= 2)) && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED))) && EponasSong;}}), }, {}); - areaTable[KAK_WINDMILL] = Area("Kak Windmill", "", NONE, NO_DAY_NIGHT_CYCLE, { + areaTable[KAK_WINDMILL] = Area("Kak Windmill", "Windmill and Dampes Grave", NONE, NO_DAY_NIGHT_CYCLE, { //Events EventAccess(&DrainWell, {[]{return DrainWell || (IsChild && CanPlay(SongOfStorms));}, /*Glitched*/[]{return IsChild && SongOfStorms && (CanDoGlitch(GlitchType::WindmillBombOI, GlitchDifficulty::ADVANCED) || ((Fish || Bugs) && CanShield && ((Bombs && (CanSurviveDamage || (Fairy && NumBottles >= 2))) || CanDoGlitch(GlitchType::BombHover, GlitchDifficulty::INTERMEDIATE)) && @@ -131,7 +140,7 @@ void AreaTable_Init_Kakariko() { Entrance(KAKARIKO_VILLAGE, {[]{return true;}}), }); - areaTable[KAK_BAZAAR] = Area("Kak Bazaar", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[KAK_BAZAAR] = Area("Kak Bazaar", "Kak Bazaar", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(KAK_BAZAAR_ITEM_1, {[]{return true;}}), LocationAccess(KAK_BAZAAR_ITEM_2, {[]{return true;}}), @@ -146,7 +155,7 @@ void AreaTable_Init_Kakariko() { Entrance(KAKARIKO_VILLAGE, {[]{return true;}}), }); - areaTable[KAK_SHOOTING_GALLERY] = Area("Kak Shooting Gallery", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[KAK_SHOOTING_GALLERY] = Area("Kak Shooting Gallery", "Kak Shooting Gallery", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(KAK_SHOOTING_GALLERY_REWARD, {[]{return IsAdult && Bow;}}), }, { @@ -154,7 +163,7 @@ void AreaTable_Init_Kakariko() { Entrance(KAKARIKO_VILLAGE, {[]{return true;}}), }); - areaTable[KAK_POTION_SHOP_FRONT] = Area("Kak Potion Shop Front", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[KAK_POTION_SHOP_FRONT] = Area("Kak Potion Shop Front", "Kak Potion Shop", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(KAK_POTION_SHOP_ITEM_1, {[]{return IsAdult;}}), LocationAccess(KAK_POTION_SHOP_ITEM_2, {[]{return IsAdult;}}), @@ -170,13 +179,13 @@ void AreaTable_Init_Kakariko() { Entrance(KAK_POTION_SHOP_BACK, {[]{return IsAdult;}}), }); - areaTable[KAK_POTION_SHOP_BACK] = Area("Kak Potion Shop Back", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + areaTable[KAK_POTION_SHOP_BACK] = Area("Kak Potion Shop Back", "Kak Potion Shop", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits Entrance(KAK_BACKYARD, {[]{return IsAdult;}}), Entrance(KAK_POTION_SHOP_FRONT, {[]{return true;}}), }); - areaTable[KAK_ODD_POTION_BUILDING] = Area("Kak Granny's Potion Shop", "", NONE, NO_DAY_NIGHT_CYCLE, { + areaTable[KAK_ODD_POTION_BUILDING] = Area("Kak Granny's Potion Shop", "Kak Granny's Potion Shop", NONE, NO_DAY_NIGHT_CYCLE, { //Events EventAccess(&OddPoulticeAccess, {[]{return OddPoulticeAccess || (IsAdult && (OddMushroomAccess || (OddMushroom && DisableTradeRevert)));}}), }, { @@ -186,7 +195,7 @@ void AreaTable_Init_Kakariko() { Entrance(KAK_BACKYARD, {[]{return true;}}), }); - areaTable[KAK_REDEAD_GROTTO] = Area("Kak Redead Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[KAK_REDEAD_GROTTO] = Area("Kak Redead Grotto", "Kak Redead Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(KAK_REDEAD_GROTTO_CHEST, {[]{return IsAdult || (Sticks || KokiriSword || CanUse(DINS_FIRE) || CanUse(MEGATON_HAMMER) || CanUse(MASTER_SWORD) || CanUse(BIGGORON_SWORD));}}), }, { @@ -194,7 +203,7 @@ void AreaTable_Init_Kakariko() { Entrance(KAKARIKO_VILLAGE, {[]{return true;}}), }); - areaTable[KAK_OPEN_GROTTO] = Area("Kak Open Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { + areaTable[KAK_OPEN_GROTTO] = Area("Kak Open Grotto", "Kak Open Grotto", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { //Locations LocationAccess(KAK_OPEN_GROTTO_CHEST, {[]{return true;}}), LocationAccess(KAK_OPEN_GROTTO_GOSSIP_STONE, {[]{return true;}}), @@ -230,7 +239,7 @@ void AreaTable_Init_Kakariko() { /*Glitched*/[]{return CanDoGlitch(GlitchType::BombHover, GlitchDifficulty::NOVICE) || CanDoGlitch(GlitchType::HookshotJump_Bonk, GlitchDifficulty::INTERMEDIATE) || CanDoGlitch(GlitchType::HookshotJump_Boots, GlitchDifficulty::NOVICE);}}), }); - areaTable[GRAVEYARD_SHIELD_GRAVE] = Area("Graveyard Shield Grave", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[GRAVEYARD_SHIELD_GRAVE] = Area("Graveyard Shield Grave", "Graveyard Shield Grave", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(GRAVEYARD_SHIELD_GRAVE_CHEST, {[]{return true;}}), //Free Fairies @@ -239,7 +248,7 @@ void AreaTable_Init_Kakariko() { Entrance(THE_GRAVEYARD, {[]{return true;}}), }); - areaTable[GRAVEYARD_HEART_PIECE_GRAVE] = Area("Graveyard Heart Piece Grave", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[GRAVEYARD_HEART_PIECE_GRAVE] = Area("Graveyard Heart Piece Grave", "Graveyard Heart Piece Grave", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(GRAVEYARD_HEART_PIECE_GRAVE_CHEST, {[]{return CanPlay(SunsSong);}, /*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::NOVICE) || ((Bugs || Fish) && CanShield && (Bombs && (CanSurviveDamage || (Fairy && NumBottles >= 2))) && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || ((Bugs || Fish) && CanShield && HasBombchus && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::ADVANCED))) && SunsSong;}}), @@ -248,7 +257,7 @@ void AreaTable_Init_Kakariko() { Entrance(THE_GRAVEYARD, {[]{return true;}}), }); - areaTable[GRAVEYARD_COMPOSERS_GRAVE] = Area("Graveyard Composers Grave", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[GRAVEYARD_COMPOSERS_GRAVE] = Area("Graveyard Composers Grave", "Graveyard Composers Grave", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(GRAVEYARD_COMPOSERS_GRAVE_CHEST, {[]{return HasFireSource;}, /*Glitched*/[]{return CanUse(STICKS) && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::INTERMEDIATE);}}), @@ -258,7 +267,7 @@ void AreaTable_Init_Kakariko() { Entrance(THE_GRAVEYARD, {[]{return true;}}), }); - areaTable[GRAVEYARD_DAMPES_GRAVE] = Area("Graveyard Dampes Grave", "", NONE, NO_DAY_NIGHT_CYCLE, { + areaTable[GRAVEYARD_DAMPES_GRAVE] = Area("Graveyard Dampes Grave", "Windmill and Dampes Grave", NONE, NO_DAY_NIGHT_CYCLE, { //Events EventAccess(&NutPot, {[]{return true;}}), EventAccess(&DampesWindmillAccess, {[]{return DampesWindmillAccess || (IsAdult && CanPlay(SongOfTime));}, @@ -276,7 +285,7 @@ void AreaTable_Init_Kakariko() { ((Bugs || Fish) && CanShield && HasBombchus && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::ADVANCED))) && IsAdult && SongOfTime;}}), }); - areaTable[GRAVEYARD_DAMPES_HOUSE] = Area("Graveyard Dampes House", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + areaTable[GRAVEYARD_DAMPES_HOUSE] = Area("Graveyard Dampes House", "Graveyard Dampes House", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits Entrance(THE_GRAVEYARD, {[]{return true;}}), }); diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_lost_woods.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_lost_woods.cpp index fa68b8012..47d4570fc 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_lost_woods.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_lost_woods.cpp @@ -53,7 +53,7 @@ void AreaTable_Init_LostWoods() { /*Glitched*/[]{return CanDoGlitch(GlitchType::ASlide, GlitchDifficulty::INTERMEDIATE) || CanDoGlitch(GlitchType::BombHover, GlitchDifficulty::INTERMEDIATE);}}), }); - areaTable[KF_LINKS_HOUSE] = Area("KF Link's House", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[KF_LINKS_HOUSE] = Area("KF Link's House", "KF Link's House", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(KF_LINKS_HOUSE_COW, {[]{return IsAdult && CanPlay(EponasSong) && LinksCow;}, /*Glitched*/[]{return (CanDoGlitch(GlitchType::IndoorBombOI, GlitchDifficulty::EXPERT) || ((Bugs || Fish) && CanShield && (CanSurviveDamage || (NumBottles >= 2 && Fairy)) && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::EXPERT))) && CanDoGlitch(GlitchType::RestrictedItems, GlitchDifficulty::NOVICE) && Bombs && IsAdult && EponasSong && LinksCow;}}), @@ -62,7 +62,7 @@ void AreaTable_Init_LostWoods() { Entrance(KOKIRI_FOREST, {[]{return true;}}) }); - areaTable[KF_MIDOS_HOUSE] = Area("KF Mido's House", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[KF_MIDOS_HOUSE] = Area("KF Mido's House", "KF Mido's House", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(KF_MIDOS_TOP_LEFT_CHEST, {[]{return true;}}), LocationAccess(KF_MIDOS_TOP_RIGHT_CHEST, {[]{return true;}}), @@ -73,22 +73,22 @@ void AreaTable_Init_LostWoods() { Entrance(KOKIRI_FOREST, {[]{return true;}}), }); - areaTable[KF_SARIAS_HOUSE] = Area("KF Saria's House", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + areaTable[KF_SARIAS_HOUSE] = Area("KF Saria's House", "KF Saria's House", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits Entrance(KOKIRI_FOREST, {[]{return true;}}), }); - areaTable[KF_HOUSE_OF_TWINS] = Area("KF House of Twins", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + areaTable[KF_HOUSE_OF_TWINS] = Area("KF House of Twins", "KF House of Twins", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits Entrance(KOKIRI_FOREST, {[]{return true;}}), }); - areaTable[KF_KNOW_IT_ALL_HOUSE] = Area("KF Know It All House", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { + areaTable[KF_KNOW_IT_ALL_HOUSE] = Area("KF Know It All House", "KF Know It All House", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits Entrance(KOKIRI_FOREST, {[]{return true;}}), }); - areaTable[KF_KOKIRI_SHOP] = Area("KF Kokiri Shop", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[KF_KOKIRI_SHOP] = Area("KF Kokiri Shop", "KF Kokiri Shop", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(KF_SHOP_ITEM_1, {[]{return true;}}), LocationAccess(KF_SHOP_ITEM_2, {[]{return true;}}), @@ -103,7 +103,7 @@ void AreaTable_Init_LostWoods() { Entrance(KOKIRI_FOREST, {[]{return true;}}), }); - areaTable[KF_STORMS_GROTTO] = Area("KF Storms Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { + areaTable[KF_STORMS_GROTTO] = Area("KF Storms Grotto", "KF Storms Grotto", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { //Locations LocationAccess(KF_STORMS_GROTTO_CHEST, {[]{return true;}}), LocationAccess(KF_STORMS_GROTTO_GOSSIP_STONE, {[]{return true;}}), @@ -177,7 +177,7 @@ void AreaTable_Init_LostWoods() { /*Glitched*/[]{return Here(LW_BEYOND_MIDO, []{return IsChild && CanUse(STICKS) && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED);});}}), }); - areaTable[LW_NEAR_SHORTCUTS_GROTTO] = Area("LW Near Shortcuts Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { + areaTable[LW_NEAR_SHORTCUTS_GROTTO] = Area("LW Near Shortcuts Grotto", "LW Near Shortcuts Grotto", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { //Locations LocationAccess(LW_NEAR_SHORTCUTS_GROTTO_CHEST, {[]{return true;}}), LocationAccess(LW_NEAR_SHORTCUTS_GROTTO_GOSSIP_STONE, {[]{return true;}}), @@ -186,7 +186,7 @@ void AreaTable_Init_LostWoods() { Entrance(THE_LOST_WOODS, {[]{return true;}}), }); - areaTable[DEKU_THEATER] = Area("Deku Theater", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[DEKU_THEATER] = Area("Deku Theater", "Deku Theater", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(DEKU_THEATER_SKULL_MASK, {[]{return IsChild && SkullMask;}}), LocationAccess(DEKU_THEATER_MASK_OF_TRUTH, {[]{return IsChild && MaskOfTruth;}}), @@ -195,7 +195,7 @@ void AreaTable_Init_LostWoods() { Entrance(LW_BEYOND_MIDO, {[]{return true;}}), }); - areaTable[LW_SCRUBS_GROTTO] = Area("LW Scrubs Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[LW_SCRUBS_GROTTO] = Area("LW Scrubs Grotto", "LW Scrubs Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(LW_DEKU_SCRUB_GROTTO_REAR, {[]{return CanStunDeku;}}), LocationAccess(LW_DEKU_SCRUB_GROTTO_FRONT, {[]{return CanStunDeku;}}), @@ -232,7 +232,7 @@ void AreaTable_Init_LostWoods() { /*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::INTERMEDIATE) || ((Bugs || Fish) && CanShield && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || ((Bugs || Fish) && HasBombchus && CanShield && CanDoGlitch(GlitchType::ActionSwap, GlitchDifficulty::ADVANCED))) && SongOfStorms && (ShardOfAgony || LogicGrottosWithoutAgony);}}), }); - areaTable[SFM_FAIRY_GROTTO] = Area("SFM Fairy Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, { + areaTable[SFM_FAIRY_GROTTO] = Area("SFM Fairy Grotto", "SFM Fairy Grotto", NONE, NO_DAY_NIGHT_CYCLE, { //Events EventAccess(&FreeFairies, {[]{return true;}}), }, {}, { @@ -240,7 +240,7 @@ void AreaTable_Init_LostWoods() { Entrance(SACRED_FOREST_MEADOW, {[]{return true;}}), }); - areaTable[SFM_WOLFOS_GROTTO] = Area("SFM Wolfos Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[SFM_WOLFOS_GROTTO] = Area("SFM Wolfos Grotto", "SFM Wolfos Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(SFM_WOLFOS_GROTTO_CHEST, {[]{return IsAdult || Slingshot || Sticks || KokiriSword || CanUse(DINS_FIRE) || CanUse(MEGATON_HAMMER) || CanUse(MASTER_SWORD) || CanUse(BIGGORON_SWORD);}}), }, { @@ -248,7 +248,7 @@ void AreaTable_Init_LostWoods() { Entrance(SFM_ENTRYWAY, {[]{return true;}}), }); - areaTable[SFM_STORMS_GROTTO] = Area("SFM Storms Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[SFM_STORMS_GROTTO] = Area("SFM Storms Grotto", "SFM Storms Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(SFM_DEKU_SCRUB_GROTTO_REAR, {[]{return CanStunDeku;}}), LocationAccess(SFM_DEKU_SCRUB_GROTTO_FRONT, {[]{return CanStunDeku;}}), diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_spirit_temple.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_spirit_temple.cpp index 21668ae95..cfe99555b 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_spirit_temple.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_spirit_temple.cpp @@ -12,9 +12,9 @@ void AreaTable_Init_SpiritTemple() { ---------------------------*/ areaTable[SPIRIT_TEMPLE_ENTRYWAY] = Area("Spirit Temple Entryway", "Spirit Temple", SPIRIT_TEMPLE, NO_DAY_NIGHT_CYCLE, {}, {}, { //Exits - Entrance(SPIRIT_TEMPLE_LOBBY, {[]{return Dungeon::SpiritTemple.IsVanilla();}}), - Entrance(SPIRIT_TEMPLE_MQ_LOBBY, {[]{return Dungeon::SpiritTemple.IsMQ();}}), - Entrance(DESERT_COLOSSUS, {[]{return true;}}), + Entrance(SPIRIT_TEMPLE_LOBBY, {[]{return Dungeon::SpiritTemple.IsVanilla();}}), + Entrance(SPIRIT_TEMPLE_MQ_LOBBY, {[]{return Dungeon::SpiritTemple.IsMQ();}}), + Entrance(DESERT_COLOSSUS_FROM_SPIRIT_ENTRYWAY, {[]{return true;}}), }); /*-------------------------- diff --git a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_zoras_domain.cpp b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_zoras_domain.cpp index 3b257e16a..df78d7164 100644 --- a/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_zoras_domain.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/location_access/locacc_zoras_domain.cpp @@ -76,7 +76,7 @@ void AreaTable_Init_ZorasDomain() { Entrance(ZORAS_DOMAIN, {[]{return true;}}), }); - areaTable[ZR_OPEN_GROTTO] = Area("ZR Open Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { + areaTable[ZR_OPEN_GROTTO] = Area("ZR Open Grotto", "ZR Open Grotto", NONE, NO_DAY_NIGHT_CYCLE, grottoEvents, { //Locations LocationAccess(ZR_OPEN_GROTTO_CHEST, {[]{return true;}}), LocationAccess(ZR_OPEN_GROTTO_GOSSIP_STONE, {[]{return true;}}), @@ -85,7 +85,7 @@ void AreaTable_Init_ZorasDomain() { Entrance(ZORAS_RIVER, {[]{return true;}}), }); - areaTable[ZR_FAIRY_GROTTO] = Area("ZR Fairy Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, { + areaTable[ZR_FAIRY_GROTTO] = Area("ZR Fairy Grotto", "ZR Fairy Grotto", NONE, NO_DAY_NIGHT_CYCLE, { //Event EventAccess(&FreeFairies, {[]{return true;}}), }, {}, { @@ -93,7 +93,7 @@ void AreaTable_Init_ZorasDomain() { Entrance(ZORAS_RIVER, {[]{return true;}}), }); - areaTable[ZR_STORMS_GROTTO] = Area("ZR Storms Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[ZR_STORMS_GROTTO] = Area("ZR Storms Grotto", "ZR Storms Grotto", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(ZR_DEKU_SCRUB_GROTTO_REAR, {[]{return CanStunDeku;}}), LocationAccess(ZR_DEKU_SCRUB_GROTTO_FRONT, {[]{return CanStunDeku;}}), @@ -142,7 +142,7 @@ void AreaTable_Init_ZorasDomain() { Entrance(ZORAS_FOUNTAIN, {[]{return true;}}), }); - areaTable[ZD_SHOP] = Area("ZD Shop", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[ZD_SHOP] = Area("ZD Shop", "ZD Shop", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(ZD_SHOP_ITEM_1, {[]{return true;}}), LocationAccess(ZD_SHOP_ITEM_2, {[]{return true;}}), @@ -157,7 +157,7 @@ void AreaTable_Init_ZorasDomain() { Entrance(ZORAS_DOMAIN, {[]{return true;}}), }); - areaTable[ZD_STORMS_GROTTO] = Area("ZD Storms Grotto", "", NONE, NO_DAY_NIGHT_CYCLE, { + areaTable[ZD_STORMS_GROTTO] = Area("ZD Storms Grotto", "ZD Storms Grotto", NONE, NO_DAY_NIGHT_CYCLE, { //Events EventAccess(&FreeFairies, {[]{return true;}}), }, {}, { @@ -191,7 +191,7 @@ void AreaTable_Init_ZorasDomain() { /*Glitched*/[]{return IsChild && (KokiriSword || Sticks) && CanShield && (CanDoGlitch(GlitchType::SeamWalk, GlitchDifficulty::ADVANCED) || (CanDoGlitch(GlitchType::ISG, GlitchDifficulty::NOVICE) && CanDoGlitch(GlitchType::SeamWalk, GlitchDifficulty::INTERMEDIATE)));}}), }); - areaTable[ZF_GREAT_FAIRY_FOUNTAIN] = Area("ZF Great Fairy Fountain", "", NONE, NO_DAY_NIGHT_CYCLE, {}, { + areaTable[ZF_GREAT_FAIRY_FOUNTAIN] = Area("ZF Great Fairy Fountain", "ZF Great Fairy Fountain", NONE, NO_DAY_NIGHT_CYCLE, {}, { //Locations LocationAccess(ZF_GREAT_FAIRY_REWARD, {[]{return CanPlay(ZeldasLullaby);}, /*Glitched*/[]{return (CanDoGlitch(GlitchType::OutdoorBombOI, GlitchDifficulty::INTERMEDIATE) || ((Bugs || Fish) && CanShield && (CanSurviveDamage || (Fairy && NumBottles >= 2)) && CanDoGlitch(GlitchType::QPA, GlitchDifficulty::ADVANCED)) || diff --git a/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.cpp b/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.cpp index e3bafb31f..bcadae7ed 100644 --- a/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.cpp @@ -194,6 +194,52 @@ string_view grottoEntrancesDesc = "Shuffle the pool of grotto entrances, i "all graves, small Fairy Fountains and the Lost\n" // "Woods Stage."; // /*------------------------------ // +| OWL DROPS | // +------------------------------*/ // +string_view owlDropsDesc = "Randomize where Kaepora Gaebora (the Owl) drops\n"// + "you at when you talk to him at Lake Hylia or at\n"// + "the top of Death Mountain Trail."; // +/*------------------------------ // +| WARP SONGS | // +------------------------------*/ // +string_view warpSongsDesc = "Randomize where each of the 6 warp songs leads to."; + // +/*------------------------------ // +| OVERWORLD SPAWNS | // +------------------------------*/ // +string_view overworldSpawnsDesc = "Randomize where you start as Child or Adult when\n" + "loading a save in the Overworld. This means you\n"// + "may not necessarily spawn inside Link's House or\n" + "Temple of Time.\n" // + "\n" // + "This stays consistent after saving and loading the" + "game again."; // +/*------------------------------ // +| MIXED ENTRANCE POOLS | // +------------------------------*/ // +string_view mixedPoolsDesc = "Shuffle entrances into a mixed pool instead of\n" // + "separate ones. For example, enabling the settings\n" + "to shuffle grotto, dungeon, and overworld\n" // + "entrances and selecting grotto and dungeon\n" // + "entrances here will allow a dungeon to be inside a" + "grotto or vice versa, while overworld entrances\n"// + "are shuffled in their own separate pool and\n" // + "indoors stay vanilla."; // +string_view mixDungeonsDesc = "Dungeon entrances will be part of the mixed pool."; +string_view mixOverworldDesc = "Overworld entrances will be part of the mixed\n" // + "pool."; // +string_view mixInteriorsDesc = "Interior entrances will be part of the mixed pool."; +string_view mixGrottosDesc = "Grotto entrances will be part of the mixed pool.";// +/*------------------------------ // +| DECOUPLED ENTRANCES | // +------------------------------*/ // +string_view decoupledEntrancesDesc = "Decouple entrances when shuffling them. This means" + "you are no longer guaranteed to end up back where " + "you came from when you go back through an\n" // + "entrance. This also adds the one-way entrance from" + "Gerudo Valley to Lake Hylia in the pool of\n" // + "overworld entrances when they are shuffled."; // +/*------------------------------ // | BOMBCHUS IN LOGIC | // ------------------------------*/ // string_view bombchuLogicDesc = "Bombchus are properly considered in logic.\n" // diff --git a/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.hpp b/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.hpp index 6b9df1712..4594cc3a6 100644 --- a/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/setting_descriptions.hpp @@ -61,6 +61,20 @@ extern string_view overworldEntrancesDesc; extern string_view grottoEntrancesDesc; +extern string_view owlDropsDesc; + +extern string_view warpSongsDesc; + +extern string_view overworldSpawnsDesc; + +extern string_view mixedPoolsDesc; +extern string_view mixDungeonsDesc; +extern string_view mixOverworldDesc; +extern string_view mixInteriorsDesc; +extern string_view mixGrottosDesc; + +extern string_view decoupledEntrancesDesc; + extern string_view interiorEntrancesOff; extern string_view interiorEntrancesSimple; extern string_view interiorEntrancesAll; diff --git a/soh/soh/Enhancements/randomizer/3drando/settings.cpp b/soh/soh/Enhancements/randomizer/3drando/settings.cpp index d6cb80c79..d0615e124 100644 --- a/soh/soh/Enhancements/randomizer/3drando/settings.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/settings.cpp @@ -88,10 +88,19 @@ namespace Settings { Option StartingAge = Option::U8 ("Starting Age", {"Child", "Adult", "Random"}, {ageDesc}, OptionCategory::Setting, AGE_CHILD); uint8_t ResolvedStartingAge; Option ShuffleEntrances = Option::Bool("Shuffle Entrances", {"Off", "On"}, {shuffleEntrancesDesc}); - Option ShuffleDungeonEntrances = Option::U8 ("Dungeon Entrances", {"Off", "On", "On + Ganon"}, {dungeonEntrancesDesc}); - Option ShuffleOverworldEntrances = Option::Bool("Overworld Entrances", {"Off", "On"}, {overworldEntrancesDesc}); - Option ShuffleInteriorEntrances = Option::U8 ("Interior Entrances", {"Off", "Simple", "All"}, {interiorEntrancesOff, interiorEntrancesSimple, interiorEntrancesAll}); - Option ShuffleGrottoEntrances = Option::Bool("Grottos Entrances", {"Off", "On"}, {grottoEntrancesDesc}); + Option ShuffleDungeonEntrances = Option::U8 ("Dungeon Entrances", {"Off", "On", "On + Ganon"}, {dungeonEntrancesDesc}); + Option ShuffleOverworldEntrances = Option::Bool("Overworld Entrances", {"Off", "On"}, {overworldEntrancesDesc}); + Option ShuffleInteriorEntrances = Option::U8 ("Interior Entrances", {"Off", "Simple", "All"}, {interiorEntrancesOff, interiorEntrancesSimple, interiorEntrancesAll}); + Option ShuffleGrottoEntrances = Option::Bool("Grottos Entrances", {"Off", "On"}, {grottoEntrancesDesc}); + Option ShuffleOwlDrops = Option::Bool("Owl Drops", {"Off", "On"}, {owlDropsDesc}); + Option ShuffleWarpSongs = Option::Bool("Warp Songs", {"Off", "On"}, {warpSongsDesc}); + Option ShuffleOverworldSpawns = Option::Bool("Overworld Spawns", {"Off", "On"}, {overworldSpawnsDesc}); + Option MixedEntrancePools = Option::Bool("Mixed Entrance Pools", {"Off", "On"}, {mixedPoolsDesc}); + Option MixDungeons = Option::Bool("Mix Dungeons", {"Off", "On"}, {mixDungeonsDesc}); + Option MixOverworld = Option::Bool("Mix Overworld", {"Off", "On"}, {mixOverworldDesc}); + Option MixInteriors = Option::Bool("Mix Interiors", {"Off", "On"}, {mixInteriorsDesc}); + Option MixGrottos = Option::Bool("Mix Grottos", {"Off", "On"}, {mixGrottosDesc}); + Option DecoupleEntrances = Option::Bool("Decouple Entrances", {"Off", "On"}, {decoupledEntrancesDesc}); Option BombchusInLogic = Option::Bool("Bombchus in Logic", {"Off", "On"}, {bombchuLogicDesc}); Option AmmoDrops = Option::U8 ("Ammo Drops", {"On", "On + Bombchu", "Off"}, {defaultAmmoDropsDesc, bombchuDropsDesc, noAmmoDropsDesc}, OptionCategory::Setting, AMMODROPS_BOMBCHU); Option HeartDropRefill = Option::U8 ("Heart Drops and Refills",{"On", "No Drop", "No Refill", "Off"}, {defaultHeartDropsDesc, noHeartDropsDesc, noHeartRefillDesc, scarceHeartsDesc}, OptionCategory::Setting, HEARTDROPREFILL_VANILLA); @@ -119,6 +128,15 @@ namespace Settings { &ShuffleOverworldEntrances, &ShuffleInteriorEntrances, &ShuffleGrottoEntrances, + &ShuffleOwlDrops, + &ShuffleWarpSongs, + &ShuffleOverworldSpawns, + &MixedEntrancePools, + &MixDungeons, + &MixOverworld, + &MixInteriors, + &MixGrottos, + &DecoupleEntrances, &BombchusInLogic, &AmmoDrops, &HeartDropRefill, @@ -1256,6 +1274,15 @@ namespace Settings { ctx.shuffleOverworldEntrances = (ShuffleOverworldEntrances) ? 1 : 0; ctx.shuffleInteriorEntrances = ShuffleInteriorEntrances.Value(); ctx.shuffleGrottoEntrances = (ShuffleGrottoEntrances) ? 1 : 0; + ctx.shuffleOwlDrops = (ShuffleOwlDrops) ? 1 : 0; + ctx.shuffleWarpSongs = (ShuffleWarpSongs) ? 1 : 0; + ctx.shuffleOverworldSpawns = (ShuffleOverworldSpawns) ? 1 : 0; + ctx.mixedEntrancePools = (MixedEntrancePools) ? 1 : 0; + ctx.mixDungeons = (MixDungeons) ? 1 : 0; + ctx.mixOverworld = (MixOverworld) ? 1 : 0; + ctx.mixInteriors = (MixInteriors) ? 1 : 0; + ctx.mixGrottos = (MixGrottos) ? 1 : 0; + ctx.decoupleEntrances = (DecoupleEntrances) ? 1 : 0; ctx.bombchusInLogic = (BombchusInLogic) ? 1 : 0; ctx.ammoDrops = AmmoDrops.Value(); ctx.heartDropRefill = HeartDropRefill.Value(); @@ -1929,6 +1956,11 @@ namespace Settings { ShuffleOverworldEntrances.Unhide(); ShuffleInteriorEntrances.Unhide(); ShuffleGrottoEntrances.Unhide(); + ShuffleOwlDrops.Unhide(); + ShuffleWarpSongs.Unhide(); + ShuffleOverworldSpawns.Unhide(); + MixedEntrancePools.Unhide(); + DecoupleEntrances.Unhide(); } else { ShuffleDungeonEntrances.SetSelectedIndex(SHUFFLEDUNGEONS_OFF); ShuffleDungeonEntrances.Hide(); @@ -1938,6 +1970,56 @@ namespace Settings { ShuffleInteriorEntrances.Hide(); ShuffleGrottoEntrances.SetSelectedIndex(OFF); ShuffleGrottoEntrances.Hide(); + ShuffleOwlDrops.SetSelectedIndex(OFF); + ShuffleOwlDrops.Hide(); + ShuffleWarpSongs.SetSelectedIndex(OFF); + ShuffleWarpSongs.Hide(); + ShuffleOverworldSpawns.SetSelectedIndex(OFF); + ShuffleOverworldSpawns.Hide(); + MixedEntrancePools.SetSelectedIndex(OFF); + MixedEntrancePools.Hide(); + DecoupleEntrances.SetSelectedIndex(OFF); + DecoupleEntrances.Hide(); + } + + // Only show the options for mixing each pool if they're already being shuffled + if (MixedEntrancePools) { + if (ShuffleDungeonEntrances) { + MixDungeons.Unhide(); + } else { + MixDungeons.Hide(); + MixDungeons.SetSelectedIndex(OFF); + } + + if (ShuffleOverworldEntrances) { + MixOverworld.Unhide(); + } else { + MixOverworld.Hide(); + MixOverworld.SetSelectedIndex(OFF); + } + + if (ShuffleInteriorEntrances.IsNot(OFF)) { + MixInteriors.Unhide(); + } else { + MixInteriors.Hide(); + MixInteriors.SetSelectedIndex(OFF); + } + + if (ShuffleGrottoEntrances) { + MixGrottos.Unhide(); + } else { + MixGrottos.Hide(); + MixGrottos.SetSelectedIndex(OFF); + } + } else { + MixDungeons.Hide(); + MixDungeons.SetSelectedIndex(OFF); + MixOverworld.Hide(); + MixOverworld.SetSelectedIndex(OFF); + MixInteriors.Hide(); + MixInteriors.SetSelectedIndex(OFF); + MixGrottos.Hide(); + MixGrottos.SetSelectedIndex(OFF); } } @@ -2365,6 +2447,18 @@ namespace Settings { ShuffleOverworldEntrances.SetSelectedIndex(OFF); ShuffleInteriorEntrances.SetSelectedIndex(OFF); ShuffleGrottoEntrances.SetSelectedIndex(OFF); + ShuffleOwlDrops.SetSelectedIndex(OFF); + ShuffleWarpSongs.SetSelectedIndex(OFF); + ShuffleOverworldSpawns.SetSelectedIndex(OFF); + MixedEntrancePools.SetSelectedIndex(OFF); + DecoupleEntrances.SetSelectedIndex(OFF); + } + + if (!MixedEntrancePools) { + MixDungeons.SetSelectedIndex(OFF); + MixOverworld.SetSelectedIndex(OFF); + MixInteriors.SetSelectedIndex(OFF); + MixGrottos.SetSelectedIndex(OFF); } // Shuffle Settings @@ -2569,6 +2663,15 @@ namespace Settings { ShuffleOverworldEntrances.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_OVERWORLD_ENTRANCES]); ShuffleInteriorEntrances.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_INTERIOR_ENTRANCES]); ShuffleGrottoEntrances.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_GROTTO_ENTRANCES]); + ShuffleOwlDrops.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_OWL_DROPS]); + ShuffleWarpSongs.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_WARP_SONGS]); + ShuffleOverworldSpawns.SetSelectedIndex(cvarSettings[RSK_SHUFFLE_OVERWORLD_SPAWNS]); + MixedEntrancePools.SetSelectedIndex(cvarSettings[RSK_MIXED_ENTRANCE_POOLS]); + MixDungeons.SetSelectedIndex(cvarSettings[RSK_MIX_DUNGEON_ENTRANCES]); + MixOverworld.SetSelectedIndex(cvarSettings[RSK_MIX_OVERWORLD_ENTRANCES]); + MixInteriors.SetSelectedIndex(cvarSettings[RSK_MIX_INTERIOR_ENTRANCES]); + MixGrottos.SetSelectedIndex(cvarSettings[RSK_MIX_GROTTO_ENTRANCES]); + DecoupleEntrances.SetSelectedIndex(cvarSettings[RSK_DECOUPLED_ENTRANCES]); // if we skip child zelda, we start with zelda's letter, and malon starts // at the ranch, so we should *not* shuffle the weird egg diff --git a/soh/soh/Enhancements/randomizer/3drando/settings.hpp b/soh/soh/Enhancements/randomizer/3drando/settings.hpp index bb89f5a35..ae5d58723 100644 --- a/soh/soh/Enhancements/randomizer/3drando/settings.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/settings.hpp @@ -390,6 +390,15 @@ typedef struct { uint8_t shuffleOverworldEntrances; uint8_t shuffleInteriorEntrances; uint8_t shuffleGrottoEntrances; + uint8_t shuffleOwlDrops; + uint8_t shuffleWarpSongs; + uint8_t shuffleOverworldSpawns; + uint8_t mixedEntrancePools; + uint8_t mixDungeons; + uint8_t mixOverworld; + uint8_t mixInteriors; + uint8_t mixGrottos; + uint8_t decoupleEntrances; uint8_t bombchusInLogic; uint8_t ammoDrops; uint8_t heartDropRefill; @@ -897,6 +906,15 @@ void UpdateSettings(std::unordered_map cvarSettin extern Option ShuffleOverworldEntrances; extern Option ShuffleInteriorEntrances; extern Option ShuffleGrottoEntrances; + extern Option ShuffleOwlDrops; + extern Option ShuffleWarpSongs; + extern Option ShuffleOverworldSpawns; + extern Option MixedEntrancePools; + extern Option MixDungeons; + extern Option MixOverworld; + extern Option MixInteriors; + extern Option MixGrottos; + extern Option DecoupleEntrances; extern Option BombchusInLogic; extern Option AmmoDrops; extern Option HeartDropRefill; diff --git a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp index 32be2d679..8123843b2 100644 --- a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp @@ -298,41 +298,47 @@ static void WriteLocation( //Writes a shuffled entrance to the specified node static void WriteShuffledEntrance(std::string sphereString, Entrance* entrance) { int16_t originalIndex = entrance->GetIndex(); - int16_t destinationIndex = entrance->GetReverse()->GetIndex(); + int16_t destinationIndex = -1; int16_t originalBlueWarp = entrance->GetBlueWarp(); - int16_t replacementBlueWarp = entrance->GetReplacement()->GetReverse()->GetBlueWarp(); + int16_t replacementBlueWarp = -1; int16_t replacementIndex = entrance->GetReplacement()->GetIndex(); - int16_t replacementDestinationIndex = entrance->GetReplacement()->GetReverse()->GetIndex(); + int16_t replacementDestinationIndex = -1; std::string name = entrance->GetName(); std::string text = entrance->GetConnectedRegion()->regionName + " from " + entrance->GetReplacement()->GetParentRegion()->regionName; + if (entrance->GetReverse() != nullptr && !Settings::DecoupleEntrances) { + destinationIndex = entrance->GetReverse()->GetIndex(); + replacementDestinationIndex = entrance->GetReplacement()->GetReverse()->GetIndex(); + replacementBlueWarp = entrance->GetReplacement()->GetReverse()->GetBlueWarp(); + } + + json entranceJson = json::object({ + {"index", originalIndex}, + {"destination", destinationIndex}, + {"blueWarp", originalBlueWarp}, + {"override", replacementIndex}, + {"overrideDestination", replacementDestinationIndex}, + }); + + jsonData["entrances"].push_back(entranceJson); + + // When decoupled entrances is off, handle saving reverse entrances with blue warps + if (entrance->GetReverse() != nullptr && !Settings::DecoupleEntrances) { + json reverseEntranceJson = json::object({ + {"index", replacementDestinationIndex}, + {"destination", replacementIndex}, + {"blueWarp", replacementBlueWarp}, + {"override", destinationIndex}, + {"overrideDestination", originalIndex}, + }); + + jsonData["entrances"].push_back(reverseEntranceJson); + } + switch (gSaveContext.language) { case LANGUAGE_ENG: case LANGUAGE_FRA: default: - json entranceJson = json::object({ - {"index", originalIndex}, - {"destination", destinationIndex}, - {"blueWarp", originalBlueWarp}, - {"override", replacementIndex}, - {"overrideDestination", replacementDestinationIndex}, - }); - - jsonData["entrances"].push_back(entranceJson); - - // When decoupled entrances is off, handle saving reverse entrances with blue warps - if (!false) { // RANDOTODO: add check for decoupled entrances - json reverseEntranceJson = json::object({ - {"index", replacementDestinationIndex}, - {"destination", replacementIndex}, - {"blueWarp", replacementBlueWarp}, - {"override", destinationIndex}, - {"overrideDestination", originalIndex}, - }); - - jsonData["entrances"].push_back(reverseEntranceJson); - } - jsonData["entrancesMap"][sphereString][name] = text; break; } @@ -628,12 +634,24 @@ static void WriteHints(int language) { default: unformattedGanonText = GetGanonText().GetEnglish(); unformattedGanonHintText = GetGanonHintText().GetEnglish(); + jsonData["warpMinuetText"] = GetWarpMinuetText().GetEnglish(); + jsonData["warpBoleroText"] = GetWarpBoleroText().GetEnglish(); + jsonData["warpSerenadeText"] = GetWarpSerenadeText().GetEnglish(); + jsonData["warpRequiemText"] = GetWarpRequiemText().GetEnglish(); + jsonData["warpNocturne"] = GetWarpNocturneText().GetEnglish(); + jsonData["warpPreludeText"] = GetWarpPreludeText().GetEnglish(); jsonData["childAltarText"] = GetChildAltarText().GetEnglish(); jsonData["adultAltarText"] = GetAdultAltarText().GetEnglish(); break; case 2: unformattedGanonText = GetGanonText().GetFrench(); unformattedGanonHintText = GetGanonHintText().GetFrench(); + jsonData["warpMinuetText"] = GetWarpMinuetText().GetFrench(); + jsonData["warpBoleroText"] = GetWarpBoleroText().GetFrench(); + jsonData["warpSerenadeText"] = GetWarpSerenadeText().GetFrench(); + jsonData["warpRequiemText"] = GetWarpRequiemText().GetFrench(); + jsonData["warpNocturne"] = GetWarpNocturneText().GetFrench(); + jsonData["warpPreludeText"] = GetWarpPreludeText().GetFrench(); jsonData["childAltarText"] = GetChildAltarText().GetFrench(); jsonData["adultAltarText"] = GetAdultAltarText().GetFrench(); break; diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 7d5d4bcf5..cc4a0ada7 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -224,6 +224,15 @@ std::unordered_map SpoilerfileSettingNameToEn { "World Settings:Overworld Entrances", RSK_SHUFFLE_OVERWORLD_ENTRANCES }, { "World Settings:Interior Entrances", RSK_SHUFFLE_INTERIOR_ENTRANCES }, { "World Settings:Grottos Entrances", RSK_SHUFFLE_GROTTO_ENTRANCES }, + { "World Settings:Owl Drops", RSK_SHUFFLE_OWL_DROPS }, + { "World Settings:Warp Songs", RSK_SHUFFLE_WARP_SONGS }, + { "World Settings:Overworld Spawns", RSK_SHUFFLE_OVERWORLD_SPAWNS }, + { "World Settings:Mixed Entrance Pools", RSK_MIXED_ENTRANCE_POOLS }, + { "World Settings:Mix Dungeons", RSK_MIX_DUNGEON_ENTRANCES }, + { "World Settings:Mix Overworld", RSK_MIX_OVERWORLD_ENTRANCES }, + { "World Settings:Mix Interiors", RSK_MIX_INTERIOR_ENTRANCES }, + { "World Settings:Mix Grottos", RSK_MIX_GROTTO_ENTRANCES }, + { "World Settings:Decouple Entrances", RSK_DECOUPLED_ENTRANCES }, { "Misc Settings:Gossip Stone Hints", RSK_GOSSIP_STONE_HINTS }, { "Misc Settings:Hint Clarity", RSK_HINT_CLARITY }, { "Misc Settings:Hint Distribution", RSK_HINT_DISTRIBUTION }, @@ -327,6 +336,12 @@ void Randomizer::LoadHintLocations(const char* spoilerFileName) { CustomMessageManager::Instance->CreateMessage( Randomizer::hintMessageTableID, hintLocation.check, { TEXTBOX_TYPE_BLUE, TEXTBOX_POS_BOTTOM, hintLocation.hintText, hintLocation.hintText, hintLocation.hintText }); } + + CustomMessageManager::Instance->CreateMessage(Randomizer::hintMessageTableID, TEXT_WARP_RANDOM_REPLACED_TEXT, + { TEXTBOX_TYPE_BLACK, TEXTBOX_POS_BOTTOM, + "Warp to&{{location}}?\x1B&%gOK&No%w\x02", + "Warp to&{{location}}?\x1B&%gOK&No%w\x02", // TODO: German translation + "Se téléporter vers&{{location}}?\x1B&%gOK!&Non%w\x02" }); } std::vector shopItemRandomizerChecks = { @@ -716,6 +731,15 @@ void Randomizer::ParseRandomizerSettingsFile(const char* spoilerFileName) { case RSK_SHUFFLE_ENTRANCES: case RSK_SHUFFLE_OVERWORLD_ENTRANCES: case RSK_SHUFFLE_GROTTO_ENTRANCES: + case RSK_SHUFFLE_OWL_DROPS: + case RSK_SHUFFLE_WARP_SONGS: + case RSK_SHUFFLE_OVERWORLD_SPAWNS: + case RSK_MIXED_ENTRANCE_POOLS: + case RSK_MIX_DUNGEON_ENTRANCES: + case RSK_MIX_OVERWORLD_ENTRANCES: + case RSK_MIX_INTERIOR_ENTRANCES: + case RSK_MIX_GROTTO_ENTRANCES: + case RSK_DECOUPLED_ENTRANCES: if(it.value() == "Off") { gSaveContext.randoSettings[index].value = RO_GENERIC_OFF; } else if(it.value() == "On") { @@ -1101,6 +1125,30 @@ void Randomizer::ParseHintLocationsFile(const char* spoilerFileName) { strncpy(gSaveContext.ganonText, formattedGanonJsonText.c_str(), sizeof(gSaveContext.ganonText) - 1); gSaveContext.ganonText[sizeof(gSaveContext.ganonText) - 1] = 0; + std::string warpMinuetJsonText = spoilerFileJson["warpMinuetText"].get(); + strncpy(gSaveContext.warpMinuetText, warpMinuetJsonText.c_str(), sizeof(gSaveContext.warpMinuetText) - 1); + gSaveContext.warpMinuetText[sizeof(gSaveContext.warpMinuetText) - 1] = 0; + + std::string warpBoleroJsonText = spoilerFileJson["warpBoleroText"].get(); + strncpy(gSaveContext.warpBoleroText, warpBoleroJsonText.c_str(), sizeof(gSaveContext.warpBoleroText) - 1); + gSaveContext.warpBoleroText[sizeof(gSaveContext.warpBoleroText) - 1] = 0; + + std::string warpSerenadeJsonText = spoilerFileJson["warpSerenadeText"].get(); + strncpy(gSaveContext.warpSerenadeText, warpSerenadeJsonText.c_str(), sizeof(gSaveContext.warpSerenadeText) - 1); + gSaveContext.warpSerenadeText[sizeof(gSaveContext.warpSerenadeText) - 1] = 0; + + std::string warpRequiemJsonText = spoilerFileJson["warpRequiemText"].get(); + strncpy(gSaveContext.warpRequiemText, warpRequiemJsonText.c_str(), sizeof(gSaveContext.warpRequiemText) - 1); + gSaveContext.warpRequiemText[sizeof(gSaveContext.warpRequiemText) - 1] = 0; + + std::string warpNocturneJsonText = spoilerFileJson["warpNocturneText"].get(); + strncpy(gSaveContext.warpNocturneText, warpNocturneJsonText.c_str(), sizeof(gSaveContext.warpNocturneText) - 1); + gSaveContext.warpNocturneText[sizeof(gSaveContext.warpNocturneText) - 1] = 0; + + std::string warpPreludeJsonText = spoilerFileJson["warpPreludeText"].get(); + strncpy(gSaveContext.warpPreludeText, warpPreludeJsonText.c_str(), sizeof(gSaveContext.warpPreludeText) - 1); + gSaveContext.warpPreludeText[sizeof(gSaveContext.warpPreludeText) - 1] = 0; + json hintsJson = spoilerFileJson["hints"]; int index = 0; for (auto it = hintsJson.begin(); it != hintsJson.end(); ++it) { @@ -2682,15 +2730,27 @@ void GenerateRandomizerImgui() { } // Enable if any of the entrance rando options are enabled. - cvarSettings[RSK_SHUFFLE_ENTRANCES] = CVar_GetS32("gRandomizeShuffleDungeonsEntrances", 0) || - CVar_GetS32("gRandomizeShuffleOverworldEntrances", 0) || - CVar_GetS32("gRandomizeShuffleInteriorsEntrances", 0) || - CVar_GetS32("gRandomizeShuffleGrottosEntrances", 0); + cvarSettings[RSK_SHUFFLE_ENTRANCES] = CVar_GetS32("gRandomizeShuffleDungeonsEntrances", RO_DUNGEON_ENTRANCE_SHUFFLE_OFF) || + CVar_GetS32("gRandomizeShuffleOverworldEntrances", RO_GENERIC_OFF) || + CVar_GetS32("gRandomizeShuffleInteriorsEntrances", RO_INTERIOR_ENTRANCE_SHUFFLE_OFF) || + CVar_GetS32("gRandomizeShuffleGrottosEntrances", RO_GENERIC_OFF) || + CVar_GetS32("gRandomizeShuffleOwlDrops", RO_GENERIC_OFF) || + CVar_GetS32("gRandomizeShuffleWarpSongs", RO_GENERIC_OFF) || + CVar_GetS32("gRandomizeShuffleOverworldSpawns", RO_GENERIC_OFF); - cvarSettings[RSK_SHUFFLE_DUNGEON_ENTRANCES] = CVar_GetS32("gRandomizeShuffleDungeonsEntrances", 0); - cvarSettings[RSK_SHUFFLE_OVERWORLD_ENTRANCES] = CVar_GetS32("gRandomizeShuffleOverworldEntrances", 0); - cvarSettings[RSK_SHUFFLE_INTERIOR_ENTRANCES] = CVar_GetS32("gRandomizeShuffleInteriorsEntrances", 0); - cvarSettings[RSK_SHUFFLE_GROTTO_ENTRANCES] = CVar_GetS32("gRandomizeShuffleGrottosEntrances", 0); + cvarSettings[RSK_SHUFFLE_DUNGEON_ENTRANCES] = CVar_GetS32("gRandomizeShuffleDungeonsEntrances", RO_DUNGEON_ENTRANCE_SHUFFLE_OFF); + cvarSettings[RSK_SHUFFLE_OVERWORLD_ENTRANCES] = CVar_GetS32("gRandomizeShuffleOverworldEntrances", RO_GENERIC_OFF); + cvarSettings[RSK_SHUFFLE_INTERIOR_ENTRANCES] = CVar_GetS32("gRandomizeShuffleInteriorsEntrances", RO_INTERIOR_ENTRANCE_SHUFFLE_OFF); + cvarSettings[RSK_SHUFFLE_GROTTO_ENTRANCES] = CVar_GetS32("gRandomizeShuffleGrottosEntrances", RO_GENERIC_OFF); + cvarSettings[RSK_SHUFFLE_OWL_DROPS] = CVar_GetS32("gRandomizeShuffleOwlDrops", RO_GENERIC_OFF); + cvarSettings[RSK_SHUFFLE_WARP_SONGS] = CVar_GetS32("gRandomizeShuffleWarpSongs", RO_GENERIC_OFF); + cvarSettings[RSK_SHUFFLE_OVERWORLD_SPAWNS] = CVar_GetS32("gRandomizeShuffleOverworldSpawns", RO_GENERIC_OFF); + cvarSettings[RSK_MIXED_ENTRANCE_POOLS] = CVar_GetS32("gRandomizeMixedEntrances", RO_GENERIC_OFF); + cvarSettings[RSK_MIX_DUNGEON_ENTRANCES] = CVar_GetS32("gRandomizeMixDungeons", RO_GENERIC_OFF); + cvarSettings[RSK_MIX_OVERWORLD_ENTRANCES] = CVar_GetS32("gRandomizeMixOverworld", RO_GENERIC_OFF); + cvarSettings[RSK_MIX_INTERIOR_ENTRANCES] = CVar_GetS32("gRandomizeMixInteriors", RO_GENERIC_OFF); + cvarSettings[RSK_MIX_GROTTO_ENTRANCES] = CVar_GetS32("gRandomizeMixGrottos", RO_GENERIC_OFF); + cvarSettings[RSK_DECOUPLED_ENTRANCES] = CVar_GetS32("gRandomizeShuffleDecoupledEntrances", RO_GENERIC_OFF); // todo: this efficently when we build out cvar array support std::set excludedLocations; @@ -3108,6 +3168,88 @@ void DrawRandoEditor(bool& open) { "Shuffle the pool of grotto entrances, including all graves, small Fairy fountains and the Deku Theatre." ); + UIWidgets::PaddedSeparator(); + + // Shuffle Owl Drops + UIWidgets::EnhancementCheckbox("Shuffle Owl Drops", "gRandomizeShuffleOwlDrops"); + UIWidgets::InsertHelpHoverText( + "Randomize where Kaepora Gaebora (the Owl) drops you at when you talk " + "to him at Lake Hylia or at the top of Death Mountain Trail." + ); + + UIWidgets::PaddedSeparator(); + + // Shuffle Warp Songs + UIWidgets::EnhancementCheckbox("Shuffle Warp Songs", "gRandomizeShuffleWarpSongs"); + UIWidgets::InsertHelpHoverText( + "Randomize where each of the 6 warp songs leads to." + ); + + UIWidgets::PaddedSeparator(); + + // Shuffle Overworld Spawns + UIWidgets::EnhancementCheckbox("Shuffle Overworld Spawns", "gRandomizeShuffleOverworldSpawns"); + UIWidgets::InsertHelpHoverText( + "Randomize where you start as Child or Adult when loading a save in the Overworld. This " + "means you may not necessarily spawn inside Link's House or Temple of Time.\n" + "\n" + "This stays consistent after saving and loading the game again.\n" + "\n" + "Keep in mind you may need to temporarily disable the \"Remember Save Location\" time saver to " + "be able use the spawn positions, especially if they are the only logical way to get to certain areas." + ); + + UIWidgets::PaddedSeparator(); + + // Shuffle Decoupled Entrances + UIWidgets::EnhancementCheckbox("Shuffle Decoupled Entrances", "gRandomizeShuffleDecoupledEntrances"); + UIWidgets::InsertHelpHoverText( + "Decouple entrances when shuffling them. This means you are no longer guaranteed " + "to end up back where you came from when you go back through an entrance.\n" + "\n" + "This also adds the one-way entrance from Gerudo Valley to Lake Hylia in the pool of " + "overworld entrances when they are shuffled." + ); + + UIWidgets::PaddedSeparator(); + + // Mixed Entrance Pools + UIWidgets::EnhancementCheckbox("Mixed Entrance Pools", "gRandomizeMixedEntrances"); + UIWidgets::InsertHelpHoverText( + "Shuffle entrances into a mixed pool instead of separate ones.\n" + "\n" + "For example, enabling the settings to shuffle grotto, dungeon, and overworld entrances and " + "selecting grotto and dungeon entrances here will allow a dungeon to be inside a grotto or " + "vice versa, while overworld entrances are shuffled in their own separate pool and indoors stay vanilla." + ); + + if (CVar_GetS32("gRandomizeMixedEntrances", RO_GENERIC_OFF)) { + if (CVar_GetS32("gRandomizeShuffleDungeonsEntrances", RO_GENERIC_OFF)) { + UIWidgets::Spacer(0); + ImGui::SetCursorPosX(20); + UIWidgets::EnhancementCheckbox("Mix Dungeons", "gRandomizeMixDungeons"); + UIWidgets::InsertHelpHoverText("Dungeon entrances will be part of the mixed pool"); + } + if (CVar_GetS32("gRandomizeShuffleOverworldEntrances", RO_GENERIC_OFF)) { + UIWidgets::Spacer(0); + ImGui::SetCursorPosX(20); + UIWidgets::EnhancementCheckbox("Mix Overworld", "gRandomizeMixOverworld"); + UIWidgets::InsertHelpHoverText("Overworld entrances will be part of the mixed pool"); + } + if (CVar_GetS32("gRandomizeShuffleInteriorsEntrances", RO_GENERIC_OFF)) { + UIWidgets::Spacer(0); + ImGui::SetCursorPosX(20); + UIWidgets::EnhancementCheckbox("Mix Interiors", "gRandomizeMixInteriors"); + UIWidgets::InsertHelpHoverText("Interior entrances will be part of the mixed pool"); + } + if (CVar_GetS32("gRandomizeShuffleGrottosEntrances", RO_GENERIC_OFF)) { + UIWidgets::Spacer(0); + ImGui::SetCursorPosX(20); + UIWidgets::EnhancementCheckbox("Mix Grotts", "gRandomizeMixGrottos"); + UIWidgets::InsertHelpHoverText("Grotto entrances will be part of the mixed pool"); + } + } + ImGui::PopItemWidth(); ImGui::EndChild(); ImGui::EndTable(); @@ -4016,6 +4158,47 @@ void DrawRandoEditor(bool& open) { ImGui::End(); } +CustomMessageEntry Randomizer::GetWarpSongMessage(u16 textId, bool mysterious) { + CustomMessageEntry messageEntry = CustomMessageManager::Instance->RetrieveMessage( + Randomizer::hintMessageTableID, TEXT_WARP_RANDOM_REPLACED_TEXT); + if (mysterious) { + std::vector locationName ={ + "a mysterious place", + "a mysterious place", // TODO: German translation + "un endroit mystérieux", + }; + + CustomMessageManager::ReplaceStringInMessage(messageEntry, "{{location}}", locationName[0], + locationName[1], locationName[2]); + return messageEntry; + } + + std::string locationName; + switch (textId) { + case TEXT_WARP_MINUET_OF_FOREST: + locationName = std::string(gSaveContext.warpMinuetText); + break; + case TEXT_WARP_BOLERO_OF_FIRE: + locationName = std::string(gSaveContext.warpBoleroText); + break; + case TEXT_WARP_SERENADE_OF_WATER: + locationName = std::string(gSaveContext.warpSerenadeText); + break; + case TEXT_WARP_REQUIEM_OF_SPIRIT: + locationName = std::string(gSaveContext.warpRequiemText); + break; + case TEXT_WARP_NOCTURNE_OF_SHADOW: + locationName = std::string(gSaveContext.warpNocturneText); + break; + case TEXT_WARP_PRELUDE_OF_LIGHT: + locationName = std::string(gSaveContext.warpPreludeText); + break; + } + + CustomMessageManager::ReplaceStringInMessage(messageEntry, "{{location}}", locationName); + return messageEntry; +} + CustomMessageEntry Randomizer::GetMerchantMessage(RandomizerInf randomizerInf, u16 textId, bool mysterious) { CustomMessageEntry messageEntry = CustomMessageManager::Instance->RetrieveMessage(Randomizer::merchantMessageTableID, textId); RandomizerCheck rc = GetCheckFromRandomizerInf(randomizerInf); diff --git a/soh/soh/Enhancements/randomizer/randomizer.h b/soh/soh/Enhancements/randomizer/randomizer.h index cca97fd86..067b66332 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.h +++ b/soh/soh/Enhancements/randomizer/randomizer.h @@ -77,6 +77,7 @@ class Randomizer { GetItemID GetItemIdFromRandomizerGet(RandomizerGet randoGet, GetItemID ogItemId); ItemObtainability GetItemObtainabilityFromRandomizerCheck(RandomizerCheck randomizerCheck); ItemObtainability GetItemObtainabilityFromRandomizerGet(RandomizerGet randomizerCheck); + CustomMessageEntry GetWarpSongMessage(u16 textId, bool mysterious = false); CustomMessageEntry GetMerchantMessage(RandomizerInf randomizerInf, u16 textId, bool mysterious = false); CustomMessageEntry GetMapGetItemMessageWithHint(GetItemEntry itemEntry); static void CreateCustomMessages(); diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance.c b/soh/soh/Enhancements/randomizer/randomizer_entrance.c index c00f8d42c..de696c05b 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance.c +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance.c @@ -59,6 +59,14 @@ static void Entrance_SeparateOGCFairyFountainExit(void) { } } +static void Entrance_SeparateAdultSpawnAndPrelude() { + // Overwrite unused entrance 0x0282 with values from 0x05F4 to use it as the + // Adult Spawn index and separate it from Prelude of Light + for (size_t i = 0; i < 4; ++i) { + gEntranceTable[0x282 + i] = gEntranceTable[0x5F4 + i]; + } +} + void Entrance_CopyOriginalEntranceTable(void) { if (!hasCopiedEntranceTable) { memcpy(originalEntranceTable, gEntranceTable, sizeof(EntranceInfo) * 1556); @@ -91,6 +99,7 @@ void Entrance_Init(void) { } Entrance_SeparateOGCFairyFountainExit(); + Entrance_SeparateAdultSpawnAndPrelude(); // Initialize the entrance override table with each index leading to itself. An // index referring to itself means that the entrance is not currently shuffled. @@ -188,7 +197,7 @@ s16 Entrance_OverrideNextIndex(s16 nextEntranceIndex) { // Exiting through the crawl space from Hyrule Castle courtyard is the same exit as leaving Ganon's castle // If we came from the Castle courtyard, then don't override the entrance to keep Link in Hyrule Castle area - if (gPlayState->sceneNum == 69 && nextEntranceIndex == 0x023D) { + if (gPlayState != NULL && gPlayState->sceneNum == 69 && nextEntranceIndex == 0x023D) { return nextEntranceIndex; } @@ -279,15 +288,58 @@ void Entrance_SetSavewarpEntrance(void) { } else if (scene == SCENE_GERUDOWAY) { // Theives hideout gSaveContext.entranceIndex = 0x0486; // Gerudo Fortress -> Thieve's Hideout spawn 0 } else if (scene == SCENE_LINK_HOME) { - gSaveContext.entranceIndex = LINK_HOUSE_SAVEWARP_ENTRANCE; + gSaveContext.entranceIndex = Entrance_OverrideNextIndex(LINK_HOUSE_SAVEWARP_ENTRANCE); } else if (LINK_IS_CHILD) { - gSaveContext.entranceIndex = Entrance_GetOverride(LINK_HOUSE_SAVEWARP_ENTRANCE); + gSaveContext.entranceIndex = Entrance_OverrideNextIndex(LINK_HOUSE_SAVEWARP_ENTRANCE); // Child Overworld Spawn } else { - gSaveContext.entranceIndex = Entrance_GetOverride(0x05F4); // Temple of Time Adult Spawn + gSaveContext.entranceIndex = Entrance_OverrideNextIndex(0x0282); // Adult Overworld Spawn (Normally 0x5F4, but 0x282 has been repurposed to differentiate from Prelude which also uses 0x5F4) + } +} + +void Entrance_SetWarpSongEntrance(void) { + gPlayState->sceneLoadFlag = 0x14; + gPlayState->fadeTransition = 5; + switch (gPlayState->msgCtx.lastPlayedSong) { + case 0: + gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x0600); // Minuet + break; + case 1: + gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x04F6); // Bolero + break; + case 2: + gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x0604); // Serenade + break; + case 3: + gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x01F1); // Requiem + break; + case 4: + gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x0568); // Nocturne + break; + case 5: + gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x05F4); // Prelude + break; + default: + gPlayState->sceneLoadFlag = 0; // if something goes wrong, the animation plays normally + } + + // If one of the warp songs happens to lead to a grotto return, then we + // have to force the grotto return afterwards + Grotto_ForceGrottoReturnOnSpecialEntrance(); + + if (gSaveContext.gameMode != 0) { + // During DHWW the cutscene must play at the destination + gSaveContext.respawnFlag = -3; + } else if (gSaveContext.respawnFlag == -3) { + // Unset Zoneout Type -3 to avoid cutscene at destination (technically it's not needed) + gSaveContext.respawnFlag = 0; } } void Entrance_OverrideBlueWarp(void) { + // Set nextEntranceIndex as a flag so that Grotto_CheckSpecialEntrance + // won't return index 0x7FFF, which can't work to override blue warps. + gPlayState->nextEntranceIndex = 0; + switch (gPlayState->sceneNum) { case SCENE_YDAN_BOSS: // Ghoma boss room gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(0x0457); @@ -322,6 +374,8 @@ void Entrance_OverrideCutsceneEntrance(u16 cutsceneCmd) { gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(newJabuJabusBellyEntrance); gPlayState->sceneLoadFlag = 0x14; gPlayState->fadeTransition = 2; + // In case Jabu's mouth leads to a grotto return + Grotto_ForceGrottoReturnOnSpecialEntrance(); break; } } @@ -506,7 +560,7 @@ void Entrance_OverrideGeurdoGuardCapture(void) { } void Entrance_OverrideSpawnScene(s32 sceneNum, s32 spawn) { - if (Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) == 2) { // Shuffle Ganon's Castle + if (Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) == RO_DUNGEON_ENTRANCE_SHUFFLE_ON_PLUS_GANON) { // Move Hyrule's Castle Courtyard exit spawn to be before the crates so players don't skip Talon if (sceneNum == 95 && spawn == 1) { gPlayState->linkActorEntry->pos.x = 0x033A; diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance.h b/soh/soh/Enhancements/randomizer/randomizer_entrance.h index 3836770e7..8ff1153d4 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance.h +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance.h @@ -40,6 +40,7 @@ int16_t Entrance_OverrideNextIndex(int16_t nextEntranceIndex); int16_t Entrance_OverrideDynamicExit(int16_t dynamicExitIndex); uint32_t Entrance_SceneAndSpawnAre(uint8_t scene, uint8_t spawn); void Entrance_SetSavewarpEntrance(void); +void Entrance_SetWarpSongEntrance(void); void Entrance_OverrideBlueWarp(void); void Entrance_OverrideCutsceneEntrance(uint16_t cutsceneCmd); void Entrance_HandleEponaState(void); diff --git a/soh/soh/Enhancements/randomizer/randomizer_grotto.c b/soh/soh/Enhancements/randomizer/randomizer_grotto.c index 4326f646b..17505e3be 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_grotto.c +++ b/soh/soh/Enhancements/randomizer/randomizer_grotto.c @@ -87,6 +87,7 @@ static s16 grottoExitList[NUM_GROTTOS] = {0}; static s16 grottoLoadList[NUM_GROTTOS] = {0}; static s8 grottoId = 0xFF; static s8 lastEntranceType = NOT_GROTTO; +static u8 overridingNextEntrance = false; // Initialize both lists so that each index refers to itself. An index referring // to itself means that the entrance is not shuffled. Indices will be overwritten @@ -111,24 +112,20 @@ void Grotto_SetLoadOverride(s16 originalIndex, s16 overrideIndex) { } static void Grotto_SetupReturnInfo(GrottoReturnInfo grotto, RespawnMode respawnMode) { - // Set necessary grotto return data to the Entrance Point, so that voiding out and setting FW work correctly - gSaveContext.respawn[respawnMode].entranceIndex = grotto.entranceIndex; - gSaveContext.respawn[respawnMode].roomIndex = grotto.room; - - if (false /*mixGrottos == ON*/ || false /*decoupledEntrances == ON*/) { + // Set necessary grotto return data to the Entrance Point, so that voiding out and setting FW work correctly + gSaveContext.respawn[respawnMode].entranceIndex = grotto.entranceIndex; + gSaveContext.respawn[respawnMode].roomIndex = grotto.room; gSaveContext.respawn[respawnMode].playerParams = 0x04FF; // exiting grotto with no initial camera focus - } - - gSaveContext.respawn[respawnMode].yaw = grotto.angle; - gSaveContext.respawn[respawnMode].pos = grotto.pos; - //TODO If Mixed Entrance Pools or decoupled entrances are active, set these flags to 0 instead of restoring them - if (false /*mixGrottos == ON*/ || false /*decoupledEntrances == ON*/) { - gSaveContext.respawn[respawnMode].tempSwchFlags = 0; - gSaveContext.respawn[respawnMode].tempCollectFlags = 0; - } else { - gSaveContext.respawn[respawnMode].tempSwchFlags = gSaveContext.respawn[RESPAWN_MODE_RETURN].tempSwchFlags; - gSaveContext.respawn[respawnMode].tempCollectFlags = gSaveContext.respawn[RESPAWN_MODE_RETURN].tempCollectFlags; - } + gSaveContext.respawn[respawnMode].yaw = grotto.angle; + gSaveContext.respawn[respawnMode].pos = grotto.pos; + // If Mixed Entrance Pools or decoupled entrances are active, set these flags to 0 instead of restoring them + if (Randomizer_GetSettingValue(RSK_MIX_GROTTO_ENTRANCES) || Randomizer_GetSettingValue(RSK_DECOUPLED_ENTRANCES)) { + gSaveContext.respawn[respawnMode].tempSwchFlags = 0; + gSaveContext.respawn[respawnMode].tempCollectFlags = 0; + } else { + gSaveContext.respawn[respawnMode].tempSwchFlags = gSaveContext.respawn[RESPAWN_MODE_RETURN].tempSwchFlags; + gSaveContext.respawn[respawnMode].tempCollectFlags = gSaveContext.respawn[RESPAWN_MODE_RETURN].tempCollectFlags; + } } // Translates and overrides the passed in entrance index if it corresponds to a @@ -136,7 +133,7 @@ static void Grotto_SetupReturnInfo(GrottoReturnInfo grotto, RespawnMode respawnM s16 Grotto_OverrideSpecialEntrance(s16 nextEntranceIndex) { // Don't change anything unless grotto shuffle has been enabled - if (!Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES)) { + if (!Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES) && !Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_SPAWNS) && !Randomizer_GetSettingValue(RSK_SHUFFLE_WARP_SONGS)) { return nextEntranceIndex; } @@ -157,13 +154,18 @@ s16 Grotto_OverrideSpecialEntrance(s16 nextEntranceIndex) { Grotto_SetupReturnInfo(grotto, RESPAWN_MODE_RETURN); Grotto_SetupReturnInfo(grotto, RESPAWN_MODE_DOWN); - // When the nextEntranceIndex is determined by a dynamic exit, we have - // to set the respawn information and nextEntranceIndex manually + // When the nextEntranceIndex is determined by a dynamic exit, + // or set by Entrance_OverrideBlueWarp to mark a blue warp entrance, + // we have to set the respawn information and nextEntranceIndex manually if (gPlayState != NULL && gPlayState->nextEntranceIndex != -1) { gSaveContext.respawnFlag = 2; nextEntranceIndex = grotto.entranceIndex; gPlayState->fadeTransition = 3; gSaveContext.nextTransitionType = 3; + } else if (gPlayState == NULL) { // Handle spawn position when loading from a save file + gSaveContext.respawnFlag = 2; + nextEntranceIndex = grotto.entranceIndex; + gSaveContext.nextTransitionType = 3; // Otherwise return 0x7FFF and let the game handle it } else { nextEntranceIndex = 0x7FFF; @@ -185,6 +187,7 @@ s16 Grotto_OverrideSpecialEntrance(s16 nextEntranceIndex) { lastEntranceType = NOT_GROTTO; } + overridingNextEntrance = true; return nextEntranceIndex; } @@ -192,8 +195,8 @@ s16 Grotto_OverrideSpecialEntrance(s16 nextEntranceIndex) { // thisx - pointer to the grotto actor void Grotto_OverrideActorEntrance(Actor* thisx) { - // Vanilla Behavior if grottos aren't shuffled - if (!Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES)) { + // 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) && !Randomizer_GetSettingValue(RSK_SHUFFLE_WARP_SONGS)) { return; } @@ -216,10 +219,22 @@ void Grotto_OverrideActorEntrance(Actor* thisx) { } } +// Set necessary flags for when warp songs/overworld spawns are shuffled to grotto return points +void Grotto_ForceGrottoReturnOnSpecialEntrance(void) { + if (lastEntranceType == GROTTO_RETURN && (Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES) || Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_SPAWNS) || Randomizer_GetSettingValue(RSK_SHUFFLE_WARP_SONGS))) { + gSaveContext.respawnFlag = 2; + gSaveContext.respawn[RESPAWN_MODE_RETURN].playerParams = 0x4FF; + gSaveContext.respawn[RESPAWN_MODE_RETURN].pos = grottoReturnTable[grottoId].pos; + // Clear current temp flags + gSaveContext.respawn[RESPAWN_MODE_RETURN].tempSwchFlags = 0; + gSaveContext.respawn[RESPAWN_MODE_RETURN].tempCollectFlags = 0; + } +} + // Set the respawn flag for when we want to return from a grotto entrance // Used for Sun's Song and Game Over, which usually don't restore saved position data void Grotto_ForceGrottoReturn(void) { - if (lastEntranceType == GROTTO_RETURN && Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES)) { + if (lastEntranceType == GROTTO_RETURN && (Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES) || Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_SPAWNS) || Randomizer_GetSettingValue(RSK_SHUFFLE_WARP_SONGS))) { gSaveContext.respawnFlag = 2; gSaveContext.respawn[RESPAWN_MODE_RETURN].playerParams = 0x0DFF; gSaveContext.respawn[RESPAWN_MODE_RETURN].pos = grottoReturnTable[grottoId].pos; @@ -231,7 +246,7 @@ void Grotto_ForceGrottoReturn(void) { // Used for the DMT special voids, which usually don't restore saved position data void Grotto_ForceRegularVoidOut(void) { - if (lastEntranceType == GROTTO_RETURN && Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES)) { + if (lastEntranceType == GROTTO_RETURN && (Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES) || Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_SPAWNS) || Randomizer_GetSettingValue(RSK_SHUFFLE_WARP_SONGS))) { gSaveContext.respawn[RESPAWN_MODE_DOWN] = gSaveContext.respawn[RESPAWN_MODE_RETURN]; gSaveContext.respawn[RESPAWN_MODE_DOWN].playerParams = 0x0DFF; gSaveContext.respawn[RESPAWN_MODE_DOWN].pos = grottoReturnTable[grottoId].pos; @@ -242,13 +257,26 @@ void Grotto_ForceRegularVoidOut(void) { // If returning to a FW point saved at a grotto exit, copy the FW data to the Grotto Return Point // so that Sun's Song and Game Over will behave correctly void Grotto_SetupReturnInfoOnFWReturn(void) { - if (Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES)) { + if (Randomizer_GetSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES) || Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_SPAWNS) || Randomizer_GetSettingValue(RSK_SHUFFLE_WARP_SONGS) && + gSaveContext.fw.playerParams == 0x4FF) { gSaveContext.respawn[RESPAWN_MODE_RETURN] = gSaveContext.respawn[RESPAWN_MODE_TOP]; gSaveContext.respawn[RESPAWN_MODE_RETURN].playerParams = 0x0DFF; lastEntranceType = GROTTO_RETURN; + } else { + lastEntranceType = NOT_GROTTO; } } +// If a scene transition is not overridden at all (i.e. guards throwing Link out / quitting game) +// the lastEntranceType must be cleared to avoid messing up savewarps and deathwarps. +// This does not apply to void out and other respawns, which should keep the lastEntranceType. +void Grotto_SanitizeEntranceType(void) { + if (!overridingNextEntrance && gSaveContext.respawnFlag == 0) { + lastEntranceType = NOT_GROTTO; + } + overridingNextEntrance = false; +} + // Get the renamed entrance index based on the grotto contents and exit scene number s16 Grotto_GetRenamedGrottoIndexFromOriginal(s8 content, s8 scene) { for (s16 index = 0; index < NUM_GROTTOS; index++) { diff --git a/soh/soh/Enhancements/randomizer/randomizer_grotto.h b/soh/soh/Enhancements/randomizer/randomizer_grotto.h index b8f4b06fd..6a10d651d 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_grotto.h +++ b/soh/soh/Enhancements/randomizer/randomizer_grotto.h @@ -25,8 +25,10 @@ void Grotto_InitExitAndLoadLists(void); void Grotto_SetExitOverride(s16 originalIndex, s16 overrideIndex); void Grotto_SetLoadOverride(s16 originalIndex, s16 overrideIndex); s16 Grotto_OverrideSpecialEntrance(s16 nextEntranceIndex); +void Grotto_ForceGrottoReturnOnSpecialEntrance(void); void Grotto_ForceGrottoReturn(void); void Grotto_ForceRegularVoidOut(void); +void Grotto_SanitizeEntranceType(void); s16 Grotto_GetRenamedGrottoIndexFromOriginal(s8 content, s8 scene); #endif //_RANDO_GROTTO_H_ diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index e216856ab..527397b3a 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1992,6 +1992,9 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { } else if (Randomizer_GetSettingValue(RSK_BOMBCHUS_IN_LOGIC) && (textId == TEXT_BUY_BOMBCHU_10_DESC || textId == TEXT_BUY_BOMBCHU_10_PROMPT)) { messageEntry = CustomMessageManager::Instance->RetrieveMessage(customMessageTableID, textId); + } else if (Randomizer_GetSettingValue(RSK_SHUFFLE_WARP_SONGS) && + (textId >= TEXT_WARP_MINUET_OF_FOREST && textId <= TEXT_WARP_PRELUDE_OF_LIGHT)) { + messageEntry = OTRGlobals::Instance->gRandomizer->GetWarpSongMessage(textId, false); } } if (textId == TEXT_GS_NO_FREEZE || textId == TEXT_GS_FREEZE) { diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 2aa364007..153e2a6ef 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -175,6 +175,24 @@ void SaveManager::LoadRandomizerVersion2() { std::string ganonText; SaveManager::Instance->LoadData("ganonText", ganonText); memcpy(gSaveContext.ganonText, ganonText.c_str(), ganonText.length()); + std::string warpMinuetText; + SaveManager::Instance->LoadData("warpMinuetText", warpMinuetText); + memcpy(gSaveContext.warpMinuetText, warpMinuetText.c_str(), warpMinuetText.length()); + std::string warpBoleroText; + SaveManager::Instance->LoadData("warpBoleroText", warpBoleroText); + memcpy(gSaveContext.warpBoleroText, warpBoleroText.c_str(), warpBoleroText.length()); + std::string warpSerenadeText; + SaveManager::Instance->LoadData("warpSerenadeText", warpSerenadeText); + memcpy(gSaveContext.warpSerenadeText, warpSerenadeText.c_str(), warpSerenadeText.length()); + std::string warpRequiemText; + SaveManager::Instance->LoadData("warpRequiemText", warpRequiemText); + memcpy(gSaveContext.warpRequiemText, warpRequiemText.c_str(), warpRequiemText.length()); + std::string warpNocturneText; + SaveManager::Instance->LoadData("warpNocturneText", warpNocturneText); + memcpy(gSaveContext.warpNocturneText, warpNocturneText.c_str(), warpNocturneText.length()); + std::string warpPreludeText; + SaveManager::Instance->LoadData("warpPreludeText", warpPreludeText); + memcpy(gSaveContext.warpPreludeText, warpPreludeText.c_str(), warpPreludeText.length()); SaveManager::Instance->LoadData("adultTradeItems", gSaveContext.adultTradeItems); @@ -246,6 +264,12 @@ void SaveManager::SaveRandomizer() { SaveManager::Instance->SaveData("adultAltarText", gSaveContext.adultAltarText); SaveManager::Instance->SaveData("ganonHintText", gSaveContext.ganonHintText); SaveManager::Instance->SaveData("ganonText", gSaveContext.ganonText); + SaveManager::Instance->SaveData("warpMinuetText", gSaveContext.warpMinuetText); + SaveManager::Instance->SaveData("warpBoleroText", gSaveContext.warpBoleroText); + SaveManager::Instance->SaveData("warpSerenadeText", gSaveContext.warpSerenadeText); + SaveManager::Instance->SaveData("warpRequiemText", gSaveContext.warpRequiemText); + SaveManager::Instance->SaveData("warpNocturneText", gSaveContext.warpNocturneText); + SaveManager::Instance->SaveData("warpPreludeText", gSaveContext.warpPreludeText); SaveManager::Instance->SaveData("adultTradeItems", gSaveContext.adultTradeItems); diff --git a/soh/src/code/z_sram.c b/soh/src/code/z_sram.c index 1b3c0ff18..db6b3299a 100644 --- a/soh/src/code/z_sram.c +++ b/soh/src/code/z_sram.c @@ -200,14 +200,6 @@ void Sram_OpenSave() { // Setup the modified entrance table and entrance shuffle table for rando if (gSaveContext.n64ddFlag) { Entrance_Init(); - if (!CVar_GetS32("gRememberSaveLocation", 0) || gSaveContext.savedSceneNum == SCENE_YOUSEI_IZUMI_TATE || - gSaveContext.savedSceneNum == SCENE_KAKUSIANA) { - Entrance_SetSavewarpEntrance(); - } - } else { - // When going from a rando save to a vanilla save within the same game instance - // we need to reset the entrance table back to its vanilla state - Entrance_ResetEntranceTable(); } osSyncPrintf("scene_no = %d\n", gSaveContext.entranceIndex); @@ -390,11 +382,18 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx) { break; case RO_AGE_CHILD: //Child gSaveContext.linkAge = 1; + gSaveContext.savedSceneNum = -1; break; default: break; } + if (Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_SPAWNS)) { + // Override the spawn entrance so entrance rando can take control, + // and to prevent remember save location from breaking inital spawn + gSaveContext.entranceIndex = -1; + } + int doorOfTime = Randomizer_GetSettingValue(RSK_DOOR_OF_TIME); switch (doorOfTime) { case RO_DOOROFTIME_OPEN: @@ -580,4 +579,8 @@ void Sram_InitSram(GameState* gameState) { Save_Init(); func_800F6700(gSaveContext.audioSetting); + + // When going from a rando save to a vanilla save within the same game instance + // we need to reset the entrance table back to its vanilla state + Entrance_ResetEntranceTable(); } diff --git a/soh/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c b/soh/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c index 9e05d9e41..9e8aed86a 100644 --- a/soh/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c +++ b/soh/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c @@ -425,6 +425,11 @@ void DemoKankyo_KillDoorOfTimeCollision(DemoKankyo* this, PlayState* play) { void DemoKankyo_Update(Actor* thisx, PlayState* play) { DemoKankyo* this = (DemoKankyo*)thisx; this->actionFunc(this, play); + + if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_WARP_SONGS) && + thisx->params == 0x000F) { // Warp Song particles + Entrance_SetWarpSongEntrance(); + } } void DemoKankyo_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Owl/z_en_owl.c b/soh/src/overlays/actors/ovl_En_Owl/z_en_owl.c index 065ac7088..b3c21b1f6 100644 --- a/soh/src/overlays/actors/ovl_En_Owl/z_en_owl.c +++ b/soh/src/overlays/actors/ovl_En_Owl/z_en_owl.c @@ -958,7 +958,11 @@ void func_80ACC00C(EnOwl* this, PlayState* play) { osSyncPrintf("SPOT 06 の デモがはしった\n"); // "Demo of SPOT 06 has been completed" osSyncPrintf(VT_RST); if (gSaveContext.n64ddFlag) { - play->nextEntranceIndex = 0x027E; + if (Randomizer_GetSettingValue(RSK_SHUFFLE_OWL_DROPS)) { + play->nextEntranceIndex = Entrance_OverrideNextIndex(0x027E); + } else { + play->nextEntranceIndex = 0x027E; + } play->sceneLoadFlag = 0x14; play->fadeTransition = 2; break; @@ -969,7 +973,11 @@ void func_80ACC00C(EnOwl* this, PlayState* play) { case 8: case 9: if (gSaveContext.n64ddFlag) { - play->nextEntranceIndex = 0x0554; + if (Randomizer_GetSettingValue(RSK_SHUFFLE_OWL_DROPS)) { + play->nextEntranceIndex = Entrance_OverrideNextIndex(0x0554); + } else { + play->nextEntranceIndex = 0x0554; + } play->sceneLoadFlag = 0x14; play->fadeTransition = 2; break; diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 7c2ff4ed0..5bbb42c7d 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -9591,6 +9591,11 @@ void Player_Init(Actor* thisx, PlayState* play2) { s32 sp50; s32 sp4C; + // In ER, once Link has spawned we know the scene has loaded, so we can sanitize the last known entrance type + if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) { + Grotto_SanitizeEntranceType(); + } + play->shootingGalleryStatus = play->bombchuBowlingStatus = 0; play->playerInit = Player_InitCommon; diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c index c74f8e513..f795b9ddd 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c +++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c @@ -2197,6 +2197,15 @@ void FileChoose_LoadGame(GameState* thisx) { gSaveContext.inventory.equipment ^= (gBitFlags[swordEquipMask - 1] << BOMSWAP16(gEquipShifts[EQUIP_SWORD])); } } + + // Handle randomized spawn positions after the save context has been setup from load + // When remeber save location is on, set save warp if the save was in an a grotto, or + // the entrance index is -1 from shuffle overwarld spawn + if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES) && ((!CVar_GetS32("gRememberSaveLocation", 0) || + gSaveContext.savedSceneNum == SCENE_YOUSEI_IZUMI_TATE || gSaveContext.savedSceneNum == SCENE_KAKUSIANA) || + (CVar_GetS32("gRememberSaveLocation", 0) && Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_SPAWNS) && gSaveContext.entranceIndex == -1))) { + Entrance_SetSavewarpEntrance(); + } } static void (*gSelectModeUpdateFuncs[])(GameState*) = {