From a550558347813f9b7a434edf42340fd64927549e Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Sat, 25 Jan 2025 14:44:08 +0100 Subject: [PATCH] tr1/game-flow: move demos to own level sequence --- data/tr1/ship/cfg/TR1X_gameflow.json5 | 45 ++++++++++++++++- data/tr1/ship/cfg/TR1X_strings.json5 | 18 +++++++ data/tr2/ship/cfg/TR2X_gameflow.json5 | 6 +-- docs/tr1/GAMEFLOW.md | 24 ++++----- src/libtrx/game/game_string_table/common.c | 19 ++++--- src/libtrx/game/game_string_table/reader.c | 2 +- .../include/libtrx/game/game_flow/types.h | 5 +- src/tr1/game/demo.c | 49 ++++++------------- src/tr1/game/game_flow/common.c | 18 ++++--- src/tr1/game/game_flow/reader.c | 20 +++----- src/tr1/game/game_flow/sequencer.c | 6 +-- src/tr1/game/inventory_ring/control.c | 2 +- 12 files changed, 129 insertions(+), 85 deletions(-) diff --git a/data/tr1/ship/cfg/TR1X_gameflow.json5 b/data/tr1/ship/cfg/TR1X_gameflow.json5 index f853cae49..8a50a4701 100644 --- a/data/tr1/ship/cfg/TR1X_gameflow.json5 +++ b/data/tr1/ship/cfg/TR1X_gameflow.json5 @@ -87,7 +87,6 @@ {"type": "level_stats", "level_id": 2}, {"type": "exit_to_level", "level_id": 3}, ], - "demo": true, }, // Level 3: Lost Valley @@ -108,7 +107,6 @@ {"type": "level_stats", "level_id": 3}, {"type": "exit_to_level", "level_id": 4}, ], - "demo": true, }, // Level 4: Tomb of Qualopec @@ -506,6 +504,49 @@ }, ], + "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": true, + }, + + // 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}, + ], + "demo": true, + }, + + ], + // 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 d2ac09dd7..d47c8ff04 100644 --- a/data/tr1/ship/cfg/TR1X_strings.json5 +++ b/data/tr1/ship/cfg/TR1X_strings.json5 @@ -162,6 +162,24 @@ }, ], + "demos": [ + { + "title": "City of Vilcabamba", + "objects": { + "key_1": {"name": "Silver Key"}, + "puzzle_1": {"name": "Gold Idol"}, + }, + }, + + { + "title": "Lost Valley", + "objects": { + "puzzle_1": {"name": "Machine Cog"}, + }, + }, + + ], + "objects": { "small_medipack": {"name": "Small Medi Pack"}, "large_medipack": {"name": "Large Medi Pack"}, diff --git a/data/tr2/ship/cfg/TR2X_gameflow.json5 b/data/tr2/ship/cfg/TR2X_gameflow.json5 index e2db05a2b..805851a30 100644 --- a/data/tr2/ship/cfg/TR2X_gameflow.json5 +++ b/data/tr2/ship/cfg/TR2X_gameflow.json5 @@ -315,7 +315,7 @@ ], "demos": [ - // 0. Venice + // Demo 1: Venice { "path": "data/boat.tr2", "music_track": -1, @@ -326,7 +326,7 @@ ], }, - // 1. Wreck of the Maria Doria + // Demo 2: Wreck of the Maria Doria { "path": "data/keel.tr2", "music_track": 31, @@ -342,7 +342,7 @@ ], }, - // 2. Tibetan Foothills + // Demo 3: Tibetan Foothills { "path": "data/skidoo.tr2", "music_track": 33, diff --git a/docs/tr1/GAMEFLOW.md b/docs/tr1/GAMEFLOW.md index 34f619fb3..87a414387 100644 --- a/docs/tr1/GAMEFLOW.md +++ b/docs/tr1/GAMEFLOW.md @@ -42,6 +42,12 @@ various pieces of global behaviour. // etc }, ], +"demos": [ + { + "path": "data/gym.phd", + // etc + }, +], "fmvs": [ {"path": "data/snow.rpl"}, // etc @@ -228,7 +234,6 @@ Following are each of the properties available within a level. "water_color": [0.7, 0.5, 0.85], "draw_distance_fade": 34.0, "draw_distance_max": 50.0, - "demo": true, "unobtainable_pickups": 1, "unobtainable_kills": 1, "inherit_injections": false, @@ -257,17 +262,6 @@ Following are each of the properties available within a level. Required Description - - - demo - - Boolean - No - - Flag to indicate that the level has available demo data to play out from - the title screen. - - draw_distance_fade @@ -756,6 +750,12 @@ default gameflow for examples. +## Demos +The `demos` section shows 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. + ## Bonus levels The gameflow supports bonus levels, which are unlocked only when the player collects all secrets in the game's normal levels. These bonus levels behave just diff --git a/src/libtrx/game/game_string_table/common.c b/src/libtrx/game/game_string_table/common.c index 3abbc2f6f..4e56f790f 100644 --- a/src/libtrx/game/game_string_table/common.c +++ b/src/libtrx/game/game_string_table/common.c @@ -91,13 +91,13 @@ void GameStringTable_Apply(const GAME_FLOW_LEVEL *const level) GF_SetLevelTitle( GF_GetLevel(i, GFL_NORMAL), gs_file->levels.entries[i].title); } - -#if TR_VERSION == 2 - // TODO: TR1 still has everything in a single linear sequence for (int32_t i = 0; i < GF_GetLevelCount(GFL_DEMO); i++) { GF_SetLevelTitle( 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); @@ -105,11 +105,18 @@ void GameStringTable_Apply(const GAME_FLOW_LEVEL *const level) #endif if (level != NULL) { + const GS_LEVEL_TABLE *level_table = NULL; #if TR_VERSION == 1 - // TODO: TR1 still has everything in a single linear sequence - const GS_LEVEL_TABLE *const level_table = &gs_file->levels; + // TODO: TR1 still has most levels in a single linear sequence + switch (level->type) { + default: + level_table = &gs_file->levels; + break; + case GFL_DEMO: + level_table = &gs_file->demos; + break; + } #elif TR_VERSION == 2 - const GS_LEVEL_TABLE *level_table = NULL; switch (level->type) { case GFL_NORMAL: case GFL_SAVED: diff --git a/src/libtrx/game/game_string_table/reader.c b/src/libtrx/game/game_string_table/reader.c index 6776a36eb..c26521cc8 100644 --- a/src/libtrx/game/game_string_table/reader.c +++ b/src/libtrx/game/game_string_table/reader.c @@ -146,9 +146,9 @@ void GameStringTable_LoadFromFile(const char *const path) JSON_OBJECT *root_obj = JSON_ValueAsObject(root); 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, "demos", GFL_DEMO, &gs_file->demos); M_LoadLevelsFromJSON( root_obj, "cutscenes", GFL_CUTSCENE, &gs_file->cutscenes); #endif diff --git a/src/libtrx/include/libtrx/game/game_flow/types.h b/src/libtrx/include/libtrx/game/game_flow/types.h index ddf50ff92..c740a5326 100644 --- a/src/libtrx/include/libtrx/game/game_flow/types.h +++ b/src/libtrx/include/libtrx/game/game_flow/types.h @@ -69,8 +69,6 @@ typedef struct { #if TR_VERSION == 1 GAME_FLOW_LEVEL_SETTINGS settings; - bool demo; - struct { uint32_t pickups; uint32_t kills; @@ -110,13 +108,13 @@ typedef struct { int32_t cutscene_count; GAME_FLOW_LEVEL *cutscenes; }; +#endif // demos struct { int32_t demo_count; GAME_FLOW_LEVEL *demos; }; -#endif // FMVs struct { @@ -134,7 +132,6 @@ typedef struct { // global settings struct { float demo_delay; - bool has_demo; char *main_menu_background_path; bool enable_tr2_item_drops; bool convert_dropped_guns; diff --git a/src/tr1/game/demo.c b/src/tr1/game/demo.c index f175f02e8..9be27434f 100644 --- a/src/tr1/game/demo.c +++ b/src/tr1/game/demo.c @@ -40,6 +40,7 @@ typedef struct { TEXTSTRING *text; } M_PRIV; +static int32_t m_LastDemoNum = 0; static M_PRIV m_Priv; static void M_PrepareConfig(M_PRIV *const p); @@ -70,8 +71,18 @@ static void M_PrepareResumeInfo(M_PRIV *const p) RESUME_INFO *const resume_info = GF_GetResumeInfo(p->level); p->old_resume_info = *resume_info; resume_info->flags.available = 1; + resume_info->flags.costume = 0; + resume_info->num_medis = 0; + resume_info->num_big_medis = 0; + resume_info->num_scions = 0; resume_info->flags.got_pistols = 1; + resume_info->flags.got_shotgun = 0; + resume_info->flags.got_magnums = 0; + resume_info->flags.got_uzis = 0; resume_info->pistol_ammo = 1000; + resume_info->shotgun_ammo = 0; + resume_info->magnum_ammo = 0; + resume_info->uzi_ammo = 0; resume_info->gun_status = LGS_ARMLESS; resume_info->equipped_gun_type = LGT_PISTOLS; resume_info->holsters_gun_type = LGT_PISTOLS; @@ -235,41 +246,13 @@ void Demo_Unpause(void) int32_t Demo_ChooseLevel(const int32_t demo_num) { M_PRIV *const p = &m_Priv; - - bool any_demos = false; - for (int32_t i = g_GameFlow.first_level_num; i <= g_GameFlow.last_level_num; - i++) { - if (g_GameFlow.levels[i].demo) { - any_demos = true; - } - } - if (!any_demos) { - return -1; - } - - if (demo_num >= 0) { - int32_t j = 0; - for (int32_t i = g_GameFlow.first_level_num; - i <= g_GameFlow.last_level_num; i++) { - if (g_GameFlow.levels[i].demo) { - if (j == demo_num) { - return i; - } - j++; - } - } + if (GF_GetDemoCount() <= 0) { return -1; + } else if (demo_num < 0 || demo_num >= GF_GetDemoCount()) { + return (m_LastDemoNum++) % GF_GetDemoCount(); + } else { + return demo_num; } - - // pick the next demo - int16_t level_num = p->level != NULL ? p->level->num : -1; - do { - level_num++; - if (level_num > g_GameFlow.last_level_num) { - level_num = g_GameFlow.first_level_num; - } - } while (!g_GameFlow.levels[level_num].demo); - return level_num; } GAME_FLOW_COMMAND Demo_Control(void) diff --git a/src/tr1/game/game_flow/common.c b/src/tr1/game/game_flow/common.c index 73c36824d..9c7a4a344 100644 --- a/src/tr1/game/game_flow/common.c +++ b/src/tr1/game/game_flow/common.c @@ -56,6 +56,8 @@ int32_t GF_GetLevelCount(const GAME_FLOW_LEVEL_TYPE level_type) return 1; case GFL_GYM: return 1; + case GFL_DEMO: + return g_GameFlow.demo_count; default: return g_GameFlow.level_count; } @@ -63,14 +65,7 @@ int32_t GF_GetLevelCount(const GAME_FLOW_LEVEL_TYPE level_type) int32_t GF_GetDemoCount(void) { - int32_t demo_count = 0; - for (int32_t i = g_GameFlow.first_level_num; i <= g_GameFlow.last_level_num; - i++) { - if (g_GameFlow.levels[i].demo) { - demo_count++; - } - } - return demo_count; + return g_GameFlow.demo_count; } void GF_SetLevelTitle(GAME_FLOW_LEVEL *const level, const char *const title) @@ -96,6 +91,13 @@ GAME_FLOW_LEVEL *GF_GetLevel( case GFL_TITLE: return &g_GameFlow.levels[g_GameFlow.title_level_num]; + case GFL_DEMO: + if (num < 0 || num >= GF_GetDemoCount()) { + LOG_ERROR("Invalid demo number: %d", num); + return NULL; + } + return &g_GameFlow.demos[num]; + default: if (num < 0 || num >= GF_GetLevelCount(level_type)) { LOG_ERROR("Invalid level number: %d", num); diff --git a/src/tr1/game/game_flow/reader.c b/src/tr1/game/game_flow/reader.c index 6d5ee5ffa..a3e78ad8a 100644 --- a/src/tr1/game/game_flow/reader.c +++ b/src/tr1/game/game_flow/reader.c @@ -524,17 +524,6 @@ static void M_LoadLevel( level->music_track = tmp; } - { - const int32_t tmp = - JSON_ObjectGetBool(jlvl_obj, "demo", JSON_INVALID_BOOL); - if (tmp != JSON_INVALID_BOOL) { - level->demo = tmp; - gf->has_demo |= tmp; - } else { - level->demo = false; - } - } - level->settings = gf->settings; M_LoadSettings(jlvl_obj, &level->settings); @@ -615,7 +604,6 @@ static void M_LoadLevels(JSON_OBJECT *const obj, GAME_FLOW *const gf) JSON_ARRAY_ELEMENT *jlvl_elem = jlvl_arr->start; int32_t level_num = 0; - gf->has_demo = 0; gf->gym_level_num = -1; gf->first_level_num = -1; gf->last_level_num = -1; @@ -634,6 +622,13 @@ static void M_LoadLevels(JSON_OBJECT *const obj, GAME_FLOW *const gf) } } +static void M_LoadDemos(JSON_OBJECT *obj, GAME_FLOW *const gf) +{ + M_LoadArray( + obj, "demos", sizeof(GAME_FLOW_LEVEL), (M_LOAD_ARRAY_FUNC)M_LoadLevel, + gf, &gf->demo_count, (void **)&gf->demos, (void *)(intptr_t)GFL_DEMO); +} + static void M_LoadFMV( JSON_OBJECT *const obj, const GAME_FLOW *const gf, GAME_FLOW_FMV *const fmv, size_t idx, void *const user_arg) @@ -731,6 +726,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_LoadDemos(root_obj, gf); M_LoadFMVs(root_obj, gf); if (root != NULL) { diff --git a/src/tr1/game/game_flow/sequencer.c b/src/tr1/game/game_flow/sequencer.c index e4d3d86ad..f0666f0a1 100644 --- a/src/tr1/game/game_flow/sequencer.c +++ b/src/tr1/game/game_flow/sequencer.c @@ -403,9 +403,9 @@ GAME_FLOW_COMMAND GF_LoadLevel( GAME_FLOW_COMMAND GF_DoDemoSequence(int32_t demo_num) { - const int32_t level_num = Demo_ChooseLevel(demo_num); - if (level_num < 0) { + demo_num = Demo_ChooseLevel(demo_num); + if (demo_num < 0) { return (GAME_FLOW_COMMAND) { .action = GF_EXIT_TO_TITLE }; } - return GF_InterpretSequence(level_num, GFL_DEMO); + return GF_InterpretSequence(demo_num, GFL_DEMO); } diff --git a/src/tr1/game/inventory_ring/control.c b/src/tr1/game/inventory_ring/control.c index 2a11dad9a..befd011d7 100644 --- a/src/tr1/game/inventory_ring/control.c +++ b/src/tr1/game/inventory_ring/control.c @@ -815,7 +815,7 @@ static GAME_FLOW_COMMAND M_Control(INV_RING *const ring) static bool M_CheckDemoTimer(const INV_RING *const ring) { - if (!g_Config.gameplay.enable_demo || !g_GameFlow.has_demo) { + if (!g_Config.gameplay.enable_demo || GF_GetDemoCount() == 0) { return false; }