diff --git a/docs/MODDING.md b/docs/MODDING.md index 1f4f4b607..d410a0d06 100644 --- a/docs/MODDING.md +++ b/docs/MODDING.md @@ -41,15 +41,28 @@ You can name your branch whatever you want, but it's recommended to name it some The limit is your imagination. You can add new features, fix bugs, add new mods, or even change the way the game works. We will demonstrate this by creating a mod that changes the speed of the day/night cycle. -Let's being by finding where the time is updated. Thankfully in the save editor we have a slider already hooked up to the time of day so we can check there for reference. The save editor file is at `soh/soh/Enhancements/debugger/debugSaveEditor.cpp`, if we do a quick search within that file for time we will find the following at line 400: +Let's begin by finding where the time is updated. Thankfully in the save editor we have a slider already hooked up to the time of day so we can check there for reference. The save editor file is at `soh/soh/Enhancements/debugger/debugSaveEditor.cpp`, if we do a quick search within that file for time we will find the following at around line 217: ```cpp -const uint16_t dayTimeMin = 0; -const uint16_t dayTimeMax = 0xFFFF; -ImGui::SliderScalar("Time", ImGuiDataType_U16, &gSaveContext.dayTime, &dayTimeMin, &dayTimeMax); + SliderInt("Time", (int32_t*)&gSaveContext.dayTime, intSliderOptionsBase.Min(0).Max(0xFFFF).Tooltip("Time of day")); + if (Button("Dawn", buttonOptionsBase)) { + gSaveContext.dayTime = 0x4000; + } + ImGui::SameLine(); + if (Button("Noon", buttonOptionsBase)) { + gSaveContext.dayTime = 0x8000; + } + ImGui::SameLine(); + if (Button("Sunset", buttonOptionsBase)) { + gSaveContext.dayTime = 0xC001; + } + ImGui::SameLine(); + if (Button("Midnight", buttonOptionsBase)) { + gSaveContext.dayTime = 0; + } ``` -So this tells us that `gSaveContext.dayTime` is what we're looking for. Let's now do a global search for this to see if we can find where it is updated. We find the following in `soh/src/code/z_kankyo.c` line 925: +So this tells us that `gSaveContext.dayTime` is what we're looking for. Let's now do a global search for this to see if we can find where it is updated. We find the following in `soh/src/code/z_kankyo.c` around line 925: ```cpp if (IS_DAY || gTimeIncrement >= 0x190) { @@ -71,16 +84,19 @@ if (IS_DAY || gTimeIncrement >= 0x190) { } ``` -Rebuild the game and launch it, then load a save file. You should see that the time of day is now moving much faster. Terrific! While we could wrap this up and call it a day, we could make this user configurable by making a few more changes. I think a slider would be good for this, there's a slider in the cheat menu that we can use as a reference. Let's find it in `soh/soh/SohMenuBar.cpp` around line 1120: +Rebuild the game and launch it, then load a save file. You should see that the time of day is now moving much faster. Terrific! While we could wrap this up and call it a day, we could make this user configurable by making a few more changes. I think a slider would be good for this, there's a slider in the cheat menu that we can use as a reference. Let's find it in `soh/soh/SohGui/SohMenuEnhancements.cpp` around line 1565: ```cpp -UIWidgets::EnhancementSliderFloat("Hookshot Reach Multiplier: %.1fx", "##gCheatHookshotReachMultiplier", "gCheatHookshotReachMultiplier", 1.0f, 5.0f, "", 1.0f, false); + AddWidget(path, "Hookshot Reach Multiplier: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_CHEAT("HookshotReachMultiplier")) + .Options(FloatSliderOptions().Format("%.2f").Min(1.0f).Max(5.0f)); ``` - -The float values being passed in here are `minimum`, `maximum`, and `default` respectively. We'll make our minimum 0.2 to allow it to move slower, and our maximum 5.0 to allow it to move up to 5x faster. We'll also set the default to 1.0 so that it doesn't change the behavior by default. Copy this line and paste it below, then make the relevant changes: +This adds a `Widget` which sets a CVar, which then sets the options of the slider. We'll make our minimum 0.2 to allow it to move slower, and our maximum 5.0 to allow it to move up to 5x faster. We'll also set the default to 1.0 so that it doesn't change the behavior by default. Copy this line and paste it below, then make the relevant changes: ```cpp -UIWidgets::EnhancementSliderFloat("Time Multiplier: %.1fx", "##gCheatTimeMultiplier", "gCheatTimeMultiplier", 0.2f, 5.0f, "", 1.0f, false); + AddWidget(path, "Time Multiplier: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_CHEAT("TimeOfDayMultiplier")) + .Options(FloatSliderOptions().Format("%.2f").Min(0.2f).Max(5.0f).DefaultValue(1.0f)); ``` Now we need to replace our hard coded values with the new variable. We can do this by replacing the `10` with a cvar call @@ -88,10 +104,10 @@ Now we need to replace our hard coded values with the new variable. We can do th ```diff if (IS_DAY || gTimeIncrement >= 0x190) { - gSaveContext.dayTime += gTimeIncrement * 10; -+ gSaveContext.dayTime += gTimeIncrement * CVarGetFloat("gCheatTimeMultiplier", 1.0f); ++ gSaveContext.dayTime += gTimeIncrement * CVarGetFloat(CVAR_CHEAT("TimeOfDayMultiplier"),1.0f); } else { - gSaveContext.dayTime += gTimeIncrement * 2 * 10; -+ gSaveContext.dayTime += gTimeIncrement * 2 * CVarGetFloat("gCheatTimeMultiplier", 1.0f); ++ gSaveContext.dayTime += gTimeIncrement * 2 * CVarGetFloat(CVAR_CHEAT("TimeOfDayMultiplier"),1.0f); } ``` diff --git a/soh/include/z64actor.h b/soh/include/z64actor.h index 6d8fd3ca0..77172bda4 100644 --- a/soh/include/z64actor.h +++ b/soh/include/z64actor.h @@ -266,9 +266,6 @@ typedef struct Actor { /* 0x134 */ ActorFunc draw; // Draw Routine. Called by `Actor_Draw` /* 0x138 */ ActorResetFunc reset; /* 0x13C */ char dbgPad[0x10]; // Padding that only exists in the debug rom - // #region SOH [General] - /* */ u8 maximumHealth; // Max health value for use with health bars, set on actor init - // #endregion } Actor; // size = 0x14C typedef enum { diff --git a/soh/soh/Enhancements/CuccosToReturn.cpp b/soh/soh/Enhancements/CuccosToReturn.cpp new file mode 100644 index 000000000..317437216 --- /dev/null +++ b/soh/soh/Enhancements/CuccosToReturn.cpp @@ -0,0 +1,19 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" +#include "soh/Enhancements/randomizer/context.h" + +extern "C" { +extern PlayState* gPlayState; +#include "src/overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.h" +} + +void RegisterCuccosToReturn() { + COND_VB_SHOULD(VB_SET_CUCCO_COUNT, CVarGetInteger(CVAR_ENHANCEMENT("CuccosToReturn"), 7) != 7, { + EnNiwLady* enNiwLady = va_arg(args, EnNiwLady*); + // Override starting Cucco count using setting value + enNiwLady->cuccosInPen = 7 - CVarGetInteger(CVAR_ENHANCEMENT("CuccosToReturn"), 7); + *should = false; + }); +} + +static RegisterShipInitFunc initFunc(RegisterCuccosToReturn, { CVAR_ENHANCEMENT("CuccosToReturn") }); diff --git a/soh/soh/Enhancements/Presets/Presets.cpp b/soh/soh/Enhancements/Presets/Presets.cpp index d50b45360..b84cb95ca 100644 --- a/soh/soh/Enhancements/Presets/Presets.cpp +++ b/soh/soh/Enhancements/Presets/Presets.cpp @@ -110,6 +110,7 @@ void applyPreset(std::string presetName, std::vector includeSecti } } } + ShipInit::InitAll(); } void DrawPresetSelector(std::vector includeSections, std::string presetLoc, bool disabled) { diff --git a/soh/soh/Enhancements/SkipAmyPuzzle.cpp b/soh/soh/Enhancements/SkipAmyPuzzle.cpp new file mode 100644 index 000000000..27486e04c --- /dev/null +++ b/soh/soh/Enhancements/SkipAmyPuzzle.cpp @@ -0,0 +1,8 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" + +void RegisterSkipAmyPuzzle() { + COND_VB_SHOULD(VB_AMY_SOLVE, CVarGetInteger(CVAR_ENHANCEMENT("SkipAmyPuzzle"), 0), { *should = true; }); +} + +static RegisterShipInitFunc initFunc(RegisterSkipAmyPuzzle, { CVAR_ENHANCEMENT("SkipAmyPuzzle") }); \ No newline at end of file diff --git a/soh/soh/Enhancements/SwitchTimerMultiplier.cpp b/soh/soh/Enhancements/SwitchTimerMultiplier.cpp new file mode 100644 index 000000000..94982837d --- /dev/null +++ b/soh/soh/Enhancements/SwitchTimerMultiplier.cpp @@ -0,0 +1,29 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +extern PlayState* gPlayState; +} + +void RegisterSwitchTimerMultiplier() { + COND_VB_SHOULD(VB_SWITCH_TIMER_TICK, CVarGetInteger(CVAR_ENHANCEMENT("SwitchTimerMultiplier"), 0) != 0, { + int multiplier = CVarGetInteger(CVAR_ENHANCEMENT("SwitchTimerMultiplier"), 0); + if (multiplier != 0) { + Actor* actor = va_arg(args, Actor*); + if (multiplier < -3 && actor->id == ACTOR_OBJ_SYOKUDAI) { + multiplier = -3; + } else if (multiplier < -4 && actor->id == ACTOR_BG_GND_DARKMEIRO) { + multiplier = -4; + } + + if (multiplier > 0 && gPlayState->gameplayFrames % (multiplier + 1) != 0) { + *should = false; + } else if (gPlayState->gameplayFrames % (6 + multiplier) == 0) { + s16* timer = va_arg(args, s16*); + *timer -= *timer > 1; + } + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterSwitchTimerMultiplier, { CVAR_ENHANCEMENT("SwitchTimerMultiplier") }); diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp index 674adcdfb..e15b8abd8 100644 --- a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp +++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp @@ -10,8 +10,6 @@ extern "C" { #include "variables.h" } -#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get() - static bool sEnteredBlueWarp = false; /** diff --git a/soh/soh/Enhancements/TreesDropSticks.cpp b/soh/soh/Enhancements/TreesDropSticks.cpp new file mode 100644 index 000000000..7fdc4eb0a --- /dev/null +++ b/soh/soh/Enhancements/TreesDropSticks.cpp @@ -0,0 +1,31 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "src/overlays/actors/ovl_En_Wood02/z_en_wood02.h" +} + +extern PlayState* gPlayState; + +void RegisterTreesDropSticks() { + COND_VB_SHOULD(VB_TREE_DROP_COLLECTIBLE, CVarGetInteger(CVAR_ENHANCEMENT("TreesDropSticks"), 0), { + if (INV_CONTENT(ITEM_STICK) != ITEM_NONE) { + EnWood02* tree = va_arg(args, EnWood02*); + Vec3f dropsSpawnPt = tree->actor.world.pos; + dropsSpawnPt.y += 200.0f; + + *should = false; + for (s32 numDrops = Rand_Next() % 4; numDrops > 0; numDrops--) { + Item_DropCollectible(gPlayState, &dropsSpawnPt, ITEM00_STICK); + } + } + }); + + COND_VB_SHOULD(VB_PREVENT_ADULT_STICK, CVarGetInteger(CVAR_ENHANCEMENT("TreesDropSticks"), 0), { + if (INV_CONTENT(ITEM_STICK) != ITEM_NONE) { + *should = false; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterTreesDropSticks, { CVAR_ENHANCEMENT("TreesDropSticks") }); diff --git a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h index 3bcbaf7b0..b5bc0fd6e 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h +++ b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h @@ -163,6 +163,7 @@ typedef enum { TEXT_ANJU_ROUND_THEM_UP_OR_YOULL_PAY = 0x503C, TEXT_ANJU_DONT_TEASE_MY_CUCCOS = 0x503D, TEXT_BIG_POE_COLLECTED_RANDO = 0x5090, + TEXT_GERUDO_GUARD_FRIENDLY = 0x6005, TEXT_HBA_NOT_ON_HORSE = 0x603F, TEXT_HBA_INITIAL_EXPLAINATION = 0x6040, TEXT_HBA_WANT_TO_TRY_AGAIN_YES_NO = 0x6041, diff --git a/soh/soh/Enhancements/debugconsole.cpp b/soh/soh/Enhancements/debugconsole.cpp index db5cc824f..d25c3cfff 100644 --- a/soh/soh/Enhancements/debugconsole.cpp +++ b/soh/soh/Enhancements/debugconsole.cpp @@ -11,6 +11,7 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/cosmetics/CosmeticsEditor.h" #include "soh/Enhancements/audio/AudioEditor.h" +#include "soh/Enhancements/randomizer/logic.h" #define Path _Path #define PATH_HACK @@ -1450,6 +1451,55 @@ static bool SfxHandler(std::shared_ptr Console, const std::vector return 0; } +static bool AvailableChecksProcessUndiscoveredExitsHandler(std::shared_ptr Console, + const std::vector& args, std::string* output) { + const auto& logic = Rando::Context::GetInstance()->GetLogic(); + bool enabled = false; + + if (args.size() == 1) { + enabled = !logic->ACProcessUndiscoveredExits; + } else { + try { + enabled = std::stoi(args[1]); + } catch (std::invalid_argument const& ex) { + ERROR_MESSAGE("[SOH] Enable should be 0 or 1"); + return 1; + } + } + + logic->ACProcessUndiscoveredExits = enabled; + INFO_MESSAGE("[SOH] Available Checks - Process Undiscovered Exits %s", + logic->ACProcessUndiscoveredExits ? "enabled" : "disabled"); + + if (GameInteractor::IsSaveLoaded(true)) { + CheckTracker::RecalculateAvailableChecks(); + } + + return 0; +} + +static bool AvailableChecksRecalculateHandler(std::shared_ptr Console, + const std::vector& args, std::string* output) { + RandomizerRegion startingRegion = RR_ROOT; + + if (args.size() > 1) { + try { + startingRegion = static_cast(std::stoi(args[1])); + } catch (std::invalid_argument const& ex) { + ERROR_MESSAGE("[SOH] Region should be a number"); + return 1; + } + + if (startingRegion <= RR_NONE || startingRegion >= RR_MAX) { + ERROR_MESSAGE("[SOH] Region should be between 1 and %d", RR_MAX - 1); + return 1; + } + } + + CheckTracker::RecalculateAvailableChecks(startingRegion); + return 0; +} + void DebugConsole_Init(void) { // Console CMD_REGISTER("file_select", { FileSelectHandler, "Returns to the file select." }); @@ -1708,5 +1758,15 @@ void DebugConsole_Init(void) { { "group_name", Ship::ArgumentType::TEXT, true }, } }); + CMD_REGISTER("acpue", { AvailableChecksProcessUndiscoveredExitsHandler, + "Available Checks - Process Undiscovered Exits", + { { "enable", Ship::ArgumentType::NUMBER, true } } }); + + CMD_REGISTER("acr", { AvailableChecksRecalculateHandler, + "Available Checks - Recalculate", + { + { "starting_region", Ship::ArgumentType::NUMBER, true }, + } }); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } diff --git a/soh/soh/Enhancements/enemyrandomizer.cpp b/soh/soh/Enhancements/enemyrandomizer.cpp index 84e1d040e..2e15a9ed9 100644 --- a/soh/soh/Enhancements/enemyrandomizer.cpp +++ b/soh/soh/Enhancements/enemyrandomizer.cpp @@ -11,17 +11,32 @@ extern "C" { #include +#include "src/overlays/actors/ovl_En_Rr/z_en_rr.h" } -const char* enemyCVarList[] = { - CVAR_ENHANCEMENT("RandomizedEnemyList.Armos"), CVAR_ENHANCEMENT("RandomizedEnemyList.Arwing"), - CVAR_ENHANCEMENT("RandomizedEnemyList.BabyDodongo"), CVAR_ENHANCEMENT("RandomizedEnemyList.Bari"), - CVAR_ENHANCEMENT("RandomizedEnemyList.Beamos"), CVAR_ENHANCEMENT("RandomizedEnemyList.BigSkulltula"), - CVAR_ENHANCEMENT("RandomizedEnemyList.BigStalchild"), CVAR_ENHANCEMENT("RandomizedEnemyList.Biri"), - CVAR_ENHANCEMENT("RandomizedEnemyList.BlackKnuckle"), CVAR_ENHANCEMENT("RandomizedEnemyList.BlueTektite"), - CVAR_ENHANCEMENT("RandomizedEnemyList.Bubble"), CVAR_ENHANCEMENT("RandomizedEnemyList.ClubMoblin"), - CVAR_ENHANCEMENT("RandomizedEnemyList.DarkLink"), CVAR_ENHANCEMENT("RandomizedEnemyList.Dinolfos"), - CVAR_ENHANCEMENT("RandomizedEnemyList.Dodongo"), CVAR_ENHANCEMENT("RandomizedEnemyList.FireKeese"), +#define CVAR_ENEMY_RANDOMIZER_NAME CVAR_ENHANCEMENT("RandomizedEnemies") +#define CVAR_ENEMY_RANDOMIZER_DEFAULT ENEMY_RANDOMIZER_OFF +#define CVAR_ENEMY_RANDOMIZER_VALUE CVarGetInteger(CVAR_ENEMY_RANDOMIZER_NAME, CVAR_ENEMY_RANDOMIZER_DEFAULT) + +typedef struct EnemyEntry { + int16_t id; + int16_t params; +} EnemyEntry; + +bool IsEnemyFoundToRandomize(int16_t sceneNum, int8_t roomNum, int16_t actorId, int16_t params, float posX); +bool IsEnemyAllowedToSpawn(int16_t sceneNum, int8_t roomNum, EnemyEntry enemy); +EnemyEntry GetRandomizedEnemyEntry(uint32_t seed, PlayState* play); + +const char* enemyCVarList[RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE] = { + CVAR_ENHANCEMENT("RandomizedEnemyList.Anubis"), CVAR_ENHANCEMENT("RandomizedEnemyList.Armos"), + CVAR_ENHANCEMENT("RandomizedEnemyList.Arwing"), CVAR_ENHANCEMENT("RandomizedEnemyList.BabyDodongo"), + CVAR_ENHANCEMENT("RandomizedEnemyList.Bari"), CVAR_ENHANCEMENT("RandomizedEnemyList.Beamos"), + CVAR_ENHANCEMENT("RandomizedEnemyList.BigSkulltula"), CVAR_ENHANCEMENT("RandomizedEnemyList.BigStalchild"), + CVAR_ENHANCEMENT("RandomizedEnemyList.Biri"), CVAR_ENHANCEMENT("RandomizedEnemyList.BlackKnuckle"), + CVAR_ENHANCEMENT("RandomizedEnemyList.BlueTektite"), CVAR_ENHANCEMENT("RandomizedEnemyList.Bubble"), + CVAR_ENHANCEMENT("RandomizedEnemyList.ClubMoblin"), CVAR_ENHANCEMENT("RandomizedEnemyList.DarkLink"), + CVAR_ENHANCEMENT("RandomizedEnemyList.Dinolfos"), CVAR_ENHANCEMENT("RandomizedEnemyList.Dodongo"), + CVAR_ENHANCEMENT("RandomizedEnemyList.FireKeese"), /*CVAR_ENHANCEMENT("RandomizedEnemyList.FlareDancer"),*/ CVAR_ENHANCEMENT("RandomizedEnemyList.FloorTile"), CVAR_ENHANCEMENT("RandomizedEnemyList.Floormaster"), CVAR_ENHANCEMENT("RandomizedEnemyList.FlyingPeahat"), CVAR_ENHANCEMENT("RandomizedEnemyList.FlyingPot"), CVAR_ENHANCEMENT("RandomizedEnemyList.Freezard"), CVAR_ENHANCEMENT("RandomizedEnemyList.Gibdo"), @@ -30,18 +45,20 @@ const char* enemyCVarList[] = { CVAR_ENHANCEMENT("RandomizedEnemyList.Keese"), CVAR_ENHANCEMENT("RandomizedEnemyList.LargeBaba"), CVAR_ENHANCEMENT("RandomizedEnemyList.LikeLike"), CVAR_ENHANCEMENT("RandomizedEnemyList.Lizalfos"), CVAR_ENHANCEMENT("RandomizedEnemyList.MadScrub"), CVAR_ENHANCEMENT("RandomizedEnemyList.NormalWolfos"), - CVAR_ENHANCEMENT("RandomizedEnemyList.PeahatLarva"), CVAR_ENHANCEMENT("RandomizedEnemyList.Redead"), - CVAR_ENHANCEMENT("RandomizedEnemyList.RedTektite"), CVAR_ENHANCEMENT("RandomizedEnemyList.Shabom"), - CVAR_ENHANCEMENT("RandomizedEnemyList.ShellBlade"), CVAR_ENHANCEMENT("RandomizedEnemyList.Skulltula"), + CVAR_ENHANCEMENT("RandomizedEnemyList.PeahatLarva"), /*CVAR_ENHANCEMENT("RandomizedEnemyList.Poe"),*/ + CVAR_ENHANCEMENT("RandomizedEnemyList.Redead"), CVAR_ENHANCEMENT("RandomizedEnemyList.RedTektite"), + CVAR_ENHANCEMENT("RandomizedEnemyList.Shabom"), CVAR_ENHANCEMENT("RandomizedEnemyList.ShellBlade"), + CVAR_ENHANCEMENT("RandomizedEnemyList.Skulltula"), CVAR_ENHANCEMENT("RandomizedEnemyList.SkullKid"), CVAR_ENHANCEMENT("RandomizedEnemyList.SmallBaba"), CVAR_ENHANCEMENT("RandomizedEnemyList.SmallStalchild"), - CVAR_ENHANCEMENT("RandomizedEnemyList.Spike"), CVAR_ENHANCEMENT("RandomizedEnemyList.Stalfos"), - CVAR_ENHANCEMENT("RandomizedEnemyList.Stinger"), CVAR_ENHANCEMENT("RandomizedEnemyList.Tailparasan"), - CVAR_ENHANCEMENT("RandomizedEnemyList.TorchSlug"), CVAR_ENHANCEMENT("RandomizedEnemyList.Wallmaster"), - CVAR_ENHANCEMENT("RandomizedEnemyList.WhiteKnuckle"), CVAR_ENHANCEMENT("RandomizedEnemyList.WhiteWolfos"), - CVAR_ENHANCEMENT("RandomizedEnemyList.WitheredBaba"), + CVAR_ENHANCEMENT("RandomizedEnemyList.SpearMoblin"), CVAR_ENHANCEMENT("RandomizedEnemyList.Spike"), + CVAR_ENHANCEMENT("RandomizedEnemyList.Stalfos"), CVAR_ENHANCEMENT("RandomizedEnemyList.Stinger"), + CVAR_ENHANCEMENT("RandomizedEnemyList.Tailparasan"), CVAR_ENHANCEMENT("RandomizedEnemyList.TorchSlug"), + CVAR_ENHANCEMENT("RandomizedEnemyList.Wallmaster"), CVAR_ENHANCEMENT("RandomizedEnemyList.WhiteKnuckle"), + CVAR_ENHANCEMENT("RandomizedEnemyList.WhiteWolfos"), CVAR_ENHANCEMENT("RandomizedEnemyList.WitheredBaba"), }; -const char* enemyNameList[] = { +const char* enemyNameList[RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE] = { + "Anubis", "Armos", "Arwing", "Baby Dodongo", @@ -58,6 +75,7 @@ const char* enemyNameList[] = { "Dinolfos", "Dodongo", "Fire Keese", + //"Flare Dancer", "Floor Tile", "Floormaster", "Flying Peahat", @@ -75,13 +93,16 @@ const char* enemyNameList[] = { "Mad Scrub", "Wolfos (Normal)", "Peahat Larva", + //"Poe", "Redead", "Red Tektite", "Shabom", "Shell Blade", "Skulltula", + "Skull Kid", "Small Deku Baba", "Stalchild (Small)", + "Spear Moblin", "Spike", "Stalfos", "Stinger", @@ -94,98 +115,105 @@ const char* enemyNameList[] = { }; static EnemyEntry randomizedEnemySpawnTable[RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE] = { - { ACTOR_EN_AM, -1 }, // Armos - { ACTOR_EN_CLEAR_TAG, 1 }, // Arwing - { ACTOR_EN_DODOJR, 0 }, // Baby Dodongo - { ACTOR_EN_VALI, -1 }, // Bari (big jellyfish) - { ACTOR_EN_VM, 1280 }, // Beamos - { ACTOR_EN_ST, 1 }, // Skulltula (big) - { ACTOR_EN_SKB, 20 }, // Stalchild (big) - { ACTOR_EN_BILI, 0 }, // Biri (jellyfish) - { ACTOR_EN_IK, 2 }, // Iron Knuckle (black, standing) - { ACTOR_EN_TITE, -2 }, // Tektite (blue) - { ACTOR_EN_BB, -1 }, // Bubble (flying skull enemy) (blue) - { ACTOR_EN_MB, 0 }, // Moblins (Club) - { ACTOR_EN_TORCH2, 0 }, // Dark Link - { ACTOR_EN_ZF, -2 }, // Dinolfos - { ACTOR_EN_DODONGO, -1 }, // Dodongo - { ACTOR_EN_FIREFLY, 1 }, // Fire Keese - { ACTOR_EN_YUKABYUN, 0 }, // Flying Floor Tile - { ACTOR_EN_FLOORMAS, 0 }, // Floormaster - { ACTOR_EN_PEEHAT, -1 }, // Flying Peahat (big grounded, doesn't spawn larva) - { ACTOR_EN_TUBO_TRAP, 0 }, // Flying pot - { ACTOR_EN_FZ, 0 }, // Freezard - { ACTOR_EN_RD, 32766 }, // Gibdo (standing) - { ACTOR_EN_GOMA, 7 }, // Gohma Larva (Non-Gohma rooms) - { ACTOR_EN_CROW, 0 }, // Guay - { ACTOR_EN_FIREFLY, 4 }, // Ice Keese - { ACTOR_EN_ST, 2 }, // Skulltula (invisible) - { ACTOR_EN_FIREFLY, 2 }, // Regular Keese - { ACTOR_EN_DEKUBABA, 1 }, // Deku Baba (large) + { ACTOR_EN_ANUBICE_TAG, 1 }, // Anubis + { ACTOR_EN_AM, -1 }, // Armos + { ACTOR_EN_CLEAR_TAG, 1 }, // Arwing + { ACTOR_EN_DODOJR, 0 }, // Baby Dodongo + { ACTOR_EN_VALI, -1 }, // Bari (big jellyfish) + { ACTOR_EN_VM, 1280 }, // Beamos + { ACTOR_EN_ST, 1 }, // Skulltula (big) + { ACTOR_EN_SKB, 20 }, // Stalchild (big) + { ACTOR_EN_BILI, 0 }, // Biri (jellyfish) + { ACTOR_EN_IK, 2 }, // Iron Knuckle (black, standing) + { ACTOR_EN_TITE, -2 }, // Tektite (blue) + { ACTOR_EN_BB, -1 }, // Bubble (flying skull enemy) (blue) + { ACTOR_EN_MB, 0 }, // Club Moblin + { ACTOR_EN_TORCH2, 0 }, // Dark Link + { ACTOR_EN_ZF, -2 }, // Dinolfos + { ACTOR_EN_DODONGO, -1 }, // Dodongo + { ACTOR_EN_FIREFLY, 1 }, // Fire Keese + // { ACTOR_EN_FD, 0 }, // Flare Dancer (possible cause of crashes because of spawning flame actors on + // sloped ground) + { ACTOR_EN_YUKABYUN, 0 }, // Flying Floor Tile + { ACTOR_EN_FLOORMAS, 0 }, // Floormaster + { ACTOR_EN_PEEHAT, -1 }, // Flying Peahat (big grounded, doesn't spawn larva) + { ACTOR_EN_TUBO_TRAP, 0 }, // Flying pot + { ACTOR_EN_FZ, 0 }, // Freezard + { ACTOR_EN_RD, 32766 }, // Gibdo (standing) + { ACTOR_EN_GOMA, 7 }, // Gohma Larva (Non-Gohma rooms) + { ACTOR_EN_CROW, 0 }, // Guay + { ACTOR_EN_FIREFLY, 4 }, // Ice Keese + { ACTOR_EN_ST, 2 }, // Skulltula (invisible) + { ACTOR_EN_FIREFLY, 2 }, // Regular Keese + { ACTOR_EN_DEKUBABA, 1 }, // Deku Baba (large) + // Doesn't work (reliant on surface and also normally used in tandem with a leever spawner, kills itself too quickly + // otherwise) { ACTOR_EN_REEBA, 0 }, // Leever { ACTOR_EN_RR, 0 }, // Like-Like { ACTOR_EN_ZF, -1 }, // Lizalfos { ACTOR_EN_DEKUNUTS, 768 }, // Mad Scrub (triple attack) (projectiles don't work) { ACTOR_EN_WF, 0 }, // Wolfos (normal) - { ACTOR_EN_PEEHAT, 1 }, // Flying Peahat Larva - { ACTOR_EN_RD, 1 }, // Redead (standing) - { ACTOR_EN_TITE, -1 }, // Tektite (red) - { ACTOR_EN_BUBBLE, 0 }, // Shabom (bubble) - { ACTOR_EN_SB, 0 }, // Shell Blade - { ACTOR_EN_ST, 0 }, // Skulltula (normal) - { ACTOR_EN_DEKUBABA, 0 }, // Deku Baba (small) - { ACTOR_EN_SKB, 1 }, // Stalchild (small) - { ACTOR_EN_NY, 0 }, // Spike (rolling enemy) - { ACTOR_EN_TEST, 2 }, // Stalfos - { ACTOR_EN_EIYER, 10 }, // Stinger (land) (One in formation, sink under floor and do not activate) - { ACTOR_EN_TP, -1 }, // Electric Tailpasaran - { ACTOR_EN_BW, 0 }, // Torch Slug - { ACTOR_EN_WALLMAS, 1 }, // Wallmaster - { ACTOR_EN_IK, 3 }, // Iron Knuckle (white, standing) - { ACTOR_EN_WF, 1 }, // Wolfos (white) - { ACTOR_EN_KAREBABA, 0 }, // Withered Deku Baba - - // Doesn't work {ACTOR_EN_POH, 0}, // Poe (Seems to rely on other objects?) - // Doesn't work {ACTOR_EN_POH, 2}, // Poe (composer Sharp) (Seems to rely on other objects?) - // Doesn't work {ACTOR_EN_POH, 3}, // Poe (composer Flat) (Seems to rely on other objects?) - // Doesn't work {ACTOR_EN_OKUTA, 0}, // Octorok (actor directly uses water box collision to handle hiding/popping - // up) Doesn't work {ACTOR_EN_REEBA, 0}, // Leever (reliant on surface and also normally used in tandem with a - // leever spawner, kills itself too quickly otherwise) Kinda doesn't work { ACTOR_EN_FD, 0 }, // Flare Dancer (jumps - // out of bounds a lot, and possible cause of crashes because of spawning a ton of flame actors) + // Doesn't work (actor directly uses water box collision to handle hiding/popping up) + // { ACTOR_EN_OKUTA, 0 }, // Octorok + { ACTOR_EN_PEEHAT, 1 }, // Flying Peahat Larva + // Doesn't work (Seems to rely on other objects?) + // { ACTOR_EN_POH, 0 }, // Poe + // Doesn't work (Seems to rely on other objects?) + // { ACTOR_EN_POH, 2 }, // Poe (composer Sharp) + // Doesn't work (Seems to rely on other objects?) + // { ACTOR_EN_POH, 3 }, // Poe (composer Flat) + { ACTOR_EN_RD, 1 }, // Redead (standing) + { ACTOR_EN_TITE, -1 }, // Tektite (red) + { ACTOR_EN_BUBBLE, 0 }, // Shabom (bubble) + { ACTOR_EN_SB, 0 }, // Shell Blade + { ACTOR_EN_ST, 0 }, // Skulltula (normal) + { ACTOR_EN_SKJ, 4159 }, // Skull Kid + { ACTOR_EN_DEKUBABA, 0 }, // Deku Baba (small) + { ACTOR_EN_SKB, 1 }, // Stalchild (small) + { ACTOR_EN_MB, -1 }, // Spear Moblin + { ACTOR_EN_NY, 0 }, // Spike (rolling enemy) + { ACTOR_EN_TEST, 2 }, // Stalfos + { ACTOR_EN_EIYER, 10 }, // Stinger (land) (One in formation, sink under floor and do not activate) + { ACTOR_EN_TP, -1 }, // Electric Tailpasaran + { ACTOR_EN_BW, 0 }, // Torch Slug + { ACTOR_EN_WALLMAS, 1 }, // Wallmaster + { ACTOR_EN_IK, 3 }, // Iron Knuckle (white, standing) + { ACTOR_EN_WF, 1 }, // Wolfos (white) + { ACTOR_EN_KAREBABA, 0 }, // Withered Deku Baba }; static int enemiesToRandomize[] = { - ACTOR_EN_FIREFLY, // Keese (including fire/ice) - ACTOR_EN_TEST, // Stalfos - ACTOR_EN_TITE, // Tektite - ACTOR_EN_POH, // Poe (normal, blue rupee, composers) - ACTOR_EN_OKUTA, // Octorok - ACTOR_EN_WALLMAS, // Wallmaster - ACTOR_EN_DODONGO, // Dodongo - // ACTOR_EN_REEBA, // Leever (reliant on spawner (z_e_encount1.c) - ACTOR_EN_PEEHAT, // Flying Peahat, big one spawning larva, larva - ACTOR_EN_ZF, // Lizalfos, Dinolfos - ACTOR_EN_GOMA, // Gohma Larva (normal, eggs, gohma eggs) - ACTOR_EN_BUBBLE, // Shabom (bubble) - ACTOR_EN_DODOJR, // Baby Dodongo - ACTOR_EN_TORCH2, // Dark Link - ACTOR_EN_BILI, // Biri (small jellyfish) - ACTOR_EN_TP, // Electric Tailpasaran - ACTOR_EN_ST, // Skulltula (normal, big, invisible) - ACTOR_EN_BW, // Torch Slug - ACTOR_EN_EIYER, // Stinger (land) - ACTOR_EN_MB, // Moblins (Club, spear) - ACTOR_EN_DEKUBABA, // Deku Baba (small, large) - ACTOR_EN_AM, // Armos (enemy variant) - ACTOR_EN_DEKUNUTS, // Mad Scrub (single attack, triple attack) - ACTOR_EN_VALI, // Bari (big jellyfish) (spawns very high up) - ACTOR_EN_BB, // Bubble (flying skull enemy) (all colors) - ACTOR_EN_YUKABYUN, // Flying Floor Tile - ACTOR_EN_VM, // Beamos - ACTOR_EN_FLOORMAS, // Floormaster - ACTOR_EN_RD, // Redead, Gibdo - ACTOR_EN_SW, // Skullwalltula - // ACTOR_EN_FD, // Flare Dancer (can be randomized, but not randomized to, so keeping it in vanilla locations - // means it at least shows up in the game) + ACTOR_EN_ANUBICE_TAG, // Anubis + ACTOR_EN_FIREFLY, // Keese (including fire/ice) + ACTOR_EN_TEST, // Stalfos + ACTOR_EN_TITE, // Tektite + ACTOR_EN_POH, // Poe (normal, blue rupee, composers) + ACTOR_EN_OKUTA, // Octorok + ACTOR_EN_WALLMAS, // Wallmaster + ACTOR_EN_DODONGO, // Dodongo + // ACTOR_EN_REEBA, // Leever (reliant on spawner (z_en_encount1.c)) + ACTOR_EN_PEEHAT, // Flying Peahat, big one spawning larva, larva + ACTOR_EN_ZF, // Lizalfos, Dinolfos + ACTOR_EN_GOMA, // Gohma Larva (normal, eggs, gohma eggs) + ACTOR_EN_BUBBLE, // Shabom (bubble) + ACTOR_EN_DODOJR, // Baby Dodongo + ACTOR_EN_TORCH2, // Dark Link + ACTOR_EN_BILI, // Biri (small jellyfish) + ACTOR_EN_TP, // Electric Tailpasaran + ACTOR_EN_ST, // Skulltula (normal, big, invisible) + ACTOR_EN_BW, // Torch Slug + ACTOR_EN_EIYER, // Stinger (land) + ACTOR_EN_MB, // Moblins (Club, spear) + ACTOR_EN_DEKUBABA, // Deku Baba (small, large) + ACTOR_EN_AM, // Armos (enemy variant) + ACTOR_EN_DEKUNUTS, // Mad Scrub (single attack, triple attack) + ACTOR_EN_VALI, // Bari (big jellyfish) (spawns very high up) + ACTOR_EN_BB, // Bubble (flying skull enemy) (all colors) + ACTOR_EN_YUKABYUN, // Flying Floor Tile + ACTOR_EN_VM, // Beamos + ACTOR_EN_FLOORMAS, // Floormaster + ACTOR_EN_RD, // Redead, Gibdo + ACTOR_EN_SW, // Skullwalltula + ACTOR_EN_FD, // Flare Dancer ACTOR_EN_SB, // Shell Blade ACTOR_EN_KAREBABA, // Withered Deku Baba ACTOR_EN_RR, // Like-Like @@ -198,6 +226,7 @@ static int enemiesToRandomize[] = { ACTOR_EN_WF, // Wolfos ACTOR_EN_SKB, // Stalchild ACTOR_EN_CROW, // Guay + ACTOR_EN_SKJ, // Skull Kid }; extern "C" uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, f32* posX, f32* posY, f32* posZ, int16_t* rotX, @@ -270,15 +299,7 @@ extern "C" uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, f32* po // Get randomized enemy ID and parameter. uint32_t seed = play->sceneNum + *actorId + (int)*posX + (int)*posY + (int)*posZ + *rotX + *rotY + *rotZ + *params; - EnemyEntry randomEnemy = GetRandomizedEnemyEntry(seed); - - int8_t timesRandomized = 1; - - // While randomized enemy isn't allowed in certain situations, randomize again. - while (!IsEnemyAllowedToSpawn(play->sceneNum, play->roomCtx.curRoom.num, randomEnemy)) { - randomEnemy = GetRandomizedEnemyEntry(seed + timesRandomized); - timesRandomized++; - } + EnemyEntry randomEnemy = GetRandomizedEnemyEntry(seed, play); *actorId = randomEnemy.id; *params = randomEnemy.params; @@ -322,7 +343,7 @@ static std::vector selectedEnemyList; void GetSelectedEnemies() { selectedEnemyList.clear(); - for (int i = 0; i < 49; i++) { + for (int i = 0; i < RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE; i++) { if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemyList.All"), 0)) { selectedEnemyList.push_back(randomizedEnemySpawnTable[i]); } else if (CVarGetInteger(enemyCVarList[i], 1)) { @@ -334,19 +355,28 @@ void GetSelectedEnemies() { } } -EnemyEntry GetRandomizedEnemyEntry(uint32_t seed) { +EnemyEntry GetRandomizedEnemyEntry(uint32_t seed, PlayState* play) { + std::vector filteredEnemyList = {}; if (selectedEnemyList.size() == 0) { GetSelectedEnemies(); } - if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), ENEMY_RANDOMIZER_OFF) == ENEMY_RANDOMIZER_RANDOM_SEEDED) { + for (EnemyEntry enemy : selectedEnemyList) { + if (IsEnemyAllowedToSpawn(play->sceneNum, play->roomCtx.curRoom.num, enemy)) { + filteredEnemyList.push_back(enemy); + } + } + if (filteredEnemyList.size() == 0) { + filteredEnemyList = selectedEnemyList; + } + if (CVAR_ENEMY_RANDOMIZER_VALUE == ENEMY_RANDOMIZER_RANDOM_SEEDED) { uint32_t finalSeed = seed + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : gSaveContext.ship.stats.fileCreatedAt); Random_Init(finalSeed); - uint32_t randomNumber = Random(0, selectedEnemyList.size()); - return selectedEnemyList[randomNumber]; + uint32_t randomNumber = Random(0, filteredEnemyList.size()); + return filteredEnemyList[randomNumber]; } else { - uint32_t randomSelectedEnemy = Random(0, selectedEnemyList.size()); - return selectedEnemyList[randomSelectedEnemy]; + uint32_t randomSelectedEnemy = Random(0, filteredEnemyList.size()); + return filteredEnemyList[randomSelectedEnemy]; } } @@ -408,6 +438,8 @@ bool IsEnemyFoundToRandomize(int16_t sceneNum, int8_t roomNum, int16_t actorId, case ACTOR_EN_SB: case ACTOR_EN_NY: return (!(!isMQ && sceneNum == SCENE_WATER_TEMPLE && roomNum == 2)); + case ACTOR_EN_SKJ: + return !(sceneNum == SCENE_LOST_WOODS && LINK_IS_CHILD); default: return 1; } @@ -419,19 +451,19 @@ bool IsEnemyFoundToRandomize(int16_t sceneNum, int8_t roomNum, int16_t actorId, } bool IsEnemyAllowedToSpawn(int16_t sceneNum, int8_t roomNum, EnemyEntry enemy) { - uint32_t isMQ = ResourceMgr_IsSceneMasterQuest(sceneNum); // Freezard - Child Link can only kill this with jump slash Deku Sticks or other equipment like bombs. // Beamos - Needs bombs. + // Anubis - Needs fire. // Shell Blade & Spike - Child Link can't kill these with sword or Deku Stick. - // Arwing & Dark Link - Both go out of bounds way too easily, softlocking the player. + // Flare dancer, Arwing & Dark Link - Both go out of bounds way too easily, softlocking the player. // Wallmaster - Not easily visible, often makes players think they're softlocked and that there's no enemies left. // Club Moblin - Many issues with them falling or placing out of bounds. Maybe fixable in the future? - bool enemiesToExcludeClearRooms = enemy.id == ACTOR_EN_FZ || enemy.id == ACTOR_EN_VM || enemy.id == ACTOR_EN_SB || - enemy.id == ACTOR_EN_NY || enemy.id == ACTOR_EN_CLEAR_TAG || - enemy.id == ACTOR_EN_WALLMAS || enemy.id == ACTOR_EN_TORCH2 || - enemy.id == ACTOR_EN_MB; + bool enemiesToExcludeClearRooms = + enemy.id == ACTOR_EN_FZ || enemy.id == ACTOR_EN_VM || enemy.id == ACTOR_EN_SB || enemy.id == ACTOR_EN_NY || + enemy.id == ACTOR_EN_CLEAR_TAG || enemy.id == ACTOR_EN_WALLMAS || enemy.id == ACTOR_EN_TORCH2 || + (enemy.id == ACTOR_EN_MB && enemy.params == 0) || enemy.id == ACTOR_EN_FD || enemy.id == ACTOR_EN_ANUBICE_TAG; // Bari - Spawns 3 more enemies, potentially extremely difficult in timed rooms. bool enemiesToExcludeTimedRooms = enemiesToExcludeClearRooms || enemy.id == ACTOR_EN_VALI; @@ -532,3 +564,53 @@ bool IsEnemyAllowedToSpawn(int16_t sceneNum, int8_t roomNum, EnemyEntry enemy) { return 1; } } + +void FixClubMoblinScale(void* ptr) { + Actor* actor = (Actor*)ptr; + if (actor->params == -1) { + Actor_SetScale(actor, 0.014f); + } +} + +void RegisterEnemyRandomizer() { + COND_ID_HOOK(OnActorInit, ACTOR_EN_MB, CVAR_ENEMY_RANDOMIZER_VALUE, FixClubMoblinScale); + // prevent dark link from triggering a voidout + COND_VB_SHOULD(VB_TRIGGER_VOIDOUT, CVAR_ENEMY_RANDOMIZER_VALUE != CVAR_ENEMY_RANDOMIZER_DEFAULT, { + Actor* actor = va_arg(args, Actor*); + + if (actor->category != ACTORCAT_PLAYER) { + *should = false; + Actor_Kill(actor); + } + }); + + // prevent dark link dealing fall damage to the player + COND_VB_SHOULD(VB_RECIEVE_FALL_DAMAGE, CVAR_ENEMY_RANDOMIZER_VALUE != CVAR_ENEMY_RANDOMIZER_DEFAULT, { + Actor* actor = va_arg(args, Actor*); + + if (actor->category != ACTORCAT_PLAYER) { + *should = false; + } + }); + + // prevent dark link from interfering with HESS/recoil/etc when at more than 100 away from him + COND_VB_SHOULD(VB_TORCH2_HANDLE_CLANKING, CVAR_ENEMY_RANDOMIZER_VALUE != CVAR_ENEMY_RANDOMIZER_DEFAULT, { + Actor* darkLink = va_arg(args, Actor*); + + if (darkLink->xzDistToPlayer > 100.0f) { + *should = false; + } + }); + + // prevent dark link from being grabbed by like likes and therefore grabbing the player + COND_VB_SHOULD(VB_LIKE_LIKE_GRAB_PLAYER, CVAR_ENEMY_RANDOMIZER_VALUE != CVAR_ENEMY_RANDOMIZER_DEFAULT, { + EnRr* likeLike = va_arg(args, EnRr*); + + if (!(likeLike->collider1.base.oc != NULL && likeLike->collider1.base.oc->category == ACTORCAT_PLAYER) && + !(likeLike->collider2.base.oc != NULL && likeLike->collider2.base.oc->category == ACTORCAT_PLAYER)) { + *should = false; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterEnemyRandomizer, { CVAR_ENEMY_RANDOMIZER_NAME }); \ No newline at end of file diff --git a/soh/soh/Enhancements/enemyrandomizer.h b/soh/soh/Enhancements/enemyrandomizer.h index 4c4fd5576..9220f2db0 100644 --- a/soh/soh/Enhancements/enemyrandomizer.h +++ b/soh/soh/Enhancements/enemyrandomizer.h @@ -1,23 +1,17 @@ #pragma once -#include +#include +#include "item-tables/ItemTableTypes.h" -typedef struct EnemyEntry { - int16_t id; - int16_t params; -} EnemyEntry; - -#define RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE 49 - -bool IsEnemyFoundToRandomize(int16_t sceneNum, int8_t roomNum, int16_t actorId, int16_t params, float posX); -bool IsEnemyAllowedToSpawn(int16_t sceneNum, int8_t roomNum, EnemyEntry enemy); -EnemyEntry GetRandomizedEnemyEntry(uint32_t seed); +#define RANDOMIZED_ENEMY_SPAWN_TABLE_SIZE 52 extern const char* enemyCVarList[]; extern const char* enemyNameList[]; extern void GetSelectedEnemies(); #ifndef __cplusplus -uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, f32* posX, f32* posY, f32* posZ, int16_t* rotX, +struct PlayState; + +uint8_t GetRandomizedEnemy(struct PlayState* play, int16_t* actorId, f32* posX, f32* posY, f32* posZ, int16_t* rotX, int16_t* rotY, int16_t* rotZ, int16_t* params); #endif diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index c2b8c1756..f4c05f98c 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -20,6 +20,14 @@ typedef enum { // - `int32_t` (entrance index) (promoted from `uint16_t` by va_arg) VB_ALLOW_ENTRANCE_CS_FOR_EITHER_AGE, + // #### `result` + // ```c + // sBgPoEventPuzzleState == 0xF + // ``` + // #### `args` + // - None + VB_AMY_SOLVE, + // #### `result` // ```c // this->actor.textId == 0x401A @@ -567,6 +575,14 @@ typedef enum { // - None VB_GANON_HEAL_BEFORE_FIGHT, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnGe2` + VB_GERUDO_GUARD_SET_ACTION_AFTER_TALK, + // #### `result` // See logic in // ```c @@ -1689,6 +1705,15 @@ typedef enum { // - `*ObjTsubo` VB_POT_SETUP_DRAW, + // #### `result` + // ```c + // dropId == ITEM00_STICK + // ``` + // #### `args` + // - None + VB_PREVENT_ADULT_STICK, + + // #### `result` // #### `result` // ```c // true @@ -1975,6 +2000,15 @@ typedef enum { // - `*ShotSun` VB_SPAWN_SONG_FAIRY, + // #### `result` + // ```c + // varies, never set should to true + // ``` + // #### `args` + // - `*Actor` + // - `*s16` - timer value + VB_SWITCH_TIMER_TICK, + // #### `result` // ```c // (this->stateFlags1 & PLAYER_STATE1_CARRYING_ACTOR) && (this->heldActor != NULL) && @@ -2065,6 +2099,13 @@ typedef enum { VB_TRANSITION_TO_SAVE_SCREEN_ON_DEATH, // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnWood02` + VB_TREE_DROP_COLLECTIBLE, + // ```c // true // ``` @@ -2088,6 +2129,38 @@ typedef enum { // #### `args` // - `*EnWonderTalk2` VB_WONDER_TALK, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` + VB_TRIGGER_VOIDOUT, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` + VB_TORCH2_HANDLE_CLANKING, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` + VB_RECIEVE_FALL_DAMAGE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnRr` + VB_LIKE_LIKE_GRAB_PLAYER, } GIVanillaBehavior; #endif diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 64c4265d0..05e9d3d74 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -98,6 +98,13 @@ void SwitchAge() { gSaveContext.nextTransitionType = TRANS_TYPE_FADE_BLACK_FAST; gPlayState->linkAgeOnLoad ^= 1; + // Discover adult/child spawns + if (gPlayState->linkAgeOnLoad == LINK_AGE_ADULT) { + Entrance_SetEntranceDiscovered(ENTR_HYRULE_FIELD_10, false); + } else { + Entrance_SetEntranceDiscovered(ENTR_LINKS_HOUSE_CHILD_SPAWN, false); + } + static HOOK_ID hookId = 0; hookId = REGISTER_VB_SHOULD(VB_INFLICT_VOID_DAMAGE, { *should = false; diff --git a/soh/soh/Enhancements/randomizer/3drando/fill.cpp b/soh/soh/Enhancements/randomizer/3drando/fill.cpp index 9ed633472..2ac5a3592 100644 --- a/soh/soh/Enhancements/randomizer/3drando/fill.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/fill.cpp @@ -201,6 +201,13 @@ void ProcessExits(Region* region, GetAccessibleLocationsStruct& gals, Randomizer bool stopOnBeatable = false, bool addToPlaythrough = false) { auto ctx = Rando::Context::GetInstance(); for (auto& exit : region->exits) { + int16_t entranceIndex = exit.GetIndex(); + if (!logic->ACProcessUndiscoveredExits && logic->CalculatingAvailableChecks && + ctx->GetOption(RSK_SHUFFLE_ENTRANCES).Get() && exit.IsShuffled() && entranceIndex != -1 && + !Entrance_GetIsEntranceDiscovered(entranceIndex)) { + continue; + } + Region* exitRegion = exit.GetConnectedRegion(); // Update Time of Day Access for the exit if (UpdateToDAccess(&exit, exitRegion)) { @@ -412,18 +419,13 @@ bool AddCheckToLogic(LocationAccess& locPair, GetAccessibleLocationsStruct& gals Rando::ItemLocation* location = ctx->GetItemLocation(loc); RandomizerGet locItem = location->GetPlacedRandomizerGet(); - if (!location->IsAddedToPool() && locPair.ConditionsMet(parentRegion, gals.calculatingAvailableChecks)) { - if (gals.calculatingAvailableChecks) { - gals.accessibleLocations.push_back(loc); - StopPerformanceTimer(PT_LOCATION_LOGIC); - return false; - } - + if (!location->IsAddedToPool() && locPair.ConditionsMet(parentRegion, logic->CalculatingAvailableChecks)) { location->AddToPool(); - if (locItem == RG_NONE) { + if (locItem == RG_NONE || logic->CalculatingAvailableChecks) { gals.accessibleLocations.push_back(loc); // Empty location, consider for placement - } else { + } + if (locItem != RG_NONE) { // If ignore has a value, we want to check if the item location should be considered or not // This is necessary due to the below preprocessing for playthrough generation if (ignore != RG_NONE) { @@ -519,11 +521,32 @@ void ProcessRegion(Region* region, GetAccessibleLocationsStruct& gals, Randomize // Return any of the targetLocations that are accessible in logic std::vector ReachabilitySearch(const std::vector& targetLocations, RandomizerGet ignore /* = RG_NONE*/, - bool calculatingAvailableChecks /* = false */) { + bool calculatingAvailableChecks /* = false */, + RandomizerRegion startingRegion /* = RR_ROOT */) { auto ctx = Rando::Context::GetInstance(); GetAccessibleLocationsStruct gals(0); - gals.calculatingAvailableChecks = calculatingAvailableChecks; ResetLogic(ctx, gals, !calculatingAvailableChecks); + if (startingRegion != RR_ROOT) { + gals.regionPool.insert(gals.regionPool.begin(), startingRegion); + + const auto& region = RegionTable(startingRegion); + if (ctx->GetOption(RSK_SELECTED_STARTING_AGE).Is(RO_AGE_CHILD)) { + region->childDay = true; + } else { + region->adultDay = true; + } + if (region->timePass) { + if (ctx->GetOption(RSK_SELECTED_STARTING_AGE).Is(RO_AGE_CHILD)) { + region->childNight = true; + } else { + region->adultNight = true; + } + } + } + if (calculatingAvailableChecks) { + logic->Reset(false); + logic->CalculatingAvailableChecks = true; + } do { gals.InitLoop(); for (size_t i = 0; i < gals.regionPool.size(); i++) { @@ -1015,7 +1038,8 @@ static void RandomizeOwnDungeon(const Rando::DungeonInfo* dungeon) { // filter out locations that may be required to have songs placed at them dungeonLocations = FilterFromPool(dungeonLocations, [ctx](const auto loc) { - if (ctx->GetOption(RSK_SHUFFLE_SONGS).Is(RO_SONG_SHUFFLE_SONG_LOCATIONS)) { + if (ctx->GetOption(RSK_SHUFFLE_SONGS).Is(RO_SONG_SHUFFLE_SONG_LOCATIONS) || + ctx->GetOption(RSK_SHUFFLE_SONGS).Is(RO_SONG_SHUFFLE_OFF)) { return !(Rando::StaticData::GetLocation(loc)->GetRCType() == RCTYPE_SONG_LOCATION); } if (ctx->GetOption(RSK_SHUFFLE_SONGS).Is(RO_SONG_SHUFFLE_DUNGEON_REWARDS)) { @@ -1344,8 +1368,8 @@ int Fill() { StartPerformanceTimer(PT_LIMITED_CHECKS); // Then Place songs if song shuffle is set to specific locations - if (ctx->GetOption(RSK_SHUFFLE_SONGS).IsNot(RO_SONG_SHUFFLE_ANYWHERE)) { - + if (ctx->GetOption(RSK_SHUFFLE_SONGS).IsNot(RO_SONG_SHUFFLE_ANYWHERE) && + ctx->GetOption(RSK_SHUFFLE_SONGS).IsNot(RO_SONG_SHUFFLE_OFF)) { // Get each song std::vector songs = FilterAndEraseFromPool(ItemPool, [](const auto i) { return Rando::StaticData::RetrieveItem(i).GetItemType() == ITEMTYPE_SONG; diff --git a/soh/soh/Enhancements/randomizer/3drando/fill.hpp b/soh/soh/Enhancements/randomizer/3drando/fill.hpp index 484331104..0e032985e 100644 --- a/soh/soh/Enhancements/randomizer/3drando/fill.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/fill.hpp @@ -58,6 +58,7 @@ struct GetAccessibleLocationsStruct { void ClearProgress(); void VanillaFill(); int Fill(); +void SetAreas(); std::vector GetEmptyLocations(std::vector allowedLocations); @@ -65,8 +66,8 @@ void ProcessRegion(Region* region, GetAccessibleLocationsStruct& gals, Randomize bool stopOnBeatable = false, bool addToPlaythrough = false); std::vector ReachabilitySearch(const std::vector& allowedLocations, - RandomizerGet ignore = RG_NONE, - bool calculatingAvailableChecks = false); + RandomizerGet ignore = RG_NONE, bool calculatingAvailableChecks = false, + RandomizerRegion startingRegion = RR_ROOT); void GeneratePlaythrough(); diff --git a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp index 9a858e059..fbe800662 100644 --- a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp @@ -1133,11 +1133,26 @@ void GenerateItemPool() { } } - // add extra songs only if song shuffle is anywhere - AddItemsToPool(ItemPool, songList); - if (ctx->GetOption(RSK_SHUFFLE_SONGS).Is(RO_SONG_SHUFFLE_ANYWHERE) && - ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { - AddItemsToPool(PendingJunkPool, songList); + if (ctx->GetOption(RSK_SHUFFLE_SONGS).IsNot(RO_SONG_SHUFFLE_OFF)) { + AddItemsToPool(ItemPool, songList); + // add extra songs only if song shuffle is anywhere + if (ctx->GetOption(RSK_SHUFFLE_SONGS).Is(RO_SONG_SHUFFLE_ANYWHERE) && + ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { + AddItemsToPool(PendingJunkPool, songList); + } + } else { + ctx->PlaceItemInLocation(RC_SHEIK_IN_FOREST, RG_MINUET_OF_FOREST, false, true); + ctx->PlaceItemInLocation(RC_SHEIK_IN_CRATER, RG_BOLERO_OF_FIRE, false, true); + ctx->PlaceItemInLocation(RC_SHEIK_IN_ICE_CAVERN, RG_SERENADE_OF_WATER, false, true); + ctx->PlaceItemInLocation(RC_SHEIK_AT_COLOSSUS, RG_REQUIEM_OF_SPIRIT, false, true); + ctx->PlaceItemInLocation(RC_SHEIK_IN_KAKARIKO, RG_NOCTURNE_OF_SHADOW, false, true); + ctx->PlaceItemInLocation(RC_SHEIK_AT_TEMPLE, RG_PRELUDE_OF_LIGHT, false, true); + ctx->PlaceItemInLocation(RC_SONG_FROM_IMPA, RG_ZELDAS_LULLABY, false, true); + ctx->PlaceItemInLocation(RC_SONG_FROM_MALON, RG_EPONAS_SONG, false, true); + ctx->PlaceItemInLocation(RC_SONG_FROM_SARIA, RG_SARIAS_SONG, false, true); + ctx->PlaceItemInLocation(RC_SONG_FROM_ROYAL_FAMILYS_TOMB, RG_SUNS_SONG, false, true); + ctx->PlaceItemInLocation(RC_SONG_FROM_OCARINA_OF_TIME, RG_SONG_OF_TIME, false, true); + ctx->PlaceItemInLocation(RC_SONG_FROM_WINDMILL, RG_SONG_OF_STORMS, false, true); } /*For item pool generation, dungeon items are either placed in their vanilla diff --git a/soh/soh/Enhancements/randomizer/LockOverworldDoors.cpp b/soh/soh/Enhancements/randomizer/LockOverworldDoors.cpp index 642fa7f42..04e0e4ea5 100644 --- a/soh/soh/Enhancements/randomizer/LockOverworldDoors.cpp +++ b/soh/soh/Enhancements/randomizer/LockOverworldDoors.cpp @@ -9,8 +9,6 @@ extern PlayState* gPlayState; #include "src/overlays/actors/ovl_En_Door/z_en_door.h" } -#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get() - using SceneDoorParamsPair = std::pair; std::map lookupTable = { // clang-format off diff --git a/soh/soh/Enhancements/randomizer/ShuffleBeehives.cpp b/soh/soh/Enhancements/randomizer/ShuffleBeehives.cpp index 60969a0c0..03f6dfa65 100644 --- a/soh/soh/Enhancements/randomizer/ShuffleBeehives.cpp +++ b/soh/soh/Enhancements/randomizer/ShuffleBeehives.cpp @@ -11,8 +11,7 @@ extern void EnItem00_DrawRandomizedItem(EnItem00* enItem00, PlayState* play); void ObjComb_RandomizerChooseItemDrop(ObjComb* objComb, PlayState* play) { s16 params = objComb->actor.params & 0x1F; - if (Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_BEEHIVES).Get() && - !Flags_GetRandomizerInf(objComb->beehiveIdentity.randomizerInf)) { + if (RAND_GET_OPTION(RSK_SHUFFLE_BEEHIVES) && !Flags_GetRandomizerInf(objComb->beehiveIdentity.randomizerInf)) { EnItem00* item00 = (EnItem00*)Item_DropCollectible2(play, &objComb->actor.world.pos, ITEM00_SOH_DUMMY); item00->randoInf = objComb->beehiveIdentity.randomizerInf; item00->itemEntry = @@ -41,8 +40,7 @@ void ObjComb_RandomizerWait(ObjComb* objComb, PlayState* play) { s32 dmgFlags; objComb->unk_1B0 -= 50; - if (Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_BEEHIVES).Get() && - !Flags_GetRandomizerInf(objComb->beehiveIdentity.randomizerInf)) { + if (RAND_GET_OPTION(RSK_SHUFFLE_BEEHIVES) && !Flags_GetRandomizerInf(objComb->beehiveIdentity.randomizerInf)) { if (objComb->unk_1B0 <= -5000) { objComb->unk_1B0 = 1500; } @@ -85,7 +83,7 @@ void ObjComb_RandomizerUpdate(void* actor) { } void RegisterShuffleBeehives() { - bool shouldRegister = IS_RANDO && Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_BEEHIVES).Get(); + bool shouldRegister = IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_BEEHIVES); COND_ID_HOOK(OnActorInit, ACTOR_OBJ_COMB, shouldRegister, ObjComb_RandomizerInit); COND_ID_HOOK(OnActorUpdate, ACTOR_OBJ_COMB, shouldRegister, ObjComb_RandomizerUpdate); diff --git a/soh/soh/Enhancements/randomizer/ShuffleCows.cpp b/soh/soh/Enhancements/randomizer/ShuffleCows.cpp index ac2e9529e..17ae3a834 100644 --- a/soh/soh/Enhancements/randomizer/ShuffleCows.cpp +++ b/soh/soh/Enhancements/randomizer/ShuffleCows.cpp @@ -34,7 +34,7 @@ void EnCow_MoveForRandomizer(EnCow* enCow, PlayState* play) { } void RegisterShuffleCows() { - bool shouldRegister = IS_RANDO && Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_COWS).Get(); + bool shouldRegister = IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_COWS); COND_VB_SHOULD(VB_GIVE_ITEM_FROM_COW, shouldRegister, { EnCow* enCow = va_arg(args, EnCow*); diff --git a/soh/soh/Enhancements/randomizer/ShuffleCrates.cpp b/soh/soh/Enhancements/randomizer/ShuffleCrates.cpp index 343221d63..4c6ab0aa5 100644 --- a/soh/soh/Enhancements/randomizer/ShuffleCrates.cpp +++ b/soh/soh/Enhancements/randomizer/ShuffleCrates.cpp @@ -15,8 +15,6 @@ extern "C" { extern PlayState* gPlayState; } -#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get() - extern void EnItem00_DrawRandomizedItem(EnItem00* enItem00, PlayState* play); extern "C" void ObjKibako2_RandomizerDraw(Actor* thisx, PlayState* play) { @@ -158,7 +156,7 @@ extern "C" void ObjKibako_RandomizerDraw(Actor* thisx, PlayState* play) { uint8_t ObjKibako2_RandomizerHoldsItem(ObjKibako2* crateActor, PlayState* play) { RandomizerCheck rc = crateActor->crateIdentity.randomizerCheck; uint8_t isDungeon = Rando::StaticData::GetLocation(rc)->IsDungeon(); - uint8_t crateSetting = Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_CRATES).Get(); + uint8_t crateSetting = RAND_GET_OPTION(RSK_SHUFFLE_CRATES); // Don't pull randomized item if crate isn't randomized or is already checked if (!IS_RANDO || (crateSetting == RO_SHUFFLE_CRATES_OVERWORLD && isDungeon) || @@ -174,7 +172,7 @@ uint8_t ObjKibako2_RandomizerHoldsItem(ObjKibako2* crateActor, PlayState* play) uint8_t ObjKibako_RandomizerHoldsItem(ObjKibako* smallCrateActor, PlayState* play) { RandomizerCheck rc = smallCrateActor->smallCrateIdentity.randomizerCheck; uint8_t isDungeon = Rando::StaticData::GetLocation(rc)->IsDungeon(); - uint8_t crateSetting = Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_CRATES).Get(); + uint8_t crateSetting = RAND_GET_OPTION(RSK_SHUFFLE_CRATES); // Don't pull randomized item if crate isn't randomized or is already checked if (!IS_RANDO || (crateSetting == RO_SHUFFLE_CRATES_OVERWORLD && isDungeon) || @@ -211,7 +209,7 @@ void ObjKibako_RandomizerSpawnCollectible(ObjKibako* smallCrateActor, PlayState* void ObjKibako2_RandomizerInit(void* actorRef) { Actor* actor = static_cast(actorRef); - uint8_t logicSetting = Rando::Context::GetInstance()->GetOption(RSK_LOGIC_RULES).Get(); + uint8_t logicSetting = RAND_GET_OPTION(RSK_LOGIC_RULES); // don't shuffle two OOB crates in GF and don't shuffle child GV/GF crates when not in no logic if (actor->id != ACTOR_OBJ_KIBAKO2 || @@ -362,7 +360,7 @@ void Rando::StaticData::RegisterCrateLocations() { locationTable[RC_KAK_NEAR_IMPAS_HOUSE_ADULT_CRATE_1] = Location::Crate(RC_KAK_NEAR_IMPAS_HOUSE_ADULT_CRATE_1, RCQUEST_BOTH, RCAREA_KAKARIKO_VILLAGE, SCENE_KAKARIKO_VILLAGE, TWO_ACTOR_PARAMS(-389, 1518), "Near Impas House Adult Crate 1", RHT_CRATE_KAKARIKO_VILLAGE, RG_GREEN_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_KAK_NEAR_IMPAS_HOUSE_ADULT_CRATE_1)); locationTable[RC_KAK_NEAR_IMPAS_HOUSE_ADULT_CRATE_2] = Location::Crate(RC_KAK_NEAR_IMPAS_HOUSE_ADULT_CRATE_2, RCQUEST_BOTH, RCAREA_KAKARIKO_VILLAGE, SCENE_KAKARIKO_VILLAGE, TWO_ACTOR_PARAMS(-389, 1470), "Near Impas House Adult Crate 2", RHT_CRATE_KAKARIKO_VILLAGE, RG_GREEN_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_KAK_NEAR_IMPAS_HOUSE_ADULT_CRATE_2)); locationTable[RC_KAK_NEAR_BAZAAR_ADULT_CRATE_1] = Location::Crate(RC_KAK_NEAR_BAZAAR_ADULT_CRATE_1, RCQUEST_BOTH, RCAREA_KAKARIKO_VILLAGE, SCENE_KAKARIKO_VILLAGE, TWO_ACTOR_PARAMS(-433, -401), "Near Bazaar Adult Crate 1", RHT_CRATE_KAKARIKO_VILLAGE, RG_GREEN_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_KAK_NEAR_BAZAAR_ADULT_CRATE_1)); - locationTable[RC_KAK_NEAR_BAZAAR_ADULT_CRATE_2] = Location::Crate(RC_KAK_NEAR_BAZAAR_ADULT_CRATE_2, RCQUEST_BOTH, RCAREA_KAKARIKO_VILLAGE, SCENE_KAKARIKO_VILLAGE, TWO_ACTOR_PARAMS(-489, -424), "Near Bazaar Adult Crate 2`", RHT_CRATE_KAKARIKO_VILLAGE, RG_GREEN_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_KAK_NEAR_BAZAAR_ADULT_CRATE_2)); + locationTable[RC_KAK_NEAR_BAZAAR_ADULT_CRATE_2] = Location::Crate(RC_KAK_NEAR_BAZAAR_ADULT_CRATE_2, RCQUEST_BOTH, RCAREA_KAKARIKO_VILLAGE, SCENE_KAKARIKO_VILLAGE, TWO_ACTOR_PARAMS(-489, -424), "Near Bazaar Adult Crate 2", RHT_CRATE_KAKARIKO_VILLAGE, RG_GREEN_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_KAK_NEAR_BAZAAR_ADULT_CRATE_2)); locationTable[RC_KAK_BEHIND_GS_HOUSE_ADULT_CRATE] = Location::Crate(RC_KAK_BEHIND_GS_HOUSE_ADULT_CRATE, RCQUEST_BOTH, RCAREA_KAKARIKO_VILLAGE, SCENE_KAKARIKO_VILLAGE, TWO_ACTOR_PARAMS(-724, 871), "Behind GS House Adult Crate", RHT_CRATE_KAKARIKO_VILLAGE, RG_GREEN_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_KAK_BEHIND_GS_HOUSE_ADULT_CRATE)); locationTable[RC_KAK_NEAR_GY_CHILD_CRATE] = Location::Crate(RC_KAK_NEAR_GY_CHILD_CRATE, RCQUEST_BOTH, RCAREA_KAKARIKO_VILLAGE, SCENE_KAKARIKO_VILLAGE, TWO_ACTOR_PARAMS(1732, 1366), "Near Graveyard Child Crate", RHT_CRATE_KAKARIKO_VILLAGE, RG_GREEN_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_KAK_NEAR_GY_CHILD_CRATE)); locationTable[RC_KAK_NEAR_WINDMILL_CHILD_CRATE] = Location::Crate(RC_KAK_NEAR_WINDMILL_CHILD_CRATE, RCQUEST_BOTH, RCAREA_KAKARIKO_VILLAGE, SCENE_KAKARIKO_VILLAGE, TWO_ACTOR_PARAMS(1170, 601), "Near Windmill Child Crate", RHT_CRATE_KAKARIKO_VILLAGE, RG_GREEN_RUPEE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_KAK_NEAR_WINDMILL_CHILD_CRATE)); diff --git a/soh/soh/Enhancements/randomizer/ShuffleFreestanding.cpp b/soh/soh/Enhancements/randomizer/ShuffleFreestanding.cpp index 03fd33981..fbd459458 100644 --- a/soh/soh/Enhancements/randomizer/ShuffleFreestanding.cpp +++ b/soh/soh/Enhancements/randomizer/ShuffleFreestanding.cpp @@ -23,7 +23,7 @@ void ShuffleFreestanding_OnVanillaBehaviorHandler(GIVanillaBehavior id, bool* sh Rando::Location* loc = OTRGlobals::Instance->gRandomizer->GetCheckObjectFromActor(item00->actor.id, gPlayState->sceneNum, params); uint8_t isDungeon = loc->IsDungeon(); - uint8_t freestandingSetting = Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_FREESTANDING).Get(); + uint8_t freestandingSetting = RAND_GET_OPTION(RSK_SHUFFLE_FREESTANDING); RandomizerCheck randomizerCheck = loc->GetRandomizerCheck(); bool checkObtained = Rando::Context::GetInstance()->GetItemLocation(randomizerCheck)->HasObtained(); diff --git a/soh/soh/Enhancements/randomizer/ShuffleGrass.cpp b/soh/soh/Enhancements/randomizer/ShuffleGrass.cpp index 1aa0970d2..28f06e9d4 100644 --- a/soh/soh/Enhancements/randomizer/ShuffleGrass.cpp +++ b/soh/soh/Enhancements/randomizer/ShuffleGrass.cpp @@ -11,8 +11,6 @@ extern "C" { extern PlayState* gPlayState; } -#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get() - extern void EnItem00_DrawRandomizedItem(EnItem00* enItem00, PlayState* play); void DrawTypeOfGrass(EnKusa* grassActor, Gfx* bushDList, Gfx* grassDList, PlayState* play) { @@ -96,7 +94,7 @@ uint8_t EnKusa_RandomizerHoldsItem(EnKusa* grassActor, PlayState* play) { RandomizerCheck rc = grassActor->grassIdentity.randomizerCheck; uint8_t isDungeon = Rando::StaticData::GetLocation(rc)->IsDungeon(); - uint8_t grassSetting = Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_GRASS).Get(); + uint8_t grassSetting = RAND_GET_OPTION(RSK_SHUFFLE_GRASS); // Don't pull randomized item if grass isn't randomized or is already checked if (!IS_RANDO || (grassSetting == RO_SHUFFLE_GRASS_OVERWORLD && isDungeon) || diff --git a/soh/soh/Enhancements/randomizer/ShufflePots.cpp b/soh/soh/Enhancements/randomizer/ShufflePots.cpp index 2021d225a..319237b98 100644 --- a/soh/soh/Enhancements/randomizer/ShufflePots.cpp +++ b/soh/soh/Enhancements/randomizer/ShufflePots.cpp @@ -27,7 +27,7 @@ extern "C" void ObjTsubo_RandomizerDraw(Actor* thisx, PlayState* play) { uint8_t ObjTsubo_RandomizerHoldsItem(ObjTsubo* potActor, PlayState* play) { RandomizerCheck rc = potActor->potIdentity.randomizerCheck; uint8_t isDungeon = Rando::StaticData::GetLocation(rc)->IsDungeon(); - uint8_t potSetting = Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_POTS).Get(); + uint8_t potSetting = RAND_GET_OPTION(RSK_SHUFFLE_POTS); // Don't pull randomized item if pot isn't randomized or is already checked if (!IS_RANDO || (potSetting == RO_SHUFFLE_POTS_OVERWORLD && isDungeon) || @@ -85,7 +85,7 @@ void ShufflePots_OnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va // Unlock early Ganon's Boss Key doors to allow access to the pots there when pots are shuffled in dungeon if (id == VB_LOCK_BOSS_DOOR) { DoorShutter* doorActor = va_arg(args, DoorShutter*); - uint8_t shufflePotSetting = Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_POTS).Get(); + uint8_t shufflePotSetting = RAND_GET_OPTION(RSK_SHUFFLE_POTS); if (gPlayState->sceneNum == SCENE_GANONS_TOWER && doorActor->dyna.actor.world.pos.y == 800 && (shufflePotSetting == RO_SHUFFLE_POTS_DUNGEONS || shufflePotSetting == RO_SHUFFLE_POTS_ALL)) { *should = false; diff --git a/soh/soh/Enhancements/randomizer/ShuffleSongs.cpp b/soh/soh/Enhancements/randomizer/ShuffleSongs.cpp new file mode 100644 index 000000000..8bece13a1 --- /dev/null +++ b/soh/soh/Enhancements/randomizer/ShuffleSongs.cpp @@ -0,0 +1,26 @@ +#include "soh/ShipInit.hpp" +#include "location.h" +#include "static_data.h" + +void Rando::StaticData::RegisterSongLocations() { + static bool registered = false; + if (registered) + return; + registered = true; + // clang-format off + locationTable[RC_SHEIK_IN_FOREST] = Location::Base(RC_SHEIK_IN_FOREST, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_SACRED_FOREST_MEADOW, 0x00, "Sheik in Forest", "Sheik in Forest", RHT_SHEIK_IN_FOREST, RG_MINUET_OF_FOREST, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_MINUET_OF_FOREST), true); + locationTable[RC_SHEIK_IN_CRATER] = Location::Base(RC_SHEIK_IN_CRATER, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_DEATH_MOUNTAIN_CRATER, 0x00, "Sheik in Crater", "Sheik in Crater", RHT_SHEIK_IN_CRATER, RG_BOLERO_OF_FIRE, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_BOLERO_OF_FIRE), true); + locationTable[RC_SHEIK_IN_ICE_CAVERN] = Location::Base(RC_SHEIK_IN_ICE_CAVERN, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_ICE_CAVERN, 0x00, "Sheik in Ice Cavern", "Sheik in Ice Cavern", RHT_SHEIK_IN_ICE_CAVERN, RG_SERENADE_OF_WATER, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_SERENADE_OF_WATER), true); + locationTable[RC_SHEIK_AT_COLOSSUS] = Location::Base(RC_SHEIK_AT_COLOSSUS, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_DESERT_COLOSSUS, 0x00, "Sheik at Colossus", "Sheik at Colossus", RHT_SHEIK_AT_COLOSSUS, RG_REQUIEM_OF_SPIRIT, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_REQUIEM_OF_SPIRIT), true); + locationTable[RC_SHEIK_IN_KAKARIKO] = Location::Base(RC_SHEIK_IN_KAKARIKO, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_KAKARIKO_VILLAGE, 0x00, "Sheik in Kakariko", "Sheik in Kakariko", RHT_SHEIK_IN_KAKARIKO, RG_NOCTURNE_OF_SHADOW, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL), true); + locationTable[RC_SHEIK_AT_TEMPLE] = Location::Base(RC_SHEIK_AT_TEMPLE, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_TEMPLE_OF_TIME, 0x00, "Sheik at Temple", "Sheik at Temple", RHT_SHEIK_AT_TEMPLE, RG_PRELUDE_OF_LIGHT, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_PRELUDE_OF_LIGHT), true); + locationTable[RC_SONG_FROM_IMPA] = Location::Base(RC_SONG_FROM_IMPA, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, RCAREA_HYRULE_CASTLE, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, "Song from Impa", "Song from Impa", RHT_SONG_FROM_IMPA, RG_ZELDAS_LULLABY, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_ZELDAS_LULLABY), true); + locationTable[RC_SONG_FROM_MALON] = Location::Base(RC_SONG_FROM_MALON, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_LON_LON_RANCH, 0x00, "Song from Malon", "Song from Malon", RHT_SONG_FROM_MALON, RG_EPONAS_SONG, SpoilerCollectionCheck::RandomizerInf(RAND_INF_LEARNED_EPONA_SONG), true); + locationTable[RC_SONG_FROM_SARIA] = Location::Base(RC_SONG_FROM_SARIA, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_SACRED_FOREST_MEADOW, 0x00, "Song from Saria", "Song from Saria", RHT_SONG_FROM_SARIA, RG_SARIAS_SONG, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_SARIAS_SONG), true); + locationTable[RC_SONG_FROM_ROYAL_FAMILYS_TOMB] = Location::Base(RC_SONG_FROM_ROYAL_FAMILYS_TOMB, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_ROYAL_FAMILYS_TOMB, 0x00, "Song from Royal Family's Tomb", "Song from Royal Family's Tomb", RHT_SONG_FROM_ROYAL_FAMILYS_TOMB, RG_SUNS_SONG, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_SUNS_SONG), true); + locationTable[RC_SONG_FROM_OCARINA_OF_TIME] = Location::Base(RC_SONG_FROM_OCARINA_OF_TIME, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_HYRULE_FIELD, 0x00, "Song from Ocarina of Time", "Song from Ocarina of Time", RHT_SONG_FROM_OCARINA_OF_TIME, RG_SONG_OF_TIME, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_SONG_OF_TIME), true); + locationTable[RC_SONG_FROM_WINDMILL] = Location::Base(RC_SONG_FROM_WINDMILL, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, RCAREA_KAKARIKO_VILLAGE, ACTOR_ID_MAX, SCENE_WINDMILL_AND_DAMPES_GRAVE, 0x00, "Song from Windmill", "Song from Windmill", RHT_SONG_FROM_WINDMILL, RG_SONG_OF_STORMS, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_SONG_OF_STORMS), true); + // clang-format-on +} + +static RegisterShipInitFunc initSongLocations(Rando::StaticData::RegisterSongLocations); diff --git a/soh/soh/Enhancements/randomizer/context.cpp b/soh/soh/Enhancements/randomizer/context.cpp index e3a914e87..2ee39006d 100644 --- a/soh/soh/Enhancements/randomizer/context.cpp +++ b/soh/soh/Enhancements/randomizer/context.cpp @@ -11,6 +11,7 @@ #include "fishsanity.h" #include "macros.h" #include "3drando/hints.hpp" +#include "soh/util.h" #include "../kaleido.h" #include @@ -179,6 +180,7 @@ void Context::GenerateLocationPool() { location.GetRandomizerCheck() == RC_LW_DEKU_SCRUB_NEAR_BRIDGE || location.GetRandomizerCheck() == RC_HF_DEKU_SCRUB_GROTTO)) || (location.GetRCType() == RCTYPE_ADULT_TRADE && mOptions[RSK_SHUFFLE_ADULT_TRADE].Is(RO_GENERIC_OFF)) || + (location.GetRCType() == RCTYPE_SONG_LOCATION && mOptions[RSK_SHUFFLE_SONGS].Is(RO_SONG_SHUFFLE_OFF)) || (location.GetRCType() == RCTYPE_COW && mOptions[RSK_SHUFFLE_COWS].Is(RO_GENERIC_OFF)) || (location.GetRandomizerCheck() == RC_LH_HYRULE_LOACH && mOptions[RSK_FISHSANITY].IsNot(RO_FISHSANITY_HYRULE_LOACH)) || @@ -369,25 +371,8 @@ GetItemEntry Context::GetFinalGIEntry(const RandomizerCheck rc, const bool check return giEntry; } -std::string sanitize(std::string stringValue) { - // Add backslashes. - for (auto i = stringValue.begin();;) { - auto const pos = - std::find_if(i, stringValue.end(), [](char const c) { return '\\' == c || '\'' == c || '"' == c; }); - if (pos == stringValue.end()) { - break; - } - i = std::next(stringValue.insert(pos, '\\'), 2); - } - - // Removes others. - std::erase_if(stringValue, [](char const c) { return '\n' == c || '\r' == c || '\0' == c || '\x1A' == c; }); - - return stringValue; -} - void Context::ParseSpoiler(const char* spoilerFileName) { - std::ifstream spoilerFileStream(sanitize(spoilerFileName)); + std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName)); if (!spoilerFileStream) { return; } @@ -396,11 +381,13 @@ void Context::ParseSpoiler(const char* spoilerFileName) { try { nlohmann::json spoilerFileJson; spoilerFileStream >> spoilerFileJson; + spoilerFileStream.close(); ParseHashIconIndexesJson(spoilerFileJson); Rando::Settings::GetInstance()->ParseJson(spoilerFileJson); ParseItemLocationsJson(spoilerFileJson); - ParseHintJson(spoilerFileJson); + ParseTricksJson(spoilerFileJson); mEntranceShuffler->ParseJson(spoilerFileJson); + ParseHintJson(spoilerFileJson); mDungeons->ParseJson(spoilerFileJson); mTrials->ParseJson(spoilerFileJson); mSpoilerLoaded = true; @@ -468,6 +455,17 @@ void Context::ParseHintJson(nlohmann::json spoilerFileJson) { CreateStaticHints(); } +void Context::ParseTricksJson(nlohmann::json spoilerFileJson) { + nlohmann::json enabledTricksJson = spoilerFileJson["enabledTricks"]; + const auto& settings = Rando::Settings::GetInstance(); + for (auto it : enabledTricksJson) { + int rt = settings->GetRandomizerTrickByName(it); + if (rt != -1) { + mTrickOptions[rt].Set(RO_GENERIC_ON); + } + } +} + std::shared_ptr Context::GetEntranceShuffler() { return mEntranceShuffler; } @@ -523,6 +521,10 @@ RandoOptionLACSCondition Context::LACSCondition() const { return mLACSCondition; } +void Context::LACSCondition(RandoOptionLACSCondition lacsCondition) { + mLACSCondition = lacsCondition; +} + std::shared_ptr Context::GetKaleido() { if (mKaleido == nullptr) { mKaleido = std::make_shared(); diff --git a/soh/soh/Enhancements/randomizer/context.h b/soh/soh/Enhancements/randomizer/context.h index 7f18f7a9d..968947756 100644 --- a/soh/soh/Enhancements/randomizer/context.h +++ b/soh/soh/Enhancements/randomizer/context.h @@ -14,6 +14,8 @@ #include #include +#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get() + /** * @brief Singleton for storing and accessing dynamic Randomizer-related data * @@ -104,12 +106,22 @@ class Context { * @return RandoOptionLACSCondition */ RandoOptionLACSCondition LACSCondition() const; + + /** + * @brief Sets the resolved Light Arrow CutScene check condition. + * There is no direct option for this, it is inferred based on the value of a few other options. + * + * @param lacsCondition + */ + void LACSCondition(RandoOptionLACSCondition lacsCondition); + GetItemEntry GetFinalGIEntry(RandomizerCheck rc, bool checkObtainability = true, GetItemID ogItemId = GI_NONE); void ParseSpoiler(const char* spoilerFileName); void ParseHashIconIndexesJson(nlohmann::json spoilerFileJson); void ParseItemLocationsJson(nlohmann::json spoilerFileJson); void WriteHintJson(nlohmann::ordered_json& spoilerFileJson); void ParseHintJson(nlohmann::json spoilerFileJson); + void ParseTricksJson(nlohmann::json spoilerFileJson); std::map overrides = {}; std::vector> playthroughLocations = {}; std::vector everyPossibleLocation = {}; diff --git a/soh/soh/Enhancements/randomizer/entrance.cpp b/soh/soh/Enhancements/randomizer/entrance.cpp index 6a7a52691..52657a6ab 100644 --- a/soh/soh/Enhancements/randomizer/entrance.cpp +++ b/soh/soh/Enhancements/randomizer/entrance.cpp @@ -250,7 +250,319 @@ std::string EntranceNameByRegions(RandomizerRegion parentRegion, RandomizerRegio return RegionTable(parentRegion)->regionName + " -> " + RegionTable(connectedRegion)->regionName; } -void SetAllEntrancesData(std::vector& entranceShuffleTable) { +std::unordered_map entranceMap; + +void SetAllEntrancesData() { + std::vector entranceShuffleTable = { + // clang-format off + // Type Parent Region Connected Region Index + { { EntranceType::Dungeon, RR_KF_OUTSIDE_DEKU_TREE, RR_DEKU_TREE_ENTRYWAY, ENTR_DEKU_TREE_ENTRANCE }, + { EntranceType::Dungeon, RR_DEKU_TREE_ENTRYWAY, RR_KF_OUTSIDE_DEKU_TREE, ENTR_KOKIRI_FOREST_OUTSIDE_DEKU_TREE } }, + { { EntranceType::Dungeon, RR_DEATH_MOUNTAIN_TRAIL, RR_DODONGOS_CAVERN_ENTRYWAY, ENTR_DODONGOS_CAVERN_ENTRANCE }, + { EntranceType::Dungeon, RR_DODONGOS_CAVERN_ENTRYWAY, RR_DEATH_MOUNTAIN_TRAIL, ENTR_DEATH_MOUNTAIN_TRAIL_OUTSIDE_DODONGOS_CAVERN } }, + { { EntranceType::Dungeon, RR_ZORAS_FOUNTAIN, RR_JABU_JABUS_BELLY_ENTRYWAY, ENTR_JABU_JABU_ENTRANCE }, + { EntranceType::Dungeon, RR_JABU_JABUS_BELLY_ENTRYWAY, RR_ZORAS_FOUNTAIN, ENTR_ZORAS_FOUNTAIN_OUTSIDE_JABU_JABU } }, + { { EntranceType::Dungeon, RR_SACRED_FOREST_MEADOW, RR_FOREST_TEMPLE_ENTRYWAY, ENTR_FOREST_TEMPLE_ENTRANCE }, + { EntranceType::Dungeon, RR_FOREST_TEMPLE_ENTRYWAY, RR_SACRED_FOREST_MEADOW, ENTR_SACRED_FOREST_MEADOW_OUTSIDE_TEMPLE } }, + { { EntranceType::Dungeon, RR_DMC_CENTRAL_LOCAL, RR_FIRE_TEMPLE_ENTRYWAY, ENTR_FIRE_TEMPLE_ENTRANCE }, + { EntranceType::Dungeon, RR_FIRE_TEMPLE_ENTRYWAY, RR_DMC_CENTRAL_LOCAL, ENTR_DEATH_MOUNTAIN_CRATER_OUTSIDE_TEMPLE } }, + { { EntranceType::Dungeon, RR_LAKE_HYLIA, RR_WATER_TEMPLE_ENTRYWAY, ENTR_WATER_TEMPLE_ENTRANCE }, + { EntranceType::Dungeon, RR_WATER_TEMPLE_ENTRYWAY, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_OUTSIDE_TEMPLE } }, + { { EntranceType::Dungeon, RR_DESERT_COLOSSUS, RR_SPIRIT_TEMPLE_ENTRYWAY, ENTR_SPIRIT_TEMPLE_ENTRANCE }, + { EntranceType::Dungeon, RR_SPIRIT_TEMPLE_ENTRYWAY, RR_DESERT_COLOSSUS_OUTSIDE_TEMPLE, ENTR_DESERT_COLOSSUS_OUTSIDE_TEMPLE } }, + { { EntranceType::Dungeon, RR_GRAVEYARD_WARP_PAD_REGION, RR_SHADOW_TEMPLE_ENTRYWAY, ENTR_SHADOW_TEMPLE_ENTRANCE }, + { EntranceType::Dungeon, RR_SHADOW_TEMPLE_ENTRYWAY, RR_GRAVEYARD_WARP_PAD_REGION, ENTR_GRAVEYARD_OUTSIDE_TEMPLE } }, + { { EntranceType::Dungeon, RR_KAK_WELL, RR_BOTTOM_OF_THE_WELL_ENTRYWAY, ENTR_BOTTOM_OF_THE_WELL_ENTRANCE }, + { EntranceType::Dungeon, RR_BOTTOM_OF_THE_WELL_ENTRYWAY, RR_KAK_WELL, ENTR_KAKARIKO_VILLAGE_OUTSIDE_BOTTOM_OF_THE_WELL } }, + { { EntranceType::Dungeon, RR_ZF_LEDGE, RR_ICE_CAVERN_ENTRYWAY, ENTR_ICE_CAVERN_ENTRANCE }, + { EntranceType::Dungeon, RR_ICE_CAVERN_ENTRYWAY, RR_ZF_LEDGE, ENTR_ZORAS_FOUNTAIN_OUTSIDE_ICE_CAVERN } }, + { { EntranceType::Dungeon, RR_GERUDO_FORTRESS, RR_GERUDO_TRAINING_GROUND_ENTRYWAY, ENTR_GERUDO_TRAINING_GROUND_ENTRANCE }, + { EntranceType::Dungeon, RR_GERUDO_TRAINING_GROUND_ENTRYWAY, RR_GERUDO_FORTRESS, ENTR_GERUDOS_FORTRESS_OUTSIDE_GERUDO_TRAINING_GROUND } }, + { { EntranceType::GanonDungeon, RR_GANONS_CASTLE_LEDGE, RR_GANONS_CASTLE_ENTRYWAY, ENTR_INSIDE_GANONS_CASTLE_ENTRANCE }, + { EntranceType::GanonDungeon, RR_GANONS_CASTLE_ENTRYWAY, RR_CASTLE_GROUNDS_FROM_GANONS_CASTLE, ENTR_CASTLE_GROUNDS_RAINBOW_BRIDGE_EXIT } }, + + { { EntranceType::Interior, RR_KOKIRI_FOREST, RR_KF_MIDOS_HOUSE, ENTR_MIDOS_HOUSE_0 }, + { EntranceType::Interior, RR_KF_MIDOS_HOUSE, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_OUTSIDE_MIDOS_HOUSE } }, + { { EntranceType::Interior, RR_KOKIRI_FOREST, RR_KF_SARIAS_HOUSE, ENTR_SARIAS_HOUSE_0 }, + { EntranceType::Interior, RR_KF_SARIAS_HOUSE, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_OUTSIDE_SARIAS_HOUSE } }, + { { EntranceType::Interior, RR_KOKIRI_FOREST, RR_KF_HOUSE_OF_TWINS, ENTR_TWINS_HOUSE_0 }, + { EntranceType::Interior, RR_KF_HOUSE_OF_TWINS, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_OUTSIDE_TWINS_HOUSE } }, + { { EntranceType::Interior, RR_KOKIRI_FOREST, RR_KF_KNOW_IT_ALL_HOUSE, ENTR_KNOW_IT_ALL_BROS_HOUSE_0 }, + { EntranceType::Interior, RR_KF_KNOW_IT_ALL_HOUSE, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_OUTSIDE_KNOW_IT_ALL_HOUSE } }, + { { EntranceType::Interior, RR_KOKIRI_FOREST, RR_KF_KOKIRI_SHOP, ENTR_KOKIRI_SHOP_0 }, + { EntranceType::Interior, RR_KF_KOKIRI_SHOP, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_OUTSIDE_SHOP } }, + { { EntranceType::Interior, RR_LAKE_HYLIA, RR_LH_LAB, ENTR_LAKESIDE_LABORATORY_0 }, + { EntranceType::Interior, RR_LH_LAB, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_OUTSIDE_LAB } }, + { { EntranceType::Interior, RR_LH_FISHING_ISLAND, RR_LH_FISHING_POND, ENTR_FISHING_POND_0 }, + { EntranceType::Interior, RR_LH_FISHING_POND, RR_LH_FISHING_ISLAND, ENTR_LAKE_HYLIA_OUTSIDE_FISHING_POND } }, + { { EntranceType::Interior, RR_GV_FORTRESS_SIDE, RR_GV_CARPENTER_TENT, ENTR_CARPENTERS_TENT_0 }, + { EntranceType::Interior, RR_GV_CARPENTER_TENT, RR_GV_FORTRESS_SIDE, ENTR_GERUDO_VALLEY_OUTSIDE_TENT } }, + { { EntranceType::Interior, RR_MARKET_ENTRANCE, RR_MARKET_GUARD_HOUSE, ENTR_MARKET_GUARD_HOUSE_0 }, + { EntranceType::Interior, RR_MARKET_GUARD_HOUSE, RR_MARKET_ENTRANCE, ENTR_MARKET_ENTRANCE_OUTSIDE_GUARD_HOUSE } }, + { { EntranceType::Interior, RR_THE_MARKET, RR_MARKET_MASK_SHOP, ENTR_HAPPY_MASK_SHOP_0 }, + { EntranceType::Interior, RR_MARKET_MASK_SHOP, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_HAPPY_MASK_SHOP } }, + { { EntranceType::Interior, RR_THE_MARKET, RR_MARKET_BOMBCHU_BOWLING, ENTR_BOMBCHU_BOWLING_ALLEY_0 }, + { EntranceType::Interior, RR_MARKET_BOMBCHU_BOWLING, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_BOMBCHU_BOWLING } }, + { { EntranceType::Interior, RR_THE_MARKET, RR_MARKET_POTION_SHOP, ENTR_POTION_SHOP_MARKET_0 }, + { EntranceType::Interior, RR_MARKET_POTION_SHOP, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_POTION_SHOP } }, + { { EntranceType::Interior, RR_THE_MARKET, RR_MARKET_TREASURE_CHEST_GAME, ENTR_TREASURE_BOX_SHOP_0 }, + { EntranceType::Interior, RR_MARKET_TREASURE_CHEST_GAME, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_TREASURE_BOX_SHOP } }, + { { EntranceType::Interior, RR_MARKET_BACK_ALLEY, RR_MARKET_BOMBCHU_SHOP, ENTR_BOMBCHU_SHOP_1 }, + { EntranceType::Interior, RR_MARKET_BOMBCHU_SHOP, RR_MARKET_BACK_ALLEY, ENTR_BACK_ALLEY_DAY_OUTSIDE_BOMBCHU_SHOP } }, + { { EntranceType::Interior, RR_MARKET_BACK_ALLEY, RR_MARKET_MAN_IN_GREEN_HOUSE, ENTR_BACK_ALLEY_MAN_IN_GREEN_HOUSE }, + { EntranceType::Interior, RR_MARKET_MAN_IN_GREEN_HOUSE, RR_MARKET_BACK_ALLEY, ENTR_BACK_ALLEY_DAY_OUTSIDE_MAN_IN_GREEN_HOUSE } }, + { { EntranceType::Interior, RR_KAKARIKO_VILLAGE, RR_KAK_CARPENTER_BOSS_HOUSE, ENTR_KAKARIKO_CENTER_GUEST_HOUSE_0 }, + { EntranceType::Interior, RR_KAK_CARPENTER_BOSS_HOUSE, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_CENTER_GUEST_HOUSE } }, + { { EntranceType::Interior, RR_KAKARIKO_VILLAGE, RR_KAK_HOUSE_OF_SKULLTULA, ENTR_HOUSE_OF_SKULLTULA_0 }, + { EntranceType::Interior, RR_KAK_HOUSE_OF_SKULLTULA, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_SKULKLTULA_HOUSE } }, + { { EntranceType::Interior, RR_KAKARIKO_VILLAGE, RR_KAK_IMPAS_HOUSE, ENTR_IMPAS_HOUSE_FRONT }, + { EntranceType::Interior, RR_KAK_IMPAS_HOUSE, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_FRONT } }, + { { EntranceType::Interior, RR_KAK_IMPAS_LEDGE, RR_KAK_IMPAS_HOUSE_BACK, ENTR_IMPAS_HOUSE_BACK }, + { EntranceType::Interior, RR_KAK_IMPAS_HOUSE_BACK, RR_KAK_IMPAS_LEDGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_BACK } }, + { { EntranceType::Interior, RR_KAK_BACKYARD, RR_KAK_ODD_POTION_BUILDING, ENTR_POTION_SHOP_GRANNY_0 }, + { EntranceType::Interior, RR_KAK_ODD_POTION_BUILDING, RR_KAK_BACKYARD, ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOP_GRANNY } }, + { { EntranceType::Interior, RR_THE_GRAVEYARD, RR_GRAVEYARD_DAMPES_HOUSE, ENTR_GRAVEKEEPERS_HUT_0 }, + { EntranceType::Interior, RR_GRAVEYARD_DAMPES_HOUSE, RR_THE_GRAVEYARD, ENTR_GRAVEYARD_OUTSIDE_DAMPES_HUT } }, + { { EntranceType::Interior, RR_GORON_CITY, RR_GC_SHOP, ENTR_GORON_SHOP_0 }, + { EntranceType::Interior, RR_GC_SHOP, RR_GORON_CITY, ENTR_GORON_CITY_OUTSIDE_SHOP } }, + { { EntranceType::Interior, RR_ZORAS_DOMAIN, RR_ZD_SHOP, ENTR_ZORA_SHOP_0 }, + { EntranceType::Interior, RR_ZD_SHOP, RR_ZORAS_DOMAIN, ENTR_ZORAS_DOMAIN_OUTSIDE_SHOP } }, + { { EntranceType::Interior, RR_LON_LON_RANCH, RR_LLR_TALONS_HOUSE, ENTR_LON_LON_BUILDINGS_TALONS_HOUSE }, + { EntranceType::Interior, RR_LLR_TALONS_HOUSE, RR_LON_LON_RANCH, ENTR_LON_LON_RANCH_OUTSIDE_TALONS_HOUSE } }, + { { EntranceType::Interior, RR_LON_LON_RANCH, RR_LLR_STABLES, ENTR_STABLE_0 }, + { EntranceType::Interior, RR_LLR_STABLES, RR_LON_LON_RANCH, ENTR_LON_LON_RANCH_OUTSIDE_STABLES } }, + { { EntranceType::Interior, RR_LON_LON_RANCH, RR_LLR_TOWER, ENTR_LON_LON_BUILDINGS_TOWER }, + { EntranceType::Interior, RR_LLR_TOWER, RR_LON_LON_RANCH, ENTR_LON_LON_RANCH_OUTSIDE_TOWER } }, + { { EntranceType::Interior, RR_THE_MARKET, RR_MARKET_BAZAAR, ENTR_BAZAAR_1 }, + { EntranceType::Interior, RR_MARKET_BAZAAR, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_BAZAAR } }, + { { EntranceType::Interior, RR_THE_MARKET, RR_MARKET_SHOOTING_GALLERY, ENTR_SHOOTING_GALLERY_1 }, + { EntranceType::Interior, RR_MARKET_SHOOTING_GALLERY, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_SHOOTING_GALLERY } }, + { { EntranceType::Interior, RR_KAKARIKO_VILLAGE, RR_KAK_BAZAAR, ENTR_BAZAAR_0 }, + { EntranceType::Interior, RR_KAK_BAZAAR, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_BAZAAR } }, + { { EntranceType::Interior, RR_KAKARIKO_VILLAGE, RR_KAK_SHOOTING_GALLERY, ENTR_SHOOTING_GALLERY_0 }, + { EntranceType::Interior, RR_KAK_SHOOTING_GALLERY, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOOTING_GALLERY } }, + { { EntranceType::Interior, RR_DESERT_COLOSSUS, RR_COLOSSUS_GREAT_FAIRY_FOUNTAIN, ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_NAYRUS_COLOSSUS }, + { EntranceType::Interior, RR_COLOSSUS_GREAT_FAIRY_FOUNTAIN, RR_DESERT_COLOSSUS, ENTR_DESERT_COLOSSUS_GREAT_FAIRY_EXIT } }, + { { EntranceType::Interior, RR_HYRULE_CASTLE_GROUNDS, RR_HC_GREAT_FAIRY_FOUNTAIN, ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_DINS_HC }, + { EntranceType::Interior, RR_HC_GREAT_FAIRY_FOUNTAIN, RR_CASTLE_GROUNDS, ENTR_CASTLE_GROUNDS_GREAT_FAIRY_EXIT } }, + { { EntranceType::Interior, RR_GANONS_CASTLE_GROUNDS, RR_OGC_GREAT_FAIRY_FOUNTAIN, ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_OGC_DD }, + // 0x3E8 is an unused entrance index repruposed to differentiate between the HC and OGC fairy + // fountain exits (normally they both use 0x340) + { EntranceType::Interior, RR_OGC_GREAT_FAIRY_FOUNTAIN, RR_CASTLE_GROUNDS, ENTR_POTION_SHOP_KAKARIKO_1 } }, + { { EntranceType::Interior, RR_DMC_LOWER_NEARBY, RR_DMC_GREAT_FAIRY_FOUNTAIN, ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMC }, + { EntranceType::Interior, RR_DMC_GREAT_FAIRY_FOUNTAIN, RR_DMC_LOWER_LOCAL, ENTR_DEATH_MOUNTAIN_CRATER_GREAT_FAIRY_EXIT } }, + { { EntranceType::Interior, RR_DEATH_MOUNTAIN_SUMMIT, RR_DMT_GREAT_FAIRY_FOUNTAIN, ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMT }, + { EntranceType::Interior, RR_DMT_GREAT_FAIRY_FOUNTAIN, RR_DEATH_MOUNTAIN_SUMMIT, ENTR_DEATH_MOUNTAIN_TRAIL_GREAT_FAIRY_EXIT } }, + { { EntranceType::Interior, RR_ZORAS_FOUNTAIN, RR_ZF_GREAT_FAIRY_FOUNTAIN, ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_FARORES_ZF }, + { EntranceType::Interior, RR_ZF_GREAT_FAIRY_FOUNTAIN, RR_ZORAS_FOUNTAIN, ENTR_ZORAS_FOUNTAIN_OUTSIDE_GREAT_FAIRY } }, + + { { EntranceType::SpecialInterior, RR_KOKIRI_FOREST, RR_KF_LINKS_HOUSE, ENTR_LINKS_HOUSE_1 }, + { EntranceType::SpecialInterior, RR_KF_LINKS_HOUSE, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_OUTSIDE_LINKS_HOUSE } }, + { { EntranceType::SpecialInterior, RR_TOT_ENTRANCE, RR_TEMPLE_OF_TIME, ENTR_TEMPLE_OF_TIME_ENTRANCE }, + { EntranceType::SpecialInterior, RR_TEMPLE_OF_TIME, RR_TOT_ENTRANCE, ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_OUTSIDE_TEMPLE } }, + { { EntranceType::SpecialInterior, RR_KAKARIKO_VILLAGE, RR_KAK_WINDMILL, ENTR_WINDMILL_AND_DAMPES_GRAVE_WINDMILL }, + { EntranceType::SpecialInterior, RR_KAK_WINDMILL, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_WINDMILL } }, + { { EntranceType::SpecialInterior, RR_KAKARIKO_VILLAGE, RR_KAK_POTION_SHOP_FRONT, ENTR_POTION_SHOP_KAKARIKO_FRONT }, + { EntranceType::SpecialInterior, RR_KAK_POTION_SHOP_FRONT, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_FRONT } }, + { { EntranceType::SpecialInterior, RR_KAK_BACKYARD, RR_KAK_POTION_SHOP_BACK, ENTR_POTION_SHOP_KAKARIKO_BACK }, + { EntranceType::SpecialInterior, RR_KAK_POTION_SHOP_BACK, RR_KAK_BACKYARD, ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_BACK } }, + + // Grotto Loads use an entrance index of 0x0700 + their grotto id. The id is used as index for the + // grottoLoadTable in soh/soh/Enhancements/randomizer/randomizer_grotto.c + // Grotto Returns use an entrance index of 0x0800 + their grotto id. The id is used as index for the + // grottoReturnTable in soh/soh/Enhancements/randomizer/randomizer_grotto.c + { { EntranceType::GrottoGrave, RR_DESERT_COLOSSUS, RR_COLOSSUS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_COLOSSUS_OFFSET) }, + { EntranceType::GrottoGrave, RR_COLOSSUS_GROTTO, RR_DESERT_COLOSSUS, ENTRANCE_GROTTO_EXIT(GROTTO_COLOSSUS_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_LAKE_HYLIA, RR_LH_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_LH_OFFSET) }, + { EntranceType::GrottoGrave, RR_LH_GROTTO, RR_LAKE_HYLIA, ENTRANCE_GROTTO_EXIT(GROTTO_LH_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_ZORAS_RIVER, RR_ZR_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_ZR_STORMS_OFFSET) }, + { EntranceType::GrottoGrave, RR_ZR_STORMS_GROTTO, RR_ZORAS_RIVER, ENTRANCE_GROTTO_EXIT(GROTTO_ZR_STORMS_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_ZORAS_RIVER, RR_ZR_FAIRY_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_ZR_FAIRY_OFFSET) }, + { EntranceType::GrottoGrave, RR_ZR_FAIRY_GROTTO, RR_ZORAS_RIVER, ENTRANCE_GROTTO_EXIT(GROTTO_ZR_FAIRY_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_ZORAS_RIVER, RR_ZR_OPEN_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_ZR_OPEN_OFFSET) }, + { EntranceType::GrottoGrave, RR_ZR_OPEN_GROTTO, RR_ZORAS_RIVER, ENTRANCE_GROTTO_EXIT(GROTTO_ZR_OPEN_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_DMC_LOWER_NEARBY, RR_DMC_HAMMER_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_DMC_HAMMER_OFFSET) }, + { EntranceType::GrottoGrave, RR_DMC_HAMMER_GROTTO, RR_DMC_LOWER_LOCAL, ENTRANCE_GROTTO_EXIT(GROTTO_DMC_HAMMER_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_DMC_UPPER_NEARBY, RR_DMC_UPPER_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_DMC_UPPER_OFFSET) }, + { EntranceType::GrottoGrave, RR_DMC_UPPER_GROTTO, RR_DMC_UPPER_LOCAL, ENTRANCE_GROTTO_EXIT(GROTTO_DMC_UPPER_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_GC_GROTTO_PLATFORM, RR_GC_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_GORON_CITY_OFFSET) }, + { EntranceType::GrottoGrave, RR_GC_GROTTO, RR_GC_GROTTO_PLATFORM, ENTRANCE_GROTTO_EXIT(GROTTO_GORON_CITY_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_DEATH_MOUNTAIN_TRAIL, RR_DMT_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_DMT_STORMS_OFFSET) }, + { EntranceType::GrottoGrave, RR_DMT_STORMS_GROTTO, RR_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROTTO_EXIT(GROTTO_DMT_STORMS_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_DEATH_MOUNTAIN_SUMMIT, RR_DMT_COW_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_DMT_COW_OFFSET) }, + { EntranceType::GrottoGrave, RR_DMT_COW_GROTTO, RR_DEATH_MOUNTAIN_SUMMIT, ENTRANCE_GROTTO_EXIT(GROTTO_DMT_COW_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_KAK_BACKYARD, RR_KAK_OPEN_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_KAK_OPEN_OFFSET) }, + { EntranceType::GrottoGrave, RR_KAK_OPEN_GROTTO, RR_KAK_BACKYARD, ENTRANCE_GROTTO_EXIT(GROTTO_KAK_OPEN_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_KAKARIKO_VILLAGE, RR_KAK_REDEAD_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_KAK_REDEAD_OFFSET) }, + { EntranceType::GrottoGrave, RR_KAK_REDEAD_GROTTO, RR_KAKARIKO_VILLAGE, ENTRANCE_GROTTO_EXIT(GROTTO_KAK_REDEAD_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_HYRULE_CASTLE_GROUNDS, RR_HC_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HC_STORMS_OFFSET) }, + { EntranceType::GrottoGrave, RR_HC_STORMS_GROTTO, RR_CASTLE_GROUNDS, ENTRANCE_GROTTO_EXIT(GROTTO_HC_STORMS_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_TEKTITE_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_TEKTITE_OFFSET) }, + { EntranceType::GrottoGrave, RR_HF_TEKTITE_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_TEKTITE_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_NEAR_KAK_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_NEAR_KAK_OFFSET) }, + { EntranceType::GrottoGrave, RR_HF_NEAR_KAK_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_NEAR_KAK_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_FAIRY_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_FAIRY_OFFSET) }, + { EntranceType::GrottoGrave, RR_HF_FAIRY_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_FAIRY_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_NEAR_MARKET_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_NEAR_MARKET_OFFSET) }, + { EntranceType::GrottoGrave, RR_HF_NEAR_MARKET_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_NEAR_MARKET_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_COW_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_COW_OFFSET) }, + { EntranceType::GrottoGrave, RR_HF_COW_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_COW_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_INSIDE_FENCE_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_INSIDE_FENCE_OFFSET) }, + { EntranceType::GrottoGrave, RR_HF_INSIDE_FENCE_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_INSIDE_FENCE_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_OPEN_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_OPEN_OFFSET) }, + { EntranceType::GrottoGrave, RR_HF_OPEN_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_OPEN_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_SOUTHEAST_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_SOUTHEAST_OFFSET) }, + { EntranceType::GrottoGrave, RR_HF_SOUTHEAST_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_SOUTHEAST_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_LON_LON_RANCH, RR_LLR_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_LLR_OFFSET) }, + { EntranceType::GrottoGrave, RR_LLR_GROTTO, RR_LON_LON_RANCH, ENTRANCE_GROTTO_EXIT(GROTTO_LLR_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_SFM_ENTRYWAY, RR_SFM_WOLFOS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_SFM_WOLFOS_OFFSET) }, + { EntranceType::GrottoGrave, RR_SFM_WOLFOS_GROTTO, RR_SFM_ENTRYWAY, ENTRANCE_GROTTO_EXIT(GROTTO_SFM_WOLFOS_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_SACRED_FOREST_MEADOW, RR_SFM_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_SFM_STORMS_OFFSET) }, + { EntranceType::GrottoGrave, RR_SFM_STORMS_GROTTO, RR_SACRED_FOREST_MEADOW, ENTRANCE_GROTTO_EXIT(GROTTO_SFM_STORMS_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_SACRED_FOREST_MEADOW, RR_SFM_FAIRY_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_SFM_FAIRY_OFFSET) }, + { EntranceType::GrottoGrave, RR_SFM_FAIRY_GROTTO, RR_SACRED_FOREST_MEADOW, ENTRANCE_GROTTO_EXIT(GROTTO_SFM_FAIRY_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_LW_BEYOND_MIDO, RR_LW_SCRUBS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_LW_SCRUBS_OFFSET) }, + { EntranceType::GrottoGrave, RR_LW_SCRUBS_GROTTO, RR_LW_BEYOND_MIDO, ENTRANCE_GROTTO_EXIT(GROTTO_LW_SCRUBS_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_THE_LOST_WOODS, RR_LW_NEAR_SHORTCUTS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_LW_NEAR_SHORTCUTS_OFFSET) }, + { EntranceType::GrottoGrave, RR_LW_NEAR_SHORTCUTS_GROTTO, RR_THE_LOST_WOODS, ENTRANCE_GROTTO_EXIT(GROTTO_LW_NEAR_SHORTCUTS_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_KOKIRI_FOREST, RR_KF_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_KF_STORMS_OFFSET) }, + { EntranceType::GrottoGrave, RR_KF_STORMS_GROTTO, RR_KOKIRI_FOREST, ENTRANCE_GROTTO_EXIT(GROTTO_KF_STORMS_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_ZORAS_DOMAIN_ISLAND, RR_ZD_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_ZD_STORMS_OFFSET) }, + { EntranceType::GrottoGrave, RR_ZD_STORMS_GROTTO, RR_ZORAS_DOMAIN_ISLAND, ENTRANCE_GROTTO_EXIT(GROTTO_ZD_STORMS_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_GERUDO_FORTRESS, RR_GF_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_GF_STORMS_OFFSET) }, + { EntranceType::GrottoGrave, RR_GF_STORMS_GROTTO, RR_GERUDO_FORTRESS, ENTRANCE_GROTTO_EXIT(GROTTO_GF_STORMS_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_GV_FORTRESS_SIDE, RR_GV_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_GV_STORMS_OFFSET) }, + { EntranceType::GrottoGrave, RR_GV_STORMS_GROTTO, RR_GV_FORTRESS_SIDE, ENTRANCE_GROTTO_EXIT(GROTTO_GV_STORMS_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_GV_GROTTO_LEDGE, RR_GV_OCTOROK_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_GV_OCTOROK_OFFSET) }, + { EntranceType::GrottoGrave, RR_GV_OCTOROK_GROTTO, RR_GV_GROTTO_LEDGE, ENTRANCE_GROTTO_EXIT(GROTTO_GV_OCTOROK_OFFSET) } }, + { { EntranceType::GrottoGrave, RR_LW_BEYOND_MIDO, RR_DEKU_THEATER, ENTRANCE_GROTTO_LOAD(GROTTO_LW_DEKU_THEATRE_OFFSET) }, + { EntranceType::GrottoGrave, RR_DEKU_THEATER, RR_LW_BEYOND_MIDO, ENTRANCE_GROTTO_EXIT(GROTTO_LW_DEKU_THEATRE_OFFSET) } }, + + // Graves have their own specified entrance indices + { { EntranceType::GrottoGrave, RR_THE_GRAVEYARD, RR_GRAVEYARD_SHIELD_GRAVE, ENTR_GRAVE_WITH_FAIRYS_FOUNTAIN_0 }, + { EntranceType::GrottoGrave, RR_GRAVEYARD_SHIELD_GRAVE, RR_THE_GRAVEYARD, ENTR_GRAVEYARD_SHIELD_GRAVE_EXIT } }, + { { EntranceType::GrottoGrave, RR_THE_GRAVEYARD, RR_GRAVEYARD_HEART_PIECE_GRAVE, ENTR_REDEAD_GRAVE_0 }, + { EntranceType::GrottoGrave, RR_GRAVEYARD_HEART_PIECE_GRAVE, RR_THE_GRAVEYARD, ENTR_GRAVEYARD_HEART_PIECE_GRAVE_EXIT } }, + { { EntranceType::GrottoGrave, RR_THE_GRAVEYARD, RR_GRAVEYARD_COMPOSERS_GRAVE, ENTR_ROYAL_FAMILYS_TOMB_0 }, + { EntranceType::GrottoGrave, RR_GRAVEYARD_COMPOSERS_GRAVE, RR_THE_GRAVEYARD, ENTR_GRAVEYARD_ROYAL_TOMB_EXIT } }, + { { EntranceType::GrottoGrave, RR_THE_GRAVEYARD, RR_GRAVEYARD_DAMPES_GRAVE, ENTR_WINDMILL_AND_DAMPES_GRAVE_GRAVE }, + { EntranceType::GrottoGrave, RR_GRAVEYARD_DAMPES_GRAVE, RR_THE_GRAVEYARD, ENTR_GRAVEYARD_DAMPES_GRAVE_EXIT } }, + + { { EntranceType::Overworld, RR_KOKIRI_FOREST, RR_LW_BRIDGE_FROM_FOREST, ENTR_LOST_WOODS_BRIDGE_EAST_EXIT }, + { EntranceType::Overworld, RR_LW_BRIDGE, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_LOWER_EXIT } }, + { { EntranceType::Overworld, RR_KOKIRI_FOREST, RR_THE_LOST_WOODS, ENTR_LOST_WOODS_SOUTH_EXIT }, + { EntranceType::Overworld, RR_LW_FOREST_EXIT, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_UPPER_EXIT } }, + { { EntranceType::Overworld, RR_THE_LOST_WOODS, RR_GC_WOODS_WARP, ENTR_GORON_CITY_TUNNEL_SHORTCUT }, + { EntranceType::Overworld, RR_GC_WOODS_WARP, RR_THE_LOST_WOODS, ENTR_LOST_WOODS_TUNNEL_SHORTCUT } }, + { { EntranceType::Overworld, RR_THE_LOST_WOODS, RR_ZORAS_RIVER, ENTR_ZORAS_RIVER_UNDERWATER_SHORTCUT }, + { EntranceType::Overworld, RR_ZORAS_RIVER, RR_THE_LOST_WOODS, ENTR_LOST_WOODS_UNDERWATER_SHORTCUT } }, + { { EntranceType::Overworld, RR_LW_BEYOND_MIDO, RR_SFM_ENTRYWAY, ENTR_SACRED_FOREST_MEADOW_SOUTH_EXIT }, + { EntranceType::Overworld, RR_SFM_ENTRYWAY, RR_LW_BEYOND_MIDO, ENTR_LOST_WOODS_NORTH_EXIT } }, + { { EntranceType::Overworld, RR_LW_BRIDGE, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_WOODED_EXIT }, + { EntranceType::Overworld, RR_HYRULE_FIELD, RR_LW_BRIDGE, ENTR_LOST_WOODS_BRIDGE_WEST_EXIT } }, + { { EntranceType::Overworld, RR_HYRULE_FIELD, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_NORTH_EXIT }, + { EntranceType::Overworld, RR_LAKE_HYLIA, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_FENCE_EXIT } }, + { { EntranceType::Overworld, RR_HYRULE_FIELD, RR_GERUDO_VALLEY, ENTR_GERUDO_VALLEY_EAST_EXIT }, + { EntranceType::Overworld, RR_GERUDO_VALLEY, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_ROCKY_PATH } }, + { { EntranceType::Overworld, RR_HYRULE_FIELD, RR_MARKET_ENTRANCE, ENTR_MARKET_ENTRANCE_NEAR_GUARD_EXIT }, + { EntranceType::Overworld, RR_MARKET_ENTRANCE, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_ON_BRIDGE_SPAWN } }, + { { EntranceType::Overworld, RR_HYRULE_FIELD, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_FRONT_GATE }, + { EntranceType::Overworld, RR_KAKARIKO_VILLAGE, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_STAIRS_EXIT } }, + { { EntranceType::Overworld, RR_HYRULE_FIELD, RR_ZR_FRONT, ENTR_ZORAS_RIVER_WEST_EXIT }, + { EntranceType::Overworld, RR_ZR_FRONT, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_RIVER_EXIT } }, + { { EntranceType::Overworld, RR_HYRULE_FIELD, RR_LON_LON_RANCH, ENTR_LON_LON_RANCH_ENTRANCE }, + { EntranceType::Overworld, RR_LON_LON_RANCH, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_CENTER_EXIT } }, + { { EntranceType::Overworld, RR_LAKE_HYLIA, RR_ZORAS_DOMAIN, ENTR_ZORAS_DOMAIN_UNDERWATER_SHORTCUT }, + { EntranceType::Overworld, RR_ZORAS_DOMAIN, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_UNDERWATER_SHORTCUT } }, + { { EntranceType::Overworld, RR_GV_FORTRESS_SIDE, RR_GERUDO_FORTRESS, ENTR_GERUDOS_FORTRESS_EAST_EXIT }, + { EntranceType::Overworld, RR_GERUDO_FORTRESS, RR_GV_FORTRESS_SIDE, ENTR_GERUDO_VALLEY_WEST_EXIT } }, + { { EntranceType::Overworld, RR_GF_OUTSIDE_GATE, RR_WASTELAND_NEAR_FORTRESS, ENTR_HAUNTED_WASTELAND_EAST_EXIT }, + { EntranceType::Overworld, RR_WASTELAND_NEAR_FORTRESS, RR_GF_OUTSIDE_GATE, ENTR_GERUDOS_FORTRESS_GATE_EXIT } }, + { { EntranceType::Overworld, RR_WASTELAND_NEAR_COLOSSUS, RR_DESERT_COLOSSUS, ENTR_DESERT_COLOSSUS_EAST_EXIT }, + { EntranceType::Overworld, RR_DESERT_COLOSSUS, RR_WASTELAND_NEAR_COLOSSUS, ENTR_HAUNTED_WASTELAND_WEST_EXIT } }, + { { EntranceType::Overworld, RR_MARKET_ENTRANCE, RR_THE_MARKET, ENTR_MARKET_SOUTH_EXIT }, + { EntranceType::Overworld, RR_THE_MARKET, RR_MARKET_ENTRANCE, ENTR_MARKET_ENTRANCE_NORTH_EXIT } }, + { { EntranceType::Overworld, RR_THE_MARKET, RR_CASTLE_GROUNDS, ENTR_CASTLE_GROUNDS_SOUTH_EXIT }, + { EntranceType::Overworld, RR_CASTLE_GROUNDS, RR_THE_MARKET, ENTR_MARKET_DAY_CASTLE_EXIT } }, + { { EntranceType::Overworld, RR_THE_MARKET, RR_TOT_ENTRANCE, ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_GOSSIP_STONE_EXIT }, + { EntranceType::Overworld, RR_TOT_ENTRANCE, RR_THE_MARKET, ENTR_MARKET_DAY_TEMPLE_EXIT } }, + { { EntranceType::Overworld, RR_KAKARIKO_VILLAGE, RR_THE_GRAVEYARD, ENTR_GRAVEYARD_ENTRANCE }, + { EntranceType::Overworld, RR_THE_GRAVEYARD, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_SOUTHEAST_EXIT } }, + { { EntranceType::Overworld, RR_KAK_BEHIND_GATE, RR_DEATH_MOUNTAIN_TRAIL, ENTR_DEATH_MOUNTAIN_TRAIL_BOTTOM_EXIT }, + { EntranceType::Overworld, RR_DEATH_MOUNTAIN_TRAIL, RR_KAK_BEHIND_GATE, ENTR_KAKARIKO_VILLAGE_GUARD_GATE } }, + { { EntranceType::Overworld, RR_DEATH_MOUNTAIN_TRAIL, RR_GORON_CITY, ENTR_GORON_CITY_UPPER_EXIT }, + { EntranceType::Overworld, RR_GORON_CITY, RR_DEATH_MOUNTAIN_TRAIL, ENTR_DEATH_MOUNTAIN_TRAIL_GC_EXIT } }, + { { EntranceType::Overworld, RR_GC_DARUNIAS_CHAMBER, RR_DMC_LOWER_LOCAL, ENTR_DEATH_MOUNTAIN_CRATER_GC_EXIT }, + { EntranceType::Overworld, RR_DMC_LOWER_NEARBY, RR_GC_DARUNIAS_CHAMBER, ENTR_GORON_CITY_DARUNIA_ROOM_EXIT } }, + { { EntranceType::Overworld, RR_DEATH_MOUNTAIN_SUMMIT, RR_DMC_UPPER_LOCAL, ENTR_DEATH_MOUNTAIN_CRATER_UPPER_EXIT }, + { EntranceType::Overworld, RR_DMC_UPPER_NEARBY, RR_DEATH_MOUNTAIN_SUMMIT, ENTR_DEATH_MOUNTAIN_TRAIL_SUMMIT_EXIT } }, + { { EntranceType::Overworld, RR_ZR_BEHIND_WATERFALL, RR_ZORAS_DOMAIN, ENTR_ZORAS_DOMAIN_ENTRANCE }, + { EntranceType::Overworld, RR_ZORAS_DOMAIN, RR_ZR_BEHIND_WATERFALL, ENTR_ZORAS_RIVER_WATERFALL_EXIT } }, + { { EntranceType::Overworld, RR_ZD_BEHIND_KING_ZORA, RR_ZORAS_FOUNTAIN, ENTR_ZORAS_FOUNTAIN_TUNNEL_EXIT }, + { EntranceType::Overworld, RR_ZORAS_FOUNTAIN, RR_ZD_BEHIND_KING_ZORA, ENTR_ZORAS_DOMAIN_KING_ZORA_EXIT } }, + + { { EntranceType::Overworld, RR_GV_LOWER_STREAM, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_RIVER_EXIT }, + NO_RETURN_ENTRANCE }, + + { { EntranceType::OwlDrop, RR_LH_OWL_FLIGHT, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_OWL_DROP }, + NO_RETURN_ENTRANCE }, + { { EntranceType::OwlDrop, RR_DMT_OWL_FLIGHT, RR_KAK_IMPAS_ROOFTOP, ENTR_KAKARIKO_VILLAGE_OWL_DROP }, + NO_RETURN_ENTRANCE }, + + { { EntranceType::Spawn, RR_CHILD_SPAWN, RR_KF_LINKS_HOUSE, ENTR_LINKS_HOUSE_CHILD_SPAWN }, + NO_RETURN_ENTRANCE }, + { { EntranceType::Spawn, RR_ADULT_SPAWN, RR_TEMPLE_OF_TIME, ENTR_HYRULE_FIELD_10 }, + 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, RR_MINUET_OF_FOREST_WARP, RR_SACRED_FOREST_MEADOW, ENTR_SACRED_FOREST_MEADOW_WARP_PAD }, + NO_RETURN_ENTRANCE }, + { { EntranceType::WarpSong, RR_BOLERO_OF_FIRE_WARP, RR_DMC_CENTRAL_LOCAL, ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD }, + NO_RETURN_ENTRANCE }, + { { EntranceType::WarpSong, RR_SERENADE_OF_WATER_WARP, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_WARP_PAD }, + NO_RETURN_ENTRANCE }, + { { EntranceType::WarpSong, RR_REQUIEM_OF_SPIRIT_WARP, RR_DESERT_COLOSSUS, ENTR_DESERT_COLOSSUS_WARP_PAD }, + NO_RETURN_ENTRANCE }, + { { EntranceType::WarpSong, RR_NOCTURNE_OF_SHADOW_WARP, RR_GRAVEYARD_WARP_PAD_REGION, ENTR_GRAVEYARD_WARP_PAD }, + NO_RETURN_ENTRANCE }, + { { EntranceType::WarpSong, RR_PRELUDE_OF_LIGHT_WARP, RR_TEMPLE_OF_TIME, ENTR_TEMPLE_OF_TIME_WARP_PAD }, + NO_RETURN_ENTRANCE }, + + { { EntranceType::ChildBoss, RR_DEKU_TREE_BOSS_ENTRYWAY, RR_DEKU_TREE_BOSS_ROOM, ENTR_DEKU_TREE_BOSS_ENTRANCE }, + { EntranceType::ChildBoss, RR_DEKU_TREE_BOSS_ROOM, RR_DEKU_TREE_BOSS_EXIT, ENTR_DEKU_TREE_BOSS_DOOR } }, + { { EntranceType::ChildBoss, RR_DODONGOS_CAVERN_BOSS_ENTRYWAY, RR_DODONGOS_CAVERN_BOSS_ROOM, ENTR_DODONGOS_CAVERN_BOSS_ENTRANCE }, + { EntranceType::ChildBoss, RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_EXIT, ENTR_DODONGOS_CAVERN_BOSS_DOOR } }, + { { EntranceType::ChildBoss, RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY, RR_JABU_JABUS_BELLY_BOSS_ROOM, ENTR_JABU_JABU_BOSS_ENTRANCE }, + { EntranceType::ChildBoss, RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_EXIT, ENTR_JABU_JABU_BOSS_DOOR } }, + { { EntranceType::AdultBoss, RR_FOREST_TEMPLE_BOSS_ENTRYWAY, RR_FOREST_TEMPLE_BOSS_ROOM, ENTR_FOREST_TEMPLE_BOSS_ENTRANCE }, + { EntranceType::AdultBoss, RR_FOREST_TEMPLE_BOSS_ROOM, RR_FOREST_TEMPLE_BOSS_ENTRYWAY, ENTR_FOREST_TEMPLE_BOSS_DOOR } }, + { { EntranceType::AdultBoss, RR_FIRE_TEMPLE_BOSS_ENTRYWAY, RR_FIRE_TEMPLE_BOSS_ROOM, ENTR_FIRE_TEMPLE_BOSS_ENTRANCE }, + { EntranceType::AdultBoss, RR_FIRE_TEMPLE_BOSS_ROOM, RR_FIRE_TEMPLE_BOSS_ENTRYWAY, ENTR_FIRE_TEMPLE_BOSS_DOOR } }, + { { EntranceType::AdultBoss, RR_WATER_TEMPLE_BOSS_ENTRYWAY, RR_WATER_TEMPLE_BOSS_ROOM, ENTR_WATER_TEMPLE_BOSS_ENTRANCE }, + { EntranceType::AdultBoss, RR_WATER_TEMPLE_BOSS_ROOM, RR_WATER_TEMPLE_BOSS_ENTRYWAY, ENTR_WATER_TEMPLE_BOSS_DOOR } }, + { { EntranceType::AdultBoss, RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY, RR_SPIRIT_TEMPLE_BOSS_ROOM, ENTR_SPIRIT_TEMPLE_BOSS_ENTRANCE }, + { EntranceType::AdultBoss, RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY, ENTR_SPIRIT_TEMPLE_BOSS_DOOR } }, + { { EntranceType::AdultBoss, RR_SHADOW_TEMPLE_BOSS_ENTRYWAY, RR_SHADOW_TEMPLE_BOSS_ROOM, ENTR_SHADOW_TEMPLE_BOSS_ENTRANCE }, + { EntranceType::AdultBoss, RR_SHADOW_TEMPLE_BOSS_ROOM, RR_SHADOW_TEMPLE_BOSS_ENTRYWAY, ENTR_SHADOW_TEMPLE_BOSS_DOOR } }, + + { { EntranceType::BlueWarp, RR_DEKU_TREE_BOSS_ROOM, RR_KF_OUTSIDE_DEKU_TREE, ENTR_KOKIRI_FOREST_DEKU_TREE_BLUE_WARP }, + NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DEATH_MOUNTAIN_TRAIL, ENTR_DEATH_MOUNTAIN_TRAIL_DODONGO_BLUE_WARP }, + NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_ZORAS_FOUNTAIN, ENTR_ZORAS_FOUNTAIN_JABU_JABU_BLUE_WARP }, + NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_FOREST_TEMPLE_BOSS_ROOM, RR_SACRED_FOREST_MEADOW, ENTR_SACRED_FOREST_MEADOW_FOREST_TEMPLE_BLUE_WARP }, + NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_FIRE_TEMPLE_BOSS_ROOM, RR_DMC_CENTRAL_LOCAL, ENTR_DEATH_MOUNTAIN_CRATER_FIRE_TEMPLE_BLUE_WARP }, + NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_WATER_TEMPLE_BOSS_ROOM, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_WATER_TEMPLE_BLUE_WARP }, + NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_DESERT_COLOSSUS, ENTR_DESERT_COLOSSUS_SPIRIT_TEMPLE_BLUE_WARP }, + NO_RETURN_ENTRANCE }, + { { EntranceType::BlueWarp, RR_SHADOW_TEMPLE_BOSS_ROOM, RR_GRAVEYARD_WARP_PAD_REGION, ENTR_GRAVEYARD_SHADOW_TEMPLE_BLUE_WARP }, + NO_RETURN_ENTRANCE }, + // clang-format on + }; + auto ctx = Rando::Context::GetInstance(); for (auto& entrancePair : entranceShuffleTable) { @@ -262,6 +574,7 @@ void SetAllEntrancesData(std::vector& entranceShuffleTable) { forwardEntrance->SetIndex(forwardEntry.index); forwardEntrance->SetType(forwardEntry.type); forwardEntrance->SetAsPrimary(); + entranceMap[forwardEntry.index] = forwardEntrance; // When decouple entrances is on, mark the forward entrance if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) { @@ -273,6 +586,7 @@ void SetAllEntrancesData(std::vector& entranceShuffleTable) { returnEntrance->SetIndex(returnEntry.index); returnEntrance->SetType(returnEntry.type); forwardEntrance->BindTwoWay(returnEntrance); + entranceMap[returnEntry.index] = returnEntrance; // Mark reverse entrance as decoupled if (ctx->GetOption(RSK_DECOUPLED_ENTRANCES)) { @@ -855,316 +1169,6 @@ int EntranceShuffler::ShuffleAllEntrances() { mTotalRandomizableEntrances = 0; mCurNumRandomizedEntrances = 0; - std::vector entranceShuffleTable = { - // clang-format off - // Type Parent Region Connected Region Index - { { EntranceType::Dungeon, RR_KF_OUTSIDE_DEKU_TREE, RR_DEKU_TREE_ENTRYWAY, ENTR_DEKU_TREE_ENTRANCE }, - { EntranceType::Dungeon, RR_DEKU_TREE_ENTRYWAY, RR_KF_OUTSIDE_DEKU_TREE, ENTR_KOKIRI_FOREST_OUTSIDE_DEKU_TREE } }, - { { EntranceType::Dungeon, RR_DEATH_MOUNTAIN_TRAIL, RR_DODONGOS_CAVERN_ENTRYWAY, ENTR_DODONGOS_CAVERN_ENTRANCE }, - { EntranceType::Dungeon, RR_DODONGOS_CAVERN_ENTRYWAY, RR_DEATH_MOUNTAIN_TRAIL, ENTR_DEATH_MOUNTAIN_TRAIL_OUTSIDE_DODONGOS_CAVERN } }, - { { EntranceType::Dungeon, RR_ZORAS_FOUNTAIN, RR_JABU_JABUS_BELLY_ENTRYWAY, ENTR_JABU_JABU_ENTRANCE }, - { EntranceType::Dungeon, RR_JABU_JABUS_BELLY_ENTRYWAY, RR_ZORAS_FOUNTAIN, ENTR_ZORAS_FOUNTAIN_OUTSIDE_JABU_JABU } }, - { { EntranceType::Dungeon, RR_SACRED_FOREST_MEADOW, RR_FOREST_TEMPLE_ENTRYWAY, ENTR_FOREST_TEMPLE_ENTRANCE }, - { EntranceType::Dungeon, RR_FOREST_TEMPLE_ENTRYWAY, RR_SACRED_FOREST_MEADOW, ENTR_SACRED_FOREST_MEADOW_OUTSIDE_TEMPLE } }, - { { EntranceType::Dungeon, RR_DMC_CENTRAL_LOCAL, RR_FIRE_TEMPLE_ENTRYWAY, ENTR_FIRE_TEMPLE_ENTRANCE }, - { EntranceType::Dungeon, RR_FIRE_TEMPLE_ENTRYWAY, RR_DMC_CENTRAL_LOCAL, ENTR_DEATH_MOUNTAIN_CRATER_OUTSIDE_TEMPLE } }, - { { EntranceType::Dungeon, RR_LAKE_HYLIA, RR_WATER_TEMPLE_ENTRYWAY, ENTR_WATER_TEMPLE_ENTRANCE }, - { EntranceType::Dungeon, RR_WATER_TEMPLE_ENTRYWAY, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_OUTSIDE_TEMPLE } }, - { { EntranceType::Dungeon, RR_DESERT_COLOSSUS, RR_SPIRIT_TEMPLE_ENTRYWAY, ENTR_SPIRIT_TEMPLE_ENTRANCE }, - { EntranceType::Dungeon, RR_SPIRIT_TEMPLE_ENTRYWAY, RR_DESERT_COLOSSUS_OUTSIDE_TEMPLE, ENTR_DESERT_COLOSSUS_OUTSIDE_TEMPLE } }, - { { EntranceType::Dungeon, RR_GRAVEYARD_WARP_PAD_REGION, RR_SHADOW_TEMPLE_ENTRYWAY, ENTR_SHADOW_TEMPLE_ENTRANCE }, - { EntranceType::Dungeon, RR_SHADOW_TEMPLE_ENTRYWAY, RR_GRAVEYARD_WARP_PAD_REGION, ENTR_GRAVEYARD_OUTSIDE_TEMPLE } }, - { { EntranceType::Dungeon, RR_KAK_WELL, RR_BOTTOM_OF_THE_WELL_ENTRYWAY, ENTR_BOTTOM_OF_THE_WELL_ENTRANCE }, - { EntranceType::Dungeon, RR_BOTTOM_OF_THE_WELL_ENTRYWAY, RR_KAK_WELL, ENTR_KAKARIKO_VILLAGE_OUTSIDE_BOTTOM_OF_THE_WELL } }, - { { EntranceType::Dungeon, RR_ZF_LEDGE, RR_ICE_CAVERN_ENTRYWAY, ENTR_ICE_CAVERN_ENTRANCE }, - { EntranceType::Dungeon, RR_ICE_CAVERN_ENTRYWAY, RR_ZF_LEDGE, ENTR_ZORAS_FOUNTAIN_OUTSIDE_ICE_CAVERN } }, - { { EntranceType::Dungeon, RR_GERUDO_FORTRESS, RR_GERUDO_TRAINING_GROUND_ENTRYWAY, ENTR_GERUDO_TRAINING_GROUND_ENTRANCE }, - { EntranceType::Dungeon, RR_GERUDO_TRAINING_GROUND_ENTRYWAY, RR_GERUDO_FORTRESS, ENTR_GERUDOS_FORTRESS_OUTSIDE_GERUDO_TRAINING_GROUND } }, - { { EntranceType::GanonDungeon, RR_GANONS_CASTLE_LEDGE, RR_GANONS_CASTLE_ENTRYWAY, ENTR_INSIDE_GANONS_CASTLE_ENTRANCE }, - { EntranceType::GanonDungeon, RR_GANONS_CASTLE_ENTRYWAY, RR_CASTLE_GROUNDS_FROM_GANONS_CASTLE, ENTR_CASTLE_GROUNDS_RAINBOW_BRIDGE_EXIT } }, - - { { EntranceType::Interior, RR_KOKIRI_FOREST, RR_KF_MIDOS_HOUSE, ENTR_MIDOS_HOUSE_0 }, - { EntranceType::Interior, RR_KF_MIDOS_HOUSE, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_OUTSIDE_MIDOS_HOUSE } }, - { { EntranceType::Interior, RR_KOKIRI_FOREST, RR_KF_SARIAS_HOUSE, ENTR_SARIAS_HOUSE_0 }, - { EntranceType::Interior, RR_KF_SARIAS_HOUSE, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_OUTSIDE_SARIAS_HOUSE } }, - { { EntranceType::Interior, RR_KOKIRI_FOREST, RR_KF_HOUSE_OF_TWINS, ENTR_TWINS_HOUSE_0 }, - { EntranceType::Interior, RR_KF_HOUSE_OF_TWINS, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_OUTSIDE_TWINS_HOUSE } }, - { { EntranceType::Interior, RR_KOKIRI_FOREST, RR_KF_KNOW_IT_ALL_HOUSE, ENTR_KNOW_IT_ALL_BROS_HOUSE_0 }, - { EntranceType::Interior, RR_KF_KNOW_IT_ALL_HOUSE, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_OUTSIDE_KNOW_IT_ALL_HOUSE } }, - { { EntranceType::Interior, RR_KOKIRI_FOREST, RR_KF_KOKIRI_SHOP, ENTR_KOKIRI_SHOP_0 }, - { EntranceType::Interior, RR_KF_KOKIRI_SHOP, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_OUTSIDE_SHOP } }, - { { EntranceType::Interior, RR_LAKE_HYLIA, RR_LH_LAB, ENTR_LAKESIDE_LABORATORY_0 }, - { EntranceType::Interior, RR_LH_LAB, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_OUTSIDE_LAB } }, - { { EntranceType::Interior, RR_LH_FISHING_ISLAND, RR_LH_FISHING_POND, ENTR_FISHING_POND_0 }, - { EntranceType::Interior, RR_LH_FISHING_POND, RR_LH_FISHING_ISLAND, ENTR_LAKE_HYLIA_OUTSIDE_FISHING_POND } }, - { { EntranceType::Interior, RR_GV_FORTRESS_SIDE, RR_GV_CARPENTER_TENT, ENTR_CARPENTERS_TENT_0 }, - { EntranceType::Interior, RR_GV_CARPENTER_TENT, RR_GV_FORTRESS_SIDE, ENTR_GERUDO_VALLEY_OUTSIDE_TENT } }, - { { EntranceType::Interior, RR_MARKET_ENTRANCE, RR_MARKET_GUARD_HOUSE, ENTR_MARKET_GUARD_HOUSE_0 }, - { EntranceType::Interior, RR_MARKET_GUARD_HOUSE, RR_MARKET_ENTRANCE, ENTR_MARKET_ENTRANCE_OUTSIDE_GUARD_HOUSE } }, - { { EntranceType::Interior, RR_THE_MARKET, RR_MARKET_MASK_SHOP, ENTR_HAPPY_MASK_SHOP_0 }, - { EntranceType::Interior, RR_MARKET_MASK_SHOP, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_HAPPY_MASK_SHOP } }, - { { EntranceType::Interior, RR_THE_MARKET, RR_MARKET_BOMBCHU_BOWLING, ENTR_BOMBCHU_BOWLING_ALLEY_0 }, - { EntranceType::Interior, RR_MARKET_BOMBCHU_BOWLING, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_BOMBCHU_BOWLING } }, - { { EntranceType::Interior, RR_THE_MARKET, RR_MARKET_POTION_SHOP, ENTR_POTION_SHOP_MARKET_0 }, - { EntranceType::Interior, RR_MARKET_POTION_SHOP, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_POTION_SHOP } }, - { { EntranceType::Interior, RR_THE_MARKET, RR_MARKET_TREASURE_CHEST_GAME, ENTR_TREASURE_BOX_SHOP_0 }, - { EntranceType::Interior, RR_MARKET_TREASURE_CHEST_GAME, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_TREASURE_BOX_SHOP } }, - { { EntranceType::Interior, RR_MARKET_BACK_ALLEY, RR_MARKET_BOMBCHU_SHOP, ENTR_BOMBCHU_SHOP_1 }, - { EntranceType::Interior, RR_MARKET_BOMBCHU_SHOP, RR_MARKET_BACK_ALLEY, ENTR_BACK_ALLEY_DAY_OUTSIDE_BOMBCHU_SHOP } }, - { { EntranceType::Interior, RR_MARKET_BACK_ALLEY, RR_MARKET_MAN_IN_GREEN_HOUSE, ENTR_BACK_ALLEY_MAN_IN_GREEN_HOUSE }, - { EntranceType::Interior, RR_MARKET_MAN_IN_GREEN_HOUSE, RR_MARKET_BACK_ALLEY, ENTR_BACK_ALLEY_DAY_OUTSIDE_MAN_IN_GREEN_HOUSE } }, - { { EntranceType::Interior, RR_KAKARIKO_VILLAGE, RR_KAK_CARPENTER_BOSS_HOUSE, ENTR_KAKARIKO_CENTER_GUEST_HOUSE_0 }, - { EntranceType::Interior, RR_KAK_CARPENTER_BOSS_HOUSE, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_CENTER_GUEST_HOUSE } }, - { { EntranceType::Interior, RR_KAKARIKO_VILLAGE, RR_KAK_HOUSE_OF_SKULLTULA, ENTR_HOUSE_OF_SKULLTULA_0 }, - { EntranceType::Interior, RR_KAK_HOUSE_OF_SKULLTULA, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_SKULKLTULA_HOUSE } }, - { { EntranceType::Interior, RR_KAKARIKO_VILLAGE, RR_KAK_IMPAS_HOUSE, ENTR_IMPAS_HOUSE_FRONT }, - { EntranceType::Interior, RR_KAK_IMPAS_HOUSE, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_FRONT } }, - { { EntranceType::Interior, RR_KAK_IMPAS_LEDGE, RR_KAK_IMPAS_HOUSE_BACK, ENTR_IMPAS_HOUSE_BACK }, - { EntranceType::Interior, RR_KAK_IMPAS_HOUSE_BACK, RR_KAK_IMPAS_LEDGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_BACK } }, - { { EntranceType::Interior, RR_KAK_BACKYARD, RR_KAK_ODD_POTION_BUILDING, ENTR_POTION_SHOP_GRANNY_0 }, - { EntranceType::Interior, RR_KAK_ODD_POTION_BUILDING, RR_KAK_BACKYARD, ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOP_GRANNY } }, - { { EntranceType::Interior, RR_THE_GRAVEYARD, RR_GRAVEYARD_DAMPES_HOUSE, ENTR_GRAVEKEEPERS_HUT_0 }, - { EntranceType::Interior, RR_GRAVEYARD_DAMPES_HOUSE, RR_THE_GRAVEYARD, ENTR_GRAVEYARD_OUTSIDE_DAMPES_HUT } }, - { { EntranceType::Interior, RR_GORON_CITY, RR_GC_SHOP, ENTR_GORON_SHOP_0 }, - { EntranceType::Interior, RR_GC_SHOP, RR_GORON_CITY, ENTR_GORON_CITY_OUTSIDE_SHOP } }, - { { EntranceType::Interior, RR_ZORAS_DOMAIN, RR_ZD_SHOP, ENTR_ZORA_SHOP_0 }, - { EntranceType::Interior, RR_ZD_SHOP, RR_ZORAS_DOMAIN, ENTR_ZORAS_DOMAIN_OUTSIDE_SHOP } }, - { { EntranceType::Interior, RR_LON_LON_RANCH, RR_LLR_TALONS_HOUSE, ENTR_LON_LON_BUILDINGS_TALONS_HOUSE }, - { EntranceType::Interior, RR_LLR_TALONS_HOUSE, RR_LON_LON_RANCH, ENTR_LON_LON_RANCH_OUTSIDE_TALONS_HOUSE } }, - { { EntranceType::Interior, RR_LON_LON_RANCH, RR_LLR_STABLES, ENTR_STABLE_0 }, - { EntranceType::Interior, RR_LLR_STABLES, RR_LON_LON_RANCH, ENTR_LON_LON_RANCH_OUTSIDE_STABLES } }, - { { EntranceType::Interior, RR_LON_LON_RANCH, RR_LLR_TOWER, ENTR_LON_LON_BUILDINGS_TOWER }, - { EntranceType::Interior, RR_LLR_TOWER, RR_LON_LON_RANCH, ENTR_LON_LON_RANCH_OUTSIDE_TOWER } }, - { { EntranceType::Interior, RR_THE_MARKET, RR_MARKET_BAZAAR, ENTR_BAZAAR_1 }, - { EntranceType::Interior, RR_MARKET_BAZAAR, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_BAZAAR } }, - { { EntranceType::Interior, RR_THE_MARKET, RR_MARKET_SHOOTING_GALLERY, ENTR_SHOOTING_GALLERY_1 }, - { EntranceType::Interior, RR_MARKET_SHOOTING_GALLERY, RR_THE_MARKET, ENTR_MARKET_DAY_OUTSIDE_SHOOTING_GALLERY } }, - { { EntranceType::Interior, RR_KAKARIKO_VILLAGE, RR_KAK_BAZAAR, ENTR_BAZAAR_0 }, - { EntranceType::Interior, RR_KAK_BAZAAR, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_BAZAAR } }, - { { EntranceType::Interior, RR_KAKARIKO_VILLAGE, RR_KAK_SHOOTING_GALLERY, ENTR_SHOOTING_GALLERY_0 }, - { EntranceType::Interior, RR_KAK_SHOOTING_GALLERY, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOOTING_GALLERY } }, - { { EntranceType::Interior, RR_DESERT_COLOSSUS, RR_COLOSSUS_GREAT_FAIRY_FOUNTAIN, ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_NAYRUS_COLOSSUS }, - { EntranceType::Interior, RR_COLOSSUS_GREAT_FAIRY_FOUNTAIN, RR_DESERT_COLOSSUS, ENTR_DESERT_COLOSSUS_GREAT_FAIRY_EXIT } }, - { { EntranceType::Interior, RR_HYRULE_CASTLE_GROUNDS, RR_HC_GREAT_FAIRY_FOUNTAIN, ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_DINS_HC }, - { EntranceType::Interior, RR_HC_GREAT_FAIRY_FOUNTAIN, RR_CASTLE_GROUNDS, ENTR_CASTLE_GROUNDS_GREAT_FAIRY_EXIT } }, - { { EntranceType::Interior, RR_GANONS_CASTLE_GROUNDS, RR_OGC_GREAT_FAIRY_FOUNTAIN, ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_OGC_DD }, - // 0x3E8 is an unused entrance index repruposed to differentiate between the HC and OGC fairy - // fountain exits (normally they both use 0x340) - { EntranceType::Interior, RR_OGC_GREAT_FAIRY_FOUNTAIN, RR_CASTLE_GROUNDS, ENTR_POTION_SHOP_KAKARIKO_1 } }, - { { EntranceType::Interior, RR_DMC_LOWER_NEARBY, RR_DMC_GREAT_FAIRY_FOUNTAIN, ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMC }, - { EntranceType::Interior, RR_DMC_GREAT_FAIRY_FOUNTAIN, RR_DMC_LOWER_LOCAL, ENTR_DEATH_MOUNTAIN_CRATER_GREAT_FAIRY_EXIT } }, - { { EntranceType::Interior, RR_DEATH_MOUNTAIN_SUMMIT, RR_DMT_GREAT_FAIRY_FOUNTAIN, ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMT }, - { EntranceType::Interior, RR_DMT_GREAT_FAIRY_FOUNTAIN, RR_DEATH_MOUNTAIN_SUMMIT, ENTR_DEATH_MOUNTAIN_TRAIL_GREAT_FAIRY_EXIT } }, - { { EntranceType::Interior, RR_ZORAS_FOUNTAIN, RR_ZF_GREAT_FAIRY_FOUNTAIN, ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_FARORES_ZF }, - { EntranceType::Interior, RR_ZF_GREAT_FAIRY_FOUNTAIN, RR_ZORAS_FOUNTAIN, ENTR_ZORAS_FOUNTAIN_OUTSIDE_GREAT_FAIRY } }, - - { { EntranceType::SpecialInterior, RR_KOKIRI_FOREST, RR_KF_LINKS_HOUSE, ENTR_LINKS_HOUSE_1 }, - { EntranceType::SpecialInterior, RR_KF_LINKS_HOUSE, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_OUTSIDE_LINKS_HOUSE } }, - { { EntranceType::SpecialInterior, RR_TOT_ENTRANCE, RR_TEMPLE_OF_TIME, ENTR_TEMPLE_OF_TIME_ENTRANCE }, - { EntranceType::SpecialInterior, RR_TEMPLE_OF_TIME, RR_TOT_ENTRANCE, ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_OUTSIDE_TEMPLE } }, - { { EntranceType::SpecialInterior, RR_KAKARIKO_VILLAGE, RR_KAK_WINDMILL, ENTR_WINDMILL_AND_DAMPES_GRAVE_WINDMILL }, - { EntranceType::SpecialInterior, RR_KAK_WINDMILL, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_WINDMILL } }, - { { EntranceType::SpecialInterior, RR_KAKARIKO_VILLAGE, RR_KAK_POTION_SHOP_FRONT, ENTR_POTION_SHOP_KAKARIKO_FRONT }, - { EntranceType::SpecialInterior, RR_KAK_POTION_SHOP_FRONT, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_FRONT } }, - { { EntranceType::SpecialInterior, RR_KAK_BACKYARD, RR_KAK_POTION_SHOP_BACK, ENTR_POTION_SHOP_KAKARIKO_BACK }, - { EntranceType::SpecialInterior, RR_KAK_POTION_SHOP_BACK, RR_KAK_BACKYARD, ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_BACK } }, - - // Grotto Loads use an entrance index of 0x0700 + their grotto id. The id is used as index for the - // grottoLoadTable in soh/soh/Enhancements/randomizer/randomizer_grotto.c - // Grotto Returns use an entrance index of 0x0800 + their grotto id. The id is used as index for the - // grottoReturnTable in soh/soh/Enhancements/randomizer/randomizer_grotto.c - { { EntranceType::GrottoGrave, RR_DESERT_COLOSSUS, RR_COLOSSUS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_COLOSSUS_OFFSET) }, - { EntranceType::GrottoGrave, RR_COLOSSUS_GROTTO, RR_DESERT_COLOSSUS, ENTRANCE_GROTTO_EXIT(GROTTO_COLOSSUS_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_LAKE_HYLIA, RR_LH_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_LH_OFFSET) }, - { EntranceType::GrottoGrave, RR_LH_GROTTO, RR_LAKE_HYLIA, ENTRANCE_GROTTO_EXIT(GROTTO_LH_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_ZORAS_RIVER, RR_ZR_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_ZR_STORMS_OFFSET) }, - { EntranceType::GrottoGrave, RR_ZR_STORMS_GROTTO, RR_ZORAS_RIVER, ENTRANCE_GROTTO_EXIT(GROTTO_ZR_STORMS_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_ZORAS_RIVER, RR_ZR_FAIRY_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_ZR_FAIRY_OFFSET) }, - { EntranceType::GrottoGrave, RR_ZR_FAIRY_GROTTO, RR_ZORAS_RIVER, ENTRANCE_GROTTO_EXIT(GROTTO_ZR_FAIRY_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_ZORAS_RIVER, RR_ZR_OPEN_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_ZR_OPEN_OFFSET) }, - { EntranceType::GrottoGrave, RR_ZR_OPEN_GROTTO, RR_ZORAS_RIVER, ENTRANCE_GROTTO_EXIT(GROTTO_ZR_OPEN_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_DMC_LOWER_NEARBY, RR_DMC_HAMMER_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_DMC_HAMMER_OFFSET) }, - { EntranceType::GrottoGrave, RR_DMC_HAMMER_GROTTO, RR_DMC_LOWER_LOCAL, ENTRANCE_GROTTO_EXIT(GROTTO_DMC_HAMMER_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_DMC_UPPER_NEARBY, RR_DMC_UPPER_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_DMC_UPPER_OFFSET) }, - { EntranceType::GrottoGrave, RR_DMC_UPPER_GROTTO, RR_DMC_UPPER_LOCAL, ENTRANCE_GROTTO_EXIT(GROTTO_DMC_UPPER_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_GC_GROTTO_PLATFORM, RR_GC_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_GORON_CITY_OFFSET) }, - { EntranceType::GrottoGrave, RR_GC_GROTTO, RR_GC_GROTTO_PLATFORM, ENTRANCE_GROTTO_EXIT(GROTTO_GORON_CITY_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_DEATH_MOUNTAIN_TRAIL, RR_DMT_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_DMT_STORMS_OFFSET) }, - { EntranceType::GrottoGrave, RR_DMT_STORMS_GROTTO, RR_DEATH_MOUNTAIN_TRAIL, ENTRANCE_GROTTO_EXIT(GROTTO_DMT_STORMS_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_DEATH_MOUNTAIN_SUMMIT, RR_DMT_COW_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_DMT_COW_OFFSET) }, - { EntranceType::GrottoGrave, RR_DMT_COW_GROTTO, RR_DEATH_MOUNTAIN_SUMMIT, ENTRANCE_GROTTO_EXIT(GROTTO_DMT_COW_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_KAK_BACKYARD, RR_KAK_OPEN_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_KAK_OPEN_OFFSET) }, - { EntranceType::GrottoGrave, RR_KAK_OPEN_GROTTO, RR_KAK_BACKYARD, ENTRANCE_GROTTO_EXIT(GROTTO_KAK_OPEN_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_KAKARIKO_VILLAGE, RR_KAK_REDEAD_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_KAK_REDEAD_OFFSET) }, - { EntranceType::GrottoGrave, RR_KAK_REDEAD_GROTTO, RR_KAKARIKO_VILLAGE, ENTRANCE_GROTTO_EXIT(GROTTO_KAK_REDEAD_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_HYRULE_CASTLE_GROUNDS, RR_HC_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HC_STORMS_OFFSET) }, - { EntranceType::GrottoGrave, RR_HC_STORMS_GROTTO, RR_CASTLE_GROUNDS, ENTRANCE_GROTTO_EXIT(GROTTO_HC_STORMS_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_TEKTITE_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_TEKTITE_OFFSET) }, - { EntranceType::GrottoGrave, RR_HF_TEKTITE_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_TEKTITE_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_NEAR_KAK_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_NEAR_KAK_OFFSET) }, - { EntranceType::GrottoGrave, RR_HF_NEAR_KAK_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_NEAR_KAK_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_FAIRY_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_FAIRY_OFFSET) }, - { EntranceType::GrottoGrave, RR_HF_FAIRY_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_FAIRY_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_NEAR_MARKET_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_NEAR_MARKET_OFFSET) }, - { EntranceType::GrottoGrave, RR_HF_NEAR_MARKET_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_NEAR_MARKET_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_COW_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_COW_OFFSET) }, - { EntranceType::GrottoGrave, RR_HF_COW_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_COW_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_INSIDE_FENCE_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_INSIDE_FENCE_OFFSET) }, - { EntranceType::GrottoGrave, RR_HF_INSIDE_FENCE_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_INSIDE_FENCE_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_OPEN_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_OPEN_OFFSET) }, - { EntranceType::GrottoGrave, RR_HF_OPEN_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_OPEN_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_HYRULE_FIELD, RR_HF_SOUTHEAST_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_HF_SOUTHEAST_OFFSET) }, - { EntranceType::GrottoGrave, RR_HF_SOUTHEAST_GROTTO, RR_HYRULE_FIELD, ENTRANCE_GROTTO_EXIT(GROTTO_HF_SOUTHEAST_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_LON_LON_RANCH, RR_LLR_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_LLR_OFFSET) }, - { EntranceType::GrottoGrave, RR_LLR_GROTTO, RR_LON_LON_RANCH, ENTRANCE_GROTTO_EXIT(GROTTO_LLR_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_SFM_ENTRYWAY, RR_SFM_WOLFOS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_SFM_WOLFOS_OFFSET) }, - { EntranceType::GrottoGrave, RR_SFM_WOLFOS_GROTTO, RR_SFM_ENTRYWAY, ENTRANCE_GROTTO_EXIT(GROTTO_SFM_WOLFOS_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_SACRED_FOREST_MEADOW, RR_SFM_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_SFM_STORMS_OFFSET) }, - { EntranceType::GrottoGrave, RR_SFM_STORMS_GROTTO, RR_SACRED_FOREST_MEADOW, ENTRANCE_GROTTO_EXIT(GROTTO_SFM_STORMS_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_SACRED_FOREST_MEADOW, RR_SFM_FAIRY_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_SFM_FAIRY_OFFSET) }, - { EntranceType::GrottoGrave, RR_SFM_FAIRY_GROTTO, RR_SACRED_FOREST_MEADOW, ENTRANCE_GROTTO_EXIT(GROTTO_SFM_FAIRY_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_LW_BEYOND_MIDO, RR_LW_SCRUBS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_LW_SCRUBS_OFFSET) }, - { EntranceType::GrottoGrave, RR_LW_SCRUBS_GROTTO, RR_LW_BEYOND_MIDO, ENTRANCE_GROTTO_EXIT(GROTTO_LW_SCRUBS_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_THE_LOST_WOODS, RR_LW_NEAR_SHORTCUTS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_LW_NEAR_SHORTCUTS_OFFSET) }, - { EntranceType::GrottoGrave, RR_LW_NEAR_SHORTCUTS_GROTTO, RR_THE_LOST_WOODS, ENTRANCE_GROTTO_EXIT(GROTTO_LW_NEAR_SHORTCUTS_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_KOKIRI_FOREST, RR_KF_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_KF_STORMS_OFFSET) }, - { EntranceType::GrottoGrave, RR_KF_STORMS_GROTTO, RR_KOKIRI_FOREST, ENTRANCE_GROTTO_EXIT(GROTTO_KF_STORMS_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_ZORAS_DOMAIN_ISLAND, RR_ZD_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_ZD_STORMS_OFFSET) }, - { EntranceType::GrottoGrave, RR_ZD_STORMS_GROTTO, RR_ZORAS_DOMAIN_ISLAND, ENTRANCE_GROTTO_EXIT(GROTTO_ZD_STORMS_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_GERUDO_FORTRESS, RR_GF_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_GF_STORMS_OFFSET) }, - { EntranceType::GrottoGrave, RR_GF_STORMS_GROTTO, RR_GERUDO_FORTRESS, ENTRANCE_GROTTO_EXIT(GROTTO_GF_STORMS_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_GV_FORTRESS_SIDE, RR_GV_STORMS_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_GV_STORMS_OFFSET) }, - { EntranceType::GrottoGrave, RR_GV_STORMS_GROTTO, RR_GV_FORTRESS_SIDE, ENTRANCE_GROTTO_EXIT(GROTTO_GV_STORMS_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_GV_GROTTO_LEDGE, RR_GV_OCTOROK_GROTTO, ENTRANCE_GROTTO_LOAD(GROTTO_GV_OCTOROK_OFFSET) }, - { EntranceType::GrottoGrave, RR_GV_OCTOROK_GROTTO, RR_GV_GROTTO_LEDGE, ENTRANCE_GROTTO_EXIT(GROTTO_GV_OCTOROK_OFFSET) } }, - { { EntranceType::GrottoGrave, RR_LW_BEYOND_MIDO, RR_DEKU_THEATER, ENTRANCE_GROTTO_LOAD(GROTTO_LW_DEKU_THEATRE_OFFSET) }, - { EntranceType::GrottoGrave, RR_DEKU_THEATER, RR_LW_BEYOND_MIDO, ENTRANCE_GROTTO_EXIT(GROTTO_LW_DEKU_THEATRE_OFFSET) } }, - - // Graves have their own specified entrance indices - { { EntranceType::GrottoGrave, RR_THE_GRAVEYARD, RR_GRAVEYARD_SHIELD_GRAVE, ENTR_GRAVE_WITH_FAIRYS_FOUNTAIN_0 }, - { EntranceType::GrottoGrave, RR_GRAVEYARD_SHIELD_GRAVE, RR_THE_GRAVEYARD, ENTR_GRAVEYARD_SHIELD_GRAVE_EXIT } }, - { { EntranceType::GrottoGrave, RR_THE_GRAVEYARD, RR_GRAVEYARD_HEART_PIECE_GRAVE, ENTR_REDEAD_GRAVE_0 }, - { EntranceType::GrottoGrave, RR_GRAVEYARD_HEART_PIECE_GRAVE, RR_THE_GRAVEYARD, ENTR_GRAVEYARD_HEART_PIECE_GRAVE_EXIT } }, - { { EntranceType::GrottoGrave, RR_THE_GRAVEYARD, RR_GRAVEYARD_COMPOSERS_GRAVE, ENTR_ROYAL_FAMILYS_TOMB_0 }, - { EntranceType::GrottoGrave, RR_GRAVEYARD_COMPOSERS_GRAVE, RR_THE_GRAVEYARD, ENTR_GRAVEYARD_ROYAL_TOMB_EXIT } }, - { { EntranceType::GrottoGrave, RR_THE_GRAVEYARD, RR_GRAVEYARD_DAMPES_GRAVE, ENTR_WINDMILL_AND_DAMPES_GRAVE_GRAVE }, - { EntranceType::GrottoGrave, RR_GRAVEYARD_DAMPES_GRAVE, RR_THE_GRAVEYARD, ENTR_GRAVEYARD_DAMPES_GRAVE_EXIT } }, - - { { EntranceType::Overworld, RR_KOKIRI_FOREST, RR_LW_BRIDGE_FROM_FOREST, ENTR_LOST_WOODS_BRIDGE_EAST_EXIT }, - { EntranceType::Overworld, RR_LW_BRIDGE, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_LOWER_EXIT } }, - { { EntranceType::Overworld, RR_KOKIRI_FOREST, RR_THE_LOST_WOODS, ENTR_LOST_WOODS_SOUTH_EXIT }, - { EntranceType::Overworld, RR_LW_FOREST_EXIT, RR_KOKIRI_FOREST, ENTR_KOKIRI_FOREST_UPPER_EXIT } }, - { { EntranceType::Overworld, RR_THE_LOST_WOODS, RR_GC_WOODS_WARP, ENTR_GORON_CITY_TUNNEL_SHORTCUT }, - { EntranceType::Overworld, RR_GC_WOODS_WARP, RR_THE_LOST_WOODS, ENTR_LOST_WOODS_TUNNEL_SHORTCUT } }, - { { EntranceType::Overworld, RR_THE_LOST_WOODS, RR_ZORAS_RIVER, ENTR_ZORAS_RIVER_UNDERWATER_SHORTCUT }, - { EntranceType::Overworld, RR_ZORAS_RIVER, RR_THE_LOST_WOODS, ENTR_LOST_WOODS_UNDERWATER_SHORTCUT } }, - { { EntranceType::Overworld, RR_LW_BEYOND_MIDO, RR_SFM_ENTRYWAY, ENTR_SACRED_FOREST_MEADOW_SOUTH_EXIT }, - { EntranceType::Overworld, RR_SFM_ENTRYWAY, RR_LW_BEYOND_MIDO, ENTR_LOST_WOODS_NORTH_EXIT } }, - { { EntranceType::Overworld, RR_LW_BRIDGE, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_WOODED_EXIT }, - { EntranceType::Overworld, RR_HYRULE_FIELD, RR_LW_BRIDGE, ENTR_LOST_WOODS_BRIDGE_WEST_EXIT } }, - { { EntranceType::Overworld, RR_HYRULE_FIELD, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_NORTH_EXIT }, - { EntranceType::Overworld, RR_LAKE_HYLIA, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_FENCE_EXIT } }, - { { EntranceType::Overworld, RR_HYRULE_FIELD, RR_GERUDO_VALLEY, ENTR_GERUDO_VALLEY_EAST_EXIT }, - { EntranceType::Overworld, RR_GERUDO_VALLEY, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_ROCKY_PATH } }, - { { EntranceType::Overworld, RR_HYRULE_FIELD, RR_MARKET_ENTRANCE, ENTR_MARKET_ENTRANCE_NEAR_GUARD_EXIT }, - { EntranceType::Overworld, RR_MARKET_ENTRANCE, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_ON_BRIDGE_SPAWN } }, - { { EntranceType::Overworld, RR_HYRULE_FIELD, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_FRONT_GATE }, - { EntranceType::Overworld, RR_KAKARIKO_VILLAGE, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_STAIRS_EXIT } }, - { { EntranceType::Overworld, RR_HYRULE_FIELD, RR_ZR_FRONT, ENTR_ZORAS_RIVER_WEST_EXIT }, - { EntranceType::Overworld, RR_ZR_FRONT, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_RIVER_EXIT } }, - { { EntranceType::Overworld, RR_HYRULE_FIELD, RR_LON_LON_RANCH, ENTR_LON_LON_RANCH_ENTRANCE }, - { EntranceType::Overworld, RR_LON_LON_RANCH, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_CENTER_EXIT } }, - { { EntranceType::Overworld, RR_LAKE_HYLIA, RR_ZORAS_DOMAIN, ENTR_ZORAS_DOMAIN_UNDERWATER_SHORTCUT }, - { EntranceType::Overworld, RR_ZORAS_DOMAIN, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_UNDERWATER_SHORTCUT } }, - { { EntranceType::Overworld, RR_GV_FORTRESS_SIDE, RR_GERUDO_FORTRESS, ENTR_GERUDOS_FORTRESS_EAST_EXIT }, - { EntranceType::Overworld, RR_GERUDO_FORTRESS, RR_GV_FORTRESS_SIDE, ENTR_GERUDO_VALLEY_WEST_EXIT } }, - { { EntranceType::Overworld, RR_GF_OUTSIDE_GATE, RR_WASTELAND_NEAR_FORTRESS, ENTR_HAUNTED_WASTELAND_EAST_EXIT }, - { EntranceType::Overworld, RR_WASTELAND_NEAR_FORTRESS, RR_GF_OUTSIDE_GATE, ENTR_GERUDOS_FORTRESS_GATE_EXIT } }, - { { EntranceType::Overworld, RR_WASTELAND_NEAR_COLOSSUS, RR_DESERT_COLOSSUS, ENTR_DESERT_COLOSSUS_EAST_EXIT }, - { EntranceType::Overworld, RR_DESERT_COLOSSUS, RR_WASTELAND_NEAR_COLOSSUS, ENTR_HAUNTED_WASTELAND_WEST_EXIT } }, - { { EntranceType::Overworld, RR_MARKET_ENTRANCE, RR_THE_MARKET, ENTR_MARKET_SOUTH_EXIT }, - { EntranceType::Overworld, RR_THE_MARKET, RR_MARKET_ENTRANCE, ENTR_MARKET_ENTRANCE_NORTH_EXIT } }, - { { EntranceType::Overworld, RR_THE_MARKET, RR_CASTLE_GROUNDS, ENTR_CASTLE_GROUNDS_SOUTH_EXIT }, - { EntranceType::Overworld, RR_CASTLE_GROUNDS, RR_THE_MARKET, ENTR_MARKET_DAY_CASTLE_EXIT } }, - { { EntranceType::Overworld, RR_THE_MARKET, RR_TOT_ENTRANCE, ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_GOSSIP_STONE_EXIT }, - { EntranceType::Overworld, RR_TOT_ENTRANCE, RR_THE_MARKET, ENTR_MARKET_DAY_TEMPLE_EXIT } }, - { { EntranceType::Overworld, RR_KAKARIKO_VILLAGE, RR_THE_GRAVEYARD, ENTR_GRAVEYARD_ENTRANCE }, - { EntranceType::Overworld, RR_THE_GRAVEYARD, RR_KAKARIKO_VILLAGE, ENTR_KAKARIKO_VILLAGE_SOUTHEAST_EXIT } }, - { { EntranceType::Overworld, RR_KAK_BEHIND_GATE, RR_DEATH_MOUNTAIN_TRAIL, ENTR_DEATH_MOUNTAIN_TRAIL_BOTTOM_EXIT }, - { EntranceType::Overworld, RR_DEATH_MOUNTAIN_TRAIL, RR_KAK_BEHIND_GATE, ENTR_KAKARIKO_VILLAGE_GUARD_GATE } }, - { { EntranceType::Overworld, RR_DEATH_MOUNTAIN_TRAIL, RR_GORON_CITY, ENTR_GORON_CITY_UPPER_EXIT }, - { EntranceType::Overworld, RR_GORON_CITY, RR_DEATH_MOUNTAIN_TRAIL, ENTR_DEATH_MOUNTAIN_TRAIL_GC_EXIT } }, - { { EntranceType::Overworld, RR_GC_DARUNIAS_CHAMBER, RR_DMC_LOWER_LOCAL, ENTR_DEATH_MOUNTAIN_CRATER_GC_EXIT }, - { EntranceType::Overworld, RR_DMC_LOWER_NEARBY, RR_GC_DARUNIAS_CHAMBER, ENTR_GORON_CITY_DARUNIA_ROOM_EXIT } }, - { { EntranceType::Overworld, RR_DEATH_MOUNTAIN_SUMMIT, RR_DMC_UPPER_LOCAL, ENTR_DEATH_MOUNTAIN_CRATER_UPPER_EXIT }, - { EntranceType::Overworld, RR_DMC_UPPER_NEARBY, RR_DEATH_MOUNTAIN_SUMMIT, ENTR_DEATH_MOUNTAIN_TRAIL_SUMMIT_EXIT } }, - { { EntranceType::Overworld, RR_ZR_BEHIND_WATERFALL, RR_ZORAS_DOMAIN, ENTR_ZORAS_DOMAIN_ENTRANCE }, - { EntranceType::Overworld, RR_ZORAS_DOMAIN, RR_ZR_BEHIND_WATERFALL, ENTR_ZORAS_RIVER_WATERFALL_EXIT } }, - { { EntranceType::Overworld, RR_ZD_BEHIND_KING_ZORA, RR_ZORAS_FOUNTAIN, ENTR_ZORAS_FOUNTAIN_TUNNEL_EXIT }, - { EntranceType::Overworld, RR_ZORAS_FOUNTAIN, RR_ZD_BEHIND_KING_ZORA, ENTR_ZORAS_DOMAIN_KING_ZORA_EXIT } }, - - { { EntranceType::Overworld, RR_GV_LOWER_STREAM, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_RIVER_EXIT }, - NO_RETURN_ENTRANCE }, - - { { EntranceType::OwlDrop, RR_LH_OWL_FLIGHT, RR_HYRULE_FIELD, ENTR_HYRULE_FIELD_OWL_DROP }, - NO_RETURN_ENTRANCE }, - { { EntranceType::OwlDrop, RR_DMT_OWL_FLIGHT, RR_KAK_IMPAS_ROOFTOP, ENTR_KAKARIKO_VILLAGE_OWL_DROP }, - NO_RETURN_ENTRANCE }, - - { { EntranceType::Spawn, RR_CHILD_SPAWN, RR_KF_LINKS_HOUSE, ENTR_LINKS_HOUSE_CHILD_SPAWN }, - NO_RETURN_ENTRANCE }, - { { EntranceType::Spawn, RR_ADULT_SPAWN, RR_TEMPLE_OF_TIME, ENTR_HYRULE_FIELD_10 }, - 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, RR_MINUET_OF_FOREST_WARP, RR_SACRED_FOREST_MEADOW, ENTR_SACRED_FOREST_MEADOW_WARP_PAD }, - NO_RETURN_ENTRANCE }, - { { EntranceType::WarpSong, RR_BOLERO_OF_FIRE_WARP, RR_DMC_CENTRAL_LOCAL, ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD }, - NO_RETURN_ENTRANCE }, - { { EntranceType::WarpSong, RR_SERENADE_OF_WATER_WARP, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_WARP_PAD }, - NO_RETURN_ENTRANCE }, - { { EntranceType::WarpSong, RR_REQUIEM_OF_SPIRIT_WARP, RR_DESERT_COLOSSUS, ENTR_DESERT_COLOSSUS_WARP_PAD }, - NO_RETURN_ENTRANCE }, - { { EntranceType::WarpSong, RR_NOCTURNE_OF_SHADOW_WARP, RR_GRAVEYARD_WARP_PAD_REGION, ENTR_GRAVEYARD_WARP_PAD }, - NO_RETURN_ENTRANCE }, - { { EntranceType::WarpSong, RR_PRELUDE_OF_LIGHT_WARP, RR_TEMPLE_OF_TIME, ENTR_TEMPLE_OF_TIME_WARP_PAD }, - NO_RETURN_ENTRANCE }, - - { { EntranceType::ChildBoss, RR_DEKU_TREE_BOSS_ENTRYWAY, RR_DEKU_TREE_BOSS_ROOM, ENTR_DEKU_TREE_BOSS_ENTRANCE }, - { EntranceType::ChildBoss, RR_DEKU_TREE_BOSS_ROOM, RR_DEKU_TREE_BOSS_ENTRYWAY, ENTR_DEKU_TREE_BOSS_DOOR } }, - { { EntranceType::ChildBoss, RR_DODONGOS_CAVERN_BOSS_ENTRYWAY, RR_DODONGOS_CAVERN_BOSS_ROOM, ENTR_DODONGOS_CAVERN_BOSS_ENTRANCE }, - { EntranceType::ChildBoss, RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_ENTRYWAY, ENTR_DODONGOS_CAVERN_BOSS_DOOR } }, - { { EntranceType::ChildBoss, RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY, RR_JABU_JABUS_BELLY_BOSS_ROOM, ENTR_JABU_JABU_BOSS_ENTRANCE }, - { EntranceType::ChildBoss, RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY, ENTR_JABU_JABU_BOSS_DOOR } }, - { { EntranceType::AdultBoss, RR_FOREST_TEMPLE_BOSS_ENTRYWAY, RR_FOREST_TEMPLE_BOSS_ROOM, ENTR_FOREST_TEMPLE_BOSS_ENTRANCE }, - { EntranceType::AdultBoss, RR_FOREST_TEMPLE_BOSS_ROOM, RR_FOREST_TEMPLE_BOSS_ENTRYWAY, ENTR_FOREST_TEMPLE_BOSS_DOOR } }, - { { EntranceType::AdultBoss, RR_FIRE_TEMPLE_BOSS_ENTRYWAY, RR_FIRE_TEMPLE_BOSS_ROOM, ENTR_FIRE_TEMPLE_BOSS_ENTRANCE }, - { EntranceType::AdultBoss, RR_FIRE_TEMPLE_BOSS_ROOM, RR_FIRE_TEMPLE_BOSS_ENTRYWAY, ENTR_FIRE_TEMPLE_BOSS_DOOR } }, - { { EntranceType::AdultBoss, RR_WATER_TEMPLE_BOSS_ENTRYWAY, RR_WATER_TEMPLE_BOSS_ROOM, ENTR_WATER_TEMPLE_BOSS_ENTRANCE }, - { EntranceType::AdultBoss, RR_WATER_TEMPLE_BOSS_ROOM, RR_WATER_TEMPLE_BOSS_ENTRYWAY, ENTR_WATER_TEMPLE_BOSS_DOOR } }, - { { EntranceType::AdultBoss, RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY, RR_SPIRIT_TEMPLE_BOSS_ROOM, ENTR_SPIRIT_TEMPLE_BOSS_ENTRANCE }, - { EntranceType::AdultBoss, RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY, ENTR_SPIRIT_TEMPLE_BOSS_DOOR } }, - { { EntranceType::AdultBoss, RR_SHADOW_TEMPLE_BOSS_ENTRYWAY, RR_SHADOW_TEMPLE_BOSS_ROOM, ENTR_SHADOW_TEMPLE_BOSS_ENTRANCE }, - { EntranceType::AdultBoss, RR_SHADOW_TEMPLE_BOSS_ROOM, RR_SHADOW_TEMPLE_BOSS_ENTRYWAY, ENTR_SHADOW_TEMPLE_BOSS_DOOR } }, - - { { EntranceType::BlueWarp, RR_DEKU_TREE_BOSS_ROOM, RR_KF_OUTSIDE_DEKU_TREE, ENTR_KOKIRI_FOREST_DEKU_TREE_BLUE_WARP }, - NO_RETURN_ENTRANCE }, - { { EntranceType::BlueWarp, RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DEATH_MOUNTAIN_TRAIL, ENTR_DEATH_MOUNTAIN_TRAIL_DODONGO_BLUE_WARP }, - NO_RETURN_ENTRANCE }, - { { EntranceType::BlueWarp, RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_ZORAS_FOUNTAIN, ENTR_ZORAS_FOUNTAIN_JABU_JABU_BLUE_WARP }, - NO_RETURN_ENTRANCE }, - { { EntranceType::BlueWarp, RR_FOREST_TEMPLE_BOSS_ROOM, RR_SACRED_FOREST_MEADOW, ENTR_SACRED_FOREST_MEADOW_FOREST_TEMPLE_BLUE_WARP }, - NO_RETURN_ENTRANCE }, - { { EntranceType::BlueWarp, RR_FIRE_TEMPLE_BOSS_ROOM, RR_DMC_CENTRAL_LOCAL, ENTR_DEATH_MOUNTAIN_CRATER_FIRE_TEMPLE_BLUE_WARP }, - NO_RETURN_ENTRANCE }, - { { EntranceType::BlueWarp, RR_WATER_TEMPLE_BOSS_ROOM, RR_LAKE_HYLIA, ENTR_LAKE_HYLIA_WATER_TEMPLE_BLUE_WARP }, - NO_RETURN_ENTRANCE }, - { { EntranceType::BlueWarp, RR_SPIRIT_TEMPLE_BOSS_ROOM, RR_DESERT_COLOSSUS, ENTR_DESERT_COLOSSUS_SPIRIT_TEMPLE_BLUE_WARP }, - NO_RETURN_ENTRANCE }, - { { EntranceType::BlueWarp, RR_SHADOW_TEMPLE_BOSS_ROOM, RR_GRAVEYARD_WARP_PAD_REGION, ENTR_GRAVEYARD_SHADOW_TEMPLE_BLUE_WARP }, - NO_RETURN_ENTRANCE }, - // clang-format on - }; - std::map priorityEntranceTable = { { "Bolero", { { RR_DMC_CENTRAL_LOCAL }, { EntranceType::OwlDrop, EntranceType::WarpSong } } }, { "Nocturne", @@ -1176,7 +1180,7 @@ int EntranceShuffler::ShuffleAllEntrances() { }; mEntranceShuffleFailure = false; - SetAllEntrancesData(entranceShuffleTable); + SetAllEntrancesData(); EntrancePools oneWayEntrancePools = {}; EntrancePools entrancePools = {}; @@ -1486,11 +1490,11 @@ int EntranceShuffler::ShuffleAllEntrances() { if (true /* ctx->GetOption(RSK_SHUFFLE_BLUEWARP_ENTRANCES).Is(RO_BLUEWARP_ENTRANCE_SHUFFLE_DUNGEON) */) { // If a boss room is inside a boss door, make the blue warp go outside the dungeon's entrance std::map bossExits = { - { EntranceNameByRegions(RR_DEKU_TREE_BOSS_ROOM, RR_DEKU_TREE_BOSS_ENTRYWAY), + { EntranceNameByRegions(RR_DEKU_TREE_BOSS_ROOM, RR_DEKU_TREE_BOSS_EXIT), GetEntrance(RR_DEKU_TREE_ENTRYWAY, RR_KF_OUTSIDE_DEKU_TREE) }, - { EntranceNameByRegions(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_ENTRYWAY), + { EntranceNameByRegions(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_EXIT), GetEntrance(RR_DODONGOS_CAVERN_ENTRYWAY, RR_DEATH_MOUNTAIN_TRAIL) }, - { EntranceNameByRegions(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY), + { EntranceNameByRegions(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_EXIT), GetEntrance(RR_JABU_JABUS_BELLY_ENTRYWAY, RR_ZORAS_FOUNTAIN) }, { EntranceNameByRegions(RR_FOREST_TEMPLE_BOSS_ROOM, RR_FOREST_TEMPLE_BOSS_ENTRYWAY), GetEntrance(RR_FOREST_TEMPLE_ENTRYWAY, RR_SACRED_FOREST_MEADOW) }, @@ -1528,11 +1532,11 @@ int EntranceShuffler::ShuffleAllEntrances() { // Pair std::vector bossRoomExitPairs = { { GetEntrance(RR_DEKU_TREE_BOSS_ROOM, RR_KF_OUTSIDE_DEKU_TREE), - GetEntrance(RR_DEKU_TREE_BOSS_ROOM, RR_DEKU_TREE_BOSS_ENTRYWAY) }, + GetEntrance(RR_DEKU_TREE_BOSS_ROOM, RR_DEKU_TREE_BOSS_EXIT) }, { GetEntrance(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DEATH_MOUNTAIN_TRAIL), - GetEntrance(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_ENTRYWAY) }, + GetEntrance(RR_DODONGOS_CAVERN_BOSS_ROOM, RR_DODONGOS_CAVERN_BOSS_EXIT) }, { GetEntrance(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_ZORAS_FOUNTAIN), - GetEntrance(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY) }, + GetEntrance(RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_JABU_JABUS_BELLY_BOSS_EXIT) }, { GetEntrance(RR_FOREST_TEMPLE_BOSS_ROOM, RR_SACRED_FOREST_MEADOW), GetEntrance(RR_FOREST_TEMPLE_BOSS_ROOM, RR_FOREST_TEMPLE_BOSS_ENTRYWAY) }, { GetEntrance(RR_FIRE_TEMPLE_BOSS_ROOM, RR_DMC_CENTRAL_LOCAL), @@ -1660,6 +1664,30 @@ void EntranceShuffler::ParseJson(nlohmann::json spoilerFileJson) { } } } catch (const std::exception& e) { throw e; } + // We may need to reset more things here or elsewhere in spoiler loading + RegionTable_Init(); + ApplyEntranceOverrides(); + SetAreas(); +} + +void EntranceShuffler::ApplyEntranceOverrides() { + SetAllEntrancesData(); + + for (size_t i = 0; i < entranceOverrides.size(); i++) { + EntranceOverride entranceOverride = entranceOverrides[i]; + + if (entranceOverride.index == 0 && entranceOverride.destination == 0 && entranceOverride.override == 0 && + entranceOverride.overrideDestination == 0) { + continue; + } + + Entrance* entrance = entranceMap[entranceOverride.index]; + Entrance* overrideEntrance = entranceMap[entranceOverride.override]; + + entrance->Disconnect(); + entrance->Connect(overrideEntrance->GetOriginalConnectedRegionKey()); + entrance->SetAsShuffled(); + } } } // namespace Rando diff --git a/soh/soh/Enhancements/randomizer/entrance.h b/soh/soh/Enhancements/randomizer/entrance.h index 8fc192ca1..5b14611fe 100644 --- a/soh/soh/Enhancements/randomizer/entrance.h +++ b/soh/soh/Enhancements/randomizer/entrance.h @@ -128,6 +128,7 @@ class EntranceShuffler { void CreateEntranceOverrides(); void UnshuffleAllEntrances(); void ParseJson(nlohmann::json spoilerFileJson); + void ApplyEntranceOverrides(); private: std::vector AssumeEntrancePool(std::vector& entrancePool); diff --git a/soh/soh/Enhancements/randomizer/fishsanity.cpp b/soh/soh/Enhancements/randomizer/fishsanity.cpp index 1b7eb7b92..e34799156 100644 --- a/soh/soh/Enhancements/randomizer/fishsanity.cpp +++ b/soh/soh/Enhancements/randomizer/fishsanity.cpp @@ -15,10 +15,6 @@ extern SaveContext gSaveContext; extern PlayState* gPlayState; } -#define FSi OTRGlobals::Instance->gRandoContext->GetFishsanity() - -#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get() - /** * @brief Parallel list of pond fish checks for both ages */ @@ -488,15 +484,15 @@ void Fishsanity::OnItemReceiveHandler(GetItemEntry itemEntry) { // C interface extern "C" { bool Randomizer_GetPondFishShuffled() { - return FSi->GetPondFishShuffled(); + return Rando::Context::GetInstance()->GetFishsanity()->GetPondFishShuffled(); } bool Randomizer_GetOverworldFishShuffled() { - return FSi->GetOverworldFishShuffled(); + return Rando::Context::GetInstance()->GetFishsanity()->GetOverworldFishShuffled(); } bool Randomizer_IsAdultPond() { - return FSi->IsAdultPond(); + return Rando::Context::GetInstance()->GetFishsanity()->IsAdultPond(); } void Fishsanity_DrawEffShadow(Actor* actor, Lights* lights, PlayState* play) { diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index 5da887494..994e0fba6 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -46,6 +46,7 @@ extern "C" { #include "src/overlays/actors/ovl_En_Hy/z_en_hy.h" #include "src/overlays/actors/ovl_En_Bom_Bowl_Pit/z_en_bom_bowl_pit.h" #include "src/overlays/actors/ovl_En_Ge1/z_en_ge1.h" +#include "src/overlays/actors/ovl_En_Ge2/z_en_ge2.h" #include "src/overlays/actors/ovl_En_Ds/z_en_ds.h" #include "src/overlays/actors/ovl_En_Gm/z_en_gm.h" #include "src/overlays/actors/ovl_En_Js/z_en_js.h" @@ -55,7 +56,6 @@ extern "C" { #include "src/overlays/actors/ovl_En_Xc/z_en_xc.h" #include "src/overlays/actors/ovl_Fishing/z_fishing.h" #include "src/overlays/actors/ovl_En_Mk/z_en_mk.h" -#include "src/overlays/actors/ovl_En_Ge1/z_en_ge1.h" #include "draw.h" extern SaveContext gSaveContext; @@ -69,10 +69,10 @@ extern void EnMk_Wait(EnMk* enMk, PlayState* play); extern void func_80ABA778(EnNiwLady* enNiwLady, PlayState* play); extern void EnGe1_Wait_Archery(EnGe1* enGe1, PlayState* play); extern void EnGe1_SetAnimationIdle(EnGe1* enGe1); +extern void EnGe1_SetAnimationIdle(EnGe1* enGe1); +extern void EnGe2_SetupCapturePlayer(EnGe2* enGe2, PlayState* play); } -#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get() - bool LocMatchesQuest(Rando::Location loc) { if (loc.GetQuest() == RCQUEST_BOTH) { return true; @@ -927,13 +927,6 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l *should = Flags_GetRandomizerInf(RAND_INF_LEARNED_EPONA_SONG); break; } - case VB_SET_CUCCO_COUNT: { - EnNiwLady* enNiwLady = va_arg(args, EnNiwLady*); - // Override starting Cucco count using setting value - enNiwLady->cuccosInPen = 7 - RAND_GET_OPTION(RSK_CUCCO_COUNT); - *should = false; - break; - } case VB_KING_ZORA_THANK_CHILD: { // Allow turning in Ruto's letter even if you have already rescued her if (!Flags_GetEventChkInf(EVENTCHKINF_KING_ZORA_MOVED)) { @@ -1444,6 +1437,13 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l } break; } + case VB_GERUDO_GUARD_SET_ACTION_AFTER_TALK: + if (gPlayState->msgCtx.choiceIndex == 0) { + EnGe2* enGe2 = va_arg(args, EnGe2*); + EnGe2_SetupCapturePlayer(enGe2, gPlayState); + *should = false; + } + break; case VB_GERUDOS_BE_FRIENDLY: { *should = CHECK_QUEST_ITEM(QUEST_GERUDO_CARD); break; @@ -2144,7 +2144,7 @@ void RandomizerOnActorUpdateHandler(void* refActor) { shutterDoor->unk_16E = 0; } } else if (actor->id == ACTOR_DOOR_GERUDO) { - DoorGerudo* gerudoDoor = (DoorGerudo*)actor; + DoorGerudo* gerudoDoor = reinterpret_cast(actor); gerudoDoor->actionFunc = func_8099485C; gerudoDoor->dyna.actor.world.pos.y = gerudoDoor->dyna.actor.home.pos.y + 200.0f; } diff --git a/soh/soh/Enhancements/randomizer/item.cpp b/soh/soh/Enhancements/randomizer/item.cpp index ee0dc9403..3b75a0c95 100644 --- a/soh/soh/Enhancements/randomizer/item.cpp +++ b/soh/soh/Enhancements/randomizer/item.cpp @@ -46,14 +46,20 @@ Item::~Item() = default; void Item::ApplyEffect() const { auto ctx = Rando::Context::GetInstance(); - ctx->GetLogic()->ApplyItemEffect(StaticData::RetrieveItem(randomizerGet), true); - ctx->GetLogic()->SetInLogic(logicVal, true); + auto logic = ctx->GetLogic(); + if (!logic->CalculatingAvailableChecks) { + logic->ApplyItemEffect(StaticData::RetrieveItem(randomizerGet), true); + } + logic->SetInLogic(logicVal, true); } void Item::UndoEffect() const { auto ctx = Rando::Context::GetInstance(); - ctx->GetLogic()->ApplyItemEffect(StaticData::RetrieveItem(randomizerGet), false); - ctx->GetLogic()->SetInLogic(logicVal, false); + auto logic = ctx->GetLogic(); + if (!logic->CalculatingAvailableChecks) { + logic->ApplyItemEffect(StaticData::RetrieveItem(randomizerGet), false); + } + logic->SetInLogic(logicVal, false); } const Text& Item::GetName() const { diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/deku_tree.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/deku_tree.cpp index ad3acef47..38a2dc63e 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/deku_tree.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/deku_tree.cpp @@ -437,10 +437,14 @@ void RegionTable_Init_DekuTree() { // Boss Room areaTable[RR_DEKU_TREE_BOSS_ENTRYWAY] = Region("Deku Tree Boss Entryway", SCENE_DEKU_TREE, {}, {}, { + // Exits + Entrance(RR_DEKU_TREE_BOSS_ROOM, []{return true;}), + }); + + areaTable[RR_DEKU_TREE_BOSS_EXIT] = Region("Deku Tree Boss Exit", "Deku Tree", {RA_DEKU_TREE}, NO_DAY_NIGHT_CYCLE, {}, {}, { // Exits Entrance(RR_DEKU_TREE_OUTSIDE_BOSS_ROOM, []{return ctx->GetDungeon(DEKU_TREE)->IsVanilla();}), Entrance(RR_DEKU_TREE_MQ_OUTSIDE_BOSS_ROOM, []{return ctx->GetDungeon(DEKU_TREE)->IsMQ();}), - Entrance(RR_DEKU_TREE_BOSS_ROOM, []{return true;}), }); areaTable[RR_DEKU_TREE_BOSS_ROOM] = Region("Deku Tree Boss Room", SCENE_DEKU_TREE_BOSS, { @@ -460,8 +464,8 @@ void RegionTable_Init_DekuTree() { LOCATION(RC_DEKU_TREE_QUEEN_GOHMA_GRASS_8, logic->CanCutShrubs()), }, { // Exits - Entrance(RR_DEKU_TREE_BOSS_ENTRYWAY, []{return true;}), - Entrance(RR_KF_OUTSIDE_DEKU_TREE, []{return logic->DekuTreeClear;}, false), + Entrance(RR_DEKU_TREE_BOSS_EXIT, []{return true;}), + Entrance(RR_KF_OUTSIDE_DEKU_TREE, []{return logic->DekuTreeClear;}, false), }); // clang-format on diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp index 7986512ce..4f56d0f65 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp @@ -268,6 +268,7 @@ void RegionTable_Init_DodongosCavern() { LOCATION(RC_DODONGOS_CAVERN_MQ_DEKU_SCRUB_LOBBY_FRONT, logic->CanStunDeku()), }, { //Exits + Entrance(RR_DODONGOS_CAVERN_MQ_BEGINNING, []{return true;}), Entrance(RR_DODONGOS_CAVERN_MQ_GOSSIP_STONE, []{return Here(RR_DODONGOS_CAVERN_MQ_LOBBY, []{return logic->CanBreakMudWalls() || logic->HasItem(RG_GORONS_BRACELET);});}), Entrance(RR_DODONGOS_CAVERN_MQ_MOUTH_SIDE_BRIDGE, []{return Here(RR_DODONGOS_CAVERN_MQ_LOBBY, []{return logic->BlastOrSmash() || logic->HasItem(RG_GORONS_BRACELET);});}), Entrance(RR_DODONGOS_CAVERN_MQ_STAIRS_LOWER, []{return Here(RR_DODONGOS_CAVERN_MQ_LOBBY, []{return logic->BlastOrSmash() || logic->HasItem(RG_GORONS_BRACELET);});}), @@ -559,10 +560,14 @@ void RegionTable_Init_DodongosCavern() { // Boss Room areaTable[RR_DODONGOS_CAVERN_BOSS_ENTRYWAY] = Region("Dodongos Cavern Boss Entryway", SCENE_DODONGOS_CAVERN, {}, {}, { + // Exits + Entrance(RR_DODONGOS_CAVERN_BOSS_ROOM, []{return true;}), + }); + + areaTable[RR_DODONGOS_CAVERN_BOSS_EXIT] = Region("Dodongos Cavern Boss Exit", SCENE_DODONGOS_CAVERN, {}, {}, { // Exits Entrance(RR_DODONGOS_CAVERN_BOSS_AREA, []{return ctx->GetDungeon(DODONGOS_CAVERN)->IsVanilla();}), Entrance(RR_DODONGOS_CAVERN_MQ_BEHIND_MOUTH, []{return ctx->GetDungeon(DODONGOS_CAVERN)->IsMQ();}), - Entrance(RR_DODONGOS_CAVERN_BOSS_ROOM, []{return true;}), }); areaTable[RR_DODONGOS_CAVERN_BOSS_ROOM] = Region("Dodongos Cavern Boss Room", SCENE_DODONGOS_CAVERN_BOSS, { @@ -575,8 +580,8 @@ void RegionTable_Init_DodongosCavern() { LOCATION(RC_KING_DODONGO, logic->DodongosCavernClear), }, { // Exits - Entrance(RR_DODONGOS_CAVERN_BOSS_ENTRYWAY, []{return true;}), - Entrance(RR_DEATH_MOUNTAIN_TRAIL, []{return logic->DodongosCavernClear;}, false), + Entrance(RR_DODONGOS_CAVERN_BOSS_EXIT, []{return true;}), + Entrance(RR_DEATH_MOUNTAIN_TRAIL, []{return logic->DodongosCavernClear;}, false), }); // clang-format on diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp index 867bb77ec..f57d6bd21 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp @@ -38,7 +38,7 @@ void RegionTable_Init_FireTemple() { }, { //Exits Entrance(RR_FIRE_TEMPLE_FIRST_ROOM, []{return true;}), - Entrance(RR_FIRE_TEMPLE_BOSS_ENTRYWAY, []{return logic->HasItem(RG_FIRE_TEMPLE_BOSS_KEY) && ((logic->IsAdult && (ctx->GetTrickOption(RT_FIRE_BOSS_DOOR_JUMP) || Here(RR_FIRE_TEMPLE_FIRE_MAZE_UPPER, []{return logic->CanUse(RG_MEGATON_HAMMER);}))) || logic->CanUse(RG_HOVER_BOOTS));}), + Entrance(RR_FIRE_TEMPLE_BOSS_ENTRYWAY, []{return logic->IsAdult && (ctx->GetTrickOption(RT_FIRE_BOSS_DOOR_JUMP) || Here(RR_FIRE_TEMPLE_FIRE_MAZE_UPPER, []{return logic->CanUse(RG_MEGATON_HAMMER);}) || logic->CanUse(RG_HOVER_BOOTS));}), }); areaTable[RR_FIRE_TEMPLE_LOOP_ENEMIES] = Region("Fire Temple Loop Enemies", SCENE_FIRE_TEMPLE, {}, {}, { @@ -444,7 +444,7 @@ void RegionTable_Init_FireTemple() { Entrance(RR_FIRE_TEMPLE_MQ_FIRST_ROOM_UPPER, []{return true;}), //Child cannot make it to the north side torches without a hook without specifically bunny hood speed + hover boots Entrance(RR_FIRE_TEMPLE_MQ_NEAR_BOSS_ROOM_NORTH, []{return logic->FireTimer() > 32 && (logic->CanUse(RG_HOOKSHOT) || (logic->IsAdult && logic->CanUse(RG_HOVER_BOOTS)));}), - Entrance(RR_FIRE_TEMPLE_BOSS_ENTRYWAY, []{return logic->HasItem(RG_FIRE_TEMPLE_BOSS_KEY) && logic->FireTimer() >= 15 && ((logic->IsAdult && (ctx->GetTrickOption(RT_FIRE_BOSS_DOOR_JUMP) || logic->CanUse(RG_HOVER_BOOTS))) || (logic->IsAdult && logic->HitFireTemplePlatform) || (logic->HitFireTemplePlatform && logic->CanUse(RG_HOVER_BOOTS)));}), + Entrance(RR_FIRE_TEMPLE_BOSS_ENTRYWAY, []{return logic->FireTimer() >= 15 && ((logic->IsAdult && (ctx->GetTrickOption(RT_FIRE_BOSS_DOOR_JUMP) || logic->CanUse(RG_HOVER_BOOTS))) || (logic->IsAdult && logic->HitFireTemplePlatform) || (logic->HitFireTemplePlatform && logic->CanUse(RG_HOVER_BOOTS)));}), }); //This room assumes tunic logic is handled on entry. @@ -739,7 +739,7 @@ void RegionTable_Init_FireTemple() { // Exits Entrance(RR_FIRE_TEMPLE_NEAR_BOSS_ROOM, []{return ctx->GetDungeon(FIRE_TEMPLE)->IsVanilla() && false;}), Entrance(RR_FIRE_TEMPLE_MQ_NEAR_BOSS_ROOM, []{return ctx->GetDungeon(FIRE_TEMPLE)->IsMQ() && false;}), - Entrance(RR_FIRE_TEMPLE_BOSS_ROOM, []{return true;}), + Entrance(RR_FIRE_TEMPLE_BOSS_ROOM, []{return logic->HasItem(RG_FIRE_TEMPLE_BOSS_KEY);}), }); areaTable[RR_FIRE_TEMPLE_BOSS_ROOM] = Region("Fire Temple Boss Room", SCENE_FIRE_TEMPLE_BOSS, { diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp index 097618de0..d3149c009 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/forest_temple.cpp @@ -298,7 +298,7 @@ void RegionTable_Init_ForestTemple() { }, { //Exits Entrance(RR_FOREST_TEMPLE_LOBBY, []{return true;}), - Entrance(RR_FOREST_TEMPLE_BOSS_ENTRYWAY, []{return logic->HasItem(RG_FOREST_TEMPLE_BOSS_KEY);}), + Entrance(RR_FOREST_TEMPLE_BOSS_ENTRYWAY, []{return true;}), }); #pragma endregion @@ -591,7 +591,7 @@ void RegionTable_Init_ForestTemple() { areaTable[RR_FOREST_TEMPLE_MQ_BOSS_REGION] = Region("Forest Temple MQ Boss Region", SCENE_FOREST_TEMPLE, {}, {}, { //Exits Entrance(RR_FOREST_TEMPLE_MQ_BASEMENT, []{return logic->ForestOpenBossCorridor;}), - Entrance(RR_FOREST_TEMPLE_BOSS_ENTRYWAY, []{return logic->HasItem(RG_FOREST_TEMPLE_BOSS_KEY);}), + Entrance(RR_FOREST_TEMPLE_BOSS_ENTRYWAY, []{return true;}), }); #pragma endregion @@ -601,7 +601,7 @@ void RegionTable_Init_ForestTemple() { // Exits Entrance(RR_FOREST_TEMPLE_BOSS_REGION, []{return ctx->GetDungeon(FOREST_TEMPLE)->IsVanilla() && false;}), Entrance(RR_FOREST_TEMPLE_MQ_BOSS_REGION, []{return ctx->GetDungeon(FOREST_TEMPLE)->IsMQ() && false;}), - Entrance(RR_FOREST_TEMPLE_BOSS_ROOM, []{return true;}), + Entrance(RR_FOREST_TEMPLE_BOSS_ROOM, []{return logic->HasItem(RG_FOREST_TEMPLE_BOSS_KEY);}), }); areaTable[RR_FOREST_TEMPLE_BOSS_ROOM] = Region("Forest Temple Boss Room", SCENE_FOREST_TEMPLE_BOSS, { diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp index 8d7c37073..8571ba384 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp @@ -29,12 +29,7 @@ void RegionTable_Init_GanonsCastle() { Entrance(RR_GANONS_CASTLE_SHADOW_TRIAL, []{return true;}), Entrance(RR_GANONS_CASTLE_SPIRIT_TRIAL, []{return true;}), Entrance(RR_GANONS_CASTLE_LIGHT_TRIAL, []{return logic->CanUse(RG_GOLDEN_GAUNTLETS);}), - Entrance(RR_GANONS_TOWER_ENTRYWAY, []{return (logic->ForestTrialClear || ctx->GetTrial(TK_FOREST_TRIAL)->IsSkipped()) && - (logic->FireTrialClear || ctx->GetTrial(TK_FIRE_TRIAL)->IsSkipped()) && - (logic->WaterTrialClear || ctx->GetTrial(TK_WATER_TRIAL)->IsSkipped()) && - (logic->ShadowTrialClear || ctx->GetTrial(TK_SHADOW_TRIAL)->IsSkipped()) && - (logic->SpiritTrialClear || ctx->GetTrial(TK_SPIRIT_TRIAL)->IsSkipped()) && - (logic->LightTrialClear || ctx->GetTrial(TK_LIGHT_TRIAL)->IsSkipped());}), + Entrance(RR_GANONS_TOWER_ENTRYWAY, []{return true;}), Entrance(RR_GANONS_CASTLE_DEKU_SCRUBS, []{return ctx->GetTrickOption(RT_LENS_GANON) || logic->CanUse(RG_LENS_OF_TRUTH);}), }); @@ -162,13 +157,7 @@ void RegionTable_Init_GanonsCastle() { Entrance(RR_GANONS_CASTLE_MQ_SHADOW_TRIAL_STARTING_LEDGE, []{return true;}), Entrance(RR_GANONS_CASTLE_MQ_SPIRIT_TRIAL_CHAIRS_ROOM, []{return true;}), Entrance(RR_GANONS_CASTLE_MQ_LIGHT_TRIAL_DINOLFOS_ROOM, []{return Here(RR_GANONS_CASTLE_MQ_MAIN, []{return logic->CanUse(RG_GOLDEN_GAUNTLETS);});}), - //RANDOTODO could we just set these events automatically based on the setting? - Entrance(RR_GANONS_TOWER_ENTRYWAY, []{return (logic->ForestTrialClear || ctx->GetTrial(TK_FOREST_TRIAL)->IsSkipped()) && - (logic->FireTrialClear || ctx->GetTrial(TK_FIRE_TRIAL)->IsSkipped()) && - (logic->WaterTrialClear || ctx->GetTrial(TK_WATER_TRIAL)->IsSkipped()) && - (logic->ShadowTrialClear || ctx->GetTrial(TK_SHADOW_TRIAL)->IsSkipped()) && - (logic->SpiritTrialClear || ctx->GetTrial(TK_SPIRIT_TRIAL)->IsSkipped()) && - (logic->LightTrialClear || ctx->GetTrial(TK_LIGHT_TRIAL)->IsSkipped());}), + Entrance(RR_GANONS_TOWER_ENTRYWAY, []{return true;}), Entrance(RR_GANONS_CASTLE_MQ_DEKU_SCRUBS, []{return ctx->GetTrickOption(RT_LENS_GANON_MQ) || logic->CanUse(RG_LENS_OF_TRUTH);}), }); @@ -436,7 +425,13 @@ void RegionTable_Init_GanonsCastle() { //Exits Entrance(RR_GANONS_CASTLE_LOBBY, []{return ctx->GetDungeon(GANONS_CASTLE)->IsVanilla();}), Entrance(RR_GANONS_CASTLE_MQ_MAIN, []{return ctx->GetDungeon(GANONS_CASTLE)->IsMQ();}), - Entrance(RR_GANONS_TOWER_FLOOR_1, []{return true;}), + //RANDOTODO could we just set these events automatically based on the setting? + Entrance(RR_GANONS_TOWER_FLOOR_1, []{return (logic->ForestTrialClear || ctx->GetTrial(TK_FOREST_TRIAL)->IsSkipped()) && + (logic->FireTrialClear || ctx->GetTrial(TK_FIRE_TRIAL)->IsSkipped()) && + (logic->WaterTrialClear || ctx->GetTrial(TK_WATER_TRIAL)->IsSkipped()) && + (logic->ShadowTrialClear || ctx->GetTrial(TK_SHADOW_TRIAL)->IsSkipped()) && + (logic->SpiritTrialClear || ctx->GetTrial(TK_SPIRIT_TRIAL)->IsSkipped()) && + (logic->LightTrialClear || ctx->GetTrial(TK_LIGHT_TRIAL)->IsSkipped());}), }); areaTable[RR_GANONS_TOWER_FLOOR_1] = Region("Ganon's Tower Floor 1", SCENE_GANONS_TOWER, {}, {}, { diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp index 4057e2c38..5151377ce 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp @@ -84,7 +84,7 @@ void RegionTable_Init_IceCavern() { //Exits //the switch for the glass blocking the entrance is linked to the switch that controls the glass around the skulltulla in RR_ICE_CAVERN_MQ_SCARECROW_ROOM //if you clear the ice, you can hit it with a pot from here. - Entrance(RR_ICE_CAVERN_BEGINNING, []{return logic->BlueFire();}), + Entrance(RR_ICE_CAVERN_MQ_BEGINNING, []{return logic->BlueFire();}), Entrance(RR_ICE_CAVERN_MQ_MAP_ROOM, []{return Here(RR_ICE_CAVERN_MQ_BEGINNING, []{return logic->CanKillEnemy(RE_WHITE_WOLFOS) && logic->CanKillEnemy(RE_FREEZARD);});}), Entrance(RR_ICE_CAVERN_MQ_COMPASS_ROOM, []{return logic->IsAdult && logic->BlueFire();}), Entrance(RR_ICE_CAVERN_MQ_SCARECROW_ROOM, []{return logic->BlueFire();}), diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/jabujabus_belly.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/jabujabus_belly.cpp index 2685e02f8..31026b189 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/jabujabus_belly.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/jabujabus_belly.cpp @@ -353,10 +353,14 @@ void RegionTable_Init_JabuJabusBelly() { // Boss Room areaTable[RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY] = Region("Jabu Jabus Belly Boss Entryway", SCENE_JABU_JABU, {}, {}, { + // Exits + Entrance(RR_JABU_JABUS_BELLY_BOSS_ROOM, []{return true;}), + }); + + areaTable[RR_JABU_JABUS_BELLY_BOSS_EXIT] = Region("Jabu Jabus Belly Boss Exit", "Jabu Jabus Belly", {RA_JABU_JABUS_BELLY}, NO_DAY_NIGHT_CYCLE, {}, {}, { // Exits Entrance(RR_JABU_JABUS_BELLY_NEAR_BOSS_ROOM, []{return ctx->GetDungeon(JABU_JABUS_BELLY)->IsVanilla();}), Entrance(RR_JABU_JABUS_BELLY_MQ_EAST_ROOM, []{return ctx->GetDungeon(JABU_JABUS_BELLY)->IsMQ();}), - Entrance(RR_JABU_JABUS_BELLY_BOSS_ROOM, []{return true;}), }); areaTable[RR_JABU_JABUS_BELLY_BOSS_ROOM] = Region("Jabu Jabus Belly Boss Room", SCENE_JABU_JABU_BOSS, { @@ -374,8 +378,8 @@ void RegionTable_Init_JabuJabusBelly() { LOCATION(RC_BARINADE, logic->JabuJabusBellyClear), }, { // Exits - Entrance(RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY, []{return false;}), - Entrance(RR_ZORAS_FOUNTAIN, []{return logic->JabuJabusBellyClear;}, false), + Entrance(RR_JABU_JABUS_BELLY_BOSS_EXIT, []{return false;}), + Entrance(RR_ZORAS_FOUNTAIN, []{return logic->JabuJabusBellyClear;}, false), }); // clang-format on diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp index aa07ca269..127320129 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp @@ -112,7 +112,7 @@ void RegionTable_Init_ShadowTemple() { LOCATION(RC_SHADOW_TEMPLE_AFTER_SHIP_LOWER_HEART, (logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_DISTANT_SCARECROW) || (ctx->GetTrickOption(RT_SHADOW_STATUE) && logic->CanUse(RG_BOMBCHU_5))) && logic->CanUse(RG_SONG_OF_TIME) || (logic->CanUse(RG_DISTANT_SCARECROW) && logic->CanUse(RG_HOVER_BOOTS))), }, { //Exits - Entrance(RR_SHADOW_TEMPLE_BOSS_ENTRYWAY, []{return (logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_DISTANT_SCARECROW) || (ctx->GetTrickOption(RT_SHADOW_STATUE) && logic->CanUse(RG_BOMBCHU_5))) && logic->SmallKeys(RR_SHADOW_TEMPLE, 5) && logic->CanUse(RG_HOVER_BOOTS) && logic->HasItem(RG_SHADOW_TEMPLE_BOSS_KEY);}) + Entrance(RR_SHADOW_TEMPLE_BOSS_ENTRYWAY, []{return (logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_DISTANT_SCARECROW) || (ctx->GetTrickOption(RT_SHADOW_STATUE) && logic->CanUse(RG_BOMBCHU_5))) && logic->SmallKeys(RR_SHADOW_TEMPLE, 5) && logic->CanUse(RG_HOVER_BOOTS);}) }); #pragma endregion @@ -370,7 +370,7 @@ void RegionTable_Init_ShadowTemple() { }, { //Exits Entrance(RR_SHADOW_TEMPLE_MQ_ACROSS_CHASM, []{return logic->CanUse(RG_HOVER_BOOTS) && (ctx->GetTrickOption(RT_LENS_SHADOW_MQ) || logic->CanUse(RG_LENS_OF_TRUTH));}), - Entrance(RR_SHADOW_TEMPLE_BOSS_ENTRYWAY, []{return logic->HasItem(RG_SHADOW_TEMPLE_BOSS_KEY);}), + Entrance(RR_SHADOW_TEMPLE_BOSS_ENTRYWAY, []{return true;}), }); //Assumes lens is checked on entry @@ -403,7 +403,7 @@ void RegionTable_Init_ShadowTemple() { // Exits Entrance(RR_SHADOW_TEMPLE_BEYOND_BOAT, []{return ctx->GetDungeon(SHADOW_TEMPLE)->IsVanilla() && false;}), Entrance(RR_SHADOW_TEMPLE_MQ_BEYOND_BOAT, []{return ctx->GetDungeon(SHADOW_TEMPLE)->IsMQ() && false;}), - Entrance(RR_SHADOW_TEMPLE_BOSS_ROOM, []{return true;}), + Entrance(RR_SHADOW_TEMPLE_BOSS_ROOM, []{return logic->HasItem(RG_SHADOW_TEMPLE_BOSS_KEY);}), }); areaTable[RR_SHADOW_TEMPLE_BOSS_ROOM] = Region("Shadow Temple Boss Room", SCENE_SHADOW_TEMPLE_BOSS, { diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/spirit_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/spirit_temple.cpp index 728762080..98c783e20 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/spirit_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/spirit_temple.cpp @@ -145,7 +145,7 @@ void RegionTable_Init_SpiritTemple() { areaTable[RR_SPIRIT_TEMPLE_INSIDE_STATUE_HEAD] = Region("Spirit Temple Inside Statue Head", SCENE_SPIRIT_TEMPLE, {}, {}, { // Exits Entrance(RR_SPIRIT_TEMPLE_CENTRAL_CHAMBER, []{return true;}), - Entrance(RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY, []{return logic->HasItem(RG_SPIRIT_TEMPLE_BOSS_KEY);}), + Entrance(RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY, []{return true;}), }); #pragma endregion @@ -541,7 +541,7 @@ void RegionTable_Init_SpiritTemple() { areaTable[RR_SPIRIT_TEMPLE_MQ_INSIDE_STATUE_HEAD] = Region("Spirit Temple MQ Inside Statue Head", SCENE_SPIRIT_TEMPLE, {}, {}, { // Exits Entrance(RR_SPIRIT_TEMPLE_MQ_LOBBY, []{return true;}), - Entrance(RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY, []{return logic->HasItem(RG_SPIRIT_TEMPLE_BOSS_KEY);}), + Entrance(RR_SPIRIT_TEMPLE_BOSS_ENTRYWAY, []{return true;}), }); #pragma endregion @@ -551,7 +551,7 @@ void RegionTable_Init_SpiritTemple() { // Exits Entrance(RR_SPIRIT_TEMPLE_INSIDE_STATUE_HEAD, []{return ctx->GetDungeon(SPIRIT_TEMPLE)->IsVanilla() && false;}), Entrance(RR_SPIRIT_TEMPLE_MQ_INSIDE_STATUE_HEAD, []{return ctx->GetDungeon(SPIRIT_TEMPLE)->IsMQ() && false;}), - Entrance(RR_SPIRIT_TEMPLE_BOSS_ROOM, []{return true;}), + Entrance(RR_SPIRIT_TEMPLE_BOSS_ROOM, []{return logic->HasItem(RG_SPIRIT_TEMPLE_BOSS_KEY);}), }); areaTable[RR_SPIRIT_TEMPLE_BOSS_ROOM] = Region("Spirit Temple Boss Room", SCENE_SPIRIT_TEMPLE_BOSS, { diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/water_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/water_temple.cpp index b2a71820b..e8fb4e6b6 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/water_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/water_temple.cpp @@ -277,7 +277,7 @@ void RegionTable_Init_WaterTemple() { }, { //Exits Entrance(RR_WATER_TEMPLE_LOBBY, []{return true;}), - Entrance(RR_WATER_TEMPLE_BOSS_ENTRYWAY, []{return logic->HasItem(RG_WATER_TEMPLE_BOSS_KEY);}), + Entrance(RR_WATER_TEMPLE_BOSS_ENTRYWAY, []{return true;}), }); #pragma endregion @@ -365,7 +365,7 @@ void RegionTable_Init_WaterTemple() { areaTable[RR_WATER_TEMPLE_MQ_BOSS_DOOR] = Region("Water Temple MQ Main", SCENE_WATER_TEMPLE, {}, {}, { //Exits Entrance(RR_WATER_TEMPLE_MQ_3F_NORTH_LEDGE, []{return logic->CanUse(RG_ICE_ARROWS) || logic->TakeDamage();}), - Entrance(RR_WATER_TEMPLE_BOSS_ENTRYWAY, []{return logic->HasItem(RG_WATER_TEMPLE_BOSS_KEY);}), + Entrance(RR_WATER_TEMPLE_BOSS_ENTRYWAY, []{return true;}), }); areaTable[RR_WATER_TEMPLE_MQ_EAST_TOWER] = Region("Water Temple MQ East Tower", SCENE_WATER_TEMPLE, { @@ -843,7 +843,7 @@ void RegionTable_Init_WaterTemple() { // Exits Entrance(RR_WATER_TEMPLE_PRE_BOSS_ROOM, []{return ctx->GetDungeon(WATER_TEMPLE)->IsVanilla() && false;}), Entrance(RR_WATER_TEMPLE_MQ_BOSS_DOOR, []{return ctx->GetDungeon(WATER_TEMPLE)->IsMQ() && false;}), - Entrance(RR_WATER_TEMPLE_BOSS_ROOM, []{return true;}), + Entrance(RR_WATER_TEMPLE_BOSS_ROOM, []{return logic->HasItem(RG_WATER_TEMPLE_BOSS_KEY);}), }); areaTable[RR_WATER_TEMPLE_BOSS_ROOM] = Region("Water Temple Boss Room", SCENE_WATER_TEMPLE_BOSS, { diff --git a/soh/soh/Enhancements/randomizer/location_access/overworld/lake_hylia.cpp b/soh/soh/Enhancements/randomizer/location_access/overworld/lake_hylia.cpp index 2eaa253aa..f5335b084 100644 --- a/soh/soh/Enhancements/randomizer/location_access/overworld/lake_hylia.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/overworld/lake_hylia.cpp @@ -117,7 +117,7 @@ void RegionTable_Init_LakeHylia() { areaTable[RR_LH_LAB] = Region("LH Lab", SCENE_LAKESIDE_LABORATORY, {}, { //Locations - LOCATION(RC_LH_LAB_DIVE, logic->HasItem(RG_GOLDEN_SCALE) || (ctx->GetTrickOption(RT_LH_LAB_DIVING) && logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_HOOKSHOT))), + LOCATION(RC_LH_LAB_DIVE, logic->HasItem(RG_GOLDEN_SCALE) || (ctx->GetTrickOption(RT_LH_LAB_DIVING) && logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_HOOKSHOT) && logic->HasItem(RG_BRONZE_SCALE))), LOCATION(RC_LH_TRADE_FROG, logic->IsAdult && logic->CanUse(RG_EYEBALL_FROG)), LOCATION(RC_LH_GS_LAB_CRATE, logic->CanUse(RG_IRON_BOOTS) && logic->CanUse(RG_HOOKSHOT) && logic->CanBreakCrates()), LOCATION(RC_LH_LAB_FRONT_RUPEE, logic->CanUse(RG_IRON_BOOTS) || logic->HasItem(RG_GOLDEN_SCALE)), diff --git a/soh/soh/Enhancements/randomizer/location_list.cpp b/soh/soh/Enhancements/randomizer/location_list.cpp index 2812b0135..597828e34 100644 --- a/soh/soh/Enhancements/randomizer/location_list.cpp +++ b/soh/soh/Enhancements/randomizer/location_list.cpp @@ -828,20 +828,6 @@ void Rando::StaticData::InitLocationTable() { locationTable[RC_DMC_GREAT_FAIRY_REWARD] = Location::Base(RC_DMC_GREAT_FAIRY_REWARD, RCQUEST_BOTH, RCTYPE_STANDARD, RCAREA_DEATH_MOUNTAIN_CRATER, ACTOR_BG_DY_YOSEIZO, SCENE_GREAT_FAIRYS_FOUNTAIN_MAGIC, 2, "Great Fairy Reward", RHT_DMC_GREAT_FAIRY_REWARD, RG_PROGRESSIVE_MAGIC_METER, SpoilerCollectionCheck::RandomizerInf(RAND_INF_DMC_GREAT_FAIRY_REWARD), true); locationTable[RC_OGC_GREAT_FAIRY_REWARD] = Location::Base(RC_OGC_GREAT_FAIRY_REWARD, RCQUEST_BOTH, RCTYPE_STANDARD, RCAREA_HYRULE_CASTLE, ACTOR_BG_DY_YOSEIZO, SCENE_GREAT_FAIRYS_FOUNTAIN_MAGIC, 3, "OGC Great Fairy Reward", "OGC Great Fairy Reward", RHT_OGC_GREAT_FAIRY_REWARD, RG_DOUBLE_DEFENSE, SpoilerCollectionCheck::RandomizerInf(RAND_INF_OGC_GREAT_FAIRY_REWARD), true); - // Songs - locationTable[RC_SHEIK_IN_FOREST] = Location::Base(RC_SHEIK_IN_FOREST, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_SACRED_FOREST_MEADOW, 0x00, "Sheik in Forest", "Sheik in Forest", RHT_SHEIK_IN_FOREST, RG_MINUET_OF_FOREST, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_MINUET_OF_FOREST), true); - locationTable[RC_SHEIK_IN_CRATER] = Location::Base(RC_SHEIK_IN_CRATER, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_DEATH_MOUNTAIN_CRATER, 0x00, "Sheik in Crater", "Sheik in Crater", RHT_SHEIK_IN_CRATER, RG_BOLERO_OF_FIRE, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_BOLERO_OF_FIRE), true); - locationTable[RC_SHEIK_IN_ICE_CAVERN] = Location::Base(RC_SHEIK_IN_ICE_CAVERN, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_ICE_CAVERN, 0x00, "Sheik in Ice Cavern", "Sheik in Ice Cavern", RHT_SHEIK_IN_ICE_CAVERN, RG_SERENADE_OF_WATER, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_SERENADE_OF_WATER), true); - locationTable[RC_SHEIK_AT_COLOSSUS] = Location::Base(RC_SHEIK_AT_COLOSSUS, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_DESERT_COLOSSUS, 0x00, "Sheik at Colossus", "Sheik at Colossus", RHT_SHEIK_AT_COLOSSUS, RG_REQUIEM_OF_SPIRIT, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_REQUIEM_OF_SPIRIT), true); - locationTable[RC_SHEIK_IN_KAKARIKO] = Location::Base(RC_SHEIK_IN_KAKARIKO, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_KAKARIKO_VILLAGE, 0x00, "Sheik in Kakariko", "Sheik in Kakariko", RHT_SHEIK_IN_KAKARIKO, RG_NOCTURNE_OF_SHADOW, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL), true); - locationTable[RC_SHEIK_AT_TEMPLE] = Location::Base(RC_SHEIK_AT_TEMPLE, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_TEMPLE_OF_TIME, 0x00, "Sheik at Temple", "Sheik at Temple", RHT_SHEIK_AT_TEMPLE, RG_PRELUDE_OF_LIGHT, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_PRELUDE_OF_LIGHT), true); - locationTable[RC_SONG_FROM_IMPA] = Location::Base(RC_SONG_FROM_IMPA, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, RCAREA_HYRULE_CASTLE, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, "Song from Impa", "Song from Impa", RHT_SONG_FROM_IMPA, RG_ZELDAS_LULLABY, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_ZELDAS_LULLABY), true); - locationTable[RC_SONG_FROM_MALON] = Location::Base(RC_SONG_FROM_MALON, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_LON_LON_RANCH, 0x00, "Song from Malon", "Song from Malon", RHT_SONG_FROM_MALON, RG_EPONAS_SONG, SpoilerCollectionCheck::RandomizerInf(RAND_INF_LEARNED_EPONA_SONG), true); - locationTable[RC_SONG_FROM_SARIA] = Location::Base(RC_SONG_FROM_SARIA, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_SACRED_FOREST_MEADOW, 0x00, "Song from Saria", "Song from Saria", RHT_SONG_FROM_SARIA, RG_SARIAS_SONG, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_SARIAS_SONG), true); - locationTable[RC_SONG_FROM_ROYAL_FAMILYS_TOMB] = Location::Base(RC_SONG_FROM_ROYAL_FAMILYS_TOMB, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_ROYAL_FAMILYS_TOMB, 0x00, "Song from Royal Family's Tomb", "Song from Royal Family's Tomb", RHT_SONG_FROM_ROYAL_FAMILYS_TOMB, RG_SUNS_SONG, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_SUNS_SONG), true); - locationTable[RC_SONG_FROM_OCARINA_OF_TIME] = Location::Base(RC_SONG_FROM_OCARINA_OF_TIME, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, ACTOR_ID_MAX, SCENE_HYRULE_FIELD, 0x00, "Song from Ocarina of Time", "Song from Ocarina of Time", RHT_SONG_FROM_OCARINA_OF_TIME, RG_SONG_OF_TIME, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_SONG_OF_TIME), true); - locationTable[RC_SONG_FROM_WINDMILL] = Location::Base(RC_SONG_FROM_WINDMILL, RCQUEST_BOTH, RCTYPE_SONG_LOCATION, RCAREA_KAKARIKO_VILLAGE, ACTOR_ID_MAX, SCENE_WINDMILL_AND_DAMPES_GRAVE, 0x00, "Song from Windmill", "Song from Windmill", RHT_SONG_FROM_WINDMILL, RG_SONG_OF_STORMS, SpoilerCollectionCheck::EventChkInf(EVENTCHKINF_LEARNED_SONG_OF_STORMS), true); - /*------------------------------- --- SHOPS --- 8 6 2 4 diff --git a/soh/soh/Enhancements/randomizer/logic.cpp b/soh/soh/Enhancements/randomizer/logic.cpp index ca52009ac..da20315a2 100644 --- a/soh/soh/Enhancements/randomizer/logic.cpp +++ b/soh/soh/Enhancements/randomizer/logic.cpp @@ -2328,8 +2328,10 @@ void Logic::SetInLogic(LogicVal logicVal, bool value) { inLogic[logicVal] = value; } -void Logic::Reset() { - NewSaveContext(); +void Logic::Reset(bool resetSaveContext /*= true*/) { + if (resetSaveContext) { + NewSaveContext(); + } StartPerformanceTimer(PT_LOGIC_RESET); memset(inLogic, false, sizeof(inLogic)); // Settings-dependent variables @@ -2368,37 +2370,39 @@ void Logic::Reset() { ShadowTrialClear = false; LightTrialClear = false; - // Ocarina C Buttons - bool ocBtnShuffle = ctx->GetOption(RSK_SHUFFLE_OCARINA_BUTTONS).Is(true); - SetRandoInf(RAND_INF_HAS_OCARINA_A, !ocBtnShuffle); - SetRandoInf(RAND_INF_HAS_OCARINA_C_UP, !ocBtnShuffle); - SetRandoInf(RAND_INF_HAS_OCARINA_C_DOWN, !ocBtnShuffle); - SetRandoInf(RAND_INF_HAS_OCARINA_C_LEFT, !ocBtnShuffle); - SetRandoInf(RAND_INF_HAS_OCARINA_C_RIGHT, !ocBtnShuffle); + if (resetSaveContext) { + // Ocarina C Buttons + bool ocBtnShuffle = ctx->GetOption(RSK_SHUFFLE_OCARINA_BUTTONS).Is(true); + SetRandoInf(RAND_INF_HAS_OCARINA_A, !ocBtnShuffle); + SetRandoInf(RAND_INF_HAS_OCARINA_C_UP, !ocBtnShuffle); + SetRandoInf(RAND_INF_HAS_OCARINA_C_DOWN, !ocBtnShuffle); + SetRandoInf(RAND_INF_HAS_OCARINA_C_LEFT, !ocBtnShuffle); + SetRandoInf(RAND_INF_HAS_OCARINA_C_RIGHT, !ocBtnShuffle); - // Progressive Items - SetUpgrade(UPG_STICKS, ctx->GetOption(RSK_SHUFFLE_DEKU_STICK_BAG).Is(true) ? 0 : 1); - SetUpgrade(UPG_NUTS, ctx->GetOption(RSK_SHUFFLE_DEKU_NUT_BAG).Is(true) ? 0 : 1); + // Progressive Items + SetUpgrade(UPG_STICKS, ctx->GetOption(RSK_SHUFFLE_DEKU_STICK_BAG).Is(true) ? 0 : 1); + SetUpgrade(UPG_NUTS, ctx->GetOption(RSK_SHUFFLE_DEKU_NUT_BAG).Is(true) ? 0 : 1); - // If we're not shuffling swim, we start with it - if (ctx->GetOption(RSK_SHUFFLE_SWIM).Is(false)) { - SetRandoInf(RAND_INF_CAN_SWIM, true); - } + // If we're not shuffling swim, we start with it + if (ctx->GetOption(RSK_SHUFFLE_SWIM).Is(false)) { + SetRandoInf(RAND_INF_CAN_SWIM, true); + } - // If we're not shuffling child's wallet, we start with it - if (ctx->GetOption(RSK_SHUFFLE_CHILD_WALLET).Is(false)) { - SetRandoInf(RAND_INF_HAS_WALLET, true); - } + // If we're not shuffling child's wallet, we start with it + if (ctx->GetOption(RSK_SHUFFLE_CHILD_WALLET).Is(false)) { + SetRandoInf(RAND_INF_HAS_WALLET, true); + } - // If we're not shuffling fishing pole, we start with it - if (ctx->GetOption(RSK_SHUFFLE_FISHING_POLE).Is(false)) { - SetRandoInf(RAND_INF_FISHING_POLE_FOUND, true); - } + // If we're not shuffling fishing pole, we start with it + if (ctx->GetOption(RSK_SHUFFLE_FISHING_POLE).Is(false)) { + SetRandoInf(RAND_INF_FISHING_POLE_FOUND, true); + } - // If not keysanity, start with 1 logical key to account for automatically unlocking the basement door in vanilla - // FiT - if (!IsFireLoopLocked && ctx->GetDungeon(Rando::FIRE_TEMPLE)->IsVanilla()) { - SetSmallKeyCount(SCENE_FIRE_TEMPLE, 1); + // If not keysanity, start with 1 logical key to account for automatically unlocking the basement door in + // vanilla FiT + if (!IsFireLoopLocked && ctx->GetDungeon(Rando::FIRE_TEMPLE)->IsVanilla()) { + SetSmallKeyCount(SCENE_FIRE_TEMPLE, 1); + } } // Bottle Count @@ -2450,7 +2454,9 @@ void Logic::Reset() { // Other AtDay = false; AtNight = false; - GetSaveContext()->linkAge = !ctx->GetOption(RSK_SELECTED_STARTING_AGE).Get(); + if (resetSaveContext) { + GetSaveContext()->linkAge = !ctx->GetOption(RSK_SELECTED_STARTING_AGE).Get(); + } // Events ShowedMidoSwordAndShield = false; @@ -2515,6 +2521,8 @@ void Logic::Reset() { Spirit1FSilverRupees = false; JabuRutoIn1F = false; + CalculatingAvailableChecks = false; + StopPerformanceTimer(PT_LOGIC_RESET); } } // namespace Rando diff --git a/soh/soh/Enhancements/randomizer/logic.h b/soh/soh/Enhancements/randomizer/logic.h index 19d7e173e..9a5c87751 100644 --- a/soh/soh/Enhancements/randomizer/logic.h +++ b/soh/soh/Enhancements/randomizer/logic.h @@ -177,6 +177,9 @@ class Logic { /* --- END OF HELPERS AND LOCATION ACCESS --- */ + bool CalculatingAvailableChecks = false; + bool ACProcessUndiscoveredExits = false; + SaveContext* mSaveContext = nullptr; Logic(); bool CanUse(RandomizerGet itemName); @@ -248,7 +251,7 @@ class Logic { bool CanUseProjectile(); bool CanBuildRainbowBridge(); bool CanTriggerLACS(); - void Reset(); + void Reset(bool resetSaveContext = true); void SetContext(std::shared_ptr _ctx); bool GetInLogic(LogicVal logicVal); void SetInLogic(LogicVal logicVal, bool remove); diff --git a/soh/soh/Enhancements/randomizer/option_descriptions.cpp b/soh/soh/Enhancements/randomizer/option_descriptions.cpp index 8dc07ce7a..16c89a1c4 100644 --- a/soh/soh/Enhancements/randomizer/option_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/option_descriptions.cpp @@ -196,6 +196,8 @@ void Settings::CreateOptionDescriptions() { mOptionDescriptions[RSK_MIX_INTERIOR_ENTRANCES] = "Interior entrances will be part of the mixed pool."; mOptionDescriptions[RSK_MIX_GROTTO_ENTRANCES] = "Grotto entrances will be part of the mixed pool."; mOptionDescriptions[RSK_SHUFFLE_SONGS] = + "Off - Songs will appear at their vanilla locations.\n" + "\n" "Song locations - Songs will only appear at locations that normally teach songs.\n" "\n" "Dungeon rewards - Songs appear after beating a major dungeon boss.\n" @@ -593,7 +595,6 @@ void Settings::CreateOptionDescriptions() { "\n" "Greg as Wildcard - Greg does not change logic, Greg helps obtain GBK, max number of " "rewards on slider does not change."; - mOptionDescriptions[RSK_CUCCO_COUNT] = "The amount of cuccos needed to claim the reward from Anju the Cucco Lady."; mOptionDescriptions[RSK_BIG_POE_COUNT] = "The Poe collector will give a reward for turning in this many Big Poes."; mOptionDescriptions[RSK_SKIP_CHILD_STEALTH] = "The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards."; diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index f7279ff80..623aa01f9 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -365,10 +365,20 @@ std::unordered_map getItemIdToItemId = { bool Randomizer::SpoilerFileExists(const char* spoilerFileName) { if (strcmp(spoilerFileName, "") != 0) { std::ifstream spoilerFileStream(SohUtils::Sanitize(spoilerFileName)); - if (!spoilerFileStream) { - return false; - } else { - return true; + if (spoilerFileStream) { + nlohmann::json contents; + spoilerFileStream >> contents; + spoilerFileStream.close(); + if (contents.contains("version") && + strcmp(std::string(contents["version"]).c_str(), (char*)gBuildVersion) == 0) { + return true; + } else { + SohGui::RegisterPopup( + "Old Spoiler Version", + "The spoiler file located at\n" + std::string(spoilerFileName) + + "\nwas made by a version that doesn't match the currently running version.\n" + + "Loading for this file has been cancelled."); + } } } @@ -4393,7 +4403,7 @@ CustomMessage Randomizer::GetFishingPondOwnerMessage(u16 originalTextId) { "fischen!", "Désolé, mais l'étang est fermé.&J'ai perdu ma bonne %rCanne à Pêche%w...&Impossible de pêcher sans elle!"); - if (Rando::Context::GetInstance()->GetOption(RSK_FISHING_POLE_HINT)) { + if (GetRandoSettingValue(RSK_FISHING_POLE_HINT)) { messageEntry = messageEntry + CustomMessage(ctx->GetHint(RH_FISHING_POLE)->GetHintMessage()); } diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index 9622789f4..5075182b3 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -577,6 +577,7 @@ typedef enum { RR_DEKU_TREE_MQ_OUTSIDE_BOSS_ROOM, RR_DEKU_TREE_BOSS_ENTRYWAY, + RR_DEKU_TREE_BOSS_EXIT, RR_DEKU_TREE_BOSS_ROOM, RR_DODONGOS_CAVERN_BEGINNING, @@ -624,7 +625,9 @@ typedef enum { RR_DODONGOS_CAVERN_MQ_BEHIND_MOUTH, RR_DODONGOS_CAVERN_MQ_BACK_BEHIND_FIRE, RR_DODONGOS_CAVERN_MQ_BACK_SWITCH_GRAVE, + RR_DODONGOS_CAVERN_BOSS_ENTRYWAY, + RR_DODONGOS_CAVERN_BOSS_EXIT, RR_DODONGOS_CAVERN_BOSS_ROOM, RR_JABU_JABUS_BELLY_BEGINNING, @@ -653,6 +656,7 @@ typedef enum { RR_JABU_JABUS_BELLY_MQ_EAST_ROOM, RR_JABU_JABUS_BELLY_BOSS_ENTRYWAY, + RR_JABU_JABUS_BELLY_BOSS_EXIT, RR_JABU_JABUS_BELLY_BOSS_ROOM, RR_FOREST_TEMPLE_FIRST_ROOM, @@ -5808,7 +5812,6 @@ typedef enum { RSK_STARTING_NUTS, RSK_FULL_WALLETS, RSK_SHUFFLE_CHEST_MINIGAME, - RSK_CUCCO_COUNT, RSK_BIG_POE_COUNT, RSK_SKIP_EPONA_RACE, RSK_COMPLETE_MASK_QUEST, @@ -6152,6 +6155,7 @@ typedef enum { // Song shuffle Settings (Song locations, Dungeon rewards, anywhere) typedef enum { + RO_SONG_SHUFFLE_OFF, RO_SONG_SHUFFLE_SONG_LOCATIONS, RO_SONG_SHUFFLE_DUNGEON_REWARDS, RO_SONG_SHUFFLE_ANYWHERE, diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp index 4d7d7a59a..1a8eca6dc 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp @@ -137,8 +137,10 @@ void RandomizerCheckObjects::UpdateImGuiVisibility() { CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleMerchants"), RO_SHUFFLE_MERCHANTS_OFF) != RO_SHUFFLE_MERCHANTS_OFF) && (location.GetRCType() != RCTYPE_SONG_LOCATION || - CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleSongs"), RO_SONG_SHUFFLE_SONG_LOCATIONS) != - RO_SONG_SHUFFLE_SONG_LOCATIONS) && // song locations + (CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleSongs"), RO_SONG_SHUFFLE_SONG_LOCATIONS) != + RO_SONG_SHUFFLE_SONG_LOCATIONS && + CVarGetInteger(CVAR_RANDOMIZER_SETTING("ShuffleSongs"), RO_SONG_SHUFFLE_SONG_LOCATIONS) != + RO_SONG_SHUFFLE_OFF)) && // song locations ((location.GetRCType() != RCTYPE_BOSS_HEART_OR_OTHER_REWARD && location.GetRandomizerCheck() != RC_SONG_FROM_IMPA && location.GetRandomizerCheck() != RC_SHEIK_IN_ICE_CAVERN) || diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index 52b977032..adaf03cea 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -51,6 +51,7 @@ bool showBeans; bool showScrubs; bool showMajorScrubs; bool showMerchants; +bool showSongs; bool showBeehives; bool showCows; bool showOverworldFreestanding; @@ -584,6 +585,13 @@ void CheckTrackerLoadGame(int32_t fileNum) { UpdateAllOrdering(); UpdateInventoryChecks(); UpdateFilters(); + + RegionTable_Init(); + + if (Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_ENTRANCES).Get()) { + Rando::Context::GetInstance()->GetEntranceShuffler()->ApplyEntranceOverrides(); + } + RecalculateAvailableChecks(); } @@ -902,7 +910,6 @@ void LoadFile() { SaveManager::Instance->LoadData("areasSpoiled", areasSpoiled, (uint32_t)0); UpdateAllOrdering(); UpdateAllAreas(); - RegionTable_Init(); } void Teardown() { @@ -1306,6 +1313,9 @@ void LoadSettings() { OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_MERCHANTS) == RO_SHUFFLE_MERCHANTS_ALL : true; + showSongs = IS_RANDO + ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_SONGS) != RO_SONG_SHUFFLE_OFF + : false; showBeehives = IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_BEEHIVES) == RO_GENERIC_YES : false; @@ -1491,6 +1501,27 @@ void LoadSettings() { showOverworldFreestanding = false; showDungeonFreestanding = true; } + + switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GANONS_BOSS_KEY)) { + case RO_GANON_BOSS_KEY_LACS_STONES: + Rando::Context::GetInstance()->LACSCondition(RO_LACS_STONES); + break; + case RO_GANON_BOSS_KEY_LACS_MEDALLIONS: + Rando::Context::GetInstance()->LACSCondition(RO_LACS_MEDALLIONS); + break; + case RO_GANON_BOSS_KEY_LACS_REWARDS: + Rando::Context::GetInstance()->LACSCondition(RO_LACS_REWARDS); + break; + case RO_GANON_BOSS_KEY_LACS_DUNGEONS: + Rando::Context::GetInstance()->LACSCondition(RO_LACS_DUNGEONS); + break; + case RO_GANON_BOSS_KEY_LACS_TOKENS: + Rando::Context::GetInstance()->LACSCondition(RO_LACS_TOKENS); + break; + default: + Rando::Context::GetInstance()->LACSCondition(RO_LACS_VANILLA); + break; + } } bool IsCheckShuffled(RandomizerCheck rc) { @@ -1516,6 +1547,7 @@ bool IsCheckShuffled(RandomizerCheck rc) { (showMajorScrubs && (rc == RC_LW_DEKU_SCRUB_NEAR_BRIDGE || // The 3 scrubs that are always randomized rc == RC_HF_DEKU_SCRUB_GROTTO || rc == RC_LW_DEKU_SCRUB_GROTTO_FRONT))) && (loc->GetRCType() != RCTYPE_MERCHANT || showMerchants) && + (loc->GetRCType() != RCTYPE_SONG_LOCATION || showSongs) && (loc->GetRCType() != RCTYPE_BEEHIVE || showBeehives) && (loc->GetRCType() != RCTYPE_OCARINA || showOcarinas) && (loc->GetRCType() != RCTYPE_SKULL_TOKEN || alwaysShowGS || @@ -1830,7 +1862,7 @@ void DrawLocation(RandomizerCheck rc) { case RCSHOW_IDENTIFIED: case RCSHOW_SEEN: if (IS_RANDO) { - if (itemLoc->GetPlacedRandomizerGet() == RG_ICE_TRAP && !mystery && !itemLoc->IsAddedToPool()) { + if (itemLoc->GetPlacedRandomizerGet() == RG_ICE_TRAP && !mystery) { if (status == RCSHOW_IDENTIFIED) { txt = OTRGlobals::Instance->gRandoContext->overrides[rc].GetTrickName().GetForLanguage( gSaveContext.language); @@ -1840,11 +1872,10 @@ void DrawLocation(RandomizerCheck rc) { .GetName() .GetForLanguage(gSaveContext.language); } - } else if (!mystery && !itemLoc->IsAddedToPool()) { + } else if (!mystery) { txt = itemLoc->GetPlacedItem().GetName().GetForLanguage(gSaveContext.language); } - if (IsVisibleInCheckTracker(rc) && status == RCSHOW_IDENTIFIED && !mystery && - !itemLoc->IsAddedToPool()) { + if (IsVisibleInCheckTracker(rc) && status == RCSHOW_IDENTIFIED && !mystery) { auto price = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->GetPrice(); if (price) { txt += fmt::format(" - {}", price); @@ -1968,7 +1999,7 @@ void ImGuiDrawTwoColorPickerSection(const char* text, const char* cvarMainName, UIWidgets::PopStyleCombobox(); } -void RecalculateAvailableChecks() { +void RecalculateAvailableChecks(RandomizerRegion startingRegion /* = RR_ROOT */) { if (!enableAvailableChecks) { return; } @@ -1976,20 +2007,22 @@ void RecalculateAvailableChecks() { ResetPerformanceTimer(PT_RECALCULATE_AVAILABLE_CHECKS); StartPerformanceTimer(PT_RECALCULATE_AVAILABLE_CHECKS); + const auto& ctx = Rando::Context::GetInstance(); + std::vector targetLocations; targetLocations.reserve(RC_MAX); for (auto& location : Rando::StaticData::GetLocationTable()) { RandomizerCheck rc = location.GetRandomizerCheck(); - Rando::ItemLocation* itemLocation = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc); + Rando::ItemLocation* itemLocation = ctx->GetItemLocation(rc); itemLocation->SetAvailable(false); if (!itemLocation->HasObtained()) { targetLocations.emplace_back(rc); } } - std::vector availableChecks = ReachabilitySearch(targetLocations, RG_NONE, true); + std::vector availableChecks = ReachabilitySearch(targetLocations, RG_NONE, true, startingRegion); for (auto& rc : availableChecks) { - const auto& itemLocation = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc); + const auto& itemLocation = ctx->GetItemLocation(rc); itemLocation->SetAvailable(true); } @@ -1997,7 +2030,7 @@ void RecalculateAvailableChecks() { for (auto& [rcArea, vec] : checksByArea) { areaChecksAvailable[rcArea] = 0; for (auto& rc : vec) { - Rando::ItemLocation* itemLocation = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc); + Rando::ItemLocation* itemLocation = ctx->GetItemLocation(rc); if (itemLocation->IsAvailable() && IsVisibleInCheckTracker(rc) && !IsCheckHidden(rc)) { areaChecksAvailable[rcArea]++; } diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h index 695899bab..38c38a39b 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h @@ -61,6 +61,6 @@ void UpdateAllOrdering(); void UpdateAllAreas(); void RecalculateAllAreaTotals(); void SpoilAreaFromCheck(RandomizerCheck rc); -void RecalculateAvailableChecks(); +void RecalculateAvailableChecks(RandomizerRegion startingRegion = RR_ROOT); void CheckTracker_LoadFromPreset(nlohmann::json info); } // namespace CheckTracker diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance.c b/soh/soh/Enhancements/randomizer/randomizer_entrance.c index 11d17e621..e32223124 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance.c +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance.c @@ -253,7 +253,9 @@ void Entrance_Init(void) { for (s16 i = 0; i < 4; i++) { // Zero out the bit in the field which tells the game to keep playing // background music for all four scene setups at each index - gEntranceTable[override + i].field &= ~ENTRANCE_INFO_CONTINUE_BGM_FLAG; + if (override + i < ENTRANCE_TABLE_SIZE) { + gEntranceTable[override + i].field &= ~ENTRANCE_INFO_CONTINUE_BGM_FLAG; + } } } } @@ -809,6 +811,7 @@ void Entrance_SetEntranceDiscovered(u16 entranceIndex, u8 isReversedEntrance) { if (idx < SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT) { u32 entranceBit = 1 << (entranceIndex - (idx * bitsPerIndex)); gSaveContext.ship.stats.entrancesDiscovered[idx] |= entranceBit; + CheckTracker_RecalculateAvailableChecks(); // Set reverse entrance when not decoupled if (!Randomizer_GetSettingValue(RSK_DECOUPLED_ENTRANCES) && !isReversedEntrance) { diff --git a/soh/soh/Enhancements/randomizer/savefile.cpp b/soh/soh/Enhancements/randomizer/savefile.cpp index 884ade150..05a10a2b3 100644 --- a/soh/soh/Enhancements/randomizer/savefile.cpp +++ b/soh/soh/Enhancements/randomizer/savefile.cpp @@ -232,6 +232,8 @@ extern "C" void Randomizer_InitSaveFile() { // Reset triforce pieces collected. gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected = 0; + SetStartingItems(); + // Set Cutscene flags and texts to skip them. Flags_SetEventChkInf(EVENTCHKINF_FIRST_SPOKE_TO_MIDO); Flags_SetInfTable(INFTABLE_SPOKE_TO_KAEPORA_IN_LAKE_HYLIA); @@ -430,6 +432,4 @@ extern "C" void Randomizer_InitSaveFile() { gSaveContext.itemGetInf[3] |= 0x800; // Bunny Hood related gSaveContext.itemGetInf[3] |= 0x8000; // Obtained Mask of Truth } - - SetStartingItems(); } diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index d94df9ae5..7715e332d 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -177,7 +177,7 @@ void Settings::CreateOptions() { OPT_U8(RSK_MQ_GANONS_CASTLE, "Ganon's Castle Quest", {"Vanilla", "Master Quest", "Random"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("MQDungeonsGanonsCastle"), "", WidgetType::Combobox, RO_MQ_SET_VANILLA); OPT_U8(RSK_SHUFFLE_DUNGEON_REWARDS, "Shuffle Dungeon Rewards", {"Vanilla", "End of Dungeons", "Any Dungeon", "Overworld", "Anywhere"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleDungeonReward"), mOptionDescriptions[RSK_SHUFFLE_DUNGEON_REWARDS], WidgetType::Combobox, RO_DUNGEON_REWARDS_END_OF_DUNGEON); OPT_U8(RSK_LINKS_POCKET, "Link's Pocket", {"Dungeon Reward", "Advancement", "Anything", "Nothing"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("LinksPocket"), "", WidgetType::Combobox, RO_LINKS_POCKET_DUNGEON_REWARD); - OPT_U8(RSK_SHUFFLE_SONGS, "Shuffle Songs", {"Song Locations", "Dungeon Rewards", "Anywhere"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleSongs"), mOptionDescriptions[RSK_SHUFFLE_SONGS], WidgetType::Combobox, RO_SONG_SHUFFLE_SONG_LOCATIONS); + OPT_U8(RSK_SHUFFLE_SONGS, "Shuffle Songs", {"Off", "Song Locations", "Dungeon Rewards", "Anywhere"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleSongs"), mOptionDescriptions[RSK_SHUFFLE_SONGS], WidgetType::Combobox, RO_SONG_SHUFFLE_SONG_LOCATIONS); OPT_U8(RSK_SHOPSANITY, "Shop Shuffle", {"Off", "Specific Count", "Random"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("Shopsanity"), mOptionDescriptions[RSK_SHOPSANITY], WidgetType::Combobox, RO_SHOPSANITY_OFF); OPT_U8(RSK_SHOPSANITY_COUNT, "Shops Item Count", {NumOpts(0, 7/*8*/)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShopsanityCount"), mOptionDescriptions[RSK_SHOPSANITY_COUNT], WidgetType::Slider, 0, false, IMFLAG_NONE); OPT_U8(RSK_SHOPSANITY_PRICES, "Shops Prices", {"Vanilla", "Cheap Balanced", "Balanced", "Fixed", "Range", "Set By Wallet"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShopsanityPrices"), mOptionDescriptions[RSK_SHOPSANITY_PRICES], WidgetType::Combobox, RO_PRICE_VANILLA, false, IMFLAG_NONE); @@ -269,7 +269,6 @@ void Settings::CreateOptions() { OPT_BOOL(RSK_SKIP_EPONA_RACE, "Skip Epona Race", {"Don't Skip", "Skip"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("SkipEponaRace"), mOptionDescriptions[RSK_SKIP_EPONA_RACE], WidgetType::Checkbox, RO_GENERIC_DONT_SKIP); OPT_BOOL(RSK_SKIP_SCARECROWS_SONG, "Skip Scarecrow's Song", CVAR_RANDOMIZER_SETTING("SkipScarecrowsSong"), mOptionDescriptions[RSK_SKIP_SCARECROWS_SONG]); OPT_U8(RSK_BIG_POE_COUNT, "Big Poe Target Count", {NumOpts(0, 10)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("BigPoeTargetCount"), mOptionDescriptions[RSK_BIG_POE_COUNT], WidgetType::Slider, 10); - OPT_U8(RSK_CUCCO_COUNT, "Cuccos to return", {NumOpts(0, 7)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("CuccosToReturn"), mOptionDescriptions[RSK_CUCCO_COUNT], WidgetType::Slider, 7); OPT_BOOL(RSK_COMPLETE_MASK_QUEST, "Complete Mask Quest", CVAR_RANDOMIZER_SETTING("CompleteMaskQuest"), mOptionDescriptions[RSK_COMPLETE_MASK_QUEST]); OPT_U8(RSK_GOSSIP_STONE_HINTS, "Gossip Stone Hints", {"No Hints", "Need Nothing", "Mask of Truth", "Stone of Agony"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GossipStoneHints"), mOptionDescriptions[RSK_GOSSIP_STONE_HINTS], WidgetType::Combobox, RO_GOSSIP_STONES_NEED_NOTHING, false, IMFLAG_NONE); OPT_U8(RSK_HINT_CLARITY, "Hint Clarity", {"Obscure", "Ambiguous", "Clear"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("HintClarity"), mOptionDescriptions[RSK_HINT_CLARITY], WidgetType::Combobox, RO_HINT_CLARITY_CLEAR, true, IMFLAG_INDENT); @@ -1316,8 +1315,8 @@ void Settings::CreateOptions() { WidgetContainerType::TABLE); mOptionGroups[RSG_TIMESAVERS_IMGUI] = OptionGroup::SubGroup( "Timesavers", - { &mOptions[RSK_CUCCO_COUNT], &mOptions[RSK_BIG_POE_COUNT], &mOptions[RSK_SKIP_CHILD_ZELDA], - &mOptions[RSK_SKIP_EPONA_RACE], &mOptions[RSK_COMPLETE_MASK_QUEST], &mOptions[RSK_SKIP_SCARECROWS_SONG] }, + { &mOptions[RSK_BIG_POE_COUNT], &mOptions[RSK_SKIP_CHILD_ZELDA], &mOptions[RSK_SKIP_EPONA_RACE], + &mOptions[RSK_COMPLETE_MASK_QUEST], &mOptions[RSK_SKIP_SCARECROWS_SONG] }, WidgetContainerType::COLUMN); mOptionGroups[RSG_ITEM_POOL_HINTS_IMGUI] = OptionGroup::SubGroup("", { @@ -1582,7 +1581,6 @@ void Settings::CreateOptions() { &mOptions[RSK_SKIP_EPONA_RACE], &mOptions[RSK_SKIP_SCARECROWS_SONG], &mOptions[RSK_BIG_POE_COUNT], - &mOptions[RSK_CUCCO_COUNT], &mOptions[RSK_COMPLETE_MASK_QUEST], }); mOptionGroups[RSG_MISC] = OptionGroup("Miscellaneous Settings", @@ -1744,6 +1742,14 @@ TrickOption& Settings::GetTrickOption(const RandomizerTrick key) { return mTrickOptions[key]; } +int Settings::GetRandomizerTrickByName(const std::string& name) { + const auto& it = mTrickNameToEnum.find(name); + if (it == mTrickNameToEnum.end()) { + return -1; + } + return it->second; +} + void Context::ResetTrickOptions() { for (int count = 0; count < RT_MAX; count++) { mTrickOptions[count].Set(0); // RANDOTODO this can probably be done better @@ -2878,11 +2884,8 @@ void Context::FinalizeSettings(const std::set& excludedLocation if (mOptions[RSK_FISHSANITY].IsNot(RO_FISHSANITY_HYRULE_LOACH)) { mOptions[RSK_LOACH_HINT].Set(RO_GENERIC_OFF); } - - if (mOptions[RSK_CUCCO_COUNT].Is(0)) { - mOptions[RSK_CHICKENS_HINT].Set(RO_GENERIC_OFF); - } } + void Settings::ParseJson(nlohmann::json spoilerFileJson) { mContext->SetSeedString(spoilerFileJson["seed"].get()); mContext->SetSeed(spoilerFileJson["finalSeed"].get()); diff --git a/soh/soh/Enhancements/randomizer/settings.h b/soh/soh/Enhancements/randomizer/settings.h index c820a9df9..ed214471a 100644 --- a/soh/soh/Enhancements/randomizer/settings.h +++ b/soh/soh/Enhancements/randomizer/settings.h @@ -50,6 +50,14 @@ class Settings { */ TrickOption& GetTrickOption(RandomizerTrick key); + /** + * @brief Get the RandomizerTrick corresponding to the provided name. + * + * @param name + * @return int RandomizerTrick index or -1 if not found + */ + int GetRandomizerTrickByName(const std::string& name); + /** * @brief Returns a reference to the entire array of options. * diff --git a/soh/soh/Enhancements/randomizer/static_data.h b/soh/soh/Enhancements/randomizer/static_data.h index 1be34e772..7bdec17c1 100644 --- a/soh/soh/Enhancements/randomizer/static_data.h +++ b/soh/soh/Enhancements/randomizer/static_data.h @@ -49,6 +49,7 @@ class StaticData { static std::vector GetPondFishLocations(); static std::vector GetOverworldFishLocations(); static std::vector GetOverworldFairyLocations(); + static void RegisterSongLocations(); static void RegisterBeehiveLocations(); static void RegisterCowLocations(); static void RegisterFishLocations(); diff --git a/soh/soh/Enhancements/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/timesaver_hook_handlers.cpp index 9f488efce..dd8185c1d 100644 --- a/soh/soh/Enhancements/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/timesaver_hook_handlers.cpp @@ -28,6 +28,7 @@ extern "C" { #include "src/overlays/actors/ovl_En_Daiku/z_en_daiku.h" #include "src/overlays/actors/ovl_Bg_Spot02_Objects/z_bg_spot02_objects.h" #include "src/overlays/actors/ovl_Bg_Spot03_Taki/z_bg_spot03_taki.h" +#include "src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.h" #include "src/overlays/actors/ovl_Bg_Hidan_Kousi/z_bg_hidan_kousi.h" #include "src/overlays/actors/ovl_Bg_Dy_Yoseizo/z_bg_dy_yoseizo.h" #include "src/overlays/actors/ovl_En_Dnt_Demo/z_en_dnt_demo.h" @@ -49,8 +50,6 @@ extern void EnRu2_SetEncounterSwitchFlag(EnRu2* enRu2, PlayState* play); extern void EnDaiku_EscapeSuccess(EnDaiku* enDaiku, PlayState* play); } -#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).Get() - void EnMa1_EndTeachSong(EnMa1* enMa1, PlayState* play) { if (Message_GetState(&gPlayState->msgCtx) == TEXT_STATE_CLOSING) { Flags_SetRandomizerInf(RAND_INF_LEARNED_EPONA_SONG); @@ -1386,3 +1385,15 @@ void TimeSaverRegisterHooks() { GameInteractor::Instance->RegisterGameHook(TimeSaverOnItemReceiveHandler); }); } + +void RegisterSkipWaterTempleGateDelay() { + COND_ID_HOOK(OnActorUpdate, ACTOR_BG_SPOT06_OBJECTS, + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO), [](void* actor) { + BgSpot06Objects* spot06 = static_cast(actor); + if (spot06->dyna.actor.params == 0) { + spot06->timer = 0; + } + }) +} + +static RegisterShipInitFunc skipWaterTempleGateDelay(RegisterSkipWaterTempleGateDelay); diff --git a/soh/soh/Enhancements/tts/tts.cpp b/soh/soh/Enhancements/tts/tts.cpp index b8a43453f..978e5ca24 100644 --- a/soh/soh/Enhancements/tts/tts.cpp +++ b/soh/soh/Enhancements/tts/tts.cpp @@ -147,28 +147,25 @@ void RegisterOnInterfaceUpdateHook() { timer = gSaveContext.subTimerSeconds; } - if (timer > 0) { - if (timer > prevTimer || (timer % 30 == 0 && prevTimer != timer)) { - uint32_t minutes = timer / 60; - uint32_t seconds = timer % 60; - char* announceBuf = ttsAnnounceBuf; - char arg[8]; // at least big enough where no s8 string will overflow - if (minutes > 0) { - snprintf(arg, sizeof(arg), "%d", minutes); - auto translation = GetParameritizedText((minutes > 1) ? "minutes_plural" : "minutes_singular", - TEXT_BANK_MISC, arg); - announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s ", translation.c_str()); - } - if (seconds > 0) { - snprintf(arg, sizeof(arg), "%d", seconds); - auto translation = GetParameritizedText((seconds > 1) ? "seconds_plural" : "seconds_singular", - TEXT_BANK_MISC, arg); - announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s", translation.c_str()); - } - assert(announceBuf < ttsAnnounceBuf + sizeof(ttsAnnounceBuf)); - SpeechSynthesizer::Instance->Speak(ttsAnnounceBuf, GetLanguageCode()); - prevTimer = timer; + if (timer > 0 && timer % (timer < 60 ? 10 : 30) == 0 && timer != prevTimer) { + uint32_t minutes = timer / 60; + uint32_t seconds = timer % 60; + char* announceBuf = ttsAnnounceBuf; + char arg[8]; // at least big enough where no s8 string will overflow + if (minutes > 0) { + snprintf(arg, sizeof(arg), "%d", minutes); + auto translation = + GetParameritizedText((minutes > 1) ? "minutes_plural" : "minutes_singular", TEXT_BANK_MISC, arg); + announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s ", translation.c_str()); } + if (seconds > 0) { + snprintf(arg, sizeof(arg), "%d", seconds); + auto translation = + GetParameritizedText((seconds > 1) ? "seconds_plural" : "seconds_singular", TEXT_BANK_MISC, arg); + announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s", translation.c_str()); + } + assert(announceBuf < ttsAnnounceBuf + sizeof(ttsAnnounceBuf)); + SpeechSynthesizer::Instance->Speak(ttsAnnounceBuf, GetLanguageCode()); } prevTimer = timer; diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index a2135006a..501af673e 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1151,6 +1151,13 @@ extern "C" void InitOTR() { "Error", "SoH does not have proper file permissions. Please move it to a folder that does and run again."); exit(1); } + if (ownPath.string().find("OneDrive") != std::string::npos) { + Extractor::ShowErrorBox( + "Error", + "SoH appears to be in a OneDrive folder, which will cause issues. " + "Please move it to a folder outside of OneDrive, like the root of a drive (e.g. \"C:\\Games\\SoH\")."); + exit(1); + } #endif #if not defined(__SWITCH__) && not defined(__WIIU__) @@ -2236,7 +2243,7 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { (Randomizer_GetSettingValue(RSK_GOSSIP_STONE_HINTS) == RO_GOSSIP_STONES_NEED_STONE && CHECK_QUEST_ITEM(QUEST_STONE_OF_AGONY)))) { - Actor* stone = GET_PLAYER(play)->talkActor; + Actor* stone = player->talkActor; RandomizerHint stoneHint = RH_NONE; s16 hintParams = stone->params & 0xFF; @@ -2294,7 +2301,7 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { (RandomizerInf)((textId - TEXT_SHOP_ITEM_RANDOM_CONFIRM) + RAND_INF_SHOP_ITEMS_KF_SHOP_ITEM_1)); messageEntry = OTRGlobals::Instance->gRandomizer->GetMerchantMessage(rc, TEXT_SHOP_ITEM_RANDOM_CONFIRM); } else if (textId == TEXT_SCRUB_RANDOM) { - EnDns* enDns = (EnDns*)GET_PLAYER(play)->talkActor; + EnDns* enDns = (EnDns*)player->talkActor; RandomizerCheck rc = OTRGlobals::Instance->gRandomizer->GetCheckFromRandomizerInf( (RandomizerInf)enDns->sohScrubIdentity.randomizerInf); messageEntry = OTRGlobals::Instance->gRandomizer->GetMerchantMessage( @@ -2344,7 +2351,7 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { messageEntry = CustomMessageManager::Instance->RetrieveMessage(Randomizer::merchantMessageTableID, textId, MF_AUTO_FORMAT); } else if (textId == TEXT_SKULLTULA_PEOPLE_IM_CURSED) { - actorParams = GET_PLAYER(play)->talkActor->params; + actorParams = player->talkActor->params; if (actorParams == 1 && ctx->GetOption(RSK_KAK_10_SKULLS_HINT)) { messageEntry = ctx->GetHint(RH_KAK_10_SKULLS_HINT)->GetHintMessage(MF_AUTO_FORMAT); } else if (actorParams == 2 && ctx->GetOption(RSK_KAK_20_SKULLS_HINT)) { @@ -2443,6 +2450,10 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { } else if (textId == TEXT_BIG_POE_COLLECTED_RANDO) { messageEntry = CustomMessageManager::Instance->RetrieveMessage(customMessageTableID, textId, MF_AUTO_FORMAT); + } else if (textId == TEXT_GERUDO_GUARD_FRIENDLY && player->talkActor->id == ACTOR_EN_GE2) { + // TODO_TRANSLATE Translate into french and german + messageEntry = CustomMessage("Want me to throw you in jail?&\x1B#Yes please&No thanks#", { QM_GREEN }); + messageEntry.AutoFormat(); } } if (textId == TEXT_GS_NO_FREEZE || textId == TEXT_GS_FREEZE) { @@ -2643,4 +2654,7 @@ void SoH_ProcessDroppedFiles(std::string filePath) { return; } } -// #endregion + +extern "C" void CheckTracker_RecalculateAvailableChecks() { + CheckTracker::RecalculateAvailableChecks(); +} diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index 2d1464e47..21316d19d 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -168,6 +168,7 @@ void Gfx_UnregisterBlendedTexture(const char* name); void Gfx_TextureCacheDelete(const uint8_t* addr); void SaveManager_ThreadPoolWait(); void CheckTracker_OnMessageClose(); +void CheckTracker_RecalculateAvailableChecks(); GetItemID RetrieveGetItemIDFromItemID(ItemID itemID); RandomizerGet RetrieveRandomizerGetFromItemID(ItemID itemID); diff --git a/soh/soh/ObjectExtension/ActorMaximumHealth.cpp b/soh/soh/ObjectExtension/ActorMaximumHealth.cpp new file mode 100644 index 000000000..9c710822f --- /dev/null +++ b/soh/soh/ObjectExtension/ActorMaximumHealth.cpp @@ -0,0 +1,29 @@ +#include "ActorMaximumHealth.h" +#include "soh/ObjectExtension/ObjectExtension.h" +#include "soh/ShipInit.hpp" +#include "soh/Enhancements/game-interactor/GameInteractor.h" + +struct ActorMaximumHealth { + u8 maximumHealth = 0; +}; +static ObjectExtension::Register ActorMaximumHealthRegister; + +u8 GetActorMaximumHealth(const Actor* actor) { + const ActorMaximumHealth* maxHealth = ObjectExtension::GetInstance().Get(actor); + return maxHealth != nullptr ? maxHealth->maximumHealth : ActorMaximumHealth{}.maximumHealth; +} + +void SetActorMaximumHealth(const Actor* actor, u8 maximumHealth) { + ObjectExtension::GetInstance().Set(actor, ActorMaximumHealth{ maximumHealth }); +} + +static void ActorMaximumHealth_Register() { + COND_HOOK(OnActorInit, true, [](void* ptr) { + Actor* actor = static_cast(ptr); + if (actor->category == ACTORCAT_ENEMY) { + SetActorMaximumHealth(actor, actor->colChkInfo.health); + } + }); +} + +RegisterShipInitFunc actorMaximumHealthInit(ActorMaximumHealth_Register); \ No newline at end of file diff --git a/soh/soh/ObjectExtension/ActorMaximumHealth.h b/soh/soh/ObjectExtension/ActorMaximumHealth.h new file mode 100644 index 000000000..d3ee67acd --- /dev/null +++ b/soh/soh/ObjectExtension/ActorMaximumHealth.h @@ -0,0 +1,17 @@ +#ifndef ACTOR_MAXIMUM_HEALTH_H +#define ACTOR_MAXIMUM_HEALTH_H + +#ifdef __cplusplus +extern "C" { +#include "z64actor.h" +#endif + +// Max health value for use with health bars, set on actor init +u8 GetActorMaximumHealth(const Actor* actor); +void SetActorMaximumHealth(const Actor* actor, u8 maximumHealth); + +#ifdef __cplusplus +} +#endif + +#endif // ACTOR_MAXIMUM_HEALTH_H \ No newline at end of file diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 0b0421fbe..57904fc84 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -119,10 +119,8 @@ SaveManager::SaveManager() { AddLoadFunction("base", 4, LoadBaseVersion4); AddSaveFunction("base", 4, SaveBase, true, SECTION_PARENT_NONE); - AddLoadFunction("randomizer", 1, LoadRandomizerVersion1); - AddLoadFunction("randomizer", 2, LoadRandomizerVersion2); - AddLoadFunction("randomizer", 3, LoadRandomizerVersion3); - AddSaveFunction("randomizer", 3, SaveRandomizer, true, SECTION_PARENT_NONE); + AddLoadFunction("randomizer", 1, LoadRandomizer); + AddSaveFunction("randomizer", 1, SaveRandomizer, true, SECTION_PARENT_NONE); AddInitFunction(InitFileImpl); @@ -157,234 +155,7 @@ SaveManager::SaveManager() { } } -// RANDOTODO should we just have dummy functions that raise warnings instead if these aren't supported? -void SaveManager::LoadRandomizerVersion1() { - auto randoContext = Rando::Context::GetInstance(); - RandomizerCheck location = RC_UNKNOWN_CHECK; - for (int i = 0; i < RC_MAX; i++) { - SaveManager::Instance->LoadData("check" + std::to_string(i), location); - SaveManager::Instance->LoadStruct("get" + std::to_string(i), [&]() { - SaveManager::Instance->LoadData("rgID", randoContext->GetItemLocation(location)->RefPlacedItem()); - if (randoContext->GetItemLocation(location)->GetPlacedRandomizerGet() == RG_ICE_TRAP) { - randoContext->overrides[location].SetLocation(location); - SaveManager::Instance->LoadData("fakeRgID", randoContext->overrides[location].RefLooksLike()); - SaveManager::Instance->LoadData("trickName", randoContext->overrides[location].GetTrickName().english); - SaveManager::Instance->LoadData("trickName", randoContext->overrides[location].GetTrickName().french); - } - }); - } - - for (uint32_t i = 0; i < randoContext->hashIconIndexes.size(); i++) { - SaveManager::Instance->LoadData("seed" + std::to_string(i), randoContext->hashIconIndexes[i]); - } - - for (int i = 0; i < RSK_MAX; i++) { - int key, value; - SaveManager::Instance->LoadData("sk" + std::to_string(i), key); - SaveManager::Instance->LoadData("sv" + std::to_string(i), value); - randoContext->GetOption(RandomizerSettingKey(key)).Set(value); - } - - for (int i = 0; i < 50; i++) { - RandomizerCheck check; - char hintText[200]; - SaveManager::Instance->LoadData("hc" + std::to_string(i), check); - for (int j = 0; j < ARRAY_COUNT(hintText); j++) { - SaveManager::Instance->LoadData("ht" + std::to_string(i) + "-" + std::to_string(j), hintText[j]); - } - RandomizerHint stoneHint = Rando::StaticData::oldVerHintOrder[i - Rando::StaticData::oldVerGossipStoneStart]; - randoContext->AddHint(stoneHint, Rando::Hint(stoneHint, { CustomMessage(hintText) })); - } - - char childAltarText[250]; - for (int i = 0; i < ARRAY_COUNT(childAltarText); i++) { - SaveManager::Instance->LoadData("cat" + std::to_string(i), childAltarText[i]); - } - randoContext->AddHint(RH_ALTAR_CHILD, Rando::Hint(RH_ALTAR_CHILD, { CustomMessage(childAltarText) })); - - char adultAltarText[750]; - for (int i = 0; i < ARRAY_COUNT(adultAltarText); i++) { - SaveManager::Instance->LoadData("aat" + std::to_string(i), adultAltarText[i]); - } - randoContext->AddHint(RH_ALTAR_ADULT, Rando::Hint(RH_ALTAR_ADULT, { CustomMessage(adultAltarText) })); - - char ganonHintText[150]; - for (int i = 0; i < ARRAY_COUNT(ganonHintText); i++) { - SaveManager::Instance->LoadData("ght" + std::to_string(i), ganonHintText[i]); - } - randoContext->AddHint(RH_GANONDORF_HINT, Rando::Hint(RH_GANONDORF_HINT, { CustomMessage(ganonHintText) })); - - char ganonText[250]; - for (int i = 0; i < ARRAY_COUNT(ganonText); i++) { - SaveManager::Instance->LoadData("gt" + std::to_string(i), ganonText[i]); - } - randoContext->AddHint(RH_GANONDORF_JOKE, Rando::Hint(RH_GANONDORF_JOKE, { CustomMessage(ganonText) })); - - SaveManager::Instance->LoadData("triforcePiecesCollected", - gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected); - - SaveManager::Instance->LoadData("pendingIceTrapCount", gSaveContext.ship.pendingIceTrapCount); - - size_t merchantPricesSize = 0; - if (randoContext->GetOption(RSK_SHUFFLE_SCRUBS).Is(RO_SCRUBS_OFF)) { - merchantPricesSize += NUM_SCRUBS; - } - if (randoContext->GetOption(RSK_SHOPSANITY).Is(RO_SHOPSANITY_OFF)) { - merchantPricesSize += NUM_SHOP_ITEMS; - } - - SaveManager::Instance->LoadArray("merchantPrices", merchantPricesSize, [&](size_t i) { - SaveManager::Instance->LoadStruct("", [&]() { - RandomizerCheck rc; - SaveManager::Instance->LoadData("check", rc); - uint32_t price; - SaveManager::Instance->LoadData("price", price); - randoContext->GetItemLocation(rc)->SetCustomPrice(price); - }); - }); -} - -// RANDOTODO if we actually support this, be less lazy -void SaveManager::LoadRandomizerVersion2() { - auto randoContext = Rando::Context::GetInstance(); - SaveManager::Instance->LoadArray("itemLocations", RC_MAX, [&](size_t i) { - SaveManager::Instance->LoadStruct("", [&]() { - SaveManager::Instance->LoadData("rgID", randoContext->GetItemLocation(i)->RefPlacedItem()); - RandomizerGet rg = RG_NONE; - SaveManager::Instance->LoadData("fakeRgID", rg, RG_NONE); - if (rg != RG_NONE) { - randoContext->overrides[static_cast(i)] = - Rando::ItemOverride(static_cast(i), rg); - SaveManager::Instance->LoadData( - "trickName", randoContext->overrides[static_cast(i)].GetTrickName().english); - SaveManager::Instance->LoadData( - "trickName", randoContext->overrides[static_cast(i)].GetTrickName().french); - } - }); - }); - - auto entranceCtx = randoContext->GetEntranceShuffler(); - SaveManager::Instance->LoadArray("entrances", ARRAY_COUNT(entranceCtx->entranceOverrides), [&](size_t i) { - SaveManager::Instance->LoadStruct("", [&]() { - SaveManager::Instance->LoadData("type", entranceCtx->entranceOverrides[i].type); - SaveManager::Instance->LoadData("index", entranceCtx->entranceOverrides[i].index); - SaveManager::Instance->LoadData("destination", entranceCtx->entranceOverrides[i].destination); - SaveManager::Instance->LoadData("override", entranceCtx->entranceOverrides[i].override); - SaveManager::Instance->LoadData("overrideDestination", - entranceCtx->entranceOverrides[i].overrideDestination); - }); - }); - - SaveManager::Instance->LoadArray("seed", randoContext->hashIconIndexes.size(), [&](size_t i) { - SaveManager::Instance->LoadData("", randoContext->hashIconIndexes[i]); - }); - - std::string inputSeed; - SaveManager::Instance->LoadData("inputSeed", inputSeed); - randoContext->SetSeedString(inputSeed); - - uint32_t finalSeed; - SaveManager::Instance->LoadData("finalSeed", finalSeed); - randoContext->SetSeed(finalSeed); - - SaveManager::Instance->LoadArray("randoSettings", RSK_MAX, [&](size_t i) { - int value = 0; - SaveManager::Instance->LoadData("", value); - randoContext->GetOption(RandomizerSettingKey(i)).Set(value); - }); - - SaveManager::Instance->LoadArray("hintLocations", RH_ZR_OPEN_GROTTO_GOSSIP_STONE + 1, [&](size_t i) { - SaveManager::Instance->LoadStruct("", [&]() { - RandomizerCheck rc = RC_UNKNOWN_CHECK; - SaveManager::Instance->LoadData("check", rc); - if (rc != RC_UNKNOWN_CHECK) { - std::string hintText; - SaveManager::Instance->LoadData("hintText", hintText); - RandomizerHint stoneHint = - Rando::StaticData::oldVerHintOrder[rc - Rando::StaticData::oldVerGossipStoneStart]; - randoContext->AddHint(stoneHint, Rando::Hint(stoneHint, { CustomMessage(hintText) })); - } - }); - }); - - std::string childAltarText; - SaveManager::Instance->LoadData("childAltarText", childAltarText); - randoContext->AddHint(RH_ALTAR_CHILD, Rando::Hint(RH_ALTAR_CHILD, { CustomMessage(childAltarText) })); - std::string adultAltarText; - SaveManager::Instance->LoadData("adultAltarText", adultAltarText); - randoContext->AddHint(RH_ALTAR_ADULT, Rando::Hint(RH_ALTAR_ADULT, { CustomMessage(adultAltarText) })); - std::string ganonHintText; - SaveManager::Instance->LoadData("ganonHintText", ganonHintText); - randoContext->AddHint(RH_GANONDORF_HINT, Rando::Hint(RH_GANONDORF_HINT, { CustomMessage(ganonHintText) })); - std::string ganonText; - SaveManager::Instance->LoadData("ganonText", ganonText); - randoContext->AddHint(RH_GANONDORF_JOKE, Rando::Hint(RH_GANONDORF_JOKE, { CustomMessage(ganonText) })); - std::string dampeText; - SaveManager::Instance->LoadData("dampeText", dampeText); - randoContext->AddHint(RH_DAMPES_DIARY, Rando::Hint(RH_DAMPES_DIARY, { CustomMessage(dampeText) })); - std::string gregHintText; - SaveManager::Instance->LoadData("gregHintText", gregHintText); - randoContext->AddHint(RH_GREG_RUPEE, Rando::Hint(RH_GREG_RUPEE, { CustomMessage(gregHintText) })); - std::string sheikText; - SaveManager::Instance->LoadData("sheikText", sheikText); - randoContext->AddHint(RH_SHEIK_HINT, Rando::Hint(RH_SHEIK_HINT, { CustomMessage(sheikText) })); - std::string sariaText; - SaveManager::Instance->LoadData("sariaText", sariaText); - randoContext->AddHint(RH_SARIA_HINT, Rando::Hint(RH_SARIA_HINT, { CustomMessage(sariaText) })); - std::string fishingPoleText; - SaveManager::Instance->LoadData("fishingPoleText", fishingPoleText); - randoContext->AddHint(RH_FISHING_POLE, Rando::Hint(RH_FISHING_POLE, { CustomMessage(fishingPoleText) })); - std::string warpMinuetText; - SaveManager::Instance->LoadData("warpMinuetText", warpMinuetText); - randoContext->AddHint(RH_MINUET_WARP_LOC, Rando::Hint(RH_MINUET_WARP_LOC, { CustomMessage(warpMinuetText) })); - std::string warpBoleroText; - SaveManager::Instance->LoadData("warpBoleroText", warpBoleroText); - randoContext->AddHint(RH_BOLERO_WARP_LOC, Rando::Hint(RH_BOLERO_WARP_LOC, { CustomMessage(warpBoleroText) })); - std::string warpSerenadeText; - SaveManager::Instance->LoadData("warpSerenadeText", warpSerenadeText); - randoContext->AddHint(RH_SERENADE_WARP_LOC, Rando::Hint(RH_SERENADE_WARP_LOC, { CustomMessage(warpSerenadeText) })); - std::string warpRequiemText; - SaveManager::Instance->LoadData("warpRequiemText", warpRequiemText); - randoContext->AddHint(RH_REQUIEM_WARP_LOC, Rando::Hint(RH_REQUIEM_WARP_LOC, { CustomMessage(warpRequiemText) })); - std::string warpNocturneText; - SaveManager::Instance->LoadData("warpNocturneText", warpNocturneText); - randoContext->AddHint(RH_NOCTURNE_WARP_LOC, Rando::Hint(RH_NOCTURNE_WARP_LOC, { CustomMessage(warpNocturneText) })); - std::string warpPreludeText; - SaveManager::Instance->LoadData("warpPreludeText", warpPreludeText); - randoContext->AddHint(RH_PRELUDE_WARP_LOC, Rando::Hint(RH_PRELUDE_WARP_LOC, { CustomMessage(warpPreludeText) })); - - SaveManager::Instance->LoadData("triforcePiecesCollected", - gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected); - - SaveManager::Instance->LoadData("pendingIceTrapCount", gSaveContext.ship.pendingIceTrapCount); - - std::shared_ptr randomizer = OTRGlobals::Instance->gRandomizer; - - size_t merchantPricesSize = 0; - SaveManager::Instance->LoadData("merchantPricesSize", merchantPricesSize); - - SaveManager::Instance->LoadArray("merchantPrices", merchantPricesSize, [&](size_t i) { - SaveManager::Instance->LoadStruct("", [&]() { - RandomizerCheck rc; - SaveManager::Instance->LoadData("check", rc); - uint32_t price; - SaveManager::Instance->LoadData("price", price); - randoContext->GetItemLocation(rc)->SetCustomPrice(price); - }); - }); - - size_t mqDungeonCount; - SaveManager::Instance->LoadData("masterQuestDungeonCount", mqDungeonCount, (size_t)0); - - randoContext->GetDungeons()->ClearAllMQ(); - SaveManager::Instance->LoadArray("masterQuestDungeons", mqDungeonCount, [&](size_t i) { - uint16_t scene; - SaveManager::Instance->LoadData("", scene); - randoContext->GetDungeons()->GetDungeonFromScene(SceneID(scene))->SetMQ(); - }); -} - -void SaveManager::LoadRandomizerVersion3() { +void SaveManager::LoadRandomizer() { auto randoContext = Rando::Context::GetInstance(); SaveManager::Instance->LoadArray("itemLocations", RC_MAX, [&](size_t i) { SaveManager::Instance->LoadStruct("", [&]() { @@ -1355,8 +1126,57 @@ void SaveManager::LoadFile(int fileNum) { switch (saveBlock["version"].get()) { case 1: for (auto& block : saveBlock["sections"].items()) { - int sectionVersion = block.value()["version"]; std::string sectionName = block.key(); + if (sectionName == "randomizer") { + bool hasStats = saveBlock["sections"].contains("sohStats"); + if (block.value()["data"].contains("aat0") || !hasStats) { // Rachael rando data + SohGui::RegisterPopup( + "Loading old file", + "The file in slot " + std::to_string(fileNum + 1) + + " appears to contain randomizer data, but is a very old format.\n" + + "The randomizer data has been removed, and this file will be treated as a vanilla " + "file.\n" + + "If this was a randomizer file, the file will not work, and should be deleted."); + input.close(); + saveMtx.unlock(); + SaveFile(fileNum); + return; + } + s16 major = saveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; + s16 minor = saveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; + s16 patch = saveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; + // block loading outdated rando save + if (!(major == gBuildVersionMajor && minor == gBuildVersionMinor && + patch == gBuildVersionPatch)) { + input.close(); + std::string newFileName = Ship::Context::GetPathRelativeToAppDirectory("Save") + + ("/file" + std::to_string(fileNum + 1) + "-" + + std::to_string(GetUnixTimestamp()) + ".bak"); + std::filesystem::path newFile(newFileName); + +#if defined(__SWITCH__) || defined(__WIIU__) + copy_file(fileName.c_str(), newFile.c_str()); +#else + std::filesystem::copy_file(fileName, newFile); +#endif + + std::filesystem::remove(fileName); + SohGui::RegisterPopup( + "Outdated Randomizer Save", + "The SoH version in the file in slot " + std::to_string(fileNum + 1) + + " does not match the currently running version.\n" + + "Non-matching rando saves are unsupported, and the file has been renamed to\n" + + " " + newFileName + "\n" + + "If this was not in error, the file should be deleted."); + saveMtx.unlock(); + SaveFile(fileNum); + return; + } + } + int sectionVersion = block.value()["version"]; + if (sectionName == "randomizer" && sectionVersion != 1) { + sectionVersion = 1; + } if (!sectionLoadHandlers.contains(sectionName)) { // Unloadable sections aren't necessarily errors, they are probably mods that were unloaded // TODO report in a more noticeable manner diff --git a/soh/soh/SaveManager.h b/soh/soh/SaveManager.h index 3c4a31243..6d7ef3b33 100644 --- a/soh/soh/SaveManager.h +++ b/soh/soh/SaveManager.h @@ -166,11 +166,7 @@ class SaveManager { static void InitFileDebug(); static void InitFileMaxed(); - static void LoadRandomizerVersion1(); - static void LoadRandomizerVersion2(); - static void LoadRandomizerVersion3(); - static void LoadTrackerData(); - static void SaveTrackerData(SaveContext* saveContext, int sectionID, bool fullSave); + static void LoadRandomizer(); static void SaveRandomizer(SaveContext* saveContext, int sectionID, bool fullSave); static void LoadBaseVersion1(); diff --git a/soh/soh/ShipInit.hpp b/soh/soh/ShipInit.hpp index 2341a1024..e96e77f91 100644 --- a/soh/soh/ShipInit.hpp +++ b/soh/soh/ShipInit.hpp @@ -3,6 +3,7 @@ #ifdef __cplusplus +#include #include #include #include diff --git a/soh/soh/SohGui/Menu.cpp b/soh/soh/SohGui/Menu.cpp index 87c362496..5a609a935 100644 --- a/soh/soh/SohGui/Menu.cpp +++ b/soh/soh/SohGui/Menu.cpp @@ -185,33 +185,35 @@ bool ModernMenuHeaderEntry(std::string label) { } uint32_t Menu::DrawSearchResults(std::string& menuSearchText) { - ImGui::BeginChild("Search Results"); int searchCount = 0; - for (auto& menuLabel : menuOrder) { - auto& menuEntry = menuEntries.at(menuLabel); - for (auto& sidebarLabel : menuEntry.sidebarOrder) { - auto& sidebar = menuEntry.sidebars[sidebarLabel]; - for (int i = 0; i < sidebar.columnWidgets.size(); i++) { - auto& column = sidebar.columnWidgets.at(i); - for (auto& info : column) { - if (info.type == WIDGET_SEARCH || info.type == WIDGET_SEPARATOR || - info.type == WIDGET_SEPARATOR_TEXT || info.isHidden) { - continue; - } - const char* tooltip = info.options->tooltip; - std::string widgetStr = std::string(info.name) + std::string(tooltip != NULL ? tooltip : ""); - std::transform(menuSearchText.begin(), menuSearchText.end(), menuSearchText.begin(), ::tolower); - menuSearchText.erase(std::remove(menuSearchText.begin(), menuSearchText.end(), ' '), - menuSearchText.end()); - std::transform(widgetStr.begin(), widgetStr.end(), widgetStr.begin(), ::tolower); - widgetStr.erase(std::remove(widgetStr.begin(), widgetStr.end(), ' '), widgetStr.end()); - if (widgetStr.find(menuSearchText) != std::string::npos) { - MenuDrawItem(info, 90 / sidebar.columnCount, menuThemeIndex); - ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(UIWidgets::Colors::Gray)); - std::string origin = fmt::format(" ({} -> {}, Col {})", menuEntry.label, sidebarLabel, i + 1); - ImGui::Text("%s", origin.c_str()); - ImGui::PopStyleColor(); - searchCount++; + if (ImGui::BeginChild("Search Results")) { + for (auto& menuLabel : menuOrder) { + auto& menuEntry = menuEntries.at(menuLabel); + for (auto& sidebarLabel : menuEntry.sidebarOrder) { + auto& sidebar = menuEntry.sidebars[sidebarLabel]; + for (int i = 0; i < sidebar.columnWidgets.size(); i++) { + auto& column = sidebar.columnWidgets.at(i); + for (auto& info : column) { + if (info.type == WIDGET_SEARCH || info.type == WIDGET_SEPARATOR || + info.type == WIDGET_SEPARATOR_TEXT || info.isHidden) { + continue; + } + const char* tooltip = info.options->tooltip; + std::string widgetStr = std::string(info.name) + std::string(tooltip != NULL ? tooltip : ""); + std::transform(menuSearchText.begin(), menuSearchText.end(), menuSearchText.begin(), ::tolower); + menuSearchText.erase(std::remove(menuSearchText.begin(), menuSearchText.end(), ' '), + menuSearchText.end()); + std::transform(widgetStr.begin(), widgetStr.end(), widgetStr.begin(), ::tolower); + widgetStr.erase(std::remove(widgetStr.begin(), widgetStr.end(), ' '), widgetStr.end()); + if (widgetStr.find(menuSearchText) != std::string::npos) { + MenuDrawItem(info, 90 / sidebar.columnCount, menuThemeIndex); + ImGui::PushStyleColor(ImGuiCol_Text, UIWidgets::ColorValues.at(UIWidgets::Colors::Gray)); + std::string origin = + fmt::format(" ({} -> {}, Col {})", menuEntry.label, sidebarLabel, i + 1); + ImGui::Text("%s", origin.c_str()); + ImGui::PopStyleColor(); + searchCount++; + } } } } diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index de3bb6be8..15cb81f9d 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1123,6 +1123,11 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip("Dying will delete your file.\n\n" ICON_FA_EXCLAMATION_TRIANGLE " WARNING " ICON_FA_EXCLAMATION_TRIANGLE "\nTHIS IS NOT REVERSIBLE!\nUSE AT YOUR OWN RISK!")); + AddWidget(path, "Switch Timer Multiplier", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("SwitchTimerMultiplier")) + .Options(IntSliderOptions().Min(-5).Max(5).DefaultValue(0).Format("%+d").Tooltip( + "-5 will be half as much time, +5 will be 6x as much time. Affects timed switches, torches, GTG statue " + "eyes, & doors in race with Dampe.")); AddWidget(path, "Always Win Goron Pot", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("GoronPot")) .Options(CheckboxOptions().Tooltip("Always get the Heart Piece/Purple Rupee from the Spinning Goron Pot.")); @@ -1138,6 +1143,10 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("CuccoStayDurationMult")) .Options(IntSliderOptions().Min(1).Max(5).DefaultValue(1).Format("%dx").Tooltip( "Cuccos will stay in place longer after putting them down, by a multiple of the value of the slider.")); + AddWidget(path, "Cuccos Needed By Anju: %d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("CuccosToReturn")) + .Options(IntSliderOptions().Min(0).Max(7).DefaultValue(7).Format("%d").Tooltip( + "The amount of cuccos needed to receive bottle from Anju the Cucco Lady.")); path.column = SECTION_COLUMN_3; AddWidget(path, "Enemies", WIDGET_SEPARATOR_TEXT); @@ -1312,6 +1321,11 @@ void SohMenu::AddMenuEnhancements() { .Format("%d notes") .Tooltip("Adjust the number of notes you need to play to end the third round.")); + AddWidget(path, "Forest Temple", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Solve Amy's Puzzle", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("SkipAmyPuzzle")) + .Options(CheckboxOptions().Tooltip("Amy's block pushing puzzle instantly solved.")); + path.column = SECTION_COLUMN_3; AddWidget(path, "Fishing", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Customize Behavior##Fishing", WIDGET_CVAR_CHECKBOX) @@ -1581,9 +1595,6 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip( "Keese and Guay no longer target you and simply ignore you as if you were wearing the " "Skull Mask.")); - AddWidget(path, "No Dampe Fire", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("NoDampeFire")) - .Options(CheckboxOptions().Tooltip("Dampe won't drop fireballs during race.")); AddWidget(path, "Glitch Aids", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Easy Frame Advancing with Pause", WIDGET_CVAR_CHECKBOX) diff --git a/soh/soh/util.cpp b/soh/soh/util.cpp index b3b6b5c7a..374e2f602 100644 --- a/soh/soh/util.cpp +++ b/soh/soh/util.cpp @@ -371,17 +371,6 @@ void SohUtils::CopyStringToCharArray(char* destination, std::string source, size } std::string SohUtils::Sanitize(std::string stringValue) { - // Add backslashes. - for (auto i = stringValue.begin();;) { - auto const pos = - std::find_if(i, stringValue.end(), [](char const c) { return '\\' == c || '\'' == c || '"' == c; }); - if (pos == stringValue.end()) { - break; - } - i = std::next(stringValue.insert(pos, '\\'), 2); - } - - // Removes others. stringValue.erase(std::remove_if(stringValue.begin(), stringValue.end(), [](char const c) { return '\n' == c || '\r' == c || '\0' == c || '\x1A' == c; }), stringValue.end()); diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 0b9d20bc5..3933f263d 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -1260,11 +1260,6 @@ void Actor_Init(Actor* actor, PlayState* play) { actor->init = NULL; GameInteractor_ExecuteOnActorInit(actor); - - // For enemy health bar we need to know the max health during init - if (actor->category == ACTORCAT_ENEMY) { - actor->maximumHealth = actor->colChkInfo.health; - } } } @@ -2625,11 +2620,6 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { actor->init = NULL; GameInteractor_ExecuteOnActorInit(actor); - - // For enemy health bar we need to know the max health during init - if (actor->category == ACTORCAT_ENEMY) { - actor->maximumHealth = actor->colChkInfo.health; - } } actor = actor->next; } else if (!Object_IsLoaded(&play->objectCtx, actor->objBankIndex)) { diff --git a/soh/src/code/z_en_item00.c b/soh/src/code/z_en_item00.c index 040549466..b6bf5ecdf 100644 --- a/soh/src/code/z_en_item00.c +++ b/soh/src/code/z_en_item00.c @@ -4,8 +4,8 @@ #include "overlays/effects/ovl_Effect_Ss_Dead_Sound/z_eff_ss_dead_sound.h" #include "textures/icon_item_static/icon_item_static.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/OTRGlobals.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/OTRGlobals.h" #define FLAGS 0 @@ -1536,7 +1536,7 @@ s16 func_8001F404(s16 dropId) { if (LINK_IS_ADULT) { if (dropId == ITEM00_SEEDS) { dropId = ITEM00_ARROWS_SMALL; - } else if ((dropId == ITEM00_STICK) && !(CVarGetInteger(CVAR_ENHANCEMENT("TreesDropSticks"), 0))) { + } else if (GameInteractor_Should(VB_PREVENT_ADULT_STICK, dropId == ITEM00_STICK)) { dropId = ITEM00_RUPEE_GREEN; } } else { diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index cc6245241..0fc923070 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -24,6 +24,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/gameplaystats.h" +#include "soh/ObjectExtension/ActorMaximumHealth.h" #include "message_data_static.h" extern MessageTableEntry* sNesMessageEntryTablePtr; @@ -3643,7 +3644,7 @@ void Interface_DrawEnemyHealthBar(TargetContext* targetCtx, PlayState* play) { f32 scaleY = -0.75f; f32 scaledHeight = -texHeight * scaleY; f32 halfBarWidth = endTexWidth + ((f32)healthbar_fillWidth / 2); - s16 healthBarFill = ((f32)actor->colChkInfo.health / actor->maximumHealth) * healthbar_fillWidth; + s16 healthBarFill = ((f32)actor->colChkInfo.health / GetActorMaximumHealth(actor)) * healthbar_fillWidth; if (anchorType == ENEMYHEALTH_ANCHOR_ACTOR) { // Get actor projected position diff --git a/soh/src/overlays/actors/ovl_Bg_Gnd_Darkmeiro/z_bg_gnd_darkmeiro.c b/soh/src/overlays/actors/ovl_Bg_Gnd_Darkmeiro/z_bg_gnd_darkmeiro.c index 620bf18ab..4ade8918c 100644 --- a/soh/src/overlays/actors/ovl_Bg_Gnd_Darkmeiro/z_bg_gnd_darkmeiro.c +++ b/soh/src/overlays/actors/ovl_Bg_Gnd_Darkmeiro/z_bg_gnd_darkmeiro.c @@ -6,6 +6,7 @@ #include "z_bg_gnd_darkmeiro.h" #include "objects/object_demo_kekkai/object_demo_kekkai.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_DRAW_CULLING_DISABLED) @@ -115,7 +116,9 @@ void BgGndDarkmeiro_UpdateBlockTimer(BgGndDarkmeiro* this, PlayState* play) { if (Flags_GetSwitch(play, ((this->dyna.actor.params >> 8) & 0x3F) + 1)) { if (this->actionFlags & 4) { if (this->timer1 > 0) { - this->timer1--; + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, true, this, &this->timer1)) { + this->timer1--; + } } else { Flags_UnsetSwitch(play, ((this->dyna.actor.params >> 8) & 0x3F) + 1); this->actionFlags &= ~4; @@ -131,7 +134,9 @@ void BgGndDarkmeiro_UpdateBlockTimer(BgGndDarkmeiro* this, PlayState* play) { if (Flags_GetSwitch(play, ((this->dyna.actor.params >> 8) & 0x3F) + 2)) { if (this->actionFlags & 8) { if (this->timer2 > 0) { - this->timer2--; + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, true, &this->timer2)) { + this->timer2--; + } } else { Flags_UnsetSwitch(play, ((this->dyna.actor.params >> 8) & 0x3F) + 2); this->actionFlags &= ~8; diff --git a/soh/src/overlays/actors/ovl_Bg_Hidan_Curtain/z_bg_hidan_curtain.c b/soh/src/overlays/actors/ovl_Bg_Hidan_Curtain/z_bg_hidan_curtain.c index d818bf03e..de1578b24 100644 --- a/soh/src/overlays/actors/ovl_Bg_Hidan_Curtain/z_bg_hidan_curtain.c +++ b/soh/src/overlays/actors/ovl_Bg_Hidan_Curtain/z_bg_hidan_curtain.c @@ -6,6 +6,7 @@ #include "z_bg_hidan_curtain.h" #include "objects/gameplay_keep/gameplay_keep.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -191,7 +192,10 @@ void BgHidanCurtain_TurnOff(BgHidanCurtain* this, PlayState* play) { } void BgHidanCurtain_WaitForTimer(BgHidanCurtain* this, PlayState* play) { - DECR(this->timer); + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, true, this, &this->timer)) { + DECR(this->timer); + } + if (this->timer == 0) { this->actionFunc = BgHidanCurtain_TurnOn; } diff --git a/soh/src/overlays/actors/ovl_Bg_Hidan_Fwbig/z_bg_hidan_fwbig.c b/soh/src/overlays/actors/ovl_Bg_Hidan_Fwbig/z_bg_hidan_fwbig.c index 2caf4183d..3d46f41c5 100644 --- a/soh/src/overlays/actors/ovl_Bg_Hidan_Fwbig/z_bg_hidan_fwbig.c +++ b/soh/src/overlays/actors/ovl_Bg_Hidan_Fwbig/z_bg_hidan_fwbig.c @@ -8,6 +8,7 @@ #include "z_bg_hidan_fwbig.h" #include "objects/gameplay_keep/gameplay_keep.h" #include "objects/object_hidan_objects/object_hidan_objects.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -165,9 +166,10 @@ void BgHidanFwbig_Lower(BgHidanFwbig* this, PlayState* play) { } void BgHidanFwbig_WaitForTimer(BgHidanFwbig* this, PlayState* play) { - if (this->timer != 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->timer != 0, this, &this->timer)) { this->timer--; } + if (this->timer == 0) { this->actionFunc = BgHidanFwbig_Rise; } diff --git a/soh/src/overlays/actors/ovl_Bg_Menkuri_Eye/z_bg_menkuri_eye.c b/soh/src/overlays/actors/ovl_Bg_Menkuri_Eye/z_bg_menkuri_eye.c index 981e13839..20851b200 100644 --- a/soh/src/overlays/actors/ovl_Bg_Menkuri_Eye/z_bg_menkuri_eye.c +++ b/soh/src/overlays/actors/ovl_Bg_Menkuri_Eye/z_bg_menkuri_eye.c @@ -6,6 +6,7 @@ #include "z_bg_menkuri_eye.h" #include "objects/object_menkuri_objects/object_menkuri_objects.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS ACTOR_FLAG_DRAW_CULLING_DISABLED @@ -90,7 +91,8 @@ void BgMenkuriEye_Update(Actor* thisx, PlayState* play) { if (!Flags_GetSwitch(play, this->actor.params)) { if (this->framesUntilDisable != -1) { - if (this->framesUntilDisable != 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->framesUntilDisable != 0, this, + &this->framesUntilDisable)) { this->framesUntilDisable -= 1; } if (this->framesUntilDisable == 0) { diff --git a/soh/src/overlays/actors/ovl_Bg_Mizu_Shutter/z_bg_mizu_shutter.c b/soh/src/overlays/actors/ovl_Bg_Mizu_Shutter/z_bg_mizu_shutter.c index ffa56a074..19985b2ce 100644 --- a/soh/src/overlays/actors/ovl_Bg_Mizu_Shutter/z_bg_mizu_shutter.c +++ b/soh/src/overlays/actors/ovl_Bg_Mizu_Shutter/z_bg_mizu_shutter.c @@ -1,5 +1,6 @@ #include "z_bg_mizu_shutter.h" #include "objects/object_mizu_objects/object_mizu_objects.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -137,7 +138,9 @@ void BgMizuShutter_Move(BgMizuShutter* this, PlayState* play) { void BgMizuShutter_WaitForTimer(BgMizuShutter* this, PlayState* play) { if (this->timerMax != 0x3F * 20) { - this->timer--; + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, true, this, &this->timer)) { + this->timer--; + } func_8002F994(&this->dyna.actor, this->timer); if (this->timer == 0) { Audio_PlayActorSound2(&this->dyna.actor, NA_SE_EV_METALDOOR_CLOSE); diff --git a/soh/src/overlays/actors/ovl_Bg_Po_Event/z_bg_po_event.c b/soh/src/overlays/actors/ovl_Bg_Po_Event/z_bg_po_event.c index 88f4c7c12..ae9c9ab21 100644 --- a/soh/src/overlays/actors/ovl_Bg_Po_Event/z_bg_po_event.c +++ b/soh/src/overlays/actors/ovl_Bg_Po_Event/z_bg_po_event.c @@ -6,6 +6,8 @@ #include "z_bg_po_event.h" #include "objects/object_po_sisters/object_po_sisters.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS 0 @@ -333,7 +335,7 @@ void BgPoEvent_BlockIdle(BgPoEvent* this, PlayState* play) { Player* player = GET_PLAYER(play); Actor* amy; - if (sBgPoEventPuzzleState == 0xF) { + if (GameInteractor_Should(VB_AMY_SOLVE, sBgPoEventPuzzleState == 0xF)) { this->actionFunc = BgPoEvent_BlockSolved; if ((this->type == 0) && (this->index == 0)) { amy = Actor_Spawn(&play->actorCtx, play, ACTOR_EN_PO_SISTERS, this->dyna.actor.world.pos.x + 30.0f, diff --git a/soh/src/overlays/actors/ovl_Bg_Relay_Objects/z_bg_relay_objects.c b/soh/src/overlays/actors/ovl_Bg_Relay_Objects/z_bg_relay_objects.c index f7b663cf9..40887b202 100644 --- a/soh/src/overlays/actors/ovl_Bg_Relay_Objects/z_bg_relay_objects.c +++ b/soh/src/overlays/actors/ovl_Bg_Relay_Objects/z_bg_relay_objects.c @@ -6,6 +6,7 @@ #include "z_bg_relay_objects.h" #include "objects/object_relay_objects/object_relay_objects.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -133,7 +134,7 @@ void func_808A90F4(BgRelayObjects* this, PlayState* play) { void func_808A91AC(BgRelayObjects* this, PlayState* play) { if (this->unk_169 != 5) { - if (this->timer != 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->timer != 0, this, &this->timer)) { this->timer--; } func_8002F994(&this->dyna.actor, this->timer); @@ -168,7 +169,7 @@ void BgRelayObjects_DoNothing(BgRelayObjects* this, PlayState* play) { } void func_808A932C(BgRelayObjects* this, PlayState* play) { - if (this->timer != 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->timer != 0, &this->timer)) { this->timer--; } if (this->timer == 0) { diff --git a/soh/src/overlays/actors/ovl_Bg_Toki_Swd/z_bg_toki_swd.c b/soh/src/overlays/actors/ovl_Bg_Toki_Swd/z_bg_toki_swd.c index 81542eaad..a11180a7b 100644 --- a/soh/src/overlays/actors/ovl_Bg_Toki_Swd/z_bg_toki_swd.c +++ b/soh/src/overlays/actors/ovl_Bg_Toki_Swd/z_bg_toki_swd.c @@ -131,8 +131,14 @@ void func_808BAF40(BgTokiSwd* this, PlayState* play) { Item_Give(play, ITEM_SWORD_MASTER); } play->csCtx.segment = D_808BB2F0; + + // Discover adult spawn + Entrance_SetEntranceDiscovered(ENTR_HYRULE_FIELD_10, false); } else { play->csCtx.segment = D_808BB7A0; + + // Discover child spawn + Entrance_SetEntranceDiscovered(ENTR_LINKS_HOUSE_CHILD_SPAWN, false); } Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_STOP); Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_MASTER_SWORD); diff --git a/soh/src/overlays/actors/ovl_Bg_Ydan_Hasi/z_bg_ydan_hasi.c b/soh/src/overlays/actors/ovl_Bg_Ydan_Hasi/z_bg_ydan_hasi.c index 4c8ca0050..6715d140c 100644 --- a/soh/src/overlays/actors/ovl_Bg_Ydan_Hasi/z_bg_ydan_hasi.c +++ b/soh/src/overlays/actors/ovl_Bg_Ydan_Hasi/z_bg_ydan_hasi.c @@ -6,6 +6,7 @@ #include "z_bg_ydan_hasi.h" #include "objects/object_ydan_objects/object_ydan_objects.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_DRAW_CULLING_DISABLED) @@ -126,9 +127,10 @@ void BgYdanHasi_MoveWater(BgYdanHasi* this, PlayState* play) { } void BgYdanHasi_DecWaterTimer(BgYdanHasi* this, PlayState* play) { - if (this->timer != 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->timer != 0, this, &this->timer)) { this->timer--; } + func_8002F994(&this->dyna.actor, this->timer); if (this->timer == 0) { this->actionFunc = BgYdanHasi_MoveWater; @@ -145,9 +147,10 @@ void BgYdanHasi_SetupThreeBlocks(BgYdanHasi* this, PlayState* play) { } void BgYdanHasi_UpdateThreeBlocks(BgYdanHasi* this, PlayState* play) { - if (this->timer != 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->timer != 0, this, &this->timer)) { this->timer--; } + if (this->timer == 0) { if (Math_StepToF(&this->dyna.actor.world.pos.y, this->dyna.actor.home.pos.y, 3.0f) != 0) { Flags_UnsetSwitch(play, this->type); diff --git a/soh/src/overlays/actors/ovl_En_Fu/z_en_fu.c b/soh/src/overlays/actors/ovl_En_Fu/z_en_fu.c index f6cccaf3b..24c6ff5de 100644 --- a/soh/src/overlays/actors/ovl_En_Fu/z_en_fu.c +++ b/soh/src/overlays/actors/ovl_En_Fu/z_en_fu.c @@ -154,17 +154,6 @@ void EnFu_WaitChild(EnFu* this, PlayState* play) { } } -void GivePlayerRandoRewardSongOfStorms(EnFu* windmillGuy, PlayState* play, RandomizerCheck check) { - if (windmillGuy->actor.parent != NULL && windmillGuy->actor.parent->id == GET_PLAYER(play)->actor.id && - !Flags_GetTreasure(play, 0x1F)) { - Flags_SetTreasure(play, 0x1F); - windmillGuy->actionFunc = func_80A1DBD4; - } else if (!Flags_GetTreasure(play, 0x1F)) { - GetItemEntry getItemEntry = Randomizer_GetItemFromKnownCheck(check, RG_SONG_OF_STORMS); - GiveItemEntryFromActor(&windmillGuy->actor, play, getItemEntry, 10000.0f, 100.0f); - } -} - void func_80A1DB60(EnFu* this, PlayState* play) { if (play->csCtx.state == CS_STATE_IDLE) { this->actionFunc = EnFu_WaitAdult; diff --git a/soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c b/soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c index 217afe31f..13afa66f6 100644 --- a/soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c +++ b/soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c @@ -2,6 +2,7 @@ #include "objects/object_fz/object_fz.h" #include "soh/frame_interpolation.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ObjectExtension/ActorMaximumHealth.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -725,7 +726,7 @@ void EnFz_Draw(Actor* thisx, PlayState* play) { // displayLists, so we need to recompute the index based on the scaled health (using the maximum health value) and // clamp the final result for safety. if (CVarGetInteger(CVAR_ENHANCEMENT("EnemySizeScalesHealth"), 0)) { - u8 scaledHealth = (u8)(((f32)this->actor.colChkInfo.health / this->actor.maximumHealth) * 6); + u8 scaledHealth = (u8)(((f32)this->actor.colChkInfo.health / GetActorMaximumHealth(this)) * 6); index = (6 - scaledHealth) >> 1; index = CLAMP(index, 0, 2); } diff --git a/soh/src/overlays/actors/ovl_En_Ge2/z_en_ge2.c b/soh/src/overlays/actors/ovl_En_Ge2/z_en_ge2.c index 89e846a23..776a241b1 100644 --- a/soh/src/overlays/actors/ovl_En_Ge2/z_en_ge2.c +++ b/soh/src/overlays/actors/ovl_En_Ge2/z_en_ge2.c @@ -439,20 +439,21 @@ void EnGe2_LookAtPlayer(EnGe2* this, PlayState* play) { void EnGe2_SetActionAfterTalk(EnGe2* this, PlayState* play) { if (Actor_TextboxIsClosing(&this->actor, play)) { - - switch (this->actor.params & 0xFF) { - case GE2_TYPE_PATROLLING: - EnGe2_ChangeAction(this, GE2_ACTION_ABOUTTURN); - break; - case GE2_TYPE_STATIONARY: - EnGe2_ChangeAction(this, GE2_ACTION_STAND); - break; - case GE2_TYPE_GERUDO_CARD_GIVER: - this->actionFunc = EnGe2_WaitLookAtPlayer; - break; + if (GameInteractor_Should(VB_GERUDO_GUARD_SET_ACTION_AFTER_TALK, true, this)) { + switch (this->actor.params & 0xFF) { + case GE2_TYPE_PATROLLING: + EnGe2_ChangeAction(this, GE2_ACTION_ABOUTTURN); + break; + case GE2_TYPE_STATIONARY: + EnGe2_ChangeAction(this, GE2_ACTION_STAND); + break; + case GE2_TYPE_GERUDO_CARD_GIVER: + this->actionFunc = EnGe2_WaitLookAtPlayer; + break; + } + this->actor.update = EnGe2_UpdateFriendly; + this->actor.flags &= ~ACTOR_FLAG_TALK_OFFER_AUTO_ACCEPTED; } - this->actor.update = EnGe2_UpdateFriendly; - this->actor.flags &= ~ACTOR_FLAG_TALK_OFFER_AUTO_ACCEPTED; } EnGe2_TurnToFacePlayer(this, play); } diff --git a/soh/src/overlays/actors/ovl_En_Mb/z_en_mb.c b/soh/src/overlays/actors/ovl_En_Mb/z_en_mb.c index 50ac7e11b..78f75dabd 100644 --- a/soh/src/overlays/actors/ovl_En_Mb/z_en_mb.c +++ b/soh/src/overlays/actors/ovl_En_Mb/z_en_mb.c @@ -303,7 +303,7 @@ void EnMb_Init(Actor* thisx, PlayState* play) { relYawFromPlayer = this->actor.world.rot.y - Math_Vec3f_Yaw(&this->actor.world.pos, &player->actor.world.pos); - if (ABS(relYawFromPlayer) > 0x4000) { + if (ABS(relYawFromPlayer) > 0x4000 && !CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0)) { this->actor.world.rot.y = thisx->world.rot.y + 0x8000; this->actor.shape.rot.y = thisx->world.rot.y; this->actor.world.pos.z = thisx->world.pos.z + 600.0f; diff --git a/soh/src/overlays/actors/ovl_En_Rr/z_en_rr.c b/soh/src/overlays/actors/ovl_En_Rr/z_en_rr.c index 3797adafe..831e11b8c 100644 --- a/soh/src/overlays/actors/ovl_En_Rr/z_en_rr.c +++ b/soh/src/overlays/actors/ovl_En_Rr/z_en_rr.c @@ -507,7 +507,7 @@ void EnRr_CollisionCheck(EnRr* this, PlayState* play) { this->collider2.base.ocFlags1 &= ~OC1_HIT; // "catch" osSyncPrintf(VT_FGCOL(GREEN) "キャッチ(%d)!!" VT_RST "\n", this->frameCount); - if (play->grabPlayer(play, player)) { + if (GameInteractor_Should(VB_LIKE_LIKE_GRAB_PLAYER, true, this) && play->grabPlayer(play, player)) { player->actor.parent = &this->actor; this->stopScroll = false; EnRr_SetupGrabPlayer(this, player); diff --git a/soh/src/overlays/actors/ovl_En_Siofuki/z_en_siofuki.c b/soh/src/overlays/actors/ovl_En_Siofuki/z_en_siofuki.c index 664c75f82..7587b7a8b 100644 --- a/soh/src/overlays/actors/ovl_En_Siofuki/z_en_siofuki.c +++ b/soh/src/overlays/actors/ovl_En_Siofuki/z_en_siofuki.c @@ -6,6 +6,7 @@ #include "z_en_siofuki.h" #include "objects/object_siofuki/object_siofuki.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_DRAW_CULLING_DISABLED) @@ -188,7 +189,10 @@ void func_80AFC218(EnSiofuki* this, PlayState* play) { func_80AFBE8C(this, play); func_80AFC1D0(this, play); - this->timer--; + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, true, this, &this->timer)) { + this->timer--; + } + if (this->timer < 0) { Flags_UnsetSwitch(play, ((u16)this->dyna.actor.params >> 6) & 0x3F); switch (((u16)this->dyna.actor.params >> 0xC) & 0xF) { diff --git a/soh/src/overlays/actors/ovl_En_Skj/z_en_skj.c b/soh/src/overlays/actors/ovl_En_Skj/z_en_skj.c index dc629c0b7..e5407bf67 100644 --- a/soh/src/overlays/actors/ovl_En_Skj/z_en_skj.c +++ b/soh/src/overlays/actors/ovl_En_Skj/z_en_skj.c @@ -1,6 +1,7 @@ #include "z_en_skj.h" #include "overlays/actors/ovl_En_Skjneedle/z_en_skjneedle.h" #include "objects/object_skj/object_skj.h" +#include "soh/Enhancements/enhancementTypes.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" @@ -404,7 +405,9 @@ void EnSkj_Init(Actor* thisx, PlayState* play2) { default: this->actor.params = type; if (((this->actor.params != 0) && (this->actor.params != 1)) && (this->actor.params != 2)) { - if (INV_CONTENT(ITEM_TRADE_ADULT) < ITEM_SAW) { + if (INV_CONTENT(ITEM_TRADE_ADULT) < ITEM_SAW && + CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), ENEMY_RANDOMIZER_OFF) == + ENEMY_RANDOMIZER_OFF) { Actor_Kill(&this->actor); return; } diff --git a/soh/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c b/soh/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c index 4925e73cb..ceca597df 100644 --- a/soh/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c +++ b/soh/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c @@ -706,7 +706,7 @@ void EnTorch2_Update(Actor* thisx, PlayState* play2) { sStaggerCount = 0; } } - if (player->linearVelocity == -18.0f) { + if (GameInteractor_Should(VB_TORCH2_HANDLE_CLANKING, player->linearVelocity == -18.0f, this)) { if (this->actor.xzDistToPlayer > 80.0f) { player->linearVelocity = 1.2f; } else if (this->actor.xzDistToPlayer < 70.0f) { diff --git a/soh/src/overlays/actors/ovl_En_Wood02/z_en_wood02.c b/soh/src/overlays/actors/ovl_En_Wood02/z_en_wood02.c index 72218e3b4..2fe2445a6 100644 --- a/soh/src/overlays/actors/ovl_En_Wood02/z_en_wood02.c +++ b/soh/src/overlays/actors/ovl_En_Wood02/z_en_wood02.c @@ -6,6 +6,7 @@ #include "z_en_wood02.h" #include "objects/object_wood02/object_wood02.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS 0 @@ -327,7 +328,6 @@ void EnWood02_Update(Actor* thisx, PlayState* play2) { Vec3f dropsSpawnPt; s32 i; s32 leavesParams; - s32 numDrops; // Despawn extra trees in a group if out of range if ((this->spawnType == WOOD_SPAWN_SPAWNED) && (this->actor.parent != NULL)) { @@ -358,12 +358,7 @@ void EnWood02_Update(Actor* thisx, PlayState* play2) { dropsSpawnPt.y += 200.0f; if ((this->unk_14C >= 0) && (this->unk_14C < 0x64)) { - if (CVarGetInteger(CVAR_ENHANCEMENT("TreesDropSticks"), 0) && INV_CONTENT(ITEM_STICK) != ITEM_NONE) { - numDrops = Rand_ZeroOne() * 4; - for (i = 0; i < numDrops; ++i) { - Item_DropCollectible(play, &dropsSpawnPt, ITEM00_STICK); - } - } else { + if (GameInteractor_Should(VB_TREE_DROP_COLLECTIBLE, true, this)) { Item_DropCollectibleRandom(play, &this->actor, &dropsSpawnPt, this->unk_14C << 4); } } else if (this->actor.home.rot.z != 0) { diff --git a/soh/src/overlays/actors/ovl_Obj_Syokudai/z_obj_syokudai.c b/soh/src/overlays/actors/ovl_Obj_Syokudai/z_obj_syokudai.c index 4758066e2..ab81c8421 100644 --- a/soh/src/overlays/actors/ovl_Obj_Syokudai/z_obj_syokudai.c +++ b/soh/src/overlays/actors/ovl_Obj_Syokudai/z_obj_syokudai.c @@ -8,6 +8,7 @@ #include "overlays/actors/ovl_En_Arrow/z_en_arrow.h" #include "objects/gameplay_keep/gameplay_keep.h" #include "objects/object_syokudai/object_syokudai.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_HOOKSHOT_PULLS_PLAYER) @@ -239,7 +240,7 @@ void ObjSyokudai_Update(Actor* thisx, PlayState* play2) { Collider_UpdateCylinder(&this->actor, &this->colliderFlame); CollisionCheck_SetAC(play, &play->colChkCtx, &this->colliderFlame.base); - if (this->litTimer > 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->litTimer > 0, this, &this->litTimer)) { this->litTimer--; if ((this->litTimer == 0) && (torchType != 0)) { sLitTorchCount--; 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 6cc57ca22..4ae6fb744 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -4764,7 +4764,9 @@ s32 func_808382DC(Player* this, PlayState* play) { gSaveContext.respawn[RESPAWN_MODE_DOWN].yaw = respawnInfo->yaw; } - Play_TriggerVoidOut(play); + if (GameInteractor_Should(VB_TRIGGER_VOIDOUT, true, this)) { + Play_TriggerVoidOut(play); + } } Player_PlayVoiceSfx(this, NA_SE_VO_LI_TAKEN_AWAY); @@ -5129,7 +5131,9 @@ s32 Player_HandleExitsAndVoids(PlayState* play, Player* this, CollisionPoly* pol } if (exitIndex == 0) { - Play_TriggerVoidOut(play); + if (GameInteractor_Should(VB_TRIGGER_VOIDOUT, true, this)) { + Play_TriggerVoidOut(play); + } Scene_SetTransitionForNextEntrance(play); } else { play->nextEntranceIndex = play->setupExitList[exitIndex - 1]; @@ -5163,7 +5167,9 @@ s32 Player_HandleExitsAndVoids(PlayState* play, Player* this, CollisionPoly* pol SurfaceType_GetSlope(&play->colCtx, poly, bgId) == 2, play->setupExitList[exitIndex - 1])) { gSaveContext.respawn[RESPAWN_MODE_DOWN].entranceIndex = play->nextEntranceIndex; - Play_TriggerVoidOut(play); + if (GameInteractor_Should(VB_TRIGGER_VOIDOUT, true, this)) { + Play_TriggerVoidOut(play); + } gSaveContext.respawnFlag = -2; } gSaveContext.retainWeatherMode = 1; @@ -5226,7 +5232,7 @@ s32 Player_HandleExitsAndVoids(PlayState* play, Player* this, CollisionPoly* pol if (this->actor.bgCheckFlags & 1) { if (this->floorProperty == 5) { Play_TriggerRespawn(play); - } else { + } else if (GameInteractor_Should(VB_TRIGGER_VOIDOUT, true, this)) { Play_TriggerVoidOut(play); } play->transitionType = TRANS_TYPE_FADE_BLACK_FAST; @@ -9584,6 +9590,10 @@ static FallImpactInfo D_80854600[] = { s32 func_80843E64(PlayState* play, Player* this) { s32 sp34; + if (!GameInteractor_Should(VB_RECIEVE_FALL_DAMAGE, true, this)) { + return 0; + } + if ((sFloorType == 6) || (sFloorType == 9)) { sp34 = 0; } else { @@ -14977,7 +14987,7 @@ void Player_Action_8084F88C(Player* this, PlayState* play) { if (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_ENTRANCES)) { Grotto_ForceRegularVoidOut(); } - } else { + } else if (GameInteractor_Should(VB_TRIGGER_VOIDOUT, true, this)) { Play_TriggerVoidOut(play); } 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 78b167442..b715b488e 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 @@ -26,6 +26,7 @@ #include "soh/SaveManager.h" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" +#include "soh/ShipUtils.h" typedef struct { s16 left; @@ -1053,20 +1054,27 @@ void FileChoose_UpdateRandomizer() { if (!SpoilerFileExists(CVarGetString(CVAR_GENERAL("SpoilerLog"), "")) && !CVarGetInteger(CVAR_RANDOMIZER_SETTING("DontGenerateSpoiler"), 0)) { CVarSetString(CVAR_GENERAL("SpoilerLog"), ""); + Randomizer_SetSpoilerLoaded(false); } if (CVarGetInteger(CVAR_GENERAL("RandomizerNewFileDropped"), 0) != 0 || !(Randomizer_IsSeedGenerated() || Randomizer_IsSpoilerLoaded()) && SpoilerFileExists(CVarGetString(CVAR_GENERAL("SpoilerLog"), "")) && !fileSelectSpoilerFileLoaded) { if (CVarGetInteger(CVAR_GENERAL("RandomizerNewFileDropped"), 0) != 0) { - CVarSetString(CVAR_GENERAL("SpoilerLog"), CVarGetString(CVAR_GENERAL("RandomizerDroppedFile"), "")); - Audio_PlayFanfare(NA_BGM_HORSE_GOAL); + if (SpoilerFileExists(CVarGetString(CVAR_GENERAL("RandomizerDroppedFile"), ""))) { + CVarSetString(CVAR_GENERAL("SpoilerLog"), CVarGetString(CVAR_GENERAL("RandomizerDroppedFile"), "")); + Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME); + } else { + Sfx_PlaySfxCentered(NA_SE_SY_ERROR); + } } const char* fileLoc = CVarGetString(CVAR_GENERAL("SpoilerLog"), ""); CVarSetInteger(CVAR_GENERAL("RandomizerNewFileDropped"), 0); CVarSetString(CVAR_GENERAL("RandomizerDroppedFile"), ""); - Randomizer_ParseSpoiler(fileLoc); - fileSelectSpoilerFileLoaded = true; + if (!Ship_IsCStringEmpty(fileLoc)) { + Randomizer_ParseSpoiler(fileLoc); + fileSelectSpoilerFileLoaded = true; + } if (SpoilerFileExists(CVarGetString(CVAR_GENERAL("SpoilerLog"), "")) && CVarGetInteger(CVAR_RANDOMIZER_SETTING("DontGenerateSpoiler"), 0)) { @@ -1374,7 +1382,8 @@ void FileChoose_UpdateQuestMenu(GameState* thisx) { } else { defaultName = &emptyNameNES; } - } else { // GAME_REGION_NTSC + this->charPage = FS_CHAR_PAGE_HIRA; // Default to Hiragana Keyboard + } else { // GAME_REGION_NTSC defaultName = CVarGetInteger(CVAR_ENHANCEMENT("LinkDefaultName"), 0) ? &linkNameNES : &emptyNameNES; } memcpy(Save_GetSaveMetaInfo(this->buttonIndex)->playerName, defaultName, 8); @@ -1575,7 +1584,8 @@ void FileChoose_UpdateRandomizerMenu(GameState* thisx) { } else { defaultName = &emptyNameNES; } - } else { // GAME_REGION_NTSC + this->charPage = FS_CHAR_PAGE_HIRA; // Default to Hiragana Keyboard + } else { // GAME_REGION_NTSC defaultName = CVarGetInteger(CVAR_ENHANCEMENT("LinkDefaultName"), 0) ? &linkNameNES : &emptyNameNES; } memcpy(Save_GetSaveMetaInfo(this->buttonIndex)->playerName, defaultName, 8);