ref: c358f7eb00154e58ae3c88d1cc7d4b7be856ccfa
parent: 228ea81c511dc2600def5b66a20b7e46f9c1da43
parent: 42e8b05aa46936ada3de4ee68c6dc005fccadfb4
author: Simon Howard <[email protected]>
date: Fri Oct 21 07:13:12 EDT 2016
Merge pull request #784 from CapnClever/raven-demo-expansion Additional Heretic/Hexen demo support
--- 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/src/heretic/d_main.c
+++ b/src/heretic/d_main.c
@@ -1035,6 +1035,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)
{
gamemode = shareware;
--- 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
@@ -24,6 +24,7 @@
#include "deh_str.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"
@@ -109,8 +110,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
@@ -621,6 +626,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;
+ }
+ }
}
@@ -647,7 +678,6 @@
P_SetupLevel(gameepisode, gamemap, 0, gameskill);
displayplayer = consoleplayer; // view the guy you are playing
- starttime = I_GetTime();
gameaction = ga_nothing;
Z_CheckHeap();
@@ -1290,7 +1320,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
@@ -1359,7 +1390,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;
}
@@ -1619,6 +1652,9 @@
*/
#define DEMOMARKER 0x80
+#define DEMOHEADER_RESPAWN 0x20
+#define DEMOHEADER_LONGTICS 0x10
+#define DEMOHEADER_NOMONSTERS 0x02
void G_ReadDemoTiccmd(ticcmd_t * cmd)
{
@@ -1629,7 +1665,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++;
@@ -1637,15 +1685,42 @@
void G_WriteDemoTiccmd(ticcmd_t * cmd)
{
+ 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
}
@@ -1663,17 +1738,76 @@
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++;
+ demo_p++;
+
+ for (i = 1; i < MAXPLAYERS; i++)
*demo_p++ = playeringame[i];
demorecording = true;
@@ -1707,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);
@@ -1736,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/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
@@ -23,6 +23,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"
@@ -92,8 +93,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
@@ -621,6 +626,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;
+ }
+ }
}
@@ -648,7 +680,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();
@@ -1292,7 +1323,8 @@
boolean foundSpot;
int bestWeapon;
- if (G_CheckDemoStatus())
+ // quit demo unless -demoextend
+ if (!demoextend && G_CheckDemoStatus())
{
return;
}
@@ -1487,7 +1519,9 @@
int i;
gameaction = ga_nothing;
- if (G_CheckDemoStatus())
+
+ // quit demo unless -demoextend
+ if (!demoextend && G_CheckDemoStatus())
{
return;
}
@@ -1738,10 +1772,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;
@@ -1771,6 +1811,9 @@
*/
#define DEMOMARKER 0x80
+#define DEMOHEADER_RESPAWN 0x20
+#define DEMOHEADER_LONGTICS 0x10
+#define DEMOHEADER_NOMONSTERS 0x02
void G_ReadDemoTiccmd(ticcmd_t * cmd)
{
@@ -1781,7 +1824,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++;
@@ -1789,15 +1844,42 @@
void G_WriteDemoTiccmd(ticcmd_t * cmd)
{
+ 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
}
@@ -1815,21 +1897,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;
}
@@ -1861,9 +2004,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++;
}
@@ -1896,13 +2044,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
@@ -680,6 +680,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"))
{
autostart = true;
--- 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
@@ -130,6 +130,7 @@
int InfoType;
int messageson = true;
boolean mn_SuicideConsole;
+boolean demoextend; // from h2def.h
// PRIVATE DATA DEFINITIONS ------------------------------------------------
@@ -895,6 +896,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;
@@ -1003,6 +1009,12 @@
static void SCSkill(int option)
{
+ if (demoplayback)
+ {
+ // deactivate playback, return control to player
+ demoextend = false;
+ }
+
PlayerClass[consoleplayer] = MenuPClass;
G_DeferredNewGame(option);
SB_SetClassData();