diff --git a/data/tr1/ship/cfg/TR1X_gameflow.json5 b/data/tr1/ship/cfg/TR1X_gameflow.json5
index 3f87e83a4..339dba55d 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,7 +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",
+ "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},
+ ],
+ },
+
+ ],
+
+ "cutscenes": [
+ // Cut Scene 1
{
"path": "data/cut1.phd",
"type": "cutscene",
@@ -389,12 +480,10 @@
{"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",
@@ -414,12 +503,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": 9},
- {"type": "exit_to_level", "level_id": 10},
],
},
- // Level 18: Cut Scene 3
+ // Cut Scene 3
{
"path": "data/cut3.phd",
"type": "cutscene",
@@ -436,11 +523,10 @@
{"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",
@@ -463,86 +549,8 @@
{"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
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..2109e0955 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",
@@ -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,98 @@ 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",
+ "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},
+ ],
+ },
],
-},
+}
```
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..0ce92db16 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;
}
@@ -508,6 +510,10 @@ static void M_LoadLevel(
}
}
+ if (level->type == GFL_DUMMY) {
+ return;
+ }
+
{
const char *const tmp =
JSON_ObjectGetString(jlvl_obj, "path", JSON_INVALID_STRING);
@@ -625,7 +631,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 +743,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();