diff --git a/data/tr1/ship/cfg/TR1X_gameflow.json5 b/data/tr1/ship/cfg/TR1X_gameflow.json5 index 3f87e83a4..de08a4787 100644 --- a/data/tr1/ship/cfg/TR1X_gameflow.json5 +++ b/data/tr1/ship/cfg/TR1X_gameflow.json5 @@ -124,7 +124,9 @@ {"type": "loading_screen", "path": "data/images/peru.webp"}, {"type": "load_level"}, {"type": "play_level"}, - {"type": "exit_to_cine", "level_id": 16}, + {"type": "play_cutscene", "cutscene_id": 0}, + {"type": "level_stats", "level_id": 4}, + {"type": "exit_to_level", "level_id": 5}, ], }, @@ -226,7 +228,9 @@ {"type": "loading_screen", "path": "data/images/greece.webp"}, {"type": "load_level"}, {"type": "play_level"}, - {"type": "exit_to_cine", "level_id": 17}, + {"type": "play_cutscene", "cutscene_id": 1}, + {"type": "level_stats", "level_id": 9}, + {"type": "exit_to_level", "level_id": 10}, ], }, @@ -319,7 +323,8 @@ {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats", "level_id": 13}, - {"type": "exit_to_cine", "level_id": 18}, + {"type": "play_cutscene", "cutscene_id": 2}, + {"type": "exit_to_level", "level_id": 14}, ], }, @@ -340,7 +345,9 @@ {"type": "setup_bacon_lara", "anchor_room": 10}, {"type": "play_level"}, {"type": "play_fmv", "fmv_id": 9}, - {"type": "exit_to_cine", "level_id": 19}, + {"type": "play_cutscene", "cutscene_id": 3}, + {"type": "level_stats", "level_id": 14}, + {"type": "exit_to_level", "level_id": 15}, ], "unobtainable_pickups": 3, }, @@ -371,10 +378,91 @@ ], }, - // Level 16: Cut Scene 1 + // Level 16-19: Legacy savegame placeholders + {"type": "dummy"}, + {"type": "dummy"}, + {"type": "dummy"}, + {"type": "dummy"}, + + // Level 20: Title + { + "path": "data/title.phd", + "type": "title", + "music_track": 2, + "inherit_injections": false, + "injections": [ + "data/injections/pda_model.bin", + "data/injections/font.bin", + ], + "sequence": [ + {"type": "display_picture", "path": "data/images/eidos.webp", "display_time": 1}, + {"type": "play_fmv", "fmv_id": 0}, + {"type": "play_fmv", "fmv_id": 1}, + {"type": "play_fmv", "fmv_id": 2}, + {"type": "exit_to_title"}, + ], + }, + + // Level 21: Current Position + // This level is necessary to read TombATI's save files! + // OG has a special level called LV_CURRENT to handle save/load logic. + // TR1X does away without this hack. However, the existing save games + // expect the level count to match, otherwise the game will crash. + // Hence this dummy level. + { + "path": "data/current.phd", + "type": "current", + "music_track": 0, + "inherit_injections": false, + "sequence": [ + {"type": "exit_to_title"}, + ], + }, + ], + + "demos": [ + // Demo 1: City of Vilcabamba + { + "path": "data/level2.phd", + "music_track": 57, + "injections": [ + "data/injections/vilcabamba_itemrots.bin", + "data/injections/vilcabamba_textures.bin", + ], + "sequence": [ + {"type": "loading_screen", "path": "data/images/peru.webp"}, + {"type": "load_level"}, + {"type": "play_level"}, + {"type": "level_stats", "level_id": 2}, + {"type": "exit_to_level", "level_id": 3}, + ], + }, + + // Demo 2: Lost Valley + { + "path": "data/level3a.phd", + "music_track": 57, + "injections": [ + "data/injections/braid_valley.bin", + "data/injections/valley_itemrots.bin", + "data/injections/valley_skybox.bin", + "data/injections/valley_textures.bin", + ], + "sequence": [ + {"type": "loading_screen", "path": "data/images/peru.webp"}, + {"type": "load_level"}, + {"type": "play_level"}, + {"type": "level_stats", "level_id": 3}, + {"type": "exit_to_level", "level_id": 4}, + ], + }, + + ], + + "cutscenes": [ + // Cut Scene 1 { "path": "data/cut1.phd", - "type": "cutscene", "music_track": 0, "lara_type": "player_1", "inherit_injections": false, @@ -389,15 +477,12 @@ {"type": "set_cutscene_angle", "value": -23312}, {"type": "play_synced_audio", "audio_id": 23}, {"type": "play_level"}, - {"type": "level_stats", "level_id": 4}, - {"type": "exit_to_level", "level_id": 5}, ], }, - // Level 17: Cut Scene 2 + // Cut Scene 2 { "path": "data/cut2.phd", - "type": "cutscene", "music_track": 0, "lara_type": "player_1", "inherit_injections": false, @@ -414,15 +499,12 @@ {"type": "mesh_swap", "object1_id": "player_1", "object2_id": "pistol_anim", "mesh_id": 1}, {"type": "mesh_swap", "object1_id": "player_1", "object2_id": "pistol_anim", "mesh_id": 4}, {"type": "play_level"}, - {"type": "level_stats", "level_id": 9}, - {"type": "exit_to_level", "level_id": 10}, ], }, - // Level 18: Cut Scene 3 + // Cut Scene 3 { "path": "data/cut3.phd", - "type": "cutscene", "music_track": 0, "inherit_injections": false, "injections": [ @@ -436,14 +518,12 @@ {"type": "set_cutscene_angle", "value": 16384}, {"type": "play_synced_audio", "audio_id": 24}, {"type": "play_level"}, - {"type": "exit_to_level", "level_id": 14}, ], }, - // Level 19: Cut Scene 4 + // Cut Scene 4 { "path": "data/cut4.phd", - "type": "cutscene", "music_track": 0, "draw_distance_fade": 12.0, "draw_distance_max": 18.0, @@ -463,88 +543,10 @@ {"type": "mesh_swap", "object1_id": "player_1", "object2_id": "pistol_anim", "mesh_id": 1}, {"type": "mesh_swap", "object1_id": "player_1", "object2_id": "pistol_anim", "mesh_id": 4}, {"type": "play_level"}, - {"type": "level_stats", "level_id": 14}, - {"type": "exit_to_level", "level_id": 15}, - ], - }, - - // Level 20: Title - { - "path": "data/title.phd", - "type": "title", - "music_track": 2, - "inherit_injections": false, - "injections": [ - "data/injections/pda_model.bin", - "data/injections/font.bin", - ], - "sequence": [ - {"type": "display_picture", "path": "data/images/eidos.webp", "display_time": 1}, - {"type": "play_fmv", "fmv_id": 0}, - {"type": "play_fmv", "fmv_id": 1}, - {"type": "play_fmv", "fmv_id": 2}, - {"type": "exit_to_title"}, - ], - }, - - // Level 21: Current Position - // This level is necessary to read TombATI's save files! - // OG has a special level called LV_CURRENT to handle save/load logic. - // TR1X does away without this hack. However, the existing save games - // expect the level count to match, otherwise the game will crash. - // Hence this dummy level. - { - "path": "data/current.phd", - "type": "current", - "music_track": 0, - "inherit_injections": false, - "sequence": [ - {"type": "exit_to_title"}, ], }, ], - "demos": [ - // Demo 1: City of Vilcabamba - { - "path": "data/level2.phd", - "type": "normal", - "music_track": 57, - "injections": [ - "data/injections/vilcabamba_itemrots.bin", - "data/injections/vilcabamba_textures.bin", - ], - "sequence": [ - {"type": "loading_screen", "path": "data/images/peru.webp"}, - {"type": "load_level"}, - {"type": "play_level"}, - {"type": "level_stats", "level_id": 2}, - {"type": "exit_to_level", "level_id": 3}, - ], - }, - - // Demo 2: Lost Valley - { - "path": "data/level3a.phd", - "type": "normal", - "music_track": 57, - "injections": [ - "data/injections/braid_valley.bin", - "data/injections/valley_itemrots.bin", - "data/injections/valley_skybox.bin", - "data/injections/valley_textures.bin", - ], - "sequence": [ - {"type": "loading_screen", "path": "data/images/peru.webp"}, - {"type": "load_level"}, - {"type": "play_level"}, - {"type": "level_stats", "level_id": 3}, - {"type": "exit_to_level", "level_id": 4}, - ], - }, - - ], - // FMVs "fmvs": [ {"path": "fmv/core.avi"}, diff --git a/data/tr1/ship/cfg/TR1X_strings.json5 b/data/tr1/ship/cfg/TR1X_strings.json5 index d47c8ff04..c53770be7 100644 --- a/data/tr1/ship/cfg/TR1X_strings.json5 +++ b/data/tr1/ship/cfg/TR1X_strings.json5 @@ -131,25 +131,11 @@ "title": "The Great Pyramid", }, - // Level 16: Cut Scene 1 - { - "title": "Cut Scene 1", - }, - - // Level 17: Cut Scene 2 - { - "title": "Cut Scene 2", - }, - - // Level 18: Cut Scene 3 - { - "title": "Cut Scene 3", - }, - - // Level 19: Cut Scene 4 - { - "title": "Cut Scene 4", - }, + // Level 16-19: Legacy savegame placeholders + {"title": "Dummy"}, + {"title": "Dummy"}, + {"title": "Dummy"}, + {"title": "Dummy"}, // Level 20: Title { @@ -162,6 +148,13 @@ }, ], + "cutscenes": [ + {"title": "Cut Scene 1"}, + {"title": "Cut Scene 2"}, + {"title": "Cut Scene 3"}, + {"title": "Cut Scene 4"}, + ], + "demos": [ { "title": "City of Vilcabamba", diff --git a/docs/tr1/GAMEFLOW.md b/docs/tr1/GAMEFLOW.md index 87a414387..3b17283a6 100644 --- a/docs/tr1/GAMEFLOW.md +++ b/docs/tr1/GAMEFLOW.md @@ -42,6 +42,12 @@ various pieces of global behaviour. // etc }, ], +"cutscenes": [ + { + "path": "data/cut1.phd", + // etc + }, +], "demos": [ { "path": "data/gym.phd", @@ -396,9 +402,9 @@ Following are each of the properties available within a level. - cutscene + legacy - A cutscene level. + A placeholder level necessary to read TombATI's save files. @@ -516,13 +522,13 @@ default gameflow for examples. - exit_to_cine + play_cutscene - level_id + cutscene_id Integer - Exits to the specified cinematic level. + Plays the specified cinematic level (from the `cutscenes`). @@ -750,8 +756,13 @@ default gameflow for examples. +## Cutscenes +The `cutscenes` section contains all the cinematic levels, used with the +`play_cutscene` sequence. Its structure is identical to the `levels` section. + + ## Demos -The `demos` section shows all the levels that can play a demo when the player +The `demos` section contains all the levels that can play a demo when the player leaves the main inventory screen idle for a while or by using the `/demo` command. For the demos to work, these levels need to have demo data built-in. Aside from this requirement, this section works just like the `levels` section. @@ -775,92 +786,97 @@ game will exit to title. ```json5 { - // gym level definition -}, + "levels": [ + { + // gym level definition + }, -{ - "path": "data/level1.phd", - "type": "normal", - "music_track": 57, - "sequence": [ - {"type": "load_level"}, - {"type": "play_level"}, - {"type": "level_stats", "level_id": 1}, - {"type": "exit_to_level", "level_id": 2}, - ], -}, + { + "path": "data/level1.phd", + "type": "normal", + "music_track": 57, + "sequence": [ + {"type": "load_level"}, + {"type": "play_level"}, + {"type": "level_stats", "level_id": 1}, + {"type": "exit_to_level", "level_id": 2}, + ], + }, -{ - "path": "data/level2.phd", - "type": "normal", - "music_track": 57, - "sequence": [ - {"type": "load_level"}, - {"type": "play_level"}, - {"type": "level_stats", "level_id": 2}, - {"type": "exit_to_level", "level_id": 3}, - ], -}, + { + "path": "data/level2.phd", + "type": "normal", + "music_track": 57, + "sequence": [ + {"type": "load_level"}, + {"type": "play_level"}, + {"type": "level_stats", "level_id": 2}, + {"type": "exit_to_level", "level_id": 3}, + ], + }, -{ - "path": "data/level3.phd", - "type": "normal", - "music_track": 57, - "sequence": [ - {"type": "load_level"}, - {"type": "play_level"}, - {"type": "level_stats", "level_id": 3}, - {"type": "play_synced_audio", "audio_id": 19}, - {"type": "display_picture", "path": "data/end.pcx", "display_time": 7.5}, - {"type": "display_picture", "path": "data/cred1.pcx", "display_time": 7.5}, - {"type": "display_picture", "path": "data/cred2.pcx", "display_time": 7.5}, - {"type": "display_picture", "path": "data/cred3.pcx", "display_time": 7.5}, - {"type": "total_stats", "background_path": "data/install.pcx"}, - {"type": "exit_to_level", "level_id": 4}, - ], -}, + { + "path": "data/level3.phd", + "type": "normal", + "music_track": 57, + "sequence": [ + {"type": "load_level"}, + {"type": "play_level"}, + {"type": "level_stats", "level_id": 3}, + {"type": "play_synced_audio", "audio_id": 19}, + {"type": "display_picture", "path": "data/end.pcx", "display_time": 7.5}, + {"type": "display_picture", "path": "data/cred1.pcx", "display_time": 7.5}, + {"type": "display_picture", "path": "data/cred2.pcx", "display_time": 7.5}, + {"type": "display_picture", "path": "data/cred3.pcx", "display_time": 7.5}, + {"type": "total_stats", "background_path": "data/install.pcx"}, + {"type": "exit_to_level", "level_id": 4}, + ], + }, -{ - "path": "data/bonus1.phd", - "type": "bonus", - "music_track": 57, - "sequence": [ - {"type": "play_fmv", "fmv_path": "fmv/snow.avi"}, - {"type": "load_level"}, - {"type": "play_level"}, - {"type": "exit_to_cine", "level_id": 6}, - ], -}, + { + "path": "data/bonus1.phd", + "type": "bonus", + "music_track": 57, + "sequence": [ + {"type": "play_fmv", "fmv_path": "fmv/snow.avi"}, + {"type": "load_level"}, + {"type": "play_level"}, + {"type": "play_cutscene", "cutscene_id": 0}, + ], + }, -{ - "path": "data/bonus2.phd", - "type": "bonus", - "music_track": 57, - "sequence": [ - {"type": "load_level"}, - {"type": "play_level"}, - {"type": "level_stats", "level_id": 5}, - {"type": "play_synced_audio", "audio_id": 14}, - {"type": "total_stats", "background_path": "data/install.pcx"}, - {"type": "exit_to_title"}, + { + "path": "data/bonus2.phd", + "type": "bonus", + "music_track": 57, + "sequence": [ + {"type": "load_level"}, + {"type": "play_level"}, + {"type": "level_stats", "level_id": 5}, + {"type": "play_synced_audio", "audio_id": 14}, + {"type": "total_stats", "background_path": "data/install.pcx"}, + {"type": "exit_to_title"}, + ], + }, ], -}, -{ - "path": "data/bonuscut1.phd", - "type": "cutscene", - "music_track": 0, - "sequence": [ - {"type": "load_level"}, - {"type": "set_cam_x", "value": 36668}, - {"type": "set_cam_z", "value": 63180}, - {"type": "set_cutscene_angle", "value": -23312}, - {"type": "play_synced_audio", "audio_id": 23}, - {"type": "play_level"}, - {"type": "level_stats", "level_id": 4}, - {"type": "exit_to_level", "level_id": 5}, + "cutscenes": [ + { + "path": "data/bonuscut1.phd", + "music_track": 0, + "sequence": [ + {"type": "load_level"}, + {"type": "set_cam_x", "value": 36668}, + {"type": "set_cam_z", "value": 63180}, + {"type": "set_cutscene_angle", "value": -23312}, + {"type": "play_synced_audio", "audio_id": 23}, + {"type": "play_level"}, + {"type": "level_stats", "level_id": 4}, + {"type": "exit_to_level", "level_id": 5}, + ], + }, ], -}, +} ``` diff --git a/src/libtrx/game/game_string_table/common.c b/src/libtrx/game/game_string_table/common.c index 4e56f790f..0bcd1d716 100644 --- a/src/libtrx/game/game_string_table/common.c +++ b/src/libtrx/game/game_string_table/common.c @@ -96,45 +96,40 @@ void GameStringTable_Apply(const GAME_FLOW_LEVEL *const level) GF_GetLevel(i, GFL_DEMO), gs_file->demos.entries[i].title); } -#if TR_VERSION == 2 - // TODO: TR1 still has cutscene levels in a single linear sequence for (int32_t i = 0; i < GF_GetLevelCount(GFL_CUTSCENE); i++) { GF_SetLevelTitle( GF_GetLevel(i, GFL_CUTSCENE), gs_file->cutscenes.entries[i].title); } -#endif if (level != NULL) { const GS_LEVEL_TABLE *level_table = NULL; -#if TR_VERSION == 1 - // TODO: TR1 still has most levels in a single linear sequence switch (level->type) { - default: + case GFL_TITLE: + // TODO: TR1 still has some levels in a single linear sequence +#if TR_VERSION == 1 level_table = &gs_file->levels; - break; - case GFL_DEMO: - level_table = &gs_file->demos; - break; - } #elif TR_VERSION == 2 - switch (level->type) { + level_table = NULL; +#endif + break; case GFL_NORMAL: case GFL_SAVED: level_table = &gs_file->levels; break; - case GFL_DEMO: - level_table = &gs_file->demos; - break; case GFL_CUTSCENE: level_table = &gs_file->cutscenes; break; - case GFL_TITLE: - level_table = NULL; + case GFL_DEMO: + level_table = &gs_file->demos; + break; +#if TR_VERSION == 1 + case GFL_GYM: + level_table = &gs_file->levels; break; +#endif default: ASSERT_FAIL(); } -#endif if (level_table != NULL) { ASSERT(level->num >= 0); diff --git a/src/libtrx/game/game_string_table/reader.c b/src/libtrx/game/game_string_table/reader.c index cadfb3c51..8a6b19e43 100644 --- a/src/libtrx/game/game_string_table/reader.c +++ b/src/libtrx/game/game_string_table/reader.c @@ -151,11 +151,8 @@ void GameStringTable_LoadFromFile(const char *const path) M_LoadTableFromJSON(root_obj, &gs_file->global); M_LoadLevelsFromJSON(root_obj, "levels", GFL_NORMAL, &gs_file->levels); M_LoadLevelsFromJSON(root_obj, "demos", GFL_DEMO, &gs_file->demos); -#if TR_VERSION == 2 - // TODO: TR1 still has everything in a single linear sequence M_LoadLevelsFromJSON( root_obj, "cutscenes", GFL_CUTSCENE, &gs_file->cutscenes); -#endif if (root != NULL) { JSON_ValueFree(root); diff --git a/src/libtrx/include/libtrx/game/game_flow/enum.h b/src/libtrx/include/libtrx/game/game_flow/enum.h index 24ba7c5a1..3aa001a40 100644 --- a/src/libtrx/include/libtrx/game/game_flow/enum.h +++ b/src/libtrx/include/libtrx/game/game_flow/enum.h @@ -1,26 +1,36 @@ #pragma once +// TODO: Split this enum, as it currently handles too many tasks. Apart from +// level type identification, it's also used for controlling game flow (loading +// a saved level, playing the story so far etc.). typedef enum { -#if TR_VERSION == 2 - GFL_NO_LEVEL = -1, + // Genuine level types + GFL_TITLE, + GFL_NORMAL, + GFL_CUTSCENE, + GFL_DEMO, +#if TR_VERSION == 1 + GFL_GYM, + GFL_BONUS, + GFL_TITLE_DEMO_PC, + GFL_LEVEL_DEMO_PC, #endif - GFL_TITLE = 0, - GFL_NORMAL = 1, - GFL_SAVED = 2, - GFL_DEMO = 3, - GFL_CUTSCENE = 4, + #if TR_VERSION == 1 - GFL_GYM = 5, - GFL_CURRENT = 6, - GFL_RESTART = 7, - GFL_SELECT = 8, - GFL_BONUS = 9, - GFL_TITLE_DEMO_PC = 10, - GFL_LEVEL_DEMO_PC = 11, + // Legacy level types to maintain savegame backwards compatibility. + // TODO: get rid of these. + GFL_DUMMY, + GFL_CURRENT, +#endif + + // Game flow execution context-related types. + GFL_SAVED, +#if TR_VERSION == 1 + GFL_RESTART, + GFL_SELECT, #elif TR_VERSION == 2 - GFL_STORY = 5, - GFL_QUIET = 6, - GFL_MID_STORY = 7, + GFL_STORY, + GFL_MID_STORY, #endif } GAME_FLOW_LEVEL_TYPE; @@ -47,6 +57,7 @@ typedef enum { typedef enum { GFS_DISPLAY_PICTURE, GFS_PLAY_LEVEL, + GFS_PLAY_CUTSCENE, GFS_PLAY_FMV, #if TR_VERSION == 1 GFS_LEVEL_STATS, @@ -55,9 +66,7 @@ typedef enum { GFS_LOAD_LEVEL, GFS_EXIT_TO_TITLE, GFS_EXIT_TO_LEVEL, - GFS_EXIT_TO_CINE, #elif TR_VERSION == 2 - GFS_PLAY_CUTSCENE, GFS_LEVEL_COMPLETE, GFS_GAME_COMPLETE, #endif diff --git a/src/libtrx/include/libtrx/game/game_flow/types.h b/src/libtrx/include/libtrx/game/game_flow/types.h index c740a5326..dfb72ec60 100644 --- a/src/libtrx/include/libtrx/game/game_flow/types.h +++ b/src/libtrx/include/libtrx/game/game_flow/types.h @@ -102,13 +102,13 @@ typedef struct { int32_t title_level_num; #elif TR_VERSION == 2 GAME_FLOW_LEVEL *title_level; +#endif // cutscenes struct { int32_t cutscene_count; GAME_FLOW_LEVEL *cutscenes; }; -#endif // demos struct { diff --git a/src/tr1/game/game_flow/common.c b/src/tr1/game/game_flow/common.c index 583462426..402c293c5 100644 --- a/src/tr1/game/game_flow/common.c +++ b/src/tr1/game/game_flow/common.c @@ -72,6 +72,7 @@ void GF_Shutdown(void) M_FreeInjections(&gf->injections); M_FreeLevels(&gf->levels, &gf->level_count); M_FreeLevels(&gf->demos, &gf->demo_count); + M_FreeLevels(&gf->cutscenes, &gf->cutscene_count); M_FreeFMVs(gf); } @@ -82,6 +83,8 @@ int32_t GF_GetLevelCount(const GAME_FLOW_LEVEL_TYPE level_type) return 1; case GFL_GYM: return 1; + case GFL_CUTSCENE: + return g_GameFlow.cutscene_count; case GFL_DEMO: return g_GameFlow.demo_count; default: @@ -117,8 +120,15 @@ GAME_FLOW_LEVEL *GF_GetLevel( case GFL_TITLE: return &g_GameFlow.levels[g_GameFlow.title_level_num]; + case GFL_CUTSCENE: + if (num < 0 || num >= GF_GetLevelCount(level_type)) { + LOG_ERROR("Invalid cutscene number: %d", num); + return NULL; + } + return &g_GameFlow.cutscenes[num]; + case GFL_DEMO: - if (num < 0 || num >= GF_GetDemoCount()) { + if (num < 0 || num >= GF_GetLevelCount(level_type)) { LOG_ERROR("Invalid demo number: %d", num); return NULL; } diff --git a/src/tr1/game/game_flow/reader.c b/src/tr1/game/game_flow/reader.c index 613bb60dc..2701e1d1a 100644 --- a/src/tr1/game/game_flow/reader.c +++ b/src/tr1/game/game_flow/reader.c @@ -65,6 +65,8 @@ static void M_LoadLevel( JSON_OBJECT *jlvl_obj, GAME_FLOW *gf, GAME_FLOW_LEVEL *level, size_t idx, void *user_arg); 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_LoadFMV( JSON_OBJECT *obj, const GAME_FLOW *gf, GAME_FLOW_FMV *level, size_t idx, @@ -92,10 +94,10 @@ static M_SEQUENCE_EVENT_HANDLER m_SequenceEventHandlers[] = { // 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_LEVEL_STATS, M_HandleIntEvent, "level_id" }, { GFS_EXIT_TO_LEVEL, M_HandleIntEvent, "level_id" }, - { GFS_EXIT_TO_CINE, M_HandleIntEvent, "level_id" }, { GFS_SET_CAMERA_ANGLE, M_HandleIntEvent, "value" }, { GFS_PLAY_SYNCED_AUDIO, M_HandleIntEvent, "audio_id" }, { GFS_SETUP_BACON_LARA, M_HandleIntEvent, "anchor_room" }, @@ -382,8 +384,8 @@ static void M_LoadLevelSequence( } for (int32_t i = 0; i < sequence->length; i++) { - if ((sequence->events[i].type == GFS_PLAY_LEVEL - || sequence->events[i].type == GFS_LOAD_LEVEL) + 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; } @@ -495,7 +497,9 @@ static void M_LoadLevel( { level->num = idx; - { + if ((int32_t)(intptr_t)user_arg != -1) { + level->type = (GAME_FLOW_LEVEL_TYPE)(intptr_t)user_arg; + } else { const char *const tmp = JSON_ObjectGetString(jlvl_obj, "type", JSON_INVALID_STRING); if (tmp == JSON_INVALID_STRING) { @@ -508,6 +512,10 @@ static void M_LoadLevel( } } + if (level->type == GFL_DUMMY) { + return; + } + { const char *const tmp = JSON_ObjectGetString(jlvl_obj, "path", JSON_INVALID_STRING); @@ -583,6 +591,7 @@ static void M_LoadLevel( break; case GFL_BONUS: + case GFL_DEMO: case GFL_CUTSCENE: case GFL_CURRENT: break; @@ -615,7 +624,7 @@ static void M_LoadLevels(JSON_OBJECT *const obj, GAME_FLOW *const gf) M_LoadArray( obj, "levels", sizeof(GAME_FLOW_LEVEL), (M_LOAD_ARRAY_FUNC)M_LoadLevel, - gf, &gf->level_count, (void **)&gf->levels, NULL); + gf, &gf->level_count, (void **)&gf->levels, (void *)(intptr_t)-1); if (gf->title_level_num == -1) { Shell_ExitSystem("at least one level must be of title type"); @@ -625,7 +634,15 @@ static void M_LoadLevels(JSON_OBJECT *const obj, GAME_FLOW *const gf) } } -static void M_LoadDemos(JSON_OBJECT *obj, GAME_FLOW *const gf) +static void M_LoadCutscenes(JSON_OBJECT *const obj, GAME_FLOW *const gf) +{ + M_LoadArray( + obj, "cutscenes", sizeof(GAME_FLOW_LEVEL), + (M_LOAD_ARRAY_FUNC)M_LoadLevel, gf, &gf->cutscene_count, + (void **)&gf->cutscenes, (void *)(intptr_t)GFL_CUTSCENE); +} + +static void M_LoadDemos(JSON_OBJECT *const obj, GAME_FLOW *const gf) { M_LoadArray( obj, "demos", sizeof(GAME_FLOW_LEVEL), (M_LOAD_ARRAY_FUNC)M_LoadLevel, @@ -729,6 +746,7 @@ void GF_Load(const char *const path) 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); diff --git a/src/tr1/game/game_flow/sequencer.c b/src/tr1/game/game_flow/sequencer.c index a3db51c71..529926bdb 100644 --- a/src/tr1/game/game_flow/sequencer.c +++ b/src/tr1/game/game_flow/sequencer.c @@ -47,7 +47,6 @@ GF_InterpretSequence(const int32_t level_num, const GAME_FLOW_LEVEL_TYPE type) switch (event->type) { case GFS_EXIT_TO_TITLE: case GFS_EXIT_TO_LEVEL: - case GFS_EXIT_TO_CINE: case GFS_PLAY_FMV: case GFS_LEVEL_STATS: case GFS_TOTAL_STATS: @@ -199,11 +198,17 @@ GF_InterpretSequence(const int32_t level_num, const GAME_FLOW_LEVEL_TYPE type) }; } - case GFS_EXIT_TO_CINE: - return (GAME_FLOW_COMMAND) { - .action = GF_START_CINE, - .param = (int32_t)(intptr_t)event->data & ((1 << 6) - 1), - }; + case GFS_PLAY_CUTSCENE: { + const int16_t cutscene_num = (int16_t)(intptr_t)event->data; + if (type != GFL_SAVED) { + gf_cmd = GF_DoCutsceneSequence(cutscene_num); + if (gf_cmd.action != GF_NOOP + && gf_cmd.action != GF_LEVEL_COMPLETE) { + return gf_cmd; + } + } + break; + } case GFS_SET_CAMERA_ANGLE: g_CinePosition.rot = (int32_t)(intptr_t)event->data; @@ -341,7 +346,7 @@ static GAME_FLOW_COMMAND M_StorySoFar( .param = (int32_t)(intptr_t)event->data & ((1 << 6) - 1), }; - case GFS_EXIT_TO_CINE: + case GFS_PLAY_CUTSCENE: Music_Stop(); return (GAME_FLOW_COMMAND) { .action = GF_START_CINE, @@ -402,11 +407,22 @@ GAME_FLOW_COMMAND GF_LoadLevel( return (GAME_FLOW_COMMAND) { .action = GF_NOOP }; } +GAME_FLOW_COMMAND GF_DoCutsceneSequence(const int32_t cutscene_num) +{ + const GAME_FLOW_LEVEL *const level = + GF_GetLevel(cutscene_num, GFL_CUTSCENE); + if (level == NULL) { + LOG_ERROR("Missing cutscene: %d", cutscene_num); + return (GAME_FLOW_COMMAND) { .action = GF_NOOP }; + } + return GF_InterpretSequence(cutscene_num, GFL_CUTSCENE); +} + GAME_FLOW_COMMAND GF_DoDemoSequence(int32_t demo_num) { demo_num = Demo_ChooseLevel(demo_num); if (demo_num < 0) { - return (GAME_FLOW_COMMAND) { .action = GF_EXIT_TO_TITLE }; + return (GAME_FLOW_COMMAND) { .action = GF_NOOP }; } return GF_InterpretSequence(demo_num, GFL_DEMO); } diff --git a/src/tr1/game/game_flow/sequencer.h b/src/tr1/game/game_flow/sequencer.h index 1919eb02c..1a6f7a385 100644 --- a/src/tr1/game/game_flow/sequencer.h +++ b/src/tr1/game/game_flow/sequencer.h @@ -7,7 +7,7 @@ GAME_FLOW_COMMAND GF_InterpretSequence(int32_t level_num, GAME_FLOW_LEVEL_TYPE level_type); GAME_FLOW_COMMAND GF_DoDemoSequence(int32_t demo_num); -GAME_FLOW_COMMAND GF_DoCutsceneSequence(int32_t demo_num); +GAME_FLOW_COMMAND GF_DoCutsceneSequence(int32_t cutscene_num); GAME_FLOW_COMMAND GF_PlayAvailableStory(int32_t slot_num); GAME_FLOW_COMMAND GF_LoadLevel( diff --git a/src/tr1/game/shell.c b/src/tr1/game/shell.c index 14c804fff..8f2f04fb4 100644 --- a/src/tr1/game/shell.c +++ b/src/tr1/game/shell.c @@ -236,7 +236,7 @@ void Shell_Main(void) } case GF_START_CINE: - command = GF_InterpretSequence(command.param, GFL_CUTSCENE); + command = GF_DoCutsceneSequence(command.param); break; case GF_START_DEMO: { diff --git a/src/tr1/global/enum_map.def b/src/tr1/global/enum_map.def index 360177979..dfb99eacb 100644 --- a/src/tr1/global/enum_map.def +++ b/src/tr1/global/enum_map.def @@ -37,13 +37,13 @@ ENUM_MAP_DEFINE(MUSIC_LOAD_CONDITION, MUSIC_LOAD_ALWAYS, "always") ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_LOAD_LEVEL, "load_level") ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_PLAY_FMV, "play_fmv") ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_PLAY_LEVEL, "play_level") +ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_PLAY_CUTSCENE, "play_cutscene") ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_LOADING_SCREEN, "loading_screen") ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_DISPLAY_PICTURE, "display_picture") ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_LEVEL_STATS, "level_stats") ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_TOTAL_STATS, "total_stats") ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_EXIT_TO_TITLE, "exit_to_title") ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_EXIT_TO_LEVEL, "exit_to_level") -ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_EXIT_TO_CINE, "exit_to_cine") ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_SET_CAMERA_ANGLE, "set_cutscene_angle") ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_FLIP_MAP, "flip_map") ENUM_MAP_DEFINE(GAME_FLOW_SEQUENCE_EVENT_TYPE, GFS_REMOVE_WEAPONS, "remove_weapons") @@ -59,6 +59,7 @@ ENUM_MAP_DEFINE(GAME_FLOW_LEVEL_TYPE, GFL_TITLE, "title") ENUM_MAP_DEFINE(GAME_FLOW_LEVEL_TYPE, GFL_NORMAL, "normal") ENUM_MAP_DEFINE(GAME_FLOW_LEVEL_TYPE, GFL_CUTSCENE, "cutscene") ENUM_MAP_DEFINE(GAME_FLOW_LEVEL_TYPE, GFL_GYM, "gym") +ENUM_MAP_DEFINE(GAME_FLOW_LEVEL_TYPE, GFL_DUMMY, "dummy") ENUM_MAP_DEFINE(GAME_FLOW_LEVEL_TYPE, GFL_CURRENT, "current") ENUM_MAP_DEFINE(GAME_FLOW_LEVEL_TYPE, GFL_BONUS, "bonus") ENUM_MAP_DEFINE(GAME_FLOW_LEVEL_TYPE, GFL_TITLE_DEMO_PC, "title_demo_pc") diff --git a/src/tr2/game/game_flow/common.c b/src/tr2/game/game_flow/common.c index 3a65c71b5..d8504b0cd 100644 --- a/src/tr2/game/game_flow/common.c +++ b/src/tr2/game/game_flow/common.c @@ -62,10 +62,10 @@ int32_t GF_GetLevelCount(const GAME_FLOW_LEVEL_TYPE level_type) case GFL_NORMAL: case GFL_SAVED: return g_GameFlow.level_count; - case GFL_DEMO: - return g_GameFlow.demo_count; case GFL_CUTSCENE: return g_GameFlow.cutscene_count; + case GFL_DEMO: + return g_GameFlow.demo_count; default: ASSERT_FAIL(); } diff --git a/src/tr2/game/game_flow/sequencer.h b/src/tr2/game/game_flow/sequencer.h index 19131a9f4..fe7a17caf 100644 --- a/src/tr2/game/game_flow/sequencer.h +++ b/src/tr2/game/game_flow/sequencer.h @@ -12,5 +12,6 @@ GAME_FLOW_COMMAND GF_InterpretSequence( bool GF_DoFrontendSequence(void); GAME_FLOW_COMMAND GF_DoDemoSequence(int32_t demo_num); +GAME_FLOW_COMMAND GF_DoCutsceneSequence(int32_t cutscene_num); GAME_FLOW_COMMAND GF_DoLevelSequence( int32_t start_level, GAME_FLOW_LEVEL_TYPE type); diff --git a/src/tr2/game/shell/common.c b/src/tr2/game/shell/common.c index 50885667e..01f44c111 100644 --- a/src/tr2/game/shell/common.c +++ b/src/tr2/game/shell/common.c @@ -390,17 +390,13 @@ void Shell_Main(void) gf_cmd = GF_DoLevelSequence(g_SaveGame.current_level, GFL_SAVED); break; - case GF_START_CINE: { - PHASE *const cutscene_phase = Phase_Cutscene_Create(gf_cmd.param); - gf_cmd = PhaseExecutor_Run(cutscene_phase); - Phase_Cutscene_Destroy(cutscene_phase); + case GF_START_CINE: + gf_cmd = GF_DoCutsceneSequence(gf_cmd.param); break; - } - case GF_START_DEMO: { + case GF_START_DEMO: gf_cmd = GF_DoDemoSequence(gf_cmd.param); break; - } case GF_LEVEL_COMPLETE: gf_cmd = LevelCompleteSequence();