tr1/savegame: fix death counter support

Resolves #2412.
This commit is contained in:
Marcin Kurczewski 2025-01-29 16:03:33 +01:00
parent fb26660da1
commit 69f73edb08
23 changed files with 118 additions and 97 deletions

View file

@ -44,6 +44,7 @@
- fixed header and arrows disappearing when the inventory ring rotates (#2352, regression from 4.4)
- fixed Story So Far feature not playing opening FMVs from the current level (#2360, regression from 4.2)
- fixed `/play` command crashing the game when used after loading a level (#2411, regression)
- fixed various death counter problems (existing saves will have the count reset) (#2412, regression from 2.6)
- fixed `/demo` command crashing the game if no demos are present (regression from 4.1)
- fixed Lara not being able to jump or stop swimming if the related responsive config options are enabled, but enhanced animations are not present (#2397, regression from 4.6)
- improved pause screen compatibility with PS1 (#2248)

View file

@ -31,7 +31,7 @@ static PHASE_CONTROL M_Control(PHASE *const phase, const int32_t nframes)
// A change in the game flow is not natural. Force features like death
// counter to break from the currently active savegame file.
Savegame_ClearCurrentSlot();
Savegame_UnbindSlot();
return (PHASE_CONTROL) { .action = PHASE_ACTION_END, .gf_cmd = gf_cmd };
}

View file

@ -0,0 +1,22 @@
#include "game/savegame.h"
#include "log.h"
static int32_t m_BoundSlot = -1;
void Savegame_BindSlot(const int32_t slot_num)
{
m_BoundSlot = slot_num;
LOG_DEBUG("Binding save slot %d", slot_num);
}
void Savegame_UnbindSlot(void)
{
LOG_DEBUG("Resetting the save slot");
m_BoundSlot = -1;
}
int32_t Savegame_GetBoundSlot(void)
{
return m_BoundSlot;
}

View file

@ -2,8 +2,18 @@
#include <stdint.h>
// Remembers the slot used when the player starts a loaded game.
// Persists across level reloads.
void Savegame_BindSlot(int32_t slot_num);
// Removes the binding of the current slot. Used when the player exits to
// title, issues a command like `/play` etc.
void Savegame_UnbindSlot(void);
// Returns the currently bound slot number. If there is none, returns -1.
int32_t Savegame_GetBoundSlot(void);
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_ClearCurrentSlot(void);

View file

@ -150,6 +150,7 @@ sources = [
'game/phase/phase_stats.c',
'game/random.c',
'game/rooms/common.c',
'game/savegame.c',
'game/shell/common.c',
'game/sound.c',
'game/text.c',

View file

@ -117,7 +117,7 @@ GF_COMMAND Game_Stop_Legacy(void)
if (g_GameInfo.passport_selection == PASSPORT_MODE_LOAD_GAME) {
return (GF_COMMAND) {
.action = GF_START_SAVED_GAME,
.param = g_GameInfo.current_save_slot,
.param = g_GameInfo.select_save_slot,
};
} else if (g_GameInfo.passport_selection == PASSPORT_MODE_SELECT_LEVEL) {
return (GF_COMMAND) {
@ -127,7 +127,7 @@ GF_COMMAND Game_Stop_Legacy(void)
} else if (g_GameInfo.passport_selection == PASSPORT_MODE_STORY_SO_FAR) {
return (GF_COMMAND) {
.action = GF_STORY_SO_FAR,
.param = g_GameInfo.current_save_slot,
.param = g_GameInfo.select_save_slot,
};
} else if (g_GameInfo.passport_selection == PASSPORT_MODE_RESTART) {
return (GF_COMMAND) {

View file

@ -89,13 +89,14 @@ static DECLARE_EVENT_HANDLER(M_HandleLoadLevel)
// reset current info to the defaults so that we do not do
// Item_GlobalReplace in the inventory initialization routines too early
Savegame_InitCurrentInfo();
const int16_t slot_num = Savegame_GetBoundSlot();
if (!Level_Initialise(level)) {
Game_SetCurrentLevel(NULL);
GF_SetCurrentLevel(NULL);
return (GF_COMMAND) { .action = GF_EXIT_TO_TITLE };
}
if (!Savegame_Load(g_GameInfo.current_save_slot)) {
if (!Savegame_Load(slot_num)) {
LOG_ERROR("Failed to load save file!");
Game_SetCurrentLevel(NULL);
GF_SetCurrentLevel(NULL);
@ -119,12 +120,12 @@ static DECLARE_EVENT_HANDLER(M_HandleLoadLevel)
break;
case GFSC_SELECT:
if (g_GameInfo.current_save_slot != -1) {
if (Savegame_GetBoundSlot() != -1) {
// select level feature
Savegame_InitCurrentInfo();
if (level->num > GF_GetFirstLevel()->num) {
Savegame_LoadOnlyResumeInfo(
g_GameInfo.current_save_slot, &g_GameInfo);
Savegame_GetBoundSlot(), &g_GameInfo);
const GF_LEVEL *tmp_level = level;
while (tmp_level != NULL) {
Savegame_ResetCurrentInfo(tmp_level);

View file

@ -10,6 +10,7 @@
GF_COMMAND GF_TitleSequence(void)
{
Savegame_UnbindSlot();
GameStringTable_Apply(nullptr);
const GF_LEVEL *const title_level = GF_GetTitleLevel();
if (!Level_Initialise(title_level)) {

View file

@ -250,7 +250,7 @@ static GF_COMMAND M_Finish(INV_RING *const ring, const bool apply_changes)
case PASSPORT_MODE_LOAD_GAME:
return (GF_COMMAND) {
.action = GF_START_SAVED_GAME,
.param = g_GameInfo.current_save_slot,
.param = g_GameInfo.select_save_slot,
};
case PASSPORT_MODE_SELECT_LEVEL:
@ -262,12 +262,13 @@ static GF_COMMAND M_Finish(INV_RING *const ring, const bool apply_changes)
case PASSPORT_MODE_STORY_SO_FAR:
return (GF_COMMAND) {
.action = GF_STORY_SO_FAR,
.param = g_GameInfo.current_save_slot,
.param = g_GameInfo.select_save_slot,
};
case PASSPORT_MODE_NEW_GAME:
if (apply_changes) {
Savegame_InitCurrentInfo();
Savegame_UnbindSlot();
}
return (GF_COMMAND) {
.action = GF_START_GAME,
@ -276,7 +277,7 @@ static GF_COMMAND M_Finish(INV_RING *const ring, const bool apply_changes)
case PASSPORT_MODE_SAVE_GAME:
if (apply_changes) {
Savegame_Save(g_GameInfo.current_save_slot);
Savegame_Save(g_GameInfo.select_save_slot);
}
return (GF_COMMAND) { .action = GF_NOOP };
@ -300,7 +301,7 @@ static GF_COMMAND M_Finish(INV_RING *const ring, const bool apply_changes)
case O_PHOTO_OPTION:
if (apply_changes) {
Savegame_ClearCurrentSlot();
Savegame_UnbindSlot();
}
if (GF_GetGymLevel() != NULL) {
return (GF_COMMAND) {

View file

@ -273,10 +273,10 @@ void Lara_Control(void)
item->hit_points = -1;
if (!g_Lara.death_timer) {
Music_Stop();
GF_GetResumeInfo(Game_GetCurrentLevel())->stats.death_count++;
if (g_GameInfo.current_save_slot != -1) {
g_GameInfo.death_count++;
if (Savegame_GetBoundSlot() != -1) {
Savegame_UpdateDeathCounters(
g_GameInfo.current_save_slot, &g_GameInfo);
Savegame_GetBoundSlot(), &g_GameInfo);
}
}
g_Lara.death_timer++;

View file

@ -974,9 +974,6 @@ bool Level_Initialise(const GF_LEVEL *const level)
BENCHMARK *const benchmark = Benchmark_Start();
LOG_DEBUG("num=%d (%s)", level->num, level->path);
// loading a save can override it to false
g_GameInfo.death_counter_supported = true;
g_GameInfo.select_level_num = -1;
const int32_t level_num = level->num;
RESUME_INFO *const resume = GF_GetResumeInfo(level);
@ -986,7 +983,6 @@ bool Level_Initialise(const GF_LEVEL *const level)
resume->stats.secret_count = 0;
resume->stats.pickup_count = 0;
resume->stats.kill_count = 0;
resume->stats.death_count = 0;
}
g_LevelComplete = false;

View file

@ -382,7 +382,7 @@ static void M_ShowSaves(PASSPORT_MODE pending_mode)
g_InputDB = (INPUT_STATE) {};
} else if (select > 0) {
m_PassportStatus.mode = PASSPORT_MODE_BROWSE;
g_GameInfo.current_save_slot = select - 1;
g_GameInfo.select_save_slot = select - 1;
g_GameInfo.passport_selection = pending_mode;
} else if (
g_InvMode != INV_SAVE_MODE && g_InvMode != INV_SAVE_CRYSTAL_MODE
@ -400,7 +400,7 @@ static void M_ShowSelectLevel(void)
int32_t select = Requester_Display(&m_SelectLevelRequester);
if (select) {
if (select - 1 + GF_GetFirstLevel()->num
== Savegame_GetLevelNumber(g_GameInfo.current_save_slot) + 1) {
== Savegame_GetLevelNumber(g_GameInfo.select_save_slot) + 1) {
g_GameInfo.passport_selection = PASSPORT_MODE_STORY_SO_FAR;
} else if (select > 0) {
g_GameInfo.select_level_num = select - 1 + GF_GetFirstLevel()->num;
@ -435,7 +435,7 @@ static void M_LoadGame(void)
if (!g_SavegameRequester.items[g_SavegameRequester.requested].is_blocked
|| !g_SavegameRequester.is_blockable) {
if (g_InputDB.menu_right) {
g_GameInfo.current_save_slot = g_SavegameRequester.requested;
g_GameInfo.select_save_slot = g_SavegameRequester.requested;
Text_Hide(m_Text[TEXT_LEVEL_ARROW_RIGHT], true);
Requester_ClearTextstrings(&g_SavegameRequester);
M_InitSelectLevelRequester();
@ -487,14 +487,19 @@ static void M_SelectLevel(void)
} else {
M_ShowSelectLevel();
if (m_PassportStatus.mode == PASSPORT_MODE_SELECT_LEVEL) {
Text_SetPos(
m_Text[TEXT_LEVEL_ARROW_LEFT], -130,
const TEXTSTRING *const sel_item =
m_SelectLevelRequester
.items
[m_SelectLevelRequester.requested
- m_SelectLevelRequester.line_offset]
.content->pos.y);
Text_Hide(m_Text[TEXT_LEVEL_ARROW_LEFT], false);
.content;
if (sel_item != nullptr) {
Text_SetPos(
m_Text[TEXT_LEVEL_ARROW_LEFT], -130, sel_item->pos.y);
Text_Hide(m_Text[TEXT_LEVEL_ARROW_LEFT], false);
} else {
Text_Hide(m_Text[TEXT_LEVEL_ARROW_LEFT], true);
}
} else {
Text_Hide(m_Text[TEXT_LEVEL_ARROW_LEFT], true);
}
@ -534,7 +539,6 @@ static void M_NewGame(void)
} else {
g_GameInfo.save_initial_version = SAVEGAME_CURRENT_VERSION;
g_GameInfo.bonus_level_unlock = false;
Savegame_ClearCurrentSlot();
g_GameInfo.passport_selection = PASSPORT_MODE_NEW_GAME;
}
} else if (m_PassportStatus.mode == PASSPORT_MODE_NEW_GAME) {
@ -559,7 +563,6 @@ static void M_NewGame(void)
break;
}
g_GameInfo.bonus_level_unlock = false;
Savegame_ClearCurrentSlot();
g_GameInfo.passport_selection = PASSPORT_MODE_NEW_GAME;
g_GameInfo.save_initial_version = SAVEGAME_CURRENT_VERSION;
} else if (
@ -580,7 +583,7 @@ static void M_Restart(INVENTORY_ITEM *inv_item)
{
M_ChangePageTextContent(GS(PASSPORT_RESTART_LEVEL));
if (Savegame_RestartAvailable(g_GameInfo.current_save_slot)) {
if (Savegame_RestartAvailable(g_GameInfo.select_save_slot)) {
if (g_InputDB.menu_confirm) {
g_GameInfo.passport_selection = PASSPORT_MODE_RESTART;
}

View file

@ -226,6 +226,7 @@ void Savegame_ProcessItemsBeforeSave(void)
void Savegame_InitCurrentInfo(void)
{
g_GameInfo.death_count = 0;
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];
@ -351,9 +352,9 @@ void Savegame_CarryCurrentInfoToNextLevel(
LOG_INFO(
"Copying resume info from level #%d to level #%d", src_level->num,
dst_level->num);
memcpy(
GF_GetResumeInfo(dst_level), GF_GetResumeInfo(src_level),
sizeof(RESUME_INFO));
RESUME_INFO *const src_resume = GF_GetResumeInfo(src_level);
RESUME_INFO *const dst_resume = GF_GetResumeInfo(dst_level);
memcpy(dst_resume, src_resume, sizeof(RESUME_INFO));
}
void Savegame_PersistGameToCurrentInfo(const GF_LEVEL *const level)
@ -462,6 +463,7 @@ bool Savegame_Save(const int32_t slot_num)
{
GAME_INFO *const game_info = &g_GameInfo;
bool ret = true;
Savegame_BindSlot(slot_num);
File_CreateDirectory(SAVES_DIR);
@ -644,9 +646,12 @@ void Savegame_ScanSavedGames(void)
void Savegame_ScanAvailableLevels(REQUEST_INFO *req)
{
SAVEGAME_INFO *savegame_info =
&m_SavegameInfo[g_GameInfo.current_save_slot];
const int32_t slot_num = g_GameInfo.select_save_slot;
if (slot_num == -1) {
return;
}
const SAVEGAME_INFO *const savegame_info = &m_SavegameInfo[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));
@ -686,8 +691,3 @@ bool Savegame_RestartAvailable(int32_t slot_num)
SAVEGAME_INFO *savegame_info = &m_SavegameInfo[slot_num];
return savegame_info->features.restart;
}
void Savegame_ClearCurrentSlot(void)
{
g_GameInfo.current_save_slot = -1;
}

View file

@ -263,8 +263,6 @@ static bool M_LoadResumeInfo(JSON_ARRAY *resume_arr, RESUME_INFO *resume_info)
JSON_ObjectGetInt(resume_obj, "kills", resume->stats.kill_count);
resume->stats.pickup_count = JSON_ObjectGetInt(
resume_obj, "pickups", resume->stats.pickup_count);
resume->stats.death_count =
JSON_ObjectGetInt(resume_obj, "deaths", resume->stats.death_count);
resume->stats.max_secret_count = JSON_ObjectGetInt(
resume_obj, "max_secrets", resume->stats.max_secret_count);
resume->stats.max_kill_count = JSON_ObjectGetInt(
@ -358,8 +356,6 @@ static bool M_LoadDiscontinuedEndInfo(JSON_ARRAY *end_arr, GAME_INFO *game_info)
end->kill_count = JSON_ObjectGetInt(end_obj, "kills", end->kill_count);
end->pickup_count =
JSON_ObjectGetInt(end_obj, "pickups", end->pickup_count);
end->death_count =
JSON_ObjectGetInt(end_obj, "deaths", end->death_count);
end->max_secret_count =
JSON_ObjectGetInt(end_obj, "max_secrets", end->max_secret_count);
end->max_kill_count =
@ -367,7 +363,6 @@ static bool M_LoadDiscontinuedEndInfo(JSON_ARRAY *end_arr, GAME_INFO *game_info)
end->max_pickup_count =
JSON_ObjectGetInt(end_obj, "max_pickups", end->max_pickup_count);
}
game_info->death_counter_supported = true;
return true;
}
@ -383,6 +378,7 @@ static bool M_LoadMisc(
if (header_version >= VERSION_4) {
game_info->bonus_level_unlock =
JSON_ObjectGetBool(misc_obj, "bonus_level_unlock", 0);
game_info->death_count = JSON_ObjectGetInt(misc_obj, "death_count", -1);
}
return true;
}
@ -971,7 +967,6 @@ static JSON_ARRAY *M_DumpResumeInfo(RESUME_INFO *resume_info)
JSON_ObjectAppendInt(resume_obj, "kills", resume->stats.kill_count);
JSON_ObjectAppendInt(resume_obj, "secrets", resume->stats.secret_flags);
JSON_ObjectAppendInt(resume_obj, "pickups", resume->stats.pickup_count);
JSON_ObjectAppendInt(resume_obj, "deaths", resume->stats.death_count);
JSON_ObjectAppendInt(
resume_obj, "max_kills", resume->stats.max_kill_count);
JSON_ObjectAppendInt(
@ -990,6 +985,7 @@ static JSON_OBJECT *M_DumpMisc(GAME_INFO *game_info)
JSON_ObjectAppendInt(misc_obj, "bonus_flag", game_info->bonus_flag);
JSON_ObjectAppendBool(
misc_obj, "bonus_level_unlock", game_info->bonus_level_unlock);
JSON_ObjectAppendInt(misc_obj, "death_count", game_info->death_count);
return misc_obj;
}
@ -1480,45 +1476,28 @@ void Savegame_BSON_SaveToFile(MYFILE *fp, GAME_INFO *game_info)
bool Savegame_BSON_UpdateDeathCounters(MYFILE *fp, GAME_INFO *game_info)
{
bool ret = false;
bool result = false;
int32_t version;
JSON_VALUE *root = M_ParseFromFile(fp, &version);
JSON_OBJECT *root_obj = JSON_ValueAsObject(root);
if (!root_obj) {
JSON_VALUE *const root = M_ParseFromFile(fp, &version);
JSON_OBJECT *const root_obj = JSON_ValueAsObject(root);
if (root_obj == nullptr) {
LOG_ERROR("Cannot find the root object");
goto cleanup;
}
JSON_ARRAY *current_arr = JSON_ObjectGetArray(root_obj, "current_info");
if (!current_arr) {
current_arr = JSON_ObjectGetArray(root_obj, "end_info");
if (!current_arr) {
LOG_ERROR("Malformed save: invalid or missing current info array");
goto cleanup;
}
}
if ((signed)current_arr->length != GF_GetLevelTable(GFLT_MAIN)->count) {
LOG_ERROR(
"Malformed save: expected %d current info elements, got %d",
GF_GetLevelTable(GFLT_MAIN)->count, current_arr->length);
JSON_OBJECT *const misc_obj = JSON_ObjectGetObject(root_obj, "misc");
if (misc_obj == nullptr) {
LOG_ERROR("Cannot find the misc object");
goto cleanup;
}
for (int i = 0; i < (signed)current_arr->length; i++) {
JSON_OBJECT *cur_obj = JSON_ArrayGetObject(current_arr, i);
if (!cur_obj) {
LOG_ERROR("Malformed save: invalid current info");
goto cleanup;
}
RESUME_INFO *current = &game_info->current[i];
JSON_ObjectEvictKey(cur_obj, "deaths");
JSON_ObjectAppendInt(cur_obj, "deaths", current->stats.death_count);
}
JSON_ObjectEvictKey(misc_obj, "death_count");
JSON_ObjectAppendInt(misc_obj, "death_count", game_info->death_count);
File_Seek(fp, 0, FILE_SEEK_SET);
M_SaveRaw(fp, root, version);
ret = true;
result = true;
cleanup:
JSON_ValueFree(root);
return ret;
return result;
}

View file

@ -479,7 +479,7 @@ static void M_ReadResumeInfo(MYFILE *fp, GAME_INFO *game_info)
Stats_UpdateSecrets(&resume_info->stats);
M_Read(&resume_info->stats.pickup_count, sizeof(uint8_t));
M_Read(&game_info->bonus_flag, sizeof(uint8_t));
game_info->death_counter_supported = false;
game_info->death_count = -1;
}
char *Savegame_Legacy_GetSaveFileName(int32_t slot)

View file

@ -220,7 +220,7 @@ void Shell_Main(void)
LOG_ERROR("Corrupt save file!");
gf_cmd = (GF_COMMAND) { .action = GF_EXIT_TO_TITLE };
} else {
g_GameInfo.current_save_slot = slot_num;
Savegame_BindSlot(slot_num);
const GF_LEVEL *const level = GF_GetLevel(GFLT_MAIN, level_num);
gf_cmd = GF_InterpretSequence(level, GFSC_SAVED, NULL);
}

View file

@ -129,13 +129,12 @@ void Stats_ComputeFinal(GF_LEVEL_TYPE level_type, FINAL_STATS *final_stats)
if (level->type != level_type) {
continue;
}
const LEVEL_STATS *level_stats = &g_GameInfo.current[i].stats;
const LEVEL_STATS *level_stats = &GF_GetResumeInfo(level)->stats;
final_stats->kill_count += level_stats->kill_count;
final_stats->pickup_count += level_stats->pickup_count;
final_stats->secret_count += level_stats->secret_count;
final_stats->timer += level_stats->timer;
final_stats->death_count += level_stats->death_count;
final_stats->max_kill_count += level_stats->max_kill_count;
final_stats->max_secret_count += level_stats->max_secret_count;
final_stats->max_pickup_count += level_stats->max_pickup_count;

View file

@ -4,7 +4,6 @@
typedef struct STATS_COMMON {
uint32_t timer;
uint32_t death_count;
uint32_t kill_count;
uint16_t secret_count;
uint16_t pickup_count;

View file

@ -53,8 +53,11 @@ static const char *M_GetDialogTitle(UI_STATS_DIALOG *self);
static void M_AddRow(
UI_STATS_DIALOG *self, M_ROW_ROLE role, const char *key, const char *value);
static void M_AddRowFromRole(
UI_STATS_DIALOG *self, M_ROW_ROLE role, const STATS_COMMON *stats);
static void M_AddCommonRows(UI_STATS_DIALOG *self, const STATS_COMMON *stats);
UI_STATS_DIALOG *self, M_ROW_ROLE role, const STATS_COMMON *stats,
const GAME_INFO *game_info);
static void M_AddCommonRows(
UI_STATS_DIALOG *self, const STATS_COMMON *stats,
const GAME_INFO *game_info);
static void M_AddLevelStatsRows(UI_STATS_DIALOG *self);
static void M_AddFinalStatsRows(UI_STATS_DIALOG *self);
static void M_UpdateTimerRow(UI_STATS_DIALOG *self);
@ -134,7 +137,7 @@ static void M_AddRow(
static void M_AddRowFromRole(
UI_STATS_DIALOG *const self, const M_ROW_ROLE role,
const STATS_COMMON *const stats)
const STATS_COMMON *const stats, const GAME_INFO *const game_info)
{
char buf[50];
const char *const num_fmt = g_Config.gameplay.enable_detailed_stats
@ -160,7 +163,7 @@ static void M_AddRowFromRole(
break;
case M_ROW_DEATHS:
sprintf(buf, "%d", stats->death_count);
sprintf(buf, "%d", game_info->death_count);
M_AddRow(self, role, GS(STATS_DEATHS), buf);
break;
@ -175,30 +178,35 @@ static void M_AddRowFromRole(
}
static void M_AddCommonRows(
UI_STATS_DIALOG *const self, const STATS_COMMON *const stats)
UI_STATS_DIALOG *const self, const STATS_COMMON *const stats,
const GAME_INFO *const game_info)
{
M_AddRowFromRole(self, M_ROW_KILLS, stats);
M_AddRowFromRole(self, M_ROW_PICKUPS, stats);
M_AddRowFromRole(self, M_ROW_SECRETS, stats);
M_AddRowFromRole(self, M_ROW_KILLS, stats, game_info);
M_AddRowFromRole(self, M_ROW_PICKUPS, stats, game_info);
M_AddRowFromRole(self, M_ROW_SECRETS, stats, game_info);
if (g_Config.gameplay.enable_deaths_counter
&& g_GameInfo.death_counter_supported) {
M_AddRowFromRole(self, M_ROW_DEATHS, stats);
&& game_info->death_count >= 0) {
// Always use sum of all levels for the deaths.
// Deaths get stored in the resume info for the level they happen on,
// so if the player dies in Vilcabamba and reloads Caves, they should
// still see an incremented death counter.
M_AddRowFromRole(self, M_ROW_DEATHS, stats, game_info);
}
M_AddRowFromRole(self, M_ROW_TIMER, stats);
M_AddRowFromRole(self, M_ROW_TIMER, stats, game_info);
}
static void M_AddLevelStatsRows(UI_STATS_DIALOG *const self)
{
const STATS_COMMON *stats =
(STATS_COMMON *)&g_GameInfo.current[self->args.level_num].stats;
M_AddCommonRows(self, stats);
M_AddCommonRows(self, stats, &g_GameInfo);
}
static void M_AddFinalStatsRows(UI_STATS_DIALOG *const self)
{
FINAL_STATS final_stats;
Stats_ComputeFinal(self->level_type, &final_stats);
M_AddCommonRows(self, (STATS_COMMON *)&final_stats);
M_AddCommonRows(self, (STATS_COMMON *)&final_stats, &g_GameInfo);
}
static void M_UpdateTimerRow(UI_STATS_DIALOG *const self)

View file

@ -217,13 +217,14 @@ typedef struct {
typedef struct {
RESUME_INFO *current;
int32_t death_count;
uint8_t bonus_flag;
bool bonus_level_unlock;
int32_t current_save_slot;
int16_t save_initial_version;
PASSPORT_MODE passport_selection;
int32_t select_save_slot;
int32_t select_level_num;
bool death_counter_supported;
bool remove_guns;
bool remove_scions;
bool remove_ammo;

View file

@ -1093,7 +1093,3 @@ int32_t S_LoadGame(const int32_t slot_num)
File_Close(fp);
return true;
}
void Savegame_ClearCurrentSlot(void)
{
}

View file

@ -3,6 +3,8 @@
#include "game/game_flow/types.h"
#include "global/types.h"
#include <libtrx/game/savegame.h>
#include <stddef.h>
void Savegame_ResetCurrentInfo(const GF_LEVEL *level);

View file

@ -1,4 +1,4 @@
#include "game/demo.h"
#include "decomp/savegame.h"
#include "game/game.h"
#include "game/game_flow/common.h"
#include "game/game_flow/sequencer.h"
@ -12,6 +12,7 @@
GF_COMMAND GF_TitleSequence(void)
{
Savegame_UnbindSlot();
GameStringTable_Apply(NULL);
const GF_LEVEL *const title_level = GF_GetTitleLevel();
if (!Level_Initialise(title_level, GFSC_NORMAL)) {