tr1/game-flow: move demos to own level sequence

This commit is contained in:
Marcin Kurczewski 2025-01-25 14:44:08 +01:00
parent 6614bbfbbf
commit 999f3614cc
12 changed files with 128 additions and 86 deletions

View file

@ -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,47 @@
},
],
"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"},

View file

@ -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"},

View file

@ -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,

View file

@ -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.
<th>Required</th>
<th colspan="2">Description</th>
</tr>
<tr valign="top">
<td>
<code>demo</code>
</td>
<td>Boolean</td>
<td>No</td>
<td colspan="2">
Flag to indicate that the level has available demo data to play out from
the title screen.
</td>
</tr>
<tr valign="top">
<td>
<code>draw_distance_fade</code>
@ -756,6 +750,12 @@ default gameflow for examples.
</tr>
</table>
## 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

View file

@ -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) {
#if TR_VERSION == 1
// TODO: TR1 still has everything in a single linear sequence
const GS_LEVEL_TABLE *const level_table = &gs_file->levels;
#elif TR_VERSION == 2
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:
level_table = &gs_file->levels;
break;
case GFL_DEMO:
level_table = &gs_file->demos;
break;
}
#elif TR_VERSION == 2
switch (level->type) {
case GFL_NORMAL:
case GFL_SAVED:

View file

@ -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

View file

@ -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;

View file

@ -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) {
if (GF_GetDemoCount() <= 0) {
return -1;
} else if (demo_num < 0 || demo_num >= GF_GetDemoCount()) {
return (m_LastDemoNum++) % GF_GetDemoCount();
} else {
return demo_num;
}
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++;
}
}
return -1;
}
// 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)

View file

@ -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);

View file

@ -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);
@ -614,7 +603,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;
@ -633,6 +621,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)
@ -730,6 +725,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) {

View file

@ -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);
}

View file

@ -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;
}