config: fix assault course times reset on quit

Resolves #1578.
This commit is contained in:
Marcin Kurczewski 2025-04-20 20:30:44 +02:00
parent c2b005cec5
commit 6e48174a92
11 changed files with 111 additions and 32 deletions

View file

@ -22,6 +22,7 @@
- changed default pitch of the save/load dialog ingame - it's now higher.
- fixed the inability to completely mute the sounds, even at sound volume 0 (#2722)
- fixed the final two levels not allowing for secrets to be counted in the statistics (#1582)
- fixed assault course best times not being retained between game relaunches (#1578)
- fixed Lara's holsters being empty if a game flow level removes all weapons but also re-adds the pistols (#2677)
- fixed the console opening when remapping its key (#2641)
- fixed the boat when it explodes after crossing mines, where Lara's hips would appear rather than exploded boat parts (#1605)

View file

@ -183,6 +183,7 @@ as Notepad.
- added waterfalls to the savegame so that they now persist on load
- changed inventory to pause the music rather than muting it
- fixed killing the T-Rex with a grenade launcher crashing the game
- fixed assault course best times not being retained between game relaunches
- fixed secret rewards not displaying shotgun ammo
- fixed numeric keys interfering with the demos
- fixed the ammo counter being hidden while a demo plays in NG+

View file

@ -340,3 +340,51 @@ void ConfigFile_WriteEnum(
{
JSON_ObjectAppendString(obj, name, EnumMap_ToString(enum_name, value));
}
bool ConfigFile_LoadAssaultStats(
JSON_OBJECT *const root_obj, ASSAULT_STATS *const assault_stats)
{
JSON_OBJECT *const stats_obj =
JSON_ObjectGetObject(root_obj, "assault_stats");
if (stats_obj == nullptr) {
return false;
}
JSON_ARRAY *const entries_arr = JSON_ObjectGetArray(stats_obj, "entries");
if (entries_arr != nullptr) {
for (size_t i = 0; i < entries_arr->length && i < MAX_ASSAULT_TIMES;
i++) {
JSON_OBJECT *const entry_obj = JSON_ArrayGetObject(entries_arr, i);
if (entry_obj != nullptr) {
assault_stats->entries[i].time = JSON_ObjectGetInt(
entry_obj, "time", assault_stats->entries[i].time);
assault_stats->entries[i].attempt_num = JSON_ObjectGetInt(
entry_obj, "attempt_num",
assault_stats->entries[i].attempt_num);
}
}
}
assault_stats->best_time =
JSON_ObjectGetInt(stats_obj, "best_time", assault_stats->best_time);
assault_stats->total_attempts = JSON_ObjectGetInt(
stats_obj, "total_attempts", assault_stats->total_attempts);
return true;
}
bool ConfigFile_DumpAssaultStats(
JSON_OBJECT *const root_obj, const ASSAULT_STATS *const assault_stats)
{
JSON_OBJECT *const stats_obj = JSON_ObjectNew();
JSON_ARRAY *const entries_arr = JSON_ArrayNew();
for (int i = 0; i < MAX_ASSAULT_TIMES; i++) {
JSON_OBJECT *const entry_obj = JSON_ObjectNew();
JSON_ObjectAppendInt(entry_obj, "time", assault_stats->entries[i].time);
JSON_ObjectAppendInt(
entry_obj, "attempt_num", assault_stats->entries[i].attempt_num);
JSON_ArrayAppendObject(entries_arr, entry_obj);
}
JSON_ObjectAppendArray(stats_obj, "entries", entries_arr);
JSON_ObjectAppendInt(
stats_obj, "total_attempts", assault_stats->total_attempts);
JSON_ObjectAppendObject(root_obj, "assault_stats", stats_obj);
return true;
}

View file

@ -2,6 +2,7 @@
#include "config/option.h"
#include "enum_map.h"
#include "game/gym.h"
#include "json.h"
#include <stdint.h>
@ -25,3 +26,8 @@ int ConfigFile_ReadEnum(
const char *enum_name);
void ConfigFile_WriteEnum(
JSON_OBJECT *obj, const char *name, int value, const char *enum_name);
bool ConfigFile_LoadAssaultStats(
JSON_OBJECT *root_obj, ASSAULT_STATS *assault_stats);
bool ConfigFile_DumpAssaultStats(
JSON_OBJECT *root_obj, const ASSAULT_STATS *assault_stats);

View file

@ -178,6 +178,9 @@ static void M_DumpControllerLayout(
void Config_LoadFromJSON(JSON_OBJECT *root_obj)
{
ConfigFile_LoadOptions(root_obj, Config_GetOptionMap());
if (Gym_HasAssaultStats()) {
ConfigFile_LoadAssaultStats(root_obj, &g_Config.profile.assault_stats);
}
for (INPUT_LAYOUT layout = INPUT_LAYOUT_CUSTOM_1;
layout < INPUT_LAYOUT_NUMBER_OF; layout++) {
@ -186,13 +189,15 @@ void Config_LoadFromJSON(JSON_OBJECT *root_obj)
}
M_LoadLegacyOptions(root_obj);
g_Config.loaded = true;
}
void Config_DumpToJSON(JSON_OBJECT *root_obj)
{
ConfigFile_DumpOptions(root_obj, Config_GetOptionMap());
if (Gym_HasAssaultStats()) {
ConfigFile_DumpAssaultStats(root_obj, &g_Config.profile.assault_stats);
}
for (INPUT_LAYOUT layout = INPUT_LAYOUT_CUSTOM_1;
layout < INPUT_LAYOUT_NUMBER_OF; layout++) {

View file

@ -4,6 +4,7 @@
#include "config/vars.h"
#include "debug.h"
#include "game/clock.h"
#include "game/gym.h"
#include "game/input.h"
#include "log.h"
#include "utils.h"
@ -111,6 +112,9 @@ static void M_LoadLegacyOptions(JSON_OBJECT *const parent_obj)
void Config_LoadFromJSON(JSON_OBJECT *root_obj)
{
ConfigFile_LoadOptions(root_obj, Config_GetOptionMap());
if (Gym_HasAssaultStats()) {
ConfigFile_LoadAssaultStats(root_obj, &g_Config.profile.assault_stats);
}
M_LoadInputConfig(root_obj);
M_LoadLegacyOptions(root_obj);
g_Config.loaded = true;
@ -120,6 +124,9 @@ void Config_LoadFromJSON(JSON_OBJECT *root_obj)
void Config_DumpToJSON(JSON_OBJECT *root_obj)
{
ConfigFile_DumpOptions(root_obj, Config_GetOptionMap());
if (Gym_HasAssaultStats()) {
ConfigFile_DumpAssaultStats(root_obj, &g_Config.profile.assault_stats);
}
M_DumpInputConfig(root_obj);
}

View file

@ -1,5 +1,6 @@
#include "game/gym.h"
#include "config.h"
#include "game/const.h"
#include "game/game.h"
#include "game/game_flow.h"
@ -10,16 +11,15 @@
static bool m_IsInventoryOpenEnabled = true;
static bool m_IsAssaultTimerDisplay = false;
static bool m_IsAssaultTimerActive = false;
static ASSAULT_STATS m_AssaultStats = { .best_time = -1 };
static bool M_StoreAssaultTime(uint32_t time);
static bool M_StoreAssaultTime(const uint32_t time)
{
ASSAULT_STATS *const assault = &g_Config.profile.assault_stats;
int32_t insert_idx = -1;
for (int32_t i = 0; i < MAX_ASSAULT_TIMES; i++) {
if (m_AssaultStats.entries[i].time == 0
|| time < m_AssaultStats.entries[i].time) {
if (assault->entries[i].time == 0 || time < assault->entries[i].time) {
insert_idx = i;
break;
}
@ -29,13 +29,13 @@ static bool M_StoreAssaultTime(const uint32_t time)
}
for (int32_t i = MAX_ASSAULT_TIMES - 1; i > insert_idx; i--) {
m_AssaultStats.entries[i] = m_AssaultStats.entries[i - 1];
assault->entries[i] = assault->entries[i - 1];
}
m_AssaultStats.total_attempts++;
m_AssaultStats.entries[insert_idx].time = time;
m_AssaultStats.entries[insert_idx].attempt_num =
m_AssaultStats.total_attempts;
assault->total_attempts++;
assault->entries[insert_idx].time = time;
assault->entries[insert_idx].attempt_num = assault->total_attempts;
Config_Write();
return true;
}
@ -61,7 +61,7 @@ bool Gym_IsAssaultTimerActive(void)
ASSAULT_STATS Gym_GetAssaultStats(void)
{
return m_AssaultStats;
return g_Config.profile.assault_stats;
}
void Gym_ResetAssault(void)
@ -91,28 +91,28 @@ void Gym_FinishAssault(void)
return;
}
ASSAULT_STATS *const assault = &g_Config.profile.assault_stats;
const RESUME_INFO *const resume =
Savegame_GetCurrentInfo(Game_GetCurrentLevel());
M_StoreAssaultTime(resume->stats.timer);
if (m_AssaultStats.best_time < 0) {
if (assault->best_time <= 0) {
if (resume->stats.timer < 100 * LOGIC_FPS) {
// "Gosh! That was my best time yet!"
Music_Play(MX_GYM_HINT_15, MPM_ALWAYS);
m_AssaultStats.best_time = resume->stats.timer;
assault->best_time = resume->stats.timer;
} else {
// "Congratulations! You did it! But perhaps I could've been
// faster."
Music_Play(MX_GYM_HINT_17, MPM_ALWAYS);
m_AssaultStats.best_time = 100 * LOGIC_FPS;
assault->best_time = 100 * LOGIC_FPS;
}
} else if (resume->stats.timer < (uint32_t)m_AssaultStats.best_time) {
} else if (resume->stats.timer < (uint32_t)assault->best_time) {
// "Gosh! That was my best time yet!"
Music_Play(MX_GYM_HINT_15, MPM_ALWAYS);
m_AssaultStats.best_time = resume->stats.timer;
assault->best_time = resume->stats.timer;
} else if (
resume->stats.timer
< (uint32_t)m_AssaultStats.best_time + 5 * LOGIC_FPS) {
resume->stats.timer < (uint32_t)assault->best_time + 5 * LOGIC_FPS) {
// "Almost. Perhaps another try and I might beat it."
Music_Play(MX_GYM_HINT_16, MPM_ALWAYS);
} else {
@ -122,3 +122,8 @@ void Gym_FinishAssault(void)
m_IsAssaultTimerActive = false;
}
bool Gym_HasAssaultStats(void)
{
return TR_VERSION >= 2;
}

View file

@ -1,5 +1,9 @@
#pragma once
#include "./const.h"
#include <stdint.h>
typedef enum {
MUSIC_LOAD_NEVER,
MUSIC_LOAD_NON_AMBIENT,
@ -11,6 +15,15 @@ typedef enum {
UI_STYLE_PC,
} UI_STYLE;
typedef struct {
struct {
uint32_t time;
uint32_t attempt_num;
} entries[MAX_ASSAULT_TIMES];
int32_t best_time;
uint32_t total_attempts;
} ASSAULT_STATS;
#if TR_VERSION == 1
#include "./types_tr1.h"
#elif TR_VERSION == 2

View file

@ -1,12 +1,11 @@
#pragma once
#include "../game/gym.h"
#include "../game/output/types.h"
#include "../game/sound/enum.h"
#include "../gfx/common.h"
#include "../screenshot.h"
#include <stdint.h>
#define CONFIG_MIN_BRIGHTNESS 0.1f
#define CONFIG_MAX_BRIGHTNESS 2.0f
#define CONFIG_MIN_TEXT_SCALE 0.5
@ -216,5 +215,6 @@ typedef struct {
struct {
bool new_game_plus_unlock;
ASSAULT_STATS assault_stats;
} profile;
} CONFIG;

View file

@ -1,13 +1,12 @@
#pragma once
#include "../colors.h"
#include "../game/gym.h"
#include "../game/input.h"
#include "../game/sound/enum.h"
#include "../gfx/common.h"
#include "../screenshot.h"
#include <stdint.h>
typedef enum {
LIGHTING_CONTRAST_LOW,
LIGHTING_CONTRAST_MEDIUM,
@ -125,5 +124,6 @@ typedef struct {
struct {
bool new_game_plus_unlock;
ASSAULT_STATS assault_stats;
} profile;
} CONFIG;

View file

@ -1,25 +1,18 @@
#pragma once
#include "../config/types.h"
#include <stdint.h>
#define MAX_ASSAULT_TIMES 10
typedef struct {
struct {
uint32_t time;
uint32_t attempt_num;
} entries[MAX_ASSAULT_TIMES];
int32_t best_time;
uint32_t total_attempts;
} ASSAULT_STATS;
extern bool Gym_IsAccessible(void);
void Gym_SetInventoryOpenEnabled(bool enabled);
bool Gym_IsInventoryOpenEnabled(void);
bool Gym_HasAssaultStats(void);
bool Gym_IsAssaultTimerDisplay(void);
bool Gym_IsAssaultTimerActive(void);
ASSAULT_STATS Gym_GetAssaultStats(void);
void Gym_SetAssaultStats(ASSAULT_STATS stats);
void Gym_ResetAssault(void);
void Gym_StartAssault(void);