From b424e4d57ed34ce9943d6bbe2d2b5703c5f4ca2e Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Mon, 8 Jun 2026 18:26:24 +0200 Subject: Sort some files into their correct folder (#6689) --- soh/soh/Enhancements/ArrowCycle.cpp | 209 --- soh/soh/Enhancements/AssignableTunicsAndBoots.cpp | 196 --- soh/soh/Enhancements/BlueFireArrows.cpp | 55 - soh/soh/Enhancements/DisableHeatHaze.cpp | 8 - soh/soh/Enhancements/DisableJabuWobble.cpp | 8 - soh/soh/Enhancements/DisableKokiriDrawDistance.cpp | 10 - soh/soh/Enhancements/DisableSandstorm.cpp | 16 - soh/soh/Enhancements/Graphics/DisableHeatHaze.cpp | 8 + .../Enhancements/Graphics/DisableJabuWobble.cpp | 8 + .../Graphics/DisableKokiriDrawDistance.cpp | 10 + soh/soh/Enhancements/Graphics/DisableSandstorm.cpp | 16 + .../Graphics/RemoveSpinAttackDarkness.cpp | 45 + soh/soh/Enhancements/InjectItemCounts.cpp | 88 -- soh/soh/Enhancements/ItemUnequip.cpp | 87 -- soh/soh/Enhancements/Items/ArrowCycle.cpp | 209 +++ .../Items/AssignableTunicsAndBoots.cpp | 196 +++ soh/soh/Enhancements/Items/BlueFireArrows.cpp | 55 + soh/soh/Enhancements/Items/InjectItemCounts.cpp | 88 ++ soh/soh/Enhancements/Items/ItemUnequip.cpp | 87 ++ soh/soh/Enhancements/Items/MaskSelect.cpp | 23 + soh/soh/Enhancements/Items/RemoteBombchu.cpp | 206 +++ soh/soh/Enhancements/Items/SunlightArrows.cpp | 97 ++ .../Items/UnsheatheWithoutSlashing.cpp | 18 + soh/soh/Enhancements/MaskSelect.cpp | 23 - soh/soh/Enhancements/NoSkulltulaFreeze.cpp | 31 - soh/soh/Enhancements/QoL/NoSkulltulaFreeze.cpp | 31 + soh/soh/Enhancements/QoL/RebottleBlueFire.cpp | 25 + soh/soh/Enhancements/QoL/ResetHotKey.cpp | 37 + soh/soh/Enhancements/QoL/ReworkedTargeting.cpp | 57 + soh/soh/Enhancements/RebottleBlueFire.cpp | 25 - soh/soh/Enhancements/RemoteBombchu.cpp | 206 --- soh/soh/Enhancements/RemoveSpinAttackDarkness.cpp | 45 - soh/soh/Enhancements/ResetHotKey.cpp | 37 - soh/soh/Enhancements/ReworkedTargeting.cpp | 57 - soh/soh/Enhancements/SkipAmyPuzzle.cpp | 14 - soh/soh/Enhancements/SunlightArrows.cpp | 97 -- soh/soh/Enhancements/TimeSavers/SkipAmyPuzzle.cpp | 14 + .../TimeSavers/timesaver_hook_handlers.cpp | 1437 ++++++++++++++++++++ soh/soh/Enhancements/UnsheatheWithoutSlashing.cpp | 18 - soh/soh/Enhancements/timesaver_hook_handlers.cpp | 1437 -------------------- 40 files changed, 2667 insertions(+), 2667 deletions(-) delete mode 100644 soh/soh/Enhancements/ArrowCycle.cpp delete mode 100644 soh/soh/Enhancements/AssignableTunicsAndBoots.cpp delete mode 100644 soh/soh/Enhancements/BlueFireArrows.cpp delete mode 100644 soh/soh/Enhancements/DisableHeatHaze.cpp delete mode 100644 soh/soh/Enhancements/DisableJabuWobble.cpp delete mode 100644 soh/soh/Enhancements/DisableKokiriDrawDistance.cpp delete mode 100644 soh/soh/Enhancements/DisableSandstorm.cpp create mode 100644 soh/soh/Enhancements/Graphics/DisableHeatHaze.cpp create mode 100644 soh/soh/Enhancements/Graphics/DisableJabuWobble.cpp create mode 100644 soh/soh/Enhancements/Graphics/DisableKokiriDrawDistance.cpp create mode 100644 soh/soh/Enhancements/Graphics/DisableSandstorm.cpp create mode 100644 soh/soh/Enhancements/Graphics/RemoveSpinAttackDarkness.cpp delete mode 100644 soh/soh/Enhancements/InjectItemCounts.cpp delete mode 100644 soh/soh/Enhancements/ItemUnequip.cpp create mode 100644 soh/soh/Enhancements/Items/ArrowCycle.cpp create mode 100644 soh/soh/Enhancements/Items/AssignableTunicsAndBoots.cpp create mode 100644 soh/soh/Enhancements/Items/BlueFireArrows.cpp create mode 100644 soh/soh/Enhancements/Items/InjectItemCounts.cpp create mode 100644 soh/soh/Enhancements/Items/ItemUnequip.cpp create mode 100644 soh/soh/Enhancements/Items/MaskSelect.cpp create mode 100644 soh/soh/Enhancements/Items/RemoteBombchu.cpp create mode 100644 soh/soh/Enhancements/Items/SunlightArrows.cpp create mode 100644 soh/soh/Enhancements/Items/UnsheatheWithoutSlashing.cpp delete mode 100644 soh/soh/Enhancements/MaskSelect.cpp delete mode 100644 soh/soh/Enhancements/NoSkulltulaFreeze.cpp create mode 100644 soh/soh/Enhancements/QoL/NoSkulltulaFreeze.cpp create mode 100644 soh/soh/Enhancements/QoL/RebottleBlueFire.cpp create mode 100644 soh/soh/Enhancements/QoL/ResetHotKey.cpp create mode 100644 soh/soh/Enhancements/QoL/ReworkedTargeting.cpp delete mode 100644 soh/soh/Enhancements/RebottleBlueFire.cpp delete mode 100644 soh/soh/Enhancements/RemoteBombchu.cpp delete mode 100644 soh/soh/Enhancements/RemoveSpinAttackDarkness.cpp delete mode 100644 soh/soh/Enhancements/ResetHotKey.cpp delete mode 100644 soh/soh/Enhancements/ReworkedTargeting.cpp delete mode 100644 soh/soh/Enhancements/SkipAmyPuzzle.cpp delete mode 100644 soh/soh/Enhancements/SunlightArrows.cpp create mode 100644 soh/soh/Enhancements/TimeSavers/SkipAmyPuzzle.cpp create mode 100644 soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp delete mode 100644 soh/soh/Enhancements/UnsheatheWithoutSlashing.cpp delete mode 100644 soh/soh/Enhancements/timesaver_hook_handlers.cpp diff --git a/soh/soh/Enhancements/ArrowCycle.cpp b/soh/soh/Enhancements/ArrowCycle.cpp deleted file mode 100644 index e70d5dee0..000000000 --- a/soh/soh/Enhancements/ArrowCycle.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "soh/ShipInit.hpp" - -extern "C" { -#include "macros.h" -#include "variables.h" -#include "functions.h" -#include "overlays/actors/ovl_En_Arrow/z_en_arrow.h" - -s32 func_808351D4(Player* thisx, PlayState* play); // Arrow nocked -void EnArrow_Init(Actor* thisx, PlayState* play); - -extern PlayState* gPlayState; -} - -#define CVAR_ARROW_CYCLE_NAME CVAR_ENHANCEMENT("BowArrowCycle") -#define CVAR_ARROW_CYCLE_DEFAULT 0 -#define CVAR_ARROW_CYCLE_VALUE CVarGetInteger(CVAR_ARROW_CYCLE_NAME, CVAR_ARROW_CYCLE_DEFAULT) - -static const s16 sMagicArrowCosts[] = { 4, 4, 8 }; - -static const PlayerItemAction sArrowCycleOrder[] = { - PLAYER_IA_BOW, - PLAYER_IA_BOW_FIRE, - PLAYER_IA_BOW_ICE, - PLAYER_IA_BOW_LIGHT, -}; - -static bool IsHoldingBow(Player* player) { - return player->heldItemAction >= PLAYER_IA_BOW && player->heldItemAction <= PLAYER_IA_BOW_LIGHT; -} - -static bool IsHoldingMagicBow(Player* player) { - return player->heldItemAction >= PLAYER_IA_BOW_FIRE && player->heldItemAction <= PLAYER_IA_BOW_LIGHT; -} - -static bool IsAimingBow(Player* player) { - return IsHoldingBow(player) && ((player->unk_6AD == 2) || (player->upperActionFunc == func_808351D4)); -} - -static bool HasArrowType(PlayerItemAction itemAction) { - switch (itemAction) { - case PLAYER_IA_BOW: - return true; - case PLAYER_IA_BOW_FIRE: - return INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE; - case PLAYER_IA_BOW_ICE: - return INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE; - case PLAYER_IA_BOW_LIGHT: - return INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT; - default: - return false; - } -} - -static s32 GetBowItemForArrow(PlayerItemAction itemAction) { - switch (itemAction) { - case PLAYER_IA_BOW_FIRE: - return ITEM_BOW_ARROW_FIRE; - case PLAYER_IA_BOW_ICE: - return ITEM_BOW_ARROW_ICE; - case PLAYER_IA_BOW_LIGHT: - return ITEM_BOW_ARROW_LIGHT; - default: - return ITEM_BOW; - } -} - -static ArrowType GetArrowTypeForArrow(s8 itemAction) { - switch (itemAction) { - case PLAYER_IA_BOW_FIRE: - return ARROW_FIRE; - case PLAYER_IA_BOW_ICE: - return ARROW_ICE; - case PLAYER_IA_BOW_LIGHT: - return ARROW_LIGHT; - default: - return ARROW_NORMAL; - } -} - -static bool CanCycleArrows() { - Player* player = GET_PLAYER(gPlayState); - - return (LINK_IS_ADULT || CVarGetInteger(CVAR_CHEAT("TimelessEquipment"), 0)) && !gSaveContext.minigameState && - gPlayState->sceneNum != SCENE_SHOOTING_GALLERY && !(player->stateFlags1 & PLAYER_STATE1_ON_HORSE) && - player->rideActor == NULL && INV_CONTENT(SLOT_BOW) == ITEM_BOW && - (INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE || INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE || - INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT); -} - -static s8 GetNextArrowType(s8 currentArrowType) { - int currentIndex = 0; - for (int i = 0; i < (int)ARRAY_COUNT(sArrowCycleOrder); i++) { - if (sArrowCycleOrder[i] == currentArrowType) { - currentIndex = i; - break; - } - } - - for (int offset = 1; offset <= (int)ARRAY_COUNT(sArrowCycleOrder); offset++) { - int nextIndex = (currentIndex + offset) % ARRAY_COUNT(sArrowCycleOrder); - if (HasArrowType(sArrowCycleOrder[nextIndex])) { - return sArrowCycleOrder[nextIndex]; - } - } - - return PLAYER_IA_BOW; -} - -static void UpdateButtonAlpha(s16 flashAlpha, bool isButtonBow, u16* buttonAlpha) { - if (isButtonBow) { - *buttonAlpha = flashAlpha; - } -} - -static void UpdateEquippedBow(PlayState* play, s8 arrowType) { - s32 bowItem = GetBowItemForArrow((PlayerItemAction)arrowType); - bool dpadEnabled = CVarGetInteger(CVAR_ENHANCEMENT("DpadEquips"), 0); - s32 maxButton = dpadEnabled ? 7 : 3; - - for (s32 i = 1; i <= maxButton; i++) { - if ((gSaveContext.equips.buttonItems[i] == ITEM_BOW) || - (gSaveContext.equips.buttonItems[i] >= ITEM_BOW_ARROW_FIRE && - gSaveContext.equips.buttonItems[i] <= ITEM_BOW_ARROW_LIGHT)) { - gSaveContext.equips.buttonItems[i] = bowItem; - gSaveContext.equips.cButtonSlots[i - 1] = SLOT_BOW; - - if (i <= 3) { - Interface_LoadItemIcon1(play, i); - } - - gSaveContext.buttonStatus[i] = BTN_ENABLED; - } - } -} - -bool ArrowCycleMain() { - if (gPlayState == nullptr || !CanCycleArrows()) { - return false; - } - - Player* player = GET_PLAYER(gPlayState); - if (player->heldActor != NULL && player->heldActor->id == ACTOR_EN_ARROW) { - if (IsHoldingMagicBow(player) && gSaveContext.magicState != MAGIC_STATE_IDLE && player->heldActor == NULL) { - Audio_PlaySoundGeneral(NA_SE_SY_ERROR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, - &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); - return true; - } - - // reset magic state to IDLE before cycling to prevent error sound - gSaveContext.magicState = MAGIC_STATE_IDLE; - - s8 nextArrow = GetNextArrowType(player->heldItemAction); - player->heldItemAction = nextArrow; - player->itemAction = nextArrow; - Actor* arrow = player->heldActor; - - if (arrow->child != NULL) { - Actor_Kill(arrow->child); - arrow->child = NULL; - } - arrow->params = GetArrowTypeForArrow(nextArrow); - EnArrow_Init(arrow, gPlayState); - UpdateEquippedBow(gPlayState, nextArrow); - return true; - } - return false; -} - -void RegisterArrowCycle() { - // suppress shield input when R is held while aiming to allow arrow cycling - COND_VB_SHOULD(VB_EXECUTE_PLAYER_ACTION_FUNC, CVAR_ARROW_CYCLE_VALUE, { - Player* player = (Player*)va_arg(args, void*); - Input* input = (Input*)va_arg(args, void*); - if (IsAimingBow(player) && CHECK_BTN_ANY(input->press.button, BTN_R)) { - if (ArrowCycleMain()) { - input->cur.button &= ~BTN_R; - input->press.button &= ~BTN_R; - } - } - }); - - // don't consume magic on draw, but check if we have enough to fire - COND_VB_SHOULD(VB_PLAYER_ARROW_MAGIC_CONSUMPTION, CVAR_ARROW_CYCLE_VALUE, { - Player* player = va_arg(args, Player*); - int32_t magicArrowType = va_arg(args, int32_t); - int32_t* arrowType = va_arg(args, int32_t*); - - if (gSaveContext.magic < sMagicArrowCosts[magicArrowType]) { - *arrowType = ARROW_NORMAL; - } else { - *should = false; - } - }); - - COND_VB_SHOULD(VB_EN_ARROW_MAGIC_CONSUMPTION, CVAR_ARROW_CYCLE_VALUE, { - EnArrow* arrow = va_arg(args, EnArrow*); - - if (arrow->actor.params < ARROW_FIRE || arrow->actor.params > ARROW_LIGHT) { - return; - } - - int32_t magicArrowType = arrow->actor.params - ARROW_FIRE; - Magic_RequestChange(gPlayState, sMagicArrowCosts[magicArrowType], MAGIC_CONSUME_NOW); - }); -} - -static RegisterShipInitFunc initFunc(RegisterArrowCycle, { CVAR_ARROW_CYCLE_NAME }); diff --git a/soh/soh/Enhancements/AssignableTunicsAndBoots.cpp b/soh/soh/Enhancements/AssignableTunicsAndBoots.cpp deleted file mode 100644 index 535cd1400..000000000 --- a/soh/soh/Enhancements/AssignableTunicsAndBoots.cpp +++ /dev/null @@ -1,196 +0,0 @@ -#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "soh/ShipInit.hpp" - -extern "C" { -#include "macros.h" -#include "variables.h" - -extern s32 Player_GetItemOnButton(PlayState*, s32); -extern void Inventory_ChangeEquipment(s16, u16); -extern void Player_SetEquipmentData(PlayState*, Player*); -extern void func_808328EC(Player*, u16); -extern PlayState* gPlayState; -} - -static u16 sItemButtons[] = { BTN_B, BTN_CLEFT, BTN_CDOWN, BTN_CRIGHT, BTN_DUP, BTN_DDOWN, BTN_DLEFT, BTN_DRIGHT }; - -static void UseTunicBoots(Player* player, PlayState* play, Input* input) { - // Boots and tunics equip despite state - if (player->stateFlags1 & (PLAYER_STATE1_INPUT_DISABLED | PLAYER_STATE1_IN_ITEM_CS | PLAYER_STATE1_IN_CUTSCENE | - PLAYER_STATE1_TALKING | PLAYER_STATE1_DEAD) || - player->stateFlags2 & PLAYER_STATE2_OCARINA_PLAYING) { - return; - } - - s32 item = ITEM_NONE; - for (s32 i = 0; i < ARRAY_COUNT(sItemButtons); i++) { - if (CHECK_BTN_ALL(input->press.button, sItemButtons[i])) { - item = Player_GetItemOnButton(play, i); - break; - } - } - - if (item >= ITEM_SHIELD_DEKU && item <= ITEM_BOOTS_HOVER) { - if (item >= ITEM_BOOTS_KOKIRI) { - u16 bootsValue = item - ITEM_BOOTS_KOKIRI + 1; - if (CUR_EQUIP_VALUE(EQUIP_TYPE_BOOTS) == bootsValue) { - Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_KOKIRI); - } else { - Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, bootsValue); - } - Player_SetEquipmentData(play, player); - func_808328EC(player, CUR_EQUIP_VALUE(EQUIP_TYPE_BOOTS) == EQUIP_VALUE_BOOTS_IRON ? NA_SE_PL_WALK_HEAVYBOOTS - : NA_SE_PL_CHANGE_ARMS); - } else if (item >= ITEM_TUNIC_KOKIRI) { - u16 tunicValue = item - ITEM_TUNIC_KOKIRI + 1; - if (CUR_EQUIP_VALUE(EQUIP_TYPE_TUNIC) == tunicValue) { - Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_KOKIRI); - } else { - Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, tunicValue); - } - Player_SetEquipmentData(play, player); - func_808328EC(player, NA_SE_PL_CHANGE_ARMS); - } else { - u16 shieldValue = item - ITEM_SHIELD_DEKU + 1; - if (CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) != shieldValue) { - Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, shieldValue); - Player_SetEquipmentData(play, player); - func_808328EC(player, NA_SE_PL_CHANGE_ARMS); - } - } - } -} - -static void ClearAssignedTunicsBoots(int32_t unused = 0) { - for (int32_t buttonIndex = 0; buttonIndex < 8; buttonIndex++) { - int32_t item = gSaveContext.equips.buttonItems[buttonIndex]; - - if (item >= ITEM_SHIELD_DEKU && item <= ITEM_BOOTS_HOVER) { - gSaveContext.equips.buttonItems[buttonIndex] = ITEM_NONE; - } - } -} - -static void ClearDeletedAssignedEquipment(int16_t equipmentType, uint16_t equipValue) { - ItemID itemToRemove = ITEM_NONE; - - if (equipmentType == EQUIP_TYPE_TUNIC) { - switch (equipValue) { - case EQUIP_VALUE_TUNIC_KOKIRI: - break; - case EQUIP_VALUE_TUNIC_GORON: - itemToRemove = ITEM_TUNIC_GORON; - break; - case EQUIP_VALUE_TUNIC_ZORA: - itemToRemove = ITEM_TUNIC_ZORA; - break; - } - } else if (equipmentType == EQUIP_TYPE_SHIELD) { - switch (equipValue) { - case EQUIP_VALUE_SHIELD_DEKU: - itemToRemove = ITEM_SHIELD_DEKU; - break; - case EQUIP_VALUE_SHIELD_HYLIAN: - itemToRemove = ITEM_SHIELD_HYLIAN; - break; - case EQUIP_VALUE_SHIELD_MIRROR: - itemToRemove = ITEM_SHIELD_MIRROR; - break; - } - } - - if (itemToRemove == ITEM_NONE) { - return; - } - - for (int i = 1; i < ARRAY_COUNT(gSaveContext.equips.buttonItems); i++) { - if (gSaveContext.equips.buttonItems[i] == itemToRemove) { - gSaveContext.equips.buttonItems[i] = ITEM_NONE; - gSaveContext.equips.cButtonSlots[i - 1] = SLOT_NONE; - } - } -} - -#define CVAR_TUNICBOOTS_NAME CVAR_ENHANCEMENT("AssignableTunicsAndBoots") -#define CVAR_TUNICBOOTS_DEFAULT 0 -#define CVAR_TUNICBOOTS_VALUE CVarGetInteger(CVAR_TUNICBOOTS_NAME, CVAR_TUNICBOOTS_DEFAULT) -#define CVAR_TUNICBOOTS_SET (CVAR_TUNICBOOTS_VALUE != CVAR_TUNICBOOTS_DEFAULT) - -static void RegisterAssignableTunicsBoots() { - // make sure we don't change our held/equipped item when changing shield/tunic/boots - COND_VB_SHOULD(VB_CHANGE_HELD_ITEM_AND_USE_ITEM, CVAR_TUNICBOOTS_SET, { - int32_t item = va_arg(args, int32_t); - - if (item >= ITEM_SHIELD_DEKU && item <= ITEM_BOOTS_HOVER) { - *should = false; - } - }); - - // make sure we don't crash because tunics/boots don't have assoicated item actions - COND_VB_SHOULD(VB_ITEM_ACTION_BE_NONE, CVAR_TUNICBOOTS_SET, { - int32_t item = va_arg(args, int32_t); - - if (item >= ITEM_SHIELD_DEKU && item <= ITEM_BOOTS_HOVER) { - *should = true; - } - }); - - // don't throw items when the pressed button is a shield, tunic or boots - COND_VB_SHOULD(VB_THROW_OR_PUT_DOWN_HELD_ITEM, CVAR_TUNICBOOTS_SET, { - // if the vanilla condition doesn't want us to throw/put down the item, early return - if (!*should) { - return; - } - - Input* input = va_arg(args, Input*); - - s32 item = ITEM_NONE; - for (s32 i = 0; i < ARRAY_COUNT(sItemButtons); i++) { - if (CHECK_BTN_ALL(input->press.button, sItemButtons[i])) { - item = Player_GetItemOnButton(gPlayState, i); - break; - } - } - - if (item >= ITEM_SHIELD_DEKU && item <= ITEM_BOOTS_HOVER) { - *should = false; - } - }); - - // do something when the player presses a button to use the tunics/boots - COND_VB_SHOULD(VB_EXECUTE_PLAYER_ACTION_FUNC, CVAR_TUNICBOOTS_SET, { - // if the vanilla condition doesn't want us to run the actionFunc, don't do any of this - if (!*should) { - return; - } - - Player* player = va_arg(args, Player*); - - // if we're not dealing with the actual player, we're probably - // dealing with dark link, don't do any tunics/boots stuff, just - // run the vanilla action func - if (player->actor.id != ACTOR_PLAYER) { - return; - } - - *should = false; - - Input* input = va_arg(args, Input*); - - player->actionFunc(player, gPlayState); - UseTunicBoots(player, gPlayState, input); - }); - - // remove assigned equipment when it gets deleted - COND_HOOK(OnEquipmentDelete, CVAR_TUNICBOOTS_SET, ClearDeletedAssignedEquipment); - - // clear out assigned tunics/boots when the enhancement is toggled off - if (GameInteractor::IsSaveLoaded(true) && !CVAR_TUNICBOOTS_SET) { - ClearAssignedTunicsBoots(); - } - - // clear out assigned tunics/boots when loading a save with enhancement turned off - COND_HOOK(OnLoadGame, !CVAR_TUNICBOOTS_SET, ClearAssignedTunicsBoots); -} - -static RegisterShipInitFunc initFunc(RegisterAssignableTunicsBoots, { CVAR_TUNICBOOTS_NAME }); diff --git a/soh/soh/Enhancements/BlueFireArrows.cpp b/soh/soh/Enhancements/BlueFireArrows.cpp deleted file mode 100644 index 985767853..000000000 --- a/soh/soh/Enhancements/BlueFireArrows.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "soh/Enhancements/randomizer/SeedContext.h" -#include "soh/ShipInit.hpp" - -extern "C" { -#include "overlays/actors/ovl_Bg_Breakwall/z_bg_breakwall.h" -#include "overlays/actors/ovl_Bg_Ice_Shelter/z_bg_ice_shelter.h" - -extern PlayState* gPlayState; -} - -static void UpdateBlueFireCollidersBgBreakwall(void* actorPtr) { - BgBreakwall* thisx = (BgBreakwall*)actorPtr; - thisx->collider.info.bumper.dmgFlags |= DMG_ARROW_ICE; -} - -static void UpdateBlueFireCollidersBgIceShelter(void* actorPtr) { - BgIceShelter* thisx = (BgIceShelter*)actorPtr; - thisx->cylinder1.base.acFlags |= AC_TYPE_PLAYER; - thisx->cylinder1.info.bumper.dmgFlags |= DMG_ARROW_ICE; - thisx->cylinder2.base.acFlags |= AC_TYPE_PLAYER; - thisx->cylinder2.info.bumper.dmgFlags |= DMG_ARROW_ICE; -} - -static bool CheckAC(Actor* ac) { - return ac != NULL && ac->id == ACTOR_EN_ARROW && ac->child != NULL && ac->child->id == ACTOR_ARROW_ICE; -} - -void RegisterBlueFireArrowsHooks() { - bool shouldRegister = - CVarGetInteger(CVAR_ENHANCEMENT("BlueFireArrows"), 0) || (IS_RANDO && RAND_GET_OPTION(RSK_BLUE_FIRE_ARROWS)); - - COND_ID_HOOK(OnActorInit, ACTOR_BG_BREAKWALL, shouldRegister, UpdateBlueFireCollidersBgBreakwall); - COND_ID_HOOK(OnActorInit, ACTOR_BG_ICE_SHELTER, shouldRegister, UpdateBlueFireCollidersBgIceShelter); - - // fix bug where cylinder2 never checks acFlags - COND_VB_SHOULD(VB_BG_ICE_SHELTER_HIT, shouldRegister, { - BgIceShelter* thisx = va_arg(args, BgIceShelter*); - - if (thisx->cylinder2.base.acFlags & AC_HIT) { - thisx->cylinder2.base.acFlags &= ~AC_HIT; - *should = true; - } - }); - - COND_VB_SHOULD(VB_BG_ICE_SHELTER_MELT, shouldRegister, { - BgIceShelter* thisx = va_arg(args, BgIceShelter*); - - if (CheckAC(thisx->cylinder1.base.ac) || CheckAC(thisx->cylinder2.base.ac)) { - *should = true; - } - }); -} - -static RegisterShipInitFunc initFunc(RegisterBlueFireArrowsHooks, { "IS_RANDO", CVAR_ENHANCEMENT("BlueFireArrows") }); diff --git a/soh/soh/Enhancements/DisableHeatHaze.cpp b/soh/soh/Enhancements/DisableHeatHaze.cpp deleted file mode 100644 index 0f49725cb..000000000 --- a/soh/soh/Enhancements/DisableHeatHaze.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/ShipInit.hpp" - -void RegisterDisableHeatHaze() { - COND_VB_SHOULD(VB_HOT_ROOM_DISTORTION, CVarGetInteger(CVAR_SETTING("A11yNoHeatHaze"), 0), { *should = false; }); -} - -static RegisterShipInitFunc initFunc(RegisterDisableHeatHaze, { CVAR_SETTING("A11yNoHeatHaze") }); diff --git a/soh/soh/Enhancements/DisableJabuWobble.cpp b/soh/soh/Enhancements/DisableJabuWobble.cpp deleted file mode 100644 index 083e80c71..000000000 --- a/soh/soh/Enhancements/DisableJabuWobble.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/ShipInit.hpp" - -void RegisterDisableJabuWobble() { - COND_VB_SHOULD(VB_JABU_WOBBLE, CVarGetInteger(CVAR_SETTING("A11yNoJabuWobble"), 0), { *should = false; }); -} - -static RegisterShipInitFunc initFunc(RegisterDisableJabuWobble, { CVAR_SETTING("A11yNoJabuWobble") }); diff --git a/soh/soh/Enhancements/DisableKokiriDrawDistance.cpp b/soh/soh/Enhancements/DisableKokiriDrawDistance.cpp deleted file mode 100644 index 0486035db..000000000 --- a/soh/soh/Enhancements/DisableKokiriDrawDistance.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/ShipInit.hpp" - -void RegisterDisableKokiriDrawDistance() { - COND_VB_SHOULD(VB_FADE_KOKIRI, CVarGetInteger(CVAR_ENHANCEMENT("DisableKokiriDrawDistance"), 0), - { *should = false; }); -} - -static RegisterShipInitFunc initFunc(RegisterDisableKokiriDrawDistance, - { CVAR_ENHANCEMENT("DisableKokiriDrawDistance") }); diff --git a/soh/soh/Enhancements/DisableSandstorm.cpp b/soh/soh/Enhancements/DisableSandstorm.cpp deleted file mode 100644 index a13fd4fb5..000000000 --- a/soh/soh/Enhancements/DisableSandstorm.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/ShipInit.hpp" - -extern "C" PlayState* gPlayState; - -void DisableSandstormAfterTransition(int16_t sceneNum) { - if (sceneNum == SCENE_HAUNTED_WASTELAND) { - gPlayState->envCtx.sandstormState = SANDSTORM_OFF; - } -} - -void RegisterDisableSandstorm() { - COND_HOOK(OnTransitionEnd, CVarGetInteger(CVAR_CHEAT("DisableSandstorm"), 0), DisableSandstormAfterTransition); -} - -static RegisterShipInitFunc initFunc(RegisterDisableSandstorm, { CVAR_CHEAT("DisableSandstorm") }); diff --git a/soh/soh/Enhancements/Graphics/DisableHeatHaze.cpp b/soh/soh/Enhancements/Graphics/DisableHeatHaze.cpp new file mode 100644 index 000000000..0f49725cb --- /dev/null +++ b/soh/soh/Enhancements/Graphics/DisableHeatHaze.cpp @@ -0,0 +1,8 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" + +void RegisterDisableHeatHaze() { + COND_VB_SHOULD(VB_HOT_ROOM_DISTORTION, CVarGetInteger(CVAR_SETTING("A11yNoHeatHaze"), 0), { *should = false; }); +} + +static RegisterShipInitFunc initFunc(RegisterDisableHeatHaze, { CVAR_SETTING("A11yNoHeatHaze") }); diff --git a/soh/soh/Enhancements/Graphics/DisableJabuWobble.cpp b/soh/soh/Enhancements/Graphics/DisableJabuWobble.cpp new file mode 100644 index 000000000..083e80c71 --- /dev/null +++ b/soh/soh/Enhancements/Graphics/DisableJabuWobble.cpp @@ -0,0 +1,8 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" + +void RegisterDisableJabuWobble() { + COND_VB_SHOULD(VB_JABU_WOBBLE, CVarGetInteger(CVAR_SETTING("A11yNoJabuWobble"), 0), { *should = false; }); +} + +static RegisterShipInitFunc initFunc(RegisterDisableJabuWobble, { CVAR_SETTING("A11yNoJabuWobble") }); diff --git a/soh/soh/Enhancements/Graphics/DisableKokiriDrawDistance.cpp b/soh/soh/Enhancements/Graphics/DisableKokiriDrawDistance.cpp new file mode 100644 index 000000000..0486035db --- /dev/null +++ b/soh/soh/Enhancements/Graphics/DisableKokiriDrawDistance.cpp @@ -0,0 +1,10 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" + +void RegisterDisableKokiriDrawDistance() { + COND_VB_SHOULD(VB_FADE_KOKIRI, CVarGetInteger(CVAR_ENHANCEMENT("DisableKokiriDrawDistance"), 0), + { *should = false; }); +} + +static RegisterShipInitFunc initFunc(RegisterDisableKokiriDrawDistance, + { CVAR_ENHANCEMENT("DisableKokiriDrawDistance") }); diff --git a/soh/soh/Enhancements/Graphics/DisableSandstorm.cpp b/soh/soh/Enhancements/Graphics/DisableSandstorm.cpp new file mode 100644 index 000000000..a13fd4fb5 --- /dev/null +++ b/soh/soh/Enhancements/Graphics/DisableSandstorm.cpp @@ -0,0 +1,16 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" + +extern "C" PlayState* gPlayState; + +void DisableSandstormAfterTransition(int16_t sceneNum) { + if (sceneNum == SCENE_HAUNTED_WASTELAND) { + gPlayState->envCtx.sandstormState = SANDSTORM_OFF; + } +} + +void RegisterDisableSandstorm() { + COND_HOOK(OnTransitionEnd, CVarGetInteger(CVAR_CHEAT("DisableSandstorm"), 0), DisableSandstormAfterTransition); +} + +static RegisterShipInitFunc initFunc(RegisterDisableSandstorm, { CVAR_CHEAT("DisableSandstorm") }); diff --git a/soh/soh/Enhancements/Graphics/RemoveSpinAttackDarkness.cpp b/soh/soh/Enhancements/Graphics/RemoveSpinAttackDarkness.cpp new file mode 100644 index 000000000..6349a7818 --- /dev/null +++ b/soh/soh/Enhancements/Graphics/RemoveSpinAttackDarkness.cpp @@ -0,0 +1,45 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "src/overlays/actors/ovl_En_M_Thunder/z_en_m_thunder.h" +} + +void Custom_EnMThunder_Update(Actor* thisx, PlayState* play) { + EnMThunder* enMThunder = (EnMThunder*)thisx; + f32 blueRadius; + s32 redGreen; + + // If thunder effect doesn't exist (aka player doesn't have magic), + // don't do anything. + if (enMThunder->actionFunc == nullptr) { + return; + } + + enMThunder->actionFunc(enMThunder, play); + // don't call this part, it's what makes the spin attack darkness happen + // func_80A9F314(play, this->dimmingIntensity); + blueRadius = enMThunder->spinAttackTimer; + redGreen = (u32)(blueRadius * 255.0f) & 0xFF; + Lights_PointNoGlowSetInfo(&enMThunder->lightInfo, enMThunder->actor.world.pos.x, enMThunder->actor.world.pos.y, + enMThunder->actor.world.pos.z, redGreen, redGreen, (u32)(blueRadius * 100.0f), + (s32)(blueRadius * 800.0f)); +} + +void OnEnMThunderInitReplaceUpdateWithCustom(void* thunder) { + EnMThunder* enMThunder = (EnMThunder*)thunder; + enMThunder->actor.update = Custom_EnMThunder_Update; +} + +#define CVAR_REMOVESPINATTACKDARKNESS_NAME CVAR_ENHANCEMENT("RemoveSpinAttackDarkness") +#define CVAR_REMOVESPINATTACKDARKNESS_DEFAULT 0 +#define CVAR_REMOVESPINATTACKDARKNESS_VALUE \ + CVarGetInteger(CVAR_REMOVESPINATTACKDARKNESS_NAME, CVAR_REMOVESPINATTACKDARKNESS_DEFAULT) + +void RegisterCustomEnMThunderUpdate() { + COND_ID_HOOK(OnActorInit, ACTOR_EN_M_THUNDER, + CVAR_REMOVESPINATTACKDARKNESS_VALUE != CVAR_REMOVESPINATTACKDARKNESS_DEFAULT, + OnEnMThunderInitReplaceUpdateWithCustom); +} + +static RegisterShipInitFunc initFunc(RegisterCustomEnMThunderUpdate, { CVAR_REMOVESPINATTACKDARKNESS_NAME }); diff --git a/soh/soh/Enhancements/InjectItemCounts.cpp b/soh/soh/Enhancements/InjectItemCounts.cpp deleted file mode 100644 index e7e4bdcd0..000000000 --- a/soh/soh/Enhancements/InjectItemCounts.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include -#include "soh/Enhancements/randomizer/randomizer.h" - -extern "C" { -#include "variables.h" -} - -void BuildSkulltulaMessage(uint16_t* textId, bool* loadFromMessageTable) { - CustomMessage msg = - CustomMessage("You got a %rGold Skulltula Token%w!&You've collected %r[[gsCount]]%w tokens&in total!", - "Ein %rGoldenes Skulltula-Symbol%w!&Du hast nun insgesamt %r[[gsCount]]&%wGoldene " - "Skulltula-Symbole&gesammelt!", - "Vous obtenez un %rSymbole de&Skulltula d'or%w! Vous avez&collecté %r[[gsCount]]%w symboles en " - "tout!", - TEXTBOX_TYPE_BLUE); - // The freeze text cannot be manually dismissed and must be auto-dismissed. - // This is fine and even wanted when skull tokens are not shuffled, but when - // when they are shuffled we don't want to be able to manually dismiss the box. - // Otherwise if we get a token from a chest or an NPC we get stuck in the ItemGet - // animation until the text box auto-dismisses. - // RANDOTODO: Implement a way to determine if an item came from a skulltula and - // inject the auto-dismiss control code if it did. - if (CVarGetInteger(CVAR_ENHANCEMENT("SkulltulaFreeze"), 0) != 0 && - !(IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_TOKENS))) { - // Auto dismiss textbox after 0x3C (60) frames (about 3 seconds for OoT) - msg = msg + "\x0E\x3C"; - } - int16_t gsCount = gSaveContext.inventory.gsTokens; - msg.Replace("[[gsCount]]", std::to_string(gsCount)); - msg.AutoFormat(ITEM_SKULL_TOKEN); - msg.LoadIntoFont(); - *loadFromMessageTable = false; -} - -void BuildHeartContainerMessage(uint16_t* textId, bool* loadFromMessageTable) { - CustomMessage msg = CustomMessage( - "You got a %rHeart Container%w!&You've collected %r[[heartContainerCount]]%w containers&in total!", - "Ein %rHerzcontainer%w!&Du hast nun insgesamt %r[[heartContainerCount]]%w&Herzcontainer gesammelt!", - "Vous obtenez un %rCoeur&d'Energie%w! Vous en avez&collecté %r[[heartContainerCount]]%w en tout!"); - msg.Replace("[[heartContainerCount]]", std::to_string(gSaveContext.ship.stats.heartContainers + 1)); - msg.AutoFormat(ITEM_HEART_CONTAINER); - msg.LoadIntoFont(); - *loadFromMessageTable = false; -} - -void BuildHeartPieceMessage(uint16_t* textId, bool* loadFromMessageTable) { - CustomMessage msg = - CustomMessage("You got a %rHeart Piece%w!&You've collected %r[[heartPieceCount]]%w pieces&in total!", - "Ein %rHerzteil%w!&Du hast nun insgesamt %r[[heartPieceCount]]%w&Herzteile gesammelt!", - "Vous obtenez un %rQuart de&Coeur%w! Vous en avez collecté&%r[[heartPieceCount]]%w en tout!", - TEXTBOX_TYPE_BLUE); - msg.Replace("[[heartPieceCount]]", std::to_string(gSaveContext.ship.stats.heartPieces + 1)); - msg.AutoFormat(ITEM_HEART_PIECE); - msg.LoadIntoFont(); - *loadFromMessageTable = false; -} - -void InjectSkulltulaCounts_Register() { - COND_ID_HOOK(OnOpenText, TEXT_GS_FREEZE, CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula"), 0), - BuildSkulltulaMessage); - COND_ID_HOOK(OnOpenText, TEXT_GS_NO_FREEZE, CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula"), 0), - BuildSkulltulaMessage); -} - -void InjectHeartContainerCounts_Register() { - COND_ID_HOOK(OnOpenText, TEXT_HEART_CONTAINER, - CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.HeartContainer"), 0), BuildHeartContainerMessage); -} - -void InjectHeartPieceCounts_Register() { - // Heart Pieces don't have documented text IDs after the first one, but - // there are 3 more in between TEXT_HEART_PIECE and TEXT_HEART_CONTAINER. - COND_ID_HOOK(OnOpenText, TEXT_HEART_PIECE, CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"), 0), - BuildHeartPieceMessage); - COND_ID_HOOK(OnOpenText, TEXT_HEART_PIECE + 1, CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"), 0), - BuildHeartPieceMessage); - COND_ID_HOOK(OnOpenText, TEXT_HEART_PIECE + 2, CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"), 0), - BuildHeartPieceMessage); - COND_ID_HOOK(OnOpenText, TEXT_HEART_PIECE + 3, CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"), 0), - BuildHeartPieceMessage); -} - -static RegisterShipInitFunc initInjectSkulltulaCountFunc(InjectSkulltulaCounts_Register, - { CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula") }); -static RegisterShipInitFunc initInjectHeartContainerCountFunc(InjectHeartContainerCounts_Register, - { CVAR_ENHANCEMENT("InjectItemCounts.HeartContainer") }); -static RegisterShipInitFunc initInjectHeartPieceCountFunc(InjectHeartPieceCounts_Register, - { CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece") }); diff --git a/soh/soh/Enhancements/ItemUnequip.cpp b/soh/soh/Enhancements/ItemUnequip.cpp deleted file mode 100644 index df590fe25..000000000 --- a/soh/soh/Enhancements/ItemUnequip.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/ShipInit.hpp" - -extern "C" { -#include "z64.h" -#include "functions.h" -#include "macros.h" -#include "variables.h" -#include "overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h" -extern SaveContext gSaveContext; -} - -#define CVAR_ITEM_UNEQUIP_NAME CVAR_ENHANCEMENT("ItemUnequip") -#define CVAR_ITEM_UNEQUIP_DEFAULT 0 -#define CVAR_ITEM_UNEQUIP_VALUE CVarGetInteger(CVAR_ITEM_UNEQUIP_NAME, CVAR_ITEM_UNEQUIP_DEFAULT) - -void RegisterItemUnequip() { - COND_VB_SHOULD(VB_EQUIP_ITEM_TO_C_BUTTON, CVAR_ITEM_UNEQUIP_VALUE, { - PlayState* play = va_arg(args, PlayState*); - u16 cursorSlot = va_arg(args, int); - u16 cursorItem = va_arg(args, int); - - Input* input = &play->state.input[0]; - - int targetButton = -1; - - if (CHECK_BTN_ALL(input->press.button, BTN_CLEFT)) { - targetButton = 1; - } else if (CHECK_BTN_ALL(input->press.button, BTN_CDOWN)) { - targetButton = 2; - } else if (CHECK_BTN_ALL(input->press.button, BTN_CRIGHT)) { - targetButton = 3; - } else if (CVarGetInteger(CVAR_ENHANCEMENT("DpadEquips"), 0)) { - if (CHECK_BTN_ALL(input->press.button, BTN_DUP)) { - targetButton = 4; - } else if (CHECK_BTN_ALL(input->press.button, BTN_DDOWN)) { - targetButton = 5; - } else if (CHECK_BTN_ALL(input->press.button, BTN_DLEFT)) { - targetButton = 6; - } else if (CHECK_BTN_ALL(input->press.button, BTN_DRIGHT)) { - targetButton = 7; - } - } - - if (targetButton == -1) { - return; - } - - u8 equippedItem = gSaveContext.equips.buttonItems[targetButton]; - u8 equippedSlot = gSaveContext.equips.cButtonSlots[targetButton - 1]; - bool shouldUnequip = false; - - if (equippedItem == cursorItem) { - if (cursorItem >= ITEM_BOTTLE && cursorItem <= ITEM_POE) { - if (equippedSlot == cursorSlot) { - shouldUnequip = true; - } - } else { - shouldUnequip = true; - } - } else if (cursorItem == ITEM_ARROW_FIRE && equippedItem == ITEM_BOW_ARROW_FIRE) { - shouldUnequip = true; - } else if (cursorItem == ITEM_ARROW_ICE && equippedItem == ITEM_BOW_ARROW_ICE) { - shouldUnequip = true; - } else if (cursorItem == ITEM_ARROW_LIGHT && equippedItem == ITEM_BOW_ARROW_LIGHT) { - shouldUnequip = true; - } else if (cursorItem == ITEM_BOW && - (equippedItem == ITEM_BOW_ARROW_FIRE || equippedItem == ITEM_BOW_ARROW_ICE || - equippedItem == ITEM_BOW_ARROW_LIGHT)) { - shouldUnequip = true; - } - - if (shouldUnequip) { - gSaveContext.equips.buttonItems[targetButton] = ITEM_NONE; - gSaveContext.equips.cButtonSlots[targetButton - 1] = SLOT_NONE; - Interface_LoadItemIcon1(play, targetButton); - - Audio_PlaySoundGeneral(NA_SE_SY_DECIDE, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, - &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); - - *should = false; - } - }); -} - -static RegisterShipInitFunc initFunc(RegisterItemUnequip, { CVAR_ITEM_UNEQUIP_NAME }); diff --git a/soh/soh/Enhancements/Items/ArrowCycle.cpp b/soh/soh/Enhancements/Items/ArrowCycle.cpp new file mode 100644 index 000000000..e70d5dee0 --- /dev/null +++ b/soh/soh/Enhancements/Items/ArrowCycle.cpp @@ -0,0 +1,209 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "macros.h" +#include "variables.h" +#include "functions.h" +#include "overlays/actors/ovl_En_Arrow/z_en_arrow.h" + +s32 func_808351D4(Player* thisx, PlayState* play); // Arrow nocked +void EnArrow_Init(Actor* thisx, PlayState* play); + +extern PlayState* gPlayState; +} + +#define CVAR_ARROW_CYCLE_NAME CVAR_ENHANCEMENT("BowArrowCycle") +#define CVAR_ARROW_CYCLE_DEFAULT 0 +#define CVAR_ARROW_CYCLE_VALUE CVarGetInteger(CVAR_ARROW_CYCLE_NAME, CVAR_ARROW_CYCLE_DEFAULT) + +static const s16 sMagicArrowCosts[] = { 4, 4, 8 }; + +static const PlayerItemAction sArrowCycleOrder[] = { + PLAYER_IA_BOW, + PLAYER_IA_BOW_FIRE, + PLAYER_IA_BOW_ICE, + PLAYER_IA_BOW_LIGHT, +}; + +static bool IsHoldingBow(Player* player) { + return player->heldItemAction >= PLAYER_IA_BOW && player->heldItemAction <= PLAYER_IA_BOW_LIGHT; +} + +static bool IsHoldingMagicBow(Player* player) { + return player->heldItemAction >= PLAYER_IA_BOW_FIRE && player->heldItemAction <= PLAYER_IA_BOW_LIGHT; +} + +static bool IsAimingBow(Player* player) { + return IsHoldingBow(player) && ((player->unk_6AD == 2) || (player->upperActionFunc == func_808351D4)); +} + +static bool HasArrowType(PlayerItemAction itemAction) { + switch (itemAction) { + case PLAYER_IA_BOW: + return true; + case PLAYER_IA_BOW_FIRE: + return INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE; + case PLAYER_IA_BOW_ICE: + return INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE; + case PLAYER_IA_BOW_LIGHT: + return INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT; + default: + return false; + } +} + +static s32 GetBowItemForArrow(PlayerItemAction itemAction) { + switch (itemAction) { + case PLAYER_IA_BOW_FIRE: + return ITEM_BOW_ARROW_FIRE; + case PLAYER_IA_BOW_ICE: + return ITEM_BOW_ARROW_ICE; + case PLAYER_IA_BOW_LIGHT: + return ITEM_BOW_ARROW_LIGHT; + default: + return ITEM_BOW; + } +} + +static ArrowType GetArrowTypeForArrow(s8 itemAction) { + switch (itemAction) { + case PLAYER_IA_BOW_FIRE: + return ARROW_FIRE; + case PLAYER_IA_BOW_ICE: + return ARROW_ICE; + case PLAYER_IA_BOW_LIGHT: + return ARROW_LIGHT; + default: + return ARROW_NORMAL; + } +} + +static bool CanCycleArrows() { + Player* player = GET_PLAYER(gPlayState); + + return (LINK_IS_ADULT || CVarGetInteger(CVAR_CHEAT("TimelessEquipment"), 0)) && !gSaveContext.minigameState && + gPlayState->sceneNum != SCENE_SHOOTING_GALLERY && !(player->stateFlags1 & PLAYER_STATE1_ON_HORSE) && + player->rideActor == NULL && INV_CONTENT(SLOT_BOW) == ITEM_BOW && + (INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE || INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE || + INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT); +} + +static s8 GetNextArrowType(s8 currentArrowType) { + int currentIndex = 0; + for (int i = 0; i < (int)ARRAY_COUNT(sArrowCycleOrder); i++) { + if (sArrowCycleOrder[i] == currentArrowType) { + currentIndex = i; + break; + } + } + + for (int offset = 1; offset <= (int)ARRAY_COUNT(sArrowCycleOrder); offset++) { + int nextIndex = (currentIndex + offset) % ARRAY_COUNT(sArrowCycleOrder); + if (HasArrowType(sArrowCycleOrder[nextIndex])) { + return sArrowCycleOrder[nextIndex]; + } + } + + return PLAYER_IA_BOW; +} + +static void UpdateButtonAlpha(s16 flashAlpha, bool isButtonBow, u16* buttonAlpha) { + if (isButtonBow) { + *buttonAlpha = flashAlpha; + } +} + +static void UpdateEquippedBow(PlayState* play, s8 arrowType) { + s32 bowItem = GetBowItemForArrow((PlayerItemAction)arrowType); + bool dpadEnabled = CVarGetInteger(CVAR_ENHANCEMENT("DpadEquips"), 0); + s32 maxButton = dpadEnabled ? 7 : 3; + + for (s32 i = 1; i <= maxButton; i++) { + if ((gSaveContext.equips.buttonItems[i] == ITEM_BOW) || + (gSaveContext.equips.buttonItems[i] >= ITEM_BOW_ARROW_FIRE && + gSaveContext.equips.buttonItems[i] <= ITEM_BOW_ARROW_LIGHT)) { + gSaveContext.equips.buttonItems[i] = bowItem; + gSaveContext.equips.cButtonSlots[i - 1] = SLOT_BOW; + + if (i <= 3) { + Interface_LoadItemIcon1(play, i); + } + + gSaveContext.buttonStatus[i] = BTN_ENABLED; + } + } +} + +bool ArrowCycleMain() { + if (gPlayState == nullptr || !CanCycleArrows()) { + return false; + } + + Player* player = GET_PLAYER(gPlayState); + if (player->heldActor != NULL && player->heldActor->id == ACTOR_EN_ARROW) { + if (IsHoldingMagicBow(player) && gSaveContext.magicState != MAGIC_STATE_IDLE && player->heldActor == NULL) { + Audio_PlaySoundGeneral(NA_SE_SY_ERROR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + return true; + } + + // reset magic state to IDLE before cycling to prevent error sound + gSaveContext.magicState = MAGIC_STATE_IDLE; + + s8 nextArrow = GetNextArrowType(player->heldItemAction); + player->heldItemAction = nextArrow; + player->itemAction = nextArrow; + Actor* arrow = player->heldActor; + + if (arrow->child != NULL) { + Actor_Kill(arrow->child); + arrow->child = NULL; + } + arrow->params = GetArrowTypeForArrow(nextArrow); + EnArrow_Init(arrow, gPlayState); + UpdateEquippedBow(gPlayState, nextArrow); + return true; + } + return false; +} + +void RegisterArrowCycle() { + // suppress shield input when R is held while aiming to allow arrow cycling + COND_VB_SHOULD(VB_EXECUTE_PLAYER_ACTION_FUNC, CVAR_ARROW_CYCLE_VALUE, { + Player* player = (Player*)va_arg(args, void*); + Input* input = (Input*)va_arg(args, void*); + if (IsAimingBow(player) && CHECK_BTN_ANY(input->press.button, BTN_R)) { + if (ArrowCycleMain()) { + input->cur.button &= ~BTN_R; + input->press.button &= ~BTN_R; + } + } + }); + + // don't consume magic on draw, but check if we have enough to fire + COND_VB_SHOULD(VB_PLAYER_ARROW_MAGIC_CONSUMPTION, CVAR_ARROW_CYCLE_VALUE, { + Player* player = va_arg(args, Player*); + int32_t magicArrowType = va_arg(args, int32_t); + int32_t* arrowType = va_arg(args, int32_t*); + + if (gSaveContext.magic < sMagicArrowCosts[magicArrowType]) { + *arrowType = ARROW_NORMAL; + } else { + *should = false; + } + }); + + COND_VB_SHOULD(VB_EN_ARROW_MAGIC_CONSUMPTION, CVAR_ARROW_CYCLE_VALUE, { + EnArrow* arrow = va_arg(args, EnArrow*); + + if (arrow->actor.params < ARROW_FIRE || arrow->actor.params > ARROW_LIGHT) { + return; + } + + int32_t magicArrowType = arrow->actor.params - ARROW_FIRE; + Magic_RequestChange(gPlayState, sMagicArrowCosts[magicArrowType], MAGIC_CONSUME_NOW); + }); +} + +static RegisterShipInitFunc initFunc(RegisterArrowCycle, { CVAR_ARROW_CYCLE_NAME }); diff --git a/soh/soh/Enhancements/Items/AssignableTunicsAndBoots.cpp b/soh/soh/Enhancements/Items/AssignableTunicsAndBoots.cpp new file mode 100644 index 000000000..535cd1400 --- /dev/null +++ b/soh/soh/Enhancements/Items/AssignableTunicsAndBoots.cpp @@ -0,0 +1,196 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "macros.h" +#include "variables.h" + +extern s32 Player_GetItemOnButton(PlayState*, s32); +extern void Inventory_ChangeEquipment(s16, u16); +extern void Player_SetEquipmentData(PlayState*, Player*); +extern void func_808328EC(Player*, u16); +extern PlayState* gPlayState; +} + +static u16 sItemButtons[] = { BTN_B, BTN_CLEFT, BTN_CDOWN, BTN_CRIGHT, BTN_DUP, BTN_DDOWN, BTN_DLEFT, BTN_DRIGHT }; + +static void UseTunicBoots(Player* player, PlayState* play, Input* input) { + // Boots and tunics equip despite state + if (player->stateFlags1 & (PLAYER_STATE1_INPUT_DISABLED | PLAYER_STATE1_IN_ITEM_CS | PLAYER_STATE1_IN_CUTSCENE | + PLAYER_STATE1_TALKING | PLAYER_STATE1_DEAD) || + player->stateFlags2 & PLAYER_STATE2_OCARINA_PLAYING) { + return; + } + + s32 item = ITEM_NONE; + for (s32 i = 0; i < ARRAY_COUNT(sItemButtons); i++) { + if (CHECK_BTN_ALL(input->press.button, sItemButtons[i])) { + item = Player_GetItemOnButton(play, i); + break; + } + } + + if (item >= ITEM_SHIELD_DEKU && item <= ITEM_BOOTS_HOVER) { + if (item >= ITEM_BOOTS_KOKIRI) { + u16 bootsValue = item - ITEM_BOOTS_KOKIRI + 1; + if (CUR_EQUIP_VALUE(EQUIP_TYPE_BOOTS) == bootsValue) { + Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, EQUIP_VALUE_BOOTS_KOKIRI); + } else { + Inventory_ChangeEquipment(EQUIP_TYPE_BOOTS, bootsValue); + } + Player_SetEquipmentData(play, player); + func_808328EC(player, CUR_EQUIP_VALUE(EQUIP_TYPE_BOOTS) == EQUIP_VALUE_BOOTS_IRON ? NA_SE_PL_WALK_HEAVYBOOTS + : NA_SE_PL_CHANGE_ARMS); + } else if (item >= ITEM_TUNIC_KOKIRI) { + u16 tunicValue = item - ITEM_TUNIC_KOKIRI + 1; + if (CUR_EQUIP_VALUE(EQUIP_TYPE_TUNIC) == tunicValue) { + Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_KOKIRI); + } else { + Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, tunicValue); + } + Player_SetEquipmentData(play, player); + func_808328EC(player, NA_SE_PL_CHANGE_ARMS); + } else { + u16 shieldValue = item - ITEM_SHIELD_DEKU + 1; + if (CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) != shieldValue) { + Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, shieldValue); + Player_SetEquipmentData(play, player); + func_808328EC(player, NA_SE_PL_CHANGE_ARMS); + } + } + } +} + +static void ClearAssignedTunicsBoots(int32_t unused = 0) { + for (int32_t buttonIndex = 0; buttonIndex < 8; buttonIndex++) { + int32_t item = gSaveContext.equips.buttonItems[buttonIndex]; + + if (item >= ITEM_SHIELD_DEKU && item <= ITEM_BOOTS_HOVER) { + gSaveContext.equips.buttonItems[buttonIndex] = ITEM_NONE; + } + } +} + +static void ClearDeletedAssignedEquipment(int16_t equipmentType, uint16_t equipValue) { + ItemID itemToRemove = ITEM_NONE; + + if (equipmentType == EQUIP_TYPE_TUNIC) { + switch (equipValue) { + case EQUIP_VALUE_TUNIC_KOKIRI: + break; + case EQUIP_VALUE_TUNIC_GORON: + itemToRemove = ITEM_TUNIC_GORON; + break; + case EQUIP_VALUE_TUNIC_ZORA: + itemToRemove = ITEM_TUNIC_ZORA; + break; + } + } else if (equipmentType == EQUIP_TYPE_SHIELD) { + switch (equipValue) { + case EQUIP_VALUE_SHIELD_DEKU: + itemToRemove = ITEM_SHIELD_DEKU; + break; + case EQUIP_VALUE_SHIELD_HYLIAN: + itemToRemove = ITEM_SHIELD_HYLIAN; + break; + case EQUIP_VALUE_SHIELD_MIRROR: + itemToRemove = ITEM_SHIELD_MIRROR; + break; + } + } + + if (itemToRemove == ITEM_NONE) { + return; + } + + for (int i = 1; i < ARRAY_COUNT(gSaveContext.equips.buttonItems); i++) { + if (gSaveContext.equips.buttonItems[i] == itemToRemove) { + gSaveContext.equips.buttonItems[i] = ITEM_NONE; + gSaveContext.equips.cButtonSlots[i - 1] = SLOT_NONE; + } + } +} + +#define CVAR_TUNICBOOTS_NAME CVAR_ENHANCEMENT("AssignableTunicsAndBoots") +#define CVAR_TUNICBOOTS_DEFAULT 0 +#define CVAR_TUNICBOOTS_VALUE CVarGetInteger(CVAR_TUNICBOOTS_NAME, CVAR_TUNICBOOTS_DEFAULT) +#define CVAR_TUNICBOOTS_SET (CVAR_TUNICBOOTS_VALUE != CVAR_TUNICBOOTS_DEFAULT) + +static void RegisterAssignableTunicsBoots() { + // make sure we don't change our held/equipped item when changing shield/tunic/boots + COND_VB_SHOULD(VB_CHANGE_HELD_ITEM_AND_USE_ITEM, CVAR_TUNICBOOTS_SET, { + int32_t item = va_arg(args, int32_t); + + if (item >= ITEM_SHIELD_DEKU && item <= ITEM_BOOTS_HOVER) { + *should = false; + } + }); + + // make sure we don't crash because tunics/boots don't have assoicated item actions + COND_VB_SHOULD(VB_ITEM_ACTION_BE_NONE, CVAR_TUNICBOOTS_SET, { + int32_t item = va_arg(args, int32_t); + + if (item >= ITEM_SHIELD_DEKU && item <= ITEM_BOOTS_HOVER) { + *should = true; + } + }); + + // don't throw items when the pressed button is a shield, tunic or boots + COND_VB_SHOULD(VB_THROW_OR_PUT_DOWN_HELD_ITEM, CVAR_TUNICBOOTS_SET, { + // if the vanilla condition doesn't want us to throw/put down the item, early return + if (!*should) { + return; + } + + Input* input = va_arg(args, Input*); + + s32 item = ITEM_NONE; + for (s32 i = 0; i < ARRAY_COUNT(sItemButtons); i++) { + if (CHECK_BTN_ALL(input->press.button, sItemButtons[i])) { + item = Player_GetItemOnButton(gPlayState, i); + break; + } + } + + if (item >= ITEM_SHIELD_DEKU && item <= ITEM_BOOTS_HOVER) { + *should = false; + } + }); + + // do something when the player presses a button to use the tunics/boots + COND_VB_SHOULD(VB_EXECUTE_PLAYER_ACTION_FUNC, CVAR_TUNICBOOTS_SET, { + // if the vanilla condition doesn't want us to run the actionFunc, don't do any of this + if (!*should) { + return; + } + + Player* player = va_arg(args, Player*); + + // if we're not dealing with the actual player, we're probably + // dealing with dark link, don't do any tunics/boots stuff, just + // run the vanilla action func + if (player->actor.id != ACTOR_PLAYER) { + return; + } + + *should = false; + + Input* input = va_arg(args, Input*); + + player->actionFunc(player, gPlayState); + UseTunicBoots(player, gPlayState, input); + }); + + // remove assigned equipment when it gets deleted + COND_HOOK(OnEquipmentDelete, CVAR_TUNICBOOTS_SET, ClearDeletedAssignedEquipment); + + // clear out assigned tunics/boots when the enhancement is toggled off + if (GameInteractor::IsSaveLoaded(true) && !CVAR_TUNICBOOTS_SET) { + ClearAssignedTunicsBoots(); + } + + // clear out assigned tunics/boots when loading a save with enhancement turned off + COND_HOOK(OnLoadGame, !CVAR_TUNICBOOTS_SET, ClearAssignedTunicsBoots); +} + +static RegisterShipInitFunc initFunc(RegisterAssignableTunicsBoots, { CVAR_TUNICBOOTS_NAME }); diff --git a/soh/soh/Enhancements/Items/BlueFireArrows.cpp b/soh/soh/Enhancements/Items/BlueFireArrows.cpp new file mode 100644 index 000000000..985767853 --- /dev/null +++ b/soh/soh/Enhancements/Items/BlueFireArrows.cpp @@ -0,0 +1,55 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/randomizer/SeedContext.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "overlays/actors/ovl_Bg_Breakwall/z_bg_breakwall.h" +#include "overlays/actors/ovl_Bg_Ice_Shelter/z_bg_ice_shelter.h" + +extern PlayState* gPlayState; +} + +static void UpdateBlueFireCollidersBgBreakwall(void* actorPtr) { + BgBreakwall* thisx = (BgBreakwall*)actorPtr; + thisx->collider.info.bumper.dmgFlags |= DMG_ARROW_ICE; +} + +static void UpdateBlueFireCollidersBgIceShelter(void* actorPtr) { + BgIceShelter* thisx = (BgIceShelter*)actorPtr; + thisx->cylinder1.base.acFlags |= AC_TYPE_PLAYER; + thisx->cylinder1.info.bumper.dmgFlags |= DMG_ARROW_ICE; + thisx->cylinder2.base.acFlags |= AC_TYPE_PLAYER; + thisx->cylinder2.info.bumper.dmgFlags |= DMG_ARROW_ICE; +} + +static bool CheckAC(Actor* ac) { + return ac != NULL && ac->id == ACTOR_EN_ARROW && ac->child != NULL && ac->child->id == ACTOR_ARROW_ICE; +} + +void RegisterBlueFireArrowsHooks() { + bool shouldRegister = + CVarGetInteger(CVAR_ENHANCEMENT("BlueFireArrows"), 0) || (IS_RANDO && RAND_GET_OPTION(RSK_BLUE_FIRE_ARROWS)); + + COND_ID_HOOK(OnActorInit, ACTOR_BG_BREAKWALL, shouldRegister, UpdateBlueFireCollidersBgBreakwall); + COND_ID_HOOK(OnActorInit, ACTOR_BG_ICE_SHELTER, shouldRegister, UpdateBlueFireCollidersBgIceShelter); + + // fix bug where cylinder2 never checks acFlags + COND_VB_SHOULD(VB_BG_ICE_SHELTER_HIT, shouldRegister, { + BgIceShelter* thisx = va_arg(args, BgIceShelter*); + + if (thisx->cylinder2.base.acFlags & AC_HIT) { + thisx->cylinder2.base.acFlags &= ~AC_HIT; + *should = true; + } + }); + + COND_VB_SHOULD(VB_BG_ICE_SHELTER_MELT, shouldRegister, { + BgIceShelter* thisx = va_arg(args, BgIceShelter*); + + if (CheckAC(thisx->cylinder1.base.ac) || CheckAC(thisx->cylinder2.base.ac)) { + *should = true; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterBlueFireArrowsHooks, { "IS_RANDO", CVAR_ENHANCEMENT("BlueFireArrows") }); diff --git a/soh/soh/Enhancements/Items/InjectItemCounts.cpp b/soh/soh/Enhancements/Items/InjectItemCounts.cpp new file mode 100644 index 000000000..e7e4bdcd0 --- /dev/null +++ b/soh/soh/Enhancements/Items/InjectItemCounts.cpp @@ -0,0 +1,88 @@ +#include +#include "soh/Enhancements/randomizer/randomizer.h" + +extern "C" { +#include "variables.h" +} + +void BuildSkulltulaMessage(uint16_t* textId, bool* loadFromMessageTable) { + CustomMessage msg = + CustomMessage("You got a %rGold Skulltula Token%w!&You've collected %r[[gsCount]]%w tokens&in total!", + "Ein %rGoldenes Skulltula-Symbol%w!&Du hast nun insgesamt %r[[gsCount]]&%wGoldene " + "Skulltula-Symbole&gesammelt!", + "Vous obtenez un %rSymbole de&Skulltula d'or%w! Vous avez&collecté %r[[gsCount]]%w symboles en " + "tout!", + TEXTBOX_TYPE_BLUE); + // The freeze text cannot be manually dismissed and must be auto-dismissed. + // This is fine and even wanted when skull tokens are not shuffled, but when + // when they are shuffled we don't want to be able to manually dismiss the box. + // Otherwise if we get a token from a chest or an NPC we get stuck in the ItemGet + // animation until the text box auto-dismisses. + // RANDOTODO: Implement a way to determine if an item came from a skulltula and + // inject the auto-dismiss control code if it did. + if (CVarGetInteger(CVAR_ENHANCEMENT("SkulltulaFreeze"), 0) != 0 && + !(IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_TOKENS))) { + // Auto dismiss textbox after 0x3C (60) frames (about 3 seconds for OoT) + msg = msg + "\x0E\x3C"; + } + int16_t gsCount = gSaveContext.inventory.gsTokens; + msg.Replace("[[gsCount]]", std::to_string(gsCount)); + msg.AutoFormat(ITEM_SKULL_TOKEN); + msg.LoadIntoFont(); + *loadFromMessageTable = false; +} + +void BuildHeartContainerMessage(uint16_t* textId, bool* loadFromMessageTable) { + CustomMessage msg = CustomMessage( + "You got a %rHeart Container%w!&You've collected %r[[heartContainerCount]]%w containers&in total!", + "Ein %rHerzcontainer%w!&Du hast nun insgesamt %r[[heartContainerCount]]%w&Herzcontainer gesammelt!", + "Vous obtenez un %rCoeur&d'Energie%w! Vous en avez&collecté %r[[heartContainerCount]]%w en tout!"); + msg.Replace("[[heartContainerCount]]", std::to_string(gSaveContext.ship.stats.heartContainers + 1)); + msg.AutoFormat(ITEM_HEART_CONTAINER); + msg.LoadIntoFont(); + *loadFromMessageTable = false; +} + +void BuildHeartPieceMessage(uint16_t* textId, bool* loadFromMessageTable) { + CustomMessage msg = + CustomMessage("You got a %rHeart Piece%w!&You've collected %r[[heartPieceCount]]%w pieces&in total!", + "Ein %rHerzteil%w!&Du hast nun insgesamt %r[[heartPieceCount]]%w&Herzteile gesammelt!", + "Vous obtenez un %rQuart de&Coeur%w! Vous en avez collecté&%r[[heartPieceCount]]%w en tout!", + TEXTBOX_TYPE_BLUE); + msg.Replace("[[heartPieceCount]]", std::to_string(gSaveContext.ship.stats.heartPieces + 1)); + msg.AutoFormat(ITEM_HEART_PIECE); + msg.LoadIntoFont(); + *loadFromMessageTable = false; +} + +void InjectSkulltulaCounts_Register() { + COND_ID_HOOK(OnOpenText, TEXT_GS_FREEZE, CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula"), 0), + BuildSkulltulaMessage); + COND_ID_HOOK(OnOpenText, TEXT_GS_NO_FREEZE, CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula"), 0), + BuildSkulltulaMessage); +} + +void InjectHeartContainerCounts_Register() { + COND_ID_HOOK(OnOpenText, TEXT_HEART_CONTAINER, + CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.HeartContainer"), 0), BuildHeartContainerMessage); +} + +void InjectHeartPieceCounts_Register() { + // Heart Pieces don't have documented text IDs after the first one, but + // there are 3 more in between TEXT_HEART_PIECE and TEXT_HEART_CONTAINER. + COND_ID_HOOK(OnOpenText, TEXT_HEART_PIECE, CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"), 0), + BuildHeartPieceMessage); + COND_ID_HOOK(OnOpenText, TEXT_HEART_PIECE + 1, CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"), 0), + BuildHeartPieceMessage); + COND_ID_HOOK(OnOpenText, TEXT_HEART_PIECE + 2, CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"), 0), + BuildHeartPieceMessage); + COND_ID_HOOK(OnOpenText, TEXT_HEART_PIECE + 3, CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece"), 0), + BuildHeartPieceMessage); +} + +static RegisterShipInitFunc initInjectSkulltulaCountFunc(InjectSkulltulaCounts_Register, + { CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula") }); +static RegisterShipInitFunc initInjectHeartContainerCountFunc(InjectHeartContainerCounts_Register, + { CVAR_ENHANCEMENT("InjectItemCounts.HeartContainer") }); +static RegisterShipInitFunc initInjectHeartPieceCountFunc(InjectHeartPieceCounts_Register, + { CVAR_ENHANCEMENT("InjectItemCounts.HeartPiece") }); diff --git a/soh/soh/Enhancements/Items/ItemUnequip.cpp b/soh/soh/Enhancements/Items/ItemUnequip.cpp new file mode 100644 index 000000000..df590fe25 --- /dev/null +++ b/soh/soh/Enhancements/Items/ItemUnequip.cpp @@ -0,0 +1,87 @@ +#include +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "z64.h" +#include "functions.h" +#include "macros.h" +#include "variables.h" +#include "overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h" +extern SaveContext gSaveContext; +} + +#define CVAR_ITEM_UNEQUIP_NAME CVAR_ENHANCEMENT("ItemUnequip") +#define CVAR_ITEM_UNEQUIP_DEFAULT 0 +#define CVAR_ITEM_UNEQUIP_VALUE CVarGetInteger(CVAR_ITEM_UNEQUIP_NAME, CVAR_ITEM_UNEQUIP_DEFAULT) + +void RegisterItemUnequip() { + COND_VB_SHOULD(VB_EQUIP_ITEM_TO_C_BUTTON, CVAR_ITEM_UNEQUIP_VALUE, { + PlayState* play = va_arg(args, PlayState*); + u16 cursorSlot = va_arg(args, int); + u16 cursorItem = va_arg(args, int); + + Input* input = &play->state.input[0]; + + int targetButton = -1; + + if (CHECK_BTN_ALL(input->press.button, BTN_CLEFT)) { + targetButton = 1; + } else if (CHECK_BTN_ALL(input->press.button, BTN_CDOWN)) { + targetButton = 2; + } else if (CHECK_BTN_ALL(input->press.button, BTN_CRIGHT)) { + targetButton = 3; + } else if (CVarGetInteger(CVAR_ENHANCEMENT("DpadEquips"), 0)) { + if (CHECK_BTN_ALL(input->press.button, BTN_DUP)) { + targetButton = 4; + } else if (CHECK_BTN_ALL(input->press.button, BTN_DDOWN)) { + targetButton = 5; + } else if (CHECK_BTN_ALL(input->press.button, BTN_DLEFT)) { + targetButton = 6; + } else if (CHECK_BTN_ALL(input->press.button, BTN_DRIGHT)) { + targetButton = 7; + } + } + + if (targetButton == -1) { + return; + } + + u8 equippedItem = gSaveContext.equips.buttonItems[targetButton]; + u8 equippedSlot = gSaveContext.equips.cButtonSlots[targetButton - 1]; + bool shouldUnequip = false; + + if (equippedItem == cursorItem) { + if (cursorItem >= ITEM_BOTTLE && cursorItem <= ITEM_POE) { + if (equippedSlot == cursorSlot) { + shouldUnequip = true; + } + } else { + shouldUnequip = true; + } + } else if (cursorItem == ITEM_ARROW_FIRE && equippedItem == ITEM_BOW_ARROW_FIRE) { + shouldUnequip = true; + } else if (cursorItem == ITEM_ARROW_ICE && equippedItem == ITEM_BOW_ARROW_ICE) { + shouldUnequip = true; + } else if (cursorItem == ITEM_ARROW_LIGHT && equippedItem == ITEM_BOW_ARROW_LIGHT) { + shouldUnequip = true; + } else if (cursorItem == ITEM_BOW && + (equippedItem == ITEM_BOW_ARROW_FIRE || equippedItem == ITEM_BOW_ARROW_ICE || + equippedItem == ITEM_BOW_ARROW_LIGHT)) { + shouldUnequip = true; + } + + if (shouldUnequip) { + gSaveContext.equips.buttonItems[targetButton] = ITEM_NONE; + gSaveContext.equips.cButtonSlots[targetButton - 1] = SLOT_NONE; + Interface_LoadItemIcon1(play, targetButton); + + Audio_PlaySoundGeneral(NA_SE_SY_DECIDE, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + + *should = false; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterItemUnequip, { CVAR_ITEM_UNEQUIP_NAME }); diff --git a/soh/soh/Enhancements/Items/MaskSelect.cpp b/soh/soh/Enhancements/Items/MaskSelect.cpp new file mode 100644 index 000000000..2d5d2bbe3 --- /dev/null +++ b/soh/soh/Enhancements/Items/MaskSelect.cpp @@ -0,0 +1,23 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "functions.h" +#include "src/overlays/actors/ovl_En_Ossan/z_en_ossan.h" +} + +static void RegisterMaskSelectFixes() { + // Fix mask quest so replacing SOLD OUT with mask select doesn't break quest + COND_VB_SHOULD(VB_HAPPY_MASK_SHOP_CHECK_SOLD_OUT, CVarGetInteger(CVAR_ENHANCEMENT("MaskSelect"), 0), { + Actor* actor = va_arg(args, Actor*); + if (actor->params == OSSAN_TYPE_MASK && + (Flags_GetItemGetInf(ITEMGETINF_3B) && !Flags_GetEventChkInf(EVENTCHKINF_PAID_BACK_BUNNY_HOOD_FEE)) || + (Flags_GetItemGetInf(ITEMGETINF_3A) && !Flags_GetEventChkInf(EVENTCHKINF_PAID_BACK_SPOOKY_MASK_FEE)) || + (Flags_GetItemGetInf(ITEMGETINF_39) && !Flags_GetEventChkInf(EVENTCHKINF_PAID_BACK_SKULL_MASK_FEE)) || + (Flags_GetItemGetInf(ITEMGETINF_38) && !Flags_GetEventChkInf(EVENTCHKINF_PAID_BACK_KEATON_MASK_FEE))) { + *should = true; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterMaskSelectFixes, { CVAR_ENHANCEMENT("MaskSelect") }); diff --git a/soh/soh/Enhancements/Items/RemoteBombchu.cpp b/soh/soh/Enhancements/Items/RemoteBombchu.cpp new file mode 100644 index 000000000..1813db23d --- /dev/null +++ b/soh/soh/Enhancements/Items/RemoteBombchu.cpp @@ -0,0 +1,206 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "src/overlays/actors/ovl_En_Bom_Chu/z_en_bom_chu.h" + +void EnBomChu_Move(EnBomChu*, PlayState*); +void EnBomChu_Explode(EnBomChu*, PlayState*); +s32 Camera_BGCheck(Camera* camera, Vec3f* from, Vec3f* to); +} + +#define CVAR_REMOTE_BOMBCHU_NAME CVAR_ENHANCEMENT("RemoteBombchu") +#define CVAR_REMOTE_BOMBCHU_DEFAULT 0 +#define CVAR_REMOTE_BOMBCHU_VALUE CVarGetInteger(CVAR_REMOTE_BOMBCHU_NAME, CVAR_REMOTE_BOMBCHU_DEFAULT) + +// Camera constants +#define CAM_DIST 200.0f +#define CAM_HEIGHT 50.0f +#define CAM_OFFSET_UP 15.0f + +// Control constants +#define TURN_RATE 1200.0f +#define STICK_THRESHOLD 10.0f + +typedef struct { + EnBomChu* activeChu; + s16 subCamId; + bool isActive; + Vec3f cameraEye; + Vec3f cameraAt; +} RemoteBombchuState; + +static RemoteBombchuState sState = { nullptr, SUBCAM_NONE, false, { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } }; + +static void RotateVectorAroundAxis(Vec3f* vec, Vec3f* axis, f32 angle) { + Matrix_RotateAxis(angle, axis, MTXMODE_NEW); + Vec3f result; + Matrix_MultVec3f(vec, &result); + *vec = result; +} + +static void StartControl(PlayState* play) { + if (sState.isActive) + return; + + sState.subCamId = Play_CreateSubCamera(play); + if (sState.subCamId == SUBCAM_NONE) + return; + + Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_WAIT); + Play_ChangeCameraStatus(play, sState.subCamId, CAM_STAT_ACTIVE); + + // Initialize camera vectors from main camera for smooth transition + Camera* mainCam = Play_GetCamera(play, MAIN_CAM); + sState.cameraEye = mainCam->eye; + sState.cameraAt = mainCam->at; + + Player* player = GET_PLAYER(play); + player->stateFlags1 |= PLAYER_STATE1_INPUT_DISABLED; + + // Reset first-person/item camera state + player->unk_6AD = 0; + + sState.isActive = true; +} + +static void StopControl(PlayState* play) { + if (!sState.isActive) + return; + + if (sState.subCamId != SUBCAM_NONE) { + Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_ACTIVE); + Play_ClearCamera(play, sState.subCamId); + sState.subCamId = SUBCAM_NONE; + } + + Player* player = GET_PLAYER(play); + player->stateFlags1 &= ~PLAYER_STATE1_INPUT_DISABLED; + + sState.activeChu = nullptr; + sState.isActive = false; +} + +// UpdateCamera function +static void UpdateCamera(EnBomChu* chu, PlayState* play) { + Vec3f targetEye, targetAt; + + // Calculate target "At" point (looking at the bombchu, slightly above) + targetAt.x = chu->actor.world.pos.x + chu->axisUp.x * CAM_OFFSET_UP; + targetAt.y = chu->actor.world.pos.y + chu->axisUp.y * CAM_OFFSET_UP; + targetAt.z = chu->actor.world.pos.z + chu->axisUp.z * CAM_OFFSET_UP; + + // Calculate target "Eye" position (behind and above the bombchu) + targetEye.x = targetAt.x - chu->axisForwards.x * CAM_DIST + chu->axisUp.x * CAM_HEIGHT; + targetEye.y = targetAt.y - chu->axisForwards.y * CAM_DIST + chu->axisUp.y * CAM_HEIGHT; + targetEye.z = targetAt.z - chu->axisForwards.z * CAM_DIST + chu->axisUp.z * CAM_HEIGHT; + + // Smoothly approach "At" position + Math_ApproachF(&sState.cameraAt.x, targetAt.x, 0.2f, 30.0f); + Math_ApproachF(&sState.cameraAt.y, targetAt.y, 0.2f, 30.0f); + Math_ApproachF(&sState.cameraAt.z, targetAt.z, 0.2f, 30.0f); + + Camera* camera = Play_GetCamera(play, sState.subCamId); + + // Check if the ideal target eye position collides with walls + Vec3f safeEye = targetEye; + Camera_BGCheck(camera, &sState.cameraAt, &safeEye); + + // Smoothly approach the *safe* eye position + Math_ApproachF(&sState.cameraEye.x, safeEye.x, 0.2f, 50.0f); + Math_ApproachF(&sState.cameraEye.y, safeEye.y, 0.2f, 50.0f); + Math_ApproachF(&sState.cameraEye.z, safeEye.z, 0.2f, 50.0f); + + Play_CameraSetAtEye(play, sState.subCamId, &sState.cameraAt, &sState.cameraEye); +} + +static void HandleSteering(EnBomChu* chu, Input* input) { + f32 stickX = input->cur.stick_x; + if (fabsf(stickX) <= STICK_THRESHOLD) + return; + + // Calculate turn angle based on stick input + f32 turnAngle = BINANG_TO_RAD((s16)(TURN_RATE * (stickX / 85.0f))); + + // Rotate forward and left vectors around the up axis + RotateVectorAroundAxis(&chu->axisForwards, &chu->axisUp, -turnAngle); + RotateVectorAroundAxis(&chu->axisLeft, &chu->axisUp, -turnAngle); + + // Update actor rotation from the new orientation + MtxF mf; + mf.xx = chu->axisLeft.x; + mf.yx = chu->axisLeft.y; + mf.zx = chu->axisLeft.z; + mf.xy = chu->axisUp.x; + mf.yy = chu->axisUp.y; + mf.zy = chu->axisUp.z; + mf.xz = chu->axisForwards.x; + mf.yz = chu->axisForwards.y; + mf.zz = chu->axisForwards.z; + + Matrix_MtxFToYXZRotS(&mf, &chu->actor.world.rot, 0); + chu->actor.world.rot.x = -chu->actor.world.rot.x; + chu->actor.shape.rot = chu->actor.world.rot; + chu->actor.shape.rot.x = -chu->actor.shape.rot.x; +} + +static void HandleInput(EnBomChu* chu, Input* input, PlayState* play) { + HandleSteering(chu, input); + + if (input->press.button & BTN_B) { + EnBomChu_Explode(chu, play); + } + + if (input->press.button & BTN_A) { + StopControl(play); + } +} + +// Track the most recently spawned bombchu +static void OnActorInit(void* refActor) { + sState.activeChu = (EnBomChu*)refActor; +} + +// Clean up if active bombchu is destroyed +static void OnActorDestroy(void* refActor) { + if (refActor == sState.activeChu) { + if (sState.isActive) { + StopControl(gPlayState); + } + sState.activeChu = nullptr; + } +} + +// Main update logic +static void OnActorUpdate(void* refActor) { + if (refActor != sState.activeChu) + return; + + if (sState.activeChu->actionFunc != EnBomChu_Move) + return; + + // Start control when bombchu begins moving + if (!sState.isActive) { + StartControl(gPlayState); + } + + // Exit early if control was not established or was stopped + if (!sState.isActive) + return; + + HandleInput(sState.activeChu, &gPlayState->state.input[0], gPlayState); + + // Check again after input handling (user might have pressed A to stop) + if (!sState.isActive) + return; + + UpdateCamera(sState.activeChu, gPlayState); +} + +void RegisterRemoteBombchu() { + COND_ID_HOOK(OnActorInit, ACTOR_EN_BOM_CHU, CVAR_REMOTE_BOMBCHU_VALUE, OnActorInit); + COND_ID_HOOK(OnActorDestroy, ACTOR_EN_BOM_CHU, CVAR_REMOTE_BOMBCHU_VALUE, OnActorDestroy); + COND_ID_HOOK(OnActorUpdate, ACTOR_EN_BOM_CHU, CVAR_REMOTE_BOMBCHU_VALUE, OnActorUpdate); +} + +static RegisterShipInitFunc initFunc(RegisterRemoteBombchu, { CVAR_REMOTE_BOMBCHU_NAME }); diff --git a/soh/soh/Enhancements/Items/SunlightArrows.cpp b/soh/soh/Enhancements/Items/SunlightArrows.cpp new file mode 100644 index 000000000..73e894c24 --- /dev/null +++ b/soh/soh/Enhancements/Items/SunlightArrows.cpp @@ -0,0 +1,97 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/randomizer/SeedContext.h" +#include "soh/ShipInit.hpp" +#include "soh/ObjectExtension/ObjectExtension.h" + +extern "C" { +#include "overlays/actors/ovl_Obj_Lightswitch/z_obj_lightswitch.h" +} + +static ColliderJntSphElementInit sColliderLightArrowElementInit[] = { + { + { + ELEMTYPE_UNK0, + { 0x00000000, 0x00, 0x00 }, + { 0x00202000, 0x00, 0x00 }, + TOUCH_NONE, + BUMP_ON, + OCELEM_ON, + }, + { 0, { { 0, 0, 0 }, 19 }, 100 }, + }, +}; +static ColliderJntSphInit sColliderLightArrowInit = { + { + COLTYPE_NONE, + AT_NONE, + AC_ON | AC_TYPE_PLAYER, + OC1_ON | OC1_TYPE_ALL, + OC2_TYPE_2, + COLSHAPE_JNTSPH, + }, + 1, + sColliderLightArrowElementInit, +}; + +struct SunlightArrowData { + bool activatedByLightArrow = false; +}; + +static ObjectExtension::Register SunlightArrowDataRegister; + +void RegisterSunlightArrowsHooks() { + bool shouldRegister = + CVarGetInteger(CVAR_ENHANCEMENT("SunlightArrows"), 0) || (IS_RANDO && RAND_GET_OPTION(RSK_SUNLIGHT_ARROWS)); + + COND_ID_HOOK(OnActorInit, ACTOR_OBJ_LIGHTSWITCH, shouldRegister, [](void* actor) { + auto* thisx = (ObjLightswitch*)actor; + Collider_SetJntSph(gPlayState, &thisx->collider, &thisx->actor, &sColliderLightArrowInit, thisx->colliderItems); + Collider_UpdateSpheres(0, &thisx->collider); + }); + + COND_ID_HOOK(OnActorDestroy, ACTOR_OBJ_LIGHTSWITCH, shouldRegister, [](void* actor) { + auto* thisx = (ObjLightswitch*)actor; + + auto sunData = ObjectExtension::GetInstance().Get(&thisx->actor); + if (sunData != nullptr && sunData->activatedByLightArrow) { + switch (thisx->actor.params >> 4 & 3) { + case OBJLIGHTSWITCH_TYPE_STAY_ON: + case OBJLIGHTSWITCH_TYPE_2: + case OBJLIGHTSWITCH_TYPE_1: + // Unset the switch flag on room exit to prevent the rock in the wall from + // vanishing on its own after activating the sun switch by Light Arrow + // Also prevents the cobra mirror from rotating to face the sun on its own + // Makes sun switches temporary when activated by Light Arrows (will turn off on room exit) + if (thisx->actor.room != 25) { + Flags_UnsetSwitch(gPlayState, thisx->actor.params >> 8 & 0x3F); + } + break; + case OBJLIGHTSWITCH_TYPE_BURN: + break; + } + } + }); + + COND_VB_SHOULD(VB_LIGHTSWITCH_OFF, shouldRegister, { + ObjLightswitch* thisx = va_arg(args, ObjLightswitch*); + auto sunData = ObjectExtension::GetInstance().Get(&thisx->actor); + if (sunData != nullptr && sunData->activatedByLightArrow) { + *should = false; + } + }); + + COND_ID_HOOK(ShouldActorUpdate, ACTOR_OBJ_LIGHTSWITCH, shouldRegister, [](void* actorPtr, bool* result) { + ObjLightswitch* thisx = (ObjLightswitch*)actorPtr; + if ((thisx->collider.base.acFlags & AC_HIT) && thisx->collider.base.ac != nullptr) { + auto sunData = ObjectExtension::GetInstance().Get(&thisx->actor); + if (sunData == nullptr) { + ObjectExtension::GetInstance().Set(&thisx->actor, SunlightArrowData{}); + sunData = ObjectExtension::GetInstance().Get(&thisx->actor); + } + + sunData->activatedByLightArrow = thisx->collider.base.ac->id == ACTOR_EN_ARROW; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterSunlightArrowsHooks, { "IS_RANDO", CVAR_ENHANCEMENT("SunlightArrows") }); diff --git a/soh/soh/Enhancements/Items/UnsheatheWithoutSlashing.cpp b/soh/soh/Enhancements/Items/UnsheatheWithoutSlashing.cpp new file mode 100644 index 000000000..bb215eb7a --- /dev/null +++ b/soh/soh/Enhancements/Items/UnsheatheWithoutSlashing.cpp @@ -0,0 +1,18 @@ +#include +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" + +#define CVAR_UNSHEATHE_NAME CVAR_ENHANCEMENT("UnsheatheWithoutSlashing") +#define CVAR_UNSHEATHE_VALUE CVarGetInteger(CVAR_UNSHEATHE_NAME, 0) + +void RegisterUnsheatheWithoutSlashing() { + COND_VB_SHOULD(VB_USE_HELD_ITEM_AFTER_CHANGE, CVAR_UNSHEATHE_VALUE, { + Player* player = va_arg(args, Player*); + ItemID heldItemId = static_cast(player->heldItemId); + if ((heldItemId == ITEM_SWORD_KOKIRI) || (heldItemId == ITEM_SWORD_MASTER)) { + *should = false; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterUnsheatheWithoutSlashing, { CVAR_UNSHEATHE_NAME }); diff --git a/soh/soh/Enhancements/MaskSelect.cpp b/soh/soh/Enhancements/MaskSelect.cpp deleted file mode 100644 index 2d5d2bbe3..000000000 --- a/soh/soh/Enhancements/MaskSelect.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "soh/ShipInit.hpp" - -extern "C" { -#include "functions.h" -#include "src/overlays/actors/ovl_En_Ossan/z_en_ossan.h" -} - -static void RegisterMaskSelectFixes() { - // Fix mask quest so replacing SOLD OUT with mask select doesn't break quest - COND_VB_SHOULD(VB_HAPPY_MASK_SHOP_CHECK_SOLD_OUT, CVarGetInteger(CVAR_ENHANCEMENT("MaskSelect"), 0), { - Actor* actor = va_arg(args, Actor*); - if (actor->params == OSSAN_TYPE_MASK && - (Flags_GetItemGetInf(ITEMGETINF_3B) && !Flags_GetEventChkInf(EVENTCHKINF_PAID_BACK_BUNNY_HOOD_FEE)) || - (Flags_GetItemGetInf(ITEMGETINF_3A) && !Flags_GetEventChkInf(EVENTCHKINF_PAID_BACK_SPOOKY_MASK_FEE)) || - (Flags_GetItemGetInf(ITEMGETINF_39) && !Flags_GetEventChkInf(EVENTCHKINF_PAID_BACK_SKULL_MASK_FEE)) || - (Flags_GetItemGetInf(ITEMGETINF_38) && !Flags_GetEventChkInf(EVENTCHKINF_PAID_BACK_KEATON_MASK_FEE))) { - *should = true; - } - }); -} - -static RegisterShipInitFunc initFunc(RegisterMaskSelectFixes, { CVAR_ENHANCEMENT("MaskSelect") }); diff --git a/soh/soh/Enhancements/NoSkulltulaFreeze.cpp b/soh/soh/Enhancements/NoSkulltulaFreeze.cpp deleted file mode 100644 index 66e6132a1..000000000 --- a/soh/soh/Enhancements/NoSkulltulaFreeze.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include "soh/Enhancements/randomizer/randomizer.h" - -extern "C" { -#include "variables.h" -} - -void AutoDismissSkulltulaMessage(uint16_t* textId, bool* loadFromMessageTable) { - if (IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_TOKENS)) { - *loadFromMessageTable = true; - return; - } - *loadFromMessageTable = false; - CustomMessage msg = CustomMessage::LoadVanillaMessageTableEntry(TEXT_GS_FREEZE); - msg.Replace(CustomMessage::MESSAGE_END(), "\x0E\x3C"); - msg += CustomMessage::MESSAGE_END(); - msg.LoadIntoFont(); -} - -void NoSkulltulaFreeze_Register() { - COND_ID_HOOK(OnOpenText, TEXT_GS_FREEZE, - CVarGetInteger(CVAR_ENHANCEMENT("SkulltulaFreeze"), 0) && - CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula"), 0) == 0, - AutoDismissSkulltulaMessage); - COND_ID_HOOK(OnOpenText, TEXT_GS_NO_FREEZE, - CVarGetInteger(CVAR_ENHANCEMENT("SkulltulaFreeze"), 0) && - CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula"), 0) == 0, - AutoDismissSkulltulaMessage); -} - -static RegisterShipInitFunc initFunc(NoSkulltulaFreeze_Register, { CVAR_ENHANCEMENT("SkulltulaFreeze") }); \ No newline at end of file diff --git a/soh/soh/Enhancements/QoL/NoSkulltulaFreeze.cpp b/soh/soh/Enhancements/QoL/NoSkulltulaFreeze.cpp new file mode 100644 index 000000000..66e6132a1 --- /dev/null +++ b/soh/soh/Enhancements/QoL/NoSkulltulaFreeze.cpp @@ -0,0 +1,31 @@ +#include +#include "soh/Enhancements/randomizer/randomizer.h" + +extern "C" { +#include "variables.h" +} + +void AutoDismissSkulltulaMessage(uint16_t* textId, bool* loadFromMessageTable) { + if (IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_TOKENS)) { + *loadFromMessageTable = true; + return; + } + *loadFromMessageTable = false; + CustomMessage msg = CustomMessage::LoadVanillaMessageTableEntry(TEXT_GS_FREEZE); + msg.Replace(CustomMessage::MESSAGE_END(), "\x0E\x3C"); + msg += CustomMessage::MESSAGE_END(); + msg.LoadIntoFont(); +} + +void NoSkulltulaFreeze_Register() { + COND_ID_HOOK(OnOpenText, TEXT_GS_FREEZE, + CVarGetInteger(CVAR_ENHANCEMENT("SkulltulaFreeze"), 0) && + CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula"), 0) == 0, + AutoDismissSkulltulaMessage); + COND_ID_HOOK(OnOpenText, TEXT_GS_NO_FREEZE, + CVarGetInteger(CVAR_ENHANCEMENT("SkulltulaFreeze"), 0) && + CVarGetInteger(CVAR_ENHANCEMENT("InjectItemCounts.GoldSkulltula"), 0) == 0, + AutoDismissSkulltulaMessage); +} + +static RegisterShipInitFunc initFunc(NoSkulltulaFreeze_Register, { CVAR_ENHANCEMENT("SkulltulaFreeze") }); \ No newline at end of file diff --git a/soh/soh/Enhancements/QoL/RebottleBlueFire.cpp b/soh/soh/Enhancements/QoL/RebottleBlueFire.cpp new file mode 100644 index 000000000..8ca11e4e6 --- /dev/null +++ b/soh/soh/Enhancements/QoL/RebottleBlueFire.cpp @@ -0,0 +1,25 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "src/overlays/actors/ovl_En_Ice_Hono/z_en_ice_hono.h" +void EnIceHono_CapturableFlame(EnIceHono* thisx, PlayState* play); +u32 EnIceHono_InBottleRange(EnIceHono* thisx, PlayState* play); +} + +extern PlayState* gPlayState; + +void OnEnIceHonoUpdate(void* actor) { + EnIceHono* thisx = (EnIceHono*)actor; + if (thisx->actionFunc != EnIceHono_CapturableFlame && EnIceHono_InBottleRange(thisx, gPlayState)) { + // GI_MAX in this case allows the player to catch the actor in a bottle + Actor_OfferGetItem(&thisx->actor, gPlayState, GI_MAX, 60.0f, 100.0f); + } +} + +void RegisterRebottleBlueFire() { + COND_ID_HOOK(OnActorUpdate, ACTOR_EN_ICE_HONO, CVarGetInteger(CVAR_ENHANCEMENT("RebottleBlueFire"), 0), + OnEnIceHonoUpdate); +} + +static RegisterShipInitFunc initFunc(RegisterRebottleBlueFire, { CVAR_ENHANCEMENT("RebottleBlueFire") }); \ No newline at end of file diff --git a/soh/soh/Enhancements/QoL/ResetHotKey.cpp b/soh/soh/Enhancements/QoL/ResetHotKey.cpp new file mode 100644 index 000000000..644b797f0 --- /dev/null +++ b/soh/soh/Enhancements/QoL/ResetHotKey.cpp @@ -0,0 +1,37 @@ +#include +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" +#include "functions.h" +#include "soh/OTRGlobals.h" + +extern "C" { +#include "z64.h" +#include "overlays/gamestates/ovl_file_choose/file_choose.h" +} + +static constexpr int32_t CVAR_RESET_BTN_MASK_DEFAULT = BTN_CUSTOM_MODIFIER2; +#define CVAR_RESET_BTN_MASK_NAME "gSettings.ResetBtn" +#define CVAR_RESET_BTN_MASK_VALUE CVarGetInteger(CVAR_RESET_BTN_MASK_NAME, CVAR_RESET_BTN_MASK_DEFAULT) + +static void OnGameStateMainStartResetHotkey() { + const int32_t packed = CVarGetInteger("gSettings.ResetBtn", BTN_CUSTOM_MODIFIER2); + + const uint16_t mask = static_cast(packed & 0xFFFF); + + if (mask != 0 && CHECK_BTN_ANY(gGameState->input[0].press.button, mask) && + CHECK_BTN_ALL(gGameState->input[0].cur.button, mask)) { + + auto consoleWin = std::reinterpret_pointer_cast( + Ship::Context::GetRawInstance()->GetWindow()->GetGui()->GetGuiWindow("Console")); + + if (consoleWin) { + consoleWin->Dispatch("reset"); + } + } +} + +static void RegisterResetHotkey() { + COND_HOOK(OnGameStateMainStart, true, OnGameStateMainStartResetHotkey); +} + +static RegisterShipInitFunc initFuncResetHotkey(RegisterResetHotkey, { CVAR_RESET_BTN_MASK_NAME }); diff --git a/soh/soh/Enhancements/QoL/ReworkedTargeting.cpp b/soh/soh/Enhancements/QoL/ReworkedTargeting.cpp new file mode 100644 index 000000000..aa2b5f06c --- /dev/null +++ b/soh/soh/Enhancements/QoL/ReworkedTargeting.cpp @@ -0,0 +1,57 @@ +#include +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" +#include "soh/OTRGlobals.h" + +extern "C" { +#include "z64.h" +#include "macros.h" +#include "functions.h" +extern PlayState* gPlayState; +} + +#define CVAR_REWORKED_TARGETING_NAME CVAR_ENHANCEMENT("ReworkedTargeting.Enabled") +#define CVAR_REWORKED_TARGETING_VALUE CVarGetInteger(CVAR_REWORKED_TARGETING_NAME, 0) + +static bool sTriggeredByButtonCombo = false; + +void RegisterReworkedTargeting() { + + COND_VB_SHOULD(VB_TOGGLE_Z_TARGET_SWITCH_TARGETS, CVAR_REWORKED_TARGETING_VALUE, { + Player* player = GET_PLAYER(gPlayState); + if (player->focusActor != NULL && !sTriggeredByButtonCombo) { + *should = false; + } + sTriggeredByButtonCombo = false; + }); + + COND_VB_SHOULD(VB_TOGGLE_Z_TARGET_SWITCH_DIRECTION, CVAR_REWORKED_TARGETING_VALUE, { + if (*should) + return; + + Player* player = GET_PLAYER(gPlayState); + + if (player->focusActor != NULL) { + Input* input = &gPlayState->state.input[0]; + + static bool wasButtonComboActive = false; + + const s32 targetSwitchMask = CVarGetInteger(CVAR_ENHANCEMENT("ReworkedTargeting.Btn"), 0); + bool isButtonComboActive = CHECK_BTN_ANY(input->press.button, targetSwitchMask); + + if (isButtonComboActive && !wasButtonComboActive) { + Actor* nextTarget = gPlayState->actorCtx.targetCtx.unk_94; + if (nextTarget != NULL) { + sTriggeredByButtonCombo = true; + *should = true; + } else { + *should = false; + } + } + + wasButtonComboActive = isButtonComboActive; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterReworkedTargeting, { CVAR_REWORKED_TARGETING_NAME }); diff --git a/soh/soh/Enhancements/RebottleBlueFire.cpp b/soh/soh/Enhancements/RebottleBlueFire.cpp deleted file mode 100644 index 8ca11e4e6..000000000 --- a/soh/soh/Enhancements/RebottleBlueFire.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "soh/ShipInit.hpp" - -extern "C" { -#include "src/overlays/actors/ovl_En_Ice_Hono/z_en_ice_hono.h" -void EnIceHono_CapturableFlame(EnIceHono* thisx, PlayState* play); -u32 EnIceHono_InBottleRange(EnIceHono* thisx, PlayState* play); -} - -extern PlayState* gPlayState; - -void OnEnIceHonoUpdate(void* actor) { - EnIceHono* thisx = (EnIceHono*)actor; - if (thisx->actionFunc != EnIceHono_CapturableFlame && EnIceHono_InBottleRange(thisx, gPlayState)) { - // GI_MAX in this case allows the player to catch the actor in a bottle - Actor_OfferGetItem(&thisx->actor, gPlayState, GI_MAX, 60.0f, 100.0f); - } -} - -void RegisterRebottleBlueFire() { - COND_ID_HOOK(OnActorUpdate, ACTOR_EN_ICE_HONO, CVarGetInteger(CVAR_ENHANCEMENT("RebottleBlueFire"), 0), - OnEnIceHonoUpdate); -} - -static RegisterShipInitFunc initFunc(RegisterRebottleBlueFire, { CVAR_ENHANCEMENT("RebottleBlueFire") }); \ No newline at end of file diff --git a/soh/soh/Enhancements/RemoteBombchu.cpp b/soh/soh/Enhancements/RemoteBombchu.cpp deleted file mode 100644 index 1813db23d..000000000 --- a/soh/soh/Enhancements/RemoteBombchu.cpp +++ /dev/null @@ -1,206 +0,0 @@ -#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "soh/ShipInit.hpp" - -extern "C" { -#include "src/overlays/actors/ovl_En_Bom_Chu/z_en_bom_chu.h" - -void EnBomChu_Move(EnBomChu*, PlayState*); -void EnBomChu_Explode(EnBomChu*, PlayState*); -s32 Camera_BGCheck(Camera* camera, Vec3f* from, Vec3f* to); -} - -#define CVAR_REMOTE_BOMBCHU_NAME CVAR_ENHANCEMENT("RemoteBombchu") -#define CVAR_REMOTE_BOMBCHU_DEFAULT 0 -#define CVAR_REMOTE_BOMBCHU_VALUE CVarGetInteger(CVAR_REMOTE_BOMBCHU_NAME, CVAR_REMOTE_BOMBCHU_DEFAULT) - -// Camera constants -#define CAM_DIST 200.0f -#define CAM_HEIGHT 50.0f -#define CAM_OFFSET_UP 15.0f - -// Control constants -#define TURN_RATE 1200.0f -#define STICK_THRESHOLD 10.0f - -typedef struct { - EnBomChu* activeChu; - s16 subCamId; - bool isActive; - Vec3f cameraEye; - Vec3f cameraAt; -} RemoteBombchuState; - -static RemoteBombchuState sState = { nullptr, SUBCAM_NONE, false, { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } }; - -static void RotateVectorAroundAxis(Vec3f* vec, Vec3f* axis, f32 angle) { - Matrix_RotateAxis(angle, axis, MTXMODE_NEW); - Vec3f result; - Matrix_MultVec3f(vec, &result); - *vec = result; -} - -static void StartControl(PlayState* play) { - if (sState.isActive) - return; - - sState.subCamId = Play_CreateSubCamera(play); - if (sState.subCamId == SUBCAM_NONE) - return; - - Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_WAIT); - Play_ChangeCameraStatus(play, sState.subCamId, CAM_STAT_ACTIVE); - - // Initialize camera vectors from main camera for smooth transition - Camera* mainCam = Play_GetCamera(play, MAIN_CAM); - sState.cameraEye = mainCam->eye; - sState.cameraAt = mainCam->at; - - Player* player = GET_PLAYER(play); - player->stateFlags1 |= PLAYER_STATE1_INPUT_DISABLED; - - // Reset first-person/item camera state - player->unk_6AD = 0; - - sState.isActive = true; -} - -static void StopControl(PlayState* play) { - if (!sState.isActive) - return; - - if (sState.subCamId != SUBCAM_NONE) { - Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_ACTIVE); - Play_ClearCamera(play, sState.subCamId); - sState.subCamId = SUBCAM_NONE; - } - - Player* player = GET_PLAYER(play); - player->stateFlags1 &= ~PLAYER_STATE1_INPUT_DISABLED; - - sState.activeChu = nullptr; - sState.isActive = false; -} - -// UpdateCamera function -static void UpdateCamera(EnBomChu* chu, PlayState* play) { - Vec3f targetEye, targetAt; - - // Calculate target "At" point (looking at the bombchu, slightly above) - targetAt.x = chu->actor.world.pos.x + chu->axisUp.x * CAM_OFFSET_UP; - targetAt.y = chu->actor.world.pos.y + chu->axisUp.y * CAM_OFFSET_UP; - targetAt.z = chu->actor.world.pos.z + chu->axisUp.z * CAM_OFFSET_UP; - - // Calculate target "Eye" position (behind and above the bombchu) - targetEye.x = targetAt.x - chu->axisForwards.x * CAM_DIST + chu->axisUp.x * CAM_HEIGHT; - targetEye.y = targetAt.y - chu->axisForwards.y * CAM_DIST + chu->axisUp.y * CAM_HEIGHT; - targetEye.z = targetAt.z - chu->axisForwards.z * CAM_DIST + chu->axisUp.z * CAM_HEIGHT; - - // Smoothly approach "At" position - Math_ApproachF(&sState.cameraAt.x, targetAt.x, 0.2f, 30.0f); - Math_ApproachF(&sState.cameraAt.y, targetAt.y, 0.2f, 30.0f); - Math_ApproachF(&sState.cameraAt.z, targetAt.z, 0.2f, 30.0f); - - Camera* camera = Play_GetCamera(play, sState.subCamId); - - // Check if the ideal target eye position collides with walls - Vec3f safeEye = targetEye; - Camera_BGCheck(camera, &sState.cameraAt, &safeEye); - - // Smoothly approach the *safe* eye position - Math_ApproachF(&sState.cameraEye.x, safeEye.x, 0.2f, 50.0f); - Math_ApproachF(&sState.cameraEye.y, safeEye.y, 0.2f, 50.0f); - Math_ApproachF(&sState.cameraEye.z, safeEye.z, 0.2f, 50.0f); - - Play_CameraSetAtEye(play, sState.subCamId, &sState.cameraAt, &sState.cameraEye); -} - -static void HandleSteering(EnBomChu* chu, Input* input) { - f32 stickX = input->cur.stick_x; - if (fabsf(stickX) <= STICK_THRESHOLD) - return; - - // Calculate turn angle based on stick input - f32 turnAngle = BINANG_TO_RAD((s16)(TURN_RATE * (stickX / 85.0f))); - - // Rotate forward and left vectors around the up axis - RotateVectorAroundAxis(&chu->axisForwards, &chu->axisUp, -turnAngle); - RotateVectorAroundAxis(&chu->axisLeft, &chu->axisUp, -turnAngle); - - // Update actor rotation from the new orientation - MtxF mf; - mf.xx = chu->axisLeft.x; - mf.yx = chu->axisLeft.y; - mf.zx = chu->axisLeft.z; - mf.xy = chu->axisUp.x; - mf.yy = chu->axisUp.y; - mf.zy = chu->axisUp.z; - mf.xz = chu->axisForwards.x; - mf.yz = chu->axisForwards.y; - mf.zz = chu->axisForwards.z; - - Matrix_MtxFToYXZRotS(&mf, &chu->actor.world.rot, 0); - chu->actor.world.rot.x = -chu->actor.world.rot.x; - chu->actor.shape.rot = chu->actor.world.rot; - chu->actor.shape.rot.x = -chu->actor.shape.rot.x; -} - -static void HandleInput(EnBomChu* chu, Input* input, PlayState* play) { - HandleSteering(chu, input); - - if (input->press.button & BTN_B) { - EnBomChu_Explode(chu, play); - } - - if (input->press.button & BTN_A) { - StopControl(play); - } -} - -// Track the most recently spawned bombchu -static void OnActorInit(void* refActor) { - sState.activeChu = (EnBomChu*)refActor; -} - -// Clean up if active bombchu is destroyed -static void OnActorDestroy(void* refActor) { - if (refActor == sState.activeChu) { - if (sState.isActive) { - StopControl(gPlayState); - } - sState.activeChu = nullptr; - } -} - -// Main update logic -static void OnActorUpdate(void* refActor) { - if (refActor != sState.activeChu) - return; - - if (sState.activeChu->actionFunc != EnBomChu_Move) - return; - - // Start control when bombchu begins moving - if (!sState.isActive) { - StartControl(gPlayState); - } - - // Exit early if control was not established or was stopped - if (!sState.isActive) - return; - - HandleInput(sState.activeChu, &gPlayState->state.input[0], gPlayState); - - // Check again after input handling (user might have pressed A to stop) - if (!sState.isActive) - return; - - UpdateCamera(sState.activeChu, gPlayState); -} - -void RegisterRemoteBombchu() { - COND_ID_HOOK(OnActorInit, ACTOR_EN_BOM_CHU, CVAR_REMOTE_BOMBCHU_VALUE, OnActorInit); - COND_ID_HOOK(OnActorDestroy, ACTOR_EN_BOM_CHU, CVAR_REMOTE_BOMBCHU_VALUE, OnActorDestroy); - COND_ID_HOOK(OnActorUpdate, ACTOR_EN_BOM_CHU, CVAR_REMOTE_BOMBCHU_VALUE, OnActorUpdate); -} - -static RegisterShipInitFunc initFunc(RegisterRemoteBombchu, { CVAR_REMOTE_BOMBCHU_NAME }); diff --git a/soh/soh/Enhancements/RemoveSpinAttackDarkness.cpp b/soh/soh/Enhancements/RemoveSpinAttackDarkness.cpp deleted file mode 100644 index 6349a7818..000000000 --- a/soh/soh/Enhancements/RemoveSpinAttackDarkness.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "soh/ShipInit.hpp" - -extern "C" { -#include "src/overlays/actors/ovl_En_M_Thunder/z_en_m_thunder.h" -} - -void Custom_EnMThunder_Update(Actor* thisx, PlayState* play) { - EnMThunder* enMThunder = (EnMThunder*)thisx; - f32 blueRadius; - s32 redGreen; - - // If thunder effect doesn't exist (aka player doesn't have magic), - // don't do anything. - if (enMThunder->actionFunc == nullptr) { - return; - } - - enMThunder->actionFunc(enMThunder, play); - // don't call this part, it's what makes the spin attack darkness happen - // func_80A9F314(play, this->dimmingIntensity); - blueRadius = enMThunder->spinAttackTimer; - redGreen = (u32)(blueRadius * 255.0f) & 0xFF; - Lights_PointNoGlowSetInfo(&enMThunder->lightInfo, enMThunder->actor.world.pos.x, enMThunder->actor.world.pos.y, - enMThunder->actor.world.pos.z, redGreen, redGreen, (u32)(blueRadius * 100.0f), - (s32)(blueRadius * 800.0f)); -} - -void OnEnMThunderInitReplaceUpdateWithCustom(void* thunder) { - EnMThunder* enMThunder = (EnMThunder*)thunder; - enMThunder->actor.update = Custom_EnMThunder_Update; -} - -#define CVAR_REMOVESPINATTACKDARKNESS_NAME CVAR_ENHANCEMENT("RemoveSpinAttackDarkness") -#define CVAR_REMOVESPINATTACKDARKNESS_DEFAULT 0 -#define CVAR_REMOVESPINATTACKDARKNESS_VALUE \ - CVarGetInteger(CVAR_REMOVESPINATTACKDARKNESS_NAME, CVAR_REMOVESPINATTACKDARKNESS_DEFAULT) - -void RegisterCustomEnMThunderUpdate() { - COND_ID_HOOK(OnActorInit, ACTOR_EN_M_THUNDER, - CVAR_REMOVESPINATTACKDARKNESS_VALUE != CVAR_REMOVESPINATTACKDARKNESS_DEFAULT, - OnEnMThunderInitReplaceUpdateWithCustom); -} - -static RegisterShipInitFunc initFunc(RegisterCustomEnMThunderUpdate, { CVAR_REMOVESPINATTACKDARKNESS_NAME }); diff --git a/soh/soh/Enhancements/ResetHotKey.cpp b/soh/soh/Enhancements/ResetHotKey.cpp deleted file mode 100644 index 644b797f0..000000000 --- a/soh/soh/Enhancements/ResetHotKey.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include -#include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/ShipInit.hpp" -#include "functions.h" -#include "soh/OTRGlobals.h" - -extern "C" { -#include "z64.h" -#include "overlays/gamestates/ovl_file_choose/file_choose.h" -} - -static constexpr int32_t CVAR_RESET_BTN_MASK_DEFAULT = BTN_CUSTOM_MODIFIER2; -#define CVAR_RESET_BTN_MASK_NAME "gSettings.ResetBtn" -#define CVAR_RESET_BTN_MASK_VALUE CVarGetInteger(CVAR_RESET_BTN_MASK_NAME, CVAR_RESET_BTN_MASK_DEFAULT) - -static void OnGameStateMainStartResetHotkey() { - const int32_t packed = CVarGetInteger("gSettings.ResetBtn", BTN_CUSTOM_MODIFIER2); - - const uint16_t mask = static_cast(packed & 0xFFFF); - - if (mask != 0 && CHECK_BTN_ANY(gGameState->input[0].press.button, mask) && - CHECK_BTN_ALL(gGameState->input[0].cur.button, mask)) { - - auto consoleWin = std::reinterpret_pointer_cast( - Ship::Context::GetRawInstance()->GetWindow()->GetGui()->GetGuiWindow("Console")); - - if (consoleWin) { - consoleWin->Dispatch("reset"); - } - } -} - -static void RegisterResetHotkey() { - COND_HOOK(OnGameStateMainStart, true, OnGameStateMainStartResetHotkey); -} - -static RegisterShipInitFunc initFuncResetHotkey(RegisterResetHotkey, { CVAR_RESET_BTN_MASK_NAME }); diff --git a/soh/soh/Enhancements/ReworkedTargeting.cpp b/soh/soh/Enhancements/ReworkedTargeting.cpp deleted file mode 100644 index aa2b5f06c..000000000 --- a/soh/soh/Enhancements/ReworkedTargeting.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include -#include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/ShipInit.hpp" -#include "soh/OTRGlobals.h" - -extern "C" { -#include "z64.h" -#include "macros.h" -#include "functions.h" -extern PlayState* gPlayState; -} - -#define CVAR_REWORKED_TARGETING_NAME CVAR_ENHANCEMENT("ReworkedTargeting.Enabled") -#define CVAR_REWORKED_TARGETING_VALUE CVarGetInteger(CVAR_REWORKED_TARGETING_NAME, 0) - -static bool sTriggeredByButtonCombo = false; - -void RegisterReworkedTargeting() { - - COND_VB_SHOULD(VB_TOGGLE_Z_TARGET_SWITCH_TARGETS, CVAR_REWORKED_TARGETING_VALUE, { - Player* player = GET_PLAYER(gPlayState); - if (player->focusActor != NULL && !sTriggeredByButtonCombo) { - *should = false; - } - sTriggeredByButtonCombo = false; - }); - - COND_VB_SHOULD(VB_TOGGLE_Z_TARGET_SWITCH_DIRECTION, CVAR_REWORKED_TARGETING_VALUE, { - if (*should) - return; - - Player* player = GET_PLAYER(gPlayState); - - if (player->focusActor != NULL) { - Input* input = &gPlayState->state.input[0]; - - static bool wasButtonComboActive = false; - - const s32 targetSwitchMask = CVarGetInteger(CVAR_ENHANCEMENT("ReworkedTargeting.Btn"), 0); - bool isButtonComboActive = CHECK_BTN_ANY(input->press.button, targetSwitchMask); - - if (isButtonComboActive && !wasButtonComboActive) { - Actor* nextTarget = gPlayState->actorCtx.targetCtx.unk_94; - if (nextTarget != NULL) { - sTriggeredByButtonCombo = true; - *should = true; - } else { - *should = false; - } - } - - wasButtonComboActive = isButtonComboActive; - } - }); -} - -static RegisterShipInitFunc initFunc(RegisterReworkedTargeting, { CVAR_REWORKED_TARGETING_NAME }); diff --git a/soh/soh/Enhancements/SkipAmyPuzzle.cpp b/soh/soh/Enhancements/SkipAmyPuzzle.cpp deleted file mode 100644 index 8409390ef..000000000 --- a/soh/soh/Enhancements/SkipAmyPuzzle.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/ShipInit.hpp" - -extern "C" { -#include "functions.h" -extern SaveContext gSaveContext; -} - -void RegisterSkipAmyPuzzle() { - COND_VB_SHOULD(VB_AMY_SOLVE, CVarGetInteger(CVAR_ENHANCEMENT("SkipAmyPuzzle"), 0), - { *should = !IS_RANDO || Flags_GetRandomizerInf(RAND_INF_CAN_GRAB); }); -} - -static RegisterShipInitFunc initFunc(RegisterSkipAmyPuzzle, { CVAR_ENHANCEMENT("SkipAmyPuzzle") }); \ No newline at end of file diff --git a/soh/soh/Enhancements/SunlightArrows.cpp b/soh/soh/Enhancements/SunlightArrows.cpp deleted file mode 100644 index 73e894c24..000000000 --- a/soh/soh/Enhancements/SunlightArrows.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "soh/Enhancements/randomizer/SeedContext.h" -#include "soh/ShipInit.hpp" -#include "soh/ObjectExtension/ObjectExtension.h" - -extern "C" { -#include "overlays/actors/ovl_Obj_Lightswitch/z_obj_lightswitch.h" -} - -static ColliderJntSphElementInit sColliderLightArrowElementInit[] = { - { - { - ELEMTYPE_UNK0, - { 0x00000000, 0x00, 0x00 }, - { 0x00202000, 0x00, 0x00 }, - TOUCH_NONE, - BUMP_ON, - OCELEM_ON, - }, - { 0, { { 0, 0, 0 }, 19 }, 100 }, - }, -}; -static ColliderJntSphInit sColliderLightArrowInit = { - { - COLTYPE_NONE, - AT_NONE, - AC_ON | AC_TYPE_PLAYER, - OC1_ON | OC1_TYPE_ALL, - OC2_TYPE_2, - COLSHAPE_JNTSPH, - }, - 1, - sColliderLightArrowElementInit, -}; - -struct SunlightArrowData { - bool activatedByLightArrow = false; -}; - -static ObjectExtension::Register SunlightArrowDataRegister; - -void RegisterSunlightArrowsHooks() { - bool shouldRegister = - CVarGetInteger(CVAR_ENHANCEMENT("SunlightArrows"), 0) || (IS_RANDO && RAND_GET_OPTION(RSK_SUNLIGHT_ARROWS)); - - COND_ID_HOOK(OnActorInit, ACTOR_OBJ_LIGHTSWITCH, shouldRegister, [](void* actor) { - auto* thisx = (ObjLightswitch*)actor; - Collider_SetJntSph(gPlayState, &thisx->collider, &thisx->actor, &sColliderLightArrowInit, thisx->colliderItems); - Collider_UpdateSpheres(0, &thisx->collider); - }); - - COND_ID_HOOK(OnActorDestroy, ACTOR_OBJ_LIGHTSWITCH, shouldRegister, [](void* actor) { - auto* thisx = (ObjLightswitch*)actor; - - auto sunData = ObjectExtension::GetInstance().Get(&thisx->actor); - if (sunData != nullptr && sunData->activatedByLightArrow) { - switch (thisx->actor.params >> 4 & 3) { - case OBJLIGHTSWITCH_TYPE_STAY_ON: - case OBJLIGHTSWITCH_TYPE_2: - case OBJLIGHTSWITCH_TYPE_1: - // Unset the switch flag on room exit to prevent the rock in the wall from - // vanishing on its own after activating the sun switch by Light Arrow - // Also prevents the cobra mirror from rotating to face the sun on its own - // Makes sun switches temporary when activated by Light Arrows (will turn off on room exit) - if (thisx->actor.room != 25) { - Flags_UnsetSwitch(gPlayState, thisx->actor.params >> 8 & 0x3F); - } - break; - case OBJLIGHTSWITCH_TYPE_BURN: - break; - } - } - }); - - COND_VB_SHOULD(VB_LIGHTSWITCH_OFF, shouldRegister, { - ObjLightswitch* thisx = va_arg(args, ObjLightswitch*); - auto sunData = ObjectExtension::GetInstance().Get(&thisx->actor); - if (sunData != nullptr && sunData->activatedByLightArrow) { - *should = false; - } - }); - - COND_ID_HOOK(ShouldActorUpdate, ACTOR_OBJ_LIGHTSWITCH, shouldRegister, [](void* actorPtr, bool* result) { - ObjLightswitch* thisx = (ObjLightswitch*)actorPtr; - if ((thisx->collider.base.acFlags & AC_HIT) && thisx->collider.base.ac != nullptr) { - auto sunData = ObjectExtension::GetInstance().Get(&thisx->actor); - if (sunData == nullptr) { - ObjectExtension::GetInstance().Set(&thisx->actor, SunlightArrowData{}); - sunData = ObjectExtension::GetInstance().Get(&thisx->actor); - } - - sunData->activatedByLightArrow = thisx->collider.base.ac->id == ACTOR_EN_ARROW; - } - }); -} - -static RegisterShipInitFunc initFunc(RegisterSunlightArrowsHooks, { "IS_RANDO", CVAR_ENHANCEMENT("SunlightArrows") }); diff --git a/soh/soh/Enhancements/TimeSavers/SkipAmyPuzzle.cpp b/soh/soh/Enhancements/TimeSavers/SkipAmyPuzzle.cpp new file mode 100644 index 000000000..8409390ef --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/SkipAmyPuzzle.cpp @@ -0,0 +1,14 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "functions.h" +extern SaveContext gSaveContext; +} + +void RegisterSkipAmyPuzzle() { + COND_VB_SHOULD(VB_AMY_SOLVE, CVarGetInteger(CVAR_ENHANCEMENT("SkipAmyPuzzle"), 0), + { *should = !IS_RANDO || Flags_GetRandomizerInf(RAND_INF_CAN_GRAB); }); +} + +static RegisterShipInitFunc initFunc(RegisterSkipAmyPuzzle, { CVAR_ENHANCEMENT("SkipAmyPuzzle") }); \ No newline at end of file diff --git a/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp new file mode 100644 index 000000000..e272b13c7 --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/timesaver_hook_handlers.cpp @@ -0,0 +1,1437 @@ +#include +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/enhancementTypes.h" +#include "soh/Enhancements/randomizer/SeedContext.h" + +extern "C" { +#include "src/overlays/actors/ovl_En_Wonder_Talk2/z_en_wonder_talk2.h" +#include "src/overlays/actors/ovl_Elf_Msg/z_elf_msg.h" +#include "src/overlays/actors/ovl_Obj_Switch/z_obj_switch.h" +#include "src/overlays/actors/ovl_Bg_Bdan_Switch/z_bg_bdan_switch.h" +#include "src/overlays/actors/ovl_En_Owl/z_en_owl.h" +#include "src/overlays/actors/ovl_En_Go2/z_en_go2.h" +#include "src/overlays/actors/ovl_En_Heishi2/z_en_heishi2.h" +#include "src/overlays/actors/ovl_En_Ma1/z_en_ma1.h" +#include "src/overlays/actors/ovl_En_Ru2/z_en_ru2.h" +#include "src/overlays/actors/ovl_En_Box/z_en_box.h" +#include "src/overlays/actors/ovl_Demo_Im/z_demo_im.h" +#include "src/overlays/actors/ovl_Demo_Kekkai/z_demo_kekkai.h" +#include "src/overlays/actors/ovl_En_Sa/z_en_sa.h" +#include "src/overlays/actors/ovl_Bg_Ddan_Kd/z_bg_ddan_kd.h" +#include "src/overlays/actors/ovl_En_Tk/z_en_tk.h" +#include "src/overlays/actors/ovl_En_Fu/z_en_fu.h" +#include "src/overlays/actors/ovl_En_Jj/z_en_jj.h" +#include "src/overlays/actors/ovl_En_Daiku/z_en_daiku.h" +#include "src/overlays/actors/ovl_Bg_Spot02_Objects/z_bg_spot02_objects.h" +#include "src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.h" +#include "src/overlays/actors/ovl_Bg_Spot03_Taki/z_bg_spot03_taki.h" +#include "src/overlays/actors/ovl_Bg_Hidan_Kousi/z_bg_hidan_kousi.h" +#include "src/overlays/actors/ovl_Bg_Dy_Yoseizo/z_bg_dy_yoseizo.h" +#include "src/overlays/actors/ovl_En_Dnt_Demo/z_en_dnt_demo.h" +#include "src/overlays/actors/ovl_En_Po_Sisters/z_en_po_sisters.h" +#include "src/overlays/actors/ovl_Obj_Lightswitch/z_obj_lightswitch.h" +#include "src/overlays/actors/ovl_Bg_Jya_Bombchuiwa/z_bg_jya_bombchuiwa.h" +#include +#include +#include +extern SaveContext gSaveContext; +extern PlayState* gPlayState; +extern int32_t D_8011D3AC; + +extern void BgSpot03Taki_HandleWaterfallState(BgSpot03Taki* bgSpot03Taki, PlayState* play); +extern void BgSpot03Taki_ApplyOpeningAlpha(BgSpot03Taki* bgSpot03Taki, s32 bufferIndex); + +extern void EnGo2_CurledUp(EnGo2* enGo2, PlayState* play); + +extern void EnRu2_SetEncounterSwitchFlag(EnRu2* enRu2, PlayState* play); + +extern void EnDaiku_EscapeSuccess(EnDaiku* enDaiku, PlayState* play); + +extern void EnJj_WaitToOpenMouth(EnJj* enJj, PlayState* play); +extern void EnJj_WaitForFish(EnJj* enJj, PlayState* play); +extern void EnJj_SetupAction(EnJj* enJj, EnJjActionFunc actionFunc); +} + +void EnMa1_EndTeachSong(EnMa1* enMa1, PlayState* play) { + if (Message_GetState(&gPlayState->msgCtx) == TEXT_STATE_CLOSING) { + Flags_SetRandomizerInf(RAND_INF_LEARNED_EPONA_SONG); + Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME); + enMa1->actor.flags &= ~ACTOR_FLAG_TALK_OFFER_AUTO_ACCEPTED; + play->msgCtx.ocarinaMode = OCARINA_MODE_04; + enMa1->actionFunc = EnMa1_Idle; + enMa1->singingDisabled = 1; + enMa1->interactInfo.talkState = NPC_TALK_STATE_IDLE; + return; + } +} + +void EnFu_EndTeachSong(EnFu* enFu, PlayState* play) { + if (Message_GetState(&gPlayState->msgCtx) == TEXT_STATE_CLOSING) { + Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME); + enFu->actionFunc = EnFu_WaitAdult; + enFu->actor.flags &= ~ACTOR_FLAG_TALK_OFFER_AUTO_ACCEPTED; + + play->msgCtx.ocarinaMode = OCARINA_MODE_04; + Flags_SetEventChkInf(EVENTCHKINF_PLAYED_SONG_OF_STORMS_IN_WINDMILL); + Flags_SetEventChkInf(EVENTCHKINF_LEARNED_SONG_OF_STORMS); + return; + } +} + +void EnDntDemo_JudgeSkipToReward(EnDntDemo* enDntDemo, PlayState* play) { + // todo: figure out a better way to handle toggling so we don't + // need to double check cvars like this + if (!(IS_RANDO || CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) { + EnDntDemo_Judge(enDntDemo, play); + return; + } else if ((IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_SPEAK)) || enDntDemo->actor.xzDistToPlayer > 30.0f) { + if (enDntDemo->judgeTimer > 0 && enDntDemo->judgeTimer < 40) { + enDntDemo->judgeTimer = 40; + } + EnDntDemo_Judge(enDntDemo, play); + return; + } + + switch (Player_GetMask(play)) { + case PLAYER_MASK_SKULL: { + Flags_SetItemGetInf(ITEMGETINF_OBTAINED_STICK_UPGRADE_FROM_STAGE); + return; + } + case PLAYER_MASK_TRUTH: { + if (GameInteractor_Should(VB_DEKU_SCRUBS_REACT_TO_MASK_OF_TRUTH, true)) { + Flags_SetItemGetInf(ITEMGETINF_OBTAINED_NUT_UPGRADE_FROM_STAGE); + } + return; + } + default: { + EnDntDemo_Judge(enDntDemo, play); + return; + } + } +} + +void BgSpot03Taki_KeepOpen(BgSpot03Taki* bgSpot03Taki, PlayState* play) { +} + +static u32 successChimeCooldown = 0; +void RateLimitedSuccessChime() { + if (gPlayState->gameplayFrames > successChimeCooldown) { + Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME); + successChimeCooldown = gPlayState->gameplayFrames + 120; + } +} + +bool ForcedDialogIsDisabled(ForcedDialogMode type) { + return (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipForcedDialog"), + IS_RANDO ? FORCED_DIALOG_SKIP_ALL : FORCED_DIALOG_SKIP_NONE) & + type) != 0; +} + +void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_list originalArgs) { + va_list args; + va_copy(args, originalArgs); + + switch (id) { + case VB_PLAY_TRANSITION_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO) { + // Song of Time + if (gSaveContext.entranceIndex == ENTR_TEMPLE_OF_TIME_ENTRANCE && + gSaveContext.cutsceneIndex == 0xFFF7) { + gSaveContext.entranceIndex = ENTR_HYRULE_FIELD_16; + gSaveContext.cutsceneIndex = 0; + gSaveContext.nextTransitionType = 3; + *should = false; + } + + // Requiem of Spirit + if ((gSaveContext.entranceIndex == ENTR_DESERT_COLOSSUS_OUTSIDE_TEMPLE) && + !Flags_GetEventChkInf(EVENTCHKINF_LEARNED_REQUIEM_OF_SPIRIT)) { + Flags_SetEventChkInf(EVENTCHKINF_LEARNED_REQUIEM_OF_SPIRIT); + // Normally happens in the cutscene + gSaveContext.dayTime = gSaveContext.skyboxTime = 0xAC60; + if (GameInteractor_Should(VB_GIVE_ITEM_SONG, true, ITEM_SONG_REQUIEM)) { + Item_Give(gPlayState, ITEM_SONG_REQUIEM); + } + *should = false; + } + + u8 meetsBurningKakRequirements = LINK_IS_ADULT && gSaveContext.cutsceneIndex < 0xFFF0 && + gSaveContext.entranceIndex == ENTR_KAKARIKO_VILLAGE_FRONT_GATE && + Flags_GetEventChkInf(EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP) && + Flags_GetEventChkInf(EVENTCHKINF_USED_FIRE_TEMPLE_BLUE_WARP) && + Flags_GetEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP) && + !Flags_GetEventChkInf(EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL); + if (GameInteractor_Should(VB_BE_ELIGIBLE_FOR_NOCTURNE_OF_SHADOW, meetsBurningKakRequirements)) { + Flags_SetEventChkInf(EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL); + // Normally happens in the cutscene + Flags_SetEventChkInf(EVENTCHKINF_LEARNED_NOCTURNE_OF_SHADOW); + if (GameInteractor_Should(VB_GIVE_ITEM_SONG, true, ITEM_SONG_NOCTURNE)) { + Item_Give(gPlayState, ITEM_SONG_NOCTURNE); + } + *should = false; + } + } + + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + // LACS + bool meetsLACSRequirements = + LINK_IS_ADULT && + (gEntranceTable[((void)0, gSaveContext.entranceIndex)].scene == SCENE_TEMPLE_OF_TIME) && + CHECK_QUEST_ITEM(QUEST_MEDALLION_SPIRIT) && CHECK_QUEST_ITEM(QUEST_MEDALLION_SHADOW) && + !Flags_GetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS); + if (GameInteractor_Should(VB_BE_ELIGIBLE_FOR_LIGHT_ARROWS, meetsLACSRequirements)) { + Flags_SetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS); + if (GameInteractor_Should(VB_GIVE_ITEM_LIGHT_ARROW, true)) { + Item_Give(gPlayState, ITEM_ARROW_LIGHT); + } + *should = false; + } + } + + if (gSaveContext.entranceIndex == ENTR_GANONS_TOWER_COLLAPSE_EXTERIOR_0) { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipTowerEscape"), false) || IS_BOSS_RUSH) { + Flags_SetEventChkInf(EVENTCHKINF_WATCHED_GANONS_CASTLE_COLLAPSE_CAUGHT_BY_GERUDO); + gSaveContext.entranceIndex = ENTR_GANON_BOSS_0; + *should = false; + } + } + + if (gSaveContext.entranceIndex == ENTR_CASTLE_COURTYARD_GUARDS_DAY_0) { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipChildStealth"), false)) { + gSaveContext.entranceIndex = ENTR_CASTLE_COURTYARD_ZELDA_0; + *should = false; + } + } + break; + } + case VB_PLAY_ENTRANCE_CS: { + s32 entranceFlag = va_arg(args, s32); + s32 entranceIndex = va_arg(args, s32); + + // Epona LLR fence jump cutscenes not skipped to allow the player and epona to load in the world correctly + // Nabooru fight cutscene is handled by boss intro skip instead (which deals with other flags needing to be + // set) + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), IS_RANDO) && + (entranceFlag != EVENTCHKINF_EPONA_OBTAINED) && entranceIndex != ENTR_SPIRIT_TEMPLE_BOSS_ENTRANCE) { + *should = false; + + // Check for dispulsion of Ganon's Tower barrier + switch (entranceIndex) { + case ENTR_INSIDE_GANONS_CASTLE_2: + case ENTR_INSIDE_GANONS_CASTLE_3: + case ENTR_INSIDE_GANONS_CASTLE_4: + case ENTR_INSIDE_GANONS_CASTLE_5: + case ENTR_INSIDE_GANONS_CASTLE_6: + case ENTR_INSIDE_GANONS_CASTLE_7: + if (Flags_GetEventChkInf(EVENTCHKINF_COMPLETED_FOREST_TRIAL) && + Flags_GetEventChkInf(EVENTCHKINF_COMPLETED_WATER_TRIAL) && + Flags_GetEventChkInf(EVENTCHKINF_COMPLETED_SHADOW_TRIAL) && + Flags_GetEventChkInf(EVENTCHKINF_COMPLETED_FIRE_TRIAL) && + Flags_GetEventChkInf(EVENTCHKINF_COMPLETED_LIGHT_TRIAL) && + Flags_GetEventChkInf(EVENTCHKINF_COMPLETED_SPIRIT_TRIAL)) { + Flags_SetEventChkInf(EVENTCHKINF_DISPELLED_GANONS_TOWER_BARRIER); + } + break; + } + } + break; + } + case VB_PLAY_ONEPOINT_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO)) { + s16* csId = va_arg(args, s16*); + BgSpot03Taki* taki = NULL; + switch (*csId) { + case 3120: + case 3150: + case 4180: + case 4100: + *should = false; + RateLimitedSuccessChime(); + Message_CloseTextbox(gPlayState); + taki = (BgSpot03Taki*)Actor_FindNearby(gPlayState, &GET_PLAYER(gPlayState)->actor, + ACTOR_BG_SPOT03_TAKI, ACTORCAT_BG, 999.0f); + if (taki != NULL) { + func_8003EBF8(gPlayState, &gPlayState->colCtx.dyna, taki->dyna.bgId); + } + break; + default: + SPDLOG_INFO("VB_PLAY_ONEPOINT_CS {}", *csId); + break; + } + } + break; + } + case VB_PLAY_ONEPOINT_ACTOR_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO)) { + Actor* actor = va_arg(args, Actor*); + + // there are a few checks throughout the game (such as chest spawns) that rely on this + // the checks are for func_8005B198() == this->dyna.actor.category + // func_8005B198 just returns D_8011D3AC + // D_8011D3AC is set to camera->target->category in Camera_Demo5 + D_8011D3AC = actor->category; + + switch (actor->category) { + case ACTORCAT_BG: + if (actor->id == ACTOR_BG_DDAN_KD) { + BgDdanKd* ddanKd = (BgDdanKd*)actor; + Flags_SetSwitch(gPlayState, ddanKd->dyna.actor.params); + } + if (actor->id == ACTOR_BG_MORI_HINERI) { + break; + } + // This is handled in the FasterHeavyBlockLift enhancement + if (actor->id == ACTOR_BG_HEAVY_BLOCK) { + break; + } + + RateLimitedSuccessChime(); + *should = false; + break; + } + switch (actor->id) { + case ACTOR_OBJ_SWITCH: { + // The DC boss door can be unlocked with OI; One Point is required for it + // The Water Temple Dragon Room chest can be obtained with a cutscene dive + if (((actor->params == 8224 && gPlayState->sceneNum == SCENE_DODONGOS_CAVERN) || + (actor->params == 6979 && gPlayState->sceneNum == SCENE_WATER_TEMPLE)) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { + break; + } + ObjSwitch* switchActor = (ObjSwitch*)actor; + switchActor->cooldownTimer = 0; + *should = false; + RateLimitedSuccessChime(); + break; + } + case ACTOR_BG_BDAN_SWITCH: { + // The switch in jabu that you are intended to press with a box to reach barinade + // can be skipped by either a frame perfect roll open or with OI + // Additionally, the blue switch that you are intended to press with Ruto + // can be skipped with OI + // The One Point for that switch is used in common setups for the former and is required for the + // latter to work + if ((actor->params == 14848 || actor->params == 14336) && + gPlayState->sceneNum == SCENE_JABU_JABU && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { + break; + } + BgBdanSwitch* switchActor = (BgBdanSwitch*)actor; + switchActor->unk_1D8 = 0; + switchActor->unk_1DA = 0; + *should = false; + RateLimitedSuccessChime(); + break; + } + case ACTOR_BG_HIDAN_KOUSI: { + BgHidanKousi* switchActor = (BgHidanKousi*)actor; + BgHidanKousi_SetupAction(switchActor, func_80889C18); + *should = false; + RateLimitedSuccessChime(); + break; + } + case ACTOR_BG_JYA_BOMBCHUIWA: { + BgJyaBombchuiwa* bombchuiwa = (BgJyaBombchuiwa*)actor; + if (!(bombchuiwa->drawFlags & 4) && bombchuiwa->timer >= 0 && bombchuiwa->timer < 9) { + bombchuiwa->timer = 9; + } + *should = false; + RateLimitedSuccessChime(); + break; + } + case ACTOR_EN_GO2: { + EnGo2* biggoron = (EnGo2*)actor; + biggoron->isAwake = true; + *should = false; + break; + } + case ACTOR_EN_BOX: { + // The chest that drops in MQ Jabu allowing unintended door entry + if (actor->params == -30457 && gPlayState->sceneNum == SCENE_JABU_JABU && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { + break; + } + EnBox* boxActor = (EnBox*)actor; + *should = false; + RateLimitedSuccessChime(); + break; + } + case ACTOR_EN_SIOFUKI: { + // The Spirit Temple MQ water jet cutscene is required for an actor glitch + // setup that skips the grate + if (actor->params == 6359 && gPlayState->sceneNum == SCENE_SPIRIT_TEMPLE && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { + *should = true; + break; + } + *should = false; + break; + } + case ACTOR_BG_HIDAN_FWBIG: + case ACTOR_EN_EX_ITEM: + case ACTOR_EN_DNT_NOMAL: + case ACTOR_EN_DNT_DEMO: + case ACTOR_BG_HAKA_ZOU: { + *should = false; + break; + } + case ACTOR_EN_TA: + case ACTOR_DOOR_SHUTTER: { + // The shutter cutscene occurs post-switch cutscene to focus Link on the unlocked doors + if (((actor->params == 9402 && gPlayState->sceneNum == SCENE_JABU_JABU) || + (actor->params == 20460 && gPlayState->sceneNum == SCENE_DODONGOS_CAVERN)) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { + break; + } + *should = false; + RateLimitedSuccessChime(); + break; + } + case ACTOR_OBJ_LIGHTSWITCH: { + ObjLightswitch* lightswitch = (ObjLightswitch*)actor; + lightswitch->toggleDelay = 0; + *should = false; + RateLimitedSuccessChime(); + break; + } + case ACTOR_BG_ICE_SHUTTER: + case ACTOR_OBJ_SYOKUDAI: + case ACTOR_OBJ_TIMEBLOCK: + case ACTOR_EN_PO_SISTERS: + case ACTOR_OBJ_ICE_POLY: + case ACTOR_BG_YDAN_MARUTA: + case ACTOR_BG_SPOT18_SHUTTER: + case ACTOR_BG_SPOT05_SOKO: + case ACTOR_BG_SPOT06_OBJECTS: + case ACTOR_BG_SPOT18_BASKET: + case ACTOR_BG_HIDAN_CURTAIN: + case ACTOR_BG_MORI_HINERI: + case ACTOR_BG_MIZU_SHUTTER: + case ACTOR_SHOT_SUN: + case ACTOR_BG_HAKA_GATE: + case ACTOR_EN_KAKASI2: + case ACTOR_EN_DNT_JIJI: + *should = false; + RateLimitedSuccessChime(); + break; + } + if (*should) { + SPDLOG_INFO("VB_PLAY_ONEPOINT_ACTOR_CS ID:{} Cat:{}", actor->id, actor->category); + } + } + break; + } + case VB_FREEZE_LINK_FOR_FOREST_PILLARS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO)) { + *should = false; + } + break; + case VB_SHOW_TITLE_CARD: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), IS_RANDO)) { + *should = false; + } + break; + case VB_WONDER_TALK: { + // We want to show the frog hint if it is on, regardless of cutscene settings + if (ForcedDialogIsDisabled(FORCED_DIALOG_SKIP_NPC) && + !(gPlayState->sceneNum == SCENE_ZORAS_RIVER && IS_RANDO && RAND_GET_OPTION(RSK_FROGS_HINT))) { + *should = false; + } + + // If it's near a jailed carpenter, skip it along with introduction of Gerudo mini-boss + if (gPlayState->sceneNum == SCENE_THIEVES_HIDEOUT && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { + EnWonderTalk2* enWonderTalk = va_arg(args, EnWonderTalk2*); + EnDaiku* enDaiku = + (EnDaiku*)Actor_FindNearby(gPlayState, &enWonderTalk->actor, ACTOR_EN_DAIKU, ACTORCAT_NPC, 999.0f); + if (enDaiku != NULL) { + Flags_SetSwitch(gPlayState, enDaiku->startFightSwitchFlag); + *should = false; + } + } + break; + } + case VB_NAVI_TALK: { + if (ForcedDialogIsDisabled(FORCED_DIALOG_SKIP_NAVI)) { + ElfMsg* naviTalk = va_arg(args, ElfMsg*); + int32_t paramsHighByte = naviTalk->actor.params >> 8; + if ((paramsHighByte & 0x80) != 0) { + break; + } + if ((paramsHighByte & 0x3F) != 0x3F) { + Flags_SetSwitch(gPlayState, paramsHighByte & 0x3F); + } + Actor_Kill(&naviTalk->actor); + *should = false; + } + break; + } + case VB_END_GERUDO_MEMBERSHIP_TALK: { + if (ForcedDialogIsDisabled(FORCED_DIALOG_SKIP_NPC)) { + *should = true; + } + break; + } + case VB_GORON_LINK_BE_SCARED: { + if (ForcedDialogIsDisabled(FORCED_DIALOG_SKIP_NPC)) { + EnGo2* goronLink = va_arg(args, EnGo2*); + goronLink->trackingMode = NPC_TRACKING_NONE; + goronLink->unk_211 = false; + goronLink->isAwake = false; + goronLink->actionFunc = EnGo2_CurledUp; + Flags_SetInfTable(INFTABLE_STOPPED_GORON_LINKS_ROLLING); + *should = false; + } + break; + } + case VB_NOT_BE_GREETED_BY_SARIA: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), IS_RANDO) && + !Flags_GetInfTable(INFTABLE_GREETED_BY_SARIA)) { + Flags_SetInfTable(INFTABLE_GREETED_BY_SARIA); + *should = true; + } + break; + case VB_PLAY_SHIEK_BLOCK_MASTER_SWORD_CS: + case VB_GIVE_ITEM_LIGHT_ARROW: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + *should = false; + } + break; + case VB_PLAY_NABOORU_CAPTURED_CS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + Flags_SetEventChkInf(EVENTCHKINF_NABOORU_CAPTURED_BY_TWINROVA); + *should = false; + } + break; + case VB_PLAY_PULL_MASTER_SWORD_CS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + if (!Flags_GetEventChkInf(EVENTCHKINF_PULLED_MASTER_SWORD_FROM_PEDESTAL)) { + // Normally, these would be done in the cutscene, but we're skipping it + Flags_SetEventChkInf(EVENTCHKINF_PULLED_MASTER_SWORD_FROM_PEDESTAL); + Flags_SetEventChkInf(EVENTCHKINF_ENTERED_MASTER_SWORD_CHAMBER); + Flags_SetEventChkInf(EVENTCHKINF_SHEIK_SPAWNED_AT_MASTER_SWORD_PEDESTAL); + Flags_SetEventChkInf(EVENTCHKINF_TIME_TRAVELED_TO_ADULT); + if (GameInteractor_Should(VB_GIVE_ITEM_LIGHT_MEDALLION, true)) { + Item_Give(gPlayState, ITEM_MEDALLION_LIGHT); + } + } + *should = false; + } + break; + case VB_PLAY_DISPEL_BARRIER_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + static s16 trialEntrances[] = { + 0, + ENTR_INSIDE_GANONS_CASTLE_3, + ENTR_INSIDE_GANONS_CASTLE_6, + ENTR_INSIDE_GANONS_CASTLE_5, + ENTR_INSIDE_GANONS_CASTLE_4, + ENTR_INSIDE_GANONS_CASTLE_7, + ENTR_INSIDE_GANONS_CASTLE_2, + }; + RateLimitedSuccessChime(); + DemoKekkai* kekkai = va_arg(args, DemoKekkai*); + gPlayState->nextEntranceIndex = trialEntrances[kekkai->actor.params]; + gPlayState->transitionTrigger = TRANS_TRIGGER_START; + gPlayState->transitionType = TRANS_TYPE_FADE_BLACK; + *should = false; + } + break; + } + case VB_OWL_INTERACTION: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), IS_RANDO) && *should) { + EnOwl* enOwl = va_arg(args, EnOwl*); + s32 owlType = (enOwl->actor.params & 0xFC0) >> 6; + + if (((enOwl->actor.params & 0xFC0) >> 6) == 1) { + Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_KAEPORA_BY_LOST_WOODS); + } + + func_80ACA62C(enOwl, gPlayState); + *should = false; + } + break; + } + case VB_OVERRIDE_LINK_THE_GORON_DIALOGUE: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { + u16* textId = va_arg(args, u16*); + // If the doors are not open yet, prioritize opening them + if (!Flags_GetInfTable(INFTABLE_GORON_CITY_DOORS_UNLOCKED)) { + *textId = 0x3036; + *should = true; + } + } + break; + } + case VB_PLAY_MWEEP_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), 0)) { + *should = false; + Inventory_ReplaceItem(gPlayState, ITEM_LETTER_RUTO, ITEM_BOTTLE); + Flags_SetEventChkInf(EVENTCHKINF_KING_ZORA_MOVED); + } + break; + } + case VB_PLAY_BEAN_PLANTING_CS: + case VB_PLAY_EYEDROP_CREATION_ANIM: + case VB_PLAY_EYEDROPS_CS: + case VB_PLAY_DROP_FISH_FOR_JABU_CS: + case VB_PLAY_DARUNIAS_JOY_CS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { + *should = false; + } + break; + case VB_PLAY_ZELDAS_LULLABY_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO) { + DemoIm* demoIm = va_arg(args, DemoIm*); + Player* player = GET_PLAYER(gPlayState); + player->stateFlags1 |= PLAYER_STATE1_IN_CUTSCENE; + player->stateFlags1 |= PLAYER_STATE1_GETTING_ITEM; + func_80986794(demoIm); + + static uint32_t demoImUpdateHook = 0; + static uint32_t demoImKillHook = 0; + demoImUpdateHook = GameInteractor::Instance->RegisterGameHook( + [](void* actorRef) mutable { + Actor* actor = static_cast(actorRef); + if (actor->id == ACTOR_DEMO_IM && + (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || + IS_RANDO)) { + DemoIm* demoIm = static_cast(actorRef); + Player* player = GET_PLAYER(gPlayState); + player->stateFlags1 |= PLAYER_STATE1_IN_CUTSCENE; + player->stateFlags1 |= PLAYER_STATE1_GETTING_ITEM; + + if (Animation_OnFrame(&demoIm->skelAnime, 25.0f)) { + Audio_PlaySoundGeneral(NA_SE_IT_DEKU, &demoIm->actor.projectedPos, 4, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultReverb); + GameInteractor::Instance->UnregisterGameHook( + demoImUpdateHook); + GameInteractor::Instance->UnregisterGameHook( + demoImKillHook); + demoImUpdateHook = 0; + demoImKillHook = 0; + } else if (Animation_OnFrame(&demoIm->skelAnime, 15.0f)) { + Player* player = GET_PLAYER(gPlayState); + // SOH [Randomizer] In entrance rando have impa bring link back to the front of castle + // grounds + if (IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_OVERWORLD_ENTRANCES)) { + gPlayState->nextEntranceIndex = ENTR_CASTLE_GROUNDS_SOUTH_EXIT; + } else { + gPlayState->nextEntranceIndex = ENTR_HYRULE_FIELD_17; + } + gSaveContext.dayTime = gSaveContext.skyboxTime = 0x8000; + gPlayState->transitionType = TRANS_TYPE_FADE_WHITE; + gPlayState->transitionTrigger = TRANS_TRIGGER_START; + gSaveContext.nextTransitionType = 2; + Player_SetCsActionWithHaltedActors(gPlayState, &player->actor, 8); + } + } + }); + demoImKillHook = GameInteractor::Instance->RegisterGameHook( + [](int16_t sceneNum) mutable { + GameInteractor::Instance->UnregisterGameHook(demoImUpdateHook); + GameInteractor::Instance->UnregisterGameHook(demoImKillHook); + demoImUpdateHook = 0; + demoImKillHook = 0; + }); + *should = false; + } + break; + } + case VB_PLAY_SARIAS_SONG_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO) { + EnSa* enSa = va_arg(args, EnSa*); + enSa->actionFunc = func_80AF6B20; + *should = false; + } + break; + } + case VB_DESPAWN_HORSE_RACE_COW: { + if (Flags_GetEventChkInf(EVENTCHKINF_WON_COW_IN_MALONS_RACE) && + CVarGetInteger(CVAR_ENHANCEMENT("CowOfTime"), 0)) { + *should = false; + } + break; + } + case VB_PLAY_DRAIN_WELL_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + *should = false; + Flags_SetSwitch(gPlayState, 0x2); + Flags_SetEventChkInf(EVENTCHKINF_PLAYED_SONG_OF_STORMS_IN_WINDMILL); + Flags_SetEventChkInf(EVENTCHKINF_DRAINED_WELL_IN_KAKARIKO); + } + break; + } + case VB_PLAY_SUNS_SONG_CS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO) { + *should = false; + Flags_SetEventChkInf(EVENTCHKINF_LEARNED_SUNS_SONG); + // SoH [Randomizer] TODO: Increment time X amount (find out X) + // When time is 0, it's changed to 0x46A7 + // When it's 0x8000, it's changed to 0xC090 + } + break; + case VB_PLAY_ROYAL_FAMILY_TOMB_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { + *should = false; + } + break; + } + case VB_PLAY_ROYAL_FAMILY_TOMB_EXPLODE: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { + *should = Flags_GetEventChkInf(EVENTCHKINF_DESTROYED_ROYAL_FAMILY_TOMB); + } + break; + } + case VB_PLAY_CARPENTER_FREE_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO)) { + EnDaiku* enDaiku = va_arg(args, EnDaiku*); + if (enDaiku->subCamActive) { + enDaiku->subCamActive = false; + EnDaiku_EscapeSuccess(enDaiku, gPlayState); + } + *should = false; + } + break; + } + case VB_PLAY_GORON_FREE_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + *should = false; + } + break; + } + case VB_PLAY_DOOR_OF_TIME_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { + *should = false; + Flags_SetEventChkInf(EVENTCHKINF_OPENED_THE_DOOR_OF_TIME); + Flags_SetEnv(gPlayState, 2); + Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME); + } + break; + } + case VB_PLAY_FIRE_ARROW_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { + *should = false; + } + break; + } + case VB_GIVE_ITEM_SONG: + case VB_PLAY_MINUET_OF_FOREST_CS: + case VB_PLAY_BOLERO_OF_FIRE_CS: + case VB_PLAY_SERENADE_OF_WATER_CS: + case VB_PLAY_SONG_OF_STORMS_CS: + case VB_PLAY_PRELUDE_OF_LIGHT_CS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO) { + *should = false; + } + break; + case VB_FREEZE_ON_SKULL_TOKEN: + if (CVarGetInteger(CVAR_ENHANCEMENT("SkulltulaFreeze"), 0)) { + *should = false; + } + break; + case VB_BE_VALID_GRAVEDIGGING_SPOT: + if (CVarGetInteger(CVAR_ENHANCEMENT("DampeWin"), IS_RANDO)) { + EnTk* enTk = va_arg(args, EnTk*); + enTk->validDigHere = true; + *should = true; + } + break; + case VB_BE_DAMPE_GRAVEDIGGING_GRAND_PRIZE: + if (CVarGetInteger(CVAR_ENHANCEMENT("DampeWin"), IS_RANDO)) { + EnTk* enTk = va_arg(args, EnTk*); + enTk->currentReward = 3; + *should = true; + } + break; + case VB_DAMPE_GRAVEDIGGING_GRAND_PRIZE_BE_HEART_PIECE: + if (CVarGetInteger(CVAR_ENHANCEMENT("GravediggingTourFix"), 0) || IS_RANDO) { + *should = !Flags_GetCollectible(gPlayState, COLLECTFLAG_GRAVEDIGGING_HEART_PIECE); + } + break; + case VB_FIX_SAW_SOFTLOCK: + // Animation Count should be no more than 1 to guarantee putaway is complete after giving the saw + // As this is vanilla behavior, it only applies with the Fix toggle or Skip Text enabled. + *should = (CVarGetInteger(CVAR_ENHANCEMENT("FixSawSoftlock"), 0) != 0 || + CVarGetInteger(CVAR_ENHANCEMENT("SkipText"), 0) != 0) + ? gPlayState->animationCtx.animationCount > 1 + : *should; + break; + case VB_BIGGORON_CONSIDER_SWORD_FORGED: + *should = Environment_GetBgsDayCount() >= CVarGetInteger(CVAR_ENHANCEMENT("ForgeTime"), 3); + break; + case VB_BE_ELIGIBLE_FOR_GREAT_FAIRY_REWARD: { + BgDyYoseizo* bgDyYoseizo = va_arg(args, BgDyYoseizo*); + RandomizerInf flag = RAND_INF_MAX; + + if (gPlayState->sceneNum == SCENE_GREAT_FAIRYS_FOUNTAIN_SPELLS) { + switch (bgDyYoseizo->fountainType) { + case 0: + flag = RAND_INF_ZF_GREAT_FAIRY_REWARD; + break; + case 1: + flag = RAND_INF_HC_GREAT_FAIRY_REWARD; + break; + case 2: + flag = RAND_INF_COLOSSUS_GREAT_FAIRY_REWARD; + break; + } + } else { + switch (bgDyYoseizo->fountainType) { + case 0: + flag = RAND_INF_DMT_GREAT_FAIRY_REWARD; + break; + case 1: + flag = RAND_INF_DMC_GREAT_FAIRY_REWARD; + break; + case 2: + flag = RAND_INF_OGC_GREAT_FAIRY_REWARD; + break; + } + } + + if (flag != RAND_INF_MAX && + (IS_RANDO || CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) { + if (IS_RANDO || *should) { + Flags_SetRandomizerInf(flag); + gSaveContext.healthAccumulator = MAX_HEALTH; + Magic_Fill(gPlayState); + } + *should = false; + } + + break; + } + case VB_PLAY_GATE_OPENING_OR_CLOSING_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { + EnHeishi2* enHeishi2 = va_arg(args, EnHeishi2*); + enHeishi2->unk_2F2[0] = 0; + + // The second argument determines whether the vanilla code should be run anyway. It + // should be set to `true` ONLY IF said code calls `Play_ClearCamera`, false otherwise. + bool clearCamera = (bool)va_arg(args, int); + *should = clearCamera && enHeishi2->cameraId != MAIN_CAM; + } + break; + } + case VB_PLAY_RAINBOW_BRIDGE_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + *should = false; + if (!Flags_GetEventChkInf(EVENTCHKINF_RAINBOW_BRIDGE_BUILT)) { + func_800F595C(NA_BGM_BRIDGE_TO_GANONS); + // This would have been set 2 frames later, but we're skipping now so the sound doesn't play twice + Flags_SetEventChkInf(EVENTCHKINF_RAINBOW_BRIDGE_BUILT); + } + } + break; + } + case VB_PHANTOM_GANON_DEATH_SCENE: { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), IS_RANDO || IS_BOSS_RUSH)) { + *should = false; + BossGanondrof* pg = va_arg(args, BossGanondrof*); + Player* player = GET_PLAYER(gPlayState); + if (pg->work[GND_ACTION_STATE] == DEATH_SPASM) { + // Skip to death scream animation and move ganondrof to middle + pg->deathState = DEATH_SCREAM; + pg->timers[0] = 50; + AnimationHeader* screamAnim = (AnimationHeader*)gPhantomGanonScreamAnim; + Animation_MorphToLoop(&pg->skelAnime, screamAnim, -10.0f); + pg->actor.world.pos.x = GND_BOSSROOM_CENTER_X; + pg->actor.world.pos.y = GND_BOSSROOM_CENTER_Y + 83.0f; + pg->actor.world.pos.z = GND_BOSSROOM_CENTER_Z; + pg->actor.shape.rot.y = 0; + pg->work[GND_BODY_DECAY_INDEX] = 0; + Audio_PlayActorSound2(&pg->actor, NA_SE_EN_FANTOM_LAST); + + // Move Player out of the center of the room + player->actor.world.pos.x = GND_BOSSROOM_CENTER_X - 200.0f; + player->actor.world.pos.z = GND_BOSSROOM_CENTER_Z; + } + } + break; + } + case VB_NABOORU_KNUCKLE_DEATH_SCENE: { + EnIk* ik = va_arg(args, EnIk*); + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), IS_RANDO)) { + // Because no CS in rando, we hide the death of the knuckle by spawning flames and kill the actor + if ((ik->actor.colChkInfo.health <= 10)) { + s32 i; + Vec3f pos; + Vec3f sp7C = { 0.0f, 0.5f, 0.0f }; + int flameAmount = 100; + + for (i = flameAmount; i >= 0; i--) { + pos.x = ik->actor.world.pos.x + Rand_CenteredFloat(120.0f); + pos.z = ik->actor.world.pos.z + Rand_CenteredFloat(120.0f); + pos.y = ik->actor.world.pos.y + 20.0f + Rand_CenteredFloat(120.0f); + EffectSsDeadDb_Spawn(gPlayState, &pos, &sp7C, &sp7C, 100, 0, 255, 255, 255, 255, 0, 0, 255, 1, + 9, true); + } + Actor_Kill(&ik->actor); + } + *should = false; + } + break; + } + case VB_PLAY_SLOW_CHEST_CS: { + if (CVarGetInteger(CVAR_ENHANCEMENT("FastChests"), 0)) { + *should = false; + } + break; + } + case VB_SKIP_SCARECROWS_SONG: { + if (gPlayState->msgCtx.msgMode == MSGMODE_OCARINA_PLAYING && + CVarGetInteger(CVAR_ENHANCEMENT("InstantScarecrow"), 0) && gSaveContext.scarecrowSpawnSongSet) { + *should = true; + } + break; + } + default: + break; + } + + va_end(args); +} + +static uint32_t enMa1UpdateHook = 0; +static uint32_t enMa1KillHook = 0; +static uint32_t enFuUpdateHook = 0; +static uint32_t enFuKillHook = 0; +static uint32_t enJjUpdateHook = 0; +static uint32_t enJjKillHook = 0; +static uint32_t bgSpot02UpdateHook = 0; +static uint32_t bgSpot02KillHook = 0; +static uint32_t bgSpot03UpdateHook = 0; +static uint32_t bgSpot03KillHook = 0; +static uint32_t enPoSistersUpdateHook = 0; +static uint32_t enPoSistersKillHook = 0; +void TimeSaverOnActorInitHandler(void* actorRef) { + Actor* actor = static_cast(actorRef); + + if (actor->id == ACTOR_EN_MA1 && gPlayState->sceneNum == SCENE_LON_LON_RANCH) { + enMa1UpdateHook = + GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { + Actor* innerActor = static_cast(innerActorRef); + if (innerActor->id == ACTOR_EN_MA1 && + (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO)) { + EnMa1* enMa1 = static_cast(innerActorRef); + if (enMa1->actionFunc == EnMa1_StartTeachSong) { + enMa1->actionFunc = EnMa1_EndTeachSong; + GameInteractor::Instance->UnregisterGameHook(enMa1UpdateHook); + GameInteractor::Instance->UnregisterGameHook(enMa1KillHook); + enMa1UpdateHook = 0; + enMa1KillHook = 0; + // They've already learned the song + } else if (enMa1->actionFunc == EnMa1_Idle) { + GameInteractor::Instance->UnregisterGameHook(enMa1UpdateHook); + GameInteractor::Instance->UnregisterGameHook(enMa1KillHook); + enMa1UpdateHook = 0; + enMa1KillHook = 0; + } + } + }); + enMa1KillHook = + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { + GameInteractor::Instance->UnregisterGameHook(enMa1UpdateHook); + GameInteractor::Instance->UnregisterGameHook(enMa1KillHook); + enMa1UpdateHook = 0; + enMa1KillHook = 0; + }); + } + + if (actor->id == ACTOR_EN_FU) { + enFuUpdateHook = + GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { + Actor* innerActor = static_cast(innerActorRef); + if (innerActor->id == ACTOR_EN_FU && + (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO)) { + EnFu* enFu = static_cast(innerActorRef); + if (enFu->actionFunc == EnFu_TeachSong) { + enFu->actionFunc = EnFu_EndTeachSong; + GameInteractor::Instance->UnregisterGameHook(enFuUpdateHook); + GameInteractor::Instance->UnregisterGameHook(enFuKillHook); + enFuUpdateHook = 0; + enFuKillHook = 0; + } + } + }); + enFuKillHook = + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { + GameInteractor::Instance->UnregisterGameHook(enFuUpdateHook); + GameInteractor::Instance->UnregisterGameHook(enFuKillHook); + enFuUpdateHook = 0; + enFuKillHook = 0; + }); + } + + if (actor->id == ACTOR_EN_JJ) { + enJjUpdateHook = + GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { + Actor* innerActor = static_cast(innerActorRef); + + if (innerActor->id != ACTOR_EN_JJ || Flags_GetEventChkInf(EVENTCHKINF_OFFERED_FISH_TO_JABU_JABU)) { + return; + } + + bool shouldOpen = IS_RANDO ? RAND_GET_OPTION(RSK_JABU_OPEN).Get() + : CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipJabuJabuFish"), 0); + if (!shouldOpen) { + return; + } + + EnJj* enJj = static_cast(innerActorRef); + if (enJj->actionFunc == EnJj_WaitForFish) { + EnJj_SetupAction(enJj, EnJj_WaitToOpenMouth); + GameInteractor::Instance->UnregisterGameHook(enJjUpdateHook); + GameInteractor::Instance->UnregisterGameHook(enJjKillHook); + enJjUpdateHook = 0; + enJjKillHook = 0; + } + }); + enJjKillHook = + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { + GameInteractor::Instance->UnregisterGameHook(enJjUpdateHook); + GameInteractor::Instance->UnregisterGameHook(enJjKillHook); + enJjUpdateHook = 0; + enJjKillHook = 0; + }); + } + + if (actor->id == ACTOR_EN_OWL && gPlayState->sceneNum == SCENE_ZORAS_RIVER && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"), 0) == 2) { + Actor_Kill(actor); + } + + if (actor->id == ACTOR_BG_SPOT02_OBJECTS && actor->params == 2) { + bgSpot02UpdateHook = + GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { + Actor* innerActor = static_cast(innerActorRef); + if (innerActor->id == ACTOR_BG_SPOT02_OBJECTS && innerActor->params == 2 && + (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) { + BgSpot02Objects* bgSpot02 = static_cast(innerActorRef); + if (bgSpot02->actionFunc == func_808ACC34) { + bgSpot02->actionFunc = func_808AC908; + GameInteractor::Instance->UnregisterGameHook(bgSpot02UpdateHook); + GameInteractor::Instance->UnregisterGameHook(bgSpot02KillHook); + bgSpot02UpdateHook = 0; + bgSpot02KillHook = 0; + } + } + }); + bgSpot02KillHook = + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { + GameInteractor::Instance->UnregisterGameHook(bgSpot02UpdateHook); + GameInteractor::Instance->UnregisterGameHook(bgSpot02KillHook); + bgSpot02UpdateHook = 0; + bgSpot02KillHook = 0; + }); + } + + if (actor->id == ACTOR_BG_SPOT03_TAKI) { + bgSpot03UpdateHook = + GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { + Actor* innerActor = static_cast(innerActorRef); + + if (innerActor->id != ACTOR_BG_SPOT03_TAKI) { + return; + } + + bool shouldKeepOpen = RAND_GET_OPTION(RSK_SLEEPING_WATERFALL) && IS_RANDO; + if (!shouldKeepOpen) { + int enhancement = CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"), 0); + shouldKeepOpen = + (enhancement == 2 && + ((CHECK_QUEST_ITEM(QUEST_SONG_LULLABY) && (INV_CONTENT(ITEM_OCARINA_TIME) != ITEM_NONE)))) || + (enhancement == 1 && Flags_GetEventChkInf(EVENTCHKINF_OPENED_ZORAS_DOMAIN)); + } + + if (!shouldKeepOpen) { + return; + } + + BgSpot03Taki* bgSpot03 = static_cast(innerActorRef); + if (bgSpot03->actionFunc == BgSpot03Taki_HandleWaterfallState) { + bgSpot03->actionFunc = BgSpot03Taki_KeepOpen; + bgSpot03->state = WATERFALL_OPENED; + bgSpot03->openingAlpha = 0.0f; + Flags_SetSwitch(gPlayState, bgSpot03->switchFlag); + func_8003EBF8(gPlayState, &gPlayState->colCtx.dyna, bgSpot03->dyna.bgId); + BgSpot03Taki_ApplyOpeningAlpha(bgSpot03, 0); + BgSpot03Taki_ApplyOpeningAlpha(bgSpot03, 1); + + GameInteractor::Instance->UnregisterGameHook(bgSpot03UpdateHook); + GameInteractor::Instance->UnregisterGameHook(bgSpot03KillHook); + bgSpot03UpdateHook = 0; + bgSpot03KillHook = 0; + } + }); + bgSpot03KillHook = + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { + GameInteractor::Instance->UnregisterGameHook(bgSpot03UpdateHook); + GameInteractor::Instance->UnregisterGameHook(bgSpot03KillHook); + bgSpot03UpdateHook = 0; + bgSpot03KillHook = 0; + }); + } + + if (actor->id == ACTOR_EN_DNT_DEMO && + (IS_RANDO || CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) { + EnDntDemo* enDntDemo = static_cast(actorRef); + enDntDemo->actionFunc = EnDntDemo_JudgeSkipToReward; + } + + // Forest Temple entrance cutscene + // This is a bit of a hack, we can't effectively override the behavior of the torches + // or poes from which the cutscene is triggered until we can have a "BeforeActorInit" hook. + // So for now we're just going to set the flag before they get to the room the cutscene is in + if (gPlayState->sceneNum == SCENE_FOREST_TEMPLE && actor->id == ACTOR_EN_DOOR && + !Flags_GetSwitch(gPlayState, 0x1B) && !Flags_GetSwitch(gPlayState, 0x1C)) { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) && + !CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { + Flags_SetSwitch(gPlayState, 0x1B); + } + } + + // Forest Temple purple poe fight speedup + if (actor->id == ACTOR_EN_PO_SISTERS && actor->params == 28) { + enPoSistersUpdateHook = + GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { + Actor* innerActor = static_cast(innerActorRef); + if (innerActor->id == ACTOR_EN_PO_SISTERS && innerActor->params == 28 && + (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) { + EnPoSisters* enPoSisters = static_cast(innerActorRef); + if (enPoSisters->actionFunc == func_80ADB338) { + enPoSisters->unk_19C = 0; + } + } + }); + enPoSistersKillHook = + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { + GameInteractor::Instance->UnregisterGameHook(enPoSistersUpdateHook); + GameInteractor::Instance->UnregisterGameHook(enPoSistersKillHook); + enPoSistersUpdateHook = 0; + enPoSistersKillHook = 0; + }); + } + + // Fire Temple Darunia cutscene + if (actor->id == ACTOR_EN_DU && gPlayState->sceneNum == SCENE_FIRE_TEMPLE) { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) && + !CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { + Flags_SetInfTable(INFTABLE_SPOKE_TO_DARUNIA_IN_FIRE_TEMPLE); + Actor_Kill(actor); + } + } + + // Water Temple Ruto cutscene + if (actor->id == ACTOR_EN_RU2 && gPlayState->sceneNum == SCENE_WATER_TEMPLE) { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + EnRu2* enRu2 = (EnRu2*)actor; + EnRu2_SetEncounterSwitchFlag(enRu2, gPlayState); + Actor_Kill(actor); + } + } +} + +void TimeSaverOnSceneInitHandler(int16_t sceneNum) { + switch (sceneNum) { + case SCENE_HYRULE_CASTLE: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) && + !Flags_GetInfTable(INFTABLE_ENTERED_HYRULE_CASTLE)) { + Flags_SetInfTable(INFTABLE_ENTERED_HYRULE_CASTLE); + Flags_SetInfTable(INFTABLE_MET_CHILD_MALON_AT_CASTLE_OR_MARKET); + Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_CHILD_MALON_AT_CASTLE_OR_MARKET); + } + break; + case SCENE_LON_LON_RANCH: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) && + GameInteractor_Should(VB_MALON_RETURN_FROM_CASTLE, + Flags_GetEventChkInf(EVENTCHKINF_TALON_RETURNED_FROM_CASTLE)) && + (!IS_RANDO || !RAND_GET_OPTION(RSK_SHUFFLE_SPEAK) || + Flags_GetRandomizerInf(RAND_INF_CAN_SPEAK_HYLIAN))) { + Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_CHILD_MALON_AT_RANCH); + Flags_SetInfTable(INFTABLE_CHILD_MALON_SAID_EPONA_WAS_AFRAID_OF_YOU); + Flags_SetEventChkInf(EVENTCHKINF_INVITED_TO_SING_WITH_CHILD_MALON); + } + break; + case SCENE_DEKU_TREE_BOSS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { + if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_GOHMA_BATTLE)) { + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_GOHMA_BATTLE); + } + } + break; + case SCENE_DODONGOS_CAVERN_BOSS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { + if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_KING_DODONGO_BATTLE)) { + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_KING_DODONGO_BATTLE); + } + } + break; + case SCENE_JABU_JABU_BOSS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { + if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_BARINA_BATTLE)) { + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_BARINA_BATTLE); + } + } + break; + case SCENE_FOREST_TEMPLE_BOSS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { + if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_PHANTOM_GANON_BATTLE)) { + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_PHANTOM_GANON_BATTLE); + } + } + break; + case SCENE_FIRE_TEMPLE_BOSS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { + if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_VOLVAGIA_BATTLE)) { + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_VOLVAGIA_BATTLE); + } + } + break; + case SCENE_WATER_TEMPLE_BOSS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { + if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_MORPHA_BATTLE)) { + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_MORPHA_BATTLE); + } + } + break; + case SCENE_SPIRIT_TEMPLE_BOSS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { + if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_TWINROVA_BATTLE)) { + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_TWINROVA_BATTLE); + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_NABOORU_BATTLE); + Flags_SetEventChkInf(EVENTCHKINF_NABOORU_ORDERED_TO_FIGHT_BY_TWINROVA); + } + } + break; + case SCENE_SHADOW_TEMPLE_BOSS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { + if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_BONGO_BONGO_BATTLE)) { + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_BONGO_BONGO_BATTLE); + } + } + break; + case SCENE_GANONDORF_BOSS: + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { + if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_GANONDORF_BATTLE)) { + Flags_SetEventChkInf(EVENTCHKINF_BEGAN_GANONDORF_BATTLE); + } + } + break; + } +} + +static GetItemEntry vanillaQueuedItemEntry = GET_ITEM_NONE; + +extern void TimeSaverQueueItem(RandomizerGet randoGet) { + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(randoGet).GetGIEntry_Copy(); +} + +void TimeSaverOnFlagSetHandler(int16_t flagType, int16_t flag) { + // Do nothing when in a boss rush + if (IS_BOSS_RUSH) { + return; + } + + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), 0)) { + switch (flagType) { + case FLAG_EVENT_CHECK_INF: + switch (flag) { + case EVENTCHKINF_SPOKE_TO_SARIA_ON_BRIDGE: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FAIRY_OCARINA).GetGIEntry_Copy(); + break; + case EVENTCHKINF_OBTAINED_KOKIRI_EMERALD_DEKU_TREE_DEAD: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_KOKIRI_EMERALD).GetGIEntry_Copy(); + break; + case EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_GORON_RUBY).GetGIEntry_Copy(); + break; + case EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_ZORA_SAPPHIRE).GetGIEntry_Copy(); + break; + case EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FOREST_MEDALLION).GetGIEntry_Copy(); + break; + case EVENTCHKINF_USED_FIRE_TEMPLE_BLUE_WARP: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FIRE_MEDALLION).GetGIEntry_Copy(); + break; + case EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_WATER_MEDALLION).GetGIEntry_Copy(); + break; + case EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_LIGHT_ARROWS).GetGIEntry_Copy(); + break; + case EVENTCHKINF_TIME_TRAVELED_TO_ADULT: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_LIGHT_MEDALLION).GetGIEntry_Copy(); + break; + } + break; + case FLAG_RANDOMIZER_INF: + switch (flag) { + case RAND_INF_DUNGEONS_DONE_SHADOW_TEMPLE: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SHADOW_MEDALLION).GetGIEntry_Copy(); + break; + case RAND_INF_DUNGEONS_DONE_SPIRIT_TEMPLE: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SPIRIT_MEDALLION).GetGIEntry_Copy(); + break; + } + break; + } + } + + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), 0)) { + switch (flagType) { + case FLAG_RANDOMIZER_INF: + switch (flag) { + case RAND_INF_ZF_GREAT_FAIRY_REWARD: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FARORES_WIND).GetGIEntry_Copy(); + break; + case RAND_INF_HC_GREAT_FAIRY_REWARD: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_DINS_FIRE).GetGIEntry_Copy(); + break; + case RAND_INF_COLOSSUS_GREAT_FAIRY_REWARD: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_NAYRUS_LOVE).GetGIEntry_Copy(); + break; + case RAND_INF_DMT_GREAT_FAIRY_REWARD: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_MAGIC_SINGLE).GetGIEntry_Copy(); + break; + case RAND_INF_DMC_GREAT_FAIRY_REWARD: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_MAGIC_DOUBLE).GetGIEntry_Copy(); + break; + case RAND_INF_OGC_GREAT_FAIRY_REWARD: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_DOUBLE_DEFENSE).GetGIEntry_Copy(); + break; + } + break; + case FLAG_ITEM_GET_INF: + switch (flag) { + case ITEMGETINF_OBTAINED_STICK_UPGRADE_FROM_STAGE: { + TimeSaverQueueItem(CUR_UPG_VALUE(UPG_STICKS) == 2 ? RG_DEKU_STICK_CAPACITY_30 + : RG_DEKU_STICK_CAPACITY_20); + break; + } + case ITEMGETINF_OBTAINED_NUT_UPGRADE_FROM_STAGE: { + TimeSaverQueueItem(CUR_UPG_VALUE(UPG_NUTS) == 2 ? RG_DEKU_NUT_CAPACITY_40 + : RG_DEKU_NUT_CAPACITY_30); + break; + } + } + break; + } + } + + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), 0)) { + switch (flagType) { + case FLAG_EVENT_CHECK_INF: + switch (flag) { + case EVENTCHKINF_LEARNED_ZELDAS_LULLABY: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_ZELDAS_LULLABY).GetGIEntry_Copy(); + break; + case EVENTCHKINF_LEARNED_MINUET_OF_FOREST: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_MINUET_OF_FOREST).GetGIEntry_Copy(); + break; + case EVENTCHKINF_LEARNED_BOLERO_OF_FIRE: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_BOLERO_OF_FIRE).GetGIEntry_Copy(); + break; + case EVENTCHKINF_LEARNED_SERENADE_OF_WATER: + vanillaQueuedItemEntry = + Rando::StaticData::RetrieveItem(RG_SERENADE_OF_WATER).GetGIEntry_Copy(); + break; + case EVENTCHKINF_LEARNED_REQUIEM_OF_SPIRIT: + vanillaQueuedItemEntry = + Rando::StaticData::RetrieveItem(RG_REQUIEM_OF_SPIRIT).GetGIEntry_Copy(); + break; + case EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL: + vanillaQueuedItemEntry = + Rando::StaticData::RetrieveItem(RG_NOCTURNE_OF_SHADOW).GetGIEntry_Copy(); + break; + case EVENTCHKINF_LEARNED_PRELUDE_OF_LIGHT: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_PRELUDE_OF_LIGHT).GetGIEntry_Copy(); + break; + case EVENTCHKINF_LEARNED_SARIAS_SONG: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SARIAS_SONG).GetGIEntry_Copy(); + break; + case EVENTCHKINF_LEARNED_SONG_OF_TIME: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SONG_OF_TIME).GetGIEntry_Copy(); + break; + case EVENTCHKINF_LEARNED_SONG_OF_STORMS: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SONG_OF_STORMS).GetGIEntry_Copy(); + break; + case EVENTCHKINF_LEARNED_SUNS_SONG: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SUNS_SONG).GetGIEntry_Copy(); + break; + } + break; + case FLAG_RANDOMIZER_INF: + switch (flag) { + case RAND_INF_LEARNED_EPONA_SONG: + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_EPONAS_SONG).GetGIEntry_Copy(); + break; + } + break; + } + } +} + +void TimeSaverOnPlayerUpdateHandler() { + if (vanillaQueuedItemEntry.itemId == ITEM_NONE) + return; + + Player* player = GET_PLAYER(gPlayState); + if (player == NULL || Player_InBlockingCsMode(gPlayState, player) || + player->stateFlags1 & PLAYER_STATE1_IN_ITEM_CS || player->stateFlags1 & PLAYER_STATE1_GETTING_ITEM || + player->stateFlags1 & PLAYER_STATE1_CARRYING_ACTOR) { + return; + } + + SPDLOG_INFO("Attempting to give Item: mod {} item {}", vanillaQueuedItemEntry.modIndex, + vanillaQueuedItemEntry.itemId); + GiveItemEntryWithoutActor(gPlayState, vanillaQueuedItemEntry); + if (player->stateFlags1 & PLAYER_STATE1_IN_WATER) { + // Allow the player to receive the item while swimming + player->stateFlags2 |= PLAYER_STATE2_UNDERWATER; + Player_ActionHandler_2(player, gPlayState); + } +} + +void TimeSaverOnItemReceiveHandler(GetItemEntry receivedItemEntry) { + if (vanillaQueuedItemEntry.itemId == ITEM_NONE) + return; + + if (vanillaQueuedItemEntry.modIndex == receivedItemEntry.modIndex && + vanillaQueuedItemEntry.itemId == receivedItemEntry.itemId) { + SPDLOG_INFO("Item received: mod {} item {}", receivedItemEntry.modIndex, receivedItemEntry.itemId); + vanillaQueuedItemEntry = GET_ITEM_NONE; + } +} + +static void TimeSaverRegisterHooks() { + COND_HOOK(OnSceneInit, + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) || + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO), + TimeSaverOnSceneInitHandler); + COND_HOOK(OnVanillaBehavior, true, TimeSaverOnVanillaBehaviorHandler); + COND_HOOK(OnActorInit, true, TimeSaverOnActorInitHandler); + + // item queue for use outside rando, rando has its own queue + COND_HOOK(OnLoadGame, !IS_RANDO, [](int32_t fileNum) { + vanillaQueuedItemEntry = GET_ITEM_NONE; + successChimeCooldown = 0; + }); + COND_HOOK(OnItemReceive, !IS_RANDO, TimeSaverOnItemReceiveHandler); + COND_HOOK(OnPlayerUpdate, !IS_RANDO, TimeSaverOnPlayerUpdateHandler); + COND_HOOK(OnFlagSet, + !IS_RANDO && (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), 0) || + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), 0) || + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), 0)), + TimeSaverOnFlagSetHandler); +} + +static RegisterShipInitFunc initFunc_RegisterHooks(TimeSaverRegisterHooks, + { "IS_RANDO", CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), + CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), + CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), + CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong") }); diff --git a/soh/soh/Enhancements/UnsheatheWithoutSlashing.cpp b/soh/soh/Enhancements/UnsheatheWithoutSlashing.cpp deleted file mode 100644 index bb215eb7a..000000000 --- a/soh/soh/Enhancements/UnsheatheWithoutSlashing.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/ShipInit.hpp" - -#define CVAR_UNSHEATHE_NAME CVAR_ENHANCEMENT("UnsheatheWithoutSlashing") -#define CVAR_UNSHEATHE_VALUE CVarGetInteger(CVAR_UNSHEATHE_NAME, 0) - -void RegisterUnsheatheWithoutSlashing() { - COND_VB_SHOULD(VB_USE_HELD_ITEM_AFTER_CHANGE, CVAR_UNSHEATHE_VALUE, { - Player* player = va_arg(args, Player*); - ItemID heldItemId = static_cast(player->heldItemId); - if ((heldItemId == ITEM_SWORD_KOKIRI) || (heldItemId == ITEM_SWORD_MASTER)) { - *should = false; - } - }); -} - -static RegisterShipInitFunc initFunc(RegisterUnsheatheWithoutSlashing, { CVAR_UNSHEATHE_NAME }); diff --git a/soh/soh/Enhancements/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/timesaver_hook_handlers.cpp deleted file mode 100644 index e272b13c7..000000000 --- a/soh/soh/Enhancements/timesaver_hook_handlers.cpp +++ /dev/null @@ -1,1437 +0,0 @@ -#include -#include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "soh/Enhancements/enhancementTypes.h" -#include "soh/Enhancements/randomizer/SeedContext.h" - -extern "C" { -#include "src/overlays/actors/ovl_En_Wonder_Talk2/z_en_wonder_talk2.h" -#include "src/overlays/actors/ovl_Elf_Msg/z_elf_msg.h" -#include "src/overlays/actors/ovl_Obj_Switch/z_obj_switch.h" -#include "src/overlays/actors/ovl_Bg_Bdan_Switch/z_bg_bdan_switch.h" -#include "src/overlays/actors/ovl_En_Owl/z_en_owl.h" -#include "src/overlays/actors/ovl_En_Go2/z_en_go2.h" -#include "src/overlays/actors/ovl_En_Heishi2/z_en_heishi2.h" -#include "src/overlays/actors/ovl_En_Ma1/z_en_ma1.h" -#include "src/overlays/actors/ovl_En_Ru2/z_en_ru2.h" -#include "src/overlays/actors/ovl_En_Box/z_en_box.h" -#include "src/overlays/actors/ovl_Demo_Im/z_demo_im.h" -#include "src/overlays/actors/ovl_Demo_Kekkai/z_demo_kekkai.h" -#include "src/overlays/actors/ovl_En_Sa/z_en_sa.h" -#include "src/overlays/actors/ovl_Bg_Ddan_Kd/z_bg_ddan_kd.h" -#include "src/overlays/actors/ovl_En_Tk/z_en_tk.h" -#include "src/overlays/actors/ovl_En_Fu/z_en_fu.h" -#include "src/overlays/actors/ovl_En_Jj/z_en_jj.h" -#include "src/overlays/actors/ovl_En_Daiku/z_en_daiku.h" -#include "src/overlays/actors/ovl_Bg_Spot02_Objects/z_bg_spot02_objects.h" -#include "src/overlays/actors/ovl_Bg_Spot06_Objects/z_bg_spot06_objects.h" -#include "src/overlays/actors/ovl_Bg_Spot03_Taki/z_bg_spot03_taki.h" -#include "src/overlays/actors/ovl_Bg_Hidan_Kousi/z_bg_hidan_kousi.h" -#include "src/overlays/actors/ovl_Bg_Dy_Yoseizo/z_bg_dy_yoseizo.h" -#include "src/overlays/actors/ovl_En_Dnt_Demo/z_en_dnt_demo.h" -#include "src/overlays/actors/ovl_En_Po_Sisters/z_en_po_sisters.h" -#include "src/overlays/actors/ovl_Obj_Lightswitch/z_obj_lightswitch.h" -#include "src/overlays/actors/ovl_Bg_Jya_Bombchuiwa/z_bg_jya_bombchuiwa.h" -#include -#include -#include -extern SaveContext gSaveContext; -extern PlayState* gPlayState; -extern int32_t D_8011D3AC; - -extern void BgSpot03Taki_HandleWaterfallState(BgSpot03Taki* bgSpot03Taki, PlayState* play); -extern void BgSpot03Taki_ApplyOpeningAlpha(BgSpot03Taki* bgSpot03Taki, s32 bufferIndex); - -extern void EnGo2_CurledUp(EnGo2* enGo2, PlayState* play); - -extern void EnRu2_SetEncounterSwitchFlag(EnRu2* enRu2, PlayState* play); - -extern void EnDaiku_EscapeSuccess(EnDaiku* enDaiku, PlayState* play); - -extern void EnJj_WaitToOpenMouth(EnJj* enJj, PlayState* play); -extern void EnJj_WaitForFish(EnJj* enJj, PlayState* play); -extern void EnJj_SetupAction(EnJj* enJj, EnJjActionFunc actionFunc); -} - -void EnMa1_EndTeachSong(EnMa1* enMa1, PlayState* play) { - if (Message_GetState(&gPlayState->msgCtx) == TEXT_STATE_CLOSING) { - Flags_SetRandomizerInf(RAND_INF_LEARNED_EPONA_SONG); - Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME); - enMa1->actor.flags &= ~ACTOR_FLAG_TALK_OFFER_AUTO_ACCEPTED; - play->msgCtx.ocarinaMode = OCARINA_MODE_04; - enMa1->actionFunc = EnMa1_Idle; - enMa1->singingDisabled = 1; - enMa1->interactInfo.talkState = NPC_TALK_STATE_IDLE; - return; - } -} - -void EnFu_EndTeachSong(EnFu* enFu, PlayState* play) { - if (Message_GetState(&gPlayState->msgCtx) == TEXT_STATE_CLOSING) { - Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME); - enFu->actionFunc = EnFu_WaitAdult; - enFu->actor.flags &= ~ACTOR_FLAG_TALK_OFFER_AUTO_ACCEPTED; - - play->msgCtx.ocarinaMode = OCARINA_MODE_04; - Flags_SetEventChkInf(EVENTCHKINF_PLAYED_SONG_OF_STORMS_IN_WINDMILL); - Flags_SetEventChkInf(EVENTCHKINF_LEARNED_SONG_OF_STORMS); - return; - } -} - -void EnDntDemo_JudgeSkipToReward(EnDntDemo* enDntDemo, PlayState* play) { - // todo: figure out a better way to handle toggling so we don't - // need to double check cvars like this - if (!(IS_RANDO || CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) { - EnDntDemo_Judge(enDntDemo, play); - return; - } else if ((IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_SPEAK)) || enDntDemo->actor.xzDistToPlayer > 30.0f) { - if (enDntDemo->judgeTimer > 0 && enDntDemo->judgeTimer < 40) { - enDntDemo->judgeTimer = 40; - } - EnDntDemo_Judge(enDntDemo, play); - return; - } - - switch (Player_GetMask(play)) { - case PLAYER_MASK_SKULL: { - Flags_SetItemGetInf(ITEMGETINF_OBTAINED_STICK_UPGRADE_FROM_STAGE); - return; - } - case PLAYER_MASK_TRUTH: { - if (GameInteractor_Should(VB_DEKU_SCRUBS_REACT_TO_MASK_OF_TRUTH, true)) { - Flags_SetItemGetInf(ITEMGETINF_OBTAINED_NUT_UPGRADE_FROM_STAGE); - } - return; - } - default: { - EnDntDemo_Judge(enDntDemo, play); - return; - } - } -} - -void BgSpot03Taki_KeepOpen(BgSpot03Taki* bgSpot03Taki, PlayState* play) { -} - -static u32 successChimeCooldown = 0; -void RateLimitedSuccessChime() { - if (gPlayState->gameplayFrames > successChimeCooldown) { - Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME); - successChimeCooldown = gPlayState->gameplayFrames + 120; - } -} - -bool ForcedDialogIsDisabled(ForcedDialogMode type) { - return (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipForcedDialog"), - IS_RANDO ? FORCED_DIALOG_SKIP_ALL : FORCED_DIALOG_SKIP_NONE) & - type) != 0; -} - -void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_list originalArgs) { - va_list args; - va_copy(args, originalArgs); - - switch (id) { - case VB_PLAY_TRANSITION_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO) { - // Song of Time - if (gSaveContext.entranceIndex == ENTR_TEMPLE_OF_TIME_ENTRANCE && - gSaveContext.cutsceneIndex == 0xFFF7) { - gSaveContext.entranceIndex = ENTR_HYRULE_FIELD_16; - gSaveContext.cutsceneIndex = 0; - gSaveContext.nextTransitionType = 3; - *should = false; - } - - // Requiem of Spirit - if ((gSaveContext.entranceIndex == ENTR_DESERT_COLOSSUS_OUTSIDE_TEMPLE) && - !Flags_GetEventChkInf(EVENTCHKINF_LEARNED_REQUIEM_OF_SPIRIT)) { - Flags_SetEventChkInf(EVENTCHKINF_LEARNED_REQUIEM_OF_SPIRIT); - // Normally happens in the cutscene - gSaveContext.dayTime = gSaveContext.skyboxTime = 0xAC60; - if (GameInteractor_Should(VB_GIVE_ITEM_SONG, true, ITEM_SONG_REQUIEM)) { - Item_Give(gPlayState, ITEM_SONG_REQUIEM); - } - *should = false; - } - - u8 meetsBurningKakRequirements = LINK_IS_ADULT && gSaveContext.cutsceneIndex < 0xFFF0 && - gSaveContext.entranceIndex == ENTR_KAKARIKO_VILLAGE_FRONT_GATE && - Flags_GetEventChkInf(EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP) && - Flags_GetEventChkInf(EVENTCHKINF_USED_FIRE_TEMPLE_BLUE_WARP) && - Flags_GetEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP) && - !Flags_GetEventChkInf(EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL); - if (GameInteractor_Should(VB_BE_ELIGIBLE_FOR_NOCTURNE_OF_SHADOW, meetsBurningKakRequirements)) { - Flags_SetEventChkInf(EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL); - // Normally happens in the cutscene - Flags_SetEventChkInf(EVENTCHKINF_LEARNED_NOCTURNE_OF_SHADOW); - if (GameInteractor_Should(VB_GIVE_ITEM_SONG, true, ITEM_SONG_NOCTURNE)) { - Item_Give(gPlayState, ITEM_SONG_NOCTURNE); - } - *should = false; - } - } - - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - // LACS - bool meetsLACSRequirements = - LINK_IS_ADULT && - (gEntranceTable[((void)0, gSaveContext.entranceIndex)].scene == SCENE_TEMPLE_OF_TIME) && - CHECK_QUEST_ITEM(QUEST_MEDALLION_SPIRIT) && CHECK_QUEST_ITEM(QUEST_MEDALLION_SHADOW) && - !Flags_GetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS); - if (GameInteractor_Should(VB_BE_ELIGIBLE_FOR_LIGHT_ARROWS, meetsLACSRequirements)) { - Flags_SetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS); - if (GameInteractor_Should(VB_GIVE_ITEM_LIGHT_ARROW, true)) { - Item_Give(gPlayState, ITEM_ARROW_LIGHT); - } - *should = false; - } - } - - if (gSaveContext.entranceIndex == ENTR_GANONS_TOWER_COLLAPSE_EXTERIOR_0) { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipTowerEscape"), false) || IS_BOSS_RUSH) { - Flags_SetEventChkInf(EVENTCHKINF_WATCHED_GANONS_CASTLE_COLLAPSE_CAUGHT_BY_GERUDO); - gSaveContext.entranceIndex = ENTR_GANON_BOSS_0; - *should = false; - } - } - - if (gSaveContext.entranceIndex == ENTR_CASTLE_COURTYARD_GUARDS_DAY_0) { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipChildStealth"), false)) { - gSaveContext.entranceIndex = ENTR_CASTLE_COURTYARD_ZELDA_0; - *should = false; - } - } - break; - } - case VB_PLAY_ENTRANCE_CS: { - s32 entranceFlag = va_arg(args, s32); - s32 entranceIndex = va_arg(args, s32); - - // Epona LLR fence jump cutscenes not skipped to allow the player and epona to load in the world correctly - // Nabooru fight cutscene is handled by boss intro skip instead (which deals with other flags needing to be - // set) - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), IS_RANDO) && - (entranceFlag != EVENTCHKINF_EPONA_OBTAINED) && entranceIndex != ENTR_SPIRIT_TEMPLE_BOSS_ENTRANCE) { - *should = false; - - // Check for dispulsion of Ganon's Tower barrier - switch (entranceIndex) { - case ENTR_INSIDE_GANONS_CASTLE_2: - case ENTR_INSIDE_GANONS_CASTLE_3: - case ENTR_INSIDE_GANONS_CASTLE_4: - case ENTR_INSIDE_GANONS_CASTLE_5: - case ENTR_INSIDE_GANONS_CASTLE_6: - case ENTR_INSIDE_GANONS_CASTLE_7: - if (Flags_GetEventChkInf(EVENTCHKINF_COMPLETED_FOREST_TRIAL) && - Flags_GetEventChkInf(EVENTCHKINF_COMPLETED_WATER_TRIAL) && - Flags_GetEventChkInf(EVENTCHKINF_COMPLETED_SHADOW_TRIAL) && - Flags_GetEventChkInf(EVENTCHKINF_COMPLETED_FIRE_TRIAL) && - Flags_GetEventChkInf(EVENTCHKINF_COMPLETED_LIGHT_TRIAL) && - Flags_GetEventChkInf(EVENTCHKINF_COMPLETED_SPIRIT_TRIAL)) { - Flags_SetEventChkInf(EVENTCHKINF_DISPELLED_GANONS_TOWER_BARRIER); - } - break; - } - } - break; - } - case VB_PLAY_ONEPOINT_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO)) { - s16* csId = va_arg(args, s16*); - BgSpot03Taki* taki = NULL; - switch (*csId) { - case 3120: - case 3150: - case 4180: - case 4100: - *should = false; - RateLimitedSuccessChime(); - Message_CloseTextbox(gPlayState); - taki = (BgSpot03Taki*)Actor_FindNearby(gPlayState, &GET_PLAYER(gPlayState)->actor, - ACTOR_BG_SPOT03_TAKI, ACTORCAT_BG, 999.0f); - if (taki != NULL) { - func_8003EBF8(gPlayState, &gPlayState->colCtx.dyna, taki->dyna.bgId); - } - break; - default: - SPDLOG_INFO("VB_PLAY_ONEPOINT_CS {}", *csId); - break; - } - } - break; - } - case VB_PLAY_ONEPOINT_ACTOR_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO)) { - Actor* actor = va_arg(args, Actor*); - - // there are a few checks throughout the game (such as chest spawns) that rely on this - // the checks are for func_8005B198() == this->dyna.actor.category - // func_8005B198 just returns D_8011D3AC - // D_8011D3AC is set to camera->target->category in Camera_Demo5 - D_8011D3AC = actor->category; - - switch (actor->category) { - case ACTORCAT_BG: - if (actor->id == ACTOR_BG_DDAN_KD) { - BgDdanKd* ddanKd = (BgDdanKd*)actor; - Flags_SetSwitch(gPlayState, ddanKd->dyna.actor.params); - } - if (actor->id == ACTOR_BG_MORI_HINERI) { - break; - } - // This is handled in the FasterHeavyBlockLift enhancement - if (actor->id == ACTOR_BG_HEAVY_BLOCK) { - break; - } - - RateLimitedSuccessChime(); - *should = false; - break; - } - switch (actor->id) { - case ACTOR_OBJ_SWITCH: { - // The DC boss door can be unlocked with OI; One Point is required for it - // The Water Temple Dragon Room chest can be obtained with a cutscene dive - if (((actor->params == 8224 && gPlayState->sceneNum == SCENE_DODONGOS_CAVERN) || - (actor->params == 6979 && gPlayState->sceneNum == SCENE_WATER_TEMPLE)) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { - break; - } - ObjSwitch* switchActor = (ObjSwitch*)actor; - switchActor->cooldownTimer = 0; - *should = false; - RateLimitedSuccessChime(); - break; - } - case ACTOR_BG_BDAN_SWITCH: { - // The switch in jabu that you are intended to press with a box to reach barinade - // can be skipped by either a frame perfect roll open or with OI - // Additionally, the blue switch that you are intended to press with Ruto - // can be skipped with OI - // The One Point for that switch is used in common setups for the former and is required for the - // latter to work - if ((actor->params == 14848 || actor->params == 14336) && - gPlayState->sceneNum == SCENE_JABU_JABU && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { - break; - } - BgBdanSwitch* switchActor = (BgBdanSwitch*)actor; - switchActor->unk_1D8 = 0; - switchActor->unk_1DA = 0; - *should = false; - RateLimitedSuccessChime(); - break; - } - case ACTOR_BG_HIDAN_KOUSI: { - BgHidanKousi* switchActor = (BgHidanKousi*)actor; - BgHidanKousi_SetupAction(switchActor, func_80889C18); - *should = false; - RateLimitedSuccessChime(); - break; - } - case ACTOR_BG_JYA_BOMBCHUIWA: { - BgJyaBombchuiwa* bombchuiwa = (BgJyaBombchuiwa*)actor; - if (!(bombchuiwa->drawFlags & 4) && bombchuiwa->timer >= 0 && bombchuiwa->timer < 9) { - bombchuiwa->timer = 9; - } - *should = false; - RateLimitedSuccessChime(); - break; - } - case ACTOR_EN_GO2: { - EnGo2* biggoron = (EnGo2*)actor; - biggoron->isAwake = true; - *should = false; - break; - } - case ACTOR_EN_BOX: { - // The chest that drops in MQ Jabu allowing unintended door entry - if (actor->params == -30457 && gPlayState->sceneNum == SCENE_JABU_JABU && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { - break; - } - EnBox* boxActor = (EnBox*)actor; - *should = false; - RateLimitedSuccessChime(); - break; - } - case ACTOR_EN_SIOFUKI: { - // The Spirit Temple MQ water jet cutscene is required for an actor glitch - // setup that skips the grate - if (actor->params == 6359 && gPlayState->sceneNum == SCENE_SPIRIT_TEMPLE && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { - *should = true; - break; - } - *should = false; - break; - } - case ACTOR_BG_HIDAN_FWBIG: - case ACTOR_EN_EX_ITEM: - case ACTOR_EN_DNT_NOMAL: - case ACTOR_EN_DNT_DEMO: - case ACTOR_BG_HAKA_ZOU: { - *should = false; - break; - } - case ACTOR_EN_TA: - case ACTOR_DOOR_SHUTTER: { - // The shutter cutscene occurs post-switch cutscene to focus Link on the unlocked doors - if (((actor->params == 9402 && gPlayState->sceneNum == SCENE_JABU_JABU) || - (actor->params == 20460 && gPlayState->sceneNum == SCENE_DODONGOS_CAVERN)) && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { - break; - } - *should = false; - RateLimitedSuccessChime(); - break; - } - case ACTOR_OBJ_LIGHTSWITCH: { - ObjLightswitch* lightswitch = (ObjLightswitch*)actor; - lightswitch->toggleDelay = 0; - *should = false; - RateLimitedSuccessChime(); - break; - } - case ACTOR_BG_ICE_SHUTTER: - case ACTOR_OBJ_SYOKUDAI: - case ACTOR_OBJ_TIMEBLOCK: - case ACTOR_EN_PO_SISTERS: - case ACTOR_OBJ_ICE_POLY: - case ACTOR_BG_YDAN_MARUTA: - case ACTOR_BG_SPOT18_SHUTTER: - case ACTOR_BG_SPOT05_SOKO: - case ACTOR_BG_SPOT06_OBJECTS: - case ACTOR_BG_SPOT18_BASKET: - case ACTOR_BG_HIDAN_CURTAIN: - case ACTOR_BG_MORI_HINERI: - case ACTOR_BG_MIZU_SHUTTER: - case ACTOR_SHOT_SUN: - case ACTOR_BG_HAKA_GATE: - case ACTOR_EN_KAKASI2: - case ACTOR_EN_DNT_JIJI: - *should = false; - RateLimitedSuccessChime(); - break; - } - if (*should) { - SPDLOG_INFO("VB_PLAY_ONEPOINT_ACTOR_CS ID:{} Cat:{}", actor->id, actor->category); - } - } - break; - } - case VB_FREEZE_LINK_FOR_FOREST_PILLARS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO)) { - *should = false; - } - break; - case VB_SHOW_TITLE_CARD: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.DisableTitleCard"), IS_RANDO)) { - *should = false; - } - break; - case VB_WONDER_TALK: { - // We want to show the frog hint if it is on, regardless of cutscene settings - if (ForcedDialogIsDisabled(FORCED_DIALOG_SKIP_NPC) && - !(gPlayState->sceneNum == SCENE_ZORAS_RIVER && IS_RANDO && RAND_GET_OPTION(RSK_FROGS_HINT))) { - *should = false; - } - - // If it's near a jailed carpenter, skip it along with introduction of Gerudo mini-boss - if (gPlayState->sceneNum == SCENE_THIEVES_HIDEOUT && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { - EnWonderTalk2* enWonderTalk = va_arg(args, EnWonderTalk2*); - EnDaiku* enDaiku = - (EnDaiku*)Actor_FindNearby(gPlayState, &enWonderTalk->actor, ACTOR_EN_DAIKU, ACTORCAT_NPC, 999.0f); - if (enDaiku != NULL) { - Flags_SetSwitch(gPlayState, enDaiku->startFightSwitchFlag); - *should = false; - } - } - break; - } - case VB_NAVI_TALK: { - if (ForcedDialogIsDisabled(FORCED_DIALOG_SKIP_NAVI)) { - ElfMsg* naviTalk = va_arg(args, ElfMsg*); - int32_t paramsHighByte = naviTalk->actor.params >> 8; - if ((paramsHighByte & 0x80) != 0) { - break; - } - if ((paramsHighByte & 0x3F) != 0x3F) { - Flags_SetSwitch(gPlayState, paramsHighByte & 0x3F); - } - Actor_Kill(&naviTalk->actor); - *should = false; - } - break; - } - case VB_END_GERUDO_MEMBERSHIP_TALK: { - if (ForcedDialogIsDisabled(FORCED_DIALOG_SKIP_NPC)) { - *should = true; - } - break; - } - case VB_GORON_LINK_BE_SCARED: { - if (ForcedDialogIsDisabled(FORCED_DIALOG_SKIP_NPC)) { - EnGo2* goronLink = va_arg(args, EnGo2*); - goronLink->trackingMode = NPC_TRACKING_NONE; - goronLink->unk_211 = false; - goronLink->isAwake = false; - goronLink->actionFunc = EnGo2_CurledUp; - Flags_SetInfTable(INFTABLE_STOPPED_GORON_LINKS_ROLLING); - *should = false; - } - break; - } - case VB_NOT_BE_GREETED_BY_SARIA: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Entrances"), IS_RANDO) && - !Flags_GetInfTable(INFTABLE_GREETED_BY_SARIA)) { - Flags_SetInfTable(INFTABLE_GREETED_BY_SARIA); - *should = true; - } - break; - case VB_PLAY_SHIEK_BLOCK_MASTER_SWORD_CS: - case VB_GIVE_ITEM_LIGHT_ARROW: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - *should = false; - } - break; - case VB_PLAY_NABOORU_CAPTURED_CS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - Flags_SetEventChkInf(EVENTCHKINF_NABOORU_CAPTURED_BY_TWINROVA); - *should = false; - } - break; - case VB_PLAY_PULL_MASTER_SWORD_CS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - if (!Flags_GetEventChkInf(EVENTCHKINF_PULLED_MASTER_SWORD_FROM_PEDESTAL)) { - // Normally, these would be done in the cutscene, but we're skipping it - Flags_SetEventChkInf(EVENTCHKINF_PULLED_MASTER_SWORD_FROM_PEDESTAL); - Flags_SetEventChkInf(EVENTCHKINF_ENTERED_MASTER_SWORD_CHAMBER); - Flags_SetEventChkInf(EVENTCHKINF_SHEIK_SPAWNED_AT_MASTER_SWORD_PEDESTAL); - Flags_SetEventChkInf(EVENTCHKINF_TIME_TRAVELED_TO_ADULT); - if (GameInteractor_Should(VB_GIVE_ITEM_LIGHT_MEDALLION, true)) { - Item_Give(gPlayState, ITEM_MEDALLION_LIGHT); - } - } - *should = false; - } - break; - case VB_PLAY_DISPEL_BARRIER_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - static s16 trialEntrances[] = { - 0, - ENTR_INSIDE_GANONS_CASTLE_3, - ENTR_INSIDE_GANONS_CASTLE_6, - ENTR_INSIDE_GANONS_CASTLE_5, - ENTR_INSIDE_GANONS_CASTLE_4, - ENTR_INSIDE_GANONS_CASTLE_7, - ENTR_INSIDE_GANONS_CASTLE_2, - }; - RateLimitedSuccessChime(); - DemoKekkai* kekkai = va_arg(args, DemoKekkai*); - gPlayState->nextEntranceIndex = trialEntrances[kekkai->actor.params]; - gPlayState->transitionTrigger = TRANS_TRIGGER_START; - gPlayState->transitionType = TRANS_TYPE_FADE_BLACK; - *should = false; - } - break; - } - case VB_OWL_INTERACTION: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipOwlInteractions"), IS_RANDO) && *should) { - EnOwl* enOwl = va_arg(args, EnOwl*); - s32 owlType = (enOwl->actor.params & 0xFC0) >> 6; - - if (((enOwl->actor.params & 0xFC0) >> 6) == 1) { - Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_KAEPORA_BY_LOST_WOODS); - } - - func_80ACA62C(enOwl, gPlayState); - *should = false; - } - break; - } - case VB_OVERRIDE_LINK_THE_GORON_DIALOGUE: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { - u16* textId = va_arg(args, u16*); - // If the doors are not open yet, prioritize opening them - if (!Flags_GetInfTable(INFTABLE_GORON_CITY_DOORS_UNLOCKED)) { - *textId = 0x3036; - *should = true; - } - } - break; - } - case VB_PLAY_MWEEP_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), 0)) { - *should = false; - Inventory_ReplaceItem(gPlayState, ITEM_LETTER_RUTO, ITEM_BOTTLE); - Flags_SetEventChkInf(EVENTCHKINF_KING_ZORA_MOVED); - } - break; - } - case VB_PLAY_BEAN_PLANTING_CS: - case VB_PLAY_EYEDROP_CREATION_ANIM: - case VB_PLAY_EYEDROPS_CS: - case VB_PLAY_DROP_FISH_FOR_JABU_CS: - case VB_PLAY_DARUNIAS_JOY_CS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { - *should = false; - } - break; - case VB_PLAY_ZELDAS_LULLABY_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO) { - DemoIm* demoIm = va_arg(args, DemoIm*); - Player* player = GET_PLAYER(gPlayState); - player->stateFlags1 |= PLAYER_STATE1_IN_CUTSCENE; - player->stateFlags1 |= PLAYER_STATE1_GETTING_ITEM; - func_80986794(demoIm); - - static uint32_t demoImUpdateHook = 0; - static uint32_t demoImKillHook = 0; - demoImUpdateHook = GameInteractor::Instance->RegisterGameHook( - [](void* actorRef) mutable { - Actor* actor = static_cast(actorRef); - if (actor->id == ACTOR_DEMO_IM && - (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || - IS_RANDO)) { - DemoIm* demoIm = static_cast(actorRef); - Player* player = GET_PLAYER(gPlayState); - player->stateFlags1 |= PLAYER_STATE1_IN_CUTSCENE; - player->stateFlags1 |= PLAYER_STATE1_GETTING_ITEM; - - if (Animation_OnFrame(&demoIm->skelAnime, 25.0f)) { - Audio_PlaySoundGeneral(NA_SE_IT_DEKU, &demoIm->actor.projectedPos, 4, - &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, - &gSfxDefaultReverb); - GameInteractor::Instance->UnregisterGameHook( - demoImUpdateHook); - GameInteractor::Instance->UnregisterGameHook( - demoImKillHook); - demoImUpdateHook = 0; - demoImKillHook = 0; - } else if (Animation_OnFrame(&demoIm->skelAnime, 15.0f)) { - Player* player = GET_PLAYER(gPlayState); - // SOH [Randomizer] In entrance rando have impa bring link back to the front of castle - // grounds - if (IS_RANDO && RAND_GET_OPTION(RSK_SHUFFLE_OVERWORLD_ENTRANCES)) { - gPlayState->nextEntranceIndex = ENTR_CASTLE_GROUNDS_SOUTH_EXIT; - } else { - gPlayState->nextEntranceIndex = ENTR_HYRULE_FIELD_17; - } - gSaveContext.dayTime = gSaveContext.skyboxTime = 0x8000; - gPlayState->transitionType = TRANS_TYPE_FADE_WHITE; - gPlayState->transitionTrigger = TRANS_TRIGGER_START; - gSaveContext.nextTransitionType = 2; - Player_SetCsActionWithHaltedActors(gPlayState, &player->actor, 8); - } - } - }); - demoImKillHook = GameInteractor::Instance->RegisterGameHook( - [](int16_t sceneNum) mutable { - GameInteractor::Instance->UnregisterGameHook(demoImUpdateHook); - GameInteractor::Instance->UnregisterGameHook(demoImKillHook); - demoImUpdateHook = 0; - demoImKillHook = 0; - }); - *should = false; - } - break; - } - case VB_PLAY_SARIAS_SONG_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO) { - EnSa* enSa = va_arg(args, EnSa*); - enSa->actionFunc = func_80AF6B20; - *should = false; - } - break; - } - case VB_DESPAWN_HORSE_RACE_COW: { - if (Flags_GetEventChkInf(EVENTCHKINF_WON_COW_IN_MALONS_RACE) && - CVarGetInteger(CVAR_ENHANCEMENT("CowOfTime"), 0)) { - *should = false; - } - break; - } - case VB_PLAY_DRAIN_WELL_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - *should = false; - Flags_SetSwitch(gPlayState, 0x2); - Flags_SetEventChkInf(EVENTCHKINF_PLAYED_SONG_OF_STORMS_IN_WINDMILL); - Flags_SetEventChkInf(EVENTCHKINF_DRAINED_WELL_IN_KAKARIKO); - } - break; - } - case VB_PLAY_SUNS_SONG_CS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO) { - *should = false; - Flags_SetEventChkInf(EVENTCHKINF_LEARNED_SUNS_SONG); - // SoH [Randomizer] TODO: Increment time X amount (find out X) - // When time is 0, it's changed to 0x46A7 - // When it's 0x8000, it's changed to 0xC090 - } - break; - case VB_PLAY_ROYAL_FAMILY_TOMB_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { - *should = false; - } - break; - } - case VB_PLAY_ROYAL_FAMILY_TOMB_EXPLODE: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { - *should = Flags_GetEventChkInf(EVENTCHKINF_DESTROYED_ROYAL_FAMILY_TOMB); - } - break; - } - case VB_PLAY_CARPENTER_FREE_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO)) { - EnDaiku* enDaiku = va_arg(args, EnDaiku*); - if (enDaiku->subCamActive) { - enDaiku->subCamActive = false; - EnDaiku_EscapeSuccess(enDaiku, gPlayState); - } - *should = false; - } - break; - } - case VB_PLAY_GORON_FREE_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - *should = false; - } - break; - } - case VB_PLAY_DOOR_OF_TIME_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { - *should = false; - Flags_SetEventChkInf(EVENTCHKINF_OPENED_THE_DOOR_OF_TIME); - Flags_SetEnv(gPlayState, 2); - Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME); - } - break; - } - case VB_PLAY_FIRE_ARROW_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { - *should = false; - } - break; - } - case VB_GIVE_ITEM_SONG: - case VB_PLAY_MINUET_OF_FOREST_CS: - case VB_PLAY_BOLERO_OF_FIRE_CS: - case VB_PLAY_SERENADE_OF_WATER_CS: - case VB_PLAY_SONG_OF_STORMS_CS: - case VB_PLAY_PRELUDE_OF_LIGHT_CS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO) { - *should = false; - } - break; - case VB_FREEZE_ON_SKULL_TOKEN: - if (CVarGetInteger(CVAR_ENHANCEMENT("SkulltulaFreeze"), 0)) { - *should = false; - } - break; - case VB_BE_VALID_GRAVEDIGGING_SPOT: - if (CVarGetInteger(CVAR_ENHANCEMENT("DampeWin"), IS_RANDO)) { - EnTk* enTk = va_arg(args, EnTk*); - enTk->validDigHere = true; - *should = true; - } - break; - case VB_BE_DAMPE_GRAVEDIGGING_GRAND_PRIZE: - if (CVarGetInteger(CVAR_ENHANCEMENT("DampeWin"), IS_RANDO)) { - EnTk* enTk = va_arg(args, EnTk*); - enTk->currentReward = 3; - *should = true; - } - break; - case VB_DAMPE_GRAVEDIGGING_GRAND_PRIZE_BE_HEART_PIECE: - if (CVarGetInteger(CVAR_ENHANCEMENT("GravediggingTourFix"), 0) || IS_RANDO) { - *should = !Flags_GetCollectible(gPlayState, COLLECTFLAG_GRAVEDIGGING_HEART_PIECE); - } - break; - case VB_FIX_SAW_SOFTLOCK: - // Animation Count should be no more than 1 to guarantee putaway is complete after giving the saw - // As this is vanilla behavior, it only applies with the Fix toggle or Skip Text enabled. - *should = (CVarGetInteger(CVAR_ENHANCEMENT("FixSawSoftlock"), 0) != 0 || - CVarGetInteger(CVAR_ENHANCEMENT("SkipText"), 0) != 0) - ? gPlayState->animationCtx.animationCount > 1 - : *should; - break; - case VB_BIGGORON_CONSIDER_SWORD_FORGED: - *should = Environment_GetBgsDayCount() >= CVarGetInteger(CVAR_ENHANCEMENT("ForgeTime"), 3); - break; - case VB_BE_ELIGIBLE_FOR_GREAT_FAIRY_REWARD: { - BgDyYoseizo* bgDyYoseizo = va_arg(args, BgDyYoseizo*); - RandomizerInf flag = RAND_INF_MAX; - - if (gPlayState->sceneNum == SCENE_GREAT_FAIRYS_FOUNTAIN_SPELLS) { - switch (bgDyYoseizo->fountainType) { - case 0: - flag = RAND_INF_ZF_GREAT_FAIRY_REWARD; - break; - case 1: - flag = RAND_INF_HC_GREAT_FAIRY_REWARD; - break; - case 2: - flag = RAND_INF_COLOSSUS_GREAT_FAIRY_REWARD; - break; - } - } else { - switch (bgDyYoseizo->fountainType) { - case 0: - flag = RAND_INF_DMT_GREAT_FAIRY_REWARD; - break; - case 1: - flag = RAND_INF_DMC_GREAT_FAIRY_REWARD; - break; - case 2: - flag = RAND_INF_OGC_GREAT_FAIRY_REWARD; - break; - } - } - - if (flag != RAND_INF_MAX && - (IS_RANDO || CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) { - if (IS_RANDO || *should) { - Flags_SetRandomizerInf(flag); - gSaveContext.healthAccumulator = MAX_HEALTH; - Magic_Fill(gPlayState); - } - *should = false; - } - - break; - } - case VB_PLAY_GATE_OPENING_OR_CLOSING_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { - EnHeishi2* enHeishi2 = va_arg(args, EnHeishi2*); - enHeishi2->unk_2F2[0] = 0; - - // The second argument determines whether the vanilla code should be run anyway. It - // should be set to `true` ONLY IF said code calls `Play_ClearCamera`, false otherwise. - bool clearCamera = (bool)va_arg(args, int); - *should = clearCamera && enHeishi2->cameraId != MAIN_CAM; - } - break; - } - case VB_PLAY_RAINBOW_BRIDGE_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - *should = false; - if (!Flags_GetEventChkInf(EVENTCHKINF_RAINBOW_BRIDGE_BUILT)) { - func_800F595C(NA_BGM_BRIDGE_TO_GANONS); - // This would have been set 2 frames later, but we're skipping now so the sound doesn't play twice - Flags_SetEventChkInf(EVENTCHKINF_RAINBOW_BRIDGE_BUILT); - } - } - break; - } - case VB_PHANTOM_GANON_DEATH_SCENE: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), IS_RANDO || IS_BOSS_RUSH)) { - *should = false; - BossGanondrof* pg = va_arg(args, BossGanondrof*); - Player* player = GET_PLAYER(gPlayState); - if (pg->work[GND_ACTION_STATE] == DEATH_SPASM) { - // Skip to death scream animation and move ganondrof to middle - pg->deathState = DEATH_SCREAM; - pg->timers[0] = 50; - AnimationHeader* screamAnim = (AnimationHeader*)gPhantomGanonScreamAnim; - Animation_MorphToLoop(&pg->skelAnime, screamAnim, -10.0f); - pg->actor.world.pos.x = GND_BOSSROOM_CENTER_X; - pg->actor.world.pos.y = GND_BOSSROOM_CENTER_Y + 83.0f; - pg->actor.world.pos.z = GND_BOSSROOM_CENTER_Z; - pg->actor.shape.rot.y = 0; - pg->work[GND_BODY_DECAY_INDEX] = 0; - Audio_PlayActorSound2(&pg->actor, NA_SE_EN_FANTOM_LAST); - - // Move Player out of the center of the room - player->actor.world.pos.x = GND_BOSSROOM_CENTER_X - 200.0f; - player->actor.world.pos.z = GND_BOSSROOM_CENTER_Z; - } - } - break; - } - case VB_NABOORU_KNUCKLE_DEATH_SCENE: { - EnIk* ik = va_arg(args, EnIk*); - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.QuickBossDeaths"), IS_RANDO)) { - // Because no CS in rando, we hide the death of the knuckle by spawning flames and kill the actor - if ((ik->actor.colChkInfo.health <= 10)) { - s32 i; - Vec3f pos; - Vec3f sp7C = { 0.0f, 0.5f, 0.0f }; - int flameAmount = 100; - - for (i = flameAmount; i >= 0; i--) { - pos.x = ik->actor.world.pos.x + Rand_CenteredFloat(120.0f); - pos.z = ik->actor.world.pos.z + Rand_CenteredFloat(120.0f); - pos.y = ik->actor.world.pos.y + 20.0f + Rand_CenteredFloat(120.0f); - EffectSsDeadDb_Spawn(gPlayState, &pos, &sp7C, &sp7C, 100, 0, 255, 255, 255, 255, 0, 0, 255, 1, - 9, true); - } - Actor_Kill(&ik->actor); - } - *should = false; - } - break; - } - case VB_PLAY_SLOW_CHEST_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("FastChests"), 0)) { - *should = false; - } - break; - } - case VB_SKIP_SCARECROWS_SONG: { - if (gPlayState->msgCtx.msgMode == MSGMODE_OCARINA_PLAYING && - CVarGetInteger(CVAR_ENHANCEMENT("InstantScarecrow"), 0) && gSaveContext.scarecrowSpawnSongSet) { - *should = true; - } - break; - } - default: - break; - } - - va_end(args); -} - -static uint32_t enMa1UpdateHook = 0; -static uint32_t enMa1KillHook = 0; -static uint32_t enFuUpdateHook = 0; -static uint32_t enFuKillHook = 0; -static uint32_t enJjUpdateHook = 0; -static uint32_t enJjKillHook = 0; -static uint32_t bgSpot02UpdateHook = 0; -static uint32_t bgSpot02KillHook = 0; -static uint32_t bgSpot03UpdateHook = 0; -static uint32_t bgSpot03KillHook = 0; -static uint32_t enPoSistersUpdateHook = 0; -static uint32_t enPoSistersKillHook = 0; -void TimeSaverOnActorInitHandler(void* actorRef) { - Actor* actor = static_cast(actorRef); - - if (actor->id == ACTOR_EN_MA1 && gPlayState->sceneNum == SCENE_LON_LON_RANCH) { - enMa1UpdateHook = - GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { - Actor* innerActor = static_cast(innerActorRef); - if (innerActor->id == ACTOR_EN_MA1 && - (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO)) { - EnMa1* enMa1 = static_cast(innerActorRef); - if (enMa1->actionFunc == EnMa1_StartTeachSong) { - enMa1->actionFunc = EnMa1_EndTeachSong; - GameInteractor::Instance->UnregisterGameHook(enMa1UpdateHook); - GameInteractor::Instance->UnregisterGameHook(enMa1KillHook); - enMa1UpdateHook = 0; - enMa1KillHook = 0; - // They've already learned the song - } else if (enMa1->actionFunc == EnMa1_Idle) { - GameInteractor::Instance->UnregisterGameHook(enMa1UpdateHook); - GameInteractor::Instance->UnregisterGameHook(enMa1KillHook); - enMa1UpdateHook = 0; - enMa1KillHook = 0; - } - } - }); - enMa1KillHook = - GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { - GameInteractor::Instance->UnregisterGameHook(enMa1UpdateHook); - GameInteractor::Instance->UnregisterGameHook(enMa1KillHook); - enMa1UpdateHook = 0; - enMa1KillHook = 0; - }); - } - - if (actor->id == ACTOR_EN_FU) { - enFuUpdateHook = - GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { - Actor* innerActor = static_cast(innerActorRef); - if (innerActor->id == ACTOR_EN_FU && - (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO)) { - EnFu* enFu = static_cast(innerActorRef); - if (enFu->actionFunc == EnFu_TeachSong) { - enFu->actionFunc = EnFu_EndTeachSong; - GameInteractor::Instance->UnregisterGameHook(enFuUpdateHook); - GameInteractor::Instance->UnregisterGameHook(enFuKillHook); - enFuUpdateHook = 0; - enFuKillHook = 0; - } - } - }); - enFuKillHook = - GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { - GameInteractor::Instance->UnregisterGameHook(enFuUpdateHook); - GameInteractor::Instance->UnregisterGameHook(enFuKillHook); - enFuUpdateHook = 0; - enFuKillHook = 0; - }); - } - - if (actor->id == ACTOR_EN_JJ) { - enJjUpdateHook = - GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { - Actor* innerActor = static_cast(innerActorRef); - - if (innerActor->id != ACTOR_EN_JJ || Flags_GetEventChkInf(EVENTCHKINF_OFFERED_FISH_TO_JABU_JABU)) { - return; - } - - bool shouldOpen = IS_RANDO ? RAND_GET_OPTION(RSK_JABU_OPEN).Get() - : CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipJabuJabuFish"), 0); - if (!shouldOpen) { - return; - } - - EnJj* enJj = static_cast(innerActorRef); - if (enJj->actionFunc == EnJj_WaitForFish) { - EnJj_SetupAction(enJj, EnJj_WaitToOpenMouth); - GameInteractor::Instance->UnregisterGameHook(enJjUpdateHook); - GameInteractor::Instance->UnregisterGameHook(enJjKillHook); - enJjUpdateHook = 0; - enJjKillHook = 0; - } - }); - enJjKillHook = - GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { - GameInteractor::Instance->UnregisterGameHook(enJjUpdateHook); - GameInteractor::Instance->UnregisterGameHook(enJjKillHook); - enJjUpdateHook = 0; - enJjKillHook = 0; - }); - } - - if (actor->id == ACTOR_EN_OWL && gPlayState->sceneNum == SCENE_ZORAS_RIVER && - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"), 0) == 2) { - Actor_Kill(actor); - } - - if (actor->id == ACTOR_BG_SPOT02_OBJECTS && actor->params == 2) { - bgSpot02UpdateHook = - GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { - Actor* innerActor = static_cast(innerActorRef); - if (innerActor->id == ACTOR_BG_SPOT02_OBJECTS && innerActor->params == 2 && - (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) { - BgSpot02Objects* bgSpot02 = static_cast(innerActorRef); - if (bgSpot02->actionFunc == func_808ACC34) { - bgSpot02->actionFunc = func_808AC908; - GameInteractor::Instance->UnregisterGameHook(bgSpot02UpdateHook); - GameInteractor::Instance->UnregisterGameHook(bgSpot02KillHook); - bgSpot02UpdateHook = 0; - bgSpot02KillHook = 0; - } - } - }); - bgSpot02KillHook = - GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { - GameInteractor::Instance->UnregisterGameHook(bgSpot02UpdateHook); - GameInteractor::Instance->UnregisterGameHook(bgSpot02KillHook); - bgSpot02UpdateHook = 0; - bgSpot02KillHook = 0; - }); - } - - if (actor->id == ACTOR_BG_SPOT03_TAKI) { - bgSpot03UpdateHook = - GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { - Actor* innerActor = static_cast(innerActorRef); - - if (innerActor->id != ACTOR_BG_SPOT03_TAKI) { - return; - } - - bool shouldKeepOpen = RAND_GET_OPTION(RSK_SLEEPING_WATERFALL) && IS_RANDO; - if (!shouldKeepOpen) { - int enhancement = CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SleepingWaterfall"), 0); - shouldKeepOpen = - (enhancement == 2 && - ((CHECK_QUEST_ITEM(QUEST_SONG_LULLABY) && (INV_CONTENT(ITEM_OCARINA_TIME) != ITEM_NONE)))) || - (enhancement == 1 && Flags_GetEventChkInf(EVENTCHKINF_OPENED_ZORAS_DOMAIN)); - } - - if (!shouldKeepOpen) { - return; - } - - BgSpot03Taki* bgSpot03 = static_cast(innerActorRef); - if (bgSpot03->actionFunc == BgSpot03Taki_HandleWaterfallState) { - bgSpot03->actionFunc = BgSpot03Taki_KeepOpen; - bgSpot03->state = WATERFALL_OPENED; - bgSpot03->openingAlpha = 0.0f; - Flags_SetSwitch(gPlayState, bgSpot03->switchFlag); - func_8003EBF8(gPlayState, &gPlayState->colCtx.dyna, bgSpot03->dyna.bgId); - BgSpot03Taki_ApplyOpeningAlpha(bgSpot03, 0); - BgSpot03Taki_ApplyOpeningAlpha(bgSpot03, 1); - - GameInteractor::Instance->UnregisterGameHook(bgSpot03UpdateHook); - GameInteractor::Instance->UnregisterGameHook(bgSpot03KillHook); - bgSpot03UpdateHook = 0; - bgSpot03KillHook = 0; - } - }); - bgSpot03KillHook = - GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { - GameInteractor::Instance->UnregisterGameHook(bgSpot03UpdateHook); - GameInteractor::Instance->UnregisterGameHook(bgSpot03KillHook); - bgSpot03UpdateHook = 0; - bgSpot03KillHook = 0; - }); - } - - if (actor->id == ACTOR_EN_DNT_DEMO && - (IS_RANDO || CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) { - EnDntDemo* enDntDemo = static_cast(actorRef); - enDntDemo->actionFunc = EnDntDemo_JudgeSkipToReward; - } - - // Forest Temple entrance cutscene - // This is a bit of a hack, we can't effectively override the behavior of the torches - // or poes from which the cutscene is triggered until we can have a "BeforeActorInit" hook. - // So for now we're just going to set the flag before they get to the room the cutscene is in - if (gPlayState->sceneNum == SCENE_FOREST_TEMPLE && actor->id == ACTOR_EN_DOOR && - !Flags_GetSwitch(gPlayState, 0x1B) && !Flags_GetSwitch(gPlayState, 0x1C)) { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) && - !CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { - Flags_SetSwitch(gPlayState, 0x1B); - } - } - - // Forest Temple purple poe fight speedup - if (actor->id == ACTOR_EN_PO_SISTERS && actor->params == 28) { - enPoSistersUpdateHook = - GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { - Actor* innerActor = static_cast(innerActorRef); - if (innerActor->id == ACTOR_EN_PO_SISTERS && innerActor->params == 28 && - (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) { - EnPoSisters* enPoSisters = static_cast(innerActorRef); - if (enPoSisters->actionFunc == func_80ADB338) { - enPoSisters->unk_19C = 0; - } - } - }); - enPoSistersKillHook = - GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { - GameInteractor::Instance->UnregisterGameHook(enPoSistersUpdateHook); - GameInteractor::Instance->UnregisterGameHook(enPoSistersKillHook); - enPoSistersUpdateHook = 0; - enPoSistersKillHook = 0; - }); - } - - // Fire Temple Darunia cutscene - if (actor->id == ACTOR_EN_DU && gPlayState->sceneNum == SCENE_FIRE_TEMPLE) { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) && - !CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { - Flags_SetInfTable(INFTABLE_SPOKE_TO_DARUNIA_IN_FIRE_TEMPLE); - Actor_Kill(actor); - } - } - - // Water Temple Ruto cutscene - if (actor->id == ACTOR_EN_RU2 && gPlayState->sceneNum == SCENE_WATER_TEMPLE) { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - EnRu2* enRu2 = (EnRu2*)actor; - EnRu2_SetEncounterSwitchFlag(enRu2, gPlayState); - Actor_Kill(actor); - } - } -} - -void TimeSaverOnSceneInitHandler(int16_t sceneNum) { - switch (sceneNum) { - case SCENE_HYRULE_CASTLE: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) && - !Flags_GetInfTable(INFTABLE_ENTERED_HYRULE_CASTLE)) { - Flags_SetInfTable(INFTABLE_ENTERED_HYRULE_CASTLE); - Flags_SetInfTable(INFTABLE_MET_CHILD_MALON_AT_CASTLE_OR_MARKET); - Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_CHILD_MALON_AT_CASTLE_OR_MARKET); - } - break; - case SCENE_LON_LON_RANCH: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) && - GameInteractor_Should(VB_MALON_RETURN_FROM_CASTLE, - Flags_GetEventChkInf(EVENTCHKINF_TALON_RETURNED_FROM_CASTLE)) && - (!IS_RANDO || !RAND_GET_OPTION(RSK_SHUFFLE_SPEAK) || - Flags_GetRandomizerInf(RAND_INF_CAN_SPEAK_HYLIAN))) { - Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_CHILD_MALON_AT_RANCH); - Flags_SetInfTable(INFTABLE_CHILD_MALON_SAID_EPONA_WAS_AFRAID_OF_YOU); - Flags_SetEventChkInf(EVENTCHKINF_INVITED_TO_SING_WITH_CHILD_MALON); - } - break; - case SCENE_DEKU_TREE_BOSS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { - if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_GOHMA_BATTLE)) { - Flags_SetEventChkInf(EVENTCHKINF_BEGAN_GOHMA_BATTLE); - } - } - break; - case SCENE_DODONGOS_CAVERN_BOSS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { - if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_KING_DODONGO_BATTLE)) { - Flags_SetEventChkInf(EVENTCHKINF_BEGAN_KING_DODONGO_BATTLE); - } - } - break; - case SCENE_JABU_JABU_BOSS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { - if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_BARINA_BATTLE)) { - Flags_SetEventChkInf(EVENTCHKINF_BEGAN_BARINA_BATTLE); - } - } - break; - case SCENE_FOREST_TEMPLE_BOSS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { - if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_PHANTOM_GANON_BATTLE)) { - Flags_SetEventChkInf(EVENTCHKINF_BEGAN_PHANTOM_GANON_BATTLE); - } - } - break; - case SCENE_FIRE_TEMPLE_BOSS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { - if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_VOLVAGIA_BATTLE)) { - Flags_SetEventChkInf(EVENTCHKINF_BEGAN_VOLVAGIA_BATTLE); - } - } - break; - case SCENE_WATER_TEMPLE_BOSS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { - if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_MORPHA_BATTLE)) { - Flags_SetEventChkInf(EVENTCHKINF_BEGAN_MORPHA_BATTLE); - } - } - break; - case SCENE_SPIRIT_TEMPLE_BOSS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { - if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_TWINROVA_BATTLE)) { - Flags_SetEventChkInf(EVENTCHKINF_BEGAN_TWINROVA_BATTLE); - Flags_SetEventChkInf(EVENTCHKINF_BEGAN_NABOORU_BATTLE); - Flags_SetEventChkInf(EVENTCHKINF_NABOORU_ORDERED_TO_FIGHT_BY_TWINROVA); - } - } - break; - case SCENE_SHADOW_TEMPLE_BOSS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { - if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_BONGO_BONGO_BATTLE)) { - Flags_SetEventChkInf(EVENTCHKINF_BEGAN_BONGO_BONGO_BATTLE); - } - } - break; - case SCENE_GANONDORF_BOSS: - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO)) { - if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_GANONDORF_BATTLE)) { - Flags_SetEventChkInf(EVENTCHKINF_BEGAN_GANONDORF_BATTLE); - } - } - break; - } -} - -static GetItemEntry vanillaQueuedItemEntry = GET_ITEM_NONE; - -extern void TimeSaverQueueItem(RandomizerGet randoGet) { - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(randoGet).GetGIEntry_Copy(); -} - -void TimeSaverOnFlagSetHandler(int16_t flagType, int16_t flag) { - // Do nothing when in a boss rush - if (IS_BOSS_RUSH) { - return; - } - - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), 0)) { - switch (flagType) { - case FLAG_EVENT_CHECK_INF: - switch (flag) { - case EVENTCHKINF_SPOKE_TO_SARIA_ON_BRIDGE: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FAIRY_OCARINA).GetGIEntry_Copy(); - break; - case EVENTCHKINF_OBTAINED_KOKIRI_EMERALD_DEKU_TREE_DEAD: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_KOKIRI_EMERALD).GetGIEntry_Copy(); - break; - case EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_GORON_RUBY).GetGIEntry_Copy(); - break; - case EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_ZORA_SAPPHIRE).GetGIEntry_Copy(); - break; - case EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FOREST_MEDALLION).GetGIEntry_Copy(); - break; - case EVENTCHKINF_USED_FIRE_TEMPLE_BLUE_WARP: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FIRE_MEDALLION).GetGIEntry_Copy(); - break; - case EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_WATER_MEDALLION).GetGIEntry_Copy(); - break; - case EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_LIGHT_ARROWS).GetGIEntry_Copy(); - break; - case EVENTCHKINF_TIME_TRAVELED_TO_ADULT: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_LIGHT_MEDALLION).GetGIEntry_Copy(); - break; - } - break; - case FLAG_RANDOMIZER_INF: - switch (flag) { - case RAND_INF_DUNGEONS_DONE_SHADOW_TEMPLE: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SHADOW_MEDALLION).GetGIEntry_Copy(); - break; - case RAND_INF_DUNGEONS_DONE_SPIRIT_TEMPLE: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SPIRIT_MEDALLION).GetGIEntry_Copy(); - break; - } - break; - } - } - - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), 0)) { - switch (flagType) { - case FLAG_RANDOMIZER_INF: - switch (flag) { - case RAND_INF_ZF_GREAT_FAIRY_REWARD: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_FARORES_WIND).GetGIEntry_Copy(); - break; - case RAND_INF_HC_GREAT_FAIRY_REWARD: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_DINS_FIRE).GetGIEntry_Copy(); - break; - case RAND_INF_COLOSSUS_GREAT_FAIRY_REWARD: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_NAYRUS_LOVE).GetGIEntry_Copy(); - break; - case RAND_INF_DMT_GREAT_FAIRY_REWARD: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_MAGIC_SINGLE).GetGIEntry_Copy(); - break; - case RAND_INF_DMC_GREAT_FAIRY_REWARD: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_MAGIC_DOUBLE).GetGIEntry_Copy(); - break; - case RAND_INF_OGC_GREAT_FAIRY_REWARD: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_DOUBLE_DEFENSE).GetGIEntry_Copy(); - break; - } - break; - case FLAG_ITEM_GET_INF: - switch (flag) { - case ITEMGETINF_OBTAINED_STICK_UPGRADE_FROM_STAGE: { - TimeSaverQueueItem(CUR_UPG_VALUE(UPG_STICKS) == 2 ? RG_DEKU_STICK_CAPACITY_30 - : RG_DEKU_STICK_CAPACITY_20); - break; - } - case ITEMGETINF_OBTAINED_NUT_UPGRADE_FROM_STAGE: { - TimeSaverQueueItem(CUR_UPG_VALUE(UPG_NUTS) == 2 ? RG_DEKU_NUT_CAPACITY_40 - : RG_DEKU_NUT_CAPACITY_30); - break; - } - } - break; - } - } - - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), 0)) { - switch (flagType) { - case FLAG_EVENT_CHECK_INF: - switch (flag) { - case EVENTCHKINF_LEARNED_ZELDAS_LULLABY: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_ZELDAS_LULLABY).GetGIEntry_Copy(); - break; - case EVENTCHKINF_LEARNED_MINUET_OF_FOREST: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_MINUET_OF_FOREST).GetGIEntry_Copy(); - break; - case EVENTCHKINF_LEARNED_BOLERO_OF_FIRE: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_BOLERO_OF_FIRE).GetGIEntry_Copy(); - break; - case EVENTCHKINF_LEARNED_SERENADE_OF_WATER: - vanillaQueuedItemEntry = - Rando::StaticData::RetrieveItem(RG_SERENADE_OF_WATER).GetGIEntry_Copy(); - break; - case EVENTCHKINF_LEARNED_REQUIEM_OF_SPIRIT: - vanillaQueuedItemEntry = - Rando::StaticData::RetrieveItem(RG_REQUIEM_OF_SPIRIT).GetGIEntry_Copy(); - break; - case EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL: - vanillaQueuedItemEntry = - Rando::StaticData::RetrieveItem(RG_NOCTURNE_OF_SHADOW).GetGIEntry_Copy(); - break; - case EVENTCHKINF_LEARNED_PRELUDE_OF_LIGHT: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_PRELUDE_OF_LIGHT).GetGIEntry_Copy(); - break; - case EVENTCHKINF_LEARNED_SARIAS_SONG: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SARIAS_SONG).GetGIEntry_Copy(); - break; - case EVENTCHKINF_LEARNED_SONG_OF_TIME: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SONG_OF_TIME).GetGIEntry_Copy(); - break; - case EVENTCHKINF_LEARNED_SONG_OF_STORMS: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SONG_OF_STORMS).GetGIEntry_Copy(); - break; - case EVENTCHKINF_LEARNED_SUNS_SONG: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_SUNS_SONG).GetGIEntry_Copy(); - break; - } - break; - case FLAG_RANDOMIZER_INF: - switch (flag) { - case RAND_INF_LEARNED_EPONA_SONG: - vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_EPONAS_SONG).GetGIEntry_Copy(); - break; - } - break; - } - } -} - -void TimeSaverOnPlayerUpdateHandler() { - if (vanillaQueuedItemEntry.itemId == ITEM_NONE) - return; - - Player* player = GET_PLAYER(gPlayState); - if (player == NULL || Player_InBlockingCsMode(gPlayState, player) || - player->stateFlags1 & PLAYER_STATE1_IN_ITEM_CS || player->stateFlags1 & PLAYER_STATE1_GETTING_ITEM || - player->stateFlags1 & PLAYER_STATE1_CARRYING_ACTOR) { - return; - } - - SPDLOG_INFO("Attempting to give Item: mod {} item {}", vanillaQueuedItemEntry.modIndex, - vanillaQueuedItemEntry.itemId); - GiveItemEntryWithoutActor(gPlayState, vanillaQueuedItemEntry); - if (player->stateFlags1 & PLAYER_STATE1_IN_WATER) { - // Allow the player to receive the item while swimming - player->stateFlags2 |= PLAYER_STATE2_UNDERWATER; - Player_ActionHandler_2(player, gPlayState); - } -} - -void TimeSaverOnItemReceiveHandler(GetItemEntry receivedItemEntry) { - if (vanillaQueuedItemEntry.itemId == ITEM_NONE) - return; - - if (vanillaQueuedItemEntry.modIndex == receivedItemEntry.modIndex && - vanillaQueuedItemEntry.itemId == receivedItemEntry.itemId) { - SPDLOG_INFO("Item received: mod {} item {}", receivedItemEntry.modIndex, receivedItemEntry.itemId); - vanillaQueuedItemEntry = GET_ITEM_NONE; - } -} - -static void TimeSaverRegisterHooks() { - COND_HOOK(OnSceneInit, - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), IS_RANDO), - TimeSaverOnSceneInitHandler); - COND_HOOK(OnVanillaBehavior, true, TimeSaverOnVanillaBehaviorHandler); - COND_HOOK(OnActorInit, true, TimeSaverOnActorInitHandler); - - // item queue for use outside rando, rando has its own queue - COND_HOOK(OnLoadGame, !IS_RANDO, [](int32_t fileNum) { - vanillaQueuedItemEntry = GET_ITEM_NONE; - successChimeCooldown = 0; - }); - COND_HOOK(OnItemReceive, !IS_RANDO, TimeSaverOnItemReceiveHandler); - COND_HOOK(OnPlayerUpdate, !IS_RANDO, TimeSaverOnPlayerUpdateHandler); - COND_HOOK(OnFlagSet, - !IS_RANDO && (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), 0) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), 0) || - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), 0)), - TimeSaverOnFlagSetHandler); -} - -static RegisterShipInitFunc initFunc_RegisterHooks(TimeSaverRegisterHooks, - { "IS_RANDO", CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), - CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro"), - CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), - CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong") }); -- cgit v1.2.3