mirror of
https://github.com/LostArtefacts/TRX.git
synced 2025-04-28 12:47:58 +03:00
libtrx/config: support enforced config settings
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.
This commit is contained in:
parent
32520128f5
commit
3ca54568b2
6 changed files with 155 additions and 13 deletions
|
@ -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}`
|
||||
|
|
|
@ -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
|
||||
|
@ -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,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)
|
||||
|
|
|
@ -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 JSON_VALUE *M_ReadRoot(const char *const cfg_data)
|
||||
{
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
static bool M_ReadFromJSON(
|
||||
const char *cfg_data, void (*load)(JSON_OBJECT *root_obj))
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
JSON_PARSE_RESULT parse_result;
|
||||
JSON_VALUE *root = JSON_ParseEx(
|
||||
cfg_data, strlen(cfg_data), JSON_PARSE_FLAGS_ALLOW_JSON5, NULL, NULL,
|
||||
&parse_result);
|
||||
JSON_VALUE *root = M_ReadRoot(cfg_data);
|
||||
if (root != NULL) {
|
||||
result = true;
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#define JSON_INVALID_NUMBER 0x7FFFFFFF
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue