Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enhancement] Persist entrance and respawn information in saves #890

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 @@ -87,6 +87,7 @@ typedef enum {
VB_HONEY_AND_DARLING_MINIGAME_FINISH,
VB_MINIMAP_TOGGLE,
VB_MONKEY_WAIT_TO_TALK_AFTER_APPROACH,
VB_START_CUTSCENE,
} GIVanillaBehavior;

typedef enum {
Expand Down Expand Up @@ -308,6 +309,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 @@ -358,6 +360,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
28 changes: 28 additions & 0 deletions mm/2s2h/SaveManager/Migrations/6.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include "2s2h/SaveManager/SaveManager.h"
#include "z64.h"

// Add respawn info to saves
void SaveManager_Migration_6(nlohmann::json& j) {
if (!j["save"]["shipSaveInfo"].contains("respawn")) {
j["save"]["shipSaveInfo"]["respawn"] = {};
}

for (int i = 0; i < RESPAWN_MODE_MAX; i++) {
j["save"]["shipSaveInfo"]["respawn"][i] = {
{ "pos",
{
{ "x", 0.0 },
{ "y", 0.0 },
{ "z", 0.0 },
} },
{ "yaw", 0 },
{ "playerParams", 0 },
{ "entrance", 0 },
{ "roomIndex", 0 },
{ "data", 0 },
{ "tempSwitchFlags", 0 },
{ "unk_18", 0 },
{ "tempCollectFlags", 0 },
};
}
}
5 changes: 4 additions & 1 deletion mm/2s2h/SaveManager/SaveManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,14 @@ const std::filesystem::path savesFolderPath(Ship::Context::GetPathRelativeToAppD
// - Create the migration file in the Migrations folder with the name `{CURRENT_SAVE_VERSION}.cpp`
// - Add the migration function definition below and add it to the `migrations` map with the key being the previous
// version
const uint32_t CURRENT_SAVE_VERSION = 5;
const uint32_t CURRENT_SAVE_VERSION = 6;

void SaveManager_Migration_1(nlohmann::json& j);
void SaveManager_Migration_2(nlohmann::json& j);
void SaveManager_Migration_3(nlohmann::json& j);
void SaveManager_Migration_4(nlohmann::json& j);
void SaveManager_Migration_5(nlohmann::json& j);
void SaveManager_Migration_6(nlohmann::json& j);

const std::unordered_map<uint32_t, std::function<void(nlohmann::json&)>> migrations = {
// Pre-1.0.0 Migrations, deprecated
Expand All @@ -65,6 +66,8 @@ const std::unordered_map<uint32_t, std::function<void(nlohmann::json&)>> migrati
{ 3, SaveManager_Migration_4 },
// Base Migration
{ 4, SaveManager_Migration_5 },
// Persist respawn data
{ 5, SaveManager_Migration_6 },
};

int SaveManager_MigrateSave(nlohmann::json& j) {
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 @@ -13,6 +13,7 @@
#include "interface/parameter_static/parameter_static.h"
#include "misc/title_static/title_static.h"
#include "2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h"
#include "2s2h/GameInteractor/GameInteractor.h"
#include "2s2h_assets.h"
#include <string.h>
#include "BenPort.h"
Expand Down Expand Up @@ -2231,6 +2232,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 @@ -3561,10 +3561,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
Loading