/*
 * Much of the code here was borrowed from https://github.com/gamestabled/OoT3D_Randomizer/blob/main/code/src/entrance.c
 * It's been adapted for SoH to use our gPlayState vs their gGlobalContext with slightly different named properties, and our enums for some scenes/entrances.
 * 
 * Unlike 3DS rando, we need to be able to support the user loading up vanilla and rando saves, so the logic around
 * modifying the entrance table requires that we save the original table and reset whenever loading a vanilla save.
 * A modified dynamicExitList is manually included since we can't read it from addressing like 3ds rando.
 */

#include "randomizer_entrance.h"
#include "randomizer_grotto.h"
#include "soh/OTRGlobals.h"
#include "soh/SaveManager.h"
#include <string.h>

#include "global.h"
#include "entrance.h"

extern PlayState* gPlayState;

//Overwrite the dynamic exit for the OGC Fairy Fountain to be 0x3E8 instead
//of 0x340 (0x340 will stay as the exit for the HC Fairy Fountain -> Castle Grounds)
s16 dynamicExitList[] = {
    ENTR_DEATH_MOUNTAIN_TRAIL_GREAT_FAIRY_EXIT,
    ENTR_DEATH_MOUNTAIN_CRATER_GREAT_FAIRY_EXIT,
    ENTR_POTION_SHOP_KAKARIKO_1, // OGC Fairy -- ENTR_POTION_SHOP_KAKARIKO_1 unused
    ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_FRONT,
    ENTR_MARKET_DAY_OUTSIDE_POTION_SHOP,
    ENTR_KAKARIKO_VILLAGE_OUTSIDE_BAZAAR,
    ENTR_MARKET_DAY_OUTSIDE_BAZAAR,
    ENTR_KAKARIKO_VILLAGE_OUTSIDE_SKULKLTULA_HOUSE,
    ENTR_BACK_ALLEY_DAY_OUTSIDE_BOMBCHU_SHOP,
    ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOOTING_GALLERY,
    ENTR_MARKET_DAY_OUTSIDE_SHOOTING_GALLERY,
    ENTR_ZORAS_FOUNTAIN_OUTSIDE_GREAT_FAIRY,
    ENTR_CASTLE_GROUNDS_GREAT_FAIRY_EXIT, // HC Fairy
    ENTR_DESERT_COLOSSUS_GREAT_FAIRY_EXIT
};

// Warp Song indices array : 0x53C33C = { ENTR_SACRED_FOREST_MEADOW_WARP_PAD, ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD, ENTR_LAKE_HYLIA_WARP_PAD, ENTR_DESERT_COLOSSUS_WARP_PAD, ENTR_GRAVEYARD_WARP_PAD, ENTR_TEMPLE_OF_TIME_WARP_PAD }

// Owl Flights : 0x492064 and 0x492080

static s16 entranceOverrideTable[ENTRANCE_TABLE_SIZE] = {0};
// Boss scenes (normalize boss scene range to 0 on lookup) mapped to save/death warp entrance
static s16 bossSceneSaveDeathWarps[SHUFFLEABLE_BOSS_COUNT] = {0};
static ActorEntry modifiedLinkActorEntry = {0};

EntranceInfo originalEntranceTable[ENTRANCE_TABLE_SIZE] = {0};

typedef struct {
    s16 entryway;
    s16 exit;
    s16 bossDoor;
    s16 bossDoorReverse;
    s16 blueWarp;
    s16 scene;
    s16 bossScene;
} DungeonEntranceInfo;

static DungeonEntranceInfo dungeons[] = {
    //entryway                       exit,                                              boss,                               reverse,                        bluewarp,                                          dungeon scene,         boss scene
    { ENTR_DEKU_TREE_ENTRANCE,       ENTR_KOKIRI_FOREST_OUTSIDE_DEKU_TREE,              ENTR_DEKU_TREE_BOSS_ENTRANCE,       ENTR_DEKU_TREE_BOSS_DOOR,       ENTR_KOKIRI_FOREST_DEKU_TREE_BLUE_WARP,            SCENE_DEKU_TREE,       SCENE_DEKU_TREE_BOSS },
    { ENTR_DODONGOS_CAVERN_ENTRANCE, ENTR_DEATH_MOUNTAIN_TRAIL_OUTSIDE_DODONGOS_CAVERN, ENTR_DODONGOS_CAVERN_BOSS_ENTRANCE, ENTR_DODONGOS_CAVERN_BOSS_DOOR, ENTR_DEATH_MOUNTAIN_TRAIL_DODONGO_BLUE_WARP,       SCENE_DODONGOS_CAVERN, SCENE_DODONGOS_CAVERN_BOSS },
    { ENTR_JABU_JABU_ENTRANCE,       ENTR_ZORAS_FOUNTAIN_OUTSIDE_JABU_JABU,             ENTR_JABU_JABU_BOSS_ENTRANCE,       ENTR_JABU_JABU_BOSS_DOOR,       ENTR_ZORAS_FOUNTAIN_JABU_JABU_BLUE_WARP,           SCENE_JABU_JABU,       SCENE_JABU_JABU_BOSS },
    { ENTR_FOREST_TEMPLE_ENTRANCE,   ENTR_SACRED_FOREST_MEADOW_OUTSIDE_TEMPLE,          ENTR_FOREST_TEMPLE_BOSS_ENTRANCE,   ENTR_FOREST_TEMPLE_BOSS_DOOR,   ENTR_SACRED_FOREST_MEADOW_FOREST_TEMPLE_BLUE_WARP, SCENE_FOREST_TEMPLE,   SCENE_FOREST_TEMPLE_BOSS },
    { ENTR_FIRE_TEMPLE_ENTRANCE,     ENTR_DEATH_MOUNTAIN_CRATER_OUTSIDE_TEMPLE,         ENTR_FIRE_TEMPLE_BOSS_ENTRANCE,     ENTR_FIRE_TEMPLE_BOSS_DOOR,     ENTR_DEATH_MOUNTAIN_CRATER_FIRE_TEMPLE_BLUE_WARP,  SCENE_FIRE_TEMPLE,     SCENE_FIRE_TEMPLE_BOSS },
    { ENTR_WATER_TEMPLE_ENTRANCE,    ENTR_LAKE_HYLIA_OUTSIDE_TEMPLE,                    ENTR_WATER_TEMPLE_BOSS_ENTRANCE,    ENTR_WATER_TEMPLE_BOSS_DOOR,    ENTR_LAKE_HYLIA_WATER_TEMPLE_BLUE_WARP,            SCENE_WATER_TEMPLE,    SCENE_WATER_TEMPLE_BOSS },
    { ENTR_SPIRIT_TEMPLE_ENTRANCE,   ENTR_DESERT_COLOSSUS_OUTSIDE_TEMPLE,               ENTR_SPIRIT_TEMPLE_BOSS_ENTRANCE,   ENTR_SPIRIT_TEMPLE_BOSS_DOOR,   ENTR_DESERT_COLOSSUS_SPIRIT_TEMPLE_BLUE_WARP,      SCENE_SPIRIT_TEMPLE,   SCENE_SPIRIT_TEMPLE_BOSS },
    { ENTR_SHADOW_TEMPLE_ENTRANCE,   ENTR_GRAVEYARD_OUTSIDE_TEMPLE,                     ENTR_SHADOW_TEMPLE_BOSS_ENTRANCE,   ENTR_SHADOW_TEMPLE_BOSS_DOOR,   ENTR_GRAVEYARD_SHADOW_TEMPLE_BLUE_WARP,            SCENE_SHADOW_TEMPLE,   SCENE_SHADOW_TEMPLE_BOSS },
};

static s8 hasCopiedEntranceTable = 0;
static s8 hasModifiedEntranceTable = 0;

void Entrance_SetEntranceDiscovered(u16 entranceIndex, u8 isReversedEntrance);

u8 Entrance_EntranceIsNull(EntranceOverride* entranceOverride) {
    return entranceOverride->index == 0 && entranceOverride->destination == 0 && entranceOverride->override == 0 &&
           entranceOverride->overrideDestination == 0;
}

static void Entrance_SeparateOGCFairyFountainExit(void) {
    //Overwrite unused entrance 0x03E8 (ENTR_POTION_SHOP_KAKARIKO_1) with values from 0x0340 (ENTR_CASTLE_GROUNDS_GREAT_FAIRY_EXIT) to use it as the
    //exit from OGC Great Fairy Fountain -> Castle Grounds
    for (size_t i = 0; i < 4; ++i) {
        gEntranceTable[ENTR_POTION_SHOP_KAKARIKO_1 + i] = gEntranceTable[ENTR_CASTLE_GROUNDS_GREAT_FAIRY_EXIT + i];
    }
}

static void Entrance_SeparateAdultSpawnAndPrelude() {
    // Overwrite unused entrance 0x0282 (ENTR_HYRULE_FIELD_10) with values from 0x05F4 (ENTR_TEMPLE_OF_TIME_WARP_PAD) to use it as the
    // Adult Spawn index and separate it from Prelude of Light
    for (size_t i = 0; i < 4; ++i) {
        gEntranceTable[ENTR_HYRULE_FIELD_10 + i] = gEntranceTable[ENTR_TEMPLE_OF_TIME_WARP_PAD + i];
    }
}

// Fix Adult dungeon blue warps as Child by assigning the child values for the warp pads
static void Entrance_ReplaceChildTempleWarps() {
    if (Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF ||
        Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF) {
        // Forest Temple
        gEntranceTable[ENTR_SACRED_FOREST_MEADOW_FOREST_TEMPLE_BLUE_WARP] = gEntranceTable[ENTR_SACRED_FOREST_MEADOW_WARP_PAD];
        gEntranceTable[ENTR_SACRED_FOREST_MEADOW_3_1] = gEntranceTable[ENTR_SACRED_FOREST_MEADOW_2_1];
        // Fire Temple
        gEntranceTable[ENTR_DEATH_MOUNTAIN_CRATER_FIRE_TEMPLE_BLUE_WARP] = gEntranceTable[ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD];
        gEntranceTable[ENTR_DEATH_MOUNTAIN_CRATER_5_1] = gEntranceTable[ENTR_DEATH_MOUNTAIN_CRATER_4_1];
        // Water Temple
        gEntranceTable[ENTR_LAKE_HYLIA_WATER_TEMPLE_BLUE_WARP] = gEntranceTable[ENTR_LAKE_HYLIA_WARP_PAD];
        gEntranceTable[ENTR_LAKE_HYLIA_9_1] = gEntranceTable[ENTR_LAKE_HYLIA_8_1];
        // Shadow Temple
        gEntranceTable[ENTR_GRAVEYARD_SHADOW_TEMPLE_BLUE_WARP] = gEntranceTable[ENTR_GRAVEYARD_WARP_PAD];
        gEntranceTable[ENTR_GRAVEYARD_8_1] = gEntranceTable[ENTR_GRAVEYARD_7_1];
        // Spirit Temple
        gEntranceTable[ENTR_DESERT_COLOSSUS_SPIRIT_TEMPLE_BLUE_WARP] = gEntranceTable[ENTR_DESERT_COLOSSUS_WARP_PAD];
        gEntranceTable[ENTR_DESERT_COLOSSUS_8_1] = gEntranceTable[ENTR_DESERT_COLOSSUS_5_1];
    }
}

void Entrance_CopyOriginalEntranceTable(void) {
    if (!hasCopiedEntranceTable) {
        memcpy(originalEntranceTable, gEntranceTable, sizeof(EntranceInfo) * ENTRANCE_TABLE_SIZE);
        hasCopiedEntranceTable = 1;
    }
}

void Entrance_ResetEntranceTable(void) {
    if (hasCopiedEntranceTable && hasModifiedEntranceTable) {
        memcpy(gEntranceTable, originalEntranceTable, sizeof(EntranceInfo) * ENTRANCE_TABLE_SIZE);
        hasModifiedEntranceTable = 0;
    }
}

void Entrance_Init(void) {
    EntranceOverride* entranceOverrides = Randomizer_GetEntranceOverrides();
    s32 index;

    Entrance_CopyOriginalEntranceTable();

    // Skip Child Stealth if given by settings
    if (Randomizer_GetSettingValue(RSK_SKIP_CHILD_STEALTH)) {
        gEntranceTable[ENTR_CASTLE_COURTYARD_GUARDS_DAY_0].scene = SCENE_CASTLE_COURTYARD_ZELDA;
        gEntranceTable[ENTR_CASTLE_COURTYARD_GUARDS_DAY_0].spawn = 0;
        gEntranceTable[ENTR_CASTLE_COURTYARD_GUARDS_DAY_0].field = ENTRANCE_INFO_FIELD(false, false, TRANS_TYPE_FADE_WHITE, TRANS_TYPE_FADE_WHITE);
    }

    // Delete the title card and add a fade in for Hyrule Field from Ocarina of Time cutscene
    for (index = ENTR_HYRULE_FIELD_16; index <= ENTR_HYRULE_FIELD_16_3; ++index) {
        gEntranceTable[index].field = ENTRANCE_INFO_FIELD(false, false, TRANS_TYPE_FADE_BLACK, TRANS_TYPE_INSTANT);
    }

    Entrance_SeparateOGCFairyFountainExit();
    Entrance_SeparateAdultSpawnAndPrelude();
    Entrance_ReplaceChildTempleWarps();

    // Initialize the entrance override table with each index leading to itself. An
    // index referring to itself means that the entrance is not currently shuffled.
    for (s16 i = 0; i < ENTRANCE_TABLE_SIZE; i++) {
        entranceOverrideTable[i] = i;
    }

    // Initialize all boss room save/death warps with their vanilla dungeon entryway
    for (s16 i = 0; i < SHUFFLEABLE_BOSS_COUNT; i++) {
        bossSceneSaveDeathWarps[i] = dungeons[i].entryway;
    }

    // Initialize the grotto exit and load lists
    Grotto_InitExitAndLoadLists();

    // Then overwrite the indices which are shuffled
    for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) {

        if (Entrance_EntranceIsNull(&entranceOverrides[i])) {
            break;
        }

        s16 originalIndex = entranceOverrides[i].index;
        s16 originalDestination = entranceOverrides[i].destination;
        s16 overrideIndex = entranceOverrides[i].override;

        int16_t bossScene = -1;
        int16_t saveWarpEntrance = originalDestination; // Default save warp to the original return entrance

        // Search for boss room overrides and look for the matching save/death warp value to use
        // If the boss room is in a dungeon, use the dungeons entryway as the save warp
        // Otherwise use the "exit" value for the entrance that lead to the boss room
        for (int j = 0; j < SHUFFLEABLE_BOSS_COUNT; j++) {
            if (overrideIndex == dungeons[j].bossDoor) {
                bossScene = dungeons[j].bossScene;
            }

            if (index == dungeons[j].bossDoor) {
                saveWarpEntrance = dungeons[j].entryway;
            }
        }

        // Found a boss scene and a valid save/death warp value
        if (bossScene != -1 && saveWarpEntrance != -1) {
            bossSceneSaveDeathWarps[bossScene - SCENE_DEKU_TREE_BOSS] = saveWarpEntrance;
        }

        // Overwrite grotto related indices
        if (originalIndex >= ENTRANCE_GROTTO_EXIT_START) {
            Grotto_SetExitOverride(originalIndex, overrideIndex);
            continue;
        }

        if (originalIndex >= ENTRANCE_GROTTO_LOAD_START && originalIndex < ENTRANCE_GROTTO_EXIT_START) {
            Grotto_SetLoadOverride(originalIndex, overrideIndex);
            continue;
        }

        // Overwrite the indices which we want to shuffle, leaving the rest as they are
        entranceOverrideTable[originalIndex] = overrideIndex;

        //Override both land and water entrances for Hyrule Field -> ZR Front and vice versa
        if (originalIndex == ENTR_ZORAS_RIVER_WEST_EXIT) { //Hyrule Field -> ZR Front land entrance
            entranceOverrideTable[ENTR_ZORAS_RIVER_3] = overrideIndex;
        } else if (originalIndex == ENTR_HYRULE_FIELD_RIVER_EXIT) { //ZR Front -> Hyrule Field land entrance
            entranceOverrideTable[ENTR_HYRULE_FIELD_14] = overrideIndex;
        }
    }

    // Stop playing background music during shuffled entrance transitions
    // so that we don't get duplicated or overlapping music tracks
    if (Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES)) {

        s16 indicesToSilenceBackgroundMusic[2] = {
            // The lost woods music playing near the GC Woods Warp keeps playing
            // in the next area if the background music is allowed to keep playing
            entranceOverrideTable[ENTR_LOST_WOODS_TUNNEL_SHORTCUT], // Goron City -> Lost Woods override

            // If Malon is singing at night, then her singing will be transferred
            // to the next area if it allows the background music to keep playing
            entranceOverrideTable[ENTR_MARKET_DAY_CASTLE_EXIT], // Castle Grounds -> Market override
        };

        for (size_t j = 0; j < sizeof(indicesToSilenceBackgroundMusic) / sizeof(s16); j++) {

            s16 override = indicesToSilenceBackgroundMusic[j];
            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;
            }
        }
    }

    hasModifiedEntranceTable = 1;
}

s16 Entrance_GetOverride(s16 index) {

    // The game sometimes uses special indices from 0x7FF9 -> 0x7FFF for exiting
    // grottos and fairy fountains. These aren't handled here since the game
    // naturally handles them later.
    if (index >= ENTRANCE_TABLE_SIZE) {
        return index;
    }

    return entranceOverrideTable[index];
}

s16 Entrance_PeekNextIndexOverride(int16_t nextEntranceIndex) {
    return Grotto_GetEntranceValueHandlingGrottoRando(Entrance_GetOverride(nextEntranceIndex));
}

s16 Entrance_OverrideNextIndex(s16 nextEntranceIndex) {
    // Exiting through the crawl space from Hyrule Castle courtyard is the same exit as leaving Ganon's castle
    // Don't override the entrance if we came from the Castle courtyard (day and night scenes)
    if (gPlayState != NULL && (gPlayState->sceneNum == SCENE_CASTLE_COURTYARD_GUARDS_DAY || gPlayState->sceneNum == SCENE_CASTLE_COURTYARD_GUARDS_NIGHT) &&
        nextEntranceIndex == ENTR_CASTLE_GROUNDS_RAINBOW_BRIDGE_EXIT) {
        return nextEntranceIndex;
    }

    Entrance_SetEntranceDiscovered(nextEntranceIndex, false);
    EntranceTracker_SetLastEntranceOverride(nextEntranceIndex);
    return Grotto_OverrideSpecialEntrance(Entrance_GetOverride(nextEntranceIndex));
}

s16 Entrance_OverrideDynamicExit(s16 dynamicExitIndex) {
    Entrance_SetEntranceDiscovered(dynamicExitList[dynamicExitIndex], false);
    EntranceTracker_SetLastEntranceOverride(dynamicExitList[dynamicExitIndex]);
    return Grotto_OverrideSpecialEntrance(Entrance_GetOverride(dynamicExitList[dynamicExitIndex]));
}

u32 Entrance_SceneAndSpawnAre(u8 scene, u8 spawn) {
    s16 entranceIndex;

    // Adjust the entrance to account for the exact scene/spawn combination for child/adult and day/night
    if (!IS_DAY) {
        if (!LINK_IS_ADULT) {
            entranceIndex = gSaveContext.entranceIndex + 1;
        } else {
            entranceIndex = gSaveContext.entranceIndex + 3;
        }
    } else {
        if (!LINK_IS_ADULT) {
            entranceIndex = gSaveContext.entranceIndex;
        } else {
            entranceIndex = gSaveContext.entranceIndex + 2;
        }
    }

    EntranceInfo currentEntrance = gEntranceTable[entranceIndex];
    return currentEntrance.scene == scene && currentEntrance.spawn == spawn;
}

// Properly respawn the player after a game over, accounting for dungeon entrance randomizer
void Entrance_SetGameOverEntrance(void) {

    s16 scene = gPlayState->sceneNum;

    // When in a boss room and boss shuffle is on, use the boss scene to find the death warp entrance
    if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF &&
        scene >= SCENE_DEKU_TREE_BOSS && scene <= SCENE_SHADOW_TEMPLE_BOSS) {
        // Normalize boss scene range to 0 on lookup and handle for grotto entrances
        gSaveContext.entranceIndex = Grotto_OverrideSpecialEntrance(bossSceneSaveDeathWarps[scene - SCENE_DEKU_TREE_BOSS]);
        return;
    }

    //Set the current entrance depending on which entrance the player last came through
    switch (gSaveContext.entranceIndex) {
        case ENTR_DEKU_TREE_BOSS_ENTRANCE : //Deku Tree Boss Room
            gSaveContext.entranceIndex = ENTR_DEKU_TREE_ENTRANCE;
            return;
        case ENTR_DODONGOS_CAVERN_BOSS_ENTRANCE : //Dodongos Cavern Boss Room
            gSaveContext.entranceIndex = ENTR_DODONGOS_CAVERN_ENTRANCE;
            return;
        case ENTR_JABU_JABU_BOSS_ENTRANCE : //Jabu Jabus Belly Boss Room
            gSaveContext.entranceIndex = ENTR_JABU_JABU_ENTRANCE;
            return;
        case ENTR_FOREST_TEMPLE_BOSS_ENTRANCE : //Forest Temple Boss Room
            gSaveContext.entranceIndex = ENTR_FOREST_TEMPLE_ENTRANCE;
            return;
        case ENTR_FIRE_TEMPLE_BOSS_ENTRANCE : //Fire Temple Boss Room
            gSaveContext.entranceIndex = ENTR_FIRE_TEMPLE_ENTRANCE;
            return;
        case ENTR_WATER_TEMPLE_BOSS_ENTRANCE : //Water Temple Boss Room
            gSaveContext.entranceIndex = ENTR_WATER_TEMPLE_ENTRANCE;
            return;
        case ENTR_SPIRIT_TEMPLE_BOSS_ENTRANCE : //Spirit Temple Boss Room
            gSaveContext.entranceIndex = ENTR_SPIRIT_TEMPLE_ENTRANCE;
            return;
        case ENTR_SHADOW_TEMPLE_BOSS_ENTRANCE : //Shadow Temple Boss Room
            gSaveContext.entranceIndex = ENTR_SHADOW_TEMPLE_ENTRANCE;
            return;
        case ENTR_GANONDORF_BOSS_0 : //Ganondorf Boss Room
            gSaveContext.entranceIndex = ENTR_GANONS_TOWER_0; // Inside Ganon's Castle -> Ganon's Tower Climb
            return;
    }
}

// Properly savewarp the player accounting for dungeon entrance randomizer.
void Entrance_SetSavewarpEntrance(void) {

    s16 scene = gSaveContext.savedSceneNum;

    // When in a boss room and boss shuffle is on, use the boss scene to find the savewarp entrance
    if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF &&
        scene >= SCENE_DEKU_TREE_BOSS && scene <= SCENE_SHADOW_TEMPLE_BOSS) {
        // Normalize boss scene range to 0 on lookup and handle for grotto entrances
        gSaveContext.entranceIndex = Grotto_OverrideSpecialEntrance(bossSceneSaveDeathWarps[scene - SCENE_DEKU_TREE_BOSS]);
        return;
    }

    if (scene == SCENE_DEKU_TREE || scene == SCENE_DEKU_TREE_BOSS) {
        gSaveContext.entranceIndex = ENTR_DEKU_TREE_ENTRANCE;
    } else if (scene == SCENE_DODONGOS_CAVERN || scene == SCENE_DODONGOS_CAVERN_BOSS) {
        gSaveContext.entranceIndex = ENTR_DODONGOS_CAVERN_ENTRANCE;
    } else if (scene == SCENE_JABU_JABU || scene == SCENE_JABU_JABU_BOSS) {
        gSaveContext.entranceIndex = ENTR_JABU_JABU_ENTRANCE;
    } else if (scene == SCENE_FOREST_TEMPLE || scene == SCENE_FOREST_TEMPLE_BOSS) { //Forest Temple Boss Room
        gSaveContext.entranceIndex = ENTR_FOREST_TEMPLE_ENTRANCE;
    } else if (scene == SCENE_FIRE_TEMPLE || scene == SCENE_FIRE_TEMPLE_BOSS) { //Fire Temple Boss Room
        gSaveContext.entranceIndex = ENTR_FIRE_TEMPLE_ENTRANCE;
    } else if (scene == SCENE_WATER_TEMPLE || scene == SCENE_WATER_TEMPLE_BOSS) { //Water Temple Boss Room
        gSaveContext.entranceIndex = ENTR_WATER_TEMPLE_ENTRANCE;
    } else if (scene == SCENE_SPIRIT_TEMPLE || scene == SCENE_SPIRIT_TEMPLE_BOSS) { //Spirit Temple Boss Room
        gSaveContext.entranceIndex = ENTR_SPIRIT_TEMPLE_ENTRANCE;
    } else if (scene == SCENE_SHADOW_TEMPLE || scene == SCENE_SHADOW_TEMPLE_BOSS) { //Shadow Temple Boss Room
        gSaveContext.entranceIndex = ENTR_SHADOW_TEMPLE_ENTRANCE;
    } else if (scene == SCENE_BOTTOM_OF_THE_WELL) { // BOTW
        gSaveContext.entranceIndex = ENTR_BOTTOM_OF_THE_WELL_ENTRANCE;
    } else if (scene == SCENE_GERUDO_TRAINING_GROUND) { // GTG
        gSaveContext.entranceIndex = ENTR_GERUDO_TRAINING_GROUND_ENTRANCE;
    } else if (scene == SCENE_ICE_CAVERN) { // Ice cavern
        gSaveContext.entranceIndex = ENTR_ICE_CAVERN_ENTRANCE;
    } else if (scene == SCENE_INSIDE_GANONS_CASTLE) {
        gSaveContext.entranceIndex = ENTR_INSIDE_GANONS_CASTLE_ENTRANCE;
    } else if (scene == SCENE_GANONS_TOWER || scene == SCENE_INSIDE_GANONS_CASTLE_COLLAPSE || scene == SCENE_GANONS_TOWER_COLLAPSE_INTERIOR || scene == SCENE_GANON_BOSS || scene == SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR) {
        gSaveContext.entranceIndex = ENTR_GANONS_TOWER_0; // Inside Ganon's Castle -> Ganon's Tower Climb
    } else if (scene == SCENE_THIEVES_HIDEOUT) { // Theives hideout
        gSaveContext.entranceIndex = ENTR_THIEVES_HIDEOUT_0; // Gerudo Fortress -> Thieve's Hideout spawn 0
    } else if (scene == SCENE_LINKS_HOUSE &&
               Randomizer_GetSettingValue(RSK_SHUFFLE_INTERIOR_ENTRANCES) != RO_INTERIOR_ENTRANCE_SHUFFLE_ALL) {
        // Save warping in Link's house keeps the player there if Link's house not shuffled,
        // otherwise fallback to regular spawns
        gSaveContext.entranceIndex = ENTR_LINKS_HOUSE_CHILD_SPAWN;
    } else if (CVarGetInteger(CVAR_ENHANCEMENT("RememberSaveLocation"), 0) && scene != SCENE_FAIRYS_FOUNTAIN && scene != SCENE_GROTTOS &&
               gSaveContext.entranceIndex != ENTR_LOAD_OPENING) {
        // Use the saved entrance value with remember save location, except when in grottos/fairy fountains or if
        // the entrance index is -1 (new save)
        return;
    } else if (LINK_IS_CHILD) {
        gSaveContext.entranceIndex = Entrance_OverrideNextIndex(ENTR_LINKS_HOUSE_CHILD_SPAWN); // Child Overworld Spawn
    } else {
        gSaveContext.entranceIndex = Entrance_OverrideNextIndex(ENTR_HYRULE_FIELD_10); // Adult Overworld Spawn (Normally 0x5F4 (ENTR_TEMPLE_OF_TIME_WARP_PAD), but 0x282 (ENTR_HYRULE_FIELD_10) has been repurposed to differentiate from Prelude which also uses 0x5F4)
    }
}

void Entrance_SetWarpSongEntrance(void) {
    gPlayState->transitionTrigger = TRANS_TRIGGER_START;
    gPlayState->transitionType = TRANS_TYPE_FADE_WHITE_FAST;
    switch (gPlayState->msgCtx.lastPlayedSong) {
        case OCARINA_SONG_MINUET:
            gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_SACRED_FOREST_MEADOW_WARP_PAD); // Minuet
            break;
        case OCARINA_SONG_BOLERO:
            gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD); // Bolero
            break;
        case OCARINA_SONG_SERENADE:
            gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_LAKE_HYLIA_WARP_PAD); // Serenade
            break;
        case OCARINA_SONG_REQUIEM:
            gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_DESERT_COLOSSUS_WARP_PAD); // Requiem
            break;
        case OCARINA_SONG_NOCTURNE:
            gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_GRAVEYARD_WARP_PAD); // Nocturne
            break;
        case OCARINA_SONG_PRELUDE:
            gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_TEMPLE_OF_TIME_WARP_PAD); // Prelude
            break;
        default:
            gPlayState->transitionTrigger = TRANS_TRIGGER_OFF; // if something goes wrong, the animation plays normally
    }

    // If one of the warp songs happens to lead to a grotto return, then we
    // have to force the grotto return afterwards
    Grotto_ForceGrottoReturnOnSpecialEntrance();

    if (gSaveContext.gameMode != GAMEMODE_NORMAL) {
        // During DHWW the cutscene must play at the destination
        gSaveContext.respawnFlag = -3;
    } else if (gSaveContext.respawnFlag == -3) {
        // Unset Zoneout Type -3 to avoid cutscene at destination (technically it's not needed)
        gSaveContext.respawnFlag = 0;
    }
}

void Entrance_OverrideBlueWarp(void) {
    // Remap child 2nd visits in adult dungeons for warp pad -> bluewarp
    if (gSaveContext.entranceIndex == ENTR_SACRED_FOREST_MEADOW_WARP_PAD) {
        gSaveContext.entranceIndex = ENTR_SACRED_FOREST_MEADOW_FOREST_TEMPLE_BLUE_WARP;
    } else if (gSaveContext.entranceIndex == ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD) {
        gSaveContext.entranceIndex = ENTR_DEATH_MOUNTAIN_CRATER_FIRE_TEMPLE_BLUE_WARP;
    } else if (gSaveContext.entranceIndex == ENTR_LAKE_HYLIA_WARP_PAD) {
        gSaveContext.entranceIndex = ENTR_LAKE_HYLIA_WATER_TEMPLE_BLUE_WARP;
    } else if (gSaveContext.entranceIndex == ENTR_DESERT_COLOSSUS_WARP_PAD) {
        gSaveContext.entranceIndex = ENTR_DESERT_COLOSSUS_SPIRIT_TEMPLE_BLUE_WARP;
    } else if (gSaveContext.entranceIndex == ENTR_GRAVEYARD_WARP_PAD) {
        gSaveContext.entranceIndex = ENTR_GRAVEYARD_SHADOW_TEMPLE_BLUE_WARP;
    }

    switch (gSaveContext.entranceIndex) {
        case ENTR_KOKIRI_FOREST_DEKU_TREE_BLUE_WARP: // Gohma blue warp
        case ENTR_DEATH_MOUNTAIN_TRAIL_DODONGO_BLUE_WARP: // KD blue warp
        case ENTR_ZORAS_FOUNTAIN_JABU_JABU_BLUE_WARP: // Barinade blue warp
        case ENTR_SACRED_FOREST_MEADOW_FOREST_TEMPLE_BLUE_WARP: // Phantom Ganon blue warp
        case ENTR_DEATH_MOUNTAIN_CRATER_FIRE_TEMPLE_BLUE_WARP: // Volvagia blue warp
        case ENTR_LAKE_HYLIA_WATER_TEMPLE_BLUE_WARP: // Morpha blue warp
        case ENTR_DESERT_COLOSSUS_SPIRIT_TEMPLE_BLUE_WARP: // Bongo-Bongo blue warp
        case ENTR_GRAVEYARD_SHADOW_TEMPLE_BLUE_WARP: // Twinrova blue warp
            gSaveContext.entranceIndex = Entrance_OverrideNextIndex(gSaveContext.entranceIndex);
            return;
    }
}

void Entrance_EnableFW(void) {
    Player* player = GET_PLAYER(gPlayState);
    // Leave restriction in Tower Collapse Interior, Castle Collapse, Treasure Box Shop, Tower Collapse Exterior,
    // Grottos area, Fishing Pond, Ganon Battle and for states that disable buttons.
    if (!false /* farores wind anywhere */ ||
        gPlayState->sceneNum == SCENE_GANONS_TOWER_COLLAPSE_INTERIOR || gPlayState->sceneNum == SCENE_INSIDE_GANONS_CASTLE_COLLAPSE ||
        (gPlayState->sceneNum == SCENE_TREASURE_BOX_SHOP && !false /* shuffled chest mini game */) ||
        gPlayState->sceneNum == SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR || gPlayState->sceneNum == SCENE_GROTTOS ||
        gPlayState->sceneNum == SCENE_FISHING_POND || gPlayState->sceneNum == SCENE_GANON_BOSS ||
        gSaveContext.eventInf[0] & 0x1 ||   // Ingo's Minigame state
        player->stateFlags1 & (PLAYER_STATE1_HANGING_OFF_LEDGE | PLAYER_STATE1_CLIMBING_LADDER | PLAYER_STATE1_ON_HORSE | PLAYER_STATE1_IN_WATER) || // Swimming, riding horse, Down A, hanging from a ledge
        player->stateFlags2 & PLAYER_STATE2_CRAWLING    // Blank A
        // Shielding, spinning and getting skull tokens still disable buttons automatically
        ) {
        return;
    }

    for (size_t i = 1; i < ARRAY_COUNT(gSaveContext.equips.buttonItems); i++) {
        if (gSaveContext.equips.buttonItems[i] == ITEM_FARORES_WIND) {
            gSaveContext.buttonStatus[i] = BTN_ENABLED;
        }
    }
}

// Check if Link should still be riding epona after changing entrances
void Entrance_HandleEponaState(void) {
    s32 entrance = gPlayState->nextEntranceIndex;
    Player* player = GET_PLAYER(gPlayState);
    //If Link is riding Epona but he's about to go through an entrance where she can't spawn,
    //unset the Epona flag to avoid Master glitch, and restore temp B.
    if (Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES) && (player->stateFlags1 & PLAYER_STATE1_ON_HORSE)) {
        // Allow Master glitch to be performed on the Thieves Hideout entrance
        if (entrance == Entrance_GetOverride(ENTR_THIEVES_HIDEOUT_4)) { // Gerudo Fortress -> Theives Hideout
            return;
        }

        static const s16 validEponaEntrances[] = {
            ENTR_LAKE_HYLIA_NORTH_EXIT,       // Hyrule Field -> Lake Hylia
            ENTR_HYRULE_FIELD_FENCE_EXIT,     // Lake Hylia -> Hyrule Field
            ENTR_LAKE_HYLIA_OUTSIDE_FISHING_POND,       // LH Fishing Hole -> LH Fishing Island
            ENTR_LAKE_HYLIA_OUTSIDE_LAB,       // LH Lab -> Lake Hylia
            ENTR_GERUDO_VALLEY_EAST_EXIT,    // Hyrule Field -> Gerudo Valley
            ENTR_HYRULE_FIELD_ROCKY_PATH,     // Gerudo Valley -> Hyrule Field
            ENTR_LON_LON_RANCH_ENTRANCE,    // Hyrule Field -> Lon Lon Ranch
            ENTR_HYRULE_FIELD_CENTER_EXIT,     // Lon Lon Ranch -> Hyrule Field
            ENTR_HYRULE_FIELD_ON_BRIDGE_SPAWN,     // Market Entrance -> Hyrule Field
            ENTR_HYRULE_FIELD_RIVER_EXIT,     // ZR Front -> Hyrule Field
            ENTR_HYRULE_FIELD_WOODED_EXIT,     // LW Bridge -> Hyrule Field
            ENTR_GERUDOS_FORTRESS_EAST_EXIT, // GV Fortress Side -> Gerudo Fortress
            ENTR_GERUDO_VALLEY_WEST_EXIT,    // Gerudo Fortress -> GV Fortress Side
            ENTR_GERUDO_VALLEY_OUTSIDE_TENT,    // GV Carpenter Tent -> GV Fortress Side
            ENTR_LON_LON_RANCH_OUTSIDE_STABLES,    // LLR Stables -> Lon Lon Ranch
            ENTR_LON_LON_RANCH_OUTSIDE_TOWER,   // LLR Tower -> Lon Lon Ranch
            ENTR_LON_LON_RANCH_OUTSIDE_TALONS_HOUSE,    // LLR Talons House -> Lon Lon Ranch
            ENTR_HYRULE_FIELD_11,    // LLR Southern Fence Jump
            ENTR_HYRULE_FIELD_12,    // LLR Western Fence Jump
            ENTR_HYRULE_FIELD_13,    // LLR Eastern Fence Jump
            ENTR_HYRULE_FIELD_15,    // LLR Front Gate Jump
            // The following indices currently aren't randomized, but we'll list
            // them in case they ever are. They're all Theives Hideout -> Gerudo Fortress
            ENTR_GERUDOS_FORTRESS_1,
            ENTR_GERUDOS_FORTRESS_2,
            ENTR_GERUDOS_FORTRESS_3,
            ENTR_GERUDOS_FORTRESS_5,
        };
        for (size_t i = 0; i < ARRAY_COUNT(validEponaEntrances); i++) {
            // If the entrance is equal to any of the valid ones, return and
            // don't change anything
            if (entrance == validEponaEntrances[i]) {
                return;
            }
        }

        // Update Link's status to no longer be riding epona so that the next time link
        // enters an epona supported area, he isn't automatically placed on epona
        player->stateFlags1 &= ~PLAYER_STATE1_ON_HORSE;
        player->actor.parent = NULL;
        AREG(6) = 0;
        gSaveContext.equips.buttonItems[0] = gSaveContext.buttonStatus[0]; //"temp B"
        Interface_RandoRestoreSwordless();
    }
}

// Certain weather states don't clear normally in entrance rando, so we need to reset and re-apply
// the correct weather state based on the current entrance and scene
void Entrance_OverrideWeatherState() {
    gWeatherMode = 0;
    gPlayState->envCtx.gloomySkyMode = 0;

    // Weather only applyies to adult link
    if (LINK_IS_CHILD || gSaveContext.sceneSetupIndex >= 4) {
        return;
    }

    // Hyrule Market
    if (gSaveContext.entranceIndex == ENTR_HYRULE_FIELD_ON_BRIDGE_SPAWN) { // Hyrule Field by Market Entrance
        gWeatherMode = 1;
        return;
    }
    // Lon Lon Ranch (No Epona)
    if (!Flags_GetEventChkInf(EVENTCHKINF_EPONA_OBTAINED)){ // if you don't have Epona
        switch (gSaveContext.entranceIndex) {
            case ENTR_LON_LON_RANCH_ENTRANCE: // Lon Lon Ranch from HF
            case ENTR_HYRULE_FIELD_CENTER_EXIT: // Hyrule Field from LLR
                gWeatherMode = 2;
                return;
        }
    }
    // Water Temple
    if (!Flags_GetEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP)) { // have not beaten Water Temple
        switch (gSaveContext.entranceIndex) {
            case ENTR_ZORAS_RIVER_WATERFALL_EXIT: // Zora River from behind waterfall
            case ENTR_ZORAS_RIVER_UNDERWATER_SHORTCUT: // Zora River from LW water shortcut
            case ENTR_LOST_WOODS_UNDERWATER_SHORTCUT: // Lost Woods water shortcut from ZR
                gWeatherMode = 3;
                return;
        }
        switch (gPlayState->sceneNum) {
            case SCENE_ZORAS_DOMAIN: // Zora's Domain
            case SCENE_ZORAS_FOUNTAIN: // Zora's Fountain
                gWeatherMode = 3;
                return;
        }
    }
    // Kakariko Thunderstorm
    if (((gSaveContext.inventory.questItems & 0x7) == 0x7) && // Have forest, fire, and water medallion
        !(gSaveContext.sceneFlags[24].clear & 0x02)) { // have not beaten Bongo Bongo
        switch (gPlayState->sceneNum) {
            case SCENE_KAKARIKO_VILLAGE: // Kakariko
            case SCENE_GRAVEYARD: // Graveyard
                gPlayState->envCtx.gloomySkyMode = 2;
                switch (gSaveContext.entranceIndex) {
                    case ENTR_KAKARIKO_VILLAGE_FRONT_GATE: // Kakariko from HF
                    case ENTR_KAKARIKO_VILLAGE_GUARD_GATE: // Kakariko from Death Mountain Trail
                    case ENTR_GRAVEYARD_OUTSIDE_TEMPLE: // Graveyard from Shadow Temple
                        break;
                    default:
                        gWeatherMode = 5;
                        return;
                }
        }
    }
    // Death Mountain Cloudy
    if (!Flags_GetEventChkInf(EVENTCHKINF_USED_FIRE_TEMPLE_BLUE_WARP)) { // have not beaten Fire Temple
        if (gPlayState->nextEntranceIndex == ENTR_LOST_WOODS_TUNNEL_SHORTCUT) { // Lost Woods Goron City Shortcut
            gWeatherMode = 2;
            return;
        }
        switch (gPlayState->sceneNum) {
            case SCENE_KAKARIKO_VILLAGE: // Kakariko
            case SCENE_GRAVEYARD: // Graveyard
            case SCENE_DEATH_MOUNTAIN_TRAIL: // Death Mountain Trail
            case SCENE_DEATH_MOUNTAIN_CRATER: // Death Mountain Crater
                if (!gPlayState->envCtx.gloomySkyMode) {
                    gPlayState->envCtx.gloomySkyMode = 1;
                }
                switch (gSaveContext.entranceIndex) {
                    case ENTR_KAKARIKO_VILLAGE_FRONT_GATE: // Kakariko from HF
                    case ENTR_KAKARIKO_VILLAGE_SOUTHEAST_EXIT: // Kakariko from Graveyard
                        break;
                    default:
                        gWeatherMode = 2;
                        return;
                }
        }
    }
}

// Rectify the "Getting Caught By Gerudo" entrance index if necessary, based on the age and current scene
// In ER, Adult should be placed at the fortress entrance when getting caught in the fortress without a hookshot, instead of being thrown in the valley
// Child should always be thrown in the stream when caught in the valley, and placed at the fortress entrance from valley when caught in the fortress
void Entrance_OverrideGeurdoGuardCapture(void) {
    if (LINK_IS_CHILD) {
        gPlayState->nextEntranceIndex = ENTR_GERUDO_VALLEY_1; // Geurdo Valley thrown out
    }

    if ((LINK_IS_CHILD || Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES)) &&
        gPlayState->nextEntranceIndex == ENTR_GERUDO_VALLEY_1) { // Geurdo Valley thrown out
        if (gPlayState->sceneNum != SCENE_GERUDO_VALLEY) { // Geurdo Valley
            gPlayState->nextEntranceIndex = ENTR_GERUDOS_FORTRESS_EAST_EXIT; // Gerudo Fortress
        }
    }
}

void Entrance_OverrideSpawnScene(s32 sceneNum, s32 spawn) {
    // Copy the actorEntry properties to avoid modifying the original cached pointer
    // Then assign a pointer of our modified actoreEntry back
    modifiedLinkActorEntry.id = gPlayState->linkActorEntry->id;
    modifiedLinkActorEntry.pos = gPlayState->linkActorEntry->pos;
    modifiedLinkActorEntry.rot = gPlayState->linkActorEntry->rot;
    modifiedLinkActorEntry.params = gPlayState->linkActorEntry->params;

    if (Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) == RO_DUNGEON_ENTRANCE_SHUFFLE_ON_PLUS_GANON) {
        // Move Hyrule's Castle Courtyard exit spawn to be before the crates so players don't skip Talon
        if (sceneNum == SCENE_HYRULE_CASTLE && spawn == 1) {
            modifiedLinkActorEntry.pos.x = 0x033A;
            modifiedLinkActorEntry.pos.y = 0x0623;
            modifiedLinkActorEntry.pos.z = 0xFF22;
            gPlayState->linkActorEntry = &modifiedLinkActorEntry;
        }

        // Move Ganon's Castle exit spawn to be on the small ledge near the castle and not over the void
        // to prevent Link from falling if the bridge isn't spawned
        if (sceneNum == SCENE_OUTSIDE_GANONS_CASTLE && spawn == 1) {
            modifiedLinkActorEntry.pos.x = 0xFEA8;
            modifiedLinkActorEntry.pos.y = 0x065C;
            modifiedLinkActorEntry.pos.z = 0x0290;
            modifiedLinkActorEntry.rot.y = 0x0700;
            modifiedLinkActorEntry.params = 0x0DFF; // stationary spawn
            gPlayState->linkActorEntry = &modifiedLinkActorEntry;
        }
    }

    if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF) {
        // Repair the authentically bugged entrance when leaving Barniades boss room -> JabuJabu's belly
        // Link's position needs to be adjusted to prevent him from falling through the floor
        if (sceneNum == SCENE_JABU_JABU && spawn == 1) {
            modifiedLinkActorEntry.pos.z = 0xF7F4;
            gPlayState->linkActorEntry = &modifiedLinkActorEntry;
        }

        // Repair the authentically bugged entrance when leaving Morpha's boass room -> Water Temple
        // Link's position was at the start of the Water Temple entrance
        // This updates it to place him in the hallway outside of Morpha's boss room.
        if (sceneNum == SCENE_WATER_TEMPLE && spawn == 1) {
            modifiedLinkActorEntry.pos.x = 0xFF4C;
            modifiedLinkActorEntry.pos.y = 0x0406;
            modifiedLinkActorEntry.pos.z = 0xF828;
            modifiedLinkActorEntry.rot.y = 0x0;
            gPlayState->linkActorEntry = &modifiedLinkActorEntry;
        }
    }

    if (Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES) == RO_GENERIC_ON) {
        // Move Hyrule Field bridge spawn for child link at night to be beyond the moat so he doesn't fall in the water
        if (sceneNum == SCENE_HYRULE_FIELD && spawn == 7 && LINK_IS_CHILD && IS_NIGHT) {
            modifiedLinkActorEntry.pos.x = 0x0001;
            modifiedLinkActorEntry.pos.z = 0x049E;
            gPlayState->linkActorEntry = &modifiedLinkActorEntry;
        }
    }
}

s32 Entrance_OverrideSpawnSceneRoom(s32 sceneNum, s32 spawn, s32 roomNum) {
    if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF) {
        // Repair the authentically bugged scene/spawn info for leaving Barinade's boss room -> JabuJabu's belly
        // to load the correct room outside Barniade's boss room
        if (sceneNum == SCENE_JABU_JABU && spawn == 1) {
            return 5;
        }

        // Repair the authentically bugged scene/spawn info for leaving Morhpa's boss room -> Water Temple
        // to load the correct room for the hallway before Morpha's boss room
        if (sceneNum == SCENE_WATER_TEMPLE && spawn == 1) {
            return 11;
        }
    }

    return roomNum;
}

u8 Entrance_GetIsSceneDiscovered(u8 sceneNum) {
    u16 bitsPerIndex = sizeof(u32) * 8;
    u32 idx = sceneNum / bitsPerIndex;
    if (idx < SAVEFILE_SCENES_DISCOVERED_IDX_COUNT) {
        u32 sceneBit = 1 << (sceneNum - (idx * bitsPerIndex));
        return (gSaveContext.ship.stats.scenesDiscovered[idx] & sceneBit) != 0;
    }
    return 0;
}

void Entrance_SetSceneDiscovered(u8 sceneNum) {
    if (Entrance_GetIsSceneDiscovered(sceneNum)) {
        return;
    }

    u16 bitsPerIndex = sizeof(u32) * 8;
    u32 idx = sceneNum / bitsPerIndex;
    if (idx < SAVEFILE_SCENES_DISCOVERED_IDX_COUNT) {
        u32 sceneBit = 1 << (sceneNum - (idx * bitsPerIndex));
        gSaveContext.ship.stats.scenesDiscovered[idx] |= sceneBit;
    }
    // Save scenesDiscovered
    Save_SaveSection(SECTION_ID_SCENES);
}

u8 Entrance_GetIsEntranceDiscovered(u16 entranceIndex) {
    u16 bitsPerIndex = sizeof(u32) * 8;
    u32 idx = entranceIndex / bitsPerIndex;
    if (idx < SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT) {
        u32 entranceBit = 1 << (entranceIndex - (idx * bitsPerIndex));
        return (gSaveContext.ship.stats.entrancesDiscovered[idx] & entranceBit) != 0;
    }
    return 0;
}

void Entrance_SetEntranceDiscovered(u16 entranceIndex, u8 isReversedEntrance) {
    EntranceOverride* entranceOverrides = Randomizer_GetEntranceOverrides();
    // Skip if already set to save time from setting the connected entrance or
    // if this entrance is outside of the randomized entrance range (i.e. is a dynamic entrance)
    if (entranceIndex > MAX_ENTRANCE_RANDO_USED_INDEX || Entrance_GetIsEntranceDiscovered(entranceIndex)) {
        return;
    }

    u16 bitsPerIndex = sizeof(u32) * 8;
    u32 idx = entranceIndex / bitsPerIndex;
    if (idx < SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT) {
        u32 entranceBit = 1 << (entranceIndex - (idx * bitsPerIndex));
        gSaveContext.ship.stats.entrancesDiscovered[idx] |= entranceBit;

        // Set reverse entrance when not decoupled
        if (!Randomizer_GetSettingValue(RSK_DECOUPLED_ENTRANCES) && !isReversedEntrance) {
            for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) {
                if (entranceIndex == entranceOverrides[i].index) {
                    Entrance_SetEntranceDiscovered(entranceOverrides[i].overrideDestination, true);
                    break;
                }
            }
        }
    }

    // Save entrancesDiscovered when it is not the reversed entrance
    if (!isReversedEntrance) {
        Save_SaveSection(SECTION_ID_ENTRANCES);
    }
}
