tr2/savegame: implement BSON saves
Some checks are pending
Run code linters / Run code linters (push) Waiting to run
Publish a pre-release / Build TR1 (push) Has been skipped
Publish a pre-release / Build TR2 (push) Has been skipped
Publish a pre-release / Create a prerelease (push) Has been skipped

This implements BSON save games, similar to TR1, and opts for it to be
used in place of legacy for writing. Saves will also be stored in a
separate saves directory.
This commit is contained in:
lahm86 2025-04-04 20:01:48 +01:00
parent 5b524faec9
commit 0d80ca8f8f
16 changed files with 1299 additions and 29 deletions

View file

@ -4,6 +4,7 @@
"main_menu_picture": "data/images/title_eu.png",
"savegame_fmt_legacy": "savegame.%d",
"savegame_fmt_bson": "save_tr2_%02d.dat",
"cmd_init": {"action": "exit_to_title"},
"cmd_title": {"action": "noop"},

View file

@ -4,6 +4,7 @@
"main_menu_picture": "data/images/title_eu_gm.png",
"savegame_fmt_legacy": "savegame_gm.%d",
"savegame_fmt_bson": "save_trgm_%02d.dat",
"cmd_init": {"action": "exit_to_title"},
"cmd_title": {"action": "noop"},

View file

@ -2,7 +2,8 @@
// This file is used to enable the -l argument support.
"main_menu_picture": "data/images/title_eu.png",
"savegame_fmt_legacy": "savegame.%d",
"savegame_fmt_legacy": "savegame_custom.%d",
"savegame_fmt_bson": "save_tr2_custom_%02d.dat",
"cmd_init": {"action": "exit_to_title"},
"cmd_title": {"action": "noop"},

View file

@ -3,6 +3,8 @@
- added an installer for Windows (#2681)
- added the bonus level game flow type, which allows for levels to be unlocked if all main game secrets are found (#2668)
- added the ability for custom levels to have up to two of each secret type per level (#2674)
- added BSON savegame support, removing the limits imposed by the OG 8KB file size, so allowing for storing more data and offering improved feature support (legacy save files can still be read, similar to TR1) (#2662)
- changed savegame files to be stored in the `saves` directory (#2087)
- fixed the final two levels not allowing for secrets to be counted in the statistics (#1582)
- fixed Lara's holsters being empty if a game flow level removes all weapons but also re-adds the pistols (#2677)
- fixed the console opening when remapping its key (#2641)

View file

@ -310,6 +310,7 @@ as Notepad.
- added Linux builds
- added macOS builds
- added .jpeg/.png screenshots
- added BSON savegame support, removing the limits imposed by the OG 8KB file size, so allowing for storing more data and offering improved feature support
- added ability to skip FMVs with both the Action key
- added ability to skip end credits with the Action and Escape keys
- added the ability to specify per-level SFX files rather than enforcing the default (main.sfx) on all levels

View file

@ -91,9 +91,8 @@ void GF_Shutdown(void)
Memory_FreePointer(&gf->main_menu_background_path);
Memory_FreePointer(&gf->savegame_fmt_legacy);
#if TR_VERSION == 1
Memory_FreePointer(&gf->savegame_fmt_bson);
#else
#if TR_VERSION == 2
Memory_FreePointer(&gf->settings.sfx_path);
#endif
}

View file

@ -95,6 +95,12 @@ static void M_LoadCommonRoot(JSON_OBJECT *const obj, GAME_FLOW *const gf)
Shell_ExitSystem("'savegame_fmt_legacy' must be a string");
}
gf->savegame_fmt_legacy = Memory_DupStr(tmp_s);
tmp_s = JSON_ObjectGetString(obj, "savegame_fmt_bson", JSON_INVALID_STRING);
if (tmp_s == JSON_INVALID_STRING) {
Shell_ExitSystem("'savegame_fmt_bson' must be a string");
}
gf->savegame_fmt_bson = Memory_DupStr(tmp_s);
}
static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleIntEvent)

View file

@ -228,16 +228,9 @@ static void M_LoadLevelItemDrops(
static void M_LoadRoot(JSON_OBJECT *const obj, GAME_FLOW *const gf)
{
const char *tmp_s;
double tmp_d;
JSON_ARRAY *tmp_arr;
tmp_s = JSON_ObjectGetString(obj, "savegame_fmt_bson", JSON_INVALID_STRING);
if (tmp_s == JSON_INVALID_STRING) {
Shell_ExitSystem("'savegame_fmt_bson' must be a string");
}
gf->savegame_fmt_bson = Memory_DupStr(tmp_s);
tmp_d = JSON_ObjectGetDouble(obj, "demo_delay", -1.0);
if (tmp_d < 0.0) {
Shell_ExitSystem("'demo_delay' must be a positive number");

View file

@ -142,13 +142,13 @@ typedef struct {
GF_FMV *fmvs;
};
#if TR_VERSION == 1
// savegame settings
struct {
char *savegame_fmt_legacy;
char *savegame_fmt_bson;
};
#if TR_VERSION == 1
// global settings
struct {
float demo_delay;
@ -168,11 +168,6 @@ typedef struct {
GF_COMMAND cmd_demo_end;
};
// savegame settings
struct {
char *savegame_fmt_legacy;
};
// global settings
struct {
float demo_delay;

View file

@ -56,6 +56,9 @@ static DECLARE_GF_EVENT_HANDLER(M_HandlePlayLevel)
case GFSC_SAVED:
GF_InventoryModifier_Scan(level);
// reset current info to the defaults so that we do not do
// Item_GlobalReplace in the inventory initialization routines too early
Savegame_InitCurrentInfo();
break;
case GFSC_SELECT: {

View file

@ -5,10 +5,11 @@
#include <libtrx/filesystem.h>
#include <libtrx/game/savegame.h>
#define SAVEGAME_CURRENT_VERSION -1
#define SAVEGAME_CURRENT_VERSION 0
typedef enum {
VERSION_LEGACY = -1,
VERSION_0 = 0,
} SAVEGAME_VERSION;
typedef struct {

View file

@ -2,6 +2,7 @@
#include "game/game_flow.h"
#include "game/game_string.h"
#include "game/inventory.h"
#include "game/lara/misc.h"
#include "game/requester.h"
#include "game/savegame.h"
#include "global/vars.h"
@ -10,6 +11,7 @@
#include <libtrx/debug.h>
#include <libtrx/enum_map.h>
#include <libtrx/filesystem.h>
#include <libtrx/game/lara.h>
#include <libtrx/game/objects/traps/movable_block.h>
#include <libtrx/game/savegame.h>
#include <libtrx/log.h>
@ -17,7 +19,8 @@
#include <libtrx/strings.h>
#include <libtrx/utils.h>
#define MAX_STRATEGIES 1
#define MAX_STRATEGIES 2
#define SAVES_DIR "saves"
static STATS_COMMON *m_DefaultStats = nullptr;
static RESUME_INFO *m_ResumeInfos = nullptr;
@ -136,13 +139,15 @@ static void M_LoadPostprocess(void)
if (obj->save_flags != 0) {
item->flags &= 0xFF00;
}
if (obj->handle_save_func != nullptr) {
obj->handle_save_func(item, SAVEGAME_STAGE_AFTER_LOAD);
}
}
MovableBlock_SetupFloor();
LARA_INFO *const lara = Lara_GetLaraInfo();
if (lara->burn) {
lara->burn = 0;
Lara_CatchFire();
}
}
void Savegame_RegisterStrategy(const SAVEGAME_STRATEGY strategy)
@ -206,6 +211,7 @@ void Savegame_ScanSavedGames(void)
m_SavedGames = 0;
m_NewestSlot = -1;
M_ScanSavedGamesDir(SAVES_DIR);
M_ScanSavedGamesDir(".");
for (int32_t i = 0; i < m_SaveSlots; i++) {
@ -530,6 +536,8 @@ bool Savegame_Save(const int32_t slot_idx)
bool result = false;
Savegame_BindSlot(slot_idx);
File_CreateDirectory(SAVES_DIR);
const GF_LEVEL *const current_level = Game_GetCurrentLevel();
const char *const level_title = current_level->title;
@ -547,7 +555,8 @@ bool Savegame_Save(const int32_t slot_idx)
char *file_name =
String_Format(strategy.get_save_file_pattern_func(), slot_idx);
MYFILE *const fp = File_Open(file_name, FILE_OPEN_WRITE);
char *full_path = String_Format("%s/%s", SAVES_DIR, file_name);
MYFILE *const fp = File_Open(full_path, FILE_OPEN_WRITE);
if (fp != nullptr) {
strategy.save_to_file_func(fp);
savegame_info->format = strategy.format;
@ -562,6 +571,7 @@ bool Savegame_Save(const int32_t slot_idx)
}
Memory_FreePointer(&file_name);
Memory_FreePointer(&full_path);
}
if (result) {

File diff suppressed because it is too large Load diff

View file

@ -82,7 +82,7 @@ static bool M_LoadFromFile(MYFILE *fp);
static SAVEGAME_STRATEGY m_Strategy = {
// clang-format off
.allow_load = true,
.allow_save = true,
.allow_save = false,
.format = SAVEGAME_FORMAT_LEGACY,
.get_save_file_pattern_func = M_GetSaveFilePattern,
.fill_info_func = M_FillInfo,
@ -282,6 +282,10 @@ static void M_ReadItems(void)
M_Read(item->data, sizeof(LIFT_INFO));
break;
}
if (obj->handle_save_func != nullptr) {
obj->handle_save_func(item, SAVEGAME_STAGE_AFTER_LOAD);
}
}
}
@ -852,11 +856,6 @@ static bool M_LoadFromFile(MYFILE *const fp)
weapon_item->room_num = NO_ROOM;
}
if (g_Lara.burn) {
g_Lara.burn = 0;
Lara_CatchFire();
}
Room_SetFlipEffect(M_ReadS32());
Room_SetFlipTimer(M_ReadS32());
g_IsMonkAngry = M_ReadS32();

View file

@ -266,6 +266,7 @@ sources = [
'game/room.c',
'game/room_draw.c',
'game/savegame/common.c',
'game/savegame/savegame_bson.c',
'game/savegame/savegame_legacy.c',
'game/scaler.c',
'game/shell/common.c',