diff --git a/soh/include/z64save.h b/soh/include/z64save.h index 08ee53c4dab..a7e7ca652c7 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -365,25 +365,25 @@ typedef enum { * SaveContext.eventChkInf */ -#define EVENTCHKINF_02 0x02 +#define EVENTCHKINF_FIRST_SPOKE_TO_MIDO 0x02 #define EVENTCHKINF_03 0x03 #define EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD 0x04 -#define EVENTCHKINF_05 0x05 +#define EVENTCHKINF_DEKU_TREE_OPENED_MOUTH 0x05 #define EVENTCHKINF_OBTAINED_KOKIRI_EMERALD_DEKU_TREE_DEAD 0x07 #define EVENTCHKINF_09 0x09 #define EVENTCHKINF_0A 0x0A #define EVENTCHKINF_0B 0x0B -#define EVENTCHKINF_0C 0x0C +#define EVENTCHKINF_MET_DEKU_TREE 0x0C #define EVENTCHKINF_0F 0x0F -#define EVENTCHKINF_10 0x10 -#define EVENTCHKINF_11 0x11 -#define EVENTCHKINF_12 0x12 +#define EVENTCHKINF_SPOKE_TO_CHILD_MALON_AT_CASTLE_OR_MARKET 0x10 +#define EVENTCHKINF_SPOKE_TO_INGO_AT_RANCH_BEFORE_TALON_RETURNS 0x11 +#define EVENTCHKINF_OBTAINED_POCKET_EGG 0x12 #define EVENTCHKINF_TALON_WOKEN_IN_CASTLE 0x13 #define EVENTCHKINF_TALON_RETURNED_FROM_CASTLE 0x14 -#define EVENTCHKINF_15 0x15 -#define EVENTCHKINF_16 0x16 +#define EVENTCHKINF_SPOKE_TO_CHILD_MALON_AT_RANCH 0x15 +#define EVENTCHKINF_INVITED_TO_SING_WITH_CHILD_MALON 0x16 #define EVENTCHKINF_EPONA_OBTAINED 0x18 -#define EVENTCHKINF_1B 0x1B +#define EVENTCHKINF_RENTED_HORSE_FROM_INGO 0x1B #define EVENTCHKINF_SPOKE_TO_MIDO_AFTER_DEKU_TREES_DEATH 0x1C #define EVENTCHKINF_1D 0x1D #define EVENTCHKINF_1E 0x1E @@ -405,7 +405,7 @@ typedef enum { #define EVENTCHKINF_38 0x38 #define EVENTCHKINF_39 0x39 #define EVENTCHKINF_3A 0x3A -#define EVENTCHKINF_3B 0x3B +#define EVENTCHKINF_BEGAN_NABOORU_BATTLE 0x3B #define EVENTCHKINF_3C 0x3C // 0x40 @@ -417,21 +417,21 @@ typedef enum { #define EVENTCHKINF_41 0x41 #define EVENTCHKINF_42 0x42 #define EVENTCHKINF_43 0x43 -#define EVENTCHKINF_45 0x45 +#define EVENTCHKINF_PULLED_MASTER_SWORD_FROM_PEDESTAL 0x45 #define EVENTCHKINF_48 0x48 #define EVENTCHKINF_49 0x49 #define EVENTCHKINF_4A 0x4A -#define EVENTCHKINF_4B 0x4B +#define EVENTCHKINF_OPENED_THE_DOOR_OF_TIME 0x4B #define EVENTCHKINF_4C 0x4C #define EVENTCHKINF_4D 0x4D #define EVENTCHKINF_4E 0x4E -#define EVENTCHKINF_4F 0x4F +#define EVENTCHKINF_ENTERED_MASTER_SWORD_CHAMBER 0x4F #define EVENTCHKINF_50 0x50 #define EVENTCHKINF_51 0x51 #define EVENTCHKINF_52 0x52 #define EVENTCHKINF_54 0x54 #define EVENTCHKINF_55 0x55 -#define EVENTCHKINF_59 0x59 +#define EVENTCHKINF_LEARNED_ZELDAS_LULLABY 0x59 #define EVENTCHKINF_5A 0x5A #define EVENTCHKINF_5B 0x5B #define EVENTCHKINF_5C 0x5C @@ -449,21 +449,21 @@ typedef enum { #define EVENTCHKINF_6E 0x6E #define EVENTCHKINF_6F 0x6F -#define EVENTCHKINF_70 0x70 -#define EVENTCHKINF_71 0x71 -#define EVENTCHKINF_72 0x72 -#define EVENTCHKINF_73 0x73 -#define EVENTCHKINF_74 0x74 -#define EVENTCHKINF_75 0x75 -#define EVENTCHKINF_76 0x76 -#define EVENTCHKINF_77 0x77 -#define EVENTCHKINF_78 0x78 +#define EVENTCHKINF_BEGAN_GOHMA_BATTLE 0x70 +#define EVENTCHKINF_BEGAN_KING_DODONGO_BATTLE 0x71 +#define EVENTCHKINF_BEGAN_PHANTOM_GANON_BATTLE 0x72 +#define EVENTCHKINF_BEGAN_VOLVAGIA_BATTLE 0x73 +#define EVENTCHKINF_BEGAN_MORPHA_BATTLE 0x74 +#define EVENTCHKINF_BEGAN_TWINROVA_BATTLE 0x75 +#define EVENTCHKINF_BEGAN_BARINA_BATTLE 0x76 +#define EVENTCHKINF_BEGAN_BONGO_BONGO_BATTLE 0x77 +#define EVENTCHKINF_BEGAN_GANONDORF_BATTLE 0x78 #define EVENTCHKINF_80 0x80 #define EVENTCHKINF_82 0x82 #define EVENTCHKINF_8C 0x8C #define EVENTCHKINF_8D 0x8D #define EVENTCHKINF_8E 0x8E -#define EVENTCHKINF_8F 0x8F +#define EVENTCHKINF_PAID_BACK_BUNNY_HOOD_FEE 0x8F // 0x90-0x93 // carpenters freed from the gerudo @@ -479,45 +479,45 @@ typedef enum { #define GET_EVENTCHKINF_CARPENTERS_FREE_ALL() \ CHECK_FLAG_ALL(gSaveContext.eventChkInf[EVENTCHKINF_CARPENTERS_FREE_INDEX], EVENTCHKINF_CARPENTERS_FREE_MASK_ALL) -#define EVENTCHKINF_94 0x94 +#define EVENTCHKINF_SPOKE_TO_NABOORU_IN_SPIRIT_TEMPLE 0x94 #define EVENTCHKINF_95 0x95 #define EVENTCHKINF_96 0x96 #define EVENTCHKINF_9C 0x9C -#define EVENTCHKINF_A0 0xA0 -#define EVENTCHKINF_A1 0xA1 -#define EVENTCHKINF_A3 0xA3 -#define EVENTCHKINF_A4 0xA4 -#define EVENTCHKINF_A5 0xA5 -#define EVENTCHKINF_A6 0xA6 -#define EVENTCHKINF_A7 0xA7 -#define EVENTCHKINF_A8 0xA8 +#define EVENTCHKINF_ENTERED_HYRULE_FIELD 0xA0 +#define EVENTCHKINF_ENTERED_DEATH_MOUNTAIN_TRAIL 0xA1 +#define EVENTCHKINF_ENTERED_KAKARIKO_VILLAGE 0xA3 +#define EVENTCHKINF_ENTERED_ZORAS_DOMAIN 0xA4 +#define EVENTCHKINF_ENTERED_HYRULE_CASTLE 0xA5 +#define EVENTCHKINF_ENTERED_GORON_CITY 0xA6 +#define EVENTCHKINF_ENTERED_TEMPLE_OF_TIME 0xA7 +#define EVENTCHKINF_ENTERED_DEKU_TREE 0xA8 #define EVENTCHKINF_A9 0xA9 #define EVENTCHKINF_AA 0xAA #define EVENTCHKINF_AC 0xAC #define EVENTCHKINF_AD 0xAD -#define EVENTCHKINF_B0 0xB0 -#define EVENTCHKINF_B1 0xB1 -#define EVENTCHKINF_B2 0xB2 -#define EVENTCHKINF_B3 0xB3 -#define EVENTCHKINF_B4 0xB4 -#define EVENTCHKINF_B5 0xB5 -#define EVENTCHKINF_B6 0xB6 -#define EVENTCHKINF_B7 0xB7 -#define EVENTCHKINF_B8 0xB8 -#define EVENTCHKINF_B9 0xB9 -#define EVENTCHKINF_BA 0xBA +#define EVENTCHKINF_ENTERED_DODONGOS_CAVERN 0xB0 +#define EVENTCHKINF_ENTERED_LAKE_HYLIA 0xB1 +#define EVENTCHKINF_ENTERED_GERUDO_VALLEY 0xB2 +#define EVENTCHKINF_ENTERED_GERUDOS_FORTRESS 0xB3 +#define EVENTCHKINF_ENTERED_LON_LON_RANCH 0xB4 +#define EVENTCHKINF_ENTERED_JABU_JABUS_BELLY 0xB5 +#define EVENTCHKINF_ENTERED_GRAVEYARD 0xB6 +#define EVENTCHKINF_ENTERED_ZORAS_FOUNTAIN 0xB7 +#define EVENTCHKINF_ENTERED_DESERT_COLOSSUS 0xB8 +#define EVENTCHKINF_ENTERED_DEATH_MOUNTAIN_CRATER 0xB9 +#define EVENTCHKINF_ENTERED_GANONS_CASTLE_EXTERIOR 0xBA #define EVENTCHKINF_BB 0xBB #define EVENTCHKINF_BC 0xBC #define EVENTCHKINF_BD 0xBD #define EVENTCHKINF_BE 0xBE #define EVENTCHKINF_BF 0xBF -#define EVENTCHKINF_C0 0xC0 +#define EVENTCHKINF_NABOORU_ORDERED_TO_FIGHT_BY_TWINROVA 0xC0 #define EVENTCHKINF_C1 0xC1 #define EVENTCHKINF_C3 0xC3 -#define EVENTCHKINF_C4 0xC4 -#define EVENTCHKINF_C5 0xC5 +#define EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS 0xC4 +#define EVENTCHKINF_SHEIK_SPAWNED_AT_MASTER_SWORD_PEDESTAL 0xC5 #define EVENTCHKINF_C6 0xC6 -#define EVENTCHKINF_C7 0xC7 +#define EVENTCHKINF_WATCHED_GANONS_CASTLE_COLLAPSE_CAUGHT_BY_GERUDO 0xC7 #define EVENTCHKINF_C8 0xC8 #define EVENTCHKINF_C9 0xC9 @@ -617,7 +617,7 @@ typedef enum { * SaveContext.infTable */ -#define INFTABLE_00 0x00 +#define INFTABLE_GREETED_BY_SARIA 0x00 #define INFTABLE_01 0x01 #define INFTABLE_03 0x03 #define INFTABLE_05 0x05 @@ -646,24 +646,24 @@ typedef enum { #define INFTABLE_6A 0x6A #define INFTABLE_6C 0x6C #define INFTABLE_71 0x71 -#define INFTABLE_76 0x76 -#define INFTABLE_77 0x77 +#define INFTABLE_SHOWED_ZELDAS_LETTER_TO_GATE_GUARD 0x76 +#define INFTABLE_GATE_GUARD_PUT_ON_KEATON_MASK 0x77 #define INFTABLE_TALKED_TO_TALON_IN_RANCH_HOUSE 0x7E -#define INFTABLE_84 0x84 -#define INFTABLE_85 0x85 -#define INFTABLE_8B 0x8B +#define INFTABLE_MET_CHILD_MALON_AT_CASTLE_OR_MARKET 0x84 +#define INFTABLE_CHILD_MALON_SAID_EPONA_WAS_AFRAID_OF_YOU 0x85 +#define INFTABLE_ENTERED_HYRULE_CASTLE 0x8B #define INFTABLE_8C 0x8C #define INFTABLE_8D 0x8D #define INFTABLE_8E 0x8E #define INFTABLE_94 0x94 #define INFTABLE_97 0x97 -#define INFTABLE_9A 0x9A +#define INFTABLE_SPOKE_TO_INGO_ONCE_AS_ADULT 0x9A #define INFTABLE_A2 0xA2 #define INFTABLE_AB 0xAB #define INFTABLE_B0 0xB0 #define INFTABLE_B1 0xB1 #define INFTABLE_B4 0xB4 -#define INFTABLE_B6 0xB6 +#define INFTABLE_SPOKE_TO_POE_COLLECTOR_IN_RUINED_MARKET 0xB6 #define INFTABLE_B7 0xB7 #define INFTABLE_B8 0xB8 #define INFTABLE_B9 0xB9 @@ -705,7 +705,7 @@ typedef enum { #define INFTABLE_SPOKE_TO_GORON_LINK 0x10E #define INFTABLE_10F 0x10F #define INFTABLE_113 0x113 -#define INFTABLE_11A 0x11A +#define INFTABLE_SPOKE_TO_DARUNIA_IN_FIRE_TEMPLE 0x11A #define INFTABLE_11E 0x11E #define INFTABLE_124 0x124 #define INFTABLE_129 0x129 @@ -713,10 +713,10 @@ typedef enum { #define INFTABLE_138 0x138 #define INFTABLE_139 0x139 #define INFTABLE_140 0x140 -#define INFTABLE_141 0x141 -#define INFTABLE_142 0x142 +#define INFTABLE_RUTO_IN_JJ_MEET_RUTO 0x141 +#define INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME 0x142 #define INFTABLE_143 0x143 -#define INFTABLE_144 0x144 +#define INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE 0x144 #define INFTABLE_145 0x145 #define INFTABLE_146 0x146 #define INFTABLE_147 0x147 @@ -739,7 +739,7 @@ typedef enum { #define INFTABLE_191 0x191 #define INFTABLE_192 0x192 #define INFTABLE_193 0x193 -#define INFTABLE_195 0x195 +#define INFTABLE_SPOKE_TO_KAEPORA_IN_LAKE_HYLIA 0x195 #define INFTABLE_196 0x196 #define INFTABLE_197 0x197 #define INFTABLE_198 0x198 diff --git a/soh/soh/Enhancements/randomizer/savefile.cpp b/soh/soh/Enhancements/randomizer/savefile.cpp new file mode 100644 index 00000000000..4b546e1f013 --- /dev/null +++ b/soh/soh/Enhancements/randomizer/savefile.cpp @@ -0,0 +1,441 @@ +#include "savefile.h" +#include "soh/OTRGlobals.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" + +extern "C" { +#include +#include "variables.h" +#include "functions.h" +#include "macros.h" + +uint32_t ResourceMgr_IsSceneMasterQuest(s16 sceneNum); +uint8_t Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey); +GetItemEntry Randomizer_GetItemFromKnownCheck(RandomizerCheck randomizerCheck, GetItemID ogId); +} + +void StartingItemGive(GetItemEntry getItemEntry) { + if (getItemEntry.modIndex == MOD_NONE) { + if (getItemEntry.getItemId == GI_SWORD_BGS) { + gSaveContext.bgsFlag = true; + } + Item_Give(NULL, getItemEntry.itemId); + } else if (getItemEntry.modIndex == MOD_RANDOMIZER) { + if (getItemEntry.getItemId == RG_ICE_TRAP) { + gSaveContext.pendingIceTrapCount++; + GameInteractor::Instance->ExecuteHooks(getItemEntry); + } else { + Randomizer_Item_Give(NULL, getItemEntry); + } + } +} + +// RANDOTODO replace most of these GiveLink functions with calls to +// Item_Give in z_parameter, we'll need to update Item_Give to ensure +// nothing breaks when calling it without a valid play first +void GiveLinkRupees(int numOfRupees) { + int maxRupeeCount; + if (CUR_UPG_VALUE(UPG_WALLET) == 0) { + maxRupeeCount = 99; + } else if (CUR_UPG_VALUE(UPG_WALLET) == 1) { + maxRupeeCount = 200; + } else if (CUR_UPG_VALUE(UPG_WALLET) == 2) { + maxRupeeCount = 500; + } + + int newRupeeCount = gSaveContext.rupees; + newRupeeCount += numOfRupees; + + if (newRupeeCount > maxRupeeCount) { + gSaveContext.rupees = maxRupeeCount; + } else { + gSaveContext.rupees = newRupeeCount; + } +} + +void GiveLinkDekuSticks(int howManySticks) { + int maxStickCount; + if (CUR_UPG_VALUE(UPG_STICKS) == 0) { + INV_CONTENT(ITEM_STICK) = ITEM_STICK; + Inventory_ChangeUpgrade(UPG_STICKS, 1); + maxStickCount = 10; + } else if (CUR_UPG_VALUE(UPG_STICKS) == 1) { + maxStickCount = 10; + } else if (CUR_UPG_VALUE(UPG_STICKS) == 2) { + maxStickCount = 20; + } else if (CUR_UPG_VALUE(UPG_STICKS) == 3) { + maxStickCount = 30; + } + + if ((AMMO(ITEM_STICK) + howManySticks) > maxStickCount) { + AMMO(ITEM_STICK) = maxStickCount; + } else { + AMMO(ITEM_STICK) += howManySticks; + } +} + +void GiveLinkDekuNuts(int howManyNuts) { + int maxNutCount; + if (CUR_UPG_VALUE(UPG_NUTS) == 0) { + INV_CONTENT(ITEM_NUT) = ITEM_NUT; + Inventory_ChangeUpgrade(UPG_NUTS, 1); + maxNutCount = 20; + } else if (CUR_UPG_VALUE(UPG_NUTS) == 1) { + maxNutCount = 20; + } else if (CUR_UPG_VALUE(UPG_NUTS) == 2) { + maxNutCount = 30; + } else if (CUR_UPG_VALUE(UPG_NUTS) == 3) { + maxNutCount = 40; + } + + if ((AMMO(ITEM_NUT) + howManyNuts) > maxNutCount) { + AMMO(ITEM_NUT) = maxNutCount; + } else { + AMMO(ITEM_NUT) += howManyNuts; + } +} + +void GiveLinksPocketItem() { + if (Randomizer_GetSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING) { + GetItemEntry getItemEntry = Randomizer_GetItemFromKnownCheck(RC_LINKS_POCKET, (GetItemID)RG_NONE); + StartingItemGive(getItemEntry); + } +} + +void SetStartingItems() { + if (Randomizer_GetSettingValue(RSK_STARTING_KOKIRI_SWORD)) + Item_Give(NULL, ITEM_SWORD_KOKIRI); + if (Randomizer_GetSettingValue(RSK_STARTING_DEKU_SHIELD)) + Item_Give(NULL, ITEM_SHIELD_DEKU); + + // Songs + if (Randomizer_GetSettingValue(RSK_STARTING_ZELDAS_LULLABY)) + Item_Give(NULL, ITEM_SONG_LULLABY); + if (Randomizer_GetSettingValue(RSK_STARTING_EPONAS_SONG)) + Item_Give(NULL, ITEM_SONG_EPONA); + if (Randomizer_GetSettingValue(RSK_STARTING_SARIAS_SONG)) + Item_Give(NULL, ITEM_SONG_SARIA); + if (Randomizer_GetSettingValue(RSK_STARTING_SUNS_SONG)) + Item_Give(NULL, ITEM_SONG_SUN); + if (Randomizer_GetSettingValue(RSK_STARTING_SONG_OF_TIME)) + Item_Give(NULL, ITEM_SONG_TIME); + if (Randomizer_GetSettingValue(RSK_STARTING_SONG_OF_STORMS)) + Item_Give(NULL, ITEM_SONG_STORMS); + if (Randomizer_GetSettingValue(RSK_STARTING_MINUET_OF_FOREST)) + Item_Give(NULL, ITEM_SONG_MINUET); + if (Randomizer_GetSettingValue(RSK_STARTING_BOLERO_OF_FIRE)) + Item_Give(NULL, ITEM_SONG_BOLERO); + if (Randomizer_GetSettingValue(RSK_STARTING_SERENADE_OF_WATER)) + Item_Give(NULL, ITEM_SONG_SERENADE); + if (Randomizer_GetSettingValue(RSK_STARTING_REQUIEM_OF_SPIRIT)) + Item_Give(NULL, ITEM_SONG_REQUIEM); + if (Randomizer_GetSettingValue(RSK_STARTING_NOCTURNE_OF_SHADOW)) + Item_Give(NULL, ITEM_SONG_NOCTURNE); + if (Randomizer_GetSettingValue(RSK_STARTING_PRELUDE_OF_LIGHT)) + Item_Give(NULL, ITEM_SONG_PRELUDE); + + if (Randomizer_GetSettingValue(RSK_STARTING_SKULLTULA_TOKEN)) { + gSaveContext.inventory.questItems |= gBitFlags[QUEST_SKULL_TOKEN]; + gSaveContext.inventory.gsTokens = Randomizer_GetSettingValue(RSK_STARTING_SKULLTULA_TOKEN); + } + + if (Randomizer_GetSettingValue(RSK_STARTING_OCARINA)) { + INV_CONTENT(ITEM_OCARINA_FAIRY) = ITEM_OCARINA_FAIRY; + } + + if (Randomizer_GetSettingValue(RSK_STARTING_CONSUMABLES)) { + GiveLinkDekuSticks(10); + GiveLinkDekuNuts(20); + } + + if (Randomizer_GetSettingValue(RSK_FULL_WALLETS)) { + GiveLinkRupees(9001); + } + + if (Randomizer_GetSettingValue(RSK_STARTING_MAPS_COMPASSES) == RO_DUNGEON_ITEM_LOC_STARTWITH) { + uint32_t mapBitMask = 1 << 1; + uint32_t compassBitMask = 1 << 2; + uint32_t startingDungeonItemsBitMask = mapBitMask | compassBitMask; + for (int scene = SCENE_YDAN; scene <= SCENE_ICE_DOUKUTO; scene++) { + gSaveContext.inventory.dungeonItems[scene] |= startingDungeonItemsBitMask; + } + } + + if (Randomizer_GetSettingValue(RSK_KEYSANITY) == RO_DUNGEON_ITEM_LOC_STARTWITH) { + gSaveContext.inventory.dungeonKeys[SCENE_BMORI1] = FOREST_TEMPLE_SMALL_KEY_MAX; // Forest + gSaveContext.sohStats.dungeonKeys[SCENE_BMORI1] = FOREST_TEMPLE_SMALL_KEY_MAX; // Forest + gSaveContext.inventory.dungeonKeys[SCENE_HIDAN] = FIRE_TEMPLE_SMALL_KEY_MAX; // Fire + gSaveContext.sohStats.dungeonKeys[SCENE_HIDAN] = FIRE_TEMPLE_SMALL_KEY_MAX; // Fire + gSaveContext.inventory.dungeonKeys[SCENE_MIZUSIN] = WATER_TEMPLE_SMALL_KEY_MAX; // Water + gSaveContext.sohStats.dungeonKeys[SCENE_MIZUSIN] = WATER_TEMPLE_SMALL_KEY_MAX; // Water + gSaveContext.inventory.dungeonKeys[SCENE_JYASINZOU] = SPIRIT_TEMPLE_SMALL_KEY_MAX; // Spirit + gSaveContext.sohStats.dungeonKeys[SCENE_JYASINZOU] = SPIRIT_TEMPLE_SMALL_KEY_MAX; // Spirit + gSaveContext.inventory.dungeonKeys[SCENE_HAKADAN] = SHADOW_TEMPLE_SMALL_KEY_MAX; // Shadow + gSaveContext.sohStats.dungeonKeys[SCENE_HAKADAN] = SHADOW_TEMPLE_SMALL_KEY_MAX; // Shadow + gSaveContext.inventory.dungeonKeys[SCENE_HAKADANCH] = BOTTOM_OF_THE_WELL_SMALL_KEY_MAX; // BotW + gSaveContext.sohStats.dungeonKeys[SCENE_HAKADANCH] = BOTTOM_OF_THE_WELL_SMALL_KEY_MAX; // BotW + gSaveContext.inventory.dungeonKeys[SCENE_MEN] = GERUDO_TRAINING_GROUNDS_SMALL_KEY_MAX; // GTG + gSaveContext.sohStats.dungeonKeys[SCENE_MEN] = GERUDO_TRAINING_GROUNDS_SMALL_KEY_MAX; // GTG + gSaveContext.inventory.dungeonKeys[SCENE_GANONTIKA] = GANONS_CASTLE_SMALL_KEY_MAX; // Ganon + gSaveContext.sohStats.dungeonKeys[SCENE_GANONTIKA] = GANONS_CASTLE_SMALL_KEY_MAX; // Ganon + } else if (Randomizer_GetSettingValue(RSK_KEYSANITY) == RO_DUNGEON_ITEM_LOC_VANILLA) { + // Logic cannot handle vanilla key layout in some dungeons + // this is because vanilla expects the dungeon major item to be + // locked behind the keys, which is not always true in rando. + // We can resolve this by starting with some extra keys + if (ResourceMgr_IsSceneMasterQuest(SCENE_JYASINZOU)) { + // MQ Spirit needs 3 keys + gSaveContext.inventory.dungeonKeys[SCENE_JYASINZOU] = 3; + gSaveContext.sohStats.dungeonKeys[SCENE_JYASINZOU] = 3; + } + } + + if (Randomizer_GetSettingValue(RSK_BOSS_KEYSANITY) == RO_DUNGEON_ITEM_LOC_STARTWITH) { + gSaveContext.inventory.dungeonItems[SCENE_BMORI1] |= 1; // Forest + gSaveContext.inventory.dungeonItems[SCENE_HIDAN] |= 1; // Fire + gSaveContext.inventory.dungeonItems[SCENE_MIZUSIN] |= 1; // Water + gSaveContext.inventory.dungeonItems[SCENE_JYASINZOU] |= 1; // Spirit + gSaveContext.inventory.dungeonItems[SCENE_HAKADAN] |= 1; // Shadow + } + + if (Randomizer_GetSettingValue(RSK_GANONS_BOSS_KEY) == RO_GANON_BOSS_KEY_STARTWITH) { + gSaveContext.inventory.dungeonItems[SCENE_GANON] |= 1; + } +} + +extern "C" void Randomizer_InitSaveFile() { + // Sets all rando flags to false + for (s32 i = 0; i < ARRAY_COUNT(gSaveContext.randomizerInf); i++) { + gSaveContext.randomizerInf[i] = 0; + } + + gSaveContext.cutsceneIndex = 0; // no intro cutscene + // Starts pending ice traps out at 0 before potentially incrementing them down the line. + gSaveContext.pendingIceTrapCount = 0; + + // Set Cutscene flags and texts to skip them + Flags_SetInfTable(INFTABLE_GREETED_BY_SARIA); + Flags_SetEventChkInf(EVENTCHKINF_FIRST_SPOKE_TO_MIDO); + Flags_SetEventChkInf(EVENTCHKINF_MET_DEKU_TREE); + Flags_SetEventChkInf(EVENTCHKINF_DEKU_TREE_OPENED_MOUTH); + Flags_SetInfTable(INFTABLE_SPOKE_TO_KAEPORA_IN_LAKE_HYLIA); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_MASTER_SWORD_CHAMBER); + Flags_SetEventChkInf(EVENTCHKINF_PULLED_MASTER_SWORD_FROM_PEDESTAL); + Flags_SetEventChkInf(EVENTCHKINF_SHEIK_SPAWNED_AT_MASTER_SWORD_PEDESTAL); + Flags_SetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS); + Flags_SetEventChkInf(EVENTCHKINF_RENTED_HORSE_FROM_INGO); + Flags_SetInfTable(INFTABLE_SPOKE_TO_POE_COLLECTOR_IN_RUINED_MARKET); + Flags_SetEventChkInf(EVENTCHKINF_WATCHED_GANONS_CASTLE_COLLAPSE_CAUGHT_BY_GERUDO); + Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_NABOORU_IN_SPIRIT_TEMPLE); + + Flags_SetInfTable(INFTABLE_MET_CHILD_MALON_AT_CASTLE_OR_MARKET); + Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_CHILD_MALON_AT_CASTLE_OR_MARKET); + Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_INGO_AT_RANCH_BEFORE_TALON_RETURNS); + Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_CHILD_MALON_AT_RANCH); + Flags_SetEventChkInf(EVENTCHKINF_INVITED_TO_SING_WITH_CHILD_MALON); + Flags_SetInfTable(INFTABLE_CHILD_MALON_SAID_EPONA_WAS_AFRAID_OF_YOU); + Flags_SetInfTable(INFTABLE_SPOKE_TO_INGO_ONCE_AS_ADULT); + + // Ruto already met in jabu and spawns down the hole immediately + Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO); + Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME); + Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE); + + // Skip cutscenes before Nabooru fight + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_NABOORU_BATTLE); + Flags_SetEventChkInf(EVENTCHKINF_NABOORU_ORDERED_TO_FIGHT_BY_TWINROVA); + + // Skip boss cutscenes + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_GOHMA_BATTLE); + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_KING_DODONGO_BATTLE); + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_PHANTOM_GANON_BATTLE); + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_VOLVAGIA_BATTLE); + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_MORPHA_BATTLE); + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_TWINROVA_BATTLE); + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_BARINA_BATTLE); + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_BONGO_BONGO_BATTLE); + + // Entered areas + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_HYRULE_FIELD); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_DEATH_MOUNTAIN_TRAIL); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_KAKARIKO_VILLAGE); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_ZORAS_DOMAIN); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_HYRULE_CASTLE); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_GORON_CITY); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_TEMPLE_OF_TIME); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_DEKU_TREE); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_DODONGOS_CAVERN); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_LAKE_HYLIA); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_GERUDO_VALLEY); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_GERUDOS_FORTRESS); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_LON_LON_RANCH); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_JABU_JABUS_BELLY); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_GRAVEYARD); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_ZORAS_FOUNTAIN); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_DESERT_COLOSSUS); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_DEATH_MOUNTAIN_CRATER); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_GANONS_CASTLE_EXTERIOR); + Flags_SetInfTable(INFTABLE_ENTERED_HYRULE_CASTLE); + + // skip the z target talk instructions by the kokiri shop + gSaveContext.sceneFlags[SCENE_SPOT04].swch |= (1 << 0x1F); + + // Go away ruto (water temple first cutscene) + gSaveContext.sceneFlags[SCENE_MIZUSIN].swch |= (1 << 0x10); + + // no more kaepora + gSaveContext.sceneFlags[SCENE_SPOT00].swch |= (1 << 0xC); // hyrule field kaepora outside kokiri forest + gSaveContext.sceneFlags[SCENE_SPOT00].swch |= (1 << 0xB); // hyrule field kaepora outside lake hylia + gSaveContext.sceneFlags[SCENE_SPOT10].swch |= (1 << 0x7); // lost woods kaepora pre-saria + gSaveContext.sceneFlags[SCENE_SPOT10].swch |= (1 << 0x8); // lost woods kaepora post-saria + gSaveContext.sceneFlags[SCENE_SPOT11].swch |= (1 << 0x1F); // desert colossus kaepora + gSaveContext.sceneFlags[SCENE_SPOT15].swch |= (1 << 0x5); // hyrule castle kaepora + + if (!Randomizer_GetSettingValue(RSK_ENABLE_GLITCH_CUTSCENES)) { + Flags_SetInfTable(INFTABLE_SPOKE_TO_DARUNIA_IN_FIRE_TEMPLE); // Darunia in Fire Temple + } + + // Give Link's pocket item + GiveLinksPocketItem(); + + // shuffle adult trade quest + if (Randomizer_GetSettingValue(RSK_SHUFFLE_ADULT_TRADE)) { + gSaveContext.adultTradeItems = 0; + } + + int startingAge = Randomizer_GetSettingValue(RSK_STARTING_AGE); + switch (startingAge) { + case RO_AGE_ADULT: // Adult + gSaveContext.linkAge = LINK_AGE_ADULT; + gSaveContext.entranceIndex = 0x5F4; + gSaveContext.savedSceneNum = SCENE_SPOT20; // Set scene num manually to ToT + break; + case RO_AGE_CHILD: // Child + gSaveContext.linkAge = LINK_AGE_CHILD; + gSaveContext.savedSceneNum = -1; + break; + default: + break; + } + + if (Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_SPAWNS)) { + // Override the spawn entrance so entrance rando can take control, + // and to prevent remember save location from breaking inital spawn + gSaveContext.entranceIndex = -1; + } + + // If any trials aren't required, set them as completed + for (u16 i = RAND_INF_TRIALS_DONE_LIGHT_TRIAL; i <= RAND_INF_TRIALS_DONE_SHADOW_TRIAL; i++) { + if (!OTRGlobals::Instance->gRandomizer->IsTrialRequired((RandomizerInf)i)) { + Flags_SetRandomizerInf((RandomizerInf)i); + } + } + + if (Randomizer_GetSettingValue(RSK_SKIP_CHILD_ZELDA)) { + GetItemEntry getItemEntry = Randomizer_GetItemFromKnownCheck(RC_SONG_FROM_IMPA, (GetItemID)RG_ZELDAS_LULLABY); + StartingItemGive(getItemEntry); + + // malon/talon back at ranch + Flags_SetEventChkInf(EVENTCHKINF_OBTAINED_POCKET_EGG); + Flags_SetEventChkInf(EVENTCHKINF_TALON_WOKEN_IN_CASTLE); + Flags_SetEventChkInf(EVENTCHKINF_TALON_RETURNED_FROM_CASTLE); + + // Set "Got Zelda's Letter" flag. Also ensures Saria is back at SFM. + Flags_SetEventChkInf(EVENTCHKINF_OBTAINED_ZELDAS_LETTER); + + // Got item from impa + Flags_SetEventChkInf(EVENTCHKINF_LEARNED_ZELDAS_LULLABY); + + gSaveContext.sceneFlags[SCENE_SPOT15].swch |= (1 << 0x4); // move milk crates in hyrule castle to moat + + // set this at the end to ensure we always start with the letter + // this is for the off chance we got the weird egg from impa (which should never happen) + INV_CONTENT(ITEM_LETTER_ZELDA) = ITEM_LETTER_ZELDA; + } + + HIGH_SCORE(HS_POE_POINTS) = 1000 - (100 * Randomizer_GetSettingValue(RSK_BIG_POE_COUNT)); + + if (Randomizer_GetSettingValue(RSK_SKIP_EPONA_RACE)) { + Flags_SetEventChkInf(EVENTCHKINF_EPONA_OBTAINED); + } + + // Open lowest Vanilla Fire Temple locked door (to prevent key logic lockouts) + // Not done on keysanity since this lockout is a non issue when Fire keys can be found outside the temple + u8 keysanity = Randomizer_GetSettingValue(RSK_KEYSANITY) == RO_DUNGEON_ITEM_LOC_ANYWHERE || + Randomizer_GetSettingValue(RSK_KEYSANITY) == RO_DUNGEON_ITEM_LOC_OVERWORLD || + Randomizer_GetSettingValue(RSK_KEYSANITY) == RO_DUNGEON_ITEM_LOC_ANY_DUNGEON; + if (!ResourceMgr_IsSceneMasterQuest(SCENE_HIDAN) && !keysanity) { + gSaveContext.sceneFlags[SCENE_HIDAN].swch |= (1 << 0x17); + } + + // Opens locked Water Temple door in vanilla to prevent softlocks + // West door on the middle level that leads to the water raising thing + // Happens in 3DS rando and N64 rando as well + if (!ResourceMgr_IsSceneMasterQuest(SCENE_MIZUSIN)) { + gSaveContext.sceneFlags[SCENE_MIZUSIN].swch |= (1 << 0x15); + } + + int openForest = Randomizer_GetSettingValue(RSK_FOREST); + switch (openForest) { + case RO_FOREST_OPEN: + Flags_SetEventChkInf(EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD); + // Fallthrough + case RO_FOREST_CLOSED_DEKU: + Flags_SetEventChkInf(EVENTCHKINF_OBTAINED_KOKIRI_EMERALD_DEKU_TREE_DEAD); + break; + } + + int doorOfTime = Randomizer_GetSettingValue(RSK_DOOR_OF_TIME); + switch (doorOfTime) { + case RO_DOOROFTIME_OPEN: + Flags_SetEventChkInf(EVENTCHKINF_OPENED_THE_DOOR_OF_TIME); + break; + } + + if (Randomizer_GetSettingValue(RSK_KAK_GATE) == RO_KAK_GATE_OPEN) { + Flags_SetInfTable(INFTABLE_SHOWED_ZELDAS_LETTER_TO_GATE_GUARD); + } + + if (Randomizer_GetSettingValue(RSK_GERUDO_FORTRESS) == RO_GF_FAST || + Randomizer_GetSettingValue(RSK_GERUDO_FORTRESS) == RO_GF_OPEN) { + Flags_SetEventChkInf(EVENTCHKINF_CARPENTERS_FREE(1)); + Flags_SetEventChkInf(EVENTCHKINF_CARPENTERS_FREE(2)); + Flags_SetEventChkInf(EVENTCHKINF_CARPENTERS_FREE(3)); + gSaveContext.sceneFlags[SCENE_GERUDOWAY].swch |= (1 << 0x02); // heard yells and unlocked doors + gSaveContext.sceneFlags[SCENE_GERUDOWAY].swch |= (1 << 0x03); + gSaveContext.sceneFlags[SCENE_GERUDOWAY].swch |= (1 << 0x04); + gSaveContext.sceneFlags[SCENE_GERUDOWAY].swch |= (1 << 0x06); + gSaveContext.sceneFlags[SCENE_GERUDOWAY].swch |= (1 << 0x07); + gSaveContext.sceneFlags[SCENE_GERUDOWAY].swch |= (1 << 0x08); + gSaveContext.sceneFlags[SCENE_GERUDOWAY].swch |= (1 << 0x10); + gSaveContext.sceneFlags[SCENE_GERUDOWAY].swch |= (1 << 0x12); + gSaveContext.sceneFlags[SCENE_GERUDOWAY].swch |= (1 << 0x13); + gSaveContext.sceneFlags[SCENE_GERUDOWAY].collect |= (1 << 0x0A); // picked up keys + gSaveContext.sceneFlags[SCENE_GERUDOWAY].collect |= (1 << 0x0E); + gSaveContext.sceneFlags[SCENE_GERUDOWAY].collect |= (1 << 0x0F); + } + + if (Randomizer_GetSettingValue(RSK_GERUDO_FORTRESS) == RO_GF_OPEN) { + Flags_SetEventChkInf(EVENTCHKINF_CARPENTERS_FREE(0)); + gSaveContext.sceneFlags[SCENE_GERUDOWAY].swch |= (1 << 0x01); // heard yell and unlocked door + gSaveContext.sceneFlags[SCENE_GERUDOWAY].swch |= (1 << 0x05); + gSaveContext.sceneFlags[SCENE_GERUDOWAY].swch |= (1 << 0x11); + gSaveContext.sceneFlags[SCENE_GERUDOWAY].collect |= (1 << 0x0C); // picked up key + + if (!Randomizer_GetSettingValue(RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD)) { + Item_Give(NULL, ITEM_GERUDO_CARD); + } + } + + // complete mask quest + if (Randomizer_GetSettingValue(RSK_COMPLETE_MASK_QUEST)) { + Flags_SetInfTable(INFTABLE_GATE_GUARD_PUT_ON_KEATON_MASK); + Flags_SetEventChkInf(EVENTCHKINF_PAID_BACK_BUNNY_HOOD_FEE); + gSaveContext.itemGetInf[3] |= 0x100; // Sold Keaton Mask + gSaveContext.itemGetInf[3] |= 0x200; // Sold Skull Mask + gSaveContext.itemGetInf[3] |= 0x400; // Sold Spooky Mask + gSaveContext.itemGetInf[3] |= 0x800; // bunny hood related + gSaveContext.itemGetInf[3] |= 0x8000; // Obtained Mask of Truth + } + + SetStartingItems(); +} diff --git a/soh/soh/Enhancements/randomizer/savefile.h b/soh/soh/Enhancements/randomizer/savefile.h new file mode 100644 index 00000000000..520db19825d --- /dev/null +++ b/soh/soh/Enhancements/randomizer/savefile.h @@ -0,0 +1,14 @@ +#ifndef RANDOSAVEFILE_H +#define RANDOSAVEFILE_H + +#ifdef __cplusplus +extern "C" { +#endif + +void Randomizer_InitSaveFile(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/soh/src/code/z_sram.c b/soh/src/code/z_sram.c index 3c8b0e74c97..beb3ce147b3 100644 --- a/soh/src/code/z_sram.c +++ b/soh/src/code/z_sram.c @@ -2,10 +2,8 @@ #include "vt.h" #include -#include -#include #include "soh/Enhancements/randomizer/randomizer_entrance.h" -#include "soh/Enhancements/randomizer/adult_trade_shuffle.h" +#include "soh/Enhancements/randomizer/savefile.h" #define NUM_DUNGEONS 8 #define NUM_COWS 10 @@ -31,91 +29,6 @@ void Sram_InitDebugSave(void) { Save_InitFile(true); } -// RANDOTODO replace most of these GiveLink functions with calls to -// Item_Give in z_parameter, we'll need to update Item_Give to ensure -// nothing breaks when calling it without a valid play first -void GiveLinkRupees(int numOfRupees) { - int maxRupeeCount; - if (CUR_UPG_VALUE(UPG_WALLET) == 0) { - maxRupeeCount = 99; - } else if (CUR_UPG_VALUE(UPG_WALLET) == 1) { - maxRupeeCount = 200; - } else if (CUR_UPG_VALUE(UPG_WALLET) == 2) { - maxRupeeCount = 500; - } - - int newRupeeCount = gSaveContext.rupees; - newRupeeCount += numOfRupees; - - if (newRupeeCount > maxRupeeCount) { - gSaveContext.rupees = maxRupeeCount; - } else { - gSaveContext.rupees = newRupeeCount; - } -} - -void GiveLinkDekuSticks(int howManySticks) { - int maxStickCount; - if (CUR_UPG_VALUE(UPG_STICKS) == 0) { - INV_CONTENT(ITEM_STICK) = ITEM_STICK; - Inventory_ChangeUpgrade(UPG_STICKS, 1); - maxStickCount = 10; - } else if (CUR_UPG_VALUE(UPG_STICKS) == 1) { - maxStickCount = 10; - } else if (CUR_UPG_VALUE(UPG_STICKS) == 2) { - maxStickCount = 20; - } else if (CUR_UPG_VALUE(UPG_STICKS) == 3) { - maxStickCount = 30; - } - - if ((AMMO(ITEM_STICK) + howManySticks) > maxStickCount) { - AMMO(ITEM_STICK) = maxStickCount; - } else { - AMMO(ITEM_STICK) += howManySticks; - } -} - -void GiveLinkDekuNuts(int howManyNuts) { - int maxNutCount; - if (CUR_UPG_VALUE(UPG_NUTS) == 0) { - INV_CONTENT(ITEM_NUT) = ITEM_NUT; - Inventory_ChangeUpgrade(UPG_NUTS, 1); - maxNutCount = 20; - } else if (CUR_UPG_VALUE(UPG_NUTS) == 1) { - maxNutCount = 20; - } else if (CUR_UPG_VALUE(UPG_NUTS) == 2) { - maxNutCount = 30; - } else if (CUR_UPG_VALUE(UPG_NUTS) == 3) { - maxNutCount = 40; - } - - if ((AMMO(ITEM_NUT) + howManyNuts) > maxNutCount) { - AMMO(ITEM_NUT) = maxNutCount; - } else { - AMMO(ITEM_NUT) += howManyNuts; - } -} - -void GiveLinksPocketItem() { - if (Randomizer_GetSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING) { - GetItemEntry getItemEntry = Randomizer_GetItemFromKnownCheck(RC_LINKS_POCKET, RG_NONE); - - if (getItemEntry.modIndex == MOD_NONE) { - if (getItemEntry.getItemId == GI_SWORD_BGS) { - gSaveContext.bgsFlag = true; - } - Item_Give(NULL, getItemEntry.itemId); - } else if (getItemEntry.modIndex == MOD_RANDOMIZER) { - if (getItemEntry.getItemId == RG_ICE_TRAP) { - gSaveContext.pendingIceTrapCount++; - GameInteractor_ExecuteOnItemReceiveHooks(getItemEntry); - } else { - Randomizer_Item_Give(NULL, getItemEntry); - } - } - } -} - /** * Copy save currently on the buffer to Save Context and complete various tasks to open the save. * This includes: @@ -305,299 +218,7 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx) { fileChooseCtx->n64ddFlag = 1; gSaveContext.n64ddFlag = 1; - // Sets all rando flags to false - for (s32 i = 0; i < ARRAY_COUNT(gSaveContext.randomizerInf); i++) { - gSaveContext.randomizerInf[i] = 0; - } - - // If any trials aren't required, set them as completed - for (u16 i = RAND_INF_TRIALS_DONE_LIGHT_TRIAL; i <= RAND_INF_TRIALS_DONE_SHADOW_TRIAL; i++) { - if (!Randomizer_IsTrialRequired(i)) { - Flags_SetRandomizerInf(i); - } - } - - // Set Cutscene flags to skip them - gSaveContext.eventChkInf[0xC] |= 0x10; // returned to tot with medallions - gSaveContext.eventChkInf[0xC] |= 0x20; //sheik at tot pedestal - gSaveContext.eventChkInf[4] |= 0x20; // master sword pulled - gSaveContext.eventChkInf[4] |= 0x8000; // entered master sword chamber - gSaveContext.infTable[0] |= 1; - if (!Randomizer_GetSettingValue(RSK_ENABLE_GLITCH_CUTSCENES)) { - gSaveContext.infTable[17] |= 0x400; // Darunia in Fire Temple - } - gSaveContext.cutsceneIndex = 0; - Flags_SetEventChkInf(5); - - // Skip boss cutscenes - gSaveContext.eventChkInf[7] |= 1; // gohma - gSaveContext.eventChkInf[7] |= 2; // dodongo - gSaveContext.eventChkInf[7] |= 4; // phantom ganon - gSaveContext.eventChkInf[7] |= 8; // volvagia - gSaveContext.eventChkInf[7] |= 0x10; // morpha - gSaveContext.eventChkInf[7] |= 0x20; // twinrova - gSaveContext.eventChkInf[7] |= 0x40; // barinade - gSaveContext.eventChkInf[7] |= 0x80; // bongo bongo - - // Skip cutscene before Nabooru fight - gSaveContext.eventChkInf[3] |= 0x800; - gSaveContext.eventChkInf[12] |= 1; - - // shuffle adult trade quest - if (Randomizer_GetSettingValue(RSK_SHUFFLE_ADULT_TRADE)) { - gSaveContext.adultTradeItems = 0; - } - - // Starts pending ice traps out at 0 before potentially incrementing them down the line. - gSaveContext.pendingIceTrapCount = 0; - - // Give Link's pocket item - GiveLinksPocketItem(); - - int openForest = Randomizer_GetSettingValue(RSK_FOREST); - switch (openForest) { - case RO_FOREST_CLOSED: - break; - case RO_FOREST_CLOSED_DEKU: - Flags_SetEventChkInf(EVENTCHKINF_OBTAINED_KOKIRI_EMERALD_DEKU_TREE_DEAD); - break; - case RO_FOREST_OPEN: - Flags_SetEventChkInf(EVENTCHKINF_OBTAINED_KOKIRI_EMERALD_DEKU_TREE_DEAD); - Flags_SetEventChkInf(EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD); - break; - } - - int startingAge = Randomizer_GetSettingValue(RSK_STARTING_AGE); - switch (startingAge) { - case RO_AGE_ADULT: //Adult - gSaveContext.linkAge = 0; - gSaveContext.entranceIndex = 0x5F4; - gSaveContext.savedSceneNum = SCENE_SPOT20; //Set scene num manually to ToT - break; - case RO_AGE_CHILD: //Child - gSaveContext.linkAge = 1; - gSaveContext.savedSceneNum = -1; - break; - default: - break; - } - - if (Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_SPAWNS)) { - // Override the spawn entrance so entrance rando can take control, - // and to prevent remember save location from breaking inital spawn - gSaveContext.entranceIndex = -1; - } - - int doorOfTime = Randomizer_GetSettingValue(RSK_DOOR_OF_TIME); - switch (doorOfTime) { - case RO_DOOROFTIME_OPEN: - gSaveContext.eventChkInf[4] |= 0x800; - break; - } - - if (Randomizer_GetSettingValue(RSK_KAK_GATE) == RO_KAK_GATE_OPEN) { - gSaveContext.infTable[7] |= 0x40; - } - - if(Randomizer_GetSettingValue(RSK_STARTING_KOKIRI_SWORD)) Item_Give(NULL, ITEM_SWORD_KOKIRI); - if(Randomizer_GetSettingValue(RSK_STARTING_DEKU_SHIELD)) Item_Give(NULL, ITEM_SHIELD_DEKU); - - if (Randomizer_GetSettingValue(RSK_STARTING_ZELDAS_LULLABY)) Item_Give(NULL, ITEM_SONG_LULLABY); - if (Randomizer_GetSettingValue(RSK_STARTING_EPONAS_SONG)) Item_Give(NULL, ITEM_SONG_EPONA); - if (Randomizer_GetSettingValue(RSK_STARTING_SARIAS_SONG)) Item_Give(NULL, ITEM_SONG_SARIA); - if (Randomizer_GetSettingValue(RSK_STARTING_SUNS_SONG)) Item_Give(NULL, ITEM_SONG_SUN); - if (Randomizer_GetSettingValue(RSK_STARTING_SONG_OF_TIME)) Item_Give(NULL, ITEM_SONG_TIME); - if (Randomizer_GetSettingValue(RSK_STARTING_SONG_OF_STORMS)) Item_Give(NULL, ITEM_SONG_STORMS); - if (Randomizer_GetSettingValue(RSK_STARTING_MINUET_OF_FOREST)) Item_Give(NULL, ITEM_SONG_MINUET); - if (Randomizer_GetSettingValue(RSK_STARTING_BOLERO_OF_FIRE)) Item_Give(NULL, ITEM_SONG_BOLERO); - if (Randomizer_GetSettingValue(RSK_STARTING_SERENADE_OF_WATER)) Item_Give(NULL, ITEM_SONG_SERENADE); - if (Randomizer_GetSettingValue(RSK_STARTING_REQUIEM_OF_SPIRIT)) Item_Give(NULL, ITEM_SONG_REQUIEM); - if (Randomizer_GetSettingValue(RSK_STARTING_NOCTURNE_OF_SHADOW)) Item_Give(NULL, ITEM_SONG_NOCTURNE); - if (Randomizer_GetSettingValue(RSK_STARTING_PRELUDE_OF_LIGHT)) Item_Give(NULL, ITEM_SONG_PRELUDE); - - if(Randomizer_GetSettingValue(RSK_STARTING_SKULLTULA_TOKEN)) { - gSaveContext.inventory.questItems |= gBitFlags[QUEST_SKULL_TOKEN]; - gSaveContext.inventory.gsTokens = Randomizer_GetSettingValue(RSK_STARTING_SKULLTULA_TOKEN); - } - - if(Randomizer_GetSettingValue(RSK_STARTING_OCARINA)) { - INV_CONTENT(ITEM_OCARINA_FAIRY) = ITEM_OCARINA_FAIRY; - } - - if(Randomizer_GetSettingValue(RSK_STARTING_MAPS_COMPASSES) == RO_DUNGEON_ITEM_LOC_STARTWITH) { - uint32_t mapBitMask = 1 << 1; - uint32_t compassBitMask = 1 << 2; - uint32_t startingDungeonItemsBitMask = mapBitMask | compassBitMask; - for(int scene = SCENE_YDAN; scene <= SCENE_ICE_DOUKUTO; scene++) { - gSaveContext.inventory.dungeonItems[scene] |= startingDungeonItemsBitMask; - } - } - - if (Randomizer_GetSettingValue(RSK_STARTING_CONSUMABLES)) { - GiveLinkDekuSticks(10); - GiveLinkDekuNuts(20); - } - - if(Randomizer_GetSettingValue(RSK_SKIP_CHILD_ZELDA)) { - GetItemEntry getItem = Randomizer_GetItemFromKnownCheck(RC_SONG_FROM_IMPA, RG_ZELDAS_LULLABY); - s32 giid = getItem.getItemId; - - if (getItem.modIndex == MOD_NONE) { - if (getItem.getItemId == GI_SWORD_BGS) { - gSaveContext.bgsFlag = true; - } - Item_Give(NULL, getItem.itemId); - } else if (getItem.modIndex == MOD_RANDOMIZER) { - if (getItem.getItemId == RG_ICE_TRAP) { - gSaveContext.pendingIceTrapCount++; - GameInteractor_ExecuteOnItemReceiveHooks(getItem); - } else { - Randomizer_Item_Give(NULL, getItem); - } - } - - // malon/talon back at ranch - gSaveContext.eventChkInf[1] |= (1 << 0); - gSaveContext.eventChkInf[1] |= (1 << 2); - gSaveContext.eventChkInf[1] |= (1 << 3); - gSaveContext.eventChkInf[1] |= (1 << 4); - - // Set "Got Zelda's Letter" flag. Also ensures Saria is back at SFM. TODO: Is this flag used for anything else? - Flags_SetEventChkInf(EVENTCHKINF_OBTAINED_ZELDAS_LETTER); - - // Got item from impa - gSaveContext.eventChkInf[5] |= 0x200; - - // set this at the end to ensure we always start with the letter - // this is for the off chance we got the weird egg from impa (which should never happen) - INV_CONTENT(ITEM_LETTER_ZELDA) = ITEM_LETTER_ZELDA; - } - - if (Randomizer_GetSettingValue(RSK_FULL_WALLETS)) { - GiveLinkRupees(9001); - } - - if (Randomizer_GetSettingValue(RSK_KEYSANITY) == RO_DUNGEON_ITEM_LOC_STARTWITH) { - gSaveContext.inventory.dungeonKeys[SCENE_BMORI1] = FOREST_TEMPLE_SMALL_KEY_MAX; // Forest - gSaveContext.sohStats.dungeonKeys[SCENE_BMORI1] = FOREST_TEMPLE_SMALL_KEY_MAX; // Forest - gSaveContext.inventory.dungeonKeys[SCENE_HIDAN] = FIRE_TEMPLE_SMALL_KEY_MAX; // Fire - gSaveContext.sohStats.dungeonKeys[SCENE_HIDAN] = FIRE_TEMPLE_SMALL_KEY_MAX; // Fire - gSaveContext.inventory.dungeonKeys[SCENE_MIZUSIN] = WATER_TEMPLE_SMALL_KEY_MAX; // Water - gSaveContext.sohStats.dungeonKeys[SCENE_MIZUSIN] = WATER_TEMPLE_SMALL_KEY_MAX; // Water - gSaveContext.inventory.dungeonKeys[SCENE_JYASINZOU] = SPIRIT_TEMPLE_SMALL_KEY_MAX; // Spirit - gSaveContext.sohStats.dungeonKeys[SCENE_JYASINZOU] = SPIRIT_TEMPLE_SMALL_KEY_MAX; // Spirit - gSaveContext.inventory.dungeonKeys[SCENE_HAKADAN] = SHADOW_TEMPLE_SMALL_KEY_MAX; // Shadow - gSaveContext.sohStats.dungeonKeys[SCENE_HAKADAN] = SHADOW_TEMPLE_SMALL_KEY_MAX; // Shadow - gSaveContext.inventory.dungeonKeys[SCENE_HAKADANCH] = BOTTOM_OF_THE_WELL_SMALL_KEY_MAX; // BotW - gSaveContext.sohStats.dungeonKeys[SCENE_HAKADANCH] = BOTTOM_OF_THE_WELL_SMALL_KEY_MAX; // BotW - gSaveContext.inventory.dungeonKeys[SCENE_MEN] = GERUDO_TRAINING_GROUNDS_SMALL_KEY_MAX; // GTG - gSaveContext.sohStats.dungeonKeys[SCENE_MEN] = GERUDO_TRAINING_GROUNDS_SMALL_KEY_MAX; // GTG - gSaveContext.inventory.dungeonKeys[SCENE_GANONTIKA] = GANONS_CASTLE_SMALL_KEY_MAX; // Ganon - gSaveContext.sohStats.dungeonKeys[SCENE_GANONTIKA] = GANONS_CASTLE_SMALL_KEY_MAX; // Ganon - } else if (Randomizer_GetSettingValue(RSK_KEYSANITY) == RO_DUNGEON_ITEM_LOC_VANILLA) { - // Logic cannot handle vanilla key layout in some dungeons - // this is because vanilla expects the dungeon major item to be - // locked behind the keys, which is not always true in rando. - // We can resolve this by starting with some extra keys - if (ResourceMgr_IsSceneMasterQuest(SCENE_JYASINZOU)) { - // MQ Spirit needs 3 keys - gSaveContext.inventory.dungeonKeys[SCENE_JYASINZOU] = 3; - gSaveContext.sohStats.dungeonKeys[SCENE_JYASINZOU] = 3; - } - } - - if(Randomizer_GetSettingValue(RSK_BOSS_KEYSANITY) == RO_DUNGEON_ITEM_LOC_STARTWITH) { - gSaveContext.inventory.dungeonItems[SCENE_BMORI1] |= 1; // Forest - gSaveContext.inventory.dungeonItems[SCENE_HIDAN] |= 1; // Fire - gSaveContext.inventory.dungeonItems[SCENE_MIZUSIN] |= 1; // Water - gSaveContext.inventory.dungeonItems[SCENE_JYASINZOU] |= 1; // Spirit - gSaveContext.inventory.dungeonItems[SCENE_HAKADAN] |= 1; // Shadow - } - - if(Randomizer_GetSettingValue(RSK_GANONS_BOSS_KEY) == RO_GANON_BOSS_KEY_STARTWITH) { - gSaveContext.inventory.dungeonItems[SCENE_GANON] |= 1; - } - - HIGH_SCORE(HS_POE_POINTS) = 1000 - (100 * Randomizer_GetSettingValue(RSK_BIG_POE_COUNT)); - - if(Randomizer_GetSettingValue(RSK_SKIP_EPONA_RACE)) { - gSaveContext.eventChkInf[1] |= (1 << 8); - } - - // skip the z target talk instructions by the kokiri shop - gSaveContext.sceneFlags[85].swch |= (1 << 0x1F); - - //Ruto already met in jabu and spawns down the hole immediately - gSaveContext.infTable[20] |= 2; - gSaveContext.infTable[20] |= 4; - - // Go away ruto (water temple first cutscene) - gSaveContext.sceneFlags[SCENE_MIZUSIN].swch |= (1 << 0x10); - - // Open lowest Vanilla Fire Temple locked door (to prevent key logic lockouts) - // Not done on keysanity since this lockout is a non issue when Fire keys can be found outside the temple - u8 keysanity = Randomizer_GetSettingValue(RSK_KEYSANITY) == RO_DUNGEON_ITEM_LOC_ANYWHERE || - Randomizer_GetSettingValue(RSK_KEYSANITY) == RO_DUNGEON_ITEM_LOC_OVERWORLD || - Randomizer_GetSettingValue(RSK_KEYSANITY) == RO_DUNGEON_ITEM_LOC_ANY_DUNGEON; - if (!ResourceMgr_IsSceneMasterQuest(SCENE_HIDAN) && !keysanity) { - gSaveContext.sceneFlags[SCENE_HIDAN].swch |= (1 << 0x17); - } - - // Opens locked Water Temple door in vanilla to prevent softlocks - // West door on the middle level that leads to the water raising thing - // Happens in 3DS rando and N64 rando as well - if (!ResourceMgr_IsSceneMasterQuest(SCENE_MIZUSIN)) { - gSaveContext.sceneFlags[SCENE_MIZUSIN].swch |= (1 << 0x15); - } - - // Skip intro cutscene when bombing mud wall in Dodongo's cavern - // this also makes the lower jaw render, and the eyes react to explosives - Flags_SetEventChkInf(0xB0); - - // skip verbose lake owl, skip to "i'm on my way back to the castle" - gSaveContext.infTable[25] |= 0x20; - - if (Randomizer_GetSettingValue(RSK_GERUDO_FORTRESS) == RO_GF_FAST || - Randomizer_GetSettingValue(RSK_GERUDO_FORTRESS) == RO_GF_OPEN) { - gSaveContext.eventChkInf[9] |= 2; - gSaveContext.eventChkInf[9] |= 4; - gSaveContext.eventChkInf[9] |= 8; - gSaveContext.sceneFlags[12].swch |= (1 << 0x02); - gSaveContext.sceneFlags[12].swch |= (1 << 0x03); - gSaveContext.sceneFlags[12].swch |= (1 << 0x04); - gSaveContext.sceneFlags[12].swch |= (1 << 0x06); - gSaveContext.sceneFlags[12].swch |= (1 << 0x07); - gSaveContext.sceneFlags[12].swch |= (1 << 0x08); - gSaveContext.sceneFlags[12].swch |= (1 << 0x10); - gSaveContext.sceneFlags[12].swch |= (1 << 0x12); - gSaveContext.sceneFlags[12].swch |= (1 << 0x13); - gSaveContext.sceneFlags[12].collect |= (1 << 0x0A); - gSaveContext.sceneFlags[12].collect |= (1 << 0x0E); - gSaveContext.sceneFlags[12].collect |= (1 << 0x0F); - } - - if (Randomizer_GetSettingValue(RSK_GERUDO_FORTRESS) == RO_GF_OPEN) { - gSaveContext.eventChkInf[9] |= 1; - gSaveContext.sceneFlags[12].swch |= (1 << 0x01); - gSaveContext.sceneFlags[12].swch |= (1 << 0x05); - gSaveContext.sceneFlags[12].swch |= (1 << 0x11); - gSaveContext.sceneFlags[12].collect |= (1 << 0x0C); - - if (!Randomizer_GetSettingValue(RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD)) { - Item_Give(NULL, ITEM_GERUDO_CARD); - } - } - - // complete mask quest - if (Randomizer_GetSettingValue(RSK_COMPLETE_MASK_QUEST)) { - gSaveContext.infTable[7] |= 0x80; // Soldier Wears Keaton Mask - gSaveContext.itemGetInf[3] |= 0x100; // Sold Keaton Mask - gSaveContext.itemGetInf[3] |= 0x200; // Sold Skull Mask - gSaveContext.itemGetInf[3] |= 0x400; // Sold Spooky Mask - gSaveContext.itemGetInf[3] |= 0x800; // bunny hood related - gSaveContext.itemGetInf[3] |= 0x8000; // Obtained Mask of Truth - gSaveContext.eventChkInf[8] |= 0x8000; // sold all masks - } + Randomizer_InitSaveFile(); } Save_SaveFile();