Feedback Update #2

-Introduced new function 'PauseWarp_Idle' now that 'PauseWarp_Main' is no longer called every frame
-Added C wrapper to access 'GameInteractor::IsSaveLoaded' and scrapped the 'IsStateValid' function
-Added 'PauseWarp_Idle' to the the 'RegisterPauseWarp' function
-Refactored the code some
This commit is contained in:
mckinlee 2023-09-22 17:21:30 -04:00
parent b1e621b375
commit 362f6426ec
3 changed files with 52 additions and 69 deletions

View File

@ -45,6 +45,11 @@ bool GameInteractor::IsSaveLoaded() {
return (gPlayState == NULL || player == NULL || gSaveContext.fileNum < 0 || gSaveContext.fileNum > 2) ? false : true;
}
extern "C" bool IsSaveLoadedWrapper() {
GameInteractor* gameInteractor = GameInteractor::Instance;
return gameInteractor->IsSaveLoaded();
}
bool GameInteractor::IsGameplayPaused() {
Player* player = GET_PLAYER(gPlayState);
return (Player_InBlockingCsMode(gPlayState, player) || gPlayState->pauseCtx.state != 0 || gPlayState->msgCtx.msgMode != 0) ? true : false;

View File

@ -33,6 +33,7 @@ extern PlayState* gPlayState;
extern void Overlay_DisplayText(float duration, const char* text);
uint32_t ResourceMgr_IsSceneMasterQuest(s16 sceneNum);
void PauseWarp_Main();
void PauseWarp_Idle();
}
bool performDelayedSave = false;
bool performSave = false;
@ -979,6 +980,10 @@ void RegisterPauseWarp() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPauseMenu>([]() {
PauseWarp_Main();
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
PauseWarp_Idle();
});
}
void InitMods() {

View File

@ -1,110 +1,83 @@
// Importing necessary libraries and headers
#include <stdbool.h>
#include <stdint.h>
#include "soh/Enhancements/gameconsole.h"
#include "global.h"
#include <soh/Enhancements/custom-message/CustomMessageTypes.h>
#include "soh/Enhancements/custom-message/CustomMessageTypes.h"
#include "luslog.h"
// Defining the structure for the pause warp state, which holds different flags and cooldowns
typedef struct {
bool warpInitiated, textboxInitiated, inChoosingState, textboxIsOpen, isTextboxClosing;
int aButtonCooldown, textboxCheckCooldown, textboxOpenCooldown;
} PauseWarpState;
// Mapping the song messages. Each song corresponds to a specific text message.
static const int songMessageMap[] = { TEXT_WARP_MINUET_OF_FOREST, TEXT_WARP_BOLERO_OF_FIRE, TEXT_WARP_SERENADE_OF_WATER, TEXT_WARP_REQUIEM_OF_SPIRIT, TEXT_WARP_NOCTURNE_OF_SHADOW, TEXT_WARP_PRELUDE_OF_LIGHT };
// Mapping the song audio. Each song corresponds to a specific audio ID.
static const int songAudioMap[] = { NA_BGM_OCA_MINUET, NA_BGM_OCA_BOLERO, NA_BGM_OCA_SERENADE, NA_BGM_OCA_REQUIEM, NA_BGM_OCA_NOCTURNE, NA_BGM_OCA_LIGHT };
// Forward declaring the functions used in this file
bool IsStateValid(PlayState* play, Player* player, PauseWarpState* state);
void ResetStateFlags(PauseWarpState* state);
void InitiateWarp(PlayState* play, Player* player, int song, PauseWarpState* state);
void HandleWarpConfirmation(PlayState* play, Player* player, PauseWarpState* state);
void HandleCooldowns(PauseWarpState* state);
extern bool IsSaveLoadedWrapper();
// Checking if the state is valid. This is a sanity check to ensure we're not operating on null pointers.
bool IsStateValid(PlayState* play, Player* player, PauseWarpState* state) {
return play && player && state;
static PauseWarpState state;
bool IsPauseWarpEnabled() {
return CVarGetInteger("gPauseWarpEnabled", 0) && IsSaveLoadedWrapper();
}
// Resetting all the flags in the state to their default values (which is mostly 'false' for booleans and '0' for integers)
void ResetStateFlags(PauseWarpState* state) {
*state = (PauseWarpState){0};
}
// Initiating the warp process. Here we set all the required flags and disable player input.
void InitiateWarp(PlayState* play, Player* player, int song, PauseWarpState* state) {
int idx = song - QUEST_SONG_MINUET; // Calculating the song index
Audio_SetSoundBanksMute(0x20); //Mute current BGM
Audio_PlayFanfare(songAudioMap[idx]); // Play the corresponding fanfare
play->msgCtx.lastPlayedSong = idx; // Storing the last played song
play->pauseCtx.state = 0x12; // Setting the pause state
Message_StartTextbox(play, songMessageMap[idx], NULL); // Starting the textbox with the appropriate message
player->stateFlags1 |= PLAYER_STATE1_IN_CUTSCENE; // Disabling player input
*state = (PauseWarpState){.warpInitiated = true, .textboxOpenCooldown = 10, .aButtonCooldown = 10, .textboxCheckCooldown = 5, .textboxIsOpen = true}; // Setting the flags for warp
play->msgCtx.choiceIndex = 0; // Resetting the choice index
void InitiateWarp(PlayState* play, Player* player, int song) {
int idx = song - QUEST_SONG_MINUET;
Audio_SetSoundBanksMute(0x20);
Audio_PlayFanfare(songAudioMap[idx]);
play->msgCtx.lastPlayedSong = idx;
play->pauseCtx.state = 0x12;
Message_StartTextbox(play, songMessageMap[idx], NULL);
player->stateFlags1 |= PLAYER_STATE1_IN_CUTSCENE;
state = (PauseWarpState){.warpInitiated = true, .textboxOpenCooldown = 10, .aButtonCooldown = 10, .textboxCheckCooldown = 5, .textboxIsOpen = true};
play->msgCtx.choiceIndex = 0;
}
// Handling the warp confirmation. This is the part where the player actually gets teleported if they confirmed.
void HandleWarpConfirmation(PlayState* play, Player* player, PauseWarpState* state) {
if (play->msgCtx.choiceIndex == 0) Entrance_SetWarpSongEntrance(); // Teleporting the player if 'ok' was selected
player->stateFlags1 &= ~PLAYER_STATE1_IN_CUTSCENE; // Re-enabling player input
ResetStateFlags(state); // Resetting the state flags
void HandleWarpConfirmation(PlayState* play, Player* player) {
if (play->msgCtx.choiceIndex == 0) Entrance_SetWarpSongEntrance();
player->stateFlags1 &= ~PLAYER_STATE1_IN_CUTSCENE;
ResetStateFlags(&state);
}
// Managing the cooldowns for different actions and transitions
void HandleCooldowns(PauseWarpState* state) {
// Decreasing the cooldowns if they're greater than zero
if (state->aButtonCooldown > 0) state->aButtonCooldown--;
if (state->textboxCheckCooldown > 0) state->textboxCheckCooldown--;
if (state->textboxOpenCooldown > 0) state->textboxOpenCooldown--;
// If the textbox is closing, reset the flag
if (state->isTextboxClosing) *state = (PauseWarpState){.isTextboxClosing = false};
void HandleCooldowns() {
if (state.aButtonCooldown > 0) state.aButtonCooldown--;
if (state.textboxCheckCooldown > 0) state.textboxCheckCooldown--;
if (state.textboxOpenCooldown > 0) state.textboxOpenCooldown--;
if (state.isTextboxClosing) ResetStateFlags(&state);
}
// The main function that gets called every frame
void PauseWarp_Main() {
LUSLOG_CRITICAL("PauseWarp_Main Called");
static PauseWarpState state; // The state is static so it retains its value between function calls
// Checking if the pause warp feature is enabled
int pauseWarpEnabled = CVarGetInteger("gPauseWarpEnabled", 0);
if (!IsPauseWarpEnabled()) return;
PlayState* play = gPlayState;
Player* player = play ? GET_PLAYER(play) : NULL;
// If pause warp is not enabled or the state is invalid, reset the state and exit
if (!pauseWarpEnabled || !IsStateValid(play, player, &state)) return ResetStateFlags(&state);
// Check if a Ocarina in the Ocarina slot
if (gSaveContext.inventory.items[SLOT_OCARINA] == ITEM_NONE) return ResetStateFlags(&state);
// Checking if the 'A' button is pressed
Player* player = GET_PLAYER(play);
int aButtonPressed = CHECK_BTN_ALL(play->state.input->press.button, BTN_A);
// If 'A' is pressed and the cooldowns are zero, and we're not already warping, initiate the warp
if (aButtonPressed && !state.aButtonCooldown && !(state.warpInitiated || state.textboxInitiated || state.inChoosingState)) {
int song = play->pauseCtx.cursorPoint[PAUSE_QUEST];
// Check if the player has the selected warp song
if (!CHECK_QUEST_ITEM(song)) return;
// Initiate warp if the song is within the valid range
if (song >= QUEST_SONG_MINUET && song <= QUEST_SONG_PRELUDE) InitiateWarp(play, player, song, &state);
if (CHECK_QUEST_ITEM(song) && song >= QUEST_SONG_MINUET && song <= QUEST_SONG_PRELUDE) {
InitiateWarp(play, player, song);
}
}
}
void PauseWarp_Idle() {
if (!IsPauseWarpEnabled()) return;
PlayState* play = gPlayState;
Player* player = GET_PLAYER(play);
if (gSaveContext.inventory.items[SLOT_OCARINA] == ITEM_NONE) return ResetStateFlags(&state);
// Depending on the message mode, update the state flags
switch (play->msgCtx.msgMode) {
case 6: if (state.warpInitiated) state.textboxInitiated = true; break;
case 53: if (state.warpInitiated && state.textboxInitiated) state.inChoosingState = true; break;
case 54: if (state.warpInitiated && state.textboxInitiated && state.inChoosingState) HandleWarpConfirmation(play, player, &state); break;
case 54: if (state.warpInitiated && state.textboxInitiated && state.inChoosingState) HandleWarpConfirmation(play, player); break;
case 0: ResetStateFlags(&state); break;
}
// Finally, handle any cooldowns for the next frame
HandleCooldowns(&state);
HandleCooldowns();
}