Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ID24 DemoLoop specification #2141

Merged
merged 7 commits into from
Jan 16, 2025

Conversation

elf-alchemist
Copy link
Contributor

@elf-alchemist elf-alchemist commented Jan 12, 2025

Use and customize the DEMOLOOP lump in the following WAD file for local testing: test.zip

I may have been a bit overzealous with the comment-based documentation, in the anticipation someone may fork these cahnges for their own port.

This entire thing was done by reading the ID24 spec itself, and studying the reference implementation in RnR Doom.

Some older char * types had to be changed to const char * due to the JSON parsing code only returning in const.

Putting this up for general code review and nitpicking but currently it is only missing:

  • Slightly better fault tolerance for incorrectly defined type, triggering a reading of the primarylump field to determine if it is a DEMO or a graphic lump.
  • Figuring out the outrowipe behavior, I haven't quite yet understood how to do it, or rather where to put it up to trigger such a immediate switch or a screen melt.

image

@elf-alchemist
Copy link
Contributor Author

...I turn around for one moment and Windows decides it doesn't like me :P

src/d_demoloop.c Outdated Show resolved Hide resolved
src/d_demoloop.h Outdated Show resolved Hide resolved
src/d_demoloop.c Outdated Show resolved Hide resolved
src/d_main.c Outdated Show resolved Hide resolved
src/d_main.c Outdated Show resolved Hide resolved
src/d_main.c Outdated Show resolved Hide resolved
src/d_main.c Outdated Show resolved Hide resolved
src/d_demoloop.c Outdated Show resolved Hide resolved
src/d_demoloop.c Outdated Show resolved Hide resolved
src/d_demoloop.c Outdated Show resolved Hide resolved
src/d_demoloop.c Outdated Show resolved Hide resolved
@rfomin
Copy link
Collaborator

rfomin commented Jan 13, 2025

We have a FileIsDemoLump() function in d_main.c that could be adapted for in-memory lump.

  • Figuring out the outrowipe behavior, I haven't quite yet understood how to do it, or rather where to put it up to trigger such a immediate switch or a screen melt.

We have wipe_None and wipe_Melt in f_wipe.h and screen_melt varibale in d_main.c. Like this:

if (outro_wipe == WIPE_MELT)
{
    old_screen_melt = screen_melt;
    screen_melt = wipe_Melt;
}

We need to restore the old value of screen_melt after running DEMOLOOP, since it's a user option. We can also add support for other melt types, which would be a Woof extension.

src/d_demoloop.c Outdated Show resolved Hide resolved
src/d_demoloop.c Outdated Show resolved Hide resolved
src/d_main.c Outdated Show resolved Hide resolved
src/d_main.c Outdated Show resolved Hide resolved
src/d_main.c Outdated Show resolved Hide resolved
src/d_demoloop.h Outdated Show resolved Hide resolved
src/d_demoloop.c Outdated Show resolved Hide resolved
src/d_demoloop.c Outdated Show resolved Hide resolved
src/d_demoloop.c Outdated Show resolved Hide resolved
@fabiangreffrath
Copy link
Owner

I suggest the following changes:

diff --git a/src/d_demoloop.c b/src/d_demoloop.c
index 2e652fd7..c4833d1c 100644
--- a/src/d_demoloop.c
+++ b/src/d_demoloop.c
@@ -60,10 +60,7 @@ static demoloop_entry_t demoloop_commercial[] = {
     { "DEMO4",    "",         0,   TYPE_DEMO, WIPE_MELT },
 };
 
-// For local parsing purposes only.
-static demoloop_entry_t current_entry;
-
-demoloop_t demoloop;
+demoloop_t demoloop = NULL;
 int        demoloop_count = 0;
 
 void D_ParseDemoLoopEntry(json_t *entry)
@@ -90,6 +87,8 @@ void D_ParseDemoLoopEntry(json_t *entry)
         outro_wipe = WIPE_MELT;
     }
 
+    demoloop_entry_t current_entry = {0};
+
     // Remove pointer reference to in-memory JSON data.
     M_CopyLumpName(current_entry.primary_lump, primary_buffer);
     M_CopyLumpName(current_entry.secondary_lump, secondary_buffer);
@@ -97,9 +96,17 @@ void D_ParseDemoLoopEntry(json_t *entry)
     current_entry.duration   = seconds * TICRATE;
     current_entry.type       = type;
     current_entry.outro_wipe = outro_wipe;
+
+    // Should there be a malformed entry, discard it.
+    if (current_entry.type == TYPE_NONE)
+    {
+        return;
+    }
+
+    array_push(demoloop, current_entry);
 }
 
-void D_ParseDemoLoop(void)
+static void D_ParseDemoLoop(void)
 {
     // Does the JSON lump even exist?
     json_t *json = JS_Open("DEMOLOOP", "demoloop", (version_t){1, 0, 0});
@@ -128,27 +135,17 @@ void D_ParseDemoLoop(void)
 
     // If so, now parse them.
     json_t *entry;
-
     JS_ArrayForEach(entry, entry_list)
     {
         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++;
     }
+    demoloop_count = array_size(demoloop);
 
     // No need to keep in memory
     JS_Close("DEMOLOOP");
-    return;
 }
 
-void D_GetDefaultDemoLoop(GameMission_t mission, GameMode_t mode)
+static void D_GetDefaultDemoLoop(GameMode_t mode)
 {
     switch(mode)
     {
@@ -172,17 +169,17 @@ void D_GetDefaultDemoLoop(GameMission_t mission, GameMode_t mode)
         default:
             // How did we get here?
             demoloop = NULL;
+            demoloop_count = 0;
             break;
     }
-
-    return;
 }
 
-void D_SetupDemoLoop(void) {
+void D_SetupDemoLoop(void)
+{
     D_ParseDemoLoop();
 
     if (demoloop == NULL)
     {
-        D_GetDefaultDemoLoop(gamemission, gamemode);
+        D_GetDefaultDemoLoop(gamemode);
     }
 }
diff --git a/src/d_demoloop.h b/src/d_demoloop.h
index cf9e1cdc..33ae6142 100644
--- a/src/d_demoloop.h
+++ b/src/d_demoloop.h
@@ -56,10 +56,6 @@ extern demoloop_t demoloop;
 // Formerly 7 for Ultimate Doom & Final Doom, 6 otherwise.
 extern int        demoloop_count;
 
-// 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);
 
diff --git a/src/d_main.c b/src/d_main.c
index 79ca80f0..d349b5a1 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -436,7 +436,7 @@ void D_Display (void)
 static int demosequence;         // killough 5/2/98: made static
 static int pagetic;
 static const char *pagename;
-static demoloop_entry_t demoloop_point;
+static demoloop_t demoloop_point;
 
 //
 // D_PageTicker
@@ -477,7 +477,7 @@ void D_AdvanceDemo(void)
 void D_AdvanceDemoLoop(void)
 {
   demosequence = (demosequence + 1) % demoloop_count;
-  demoloop_point = demoloop[demosequence];
+  demoloop_point = &demoloop[demosequence];
 }
 
 void D_DoAdvanceDemo(void)
@@ -489,23 +489,23 @@ void D_DoAdvanceDemo(void)
     gameaction = ga_nothing;
 
     D_AdvanceDemoLoop();
-    switch (demoloop_point.type)
+    switch (demoloop_point->type)
     {
         case TYPE_ART:
             gamestate = GS_DEMOSCREEN;
 
             // Needed to support the Doom 3: BFG Edition variant
-            if (W_CheckNumForName(demoloop_point.primary_lump) < 0
-                && !strcasecmp(demoloop_point.primary_lump, "TITLEPIC"))
+            if (W_CheckNumForName(demoloop_point->primary_lump) < 0
+                && !strcasecmp(demoloop_point->primary_lump, "TITLEPIC"))
             {
-                M_CopyLumpName(demoloop_point.primary_lump, "DMENUPIC");
+                M_CopyLumpName(demoloop_point->primary_lump, "DMENUPIC");
             }
 
-            if (W_CheckNumForName(demoloop_point.primary_lump) >= 0)
+            if (W_CheckNumForName(demoloop_point->primary_lump) >= 0)
             {
-                pagename = demoloop_point.primary_lump;
-                pagetic = demoloop_point.duration;
-                int music = W_CheckNumForName(demoloop_point.secondary_lump);
+                pagename = demoloop_point->primary_lump;
+                pagetic = demoloop_point->duration;
+                int music = W_CheckNumForName(demoloop_point->secondary_lump);
                 if (music >= 0)
                 {
                     S_ChangeMusInfoMusic(music, false);
@@ -517,15 +517,17 @@ void D_DoAdvanceDemo(void)
         case TYPE_DEMO:
             gamestate = GS_DEMOSCREEN;
 
-            if (W_CheckNumForName(demoloop_point.primary_lump) >= 0)
+            if (W_CheckNumForName(demoloop_point->primary_lump) >= 0)
             {
-              G_DeferedPlayDemo(demoloop_point.primary_lump);
-              break;
+                G_DeferedPlayDemo(demoloop_point->primary_lump);
+                break;
             }
             // fallthrough
 
         case TYPE_NONE:
         default:
+            gamestate = GS_NONE;
+
             I_Printf(VB_WARNING,
                      "D_DoAdvanceDemo: Invalid demoloop[%d] entry, skipping",
                      demosequence);

@rfomin
Copy link
Collaborator

rfomin commented Jan 14, 2025

Support for outrowipe field:

diff --git a/src/d_main.c b/src/d_main.c
index 79ca80f0..96404bf6 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -251,7 +251,16 @@ void D_ProcessEvents (void)
 
 // wipegamestate can be set to -1 to force a wipe on the next draw
 gamestate_t    wipegamestate = GS_DEMOSCREEN;
-static int     screen_melt = wipe_Melt;
+static int     screen_melt = wipe_Melt, old_screen_melt = -1;
+
+void D_RestoreScreenMelt(void)
+{
+    if (old_screen_melt >= 0)
+    {
+        screen_melt = old_screen_melt;
+        old_screen_melt = -1;
+    }
+}
 
 void D_Display (void)
 {
@@ -531,6 +540,20 @@ void D_DoAdvanceDemo(void)
                      demosequence);
             break;
     }
+
+    if (old_screen_melt < 0)
+    {
+        old_screen_melt = screen_melt;
+    }
+
+    if (demoloop_point.outro_wipe == WIPE_IMMEDIATE)
+    {
+        screen_melt = wipe_None;
+    }
+    else if (demoloop_point.outro_wipe == WIPE_MELT)
+    {
+        screen_melt = wipe_Melt;
+    }
 }
 
 //
diff --git a/src/d_main.h b/src/d_main.h
index 865c65d8..6e72f006 100644
--- a/src/d_main.h
+++ b/src/d_main.h
@@ -62,6 +62,8 @@ void D_StartTitle(void);
 
 extern boolean advancedemo;
 
+void D_RestoreScreenMelt(void);
+
 #endif
 
 //----------------------------------------------------------------------------
diff --git a/src/g_game.c b/src/g_game.c
index 431f546a..127a1010 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2835,6 +2835,9 @@ void G_Ticker(void)
       G_DoReborn (i);
   P_MapEnd();
 
+  if (gameaction != ga_nothing && gameaction != ga_playdemo)
+    D_RestoreScreenMelt();
+
   // do things to change the game state
   while (gameaction != ga_nothing)
     switch (gameaction)

From my testing, outrowipe does not work in the KEX port?

@elf-alchemist
Copy link
Contributor Author

I suggest the following changes:

diff --git a/src/d_main.c b/src/d_main.c
index 79ca80f0..d349b5a1 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -517,15 +517,17 @@ void D_DoAdvanceDemo(void)
         case TYPE_DEMO:
             gamestate = GS_DEMOSCREEN;
 
-            if (W_CheckNumForName(demoloop_point.primary_lump) >= 0)
+            if (W_CheckNumForName(demoloop_point->primary_lump) >= 0)
             {
-              G_DeferedPlayDemo(demoloop_point.primary_lump);
-              break;
+                G_DeferedPlayDemo(demoloop_point->primary_lump);
+                break;
             }
             // fallthrough
 
         case TYPE_NONE:
         default:
+            gamestate = GS_NONE;
+
             I_Printf(VB_WARNING,
                      "D_DoAdvanceDemo: Invalid demoloop[%d] entry, skipping",
                      demosequence);

Much cleaner! Only issue here is that in case TYPE_NONE: can't reset gamestate to GS_NONE, it'll stop the loop and HOM the screen on the demoloop_commercial DEMO4 on Doom II, and really any other mistaken entry. Otherwise, I've commited these changes on my local branch.

@elf-alchemist
Copy link
Contributor Author

Support for outrowipe field:

Yup works really well. I wonder if it is possible to make the wipe work with the Art Screens, as well? It currently only checks does the wipe for entering/exiting DEMOs. Not that the specification is clear on that part.

From my testing, outrowipe does not work in the KEX port?

I'm not surprised. Seems even the reference implementation in RumAndRaisin isn't doing anything with that field.

Another thing I've noticed is that RnR uses the pre-existing wipe_Melt and wipe_None values for it's own f_wipe.c changes, perhaps a simple refactoring here is in order if and when additional Wipe values are added, as suggested before:

We can also add support for other melt types, which would be a Woof extension.

@rfomin
Copy link
Collaborator

rfomin commented Jan 14, 2025

Seems even the reference implementation in RumAndRaisin isn't doing anything with that field.

R&R has incomplete ID24 implementation, KEX port is ahead.

Maybe we should remove outrowipe support then? It's clunky since we have user wipe option. I don't think it's very useful.

@fabiangreffrath
Copy link
Owner

Maybe we should remove outrowipe support then? It's clunky since we have user wipe option. I don't think it's very useful.

Me too. If not even KEX supports this itself, why should we invest effort into this?

@elf-alchemist
Copy link
Contributor Author

It doesn't seem like such an expensive feature to keep around, even if it is wholely cosmetic. Otherwise it can be easily removed. On the very least, Woof! can say it officially supports the entire spec. Although, I'll ask GooberMan if there are any plans to extend it to further wipes, already.

@rfomin
Copy link
Collaborator

rfomin commented Jan 14, 2025

On the very least, Woof! can say it officially supports the entire spec.

Eh, not much value here. ID24 spec is way too ambitious in some parts, for example I highly doubt any port will ever support all the complevels it declares. I vote for removal.

To be honest, I don't like any alternative "wipe" effects. I remember how annoying "wipe" was in Hearthland, as Eternity Engine doesn't have an option for it in the menu, only some console command.

@elf-alchemist
Copy link
Contributor Author

I highly doubt any port will ever support all the complevels it declares. I vote for removal.

We will never. We do not need someone's development roadmap shoved down everyone else's throats.

There is no justifiable reason for anyone to even remotely accept that ridiculous non-sense as a complevel system, independent of whatever happens, Woof will, should and must only support Vanilla, Boom, MBF, MBF21 and ID24 (if it is even added at all, that is).

At any rate, I cast my vote to keep outrowipe

@fabiangreffrath
Copy link
Owner

Use and customize the DEMOLOOP lump in the following WAD file for local testing: test.zip

Please note that the DEMOLOOP lump in the WAD file embedded in test.zip is broken. There is a comma missing between "CREDIT" and "secondarylump" in line 10.

@elf-alchemist
Copy link
Contributor Author

Yup, sure enough, my hand-editing JSON habit is lagging behind. I've updated the original file.

{
  "type": "demoloop",
  "version": "1.0.0",
  "metadata": null,
  "data":
  {
    "entries": [
      {
        "primarylump": "TITLEPIC",
        "secondarylump": "D_OPENIN",
        "duration": 10.0,
        "type": 0,
        "outrowipe": 1
      },
      {
        "primarylump": "HELP",
        "secondarylump": "",
        "duration": 10.0,
        "type": 0,
        "outrowipe": 1
      },
      {
        "primarylump": "CREDIT",
        "secondarylump": "",
        "duration": 10.0,
        "type": 0,
        "outrowipe": 1
      }
    ]
  }
}

@elf-alchemist elf-alchemist marked this pull request as ready for review January 14, 2025 11:56
@fabiangreffrath
Copy link
Owner

Well, as it is now, it'd be more effort to remove support for the outrowipe than to keep it. 😁

Copy link
Owner

@fabiangreffrath fabiangreffrath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much!

@rfomin
Copy link
Collaborator

rfomin commented Jan 15, 2025

Current implementation of outrowipe is not good.

  1. What should have higher priority, menu option or DEMOLOOP setting? Currently DEMOLOOP has higher priority and menu option restored after new game/load game.
  2. outrowipe doesn't work with the art screen.
  3. What to do with strict mode? Currently it overwrites DEMOLOOP setting.

Well, as it is now, it'd be more effort to remove support for the outrowipe than to keep it. 😁

We can just revert the last commit.

@rfomin rfomin mentioned this pull request Jan 15, 2025
@fabiangreffrath
Copy link
Owner

Current implementation of outrowipe is not good.
We can just revert the last commit.

Alright. @elf-alchemist Please revert the last commit. We could add a TODO comment instead and work on a better implementation once this feature becomes relevant.

@elf-alchemist
Copy link
Contributor Author

Forgot to reply here earlier, but yes, I've reverted the change.
In that mean time, I had noticed something mildly interesting in LoR (as I was looking around for the XFINALEX1 cast sequence lump I'll be working on) that id1.wad actually has 4 DEMO lumps inside, meaning Fabian was already right in calling for the merger of the FD and D2 loops 👍

Copy link
Collaborator

@rfomin rfomin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@rfomin rfomin merged commit f9b9f23 into fabiangreffrath:master Jan 16, 2025
8 checks passed
@elf-alchemist elf-alchemist deleted the id24_demoloop branch January 16, 2025 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants