diff --git a/code/src/actors/checkable_spot.c b/code/src/actors/checkable_spot.c index bd26a3a4..c542677c 100644 --- a/code/src/actors/checkable_spot.c +++ b/code/src/actors/checkable_spot.c @@ -10,7 +10,7 @@ void EnWonderTalk_rUpdate(Actor* thisx, GlobalContext* globalCtx) { EnWonderTalk* this = (EnWonderTalk*)thisx; if (this->actionFunc == (ActorFunc)GAME_ADDR(0x2065E0) && thisx->params == 0x0FFF) { // reading ToT altar - gExtSaveData.extInf[EXTINF_TOTALTAR_FLAGS] |= (1 << gSaveContext.linkAge); + gExtSaveData.extInf.totAltarFlags |= (1 << gSaveContext.linkAge); } EnWonderTalk_Update(thisx, globalCtx); } diff --git a/code/src/actors/gorons.c b/code/src/actors/gorons.c index e3cecb0a..01a47641 100644 --- a/code/src/actors/gorons.c +++ b/code/src/actors/gorons.c @@ -9,7 +9,7 @@ void EnGo2_rBiggoronSetTextId(EnGo2* self) { Player* player = PLAYER; if (player->exchangeItemId == EXCH_ITEM_CLAIM_CHECK) { - if (gExtSaveData.extInf[EXTINF_BIGGORONTRADES] & BIGGORON_TRADED_CLAIM_CHECK) { + if (gExtSaveData.extInf.biggoronTrades & BIGGORON_TRADED_CLAIM_CHECK) { self->actor.textId = 0x3003; // "I giiiive thissss to yoooou forrr a souvenirrrrr." } else { self->actor.textId = 0x305E; // "Yourrrr sworrrrd is my finest worrrrk!" @@ -36,7 +36,7 @@ u16 EnGo2_rGetTextIdGoronDmtBiggoron(GlobalContext* globalCtx, EnGo2* self) { return 0x3058; case ITEM_CLAIM_CHECK: player->exchangeItemId = EXCH_ITEM_CLAIM_CHECK; - return (gExtSaveData.extInf[EXTINF_BIGGORONTRADES] & BIGGORON_TRADED_CLAIM_CHECK) ? 0x3053 : 0x305E; + return (gExtSaveData.extInf.biggoronTrades & BIGGORON_TRADED_CLAIM_CHECK) ? 0x3053 : 0x305E; default: player->exchangeItemId = EXCH_ITEM_NONE; return 0x3053; @@ -46,13 +46,13 @@ u16 EnGo2_rGetTextIdGoronDmtBiggoron(GlobalContext* globalCtx, EnGo2* self) { void EnGo2_AfterGiveItem(EnGo2* self, GlobalContext* globaCtx) { switch (self->getItemId) { case GI_CLAIM_CHECK: - gExtSaveData.extInf[EXTINF_BIGGORONTRADES] |= BIGGORON_TRADED_EYEDROPS; + gExtSaveData.extInf.biggoronTrades |= BIGGORON_TRADED_EYEDROPS; break; case GI_SWORD_BGS: - gExtSaveData.extInf[EXTINF_BIGGORONTRADES] |= BIGGORON_TRADED_CLAIM_CHECK; + gExtSaveData.extInf.biggoronTrades |= BIGGORON_TRADED_CLAIM_CHECK; break; case GI_PERSCRIPTION: - gExtSaveData.extInf[EXTINF_BIGGORONTRADES] |= BIGGORON_TRADED_BROKEN_SWORD; + gExtSaveData.extInf.biggoronTrades |= BIGGORON_TRADED_BROKEN_SWORD; break; } self->actionFunc = EnGo2_SetGetItem; diff --git a/code/src/enemy_souls.c b/code/src/enemy_souls.c index e52449c3..0132e668 100644 --- a/code/src/enemy_souls.c +++ b/code/src/enemy_souls.c @@ -86,14 +86,14 @@ u8 EnemySouls_GetSoulFlag(EnemySoulId soulId) { if (soulId == SOUL_NONE) { return 1; } - return gExtSaveData.extInf[EXTINF_ENEMYSOULSFLAGS_START + (soulId >> 3)] & (1 << (soulId & 0b111)); + return gExtSaveData.extInf.enemySouls[(soulId >> 3)] & (1 << (soulId & 0b111)); } void EnemySouls_SetSoulFlag(EnemySoulId soulId) { if (soulId == SOUL_NONE) { return; } - gExtSaveData.extInf[EXTINF_ENEMYSOULSFLAGS_START + (soulId >> 3)] |= (1 << (soulId & 0b111)); + gExtSaveData.extInf.enemySouls[(soulId >> 3)] |= (1 << (soulId & 0b111)); } u8 EnemySouls_CheckSoulForActor(Actor* actor) { diff --git a/code/src/item_effect.c b/code/src/item_effect.c index 853c016a..8b50e8c4 100644 --- a/code/src/item_effect.c +++ b/code/src/item_effect.c @@ -131,7 +131,7 @@ void ItemEffect_IceTrap(SaveContext* saveCtx, s16 arg1, s16 arg2) { } void ItemEffect_GiveMasterSword(SaveContext* saveCtx, s16 arg1, s16 arg2) { - gExtSaveData.extInf[EXTINF_MASTERSWORDFLAGS] |= 1; + gExtSaveData.extInf.masterSwordFlags |= 1; } void ItemEffect_EquipMasterSword(void) { diff --git a/code/src/master_sword_pedestal.c b/code/src/master_sword_pedestal.c index 5a1a93b3..17da107b 100644 --- a/code/src/master_sword_pedestal.c +++ b/code/src/master_sword_pedestal.c @@ -4,7 +4,7 @@ #include "savefile.h" u8 ShouldSkipMasterSwordCutscene() { - return gExtSaveData.extInf[EXTINF_HASTIMETRAVELED] || gSettingsContext.openDoorOfTime == OPENDOOROFTIME_OPEN; + return gExtSaveData.hasTimeTraveled || gSettingsContext.openDoorOfTime == OPENDOOROFTIME_OPEN; } void TimeTravelAdvanceCutsceneTimer() { @@ -18,12 +18,12 @@ void TimeTravelAdvanceCutsceneTimer() { } void SetTimeTraveled() { - gExtSaveData.extInf[EXTINF_HASTIMETRAVELED] = 1; + gExtSaveData.hasTimeTraveled = 1; } void Pedestal_PickUpMasterSword(void) { // Push pedestal item - if (gSettingsContext.shuffleMasterSword && !(gExtSaveData.extInf[EXTINF_MASTERSWORDFLAGS] & 2)) { + if (gSettingsContext.shuffleMasterSword && !(gExtSaveData.extInf.masterSwordFlags & 2)) { ItemOverride_PushDelayedOverride(0x00); } @@ -33,5 +33,5 @@ void Pedestal_PickUpMasterSword(void) { } // Mark pedestal item collected - gExtSaveData.extInf[EXTINF_MASTERSWORDFLAGS] |= 2; + gExtSaveData.extInf.masterSwordFlags |= 2; } diff --git a/code/src/menus.c b/code/src/menus.c index 7e7a5d2f..3d4db629 100644 --- a/code/src/menus.c +++ b/code/src/menus.c @@ -114,11 +114,11 @@ u16 GearMenu_GetRewardHint(void) { if (gGearMenuSelectedSlot >= GEARSLOT_KOKIRI_EMERALD && gGearMenuSelectedSlot <= GEARSLOT_ZORA_SAPPHIRE) { rewardId = gGearMenuSelectedSlot - GEARSLOT_KOKIRI_EMERALD; - checkedAltar = gExtSaveData.extInf[EXTINF_TOTALTAR_FLAGS] & (1 << AGE_CHILD); + checkedAltar = gExtSaveData.extInf.totAltarFlags & (1 << AGE_CHILD); } else if (gGearMenuSelectedSlot >= GEARSLOT_FOREST_MEDALLION && gGearMenuSelectedSlot <= GEARSLOT_LIGHT_MEDALLION) { rewardId = gGearMenuSelectedSlot - GEARSLOT_FOREST_MEDALLION + 3; - checkedAltar = gExtSaveData.extInf[EXTINF_TOTALTAR_FLAGS] & (1 << AGE_ADULT); + checkedAltar = gExtSaveData.extInf.totAltarFlags & (1 << AGE_ADULT); } else { return 0; } diff --git a/code/src/multiplayer.c b/code/src/multiplayer.c index e07b41d3..f89b03f2 100644 --- a/code/src/multiplayer.c +++ b/code/src/multiplayer.c @@ -136,7 +136,7 @@ static void Multiplayer_Overwrite_mSaveContext(void) { mSaveContext.magicMeterSize = gSaveContext.magicMeterSize; // ExtData for (size_t i = 0; i < EXTINF_SIZE; i++) { - mSaveContext.extInf[i] = gExtSaveData.extInf[i]; + mSaveContext.extInf[i] = gExtSaveData.extInfArray[i]; } for (size_t i = 0; i < SAVEFILE_SCENES_DISCOVERED_IDX_COUNT; i++) { mSaveContext.scenesDiscovered[i] = gExtSaveData.scenesDiscovered[i]; @@ -198,7 +198,7 @@ static void Multiplayer_Overwrite_gSaveContext(void) { gSaveContext.magicMeterSize = mSaveContext.magicMeterSize; // ExtData for (size_t i = 0; i < EXTINF_SIZE; i++) { - gExtSaveData.extInf[i] = mSaveContext.extInf[i]; + gExtSaveData.extInfArray[i] = mSaveContext.extInf[i]; } for (size_t i = 0; i < SAVEFILE_SCENES_DISCOVERED_IDX_COUNT; i++) { gExtSaveData.scenesDiscovered[i] = mSaveContext.scenesDiscovered[i]; @@ -525,7 +525,7 @@ static void Multiplayer_Sync_Init(void) { // Extra Info Table for (size_t i = 0; i < EXTINF_SIZE; i++) { - prevExtInf[i] = gExtSaveData.extInf[i]; + prevExtInf[i] = gExtSaveData.extInfArray[i]; } // Triforce Pieces @@ -854,14 +854,10 @@ static void Multiplayer_Sync_SharedProgress(void) { prevAdultTrade = gSaveContext.sceneFlags[0x60].unk; // Extra Info Table - for (size_t index = 0; index < ARRAY_SIZE(gExtSaveData.extInf); index++) { - // Don't send this to allow all current players to watch the cutscene - if (index == EXTINF_HASTIMETRAVELED) { - continue; - } - if (prevExtInf[index] != gExtSaveData.extInf[index]) { - for (size_t bit = 0; bit < BIT_COUNT(gExtSaveData.extInf[index]); bit++) { - s8 result = BitCompare(gExtSaveData.extInf[index], prevExtInf[index], bit); + for (size_t index = 0; index < ARRAY_SIZE(gExtSaveData.extInfArray); index++) { + if (prevExtInf[index] != gExtSaveData.extInfArray[index]) { + for (size_t bit = 0; bit < BIT_COUNT(gExtSaveData.extInfArray[index]); bit++) { + s8 result = BitCompare(gExtSaveData.extInfArray[index], prevExtInf[index], bit); if (result > 0) { Multiplayer_Send_ExtInfBit(index, bit, 1); } else if (result < 0) { @@ -869,9 +865,11 @@ static void Multiplayer_Sync_SharedProgress(void) { } } } - prevExtInf[index] = gExtSaveData.extInf[index]; + prevExtInf[index] = gExtSaveData.extInfArray[index]; } + // `hasTimeTraveled` is not synced to allow all current players to watch the cutscene + // Triforce Pieces if (prevTriforcePieces != gExtSaveData.triforcePieces) { Multiplayer_Send_TriforcePieces(gExtSaveData.triforcePieces - prevTriforcePieces); diff --git a/code/src/ocarina_notes.c b/code/src/ocarina_notes.c index 4d7e92f0..a236687e 100644 --- a/code/src/ocarina_notes.c +++ b/code/src/ocarina_notes.c @@ -60,12 +60,11 @@ void OcarinaNotes_Init(void) { } s32 OcarinaNotes_IsButtonOwned(OcarinaNoteButton button) { - return (gSettingsContext.shuffleOcarinaButtons == OFF) || - (gExtSaveData.extInf[EXTINF_OCARINA_BUTTONS] & (1 << button)); + return (gSettingsContext.shuffleOcarinaButtons == OFF) || (gExtSaveData.extInf.ocarinaButtons & (1 << button)); } void OcarinaNotes_RegisterButtonOwned(OcarinaNoteButton button) { - gExtSaveData.extInf[EXTINF_OCARINA_BUTTONS] |= (1 << button); + gExtSaveData.extInf.ocarinaButtons |= (1 << button); } void OcarinaNotes_MoveButtons(void* spriteStruct, Vec2f* posOffset, u32 unk, u32 spriteIndex) { diff --git a/code/src/savefile.c b/code/src/savefile.c index c39e7d63..d8ec3156 100644 --- a/code/src/savefile.c +++ b/code/src/savefile.c @@ -541,11 +541,11 @@ void SaveFile_SetStartingInventory(void) { } // Set owned ocarina buttons. If the shuffle option is disabled, this value will be ignored. - gExtSaveData.extInf[EXTINF_OCARINA_BUTTONS] = gSettingsContext.startingOcarinaButtons; + gExtSaveData.extInf.ocarinaButtons = gSettingsContext.startingOcarinaButtons; // Set owned enemy souls. If the shuffle option is disabled, these values will be ignored. for (u32 i = 0; i < sizeof(gSettingsContext.startingEnemySouls); i++) { - gExtSaveData.extInf[EXTINF_ENEMYSOULSFLAGS_START + i] = gSettingsContext.startingEnemySouls[i]; + gExtSaveData.extInf.enemySouls[i] = gSettingsContext.startingEnemySouls[i]; } } @@ -718,7 +718,7 @@ void SaveFile_InitExtSaveData(u32 saveNumber, u8 fromSaveCreation) { memset(&gExtSaveData, 0, sizeof(gExtSaveData)); gExtSaveData.version = EXTSAVEDATA_VERSION; // Do not change this line - gExtSaveData.extInf[EXTINF_MASTERSWORDFLAGS] = + gExtSaveData.extInf.masterSwordFlags = (gSettingsContext.shuffleMasterSword && !(gSettingsContext.startingEquipment & 0x2)) ? 0 : 1; gExtSaveData.permadeath = fromSaveCreation ? gSettingsContext.permadeath : 0; // Ingame Options @@ -803,7 +803,7 @@ void SaveFile_EnforceHealthLimit(void) { } u8 SaveFile_SwordlessPatchesEnabled(void) { - return gSettingsContext.shuffleMasterSword && !(gExtSaveData.extInf[EXTINF_MASTERSWORDFLAGS] & 1); + return gSettingsContext.shuffleMasterSword && !(gExtSaveData.extInf.masterSwordFlags & 1); } u8 SaveFile_BecomeAdult(void) { @@ -835,12 +835,12 @@ u8 SaveFile_BecomeAdult(void) { void SaveFile_LoadFileSwordless(void) { if (gSaveContext.linkAge == 0) { // Push pedestal item if adult and haven't received yet - if (gSettingsContext.shuffleMasterSword && !(gExtSaveData.extInf[EXTINF_MASTERSWORDFLAGS] & 2)) { + if (gSettingsContext.shuffleMasterSword && !(gExtSaveData.extInf.masterSwordFlags & 2)) { ItemOverride_PushDelayedOverride(0x00); } // Mark pedestal item collected - gExtSaveData.extInf[EXTINF_MASTERSWORDFLAGS] |= 2; + gExtSaveData.extInf.masterSwordFlags |= 2; } } diff --git a/code/src/savefile.h b/code/src/savefile.h index 8579e8ec..a1f3b32e 100644 --- a/code/src/savefile.h +++ b/code/src/savefile.h @@ -32,22 +32,24 @@ void SaveFile_EnforceHealthLimit(void); u8 SaveFile_SwordlessPatchesEnabled(void); // Increment the version number whenever the ExtSaveData structure is changed -#define EXTSAVEDATA_VERSION 16 +#define EXTSAVEDATA_VERSION 17 -typedef enum { - EXTINF_BIGGORONTRADES, - EXTINF_HASTIMETRAVELED, - EXTINF_MASTERSWORDFLAGS, - EXTINF_TOTALTAR_FLAGS, - EXTINF_ENEMYSOULSFLAGS_START, // 64 bits (at least one for each EnemySoulId) - EXTINF_ENEMYSOULSFLAGS_END = EXTINF_ENEMYSOULSFLAGS_START + 7, - EXTINF_OCARINA_BUTTONS, - EXTINF_SIZE, +typedef struct ExtInf { + u8 biggoronTrades; + u8 masterSwordFlags; + u8 totAltarFlags; + u8 enemySouls[8]; + u8 ocarinaButtons; } ExtInf; +#define EXTINF_SIZE sizeof(ExtInf) + typedef struct { - u32 version; // Needs to always be the first field of the structure - u8 extInf[EXTINF_SIZE]; // Used for various bit flags that should also be synced in multiplayer + u32 version; // Needs to always be the first field of the structure + union { + ExtInf extInf; // Used for various bit flags that should also be synced in multiplayer with shared progress + u8 extInfArray[EXTINF_SIZE]; // Used only by multiplayer code for easier management of bit flags + }; struct { Vec3i pos; s32 yaw; @@ -61,6 +63,7 @@ typedef struct { u32 playtimeSeconds; u32 scenesDiscovered[SAVEFILE_SCENES_DISCOVERED_IDX_COUNT]; u32 entrancesDiscovered[SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT]; + u8 hasTimeTraveled; u8 permadeath; u8 gloomedHeart; u8 triforcePieces; diff --git a/code/src/spoiler_data.c b/code/src/spoiler_data.c index 9cddc62a..63538ea3 100644 --- a/code/src/spoiler_data.c +++ b/code/src/spoiler_data.c @@ -79,7 +79,7 @@ u8 SpoilerData_ScrubCheck(SpoilerItemLocation* itemLoc) { } u8 SpoilerData_BiggoronCheck(u8 mask) { - return (gExtSaveData.extInf[EXTINF_BIGGORONTRADES] & mask) != 0; + return (gExtSaveData.extInf.biggoronTrades & mask) != 0; } u8 SpoilerData_GerudoTokenCheck() { @@ -110,7 +110,7 @@ u8 SpoilerData_MagicBeansCheck(SpoilerItemLocation* itemLoc) { } u8 SpoilerData_MasterSwordCheck() { - return (gExtSaveData.extInf[EXTINF_MASTERSWORDFLAGS] & 2) != 0; + return (gExtSaveData.extInf.masterSwordFlags & 2) != 0; } u8 SpoilerData_GetIsItemLocationCollected(u16 itemIndex) {