diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/SkipIntro.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/SkipIntro.cpp new file mode 100644 index 000000000..95e9c2d10 --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/SkipIntro.cpp @@ -0,0 +1,21 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/OTRGlobals.h" + +extern "C" { + #include "z64save.h" + #include "functions.h" + extern PlayState* gPlayState; + extern SaveContext gSaveContext; +} + +void SkipIntro_Register() { + REGISTER_VB_SHOULD(VB_PLAY_TRANSITION_CS, { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), IS_RANDO)) { + if (gSaveContext.entranceIndex == ENTR_LINKS_HOUSE_0 && gSaveContext.cutsceneIndex == 0xFFF1) { + gSaveContext.cutsceneIndex = 0; + *should = false; + } + } + }); +} diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp new file mode 100644 index 000000000..564ca2d8f --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp @@ -0,0 +1,143 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/OTRGlobals.h" + +extern "C" { + #include "macros.h" + #include "src/overlays/actors/ovl_En_Ko/z_en_ko.h" + #include "z64save.h" + #include "functions.h" + #include "variables.h" +} + +#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).GetSelectedOptionIndex() + +/** + * This will override the transitions into the blue warp cutscenes, set any appropriate flags, and + * set the entrance index to where you would normally end up after the blue warp cutscene. This + * should also account for the difference between your first and following visits to the blue warp. + */ +void SkipBlueWarp_ShouldPlayTransitionCS(GIVanillaBehavior _, bool* should, void* opt) { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + uint8_t isBlueWarp = 0; + // Deku Tree Blue warp + if (gSaveContext.entranceIndex == ENTR_KOKIRI_FOREST_0 && gSaveContext.cutsceneIndex == 0xFFF1) { + gSaveContext.entranceIndex = ENTR_KOKIRI_FOREST_11; + isBlueWarp = 1; + // Dodongo's Cavern Blue warp + } else if (gSaveContext.entranceIndex == ENTR_DEATH_MOUNTAIN_TRAIL_0 && gSaveContext.cutsceneIndex == 0xFFF1) { + gSaveContext.entranceIndex = ENTR_DEATH_MOUNTAIN_TRAIL_5; + isBlueWarp = 1; + // Jabu Jabu's Blue warp + } else if (gSaveContext.entranceIndex == ENTR_ZORAS_FOUNTAIN_0 && gSaveContext.cutsceneIndex == 0xFFF0) { + gSaveContext.entranceIndex = ENTR_ZORAS_FOUNTAIN_0; + isBlueWarp = 1; + // Forest Temple Blue warp + } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_FOREST) { + // Normally set in the blue warp cutscene + Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_DEKU_TREE_SPROUT); + + if (IS_RANDO) { + gSaveContext.entranceIndex = ENTR_SACRED_FOREST_MEADOW_3; + } else { + gSaveContext.entranceIndex = ENTR_KOKIRI_FOREST_12; + } + + isBlueWarp = 1; + // Fire Temple Blue warp + } else if (gSaveContext.entranceIndex == ENTR_KAKARIKO_VILLAGE_0 && gSaveContext.cutsceneIndex == 0xFFF3) { + gSaveContext.entranceIndex = ENTR_DEATH_MOUNTAIN_CRATER_5; + isBlueWarp = 1; + // Water Temple Blue warp + } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_WATER) { + // Normally set in the blue warp cutscene + gSaveContext.dayTime = gSaveContext.skyboxTime = 0x4800; + + gSaveContext.entranceIndex = ENTR_LAKE_HYLIA_9; + isBlueWarp = 1; + // Spirit Temple Blue warp + } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_SPIRIT) { + gSaveContext.entranceIndex = ENTR_DESERT_COLOSSUS_8; + isBlueWarp = 1; + // Shadow Temple Blue warp + } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_SHADOW) { + gSaveContext.entranceIndex = ENTR_GRAVEYARD_8; + isBlueWarp = 1; + } + + if (isBlueWarp) { + if (gSaveContext.entranceIndex != ENTR_LAKE_HYLIA_9) { + // Normally set in the blue warp cutscene + gSaveContext.dayTime = gSaveContext.skyboxTime = 0x8000; + } + + *should = false; + gSaveContext.cutsceneIndex = 0; + + if (IS_RANDO && (RAND_GET_OPTION(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF || RAND_GET_OPTION(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF)) { + Entrance_OverrideBlueWarp(); + } + } + } +} + +/** + * While we could rely on the Item_Give that's normally called, it's not very clear to the player that they + * received the item when skipping the blue warp cutscene, so we'll prevent that and queue it up to be given + * to the player instead. + */ +void SkipBlueWarp_ShouldGiveItem(GIVanillaBehavior _, bool* should, void* opt) { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + *should = false; + } +} + +// Todo: Move item queueing here + +/** + * This ensures the Kokiri blocking the forest exit checks if you are eligible to leave the forest + * every frame, instead of only at init. The reason we need to do this is when we skip the blue warp cutscene + * you end up getting the Kokiri Emerald after the actor has init'd, so the actor doesn't know you have it + */ +void EnKo_MoveWhenReady(EnKo* enKo, PlayState* play) { + func_80A995CC(enKo, play); + + if ((enKo->actor.params & 0xFF) == ENKO_TYPE_CHILD_3) { + if (GameInteractor_Should(VB_OPEN_KOKIRI_FOREST, CHECK_QUEST_ITEM(QUEST_KOKIRI_EMERALD), NULL)) { + enKo->collider.dim.height -= 200; + Path_CopyLastPoint(enKo->path, &enKo->actor.world.pos); + enKo->actionFunc = func_80A99384; + } + } +} + +void SkipBlueWarp_OnActorUpdate(void* actorPtr) { + EnKo* enKo = static_cast(actorPtr); + + if ( + (enKo->actor.params & 0xFF) == ENKO_TYPE_CHILD_3 && + enKo->actionFunc == func_80A995CC && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) + ) { + enKo->actionFunc = EnKo_MoveWhenReady; + } +} + +/** + * This will ensure that the Deku Tree Sprout considers the Forest Temple finished when you skip the blue warp cutscene. + * Typically this checks for if you have the medallion, and when skipping the cutscene at this point you don't have it yet. + */ +void SkipBlueWarp_ShouldDekuJrConsiderForestTempleFinished(GIVanillaBehavior _, bool* should, void* opt) { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + if (gSaveContext.entranceIndex == ENTR_KOKIRI_FOREST_11 && gSaveContext.cutsceneIndex == 0xFFF1) { + *should = Flags_GetEventChkInf(EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP); + } + } +} + +void SkipBlueWarp_Register() { + GameInteractor::Instance->RegisterGameHookForID(ACTOR_EN_KO, SkipBlueWarp_OnActorUpdate); + GameInteractor::Instance->RegisterGameHookForID(VB_PLAY_TRANSITION_CS, SkipBlueWarp_ShouldPlayTransitionCS); + GameInteractor::Instance->RegisterGameHookForID(VB_DEKU_JR_CONSIDER_FOREST_TEMPLE_FINISHED, SkipBlueWarp_ShouldDekuJrConsiderForestTempleFinished); + GameInteractor::Instance->RegisterGameHookForID(VB_GIVE_ITEM_FROM_BLUE_WARP, SkipBlueWarp_ShouldGiveItem); +} diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipDekuTreeIntro.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipDekuTreeIntro.cpp new file mode 100644 index 000000000..bac84cb63 --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipDekuTreeIntro.cpp @@ -0,0 +1,22 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/OTRGlobals.h" + +extern "C" { + #include "src/overlays/actors/ovl_Bg_Treemouth/z_bg_treemouth.h" +} + +/** + * This will skip the Deku Tree intro, and simply open the mouth as you approach it. +*/ +void SkipDekuTreeIntro_Register() { + REGISTER_VB_SHOULD(VB_PLAY_DEKU_TREE_INTRO_CS, { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + BgTreemouth* treeMouth = static_cast(opt); + Flags_SetEventChkInf(EVENTCHKINF_DEKU_TREE_OPENED_MOUTH); + Audio_PlaySoundGeneral(NA_SE_EV_WOODDOOR_OPEN, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); + BgTreemouth_SetupAction(treeMouth, func_808BC6F8); + *should = false; + } + }); +} diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipLostWoodsBridge.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipLostWoodsBridge.cpp new file mode 100644 index 000000000..f447b7386 --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipLostWoodsBridge.cpp @@ -0,0 +1,39 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/OTRGlobals.h" + +extern "C" { + #include "z64save.h" + #include "functions.h" + extern PlayState* gPlayState; + extern SaveContext gSaveContext; +} + +void SkipLostWoodsBridge_Register() { + /** + * This skips the cutscene where you speak to Saria on the bridge in Lost Woods, where she gives you the Fairy Ocarina. + */ + REGISTER_VB_SHOULD(VB_PLAY_TRANSITION_CS, { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + if ((gSaveContext.entranceIndex == ENTR_LOST_WOODS_9) && !Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_SARIA_ON_BRIDGE)) { + Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_SARIA_ON_BRIDGE); + if (GameInteractor_Should(VB_GIVE_ITEM_FAIRY_OCARINA, true, NULL)) { + Item_Give(gPlayState, ITEM_OCARINA_FAIRY); + } + *should = false; + } + } + }); + + /** + * While we could rely on the Item_Give that's normally called (and that we have above), it's not very clear to the player + * that they received the item when skipping the cutscene, so we'll prevent it, and queue it up to be given instead. + */ + REGISTER_VB_SHOULD(VB_GIVE_ITEM_FAIRY_OCARINA, { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + *should = false; + } + }); + + // Todo: Move item queueing here +} diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipToGivingZeldasLetter.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipToGivingZeldasLetter.cpp new file mode 100644 index 000000000..357125db8 --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipToGivingZeldasLetter.cpp @@ -0,0 +1,45 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/OTRGlobals.h" + +extern "C" { + #include "src/overlays/actors/ovl_En_Zl4/z_en_zl4.h" +} + +/** + * This overrides Zelda's update function to effectively skip the dialog and cutscenes played when + * you meet with her in Hyrule Castle Courtyard. As you approach her she will turn around, and talking + * with her will place you at the very last dialog option where she gives you the letter. + */ + +u16 EnZl4_GiveItemTextId(PlayState* play, Actor* actor) { + return 0x207D; +} + +void EnZl4_SkipToGivingZeldasLetter(EnZl4* enZl4, PlayState* play) { + if (enZl4->csState == 0 && enZl4->actor.xzDistToPlayer < 700.0f && EnZl4_SetNextAnim(enZl4, 3)) { + Audio_PlayFanfare(NA_BGM_APPEAR); + enZl4->csState = 8; // ZL4_CS_PLAN + } else { + Npc_UpdateTalking(play, &enZl4->actor, &enZl4->interactInfo.talkState, enZl4->collider.dim.radius + 60.0f, EnZl4_GiveItemTextId, func_80B5B9B0); + func_80B5BB78(enZl4, play); + + if (enZl4->interactInfo.talkState != NPC_TALK_STATE_IDLE) { + enZl4->talkState = 6; + enZl4->actionFunc = EnZl4_Cutscene; + } + } +} + +void SkipToGivingZeldasLetter_OnActorInit(void* actorPtr) { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + EnZl4* enZl4 = static_cast(actorPtr); + if (enZl4->actionFunc != EnZl4_Cutscene || enZl4->csState != 0) return; + + enZl4->actionFunc = EnZl4_SkipToGivingZeldasLetter; + } +} + +void SkipToGivingZeldasLetter_Register() { + GameInteractor::Instance->RegisterGameHookForID(ACTOR_EN_ZL4, SkipToGivingZeldasLetter_OnActorInit); +} diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipZeldaFleeingCastle.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipZeldaFleeingCastle.cpp new file mode 100644 index 000000000..48cc80aa7 --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipZeldaFleeingCastle.cpp @@ -0,0 +1,67 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/OTRGlobals.h" + +extern "C" { + #include "z64save.h" + #include "functions.h" + extern SaveContext gSaveContext; +} + +void SkipZeldaFleeingCastle_ShouldPlayTransitionCS(GIVanillaBehavior _, bool* should, void* opt) { + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { + if (gSaveContext.entranceIndex == ENTR_HYRULE_FIELD_0 && gSaveContext.cutsceneIndex == 0xFFF1) { + // Normally set in the cutscene + gSaveContext.dayTime = gSaveContext.skyboxTime = 0x4AAA; + + gSaveContext.cutsceneIndex = 0; + *should = false; + } + } +} + +/** + * When this cutscene is skipped, walking up to the bridge to castle town triggers a quick fade in/out + * which can be confusing to beginners, because they need to then fetch the Ocarina of Time from the water. + * To make it more obvious what happened, we'll play the sound of the Ocarina dropping into the water. + */ +static int framesSinceSpawn = 0; +static HOOK_ID itemOcarinaUpdateHook = 0; +static HOOK_ID sceneInitHook = 0; + +void SkipZeldaFleeingCastle_OnActorUpdate(void* actorPtr) { + Actor* actor = static_cast(actorPtr); + + framesSinceSpawn++; + if (framesSinceSpawn > 20) { + Audio_PlayActorSound2(actor, NA_SE_EV_BOMB_DROP_WATER); + + GameInteractor::Instance->UnregisterGameHookForPtr(itemOcarinaUpdateHook); + GameInteractor::Instance->UnregisterGameHook(sceneInitHook); + itemOcarinaUpdateHook = 0; + sceneInitHook = 0; + } +} + +void SkipZeldaFleeingCastle_OnActorInit(void* actorPtr) { + Actor* actor = static_cast(actorPtr); + + if ( + actor->params == 3 && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) + ) { + framesSinceSpawn = 0; + itemOcarinaUpdateHook = GameInteractor::Instance->RegisterGameHookForPtr((uintptr_t)actorPtr, SkipZeldaFleeingCastle_OnActorUpdate); + sceneInitHook = GameInteractor::Instance->RegisterGameHook([] (int16_t sceneNum) { + GameInteractor::Instance->UnregisterGameHookForPtr(itemOcarinaUpdateHook); + GameInteractor::Instance->UnregisterGameHook(sceneInitHook); + itemOcarinaUpdateHook = 0; + sceneInitHook = 0; + }); + } +} + +void SkipZeldaFleeingCastle_Register() { + GameInteractor::Instance->RegisterGameHookForID(ACTOR_ITEM_OCARINA, SkipZeldaFleeingCastle_OnActorInit); + GameInteractor::Instance->RegisterGameHookForID(VB_PLAY_TRANSITION_CS, SkipZeldaFleeingCastle_ShouldPlayTransitionCS); +} diff --git a/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/MoveMidoInKokiriForest.cpp b/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/MoveMidoInKokiriForest.cpp new file mode 100644 index 000000000..78a680175 --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/MoveMidoInKokiriForest.cpp @@ -0,0 +1,30 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/OTRGlobals.h" + +extern "C" { + #include "z64save.h" + #include "macros.h" + #include "variables.h" + #include "functions.h" + extern PlayState* gPlayState; + extern SaveContext gSaveContext; +} + +/** + * This simply skips the Mido interaction in Kokiri Forest, once you equip the Kokiri + * Sword and Deku Shield he will move out of the way without you needing to talk to him. + */ +void MoveMidoInKokiriForest_Register() { + REGISTER_VB_SHOULD(VB_MOVE_MIDO_IN_KOKIRI_FOREST, { + if ( + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) && + !Flags_GetEventChkInf(EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD) && + (CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) == EQUIP_VALUE_SHIELD_DEKU) && + (CUR_EQUIP_VALUE(EQUIP_TYPE_SWORD) == EQUIP_VALUE_SWORD_KOKIRI) + ) { + Flags_SetEventChkInf(EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD); + *should = true; + } + }); +} diff --git a/soh/soh/Enhancements/TimeSavers/TimeSavers.cpp b/soh/soh/Enhancements/TimeSavers/TimeSavers.cpp new file mode 100644 index 000000000..f6d2e3629 --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/TimeSavers.cpp @@ -0,0 +1,14 @@ +#include "TimeSavers.h" + +void TimeSavers_Register() { + // SkipCutscene + // Story + SkipBlueWarp_Register(); + SkipDekuTreeIntro_Register(); + SkipLostWoodsBridge_Register(); + SkipToGivingZeldasLetter_Register(); + SkipZeldaFleeingCastle_Register(); + SkipIntro_Register(); + // SkipMiscInteractions + MoveMidoInKokiriForest_Register(); +} diff --git a/soh/soh/Enhancements/TimeSavers/TimeSavers.h b/soh/soh/Enhancements/TimeSavers/TimeSavers.h new file mode 100644 index 000000000..b3b221351 --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/TimeSavers.h @@ -0,0 +1,17 @@ +#ifndef TIME_SAVERS_H +#define TIME_SAVERS_H + +void TimeSavers_Register(); + +// SkipCutscene + // Story + void SkipBlueWarp_Register(); + void SkipDekuTreeIntro_Register(); + void SkipLostWoodsBridge_Register(); + void SkipToGivingZeldasLetter_Register(); + void SkipZeldaFleeingCastle_Register(); + void SkipIntro_Register(); +// SkipMiscInteractions + void MoveMidoInKokiriForest_Register(); + +#endif // TIME_SAVERS_H diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index cca978b2d..956384e5d 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -440,7 +440,7 @@ typedef uint32_t HOOK_ID; } #define REGISTER_VB_SHOULD(flag, body) \ - GameInteractor::Instance->RegisterGameHookForID(flag, [](GIVanillaBehavior _, bool* should, void* opt) body) + GameInteractor::Instance->RegisterGameHookForID(flag, [](GIVanillaBehavior _, bool* should, void* opt) body) class GameInteractor { public: diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 72f1bc599..2efe6477e 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -11,6 +11,7 @@ #include #include "soh/Enhancements/nametag.h" #include "soh/Enhancements/timesaver_hook_handlers.h" +#include "soh/Enhancements/TimeSavers/TimeSavers.h" #include "soh/Enhancements/cheat_hook_handlers.h" #include "soh/Enhancements/randomizer/hook_handlers.h" #include "objects/object_gi_compass/object_gi_compass.h" @@ -1715,6 +1716,7 @@ void InitMods() { RandomizerRegisterHooks(); TimeSaverRegisterHooks(); CheatsRegisterHooks(); + TimeSavers_Register(); RegisterTTS(); RegisterInfiniteMoney(); RegisterInfiniteHealth(); diff --git a/soh/soh/Enhancements/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/timesaver_hook_handlers.cpp index 569459c67..28518ae14 100644 --- a/soh/soh/Enhancements/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/timesaver_hook_handlers.cpp @@ -34,20 +34,6 @@ extern int32_t D_8011D3AC; #define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).GetSelectedOptionIndex() -void EnKo_MoveWhenReady(EnKo* enKo, PlayState* play) { - func_80A995CC(enKo, play); - - if ((enKo->actor.params & 0xFF) == ENKO_TYPE_CHILD_3) { - // Typically this doesn't get get live updated in vanilla, but we need to - // live update it if we're skipping a certain cutscene or in randomizer - if (GameInteractor_Should(VB_OPEN_KOKIRI_FOREST, CHECK_QUEST_ITEM(QUEST_KOKIRI_EMERALD), NULL)) { - enKo->collider.dim.height -= 200; - Path_CopyLastPoint(enKo->path, &enKo->actor.world.pos); - enKo->actionFunc = func_80A99384; - } - } -} - void EnMa1_EndTeachSong(EnMa1* enMa1, PlayState* play) { if (Message_GetState(&gPlayState->msgCtx) == TEXT_STATE_CLOSING) { Flags_SetRandomizerInf(RAND_INF_LEARNED_EPONA_SONG); @@ -74,25 +60,6 @@ void EnFu_EndTeachSong(EnFu* enFu, PlayState* play) { } } -u16 EnZl4_GiveItemTextId(PlayState* play, Actor* actor) { - return 0x207D; -} - -void EnZl4_SkipToGivingZeldasLetter(EnZl4* enZl4, PlayState* play) { - if (enZl4->csState == 0 && enZl4->actor.xzDistToPlayer < 600.0f && EnZl4_SetNextAnim(enZl4, 3)) { - Audio_PlayFanfare(NA_BGM_APPEAR); - enZl4->csState = 8; // ZL4_CS_PLAN - } else { - Npc_UpdateTalking(play, &enZl4->actor, &enZl4->interactInfo.talkState, enZl4->collider.dim.radius + 60.0f, EnZl4_GiveItemTextId, func_80B5B9B0); - func_80B5BB78(enZl4, play); - - if (enZl4->interactInfo.talkState != NPC_TALK_STATE_IDLE) { - enZl4->talkState = 6; - enZl4->actionFunc = EnZl4_Cutscene; - } - } -} - 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 @@ -141,11 +108,6 @@ void TimeSaverOnGameFrameUpdateHandler() { void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, void* opt) { switch (id) { case VB_PLAY_TRANSITION_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), IS_RANDO) && gSaveContext.entranceIndex == ENTR_LINKS_HOUSE_0 && gSaveContext.cutsceneIndex == 0xFFF1) { - gSaveContext.cutsceneIndex = 0; - *should = false; - } - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong"), IS_RANDO) || IS_RANDO) { // Song of Time if (gSaveContext.entranceIndex == ENTR_TEMPLE_OF_TIME_0 && gSaveContext.cutsceneIndex == 0xFFF7) { @@ -185,82 +147,6 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, void* } if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - uint8_t isBlueWarp = 0; - // Deku Tree Blue warp - if (gSaveContext.entranceIndex == ENTR_KOKIRI_FOREST_0 && gSaveContext.cutsceneIndex == 0xFFF1) { - gSaveContext.entranceIndex = ENTR_KOKIRI_FOREST_11; - isBlueWarp = 1; - // Dodongo's Cavern Blue warp - } else if (gSaveContext.entranceIndex == ENTR_DEATH_MOUNTAIN_TRAIL_0 && gSaveContext.cutsceneIndex == 0xFFF1) { - gSaveContext.entranceIndex = ENTR_DEATH_MOUNTAIN_TRAIL_5; - isBlueWarp = 1; - // Jabu Jabu's Blue warp - } else if (gSaveContext.entranceIndex == ENTR_ZORAS_FOUNTAIN_0 && gSaveContext.cutsceneIndex == 0xFFF0) { - gSaveContext.entranceIndex = ENTR_ZORAS_FOUNTAIN_0; - isBlueWarp = 1; - // Forest Temple Blue warp - } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_FOREST) { - // Normally set in the blue warp cutscene - Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_DEKU_TREE_SPROUT); - - if (IS_RANDO) { - gSaveContext.entranceIndex = ENTR_SACRED_FOREST_MEADOW_3; - } else { - gSaveContext.entranceIndex = ENTR_KOKIRI_FOREST_12; - } - - isBlueWarp = 1; - // Fire Temple Blue warp - } else if (gSaveContext.entranceIndex == ENTR_KAKARIKO_VILLAGE_0 && gSaveContext.cutsceneIndex == 0xFFF3) { - gSaveContext.entranceIndex = ENTR_DEATH_MOUNTAIN_CRATER_5; - isBlueWarp = 1; - // Water Temple Blue warp - } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_WATER) { - // Normally set in the blue warp cutscene - gSaveContext.dayTime = gSaveContext.skyboxTime = 0x4800; - - gSaveContext.entranceIndex = ENTR_LAKE_HYLIA_9; - isBlueWarp = 1; - // Spirit Temple Blue warp - } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_SPIRIT) { - gSaveContext.entranceIndex = ENTR_DESERT_COLOSSUS_8; - isBlueWarp = 1; - // Shadow Temple Blue warp - } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_SHADOW) { - gSaveContext.entranceIndex = ENTR_GRAVEYARD_8; - isBlueWarp = 1; - } - - if (isBlueWarp) { - // Normally set in the blue warp cutscene - gSaveContext.dayTime = gSaveContext.skyboxTime = 0x8000; - - *should = false; - gSaveContext.cutsceneIndex = 0; - - if (IS_RANDO && (RAND_GET_OPTION(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF || RAND_GET_OPTION(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF)) { - Entrance_OverrideBlueWarp(); - } - } - - // Flee hyrule castle cutscene - if (gSaveContext.entranceIndex == ENTR_HYRULE_FIELD_0 && gSaveContext.cutsceneIndex == 0xFFF1) { - // Normally set in the blue warp cutscene - gSaveContext.dayTime = gSaveContext.skyboxTime = 0x4AAA; - - gSaveContext.cutsceneIndex = 0; - *should = false; - } - - // Lost Woods Bridge - if ((gSaveContext.entranceIndex == ENTR_LOST_WOODS_9) && !Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_SARIA_ON_BRIDGE)) { - Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_SARIA_ON_BRIDGE); - if (GameInteractor_Should(VB_GIVE_ITEM_FAIRY_OCARINA, true, NULL)) { - Item_Give(gPlayState, ITEM_OCARINA_FAIRY); - } - *should = false; - } - // LACS u8 meetsLACSRequirements = LINK_IS_ADULT && @@ -429,38 +315,7 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, void* *should = true; } break; - case VB_MOVE_MIDO_IN_KOKIRI_FOREST: - if ( - CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) && - !Flags_GetEventChkInf(EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD) && - (CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) == EQUIP_VALUE_SHIELD_DEKU) && - (CUR_EQUIP_VALUE(EQUIP_TYPE_SWORD) == EQUIP_VALUE_SWORD_KOKIRI) - ) { - Flags_SetEventChkInf(EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD); - *should = true; - } - break; - case VB_PLAY_DEKU_TREE_INTRO_CS: { - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - BgTreemouth* treeMouth = static_cast(opt); - Flags_SetEventChkInf(EVENTCHKINF_DEKU_TREE_OPENED_MOUTH); - Audio_PlaySoundGeneral(NA_SE_EV_WOODDOOR_OPEN, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); - BgTreemouth_SetupAction(treeMouth, func_808BC6F8); - *should = false; - } - break; - } - case VB_DEKU_JR_CONSIDER_FOREST_TEMPLE_FINISHED: { - // We're overriding this so that the Deku JR doesn't despawn after skipping the forest temple blue warp cutscene. - // It typically relies on the forest medallion being obtained, but that isn't given yet until after scene init - if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - *should = Flags_GetEventChkInf(EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP); - } - break; - } - case VB_GIVE_ITEM_FROM_BLUE_WARP: case VB_PLAY_SHIEK_BLOCK_MASTER_SWORD_CS: - case VB_GIVE_ITEM_FAIRY_OCARINA: case VB_GIVE_ITEM_LIGHT_ARROW: if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { *should = false; @@ -773,10 +628,6 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, void* } } -static uint32_t enKoUpdateHook = 0; -static uint32_t enKoKillHook = 0; -static uint32_t itemOcarinaUpdateHook = 0; -static uint32_t itemOcarinaframesSinceSpawn = 0; static uint32_t enMa1UpdateHook = 0; static uint32_t enMa1KillHook = 0; static uint32_t enFuUpdateHook = 0; @@ -788,50 +639,6 @@ static uint32_t enPoSistersKillHook = 0; void TimeSaverOnActorInitHandler(void* actorRef) { Actor* actor = static_cast(actorRef); - if (actor->id == ACTOR_EN_KO && (actor->params & 0xFF) == ENKO_TYPE_CHILD_3) { - enKoUpdateHook = GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { - Actor* innerActor = static_cast(innerActorRef); - if (innerActor->id == ACTOR_EN_KO && (innerActor->params & 0xFF) == ENKO_TYPE_CHILD_3 && (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO) || IS_RANDO)) { - EnKo* enKo = static_cast(innerActorRef); - // They haven't moved yet, wrap their update function so we check every frame - if (enKo->actionFunc == func_80A995CC) { - enKo->actionFunc = EnKo_MoveWhenReady; - GameInteractor::Instance->UnregisterGameHook(enKoUpdateHook); - GameInteractor::Instance->UnregisterGameHook(enKoKillHook); - enKoUpdateHook = 0; - enKoKillHook = 0; - // They have already moved - } else if (enKo->actionFunc == func_80A99384) { - GameInteractor::Instance->UnregisterGameHook(enKoUpdateHook); - GameInteractor::Instance->UnregisterGameHook(enKoKillHook); - enKoUpdateHook = 0; - enKoKillHook = 0; - } - } - }); - enKoKillHook = GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) mutable { - GameInteractor::Instance->UnregisterGameHook(enKoUpdateHook); - GameInteractor::Instance->UnregisterGameHook(enKoKillHook); - enKoUpdateHook = 0; - enKoKillHook = 0; - }); - } - - if (actor->id == ACTOR_ITEM_OCARINA && actor->params == 3 && CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - itemOcarinaframesSinceSpawn = 0; - itemOcarinaUpdateHook = GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { - Actor* innerActor = static_cast(innerActorRef); - if (innerActor->id != ACTOR_ITEM_OCARINA || innerActor->params != 3) return; - itemOcarinaframesSinceSpawn++; - if (itemOcarinaframesSinceSpawn > 20) { - Audio_PlayActorSound2(innerActor, NA_SE_EV_BOMB_DROP_WATER); - - GameInteractor::Instance->UnregisterGameHook(itemOcarinaUpdateHook); - itemOcarinaUpdateHook = 0; - } - }); - } - if (actor->id == ACTOR_EN_MA1 && gPlayState->sceneNum == SCENE_LON_LON_RANCH) { enMa1UpdateHook = GameInteractor::Instance->RegisterGameHook([](void* innerActorRef) mutable { Actor* innerActor = static_cast(innerActorRef); @@ -904,13 +711,6 @@ void TimeSaverOnActorInitHandler(void* actorRef) { }); } - if (actor->id == ACTOR_EN_ZL4 && CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { - EnZl4* enZl4 = static_cast(actorRef); - if (enZl4->actionFunc != EnZl4_Cutscene || enZl4->csState != 0) return; - - enZl4->actionFunc = EnZl4_SkipToGivingZeldasLetter; - } - 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; diff --git a/soh/src/overlays/actors/ovl_Bg_Treemouth/z_bg_treemouth.h b/soh/src/overlays/actors/ovl_Bg_Treemouth/z_bg_treemouth.h index daa33bfee..ea8189ad2 100644 --- a/soh/src/overlays/actors/ovl_Bg_Treemouth/z_bg_treemouth.h +++ b/soh/src/overlays/actors/ovl_Bg_Treemouth/z_bg_treemouth.h @@ -15,7 +15,9 @@ typedef struct BgTreemouth { /* 0x016C */ BgTreemouthActionFunc actionFunc; } BgTreemouth; // size = 0x0170 +// #region SoH [Enhancements] Externed for time savers void BgTreemouth_SetupAction(BgTreemouth* actor, BgTreemouthActionFunc actionFunc); void func_808BC6F8(BgTreemouth* actor, PlayState* play); +// #endregion #endif diff --git a/soh/src/overlays/actors/ovl_En_Ko/z_en_ko.h b/soh/src/overlays/actors/ovl_En_Ko/z_en_ko.h index 5deeef465..80ecd0664 100644 --- a/soh/src/overlays/actors/ovl_En_Ko/z_en_ko.h +++ b/soh/src/overlays/actors/ovl_En_Ko/z_en_ko.h @@ -1,8 +1,10 @@ #ifndef Z_EN_KO_H #define Z_EN_KO_H +#ifndef __cplusplus #include #include "global.h" +#endif struct EnKo; @@ -57,8 +59,9 @@ typedef enum { ENKO_FQS_ADULT_SAVED } KokiriForestQuestState; +// #region SoH [Enhancements] Externed for time savers void func_80A995CC(EnKo* actor, PlayState* play); void func_80A99384(EnKo* actor, PlayState* play); -void func_80A99560(EnKo* actor, PlayState* play); +// #endregion #endif