diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index fd5611df8..716d20b08 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -376,6 +376,10 @@ typedef enum { /*** Fixes ***/ // Vanilla condition: false GI_VB_FIX_SAW_SOFTLOCK, + + /*** Quick Boss Deaths ***/ + // Vanilla condition: true + GI_VB_PHANTOM_GANON_DEATH_SCENE, } GIVanillaBehavior; #ifdef __cplusplus diff --git a/soh/soh/Enhancements/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/timesaver_hook_handlers.cpp index 46b4353ee..52e14ab8c 100644 --- a/soh/soh/Enhancements/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/timesaver_hook_handlers.cpp @@ -24,6 +24,8 @@ extern "C" { #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 +#include extern SaveContext gSaveContext; extern PlayState* gPlayState; extern int32_t D_8011D3AC; @@ -719,6 +721,31 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, void* } break; } + case GI_VB_PHANTOM_GANON_DEATH_SCENE: { + if (CVarGetInteger("gTimeSavers.SkipCutscene.QuickBossDeaths", IS_RANDO || IS_BOSS_RUSH)) { + *should = false; + BossGanondrof* pg = static_cast(opt); + Player* player = GET_PLAYER(gPlayState); + if (pg != nullptr && 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; + } } } diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index 3821f9df4..98de7a48c 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -581,6 +581,7 @@ void DrawEnhancementsMenu() { CVarGetInteger("gTimeSavers.SkipCutscene.Story", IS_RANDO) && CVarGetInteger("gTimeSavers.SkipCutscene.LearnSong", IS_RANDO) && CVarGetInteger("gTimeSavers.SkipCutscene.BossIntro", IS_RANDO) && + CVarGetInteger("gTimeSavers.SkipCutscene.QuickBossDeaths", IS_RANDO) && CVarGetInteger("gTimeSavers.SkipCutscene.OnePoint", IS_RANDO) && CVarGetInteger("gTimeSavers.NoForcedDialog", IS_RANDO) && CVarGetInteger("gTimeSavers.SkipOwlInteractions", IS_RANDO) && @@ -592,6 +593,7 @@ void DrawEnhancementsMenu() { CVarGetInteger("gTimeSavers.SkipCutscene.Story", IS_RANDO) || CVarGetInteger("gTimeSavers.SkipCutscene.LearnSong", IS_RANDO) || CVarGetInteger("gTimeSavers.SkipCutscene.BossIntro", IS_RANDO) || + CVarGetInteger("gTimeSavers.SkipCutscene.QuickBossDeaths", IS_RANDO) || CVarGetInteger("gTimeSavers.SkipCutscene.OnePoint", IS_RANDO) || CVarGetInteger("gTimeSavers.NoForcedDialog", IS_RANDO) || CVarGetInteger("gTimeSavers.SkipOwlInteractions", IS_RANDO) || @@ -608,6 +610,7 @@ void DrawEnhancementsMenu() { CVarSetInteger("gTimeSavers.SkipCutscene.Story", 1); CVarSetInteger("gTimeSavers.SkipCutscene.LearnSong", 1); CVarSetInteger("gTimeSavers.SkipCutscene.BossIntro", 1); + CVarSetInteger("gTimeSavers.SkipCutscene.QuickBossDeaths", 1); CVarSetInteger("gTimeSavers.SkipCutscene.OnePoint", 1); CVarSetInteger("gTimeSavers.NoForcedDialog", 1); CVarSetInteger("gTimeSavers.SkipOwlInteractions", 1); @@ -619,6 +622,7 @@ void DrawEnhancementsMenu() { CVarSetInteger("gTimeSavers.SkipCutscene.Story", 0); CVarSetInteger("gTimeSavers.SkipCutscene.LearnSong", 0); CVarSetInteger("gTimeSavers.SkipCutscene.BossIntro", 0); + CVarSetInteger("gTimeSavers.SkipCutscene.QuickBossDeaths", 0); CVarSetInteger("gTimeSavers.SkipCutscene.OnePoint", 0); CVarSetInteger("gTimeSavers.NoForcedDialog", 0); CVarSetInteger("gTimeSavers.SkipOwlInteractions", 0); @@ -633,6 +637,7 @@ void DrawEnhancementsMenu() { UIWidgets::PaddedEnhancementCheckbox("Skip Story Cutscenes", "gTimeSavers.SkipCutscene.Story", false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); UIWidgets::PaddedEnhancementCheckbox("Skip Song Cutscenes", "gTimeSavers.SkipCutscene.LearnSong", false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); UIWidgets::PaddedEnhancementCheckbox("Skip Boss Introductions", "gTimeSavers.SkipCutscene.BossIntro", false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); + UIWidgets::PaddedEnhancementCheckbox("Quick Boss Deaths", "gTimeSavers.SkipCutscene.QuickBossDeaths", false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); UIWidgets::PaddedEnhancementCheckbox("Skip One Point Cutscenes (Chests, Door Unlocks, etc)", "gTimeSavers.SkipCutscene.OnePoint", false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); UIWidgets::PaddedEnhancementCheckbox("No Forced Dialog", "gTimeSavers.NoForcedDialog", false, false, false, "", UIWidgets::CheckboxGraphics::Cross, IS_RANDO); UIWidgets::Tooltip("Prevent forced conversations with Navi or other NPCs"); diff --git a/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c b/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c index 82fe2c131..0b025dcd6 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c @@ -12,19 +12,10 @@ #include "overlays/effects/ovl_Effect_Ss_Hahen/z_eff_ss_hahen.h" #include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h" #include "soh/Enhancements/boss-rush/BossRush.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED) -typedef enum { - /* 0 */ NOT_DEAD, - /* 1 */ DEATH_START, - /* 2 */ DEATH_THROES, - /* 3 */ DEATH_WARP, - /* 4 */ DEATH_SCREAM, - /* 5 */ DEATH_DISINTEGRATE, - /* 6 */ DEATH_FINISH -} BossGanondrofDeathState; - typedef enum { /* 0 */ THROW_NORMAL, /* 1 */ THROW_SLOW @@ -42,12 +33,6 @@ typedef enum { /* 3 */ CHARGE_FINISH } BossGanondrofChargeAction; -typedef enum { - /* 0 */ DEATH_SPASM, - /* 1 */ DEATH_LIMP, - /* 2 */ DEATH_HUNCHED -} BossGanondrofDeathAction; - void BossGanondrof_Init(Actor* thisx, PlayState* play); void BossGanondrof_Destroy(Actor* thisx, PlayState* play); void BossGanondrof_Update(Actor* thisx, PlayState* play); @@ -959,26 +944,13 @@ void BossGanondrof_Death(BossGanondrof* this, PlayState* play) { case DEATH_THROES: switch (this->work[GND_ACTION_STATE]) { case DEATH_SPASM: - if (Animation_OnFrame(&this->skelAnime, this->fwork[GND_END_FRAME]) && !IS_RANDO && !IS_BOSS_RUSH) { - this->fwork[GND_END_FRAME] = Animation_GetLastFrame(&gPhantomGanonAirDamageAnim); - Animation_Change(&this->skelAnime, &gPhantomGanonAirDamageAnim, 0.5f, 0.0f, - this->fwork[GND_END_FRAME], ANIMMODE_ONCE_INTERP, 0.0f); - this->work[GND_ACTION_STATE] = DEATH_LIMP; - } else if (IS_RANDO || IS_BOSS_RUSH) { - // Skip to death scream animation and move ganondrof to middle - this->deathState = DEATH_SCREAM; - this->timers[0] = 50; - Animation_MorphToLoop(&this->skelAnime, &gPhantomGanonScreamAnim, -10.0f); - this->actor.world.pos.x = GND_BOSSROOM_CENTER_X; - this->actor.world.pos.y = GND_BOSSROOM_CENTER_Y + 83.0f; - this->actor.world.pos.z = GND_BOSSROOM_CENTER_Z; - this->actor.shape.rot.y = 0; - this->work[GND_BODY_DECAY_INDEX] = 0; - Audio_PlayActorSound2(&this->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; + if (GameInteractor_Should(GI_VB_PHANTOM_GANON_DEATH_SCENE, true, this)) { + if (Animation_OnFrame(&this->skelAnime, this->fwork[GND_END_FRAME])) { + this->fwork[GND_END_FRAME] = Animation_GetLastFrame(&gPhantomGanonAirDamageAnim); + Animation_Change(&this->skelAnime, &gPhantomGanonAirDamageAnim, 0.5f, 0.0f, + this->fwork[GND_END_FRAME], ANIMMODE_ONCE_INTERP, 0.0f); + this->work[GND_ACTION_STATE] = DEATH_LIMP; + } } break; case DEATH_LIMP: @@ -991,26 +963,25 @@ void BossGanondrof_Death(BossGanondrof* this, PlayState* play) { bodyDecayLevel = 1; break; } - if (IS_RANDO || IS_BOSS_RUSH) { - break; - } - Math_ApproachS(&this->actor.shape.rot.y, this->work[GND_VARIANCE_TIMER] * -100, 5, 0xBB8); - Math_ApproachF(&this->cameraNextEye.z, this->targetPos.z + 60.0f, 0.02f, 0.5f); - Math_ApproachF(&this->actor.world.pos.y, GND_BOSSROOM_CENTER_Y + 133.0f, 0.05f, 100.0f); - this->actor.world.pos.y += Math_SinS(this->work[GND_VARIANCE_TIMER] * 1500); - this->cameraNextAt.x = this->targetPos.x; - this->cameraNextAt.y = this->targetPos.y - 10.0f; - this->cameraNextAt.z = this->targetPos.z; - if (this->timers[0] == 0) { - this->deathState = DEATH_WARP; - this->timers[0] = 350; - this->timers[1] = 50; - this->fwork[GND_CAMERA_ZOOM] = 300.0f; - this->cameraNextEye.y = GND_BOSSROOM_CENTER_Y + 233.0f; - player->actor.world.pos.x = GND_BOSSROOM_CENTER_X - 200.0f; - player->actor.world.pos.z = GND_BOSSROOM_CENTER_Z; - holdCamera = true; - bodyDecayLevel = 1; + if (GameInteractor_Should(GI_VB_PHANTOM_GANON_DEATH_SCENE, true, NULL)) { + Math_ApproachS(&this->actor.shape.rot.y, this->work[GND_VARIANCE_TIMER] * -100, 5, 0xBB8); + Math_ApproachF(&this->cameraNextEye.z, this->targetPos.z + 60.0f, 0.02f, 0.5f); + Math_ApproachF(&this->actor.world.pos.y, GND_BOSSROOM_CENTER_Y + 133.0f, 0.05f, 100.0f); + this->actor.world.pos.y += Math_SinS(this->work[GND_VARIANCE_TIMER] * 1500); + this->cameraNextAt.x = this->targetPos.x; + this->cameraNextAt.y = this->targetPos.y - 10.0f; + this->cameraNextAt.z = this->targetPos.z; + if (this->timers[0] == 0) { + this->deathState = DEATH_WARP; + this->timers[0] = 350; + this->timers[1] = 50; + this->fwork[GND_CAMERA_ZOOM] = 300.0f; + this->cameraNextEye.y = GND_BOSSROOM_CENTER_Y + 233.0f; + player->actor.world.pos.x = GND_BOSSROOM_CENTER_X - 200.0f; + player->actor.world.pos.z = GND_BOSSROOM_CENTER_Z; + holdCamera = true; + bodyDecayLevel = 1; + } } break; case DEATH_WARP: diff --git a/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.h b/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.h index ff4a841d6..1e45ca766 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.h +++ b/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.h @@ -61,6 +61,23 @@ typedef enum { /* 13 */ GND_FLOAT_COUNT = 13 } BossGanondrofF32Var; +// SOH [Enhancements] Relocated from z_boss_ganondrof.c to use in time saver. +typedef enum { + /* 0 */ NOT_DEAD, + /* 1 */ DEATH_START, + /* 2 */ DEATH_THROES, + /* 3 */ DEATH_WARP, + /* 4 */ DEATH_SCREAM, + /* 5 */ DEATH_DISINTEGRATE, + /* 6 */ DEATH_FINISH +} BossGanondrofDeathState; + +typedef enum { + /* 0 */ DEATH_SPASM, + /* 1 */ DEATH_LIMP, + /* 2 */ DEATH_HUNCHED +} BossGanondrofDeathAction; + typedef struct BossGanondrof { /* 0x0000 */ Actor actor; /* 0x014C */ SkelAnime skelAnime;