Skip to content

Commit

Permalink
libtrx/config: refactor enforced config approach
Browse files Browse the repository at this point in the history
This refactors the enforced config control from #1854 into the gameflow
and removes the legacy settings for enforcing game modes and save
crystals.

Resolves #1857.
  • Loading branch information
lahm86 committed Nov 11, 2024
1 parent c0583e1 commit f5c22f4
Show file tree
Hide file tree
Showing 23 changed files with 170 additions and 149 deletions.
2 changes: 0 additions & 2 deletions data/tr1/ship/cfg/TR1X_gameflow.json5
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
"main_menu_picture": "data/images/title.webp",
"savegame_fmt_legacy": "saveati.%d",
"savegame_fmt_bson": "save_tr1_%02d.dat",
"force_game_modes": null,
"force_save_crystals": null,
"demo_delay": 16,
"water_color": [0.45, 1.0, 1.0],
"draw_distance_fade": 22.0,
Expand Down
3 changes: 0 additions & 3 deletions data/tr1/ship/cfg/TR1X_gameflow_demo_pc.json5
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
// path to the savegame file
"savegame_fmt_legacy": "save_demo_pc.%d",
"savegame_fmt_bson": "save_demo_pc_%02d.dat",

"force_game_modes": null,
"force_save_crystals": null,
"demo_delay": 16,
"water_color": [0.45, 1.0, 1.0],
"draw_distance_fade": 22.0,
Expand Down
6 changes: 4 additions & 2 deletions data/tr1/ship/cfg/TR1X_gameflow_ub.json5
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
"main_menu_picture": "data/images/title_ub.webp",
"savegame_fmt_legacy": "saveuba.%d",
"savegame_fmt_bson": "save_trub_%02d.dat",
"force_game_modes": null,
"force_save_crystals": false,
"demo_delay": 16,
"water_color": [0.45, 1.0, 1.0],
"draw_distance_fade": 22,
Expand All @@ -23,6 +21,10 @@
"enable_tr2_item_drops": false,
"convert_dropped_guns": false,

"enforced_config": {
"enable_save_crystals": false,
},

"levels": [
// Level 0
{
Expand Down
1 change: 1 addition & 0 deletions docs/tr1/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
- improved the injection approach for Lara's responsive jumping (#1823)
- removed health cheat (we now have the `/hp` command)
- removed background for the "Reset" and "Unbind" labels in the controls dialog
- removed `force_game_modes` and `force_save_crystals` from the gameflow - see GAMEFLOW.md for details on how to enforce these settings (#1857)

## [4.5.1](https://github.com/LostArtefacts/TRX/compare/tr1-4.5...tr1-4.5.1) - 2024-10-14
- fixed mac builds missing embedded resources (#1710, regression from 4.5)
Expand Down
61 changes: 16 additions & 45 deletions docs/tr1/GAMEFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ various pieces of global behaviour.
"main_menu_picture": "data/titleh.png",
"savegame_fmt_legacy": "saveati.%d",
"savegame_fmt_bson": "save_tr1_%02d.dat",
"force_game_modes": null,
"force_save_crystals": null,
"demo_delay": 16,
"water_color": [0.45, 1.0, 1.0],
"draw_distance_fade": 22.0,
Expand All @@ -35,6 +33,9 @@ various pieces of global behaviour.
// etc
],
"convert_dropped_guns": false,
"enforced_config": {
"enable_save_crystals": false,
},
"levels": [
{
"title": "Caves",
Expand Down Expand Up @@ -125,27 +126,14 @@ various pieces of global behaviour.
</tr>
<tr valign="top">
<td>
<code>force_game_modes</code>
</td>
<td>Optional Boolean</td>
<td>No</td>
<td>
Forces game mode selection to be enabled if <code>true</code> or disabled
if <code>false</code>, so the user can't select NG+ modes until a
playthrough is completed. Overrides the config option
<code>enable_game_modes</code>. Has no action if <code>null</code>.
</td>
</tr>
<tr valign="top">
<td>
<code>force_save_crystals</code>
<a name="enforced-config"></a>
<code>enforced_config</code>
</td>
<td>Optional Boolean</td>
<td>String-to-object map</td>
<td>No</td>
<td>
Forces save crystals to be enabled if <code>true</code> or disabled if
<code>false</code>. Overrides the config option
<code>enable_save_crystals</code>. Has no action if <code>null</code>.
This allows _any_ regular game config setting to be overriden. See
<a href="#user-configuration">User configuration</a> for full details.
</td>
</tr>
<tr valign="top">
Expand Down Expand Up @@ -1488,15 +1476,15 @@ provided with the game achieves.
## User Configuration
TRX ships with a configuration tool to allow users to adjust game settings to
their taste. This tool writes to `cfg\TR1X.json5`. As a level builder, you may
wish to enforce some settings to match how your level is designed.
however wish to enforce some settings to match how your level is designed.

As an example, let's say you do not wish to add save crystals to your level, and
as a result you wish to prevent the player from enabling that option in the
config tool. To achieve this, open `cfg\TR1X.json5` in a suitable text editor
and add the following.
config tool. To achieve this, open `cfg\TR1X_gameflow.json5` in a suitable text
editor and add the following.

```json
"enforced" : {
"enforced_config" : {
"enable_save_crystals" : false,
}
```
Expand All @@ -1505,29 +1493,12 @@ This means that the game will enforce your chosen value for this particular
config setting. If the player launches the config tool, the option to toggle
save crystals will be greyed out.

You can add as many settings within the `enforced` section as needed.
You can add as many settings within the `enforced_config` section as needed.
Refer to the key names within `cfg\TR1X.json5` for reference.

Note that you do not need to ship a full `cfg\TR1X.json5` with your level, and
indeed it is not recommended to do so if you have, for example, your own custom
keyboard or controller layouts defined.

If you do not have any requirement to enforce settings, you can omit the file
altogether from your level - the game will provide defaults for all settings as
standard when it is launched.

You can also ship only the `enforced` settings. So, your _entire_ file may
appear simply as follows, and this is perfectly valid.

```json
{
"enforced" : {
"enable_save_crystals" : false,
"disable_healing_between_levels" : true,
"enable_3d_pickups" : true,
"enable_wading" : true,
}
}
```

These settings will be enforced; everything else will default, plus the player
can customise the settings you have not defined as desired.
If you do not have any requirement to enforce settings, you can omit the
`enforced_config` section from your gameflow.
14 changes: 12 additions & 2 deletions src/libtrx/config/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ void Config_Shutdown(void)

bool Config_Read(void)
{
const bool result = ConfigFile_Read(Config_GetPath(), &Config_LoadFromJSON);
const CONFIG_IO_ARGS args = {
.default_path = Config_GetPath(CFT_DEFAULT),
.enforced_path = Config_GetPath(CFT_ENFORCED),
.action = &Config_LoadFromJSON,
};
const bool result = ConfigFile_Read(&args);
if (result) {
Config_Sanitize();
Config_ApplyChanges();
Expand All @@ -30,7 +35,12 @@ bool Config_Read(void)
bool Config_Write(void)
{
Config_Sanitize();
const bool updated = ConfigFile_Write(Config_GetPath(), &Config_DumpToJSON);
const CONFIG_IO_ARGS args = {
.default_path = Config_GetPath(CFT_DEFAULT),
.enforced_path = Config_GetPath(CFT_ENFORCED),
.action = &Config_DumpToJSON,
};
const bool updated = ConfigFile_Write(&args);
if (updated) {
Config_ApplyChanges();
if (m_EventManager != NULL) {
Expand Down
101 changes: 66 additions & 35 deletions src/libtrx/config/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
#include "log.h"
#include "memory.h"

#include <assert.h>
#include <string.h>

#define ENFORCED_KEY "enforced"
#define EMPTY_ROOT "{}"
#define ENFORCED_KEY "enforced_config"

static bool M_ReadFromJSON(
const char *json, void (*load)(JSON_OBJECT *root_obj));
const char *def_json, const char *enf_json,
void (*load)(JSON_OBJECT *root_obj));
static void M_PreserveEnforcedState(
JSON_OBJECT *root_obj, JSON_VALUE *old_root);
JSON_OBJECT *root_obj, JSON_VALUE *old_root, JSON_VALUE *enf_root);
static char *M_WriteToJSON(
void (*dump)(JSON_OBJECT *root_obj), const char *old_data);
void (*dump)(JSON_OBJECT *root_obj), const char *old_data,
const char *enf_data);
static const char *M_ResolveOptionName(const char *option_name);

static JSON_VALUE *M_ReadRoot(const char *const cfg_data)
Expand All @@ -38,47 +42,56 @@ static JSON_VALUE *M_ReadRoot(const char *const cfg_data)
}

static bool M_ReadFromJSON(
const char *cfg_data, void (*load)(JSON_OBJECT *root_obj))
const char *cfg_data, const char *enf_data,
void (*load)(JSON_OBJECT *root_obj))
{
bool result = false;

JSON_VALUE *root = M_ReadRoot(cfg_data);
if (root != NULL) {
JSON_VALUE *cfg_root = M_ReadRoot(cfg_data == NULL ? EMPTY_ROOT : cfg_data);
JSON_VALUE *enf_root = M_ReadRoot(enf_data == NULL ? EMPTY_ROOT : enf_data);
if (cfg_root != NULL) {
result = true;
}

JSON_OBJECT *root_obj = JSON_ValueAsObject(root);
JSON_OBJECT *cfg_root_obj = JSON_ValueAsObject(cfg_root);
JSON_OBJECT *enf_root_obj = JSON_ValueAsObject(enf_root);

JSON_OBJECT *enforced_config = JSON_ObjectGetObject(root_obj, ENFORCED_KEY);
JSON_OBJECT *enforced_config =
JSON_ObjectGetObject(enf_root_obj, ENFORCED_KEY);
if (enforced_config != NULL) {
JSON_ObjectMerge(root_obj, enforced_config);
JSON_ObjectMerge(cfg_root_obj, enforced_config);
}

load(root_obj);
load(cfg_root_obj);

if (root) {
JSON_ValueFree(root);
if (cfg_root) {
JSON_ValueFree(cfg_root);
}
if (enf_root) {
JSON_ValueFree(enf_root);
}

return result;
}

static void M_PreserveEnforcedState(
JSON_OBJECT *const root_obj, JSON_VALUE *const old_root)
JSON_OBJECT *const root_obj, JSON_VALUE *const old_root,
JSON_VALUE *const enf_root)
{
if (old_root == NULL) {
if (old_root == NULL || enf_root == NULL) {
return;
}

JSON_OBJECT *old_root_obj = JSON_ValueAsObject(old_root);
JSON_OBJECT *enf_root_obj = JSON_ValueAsObject(enf_root);
JSON_OBJECT *enforced_obj =
JSON_ObjectGetObject(old_root_obj, ENFORCED_KEY);
JSON_ObjectGetObject(enf_root_obj, ENFORCED_KEY);
if (enforced_obj == NULL) {
return;
}

// Restore the original values for any enforced settings, provided they were
// defined, and preserve the enforced object itself in the new object.
// defined.
JSON_OBJECT_ELEMENT *elem = enforced_obj->start;
while (elem != NULL) {
const char *const name = elem->name->string;
Expand All @@ -92,25 +105,26 @@ static void M_PreserveEnforcedState(
JSON_VALUE *const old_value = JSON_ObjectGetValue(old_root_obj, name);
JSON_ObjectAppend(root_obj, name, old_value);
}

JSON_ObjectAppendObject(root_obj, ENFORCED_KEY, enforced_obj);
}

static char *M_WriteToJSON(
void (*dump)(JSON_OBJECT *root_obj), const char *const old_data)
void (*dump)(JSON_OBJECT *root_obj), const char *const old_data,
const char *const enf_data)
{
JSON_OBJECT *root_obj = JSON_ObjectNew();

dump(root_obj);

JSON_VALUE *old_root = M_ReadRoot(old_data);
M_PreserveEnforcedState(root_obj, old_root);
JSON_VALUE *enf_root = M_ReadRoot(enf_data);
M_PreserveEnforcedState(root_obj, old_root, enf_root);

JSON_VALUE *root = JSON_ValueFromObject(root_obj);
size_t size;
char *data = JSON_WritePretty(root, " ", "\n", &size);
JSON_ValueFree(root);
JSON_ValueFree(old_root);
JSON_ValueFree(enf_root);

return data;
}
Expand All @@ -124,34 +138,48 @@ static const char *M_ResolveOptionName(const char *option_name)
return option_name;
}

bool ConfigFile_Read(const char *path, void (*load)(JSON_OBJECT *root_obj))
bool ConfigFile_Read(const CONFIG_IO_ARGS *const args)
{
bool result = false;
char *cfg_data = NULL;
char *default_data = NULL;
char *enforced_data = NULL;

assert(args->default_path != NULL);
if (!File_Load(args->default_path, &default_data, NULL)) {
LOG_WARNING(
"'%s' not loaded - default settings will apply",
args->default_path);
}

if (!File_Load(path, &cfg_data, NULL)) {
LOG_WARNING("'%s' not loaded - default settings will apply", path);
result = M_ReadFromJSON("{}", load);
} else {
result = M_ReadFromJSON(cfg_data, load);
if (args->enforced_path != NULL) {
File_Load(args->enforced_path, &enforced_data, NULL);
}

Memory_FreePointer(&cfg_data);
bool result = M_ReadFromJSON(default_data, enforced_data, args->action);

Memory_FreePointer(&default_data);
Memory_FreePointer(&enforced_data);
return result;
}

bool ConfigFile_Write(const char *path, void (*dump)(JSON_OBJECT *root_obj))
bool ConfigFile_Write(const CONFIG_IO_ARGS *const args)
{
LOG_INFO("Saving user settings");

char *old_data;
File_Load(path, &old_data, NULL);
char *old_data = NULL;
char *enforced_data = NULL;

assert(args->default_path != NULL);
File_Load(args->default_path, &old_data, NULL);

if (args->enforced_path != NULL) {
File_Load(args->enforced_path, &enforced_data, NULL);
}

bool updated = false;
char *data = M_WriteToJSON(dump, old_data);
char *data = M_WriteToJSON(args->action, old_data, enforced_data);

if (old_data == NULL || strcmp(data, old_data) != 0) {
MYFILE *const fp = File_Open(path, FILE_OPEN_WRITE);
MYFILE *const fp = File_Open(args->default_path, FILE_OPEN_WRITE);
if (fp == NULL) {
LOG_ERROR("Failed to write settings!");
} else {
Expand All @@ -165,6 +193,9 @@ bool ConfigFile_Write(const char *path, void (*dump)(JSON_OBJECT *root_obj))
if (old_data != NULL) {
Memory_FreePointer(&old_data);
}
if (enforced_data != NULL) {
Memory_FreePointer(&enforced_data);
}

return updated;
}
Expand Down
Loading

0 comments on commit f5c22f4

Please sign in to comment.