Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b0f4fcd
Rename PauseWarp.cpp to PauseMenuSongs.cpp
bassdr Jul 20, 2026
69886b2
Play non-warp ocarina songs from the pause menu
bassdr Jul 20, 2026
01474d2
Cast lastPlayedSong to s8 for respawn data (fix MSVC /WX)
bassdr Jul 20, 2026
99fb7c2
Move okarina-tag activation into the enhancement, out of decomp
bassdr Jul 21, 2026
c221fc2
Fire the okarina-tag handler in the same frame
bassdr Jul 21, 2026
f68f2c7
Add a specific logic for Mido
bassdr Jul 21, 2026
b0c3376
Trigger Song of Time blocks and Great Fairies from the pause menu
bassdr Jul 22, 2026
8db8709
Add Darunia and adult Malon to the pause-menu song hand-off
bassdr Jul 22, 2026
a8289bf
Guard pause-menu songs behind an ocarina-playable check
bassdr Jul 24, 2026
7eb286a
Unify the pause-menu song hand-off into one deferred path
bassdr Jul 24, 2026
475cd3d
Document the raw decompiled symbols the staff-spot path calls
bassdr Jul 24, 2026
536a556
Show a "can't play here" message instead of silently ignoring the input
bassdr Jul 24, 2026
724fd60
Trigger the Lost Woods Skull Kid from the pause menu
bassdr Jul 24, 2026
5732c97
Factor out the shared pause-menu close, tighten a few comments
bassdr Jul 24, 2026
a207b20
Apply the ocarina-playable guard to warp songs too
bassdr Jul 27, 2026
73887b2
Retry pause-menu songs briefly and fade the "can't play" notice
bassdr Jul 28, 2026
db4a49c
Tidy pause-menu-songs macros and comments
bassdr Jul 28, 2026
944fa41
Fix rando warp songs from the pause menu warping to the wrong place
bassdr Aug 4, 2026
0cdf268
Make Skip Warp Cutscenes the only warp-song cutscene skipper
bassdr Aug 5, 2026
a29ac45
Fire the ocarina song-action hook from the pause menu
bassdr Aug 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
"StartingNotes": 5
},
"PauseMenuAnimatedLink": 1,
"PauseWarp": 1,
"PauseMenuSongs": 1,
"PersistentMasks": 1,
"PulsateBossIcon": 1,
"QuickBongoKill": 1,
Expand Down Expand Up @@ -129,6 +129,7 @@
"ToggleStrength": 1,
"TwoHandedIdle": 1,
"VisualAgony": 1,
"WarpSongSkipAnimation": 1,
"WidescreenActorCulling": 1
},
"gRandoEnhancements": null
Expand Down
26 changes: 26 additions & 0 deletions soh/soh/Enhancements/Presets/Presets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,34 @@ void DrawSectionCheck(const std::string& name, bool empty, bool* pointer, std::s
}
}

// Presets saved before an enhancement was renamed still carry the old CVar, and applying a block overwrites
// it wholesale, so translate them on load the way ConfigUpdaters translates a config. Only the in-memory
// copy is touched; the file is migrated again next launch, or cleaned up if the user saves over it.
void MigratePreset(nlohmann::json& json) {
if (!json.contains("blocks") || !json["blocks"].is_object()) {
return;
}
for (auto& block : json["blocks"]) {
if (!block.is_object() || !block.contains("gEnhancements") || !block["gEnhancements"].is_object()) {
continue;
}
auto& enhancements = block["gEnhancements"];

// PauseWarp was split into PauseMenuSongs (covers all songs) + WarpSongSkipAnimation.
// Old behavior skipped the animation, so migrate both flags on.
if (auto pauseWarp = enhancements.find("PauseWarp"); pauseWarp != enhancements.end()) {
if (pauseWarp->is_number() && pauseWarp->get<int>() != 0) {
enhancements["PauseMenuSongs"] = 1;
enhancements["WarpSongSkipAnimation"] = 1;
}
enhancements.erase("PauseWarp");
}
}
}

void ParsePreset(nlohmann::json& json, std::string name) {
try {
MigratePreset(json);
presets[json["presetName"]].presetValues = json;
presets[json["presetName"]].fileName = name;
if (json.contains("isBuiltIn")) {
Expand Down
589 changes: 589 additions & 0 deletions soh/soh/Enhancements/QoL/PauseMenuSongs.cpp

Large diffs are not rendered by default.

153 changes: 0 additions & 153 deletions soh/soh/Enhancements/QoL/PauseWarp.cpp

This file was deleted.

36 changes: 36 additions & 0 deletions soh/soh/Enhancements/QoL/SkipWarpAnimation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ShipInit.hpp"

extern "C" {
#include "functions.h"
#include "macros.h"
#include "variables.h"

extern PlayState* gPlayState;
}

static constexpr int32_t CVAR_SKIP_WARP_DEFAULT = 0;
#define CVAR_SKIP_WARP_NAME CVAR_ENHANCEMENT("WarpSongSkipAnimation")
#define CVAR_SKIP_WARP_VALUE CVarGetInteger(CVAR_SKIP_WARP_NAME, CVAR_SKIP_WARP_DEFAULT)

// DEMOKANKYO_WARP_OUT = 0x0F; the warp-song departure animation spawned by Player_Action_8084E3C4
static constexpr s16 DEMOKANKYO_WARP_OUT_PARAM = 0x0F;

static void RegisterSkipWarpHooks() {
COND_HOOK(OnActorInit, CVAR_SKIP_WARP_VALUE, [](void* refActor) {
Actor* actor = static_cast<Actor*>(refActor);
if (actor->id == ACTOR_DEMO_KANKYO && actor->params == DEMOKANKYO_WARP_OUT_PARAM) {
// Leaves right away, and settles the destination: entrance rando redirects from inside it.
Environment_WarpSongLeave(gPlayState);
// Switch arrival spawn mode from WARP_SONG to IDLE so DEMO_KANKYO WARP_IN is never spawned.
// Another mode means something else owns the arrival (a rando grotto return), with no cutscene.
RespawnData* respawn = &gSaveContext.respawn[RESPAWN_MODE_RETURN];
if (((respawn->playerParams & 0xF00) >> 8) == PLAYER_START_MODE_WARP_SONG) {
respawn->playerParams = (respawn->playerParams & ~0xF00) | (PLAYER_START_MODE_IDLE << 8);
}
Actor_Kill(actor);
}
});
}

static RegisterShipInitFunc initFunc(RegisterSkipWarpHooks, { CVAR_SKIP_WARP_NAME });
1 change: 1 addition & 0 deletions soh/soh/Enhancements/custom-message/CustomMessageTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ typedef enum {
TEXT_SAVE_MSG = 0x9213,
TEXT_CONTINUE_OVERWORLD_MSG = 0x9214,
TEXT_CONTINUE_DUNGEON_MSG = 0x9215,
TEXT_CANNOT_PLAY_OCARINA_MSG = 0x9216,
} TextIDs;

#ifdef __cplusplus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ DEFINE_HOOK(OnPlayerUpdate, ());
DEFINE_HOOK(OnSetDoAction, (uint16_t action));
DEFINE_HOOK(OnPlayerSfx, (u16 sfxId));
DEFINE_HOOK(OnOcarinaSongAction, ());
DEFINE_HOOK(OnWarpSongLeave, ());
DEFINE_HOOK(OnOcarinaNote, (uint8_t note, float modulator, int8_t bend));
DEFINE_HOOK(OnShopSlotChange, (uint8_t cursorIndex, int16_t price));
DEFINE_HOOK(OnDungeonKeyUsed, (uint16_t mapIndex));
Expand Down
4 changes: 4 additions & 0 deletions soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ void GameInteractor_ExecuteOnOcarinaSongAction() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnOcarinaSongAction>();
}

void GameInteractor_ExecuteOnWarpSongLeave() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnWarpSongLeave>();
}

void GameInteractor_ExecuteOnOcarinaNote(uint8_t note, float modulator, int8_t bend) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnOcarinaNote>(note, modulator, bend);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ void GameInteractor_ExecuteOnPlayerUpdate();
void GameInteractor_ExecuteOnSetDoAction(uint16_t action);
void GameInteractor_ExecuteOnPlayerSfx(u16 sfxId);
void GameInteractor_ExecuteOnOcarinaSongAction();
void GameInteractor_ExecuteOnWarpSongLeave();
void GameInteractor_ExecuteOnOcarinaNote(uint8_t note, float modulator, int8_t bend);
bool GameInteractor_ShouldActorInit(void* actor);
void GameInteractor_ExecuteOnActorInit(void* actor);
Expand Down
13 changes: 10 additions & 3 deletions soh/soh/Enhancements/randomizer/hook_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2757,10 +2757,12 @@ void RandomizerOnActorUpdateHandler(void* refActor) {
Flags_UnsetRandomizerInf(RAND_INF_SPIRIT_BIG_MIRROR_STATUE_TURNED);
}
}
}

// In ER, override the warp song locations. Also removes the warp song cutscene
if (RAND_GET_OPTION(RSK_SHUFFLE_ENTRANCES) && actor->id == ACTOR_DEMO_KANKYO &&
actor->params == 0x000F) { // Warp Song particles
// In ER, warp songs lead to their shuffled entrance rather than their warp pad. Every warp path commits its
// destination through Environment_WarpSongLeave, so that is the one place the override has to happen.
void RandomizerOnWarpSongLeaveHandler() {
if (RAND_GET_OPTION(RSK_SHUFFLE_ENTRANCES)) {
Entrance_SetWarpSongEntrance();
}
}
Expand Down Expand Up @@ -2927,6 +2929,7 @@ static void RandomizerRegisterHooks() {
static uint32_t afterSceneCommandsHook = 0;
static uint32_t onActorInitHook = 0;
static uint32_t onActorUpdateHook = 0;
static uint32_t onWarpSongLeaveHook = 0;
static uint32_t onPlayerUpdateHook = 0;
static uint32_t onGameFrameUpdateHook = 0;
static uint32_t onSceneSpawnActorsHook = 0;
Expand Down Expand Up @@ -2959,6 +2962,7 @@ static void RandomizerRegisterHooks() {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::AfterSceneCommands>(afterSceneCommandsHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorInit>(onActorInitHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(onActorUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnWarpSongLeave>(onWarpSongLeaveHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnPlayerUpdate>(onPlayerUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnGameFrameUpdate>(onGameFrameUpdateHook);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnSceneSpawnActors>(onSceneSpawnActorsHook);
Expand All @@ -2977,6 +2981,7 @@ static void RandomizerRegisterHooks() {
afterSceneCommandsHook = 0;
onActorInitHook = 0;
onActorUpdateHook = 0;
onWarpSongLeaveHook = 0;
onPlayerUpdateHook = 0;
onGameFrameUpdateHook = 0;
onSceneSpawnActorsHook = 0;
Expand Down Expand Up @@ -3018,6 +3023,8 @@ static void RandomizerRegisterHooks() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>(RandomizerOnActorInitHandler);
onActorUpdateHook =
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>(RandomizerOnActorUpdateHandler);
onWarpSongLeaveHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnWarpSongLeave>(
RandomizerOnWarpSongLeaveHandler);
onPlayerUpdateHook =
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>(RandomizerOnPlayerUpdateHandler);
onGameFrameUpdateHook = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>(
Expand Down
24 changes: 12 additions & 12 deletions soh/soh/Enhancements/randomizer/randomizer_entrance.c
Original file line number Diff line number Diff line change
Expand Up @@ -432,41 +432,41 @@ void Entrance_SetSavewarpEntrance(void) {
}

void Entrance_SetWarpSongEntrance(void) {
gPlayState->transitionTrigger = TRANS_TRIGGER_START;
gPlayState->transitionType = TRANS_TYPE_FADE_WHITE_FAST;
s16 warpEntrance;
switch (gPlayState->msgCtx.lastPlayedSong) {
case OCARINA_SONG_MINUET:
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_SACRED_FOREST_MEADOW_WARP_PAD); // Minuet
warpEntrance = Entrance_OverrideNextIndex(ENTR_SACRED_FOREST_MEADOW_WARP_PAD); // Minuet
break;
case OCARINA_SONG_BOLERO:
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD); // Bolero
warpEntrance = Entrance_OverrideNextIndex(ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD); // Bolero
break;
case OCARINA_SONG_SERENADE:
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_LAKE_HYLIA_WARP_PAD); // Serenade
warpEntrance = Entrance_OverrideNextIndex(ENTR_LAKE_HYLIA_WARP_PAD); // Serenade
break;
case OCARINA_SONG_REQUIEM:
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_DESERT_COLOSSUS_WARP_PAD); // Requiem
warpEntrance = Entrance_OverrideNextIndex(ENTR_DESERT_COLOSSUS_WARP_PAD); // Requiem
break;
case OCARINA_SONG_NOCTURNE:
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_GRAVEYARD_WARP_PAD); // Nocturne
warpEntrance = Entrance_OverrideNextIndex(ENTR_GRAVEYARD_WARP_PAD); // Nocturne
break;
case OCARINA_SONG_PRELUDE:
gPlayState->nextEntranceIndex = Entrance_OverrideNextIndex(ENTR_TEMPLE_OF_TIME_WARP_PAD); // Prelude
warpEntrance = Entrance_OverrideNextIndex(ENTR_TEMPLE_OF_TIME_WARP_PAD); // Prelude
break;
default:
gPlayState->transitionTrigger = TRANS_TRIGGER_OFF; // if something goes wrong, the animation plays normally
return; // unknown song: leave the warp untouched
}

// Environment_WarpSongLeave has pointed the transition at the vanilla warp pad; send it to the shuffled
// entrance instead. Skipping the warp cutscenes is the Skip Warp Cutscenes enhancement's job.
gPlayState->nextEntranceIndex = warpEntrance;

// If one of the warp songs happens to lead to a grotto return, then we
// have to force the grotto return afterwards
Grotto_ForceGrottoReturnOnSpecialEntrance();

if (gSaveContext.gameMode != GAMEMODE_NORMAL) {
// During DHWW the cutscene must play at the destination
gSaveContext.respawnFlag = -3;
} else if (gSaveContext.respawnFlag == -3) {
// Unset Zoneout Type -3 to avoid cutscene at destination (technically it's not needed)
gSaveContext.respawnFlag = 0;
}
}

Expand Down
Loading
Loading