diff --git a/soh/soh/Enhancements/QoL/SkipWarpAnimation.cpp b/soh/soh/Enhancements/QoL/SkipWarpAnimation.cpp new file mode 100644 index 0000000000..23c6198dca --- /dev/null +++ b/soh/soh/Enhancements/QoL/SkipWarpAnimation.cpp @@ -0,0 +1,37 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "functions.h" +#include "macros.h" +#include "variables.h" +#include "z64save.h" + +extern PlayState* gPlayState; +} + +#define CVAR_SKIP_WARP_NAME CVAR_ENHANCEMENT("WarpSongSkipAnimation") +#define CVAR_SKIP_WARP_VALUE CVarGetInteger(CVAR_SKIP_WARP_NAME, IS_RANDO) + +// DEMOKANKYO_WARP_OUT = 0x0F; the warp-song departure animation spawned by Player_Action_8084E3C4 +static constexpr s16 DEMOKANKYO_WARP_OUT_PARAM = 0x0F; + +static void RegisterSkipWarpHooks() { + COND_ID_HOOK(ShouldActorInit, ACTOR_DEMO_KANKYO, CVAR_SKIP_WARP_VALUE, [](void* refActor, bool* should) { + Actor* actor = static_cast(refActor); + if (actor->params != DEMOKANKYO_WARP_OUT_PARAM) { + return; + } + // Leaves right away, and settles the destination: entrance rando redirects from inside it. + Environment_WarpSongLeave(gPlayState); + // Switch arrival spawn mode from WARP_SONG to IDLE so DEMO_KANKYO WARP_IN is never spawned. + // Another mode means something else owns the arrival (a rando grotto return), with no cutscene. + RespawnData* respawn = &gSaveContext.respawn[RESPAWN_MODE_RETURN]; + if (((respawn->playerParams & 0xF00) >> 8) == PLAYER_START_MODE_WARP_SONG) { + respawn->playerParams = (respawn->playerParams & ~0xF00) | (PLAYER_START_MODE_IDLE << 8); + } + *should = false; + }); +} + +static RegisterShipInitFunc initFunc(RegisterSkipWarpHooks, { CVAR_SKIP_WARP_NAME, "IS_RANDO" }); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h index a86375d413..a3ee672f99 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h @@ -29,6 +29,7 @@ DEFINE_HOOK(OnPlayerUpdate, ()); DEFINE_HOOK(OnSetDoAction, (uint16_t action)); DEFINE_HOOK(OnPlayerSfx, (u16 sfxId)); DEFINE_HOOK(OnOcarinaSongAction, ()); +DEFINE_HOOK(OnWarpSongLeave, ()); DEFINE_HOOK(OnOcarinaNote, (uint8_t note, float modulator, int8_t bend)); DEFINE_HOOK(OnShopSlotChange, (uint8_t cursorIndex, int16_t price)); DEFINE_HOOK(OnDungeonKeyUsed, (uint16_t mapIndex)); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 711186c7ad..3b5d1f74f4 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -114,6 +114,10 @@ void GameInteractor_ExecuteOnOcarinaSongAction() { GameInteractor::Instance->ExecuteHooks(); } +void GameInteractor_ExecuteOnWarpSongLeave() { + GameInteractor::Instance->ExecuteHooks(); +} + void GameInteractor_ExecuteOnOcarinaNote(uint8_t note, float modulator, int8_t bend) { GameInteractor::Instance->ExecuteHooks(note, modulator, bend); } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index 8f54e288a6..7ce3037b88 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -31,6 +31,7 @@ void GameInteractor_ExecuteOnPlayerUpdate(); void GameInteractor_ExecuteOnSetDoAction(uint16_t action); void GameInteractor_ExecuteOnPlayerSfx(u16 sfxId); void GameInteractor_ExecuteOnOcarinaSongAction(); +void GameInteractor_ExecuteOnWarpSongLeave(); void GameInteractor_ExecuteOnOcarinaNote(uint8_t note, float modulator, int8_t bend); bool GameInteractor_ShouldActorInit(void* actor); void GameInteractor_ExecuteOnActorInit(void* actor); diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index fc4c7b9068..da334769be 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -2639,10 +2639,12 @@ void RandomizerOnActorUpdateHandler(void* refActor) { Flags_UnsetRandomizerInf(RAND_INF_SPIRIT_BIG_MIRROR_STATUE_TURNED); } } +} - // In ER, override the warp song locations. Also removes the warp song cutscene - if (RAND_GET_OPTION(RSK_SHUFFLE_ENTRANCES) && actor->id == ACTOR_DEMO_KANKYO && - actor->params == 0x000F) { // Warp Song particles +// In ER, warp songs lead to their shuffled entrance rather than their warp pad. Every warp path commits its +// destination through Environment_WarpSongLeave, so that is the one place the override has to happen. +void RandomizerOnWarpSongLeaveHandler() { + if (RAND_GET_OPTION(RSK_SHUFFLE_ENTRANCES)) { Entrance_SetWarpSongEntrance(); } } @@ -2809,6 +2811,7 @@ static void RandomizerRegisterHooks() { static uint32_t afterSceneCommandsHook = 0; static uint32_t onActorInitHook = 0; static uint32_t onActorUpdateHook = 0; + static uint32_t onWarpSongLeaveHook = 0; static uint32_t onPlayerUpdateHook = 0; static uint32_t onGameFrameUpdateHook = 0; static uint32_t onSceneSpawnActorsHook = 0; @@ -2841,6 +2844,7 @@ static void RandomizerRegisterHooks() { GameInteractor::Instance->UnregisterGameHook(afterSceneCommandsHook); GameInteractor::Instance->UnregisterGameHook(onActorInitHook); GameInteractor::Instance->UnregisterGameHook(onActorUpdateHook); + GameInteractor::Instance->UnregisterGameHook(onWarpSongLeaveHook); GameInteractor::Instance->UnregisterGameHook(onPlayerUpdateHook); GameInteractor::Instance->UnregisterGameHook(onGameFrameUpdateHook); GameInteractor::Instance->UnregisterGameHook(onSceneSpawnActorsHook); @@ -2859,6 +2863,7 @@ static void RandomizerRegisterHooks() { afterSceneCommandsHook = 0; onActorInitHook = 0; onActorUpdateHook = 0; + onWarpSongLeaveHook = 0; onPlayerUpdateHook = 0; onGameFrameUpdateHook = 0; onSceneSpawnActorsHook = 0; @@ -2900,6 +2905,8 @@ static void RandomizerRegisterHooks() { GameInteractor::Instance->RegisterGameHook(RandomizerOnActorInitHandler); onActorUpdateHook = GameInteractor::Instance->RegisterGameHook(RandomizerOnActorUpdateHandler); + onWarpSongLeaveHook = GameInteractor::Instance->RegisterGameHook( + RandomizerOnWarpSongLeaveHandler); onPlayerUpdateHook = GameInteractor::Instance->RegisterGameHook(RandomizerOnPlayerUpdateHandler); onGameFrameUpdateHook = GameInteractor::Instance->RegisterGameHook( diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance.c b/soh/soh/Enhancements/randomizer/randomizer_entrance.c index 3e1d5486ca..34302bf72c 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance.c +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance.c @@ -464,9 +464,6 @@ void Entrance_SetWarpSongEntrance(void) { if (gSaveContext.gameMode != GAMEMODE_NORMAL) { // During DHWW the cutscene must play at the destination gSaveContext.respawnFlag = -3; - } else if (gSaveContext.respawnFlag == -3) { - // Unset Zoneout Type -3 to avoid cutscene at destination (technically it's not needed) - gSaveContext.respawnFlag = 0; } } diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 5672a45f79..a11e2679df 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -453,6 +453,10 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Skip Song Cutscenes", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.LearnSong")) .Options(CheckboxOptions().DefaultValue(IS_RANDO)); + AddWidget(path, "Skip Warp Cutscenes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("WarpSongSkipAnimation")) + .Options(CheckboxOptions().DefaultValue(IS_RANDO).Tooltip( + "Warp songs skip the departure and arrival cutscenes, fading immediately to the destination.")); AddWidget(path, "Skip Boss Introductions", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.BossIntro")) .Options(CheckboxOptions().DefaultValue(IS_RANDO)); diff --git a/soh/src/code/z_kankyo.c b/soh/src/code/z_kankyo.c index dc25416a67..a790176034 100644 --- a/soh/src/code/z_kankyo.c +++ b/soh/src/code/z_kankyo.c @@ -7,6 +7,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/savestate_serialize.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" typedef enum { /* 0 */ LENS_FLARE_CIRCLE0, @@ -2534,6 +2535,9 @@ void Environment_WarpSongLeave(PlayState* play) { play->transitionType = TRANS_TYPE_FADE_WHITE; gSaveContext.nextTransitionType = TRANS_TYPE_FADE_WHITE; + // Where entrance rando redirects the warp: every warp song path decides its destination here. + GameInteractor_ExecuteOnWarpSongLeave(); + switch (play->nextEntranceIndex) { case ENTR_DEATH_MOUNTAIN_CRATER_UPPER_EXIT: Flags_SetEventChkInf(EVENTCHKINF_ENTERED_DEATH_MOUNTAIN_CRATER); diff --git a/soh/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c b/soh/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c index a14d591d35..971aa8d07c 100644 --- a/soh/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c +++ b/soh/src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c @@ -794,13 +794,7 @@ void DemoKankyo_DrawWarpSparkles(Actor* thisx, PlayState* play) { this->unk_150[i].unk_0.y = (s16)((Rand_ZeroOne() - 0.5f) * 16.0f * temp_f22); this->unk_150[i].unk_0.z = (s16)((Rand_ZeroOne() - 0.5f) * 16.0f * temp_f22); this->unk_150[i].unk_23 = 0; - - // Skip the first part of warp song cutscenes in rando - if (IS_RANDO && this->actor.params == DEMOKANKYO_WARP_OUT) { - this->unk_150[i].unk_22 = 2; - } else { - this->unk_150[i].unk_22++; - } + this->unk_150[i].unk_22++; case 1: if (this->actor.params == DEMOKANKYO_WARP_OUT) {