Skip to content

Commit

Permalink
implement id24 demoloop specification
Browse files Browse the repository at this point in the history
  • Loading branch information
elf-alchemist committed Jan 12, 2025
1 parent 6cd8c4e commit 290cc6a
Show file tree
Hide file tree
Showing 6 changed files with 427 additions and 100 deletions.
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ include(WoofSettings)
set(WOOF_SOURCES
am_map.c am_map.h
d_deh.c d_deh.h
d_demoloop.c d_demoloop.h
d_englsh.h
d_event.h
d_french.h
Expand Down
306 changes: 306 additions & 0 deletions src/d_demoloop.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
//
// Copyright (C) 2025 Guilherme Miranda
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// DESCRIPTION:
// ID24 DemoLoop

#include "doomdef.h"
#include "doomstat.h"

#include "i_printf.h"
#include "m_array.h"
#include "m_json.h"
#include "w_wad.h"

#include "d_demoloop.h"

// DEMO1 lump playback.
static const demoloop_entry_t dl_demo1 = {
"DEMO1",
"",
0,
TYPE_DEMO_LUMP,
OUTRO_WIPE_SCREEM_MELT,
};

// DEMO2 lump playback.
static const demoloop_entry_t dl_demo2 = {
"DEMO2",
"",
0,
TYPE_DEMO_LUMP,
OUTRO_WIPE_SCREEM_MELT,
};

// DEMO3 lump playback.
static const demoloop_entry_t dl_demo3 = {
"DEMO3",
"",
0,
TYPE_DEMO_LUMP,
OUTRO_WIPE_SCREEM_MELT,
};

// DEMO4 lump, only present in Ultimate Doom.
// The DEMO4 entry crashed in the original Final Doom EXE's loop.
static const demoloop_entry_t dl_demo4 = {
"DEMO4",
"",
0,
TYPE_DEMO_LUMP,
OUTRO_WIPE_SCREEM_MELT,
};

// Oddly, Doom's TITLEPIC lasts for a brifer period than Doom II.
// About ~4.857 seconds, as opposed to Doom II's exact 11 seconds.
static const demoloop_entry_t dl_doom1_titlepic = {
"TITLEPIC",
"D_INTRO",
170,
TYPE_ART_SCREEN,
OUTRO_WIPE_SCREEM_MELT,
};

// HELP2 titlescreen lasts for about ~5.714 seconds.
// Used only in Registered mode.
static const demoloop_entry_t dl_help2 = {
"HELP2",
"",
200,
TYPE_ART_SCREEN,
OUTRO_WIPE_SCREEM_MELT,
};

// Plain Doom II, Commercial mode, TITLEPIC lasts for exactly 11.0 seconds.
static const demoloop_entry_t dl_doom2_titlepic = {
"TITLEPIC",
"D_DM2TTL",
385,
TYPE_ART_SCREEN,
OUTRO_WIPE_SCREEM_MELT,
};

// CREDIT titlescreen lasts for about ~5.714 seconds.
// Used in Retail and Commercial mode.
static const demoloop_entry_t dl_credit = {
"CREDIT",
"",
200,
TYPE_ART_SCREEN,
OUTRO_WIPE_SCREEM_MELT,
};

// Used to check for fault tolerance
static const demoloop_entry_t dl_none = {
"",
"",
0,
TYPE_NONE,
OUTRO_WIPE_NONE,
};

// Doom
demoloop_entry_t demoloop_registered[6] = {
dl_doom1_titlepic,
dl_demo1,
dl_credit,
dl_demo2,
dl_help2,
dl_demo3,
};

// Ultiamte Doom
demoloop_entry_t demoloop_retail[7] = {
dl_doom1_titlepic,
dl_demo1,
dl_credit,
dl_demo2,
dl_credit,
dl_demo3,
dl_demo4,
};

// Doom II & fixed Final Doom
demoloop_entry_t demoloop_commercial[6] = {
dl_doom2_titlepic,
dl_demo1,
dl_credit,
dl_demo2,
dl_doom2_titlepic,
dl_demo3,
};

// TNT: Evilution & The Plutonia Experiement
demoloop_entry_t demoloop_final[7] = {
dl_doom2_titlepic,
dl_demo1,
dl_credit,
dl_demo2,
dl_doom2_titlepic,
dl_demo3,
dl_demo4,
};

// For local parsing purposes only.
static demoloop_entry_t current_entry;

demoloop_t demoloop;
int demoloop_count = 0;
int demoloop_current = -1;


// TODO: actually finish this and make it work
demoloop_entry_t D_ParseDemoLoopEntry(json_t *entry)
{
const char* primary_lump = JS_GetStringValue(entry, "primarylump");
const char* secondary_lump = JS_GetStringValue(entry, "secondarylump");
double seconds = JS_GetNumberValue(entry, "duration");
int type = JS_GetIntegerValue(entry, "type");
int outro_wipe = JS_GetIntegerValue(entry, "outro_wipe");

// We don't want a malformed entry to creep in, and break the titlescreen.
// If one such entry does exist, skip it.
// TODO: modify later to check locally for lump type.
if (type <= TYPE_NONE || type > TYPE_DEMO_LUMP)
{
return dl_none;
}

// Similarly, but this time it isn't game-breaking.
// Let it gracefully default to "closest vanilla behavior".
if (outro_wipe <= OUTRO_WIPE_NONE || outro_wipe > OUTRO_WIPE_SCREEM_MELT)
{
outro_wipe = OUTRO_WIPE_SCREEM_MELT;
}

// Providing the time in seconds is much more intuitive for the end users.
int duration = seconds * TICRATE;

demoloop_entry_t demoloop_entry = {
primary_lump,
secondary_lump,
duration,
type,
outro_wipe,
};

return demoloop_entry;
}

void D_ParseDemoLoop(void)
{
// Does the JSON lump even exist?
json_t *json = JS_Open("DEMOLOOP", "demoloop", (version_t){1, 0, 0});
if (json == NULL)
{
return;
}

// Does lump actually have any data?
json_t *data = JS_GetObject(json, "data");
if (JS_IsNull(data) || !JS_IsObject(data))
{
I_Printf(VB_WARNING, "DEMOLOOP: data object not defined");
JS_Close("DEMOLOOP");
return;
}

// Does is it even have the definitions we are looking for?
json_t *entry_list = JS_GetObject(data, "entries");
if (JS_IsNull(entry_list) || !JS_IsArray(entry_list))
{
I_Printf(VB_WARNING, "DEMOLOOP: no entries defined");
JS_Close("DEMOLOOP");
return;
}

// If so, now parse them.
json_t *entry;

JS_ArrayForEach(entry, entry_list)
{
current_entry = D_ParseDemoLoopEntry(entry);

// Should there be a malformed entry, discard it.
if (current_entry.type == TYPE_NONE)
{
continue;
}

array_push(demoloop, current_entry);
demoloop_count++;
}

return;
}

void D_GetDefaultDemoLoop(GameMission_t mission, GameMode_t mode)
{
switch(mission) {
case doom:
// The versions of Doom that have HELP2, instead.
if (mode == registered || mode == shareware) {
demoloop = demoloop_registered;
demoloop_count = 6;
break;
}
case pack_chex3v:
// Check for chex3d2.wad, "Chex Quest 3: Modding Edition".
if (mode == commercial) {
demoloop = demoloop_commercial;
demoloop_count = 6;
break;
}
case pack_rekkr:
case pack_chex:
// Plain Ultimate Doom
demoloop = demoloop_retail;
demoloop_count = 7;
break;

case doom2:
case pack_hacx:
// Plain Doom II
demoloop = demoloop_commercial;
demoloop_count = 6;
break;

case pack_tnt:
case pack_plut:
// Plain Final Doom
demoloop = demoloop_final;
demoloop_count = 7;
break;

case none:
default:
// How did we get here?
demoloop = NULL;
demoloop_count = 0;
break;
}

return;
}

void D_SetupDemoLoop(void) {
if (W_CheckNumForName("DEMOLOOP") >= 0)
{
D_ParseDemoLoop();
}

if (demoloop == NULL)
{
D_GetDefaultDemoLoop(gamemission, gamemode);
}
}
59 changes: 59 additions & 0 deletions src/d_demoloop.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// Copyright (C) 2025 Guilherme Miranda
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// DESCRIPTION:
// ID24 DemoLoop

#include "doomdef.h"

// Screen graphic or DEMO lump, NONE is used for fault tolerance.
typedef enum
{
TYPE_NONE = -1,
TYPE_ART_SCREEN,
TYPE_DEMO_LUMP,
} dl_type_t;

// Immediate switch or screen melt, NONE is used for fault tolerance.
typedef enum
{
OUTRO_WIPE_NONE = -1,
OUTRO_WIPE_IMMEDIATE,
OUTRO_WIPE_SCREEM_MELT,
} dl_outro_wipe_t;

// Individual demoloop units.
typedef struct
{
const char* primary_lump; // Screen graphic or DEMO lump.
const char* secondary_lump; // Music lump for screen graphic.
int duration; // Game tics.
dl_type_t type;
dl_outro_wipe_t outro_wipe;
} demoloop_entry_t;

typedef demoloop_entry_t* demoloop_t;

// Actual DemoLoop data structure.
extern demoloop_t demoloop;
// Formerly 7 for Ultimate Doom & Final Doom, 6 otherwise.
extern int demoloop_count;
// Formerly "demosequence".
extern int demoloop_current;

// Parse the DEMOLOOP, returning NULL to "demoloop" should any errors occur.
void D_ParseDemoLoop(void);
// If "demoloop" is NULL, check for defaults using mission and mode.
void D_GetDefaultDemoLoop(GameMission_t mission, GameMode_t mode);
// Perform both "D_ParseDemoLoop" and "D_GetDefaultDemoLoop".
void D_SetupDemoLoop(void);
Loading

0 comments on commit 290cc6a

Please sign in to comment.