From aa2ad2360182f384a6cd09e2961125e9a3fa7a7b Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Wed, 22 Jan 2025 19:30:34 -0600 Subject: [PATCH] Replace old pause buffer input experience with a more accurate one (#4918) --- .../Enhancements/Cheats/EasyFrameAdvance.cpp | 40 +++++++++ .../Restorations/PauseBufferInputs.cpp | 83 +++++++++++++++++++ soh/soh/Enhancements/bootcommands.c | 2 - .../game-interactor/GameInteractor.h | 1 + .../GameInteractor_HookTable.h | 1 + .../game-interactor/GameInteractor_Hooks.cpp | 4 + .../game-interactor/GameInteractor_Hooks.h | 1 + soh/soh/Enhancements/presets.h | 4 - soh/soh/SohMenuBar.cpp | 16 ++-- soh/soh/UIWidgets.cpp | 1 + soh/src/code/game.c | 2 + soh/src/code/padmgr.c | 7 -- soh/src/code/z_kaleido_setup.c | 14 +--- soh/src/code/z_play.c | 5 -- .../ovl_kaleido_scope/z_kaleido_scope_PAL.c | 8 +- 15 files changed, 145 insertions(+), 44 deletions(-) create mode 100644 soh/soh/Enhancements/Cheats/EasyFrameAdvance.cpp create mode 100644 soh/soh/Enhancements/Restorations/PauseBufferInputs.cpp diff --git a/soh/soh/Enhancements/Cheats/EasyFrameAdvance.cpp b/soh/soh/Enhancements/Cheats/EasyFrameAdvance.cpp new file mode 100644 index 000000000..ab7bba377 --- /dev/null +++ b/soh/soh/Enhancements/Cheats/EasyFrameAdvance.cpp @@ -0,0 +1,40 @@ +#include +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "variables.h" +#include "overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h" +} + +#define CVAR_FRAME_ADVANCE_NAME CVAR_CHEAT("EasyFrameAdvance") +#define CVAR_FRAME_ADVANCE_DEFAULT 0 +#define CVAR_FRAME_ADVANCE_VALUE CVarGetInteger(CVAR_FRAME_ADVANCE_NAME, CVAR_FRAME_ADVANCE_DEFAULT) + +static int frameAdvanceTimer = 0; +#define PAUSE_STATE_OFF 0 +#define PAUSE_STATE_UNPAUSE_CLOSE 19 + +void RegisterEasyFrameAdvance() { + COND_HOOK(OnGameStateMainStart, CVAR_FRAME_ADVANCE_VALUE, []() { + if (gPlayState == NULL) { + return; + } + + Input* input = &gPlayState->state.input[0]; + PauseContext* pauseCtx = &gPlayState->pauseCtx; + + if (frameAdvanceTimer > 0 && pauseCtx->state == PAUSE_STATE_OFF) { + frameAdvanceTimer--; + if (frameAdvanceTimer == 0 && CHECK_BTN_ALL(input->cur.button, BTN_START)) { + input->press.button |= BTN_START; + } + } + + if (pauseCtx->state == PAUSE_STATE_UNPAUSE_CLOSE) { + frameAdvanceTimer = 2; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterEasyFrameAdvance, { CVAR_FRAME_ADVANCE_NAME }); diff --git a/soh/soh/Enhancements/Restorations/PauseBufferInputs.cpp b/soh/soh/Enhancements/Restorations/PauseBufferInputs.cpp new file mode 100644 index 000000000..4604c00ca --- /dev/null +++ b/soh/soh/Enhancements/Restorations/PauseBufferInputs.cpp @@ -0,0 +1,83 @@ +#include +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "variables.h" +#include "overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h" +} + +#define CVAR_BUFFER_NAME CVAR_ENHANCEMENT("PauseBufferWindow") +#define CVAR_BUFFER_DEFAULT 0 +#define CVAR_BUFFER_VALUE CVarGetInteger(CVAR_BUFFER_NAME, CVAR_BUFFER_DEFAULT) + +#define CVAR_INCLUDE_NAME CVAR_ENHANCEMENT("IncludeHeldInputsBufferWindow") +#define CVAR_INCLUDE_DEFAULT 0 +#define CVAR_INCLUDE_VALUE CVarGetInteger(CVAR_INCLUDE_NAME, CVAR_INCLUDE_DEFAULT) + +#define CVAR_FRAME_ADVANCE_NAME CVAR_CHEAT("EasyFrameAdvance") +#define CVAR_FRAME_ADVANCE_DEFAULT 0 +#define CVAR_FRAME_ADVANCE_VALUE CVarGetInteger(CVAR_FRAME_ADVANCE_NAME, CVAR_FRAME_ADVANCE_DEFAULT) + +static u16 inputBufferTimer = 0; +static u16 prePauseInputs = 0; +static u16 pauseInputs = 0; +#define PAUSE_STATE_OFF 0 +#define PAUSE_STATE_OPENING_1 2 +#define PAUSE_STATE_UNPAUSE_SETUP 18 + +void RegisterPauseBufferInputs() { + COND_VB_SHOULD(VB_KALEIDO_UNPAUSE_CLOSE, CVAR_BUFFER_VALUE || CVAR_INCLUDE_VALUE, { + Input* input = &gPlayState->state.input[0]; + + // Store all inputs that were pressed during the buffer window + pauseInputs |= input->press.button; + + // If the user opts to include held inputs in the buffer window, store the held inputs, minus the held inputs when the pause menu was opened + if (CVAR_INCLUDE_VALUE && inputBufferTimer == 0) { + pauseInputs |= input->cur.button & ~prePauseInputs; + prePauseInputs = 0; + } + + // Wait a specified number of frames before continuing the unpause + inputBufferTimer++; + if (inputBufferTimer < CVAR_BUFFER_VALUE) { + *should = false; + } + }); + + COND_HOOK(OnGameStateMainStart, CVAR_BUFFER_VALUE || CVAR_INCLUDE_VALUE, []() { + if (gPlayState == NULL) { + return; + } + + Input* input = &gPlayState->state.input[0]; + PauseContext* pauseCtx = &gPlayState->pauseCtx; + + // if the input buffer timer is not 0 and the pause state is off, then the player just unpaused + if (inputBufferTimer != 0 && pauseCtx->state == PAUSE_STATE_OFF) { + inputBufferTimer = 0; + + // If the user opts into easy frame advance, remove START input + if (CVAR_FRAME_ADVANCE_VALUE) { + pauseInputs &= ~BTN_START; + } + + // So we need to re-apply the inputs that were pressed during the buffer window + input->press.button |= pauseInputs; + } + + // Reset the timer and stored inputs at the beginning of the unpause process + if (pauseCtx->state == PAUSE_STATE_UNPAUSE_SETUP && pauseCtx->unk_1F4 != 160.0f) { + inputBufferTimer = 0; + pauseInputs = 0; + } + + // If the user opts to include held inputs in the buffer window, store the held inputs at the beginning of the pause process, minus the START input + if (pauseCtx->state == PAUSE_STATE_OPENING_1 && CVAR_INCLUDE_VALUE) { + prePauseInputs = input->cur.button & ~BTN_START; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterPauseBufferInputs, { CVAR_BUFFER_NAME, CVAR_INCLUDE_NAME }); diff --git a/soh/soh/Enhancements/bootcommands.c b/soh/soh/Enhancements/bootcommands.c index c010067da..85467c6ea 100644 --- a/soh/soh/Enhancements/bootcommands.c +++ b/soh/soh/Enhancements/bootcommands.c @@ -28,8 +28,6 @@ void BootCommands_Init() CVarClear(CVAR_GENERAL("OnFileSelectNameEntry")); // Clear when soh is killed on the file name entry page CVarClear(CVAR_GENERAL("BetterDebugWarpScreenMQMode")); CVarClear(CVAR_GENERAL("BetterDebugWarpScreenMQModeScene")); - CVarClear(CVAR_GENERAL("CheatEasyPauseBufferLastInputs")); - CVarClear(CVAR_GENERAL("CheatEasyPauseBufferTimer")); #if defined(__SWITCH__) || defined(__WIIU__) CVarRegisterInteger(CVAR_IMGUI_CONTROLLER_NAV, 1); // always enable controller nav on switch/wii u #endif diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 8810a2301..572f3199e 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -286,6 +286,7 @@ typedef enum { VB_RENDER_YES_ON_CONTINUE_PROMPT, // Vanilla condition: CHECK_BTN_ALL(input->press.button, BTN_START) VB_CLOSE_PAUSE_MENU, + VB_KALEIDO_UNPAUSE_CLOSE, // Vanilla condition: true VB_SPAWN_BLUE_WARP, // Vanilla condition: this->warpTimer > sWarpTimerTarget && gSaveContext.nextCutsceneIndex == 0xFFEF diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h index a2d5c56ec..5c7b99090 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h @@ -7,6 +7,7 @@ */ DEFINE_HOOK(OnLoadGame, (int32_t fileNum)); DEFINE_HOOK(OnExitGame, (int32_t fileNum)); +DEFINE_HOOK(OnGameStateMainStart, ()); DEFINE_HOOK(OnGameFrameUpdate, ()); DEFINE_HOOK(OnItemReceive, (GetItemEntry itemEntry)); DEFINE_HOOK(OnSaleEnd, (GetItemEntry itemEntry)); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 39fc298a8..44391cac5 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -10,6 +10,10 @@ void GameInteractor_ExecuteOnExitGame(int32_t fileNum) { GameInteractor::Instance->ExecuteHooks(fileNum); } +void GameInteractor_ExecuteOnGameStateMainStart() { + GameInteractor::Instance->ExecuteHooks(); +} + void GameInteractor_ExecuteOnGameFrameUpdate() { GameInteractor::Instance->ExecuteHooks(); } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index cb238539f..ff39e69c5 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -9,6 +9,7 @@ extern "C" { // MARK: - Gameplay void GameInteractor_ExecuteOnLoadGame(int32_t fileNum); void GameInteractor_ExecuteOnExitGame(int32_t fileNum); +void GameInteractor_ExecuteOnGameStateMainStart(); void GameInteractor_ExecuteOnGameFrameUpdate(); void GameInteractor_ExecuteOnItemReceiveHooks(GetItemEntry itemEntry); void GameInteractor_ExecuteOnSaleEndHooks(GetItemEntry itemEntry); diff --git a/soh/soh/Enhancements/presets.h b/soh/soh/Enhancements/presets.h index 78e754d58..31df92831 100644 --- a/soh/soh/Enhancements/presets.h +++ b/soh/soh/Enhancements/presets.h @@ -342,8 +342,6 @@ const std::vector cheatCvars = { CVAR_CHEAT("EasyISG"), CVAR_CHEAT("EasyQPA"), CVAR_CHEAT("TimelessEquipment"), - CVAR_CHEAT("EasyPauseBuffer"), - CVAR_CHEAT("EasyInputBuffer"), CVAR_CHEAT("NoRestrictItems"), CVAR_CHEAT("FreezeTime"), CVAR_GENERAL("PrevTime"), @@ -1009,7 +1007,6 @@ const std::vector spockRacePresetEntries = { PRESET_ENTRY_S32(CVAR_ENHANCEMENT("MinimumFishWeightChild"), 3), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("GoronPot"), 1), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("ForgeTime"), 0), - PRESET_ENTRY_S32(CVAR_CHEAT("EasyPauseBuffer"), 1), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("DampeAllNight"), 1), PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("10GSHint"), 1), PRESET_ENTRY_S32(CVAR_RANDOMIZER_SETTING("20GSHint"), 1), @@ -1061,7 +1058,6 @@ const std::vector spockRaceNoLogicPresetEntries = { PRESET_ENTRY_S32(CVAR_ENHANCEMENT("AdultMasks"), 1), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("MinimumFishWeightAdult"), 6), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("AssignableTunicsAndBoots"), 1), - PRESET_ENTRY_S32(CVAR_CHEAT("EasyPauseBuffer"), 1), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("MinimumFishWeightChild"), 3), PRESET_ENTRY_S32(CVAR_ENHANCEMENT("ClimbSpeed"), 4), PRESET_ENTRY_S32(CVAR_COSMETIC("Goron.NeckLength"), 1000), diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index 1bb47877b..6269d2937 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -310,9 +310,6 @@ void DrawSettingsMenu() { ImGui::PopStyleColor(1); ImGui::PopStyleVar(3); - UIWidgets::PaddedEnhancementSliderInt("Simulated Input Lag: %d frames", "##SimulatedInputLag", CVAR_SIMULATED_INPUT_LAG, 0, 6, "", 0, true, true, false); - UIWidgets::Tooltip("Buffers your inputs to be executed a specified amount of frames later"); - ImGui::EndMenu(); } @@ -1571,7 +1568,11 @@ void DrawEnhancementsMenu() { UIWidgets::Tooltip("Restores a bug from NTSC 1.0/1.1 that allows you to obtain the eyeball frog from King Zora instead of the Zora Tunic by holding shield."); UIWidgets::PaddedEnhancementCheckbox("Pulsate boss icon", CVAR_ENHANCEMENT("PulsateBossIcon"), true, false); UIWidgets::Tooltip("Restores an unfinished feature to pulsate the boss room icon when you are in the boss room."); - + UIWidgets::PaddedEnhancementSliderInt("Pause Buffer Input Window: %d", "##PauseBufferWindow", CVAR_ENHANCEMENT("PauseBufferWindow"), 0, 40, "", 0, true, true, false); + UIWidgets::PaddedEnhancementCheckbox("Include held inputs at start of Buffer Input Window", CVAR_ENHANCEMENT("IncludeHeldInputsBufferWindow"), true, false); + UIWidgets::Tooltip("Typically, inputs that are held prior to the buffer window are not included in the buffer. This setting changes that behavior to include them. This may cause some inputs to be re-triggered undesireably, for instance Z-Targetting something you might not want to."); + UIWidgets::PaddedEnhancementSliderInt("Simulated Input Lag: %d frames", "##SimulatedInputLag", CVAR_SIMULATED_INPUT_LAG, 0, 6, "", 0, true, true, false); + UIWidgets::Tooltip("Buffers your inputs to be executed a specified amount of frames later"); ImGui::EndMenu(); } @@ -1885,11 +1886,8 @@ void DrawCheatsMenu() { UIWidgets::Tooltip("Makes every surface in the game climbable"); UIWidgets::PaddedEnhancementCheckbox("Moon Jump on L", CVAR_CHEAT("MoonJumpOnL"), true, false); UIWidgets::Tooltip("Holding L makes you float into the air"); - UIWidgets::PaddedEnhancementCheckbox("Easy Frame Advancing", CVAR_CHEAT("EasyPauseBuffer"), true, false); - UIWidgets::Tooltip("Continue holding START button when unpausing to only advance a single frame and then re-pause"); - const bool bEasyFrameAdvanceEnabled = CVarGetInteger(CVAR_CHEAT("EasyPauseBuffer"), 0); - UIWidgets::PaddedEnhancementCheckbox("Easy Input Buffering", CVAR_CHEAT("EasyInputBuffer"), true, false, bEasyFrameAdvanceEnabled, "Forced enabled when Easy Frame Advancing is enabled", UIWidgets::CheckboxGraphics::Checkmark); - UIWidgets::Tooltip("Inputs that are held down while the Subscreen is closing will be pressed when the game is resumed"); + UIWidgets::PaddedEnhancementCheckbox("New Easy Frame Advancing", CVAR_CHEAT("EasyFrameAdvance"), true, false); + UIWidgets::Tooltip("Continue holding START button when unpausing to only advance a single frame and then re-pause."); UIWidgets::PaddedEnhancementCheckbox("Drops Don't Despawn", CVAR_CHEAT("DropsDontDie"), true, false); UIWidgets::Tooltip("Drops from enemies, grass, etc. don't disappear after a set amount of time"); UIWidgets::PaddedEnhancementCheckbox("Fish Don't despawn", CVAR_CHEAT("NoFishDespawn"), true, false); diff --git a/soh/soh/UIWidgets.cpp b/soh/soh/UIWidgets.cpp index 050c6714c..6d9ca7a65 100644 --- a/soh/soh/UIWidgets.cpp +++ b/soh/soh/UIWidgets.cpp @@ -400,6 +400,7 @@ namespace UIWidgets { if (changed && (oldVal != val)) { CVarSetInteger(cvarName, val); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); } else { changed = false; } diff --git a/soh/src/code/game.c b/soh/src/code/game.c index a14266719..d7524009b 100644 --- a/soh/src/code/game.c +++ b/soh/src/code/game.c @@ -255,6 +255,8 @@ void GameState_Update(GameState* gameState) { GameState_SetFrameBuffer(gfxCtx); + GameInteractor_ExecuteOnGameStateMainStart(); + gameState->main(gameState); func_800C4344(gameState); diff --git a/soh/src/code/padmgr.c b/soh/src/code/padmgr.c index 8665fb448..a8bf778af 100644 --- a/soh/src/code/padmgr.c +++ b/soh/src/code/padmgr.c @@ -286,13 +286,6 @@ void PadMgr_ProcessInputs(PadMgr* padMgr) { Fault_AddHungupAndCrash(__FILE__, __LINE__); } - // When 3 frames are left on easy pause buffer, re-apply the last held inputs to the prev inputs - // to compute the pressed difference. This makes it so previously held inputs are continued as "held", - // but new inputs when unpausing are "pressed" out of the pause menu. - if (CVarGetInteger(CVAR_GENERAL("CheatEasyPauseBufferTimer"), 0) == 3) { - input->prev.button = CVarGetInteger(CVAR_GENERAL("CheatEasyPauseBufferLastInputs"), 0); - } - buttonDiff = input->prev.button ^ input->cur.button; input->press.button |= (u16)(buttonDiff & input->cur.button); input->rel.button |= (u16)(buttonDiff & input->prev.button); diff --git a/soh/src/code/z_kaleido_setup.c b/soh/src/code/z_kaleido_setup.c index 69ea3030f..45992279d 100644 --- a/soh/src/code/z_kaleido_setup.c +++ b/soh/src/code/z_kaleido_setup.c @@ -18,23 +18,11 @@ void KaleidoSetup_Update(PlayState* play) { play->shootingGalleryStatus <= 1 && gSaveContext.magicState != MAGIC_STATE_STEP_CAPACITY && gSaveContext.magicState != MAGIC_STATE_FILL && (play->sceneNum != SCENE_BOMBCHU_BOWLING_ALLEY || !Flags_GetSwitch(play, 0x38))) { - u8 easyPauseBufferEnabled = CVarGetInteger(CVAR_CHEAT("EasyPauseBuffer"), 0); - u8 easyPauseBufferTimer = CVarGetInteger(CVAR_GENERAL("CheatEasyPauseBufferTimer"), 0); - - // If start is not seen as pressed on the 2nd to last frame then we should end the easy frame advance flow - if (easyPauseBufferEnabled && easyPauseBufferTimer == 2 && - !CHECK_BTN_ALL(input->press.button, BTN_START)) { - CVarSetInteger(CVAR_GENERAL("CheatEasyPauseBufferTimer"), 0); - } - if (CHECK_BTN_ALL(input->cur.button, BTN_L) && CHECK_BTN_ALL(input->press.button, BTN_CUP)) { if (BREG(0)) { pauseCtx->debugState = 3; } - } else if ((CHECK_BTN_ALL(input->press.button, BTN_START) && (!easyPauseBufferEnabled || !easyPauseBufferTimer)) || - (easyPauseBufferEnabled && easyPauseBufferTimer == 1)) { // Force Kaleido open when easy pause buffer reaches 0 - // Remember last held buttons for pause buffer cheat (minus start so easy frame advance works) - CVarSetInteger(CVAR_GENERAL("CheatEasyPauseBufferLastInputs"), input->cur.button & ~(BTN_START)); + } else if (CHECK_BTN_ALL(input->press.button, BTN_START)) { gSaveContext.unk_13EE = gSaveContext.unk_13EA; diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index cc7af190d..2b4743417 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -1668,11 +1668,6 @@ time_t Play_GetRealTime() { void Play_Main(GameState* thisx) { PlayState* play = (PlayState*)thisx; - // Decrease the easy pause buffer timer every frame - if (CVarGetInteger(CVAR_GENERAL("CheatEasyPauseBufferTimer"), 0) > 0) { - CVarSetInteger(CVAR_GENERAL("CheatEasyPauseBufferTimer"), CVarGetInteger(CVAR_GENERAL("CheatEasyPauseBufferTimer"), 0) - 1); - } - if (play->envCtx.unk_EE[2] == 0 && CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) { play->envCtx.unk_EE[3] = 64; Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_OBJECT_KANKYO, 0, 0, 0, 0, 0, 0, 3, 0); diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c index 217ec24e3..e76576f85 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c @@ -3992,10 +3992,6 @@ void KaleidoScope_Update(PlayState* play) switch (pauseCtx->unk_1E4) { case 0: if (GameInteractor_Should(VB_CLOSE_PAUSE_MENU, CHECK_BTN_ALL(input->press.button, BTN_START))) { - if (CVarGetInteger(CVAR_CHEAT("EasyPauseBuffer"), 0) || CVarGetInteger(CVAR_CHEAT("EasyInputBuffer"), 0)) { - // Easy pause buffer is 13 frames, 12 for kaledio to end, and one more to advance a single frame - CVarSetInteger(CVAR_GENERAL("CheatEasyPauseBufferTimer"), 13); - } Interface_SetDoAction(play, DO_ACTION_NONE); pauseCtx->state = 0x12; WREG(2) = -6240; @@ -4546,6 +4542,10 @@ void KaleidoScope_Update(PlayState* play) break; case 0x13: + if (!GameInteractor_Should(VB_KALEIDO_UNPAUSE_CLOSE, true)) { + break; + } + pauseCtx->state = 0; R_UPDATE_RATE = 3; R_PAUSE_MENU_MODE = 0;