shithub: choc

Download patch

ref: 31285f01f292ca3b70851e5551e97f2d483924f4
parent: 85b0d244c85e3f26e889970df25ec881863ff532
parent: 15b8e6e1e47e4f733f862c16a5c18a3485bd22d4
author: Mike Swanson <mikeonthecomputer@gmail.com>
date: Mon Oct 24 10:00:30 EDT 2016

Merge branch 'master' into sdl2-branch

--- a/NEWS.md
+++ b/NEWS.md
@@ -18,6 +18,10 @@
   * The vanilla limit of 4046 lumps per WAD is now enforced. (thanks
     Jon, Quasar, Edward-san)
   * Solidsegs overflow is emulated like in vanilla. (thanks Quasar)
+  * Heretic/Hexen demo support has expanded. "-demoextend" allows
+    demos to last longer than a single level; "-shortticfix" adjusts
+    low-resolution turning to match Doom's handling; "-maxdemo" and
+    "-longtics" support. (thanks CapnClever)
 
 ### Build systems
   * Improved compatibility with BSD Make. (thanks R.Rebello)
--- a/README.Music.md
+++ b/README.Music.md
@@ -2,7 +2,7 @@
 the era, it is MIDI-based. Chocolate Doom includes a number of
 different options for music playback, detailed below.
 
-## Native MIDI playback
+# Native MIDI playback
 
 Most modern operating systems have some kind of built-in support for
 MIDI playback; some have very good quality MIDI playback (Mac OS X for
@@ -9,7 +9,7 @@
 example). To use this, choose “Native MIDI” in the sound configuration
 dialog in the setup tool.
 
-## Timidity
+# Timidity
 
 Timidity is a software-based MIDI synthesizer, and a version of it is
 included in the SDL2_mixer library used by Chocolate Doom. To use
@@ -24,7 +24,7 @@
 configuration file” widget below to enter the path to the Timidity
 configuration file (normally named timidity.cfg).
 
-## Gravis Ultrasound (GUS)
+# Gravis Ultrasound (GUS)
 
 The Gravis Ultrasound (GUS) was a PC sound card popular in the ’90s,
 notable for having wavetable synthesis that provided MIDI playback
@@ -48,7 +48,7 @@
 512KB or 768KB card instead, change the gus_ram_kb option in
 chocolate-doom.cfg.
 
-## OPL (Soundblaster / Adlib)
+# OPL (Soundblaster / Adlib)
 
 Most people playing Doom in the ’90s had Soundblaster-compatible sound
 cards, which used the Yamaha OPL series of chips for FM-based MIDI
@@ -62,7 +62,7 @@
 cards do have real hardware OPL. If you have such a card, here’s how
 to configure Chocolate Doom to use it.
 
-# Sound cards with OPL chips
+## Sound cards with OPL chips
 
 If you have an ISA sound card, it almost certainly includes an OPL
 chip. Modern computers don’t have slots for ISA cards though, so you
@@ -89,13 +89,15 @@
 If you desperately want hardware OPL music, you may be able to find
 one of these cards for sale cheap on eBay.
 
-For the cards listed above with (*) next to them, OPL support is
-disabled by default and must be explictly enabled in software.
+For the cards listed above with (\*) next to them, OPL support is
+disabled by default and must be explictly enabled in software. See
+sections below for operating system-specific instructions on how you
+may be able to do this.
 
 If your machine is not a PC, you don’t have an OPL chip, and you will
 have to use the software OPL.
 
-# Operating System support
+## Operating System support
 
 If you’re certain that you have a sound card with hardware OPL, you
 may need to take extra steps to configure your operating system to
@@ -103,7 +105,7 @@
 the chip directly, which is usually not possible in modern operating
 systems unless you are running as the superuser (root/Administrator).
 
-## Windows 9x
+### Windows 9x
 
 If you’re running Windows 95, 98 or Me, there is no need to configure
 anything. Windows allows direct access to the OPL chip. You can
@@ -112,7 +114,7 @@
 
     OPL_Init: Using driver 'Win32'.
 
-## Windows NT (including 2000, XP and later)
+### Windows NT (including 2000, XP and later)
 
 If you’re running an NT-based system, it is not possible to directly
 access the OPL chip, even when running as Administrator. Fortunately,
@@ -129,8 +131,13 @@
 
     OPL_Init: Using driver 'Win32'.
 
-## Linux
+If you have a C-Media CMI8738, you may need to enable the `FM_EN`
+flag in Windows Device Manager to access hardware OPL output. See
+[this](http://www.vogons.org/viewtopic.php?f=46&t=36445) thread on
+vogons.org for some more information.
 
+### Linux
+
 If you are using a system based on the Linux kernel, you can access
 the OPL chip directly, but you must be running as root. You can
 confirm that hardware OPL is working, by checking for this message on
@@ -145,7 +152,7 @@
     options snd-ymfpci fm_port=0x388
     options snd-cmipci fm_port=0x388
 
-## OpenBSD/NetBSD
+### OpenBSD/NetBSD
 
 You must be running as root to access the hardware OPL directly. You
 can confirm that hardware OPL is working by checking for this message
--- a/src/d_iwad.c
+++ b/src/d_iwad.c
@@ -592,6 +592,7 @@
     free(path);
 }
 
+#ifndef _WIN32
 // Add standard directories where IWADs are located on Unix systems.
 // To respect the freedesktop.org specification we support overriding
 // using standard environment variables. See the XDG Base Directory
@@ -647,6 +648,7 @@
     // XDG_DATA_DIRS mechanism, through which it can be overridden.
     AddIWADPath(env, "/games/doom");
 }
+#endif
 
 //
 // Build a list of IWAD files
--- a/src/doom/g_game.c
+++ b/src/doom/g_game.c
@@ -1542,9 +1542,6 @@
     M_StringCopy(savename, name, sizeof(savename));
     gameaction = ga_loadgame; 
 } 
- 
-#define VERSIONSIZE		16 
-
 
 void G_DoLoadGame (void) 
 { 
--- a/src/doom/p_saveg.c
+++ b/src/doom/p_saveg.c
@@ -33,9 +33,6 @@
 #include "m_misc.h"
 #include "r_state.h"
 
-#define SAVEGAME_EOF 0x1d
-#define VERSIONSIZE 16 
-
 FILE *save_stream;
 int savegamelength;
 boolean savegame_error;
--- a/src/doom/p_saveg.h
+++ b/src/doom/p_saveg.h
@@ -22,6 +22,9 @@
 
 #include <stdio.h>
 
+#define SAVEGAME_EOF 0x1d
+#define VERSIONSIZE 16
+
 // maximum size of a savegame description
 
 #define SAVESTRINGSIZE 24
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -31,6 +31,7 @@
 
 #ifdef _WIN32
 
+#include <string.h>
 #define strcasecmp stricmp
 #define strncasecmp strnicmp
 
--- a/src/heretic/d_main.c
+++ b/src/heretic/d_main.c
@@ -757,6 +757,10 @@
     M_BindMenuControls();
     M_BindMapControls();
 
+#ifdef FEATURE_MULTIPLAYER
+    NET_BindVariables();
+#endif
+
     M_BindIntVariable("mouse_sensitivity",      &mouseSensitivity);
     M_BindIntVariable("sfx_volume",             &snd_MaxVolume);
     M_BindIntVariable("music_volume",           &snd_MusicVolume);
@@ -1032,6 +1036,15 @@
 
         printf("Playing demo %s.\n", file);
     }
+
+    //!
+    // @category demo
+    //
+    // Record or playback a demo without automatically quitting
+    // after either level exit or player respawn.
+    //
+
+    demoextend = M_ParmExists("-demoextend");
 
     if (W_CheckNumForName(DEH_String("E2M1")) == -1)
     {
--- a/src/heretic/d_net.c
+++ b/src/heretic/d_net.c
@@ -115,10 +115,17 @@
     startmap = settings->map;
     startskill = settings->skill;
     // TODO startloadgame = settings->loadgame;
+    lowres_turn = settings->lowres_turn;
     nomonsters = settings->nomonsters;
     respawnparm = settings->respawn_monsters;
     consoleplayer = settings->consoleplayer;
 
+    if (lowres_turn)
+    {
+        printf("NOTE: Turning resolution is reduced; this is probably "
+               "because there is a client recording a Vanilla demo.\n");
+    }
+
     for (i = 0; i < MAXPLAYERS; ++i)
     {
         playeringame[i] = i < settings->num_players;
@@ -142,7 +149,9 @@
     settings->nomonsters = nomonsters;
     settings->respawn_monsters = respawnparm;
     settings->timelimit = 0;
-    settings->lowres_turn = false;
+
+    settings->lowres_turn = M_ParmExists("-record")
+                         && !M_ParmExists("-longtics");
 }
 
 static void InitConnectData(net_connect_data_t *connect_data)
@@ -159,7 +168,10 @@
     connect_data->gamemode = gamemode;
     connect_data->gamemission = heretic;
 
-    connect_data->lowres_turn = false;
+    // Are we recording a demo? Possibly set lowres turn mode
+
+    connect_data->lowres_turn = M_ParmExists("-record")
+                             && !M_ParmExists("-longtics");
 
     // Read checksums of our WAD directory and dehacked information
 
--- a/src/heretic/doomdef.h
+++ b/src/heretic/doomdef.h
@@ -528,7 +528,12 @@
 
 extern boolean demorecording;
 extern boolean demoplayback;
+extern boolean demoextend;      // allow demos to persist through exit/respawn
 extern int skytexture;
+
+// Truncate angleturn in ticcmds to nearest 256.
+// Used when recording Vanilla demos in netgames.
+extern boolean lowres_turn;
 
 extern gamestate_t gamestate;
 extern skill_t gameskill;
--- a/src/heretic/g_game.c
+++ b/src/heretic/g_game.c
@@ -25,6 +25,7 @@
 #include "i_input.h"
 #include "i_timer.h"
 #include "i_system.h"
+#include "m_argv.h"
 #include "m_controls.h"
 #include "m_misc.h"
 #include "m_random.h"
@@ -110,8 +111,12 @@
 
 char demoname[32];
 boolean demorecording;
+boolean longtics;               // specify high resolution turning in demos
+boolean lowres_turn;
+boolean shortticfix;            // calculate lowres turning like doom
 boolean demoplayback;
-byte *demobuffer, *demo_p;
+boolean demoextend;
+byte *demobuffer, *demo_p, *demoend;
 boolean singledemo;             // quit after playing a demo from cmdline
 
 boolean precache = true;        // if true, load all graphics at start
@@ -622,6 +627,32 @@
             BT_SPECIAL | BTS_SAVEGAME | (savegameslot << BTS_SAVESHIFT);
     }
 
+    if (lowres_turn)
+    {
+        if (shortticfix)
+        {
+            static signed short carry = 0;
+            signed short desired_angleturn;
+
+            desired_angleturn = cmd->angleturn + carry;
+
+            // round angleturn to the nearest 256 unit boundary
+            // for recording demos with single byte values for turn
+
+            cmd->angleturn = (desired_angleturn + 128) & 0xff00;
+
+            // Carry forward the error from the reduced resolution to the
+            // next tic, so that successive small movements can accumulate.
+
+            carry = desired_angleturn - cmd->angleturn;
+        }
+        else
+        {
+            // truncate angleturn to the nearest 256 boundary
+            // for recording demos with single byte values for turn
+            cmd->angleturn &= 0xff00;
+        }
+    }
 }
 
 
@@ -648,7 +679,6 @@
 
     P_SetupLevel(gameepisode, gamemap, 0, gameskill);
     displayplayer = consoleplayer;      // view the guy you are playing
-    starttime = I_GetTime();
     gameaction = ga_nothing;
     Z_CheckHeap();
 
@@ -1291,7 +1321,8 @@
 {
     int i;
 
-    if (G_CheckDemoStatus())
+    // quit demo unless -demoextend
+    if (!demoextend && G_CheckDemoStatus())
         return;
     if (!netgame)
         gameaction = ga_loadlevel;      // reload the level from scratch
@@ -1360,7 +1391,9 @@
     static int afterSecret[5] = { 7, 5, 5, 5, 4 };
 
     gameaction = ga_nothing;
-    if (G_CheckDemoStatus())
+
+    // quit demo unless -demoextend
+    if (!demoextend && G_CheckDemoStatus())
     {
         return;
     }
@@ -1620,6 +1653,9 @@
 */
 
 #define DEMOMARKER      0x80
+#define DEMOHEADER_RESPAWN    0x20
+#define DEMOHEADER_LONGTICS   0x10
+#define DEMOHEADER_NOMONSTERS 0x02
 
 void G_ReadDemoTiccmd(ticcmd_t * cmd)
 {
@@ -1630,7 +1666,19 @@
     }
     cmd->forwardmove = ((signed char) *demo_p++);
     cmd->sidemove = ((signed char) *demo_p++);
-    cmd->angleturn = ((unsigned char) *demo_p++) << 8;
+
+    // If this is a longtics demo, read back in higher resolution
+
+    if (longtics)
+    {
+        cmd->angleturn = *demo_p++;
+        cmd->angleturn |= (*demo_p++) << 8;
+    }
+    else
+    {
+        cmd->angleturn = ((unsigned char) *demo_p++) << 8;
+    }
+
     cmd->buttons = (unsigned char) *demo_p++;
     cmd->lookfly = (unsigned char) *demo_p++;
     cmd->arti = (unsigned char) *demo_p++;
@@ -1638,15 +1686,42 @@
 
 void G_WriteDemoTiccmd(ticcmd_t * cmd)
 {
-    if (gamekeydown['q'])       // press q to end demo recording
+    byte *demo_start;
+
+    if (gamekeydown[key_demo_quit]) // press to end demo recording
         G_CheckDemoStatus();
+
+    demo_start = demo_p;
+
     *demo_p++ = cmd->forwardmove;
     *demo_p++ = cmd->sidemove;
-    *demo_p++ = cmd->angleturn >> 8;
+
+    // If this is a longtics demo, record in higher resolution
+
+    if (longtics)
+    {
+        *demo_p++ = (cmd->angleturn & 0xff);
+        *demo_p++ = (cmd->angleturn >> 8) & 0xff;
+    }
+    else
+    {
+        *demo_p++ = cmd->angleturn >> 8;
+    }
+
     *demo_p++ = cmd->buttons;
     *demo_p++ = cmd->lookfly;
     *demo_p++ = cmd->arti;
-    demo_p -= 6;
+
+    // reset demo pointer back
+    demo_p = demo_start;
+
+    if (demo_p > demoend - 16)
+    {
+        // no more space
+        G_CheckDemoStatus();
+        return;
+    }
+
     G_ReadDemoTiccmd(cmd);      // make SURE it is exactly the same
 }
 
@@ -1664,17 +1739,75 @@
                   char *name)
 {
     int i;
+    int maxsize;
 
+    //!
+    // @category demo
+    //
+    // Record or playback a demo with high resolution turning.
+    //
+
+    longtics = M_ParmExists("-longtics");
+
+    // If not recording a longtics demo, record in low res
+
+    lowres_turn = !longtics;
+
+    //!
+    // @category demo
+    //
+    // Smooth out low resolution turning when recording a demo.
+    //
+
+    shortticfix = M_ParmExists("-shortticfix");
+
     G_InitNew(skill, episode, map);
     usergame = false;
     M_StringCopy(demoname, name, sizeof(demoname));
     M_StringConcat(demoname, ".lmp", sizeof(demoname));
-    demobuffer = demo_p = Z_Malloc(0x20000, PU_STATIC, NULL);
+    maxsize = 0x20000;
+
+    //!
+    // @arg <size>
+    // @category demo
+    // @vanilla
+    //
+    // Specify the demo buffer size (KiB)
+    //
+
+    i = M_CheckParmWithArgs("-maxdemo", 1);
+    if (i)
+        maxsize = atoi(myargv[i + 1]) * 1024;
+    demobuffer = Z_Malloc(maxsize, PU_STATIC, NULL);
+    demoend = demobuffer + maxsize;
+
+    demo_p = demobuffer;
     *demo_p++ = skill;
     *demo_p++ = episode;
     *demo_p++ = map;
 
-    for (i = 0; i < MAXPLAYERS; i++)
+    // Write special parameter bits onto player one byte.
+    // This aligns with vvHeretic demo usage:
+    //   0x20 = -respawn
+    //   0x10 = -longtics
+    //   0x02 = -nomonsters
+
+    *demo_p = 1; // assume player one exists
+    if (respawnparm)
+    {
+        *demo_p |= DEMOHEADER_RESPAWN;
+    }
+    if (longtics)
+    {
+        *demo_p |= DEMOHEADER_LONGTICS;
+    }
+    if (nomonsters)
+    {
+        *demo_p |= DEMOHEADER_NOMONSTERS;
+    }
+    demo_p++;
+
+    for (i = 1; i < MAXPLAYERS; i++)
         *demo_p++ = playeringame[i];
 
     demorecording = true;
@@ -1708,8 +1841,13 @@
     episode = *demo_p++;
     map = *demo_p++;
 
+    // Read special parameter bits: see G_RecordDemo() for details.
+    respawnparm = (*demo_p & DEMOHEADER_RESPAWN) != 0;
+    longtics    = (*demo_p & DEMOHEADER_LONGTICS) != 0;
+    nomonsters  = (*demo_p & DEMOHEADER_NOMONSTERS) != 0;
+
     for (i = 0; i < MAXPLAYERS; i++)
-        playeringame[i] = *demo_p++;
+        playeringame[i] = (*demo_p++) != 0;
 
     precache = false;           // don't spend a lot of time in loadlevel
     G_InitNew(skill, episode, map);
@@ -1737,12 +1875,19 @@
     episode = *demo_p++;
     map = *demo_p++;
 
+    // Read special parameter bits: see G_RecordDemo() for details.
+    respawnparm = (*demo_p & DEMOHEADER_RESPAWN) != 0;
+    longtics    = (*demo_p & DEMOHEADER_LONGTICS) != 0;
+    nomonsters  = (*demo_p & DEMOHEADER_NOMONSTERS) != 0;
+
     for (i = 0; i < MAXPLAYERS; i++)
     {
-        playeringame[i] = *demo_p++;
+        playeringame[i] = (*demo_p++) != 0;
     }
 
     G_InitNew(skill, episode, map);
+    starttime = I_GetTime();
+
     usergame = false;
     demoplayback = true;
     timingdemo = true;
--- a/src/heretic/r_bsp.c
+++ b/src/heretic/r_bsp.c
@@ -16,6 +16,7 @@
 // R_bsp.c
 
 #include "doomdef.h"
+#include "i_system.h"
 #include "m_bbox.h"
 #include "i_system.h"
 #include "r_local.h"
--- a/src/hexen/d_net.c
+++ b/src/hexen/d_net.c
@@ -116,10 +116,17 @@
     startmap = settings->map;
     startskill = settings->skill;
     // TODO startloadgame = settings->loadgame;
+    lowres_turn = settings->lowres_turn;
     nomonsters = settings->nomonsters;
     respawnparm = settings->respawn_monsters;
     consoleplayer = settings->consoleplayer;
 
+    if (lowres_turn)
+    {
+        printf("NOTE: Turning resolution is reduced; this is probably "
+               "because there is a client recording a Vanilla demo.\n");
+    }
+
     for (i=0; i<maxplayers; ++i)
     {
         playeringame[i] = i < settings->num_players;
@@ -154,7 +161,9 @@
     settings->nomonsters = nomonsters;
     settings->respawn_monsters = respawnparm;
     settings->timelimit = 0;
-    settings->lowres_turn = false;
+
+    settings->lowres_turn = M_ParmExists("-record")
+                         && !M_ParmExists("-longtics");
 }
 
 static void InitConnectData(net_connect_data_t *connect_data)
@@ -170,7 +179,11 @@
     connect_data->gamemode = gamemode;
     connect_data->gamemission = hexen;
 
-    connect_data->lowres_turn = false;
+    // Are we recording a demo? Possibly set lowres turn mode
+
+    connect_data->lowres_turn = M_ParmExists("-record")
+                             && !M_ParmExists("-longtics");
+
     connect_data->drone = false;
     connect_data->max_players = maxplayers;
 
--- a/src/hexen/g_game.c
+++ b/src/hexen/g_game.c
@@ -24,6 +24,7 @@
 #include "i_video.h"
 #include "i_system.h"
 #include "i_timer.h"
+#include "m_argv.h"
 #include "m_controls.h"
 #include "m_misc.h"
 #include "p_local.h"
@@ -93,8 +94,12 @@
 
 char demoname[32];
 boolean demorecording;
+boolean longtics;               // specify high resolution turning in demos
+boolean lowres_turn;
+boolean shortticfix;            // calculate lowres turning like doom
 boolean demoplayback;
-byte *demobuffer, *demo_p;
+boolean demoextend;
+byte *demobuffer, *demo_p, *demoend;
 boolean singledemo;             // quit after playing a demo from cmdline
 
 boolean precache = true;        // if true, load all graphics at start
@@ -622,6 +627,33 @@
         cmd->buttons =
             BT_SPECIAL | BTS_SAVEGAME | (savegameslot << BTS_SAVESHIFT);
     }
+
+    if (lowres_turn)
+    {
+        if (shortticfix)
+        {
+            static signed short carry = 0;
+            signed short desired_angleturn;
+
+            desired_angleturn = cmd->angleturn + carry;
+
+            // round angleturn to the nearest 256 unit boundary
+            // for recording demos with single byte values for turn
+
+            cmd->angleturn = (desired_angleturn + 128) & 0xff00;
+
+            // Carry forward the error from the reduced resolution to the
+            // next tic, so that successive small movements can accumulate.
+
+            carry = desired_angleturn - cmd->angleturn;
+        }
+        else
+        {
+            // truncate angleturn to the nearest 256 boundary
+            // for recording demos with single byte values for turn
+            cmd->angleturn &= 0xff00;
+        }
+    }
 }
 
 
@@ -649,7 +681,6 @@
     SN_StopAllSequences();
     P_SetupLevel(gameepisode, gamemap, 0, gameskill);
     displayplayer = consoleplayer;      // view the guy you are playing   
-    starttime = I_GetTime();
     gameaction = ga_nothing;
     Z_CheckHeap();
 
@@ -1293,7 +1324,8 @@
     boolean foundSpot;
     int bestWeapon;
 
-    if (G_CheckDemoStatus())
+    // quit demo unless -demoextend
+    if (!demoextend && G_CheckDemoStatus())
     {
         return;
     }
@@ -1488,7 +1520,9 @@
     int i;
 
     gameaction = ga_nothing;
-    if (G_CheckDemoStatus())
+
+    // quit demo unless -demoextend
+    if (!demoextend && G_CheckDemoStatus())
     {
         return;
     }
@@ -1739,10 +1773,16 @@
     }
 
     // Set up a bunch of globals
-    usergame = true;            // will be set false if a demo
+    if (!demoextend)
+    {
+        // This prevents map-loading from interrupting a demo.
+        // demoextend is set back to false only if starting a new game or
+        // loading a saved one from the menu, and only during playback.
+        demorecording = false;
+        demoplayback = false;
+        usergame = true;            // will be set false if a demo
+    }
     paused = false;
-    demorecording = false;
-    demoplayback = false;
     viewactive = true;
     gameepisode = episode;
     gamemap = map;
@@ -1772,6 +1812,9 @@
 */
 
 #define DEMOMARKER      0x80
+#define DEMOHEADER_RESPAWN    0x20
+#define DEMOHEADER_LONGTICS   0x10
+#define DEMOHEADER_NOMONSTERS 0x02
 
 void G_ReadDemoTiccmd(ticcmd_t * cmd)
 {
@@ -1782,7 +1825,19 @@
     }
     cmd->forwardmove = ((signed char) *demo_p++);
     cmd->sidemove = ((signed char) *demo_p++);
-    cmd->angleturn = ((unsigned char) *demo_p++) << 8;
+
+    // If this is a longtics demo, read back in higher resolution
+
+    if (longtics)
+    {
+        cmd->angleturn = *demo_p++;
+        cmd->angleturn |= (*demo_p++) << 8;
+    }
+    else
+    {
+        cmd->angleturn = ((unsigned char) *demo_p++) << 8;
+    }
+
     cmd->buttons = (unsigned char) *demo_p++;
     cmd->lookfly = (unsigned char) *demo_p++;
     cmd->arti = (unsigned char) *demo_p++;
@@ -1790,15 +1845,42 @@
 
 void G_WriteDemoTiccmd(ticcmd_t * cmd)
 {
-    if (gamekeydown['q'])       // press q to end demo recording
+    byte *demo_start;
+
+    if (gamekeydown[key_demo_quit]) // press to end demo recording
         G_CheckDemoStatus();
+
+    demo_start = demo_p;
+
     *demo_p++ = cmd->forwardmove;
     *demo_p++ = cmd->sidemove;
-    *demo_p++ = cmd->angleturn >> 8;
+
+    // If this is a longtics demo, record in higher resolution
+
+    if (longtics)
+    {
+        *demo_p++ = (cmd->angleturn & 0xff);
+        *demo_p++ = (cmd->angleturn >> 8) & 0xff;
+    }
+    else
+    {
+        *demo_p++ = cmd->angleturn >> 8;
+    }
+
     *demo_p++ = cmd->buttons;
     *demo_p++ = cmd->lookfly;
     *demo_p++ = cmd->arti;
-    demo_p -= 6;
+
+    // reset demo pointer back
+    demo_p = demo_start;
+
+    if (demo_p > demoend - 16)
+    {
+        // no more space
+        G_CheckDemoStatus();
+        return;
+    }
+
     G_ReadDemoTiccmd(cmd);      // make SURE it is exactly the same
 }
 
@@ -1816,21 +1898,82 @@
                   char *name)
 {
     int i;
+    int maxsize;
 
+    //!
+    // @category demo
+    //
+    // Record or playback a demo with high resolution turning.
+    //
+
+    longtics = M_ParmExists("-longtics");
+
+    // If not recording a longtics demo, record in low res
+
+    lowres_turn = !longtics;
+
+    //!
+    // @category demo
+    //
+    // Smooth out low resolution turning when recording a demo.
+    //
+
+    shortticfix = M_ParmExists("-shortticfix");
+
     G_InitNew(skill, episode, map);
     usergame = false;
     M_StringCopy(demoname, name, sizeof(demoname));
     M_StringConcat(demoname, ".lmp", sizeof(demoname));
-    demobuffer = demo_p = Z_Malloc(0x20000, PU_STATIC, NULL);
+    maxsize = 0x20000;
+
+    //!
+    // @arg <size>
+    // @category demo
+    // @vanilla
+    //
+    // Specify the demo buffer size (KiB)
+    //
+
+    i = M_CheckParmWithArgs("-maxdemo", 1);
+    if (i)
+        maxsize = atoi(myargv[i + 1]) * 1024;
+    demobuffer = Z_Malloc(maxsize, PU_STATIC, NULL);
+    demoend = demobuffer + maxsize;
+
+    demo_p = demobuffer;
     *demo_p++ = skill;
     *demo_p++ = episode;
     *demo_p++ = map;
 
-    for (i = 0; i < maxplayers; i++)
+    // Write special parameter bits onto player one byte.
+    // This aligns with vvHeretic demo usage. Hexen demo support has no
+    // precedent here so consistency with another game is chosen:
+    //   0x20 = -respawn
+    //   0x10 = -longtics
+    //   0x02 = -nomonsters
+
+    *demo_p = 1; // assume player one exists
+    if (respawnparm)
     {
+        *demo_p |= DEMOHEADER_RESPAWN;
+    }
+    if (longtics)
+    {
+        *demo_p |= DEMOHEADER_LONGTICS;
+    }
+    if (nomonsters)
+    {
+        *demo_p |= DEMOHEADER_NOMONSTERS;
+    }
+    demo_p++;
+    *demo_p++ = PlayerClass[0];
+
+    for (i = 1; i < maxplayers; i++)
+    {
         *demo_p++ = playeringame[i];
         *demo_p++ = PlayerClass[i];
     }
+
     demorecording = true;
 }
 
@@ -1862,9 +2005,14 @@
     episode = *demo_p++;
     map = *demo_p++;
 
+    // Read special parameter bits: see G_RecordDemo() for details.
+    respawnparm = (*demo_p & DEMOHEADER_RESPAWN) != 0;
+    longtics    = (*demo_p & DEMOHEADER_LONGTICS) != 0;
+    nomonsters  = (*demo_p & DEMOHEADER_NOMONSTERS) != 0;
+
     for (i = 0; i < maxplayers; i++)
     {
-        playeringame[i] = *demo_p++;
+        playeringame[i] = (*demo_p++) != 0;
         PlayerClass[i] = *demo_p++;
     }
 
@@ -1897,13 +2045,20 @@
     episode = *demo_p++;
     map = *demo_p++;
 
+    // Read special parameter bits: see G_RecordDemo() for details.
+    respawnparm = (*demo_p & DEMOHEADER_RESPAWN) != 0;
+    longtics    = (*demo_p & DEMOHEADER_LONGTICS) != 0;
+    nomonsters  = (*demo_p & DEMOHEADER_NOMONSTERS) != 0;
+
     for (i = 0; i < maxplayers; i++)
     {
-        playeringame[i] = *demo_p++;
+        playeringame[i] = (*demo_p++) != 0;
         PlayerClass[i] = *demo_p++;
     }
 
     G_InitNew(skill, episode, map);
+    starttime = I_GetTime();
+
     usergame = false;
     demoplayback = true;
     timingdemo = true;
--- a/src/hexen/h2_main.c
+++ b/src/hexen/h2_main.c
@@ -152,6 +152,10 @@
     key_multi_msgplayer[6] = CT_KEY_PLAYER7;
     key_multi_msgplayer[7] = CT_KEY_PLAYER8;
 
+#ifdef FEATURE_MULTIPLAYER
+    NET_BindVariables();
+#endif
+
     M_BindIntVariable("graphical_startup",      &graphical_startup);
     M_BindIntVariable("mouse_sensitivity",      &mouseSensitivity);
     M_BindIntVariable("sfx_volume",             &snd_MaxVolume);
@@ -677,6 +681,15 @@
 
         ST_Message("Playing demo %s.\n", myargv[p+1]);
     }
+
+    //!
+    // @category demo
+    //
+    // Record or playback a demo without automatically quitting
+    // after either level exit or player respawn.
+    //
+
+    demoextend = M_ParmExists("-demoextend");
 
     if (M_ParmExists("-testcontrols"))
     {
--- a/src/hexen/h2def.h
+++ b/src/hexen/h2def.h
@@ -633,7 +633,12 @@
 extern boolean DebugSound;      // debug flag for displaying sound info
 
 extern boolean demoplayback;
+extern boolean demoextend;      // allow demos to persist through exit/respawn
 extern int maxzone;             // Maximum chunk allocated for zone heap
+
+// Truncate angleturn in ticcmds to nearest 256.
+// Used when recording Vanilla demos in netgames.
+extern boolean lowres_turn;
 
 extern int Sky1Texture;
 extern int Sky2Texture;
--- a/src/hexen/mn_menu.c
+++ b/src/hexen/mn_menu.c
@@ -131,6 +131,7 @@
 int InfoType;
 int messageson = true;
 boolean mn_SuicideConsole;
+boolean demoextend; // from h2def.h
 
 // PRIVATE DATA DEFINITIONS ------------------------------------------------
 
@@ -896,6 +897,11 @@
 
 static void SCLoadGame(int option)
 {
+    if (demoplayback)
+    {
+        // deactivate playback, return control to player
+        demoextend = false;
+    }
     if (!SlotStatus[option])
     {                           // Don't try to load from an empty slot
         return;
@@ -1013,6 +1019,12 @@
 
 static void SCSkill(int option)
 {
+    if (demoplayback)
+    {
+        // deactivate playback, return control to player
+        demoextend = false;
+    }
+
     PlayerClass[consoleplayer] = MenuPClass;
     G_DeferredNewGame(option);
     SB_SetClassData();
--- a/src/hexen/p_acs.c
+++ b/src/hexen/p_acs.c
@@ -56,6 +56,8 @@
 
 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
 
+void CheckACSPresent(int number);
+
 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
 
 static void StartOpenACS(int number, int infoIndex, int *address);
@@ -783,6 +785,23 @@
         }
     }
     return -1;
+}
+
+//==========================================================================
+//
+// CheckACSPresent
+//
+// Placing Korax in a PWAD without extra steps will result in a crash in
+// Vanilla because the relevant ACS scripts are not initialised
+//
+//==========================================================================
+
+void CheckACSPresent(int number)
+{
+    if (GetACSIndex(number) == -1)
+    {
+        I_Error("Required ACS script %d not initialised", number);
+    }
 }
 
 //==========================================================================
--- a/src/hexen/p_enemy.c
+++ b/src/hexen/p_enemy.c
@@ -4970,6 +4970,7 @@
             P_Teleport(actor, spot->x, spot->y, spot->angle, true);
         }
 
+        CheckACSPresent(249);
         P_StartACS(249, 0, args, actor, NULL, 0);
         actor->special2.i = 1;    // Don't run again
 
@@ -5047,6 +5048,7 @@
     if (mo)
         KSpiritInit(mo, actor);
 
+    CheckACSPresent(255);
     P_StartACS(255, 0, args, actor, NULL, 0);   // Death script
 }
 
@@ -5164,18 +5166,23 @@
     switch (P_Random() % numcommands)
     {
         case 0:
+            CheckACSPresent(250);
             P_StartACS(250, 0, args, actor, NULL, 0);
             break;
         case 1:
+            CheckACSPresent(251);
             P_StartACS(251, 0, args, actor, NULL, 0);
             break;
         case 2:
+            CheckACSPresent(252);
             P_StartACS(252, 0, args, actor, NULL, 0);
             break;
         case 3:
+            CheckACSPresent(253);
             P_StartACS(253, 0, args, actor, NULL, 0);
             break;
         case 4:
+            CheckACSPresent(254);
             P_StartACS(254, 0, args, actor, NULL, 0);
             break;
     }
--- a/src/hexen/p_spec.h
+++ b/src/hexen/p_spec.h
@@ -546,6 +546,7 @@
 void P_PolyobjFinished(int po);
 void P_ACSInitNewGame(void);
 void P_CheckACSStore(void);
+void CheckACSPresent(int number);
 
 extern int ACScriptCount;
 extern byte *ActionCodeBase;
--- a/src/i_sdlsound.c
+++ b/src/i_sdlsound.c
@@ -402,6 +402,7 @@
                                    int length)
 {
     SRC_DATA src_data;
+    float *data_in;
     uint32_t i, abuf_index=0, clipped=0;
 //    uint32_t alen;
     int retn;
@@ -410,7 +411,8 @@
     Mix_Chunk *chunk;
 
     src_data.input_frames = length;
-    src_data.data_in = malloc(length * sizeof(float));
+    data_in = malloc(length * sizeof(float));
+    src_data.data_in = data_in;
     src_data.src_ratio = (double)mixer_freq / samplerate;
 
     // We include some extra space here in case of rounding-up.
@@ -426,7 +428,7 @@
         // Unclear whether 128 should be interpreted as "zero" or whether a
         // symmetrical range should be assumed.  The following assumes a
         // symmetrical range.
-        src_data.data_in[i] = data[i] / 127.5 - 1;
+        data_in[i] = data[i] / 127.5 - 1;
     }
 
     // Do the sound conversion
@@ -495,7 +497,7 @@
         expanded[abuf_index++] = cvtval_i;
     }
 
-    free(src_data.data_in);
+    free(data_in);
     free(src_data.data_out);
 
     if (clipped > 0)