Shipwright/soh/soh/Enhancements/timesaver_hook_handlers.cpp
2024-04-21 21:09:52 -05:00

1159 lines
57 KiB
C++

#include <libultraship/bridge.h>
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/randomizer/randomizerTypes.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.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_Bg_Treemouth/z_bg_treemouth.h"
#include "src/overlays/actors/ovl_En_Owl/z_en_owl.h"
#include "src/overlays/actors/ovl_En_Ko/z_en_ko.h"
#include "src/overlays/actors/ovl_En_Ma1/z_en_ma1.h"
#include "src/overlays/actors/ovl_En_Zl4/z_en_zl4.h"
#include "src/overlays/actors/ovl_Demo_Im/z_demo_im.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_Bg_Spot02_Objects/z_bg_spot02_objects.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"
extern SaveContext gSaveContext;
extern PlayState* gPlayState;
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(GI_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);
func_80078884(NA_SE_SY_CORRECT_CHIME);
enMa1->actor.flags &= ~ACTOR_FLAG_WILL_TALK;
play->msgCtx.ocarinaMode = OCARINA_MODE_04;
enMa1->actionFunc = func_80AA0D88;
enMa1->unk_1E0 = 1;
enMa1->interactInfo.talkState = NPC_TALK_STATE_IDLE;
return;
}
}
void EnFu_EndTeachSong(EnFu* enFu, PlayState* play) {
if (Message_GetState(&gPlayState->msgCtx) == TEXT_STATE_CLOSING) {
func_80078884(NA_SE_SY_CORRECT_CHIME);
enFu->actionFunc = EnFu_WaitAdult;
enFu->actor.flags &= ~ACTOR_FLAG_WILL_TALK;
play->msgCtx.ocarinaMode = OCARINA_MODE_04;
Flags_SetEventChkInf(EVENTCHKINF_PLAYED_SONG_OF_STORMS_IN_WINDMILL);
Flags_SetEventChkInf(EVENTCHKINF_LEARNED_SONG_OF_STORMS);
return;
}
}
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
if(!(IS_RANDO || CVarGetInteger("gTimeSavers.SkipMiscInteractions", IS_RANDO))) {
EnDntDemo_Judge(enDntDemo, play);
return;
}
if (enDntDemo->actor.xzDistToPlayer > 30.0f) {
EnDntDemo_Judge(enDntDemo, play);
return;
}
Player* player = GET_PLAYER(play);
switch (Player_GetMask(play)) {
case PLAYER_MASK_SKULL: {
Flags_SetItemGetInf(ITEMGETINF_OBTAINED_STICK_UPGRADE_FROM_STAGE);
return;
}
case PLAYER_MASK_TRUTH: {
Flags_SetItemGetInf(ITEMGETINF_OBTAINED_NUT_UPGRADE_FROM_STAGE);
return;
}
default: {
EnDntDemo_Judge(enDntDemo, play);
return;
}
}
}
static int successChimeCooldown = 0;
void RateLimitedSuccessChime() {
if (successChimeCooldown == 0) {
// Currently disabled, need to find a better way to do this, while being consistent with vanilla
// func_80078884(NA_SE_SY_CORRECT_CHIME);
successChimeCooldown = 120;
}
}
void TimeSaverOnGameFrameUpdateHandler() {
if (successChimeCooldown > 0) {
successChimeCooldown--;
}
}
void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, void* opt) {
switch (id) {
case GI_VB_PLAY_TRANSITION_CS: {
if (CVarGetInteger("gTimeSavers.SkipCutscene.Intro", IS_RANDO) && gSaveContext.entranceIndex == ENTR_LINKS_HOUSE_0 && gSaveContext.cutsceneIndex == 0xFFF1) {
gSaveContext.cutsceneIndex = 0;
*should = false;
}
if (CVarGetInteger("gTimeSavers.SkipCutscene.LearnSong", IS_RANDO) || IS_RANDO) {
// Song of Time
if (gSaveContext.entranceIndex == ENTR_TEMPLE_OF_TIME_0 && 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_1) && !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(GI_VB_GIVE_ITEM_REQUIEM_OF_SPIRIT, true, NULL)) {
Item_Give(gPlayState, ITEM_SONG_REQUIEM);
}
*should = false;
}
u8 meetsBurningKakRequirements =
LINK_IS_ADULT &&
gSaveContext.entranceIndex == ENTR_KAKARIKO_VILLAGE_0 &&
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(GI_VB_BE_ELIGIBLE_FOR_NOCTURNE_OF_SHADOW, meetsBurningKakRequirements, NULL)) {
Flags_SetEventChkInf(EVENTCHKINF_BONGO_BONGO_ESCAPED_FROM_WELL);
// Normally happens in the cutscene
Flags_SetEventChkInf(EVENTCHKINF_LEARNED_NOCTURNE_OF_SHADOW);
if (GameInteractor_Should(GI_VB_GIVE_ITEM_NOCTURNE_OF_SHADOW, true, NULL)) {
Item_Give(gPlayState, ITEM_SONG_NOCTURNE);
}
*should = false;
}
}
if (CVarGetInteger("gTimeSavers.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(GI_VB_GIVE_ITEM_FAIRY_OCARINA, true, NULL)) {
Item_Give(gPlayState, ITEM_OCARINA_FAIRY);
}
*should = false;
}
// LACS
u8 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(GI_VB_BE_ELIGIBLE_FOR_LIGHT_ARROWS, meetsLACSRequirements, NULL)) {
Flags_SetEventChkInf(EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS);
if (GameInteractor_Should(GI_VB_GIVE_ITEM_LIGHT_ARROW, true, NULL)) {
Item_Give(gPlayState, ITEM_ARROW_LIGHT);
}
*should = false;
}
}
if (gSaveContext.entranceIndex == ENTR_GANONS_TOWER_COLLAPSE_EXTERIOR_0) {
if (CVarGetInteger("gTimeSavers.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("gTimeSavers.SkipChildStealth", false)) {
gSaveContext.entranceIndex = ENTR_CASTLE_COURTYARD_ZELDA_0;
*should = false;
}
}
break;
}
case GI_VB_PLAY_ENTRANCE_CS: {
s32* entranceFlag = static_cast<s32*>(opt);
if (CVarGetInteger("gTimeSavers.SkipCutscene.Entrances", IS_RANDO) && (*entranceFlag != EVENTCHKINF_EPONA_OBTAINED)) {
*should = false;
}
break;
}
case GI_VB_PLAY_ONEPOINT_CS: {
if (CVarGetInteger("gTimeSavers.SkipCutscene.OnePoint", IS_RANDO)) {
s16* csId = static_cast<s16*>(opt);
switch (*csId) {
case 4180:
case 4100:
*should = false;
RateLimitedSuccessChime();
break;
default:
SPDLOG_INFO("GI_VB_PLAY_ONEPOINT_CS {}", *csId);
break;
}
}
break;
}
case GI_VB_PLAY_ONEPOINT_ACTOR_CS: {
if (CVarGetInteger("gTimeSavers.SkipCutscene.OnePoint", IS_RANDO)) {
Actor* actor = static_cast<Actor*>(opt);
// 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 = static_cast<BgDdanKd*>(opt);
Flags_SetSwitch(gPlayState, ddanKd->dyna.actor.params);
}
if (actor->id == ACTOR_BG_MORI_HINERI) {
break;
}
RateLimitedSuccessChime();
*should = false;
break;
}
switch (actor->id) {
case ACTOR_OBJ_SWITCH: {
ObjSwitch *switchActor = static_cast<ObjSwitch*>(opt);
switchActor->cooldownTimer = 0;
*should = false;
RateLimitedSuccessChime();
break;
}
case ACTOR_BG_BDAN_SWITCH: {
BgBdanSwitch* switchActor = static_cast<BgBdanSwitch*>(opt);
switchActor->unk_1D8 = 0;
switchActor->unk_1DA = 0;
*should = false;
RateLimitedSuccessChime();
break;
}
case ACTOR_BG_HIDAN_KOUSI: {
BgHidanKousi* switchActor = static_cast<BgHidanKousi*>(opt);
BgHidanKousi_SetupAction(switchActor, func_80889C18);
*should = false;
RateLimitedSuccessChime();
break;
}
case ACTOR_BG_HIDAN_FWBIG: {
*should = false;
break;
}
case ACTOR_EN_EX_ITEM: {
*should = false;
break;
}
case ACTOR_EN_DNT_NOMAL: {
*should = false;
break;
}
case ACTOR_EN_DNT_DEMO: {
*should = false;
break;
}
case ACTOR_EN_TA:
case ACTOR_DOOR_SHUTTER:
case ACTOR_BG_ICE_SHUTTER:
case ACTOR_OBJ_LIGHTSWITCH:
case ACTOR_EN_BOX:
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_SPOT18_BASKET:
case ACTOR_BG_HIDAN_CURTAIN:
*should = false;
RateLimitedSuccessChime();
break;
}
if (*should) {
SPDLOG_INFO("GI_VB_PLAY_ONEPOINT_ACTOR_CS ID:{} Cat:{}", actor->id, actor->category);
}
}
break;
}
case GI_VB_SHOW_TITLE_CARD:
if (CVarGetInteger("gTimeSavers.DisableTitleCard", IS_RANDO)) {
*should = false;
}
break;
case GI_VB_WONDER_TALK: {
if (CVarGetInteger("gTimeSavers.NoForcedDialog", IS_RANDO)) {
*should = false;
}
break;
}
case GI_VB_NAVI_TALK: {
if (CVarGetInteger("gTimeSavers.NoForcedDialog", IS_RANDO)) {
ElfMsg* naviTalk = static_cast<ElfMsg*>(opt);
Flags_SetSwitch(gPlayState, (naviTalk->actor.params >> 8) & 0x3F);
Actor_Kill(&naviTalk->actor);
*should = false;
}
break;
}
case GI_VB_NOT_BE_GREETED_BY_SARIA:
if (CVarGetInteger("gTimeSavers.SkipCutscene.Entrances", IS_RANDO) && !Flags_GetInfTable(INFTABLE_GREETED_BY_SARIA)) {
Flags_SetInfTable(INFTABLE_GREETED_BY_SARIA);
*should = true;
}
break;
case GI_VB_MOVE_MIDO_IN_KOKIRI_FOREST:
if (
CVarGetInteger("gTimeSavers.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 GI_VB_PLAY_DEKU_TREE_INTRO_CS: {
if (CVarGetInteger("gTimeSavers.SkipCutscene.Story", IS_RANDO)) {
BgTreemouth* treeMouth = static_cast<BgTreemouth*>(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 GI_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("gTimeSavers.SkipCutscene.Story", IS_RANDO)) {
*should = Flags_GetEventChkInf(EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP);
}
break;
}
case GI_VB_GIVE_ITEM_FROM_BLUE_WARP:
case GI_VB_PLAY_SHIEK_BLOCK_MASTER_SWORD_CS:
case GI_VB_GIVE_ITEM_FAIRY_OCARINA:
case GI_VB_GIVE_ITEM_LIGHT_ARROW:
if (CVarGetInteger("gTimeSavers.SkipCutscene.Story", IS_RANDO)) {
*should = false;
}
break;
case GI_VB_PLAY_NABOORU_CAPTURED_CS:
if (*should == true && CVarGetInteger("gTimeSavers.SkipCutscene.Story", IS_RANDO)) {
Flags_SetEventChkInf(EVENTCHKINF_NABOORU_CAPTURED_BY_TWINROVA);
*should = false;
}
break;
case GI_VB_PLAY_PULL_MASTER_SWORD_CS:
if (CVarGetInteger("gTimeSavers.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(GI_VB_GIVE_ITEM_LIGHT_MEDALLION, true, NULL)) {
Item_Give(gPlayState, ITEM_MEDALLION_LIGHT);
}
}
*should = false;
}
break;
case GI_VB_OWL_INTERACTION: {
if (CVarGetInteger("gTimeSavers.SkipOwlInteractions", IS_RANDO) && *should) {
EnOwl* enOwl = static_cast<EnOwl*>(opt);
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 GI_VB_OVERRIDE_LINK_THE_GORON_DIALOGUE: {
if (CVarGetInteger("gTimeSavers.SkipMiscInteractions", IS_RANDO)) {
u16* textId = static_cast<u16*>(opt);
// If the doors are not open yet, prioritize opening them
if (!Flags_GetInfTable(INFTABLE_GORON_CITY_DOORS_UNLOCKED)) {
*textId = 0x3036;
*should = true;
}
}
break;
}
case GI_VB_PLAY_EYEDROP_CREATION_ANIM:
case GI_VB_PLAY_EYEDROPS_CS:
case GI_VB_PLAY_DROP_FISH_FOR_JABU_CS:
case GI_VB_PLAY_DARUNIAS_JOY_CS:
if (CVarGetInteger("gTimeSavers.SkipMiscInteractions", IS_RANDO)) {
*should = false;
}
break;
case GI_VB_PLAY_ZELDAS_LULLABY_CS: {
if (CVarGetInteger("gTimeSavers.SkipCutscene.LearnSong", IS_RANDO) || IS_RANDO) {
DemoIm* demoIm = static_cast<DemoIm*>(opt);
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<GameInteractor::OnActorUpdate>([](void* actorRef) mutable {
Actor* actor = static_cast<Actor*>(actorRef);
if (actor->id == ACTOR_DEMO_IM && (CVarGetInteger("gTimeSavers.SkipCutscene.LearnSong", IS_RANDO) || IS_RANDO)) {
DemoIm* demoIm = static_cast<DemoIm*>(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, &D_801333E0, &D_801333E0, &D_801333E8);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(demoImUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(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_HYRULE_CASTLE_0;
} 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<GameInteractor::OnSceneInit>([](int16_t sceneNum) mutable {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(demoImUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(demoImKillHook);
demoImUpdateHook = 0;
demoImKillHook = 0;
});
*should = false;
}
break;
}
case GI_VB_PLAY_SARIAS_SONG_CS: {
if (CVarGetInteger("gTimeSavers.SkipCutscene.LearnSong", IS_RANDO) || IS_RANDO) {
EnSa* enSa = static_cast<EnSa*>(opt);
enSa->actionFunc = func_80AF6B20;
*should = false;
}
break;
}
case GI_VB_DESPAWN_HORSE_RACE_COW: {
if (Flags_GetEventChkInf(EVENTCHKINF_WON_COW_IN_MALONS_RACE) && CVarGetInteger("gCowOfTime", 0)) {
*should = false;
}
break;
}
case GI_VB_PLAY_DRAIN_WELL_CS: {
if (CVarGetInteger("gTimeSavers.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 GI_VB_PLAY_SUNS_SONG_CS:
if (CVarGetInteger("gTimeSavers.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 GI_VB_PLAY_ROYAL_FAMILY_TOMB_CS: {
if (CVarGetInteger("gTimeSavers.SkipMiscInteractions", IS_RANDO)) {
*should = false;
}
break;
}
case GI_VB_PLAY_ROYAL_FAMILY_TOMB_EXPLODE: {
if (CVarGetInteger("gTimeSavers.SkipMiscInteractions", IS_RANDO)) {
*should = Flags_GetEventChkInf(EVENTCHKINF_DESTROYED_ROYAL_FAMILY_TOMB);
}
break;
}
case GI_VB_PLAY_DOOR_OF_TIME_CS: {
if (CVarGetInteger("gTimeSavers.SkipMiscInteractions", IS_RANDO)) {
*should = false;
Flags_SetEnv(gPlayState, 2);
func_80078884(NA_SE_SY_CORRECT_CHIME);
}
break;
}
case GI_VB_GIVE_ITEM_MINUET_OF_FOREST:
case GI_VB_GIVE_ITEM_BOLERO_OF_FIRE:
case GI_VB_GIVE_ITEM_SERENADE_OF_WATER:
case GI_VB_GIVE_ITEM_REQUIEM_OF_SPIRIT:
case GI_VB_GIVE_ITEM_NOCTURNE_OF_SHADOW:
case GI_VB_GIVE_ITEM_PRELUDE_OF_LIGHT:
case GI_VB_GIVE_ITEM_ZELDAS_LULLABY:
case GI_VB_GIVE_ITEM_EPONAS_SONG:
case GI_VB_GIVE_ITEM_SARIAS_SONG:
case GI_VB_GIVE_ITEM_SUNS_SONG:
case GI_VB_GIVE_ITEM_SONG_OF_TIME:
case GI_VB_GIVE_ITEM_SONG_OF_STORMS:
case GI_VB_PLAY_MINUET_OF_FOREST_CS:
case GI_VB_PLAY_BOLERO_OF_FIRE_CS:
case GI_VB_PLAY_SERENADE_OF_WATER_CS:
case GI_VB_PLAY_PRELUDE_OF_LIGHT_CS:
if (CVarGetInteger("gTimeSavers.SkipCutscene.LearnSong", IS_RANDO) || IS_RANDO) {
*should = false;
}
break;
case GI_VB_DAMPE_IN_GRAVEYARD_DESPAWN:
if (CVarGetInteger("gDampeAllNight", 0)) {
*should = LINK_IS_ADULT || gPlayState->sceneNum != SCENE_GRAVEYARD;
}
break;
case GI_VB_BE_VALID_GRAVEDIGGING_SPOT:
if (CVarGetInteger("gDampeWin", 0)) {
EnTk *enTk = static_cast<EnTk*>(opt);
enTk->validDigHere = true;
*should = true;
}
break;
case GI_VB_BE_DAMPE_GRAVEDIGGING_GRAND_PRIZE:
if (CVarGetInteger("gDampeWin", 0)) {
EnTk *enTk = static_cast<EnTk*>(opt);
enTk->currentReward = 3;
*should = true;
}
break;
case GI_VB_DAMPE_GRAVEDIGGING_GRAND_PRIZE_BE_HEART_PIECE:
if (CVarGetInteger("gGravediggingTourFix", 0) || IS_RANDO) {
*should = !Flags_GetCollectible(gPlayState, COLLECTFLAG_GRAVEDIGGING_HEART_PIECE);
}
break;
case GI_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("gFixSawSoftlock", 0) != 0 || CVarGetInteger("gSkipText", 0) != 0) ? gPlayState->animationCtx.animationCount > 1 : *should;
break;
case GI_VB_BIGGORON_CONSIDER_SWORD_FORGED:
*should = Environment_GetBgsDayCount() >= CVarGetInteger("gForgeTime", 3);
break;
case GI_VB_BE_ELIGIBLE_FOR_GREAT_FAIRY_REWARD: {
BgDyYoseizo* bgDyYoseizo = static_cast<BgDyYoseizo*>(opt);
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("gTimeSavers.SkipMiscInteractions", IS_RANDO))) {
if (IS_RANDO || *should) {
Flags_SetRandomizerInf(flag);
gSaveContext.healthAccumulator = 0x140;
Magic_Fill(gPlayState);
}
*should = false;
}
break;
}
case GI_VB_PLAY_RAINBOW_BRIDGE_CS: {
if (CVarGetInteger("gTimeSavers.SkipCutscene.Story", IS_RANDO)) {
*should = false;
func_800F595C(NA_BGM_BRIDGE_TO_GANONS);
}
break;
}
}
}
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;
static uint32_t enFuKillHook = 0;
static uint32_t bgSpot02UpdateHook = 0;
static uint32_t bgSpot02KillHook = 0;
static uint32_t enPoSistersUpdateHook = 0;
static uint32_t enPoSistersKillHook = 0;
void TimeSaverOnActorInitHandler(void* actorRef) {
Actor* actor = static_cast<Actor*>(actorRef);
if (actor->id == ACTOR_EN_KO && (actor->params & 0xFF) == ENKO_TYPE_CHILD_3) {
enKoUpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* innerActorRef) mutable {
Actor* innerActor = static_cast<Actor*>(innerActorRef);
if (innerActor->id == ACTOR_EN_KO && (innerActor->params & 0xFF) == ENKO_TYPE_CHILD_3 && (CVarGetInteger("gTimeSavers.SkipCutscene.Story", IS_RANDO) || IS_RANDO)) {
EnKo* enKo = static_cast<EnKo*>(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<GameInteractor::OnActorUpdate>(enKoUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(enKoKillHook);
enKoUpdateHook = 0;
enKoKillHook = 0;
// They have already moved
} else if (enKo->actionFunc == func_80A99384) {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(enKoUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(enKoKillHook);
enKoUpdateHook = 0;
enKoKillHook = 0;
}
}
});
enKoKillHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int16_t sceneNum) mutable {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(enKoUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(enKoKillHook);
enKoUpdateHook = 0;
enKoKillHook = 0;
});
}
if (actor->id == ACTOR_ITEM_OCARINA && actor->params == 3 && CVarGetInteger("gTimeSavers.SkipCutscene.Story", IS_RANDO)) {
itemOcarinaframesSinceSpawn = 0;
itemOcarinaUpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* innerActorRef) mutable {
Actor* innerActor = static_cast<Actor*>(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<GameInteractor::OnActorUpdate>(itemOcarinaUpdateHook);
itemOcarinaUpdateHook = 0;
}
});
}
if (actor->id == ACTOR_EN_MA1 && gPlayState->sceneNum == SCENE_LON_LON_RANCH) {
enMa1UpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* innerActorRef) mutable {
Actor* innerActor = static_cast<Actor*>(innerActorRef);
if (innerActor->id == ACTOR_EN_MA1 && (CVarGetInteger("gTimeSavers.SkipCutscene.LearnSong", IS_RANDO) || IS_RANDO)) {
EnMa1* enMa1 = static_cast<EnMa1*>(innerActorRef);
if (enMa1->actionFunc == func_80AA106C) {
enMa1->actionFunc = EnMa1_EndTeachSong;
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(enMa1UpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(enMa1KillHook);
enMa1UpdateHook = 0;
enMa1KillHook = 0;
// They've already learned the song
} else if (enMa1->actionFunc == func_80AA0D88) {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(enMa1UpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(enMa1KillHook);
enMa1UpdateHook = 0;
enMa1KillHook = 0;
}
}
});
enMa1KillHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int16_t sceneNum) mutable {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(enMa1UpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(enMa1KillHook);
enMa1UpdateHook = 0;
enMa1KillHook = 0;
});
}
if (actor->id == ACTOR_EN_FU) {
enFuUpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* innerActorRef) mutable {
Actor* innerActor = static_cast<Actor*>(innerActorRef);
if (innerActor->id == ACTOR_EN_FU && (CVarGetInteger("gTimeSavers.SkipCutscene.LearnSong", IS_RANDO) || IS_RANDO)) {
EnFu* enFu = static_cast<EnFu*>(innerActorRef);
if (enFu->actionFunc == EnFu_TeachSong) {
enFu->actionFunc = EnFu_EndTeachSong;
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(enFuUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(enFuKillHook);
enFuUpdateHook = 0;
enFuKillHook = 0;
}
}
});
enFuKillHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int16_t sceneNum) mutable {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(enFuUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(enFuKillHook);
enFuUpdateHook = 0;
enFuKillHook = 0;
});
}
if (actor->id == ACTOR_BG_SPOT02_OBJECTS && actor->params == 2) {
bgSpot02UpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* innerActorRef) mutable {
Actor* innerActor = static_cast<Actor*>(innerActorRef);
if (innerActor->id == ACTOR_BG_SPOT02_OBJECTS && innerActor->params == 2 && (CVarGetInteger("gTimeSavers.SkipMiscInteractions", IS_RANDO))) {
BgSpot02Objects* bgSpot02 = static_cast<BgSpot02Objects*>(innerActorRef);
if (bgSpot02->actionFunc == func_808ACC34) {
bgSpot02->actionFunc = func_808AC908;
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(bgSpot02UpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(bgSpot02KillHook);
bgSpot02UpdateHook = 0;
bgSpot02KillHook = 0;
}
}
});
bgSpot02KillHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int16_t sceneNum) mutable {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(bgSpot02UpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(bgSpot02KillHook);
bgSpot02UpdateHook = 0;
bgSpot02KillHook = 0;
});
}
if (actor->id == ACTOR_EN_ZL4 && CVarGetInteger("gTimeSavers.SkipCutscene.Story", IS_RANDO)) {
EnZl4* enZl4 = static_cast<EnZl4*>(actorRef);
if (enZl4->actionFunc != EnZl4_Cutscene || enZl4->csState != 0) return;
enZl4->actionFunc = EnZl4_SkipToGivingZeldasLetter;
}
if (actor->id == ACTOR_EN_DNT_DEMO && (IS_RANDO || CVarGetInteger("gTimeSavers.SkipMiscInteractions", IS_RANDO))) {
EnDntDemo* enDntDemo = static_cast<EnDntDemo*>(actorRef);
enDntDemo->actionFunc = EnDntDemo_JudgeSkipToReward;
}
// Forest Temple entrance cutscene
if (actor->id == ACTOR_EN_PO_SISTERS && actor->params == 4124) {
if (CVarGetInteger("gTimeSavers.SkipCutscene.GlitchAiding", 0)) {
Flags_SetSwitch(gPlayState, 0x1B);
Actor_Kill(actor);
}
}
// Forest Temple purple poe fight speedup
if (actor->id == ACTOR_EN_PO_SISTERS && actor->params == 28) {
enPoSistersUpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* innerActorRef) mutable {
Actor* innerActor = static_cast<Actor*>(innerActorRef);
if (innerActor->id == ACTOR_EN_PO_SISTERS && innerActor->params == 28 && (CVarGetInteger("gTimeSavers.SkipMiscInteractions", IS_RANDO))) {
EnPoSisters* enPoSisters = static_cast<EnPoSisters*>(innerActorRef);
if (enPoSisters->actionFunc == func_80ADB338) {
enPoSisters->unk_19C = 0;
}
}
});
enPoSistersKillHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int16_t sceneNum) mutable {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(enPoSistersUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(enPoSistersKillHook);
enPoSistersUpdateHook = 0;
enPoSistersKillHook = 0;
});
}
// Fire Temple Darunia cutscene
if (actor->id == ACTOR_EN_DU && gPlayState->sceneNum == SCENE_FIRE_TEMPLE) {
if (CVarGetInteger("gTimeSavers.SkipCutscene.GlitchAiding", 0)) {
Flags_SetInfTable(INFTABLE_SPOKE_TO_DARUNIA_IN_FIRE_TEMPLE);
Actor_Kill(actor);
}
}
}
void TimeSaverOnSceneInitHandler(int16_t sceneNum) {
switch (sceneNum) {
case SCENE_HYRULE_CASTLE:
if (CVarGetInteger("gTimeSavers.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("gTimeSavers.SkipMiscInteractions", IS_RANDO) && GameInteractor_Should(GI_VB_MALON_RETURN_FROM_CASTLE, Flags_GetEventChkInf(EVENTCHKINF_TALON_RETURNED_FROM_CASTLE), NULL)) {
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("gTimeSavers.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("gTimeSavers.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("gTimeSavers.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("gTimeSavers.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("gTimeSavers.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("gTimeSavers.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("gTimeSavers.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("gTimeSavers.SkipCutscene.BossIntro", IS_RANDO)) {
if (!Flags_GetEventChkInf(EVENTCHKINF_BEGAN_BONGO_BONGO_BATTLE)) {
Flags_SetEventChkInf(EVENTCHKINF_BEGAN_BONGO_BONGO_BATTLE);
}
}
break;
}
}
static GetItemEntry vanillaQueuedItemEntry = GET_ITEM_NONE;
void TimeSaverOnFlagSetHandler(int16_t flagType, int16_t flag) {
if (!CVarGetInteger("gTimeSavers.SkipCutscene.Story", IS_RANDO)) return;
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_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_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;
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;
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;
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:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_DEKU_STICK_CAPACITY_30).GetGIEntry_Copy();
break;
case ITEMGETINF_OBTAINED_NUT_UPGRADE_FROM_STAGE:
vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_DEKU_NUT_CAPACITY_40).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_ITEM_OVER_HEAD) {
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_ActionChange_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 uint32_t onSceneInitHook = 0;
static uint32_t onVanillaBehaviorHook = 0;
static uint32_t onActorInitHook = 0;
static uint32_t onGameFrameUpdate = 0;
static uint32_t onFlagSetHook = 0;
static uint32_t onPlayerUpdateHook = 0;
static uint32_t onItemReceiveHook = 0;
void TimeSaverRegisterHooks() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>([](int32_t fileNum) mutable {
vanillaQueuedItemEntry = GET_ITEM_NONE;
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(onSceneInitHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnVanillaBehavior>(onVanillaBehaviorHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorInit>(onActorInitHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnGameFrameUpdate>(onGameFrameUpdate);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnFlagSet>(onFlagSetHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnPlayerUpdate>(onPlayerUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnItemReceive>(onItemReceiveHook);
onSceneInitHook = 0;
onVanillaBehaviorHook = 0;
onActorInitHook = 0;
onGameFrameUpdate = 0;
onFlagSetHook = 0;
onPlayerUpdateHook = 0;
onItemReceiveHook = 0;
onSceneInitHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>(TimeSaverOnSceneInitHandler);
onVanillaBehaviorHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnVanillaBehavior>(TimeSaverOnVanillaBehaviorHandler);
onActorInitHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>(TimeSaverOnActorInitHandler);
onGameFrameUpdate = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>(TimeSaverOnGameFrameUpdateHandler);
if (IS_RANDO) return;
onFlagSetHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFlagSet>(TimeSaverOnFlagSetHandler);
onPlayerUpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>(TimeSaverOnPlayerUpdateHandler);
onItemReceiveHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>(TimeSaverOnItemReceiveHandler);
});
}