diff --git a/src/libtrx/game/savegame/common.c b/src/libtrx/game/savegame/common.c index ab4be10d0..24c2250a1 100644 --- a/src/libtrx/game/savegame/common.c +++ b/src/libtrx/game/savegame/common.c @@ -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; +} diff --git a/src/libtrx/include/libtrx/game/lara/misc.h b/src/libtrx/include/libtrx/game/lara/misc.h index 16dc3b960..051374e7c 100644 --- a/src/libtrx/include/libtrx/game/lara/misc.h +++ b/src/libtrx/include/libtrx/game/lara/misc.h @@ -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); diff --git a/src/libtrx/include/libtrx/game/objects/types.h b/src/libtrx/include/libtrx/game/objects/types.h index ec4ffcdf7..81c29034d 100644 --- a/src/libtrx/include/libtrx/game/objects/types.h +++ b/src/libtrx/include/libtrx/game/objects/types.h @@ -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 diff --git a/src/libtrx/include/libtrx/game/pathing/lot.h b/src/libtrx/include/libtrx/game/pathing/lot.h index a130f3741..262bdaafa 100644 --- a/src/libtrx/include/libtrx/game/pathing/lot.h +++ b/src/libtrx/include/libtrx/game/pathing/lot.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); diff --git a/src/libtrx/include/libtrx/game/savegame/common.h b/src/libtrx/include/libtrx/game/savegame/common.h index 581e351b7..ef3b4d2c9 100644 --- a/src/libtrx/include/libtrx/game/savegame/common.h +++ b/src/libtrx/include/libtrx/game/savegame/common.h @@ -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) \ diff --git a/src/libtrx/include/libtrx/game/savegame/const.h b/src/libtrx/include/libtrx/game/savegame/const.h index 04353dcc4..a4222ce4e 100644 --- a/src/libtrx/include/libtrx/game/savegame/const.h +++ b/src/libtrx/include/libtrx/game/savegame/const.h @@ -1,4 +1,3 @@ #pragma once #define SAVEGAME_CURRENT_VERSION 8 -#define MAX_STRATEGIES 2 diff --git a/src/tr1/game/lara/misc.h b/src/tr1/game/lara/misc.h index 9e918e723..6b71bfa7e 100644 --- a/src/tr1/game/lara/misc.h +++ b/src/tr1/game/lara/misc.h @@ -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); diff --git a/src/tr1/game/lot.h b/src/tr1/game/lot.h index 0d3040a7b..8fbc00b89 100644 --- a/src/tr1/game/lot.h +++ b/src/tr1/game/lot.h @@ -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); diff --git a/src/tr1/game/savegame.h b/src/tr1/game/savegame.h index 5ef114a86..fcd5755d5 100644 --- a/src/tr1/game/savegame.h +++ b/src/tr1/game/savegame.h @@ -4,47 +4,5 @@ #include -#include - -// 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); diff --git a/src/tr1/game/savegame/savegame.c b/src/tr1/game/savegame/savegame.c index b9ad88f9a..6b9c19fbc 100644 --- a/src/tr1/game/savegame/savegame.c +++ b/src/tr1/game/savegame/savegame.c @@ -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 #include #include -#include -#include #include -#include -#include -#include -#include -#include -#include - -#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; - } - - 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); - } + return g_Config.gameplay.maximum_save_slots; } -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; -} diff --git a/src/tr2/game/lara/misc.h b/src/tr2/game/lara/misc.h index bd269d1a1..39286a548 100644 --- a/src/tr2/game/lara/misc.h +++ b/src/tr2/game/lara/misc.h @@ -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 diff --git a/src/tr2/game/lot.h b/src/tr2/game/lot.h index 0da9a0b3d..1976b2cf7 100644 --- a/src/tr2/game/lot.h +++ b/src/tr2/game/lot.h @@ -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); diff --git a/src/tr2/game/savegame.h b/src/tr2/game/savegame.h index d5a886fff..fcd5755d5 100644 --- a/src/tr2/game/savegame.h +++ b/src/tr2/game/savegame.h @@ -1,32 +1,8 @@ #pragma once -#include "game/game_flow/types.h" +#include "global/types.h" -#include #include -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); diff --git a/src/tr2/game/savegame/common.c b/src/tr2/game/savegame/common.c index de8355a92..20208dc03 100644 --- a/src/tr2/game/savegame/common.c +++ b/src/tr2/game/savegame/common.c @@ -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 #include -#include -#include #include -#include -#include -#include -#include -#include -#include #include -#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); -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); - - 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; + 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); } - - 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--; + 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; } - - return result; + memcpy(m_ReqFlags1, g_RequesterFlags1, sizeof(m_ReqFlags1)); + memcpy(m_ReqFlags2, g_RequesterFlags2, sizeof(m_ReqFlags2)); } -bool Savegame_Load(const int32_t slot_idx) +void Savegame_FillAvailableLevels(REQUEST_INFO *const req) { - const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[slot_idx]; - ASSERT(savegame_info->format != 0); + 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; - 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; + 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); } - - 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]; } diff --git a/src/tr2/global/const.h b/src/tr2/global/const.h index 513f47c14..8dd49e761 100644 --- a/src/tr2/global/const.h +++ b/src/tr2/global/const.h @@ -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