tr2/savegame: introduce strategy concept

This introduces the concept of savegame strategies, like TR1X, with the
legacy strategy being the only one currently.
This commit is contained in:
lahm86 2025-04-04 19:17:59 +01:00
parent 49ad919de3
commit 699c1d9873
6 changed files with 214 additions and 101 deletions

View file

@ -115,14 +115,13 @@ static DECLARE_GF_EVENT_HANDLER(M_HandlePlayLevel)
const int16_t slot_num = Savegame_GetBoundSlot();
if (!Savegame_Load(slot_num)) {
LOG_ERROR("Failed to load save file!");
Game_SetCurrentLevel(nullptr);
GF_SetCurrentLevel(nullptr);
return (GF_COMMAND) { .action = GF_EXIT_TO_TITLE };
}
break;
default:
if (level->type == GFL_NORMAL || level->type == GFL_BONUS) {
Savegame_SetInitialVersion(SAVEGAME_CURRENT_VERSION);
GF_InventoryModifier_Scan(Game_GetCurrentLevel());
GF_InventoryModifier_Apply(Game_GetCurrentLevel(), GF_INV_REGULAR);
}

View file

@ -2,10 +2,43 @@
#include "game/game_flow/types.h"
#include <libtrx/filesystem.h>
#include <libtrx/game/savegame.h>
#define SAVEGAME_CURRENT_VERSION -1
typedef enum {
VERSION_LEGACY = -1,
} SAVEGAME_VERSION;
typedef enum {
SAVEGAME_FORMAT_INVALID = 0,
SAVEGAME_FORMAT_LEGACY = 1,
SAVEGAME_FORMAT_BSON = 2,
} SAVEGAME_FORMAT;
typedef struct {
SAVEGAME_FORMAT format;
char *full_path;
int32_t counter;
int32_t level_num;
char *level_title;
int16_t initial_version;
} SAVEGAME_INFO;
typedef struct {
bool allow_load;
bool allow_save;
SAVEGAME_FORMAT format;
const char *(*get_save_file_pattern_func)(void);
bool (*fill_info_func)(MYFILE *fp, SAVEGAME_INFO *info);
bool (*load_from_file_func)(MYFILE *fp);
void (*save_to_file_func)(MYFILE *fp);
} SAVEGAME_STRATEGY;
void Savegame_Init(void);
void Savegame_Shutdown(void);
void Savegame_RegisterStrategy(SAVEGAME_STRATEGY strategy);
void Savegame_InitCurrentInfo(void);
@ -22,9 +55,17 @@ void Savegame_HighlightNewestSlot(void);
int32_t Savegame_GetLevelNumber(int32_t slot_idx);
int32_t Savegame_GetCounter(void);
int32_t Savegame_GetTotalCount(void);
SAVEGAME_VERSION Savegame_GetInitialVersion(void);
void Savegame_SetInitialVersion(SAVEGAME_VERSION version);
void Savegame_ProcessItemsBeforeSave(void);
void Savegame_ProcessItemsBeforeLoad(void);
void Savegame_SetDefaultStats(const GF_LEVEL *level, STATS_COMMON stats);
STATS_COMMON Savegame_GetDefaultStats(const GF_LEVEL *level);
#define REGISTER_SAVEGAME_STRATEGY(strategy_) \
__attribute__((__constructor__)) static void M_Register(void) \
{ \
Savegame_RegisterStrategy(strategy_); \
}

View file

@ -4,7 +4,6 @@
#include "game/inventory.h"
#include "game/requester.h"
#include "game/savegame.h"
#include "game/savegame/savegame_legacy.h"
#include "global/vars.h"
#include <libtrx/benchmark.h>
@ -18,6 +17,8 @@
#include <libtrx/strings.h>
#include <libtrx/utils.h>
#define MAX_STRATEGIES 1
static STATS_COMMON *m_DefaultStats = nullptr;
static RESUME_INFO *m_ResumeInfos = nullptr;
static int32_t m_SaveSlots = 0;
@ -29,9 +30,14 @@ static SAVEGAME_INFO *m_SavegameInfo = nullptr;
static uint32_t m_ReqFlags1[MAX_REQUESTER_ITEMS] = {};
static uint32_t m_ReqFlags2[MAX_REQUESTER_ITEMS] = {};
static int32_t m_StrategyCount = 0;
static SAVEGAME_STRATEGY m_Strategies[MAX_STRATEGIES];
static SAVEGAME_VERSION m_InitialVersion = VERSION_LEGACY;
static void M_ClearSlots(void);
static bool M_FillSlot(const int32_t slot_num, const char *const path);
static void M_ScanSavedGamesDir(const char *const dir_path);
static bool M_FillSlot(
SAVEGAME_STRATEGY strategy, int32_t slot_num, const char *path);
static void M_ScanSavedGamesDir(const char *dir_path);
static void M_ClearSlots(void)
{
@ -41,6 +47,7 @@ static void M_ClearSlots(void)
for (int32_t i = 0; i < m_SaveSlots; i++) {
SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[i];
savegame_info->format = SAVEGAME_FORMAT_INVALID;
savegame_info->counter = -1;
savegame_info->level_num = -1;
Memory_FreePointer(&savegame_info->full_path);
@ -48,58 +55,24 @@ static void M_ClearSlots(void)
}
}
static bool M_FillSlot(const int32_t slot_num, const char *const path)
static bool M_FillSlot(
const SAVEGAME_STRATEGY strategy, const int32_t slot_num,
const char *const path)
{
SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num];
if (strategy.format <= savegame_info->format) {
return true;
}
bool result = false;
MYFILE *const fp = File_Open(path, FILE_OPEN_READ);
if (fp != nullptr) {
// TODO: make strategy->fill_info
{
char level_title[75];
File_ReadData(fp, level_title, 75);
savegame_info->level_title = Memory_DupStr(level_title);
savegame_info->counter = File_ReadS32(fp);
for (int32_t i = 0; i < 24; i++) {
File_Skip(fp, sizeof(uint16_t)); // pistol ammo
File_Skip(fp, sizeof(uint16_t)); // magnum ammo
File_Skip(fp, sizeof(uint16_t)); // uzi ammo
File_Skip(fp, sizeof(uint16_t)); // shotgun ammo
File_Skip(fp, sizeof(uint16_t)); // m16 ammo
File_Skip(fp, sizeof(uint16_t)); // grenade ammo
File_Skip(fp, sizeof(uint16_t)); // harpoon ammo
File_Skip(fp, sizeof(uint8_t)); // small medis
File_Skip(fp, sizeof(uint8_t)); // big medis
File_Skip(fp, sizeof(uint8_t)); // reserved
File_Skip(fp, sizeof(uint8_t)); // flares
File_Skip(fp, sizeof(int8_t)); // gun status
File_Skip(fp, sizeof(int8_t)); // gun type
File_Skip(fp, sizeof(uint16_t)); // flags
File_Skip(fp, sizeof(uint16_t)); // unused
File_Skip(fp, sizeof(uint32_t)); // timer
File_Skip(fp, sizeof(uint32_t)); // ammo used
File_Skip(fp, sizeof(uint32_t)); // hits
File_Skip(fp, sizeof(uint32_t)); // distance
File_Skip(fp, sizeof(uint16_t)); // kills
File_Skip(fp, sizeof(uint8_t)); // secret flags
File_Skip(fp, sizeof(uint8_t)); // medis used
}
File_Skip(fp, sizeof(uint32_t)); // timer
File_Skip(fp, sizeof(uint32_t)); // ammo used
File_Skip(fp, sizeof(uint32_t)); // hits
File_Skip(fp, sizeof(uint32_t)); // distance
File_Skip(fp, sizeof(uint16_t)); // kills
File_Skip(fp, sizeof(uint8_t)); // secret flags
File_Skip(fp, sizeof(uint8_t)); // medis used
savegame_info->level_num = File_ReadS16(fp);
if (strategy.fill_info_func(fp, savegame_info)) {
savegame_info->format = strategy.format;
Memory_FreePointer(&savegame_info->full_path);
savegame_info->full_path = Memory_DupStr(path);
result = true;
}
Memory_FreePointer(&savegame_info->full_path);
savegame_info->full_path = Memory_DupStr(path);
result = true;
File_Close(fp);
}
return result;
@ -121,13 +94,20 @@ static void M_ScanSavedGamesDir(const char *const dir_path)
continue;
}
int32_t slot = -1;
int32_t parsed =
sscanf(file_name, g_GameFlow.savegame_fmt_legacy, &slot);
if (parsed == 1 && slot >= 0 && slot < m_SaveSlots) {
char *file_path = String_Format("%s/%s", dir_path, file_name);
M_FillSlot(slot, file_path);
Memory_FreePointer(&file_path);
for (int32_t i = 0; i < m_StrategyCount; i++) {
const SAVEGAME_STRATEGY strategy = m_Strategies[i];
if (!strategy.allow_load) {
continue;
}
int32_t slot = -1;
const int32_t parsed =
sscanf(file_name, strategy.get_save_file_pattern_func(), &slot);
if (parsed == 1 && slot >= 0 && slot < m_SaveSlots) {
char *file_path = String_Format("%s/%s", dir_path, file_name);
M_FillSlot(strategy, slot, file_path);
Memory_FreePointer(&file_path);
}
}
}
@ -165,6 +145,13 @@ static void M_LoadPostprocess(void)
MovableBlock_SetupFloor();
}
void Savegame_RegisterStrategy(const SAVEGAME_STRATEGY strategy)
{
ASSERT(m_StrategyCount < MAX_STRATEGIES);
m_Strategies[m_StrategyCount] = strategy;
m_StrategyCount++;
}
void Savegame_Init(void)
{
m_ResumeInfos = Memory_Alloc(
@ -277,6 +264,16 @@ int32_t Savegame_GetTotalCount(void)
return m_SavedGames;
}
SAVEGAME_VERSION Savegame_GetInitialVersion(void)
{
return m_InitialVersion;
}
void Savegame_SetInitialVersion(const SAVEGAME_VERSION version)
{
m_InitialVersion = version;
}
void Savegame_ProcessItemsBeforeSave(void)
{
for (int32_t i = 0; i < Item_GetLevelCount(); i++) {
@ -530,7 +527,8 @@ void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *const level)
bool Savegame_Save(const int32_t slot_idx)
{
bool ret = false;
bool result = false;
Savegame_BindSlot(slot_idx);
const GF_LEVEL *const current_level = Game_GetCurrentLevel();
const char *const level_title = current_level->title;
@ -540,24 +538,33 @@ bool Savegame_Save(const int32_t slot_idx)
SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_idx];
const bool was_slot_empty = savegame_info->full_path == nullptr;
char *file_name = String_Format(g_GameFlow.savegame_fmt_legacy, slot_idx);
MYFILE *const fp = File_Open(file_name, FILE_OPEN_WRITE);
if (fp != nullptr) {
m_SaveCounter++;
Savegame_Legacy_SaveToFile(fp);
m_SaveCounter++;
for (int32_t i = 0; i < m_StrategyCount; i++) {
const SAVEGAME_STRATEGY strategy = m_Strategies[i];
if (!strategy.allow_save || strategy.save_to_file_func == nullptr) {
continue;
}
Memory_FreePointer(&savegame_info->full_path);
savegame_info->full_path = Memory_DupStr(File_GetPath(fp));
savegame_info->counter = m_SaveCounter;
savegame_info->level_num = current_level->num;
savegame_info->level_title =
level_title != nullptr ? Memory_DupStr(level_title) : nullptr;
File_Close(fp);
ret = true;
char *file_name =
String_Format(strategy.get_save_file_pattern_func(), slot_idx);
MYFILE *const fp = File_Open(file_name, FILE_OPEN_WRITE);
if (fp != nullptr) {
strategy.save_to_file_func(fp);
savegame_info->format = strategy.format;
Memory_FreePointer(&savegame_info->full_path);
savegame_info->full_path = Memory_DupStr(File_GetPath(fp));
savegame_info->counter = m_SaveCounter;
savegame_info->level_num = current_level->num;
savegame_info->level_title =
level_title != nullptr ? Memory_DupStr(level_title) : nullptr;
File_Close(fp);
result = true;
}
Memory_FreePointer(&file_name);
}
Memory_FreePointer(&file_name);
if (ret) {
if (result) {
char save_num_text[16];
sprintf(save_num_text, "%d", m_SaveCounter);
Requester_ChangeItem(
@ -572,27 +579,37 @@ bool Savegame_Save(const int32_t slot_idx)
m_SavedGames++;
}
Savegame_HighlightNewestSlot();
} else {
m_SaveCounter--;
}
return ret;
return result;
}
bool Savegame_Load(const int32_t slot_idx)
{
const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_idx];
ASSERT(savegame_info->format != 0);
M_LoadPreprocess();
bool result = false;
char *file_name = String_Format(g_GameFlow.savegame_fmt_legacy, slot_idx);
MYFILE *const fp = File_Open(file_name, FILE_OPEN_READ);
if (fp != nullptr) {
Savegame_Legacy_LoadFromFile(fp);
File_Close(fp);
result = true;
for (int32_t i = 0; i < m_StrategyCount; i++) {
const SAVEGAME_STRATEGY strategy = m_Strategies[i];
if (strategy.format != savegame_info->format) {
continue;
}
MYFILE *const fp = File_Open(savegame_info->full_path, FILE_OPEN_READ);
if (fp != nullptr) {
result = strategy.load_from_file_func(fp);
File_Close(fp);
}
break;
}
if (result) {
M_LoadPostprocess();
}
M_LoadPostprocess();
Savegame_SetInitialVersion(m_SavegameInfo[slot_idx].initial_version);
return result;
}

View file

@ -1,5 +1,3 @@
#include "game/savegame/savegame_legacy.h"
#include "game/camera.h"
#include "game/game.h"
#include "game/game_flow.h"
@ -76,6 +74,23 @@ static void M_WriteLaraArm(const LARA_ARM *arm);
static void M_WriteAmmoInfo(const AMMO_INFO *ammo_info);
static void M_WriteFlares(void);
static const char *M_GetSaveFilePattern(void);
static bool M_FillInfo(MYFILE *fp, SAVEGAME_INFO *info);
static void M_SaveToFile(MYFILE *fp);
static bool M_LoadFromFile(MYFILE *fp);
static SAVEGAME_STRATEGY m_Strategy = {
// clang-format off
.allow_load = true,
.allow_save = true,
.format = SAVEGAME_FORMAT_LEGACY,
.get_save_file_pattern_func = M_GetSaveFilePattern,
.fill_info_func = M_FillInfo,
.load_from_file_func = M_LoadFromFile,
.save_to_file_func = M_SaveToFile,
// clang-format on
};
static void M_Reset(char *const buffer)
{
m_BufPos = 0;
@ -642,7 +657,58 @@ static void M_WriteFlares(void)
}
}
void Savegame_Legacy_SaveToFile(MYFILE *const fp)
static const char *M_GetSaveFilePattern(void)
{
return g_GameFlow.savegame_fmt_legacy;
}
static bool M_FillInfo(MYFILE *const fp, SAVEGAME_INFO *const savegame_info)
{
char level_title[75];
File_ReadData(fp, level_title, 75);
savegame_info->level_title = Memory_DupStr(level_title);
savegame_info->counter = File_ReadS32(fp);
for (int32_t i = 0; i < 24; i++) {
File_Skip(fp, sizeof(uint16_t)); // pistol ammo
File_Skip(fp, sizeof(uint16_t)); // magnum ammo
File_Skip(fp, sizeof(uint16_t)); // uzi ammo
File_Skip(fp, sizeof(uint16_t)); // shotgun ammo
File_Skip(fp, sizeof(uint16_t)); // m16 ammo
File_Skip(fp, sizeof(uint16_t)); // grenade ammo
File_Skip(fp, sizeof(uint16_t)); // harpoon ammo
File_Skip(fp, sizeof(uint8_t)); // small medis
File_Skip(fp, sizeof(uint8_t)); // big medis
File_Skip(fp, sizeof(uint8_t)); // reserved
File_Skip(fp, sizeof(uint8_t)); // flares
File_Skip(fp, sizeof(int8_t)); // gun status
File_Skip(fp, sizeof(int8_t)); // gun type
File_Skip(fp, sizeof(uint16_t)); // flags
File_Skip(fp, sizeof(uint16_t)); // unused
File_Skip(fp, sizeof(uint32_t)); // timer
File_Skip(fp, sizeof(uint32_t)); // ammo used
File_Skip(fp, sizeof(uint32_t)); // hits
File_Skip(fp, sizeof(uint32_t)); // distance
File_Skip(fp, sizeof(uint16_t)); // kills
File_Skip(fp, sizeof(uint8_t)); // secret flags
File_Skip(fp, sizeof(uint8_t)); // medis used
}
File_Skip(fp, sizeof(uint32_t)); // timer
File_Skip(fp, sizeof(uint32_t)); // ammo used
File_Skip(fp, sizeof(uint32_t)); // hits
File_Skip(fp, sizeof(uint32_t)); // distance
File_Skip(fp, sizeof(uint16_t)); // kills
File_Skip(fp, sizeof(uint8_t)); // secret flags
File_Skip(fp, sizeof(uint8_t)); // medis used
savegame_info->level_num = File_ReadS16(fp);
savegame_info->initial_version = VERSION_LEGACY;
return true;
}
static void M_SaveToFile(MYFILE *const fp)
{
char *buffer = Memory_Alloc(SAVEGAME_LEGACY_TOTAL_SIZE);
M_Reset(buffer);
@ -712,7 +778,7 @@ void Savegame_Legacy_SaveToFile(MYFILE *const fp)
Memory_FreePointer(&buffer);
}
void Savegame_Legacy_LoadFromFile(MYFILE *const fp)
static bool M_LoadFromFile(MYFILE *const fp)
{
char *buffer = Memory_Alloc(File_Size(fp));
File_Seek(fp, 0, FILE_SEEK_SET);
@ -798,4 +864,7 @@ void Savegame_Legacy_LoadFromFile(MYFILE *const fp)
M_ReadFlares();
Memory_FreePointer(&buffer);
return true;
}
REGISTER_SAVEGAME_STRATEGY(m_Strategy)

View file

@ -1,6 +0,0 @@
#pragma once
#include <libtrx/filesystem.h>
void Savegame_Legacy_SaveToFile(MYFILE *fp);
void Savegame_Legacy_LoadFromFile(MYFILE *fp);

View file

@ -128,13 +128,6 @@ typedef struct {
LEVEL_STATS stats;
} RESUME_INFO;
typedef struct {
char *full_path;
int32_t counter;
int32_t level_num;
char *level_title;
} SAVEGAME_INFO;
typedef struct {
int16_t lock_angles[4];
int16_t left_angles[4];