mirror of
https://github.com/LostArtefacts/TRX.git
synced 2025-04-28 20:58:07 +03:00
savegame: move common logic to TRX
This moves the bulk of savegame logic to TRX.
This commit is contained in:
parent
af29ece2f7
commit
57bc7610b4
15 changed files with 706 additions and 1129 deletions
|
@ -1,9 +1,163 @@
|
|||
#include "benchmark.h"
|
||||
#include "config.h"
|
||||
#include "debug.h"
|
||||
#include "enum_map.h"
|
||||
#include "game/game.h"
|
||||
#include "game/game_flow.h"
|
||||
#include "game/gun/const.h"
|
||||
#include "game/inventory.h"
|
||||
#include "game/lara.h"
|
||||
#include "game/objects.h"
|
||||
#include "game/objects/traps/movable_block.h"
|
||||
#include "game/pathing/lot.h"
|
||||
#include "game/savegame.h"
|
||||
#include "log.h"
|
||||
#include "memory.h"
|
||||
|
||||
#define MAX_STRATEGIES 2
|
||||
#define SAVES_DIR "saves"
|
||||
|
||||
static SAVEGAME_VERSION m_InitialVersion = VERSION_LEGACY;
|
||||
static SAVEGAME_INFO *m_SavegameInfo = nullptr;
|
||||
static RESUME_INFO *m_ResumeInfo = nullptr;
|
||||
static STATS_COMMON *m_DefaultStats = nullptr;
|
||||
static int32_t m_SaveSlots = 0;
|
||||
static int32_t m_SavedGames = 0;
|
||||
static int32_t m_SaveCounter = 0;
|
||||
static int32_t m_NewestSlot = -1;
|
||||
static int32_t m_BoundSlot = -1;
|
||||
|
||||
static int32_t m_StrategyCount = 0;
|
||||
static SAVEGAME_STRATEGY m_Strategies[MAX_STRATEGIES];
|
||||
|
||||
static void M_ClearSlots(void);
|
||||
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_LoadPreprocess(void);
|
||||
static void M_LoadPostprocess(void);
|
||||
|
||||
static void M_ClearSlots(void)
|
||||
{
|
||||
if (m_SavegameInfo == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
Memory_FreePointer(&savegame_info->level_title);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
File_Close(fp);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void M_ScanSavedGamesDir(const char *const dir_path)
|
||||
{
|
||||
void *const dir_handle = File_OpenDirectory(dir_path);
|
||||
if (dir_handle == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const char *const file_name = File_ReadDirectory(dir_handle);
|
||||
if (file_name == nullptr) {
|
||||
break;
|
||||
}
|
||||
if (strcmp(file_name, ".") == 0 || strcmp(file_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File_CloseDirectory(dir_handle);
|
||||
}
|
||||
|
||||
static void M_LoadPreprocess(void)
|
||||
{
|
||||
Savegame_InitCurrentInfo();
|
||||
}
|
||||
|
||||
static void M_LoadPostprocess(void)
|
||||
{
|
||||
// TODO: tidy this; skidoo drivers currently require handle_save_func to be
|
||||
// called immediately on load within the strategies.
|
||||
for (int32_t i = 0; i < Item_GetLevelCount(); i++) {
|
||||
ITEM *const item = Item_Get(i);
|
||||
const OBJECT *const obj = Object_Get(item->object_id);
|
||||
|
||||
if (obj->save_position && obj->shadow_size) {
|
||||
int16_t room_num = item->room_num;
|
||||
const SECTOR *const sector = Room_GetSector(
|
||||
item->pos.x, item->pos.y, item->pos.z, &room_num);
|
||||
item->floor =
|
||||
Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z);
|
||||
}
|
||||
|
||||
if (obj->save_flags != 0) {
|
||||
item->flags &= 0xFF00;
|
||||
}
|
||||
#if TR_VERSION == 1
|
||||
if (obj->handle_save_func != nullptr) {
|
||||
obj->handle_save_func(item, SAVEGAME_STAGE_AFTER_LOAD);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MovableBlock_SetupFloor();
|
||||
|
||||
LARA_INFO *const lara = Lara_GetLaraInfo();
|
||||
#if TR_VERSION == 1
|
||||
if (Game_GetBonusFlag() != GBF_NONE) {
|
||||
g_Config.profile.new_game_plus_unlock = true;
|
||||
}
|
||||
LOT_ClearLOT(&lara->lot);
|
||||
#else
|
||||
if (lara->burn) {
|
||||
lara->burn = 0;
|
||||
Lara_CatchFire();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SAVEGAME_VERSION Savegame_GetInitialVersion(void)
|
||||
{
|
||||
return m_InitialVersion;
|
||||
|
@ -30,3 +184,461 @@ int32_t Savegame_GetBoundSlot(void)
|
|||
{
|
||||
return m_BoundSlot;
|
||||
}
|
||||
|
||||
int32_t Savegame_GetLevelNumber(const int32_t slot_num)
|
||||
{
|
||||
return m_SavegameInfo[slot_num].level_num;
|
||||
}
|
||||
|
||||
bool Savegame_IsSlotFree(const int32_t slot_num)
|
||||
{
|
||||
return m_SavegameInfo[slot_num].level_num == -1;
|
||||
}
|
||||
|
||||
int32_t Savegame_GetCounter(void)
|
||||
{
|
||||
return m_SaveCounter;
|
||||
}
|
||||
|
||||
int32_t Savegame_GetTotalCount(void)
|
||||
{
|
||||
return m_SavedGames;
|
||||
}
|
||||
|
||||
int32_t Savegame_GetHighestSlot(void)
|
||||
{
|
||||
return m_NewestSlot;
|
||||
}
|
||||
|
||||
bool Savegame_RestartAvailable(const int32_t slot_num)
|
||||
{
|
||||
#if TR_VERSION == 1
|
||||
if (slot_num == -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num];
|
||||
return savegame_info->features.restart;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Savegame_RegisterStrategy(const SAVEGAME_STRATEGY strategy)
|
||||
{
|
||||
ASSERT(m_StrategyCount < MAX_STRATEGIES);
|
||||
m_Strategies[m_StrategyCount] = strategy;
|
||||
m_StrategyCount++;
|
||||
}
|
||||
|
||||
void Savegame_Init(void)
|
||||
{
|
||||
m_ResumeInfo = Memory_Alloc(
|
||||
sizeof(RESUME_INFO)
|
||||
* (GF_GetLevelTable(GFLT_MAIN)->count
|
||||
+ GF_GetLevelTable(GFLT_DEMOS)->count));
|
||||
|
||||
m_SaveSlots = Savegame_GetSlotCount();
|
||||
m_SavegameInfo = Memory_Alloc(sizeof(SAVEGAME_INFO) * m_SaveSlots);
|
||||
|
||||
const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_DEMOS);
|
||||
for (int32_t i = 0; i < level_table->count; i++) {
|
||||
RESUME_INFO *const resume_info =
|
||||
Savegame_GetCurrentInfo(&level_table->levels[i]);
|
||||
resume_info->flags.available = 1;
|
||||
resume_info->flags.has_pistols = 1;
|
||||
resume_info->pistol_ammo = 1000;
|
||||
resume_info->gun_status = LGS_ARMLESS;
|
||||
resume_info->equipped_gun_type = LGT_PISTOLS;
|
||||
#if TR_VERSION == 1
|
||||
resume_info->holsters_gun_type = LGT_PISTOLS;
|
||||
resume_info->back_gun_type = LGT_UNARMED;
|
||||
resume_info->lara_hitpoints = LARA_MAX_HITPOINTS;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool Savegame_IsInitialised(void)
|
||||
{
|
||||
return m_SavegameInfo != nullptr;
|
||||
}
|
||||
|
||||
void Savegame_Shutdown(void)
|
||||
{
|
||||
M_ClearSlots();
|
||||
Memory_FreePointer(&m_ResumeInfo);
|
||||
Memory_FreePointer(&m_SavegameInfo);
|
||||
Memory_FreePointer(&m_DefaultStats);
|
||||
}
|
||||
|
||||
RESUME_INFO *Savegame_GetCurrentInfo(const GF_LEVEL *const level)
|
||||
{
|
||||
ASSERT(m_ResumeInfo != nullptr);
|
||||
ASSERT(level != nullptr);
|
||||
if (GF_GetLevelTableType(level->type) == GFLT_MAIN) {
|
||||
return &m_ResumeInfo[level->num];
|
||||
} else if (level->type == GFL_DEMO) {
|
||||
return &m_ResumeInfo[GF_GetLevelTable(GFLT_MAIN)->count];
|
||||
}
|
||||
LOG_WARNING(
|
||||
"Warning: unable to get resume info for level %d (type=%s)", level->num,
|
||||
ENUM_MAP_TO_STRING(GF_LEVEL_TYPE, level->type));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Savegame_SetCurrentInfo(const int32_t current_slot, const int32_t src_slot)
|
||||
{
|
||||
m_ResumeInfo[current_slot] = m_ResumeInfo[src_slot];
|
||||
}
|
||||
|
||||
const SAVEGAME_INFO *Savegame_GetSavegameInfo(const int32_t slot_num)
|
||||
{
|
||||
return &m_SavegameInfo[slot_num];
|
||||
}
|
||||
|
||||
void Savegame_InitCurrentInfo(void)
|
||||
{
|
||||
// TODO: remove both NG+ checks in this function once the TR1 NG options are
|
||||
// ported to TR2.
|
||||
if (TR_VERSION == 2 && Game_IsBonusFlagSet(GBF_NGPLUS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN);
|
||||
for (int32_t i = 0; i < level_table->count; i++) {
|
||||
const GF_LEVEL *const level = &level_table->levels[i];
|
||||
Savegame_ResetCurrentInfo(level);
|
||||
Savegame_ApplyLogicToCurrentInfo(level);
|
||||
Savegame_GetCurrentInfo(level)->flags.available = 0;
|
||||
}
|
||||
|
||||
if (GF_GetGymLevel() != nullptr) {
|
||||
Savegame_GetCurrentInfo(GF_GetGymLevel())->flags.available = 1;
|
||||
}
|
||||
if (GF_GetFirstLevel() != nullptr) {
|
||||
Savegame_GetCurrentInfo(GF_GetFirstLevel())->flags.available = 1;
|
||||
}
|
||||
|
||||
#if TR_VERSION == 2
|
||||
Game_SetBonusFlag(GBF_NONE);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Savegame_ResetCurrentInfo(const GF_LEVEL *const level)
|
||||
{
|
||||
LOG_INFO("Resetting resume info for level #%d", level->num);
|
||||
RESUME_INFO *const current = Savegame_GetCurrentInfo(level);
|
||||
memset(current, 0, sizeof(RESUME_INFO));
|
||||
}
|
||||
|
||||
void Savegame_CarryCurrentInfoToNextLevel(
|
||||
const GF_LEVEL *const src_level, const GF_LEVEL *const dst_level)
|
||||
{
|
||||
LOG_INFO(
|
||||
"Copying resume info from level #%d to level #%d", src_level->num,
|
||||
dst_level->num);
|
||||
RESUME_INFO *const src_resume = Savegame_GetCurrentInfo(src_level);
|
||||
RESUME_INFO *const dst_resume = Savegame_GetCurrentInfo(dst_level);
|
||||
memcpy(dst_resume, src_resume, sizeof(RESUME_INFO));
|
||||
}
|
||||
|
||||
void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *const level)
|
||||
{
|
||||
RESUME_INFO *const resume = Savegame_GetCurrentInfo(level);
|
||||
LARA_INFO *const lara = Lara_GetLaraInfo();
|
||||
|
||||
#if TR_VERSION == 1
|
||||
resume->lara_hitpoints = Lara_GetItem()->hit_points;
|
||||
#endif
|
||||
resume->flags.available = 1;
|
||||
resume->small_medipacks = Inv_RequestItem(O_SMALL_MEDIPACK_ITEM);
|
||||
resume->large_medipacks = Inv_RequestItem(O_LARGE_MEDIPACK_ITEM);
|
||||
|
||||
resume->pistol_ammo = 1000;
|
||||
if (Inv_RequestItem(O_PISTOL_ITEM)) {
|
||||
resume->flags.has_pistols = 1;
|
||||
} else {
|
||||
resume->flags.has_pistols = 0;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_SHOTGUN_ITEM)) {
|
||||
resume->flags.has_shotgun = 1;
|
||||
resume->shotgun_ammo = lara->shotgun_ammo.ammo;
|
||||
} else {
|
||||
resume->flags.has_shotgun = 0;
|
||||
resume->shotgun_ammo =
|
||||
Inv_RequestItem(O_SHOTGUN_AMMO_ITEM) * SHOTGUN_AMMO_QTY;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_MAGNUM_ITEM)) {
|
||||
resume->flags.has_magnums = 1;
|
||||
resume->magnum_ammo = lara->magnum_ammo.ammo;
|
||||
} else {
|
||||
resume->flags.has_magnums = 0;
|
||||
resume->magnum_ammo =
|
||||
Inv_RequestItem(O_MAGNUM_AMMO_ITEM) * MAGNUM_AMMO_QTY;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_UZI_ITEM)) {
|
||||
resume->flags.has_uzis = 1;
|
||||
resume->uzi_ammo = lara->uzi_ammo.ammo;
|
||||
} else {
|
||||
resume->flags.has_uzis = 0;
|
||||
resume->uzi_ammo = Inv_RequestItem(O_UZI_AMMO_ITEM) * UZI_AMMO_QTY;
|
||||
}
|
||||
|
||||
#if TR_VERSION == 1
|
||||
resume->num_scions = Inv_RequestItem(O_SCION_ITEM_1);
|
||||
|
||||
resume->equipped_gun_type = lara->gun_type;
|
||||
resume->holsters_gun_type = lara->holsters_gun_type;
|
||||
resume->back_gun_type = lara->back_gun_type;
|
||||
if (lara->gun_status == LGS_READY) {
|
||||
resume->gun_status = LGS_READY;
|
||||
} else {
|
||||
resume->gun_status = LGS_ARMLESS;
|
||||
}
|
||||
#elif TR_VERSION == 2
|
||||
if (Inv_RequestItem(O_M16_ITEM)) {
|
||||
resume->flags.has_m16 = 1;
|
||||
resume->m16_ammo = lara->m16_ammo.ammo;
|
||||
} else {
|
||||
resume->flags.has_m16 = 0;
|
||||
resume->m16_ammo = Inv_RequestItem(O_M16_AMMO_ITEM) * M16_AMMO_QTY;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_HARPOON_ITEM)) {
|
||||
resume->flags.has_harpoon = 1;
|
||||
resume->harpoon_ammo = lara->harpoon_ammo.ammo;
|
||||
} else {
|
||||
resume->flags.has_harpoon = 0;
|
||||
resume->harpoon_ammo =
|
||||
Inv_RequestItem(O_HARPOON_AMMO_ITEM) * HARPOON_AMMO_QTY;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_GRENADE_ITEM)) {
|
||||
resume->flags.has_grenade = 1;
|
||||
resume->grenade_ammo = lara->grenade_ammo.ammo;
|
||||
} else {
|
||||
resume->flags.has_grenade = 0;
|
||||
resume->grenade_ammo =
|
||||
Inv_RequestItem(O_GRENADE_AMMO_ITEM) * GRENADE_AMMO_QTY;
|
||||
}
|
||||
|
||||
resume->flares = Inv_RequestItem(O_FLARE_ITEM);
|
||||
if (lara->gun_type == LGT_FLARE) {
|
||||
resume->equipped_gun_type = lara->last_gun_type;
|
||||
} else {
|
||||
resume->equipped_gun_type = lara->gun_type;
|
||||
}
|
||||
resume->gun_status = LGS_ARMLESS;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Savegame_ProcessItemsBeforeSave(void)
|
||||
{
|
||||
for (int32_t i = 0; i < Item_GetLevelCount(); i++) {
|
||||
ITEM *const item = Item_Get(i);
|
||||
const OBJECT *const obj = Object_Get(item->object_id);
|
||||
if (obj->handle_save_func != nullptr) {
|
||||
obj->handle_save_func(item, SAVEGAME_STAGE_BEFORE_SAVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Savegame_ProcessItemsBeforeLoad(void)
|
||||
{
|
||||
for (int32_t i = 0; i < Item_GetLevelCount(); i++) {
|
||||
ITEM *const item = Item_Get(i);
|
||||
const OBJECT *const obj = Object_Get(item->object_id);
|
||||
if (obj->handle_save_func != nullptr) {
|
||||
obj->handle_save_func(item, SAVEGAME_STAGE_BEFORE_LOAD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Savegame_SetDefaultStats(
|
||||
const GF_LEVEL *const level, const STATS_COMMON stats)
|
||||
{
|
||||
if (m_DefaultStats == nullptr) {
|
||||
m_DefaultStats = Memory_Alloc(
|
||||
sizeof(STATS_COMMON) * GF_GetLevelTable(GFLT_MAIN)->count);
|
||||
}
|
||||
m_DefaultStats[level->num] = stats;
|
||||
}
|
||||
|
||||
STATS_COMMON Savegame_GetDefaultStats(const GF_LEVEL *const level)
|
||||
{
|
||||
if (m_DefaultStats == nullptr
|
||||
|| (level->type != GFL_NORMAL && level->type != GFL_BONUS)) {
|
||||
return (STATS_COMMON) {};
|
||||
}
|
||||
return m_DefaultStats[level->num];
|
||||
}
|
||||
|
||||
void Savegame_ScanSavedGames(void)
|
||||
{
|
||||
BENCHMARK benchmark = Benchmark_Start();
|
||||
M_ClearSlots();
|
||||
|
||||
m_SaveCounter = 0;
|
||||
m_SavedGames = 0;
|
||||
m_NewestSlot = -1;
|
||||
|
||||
M_ScanSavedGamesDir(SAVES_DIR);
|
||||
M_ScanSavedGamesDir(".");
|
||||
|
||||
for (int32_t i = 0; i < m_SaveSlots; i++) {
|
||||
SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[i];
|
||||
if (savegame_info->level_title != nullptr) {
|
||||
if (savegame_info->counter > m_SaveCounter) {
|
||||
m_SaveCounter = savegame_info->counter;
|
||||
m_NewestSlot = i;
|
||||
}
|
||||
m_SavedGames++;
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark_End(&benchmark, nullptr);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Savegame_PersistGameToCurrentInfo(current_level);
|
||||
|
||||
#if TR_VERSION == 1
|
||||
const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN);
|
||||
for (int32_t i = 0; i < level_table->count; i++) {
|
||||
const GF_LEVEL *const level = &level_table->levels[i];
|
||||
if (level->type == GFL_CURRENT) {
|
||||
Savegame_SetCurrentInfo(i, current_level->num);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_idx];
|
||||
const bool was_slot_empty = savegame_info->full_path == nullptr;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
char *file_name =
|
||||
String_Format(strategy.get_save_file_pattern_func(), slot_idx);
|
||||
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);
|
||||
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(&full_path);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
m_NewestSlot = slot_idx;
|
||||
if (was_slot_empty) {
|
||||
m_SavedGames++;
|
||||
}
|
||||
Savegame_HighlightNewestSlot();
|
||||
} else {
|
||||
m_SaveCounter--;
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
M_LoadPostprocess();
|
||||
m_InitialVersion = m_SavegameInfo[slot_idx].initial_version;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Savegame_UpdateDeathCounters(
|
||||
const int32_t slot_num, const int32_t death_count)
|
||||
{
|
||||
ASSERT(slot_num >= 0);
|
||||
const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num];
|
||||
ASSERT(savegame_info->format != SAVEGAME_FORMAT_INVALID);
|
||||
|
||||
bool ret = false;
|
||||
for (int32_t i = 0; i < m_StrategyCount; i++) {
|
||||
const SAVEGAME_STRATEGY strategy = m_Strategies[i];
|
||||
if (savegame_info->format == strategy.format
|
||||
&& strategy.update_death_counters_func != nullptr) {
|
||||
MYFILE *const fp =
|
||||
File_Open(savegame_info->full_path, FILE_OPEN_READ_WRITE);
|
||||
if (fp != nullptr) {
|
||||
ret = strategy.update_death_counters_func(fp, death_count);
|
||||
File_Close(fp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Savegame_LoadOnlyResumeInfo(int32_t slot_num)
|
||||
{
|
||||
const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num];
|
||||
ASSERT(savegame_info->format != SAVEGAME_FORMAT_INVALID);
|
||||
|
||||
bool ret = false;
|
||||
for (int32_t i = 0; i < m_StrategyCount; i++) {
|
||||
const SAVEGAME_STRATEGY strategy = m_Strategies[i];
|
||||
if (savegame_info->format == strategy.format
|
||||
&& strategy.load_only_resume_info_func != nullptr) {
|
||||
MYFILE *const fp =
|
||||
File_Open(savegame_info->full_path, FILE_OPEN_READ);
|
||||
if (fp != nullptr) {
|
||||
ret = strategy.load_only_resume_info_func(fp);
|
||||
File_Close(fp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Savegame_SetInitialVersion(m_SavegameInfo[slot_num].initial_version);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -7,3 +7,4 @@ void Lara_Extinguish(void);
|
|||
|
||||
int16_t Lara_FloorFront(const ITEM *item, int16_t ang, int32_t dist);
|
||||
void Lara_GetCollisionInfo(const ITEM *item, COLL_INFO *coll);
|
||||
extern void Lara_CatchFire(void);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "../collision.h"
|
||||
#include "../items/types.h"
|
||||
#include "../rooms/enum.h"
|
||||
#include "../savegame.h"
|
||||
#include "../savegame/enum.h"
|
||||
#include "../types.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
extern bool LOT_EnableBaddieAI(int16_t item_num, bool always);
|
||||
extern void LOT_DisableBaddieAI(int16_t item_num);
|
||||
extern CREATURE *LOT_GetBaddieSlot(int32_t i);
|
||||
extern void LOT_ClearLOT(LOT_INFO *LOT);
|
||||
|
|
|
@ -1,12 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include "./enum.h"
|
||||
#include "../game_flow/types.h"
|
||||
#include "./types.h"
|
||||
|
||||
extern void Savegame_RegisterStrategy(SAVEGAME_STRATEGY strategy);
|
||||
// Loading a saved game is divided into two phases. First, the game reads the
|
||||
// savegame file contents to look for the level number. The rest of the save
|
||||
// data is stored in a special buffer in the g_GameInfo. Then the engine
|
||||
// continues to execute the normal game flow and loads the specified level.
|
||||
// Second phase occurs after everything finishes loading, e.g. items,
|
||||
// creatures, triggers etc., and is what actually sets Lara's health, creatures
|
||||
// status, triggers, inventory etc.
|
||||
|
||||
void Savegame_RegisterStrategy(SAVEGAME_STRATEGY strategy);
|
||||
void Savegame_Init(void);
|
||||
void Savegame_Shutdown(void);
|
||||
bool Savegame_IsInitialised(void);
|
||||
void Savegame_ScanSavedGames(void);
|
||||
|
||||
SAVEGAME_VERSION Savegame_GetInitialVersion(void);
|
||||
void Savegame_SetInitialVersion(SAVEGAME_VERSION version);
|
||||
int32_t Savegame_GetHighestSlot(void);
|
||||
int32_t Savegame_GetCounter(void);
|
||||
int32_t Savegame_GetTotalCount(void);
|
||||
int32_t Savegame_GetLevelNumber(int32_t slot_num);
|
||||
bool Savegame_IsSlotFree(int32_t slot_num);
|
||||
bool Savegame_RestartAvailable(int32_t slot_num);
|
||||
|
||||
// Remembers the slot used when the player starts a loaded game.
|
||||
// Persists across level reloads.
|
||||
|
@ -19,10 +37,28 @@ void Savegame_UnbindSlot(void);
|
|||
// Returns the currently bound slot number. If there is none, returns -1.
|
||||
int32_t Savegame_GetBoundSlot(void);
|
||||
|
||||
void Savegame_ProcessItemsBeforeLoad(void);
|
||||
void Savegame_ProcessItemsBeforeSave(void);
|
||||
bool Savegame_Load(int32_t slot_num);
|
||||
bool Savegame_Save(int32_t slot_num);
|
||||
bool Savegame_UpdateDeathCounters(int32_t slot_num, int32_t death_count);
|
||||
bool Savegame_LoadOnlyResumeInfo(int32_t slot_num);
|
||||
|
||||
void Savegame_InitCurrentInfo(void);
|
||||
void Savegame_SetCurrentInfo(int32_t current_slot, int32_t src_slot);
|
||||
RESUME_INFO *Savegame_GetCurrentInfo(const GF_LEVEL *level);
|
||||
const SAVEGAME_INFO *Savegame_GetSavegameInfo(int32_t slot_num);
|
||||
void Savegame_ResetCurrentInfo(const GF_LEVEL *level);
|
||||
void Savegame_CarryCurrentInfoToNextLevel(
|
||||
const GF_LEVEL *src_level, const GF_LEVEL *dst_level);
|
||||
void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *level);
|
||||
|
||||
void Savegame_SetDefaultStats(const GF_LEVEL *level, STATS_COMMON stats);
|
||||
STATS_COMMON Savegame_GetDefaultStats(const GF_LEVEL *level);
|
||||
|
||||
extern int32_t Savegame_GetSlotCount(void);
|
||||
extern bool Savegame_IsSlotFree(int32_t slot_num);
|
||||
extern bool Savegame_Load(int32_t slot_num);
|
||||
extern bool Savegame_Save(int32_t slot_num);
|
||||
extern void Savegame_HighlightNewestSlot(void);
|
||||
extern void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *level);
|
||||
|
||||
#define REGISTER_SAVEGAME_STRATEGY(strategy_) \
|
||||
__attribute__((__constructor__)) static void M_Register(void) \
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
#define SAVEGAME_CURRENT_VERSION 8
|
||||
#define MAX_STRATEGIES 2
|
||||
|
|
|
@ -23,4 +23,3 @@ int32_t Lara_GetWaterDepth(int32_t x, int32_t y, int32_t z, int16_t room_num);
|
|||
void Lara_TestWaterDepth(ITEM *item, const COLL_INFO *coll);
|
||||
bool Lara_TestWaterStepOut(ITEM *item, const COLL_INFO *coll);
|
||||
bool Lara_TestWaterClimbOut(ITEM *item, COLL_INFO *coll);
|
||||
void Lara_CatchFire(void);
|
||||
|
|
|
@ -10,4 +10,3 @@ void LOT_InitialiseArray(void);
|
|||
void LOT_InitialiseSlot(int16_t item_num, int32_t slot);
|
||||
void LOT_CreateZone(ITEM *item);
|
||||
void LOT_InitialiseLOT(LOT_INFO *LOT);
|
||||
void LOT_ClearLOT(LOT_INFO *LOT);
|
||||
|
|
|
@ -4,47 +4,5 @@
|
|||
|
||||
#include <libtrx/game/savegame.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Loading a saved game is divided into two phases. First, the game reads the
|
||||
// savegame file contents to look for the level number. The rest of the save
|
||||
// data is stored in a special buffer in the g_GameInfo. Then the engine
|
||||
// continues to execute the normal game flow and loads the specified level.
|
||||
// Second phase occurs after everything finishes loading, e.g. items,
|
||||
// creatures, triggers etc., and is what actually sets Lara's health, creatures
|
||||
// status, triggers, inventory etc.
|
||||
|
||||
void Savegame_Init(void);
|
||||
void Savegame_Shutdown(void);
|
||||
bool Savegame_IsInitialised(void);
|
||||
|
||||
void Savegame_InitCurrentInfo(void);
|
||||
void Savegame_SetCurrentInfo(int32_t current_slot, int32_t src_slot);
|
||||
|
||||
int32_t Savegame_GetLevelNumber(int32_t slot_num);
|
||||
|
||||
bool Savegame_UpdateDeathCounters(int32_t slot_num, int32_t death_count);
|
||||
bool Savegame_LoadOnlyResumeInfo(int32_t slot_num);
|
||||
|
||||
void Savegame_ScanSavedGames(void);
|
||||
void Savegame_FillAvailableSaves(REQUEST_INFO *req);
|
||||
void Savegame_FillAvailableLevels(REQUEST_INFO *req);
|
||||
void Savegame_HighlightNewestSlot(void);
|
||||
bool Savegame_RestartAvailable(int32_t slot_num);
|
||||
|
||||
RESUME_INFO *Savegame_GetCurrentInfo(const GF_LEVEL *level);
|
||||
|
||||
void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *level);
|
||||
void Savegame_ResetCurrentInfo(const GF_LEVEL *level);
|
||||
void Savegame_CarryCurrentInfoToNextLevel(
|
||||
const GF_LEVEL *src_level, const GF_LEVEL *dst_level);
|
||||
|
||||
// Persist Lara's inventory to the current info.
|
||||
// Used to carry over Lara's inventory between levels.
|
||||
void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *level);
|
||||
|
||||
void Savegame_ProcessItemsBeforeLoad(void);
|
||||
void Savegame_ProcessItemsBeforeSave(void);
|
||||
|
||||
int32_t Savegame_GetCounter(void);
|
||||
int32_t Savegame_GetTotalCount(void);
|
||||
|
|
|
@ -3,272 +3,21 @@
|
|||
#include "game/game.h"
|
||||
#include "game/game_flow.h"
|
||||
#include "game/game_string.h"
|
||||
#include "game/inventory.h"
|
||||
#include "game/items.h"
|
||||
#include "game/lot.h"
|
||||
#include "game/objects/vars.h"
|
||||
#include "game/requester.h"
|
||||
#include "game/room.h"
|
||||
#include "global/const.h"
|
||||
#include "global/types.h"
|
||||
#include "global/vars.h"
|
||||
|
||||
#include <libtrx/benchmark.h>
|
||||
#include <libtrx/config.h>
|
||||
#include <libtrx/debug.h>
|
||||
#include <libtrx/enum_map.h>
|
||||
#include <libtrx/filesystem.h>
|
||||
#include <libtrx/game/gun/const.h>
|
||||
#include <libtrx/game/objects/traps/movable_block.h>
|
||||
#include <libtrx/memory.h>
|
||||
#include <libtrx/strings.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define SAVES_DIR "saves"
|
||||
|
||||
static int32_t m_SaveSlots = 0;
|
||||
static int16_t m_NewestSlot = -1;
|
||||
static int32_t m_SaveCounter = 0;
|
||||
static int32_t m_SavedGames = 0;
|
||||
static SAVEGAME_INFO *m_SavegameInfo = nullptr;
|
||||
static RESUME_INFO *m_ResumeInfo = nullptr;
|
||||
|
||||
static int32_t m_StrategyCount = 0;
|
||||
static SAVEGAME_STRATEGY m_Strategies[MAX_STRATEGIES];
|
||||
|
||||
static void M_ClearSlots(void);
|
||||
static bool M_FillSlot(
|
||||
const SAVEGAME_STRATEGY strategy, int32_t slot_num, const char *path);
|
||||
static void M_ScanSavedGamesDir(const char *dir_path);
|
||||
static void M_LoadPreprocess(void);
|
||||
static void M_LoadPostprocess(void);
|
||||
|
||||
static void M_ClearSlots(void)
|
||||
int32_t Savegame_GetSlotCount(void)
|
||||
{
|
||||
if (m_SavegameInfo == nullptr) {
|
||||
return;
|
||||
return g_Config.gameplay.maximum_save_slots;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < m_SaveSlots; i++) {
|
||||
SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[i];
|
||||
savegame_info->format = 0;
|
||||
savegame_info->counter = -1;
|
||||
savegame_info->level_num = -1;
|
||||
Memory_FreePointer(&savegame_info->full_path);
|
||||
Memory_FreePointer(&savegame_info->level_title);
|
||||
}
|
||||
}
|
||||
|
||||
static bool M_FillSlot(
|
||||
const SAVEGAME_STRATEGY strategy, const int32_t slot_num,
|
||||
const char *const path)
|
||||
void Savegame_HighlightNewestSlot(void)
|
||||
{
|
||||
SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num];
|
||||
if (strategy.format <= savegame_info->format) {
|
||||
// do not override already filled slots or for less preferred strategies
|
||||
return true;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
MYFILE *const fp = File_Open(path, FILE_OPEN_READ);
|
||||
if (fp != nullptr) {
|
||||
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;
|
||||
}
|
||||
File_Close(fp);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void M_ScanSavedGamesDir(const char *const dir_path)
|
||||
{
|
||||
void *dir_handle = File_OpenDirectory(dir_path);
|
||||
if (dir_handle == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const char *file_name = File_ReadDirectory(dir_handle);
|
||||
if (file_name == nullptr) {
|
||||
break;
|
||||
}
|
||||
if (strcmp(file_name, ".") == 0 || strcmp(file_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File_CloseDirectory(dir_handle);
|
||||
}
|
||||
|
||||
static void M_LoadPreprocess(void)
|
||||
{
|
||||
Savegame_InitCurrentInfo();
|
||||
}
|
||||
|
||||
static void M_LoadPostprocess(void)
|
||||
{
|
||||
for (int32_t i = 0; i < Item_GetLevelCount(); i++) {
|
||||
ITEM *const item = Item_Get(i);
|
||||
const OBJECT *const obj = Object_Get(item->object_id);
|
||||
|
||||
if (obj->save_position && obj->shadow_size) {
|
||||
int16_t room_num = item->room_num;
|
||||
const SECTOR *const sector = Room_GetSector(
|
||||
item->pos.x, item->pos.y, item->pos.z, &room_num);
|
||||
item->floor =
|
||||
Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (Game_GetBonusFlag() != GBF_NONE) {
|
||||
g_Config.profile.new_game_plus_unlock = true;
|
||||
}
|
||||
|
||||
LOT_ClearLOT(&g_Lara.lot);
|
||||
}
|
||||
|
||||
void Savegame_RegisterStrategy(const SAVEGAME_STRATEGY strategy)
|
||||
{
|
||||
ASSERT(m_StrategyCount < MAX_STRATEGIES);
|
||||
m_Strategies[m_StrategyCount] = strategy;
|
||||
m_StrategyCount++;
|
||||
}
|
||||
|
||||
void Savegame_Init(void)
|
||||
{
|
||||
m_ResumeInfo = Memory_Alloc(
|
||||
sizeof(RESUME_INFO)
|
||||
* (GF_GetLevelTable(GFLT_MAIN)->count
|
||||
+ (GF_GetLevelTable(GFLT_DEMOS)->count >= 0 ? 1 : 0)));
|
||||
m_SaveSlots = g_Config.gameplay.maximum_save_slots;
|
||||
m_SavegameInfo = Memory_Alloc(sizeof(SAVEGAME_INFO) * m_SaveSlots);
|
||||
|
||||
const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_DEMOS);
|
||||
for (int32_t i = 0; i < level_table->count; i++) {
|
||||
RESUME_INFO *const resume_info =
|
||||
Savegame_GetCurrentInfo(&level_table->levels[i]);
|
||||
resume_info->flags.available = 1;
|
||||
resume_info->flags.costume = 0;
|
||||
resume_info->small_medipacks = 0;
|
||||
resume_info->large_medipacks = 0;
|
||||
resume_info->num_scions = 0;
|
||||
resume_info->flags.has_pistols = 1;
|
||||
resume_info->flags.has_shotgun = 0;
|
||||
resume_info->flags.has_magnums = 0;
|
||||
resume_info->flags.has_uzis = 0;
|
||||
resume_info->pistol_ammo = 1000;
|
||||
resume_info->shotgun_ammo = 0;
|
||||
resume_info->magnum_ammo = 0;
|
||||
resume_info->uzi_ammo = 0;
|
||||
resume_info->gun_status = LGS_ARMLESS;
|
||||
resume_info->equipped_gun_type = LGT_PISTOLS;
|
||||
resume_info->holsters_gun_type = LGT_PISTOLS;
|
||||
resume_info->back_gun_type = LGT_UNARMED;
|
||||
resume_info->lara_hitpoints = LARA_MAX_HITPOINTS;
|
||||
}
|
||||
}
|
||||
|
||||
RESUME_INFO *Savegame_GetCurrentInfo(const GF_LEVEL *const level)
|
||||
{
|
||||
ASSERT(m_ResumeInfo != nullptr);
|
||||
ASSERT(level != nullptr);
|
||||
if (GF_GetLevelTableType(level->type) == GFLT_MAIN) {
|
||||
return &m_ResumeInfo[level->num];
|
||||
} else if (level->type == GFL_DEMO) {
|
||||
return &m_ResumeInfo[GF_GetLevelTable(GFLT_MAIN)->count];
|
||||
}
|
||||
LOG_WARNING(
|
||||
"Warning: unable to get resume info for level %d (type=%s)", level->num,
|
||||
ENUM_MAP_TO_STRING(GF_LEVEL_TYPE, level->type));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Savegame_SetCurrentInfo(const int32_t current_slot, const int32_t src_slot)
|
||||
{
|
||||
m_ResumeInfo[current_slot] = m_ResumeInfo[src_slot];
|
||||
}
|
||||
|
||||
void Savegame_Shutdown(void)
|
||||
{
|
||||
M_ClearSlots();
|
||||
Memory_FreePointer(&m_SavegameInfo);
|
||||
Memory_FreePointer(&m_ResumeInfo);
|
||||
}
|
||||
|
||||
bool Savegame_IsInitialised(void)
|
||||
{
|
||||
return m_SavegameInfo != nullptr;
|
||||
}
|
||||
|
||||
void Savegame_ProcessItemsBeforeLoad(void)
|
||||
{
|
||||
for (int32_t i = 0; i < Item_GetLevelCount(); i++) {
|
||||
ITEM *const item = Item_Get(i);
|
||||
const OBJECT *const obj = Object_Get(item->object_id);
|
||||
if (obj->handle_save_func != nullptr) {
|
||||
obj->handle_save_func(item, SAVEGAME_STAGE_BEFORE_LOAD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Savegame_ProcessItemsBeforeSave(void)
|
||||
{
|
||||
for (int32_t i = 0; i < Item_GetLevelCount(); i++) {
|
||||
ITEM *const item = Item_Get(i);
|
||||
const OBJECT *const obj = Object_Get(item->object_id);
|
||||
if (obj->handle_save_func != nullptr) {
|
||||
obj->handle_save_func(item, SAVEGAME_STAGE_BEFORE_SAVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Savegame_InitCurrentInfo(void)
|
||||
{
|
||||
const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN);
|
||||
for (int32_t i = 0; i < level_table->count; i++) {
|
||||
const GF_LEVEL *const level = &level_table->levels[i];
|
||||
Savegame_ResetCurrentInfo(level);
|
||||
Savegame_ApplyLogicToCurrentInfo(level);
|
||||
Savegame_GetCurrentInfo(level)->flags.available = 0;
|
||||
}
|
||||
if (GF_GetGymLevel() != nullptr) {
|
||||
Savegame_GetCurrentInfo(GF_GetGymLevel())->flags.available = 1;
|
||||
}
|
||||
if (GF_GetFirstLevel() != nullptr) {
|
||||
Savegame_GetCurrentInfo(GF_GetFirstLevel())->flags.available = 1;
|
||||
}
|
||||
g_SavegameRequester.requested = MAX(0, Savegame_GetHighestSlot());
|
||||
}
|
||||
|
||||
void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *const level)
|
||||
|
@ -368,266 +117,13 @@ void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *const level)
|
|||
}
|
||||
}
|
||||
|
||||
void Savegame_ResetCurrentInfo(const GF_LEVEL *const level)
|
||||
{
|
||||
LOG_INFO("Resetting resume info for level #%d", level->num);
|
||||
RESUME_INFO *const current = Savegame_GetCurrentInfo(level);
|
||||
memset(current, 0, sizeof(RESUME_INFO));
|
||||
}
|
||||
|
||||
void Savegame_CarryCurrentInfoToNextLevel(
|
||||
const GF_LEVEL *const src_level, const GF_LEVEL *const dst_level)
|
||||
{
|
||||
LOG_INFO(
|
||||
"Copying resume info from level #%d to level #%d", src_level->num,
|
||||
dst_level->num);
|
||||
RESUME_INFO *const src_resume = Savegame_GetCurrentInfo(src_level);
|
||||
RESUME_INFO *const dst_resume = Savegame_GetCurrentInfo(dst_level);
|
||||
memcpy(dst_resume, src_resume, sizeof(RESUME_INFO));
|
||||
}
|
||||
|
||||
void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *const level)
|
||||
{
|
||||
ASSERT(level != nullptr);
|
||||
RESUME_INFO *current = Savegame_GetCurrentInfo(level);
|
||||
|
||||
current->lara_hitpoints = g_LaraItem->hit_points;
|
||||
current->flags.available = 1;
|
||||
|
||||
current->pistol_ammo = 1000;
|
||||
if (Inv_RequestItem(O_PISTOL_ITEM)) {
|
||||
current->flags.has_pistols = 1;
|
||||
} else {
|
||||
current->flags.has_pistols = 0;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_MAGNUM_ITEM)) {
|
||||
current->magnum_ammo = g_Lara.magnum_ammo.ammo;
|
||||
current->flags.has_magnums = 1;
|
||||
} else {
|
||||
current->magnum_ammo =
|
||||
Inv_RequestItem(O_MAGNUM_AMMO_ITEM) * MAGNUM_AMMO_QTY;
|
||||
current->flags.has_magnums = 0;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_UZI_ITEM)) {
|
||||
current->uzi_ammo = g_Lara.uzi_ammo.ammo;
|
||||
current->flags.has_uzis = 1;
|
||||
} else {
|
||||
current->uzi_ammo = Inv_RequestItem(O_UZI_AMMO_ITEM) * UZI_AMMO_QTY;
|
||||
current->flags.has_uzis = 0;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_SHOTGUN_ITEM)) {
|
||||
current->shotgun_ammo = g_Lara.shotgun_ammo.ammo;
|
||||
current->flags.has_shotgun = 1;
|
||||
} else {
|
||||
current->shotgun_ammo =
|
||||
Inv_RequestItem(O_SHOTGUN_AMMO_ITEM) * SHOTGUN_AMMO_QTY;
|
||||
current->flags.has_shotgun = 0;
|
||||
}
|
||||
|
||||
current->small_medipacks = Inv_RequestItem(O_SMALL_MEDIPACK_ITEM);
|
||||
current->large_medipacks = Inv_RequestItem(O_LARGE_MEDIPACK_ITEM);
|
||||
current->num_scions = Inv_RequestItem(O_SCION_ITEM_1);
|
||||
|
||||
current->equipped_gun_type = g_Lara.gun_type;
|
||||
current->holsters_gun_type = g_Lara.holsters_gun_type;
|
||||
current->back_gun_type = g_Lara.back_gun_type;
|
||||
if (g_Lara.gun_status == LGS_READY) {
|
||||
current->gun_status = LGS_READY;
|
||||
} else {
|
||||
current->gun_status = LGS_ARMLESS;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Savegame_GetLevelNumber(const int32_t slot_num)
|
||||
{
|
||||
return m_SavegameInfo[slot_num].level_num;
|
||||
}
|
||||
|
||||
int32_t Savegame_GetSlotCount(void)
|
||||
{
|
||||
return m_SaveSlots;
|
||||
}
|
||||
|
||||
bool Savegame_IsSlotFree(const int32_t slot_num)
|
||||
{
|
||||
return m_SavegameInfo[slot_num].level_num == -1;
|
||||
}
|
||||
|
||||
bool Savegame_Load(const int32_t slot_num)
|
||||
{
|
||||
const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num];
|
||||
ASSERT(savegame_info->format != SAVEGAME_FORMAT_INVALID);
|
||||
|
||||
M_LoadPreprocess();
|
||||
|
||||
bool ret = false;
|
||||
for (int32_t i = 0; i < m_StrategyCount; i++) {
|
||||
const SAVEGAME_STRATEGY strategy = m_Strategies[i];
|
||||
if (savegame_info->format == strategy.format) {
|
||||
MYFILE *const fp =
|
||||
File_Open(savegame_info->full_path, FILE_OPEN_READ);
|
||||
if (fp != nullptr) {
|
||||
ret = strategy.load_from_file_func(fp);
|
||||
File_Close(fp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
M_LoadPostprocess();
|
||||
}
|
||||
|
||||
Savegame_SetInitialVersion(m_SavegameInfo[slot_num].initial_version);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Savegame_Save(const int32_t slot_num)
|
||||
{
|
||||
bool ret = false;
|
||||
Savegame_BindSlot(slot_num);
|
||||
|
||||
File_CreateDirectory(SAVES_DIR);
|
||||
|
||||
const GF_LEVEL *const current_level = Game_GetCurrentLevel();
|
||||
Savegame_PersistGameToCurrentInfo(current_level);
|
||||
|
||||
const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN);
|
||||
for (int32_t i = 0; i < level_table->count; i++) {
|
||||
const GF_LEVEL *const level = &level_table->levels[i];
|
||||
if (level->type == GFL_CURRENT) {
|
||||
Savegame_SetCurrentInfo(i, current_level->num);
|
||||
}
|
||||
}
|
||||
const char *const level_title = Game_GetCurrentLevel()->title;
|
||||
|
||||
SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num];
|
||||
const bool was_slot_empty = savegame_info->full_path == nullptr;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
char *filename =
|
||||
String_Format(strategy.get_save_file_pattern_func(), slot_num);
|
||||
char *full_path = String_Format("%s/%s", SAVES_DIR, filename);
|
||||
|
||||
MYFILE *const fp = File_Open(full_path, FILE_OPEN_WRITE);
|
||||
if (fp != nullptr) {
|
||||
strategy.save_to_file_func(fp, savegame_info);
|
||||
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);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
Memory_FreePointer(&filename);
|
||||
Memory_FreePointer(&full_path);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
m_NewestSlot = slot_num;
|
||||
if (was_slot_empty) {
|
||||
m_SavedGames++;
|
||||
}
|
||||
Savegame_HighlightNewestSlot();
|
||||
} else {
|
||||
m_SaveCounter--;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Savegame_UpdateDeathCounters(
|
||||
const int32_t slot_num, const int32_t death_count)
|
||||
{
|
||||
ASSERT(slot_num >= 0);
|
||||
const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num];
|
||||
ASSERT(savegame_info->format != SAVEGAME_FORMAT_INVALID);
|
||||
|
||||
bool ret = false;
|
||||
for (int32_t i = 0; i < m_StrategyCount; i++) {
|
||||
const SAVEGAME_STRATEGY strategy = m_Strategies[i];
|
||||
if (savegame_info->format == strategy.format
|
||||
&& strategy.update_death_counters_func != nullptr) {
|
||||
MYFILE *const fp =
|
||||
File_Open(savegame_info->full_path, FILE_OPEN_READ_WRITE);
|
||||
if (fp != nullptr) {
|
||||
ret = strategy.update_death_counters_func(fp, death_count);
|
||||
File_Close(fp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Savegame_LoadOnlyResumeInfo(int32_t slot_num)
|
||||
{
|
||||
const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num];
|
||||
ASSERT(savegame_info->format != SAVEGAME_FORMAT_INVALID);
|
||||
|
||||
bool ret = false;
|
||||
for (int32_t i = 0; i < m_StrategyCount; i++) {
|
||||
const SAVEGAME_STRATEGY strategy = m_Strategies[i];
|
||||
if (savegame_info->format == strategy.format) {
|
||||
MYFILE *const fp =
|
||||
File_Open(savegame_info->full_path, FILE_OPEN_READ);
|
||||
if (fp != nullptr) {
|
||||
ret = strategy.load_only_resume_info_func(fp);
|
||||
File_Close(fp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Savegame_SetInitialVersion(m_SavegameInfo[slot_num].initial_version);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Savegame_ScanSavedGames(void)
|
||||
{
|
||||
BENCHMARK benchmark = Benchmark_Start();
|
||||
M_ClearSlots();
|
||||
|
||||
m_SaveCounter = 0;
|
||||
m_SavedGames = 0;
|
||||
m_NewestSlot = -1;
|
||||
|
||||
M_ScanSavedGamesDir(SAVES_DIR);
|
||||
M_ScanSavedGamesDir(".");
|
||||
|
||||
for (int32_t i = 0; i < m_SaveSlots; i++) {
|
||||
SAVEGAME_INFO *savegame_info = &m_SavegameInfo[i];
|
||||
if (savegame_info->level_title != nullptr) {
|
||||
if (savegame_info->counter > m_SaveCounter) {
|
||||
m_SaveCounter = savegame_info->counter;
|
||||
m_NewestSlot = i;
|
||||
}
|
||||
m_SavedGames++;
|
||||
}
|
||||
}
|
||||
Benchmark_End(&benchmark, nullptr);
|
||||
}
|
||||
|
||||
void Savegame_FillAvailableSaves(REQUEST_INFO *req)
|
||||
void Savegame_FillAvailableSaves(REQUEST_INFO *const req)
|
||||
{
|
||||
Requester_ClearTextstrings(req);
|
||||
Requester_Init(req, Savegame_GetSlotCount());
|
||||
|
||||
for (int i = 0; i < req->max_items; i++) {
|
||||
SAVEGAME_INFO *savegame_info = &m_SavegameInfo[i];
|
||||
for (int32_t i = 0; i < req->max_items; i++) {
|
||||
const SAVEGAME_INFO *const savegame_info = Savegame_GetSavegameInfo(i);
|
||||
|
||||
if (savegame_info->level_title != nullptr) {
|
||||
Requester_AddItem(
|
||||
|
@ -645,7 +141,7 @@ void Savegame_FillAvailableSaves(REQUEST_INFO *req)
|
|||
}
|
||||
}
|
||||
|
||||
void Savegame_FillAvailableLevels(REQUEST_INFO *req)
|
||||
void Savegame_FillAvailableLevels(REQUEST_INFO *const req)
|
||||
{
|
||||
ASSERT(req != nullptr);
|
||||
const int32_t slot_num = g_GameInfo.select_save_slot;
|
||||
|
@ -653,7 +149,8 @@ void Savegame_FillAvailableLevels(REQUEST_INFO *req)
|
|||
return;
|
||||
}
|
||||
|
||||
const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_num];
|
||||
const SAVEGAME_INFO *const savegame_info =
|
||||
Savegame_GetSavegameInfo(slot_num);
|
||||
if (!savegame_info->features.select_level) {
|
||||
Requester_AddItem(req, true, "%s", GS(PASSPORT_LEGACY_SELECT_LEVEL_1));
|
||||
Requester_AddItem(req, true, "%s", GS(PASSPORT_LEGACY_SELECT_LEVEL_2));
|
||||
|
@ -678,28 +175,3 @@ void Savegame_FillAvailableLevels(REQUEST_INFO *req)
|
|||
req->requested = 0;
|
||||
req->line_offset = 0;
|
||||
}
|
||||
|
||||
void Savegame_HighlightNewestSlot(void)
|
||||
{
|
||||
g_SavegameRequester.requested = MAX(0, m_NewestSlot);
|
||||
}
|
||||
|
||||
bool Savegame_RestartAvailable(int32_t slot_num)
|
||||
{
|
||||
if (slot_num == -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SAVEGAME_INFO *savegame_info = &m_SavegameInfo[slot_num];
|
||||
return savegame_info->features.restart;
|
||||
}
|
||||
|
||||
int32_t Savegame_GetCounter(void)
|
||||
{
|
||||
return m_SaveCounter;
|
||||
}
|
||||
|
||||
int32_t Savegame_GetTotalCount(void)
|
||||
{
|
||||
return m_SavedGames;
|
||||
}
|
||||
|
|
|
@ -72,8 +72,6 @@ void Lara_SwimCollision(ITEM *item, COLL_INFO *coll);
|
|||
|
||||
void Lara_WaterCurrent(COLL_INFO *coll);
|
||||
|
||||
void Lara_CatchFire(void);
|
||||
|
||||
void Lara_TouchLava(ITEM *item);
|
||||
|
||||
// Returns true if Lara has the M16 equipped and is in either anim state: 0
|
||||
|
|
|
@ -7,4 +7,3 @@
|
|||
void LOT_InitialiseArray(void);
|
||||
void LOT_InitialiseSlot(int16_t item_num, int32_t slot);
|
||||
void LOT_CreateZone(ITEM *item);
|
||||
void LOT_ClearLOT(LOT_INFO *LOT);
|
||||
|
|
|
@ -1,32 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "game/game_flow/types.h"
|
||||
#include "global/types.h"
|
||||
|
||||
#include <libtrx/filesystem.h>
|
||||
#include <libtrx/game/savegame.h>
|
||||
|
||||
void Savegame_Init(void);
|
||||
void Savegame_Shutdown(void);
|
||||
|
||||
void Savegame_InitCurrentInfo(void);
|
||||
|
||||
void Savegame_ResetCurrentInfo(const GF_LEVEL *level);
|
||||
RESUME_INFO *Savegame_GetCurrentInfo(const GF_LEVEL *level);
|
||||
void Savegame_CarryCurrentInfoToNextLevel(
|
||||
const GF_LEVEL *src_level, const GF_LEVEL *dst_level);
|
||||
void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *level);
|
||||
void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *level);
|
||||
|
||||
void Savegame_ScanSavedGames(void);
|
||||
void Savegame_FillAvailableSaves(REQUEST_INFO *req);
|
||||
void Savegame_FillAvailableLevels(REQUEST_INFO *req);
|
||||
void Savegame_HighlightNewestSlot(void);
|
||||
int32_t Savegame_GetLevelNumber(int32_t slot_idx);
|
||||
int32_t Savegame_GetCounter(void);
|
||||
int32_t Savegame_GetTotalCount(void);
|
||||
|
||||
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);
|
||||
|
|
|
@ -1,337 +1,31 @@
|
|||
#include "game/game.h"
|
||||
#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/types_decomp.h"
|
||||
#include "global/vars.h"
|
||||
|
||||
#include <libtrx/benchmark.h>
|
||||
#include <libtrx/debug.h>
|
||||
#include <libtrx/enum_map.h>
|
||||
#include <libtrx/filesystem.h>
|
||||
#include <libtrx/game/gun/const.h>
|
||||
#include <libtrx/game/lara.h>
|
||||
#include <libtrx/game/objects/traps/movable_block.h>
|
||||
#include <libtrx/game/savegame.h>
|
||||
#include <libtrx/log.h>
|
||||
#include <libtrx/memory.h>
|
||||
#include <libtrx/strings.h>
|
||||
#include <libtrx/utils.h>
|
||||
|
||||
#define SAVES_DIR "saves"
|
||||
|
||||
static STATS_COMMON *m_DefaultStats = nullptr;
|
||||
static RESUME_INFO *m_ResumeInfos = nullptr;
|
||||
static int32_t m_SaveSlots = 0;
|
||||
static int32_t m_NewestSlot = -1;
|
||||
static int32_t m_SaveCounter = 0;
|
||||
static int32_t m_SavedGames = 0;
|
||||
static SAVEGAME_INFO *m_SavegameInfo = nullptr;
|
||||
// TODO: make configurable
|
||||
#define MAX_SAVE_SLOTS MAX_REQUESTER_ITEMS
|
||||
|
||||
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 void M_ClearSlots(void);
|
||||
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)
|
||||
{
|
||||
if (m_SavegameInfo == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
Memory_FreePointer(&savegame_info->level_title);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
File_Close(fp);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void M_ScanSavedGamesDir(const char *const dir_path)
|
||||
{
|
||||
void *dir_handle = File_OpenDirectory(dir_path);
|
||||
if (dir_handle == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const char *file_name = File_ReadDirectory(dir_handle);
|
||||
if (file_name == nullptr) {
|
||||
break;
|
||||
}
|
||||
if (strcmp(file_name, ".") == 0 || strcmp(file_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File_CloseDirectory(dir_handle);
|
||||
}
|
||||
|
||||
static void M_LoadPreprocess(void)
|
||||
{
|
||||
Savegame_InitCurrentInfo();
|
||||
}
|
||||
|
||||
static void M_LoadPostprocess(void)
|
||||
{
|
||||
for (int32_t i = 0; i < Item_GetLevelCount(); i++) {
|
||||
ITEM *const item = Item_Get(i);
|
||||
const OBJECT *const obj = Object_Get(item->object_id);
|
||||
|
||||
if (obj->save_position && obj->shadow_size != 0) {
|
||||
int16_t room_num = item->room_num;
|
||||
const SECTOR *const sector = Room_GetSector(
|
||||
item->pos.x, item->pos.y, item->pos.z, &room_num);
|
||||
item->floor =
|
||||
Room_GetHeight(sector, item->pos.x, item->pos.y, item->pos.z);
|
||||
}
|
||||
|
||||
if (obj->save_flags != 0) {
|
||||
item->flags &= 0xFF00;
|
||||
}
|
||||
}
|
||||
|
||||
MovableBlock_SetupFloor();
|
||||
|
||||
LARA_INFO *const lara = Lara_GetLaraInfo();
|
||||
if (lara->burn) {
|
||||
lara->burn = 0;
|
||||
Lara_CatchFire();
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
sizeof(RESUME_INFO)
|
||||
* (GF_GetLevelTable(GFLT_MAIN)->count
|
||||
+ GF_GetLevelTable(GFLT_DEMOS)->count));
|
||||
|
||||
m_SaveSlots = MAX_SAVE_SLOTS; // TODO: make configurable
|
||||
m_SavegameInfo = Memory_Alloc(sizeof(SAVEGAME_INFO) * m_SaveSlots);
|
||||
|
||||
const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_DEMOS);
|
||||
for (int32_t i = 0; i < level_table->count; i++) {
|
||||
RESUME_INFO *const resume_info =
|
||||
Savegame_GetCurrentInfo(&level_table->levels[i]);
|
||||
resume_info->flags.available = 1;
|
||||
resume_info->flags.has_pistols = 1;
|
||||
resume_info->pistol_ammo = 1000;
|
||||
resume_info->gun_status = LGS_ARMLESS;
|
||||
resume_info->equipped_gun_type = LGT_PISTOLS;
|
||||
}
|
||||
}
|
||||
|
||||
void Savegame_Shutdown(void)
|
||||
{
|
||||
M_ClearSlots();
|
||||
Memory_FreePointer(&m_ResumeInfos);
|
||||
Memory_FreePointer(&m_SavegameInfo);
|
||||
Memory_FreePointer(&m_DefaultStats);
|
||||
}
|
||||
|
||||
int32_t Savegame_GetSlotCount(void)
|
||||
{
|
||||
return MAX_SAVE_SLOTS;
|
||||
}
|
||||
|
||||
bool Savegame_IsSlotFree(const int32_t slot_idx)
|
||||
{
|
||||
return m_SavegameInfo[slot_idx].level_num == -1;
|
||||
}
|
||||
|
||||
int32_t Savegame_GetLevelNumber(const int32_t slot_idx)
|
||||
{
|
||||
return m_SavegameInfo[slot_idx].level_num;
|
||||
}
|
||||
|
||||
void Savegame_ScanSavedGames(void)
|
||||
{
|
||||
BENCHMARK benchmark = Benchmark_Start();
|
||||
M_ClearSlots();
|
||||
|
||||
m_SaveCounter = 0;
|
||||
m_SavedGames = 0;
|
||||
m_NewestSlot = -1;
|
||||
|
||||
M_ScanSavedGamesDir(SAVES_DIR);
|
||||
M_ScanSavedGamesDir(".");
|
||||
|
||||
for (int32_t i = 0; i < m_SaveSlots; i++) {
|
||||
SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[i];
|
||||
if (savegame_info->level_title != nullptr) {
|
||||
if (savegame_info->counter > m_SaveCounter) {
|
||||
m_SaveCounter = savegame_info->counter;
|
||||
m_NewestSlot = i;
|
||||
}
|
||||
m_SavedGames++;
|
||||
}
|
||||
}
|
||||
|
||||
Benchmark_End(&benchmark, nullptr);
|
||||
}
|
||||
|
||||
void Savegame_FillAvailableSaves(REQUEST_INFO *const req)
|
||||
{
|
||||
Requester_Init(req);
|
||||
|
||||
for (int32_t i = 0; i < MAX_SAVE_SLOTS; i++) {
|
||||
const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[i];
|
||||
if (savegame_info->level_title != nullptr) {
|
||||
char save_num_text[16];
|
||||
sprintf(save_num_text, "%d", savegame_info->counter);
|
||||
Requester_AddItem(
|
||||
req, savegame_info->level_title, REQ_ALIGN_LEFT, save_num_text,
|
||||
REQ_ALIGN_RIGHT);
|
||||
} else {
|
||||
Requester_AddItem(req, GS(MISC_EMPTY_SLOT), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Requester_SetSize(req, 10, -32);
|
||||
if (req->selected >= req->visible_count) {
|
||||
req->line_offset = req->selected - req->visible_count + 1;
|
||||
} else if (req->selected < req->line_offset) {
|
||||
req->line_offset = req->selected;
|
||||
}
|
||||
memcpy(m_ReqFlags1, g_RequesterFlags1, sizeof(m_ReqFlags1));
|
||||
memcpy(m_ReqFlags2, g_RequesterFlags2, sizeof(m_ReqFlags2));
|
||||
}
|
||||
|
||||
void Savegame_FillAvailableLevels(REQUEST_INFO *const req)
|
||||
{
|
||||
ASSERT(req != nullptr);
|
||||
Requester_Init(req);
|
||||
Requester_SetSize(req, 10, -32);
|
||||
Requester_SetHeading(req, GS(PASSPORT_SELECT_LEVEL), 0, nullptr, 0);
|
||||
req->ready = true;
|
||||
req->selected = 0;
|
||||
|
||||
Requester_RemoveAllItems(req);
|
||||
const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN);
|
||||
for (int32_t i = 0; i < level_table->count; i++) {
|
||||
const GF_LEVEL *const level = &level_table->levels[i];
|
||||
if (level->type != GFL_GYM) {
|
||||
Requester_AddItem(req, level->title, 0, nullptr, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Savegame_HighlightNewestSlot(void)
|
||||
{
|
||||
g_SaveGameRequester.selected = MAX(0, m_NewestSlot);
|
||||
g_LoadGameRequester.selected = MAX(0, m_NewestSlot);
|
||||
}
|
||||
|
||||
int32_t Savegame_GetCounter(void)
|
||||
{
|
||||
return m_SaveCounter;
|
||||
}
|
||||
|
||||
int32_t Savegame_GetTotalCount(void)
|
||||
{
|
||||
return m_SavedGames;
|
||||
}
|
||||
|
||||
void Savegame_ProcessItemsBeforeSave(void)
|
||||
{
|
||||
for (int32_t i = 0; i < Item_GetLevelCount(); i++) {
|
||||
ITEM *const item = Item_Get(i);
|
||||
const OBJECT *const obj = Object_Get(item->object_id);
|
||||
if (obj->handle_save_func != nullptr) {
|
||||
obj->handle_save_func(item, SAVEGAME_STAGE_BEFORE_SAVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Savegame_ProcessItemsBeforeLoad(void)
|
||||
{
|
||||
for (int32_t i = 0; i < Item_GetLevelCount(); i++) {
|
||||
ITEM *const item = Item_Get(i);
|
||||
const OBJECT *const obj = Object_Get(item->object_id);
|
||||
if (obj->handle_save_func != nullptr) {
|
||||
obj->handle_save_func(item, SAVEGAME_STAGE_BEFORE_LOAD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Savegame_InitCurrentInfo(void)
|
||||
{
|
||||
if (Game_IsBonusFlagSet(GBF_NGPLUS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN);
|
||||
for (int32_t i = 0; i < level_table->count; i++) {
|
||||
const GF_LEVEL *const level = &level_table->levels[i];
|
||||
Savegame_ResetCurrentInfo(level);
|
||||
Savegame_ApplyLogicToCurrentInfo(level);
|
||||
Savegame_GetCurrentInfo(level)->flags.available = 0;
|
||||
}
|
||||
|
||||
if (GF_GetGymLevel() != nullptr) {
|
||||
Savegame_GetCurrentInfo(GF_GetGymLevel())->flags.available = 1;
|
||||
}
|
||||
if (GF_GetFirstLevel() != nullptr) {
|
||||
Savegame_GetCurrentInfo(GF_GetFirstLevel())->flags.available = 1;
|
||||
}
|
||||
Game_SetBonusFlag(GBF_NONE);
|
||||
const int32_t slot = Savegame_GetHighestSlot();
|
||||
g_SaveGameRequester.selected = MAX(0, slot);
|
||||
g_LoadGameRequester.selected = MAX(0, slot);
|
||||
}
|
||||
|
||||
void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *const level)
|
||||
|
@ -445,214 +139,48 @@ void Savegame_ApplyLogicToCurrentInfo(const GF_LEVEL *const level)
|
|||
resume->stats.max_secret_count = default_stats.max_secret_count;
|
||||
}
|
||||
|
||||
void Savegame_ResetCurrentInfo(const GF_LEVEL *const level)
|
||||
void Savegame_FillAvailableSaves(REQUEST_INFO *const req)
|
||||
{
|
||||
RESUME_INFO *const current = Savegame_GetCurrentInfo(level);
|
||||
memset(current, 0, sizeof(RESUME_INFO));
|
||||
Requester_Init(req);
|
||||
|
||||
for (int32_t i = 0; i < MAX_SAVE_SLOTS; i++) {
|
||||
const SAVEGAME_INFO *const savegame_info = Savegame_GetSavegameInfo(i);
|
||||
if (savegame_info->level_title != nullptr) {
|
||||
char save_num_text[16];
|
||||
sprintf(save_num_text, "%d", savegame_info->counter);
|
||||
Requester_AddItem(
|
||||
req, savegame_info->level_title, REQ_ALIGN_LEFT, save_num_text,
|
||||
REQ_ALIGN_RIGHT);
|
||||
} else {
|
||||
Requester_AddItem(req, GS(MISC_EMPTY_SLOT), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Savegame_CarryCurrentInfoToNextLevel(
|
||||
const GF_LEVEL *const src_level, const GF_LEVEL *const dst_level)
|
||||
Requester_SetSize(req, 10, -32);
|
||||
if (req->selected >= req->visible_count) {
|
||||
req->line_offset = req->selected - req->visible_count + 1;
|
||||
} else if (req->selected < req->line_offset) {
|
||||
req->line_offset = req->selected;
|
||||
}
|
||||
memcpy(m_ReqFlags1, g_RequesterFlags1, sizeof(m_ReqFlags1));
|
||||
memcpy(m_ReqFlags2, g_RequesterFlags2, sizeof(m_ReqFlags2));
|
||||
}
|
||||
|
||||
void Savegame_FillAvailableLevels(REQUEST_INFO *const req)
|
||||
{
|
||||
LOG_INFO(
|
||||
"Copying resume info from level #%d to level #%d", src_level->num,
|
||||
dst_level->num);
|
||||
RESUME_INFO *const src_resume = Savegame_GetCurrentInfo(src_level);
|
||||
RESUME_INFO *const dst_resume = Savegame_GetCurrentInfo(dst_level);
|
||||
memcpy(dst_resume, src_resume, sizeof(RESUME_INFO));
|
||||
ASSERT(req != nullptr);
|
||||
Requester_Init(req);
|
||||
Requester_SetSize(req, 10, -32);
|
||||
Requester_SetHeading(req, GS(PASSPORT_SELECT_LEVEL), 0, nullptr, 0);
|
||||
req->ready = true;
|
||||
req->selected = 0;
|
||||
|
||||
Requester_RemoveAllItems(req);
|
||||
const GF_LEVEL_TABLE *const level_table = GF_GetLevelTable(GFLT_MAIN);
|
||||
for (int32_t i = 0; i < level_table->count; i++) {
|
||||
const GF_LEVEL *const level = &level_table->levels[i];
|
||||
if (level->type != GFL_GYM) {
|
||||
Requester_AddItem(req, level->title, 0, nullptr, 0);
|
||||
}
|
||||
|
||||
void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *const level)
|
||||
{
|
||||
RESUME_INFO *const resume = Savegame_GetCurrentInfo(level);
|
||||
|
||||
resume->flags.available = 1;
|
||||
|
||||
if (Inv_RequestItem(O_PISTOL_ITEM)) {
|
||||
resume->flags.has_pistols = 1;
|
||||
resume->pistol_ammo = 1000;
|
||||
} else {
|
||||
resume->flags.has_pistols = 0;
|
||||
resume->pistol_ammo = 1000;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_SHOTGUN_ITEM)) {
|
||||
resume->flags.has_shotgun = 1;
|
||||
resume->shotgun_ammo = g_Lara.shotgun_ammo.ammo;
|
||||
} else {
|
||||
resume->flags.has_shotgun = 0;
|
||||
resume->shotgun_ammo =
|
||||
Inv_RequestItem(O_SHOTGUN_AMMO_ITEM) * SHOTGUN_AMMO_QTY;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_MAGNUM_ITEM)) {
|
||||
resume->flags.has_magnums = 1;
|
||||
resume->magnum_ammo = g_Lara.magnum_ammo.ammo;
|
||||
} else {
|
||||
resume->flags.has_magnums = 0;
|
||||
resume->magnum_ammo =
|
||||
Inv_RequestItem(O_MAGNUM_AMMO_ITEM) * MAGNUM_AMMO_QTY;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_UZI_ITEM)) {
|
||||
resume->flags.has_uzis = 1;
|
||||
resume->uzi_ammo = g_Lara.uzi_ammo.ammo;
|
||||
} else {
|
||||
resume->flags.has_uzis = 0;
|
||||
resume->uzi_ammo = Inv_RequestItem(O_UZI_AMMO_ITEM) * UZI_AMMO_QTY;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_M16_ITEM)) {
|
||||
resume->flags.has_m16 = 1;
|
||||
resume->m16_ammo = g_Lara.m16_ammo.ammo;
|
||||
} else {
|
||||
resume->flags.has_m16 = 0;
|
||||
resume->m16_ammo = Inv_RequestItem(O_M16_AMMO_ITEM) * M16_AMMO_QTY;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_HARPOON_ITEM)) {
|
||||
resume->flags.has_harpoon = 1;
|
||||
resume->harpoon_ammo = g_Lara.harpoon_ammo.ammo;
|
||||
} else {
|
||||
resume->flags.has_harpoon = 0;
|
||||
resume->harpoon_ammo =
|
||||
Inv_RequestItem(O_HARPOON_AMMO_ITEM) * HARPOON_AMMO_QTY;
|
||||
}
|
||||
|
||||
if (Inv_RequestItem(O_GRENADE_ITEM)) {
|
||||
resume->flags.has_grenade = 1;
|
||||
resume->grenade_ammo = g_Lara.grenade_ammo.ammo;
|
||||
} else {
|
||||
resume->flags.has_grenade = 0;
|
||||
resume->grenade_ammo =
|
||||
Inv_RequestItem(O_GRENADE_AMMO_ITEM) * GRENADE_AMMO_QTY;
|
||||
}
|
||||
|
||||
resume->flares = Inv_RequestItem(O_FLARE_ITEM);
|
||||
resume->small_medipacks = Inv_RequestItem(O_SMALL_MEDIPACK_ITEM);
|
||||
resume->large_medipacks = Inv_RequestItem(O_LARGE_MEDIPACK_ITEM);
|
||||
|
||||
if (g_Lara.gun_type == LGT_FLARE) {
|
||||
resume->equipped_gun_type = g_Lara.last_gun_type;
|
||||
} else {
|
||||
resume->equipped_gun_type = g_Lara.gun_type;
|
||||
}
|
||||
resume->gun_status = LGS_ARMLESS;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Savegame_PersistGameToCurrentInfo(current_level);
|
||||
|
||||
SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_idx];
|
||||
const bool was_slot_empty = savegame_info->full_path == nullptr;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
char *file_name =
|
||||
String_Format(strategy.get_save_file_pattern_func(), slot_idx);
|
||||
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);
|
||||
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(&full_path);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
m_NewestSlot = slot_idx;
|
||||
if (was_slot_empty) {
|
||||
m_SavedGames++;
|
||||
}
|
||||
Savegame_HighlightNewestSlot();
|
||||
} else {
|
||||
m_SaveCounter--;
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
M_LoadPostprocess();
|
||||
Savegame_SetInitialVersion(m_SavegameInfo[slot_idx].initial_version);
|
||||
return result;
|
||||
}
|
||||
|
||||
RESUME_INFO *Savegame_GetCurrentInfo(const GF_LEVEL *const level)
|
||||
{
|
||||
ASSERT(m_ResumeInfos != nullptr);
|
||||
ASSERT(level != nullptr);
|
||||
if (GF_GetLevelTableType(level->type) == GFLT_MAIN) {
|
||||
return &m_ResumeInfos[level->num];
|
||||
} else if (level->type == GFL_DEMO) {
|
||||
return &m_ResumeInfos[GF_GetLevelTable(GFLT_MAIN)->count];
|
||||
}
|
||||
LOG_WARNING(
|
||||
"Warning: unable to get resume info for level %d (type=%s)", level->num,
|
||||
ENUM_MAP_TO_STRING(GF_LEVEL_TYPE, level->type));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Savegame_SetDefaultStats(
|
||||
const GF_LEVEL *const level, const STATS_COMMON stats)
|
||||
{
|
||||
if (m_DefaultStats == nullptr) {
|
||||
m_DefaultStats = Memory_Alloc(
|
||||
sizeof(STATS_COMMON) * GF_GetLevelTable(GFLT_MAIN)->count);
|
||||
}
|
||||
m_DefaultStats[level->num] = stats;
|
||||
}
|
||||
|
||||
STATS_COMMON Savegame_GetDefaultStats(const GF_LEVEL *const level)
|
||||
{
|
||||
if (m_DefaultStats == nullptr
|
||||
|| (level->type != GFL_NORMAL && level->type != GFL_BONUS)) {
|
||||
return (STATS_COMMON) {};
|
||||
}
|
||||
return m_DefaultStats[level->num];
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
#define MAX_LEVEL_NAME_SIZE 50 // TODO: get rid of this limit
|
||||
#define MAX_DEMO_FILES MAX_LEVELS
|
||||
#define MAX_REQUESTER_ITEMS 24
|
||||
#define MAX_SAVE_SLOTS 16
|
||||
|
||||
#define DEATH_WAIT (5 * 2 * FRAMES_PER_SECOND) // = 300
|
||||
#define DEATH_WAIT_INPUT (2 * FRAMES_PER_SECOND) // = 60
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue