diff --git a/data/tr1/ship/cfg/TR1X_gameflow.json5 b/data/tr1/ship/cfg/TR1X_gameflow.json5 index 19dd9fcbf..e83ba7e01 100644 --- a/data/tr1/ship/cfg/TR1X_gameflow.json5 +++ b/data/tr1/ship/cfg/TR1X_gameflow.json5 @@ -30,7 +30,7 @@ "music_track": 2, "inherit_injections": false, "sequence": [ - {"type": "display_picture", "path": "data/images/eidos.webp", "display_time": 1}, + {"type": "display_picture", "path": "data/images/eidos.webp", "display_time": 1, "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "play_fmv", "fmv_id": 0}, {"type": "play_fmv", "fmv_id": 1}, {"type": "play_fmv", "fmv_id": 2}, @@ -51,7 +51,7 @@ "inherit_injections": false, "sequence": [ {"type": "play_fmv", "fmv_id": 3}, - {"type": "loading_screen", "path": "data/images/gym.webp"}, + {"type": "loading_screen", "path": "data/images/gym.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, @@ -74,7 +74,7 @@ "music_track": 57, "sequence": [ {"type": "play_fmv", "fmv_id": 4}, - {"type": "loading_screen", "path": "data/images/peru.webp"}, + {"type": "loading_screen", "path": "data/images/peru.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, @@ -92,7 +92,7 @@ "path": "data/level2.phd", "music_track": 57, "sequence": [ - {"type": "loading_screen", "path": "data/images/peru.webp"}, + {"type": "loading_screen", "path": "data/images/peru.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, @@ -109,7 +109,7 @@ "path": "data/level3a.phd", "music_track": 57, "sequence": [ - {"type": "loading_screen", "path": "data/images/peru.webp"}, + {"type": "loading_screen", "path": "data/images/peru.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, @@ -128,7 +128,7 @@ "path": "data/level3b.phd", "music_track": 57, "sequence": [ - {"type": "loading_screen", "path": "data/images/peru.webp"}, + {"type": "loading_screen", "path": "data/images/peru.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "play_cutscene", "cutscene_id": 0}, @@ -149,7 +149,7 @@ "music_track": 59, "sequence": [ {"type": "play_fmv", "fmv_id": 5}, - {"type": "loading_screen", "path": "data/images/greece.webp"}, + {"type": "loading_screen", "path": "data/images/greece.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, @@ -167,7 +167,7 @@ "path": "data/level5.phd", "music_track": 59, "sequence": [ - {"type": "loading_screen", "path": "data/images/greece.webp"}, + {"type": "loading_screen", "path": "data/images/greece.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, @@ -187,7 +187,7 @@ "path": "data/level6.phd", "music_track": 59, "sequence": [ - {"type": "loading_screen", "path": "data/images/greece.webp"}, + {"type": "loading_screen", "path": "data/images/greece.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, @@ -205,7 +205,7 @@ "path": "data/level7a.phd", "music_track": 58, "sequence": [ - {"type": "loading_screen", "path": "data/images/greece.webp"}, + {"type": "loading_screen", "path": "data/images/greece.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, @@ -224,7 +224,7 @@ "path": "data/level7b.phd", "music_track": 58, "sequence": [ - {"type": "loading_screen", "path": "data/images/greece.webp"}, + {"type": "loading_screen", "path": "data/images/greece.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "play_cutscene", "cutscene_id": 1}, @@ -248,7 +248,7 @@ "music_track": 59, "sequence": [ {"type": "play_fmv", "fmv_id": 6}, - {"type": "loading_screen", "path": "data/images/egypt.webp"}, + {"type": "loading_screen", "path": "data/images/egypt.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, @@ -266,7 +266,7 @@ "path": "data/level8b.phd", "music_track": 59, "sequence": [ - {"type": "loading_screen", "path": "data/images/egypt.webp"}, + {"type": "loading_screen", "path": "data/images/egypt.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, @@ -286,7 +286,7 @@ "path": "data/level8c.phd", "music_track": 59, "sequence": [ - {"type": "loading_screen", "path": "data/images/egypt.webp"}, + {"type": "loading_screen", "path": "data/images/egypt.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, @@ -305,7 +305,7 @@ "music_track": 58, "sequence": [ {"type": "play_fmv", "fmv_id": 7}, - {"type": "loading_screen", "path": "data/images/atlantis.webp"}, + {"type": "loading_screen", "path": "data/images/atlantis.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "remove_weapons"}, {"type": "remove_scions"}, {"type": "load_level"}, @@ -338,7 +338,7 @@ "music_track": 60, "sequence": [ {"type": "play_fmv", "fmv_id": 8}, - {"type": "loading_screen", "path": "data/images/atlantis.webp"}, + {"type": "loading_screen", "path": "data/images/atlantis.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "give_item", "object_id": "pistols", "quantity": 1}, {"type": "setup_bacon_lara", "anchor_room": 10}, @@ -360,16 +360,16 @@ "path": "data/level10c.phd", "music_track": 60, "sequence": [ - {"type": "loading_screen", "path": "data/images/atlantis.webp"}, + {"type": "loading_screen", "path": "data/images/atlantis.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, {"type": "play_fmv", "fmv_id": 10}, {"type": "play_music", "music_track": 19}, - {"type": "display_picture", "path": "data/images/end.webp", "display_time": 7.5}, - {"type": "display_picture", "path": "data/images/credits_1.webp", "display_time": 7.5}, - {"type": "display_picture", "path": "data/images/credits_2.webp", "display_time": 7.5}, - {"type": "display_picture", "path": "data/images/credits_3.webp", "display_time": 7.5}, + {"type": "display_picture", "path": "data/images/end.webp", "display_time": 7.5, "fade_in_time": 1.0, "fade_out_time": 1.0}, + {"type": "display_picture", "path": "data/images/credits_1.webp", "display_time": 7.5, "fade_in_time": 1.0, "fade_out_time": 1.0}, + {"type": "display_picture", "path": "data/images/credits_2.webp", "display_time": 7.5, "fade_in_time": 1.0, "fade_out_time": 1.0}, + {"type": "display_picture", "path": "data/images/credits_3.webp", "display_time": 7.5, "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "total_stats", "background_path": "data/images/install.webp"}, {"type": "exit_to_title"}, ], @@ -414,7 +414,7 @@ "data/injections/vilcabamba_textures.bin", ], "sequence": [ - {"type": "loading_screen", "path": "data/images/peru.webp"}, + {"type": "loading_screen", "path": "data/images/peru.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, @@ -433,7 +433,7 @@ "data/injections/valley_textures.bin", ], "sequence": [ - {"type": "loading_screen", "path": "data/images/peru.webp"}, + {"type": "loading_screen", "path": "data/images/peru.webp", "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "load_level"}, {"type": "play_level"}, {"type": "level_stats"}, diff --git a/data/tr1/ship/cfg/TR1X_gameflow_ub.json5 b/data/tr1/ship/cfg/TR1X_gameflow_ub.json5 index 9833de778..39772f2f5 100644 --- a/data/tr1/ship/cfg/TR1X_gameflow_ub.json5 +++ b/data/tr1/ship/cfg/TR1X_gameflow_ub.json5 @@ -36,7 +36,7 @@ "data/injections/pda_model.bin", ], "sequence": [ - {"type": "display_picture", "path": "data/images/eidos.webp", "display_time": 1}, + {"type": "display_picture", "path": "data/images/eidos.webp", "display_time": 1, "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "play_fmv", "fmv_id": 0}, {"type": "play_fmv", "fmv_id": 1}, {"type": "exit_to_title"}, @@ -110,11 +110,11 @@ {"type": "play_level"}, {"type": "level_stats"}, {"type": "play_music", "music_track": 19}, - {"type": "display_picture", "path": "data/images/end.webp", "display_time": 7.5}, - {"type": "display_picture", "path": "data/images/credits_ub.webp", "display_time": 7.5}, - {"type": "display_picture", "path": "data/images/credits_1.webp", "display_time": 7.5}, - {"type": "display_picture", "path": "data/images/credits_2.webp", "display_time": 7.5}, - {"type": "display_picture", "path": "data/images/credits_3.webp", "display_time": 7.5}, + {"type": "display_picture", "path": "data/images/end.webp", "display_time": 7.5, "fade_in_time": 1.0, "fade_out_time": 1.0}, + {"type": "display_picture", "path": "data/images/credits_ub.webp", "display_time": 7.5, "fade_in_time": 1.0, "fade_out_time": 1.0}, + {"type": "display_picture", "path": "data/images/credits_1.webp", "display_time": 7.5, "fade_in_time": 1.0, "fade_out_time": 1.0}, + {"type": "display_picture", "path": "data/images/credits_2.webp", "display_time": 7.5, "fade_in_time": 1.0, "fade_out_time": 1.0}, + {"type": "display_picture", "path": "data/images/credits_3.webp", "display_time": 7.5, "fade_in_time": 1.0, "fade_out_time": 1.0}, {"type": "total_stats", "background_path": "data/images/install.webp"}, {"type": "exit_to_title"}, ], diff --git a/docs/tr1/GAMEFLOW.md b/docs/tr1/GAMEFLOW.md index b88633733..a376f7e96 100644 --- a/docs/tr1/GAMEFLOW.md +++ b/docs/tr1/GAMEFLOW.md @@ -530,15 +530,15 @@ default gameflow for examples.
display_picture
path
display_time
fade_in_time
+ fade_out_time
+ loading_screen
path
display_picture
, except these pictures can be
+ enabled/disabled by the user with the loading screen option in the config
+ tool.
fade_in_time
+ fade_out_time
+ play_cutscene
diff --git a/src/libtrx/game/game_flow/sequencer.c b/src/libtrx/game/game_flow/sequencer.c
index d6948a7b7..66862edef 100644
--- a/src/libtrx/game/game_flow/sequencer.c
+++ b/src/libtrx/game/game_flow/sequencer.c
@@ -1,65 +1,56 @@
#include "game/game_flow/sequencer.h"
-#include "config.h"
-#include "game/inventory_ring/control.h"
-#include "game/objects/vars.h"
-#include "game/phase.h"
+#include "debug.h"
+#include "enum_map.h"
+#include "game/game_flow/sequencer_priv.h"
-GF_COMMAND GF_EnterPhotoMode(void)
+GF_COMMAND GF_InterpretSequence(
+ const GF_LEVEL *const level, GF_SEQUENCE_CONTEXT seq_ctx,
+ void *const seq_ctx_arg)
{
- PHASE *const subphase = Phase_PhotoMode_Create();
- const GF_COMMAND gf_cmd = PhaseExecutor_Run(subphase);
- Phase_PhotoMode_Destroy(subphase);
- return gf_cmd;
-}
-
-GF_COMMAND GF_PauseGame(void)
-{
- PHASE *const subphase = Phase_Pause_Create();
- const GF_COMMAND gf_cmd = PhaseExecutor_Run(subphase);
- Phase_Pause_Destroy(subphase);
- return gf_cmd;
-}
-
-GF_COMMAND GF_ShowInventory(const INVENTORY_MODE mode)
-{
- PHASE *const phase = Phase_Inventory_Create(mode);
- const GF_COMMAND gf_cmd = PhaseExecutor_Run(phase);
- Phase_Inventory_Destroy(phase);
- return gf_cmd;
-}
-
-GF_COMMAND GF_ShowInventoryKeys(const GAME_OBJECT_ID receptacle_type_id)
-{
- if (g_Config.gameplay.enable_auto_item_selection) {
- const GAME_OBJECT_ID object_id = Object_GetCognateInverse(
- receptacle_type_id, g_KeyItemToReceptacleMap);
- InvRing_SetRequestedObjectID(object_id);
+ ASSERT(level != nullptr);
+ LOG_DEBUG(
+ "running sequence for level=%d type=%d seq_ctx=%d", level->num,
+ level->type, seq_ctx);
+
+ GF_PreSequenceHook();
+
+ GF_COMMAND gf_cmd = { .action = GF_EXIT_TO_TITLE };
+
+ const GF_SEQUENCE *const sequence = &level->sequence;
+ for (int32_t i = 0; i < sequence->length; i++) {
+ const GF_SEQUENCE_EVENT *const event = &sequence->events[i];
+ LOG_DEBUG(
+ "event type=%s(%d) data=0x%x",
+ ENUM_MAP_TO_STRING(GF_SEQUENCE_EVENT_TYPE, event->type),
+ event->type, event->data);
+
+ if (GF_ShouldSkipSequenceEvent(level, event)) {
+ continue;
+ }
+
+ // Handle the event
+ if (event->type < GFS_NUMBER_OF
+ && GF_GetSequenceEventHandler(event->type) != nullptr) {
+ gf_cmd = GF_GetSequenceEventHandler(event->type)(
+ level, event, seq_ctx, seq_ctx_arg);
+ LOG_DEBUG(
+ "event type=%s(%d) data=0x%x finished, result: action=%s, "
+ "param=%d",
+ ENUM_MAP_TO_STRING(GF_SEQUENCE_EVENT_TYPE, event->type),
+ event->type, event->data,
+ ENUM_MAP_TO_STRING(GF_ACTION, gf_cmd.action), gf_cmd.param);
+ if (gf_cmd.action != GF_NOOP) {
+ return gf_cmd;
+ }
+ }
+
+ // Update sequence context if necessary
+ seq_ctx = GF_SwitchSequenceContext(event, seq_ctx);
}
- return GF_ShowInventory(INV_KEYS_MODE);
-}
-
-GF_COMMAND GF_RunDemo(const int32_t demo_num)
-{
- PHASE *const demo_phase = Phase_Demo_Create(demo_num);
- const GF_COMMAND gf_cmd = PhaseExecutor_Run(demo_phase);
- Phase_Demo_Destroy(demo_phase);
- return gf_cmd;
-}
-GF_COMMAND GF_RunCutscene(const int32_t cutscene_num)
-{
- PHASE *const cutscene_phase = Phase_Cutscene_Create(cutscene_num);
- const GF_COMMAND gf_cmd = PhaseExecutor_Run(cutscene_phase);
- Phase_Cutscene_Destroy(cutscene_phase);
- return gf_cmd;
-}
-
-GF_COMMAND GF_RunGame(
- const GF_LEVEL *const level, const GF_SEQUENCE_CONTEXT seq_ctx)
-{
- PHASE *const phase = Phase_Game_Create(level, seq_ctx);
- const GF_COMMAND gf_cmd = PhaseExecutor_Run(phase);
- Phase_Game_Destroy(phase);
+ LOG_DEBUG(
+ "sequence finished: action=%s param=%d",
+ ENUM_MAP_TO_STRING(GF_ACTION, gf_cmd.action), gf_cmd.param);
return gf_cmd;
}
diff --git a/src/libtrx/game/game_flow/sequencer_misc.c b/src/libtrx/game/game_flow/sequencer_misc.c
new file mode 100644
index 000000000..612703b43
--- /dev/null
+++ b/src/libtrx/game/game_flow/sequencer_misc.c
@@ -0,0 +1,100 @@
+#include "config.h"
+#include "game/demo.h"
+#include "game/game_flow/common.h"
+#include "game/game_flow/sequencer.h"
+#include "game/game_flow/vars.h"
+#include "game/inventory_ring/control.h"
+#include "game/objects/vars.h"
+#include "game/phase.h"
+#include "log.h"
+
+GF_COMMAND GF_EnterPhotoMode(void)
+{
+ PHASE *const subphase = Phase_PhotoMode_Create();
+ const GF_COMMAND gf_cmd = PhaseExecutor_Run(subphase);
+ Phase_PhotoMode_Destroy(subphase);
+ return gf_cmd;
+}
+
+GF_COMMAND GF_PauseGame(void)
+{
+ PHASE *const subphase = Phase_Pause_Create();
+ const GF_COMMAND gf_cmd = PhaseExecutor_Run(subphase);
+ Phase_Pause_Destroy(subphase);
+ return gf_cmd;
+}
+
+GF_COMMAND GF_ShowInventory(const INVENTORY_MODE mode)
+{
+ PHASE *const phase = Phase_Inventory_Create(mode);
+ const GF_COMMAND gf_cmd = PhaseExecutor_Run(phase);
+ Phase_Inventory_Destroy(phase);
+ return gf_cmd;
+}
+
+GF_COMMAND GF_ShowInventoryKeys(const GAME_OBJECT_ID receptacle_type_id)
+{
+ if (g_Config.gameplay.enable_auto_item_selection) {
+ const GAME_OBJECT_ID object_id = Object_GetCognateInverse(
+ receptacle_type_id, g_KeyItemToReceptacleMap);
+ InvRing_SetRequestedObjectID(object_id);
+ }
+ return GF_ShowInventory(INV_KEYS_MODE);
+}
+
+GF_COMMAND GF_RunDemo(const int32_t demo_num)
+{
+ PHASE *const demo_phase = Phase_Demo_Create(demo_num);
+ const GF_COMMAND gf_cmd = PhaseExecutor_Run(demo_phase);
+ Phase_Demo_Destroy(demo_phase);
+ return gf_cmd;
+}
+
+GF_COMMAND GF_RunCutscene(const int32_t cutscene_num)
+{
+ PHASE *const cutscene_phase = Phase_Cutscene_Create(cutscene_num);
+ const GF_COMMAND gf_cmd = PhaseExecutor_Run(cutscene_phase);
+ Phase_Cutscene_Destroy(cutscene_phase);
+ return gf_cmd;
+}
+
+GF_COMMAND GF_RunGame(
+ const GF_LEVEL *const level, const GF_SEQUENCE_CONTEXT seq_ctx)
+{
+ PHASE *const phase = Phase_Game_Create(level, seq_ctx);
+ const GF_COMMAND gf_cmd = PhaseExecutor_Run(phase);
+ Phase_Game_Destroy(phase);
+ return gf_cmd;
+}
+
+GF_COMMAND GF_DoFrontendSequence(void)
+{
+ if (g_GameFlow.title_level == nullptr) {
+ return (GF_COMMAND) { .action = GF_NOOP };
+ }
+ return GF_InterpretSequence(g_GameFlow.title_level, GFSC_NORMAL, nullptr);
+}
+
+GF_COMMAND GF_DoDemoSequence(int32_t demo_num)
+{
+ demo_num = Demo_ChooseLevel(demo_num);
+ if (demo_num < 0) {
+ return (GF_COMMAND) { .action = GF_NOOP };
+ }
+ const GF_LEVEL *const level = GF_GetLevel(GFLT_DEMOS, demo_num);
+ if (level == nullptr) {
+ LOG_ERROR("Missing demo: %d", demo_num);
+ return (GF_COMMAND) { .action = GF_NOOP };
+ }
+ return GF_InterpretSequence(level, GFSC_NORMAL, nullptr);
+}
+
+GF_COMMAND GF_DoCutsceneSequence(const int32_t cutscene_num)
+{
+ const GF_LEVEL *const level = GF_GetLevel(GFLT_CUTSCENES, cutscene_num);
+ if (level == nullptr) {
+ LOG_ERROR("Missing cutscene: %d", cutscene_num);
+ return (GF_COMMAND) { .action = GF_NOOP };
+ }
+ return GF_InterpretSequence(level, GFSC_NORMAL, nullptr);
+}
diff --git a/src/libtrx/game/game_flow/sequencer_priv.h b/src/libtrx/game/game_flow/sequencer_priv.h
new file mode 100644
index 000000000..e123163fe
--- /dev/null
+++ b/src/libtrx/game/game_flow/sequencer_priv.h
@@ -0,0 +1,10 @@
+#pragma once
+
+extern void GF_PreSequenceHook(void);
+extern GF_SEQUENCE_CONTEXT GF_SwitchSequenceContext(
+ const GF_SEQUENCE_EVENT *event, GF_SEQUENCE_CONTEXT seq_ctx);
+extern bool GF_ShouldSkipSequenceEvent(
+ const GF_LEVEL *level, const GF_SEQUENCE_EVENT *event);
+extern GF_COMMAND (
+ *GF_GetSequenceEventHandler(GF_SEQUENCE_EVENT_TYPE event_type))(
+ const GF_LEVEL *, const GF_SEQUENCE_EVENT *, GF_SEQUENCE_CONTEXT, void *);
diff --git a/src/libtrx/include/libtrx/game/enum_map.def b/src/libtrx/include/libtrx/game/enum_map.def
index d985fbc15..c1a699ad1 100644
--- a/src/libtrx/include/libtrx/game/enum_map.def
+++ b/src/libtrx/include/libtrx/game/enum_map.def
@@ -56,3 +56,63 @@ ENUM_MAP_DEFINE(UNDERWATER_MUSIC_MODE, UMM_QUIET, "quiet")
ENUM_MAP_DEFINE(UNDERWATER_MUSIC_MODE, UMM_FULL_NO_AMBIENT, "full_no_ambient")
ENUM_MAP_DEFINE(UNDERWATER_MUSIC_MODE, UMM_QUIET_NO_AMBIENT, "quiet_no_ambient")
ENUM_MAP_DEFINE(UNDERWATER_MUSIC_MODE, UMM_NONE, "none")
+
+#if TR_VERSION == 1
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_LOAD_LEVEL, "load_level")
+#endif
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_PLAY_LEVEL, "play_level")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_PLAY_FMV, "play_fmv")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_PLAY_CUTSCENE, "play_cutscene")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_PLAY_MUSIC, "play_music")
+#if TR_VERSION == 1
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_LOADING_SCREEN, "loading_screen")
+#endif
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_DISPLAY_PICTURE, "display_picture")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_LEVEL_STATS, "level_stats")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_TOTAL_STATS, "total_stats")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_EXIT_TO_TITLE, "exit_to_title")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_LEVEL_COMPLETE, "level_complete")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_SET_CAMERA_ANGLE, "set_cutscene_angle")
+#if TR_VERSION == 1
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_FLIP_MAP, "flip_map")
+#endif
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_ADD_ITEM, "give_item")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_REMOVE_WEAPONS, "remove_weapons")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_REMOVE_AMMO, "remove_ammo")
+#if TR_VERSION == 1
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_REMOVE_SCIONS, "remove_scions")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_REMOVE_MEDIPACKS, "remove_medipacks")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_MESH_SWAP, "mesh_swap")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_SETUP_BACON_LARA, "setup_bacon_lara")
+#elif TR_VERSION == 2
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_ENABLE_SUNSET, "enable_sunset")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_SET_NUM_SECRETS, "set_secret_count")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_SET_START_ANIM, "set_lara_start_anim")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_DISABLE_FLOOR, "disable_floor")
+ENUM_MAP_DEFINE(GF_SEQUENCE_EVENT_TYPE, GFS_ADD_SECRET_REWARD, "add_secret_reward")
+#endif
+
+ENUM_MAP_DEFINE(GF_LEVEL_TYPE, GFL_TITLE, "title")
+ENUM_MAP_DEFINE(GF_LEVEL_TYPE, GFL_NORMAL, "normal")
+ENUM_MAP_DEFINE(GF_LEVEL_TYPE, GFL_CUTSCENE, "cutscene")
+ENUM_MAP_DEFINE(GF_LEVEL_TYPE, GFL_GYM, "gym")
+#if TR_VERSION == 1
+ENUM_MAP_DEFINE(GF_LEVEL_TYPE, GFL_BONUS, "bonus")
+ENUM_MAP_DEFINE(GF_LEVEL_TYPE, GFL_DUMMY, "dummy")
+ENUM_MAP_DEFINE(GF_LEVEL_TYPE, GFL_CURRENT, "current")
+#endif
+
+ENUM_MAP_DEFINE(GF_ACTION, GF_NOOP, "noop")
+ENUM_MAP_DEFINE(GF_ACTION, GF_START_GAME, "play_level")
+ENUM_MAP_DEFINE(GF_ACTION, GF_START_SAVED_GAME, "load_saved_game")
+ENUM_MAP_DEFINE(GF_ACTION, GF_START_CINE, "play_cutscene")
+ENUM_MAP_DEFINE(GF_ACTION, GF_START_DEMO, "play_demo")
+ENUM_MAP_DEFINE(GF_ACTION, GF_START_FMV, "play_fmv")
+ENUM_MAP_DEFINE(GF_ACTION, GF_EXIT_TO_TITLE, "exit_to_title")
+ENUM_MAP_DEFINE(GF_ACTION, GF_LEVEL_COMPLETE, "level_complete")
+ENUM_MAP_DEFINE(GF_ACTION, GF_EXIT_GAME, "exit_game")
+ENUM_MAP_DEFINE(GF_ACTION, GF_SELECT_GAME, "select_level")
+#if TR_VERSION == 1
+ENUM_MAP_DEFINE(GF_ACTION, GF_RESTART_GAME, "restart_level")
+ENUM_MAP_DEFINE(GF_ACTION, GF_STORY_SO_FAR, "story_so_far")
+#endif
diff --git a/src/libtrx/include/libtrx/game/game_flow/sequencer.h b/src/libtrx/include/libtrx/game/game_flow/sequencer.h
index 57c4bca9e..96dc7753f 100644
--- a/src/libtrx/include/libtrx/game/game_flow/sequencer.h
+++ b/src/libtrx/include/libtrx/game/game_flow/sequencer.h
@@ -10,3 +10,11 @@ GF_COMMAND GF_ShowInventoryKeys(GAME_OBJECT_ID receptacle_type_id);
GF_COMMAND GF_RunDemo(int32_t demo_num);
GF_COMMAND GF_RunCutscene(int32_t cutscene_num);
GF_COMMAND GF_RunGame(const GF_LEVEL *level, GF_SEQUENCE_CONTEXT seq_ctx);
+
+GF_COMMAND GF_DoFrontendSequence(void);
+GF_COMMAND GF_TitleSequence(void);
+GF_COMMAND GF_DoDemoSequence(int32_t demo_num);
+GF_COMMAND GF_DoCutsceneSequence(int32_t cutscene_num);
+
+extern GF_COMMAND GF_InterpretSequence(
+ const GF_LEVEL *level, GF_SEQUENCE_CONTEXT seq_ctx, void *seq_ctx_arg);
diff --git a/src/libtrx/meson.build b/src/libtrx/meson.build
index df45ea2d7..afe6c4c67 100644
--- a/src/libtrx/meson.build
+++ b/src/libtrx/meson.build
@@ -113,6 +113,7 @@ sources = [
'game/game_flow/common.c',
'game/game_flow/reader.c',
'game/game_flow/sequencer.c',
+ 'game/game_flow/sequencer_misc.c',
'game/game_flow/vars.c',
'game/game_string.c',
'game/game_string_table/common.c',
diff --git a/src/tr1/game/game_flow/sequencer.c b/src/tr1/game/game_flow/sequencer.c
index df3a2051a..483f8871e 100644
--- a/src/tr1/game/game_flow/sequencer.c
+++ b/src/tr1/game/game_flow/sequencer.c
@@ -12,8 +12,6 @@
#include "global/vars.h"
#include