Replace old pause buffer input experience with a more accurate one (#4918)

This commit is contained in:
Garrett Cox 2025-01-22 19:30:34 -06:00 committed by GitHub
parent 69e3342808
commit aa2ad23601
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 145 additions and 44 deletions

View File

@ -0,0 +1,40 @@
#include <libultraship/libultraship.h>
#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 });

View File

@ -0,0 +1,83 @@
#include <libultraship/libultraship.h>
#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 });

View File

@ -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

View File

@ -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

View File

@ -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));

View File

@ -10,6 +10,10 @@ void GameInteractor_ExecuteOnExitGame(int32_t fileNum) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnExitGame>(fileNum);
}
void GameInteractor_ExecuteOnGameStateMainStart() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnGameStateMainStart>();
}
void GameInteractor_ExecuteOnGameFrameUpdate() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnGameFrameUpdate>();
}

View File

@ -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);

View File

@ -342,8 +342,6 @@ const std::vector<const char*> 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<PresetEntry> 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<PresetEntry> 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),

View File

@ -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);

View File

@ -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;
}

View File

@ -255,6 +255,8 @@ void GameState_Update(GameState* gameState) {
GameState_SetFrameBuffer(gfxCtx);
GameInteractor_ExecuteOnGameStateMainStart();
gameState->main(gameState);
func_800C4344(gameState);

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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;