From 2eaed8d81ea2586a8a4112f17bd3a88d3c7882db Mon Sep 17 00:00:00 2001 From: Ralphie Morell Date: Sat, 21 Oct 2023 21:51:37 -0400 Subject: Rando: Master Sword Shuffle (#2981) * The mother of all commits * Removed `GI_SWORD_MASTER`; "Master Sword" Items now actually give MS * Removed dupe MS entries in item pool; updated GIMESSAGE (should stop crashing on non-Windows); re-added MS in item list * Give Adult Link a freebie with shuffle MS on; cihld -> adult no longer gives MS; ToT Master Sword now gives correct item * add master sword GI draw func based on ToT MS object * Force `MasterSword` logic var to only update upon getting MS * Dorf funny line now activates with LA and MS in inv * Apply suggestions * Updated RAND_INF; Check Tracker changes; Gave RAND_INF and ice trap logic to ToT MS check; Fixed swordless behavior for HBA/fishing * ToT MS Check now works in check tracker; Visual bug where box hovers over non-existent MS gone; Fixed RAND_INF check with ToT MS pedestal; Ganon no longer gives free MS * adult equips no longer reset in MS shuffle * Apply (most) locacc review suggestions Co-authored-by: inspectredc <78732756+inspectredc@users.noreply.github.com> * Reorganized swordless check for interface to fit edge cases; getting master sword no longer highlights box * Edge case for BGS but no bow * Fix implicit declaration error for GI hooks (#9) * Adjusted `CanAdultAttack/Damage`; applied logic suggestions * Fixed build errors (hopefully) * Cleanup merge * get shit working again * Tidied up remaining uses of DD flag as rando indicator * make master sword invisible and fix ms flag (#10) * Add text to sheik if go mode is obtained but barrier is still up * overhaul swordless behavior in `func_80083108` * reworked ToT MS Check to have an actual GI * suggestions * Apply suggestions * Better swordless handling with temp B (#11) * better swordless handling with temp B * prevent auto save in fishing pond * prevent auto save during bombchu bowling * enum fix --------- Co-authored-by: Adam Bird Co-authored-by: inspectredc <78732756+inspectredc@users.noreply.github.com> Co-authored-by: RaelCappra Co-authored-by: Adam Bird --- soh/src/code/z_game_over.c | 1 + soh/src/code/z_parameter.c | 87 +++++++++++++++++++++++++++++++++++++++------- soh/src/code/z_play.c | 27 ++++++++------ soh/src/code/z_sram.c | 10 +++--- 4 files changed, 97 insertions(+), 28 deletions(-) (limited to 'soh/src/code') diff --git a/soh/src/code/z_game_over.c b/soh/src/code/z_game_over.c index 4b10cb5e2..2d46a1899 100644 --- a/soh/src/code/z_game_over.c +++ b/soh/src/code/z_game_over.c @@ -57,6 +57,7 @@ void GameOver_Update(PlayState* play) { if (gSaveContext.buttonStatus[0] != BTN_ENABLED) { gSaveContext.equips.buttonItems[0] = gSaveContext.buttonStatus[0]; + Interface_RandoRestoreSwordless(); } else { gSaveContext.equips.buttonItems[0] = ITEM_NONE; } diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index c41d1c188..cf3192c3d 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -811,6 +811,20 @@ void func_80082850(PlayState* play, s16 maxAlpha) { } } +// buttonStatus[0] is used to represent if the B button is disabled, but also tracks +// the last active B button item during mini-games/epona (temp B) +// Since ITEM_NONE is the same as BTN_DISABLED (255), we need a different value to help us track +// that the player was swordless before like ITEM_NONE_FE (254) +#define SWORDLESS_STATUS ITEM_NONE_FE + +// Restores swordless state when using the custom value for temp B and then clears temp B +void Interface_RandoRestoreSwordless(void) { + if (IS_RANDO && gSaveContext.buttonStatus[0] == SWORDLESS_STATUS) { + gSaveContext.equips.buttonItems[0] = ITEM_NONE; + gSaveContext.buttonStatus[0] = BTN_ENABLED; + } +} + void func_80083108(PlayState* play) { MessageContext* msgCtx = &play->msgCtx; Player* player = GET_PLAYER(play); @@ -818,13 +832,20 @@ void func_80083108(PlayState* play) { s16 i; s16 sp28 = 0; + // Check for the player being swordless in rando (no item on B and swordless flag set) + // Child is always assumed due to not finding kokiri sword yet. Adult is only checked with MS shuffle on. + u8 randoIsSwordless = IS_RANDO && (LINK_IS_CHILD || Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD)) && + gSaveContext.equips.buttonItems[0] == ITEM_NONE && Flags_GetInfTable(INFTABLE_SWORDLESS); + u8 randoWasSwordlessBefore = IS_RANDO && gSaveContext.buttonStatus[0] == SWORDLESS_STATUS; + u8 randoCanTrackSwordless = randoIsSwordless && !randoWasSwordlessBefore; + if ((gSaveContext.cutsceneIndex < 0xFFF0) || ((play->sceneNum == SCENE_LON_LON_RANCH) && (gSaveContext.cutsceneIndex == 0xFFF0))) { gSaveContext.unk_13E7 = 0; - if ((player->stateFlags1 & 0x00800000) || (play->shootingGalleryStatus > 1) || + if ((player->stateFlags1 & PLAYER_STATE1_ON_HORSE) || (play->shootingGalleryStatus > 1) || ((play->sceneNum == SCENE_BOMBCHU_BOWLING_ALLEY) && Flags_GetSwitch(play, 0x38))) { - if (gSaveContext.equips.buttonItems[0] != ITEM_NONE) { + if (gSaveContext.equips.buttonItems[0] != ITEM_NONE || randoCanTrackSwordless) { gSaveContext.unk_13E7 = 1; if (gSaveContext.buttonStatus[0] == BTN_DISABLED) { @@ -837,9 +858,14 @@ void func_80083108(PlayState* play) { if ((gSaveContext.equips.buttonItems[0] != ITEM_SLINGSHOT) && (gSaveContext.equips.buttonItems[0] != ITEM_BOW) && (gSaveContext.equips.buttonItems[0] != ITEM_BOMBCHU) && - (gSaveContext.equips.buttonItems[0] != ITEM_NONE)) { + (gSaveContext.equips.buttonItems[0] != ITEM_NONE || randoCanTrackSwordless)) { gSaveContext.buttonStatus[0] = gSaveContext.equips.buttonItems[0]; + // Track swordless status for restoration later + if (randoCanTrackSwordless) { + gSaveContext.buttonStatus[0] = SWORDLESS_STATUS; + } + if ((play->sceneNum == SCENE_BOMBCHU_BOWLING_ALLEY) && Flags_GetSwitch(play, 0x38)) { gSaveContext.equips.buttonItems[0] = ITEM_BOMBCHU; Interface_LoadItemIcon1(play, 0); @@ -875,11 +901,11 @@ void func_80083108(PlayState* play) { Interface_ChangeAlpha(8); } else if ((play->sceneNum == SCENE_BOMBCHU_BOWLING_ALLEY) && Flags_GetSwitch(play, 0x38)) { Interface_ChangeAlpha(8); - } else if (player->stateFlags1 & 0x00800000) { + } else if (player->stateFlags1 & PLAYER_STATE1_ON_HORSE) { Interface_ChangeAlpha(12); } } else { - if (player->stateFlags1 & 0x00800000) { + if (player->stateFlags1 & PLAYER_STATE1_ON_HORSE) { Interface_ChangeAlpha(12); } } @@ -891,6 +917,12 @@ void func_80083108(PlayState* play) { if (play->interfaceCtx.unk_260 != 0) { if (gSaveContext.equips.buttonItems[0] != ITEM_FISHING_POLE) { gSaveContext.buttonStatus[0] = gSaveContext.equips.buttonItems[0]; + + // Track swordless status for restoration later + if (randoCanTrackSwordless) { + gSaveContext.buttonStatus[0] = SWORDLESS_STATUS; + } + gSaveContext.equips.buttonItems[0] = ITEM_FISHING_POLE; gSaveContext.unk_13EA = 0; Interface_LoadItemIcon1(play, 0); @@ -904,6 +936,8 @@ void func_80083108(PlayState* play) { gSaveContext.equips.buttonItems[0] = gSaveContext.buttonStatus[0]; gSaveContext.unk_13EA = 0; + Interface_RandoRestoreSwordless(); + if (gSaveContext.equips.buttonItems[0] != ITEM_NONE) { Interface_LoadItemIcon1(play, 0); } @@ -974,7 +1008,7 @@ void func_80083108(PlayState* play) { } Interface_ChangeAlpha(50); - } else if ((player->stateFlags1 & 0x00200000) || (player->stateFlags2 & PLAYER_STATE2_CRAWLING)) { + } else if ((player->stateFlags1 & PLAYER_STATE1_CLIMBING_LADDER) || (player->stateFlags2 & PLAYER_STATE2_CRAWLING)) { if (gSaveContext.buttonStatus[0] != BTN_DISABLED) { gSaveContext.buttonStatus[0] = BTN_DISABLED; gSaveContext.buttonStatus[1] = BTN_DISABLED; @@ -988,7 +1022,7 @@ void func_80083108(PlayState* play) { Interface_ChangeAlpha(50); } } else if ((gSaveContext.eventInf[0] & 0xF) == 1) { - if (player->stateFlags1 & 0x00800000) { + if (player->stateFlags1 & PLAYER_STATE1_ON_HORSE) { if ((gSaveContext.equips.buttonItems[0] != ITEM_NONE) && (gSaveContext.equips.buttonItems[0] != ITEM_BOW)) { if (gSaveContext.inventory.items[SLOT_BOW] == ITEM_NONE) { @@ -1009,6 +1043,8 @@ void func_80083108(PlayState* play) { (gSaveContext.equips.buttonItems[0] != ITEM_SWORD_BGS) && (gSaveContext.equips.buttonItems[0] != ITEM_SWORD_KNIFE)) { gSaveContext.equips.buttonItems[0] = gSaveContext.buttonStatus[0]; + + Interface_RandoRestoreSwordless(); } else { gSaveContext.buttonStatus[0] = gSaveContext.equips.buttonItems[0]; } @@ -1048,8 +1084,12 @@ void func_80083108(PlayState* play) { (gSaveContext.equips.buttonItems[0] == ITEM_BOW) || (gSaveContext.equips.buttonItems[0] == ITEM_BOMBCHU) || (gSaveContext.equips.buttonItems[0] == ITEM_NONE)) { - if ((gSaveContext.equips.buttonItems[0] != ITEM_NONE) || (gSaveContext.infTable[29] == 0)) { + if ((gSaveContext.equips.buttonItems[0] != ITEM_NONE) || (gSaveContext.infTable[29] == 0) || + randoWasSwordlessBefore) { gSaveContext.equips.buttonItems[0] = gSaveContext.buttonStatus[0]; + + Interface_RandoRestoreSwordless(); + sp28 = 1; if (gSaveContext.equips.buttonItems[0] != ITEM_NONE) { @@ -1071,8 +1111,12 @@ void func_80083108(PlayState* play) { (gSaveContext.equips.buttonItems[0] == ITEM_BOW) || (gSaveContext.equips.buttonItems[0] == ITEM_BOMBCHU) || (gSaveContext.equips.buttonItems[0] == ITEM_NONE)) { - if ((gSaveContext.equips.buttonItems[0] != ITEM_NONE) || (gSaveContext.infTable[29] == 0)) { + if ((gSaveContext.equips.buttonItems[0] != ITEM_NONE) || (gSaveContext.infTable[29] == 0) || + randoWasSwordlessBefore) { gSaveContext.equips.buttonItems[0] = gSaveContext.buttonStatus[0]; + + Interface_RandoRestoreSwordless(); + sp28 = 1; if (gSaveContext.equips.buttonItems[0] != ITEM_NONE) { @@ -1412,15 +1456,20 @@ void Inventory_SwapAgeEquipment(void) { } // When becoming adult, remove swordless flag since we'll get master sword + // (Unless Master Sword is shuffled) // Only in rando to keep swordless link bugs in vanilla - if (IS_RANDO) { + if (IS_RANDO && !Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD)) { Flags_UnsetInfTable(INFTABLE_SWORDLESS); } gSaveContext.childEquips.equipment = gSaveContext.equips.equipment; - if (gSaveContext.adultEquips.buttonItems[0] == ITEM_NONE) { - gSaveContext.equips.buttonItems[0] = ITEM_SWORD_MASTER; + if (gSaveContext.adultEquips.buttonItems[0] == ITEM_NONE && !(IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) && gSaveContext.adultEquips.equipment)) { + if (!IS_RANDO || !Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD)) { + gSaveContext.equips.buttonItems[0] = ITEM_SWORD_MASTER; + } else { + gSaveContext.equips.buttonItems[0] = ITEM_NONE; + } if (gSaveContext.inventory.items[SLOT_NUT] != ITEM_NONE) { gSaveContext.equips.buttonItems[1] = ITEM_NUT; @@ -1433,7 +1482,9 @@ void Inventory_SwapAgeEquipment(void) { gSaveContext.equips.buttonItems[3] = gSaveContext.inventory.items[SLOT_OCARINA]; gSaveContext.equips.cButtonSlots[1] = SLOT_BOMB; gSaveContext.equips.cButtonSlots[2] = SLOT_OCARINA; - gSaveContext.equips.equipment = 0x1122; + + gSaveContext.equips.equipment = (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) && + gSaveContext.equips.buttonItems[0] == ITEM_NONE) ? 0x1120 :0x1122; // Set the dpad to nothing gSaveContext.equips.buttonItems[4] = ITEM_NONE; @@ -1626,11 +1677,13 @@ void func_80084BF4(PlayState* play, u16 flag) { (gSaveContext.equips.buttonItems[0] == ITEM_BOMBCHU) || (gSaveContext.equips.buttonItems[0] == ITEM_FISHING_POLE)) { gSaveContext.equips.buttonItems[0] = gSaveContext.buttonStatus[0]; + Interface_RandoRestoreSwordless(); Interface_LoadItemIcon1(play, 0); } } else if (gSaveContext.equips.buttonItems[0] == ITEM_NONE) { if ((gSaveContext.equips.buttonItems[0] != ITEM_NONE) || (gSaveContext.infTable[29] == 0)) { gSaveContext.equips.buttonItems[0] = gSaveContext.buttonStatus[0]; + Interface_RandoRestoreSwordless(); Interface_LoadItemIcon1(play, 0); } } @@ -2556,6 +2609,13 @@ u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) { return Return_Item_Entry(giEntry, RG_NONE); } + if (item == RG_MASTER_SWORD) { + if (!CHECK_OWNED_EQUIP(EQUIP_SWORD, 1)) { + gSaveContext.inventory.equipment |= gBitFlags[1] << gEquipShifts[EQUIP_SWORD]; + } + return Return_Item_Entry(giEntry, RG_NONE); + } + temp = gSaveContext.inventory.items[slot]; osSyncPrintf("Item_Register(%d)=%d %d\n", slot, item, temp); INV_CONTENT(item) = item; @@ -5723,6 +5783,7 @@ void Interface_Draw(PlayState* play) { (gSaveContext.equips.buttonItems[0] != ITEM_SWORD_KNIFE)) { if (gSaveContext.buttonStatus[0] != BTN_ENABLED) { gSaveContext.equips.buttonItems[0] = gSaveContext.buttonStatus[0]; + Interface_RandoRestoreSwordless(); } else { gSaveContext.equips.buttonItems[0] = ITEM_NONE; } diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index 817c90ecf..112a71888 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -265,6 +265,19 @@ void GivePlayerRandoRewardRequiem(PlayState* play, RandomizerCheck check) { } } +void GivePlayerRandoRewardMasterSword(PlayState* play, RandomizerCheck check) { + Player* player = GET_PLAYER(play); + + if (gSaveContext.entranceIndex == 0x02CA && LINK_IS_ADULT && player != NULL && + !Player_InBlockingCsMode(play, player) && Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) && + !Flags_GetRandomizerInf(RAND_INF_TOT_MASTER_SWORD)) { + GetItemEntry getItemEntry = Randomizer_GetItemFromKnownCheck(check, RG_MASTER_SWORD); + GiveItemEntryWithoutActor(play, getItemEntry); + player->pendingFlag.flagID = RAND_INF_TOT_MASTER_SWORD; + player->pendingFlag.flagType = FLAG_RANDOMIZER_INF; + } +} + u8 CheckStoneCount() { u8 stoneCount = 0; @@ -1433,6 +1446,7 @@ skip: GivePlayerRandoRewardZeldaLightArrowsGift(play, RC_TOT_LIGHT_ARROWS_CUTSCENE); GivePlayerRandoRewardNocturne(play, RC_SHEIK_IN_KAKARIKO); GivePlayerRandoRewardRequiem(play, RC_SHEIK_AT_COLOSSUS); + GivePlayerRandoRewardMasterSword(play, RC_TOT_MASTER_SWORD); } } @@ -2315,17 +2329,8 @@ void Play_PerformSave(PlayState* play) { if (play != NULL && gSaveContext.fileNum != 0xFF) { Play_SaveSceneFlags(play); gSaveContext.savedSceneNum = play->sceneNum; - if (gSaveContext.temporaryWeapon) { - gSaveContext.equips.buttonItems[0] = ITEM_NONE; - GET_PLAYER(play)->currentSwordItemId = ITEM_NONE; - Inventory_ChangeEquipment(EQUIP_SWORD, PLAYER_SWORD_NONE); - Save_SaveFile(); - gSaveContext.equips.buttonItems[0] = ITEM_SWORD_KOKIRI; - GET_PLAYER(play)->currentSwordItemId = ITEM_SWORD_KOKIRI; - Inventory_ChangeEquipment(EQUIP_SWORD, PLAYER_SWORD_KOKIRI); - } else { - Save_SaveFile(); - } + Save_SaveFile(); + uint8_t triforceHuntCompleted = IS_RANDO && gSaveContext.triforcePiecesCollected == Randomizer_GetSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED) && diff --git a/soh/src/code/z_sram.c b/soh/src/code/z_sram.c index 1612c0fdc..225b29dc7 100644 --- a/soh/src/code/z_sram.c +++ b/soh/src/code/z_sram.c @@ -170,10 +170,12 @@ void Sram_OpenSave() { // check for owning kokiri sword.. to restore master sword? bug or debug feature? if (LINK_AGE_IN_YEARS == YEARS_ADULT && !CHECK_OWNED_EQUIP(EQUIP_SWORD, 1)) { - gSaveContext.inventory.equipment |= gBitFlags[1] << gEquipShifts[EQUIP_SWORD]; - gSaveContext.equips.buttonItems[0] = ITEM_SWORD_MASTER; - gSaveContext.equips.equipment &= ~0xF; - gSaveContext.equips.equipment |= 2; + if (!IS_RANDO || !Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD)) { + gSaveContext.inventory.equipment |= gBitFlags[1] << gEquipShifts[EQUIP_SWORD]; + gSaveContext.equips.buttonItems[0] = ITEM_SWORD_MASTER; + gSaveContext.equips.equipment &= ~0xF; + gSaveContext.equips.equipment |= 2; + } } if (!(IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_ADULT_TRADE))) { -- cgit v1.2.3