Skip to content

Commit

Permalink
Persist entrance and respawn information in saves
Browse files Browse the repository at this point in the history
  • Loading branch information
Eblo committed Dec 10, 2024
1 parent 1d2a945 commit 88f0d3d
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 24 deletions.
42 changes: 42 additions & 0 deletions mm/2s2h/BenJsonConversions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,58 @@ void from_json(const json& j, DpadSaveInfo& dpadEquips) {
}
}

void to_json(json& j, const Vec3f& vec) {
j = json{
{ "x", vec.x },
{ "y", vec.y },
{ "z", vec.z },
};
}

void from_json(const json& j, Vec3f& vec) {
j.at("x").get_to(vec.x);
j.at("y").get_to(vec.y);
j.at("z").get_to(vec.z);
}

void to_json(json& j, const RespawnData& respawnData) {
j = json{
{ "pos", respawnData.pos },
{ "yaw", respawnData.yaw },
{ "playerParams", respawnData.playerParams },
{ "entrance", respawnData.entrance },
{ "roomIndex", respawnData.roomIndex },
{ "data", respawnData.data },
{ "tempSwitchFlags", respawnData.tempSwitchFlags },
{ "unk_18", respawnData.unk_18 },
{ "tempCollectFlags", respawnData.tempCollectFlags },
};
}

void from_json(const json& j, RespawnData& respawnData) {
j.at("pos").get_to(respawnData.pos);
j.at("yaw").get_to(respawnData.yaw);
j.at("playerParams").get_to(respawnData.playerParams);
j.at("entrance").get_to(respawnData.entrance);
j.at("roomIndex").get_to(respawnData.roomIndex);
j.at("data").get_to(respawnData.data);
j.at("tempSwitchFlags").get_to(respawnData.tempSwitchFlags);
j.at("unk_18").get_to(respawnData.unk_18);
j.at("tempCollectFlags").get_to(respawnData.tempCollectFlags);
}

void to_json(json& j, const ShipSaveInfo& shipSaveInfo) {
j = json {
{ "dpadEquips", shipSaveInfo.dpadEquips },
{ "pauseSaveEntrance", shipSaveInfo.pauseSaveEntrance },
{ "respawn", shipSaveInfo.respawn},
};
}

void from_json(const json& j, ShipSaveInfo& shipSaveInfo) {
j.at("dpadEquips").get_to(shipSaveInfo.dpadEquips);
j.at("pauseSaveEntrance").get_to(shipSaveInfo.pauseSaveEntrance);
j.at("respawn").get_to(shipSaveInfo.respawn);
}

void to_json(json& j, const ItemEquips& itemEquips) {
Expand Down
62 changes: 41 additions & 21 deletions mm/2s2h/Enhancements/Saving/SavingEnhancements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,11 @@ static uint64_t lastSaveTimestamp = GetUnixTimestamp();

static HOOK_ID autosaveGameStateUpdateHookId = 0;
static HOOK_ID autosaveGameStateDrawFinishHookId = 0;
static HOOK_ID loadCutsceneHookId = 0;

// Used for saving through Autosaves and Pause Menu saves.
extern "C" int SavingEnhancements_GetSaveEntrance() {
switch (gPlayState->sceneId) {
// Woodfall Temple + Odolwa
case SCENE_MITURIN:
case SCENE_MITURIN_BS:
return ENTRANCE(WOODFALL_TEMPLE, 0);
// Snowhead Temple + Goht
case SCENE_HAKUGIN:
case SCENE_HAKUGIN_BS:
return ENTRANCE(SNOWHEAD_TEMPLE, 0);
// Great Bay Temple + Gyorg
case SCENE_SEA:
case SCENE_SEA_BS:
return ENTRANCE(GREAT_BAY_TEMPLE, 0);
// Stone Tower Temple (+ inverted) + Twinmold
case SCENE_INISIE_N:
case SCENE_INISIE_R:
case SCENE_INISIE_BS:
return ENTRANCE(STONE_TOWER_TEMPLE, 0);
default:
return ENTRANCE(SOUTH_CLOCK_TOWN, 0);
}
return gSaveContext.save.entrance;
}

extern "C" bool SavingEnhancements_CanSave() {
Expand Down Expand Up @@ -146,6 +127,34 @@ void HandleAutoSave() {
}
}

/*
* This respawn data is used for multiple things. Beyond the obvious usage for handling player respawns, this structure
* also maintains state information when entering shared grottos. This code executes from OnSaveLoad, which runs after
* save data is populated. This must run after that, otherwise the RESPAWN_MODE_DOWN entrance would get set to
* ENTR_LOAD_OPENING, which in turn would lead to a crash if the save is within a grotto and the player dies before
* leaving.
*/
void loadRespawnData(s16 fileNum) {
for (int i = 0; i < RESPAWN_MODE_MAX; i++) {
gSaveContext.respawn[i] = gSaveContext.save.shipSaveInfo.respawn[i];
}
}

/*
* Upon loading a save, skip any cutscenes that would play if save is from a cutscene entrance (e.g. owl warps, Link
* bowing at Mikau's grave, etc.). An OnPassPlayerInputs hook is then used to unregister the entrance cutscene skip
* hook so that subsequent cutscenes are not also skipped.
*/
void skipEntranceCutsceneOnLoad(s16 fileNum) {
if (!loadCutsceneHookId) {
loadCutsceneHookId = REGISTER_VB_SHOULD(VB_START_CUTSCENE, {
if (CVarGetInteger("gEnhancements.Saving.PauseSave", 0) && loadCutsceneHookId) {
*should = false;
}
});
}
}

void RegisterSavingEnhancements() {
REGISTER_VB_SHOULD(VB_DELETE_OWL_SAVE, {
if (CVarGetInteger("gEnhancements.Saving.PersistentOwlSaves", 0) ||
Expand All @@ -157,6 +166,17 @@ void RegisterSavingEnhancements() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::BeforeEndOfCycleSave>([]() { DeleteOwlSave(); });

GameInteractor::Instance->RegisterGameHook<GameInteractor::BeforeMoonCrashSaveReset>([]() { DeleteOwlSave(); });

GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSaveLoad>(loadRespawnData);

GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSaveLoad>(skipEntranceCutsceneOnLoad);

GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPassPlayerInputs>([](Input* input) {
if (loadCutsceneHookId) {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnPassPlayerInputs>(loadCutsceneHookId);
loadCutsceneHookId = 0;
}
});
}

void RegisterAutosave() {
Expand Down
4 changes: 4 additions & 0 deletions mm/2s2h/GameInteractor/GameInteractor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ void GameInteractor_ExecuteOnSaveInit(s16 fileNum) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSaveInit>(fileNum);
}

void GameInteractor_ExecuteOnSaveLoad(s16 fileNum) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSaveLoad>(fileNum);
}

void GameInteractor_ExecuteBeforeEndOfCycleSave() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::BeforeEndOfCycleSave>();
}
Expand Down
3 changes: 3 additions & 0 deletions mm/2s2h/GameInteractor/GameInteractor.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ typedef enum {
VB_BE_HOOKSHOT_SURFACE,
VB_DEKU_GUARD_SHOW_SEARCH_BALLS,
VB_DISPLAY_SONG_OF_DOUBLE_TIME_PROMPT,
VB_START_CUTSCENE,
} GIVanillaBehavior;

typedef enum {
Expand Down Expand Up @@ -302,6 +303,7 @@ class GameInteractor {
DEFINE_HOOK(BeforeKaleidoDrawPage, (PauseContext * pauseCtx, u16 pauseIndex));
DEFINE_HOOK(AfterKaleidoDrawPage, (PauseContext * pauseCtx, u16 pauseIndex));
DEFINE_HOOK(OnSaveInit, (s16 fileNum));
DEFINE_HOOK(OnSaveLoad, (s16 fileNum));
DEFINE_HOOK(BeforeEndOfCycleSave, ());
DEFINE_HOOK(AfterEndOfCycleSave, ());
DEFINE_HOOK(BeforeMoonCrashSaveReset, ());
Expand Down Expand Up @@ -352,6 +354,7 @@ void GameInteractor_ExecuteOnKaleidoUpdate(PauseContext* pauseCtx);
void GameInteractor_ExecuteBeforeKaleidoDrawPage(PauseContext* pauseCtx, u16 pauseIndex);
void GameInteractor_ExecuteAfterKaleidoDrawPage(PauseContext* pauseCtx, u16 pauseIndex);
void GameInteractor_ExecuteOnSaveInit(s16 fileNum);
void GameInteractor_ExecuteOnSaveLoad(s16 fileNum);
void GameInteractor_ExecuteBeforeEndOfCycleSave();
void GameInteractor_ExecuteAfterEndOfCycleSave();
void GameInteractor_ExecuteBeforeMoonCrashSaveReset();
Expand Down
1 change: 1 addition & 0 deletions mm/include/z64save.h
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ typedef struct DpadSaveInfo {
typedef struct ShipSaveInfo {
DpadSaveInfo dpadEquips;
s32 pauseSaveEntrance;
RespawnData respawn[RESPAWN_MODE_MAX];
} ShipSaveInfo;
// #endregion

Expand Down
5 changes: 5 additions & 0 deletions mm/src/code/z_eventmgr.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "global.h"
#include "z64shrink_window.h"
#include "libc/string.h"
#include "2s2h/GameInteractor/GameInteractor.h"

ActorCutscene sGlobalCutsceneList[] = {
// CS_ID_GLOBAL_78
Expand Down Expand Up @@ -375,6 +376,10 @@ s16 CutsceneManager_StartWithPlayerCsAndSetFlag(s16 csId, Actor* actor) {
}

s16 CutsceneManager_Start(s16 csId, Actor* actor) {
if (!GameInteractor_Should(VB_START_CUTSCENE, true, &csId, actor)) {
return CS_ID_NONE;
}

ActorCutscene* csEntry;
Camera* subCam;
Camera* retCam;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2231,6 +2231,8 @@ void FileSelect_LoadGame(GameState* thisx) {
gSaveContext.hudVisibilityTimer = 0;

gSaveContext.save.saveInfo.playerData.tatlTimer = 0;

GameInteractor_ExecuteOnSaveLoad(gSaveContext.fileNum);
}

void (*sSelectModeUpdateFuncs[])(GameState*) = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3547,10 +3547,11 @@ void KaleidoScope_Update(PlayState* play) {
Audio_PlaySfx(NA_SE_SY_PIECE_OF_HEART);
if (CVarGetInteger("gEnhancements.Saving.PauseSave", 0)) {
gSaveContext.save.isOwlSave = true;
// 2S2H [Enhancement] Eventually we might allow them to load from their last entrance,
// but we need to first identify and fix edge cases where that doesn't work properly
// like grottos and cutscenes
// 2S2H [Enhancement] Maintain respawn information, used for grottos
gSaveContext.save.shipSaveInfo.pauseSaveEntrance = SavingEnhancements_GetSaveEntrance();
for (int i = 0; i < RESPAWN_MODE_MAX; i++) {
gSaveContext.save.shipSaveInfo.respawn[i] = gSaveContext.respawn[i];
}
}
Play_SaveCycleSceneFlags(&play->state);
gSaveContext.save.saveInfo.playerData.savedSceneId = play->sceneId;
Expand Down

0 comments on commit 88f0d3d

Please sign in to comment.