mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-12-22 16:18:50 -05:00
Split a few VB changes out into their own files (#4123)
This commit is contained in:
parent
118cd044b5
commit
6f7173a5c4
21
soh/soh/Enhancements/TimeSavers/SkipCutscene/SkipIntro.cpp
Normal file
21
soh/soh/Enhancements/TimeSavers/SkipCutscene/SkipIntro.cpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
@ -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<EnKo*>(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<GameInteractor::OnActorUpdate>(ACTOR_EN_KO, SkipBlueWarp_OnActorUpdate);
|
||||
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::OnVanillaBehavior>(VB_PLAY_TRANSITION_CS, SkipBlueWarp_ShouldPlayTransitionCS);
|
||||
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::OnVanillaBehavior>(VB_DEKU_JR_CONSIDER_FOREST_TEMPLE_FINISHED, SkipBlueWarp_ShouldDekuJrConsiderForestTempleFinished);
|
||||
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::OnVanillaBehavior>(VB_GIVE_ITEM_FROM_BLUE_WARP, SkipBlueWarp_ShouldGiveItem);
|
||||
}
|
@ -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<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;
|
||||
}
|
||||
});
|
||||
}
|
@ -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
|
||||
}
|
@ -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<EnZl4*>(actorPtr);
|
||||
if (enZl4->actionFunc != EnZl4_Cutscene || enZl4->csState != 0) return;
|
||||
|
||||
enZl4->actionFunc = EnZl4_SkipToGivingZeldasLetter;
|
||||
}
|
||||
}
|
||||
|
||||
void SkipToGivingZeldasLetter_Register() {
|
||||
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::OnActorInit>(ACTOR_EN_ZL4, SkipToGivingZeldasLetter_OnActorInit);
|
||||
}
|
@ -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<Actor*>(actorPtr);
|
||||
|
||||
framesSinceSpawn++;
|
||||
if (framesSinceSpawn > 20) {
|
||||
Audio_PlayActorSound2(actor, NA_SE_EV_BOMB_DROP_WATER);
|
||||
|
||||
GameInteractor::Instance->UnregisterGameHookForPtr<GameInteractor::OnActorUpdate>(itemOcarinaUpdateHook);
|
||||
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(sceneInitHook);
|
||||
itemOcarinaUpdateHook = 0;
|
||||
sceneInitHook = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SkipZeldaFleeingCastle_OnActorInit(void* actorPtr) {
|
||||
Actor* actor = static_cast<Actor*>(actorPtr);
|
||||
|
||||
if (
|
||||
actor->params == 3 &&
|
||||
CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)
|
||||
) {
|
||||
framesSinceSpawn = 0;
|
||||
itemOcarinaUpdateHook = GameInteractor::Instance->RegisterGameHookForPtr<GameInteractor::OnActorUpdate>((uintptr_t)actorPtr, SkipZeldaFleeingCastle_OnActorUpdate);
|
||||
sceneInitHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([] (int16_t sceneNum) {
|
||||
GameInteractor::Instance->UnregisterGameHookForPtr<GameInteractor::OnActorUpdate>(itemOcarinaUpdateHook);
|
||||
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneInit>(sceneInitHook);
|
||||
itemOcarinaUpdateHook = 0;
|
||||
sceneInitHook = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void SkipZeldaFleeingCastle_Register() {
|
||||
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::OnActorInit>(ACTOR_ITEM_OCARINA, SkipZeldaFleeingCastle_OnActorInit);
|
||||
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::OnVanillaBehavior>(VB_PLAY_TRANSITION_CS, SkipZeldaFleeingCastle_ShouldPlayTransitionCS);
|
||||
}
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
14
soh/soh/Enhancements/TimeSavers/TimeSavers.cpp
Normal file
14
soh/soh/Enhancements/TimeSavers/TimeSavers.cpp
Normal file
@ -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();
|
||||
}
|
17
soh/soh/Enhancements/TimeSavers/TimeSavers.h
Normal file
17
soh/soh/Enhancements/TimeSavers/TimeSavers.h
Normal file
@ -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
|
@ -440,7 +440,7 @@ typedef uint32_t HOOK_ID;
|
||||
}
|
||||
|
||||
#define REGISTER_VB_SHOULD(flag, body) \
|
||||
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::ShouldVanillaBehavior>(flag, [](GIVanillaBehavior _, bool* should, void* opt) body)
|
||||
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::OnVanillaBehavior>(flag, [](GIVanillaBehavior _, bool* should, void* opt) body)
|
||||
|
||||
class GameInteractor {
|
||||
public:
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <soh/Enhancements/item-tables/ItemTableManager.h>
|
||||
#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();
|
||||
|
@ -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<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 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<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(CVAR_ENHANCEMENT("TimeSavers.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(CVAR_ENHANCEMENT("TimeSavers.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);
|
||||
@ -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<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(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) {
|
||||
EnDntDemo* enDntDemo = static_cast<EnDntDemo*>(actorRef);
|
||||
enDntDemo->actionFunc = EnDntDemo_JudgeSkipToReward;
|
||||
|
@ -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
|
||||
|
@ -1,8 +1,10 @@
|
||||
#ifndef Z_EN_KO_H
|
||||
#define Z_EN_KO_H
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include <libultraship/libultra.h>
|
||||
#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
|
||||
|
Loading…
Reference in New Issue
Block a user