From 4ec06bda779980fa99a756fbf20e34bc149fce51 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Wed, 29 Jan 2025 02:23:26 +0100 Subject: [PATCH] game-flow: merge reader via libtrx --- src/{tr2 => libtrx}/game/game_flow/reader.c | 413 +++++----- src/libtrx/game/game_flow/reader_tr1.def.c | 286 +++++++ src/libtrx/game/game_flow/reader_tr2.def.c | 129 ++++ src/libtrx/include/libtrx/game/game_flow.h | 1 + .../include/libtrx}/game/game_flow/reader.h | 0 .../include/libtrx/game/game_flow/types.h | 43 +- src/libtrx/meson.build | 1 + src/tr1/game/game_flow.h | 3 +- src/tr1/game/game_flow/reader.c | 723 ------------------ src/tr1/game/game_flow/sequencer.c | 4 +- src/tr1/game/game_flow/types.h | 25 - src/tr1/meson.build | 1 - src/tr2/game/game_flow.h | 2 + src/tr2/game/game_flow/reader.h | 3 - src/tr2/game/game_flow/sequencer.c | 3 +- src/tr2/game/game_flow/types.h | 20 - src/tr2/game/shell/common.c | 1 - src/tr2/meson.build | 1 - 18 files changed, 647 insertions(+), 1012 deletions(-) rename src/{tr2 => libtrx}/game/game_flow/reader.c (70%) create mode 100644 src/libtrx/game/game_flow/reader_tr1.def.c create mode 100644 src/libtrx/game/game_flow/reader_tr2.def.c rename src/{tr1 => libtrx/include/libtrx}/game/game_flow/reader.h (100%) delete mode 100644 src/tr1/game/game_flow/reader.c delete mode 100644 src/tr2/game/game_flow/reader.h diff --git a/src/tr2/game/game_flow/reader.c b/src/libtrx/game/game_flow/reader.c similarity index 70% rename from src/tr2/game/game_flow/reader.c rename to src/libtrx/game/game_flow/reader.c index d7dcb398a..e69b8e9b4 100644 --- a/src/tr2/game/game_flow/reader.c +++ b/src/libtrx/game/game_flow/reader.c @@ -1,18 +1,16 @@ #include "game/game_flow/reader.h" -#include "game/game_flow.h" +#include "debug.h" +#include "enum_map.h" +#include "filesystem.h" #include "game/game_flow/common.h" +#include "game/game_flow/types.h" #include "game/game_flow/vars.h" +#include "game/objects/names.h" #include "game/shell.h" -#include "global/vars.h" - -#include -#include -#include -#include -#include -#include -#include +#include "json.h" +#include "log.h" +#include "memory.h" #define DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(name) \ int32_t name( \ @@ -27,118 +25,57 @@ typedef struct { void *handler_func_arg; } M_SEQUENCE_EVENT_HANDLER; +static M_SEQUENCE_EVENT_HANDLER *M_GetSequenceEventHandlers(void); +static GF_SEQUENCE_EVENT_TYPE *M_GetLevelArgSequenceEvents(void); + typedef void (*M_LOAD_ARRAY_FUNC)( JSON_OBJECT *source_elem, const GAME_FLOW *gf, void *target_elem, size_t target_elem_idx, void *user_arg); static GAME_OBJECT_ID M_GetObjectFromJSONValue(const JSON_VALUE *value); -static GF_COMMAND M_LoadCommand(JSON_OBJECT *jcmd, GF_COMMAND fallback); static void M_LoadArray( JSON_OBJECT *obj, const GAME_FLOW *gf, const char *key, int32_t *count, void **elements, size_t element_size, M_LOAD_ARRAY_FUNC load_func, void *load_func_arg); +static void M_LoadSettings(JSON_OBJECT *obj, GF_LEVEL_SETTINGS *settings); + static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleIntEvent); static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandlePictureEvent); static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleAddItemEvent); static size_t M_LoadSequenceEvent( JSON_OBJECT *event_obj, GF_SEQUENCE_EVENT *event, void *extra_data); -static void M_LoadSequence(JSON_ARRAY *jarr, GF_SEQUENCE *sequence); - -static void M_LoadGlobalInjections(JSON_OBJECT *obj, GAME_FLOW *gf); -static void M_LoadRoot(JSON_OBJECT *obj, GAME_FLOW *gf); +static void M_LoadSequence(JSON_ARRAY *jseq_arr, GF_SEQUENCE *sequence); -static void M_LoadLevel( - JSON_OBJECT *obj, const GAME_FLOW *gf, GF_LEVEL *level, size_t idx, - void *user_arg); static void M_LoadLevelInjections( JSON_OBJECT *obj, const GAME_FLOW *gf, GF_LEVEL *level); +static void M_LoadLevelGameSpecifics( + JSON_OBJECT *obj, const GAME_FLOW *gf, GF_LEVEL *level); +static void M_LoadLevelSequence(JSON_OBJECT *obj, GF_LEVEL *level); +static void M_LoadLevel( + JSON_OBJECT *jlvl_obj, const GAME_FLOW *gf, GF_LEVEL *level, size_t idx, + void *user_arg); static void M_LoadLevelTable( JSON_OBJECT *obj, const GAME_FLOW *gf, const char *key, GF_LEVEL_TABLE *level_table, GF_LEVEL_TYPE default_level_type); + static void M_LoadLevels(JSON_OBJECT *obj, GAME_FLOW *gf); static void M_LoadCutscenes(JSON_OBJECT *obj, GAME_FLOW *gf); static void M_LoadDemos(JSON_OBJECT *obj, GAME_FLOW *gf); static void M_LoadTitleLevel(JSON_OBJECT *obj, GAME_FLOW *gf); - static void M_LoadFMV( JSON_OBJECT *obj, const GAME_FLOW *gf, GF_FMV *level, size_t idx, void *user_arg); static void M_LoadFMVs(JSON_OBJECT *obj, GAME_FLOW *gf); +static void M_LoadGlobalInjections(JSON_OBJECT *obj, GAME_FLOW *gf); +static void M_LoadRoot(JSON_OBJECT *obj, GAME_FLOW *gf); -static M_SEQUENCE_EVENT_HANDLER m_SequenceEventHandlers[] = { - // clang-format off - // Events without arguments - { GFS_ENABLE_SUNSET, NULL, NULL }, - { GFS_REMOVE_WEAPONS, NULL, NULL }, - { GFS_REMOVE_AMMO, NULL, NULL }, - { GFS_LEVEL_COMPLETE, NULL, NULL }, - { GFS_LEVEL_STATS, NULL, NULL }, - { GFS_TOTAL_STATS, NULL, NULL }, - { GFS_EXIT_TO_TITLE, NULL, NULL }, - - // Events with integer arguments - { GFS_SET_NUM_SECRETS, M_HandleIntEvent, "count" }, - { GFS_SET_CAMERA_ANGLE, M_HandleIntEvent, "angle" }, - { GFS_SET_START_ANIM, M_HandleIntEvent, "anim" }, - { GFS_PLAY_LEVEL, M_HandleIntEvent, "level_id" }, - { GFS_PLAY_CUTSCENE, M_HandleIntEvent, "cutscene_id" }, - { GFS_PLAY_FMV, M_HandleIntEvent, "fmv_id" }, - { GFS_PLAY_MUSIC, M_HandleIntEvent, "music_track" }, - { GFS_DISABLE_FLOOR, M_HandleIntEvent, "height" }, - - // Special cases with custom handlers - { GFS_DISPLAY_PICTURE, M_HandlePictureEvent, NULL }, - { GFS_ADD_ITEM, M_HandleAddItemEvent, NULL }, - { GFS_ADD_SECRET_REWARD, M_HandleAddItemEvent, NULL }, - - // Sentinel to mark the end of the table - { (GF_SEQUENCE_EVENT_TYPE)-1, NULL, NULL }, - // clang-format on -}; - -static GAME_OBJECT_ID M_GetObjectFromJSONValue(const JSON_VALUE *const value) -{ - int32_t object_id = JSON_ValueGetInt(value, JSON_INVALID_NUMBER); - if (object_id == JSON_INVALID_NUMBER) { - const char *const object_key = - JSON_ValueGetString(value, JSON_INVALID_STRING); - if (object_key == JSON_INVALID_STRING) { - return NO_OBJECT; - } - object_id = Object_IdFromKey(object_key); - } - if (object_id < 0 || object_id >= O_NUMBER_OF) { - return NO_OBJECT; - } - return object_id; -} - -static GF_COMMAND M_LoadCommand( - JSON_OBJECT *const jcmd, const GF_COMMAND fallback) -{ - if (jcmd == NULL) { - return fallback; - } - - const char *const action_str = - JSON_ObjectGetString(jcmd, "action", JSON_INVALID_STRING); - const int32_t param = JSON_ObjectGetInt(jcmd, "param", -1); - if (action_str == JSON_INVALID_STRING) { - Shell_ExitSystemFmt("Unknown game flow action: %s", action_str); - return fallback; - } - - const GF_ACTION action = - ENUM_MAP_GET(GF_ACTION, action_str, (GF_ACTION)-1234); - if (action == (GF_ACTION)-1234) { - Shell_ExitSystemFmt("Unknown game flow action: %s", action_str); - return fallback; - } - - return (GF_COMMAND) { .action = action, .param = param }; -} +#if TR_VERSION == 1 + #include "./reader_tr1.def.c" +#elif TR_VERSION == 2 + #include "./reader_tr2.def.c" +#endif static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleIntEvent) { @@ -182,14 +119,63 @@ static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleAddItemEvent) if (event != NULL) { GF_ADD_ITEM_DATA *const event_data = extra_data; event_data->object_id = object_id; - event_data->qty = JSON_ObjectGetInt(event_obj, "quantity", 1); + event_data->quantity = JSON_ObjectGetInt(event_obj, "quantity", 1); +#if TR_VERSION == 2 event_data->inv_type = event->type == GFS_ADD_ITEM ? GF_INV_REGULAR : GF_INV_SECRET; +#endif event->data = event_data; } return sizeof(GF_ADD_ITEM_DATA); } +static GAME_OBJECT_ID M_GetObjectFromJSONValue(const JSON_VALUE *const value) +{ + int32_t object_id = JSON_ValueGetInt(value, JSON_INVALID_NUMBER); + if (object_id == JSON_INVALID_NUMBER) { + const char *const object_key = + JSON_ValueGetString(value, JSON_INVALID_STRING); + if (object_key == JSON_INVALID_STRING) { + return NO_OBJECT; + } + object_id = Object_IdFromKey(object_key); + } + if (object_id < 0 || object_id >= O_NUMBER_OF) { + return NO_OBJECT; + } + return object_id; +} + +static void M_LoadArray( + JSON_OBJECT *const obj, const GAME_FLOW *const gf, const char *const key, + int32_t *const count, void **const elements, const size_t element_size, + const M_LOAD_ARRAY_FUNC load_func, void *const load_func_arg) +{ + if (!JSON_ObjectContainsKey(obj, key)) { + return; + } + + JSON_ARRAY *const elem_arr = JSON_ObjectGetArray(obj, key); + if (elem_arr == NULL) { + Shell_ExitSystemFmt("'%s' must be a list", key); + } + + *count = elem_arr->length; + *elements = Memory_Alloc(element_size * (*count)); + + JSON_ARRAY_ELEMENT *elem = elem_arr->start; + for (size_t i = 0; i < elem_arr->length; i++, elem = elem->next) { + void *const element = (char *)*elements + i * element_size; + + JSON_OBJECT *const elem_obj = JSON_ValueAsObject(elem->value); + if (elem_obj == NULL) { + Shell_ExitSystemFmt("'%s' elements must be dictionaries", key); + } + + load_func(elem_obj, gf, element, i, load_func_arg); + } +} + static size_t M_LoadSequenceEvent( JSON_OBJECT *const event_obj, GF_SEQUENCE_EVENT *const event, void *const extra_data) @@ -198,7 +184,7 @@ static size_t M_LoadSequenceEvent( const GF_SEQUENCE_EVENT_TYPE type = ENUM_MAP_GET(GF_SEQUENCE_EVENT_TYPE, type_str, -1); - const M_SEQUENCE_EVENT_HANDLER *handler = m_SequenceEventHandlers; + const M_SEQUENCE_EVENT_HANDLER *handler = M_GetSequenceEventHandlers(); while (handler->event_type != (GF_SEQUENCE_EVENT_TYPE)-1 && handler->event_type != type) { handler++; @@ -207,7 +193,6 @@ static size_t M_LoadSequenceEvent( if (handler->event_type != type) { Shell_ExitSystemFmt( "Unknown game flow sequence event type: '%s'", type); - return -1; } int32_t extra_data_size = 0; @@ -227,17 +212,17 @@ static size_t M_LoadSequenceEvent( return extra_data_size; } -static void M_LoadSequence(JSON_ARRAY *const jarr, GF_SEQUENCE *const sequence) +static void M_LoadSequence( + JSON_ARRAY *const jseq_arr, GF_SEQUENCE *const sequence) { - if (jarr == NULL) { - Shell_ExitSystem("Level has no sequence"); - } - sequence->length = 0; + if (jseq_arr == NULL) { + return; + } size_t event_base_size = sizeof(GF_SEQUENCE_EVENT); size_t total_data_size = 0; - for (size_t i = 0; i < jarr->length; i++) { - JSON_OBJECT *jevent = JSON_ArrayGetObject(jarr, i); + for (size_t i = 0; i < jseq_arr->length; i++) { + JSON_OBJECT *jevent = JSON_ArrayGetObject(jseq_arr, i); const int32_t event_extra_size = M_LoadSequenceEvent(jevent, NULL, NULL); if (event_extra_size < 0) { @@ -255,7 +240,7 @@ static void M_LoadSequence(JSON_ARRAY *const jarr, GF_SEQUENCE *const sequence) int32_t j = 0; for (int32_t i = 0; i < sequence->length; i++) { - JSON_OBJECT *const jevent = JSON_ArrayGetObject(jarr, i); + JSON_OBJECT *const jevent = JSON_ArrayGetObject(jseq_arr, i); const int32_t event_extra_size = M_LoadSequenceEvent(jevent, &sequence->events[j++], extra_data_ptr); if (event_extra_size < 0) { @@ -266,99 +251,74 @@ static void M_LoadSequence(JSON_ARRAY *const jarr, GF_SEQUENCE *const sequence) } } -static void M_LoadRoot(JSON_OBJECT *const obj, GAME_FLOW *const gf) +static void M_LoadLevelInjections( + JSON_OBJECT *const jlvl_obj, const GAME_FLOW *const gf, + GF_LEVEL *const level) { - gf->cmd_init = M_LoadCommand( - JSON_ObjectGetObject(obj, "cmd_init"), - (GF_COMMAND) { .action = GF_EXIT_TO_TITLE }); - gf->cmd_title = M_LoadCommand( - JSON_ObjectGetObject(obj, "cmd_title"), - (GF_COMMAND) { .action = GF_NOOP }); - gf->cmd_death_demo_mode = M_LoadCommand( - JSON_ObjectGetObject(obj, "cmd_death_demo_mode"), - (GF_COMMAND) { .action = GF_EXIT_TO_TITLE }); - gf->cmd_death_in_game = M_LoadCommand( - JSON_ObjectGetObject(obj, "cmd_death_in_game"), - (GF_COMMAND) { .action = GF_NOOP }); - gf->cmd_demo_interrupt = M_LoadCommand( - JSON_ObjectGetObject(obj, "cmd_demo_interrupt"), - (GF_COMMAND) { .action = GF_EXIT_TO_TITLE }); - gf->cmd_demo_end = M_LoadCommand( - JSON_ObjectGetObject(obj, "cmd_demo_end"), - (GF_COMMAND) { .action = GF_EXIT_TO_TITLE }); - - gf->is_demo_version = JSON_ObjectGetBool(obj, "demo_version", false); - - // clang-format off - gf->demo_delay = JSON_ObjectGetInt(obj, "demo_delay", 30); - gf->load_save_disabled = JSON_ObjectGetBool(obj, "load_save_disabled", false); - gf->cheat_keys = JSON_ObjectGetBool(obj, "cheat_keys", true); - gf->lockout_option_ring = JSON_ObjectGetBool(obj, "lockout_option_ring", true); - gf->play_any_level = JSON_ObjectGetBool(obj, "play_any_level", false); - gf->gym_enabled = JSON_ObjectGetBool(obj, "gym_enabled", true); - gf->single_level = JSON_ObjectGetInt(obj, "single_level", -1); - // clang-format on - - gf->secret_track = JSON_ObjectGetInt(obj, "secret_track", MX_INACTIVE); - - M_LoadGlobalInjections(obj, gf); -} + const bool inherit = + JSON_ObjectGetBool(jlvl_obj, "inherit_injections", true); + JSON_ARRAY *const injections = JSON_ObjectGetArray(jlvl_obj, "injections"); -static void M_LoadGlobalInjections(JSON_OBJECT *const obj, GAME_FLOW *const gf) -{ - gf->injections.count = 0; - JSON_ARRAY *const injections = JSON_ObjectGetArray(obj, "injections"); - if (injections == NULL) { + level->injections.count = 0; + if (injections == NULL && !inherit) { return; } - gf->injections.count = injections->length; - gf->injections.data_paths = - Memory_Alloc(sizeof(char *) * injections->length); - for (size_t i = 0; i < injections->length; i++) { - const char *const str = JSON_ArrayGetString(injections, i, NULL); - gf->injections.data_paths[i] = Memory_DupStr(str); - } -} - -static void M_LoadArray( - JSON_OBJECT *const obj, const GAME_FLOW *const gf, const char *const key, - int32_t *const count, void **const elements, const size_t element_size, - const M_LOAD_ARRAY_FUNC load_func, void *const load_func_arg) -{ - if (!JSON_ObjectContainsKey(obj, key)) { - return; + if (inherit) { + level->injections.count += gf->injections.count; } - - JSON_ARRAY *const elem_arr = JSON_ObjectGetArray(obj, key); - if (elem_arr == NULL) { - Shell_ExitSystemFmt("'%s' must be a list", key); + if (injections != NULL) { + level->injections.count += injections->length; } - *count = elem_arr->length; - *elements = Memory_Alloc(element_size * (*count)); - - JSON_ARRAY_ELEMENT *elem = elem_arr->start; - for (size_t i = 0; i < elem_arr->length; i++, elem = elem->next) { - void *const element = (char *)*elements + i * element_size; + level->injections.data_paths = + Memory_Alloc(sizeof(char *) * level->injections.count); - JSON_OBJECT *const elem_obj = JSON_ValueAsObject(elem->value); - if (elem_obj == NULL) { - Shell_ExitSystemFmt("'%s' elements must be dictionaries", key); + int32_t base_index = 0; + if (inherit) { + for (int32_t i = 0; i < gf->injections.count; i++) { + level->injections.data_paths[i] = + Memory_DupStr(gf->injections.data_paths[i]); } + base_index = gf->injections.count; + } - load_func(elem_obj, gf, element, i, load_func_arg); + if (injections == NULL) { + return; + } + + for (size_t i = 0; i < injections->length; i++) { + const char *const str = JSON_ArrayGetString(injections, i, NULL); + level->injections.data_paths[base_index + i] = Memory_DupStr(str); } } -static void M_LoadLevelSequence(JSON_OBJECT *const obj, GF_LEVEL *const level) +static void M_LoadLevelSequence( + JSON_OBJECT *const jlvl_obj, GF_LEVEL *const level) { - JSON_ARRAY *const jarr = JSON_ObjectGetArray(obj, "sequence"); - if (jarr == NULL) { - Shell_ExitSystem("Level has no sequence"); + JSON_ARRAY *const jseq_arr = JSON_ObjectGetArray(jlvl_obj, "sequence"); + if (jseq_arr == NULL) { + Shell_ExitSystemFmt("level %d: 'sequence' must be a list", level->num); } + M_LoadSequence(jseq_arr, &level->sequence); + + for (int32_t i = 0; i < level->sequence.length; i++) { + GF_SEQUENCE_EVENT *const event = &level->sequence.events[i]; + + bool should_fix = false; + const GF_SEQUENCE_EVENT_TYPE *ptr = M_GetLevelArgSequenceEvents(); + while (*ptr != (GF_SEQUENCE_EVENT_TYPE)-1) { + if (event->type == *ptr) { + should_fix = true; + break; + } + ptr++; + } - M_LoadSequence(jarr, &level->sequence); + if (should_fix && (int32_t)(intptr_t)event->data == -1) { + event->data = (void *)(intptr_t)level->num; + } + } } static void M_LoadLevel( @@ -384,8 +344,7 @@ static void M_LoadLevel( } if (level->type != GFL_NORMAL - || (user_type != GFL_NORMAL && user_type != GFL_BONUS - && user_type != GFL_GYM)) { + && GF_GetLevelTableType(user_type) != GFLT_MAIN) { Shell_ExitSystemFmt( "cannot override level type=%s to %s", ENUM_MAP_TO_STRING(GF_LEVEL_TYPE, level->type), @@ -395,6 +354,12 @@ static void M_LoadLevel( } } +#if TR_VERSION == 1 + if (level->type == GFL_DUMMY) { + return; + } +#endif + { const char *const tmp = JSON_ObjectGetString(jlvl_obj, "path", JSON_INVALID_STRING); @@ -405,58 +370,25 @@ static void M_LoadLevel( level->path = Memory_DupStr(tmp); } - level->music_track = - JSON_ObjectGetInt(jlvl_obj, "music_track", MX_INACTIVE); - - M_LoadLevelSequence(jlvl_obj, level); - M_LoadLevelInjections(jlvl_obj, gf, level); - - for (int32_t i = 0; i < level->sequence.length; i++) { - if (level->sequence.events[i].type == GFS_PLAY_LEVEL - && (int32_t)(intptr_t)level->sequence.events[i].data == -1) { - level->sequence.events[i].data = (void *)(intptr_t)level->num; - } - } -} - -static void M_LoadLevelInjections( - JSON_OBJECT *const obj, const GAME_FLOW *const gf, GF_LEVEL *const level) -{ - const bool inherit = JSON_ObjectGetBool(obj, "inherit_injections", true); - JSON_ARRAY *const injections = JSON_ObjectGetArray(obj, "injections"); - - level->injections.count = 0; - if (injections == NULL && !inherit) { - return; - } - - if (inherit) { - level->injections.count += gf->injections.count; - } - if (injections != NULL) { - level->injections.count += injections->length; - } - - level->injections.data_paths = - Memory_Alloc(sizeof(char *) * level->injections.count); - int32_t base_index = 0; - - if (inherit) { - for (int32_t i = 0; i < gf->injections.count; i++) { - level->injections.data_paths[i] = - Memory_DupStr(gf->injections.data_paths[i]); + { + const JSON_VALUE *const tmp_v = + JSON_ObjectGetValue(jlvl_obj, "music_track"); + if (tmp_v != NULL) { + const int32_t tmp = JSON_ValueGetInt(tmp_v, JSON_INVALID_NUMBER); + if (tmp == JSON_INVALID_NUMBER) { + Shell_ExitSystemFmt( + "level %d: 'music_track' must be a number", level->num); + } + level->music_track = tmp; + } else { + level->music_track = MX_INACTIVE; } - base_index = gf->injections.count; } - if (injections == NULL) { - return; - } + M_LoadLevelGameSpecifics(jlvl_obj, gf, level); - for (size_t i = 0; i < injections->length; i++) { - const char *const str = JSON_ArrayGetString(injections, i, NULL); - level->injections.data_paths[base_index + i] = Memory_DupStr(str); - } + M_LoadLevelSequence(jlvl_obj, level); + M_LoadLevelInjections(jlvl_obj, gf, level); } static void M_LoadLevelTable( @@ -471,17 +403,21 @@ static void M_LoadLevelTable( static void M_LoadLevels(JSON_OBJECT *const obj, GAME_FLOW *const gf) { + JSON_ARRAY *const jlvl_arr = JSON_ObjectGetArray(obj, "levels"); + if (!jlvl_arr) { + Shell_ExitSystem("'levels' must be a list"); + } M_LoadLevelTable( obj, gf, "levels", &gf->level_tables[GFLT_MAIN], GFL_NORMAL); } -static void M_LoadCutscenes(JSON_OBJECT *obj, GAME_FLOW *const gf) +static void M_LoadCutscenes(JSON_OBJECT *const obj, GAME_FLOW *const gf) { M_LoadLevelTable( obj, gf, "cutscenes", &gf->level_tables[GFLT_CUTSCENES], GFL_CUTSCENE); } -static void M_LoadDemos(JSON_OBJECT *obj, GAME_FLOW *const gf) +static void M_LoadDemos(JSON_OBJECT *const obj, GAME_FLOW *const gf) { M_LoadLevelTable(obj, gf, "demos", &gf->level_tables[GFLT_DEMOS], GFL_DEMO); } @@ -513,6 +449,23 @@ static void M_LoadFMVs(JSON_OBJECT *const obj, GAME_FLOW *const gf) (M_LOAD_ARRAY_FUNC)M_LoadFMV, NULL); } +static void M_LoadGlobalInjections(JSON_OBJECT *const obj, GAME_FLOW *const gf) +{ + gf->injections.count = 0; + JSON_ARRAY *const injections = JSON_ObjectGetArray(obj, "injections"); + if (injections == NULL) { + return; + } + + gf->injections.count = injections->length; + gf->injections.data_paths = + Memory_Alloc(sizeof(char *) * injections->length); + for (size_t i = 0; i < injections->length; i++) { + const char *const str = JSON_ArrayGetString(injections, i, NULL); + gf->injections.data_paths[i] = Memory_DupStr(str); + } +} + void GF_Load(const char *const path) { GF_Shutdown(); diff --git a/src/libtrx/game/game_flow/reader_tr1.def.c b/src/libtrx/game/game_flow/reader_tr1.def.c new file mode 100644 index 000000000..65625472a --- /dev/null +++ b/src/libtrx/game/game_flow/reader_tr1.def.c @@ -0,0 +1,286 @@ +// NOTE: this is an included file, not a compile unit on its own. +// This is to avoid exposing symbols. + +static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleTotalStatsEvent); +static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleMeshSwapEvent); + +static void M_LoadLevelItemDrops( + JSON_OBJECT *obj, const GAME_FLOW *gf, GF_LEVEL *level); + +static GF_SEQUENCE_EVENT_TYPE m_LevelArgSequenceEvents[] = { + GFS_LOAD_LEVEL, + GFS_PLAY_LEVEL, + (GF_SEQUENCE_EVENT_TYPE)-1, +}; + +static GF_LEVEL_SETTINGS m_DefaultSettings = { + .water_color = { .r = 0.6, .g = 0.7, .b = 1.0 }, + .draw_distance_fade = 12.0f, + .draw_distance_max = 20.0f, +}; + +static M_SEQUENCE_EVENT_HANDLER m_SequenceEventHandlers[] = { + // clang-format off + // Events without arguments + { GFS_FLIP_MAP, NULL, NULL }, + { GFS_REMOVE_WEAPONS, NULL, NULL }, + { GFS_REMOVE_SCIONS, NULL, NULL }, + { GFS_REMOVE_AMMO, NULL, NULL }, + { GFS_REMOVE_MEDIPACKS, NULL, NULL }, + { GFS_EXIT_TO_TITLE, NULL, NULL }, + { GFS_LEVEL_STATS, NULL, NULL }, + { GFS_LEVEL_COMPLETE, NULL, NULL }, + + // Events with integer arguments + { GFS_LOAD_LEVEL, M_HandleIntEvent, "level_id" }, + { GFS_PLAY_LEVEL, M_HandleIntEvent, "level_id" }, + { GFS_PLAY_CUTSCENE, M_HandleIntEvent, "cutscene_id" }, + { GFS_PLAY_FMV, M_HandleIntEvent, "fmv_id" }, + { GFS_PLAY_MUSIC, M_HandleIntEvent, "music_track" }, + { GFS_SET_CAMERA_ANGLE, M_HandleIntEvent, "value" }, + { GFS_SETUP_BACON_LARA, M_HandleIntEvent, "anchor_room" }, + + // Special cases with custom handlers + { GFS_LOADING_SCREEN, M_HandlePictureEvent, NULL }, + { GFS_DISPLAY_PICTURE, M_HandlePictureEvent, NULL }, + { GFS_TOTAL_STATS, M_HandleTotalStatsEvent, NULL }, + { GFS_ADD_ITEM, M_HandleAddItemEvent, NULL }, + { GFS_MESH_SWAP, M_HandleMeshSwapEvent, NULL }, + + // Sentinel to mark the end of the table + { (GF_SEQUENCE_EVENT_TYPE)-1, NULL, NULL }, + // clang-format on +}; + +static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleTotalStatsEvent) +{ + const char *const path = + JSON_ObjectGetString(event_obj, "background_path", NULL); + if (path == NULL) { + Shell_ExitSystem("Missing picture path"); + return -1; + } + if (event != NULL) { + char *const event_data = extra_data; + strcpy(event_data, path); + event->data = event_data; + } + return strlen(path) + 1; +} + +GF_SEQUENCE_EVENT_TYPE *M_GetLevelArgSequenceEvents(void) +{ + return m_LevelArgSequenceEvents; +} + +static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleMeshSwapEvent) +{ + const GAME_OBJECT_ID object1_id = + M_GetObjectFromJSONValue(JSON_ObjectGetValue(event_obj, "object1_id")); + if (object1_id == NO_OBJECT) { + Shell_ExitSystem("'object1_id' is invalid"); + } + + const GAME_OBJECT_ID object2_id = + M_GetObjectFromJSONValue(JSON_ObjectGetValue(event_obj, "object2_id")); + if (object2_id == NO_OBJECT) { + Shell_ExitSystem("'object2_id' is invalid"); + } + + const int32_t mesh_num = + JSON_ObjectGetInt(event_obj, "mesh_id", JSON_INVALID_NUMBER); + if (mesh_num == JSON_INVALID_NUMBER) { + Shell_ExitSystem("'mesh_id' must be a number"); + } + + if (event != NULL) { + GF_MESH_SWAP_DATA *const swap_data = extra_data; + swap_data->object1_id = object1_id; + swap_data->object2_id = object2_id; + swap_data->mesh_num = mesh_num; + event->data = swap_data; + } + return sizeof(GF_MESH_SWAP_DATA); +} + +static void M_LoadSettings( + JSON_OBJECT *const obj, GF_LEVEL_SETTINGS *const settings) +{ + { + const double value = JSON_ObjectGetDouble( + obj, "draw_distance_fade", JSON_INVALID_NUMBER); + if (value != JSON_INVALID_NUMBER) { + settings->draw_distance_fade = value; + } + } + + { + const double value = + JSON_ObjectGetDouble(obj, "draw_distance_max", JSON_INVALID_NUMBER); + if (value != JSON_INVALID_NUMBER) { + settings->draw_distance_max = value; + } + } + + { + JSON_ARRAY *const tmp_arr = JSON_ObjectGetArray(obj, "water_color"); + if (tmp_arr != NULL) { + settings->water_color.r = + JSON_ArrayGetDouble(tmp_arr, 0, settings->water_color.r); + settings->water_color.g = + JSON_ArrayGetDouble(tmp_arr, 1, settings->water_color.g); + settings->water_color.b = + JSON_ArrayGetDouble(tmp_arr, 2, settings->water_color.b); + } + } +} + +static void M_LoadLevelGameSpecifics( + JSON_OBJECT *const jlvl_obj, const GAME_FLOW *const gf, + GF_LEVEL *const level) +{ + level->settings = gf->settings; + M_LoadSettings(jlvl_obj, &level->settings); + + level->unobtainable.pickups = + JSON_ObjectGetInt(jlvl_obj, "unobtainable_pickups", 0); + level->unobtainable.kills = + JSON_ObjectGetInt(jlvl_obj, "unobtainable_kills", 0); + level->unobtainable.secrets = + JSON_ObjectGetInt(jlvl_obj, "unobtainable_secrets", 0); + + { + JSON_VALUE *const tmp = JSON_ObjectGetValue(jlvl_obj, "lara_type"); + if (tmp == NULL) { + level->lara_type = O_LARA; + } else { + level->lara_type = M_GetObjectFromJSONValue(tmp); + } + if (level->lara_type == NO_OBJECT) { + Shell_ExitSystemFmt( + "level %d: 'lara_type' must be a valid game object id", + level->num); + } + } + + M_LoadLevelItemDrops(jlvl_obj, gf, level); +} + +static M_SEQUENCE_EVENT_HANDLER *M_GetSequenceEventHandlers(void) +{ + return m_SequenceEventHandlers; +} + +static void M_LoadLevelItemDrops( + JSON_OBJECT *const jlvl_obj, const GAME_FLOW *const gf, + GF_LEVEL *const level) +{ + JSON_ARRAY *const drops = JSON_ObjectGetArray(jlvl_obj, "item_drops"); + level->item_drops.count = 0; + + if (drops != NULL && gf->enable_tr2_item_drops) { + LOG_WARNING( + "TR2 item drops are enabled: gameflow-defined drops for level " + "%d will be ignored", + level->num); + return; + } + if (drops == NULL) { + return; + } + + level->item_drops.count = (signed)drops->length; + level->item_drops.data = + Memory_Alloc(sizeof(GF_DROP_ITEM_DATA) * (signed)drops->length); + + for (int32_t i = 0; i < level->item_drops.count; i++) { + GF_DROP_ITEM_DATA *data = &level->item_drops.data[i]; + JSON_OBJECT *jlvl_data = JSON_ArrayGetObject(drops, i); + + data->enemy_num = + JSON_ObjectGetInt(jlvl_data, "enemy_num", JSON_INVALID_NUMBER); + if (data->enemy_num == JSON_INVALID_NUMBER) { + Shell_ExitSystemFmt( + "level %d, item drop %d: 'enemy_num' must be a number", + level->num, i); + } + + JSON_ARRAY *object_arr = JSON_ObjectGetArray(jlvl_data, "object_ids"); + if (!object_arr) { + Shell_ExitSystemFmt( + "level %d, item drop %d: 'object_ids' must be an array", + level->num, i); + } + + data->count = (signed)object_arr->length; + data->object_ids = Memory_Alloc(sizeof(int16_t) * data->count); + for (int32_t j = 0; j < data->count; j++) { + const GAME_OBJECT_ID id = + M_GetObjectFromJSONValue(JSON_ArrayGetValue(object_arr, j)); + if (id == NO_OBJECT) { + Shell_ExitSystemFmt( + "level %d, item drop %d, index %d: 'object_id' " + "must be a valid object id", + level->num, i, j); + } + data->object_ids[j] = (int16_t)id; + } + } +} + +static void M_LoadRoot(JSON_OBJECT *const obj, GAME_FLOW *const gf) +{ + const char *tmp_s; + double tmp_d; + JSON_ARRAY *tmp_arr; + + tmp_s = JSON_ObjectGetString(obj, "main_menu_picture", JSON_INVALID_STRING); + if (tmp_s == JSON_INVALID_STRING) { + Shell_ExitSystem("'main_menu_picture' must be a string"); + } + gf->main_menu_background_path = Memory_DupStr(tmp_s); + + tmp_s = + JSON_ObjectGetString(obj, "savegame_fmt_legacy", JSON_INVALID_STRING); + if (tmp_s == JSON_INVALID_STRING) { + Shell_ExitSystem("'savegame_fmt_legacy' must be a string"); + } + gf->savegame_fmt_legacy = Memory_DupStr(tmp_s); + + tmp_s = JSON_ObjectGetString(obj, "savegame_fmt_bson", JSON_INVALID_STRING); + if (tmp_s == JSON_INVALID_STRING) { + Shell_ExitSystem("'savegame_fmt_bson' must be a string"); + } + gf->savegame_fmt_bson = Memory_DupStr(tmp_s); + + tmp_d = JSON_ObjectGetDouble(obj, "demo_delay", -1.0); + if (tmp_d < 0.0) { + Shell_ExitSystem("'demo_delay' must be a positive number"); + } + gf->demo_delay = tmp_d; + + gf->settings = m_DefaultSettings; + M_LoadSettings(obj, &gf->settings); + + tmp_arr = JSON_ObjectGetArray(obj, "injections"); + if (tmp_arr) { + gf->injections.count = tmp_arr->length; + gf->injections.data_paths = + Memory_Alloc(sizeof(char *) * tmp_arr->length); + for (size_t i = 0; i < tmp_arr->length; i++) { + const char *const str = JSON_ArrayGetString(tmp_arr, i, NULL); + gf->injections.data_paths[i] = Memory_DupStr(str); + } + } else { + gf->injections.count = 0; + } + + gf->enable_tr2_item_drops = + JSON_ObjectGetBool(obj, "enable_tr2_item_drops", false); + gf->convert_dropped_guns = + JSON_ObjectGetBool(obj, "convert_dropped_guns", false); + gf->enable_killer_pushblocks = + JSON_ObjectGetBool(obj, "enable_killer_pushblocks", true); + + M_LoadGlobalInjections(obj, gf); +} diff --git a/src/libtrx/game/game_flow/reader_tr2.def.c b/src/libtrx/game/game_flow/reader_tr2.def.c new file mode 100644 index 000000000..6b4ce6e47 --- /dev/null +++ b/src/libtrx/game/game_flow/reader_tr2.def.c @@ -0,0 +1,129 @@ +// NOTE: this is an included file, not a compile unit on its own. +// This is to avoid exposing symbols. + +static GF_COMMAND M_LoadCommand(JSON_OBJECT *jcmd, GF_COMMAND fallback); + +static GF_LEVEL_SETTINGS m_DefaultSettings = {}; + +static GF_SEQUENCE_EVENT_TYPE m_LevelArgSequenceEvents[] = { + GFS_PLAY_LEVEL, + (GF_SEQUENCE_EVENT_TYPE)-1, +}; + +static M_SEQUENCE_EVENT_HANDLER m_SequenceEventHandlers[] = { + // clang-format off + // Events without arguments + { GFS_ENABLE_SUNSET, NULL, NULL }, + { GFS_REMOVE_WEAPONS, NULL, NULL }, + { GFS_REMOVE_AMMO, NULL, NULL }, + { GFS_LEVEL_COMPLETE, NULL, NULL }, + { GFS_LEVEL_STATS, NULL, NULL }, + { GFS_TOTAL_STATS, NULL, NULL }, + { GFS_EXIT_TO_TITLE, NULL, NULL }, + + // Events with integer arguments + { GFS_SET_NUM_SECRETS, M_HandleIntEvent, "count" }, + { GFS_SET_CAMERA_ANGLE, M_HandleIntEvent, "angle" }, + { GFS_SET_START_ANIM, M_HandleIntEvent, "anim" }, + { GFS_PLAY_LEVEL, M_HandleIntEvent, "level_id" }, + { GFS_PLAY_CUTSCENE, M_HandleIntEvent, "cutscene_id" }, + { GFS_PLAY_FMV, M_HandleIntEvent, "fmv_id" }, + { GFS_PLAY_MUSIC, M_HandleIntEvent, "music_track" }, + { GFS_DISABLE_FLOOR, M_HandleIntEvent, "height" }, + + // Special cases with custom handlers + { GFS_DISPLAY_PICTURE, M_HandlePictureEvent, NULL }, + { GFS_ADD_ITEM, M_HandleAddItemEvent, NULL }, + { GFS_ADD_SECRET_REWARD, M_HandleAddItemEvent, NULL }, + + // Sentinel to mark the end of the table + { (GF_SEQUENCE_EVENT_TYPE)-1, NULL, NULL }, + // clang-format on +}; + +GF_SEQUENCE_EVENT_TYPE *M_GetLevelArgSequenceEvents(void) +{ + return m_LevelArgSequenceEvents; +} + +static void M_LoadSettings( + JSON_OBJECT *const obj, GF_LEVEL_SETTINGS *const settings) +{ +} + +static void M_LoadLevelGameSpecifics( + JSON_OBJECT *const jlvl_obj, const GAME_FLOW *const gf, + GF_LEVEL *const level) +{ +} + +static M_SEQUENCE_EVENT_HANDLER *M_GetSequenceEventHandlers(void) +{ + return m_SequenceEventHandlers; +} + +static GF_COMMAND M_LoadCommand( + JSON_OBJECT *const jcmd, const GF_COMMAND fallback) +{ + if (jcmd == NULL) { + return fallback; + } + + const char *const action_str = + JSON_ObjectGetString(jcmd, "action", JSON_INVALID_STRING); + const int32_t param = JSON_ObjectGetInt(jcmd, "param", -1); + if (action_str == JSON_INVALID_STRING) { + Shell_ExitSystemFmt("Unknown game flow action: %s", action_str); + return fallback; + } + + const GF_ACTION action = + ENUM_MAP_GET(GF_ACTION, action_str, (GF_ACTION)-1234); + if (action == (GF_ACTION)-1234) { + Shell_ExitSystemFmt("Unknown game flow action: %s", action_str); + return fallback; + } + + return (GF_COMMAND) { .action = action, .param = param }; +} + +static void M_LoadRoot(JSON_OBJECT *const obj, GAME_FLOW *const gf) +{ + gf->cmd_init = M_LoadCommand( + JSON_ObjectGetObject(obj, "cmd_init"), + (GF_COMMAND) { .action = GF_EXIT_TO_TITLE }); + gf->cmd_title = M_LoadCommand( + JSON_ObjectGetObject(obj, "cmd_title"), + (GF_COMMAND) { .action = GF_NOOP }); + gf->cmd_death_demo_mode = M_LoadCommand( + JSON_ObjectGetObject(obj, "cmd_death_demo_mode"), + (GF_COMMAND) { .action = GF_EXIT_TO_TITLE }); + gf->cmd_death_in_game = M_LoadCommand( + JSON_ObjectGetObject(obj, "cmd_death_in_game"), + (GF_COMMAND) { .action = GF_NOOP }); + gf->cmd_demo_interrupt = M_LoadCommand( + JSON_ObjectGetObject(obj, "cmd_demo_interrupt"), + (GF_COMMAND) { .action = GF_EXIT_TO_TITLE }); + gf->cmd_demo_end = M_LoadCommand( + JSON_ObjectGetObject(obj, "cmd_demo_end"), + (GF_COMMAND) { .action = GF_EXIT_TO_TITLE }); + + gf->is_demo_version = JSON_ObjectGetBool(obj, "demo_version", false); + + gf->settings = m_DefaultSettings; + M_LoadSettings(obj, &gf->settings); + + // clang-format off + gf->demo_delay = JSON_ObjectGetInt(obj, "demo_delay", 30); + gf->load_save_disabled = JSON_ObjectGetBool(obj, "load_save_disabled", false); + gf->cheat_keys = JSON_ObjectGetBool(obj, "cheat_keys", true); + gf->lockout_option_ring = JSON_ObjectGetBool(obj, "lockout_option_ring", true); + gf->play_any_level = JSON_ObjectGetBool(obj, "play_any_level", false); + gf->gym_enabled = JSON_ObjectGetBool(obj, "gym_enabled", true); + gf->single_level = JSON_ObjectGetInt(obj, "single_level", -1); + // clang-format on + + gf->secret_track = JSON_ObjectGetInt(obj, "secret_track", MX_INACTIVE); + + M_LoadGlobalInjections(obj, gf); +} diff --git a/src/libtrx/include/libtrx/game/game_flow.h b/src/libtrx/include/libtrx/game/game_flow.h index 3fd51744b..399396004 100644 --- a/src/libtrx/include/libtrx/game/game_flow.h +++ b/src/libtrx/include/libtrx/game/game_flow.h @@ -2,5 +2,6 @@ #include "./game_flow/common.h" #include "./game_flow/enum.h" +#include "./game_flow/reader.h" #include "./game_flow/sequencer.h" #include "./game_flow/types.h" diff --git a/src/tr1/game/game_flow/reader.h b/src/libtrx/include/libtrx/game/game_flow/reader.h similarity index 100% rename from src/tr1/game/game_flow/reader.h rename to src/libtrx/include/libtrx/game/game_flow/reader.h diff --git a/src/libtrx/include/libtrx/game/game_flow/types.h b/src/libtrx/include/libtrx/game/game_flow/types.h index d1d8a1976..42c00bef7 100644 --- a/src/libtrx/include/libtrx/game/game_flow/types.h +++ b/src/libtrx/include/libtrx/game/game_flow/types.h @@ -25,6 +25,38 @@ typedef struct { GF_SEQUENCE_EVENT *events; } GF_SEQUENCE; +// Concrete events data + +typedef struct { + char *path; + float display_time; + float fade_in_time; + float fade_out_time; +} GF_DISPLAY_PICTURE_DATA; + +#if TR_VERSION == 2 +typedef enum { + GF_INV_REGULAR, + GF_INV_SECRET, +} GF_INV_TYPE; +#endif + +typedef struct { + GAME_OBJECT_ID object_id; +#if TR_VERSION == 2 + GF_INV_TYPE inv_type; +#endif + int32_t quantity; +} GF_ADD_ITEM_DATA; + +#if TR_VERSION == 1 +typedef struct { + GAME_OBJECT_ID object1_id; + GAME_OBJECT_ID object2_id; + int32_t mesh_num; +} GF_MESH_SWAP_DATA; +#endif + // ---------------------------------------------------------------------------- // Game flow level structures // ---------------------------------------------------------------------------- @@ -38,13 +70,17 @@ typedef struct { const char *path; } GF_FMV; -#if TR_VERSION == 1 typedef struct { +#if TR_VERSION == 1 RGB_F water_color; float draw_distance_fade; float draw_distance_max; +#elif TR_VERSION == 2 + int32_t dummy; // silence warnings, keep the logic +#endif } GF_LEVEL_SETTINGS; +#if TR_VERSION == 1 typedef struct { int32_t enemy_num; int32_t count; @@ -66,9 +102,9 @@ typedef struct { GF_SEQUENCE sequence; INJECTION_DATA injections; -#if TR_VERSION == 1 GF_LEVEL_SETTINGS settings; +#if TR_VERSION == 1 struct { uint32_t pickups; uint32_t kills; @@ -118,8 +154,6 @@ typedef struct { bool convert_dropped_guns; bool enable_killer_pushblocks; }; - - GF_LEVEL_SETTINGS settings; #elif TR_VERSION == 2 // flow commands struct { @@ -150,5 +184,6 @@ typedef struct { #endif // other data + GF_LEVEL_SETTINGS settings; INJECTION_DATA injections; } GAME_FLOW; diff --git a/src/libtrx/meson.build b/src/libtrx/meson.build index 374a44213..df45ea2d7 100644 --- a/src/libtrx/meson.build +++ b/src/libtrx/meson.build @@ -111,6 +111,7 @@ sources = [ 'game/game.c', 'game/game_buf.c', 'game/game_flow/common.c', + 'game/game_flow/reader.c', 'game/game_flow/sequencer.c', 'game/game_flow/vars.c', 'game/game_string.c', diff --git a/src/tr1/game/game_flow.h b/src/tr1/game/game_flow.h index 9c407474e..79f766b3a 100644 --- a/src/tr1/game/game_flow.h +++ b/src/tr1/game/game_flow.h @@ -1,6 +1,7 @@ #pragma once #include "./game_flow/common.h" -#include "./game_flow/reader.h" #include "./game_flow/sequencer.h" #include "./game_flow/vars.h" + +#include diff --git a/src/tr1/game/game_flow/reader.c b/src/tr1/game/game_flow/reader.c deleted file mode 100644 index 3d7c4a7b1..000000000 --- a/src/tr1/game/game_flow/reader.c +++ /dev/null @@ -1,723 +0,0 @@ -#include "game/game_flow/reader.h" - -#include "game/game_flow/common.h" -#include "game/game_flow/types.h" -#include "game/game_flow/vars.h" -#include "game/shell.h" -#include "global/types.h" -#include "global/vars.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#define DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(name) \ - int32_t name( \ - JSON_OBJECT *event_obj, GF_SEQUENCE_EVENT *event, void *extra_data, \ - void *user_arg) -typedef int32_t (*M_SEQUENCE_EVENT_HANDLER_FUNC)( - JSON_OBJECT *event_obj, GF_SEQUENCE_EVENT *event, void *extra_data, - void *user_arg); -typedef struct { - GF_SEQUENCE_EVENT_TYPE event_type; - M_SEQUENCE_EVENT_HANDLER_FUNC handler_func; - void *handler_func_arg; -} M_SEQUENCE_EVENT_HANDLER; - -typedef void (*M_LOAD_ARRAY_FUNC)( - JSON_OBJECT *source_elem, const GAME_FLOW *gf, void *target_elem, - size_t target_elem_idx, void *user_arg); - -static GAME_OBJECT_ID M_GetObjectFromJSONValue(const JSON_VALUE *value); - -static void M_LoadArray( - JSON_OBJECT *obj, const GAME_FLOW *gf, const char *key, int32_t *count, - void **elements, size_t element_size, M_LOAD_ARRAY_FUNC load_func, - void *load_func_arg); - -static bool M_IsLegacySequence(const char *type_str); -static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleIntEvent); -static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandlePictureEvent); -static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleTotalStatsEvent); -static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleAddItemEvent); -static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleMeshSwapEvent); - -static void M_LoadSettings(JSON_OBJECT *obj, GF_LEVEL_SETTINGS *settings); -static void M_LoadLevelSequence( - JSON_OBJECT *obj, GAME_FLOW *gf, GF_LEVEL *level); -static void M_LoadLevelInjections( - JSON_OBJECT *obj, GAME_FLOW *gf, GF_LEVEL *level); -static void M_LoadLevelItemDrops( - JSON_OBJECT *obj, GAME_FLOW *gf, GF_LEVEL *level); -static void M_LoadLevel( - JSON_OBJECT *jlvl_obj, GAME_FLOW *gf, GF_LEVEL *level, size_t idx, - void *user_arg); -static void M_LoadLevelTable( - JSON_OBJECT *obj, const GAME_FLOW *gf, const char *key, - GF_LEVEL_TABLE *level_table, GF_LEVEL_TYPE default_level_type); -static void M_LoadLevels(JSON_OBJECT *obj, GAME_FLOW *gf); -static void M_LoadCutscenes(JSON_OBJECT *obj, GAME_FLOW *gf); -static void M_LoadDemos(JSON_OBJECT *obj, GAME_FLOW *gf); -static void M_LoadTitleLevel(JSON_OBJECT *obj, GAME_FLOW *gf); - -static void M_LoadFMV( - JSON_OBJECT *obj, const GAME_FLOW *gf, GF_FMV *level, size_t idx, - void *user_arg); -static void M_LoadFMVs(JSON_OBJECT *obj, GAME_FLOW *gf); - -static void M_LoadRoot(JSON_OBJECT *obj, GAME_FLOW *gf); - -static GF_LEVEL_SETTINGS m_DefaultSettings = { - .water_color = { .r = 0.6, .g = 0.7, .b = 1.0 }, - .draw_distance_fade = 12.0f, - .draw_distance_max = 20.0f, -}; - -static M_SEQUENCE_EVENT_HANDLER m_SequenceEventHandlers[] = { - // clang-format off - // Events without arguments - { GFS_FLIP_MAP, NULL, NULL }, - { GFS_REMOVE_WEAPONS, NULL, NULL }, - { GFS_REMOVE_SCIONS, NULL, NULL }, - { GFS_REMOVE_AMMO, NULL, NULL }, - { GFS_REMOVE_MEDIPACKS, NULL, NULL }, - { GFS_EXIT_TO_TITLE, NULL, NULL }, - { GFS_LEVEL_STATS, NULL, NULL }, - { GFS_LEVEL_COMPLETE, NULL, NULL }, - - // Events with integer arguments - { GFS_LOAD_LEVEL, M_HandleIntEvent, "level_id" }, - { GFS_PLAY_LEVEL, M_HandleIntEvent, "level_id" }, - { GFS_PLAY_CUTSCENE, M_HandleIntEvent, "cutscene_id" }, - { GFS_PLAY_FMV, M_HandleIntEvent, "fmv_id" }, - { GFS_PLAY_MUSIC, M_HandleIntEvent, "music_track" }, - { GFS_SET_CAMERA_ANGLE, M_HandleIntEvent, "value" }, - { GFS_SETUP_BACON_LARA, M_HandleIntEvent, "anchor_room" }, - - // Special cases with custom handlers - { GFS_LOADING_SCREEN, M_HandlePictureEvent, NULL }, - { GFS_DISPLAY_PICTURE, M_HandlePictureEvent, NULL }, - { GFS_TOTAL_STATS, M_HandleTotalStatsEvent, NULL }, - { GFS_ADD_ITEM, M_HandleAddItemEvent, NULL }, - { GFS_MESH_SWAP, M_HandleMeshSwapEvent, NULL }, - - // Sentinel to mark the end of the table - { (GF_SEQUENCE_EVENT_TYPE)-1, NULL, NULL }, - // clang-format on -}; - -static GAME_OBJECT_ID M_GetObjectFromJSONValue(const JSON_VALUE *const value) -{ - int32_t object_id = JSON_ValueGetInt(value, JSON_INVALID_NUMBER); - if (object_id == JSON_INVALID_NUMBER) { - const char *const object_key = - JSON_ValueGetString(value, JSON_INVALID_STRING); - if (object_key == JSON_INVALID_STRING) { - return NO_OBJECT; - } - object_id = Object_IdFromKey(object_key); - } - if (object_id < 0 || object_id >= O_NUMBER_OF) { - return NO_OBJECT; - } - return object_id; -} - -static void M_LoadArray( - JSON_OBJECT *const obj, const GAME_FLOW *const gf, const char *const key, - int32_t *const count, void **const elements, const size_t element_size, - const M_LOAD_ARRAY_FUNC load_func, void *const load_func_arg) -{ - if (!JSON_ObjectContainsKey(obj, key)) { - return; - } - - JSON_ARRAY *const elem_arr = JSON_ObjectGetArray(obj, key); - if (elem_arr == NULL) { - Shell_ExitSystemFmt("'%s' must be a list", key); - } - - *count = elem_arr->length; - *elements = Memory_Alloc(element_size * (*count)); - - JSON_ARRAY_ELEMENT *elem = elem_arr->start; - for (size_t i = 0; i < elem_arr->length; i++, elem = elem->next) { - void *const element = (char *)*elements + i * element_size; - - JSON_OBJECT *const elem_obj = JSON_ValueAsObject(elem->value); - if (elem_obj == NULL) { - Shell_ExitSystemFmt("'%s' elements must be dictionaries", key); - } - - load_func(elem_obj, gf, element, i, load_func_arg); - } -} - -static bool M_IsLegacySequence(const char *const type_str) -{ - return strcmp(type_str, "fix_pyramid_secret") == 0 - || strcmp(type_str, "stop_cine") == 0; -} - -static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleIntEvent) -{ - if (event != NULL) { - event->data = - (void *)(intptr_t)JSON_ObjectGetInt(event_obj, user_arg, -1); - } - return 0; -} - -static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandlePictureEvent) -{ - const char *const path = JSON_ObjectGetString(event_obj, "path", NULL); - if (path == NULL) { - Shell_ExitSystem("Missing picture path"); - return -1; - } - if (event != NULL) { - GF_DISPLAY_PICTURE_DATA *const event_data = extra_data; - event_data->path = (char *)extra_data + sizeof(GF_DISPLAY_PICTURE_DATA); - event_data->display_time = - JSON_ObjectGetDouble(event_obj, "display_time", 5.0); - strcpy(event_data->path, path); - event->data = event_data; - } - return sizeof(GF_DISPLAY_PICTURE_DATA) + strlen(path) + 1; -} - -static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleTotalStatsEvent) -{ - const char *const path = - JSON_ObjectGetString(event_obj, "background_path", NULL); - if (path == NULL) { - Shell_ExitSystem("Missing picture path"); - return -1; - } - if (event != NULL) { - char *const event_data = extra_data; - strcpy(event_data, path); - event->data = event_data; - } - return strlen(path) + 1; -} - -static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleAddItemEvent) -{ - const GAME_OBJECT_ID object_id = - M_GetObjectFromJSONValue(JSON_ObjectGetValue(event_obj, "object_id")); - if (object_id == NO_OBJECT) { - Shell_ExitSystem("Invalid item"); - return -1; - } - if (event != NULL) { - GF_ADD_ITEM_DATA *const event_data = extra_data; - event_data->object_id = object_id; - event_data->quantity = JSON_ObjectGetInt(event_obj, "quantity", 1); - event->data = event_data; - } - return sizeof(GF_ADD_ITEM_DATA); -} - -static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleMeshSwapEvent) -{ - const GAME_OBJECT_ID object1_id = - M_GetObjectFromJSONValue(JSON_ObjectGetValue(event_obj, "object1_id")); - if (object1_id == NO_OBJECT) { - Shell_ExitSystem("'object1_id' is invalid"); - } - - const GAME_OBJECT_ID object2_id = - M_GetObjectFromJSONValue(JSON_ObjectGetValue(event_obj, "object2_id")); - if (object2_id == NO_OBJECT) { - Shell_ExitSystem("'object2_id' is invalid"); - } - - const int32_t mesh_num = - JSON_ObjectGetInt(event_obj, "mesh_id", JSON_INVALID_NUMBER); - if (mesh_num == JSON_INVALID_NUMBER) { - Shell_ExitSystem("'mesh_id' must be a number"); - } - - if (event != NULL) { - GF_MESH_SWAP_DATA *const swap_data = extra_data; - swap_data->object1_id = object1_id; - swap_data->object2_id = object2_id; - swap_data->mesh_num = mesh_num; - event->data = swap_data; - } - return sizeof(GF_MESH_SWAP_DATA); -} - -static void M_LoadSettings( - JSON_OBJECT *const obj, GF_LEVEL_SETTINGS *const settings) -{ - { - const double value = JSON_ObjectGetDouble( - obj, "draw_distance_fade", JSON_INVALID_NUMBER); - if (value != JSON_INVALID_NUMBER) { - settings->draw_distance_fade = value; - } - } - - { - const double value = - JSON_ObjectGetDouble(obj, "draw_distance_max", JSON_INVALID_NUMBER); - if (value != JSON_INVALID_NUMBER) { - settings->draw_distance_max = value; - } - } - - { - JSON_ARRAY *const tmp_arr = JSON_ObjectGetArray(obj, "water_color"); - if (tmp_arr != NULL) { - settings->water_color.r = - JSON_ArrayGetDouble(tmp_arr, 0, settings->water_color.r); - settings->water_color.g = - JSON_ArrayGetDouble(tmp_arr, 1, settings->water_color.g); - settings->water_color.b = - JSON_ArrayGetDouble(tmp_arr, 2, settings->water_color.b); - } - } -} - -static size_t M_LoadSequenceEvent( - JSON_OBJECT *const event_obj, GF_SEQUENCE_EVENT *const event, - void *const extra_data) -{ - const char *const type_str = JSON_ObjectGetString(event_obj, "type", ""); - if (M_IsLegacySequence(type_str)) { - LOG_WARNING("legacy type '%s' ignored", type_str); - return -1; - } - - const GF_SEQUENCE_EVENT_TYPE type = - ENUM_MAP_GET(GF_SEQUENCE_EVENT_TYPE, type_str, -1); - - const M_SEQUENCE_EVENT_HANDLER *handler = m_SequenceEventHandlers; - while (handler->event_type != (GF_SEQUENCE_EVENT_TYPE)-1 - && handler->event_type != type) { - handler++; - } - if (handler->event_type != type) { - Shell_ExitSystemFmt( - "Unknown game flow sequence event type: '%s'", type); - } - - int32_t extra_data_size = 0; - if (handler->handler_func != NULL) { - extra_data_size = handler->handler_func( - event_obj, NULL, NULL, handler->handler_func_arg); - } - if (extra_data_size >= 0 && event != NULL) { - event->type = handler->event_type; - if (handler->handler_func != NULL) { - handler->handler_func( - event_obj, event, extra_data, handler->handler_func_arg); - } else { - event->data = NULL; - } - } - return extra_data_size; -} - -static void M_LoadLevelSequence( - JSON_OBJECT *const jlvl_obj, GAME_FLOW *const gf, GF_LEVEL *const level) -{ - JSON_ARRAY *jseq_arr = JSON_ObjectGetArray(jlvl_obj, "sequence"); - if (!jseq_arr) { - Shell_ExitSystemFmt("level %d: 'sequence' must be a list", level->num); - } - - GF_SEQUENCE *const sequence = &level->sequence; - - sequence->length = 0; - size_t event_base_size = sizeof(GF_SEQUENCE_EVENT); - size_t total_data_size = 0; - for (size_t i = 0; i < jseq_arr->length; i++) { - JSON_OBJECT *jevent = JSON_ArrayGetObject(jseq_arr, i); - const int32_t event_extra_size = - M_LoadSequenceEvent(jevent, NULL, NULL); - if (event_extra_size < 0) { - // Parsing this event failed - discard it - continue; - } - total_data_size += event_base_size; - total_data_size += event_extra_size; - sequence->length++; - } - - char *const data = Memory_Alloc(total_data_size); - char *extra_data_ptr = data + event_base_size * sequence->length; - sequence->events = (GF_SEQUENCE_EVENT *)data; - - int32_t j = 0; - for (int32_t i = 0; i < sequence->length; i++) { - JSON_OBJECT *const jevent = JSON_ArrayGetObject(jseq_arr, i); - const int32_t event_extra_size = - M_LoadSequenceEvent(jevent, &sequence->events[j++], extra_data_ptr); - if (event_extra_size < 0) { - // Parsing this event failed - discard it - continue; - } - extra_data_ptr += event_extra_size; - } - - for (int32_t i = 0; i < sequence->length; i++) { - if ((sequence->events[i].type == GFS_LOAD_LEVEL - || sequence->events[i].type == GFS_PLAY_LEVEL) - && (int32_t)(intptr_t)sequence->events[i].data == -1) { - sequence->events[i].data = (void *)(intptr_t)level->num; - } - } -} - -static void M_LoadLevelInjections( - JSON_OBJECT *const jlvl_obj, GAME_FLOW *const gf, GF_LEVEL *const level) -{ - const bool inherit = - JSON_ObjectGetBool(jlvl_obj, "inherit_injections", true); - JSON_ARRAY *const injections = JSON_ObjectGetArray(jlvl_obj, "injections"); - - level->injections.count = 0; - if (injections == NULL && !inherit) { - return; - } - - if (inherit) { - level->injections.count += gf->injections.count; - } - if (injections != NULL) { - level->injections.count += injections->length; - } - - level->injections.data_paths = - Memory_Alloc(sizeof(char *) * level->injections.count); - int32_t base_index = 0; - - if (inherit) { - for (int32_t i = 0; i < gf->injections.count; i++) { - level->injections.data_paths[i] = - Memory_DupStr(gf->injections.data_paths[i]); - } - base_index = gf->injections.count; - } - - if (injections == NULL) { - return; - } - - for (size_t i = 0; i < injections->length; i++) { - const char *const str = JSON_ArrayGetString(injections, i, NULL); - level->injections.data_paths[base_index + i] = Memory_DupStr(str); - } -} - -static void M_LoadLevelItemDrops( - JSON_OBJECT *const jlvl_obj, GAME_FLOW *const gf, GF_LEVEL *const level) -{ - JSON_ARRAY *const drops = JSON_ObjectGetArray(jlvl_obj, "item_drops"); - level->item_drops.count = 0; - - if (drops != NULL && gf->enable_tr2_item_drops) { - LOG_WARNING( - "TR2 item drops are enabled: gameflow-defined drops for level " - "%d will be ignored", - level->num); - return; - } - if (drops == NULL) { - return; - } - - level->item_drops.count = (signed)drops->length; - level->item_drops.data = - Memory_Alloc(sizeof(GF_DROP_ITEM_DATA) * (signed)drops->length); - - for (int32_t i = 0; i < level->item_drops.count; i++) { - GF_DROP_ITEM_DATA *data = &level->item_drops.data[i]; - JSON_OBJECT *jlvl_data = JSON_ArrayGetObject(drops, i); - - data->enemy_num = - JSON_ObjectGetInt(jlvl_data, "enemy_num", JSON_INVALID_NUMBER); - if (data->enemy_num == JSON_INVALID_NUMBER) { - Shell_ExitSystemFmt( - "level %d, item drop %d: 'enemy_num' must be a number", - level->num, i); - } - - JSON_ARRAY *object_arr = JSON_ObjectGetArray(jlvl_data, "object_ids"); - if (!object_arr) { - Shell_ExitSystemFmt( - "level %d, item drop %d: 'object_ids' must be an array", - level->num, i); - } - - data->count = (signed)object_arr->length; - data->object_ids = Memory_Alloc(sizeof(int16_t) * data->count); - for (int32_t j = 0; j < data->count; j++) { - const GAME_OBJECT_ID id = - M_GetObjectFromJSONValue(JSON_ArrayGetValue(object_arr, j)); - if (id == NO_OBJECT) { - Shell_ExitSystemFmt( - "level %d, item drop %d, index %d: 'object_id' " - "must be a valid object id", - level->num, i, j); - } - data->object_ids[j] = (int16_t)id; - } - } -} - -static void M_LoadLevel( - JSON_OBJECT *const jlvl_obj, GAME_FLOW *const gf, GF_LEVEL *const level, - const size_t idx, void *const user_arg) -{ - level->num = idx; - - { - level->type = (GF_LEVEL_TYPE)(intptr_t)user_arg; - const JSON_VALUE *const tmp_v = JSON_ObjectGetValue(jlvl_obj, "type"); - if (tmp_v != NULL) { - const char *const tmp = - JSON_ValueGetString(tmp_v, JSON_INVALID_STRING); - if (tmp == JSON_INVALID_STRING) { - Shell_ExitSystemFmt( - "level %d: 'type' must be a string", level->num); - } - const GF_LEVEL_TYPE user_type = - ENUM_MAP_GET(GF_LEVEL_TYPE, tmp, -1); - if (user_type == (GF_LEVEL_TYPE)-1) { - Shell_ExitSystemFmt("unrecognized type '%s'", tmp); - } - - if (level->type != GFL_NORMAL - || (user_type != GFL_NORMAL && user_type != GFL_BONUS - && user_type != GFL_GYM && user_type != GFL_CURRENT - && user_type != GFL_DUMMY)) { - Shell_ExitSystemFmt( - "cannot override level type=%s to %s", - ENUM_MAP_TO_STRING(GF_LEVEL_TYPE, level->type), - ENUM_MAP_TO_STRING(GF_LEVEL_TYPE, user_type)); - } - level->type = user_type; - } - } - - if (level->type == GFL_DUMMY) { - return; - } - - { - const char *const tmp = - JSON_ObjectGetString(jlvl_obj, "path", JSON_INVALID_STRING); - if (tmp == JSON_INVALID_STRING) { - Shell_ExitSystemFmt( - "level %d: 'file' must be a string", level->num); - } - level->path = Memory_DupStr(tmp); - } - - { - const int32_t tmp = - JSON_ObjectGetInt(jlvl_obj, "music_track", JSON_INVALID_NUMBER); - if (tmp == JSON_INVALID_NUMBER) { - Shell_ExitSystemFmt( - "level %d: 'music_track' must be a number", level->num); - } - level->music_track = tmp; - } - - level->settings = gf->settings; - M_LoadSettings(jlvl_obj, &level->settings); - - level->unobtainable.pickups = - JSON_ObjectGetInt(jlvl_obj, "unobtainable_pickups", 0); - level->unobtainable.kills = - JSON_ObjectGetInt(jlvl_obj, "unobtainable_kills", 0); - level->unobtainable.secrets = - JSON_ObjectGetInt(jlvl_obj, "unobtainable_secrets", 0); - - { - JSON_VALUE *const tmp = JSON_ObjectGetValue(jlvl_obj, "lara_type"); - if (tmp == NULL) { - level->lara_type = O_LARA; - } else { - level->lara_type = M_GetObjectFromJSONValue(tmp); - } - if (level->lara_type == NO_OBJECT) { - Shell_ExitSystemFmt( - "level %d: 'lara_type' must be a valid game object id", - level->num); - } - } - - M_LoadLevelSequence(jlvl_obj, gf, level); - M_LoadLevelInjections(jlvl_obj, gf, level); - M_LoadLevelItemDrops(jlvl_obj, gf, level); -} - -static void M_LoadLevelTable( - JSON_OBJECT *const obj, const GAME_FLOW *const gf, const char *const key, - GF_LEVEL_TABLE *const level_table, const GF_LEVEL_TYPE default_level_type) -{ - M_LoadArray( - obj, gf, key, &level_table->count, (void **)&level_table->levels, - sizeof(GF_LEVEL), (M_LOAD_ARRAY_FUNC)M_LoadLevel, - (void *)(intptr_t)default_level_type); -} - -static void M_LoadLevels(JSON_OBJECT *const obj, GAME_FLOW *const gf) -{ - JSON_ARRAY *jlvl_arr = JSON_ObjectGetArray(obj, "levels"); - if (!jlvl_arr) { - Shell_ExitSystem("'levels' must be a list"); - } - - JSON_ARRAY_ELEMENT *jlvl_elem = jlvl_arr->start; - int32_t level_num = 0; - - M_LoadLevelTable( - obj, gf, "levels", &gf->level_tables[GFLT_MAIN], GFL_NORMAL); -} - -static void M_LoadCutscenes(JSON_OBJECT *const obj, GAME_FLOW *const gf) -{ - M_LoadLevelTable( - obj, gf, "cutscenes", &gf->level_tables[GFLT_CUTSCENES], GFL_CUTSCENE); -} - -static void M_LoadDemos(JSON_OBJECT *const obj, GAME_FLOW *const gf) -{ - M_LoadLevelTable(obj, gf, "demos", &gf->level_tables[GFLT_DEMOS], GFL_DEMO); -} - -static void M_LoadTitleLevel(JSON_OBJECT *obj, GAME_FLOW *const gf) -{ - JSON_OBJECT *title_obj = JSON_ObjectGetObject(obj, "title"); - if (title_obj != NULL) { - gf->title_level = Memory_Alloc(sizeof(GF_LEVEL)); - M_LoadLevel(title_obj, gf, gf->title_level, 0, GFL_TITLE); - } -} - -static void M_LoadFMV( - JSON_OBJECT *const obj, const GAME_FLOW *const gf, GF_FMV *const fmv, - size_t idx, void *const user_arg) -{ - const char *const path = JSON_ObjectGetString(obj, "path", NULL); - if (path == NULL) { - Shell_ExitSystemFmt("Missing FMV path"); - } - fmv->path = Memory_DupStr(path); -} - -static void M_LoadFMVs(JSON_OBJECT *const obj, GAME_FLOW *const gf) -{ - M_LoadArray( - obj, gf, "fmvs", &gf->fmv_count, (void **)&gf->fmvs, sizeof(GF_FMV), - (M_LOAD_ARRAY_FUNC)M_LoadFMV, NULL); -} - -static void M_LoadRoot(JSON_OBJECT *const obj, GAME_FLOW *const gf) -{ - const char *tmp_s; - double tmp_d; - JSON_ARRAY *tmp_arr; - - tmp_s = JSON_ObjectGetString(obj, "main_menu_picture", JSON_INVALID_STRING); - if (tmp_s == JSON_INVALID_STRING) { - Shell_ExitSystem("'main_menu_picture' must be a string"); - } - gf->main_menu_background_path = Memory_DupStr(tmp_s); - - tmp_s = - JSON_ObjectGetString(obj, "savegame_fmt_legacy", JSON_INVALID_STRING); - if (tmp_s == JSON_INVALID_STRING) { - Shell_ExitSystem("'savegame_fmt_legacy' must be a string"); - } - gf->savegame_fmt_legacy = Memory_DupStr(tmp_s); - - tmp_s = JSON_ObjectGetString(obj, "savegame_fmt_bson", JSON_INVALID_STRING); - if (tmp_s == JSON_INVALID_STRING) { - Shell_ExitSystem("'savegame_fmt_bson' must be a string"); - } - gf->savegame_fmt_bson = Memory_DupStr(tmp_s); - - tmp_d = JSON_ObjectGetDouble(obj, "demo_delay", -1.0); - if (tmp_d < 0.0) { - Shell_ExitSystem("'demo_delay' must be a positive number"); - } - gf->demo_delay = tmp_d; - - gf->settings = m_DefaultSettings; - M_LoadSettings(obj, &gf->settings); - - tmp_arr = JSON_ObjectGetArray(obj, "injections"); - if (tmp_arr) { - gf->injections.count = tmp_arr->length; - gf->injections.data_paths = - Memory_Alloc(sizeof(char *) * tmp_arr->length); - for (size_t i = 0; i < tmp_arr->length; i++) { - const char *const str = JSON_ArrayGetString(tmp_arr, i, NULL); - gf->injections.data_paths[i] = Memory_DupStr(str); - } - } else { - gf->injections.count = 0; - } - - gf->enable_tr2_item_drops = - JSON_ObjectGetBool(obj, "enable_tr2_item_drops", false); - gf->convert_dropped_guns = - JSON_ObjectGetBool(obj, "convert_dropped_guns", false); - gf->enable_killer_pushblocks = - JSON_ObjectGetBool(obj, "enable_killer_pushblocks", true); -} - -void GF_Load(const char *const path) -{ - GF_Shutdown(); - - char *script_data = NULL; - if (!File_Load(path, &script_data, NULL)) { - Shell_ExitSystem("Failed to open script file"); - } - - JSON_PARSE_RESULT parse_result; - JSON_VALUE *const root = JSON_ParseEx( - script_data, strlen(script_data), JSON_PARSE_FLAGS_ALLOW_JSON5, NULL, - NULL, &parse_result); - if (root == NULL) { - Shell_ExitSystemFmt( - "Failed to parse script file: %s in line %d, char %d", - JSON_GetErrorDescription(parse_result.error), - parse_result.error_line_no, parse_result.error_row_no, script_data); - } - JSON_OBJECT *const root_obj = JSON_ValueAsObject(root); - - GAME_FLOW *const gf = &g_GameFlow; - M_LoadRoot(root_obj, gf); - M_LoadLevels(root_obj, gf); - M_LoadCutscenes(root_obj, gf); - M_LoadDemos(root_obj, gf); - M_LoadFMVs(root_obj, gf); - M_LoadTitleLevel(root_obj, gf); - - if (GF_GetTitleLevel() == NULL) { - Shell_ExitSystem("missing title level"); - } - if (GF_GetFirstLevel() == NULL) { - Shell_ExitSystem("at least one level must be of normal type"); - } - - if (root != NULL) { - JSON_ValueFree(root); - } - Memory_FreePointer(&script_data); -} diff --git a/src/tr1/game/game_flow/sequencer.c b/src/tr1/game/game_flow/sequencer.c index 7e9f41b69..df3a2051a 100644 --- a/src/tr1/game/game_flow/sequencer.c +++ b/src/tr1/game/game_flow/sequencer.c @@ -181,8 +181,8 @@ static DECLARE_EVENT_HANDLER(M_HandlePicture) PHASE *const phase = Phase_Picture_Create((PHASE_PICTURE_ARGS) { .file_name = data->path, .display_time = data->display_time, - .fade_in_time = 1.0, - .fade_out_time = 1.0, + .fade_in_time = data->fade_in_time, + .fade_out_time = data->fade_out_time, .display_time_includes_fades = false, }); gf_cmd = PhaseExecutor_Run(phase); diff --git a/src/tr1/game/game_flow/types.h b/src/tr1/game/game_flow/types.h index aeeb9448b..b8f636959 100644 --- a/src/tr1/game/game_flow/types.h +++ b/src/tr1/game/game_flow/types.h @@ -1,28 +1,3 @@ #pragma once -#include "global/types.h" - #include -#include - -// ---------------------------------------------------------------------------- -// Sequencer structures -// ---------------------------------------------------------------------------- - -// Concrete events data - -typedef struct { - char *path; - double display_time; -} GF_DISPLAY_PICTURE_DATA; - -typedef struct { - GAME_OBJECT_ID object1_id; - GAME_OBJECT_ID object2_id; - int32_t mesh_num; -} GF_MESH_SWAP_DATA; - -typedef struct { - GAME_OBJECT_ID object_id; - int32_t quantity; -} GF_ADD_ITEM_DATA; diff --git a/src/tr1/meson.build b/src/tr1/meson.build index 80fd35071..50b73f90e 100644 --- a/src/tr1/meson.build +++ b/src/tr1/meson.build @@ -113,7 +113,6 @@ sources = [ 'game/game/game.c', 'game/game/game_draw.c', 'game/game_string.c', - 'game/game_flow/reader.c', 'game/game_flow/common.c', 'game/game_flow/sequencer.c', 'game/game_flow/sequencer_misc.c', diff --git a/src/tr2/game/game_flow.h b/src/tr2/game/game_flow.h index 7d2bfc2d8..be3d6878d 100644 --- a/src/tr2/game/game_flow.h +++ b/src/tr2/game/game_flow.h @@ -4,3 +4,5 @@ #include "game/game_flow/inventory.h" #include "game/game_flow/sequencer.h" #include "game/game_flow/vars.h" + +#include diff --git a/src/tr2/game/game_flow/reader.h b/src/tr2/game/game_flow/reader.h deleted file mode 100644 index 94c4c246b..000000000 --- a/src/tr2/game/game_flow/reader.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void GF_Load(const char *path); diff --git a/src/tr2/game/game_flow/sequencer.c b/src/tr2/game/game_flow/sequencer.c index ea955e501..d6bd6efcc 100644 --- a/src/tr2/game/game_flow/sequencer.c +++ b/src/tr2/game/game_flow/sequencer.c @@ -235,7 +235,8 @@ static DECLARE_EVENT_HANDLER(M_HandleAddItem) if (seq_ctx != GFSC_STORY) { const GF_ADD_ITEM_DATA *const data = (const GF_ADD_ITEM_DATA *)event->data; - GF_InventoryModifier_Add(data->object_id, data->inv_type, data->qty); + GF_InventoryModifier_Add( + data->object_id, data->inv_type, data->quantity); } return gf_cmd; } diff --git a/src/tr2/game/game_flow/types.h b/src/tr2/game/game_flow/types.h index 8d0cb0cfc..13f4b738b 100644 --- a/src/tr2/game/game_flow/types.h +++ b/src/tr2/game/game_flow/types.h @@ -3,23 +3,3 @@ #include "global/types.h" #include - -// Concrete events data - -typedef struct { - char *path; - float display_time; - float fade_in_time; - float fade_out_time; -} GF_DISPLAY_PICTURE_DATA; - -typedef enum { - GF_INV_REGULAR, - GF_INV_SECRET, -} GF_INV_TYPE; - -typedef struct { - GAME_OBJECT_ID object_id; - GF_INV_TYPE inv_type; - int32_t qty; -} GF_ADD_ITEM_DATA; diff --git a/src/tr2/game/shell/common.c b/src/tr2/game/shell/common.c index de8754898..7273eaa6c 100644 --- a/src/tr2/game/shell/common.c +++ b/src/tr2/game/shell/common.c @@ -8,7 +8,6 @@ #include "game/fmv.h" #include "game/game.h" #include "game/game_flow.h" -#include "game/game_flow/reader.h" #include "game/game_string.h" #include "game/input.h" #include "game/music.h" diff --git a/src/tr2/meson.build b/src/tr2/meson.build index 4036ed419..647b673c0 100644 --- a/src/tr2/meson.build +++ b/src/tr2/meson.build @@ -104,7 +104,6 @@ sources = [ 'game/game.c', 'game/game_string.c', 'game/game_flow/common.c', - 'game/game_flow/reader.c', 'game/game_flow/inventory.c', 'game/game_flow/sequencer.c', 'game/game_flow/sequencer_misc.c',