From bbe3bb72b6ab53e05a30143638535110f69423b7 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Mon, 2 Dec 2024 18:56:32 -0600 Subject: [PATCH] VBify Ruto (#4602) --- soh/include/z64save.h | 16 ++-- .../MoveJabuJabuElevator.cpp | 27 ++++++ .../SkipChildRutoInteractions.cpp | 93 +++++++++++++++++++ .../Enhancements/TimeSavers/TimeSavers.cpp | 2 + soh/soh/Enhancements/TimeSavers/TimeSavers.h | 2 + .../game-interactor/GameInteractor.h | 13 +++ .../Enhancements/randomizer/hook_handlers.cpp | 6 ++ soh/soh/Enhancements/randomizer/savefile.cpp | 7 +- soh/src/overlays/actors/ovl_En_Ru1/z_en_ru1.c | 38 ++++---- 9 files changed, 175 insertions(+), 29 deletions(-) create mode 100644 soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/MoveJabuJabuElevator.cpp create mode 100644 soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/SkipChildRutoInteractions.cpp diff --git a/soh/include/z64save.h b/soh/include/z64save.h index bf0d20697..b64efb459 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -735,14 +735,14 @@ typedef enum { #define INFTABLE_12A 0x12A #define INFTABLE_138 0x138 #define INFTABLE_139 0x139 -#define INFTABLE_140 0x140 -#define INFTABLE_RUTO_IN_JJ_MEET_RUTO 0x141 -#define INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME 0x142 -#define INFTABLE_143 0x143 -#define INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE 0x144 -#define INFTABLE_145 0x145 -#define INFTABLE_146 0x146 -#define INFTABLE_147 0x147 +#define INFTABLE_140 0x140 // Left her on blue switch in fork room (causes her to spawn in fork room) +#define INFTABLE_RUTO_IN_JJ_MEET_RUTO 0x141 // Jumped down hole from hole room +#define INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME 0x142 // in the basement +#define INFTABLE_143 0x143 // Sat down in basement (causes her to get upset if this is set when actor is spawned) +#define INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE 0x144 // Entered the room with the sapphire +#define INFTABLE_145 0x145 // Thrown to sapphire (not kidnapped yet) +#define INFTABLE_146 0x146 // Kidnapped +#define INFTABLE_147 0x147 // Brought ruto back up to holes room, causes her to spawn in holes room instead of basement #define INFTABLE_160 0x160 #define INFTABLE_161 0x161 #define INFTABLE_162 0x162 diff --git a/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/MoveJabuJabuElevator.cpp b/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/MoveJabuJabuElevator.cpp new file mode 100644 index 000000000..1e1c6ceb5 --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/MoveJabuJabuElevator.cpp @@ -0,0 +1,27 @@ +#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_Bdan_Objects/z_bg_bdan_objects.h" +} + +/** + * Adjusts the behavior of the elevator to start near the bottom if you are entering the room from the bottom + */ +void MoveJabuJabuElevator_Register() { + GameInteractor::Instance->RegisterGameHookForID(ACTOR_BG_BDAN_OBJECTS, [](void* actorRef) { + Player* player = GET_PLAYER(gPlayState); + BgBdanObjects* bgBdanObjects = static_cast(actorRef); + + if (!CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { + return; + } + + if (bgBdanObjects->dyna.actor.params == 1) { + if (player->actor.world.pos.y < -500.0f) { + bgBdanObjects->timer = 220; + } + } + }); +} diff --git a/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/SkipChildRutoInteractions.cpp b/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/SkipChildRutoInteractions.cpp new file mode 100644 index 000000000..aaaf1b39f --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/SkipChildRutoInteractions.cpp @@ -0,0 +1,93 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/OTRGlobals.h" + +extern "C" { +#include "overlays/actors/ovl_En_Ru1/z_en_ru1.h" +#include "assets/objects/object_ru1/object_ru1.h" + +Actor* func_80AEB124(PlayState* play); +} + +void SkipChildRutoInteractions_Register() { + // Skips the Child Ruto introduction cutscene, where she drops down into the hole in Jabu-Jabu's Belly + REGISTER_VB_SHOULD(VB_PLAY_CHILD_RUTO_INTRO, { + EnRu1* enRu1 = va_arg(args, EnRu1*); + + if (!CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { + return; + } + + Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO); + Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME); + Flags_SetInfTable(INFTABLE_143); + enRu1->drawConfig = 1; + enRu1->actor.world.pos.x = 127.0f; + enRu1->actor.world.pos.y = -340.0f; + enRu1->actor.world.pos.z = -3041.0f; + enRu1->actor.shape.rot.y = enRu1->actor.world.rot.y = -5098; + + if (*should) { + Animation_Change(&enRu1->skelAnime, (AnimationHeader*)&gRutoChildTurnAroundAnim, 1.0f, 0, + Animation_GetLastFrame((void*)&gRutoChildTurnAroundAnim), ANIMMODE_ONCE, -8.0f); + enRu1->action = 10; + } + + *should = false; + }); + + // Skips a short dialogue sequence where Ruto tells you to throw her to the Sapphire + REGISTER_VB_SHOULD(VB_RUTO_WANT_TO_BE_TOSSED_TO_SAPPHIRE, { + if (!CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { + return; + } + + if (*should) { + Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE); + *should = false; + } + }); + + // Prevents Ruto from running to the Sapphire when she wants to be tossed to it, instead she just stands up and waits for link to get closer + REGISTER_VB_SHOULD(VB_RUTO_RUN_TO_SAPPHIRE, { + EnRu1* enRu1 = va_arg(args, EnRu1*); + DynaPolyActor* dynaPolyActor = va_arg(args, DynaPolyActor*); + + if (!CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { + return; + } + + if (*should) { + enRu1->unk_28C = (BgBdanObjects*)dynaPolyActor; + Flags_SetInfTable(INFTABLE_145); + Flags_SetSwitch(gPlayState, 0x02); + Flags_SetSwitch(gPlayState, 0x1F); + enRu1->action = 42; + Animation_Change(&enRu1->skelAnime, (AnimationHeader*)&gRutoChildWait2Anim, 1.0f, 0, + Animation_GetLastFrame((void*)&gRutoChildWait2Anim), ANIMMODE_LOOP, -8.0f); + enRu1->unk_28C->cameraSetting = 1; + Actor* sapphire = func_80AEB124(gPlayState); + if (sapphire != NULL) { + Actor_Kill(sapphire); + } + enRu1->actor.room = gPlayState->roomCtx.curRoom.num; + *should = false; + } + }); + + // This overrides the behavior that causes Ruto to get upset at you before sitting back down again when INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME is set + GameInteractor::Instance->RegisterGameHookForID(ACTOR_EN_RU1, [](void* actorRef) { + EnRu1* enRu1 = static_cast(actorRef); + if (!CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { + return; + } + + if (enRu1->action == 22) { + enRu1->action = 27; + enRu1->drawConfig = 1; + enRu1->actor.flags |= ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_FRIENDLY; + Animation_Change(&enRu1->skelAnime, (AnimationHeader*)&gRutoChildSittingAnim, 1.0f, 0.0f, + Animation_GetLastFrame((void*)&gRutoChildSittingAnim), ANIMMODE_LOOP, 0.0f); + } + }); +} diff --git a/soh/soh/Enhancements/TimeSavers/TimeSavers.cpp b/soh/soh/Enhancements/TimeSavers/TimeSavers.cpp index 6814b9332..a198f8289 100644 --- a/soh/soh/Enhancements/TimeSavers/TimeSavers.cpp +++ b/soh/soh/Enhancements/TimeSavers/TimeSavers.cpp @@ -10,7 +10,9 @@ void TimeSavers_Register() { SkipZeldaFleeingCastle_Register(); SkipIntro_Register(); // SkipMiscInteractions + MoveJabuJabuElevator_Register(); MoveMidoInKokiriForest_Register(); + SkipChildRutoInteractions_Register(); FasterHeavyBlockLift_Register(); FasterRupeeAccumulator_Register(); } diff --git a/soh/soh/Enhancements/TimeSavers/TimeSavers.h b/soh/soh/Enhancements/TimeSavers/TimeSavers.h index 0cec3edfa..ad521c6c2 100644 --- a/soh/soh/Enhancements/TimeSavers/TimeSavers.h +++ b/soh/soh/Enhancements/TimeSavers/TimeSavers.h @@ -12,7 +12,9 @@ void TimeSavers_Register(); void SkipZeldaFleeingCastle_Register(); void SkipIntro_Register(); // SkipMiscInteractions + void MoveJabuJabuElevator_Register(); void MoveMidoInKokiriForest_Register(); + void SkipChildRutoInteractions_Register(); void FasterHeavyBlockLift_Register(); void FasterRupeeAccumulator_Register(); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 61cfbe72e..8b52b9b37 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -343,6 +343,19 @@ typedef enum { VB_GIVE_RANDO_FISHING_PRIZE, VB_PLAY_THROW_ANIMATION, VB_INFLICT_VOID_DAMAGE, + // Vanilla condition: Close enough & various cutscene checks + // Opt: *EnRu1 + VB_PLAY_CHILD_RUTO_INTRO, + // Vanilla condition: !INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE && in the big okto room + // Opt: *EnRu1 + VB_RUTO_WANT_TO_BE_TOSSED_TO_SAPPHIRE, + // Vanilla condition: Landed on the platform in the big okto room + // Opt: *EnRu1 + VB_RUTO_RUN_TO_SAPPHIRE, + // Vanilla condition: !Flags_GetInfTable(INFTABLE_145) + // Opt: *EnRu1 + VB_RUTO_BE_CONSIDERED_NOT_KIDNAPPED, + /*** Give Items ***/ diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index 6ff188b97..000081cec 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -1552,6 +1552,12 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l *should = false; break; } + // We need to override the vanilla behavior here because the player might sequence break and get Ruto kidnapped before accessing other + // checks that require Ruto. So if she's kidnapped we allow her to spawn again + case VB_RUTO_BE_CONSIDERED_NOT_KIDNAPPED: { + *should = !Flags_GetInfTable(INFTABLE_145) || Flags_GetInfTable(INFTABLE_146); + break; + } case VB_FREEZE_ON_SKULL_TOKEN: case VB_TRADE_TIMER_ODD_MUSHROOM: case VB_TRADE_TIMER_FROG: diff --git a/soh/soh/Enhancements/randomizer/savefile.cpp b/soh/soh/Enhancements/randomizer/savefile.cpp index 02a31e7b5..0012c8311 100644 --- a/soh/soh/Enhancements/randomizer/savefile.cpp +++ b/soh/soh/Enhancements/randomizer/savefile.cpp @@ -253,10 +253,11 @@ extern "C" void Randomizer_InitSaveFile() { // Flags_SetInfTable(INFTABLE_CHILD_MALON_SAID_EPONA_WAS_AFRAID_OF_YOU); // Flags_SetInfTable(INFTABLE_SPOKE_TO_INGO_ONCE_AS_ADULT); + // Now handled by cutscene skips // Ruto already met in jabu and spawns down the hole immediately - Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO); - Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME); - Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE); + // Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO); + // Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_TALK_FIRST_TIME); + // Flags_SetInfTable(INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE); // Now handled by cutscene skips // Skip cutscenes before Nabooru fight diff --git a/soh/src/overlays/actors/ovl_En_Ru1/z_en_ru1.c b/soh/src/overlays/actors/ovl_En_Ru1/z_en_ru1.c index 9b6e4601e..df5b7e106 100644 --- a/soh/src/overlays/actors/ovl_En_Ru1/z_en_ru1.c +++ b/soh/src/overlays/actors/ovl_En_Ru1/z_en_ru1.c @@ -8,6 +8,7 @@ #include "objects/object_ru1/object_ru1.h" #include "vt.h" #include "soh/ResourceManagerHelpers.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_CAN_PRESS_SWITCH) @@ -763,14 +764,6 @@ void func_80AEC2C0(EnRu1* this, PlayState* play) { func_80AEC070(this, play, something); } -// Convenience function used so that Ruto always spawns in Jabu in rando, even after she's been kidnapped -// Equivalent to !Flags_GetInfTable(INFTABLE_145) in vanilla -bool shouldSpawnRuto() { - // Flags_GetInfTable(INFTABLE_146) check is to prevent Ruto from spawning during the short period of time when - // she's on the Zora's Sapphire pedestal but hasn't been kidnapped yet (would result in multiple Rutos otherwise) - return !Flags_GetInfTable(INFTABLE_145) || (IS_RANDO && (Flags_GetInfTable(INFTABLE_146))); -} - void func_80AEC320(EnRu1* this, PlayState* play) { s8 actorRoom; @@ -778,7 +771,10 @@ void func_80AEC320(EnRu1* this, PlayState* play) { func_80AEB264(this, &gRutoChildWait2Anim, 0, 0, 0); this->action = 7; EnRu1_SetMouthIndex(this, 1); - } else if ((Flags_GetInfTable(INFTABLE_147)) && !Flags_GetInfTable(INFTABLE_140) && shouldSpawnRuto()) { + } else if ( + Flags_GetInfTable(INFTABLE_147) && !Flags_GetInfTable(INFTABLE_140) && + GameInteractor_Should(VB_RUTO_BE_CONSIDERED_NOT_KIDNAPPED, !Flags_GetInfTable(INFTABLE_145), this) + ) { if (!func_80AEB020(this, play)) { func_80AEB264(this, &gRutoChildWait2Anim, 0, 0, 0); actorRoom = this->actor.room; @@ -867,9 +863,9 @@ void func_80AEC780(EnRu1* this, PlayState* play) { s32 pad; Player* player = GET_PLAYER(play); - if ((func_80AEC5FC(this, play)) && (!Play_InCsMode(play)) && + if (GameInteractor_Should(VB_PLAY_CHILD_RUTO_INTRO, (func_80AEC5FC(this, play)) && (!Play_InCsMode(play)) && (!(player->stateFlags1 & (PLAYER_STATE1_HANGING_OFF_LEDGE | PLAYER_STATE1_CLIMBING_LEDGE | PLAYER_STATE1_CLIMBING_LADDER))) && - (player->actor.bgCheckFlags & 1)) { + (player->actor.bgCheckFlags & 1), this)) { play->csCtx.segment = &D_80AF0880; gSaveContext.cutsceneTrigger = 1; @@ -1183,8 +1179,11 @@ void func_80AED414(EnRu1* this, PlayState* play) { void func_80AED44C(EnRu1* this, PlayState* play) { s8 actorRoom; - if ((Flags_GetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO)) && shouldSpawnRuto() && !Flags_GetInfTable(INFTABLE_140) && - !Flags_GetInfTable(INFTABLE_147)) { + if ( + Flags_GetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO) && + GameInteractor_Should(VB_RUTO_BE_CONSIDERED_NOT_KIDNAPPED, !Flags_GetInfTable(INFTABLE_145), this) && + !Flags_GetInfTable(INFTABLE_140) && !Flags_GetInfTable(INFTABLE_147) + ) { if (!func_80AEB020(this, play)) { func_80AEB264(this, &gRutoChildWait2Anim, 0, 0, 0); actorRoom = this->actor.room; @@ -1550,8 +1549,8 @@ s32 func_80AEE394(EnRu1* this, PlayState* play) { colCtx = &play->colCtx; floorBgId = this->actor.floorBgId; // necessary match, can't move this out of this block unfortunately dynaPolyActor = DynaPoly_GetActor(colCtx, floorBgId); - if (dynaPolyActor != NULL && dynaPolyActor->actor.id == ACTOR_BG_BDAN_OBJECTS && - dynaPolyActor->actor.params == 0 && !Player_InCsMode(play) && play->msgCtx.msgLength == 0) { + if (GameInteractor_Should(VB_RUTO_RUN_TO_SAPPHIRE, dynaPolyActor != NULL && dynaPolyActor->actor.id == ACTOR_BG_BDAN_OBJECTS && + dynaPolyActor->actor.params == 0 && !Player_InCsMode(play) && play->msgCtx.msgLength == 0, this, dynaPolyActor)) { func_80AEE02C(this); play->csCtx.segment = &D_80AF10A4; gSaveContext.cutsceneTrigger = 1; @@ -1611,7 +1610,7 @@ s32 func_80AEE6D0(EnRu1* this, PlayState* play) { s32 pad; s8 curRoomNum = play->roomCtx.curRoom.num; - if (!Flags_GetInfTable(INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE) && (func_80AEB124(play) != 0)) { + if (GameInteractor_Should(VB_RUTO_WANT_TO_BE_TOSSED_TO_SAPPHIRE, !Flags_GetInfTable(INFTABLE_RUTO_IN_JJ_WANTS_TO_BE_TOSSED_TO_SAPPHIRE) && (func_80AEB124(play) != 0), this)) { if (!Player_InCsMode(play)) { Animation_Change(&this->skelAnime, &gRutoChildSeesSapphireAnim, 1.0f, 0, Animation_GetLastFrame(&gRutoChildSquirmAnim), ANIMMODE_LOOP, -8.0f); @@ -2190,8 +2189,11 @@ void func_80AEFF40(EnRu1* this, PlayState* play) { void func_80AEFF94(EnRu1* this, PlayState* play) { s8 actorRoom; - if ((Flags_GetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO)) && (Flags_GetInfTable(INFTABLE_140)) && shouldSpawnRuto() && - (!(func_80AEB020(this, play)))) { + if ( + Flags_GetInfTable(INFTABLE_RUTO_IN_JJ_MEET_RUTO) && Flags_GetInfTable(INFTABLE_140) && + GameInteractor_Should(VB_RUTO_BE_CONSIDERED_NOT_KIDNAPPED, !Flags_GetInfTable(INFTABLE_145), this) && + (!(func_80AEB020(this, play))) + ) { func_80AEB264(this, &gRutoChildWait2Anim, 0, 0, 0); actorRoom = this->actor.room; this->action = 22;