Skip to content

Commit

Permalink
libtrx/config: support enforced config settings
Browse files Browse the repository at this point in the history
This allows an enforced object to be defined in the config file, within
which any regular config setting can be defined, and the values from
here will be enforced in the game.

Enforced settings are not shown in the config tool, but will be
preserved on write.

Resolves #1846.
  • Loading branch information
lahm86 committed Nov 10, 2024
1 parent 3252012 commit 3ca5456
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 10 deletions.
1 change: 1 addition & 0 deletions docs/tr1/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- added the ability to pause during cutscenes (#1673)
- added an option to enable responsive swim cancellation, similar to TR2+ (#1004)
- added a special target, "pickup", to item-based console commands
- added support for custom levels to enforce values for any config setting (#1846)
- changed OpenGL backend to use version 3.3, with fallback to 2.1 if initialization fails (#1738)
- changed text backend to accept named sequences. Currently supported sequences (limited by the sprites available in OG):
- `\{umlaut}`
Expand Down
48 changes: 48 additions & 0 deletions docs/tr1/GAMEFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Jump to:
- [Bonus levels](#bonus-levels)
- [Item drops](#item-drops)
- [Injections](#injections)
- [User configuration](#user-configuration)

## Global properties
The following properties are in the root of the gameflow document and control
Expand Down Expand Up @@ -1483,3 +1484,50 @@ provided with the game achieves.
</td>
</tr>
</table>

## 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.

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.

```json
"enforced" : {
"enable_save_crystals" : false,
}
```

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.

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.
1 change: 1 addition & 0 deletions docs/tr2/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-0.6...develop) - ××××-××-××
- added support for custom levels to enforce values for any config setting (#1846)

## [0.6](https://github.com/LostArtefacts/TRX/compare/tr2-0.5...tr2-0.6) - 2024-11-06
- added a fly cheat key (#1642)
Expand Down
85 changes: 75 additions & 10 deletions src/libtrx/config/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,53 @@

#include <string.h>

#define ENFORCED_KEY "enforced"

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

static bool M_ReadFromJSON(
const char *cfg_data, void (*load)(JSON_OBJECT *root_obj))
static JSON_VALUE *M_ReadRoot(const char *const cfg_data)
{
bool result = false;
if (cfg_data == NULL) {
return NULL;
}

JSON_PARSE_RESULT parse_result;
JSON_VALUE *root = JSON_ParseEx(
cfg_data, strlen(cfg_data), JSON_PARSE_FLAGS_ALLOW_JSON5, NULL, NULL,
&parse_result);
if (root != NULL) {
result = true;
} else {
if (root == NULL) {
LOG_ERROR(
"failed to parse config file: %s in line %d, char %d",
JSON_GetErrorDescription(parse_result.error),
parse_result.error_line_no, parse_result.error_row_no);
// continue to supply the default values
}

return root;
}

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

JSON_VALUE *root = M_ReadRoot(cfg_data);
if (root != NULL) {
result = true;
}

JSON_OBJECT *root_obj = JSON_ValueAsObject(root);

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

load(root_obj);

if (root) {
Expand All @@ -41,16 +63,54 @@ static bool M_ReadFromJSON(
return result;
}

static char *M_WriteToJSON(void (*dump)(JSON_OBJECT *root_obj))
static void M_PreserveEnforcedState(
JSON_OBJECT *const root_obj, JSON_VALUE *const old_root)
{
if (old_root == NULL) {
return;
}

JSON_OBJECT *old_root_obj = JSON_ValueAsObject(old_root);
JSON_OBJECT *enforced_obj =
JSON_ObjectGetObject(old_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.
JSON_OBJECT_ELEMENT *elem = enforced_obj->start;
while (elem != NULL) {
const char *const name = elem->name->string;
elem = elem->next;

JSON_ObjectEvictKey(root_obj, name);
if (!JSON_ObjectContainsKey(old_root_obj, name)) {
continue;
}

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)
{
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 *root = JSON_ValueFromObject(root_obj);
size_t size;
char *data = JSON_WritePretty(root, " ", "\n", &size);
JSON_ValueFree(root);
JSON_ValueFree(old_root);

return data;
}
Expand Down Expand Up @@ -88,7 +148,8 @@ bool ConfigFile_Write(const char *path, void (*dump)(JSON_OBJECT *root_obj))
File_Load(path, &old_data, NULL);

bool updated = false;
char *data = M_WriteToJSON(dump);
char *data = M_WriteToJSON(dump, old_data);

if (old_data == NULL || strcmp(data, old_data) != 0) {
MYFILE *const fp = File_Open(path, FILE_OPEN_WRITE);
if (fp == NULL) {
Expand All @@ -101,6 +162,10 @@ bool ConfigFile_Write(const char *path, void (*dump)(JSON_OBJECT *root_obj))
}

Memory_FreePointer(&data);
if (old_data != NULL) {
Memory_FreePointer(&old_data);
}

return updated;
}

Expand Down
3 changes: 3 additions & 0 deletions src/libtrx/include/libtrx/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#define JSON_INVALID_NUMBER 0x7FFFFFFF

#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

Expand Down Expand Up @@ -126,7 +127,9 @@ void JSON_ObjectAppendArray(JSON_OBJECT *obj, const char *key, JSON_ARRAY *arr);
void JSON_ObjectAppendObject(
JSON_OBJECT *obj, const char *key, JSON_OBJECT *obj2);

bool JSON_ObjectContainsKey(JSON_OBJECT *obj, const char *key);
void JSON_ObjectEvictKey(JSON_OBJECT *obj, const char *key);
void JSON_ObjectMerge(JSON_OBJECT *root, const JSON_OBJECT *obj);

JSON_VALUE *JSON_ObjectGetValue(JSON_OBJECT *obj, const char *key);
int JSON_ObjectGetBool(JSON_OBJECT *obj, const char *key, int d);
Expand Down
24 changes: 24 additions & 0 deletions src/libtrx/json/json_base.c
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,20 @@ void JSON_ObjectAppendObject(
JSON_ObjectAppend(obj, key, JSON_ValueFromObject(obj2));
}

bool JSON_ObjectContainsKey(JSON_OBJECT *const obj, const char *const key)
{
JSON_OBJECT_ELEMENT *elem = obj->start;
while (elem != NULL) {
if (!strcmp(elem->name->string, key)) {
return true;
}

elem = elem->next;
}

return false;
}

void JSON_ObjectEvictKey(JSON_OBJECT *obj, const char *key)
{
if (!obj) {
Expand All @@ -369,6 +383,16 @@ void JSON_ObjectEvictKey(JSON_OBJECT *obj, const char *key)
}
}

void JSON_ObjectMerge(JSON_OBJECT *const root, const JSON_OBJECT *const obj)
{
JSON_OBJECT_ELEMENT *elem = obj->start;
while (elem != NULL) {
JSON_ObjectEvictKey(root, elem->name->string);
JSON_ObjectAppend(root, elem->name->string, elem->value);
elem = elem->next;
}
}

JSON_VALUE *JSON_ObjectGetValue(JSON_OBJECT *obj, const char *key)
{
if (!obj) {
Expand Down

0 comments on commit 3ca5456

Please sign in to comment.