2022-11-14 06:13:21 -05:00
/*
* 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 3 DS 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 3 ds rando .
*/
# include "randomizer_entrance.h"
# include "randomizer_grotto.h"
# include <string.h>
# include "global.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 [ ] = { 0x045B , 0x0482 , 0x03E8 , 0x044B , 0x02A2 , 0x0201 , 0x03B8 , 0x04EE , 0x03C0 , 0x0463 , 0x01CD , 0x0394 , 0x0340 , 0x057C } ;
// OGC Fairy HC Fairy
// Warp Song indices array : 0x53C33C = { 0x0600, 0x04F6, 0x0604, 0x01F1, 0x0568, 0x05F4 }
// Owl Flights : 0x492064 and 0x492080
static s16 entranceOverrideTable [ ENTRANCE_TABLE_SIZE ] = { 0 } ;
2023-01-20 01:00:12 -05:00
// Boss scenes (normalize boss scene range to 0 on lookup) to the replaced dungeon scene it is connected to
static s16 dungeonBossSceneOverrides [ SHUFFLEABLE_BOSS_COUNT ] = { 0 } ;
static ActorEntry modifiedLinkActorEntry = { 0 } ;
2022-11-14 06:13:21 -05:00
2022-12-06 23:44:14 -05:00
EntranceInfo originalEntranceTable [ ENTRANCE_TABLE_SIZE ] = { 0 } ;
2022-11-14 06:13:21 -05:00
2023-01-20 01:00:12 -05:00
typedef struct {
s16 blueWarp ;
s16 destination ;
} BlueWarpReplacement ;
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
{ DEKU_TREE_ENTRANCE , 0x0209 , 0x040F , 0x0252 , 0x0457 , SCENE_YDAN , SCENE_YDAN_BOSS } ,
{ DODONGOS_CAVERN_ENTRANCE , 0x0242 , 0x040B , 0x00C5 , 0x047A , SCENE_DDAN , SCENE_DDAN_BOSS } ,
{ JABU_JABUS_BELLY_ENTRANCE , 0x0221 , 0x0301 , 0x0407 , 0x010E , SCENE_BDAN , SCENE_BDAN_BOSS } ,
{ FOREST_TEMPLE_ENTRANCE , 0x0215 , 0x000C , 0x024E , 0x0608 , SCENE_BMORI1 , SCENE_MORIBOSSROOM } ,
{ FIRE_TEMPLE_ENTRANCE , 0x024A , 0x0305 , 0x0175 , 0x0564 , SCENE_HIDAN , SCENE_FIRE_BS } ,
{ WATER_TEMPLE_ENTRANCE , 0x021D , 0x0417 , 0x0423 , 0x060C , SCENE_MIZUSIN , SCENE_MIZUSIN_BS } ,
{ SPIRIT_TEMPLE_ENTRANCE , 0x01E1 , 0x008D , 0x02F5 , 0x0610 , SCENE_JYASINZOU , SCENE_JYASINBOSS } ,
{ SHADOW_TEMPLE_ENTRANCE , 0x0205 , 0x0413 , 0x02B2 , 0x0580 , SCENE_HAKADAN , SCENE_HAKADAN_BS } ,
} ;
2022-11-14 06:13:21 -05:00
//These variables store the new entrance indices for dungeons so that
//savewarping and game overs respawn players at the proper entrance.
//By default, these will be their vanilla values.
static s16 newDekuTreeEntrance = DEKU_TREE_ENTRANCE ;
static s16 newDodongosCavernEntrance = DODONGOS_CAVERN_ENTRANCE ;
static s16 newJabuJabusBellyEntrance = JABU_JABUS_BELLY_ENTRANCE ;
static s16 newForestTempleEntrance = FOREST_TEMPLE_ENTRANCE ;
static s16 newFireTempleEntrance = FIRE_TEMPLE_ENTRANCE ;
static s16 newWaterTempleEntrance = WATER_TEMPLE_ENTRANCE ;
static s16 newSpiritTempleEntrance = SPIRIT_TEMPLE_ENTRANCE ;
static s16 newShadowTempleEntrance = SHADOW_TEMPLE_ENTRANCE ;
static s16 newBottomOfTheWellEntrance = BOTTOM_OF_THE_WELL_ENTRANCE ;
static s16 newGerudoTrainingGroundsEntrance = GERUDO_TRAINING_GROUNDS_ENTRANCE ;
static s16 newIceCavernEntrance = ICE_CAVERN_ENTRANCE ;
static s8 hasCopiedEntranceTable = 0 ;
static s8 hasModifiedEntranceTable = 0 ;
2022-12-06 23:44:14 -05:00
void Entrance_SetEntranceDiscovered ( u16 entranceIndex ) ;
2022-11-14 06:13:21 -05:00
u8 Entrance_EntranceIsNull ( EntranceOverride * entranceOverride ) {
return entranceOverride - > index = = 0 & & entranceOverride - > destination = = 0 & & entranceOverride - > blueWarp = = 0
& & entranceOverride - > override = = 0 & & entranceOverride - > overrideDestination = = 0 ;
}
static void Entrance_SeparateOGCFairyFountainExit ( void ) {
//Overwrite unused entrance 0x03E8 with values from 0x0340 to use it as the
//exit from OGC Great Fairy Fountain -> Castle Grounds
for ( size_t i = 0 ; i < 4 ; + + i ) {
gEntranceTable [ 0x3E8 + i ] = gEntranceTable [ 0x340 + i ] ;
}
}
2022-12-06 18:37:50 -05:00
static void Entrance_SeparateAdultSpawnAndPrelude ( ) {
// Overwrite unused entrance 0x0282 with values from 0x05F4 to use it as the
// Adult Spawn index and separate it from Prelude of Light
for ( size_t i = 0 ; i < 4 ; + + i ) {
gEntranceTable [ 0x282 + i ] = gEntranceTable [ 0x5F4 + i ] ;
}
}
2022-11-14 06:13:21 -05:00
void Entrance_CopyOriginalEntranceTable ( void ) {
if ( ! hasCopiedEntranceTable ) {
2022-12-06 23:44:14 -05:00
memcpy ( originalEntranceTable , gEntranceTable , sizeof ( EntranceInfo ) * ENTRANCE_TABLE_SIZE ) ;
2022-11-14 06:13:21 -05:00
hasCopiedEntranceTable = 1 ;
}
}
void Entrance_ResetEntranceTable ( void ) {
if ( hasCopiedEntranceTable & & hasModifiedEntranceTable ) {
2022-12-06 23:44:14 -05:00
memcpy ( gEntranceTable , originalEntranceTable , sizeof ( EntranceInfo ) * ENTRANCE_TABLE_SIZE ) ;
2022-11-14 06:13:21 -05:00
hasModifiedEntranceTable = 0 ;
}
}
void Entrance_Init ( void ) {
s32 index ;
2023-01-20 01:00:12 -05:00
size_t blueWarpRemapIdx = 0 ;
BlueWarpReplacement bluewarps [ SHUFFLEABLE_BOSS_COUNT ] = { 0 } ;
2022-11-14 06:13:21 -05:00
Entrance_CopyOriginalEntranceTable ( ) ;
// Skip Child Stealth if given by settings
if ( Randomizer_GetSettingValue ( RSK_SKIP_CHILD_STEALTH ) ) {
gEntranceTable [ 0x07A ] . scene = 0x4A ;
gEntranceTable [ 0x07A ] . spawn = 0x00 ;
gEntranceTable [ 0x07A ] . field = 0x0183 ;
}
// Delete the title card and add a fade in for Hyrule Field from Ocarina of Time cutscene
for ( index = 0x50F ; index < 0x513 ; + + index ) {
gEntranceTable [ index ] . field = 0x010B ;
}
Entrance_SeparateOGCFairyFountainExit ( ) ;
2022-12-06 18:37:50 -05:00
Entrance_SeparateAdultSpawnAndPrelude ( ) ;
2022-11-14 06:13:21 -05:00
// 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 ;
}
2023-01-20 01:00:12 -05:00
// Initialize all boss rooms connected to their vanilla dungeon
for ( s16 i = 1 ; i < SHUFFLEABLE_BOSS_COUNT ; i + + ) {
dungeonBossSceneOverrides [ i ] = i ;
}
2022-11-14 06:13:21 -05:00
// 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 ( & gSaveContext . entranceOverrides [ i ] ) ) {
break ;
}
s16 originalIndex = gSaveContext . entranceOverrides [ i ] . index ;
s16 blueWarpIndex = gSaveContext . entranceOverrides [ i ] . blueWarp ;
s16 overrideIndex = gSaveContext . entranceOverrides [ i ] . override ;
//Overwrite grotto related indices
2022-12-06 23:44:14 -05:00
if ( originalIndex > = ENTRANCE_RANDO_GROTTO_EXIT_START ) {
2022-11-14 06:13:21 -05:00
Grotto_SetExitOverride ( originalIndex , overrideIndex ) ;
continue ;
}
2022-12-06 23:44:14 -05:00
if ( originalIndex > = ENTRANCE_RANDO_GROTTO_LOAD_START & & originalIndex < ENTRANCE_RANDO_GROTTO_EXIT_START ) {
2022-11-14 06:13:21 -05:00
Grotto_SetLoadOverride ( originalIndex , overrideIndex ) ;
continue ;
}
// Overwrite the indices which we want to shuffle, leaving the rest as they are
entranceOverrideTable [ originalIndex ] = overrideIndex ;
if ( blueWarpIndex ! = 0 ) {
2023-01-20 01:00:12 -05:00
// When boss shuffle is enabled, we need to know what dungeon the boss room is connected to for
// death/save warping, and for the blue warp
if ( Randomizer_GetSettingValue ( RSK_SHUFFLE_BOSS_ENTRANCES ) ! = RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF ) {
s16 bossScene = - 1 ;
s16 replacedDungeonScene = - 1 ;
s16 replacedDungeonExit = - 1 ;
// Search for the boss scene and replaced blue warp exits
for ( s16 j = 0 ; j < = SHUFFLEABLE_BOSS_COUNT ; j + + ) {
if ( blueWarpIndex = = dungeons [ j ] . blueWarp ) {
bossScene = dungeons [ j ] . bossScene ;
}
if ( overrideIndex = = dungeons [ j ] . bossDoorReverse ) {
replacedDungeonScene = dungeons [ j ] . scene ;
replacedDungeonExit = dungeons [ j ] . exit ;
}
}
// assign the boss scene override
if ( bossScene ! = - 1 & & replacedDungeonScene ! = - 1 & & replacedDungeonExit ! = - 1 ) {
dungeonBossSceneOverrides [ bossScene - SCENE_YDAN_BOSS ] = replacedDungeonScene ;
bluewarps [ blueWarpRemapIdx ] . blueWarp = blueWarpIndex ;
bluewarps [ blueWarpRemapIdx ] . destination = replacedDungeonExit ;
blueWarpRemapIdx + + ;
}
} else {
entranceOverrideTable [ blueWarpIndex ] = overrideIndex ;
}
2022-11-14 06:13:21 -05:00
}
//Override both land and water entrances for Hyrule Field -> ZR Front and vice versa
if ( originalIndex = = 0x00EA ) { //Hyrule Field -> ZR Front land entrance
entranceOverrideTable [ 0x01D9 ] = overrideIndex ;
} else if ( originalIndex = = 0x0181 ) { //ZR Front -> Hyrule Field land entrance
entranceOverrideTable [ 0x0311 ] = overrideIndex ;
}
}
2023-01-20 01:00:12 -05:00
// If we have remapped blue warps from boss shuffle, handle setting those and grabbing the override for
// the replaced dungeons exit in the event that dungeon shuffle is also turned on
for ( size_t i = 0 ; i < ARRAY_COUNT ( bluewarps ) ; i + + ) {
if ( bluewarps [ i ] . blueWarp ! = 0 & & bluewarps [ i ] . destination ! = 0 ) {
entranceOverrideTable [ bluewarps [ i ] . blueWarp ] = Entrance_GetOverride ( bluewarps [ i ] . destination ) ;
}
}
2022-11-14 06:13:21 -05:00
// 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 bvackground music is allowed to keep playing
entranceOverrideTable [ 0x04D6 ] , // 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 [ 0x025A ] , // 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 & = ~ 0x8000 ;
}
}
}
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_OverrideNextIndex ( s16 nextEntranceIndex ) {
// When entering Spirit Temple, clear temp flags so they don't carry over to the randomized dungeon
if ( nextEntranceIndex = = 0x0082 & & Entrance_GetOverride ( nextEntranceIndex ) ! = nextEntranceIndex & &
gPlayState ! = NULL ) {
gPlayState - > actorCtx . flags . tempSwch = 0 ;
gPlayState - > actorCtx . flags . tempCollect = 0 ;
}
// Exiting through the crawl space from Hyrule Castle courtyard is the same exit as leaving Ganon's castle
2023-01-17 15:18:59 -05:00
// Don't override the entrance if we came from the Castle courtyard (day and night scenes)
2023-02-12 23:34:16 -05:00
if ( gPlayState ! = NULL & & ( gPlayState - > sceneNum = = SCENE_HAIRAL_NIWA | | gPlayState - > sceneNum = = SCENE_HAIRAL_NIWA_N ) & &
nextEntranceIndex = = 0x023D ) {
2022-11-14 06:13:21 -05:00
return nextEntranceIndex ;
}
2022-12-06 23:44:14 -05:00
Entrance_SetEntranceDiscovered ( nextEntranceIndex ) ;
EntranceTracker_SetLastEntranceOverride ( nextEntranceIndex ) ;
2022-11-14 06:13:21 -05:00
return Grotto_OverrideSpecialEntrance ( Entrance_GetOverride ( nextEntranceIndex ) ) ;
}
s16 Entrance_OverrideDynamicExit ( s16 dynamicExitIndex ) {
2022-12-06 23:44:14 -05:00
Entrance_SetEntranceDiscovered ( dynamicExitList [ dynamicExitIndex ] ) ;
EntranceTracker_SetLastEntranceOverride ( dynamicExitList [ dynamicExitIndex ] ) ;
2022-11-14 06:13:21 -05:00
return Grotto_OverrideSpecialEntrance ( Entrance_GetOverride ( dynamicExitList [ dynamicExitIndex ] ) ) ;
}
u32 Entrance_SceneAndSpawnAre ( u8 scene , u8 spawn ) {
2022-12-06 23:44:14 -05:00
s16 computedEntranceIndex ;
2023-02-12 23:34:16 -05:00
// Adjust the entrance to account for the exact scene/spawn combination for child/adult and day/night
2022-12-06 23:44:14 -05:00
if ( ! IS_DAY ) {
if ( ! LINK_IS_ADULT ) {
computedEntranceIndex = gSaveContext . entranceIndex + 1 ;
} else {
computedEntranceIndex = gSaveContext . entranceIndex + 3 ;
}
} else {
if ( ! LINK_IS_ADULT ) {
computedEntranceIndex = gSaveContext . entranceIndex ;
} else {
computedEntranceIndex = gSaveContext . entranceIndex + 2 ;
}
}
EntranceInfo currentEntrance = gEntranceTable [ computedEntranceIndex ] ;
2022-11-14 06:13:21 -05:00
return currentEntrance . scene = = scene & & currentEntrance . spawn = = spawn ;
}
2023-01-20 01:00:12 -05:00
// Properly respawn the player after a game over, accounting for dungeon entrance randomizer
2022-11-14 06:13:21 -05:00
void Entrance_SetGameOverEntrance ( void ) {
2023-01-20 01:00:12 -05:00
s16 scene = gPlayState - > sceneNum ;
// When in a boss room and boss shuffle is on, get the connected dungeon's original boss room entrance
// then run the normal game over overrides on it
if ( Randomizer_GetSettingValue ( RSK_SHUFFLE_BOSS_ENTRANCES ) ! = RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF & &
scene > = SCENE_YDAN_BOSS & & scene < = SCENE_HAKADAN_BS ) {
// Normalize boss scene range to 0 on lookup
scene = dungeonBossSceneOverrides [ scene - SCENE_YDAN_BOSS ] ;
gSaveContext . entranceIndex = dungeons [ scene ] . bossDoor ;
}
2022-11-14 06:13:21 -05:00
//Set the current entrance depending on which entrance the player last came through
switch ( gSaveContext . entranceIndex ) {
case 0x040F : //Deku Tree Boss Room
gSaveContext . entranceIndex = newDekuTreeEntrance ;
return ;
case 0x040B : //Dodongos Cavern Boss Room
gSaveContext . entranceIndex = newDodongosCavernEntrance ;
return ;
case 0x0301 : //Jabu Jabus Belly Boss Room
gSaveContext . entranceIndex = newJabuJabusBellyEntrance ;
return ;
case 0x000C : //Forest Temple Boss Room
gSaveContext . entranceIndex = newForestTempleEntrance ;
return ;
case 0x0305 : //Fire Temple Boss Room
gSaveContext . entranceIndex = newFireTempleEntrance ;
return ;
case 0x0417 : //Water Temple Boss Room
gSaveContext . entranceIndex = newWaterTempleEntrance ;
return ;
case 0x008D : //Spirit Temple Boss Room
gSaveContext . entranceIndex = newSpiritTempleEntrance ;
return ;
case 0x0413 : //Shadow Temple Boss Room
gSaveContext . entranceIndex = newShadowTempleEntrance ;
return ;
case 0x041F : //Ganondorf Boss Room
gSaveContext . entranceIndex = 0x041B ; // Inside Ganon's Castle -> Ganon's Tower Climb
return ;
}
}
2023-01-20 01:00:12 -05:00
// Properly savewarp the player accounting for dungeon entrance randomizer.
2022-11-14 06:13:21 -05:00
void Entrance_SetSavewarpEntrance ( void ) {
s16 scene = gSaveContext . savedSceneNum ;
2023-01-20 01:00:12 -05:00
// When in a boss room and boss shuffle is on, use the boss scene override to remap to its
// connected dungeon and use that for the final entrance
if ( Randomizer_GetSettingValue ( RSK_SHUFFLE_BOSS_ENTRANCES ) ! = RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF & &
scene > = SCENE_YDAN_BOSS & & scene < = SCENE_HAKADAN_BS ) {
// Normalize boss scene range to 0 on lookup
scene = dungeonBossSceneOverrides [ scene - SCENE_YDAN_BOSS ] ;
}
2022-11-14 06:13:21 -05:00
if ( scene = = SCENE_YDAN | | scene = = SCENE_YDAN_BOSS ) {
gSaveContext . entranceIndex = newDekuTreeEntrance ;
} else if ( scene = = SCENE_DDAN | | scene = = SCENE_DDAN_BOSS ) {
gSaveContext . entranceIndex = newDodongosCavernEntrance ;
} else if ( scene = = SCENE_BDAN | | scene = = SCENE_BDAN_BOSS ) {
gSaveContext . entranceIndex = newJabuJabusBellyEntrance ;
} else if ( scene = = SCENE_BMORI1 | | scene = = SCENE_MORIBOSSROOM ) { //Forest Temple Boss Room
gSaveContext . entranceIndex = newForestTempleEntrance ;
} else if ( scene = = SCENE_HIDAN | | scene = = SCENE_FIRE_BS ) { //Fire Temple Boss Room
gSaveContext . entranceIndex = newFireTempleEntrance ;
} else if ( scene = = SCENE_MIZUSIN | | scene = = SCENE_MIZUSIN_BS ) { //Water Temple Boss Room
gSaveContext . entranceIndex = newWaterTempleEntrance ;
} else if ( scene = = SCENE_JYASINZOU | | scene = = SCENE_JYASINBOSS ) { //Spirit Temple Boss Room
gSaveContext . entranceIndex = newSpiritTempleEntrance ;
} else if ( scene = = SCENE_HAKADAN | | scene = = SCENE_HAKADAN_BS ) { //Shadow Temple Boss Room
gSaveContext . entranceIndex = newShadowTempleEntrance ;
} else if ( scene = = SCENE_HAKADANCH ) { // BOTW
gSaveContext . entranceIndex = newBottomOfTheWellEntrance ;
} else if ( scene = = SCENE_MEN ) { // GTG
gSaveContext . entranceIndex = newGerudoTrainingGroundsEntrance ;
} else if ( scene = = SCENE_ICE_DOUKUTO ) { // Ice cavern
gSaveContext . entranceIndex = newIceCavernEntrance ;
} else if ( scene = = SCENE_GANONTIKA ) {
gSaveContext . entranceIndex = GANONS_CASTLE_ENTRANCE ;
} else if ( scene = = SCENE_GANON | | scene = = SCENE_GANONTIKA_SONOGO | | scene = = SCENE_GANON_SONOGO | | scene = = SCENE_GANON_DEMO | | scene = = SCENE_GANON_FINAL ) {
gSaveContext . entranceIndex = 0x041B ; // Inside Ganon's Castle -> Ganon's Tower Climb
} else if ( scene = = SCENE_GERUDOWAY ) { // Theives hideout
gSaveContext . entranceIndex = 0x0486 ; // Gerudo Fortress -> Thieve's Hideout spawn 0
} else if ( scene = = SCENE_LINK_HOME ) {
2022-12-06 18:37:50 -05:00
gSaveContext . entranceIndex = Entrance_OverrideNextIndex ( LINK_HOUSE_SAVEWARP_ENTRANCE ) ;
2022-11-14 06:13:21 -05:00
} else if ( LINK_IS_CHILD ) {
2022-12-06 18:37:50 -05:00
gSaveContext . entranceIndex = Entrance_OverrideNextIndex ( LINK_HOUSE_SAVEWARP_ENTRANCE ) ; // Child Overworld Spawn
2022-11-14 06:13:21 -05:00
} else {
2022-12-06 18:37:50 -05:00
gSaveContext . entranceIndex = Entrance_OverrideNextIndex ( 0x0282 ) ; // Adult Overworld Spawn (Normally 0x5F4, but 0x282 has been repurposed to differentiate from Prelude which also uses 0x5F4)
}
}
void Entrance_SetWarpSongEntrance ( void ) {
gPlayState - > sceneLoadFlag = 0x14 ;
gPlayState - > fadeTransition = 5 ;
switch ( gPlayState - > msgCtx . lastPlayedSong ) {
case 0 :
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x0600 ) ; // Minuet
break ;
case 1 :
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x04F6 ) ; // Bolero
break ;
case 2 :
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x0604 ) ; // Serenade
break ;
case 3 :
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x01F1 ) ; // Requiem
break ;
case 4 :
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x0568 ) ; // Nocturne
break ;
case 5 :
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x05F4 ) ; // Prelude
break ;
default :
gPlayState - > sceneLoadFlag = 0 ; // if something goes wrong, the animation plays normally
}
// If one of the warp songs happens to lead to a grotto return, then we
// have to force the grotto return afterwards
Grotto_ForceGrottoReturnOnSpecialEntrance ( ) ;
if ( gSaveContext . gameMode ! = 0 ) {
// During DHWW the cutscene must play at the destination
gSaveContext . respawnFlag = - 3 ;
} else if ( gSaveContext . respawnFlag = = - 3 ) {
// Unset Zoneout Type -3 to avoid cutscene at destination (technically it's not needed)
gSaveContext . respawnFlag = 0 ;
2022-11-14 06:13:21 -05:00
}
}
void Entrance_OverrideBlueWarp ( void ) {
2022-12-06 18:37:50 -05:00
// Set nextEntranceIndex as a flag so that Grotto_CheckSpecialEntrance
// won't return index 0x7FFF, which can't work to override blue warps.
gPlayState - > nextEntranceIndex = 0 ;
2022-11-14 06:13:21 -05:00
switch ( gPlayState - > sceneNum ) {
case SCENE_YDAN_BOSS : // Ghoma boss room
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x0457 ) ;
return ;
case SCENE_DDAN_BOSS : // King Dodongo boss room
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x047A ) ;
return ;
case SCENE_BDAN_BOSS : // Barinade boss room
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x010E ) ;
return ;
case SCENE_MORIBOSSROOM : // Phantom Ganon boss room
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x0608 ) ;
return ;
case SCENE_FIRE_BS : // Volvagia boss room
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x0564 ) ;
return ;
case SCENE_MIZUSIN_BS : // Morpha boss room
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x060C ) ;
return ;
case SCENE_JYASINBOSS : // Bongo-Bongo boss room
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x0610 ) ;
return ;
case SCENE_HAKADAN_BS : // Twinrova boss room
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( 0x0580 ) ;
return ;
}
}
void Entrance_OverrideCutsceneEntrance ( u16 cutsceneCmd ) {
switch ( cutsceneCmd ) {
case 24 : // Dropping a fish for Jabu Jabu
gPlayState - > nextEntranceIndex = Entrance_OverrideNextIndex ( newJabuJabusBellyEntrance ) ;
gPlayState - > sceneLoadFlag = 0x14 ;
gPlayState - > fadeTransition = 2 ;
2022-12-06 18:37:50 -05:00
// In case Jabu's mouth leads to a grotto return
Grotto_ForceGrottoReturnOnSpecialEntrance ( ) ;
2022-11-14 06:13:21 -05:00
break ;
}
}
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 = = 14 | | gPlayState - > sceneNum = = 15 | | ( gPlayState - > sceneNum = = 16 & & ! false /* shuffled chest mini game */ ) | |
gPlayState - > sceneNum = = 26 | | gPlayState - > sceneNum = = 62 | | gPlayState - > sceneNum = = 73 | |
gPlayState - > sceneNum = = 79 | |
gSaveContext . eventInf [ 0 ] & 0x1 | | // Ingo's Minigame state
player - > stateFlags1 & 0x08A02000 | | // Swimming, riding horse, Down A, hanging from a ledge
player - > stateFlags2 & 0x00040000 // Blank A
// Shielding, spinning and getting skull tokens still disable buttons automatically
) {
return ;
}
for ( int i = 1 ; i < 5 ; i + + ) {
if ( gSaveContext . equips . buttonItems [ i ] = = 13 ) {
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_23 ) ) {
2023-01-17 15:20:21 -05:00
// Allow Master glitch to be performed on the Thieves Hideout entrance
if ( entrance = = Entrance_GetOverride ( 0x0496 ) ) { // Gerudo Fortress -> Theives Hideout
return ;
}
2022-11-14 06:13:21 -05:00
static const s16 validEponaEntrances [ ] = {
0x0102 , // Hyrule Field -> Lake Hylia
0x0189 , // Lake Hylia -> Hyrule Field
0x0309 , // LH Fishing Hole -> LH Fishing Island
0x03CC , // LH Lab -> Lake Hylia
0x0117 , // Hyrule Field -> Gerudo Valley
0x018D , // Gerudo Valley -> Hyrule Field
0x0157 , // Hyrule Field -> Lon Lon Ranch
0x01F9 , // Lon Lon Ranch -> Hyrule Field
0x01FD , // Market Entrance -> Hyrule Field
2023-01-17 15:20:21 -05:00
0x0181 , // ZR Front -> Hyrule Field
0x0185 , // LW Bridge -> Hyrule Field
2022-11-14 06:13:21 -05:00
0x0129 , // GV Fortress Side -> Gerudo Fortress
0x022D , // Gerudo Fortress -> GV Fortress Side
0x03D0 , // GV Carpenter Tent -> GV Fortress Side
0x042F , // LLR Stables -> Lon Lon Ranch
0x05D4 , // LLR Tower -> Lon Lon Ranch
0x0378 , // LLR Talons House -> Lon Lon Ranch
0x028A , // LLR Southern Fence Jump
0x028E , // LLR Western Fence Jump
0x0292 , // LLR Eastern Fence Jump
0x0476 , // 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
0x231 ,
0x235 ,
0x239 ,
0x2BA ,
} ;
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_23 ;
player - > actor . parent = NULL ;
AREG ( 6 ) = 0 ;
gSaveContext . equips . buttonItems [ 0 ] = gSaveContext . buttonStatus [ 0 ] ; //"temp B"
}
}
// 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 = = 0x01FD ) { // Hyrule Field by Market Entrance
gWeatherMode = 1 ;
return ;
}
// Lon Lon Ranch (No Epona)
if ( ! Flags_GetEventChkInf ( 0x18 ) ) { // if you don't have Epona
switch ( gSaveContext . entranceIndex ) {
case 0x0157 : // Lon Lon Ranch from HF
case 0x01F9 : // Hyrule Field from LLR
gWeatherMode = 2 ;
return ;
}
}
// Water Temple
if ( ! ( gSaveContext . eventChkInf [ 4 ] & 0x0400 ) ) { // have not beaten Water Temple
switch ( gSaveContext . entranceIndex ) {
case 0x019D : // Zora River from behind waterfall
case 0x01DD : // Zora River from LW water shortcut
case 0x04DA : // Lost Woods water shortcut from ZR
gWeatherMode = 3 ;
return ;
}
switch ( gPlayState - > sceneNum ) {
case 88 : // Zora's Domain
case 89 : // 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 82 : // Kakariko
case 83 : // Graveyard
gPlayState - > envCtx . gloomySkyMode = 2 ;
switch ( gSaveContext . entranceIndex ) {
case 0x00DB : // Kakariko from HF
case 0x0191 : // Kakariko from Death Mountain Trail
case 0x0205 : // Graveyard from Shadow Temple
break ;
default :
gWeatherMode = 5 ;
return ;
}
}
}
// Death Mountain Cloudy
if ( ! ( gSaveContext . eventChkInf [ 4 ] & 0x0200 ) ) { // have not beaten Fire Temple
if ( gPlayState - > nextEntranceIndex = = 0x04D6 ) { // Lost Woods Goron City Shortcut
gWeatherMode = 2 ;
return ;
}
switch ( gPlayState - > sceneNum ) {
case 82 : // Kakariko
case 83 : // Graveyard
case 96 : // Death Mountain Trail
case 97 : // Death Mountain Crater
if ( ! gPlayState - > envCtx . gloomySkyMode ) {
gPlayState - > envCtx . gloomySkyMode = 1 ;
}
switch ( gSaveContext . entranceIndex ) {
case 0x00DB : // Kakariko from HF
case 0x0195 : // 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 ) {
2023-01-27 18:36:38 -05:00
gPlayState - > nextEntranceIndex = 0x1A5 ; // Geurdo Valley thrown out
2022-11-14 06:13:21 -05:00
}
if ( ( LINK_IS_CHILD | | Randomizer_GetSettingValue ( RSK_SHUFFLE_OVERWORLD_ENTRANCES ) ) & &
gPlayState - > nextEntranceIndex = = 0x1A5 ) { // Geurdo Valley thrown out
if ( gPlayState - > sceneNum ! = 0x5A ) { // Geurdo Valley
gPlayState - > nextEntranceIndex = 0x129 ; // Gerudo Fortress
}
}
}
void Entrance_OverrideSpawnScene ( s32 sceneNum , s32 spawn ) {
2023-01-20 01:00:12 -05:00
// 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 ;
2022-12-06 18:37:50 -05:00
if ( Randomizer_GetSettingValue ( RSK_SHUFFLE_DUNGEON_ENTRANCES ) = = RO_DUNGEON_ENTRANCE_SHUFFLE_ON_PLUS_GANON ) {
2022-11-14 06:13:21 -05:00
// Move Hyrule's Castle Courtyard exit spawn to be before the crates so players don't skip Talon
2023-02-12 23:34:16 -05:00
if ( sceneNum = = SCENE_SPOT15 & & spawn = = 1 ) {
2023-01-20 01:00:12 -05:00
modifiedLinkActorEntry . pos . x = 0x033A ;
modifiedLinkActorEntry . pos . y = 0x0623 ;
modifiedLinkActorEntry . pos . z = 0xFF22 ;
gPlayState - > linkActorEntry = & modifiedLinkActorEntry ;
2022-11-14 06:13:21 -05:00
}
// Move Ganon's Castle exit spawn to be on the small ledge near the castle and not over the void
2023-01-20 01:00:12 -05:00
// to prevent Link from falling if the bridge isn't spawned
2023-02-12 23:34:16 -05:00
if ( sceneNum = = SCENE_GANON_TOU & & spawn = = 1 ) {
2023-01-20 01:00:12 -05:00
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_BDAN & & 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_MIZUSIN & & spawn = = 1 ) {
modifiedLinkActorEntry . pos . x = 0xFF4C ;
modifiedLinkActorEntry . pos . y = 0x0406 ;
modifiedLinkActorEntry . pos . z = 0xF828 ;
modifiedLinkActorEntry . rot . y = 0x0 ;
gPlayState - > linkActorEntry = & modifiedLinkActorEntry ;
2022-11-14 06:13:21 -05:00
}
}
2023-02-12 23:34:16 -05:00
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_SPOT00 & & spawn = = 7 & & LINK_IS_CHILD & & IS_NIGHT ) {
modifiedLinkActorEntry . pos . x = 0x0001 ;
modifiedLinkActorEntry . pos . z = 0x049E ;
gPlayState - > linkActorEntry = & modifiedLinkActorEntry ;
}
}
2022-11-14 06:13:21 -05:00
}
2022-12-06 23:44:14 -05:00
2023-01-20 01:00:12 -05:00
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_BDAN & & 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_MIZUSIN & & spawn = = 1 ) {
return 11 ;
}
}
return roomNum ;
}
2022-12-06 23:44:14 -05:00
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 . sohStats . 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 . sohStats . scenesDiscovered [ idx ] | = sceneBit ;
}
2023-05-15 20:21:14 -04:00
// Save scenesDiscovered
Save_SaveSection ( SECTION_ID_SCENES ) ;
2022-12-06 23:44:14 -05:00
}
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 . sohStats . entrancesDiscovered [ idx ] & entranceBit ) ! = 0 ;
}
return 0 ;
}
void Entrance_SetEntranceDiscovered ( u16 entranceIndex ) {
// 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 . sohStats . entrancesDiscovered [ idx ] | = entranceBit ;
// Set connected
for ( size_t i = 0 ; i < ENTRANCE_OVERRIDES_MAX_COUNT ; i + + ) {
if ( entranceIndex = = gSaveContext . entranceOverrides [ i ] . index ) {
Entrance_SetEntranceDiscovered ( gSaveContext . entranceOverrides [ i ] . overrideDestination ) ;
break ;
}
}
}
2023-05-15 20:21:14 -04:00
// Save entrancesDiscovered
Save_SaveSection ( SECTION_ID_ENTRANCES ) ;
2022-12-06 23:44:14 -05:00
}