From 238334003171816a956099d35380b287e52c1c03 Mon Sep 17 00:00:00 2001
From: Bill Currie <bill@taniwha.org>
Date: Wed, 2 Oct 2002 21:56:45 +0000
Subject: [PATCH] mvd playback support. seems to work (get some weird entities
 hanging around but I suspect that's the mod used in the demo I was testing
 with), but probably needs some cleanup.

---
 qw/include/cl_cam.h   |   3 ++
 qw/include/cl_ents.h  |   3 +-
 qw/include/cl_parse.h |   1 +
 qw/include/client.h   |   8 +++
 qw/include/protocol.h |  12 +++++
 qw/source/cl_cam.c    |  42 ++++++++++++++-
 qw/source/cl_demo.c   | 123 ++++++++++++++++++++++++++++++++++++++----
 qw/source/cl_ents.c   | 109 ++++++++++++++++++++++++++++++++++---
 qw/source/cl_input.c  |   7 ++-
 qw/source/cl_main.c   |  30 +++++++++--
 qw/source/cl_parse.c  |  50 ++++++++++++++---
 11 files changed, 355 insertions(+), 33 deletions(-)

diff --git a/qw/include/cl_cam.h b/qw/include/cl_cam.h
index d162bbd9d..2d9c6f1ae 100644
--- a/qw/include/cl_cam.h
+++ b/qw/include/cl_cam.h
@@ -41,8 +41,11 @@
 
 extern	int	autocam;
 extern	int	spec_track; // player# of who we are tracking
+extern	int ideal_track;
 extern	struct cvar_s	*chase_active;
 
+void Cam_Lock (int playernum);
+int Cam_TrackNum (void);
 qboolean Cam_DrawViewModel(void);
 qboolean Cam_DrawPlayer(int playernum);
 void Cam_Track(usercmd_t *cmd);
diff --git a/qw/include/cl_ents.h b/qw/include/cl_ents.h
index ec1196f90..8fbfcbff7 100644
--- a/qw/include/cl_ents.h
+++ b/qw/include/cl_ents.h
@@ -32,10 +32,11 @@
 #include "QF/qtypes.h"
 
 void CL_SetSolidPlayers (int playernum);
+void CL_ClearPredict (void);
 void CL_SetUpPlayerPrediction(qboolean dopred);
 void CL_EmitEntities (void);
 void CL_ClearProjectiles (void);
-void CL_ParseProjectiles (void);
+void CL_ParseProjectiles (qboolean nail2);
 void CL_ParsePacketEntities (qboolean delta);
 void CL_SetSolidEntities (void);
 void CL_ParsePlayerinfo (void);
diff --git a/qw/include/cl_parse.h b/qw/include/cl_parse.h
index c4be728ea..5ab36598d 100644
--- a/qw/include/cl_parse.h
+++ b/qw/include/cl_parse.h
@@ -50,6 +50,7 @@ extern int	cl_gib3index;
 
 int CL_CalcNet (void);
 void CL_ParseServerMessage (void);
+void CL_ParseClientdata (void);
 void CL_NewTranslation (int slot, struct skin_s *skin);
 qboolean	CL_CheckOrDownloadFile (const char *filename);
 qboolean CL_IsUploading(void);
diff --git a/qw/include/client.h b/qw/include/client.h
index 756f3e0df..3e60cc4bf 100644
--- a/qw/include/client.h
+++ b/qw/include/client.h
@@ -102,6 +102,8 @@ typedef struct player_info_s
 	byte	translations[4*VID_GRADES*256];	// space for colormap32
 	int		translationcolor[256];
 	struct skin_s	*skin;
+	int		stats[MAX_CL_STATS];	// health, etc
+	int     prevcount;
 } player_info_t;
 
 
@@ -178,6 +180,12 @@ typedef struct
 // entering a map (and clearing client_state_t)
 	qboolean	demorecording;
 	qboolean	demoplayback;
+	qboolean	demoplayback2;
+	qboolean	findtrack;
+	int			lastto;
+	int			lasttype;
+	int			prevtime;
+	double		basetime;
 	qboolean	timedemo;
 	QFile		*demofile;
 	float		td_lastframe;		// to meter out one message a frame
diff --git a/qw/include/protocol.h b/qw/include/protocol.h
index 93bf46f88..da4c44d2c 100644
--- a/qw/include/protocol.h
+++ b/qw/include/protocol.h
@@ -118,6 +118,7 @@
 #define svc_setinfo			51		// setinfo on a client
 #define svc_serverinfo		52		// serverinfo
 #define svc_updatepl		53		// [byte] [byte]
+#define svc_nails2          54      // FIXME: from qwex. for interpolation, stores edict num
 
 // client to server ===========================================================
 
@@ -130,6 +131,17 @@
 #define clc_tmove		6		// teleport request, spectator only
 #define clc_upload		7		// teleport request, spectator only
 
+// demo recording
+
+#define dem_cmd         0
+#define dem_read        1
+#define dem_set         2
+#define dem_multiple    3
+#define dem_single      4
+#define dem_stats       5
+#define dem_all         6
+
+
 // ==============================================
 
 // playerinfo flags from server
diff --git a/qw/source/cl_cam.c b/qw/source/cl_cam.c
index 4f16ed674..b99f6ee84 100644
--- a/qw/source/cl_cam.c
+++ b/qw/source/cl_cam.c
@@ -93,6 +93,8 @@ qboolean    cam_forceview;
 vec3_t      cam_viewangles;
 
 int         spec_track = 0;				// player# of who we are tracking
+int         ideal_track = 0;
+float       last_lock = 0;
 int         autocam = CAM_NONE;
 
 
@@ -158,6 +160,14 @@ Cam_DrawPlayer (int playernum)
 	return false;
 }
 
+int
+Cam_TrackNum (void)
+{
+	if (!autocam)
+		return -1;
+	return spec_track;
+}
+
 void
 Cam_Unlock (void)
 {
@@ -174,11 +184,16 @@ void
 Cam_Lock (int playernum)
 {
 	char		st[40];
-
+printf ("Cam_Lock: %d\n", playernum);
 	snprintf (st, sizeof (st), "ptrack %i", playernum);
+	if (cls.demoplayback2) {
+		memcpy(cl.stats, cl.players[playernum].stats, sizeof (cl.stats));
+	}
+
 	MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
 	MSG_WriteString (&cls.netchan.message, st);
 	spec_track = playernum;
+	last_lock = realtime;
 	cam_forceview = true;
 	locked = false;
 	Sbar_Changed ();
@@ -355,8 +370,10 @@ Cam_CheckHighTarget (void)
 		}
 	}
 	if (j >= 0) {
-		if (!locked || cl.players[j].frags > cl.players[spec_track].frags)
+		if (!locked || cl.players[j].frags > cl.players[spec_track].frags) {
 			Cam_Lock (j);
+			ideal_track = spec_track;
+		}
 	} else
 		Cam_Unlock ();
 }
@@ -394,6 +411,23 @@ Cam_Track (usercmd_t *cmd)
 	}
 
 	frame = &cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK];
+	if (autocam && cls.demoplayback2 && 0) {
+		if (ideal_track != spec_track && realtime - last_lock > 1
+			&& frame->playerstate[ideal_track].messagenum == cl.parsecount)
+			Cam_Lock (ideal_track);
+
+		if (frame->playerstate[spec_track].messagenum != cl.parsecount) {
+			int         i;
+
+			for (i = 0; i < MAX_CLIENTS; i++) {
+				if (frame->playerstate[i].messagenum == cl.parsecount)
+					break;
+			}
+			if (i < MAX_CLIENTS)
+				Cam_Lock (i);
+		}
+	}
+
 	player = frame->playerstate + spec_track;
 	self = frame->playerstate + cl.playernum;
 
@@ -594,6 +628,8 @@ Cam_FinishMove (usercmd_t *cmd)
 		s = &cl.players[i];
 		if (s->name[0] && !s->spectator) {
 			Cam_Lock (i);
+			Con_Printf("tracking %s\n", s->name);
+			ideal_track = i;
 			return;
 		}
 		i = (i + 1) % MAX_CLIENTS;
@@ -603,6 +639,7 @@ Cam_FinishMove (usercmd_t *cmd)
 	s = &cl.players[i];
 	if (s->name[0] && !s->spectator) {
 		Cam_Lock (i);
+		ideal_track = i;
 		return;
 	}
 	Con_Printf ("No target found ...\n");
@@ -614,6 +651,7 @@ Cam_Reset (void)
 {
 	autocam = CAM_NONE;
 	spec_track = 0;
+	ideal_track = 0;
 }
 
 void
diff --git a/qw/source/cl_demo.c b/qw/source/cl_demo.c
index 6f627d8ad..7526b3e9e 100644
--- a/qw/source/cl_demo.c
+++ b/qw/source/cl_demo.c
@@ -51,6 +51,8 @@ static const char rcsid[] =
 #include "QF/sys.h"
 #include "QF/va.h"
 
+#include "cl_cam.h"
+#include "cl_ents.h"
 #include "cl_main.h"
 #include "client.h"
 #include "compat.h"
@@ -66,6 +68,7 @@ typedef struct {
 int     cl_timeframes_isactive;
 int     cl_timeframes_index;
 int     demotime_cached;
+float   nextdemotime;
 char    demoname[1024];
 double *cl_timeframes_array;
 #define CL_TIMEFRAMES_ARRAYBLOCK 4096
@@ -108,16 +111,13 @@ CL_StopPlayback (void)
 	cls.demofile = NULL;
 	CL_SetState (ca_disconnected);
 	cls.demoplayback = 0;
+	cls.demoplayback2 = 0;
 	demotime_cached = 0;
 
 	if (cls.timedemo)
 		CL_FinishTimeDemo ();
 }
 
-#define dem_cmd		0
-#define dem_read	1
-#define dem_set		2
-
 /*
 	CL_WriteDemoCmd
 
@@ -184,22 +184,51 @@ CL_WriteDemoMessage (sizebuf_t *msg)
 	Qflush (cls.demofile);
 }
 
+#if 0
+static const char *dem_names[] = {
+	"dem_cmd",
+	"dem_read",
+	"dem_set",
+	"dem_multiple",
+	"dem_single",
+	"dem_stats",
+	"dem_all",
+	"dem_invalid",
+};
+#endif
+
 qboolean
 CL_GetDemoMessage (void)
 {
-	byte		c;
+	byte		c, newtime;
 	float		demotime, f;
 	static float cached_demotime;
-	int			r, i, j;
+	static byte cached_newtime;
+	int			r, i, j, tracknum;
 	usercmd_t  *pcmd;
 
+	if (!cls.demoplayback2)
+		nextdemotime = realtime;
+	if (realtime + 1.0 < nextdemotime)
+		realtime = nextdemotime - 1.0;
+
+nextdemomessage:
 	// read the time from the packet
+	newtime = 0;
 	if (demotime_cached) {
 		demotime = cached_demotime;
+		newtime = cached_newtime;
 		demotime_cached = 0;
 	} else {
-		Qread (cls.demofile, &demotime, sizeof (demotime));
-		demotime = LittleFloat (demotime);
+		if (cls.demoplayback2) {
+			Qread (cls.demofile, &newtime, sizeof (newtime));
+			demotime = cls.basetime + (cls.prevtime + newtime) * 0.001;
+		} else {
+			Qread (cls.demofile, &demotime, sizeof (demotime));
+			demotime = LittleFloat (demotime);
+			if (!nextdemotime)
+				realtime = nextdemotime = demotime;
+		}
 	}
 
 	// decide if it is time to grab the next message        
@@ -211,6 +240,7 @@ CL_GetDemoMessage (void)
 			// rewind back to time
 			demotime_cached = 1;
 			cached_demotime = demotime;
+			cached_newtime = newtime;
 			return 0;					// already read this frame's message
 		}
 		if (!cls.td_starttime && cls.state == ca_active) {
@@ -220,29 +250,44 @@ CL_GetDemoMessage (void)
 		realtime = demotime;			// warp
 	} else if (!cl.paused && cls.state >= ca_onserver) {
 		// always grab until fully connected
-		if (realtime + 1.0 < demotime) {
+		if (!cls.demoplayback2 && realtime + 1.0 < demotime) {
 			// too far back
 			realtime = demotime - 1.0;
 			// rewind back to time
 			demotime_cached = 1;
 			cached_demotime = demotime;
+			cached_newtime = newtime;
 			return 0;
 		} else if (realtime < demotime) {
 			// rewind back to time
 			demotime_cached = 1;
 			cached_demotime = demotime;
+			cached_newtime = newtime;
 			return 0;					// don't need another message yet
 		}
 	} else
 		realtime = demotime;			// we're warping
 
+	if (realtime - nextdemotime > 0.0001) {
+		if (nextdemotime != demotime) {
+			if (cls.demoplayback2) {
+				cls.netchan.incoming_sequence++;
+				cls.netchan.incoming_acknowledged++;
+				cls.netchan.frame_latency = 0;
+			}
+		}
+	}
+	nextdemotime = demotime;
+
+	cls.prevtime += newtime;
+
 	if (cls.state < ca_demostart)
 		Host_Error ("CL_GetDemoMessage: cls.state != ca_active");
 
 	// get the msg type
 	Qread (cls.demofile, &c, sizeof (c));
 
-	switch (c) {
+	switch (c & 7) {
 		case dem_cmd:
 			// user sent input
 			net_message->message->cursize = -1;
@@ -269,6 +314,7 @@ CL_GetDemoMessage (void)
 			break;
 
 		case dem_read:
+readit:
 			// get the next message
 			Qread (cls.demofile, &net_message->message->cursize, 4);
 			net_message->message->cursize = LittleLong
@@ -282,6 +328,20 @@ CL_GetDemoMessage (void)
 				CL_StopPlayback ();
 				return 0;
 			}
+
+			if (cls.demoplayback2) {
+				tracknum = Cam_TrackNum ();
+
+				if (cls.lasttype == dem_multiple) {
+					if (tracknum == -1)
+						goto nextdemomessage;
+					if (!(cls.lastto & (1 << tracknum)))
+						goto nextdemomessage;
+				} else if (cls.lasttype == dem_single) {
+					if (tracknum == -1 || cls.lastto != spec_track)
+						goto nextdemomessage;
+				}
+			}
 			break;
 
 		case dem_set:
@@ -289,8 +349,38 @@ CL_GetDemoMessage (void)
 			cls.netchan.outgoing_sequence = LittleLong (i);
 			Qread (cls.demofile, &i, 4);
 			cls.netchan.incoming_sequence = LittleLong (i);
+			if (cls.demoplayback2) {
+				cls.netchan.incoming_acknowledged =
+					cls.netchan.incoming_sequence;
+				goto nextdemomessage;
+			}
 			break;
 
+		case dem_multiple:
+			r = Qread (cls.demofile, &i, 4);
+			if (r != 4) {
+				CL_StopPlayback ();
+				return 0;
+			}
+			cls.lastto = LittleLong (i);
+			cls.lasttype = dem_multiple;
+			goto readit;
+
+		case dem_single:
+			cls.lastto = c >> 3;
+			cls.lasttype = dem_single;
+			goto readit;
+
+		case dem_stats:
+			cls.lastto = c >> 3;
+			cls.lasttype = dem_stats;
+			goto readit;
+
+		case dem_all:
+			cls.lastto = 0;
+			cls.lasttype = dem_all;
+			goto readit;
+
 		default:
 			Con_Printf ("Corrupted demo.\n");
 			CL_StopPlayback ();
@@ -789,10 +879,23 @@ CL_StartDemo (void)
 	}
 
 	cls.demoplayback = true;
+	if (strequal (COM_FileExtension (name), ".mvd")) {
+		cls.demoplayback2 = true;
+		Con_Printf ("mvd\n");
+	} else {
+		Con_Printf ("qwd\n");
+	}
 	CL_SetState (ca_demostart);
 	Netchan_Setup (&cls.netchan, net_from, 0);
 	realtime = 0;
+	cls.findtrack = true;
+	cls.lasttype = 0;
+	cls.lastto = 0;
+	cls.prevtime = 0;
+	cls.basetime = 0;
 	demotime_cached = 0;
+	nextdemotime = 0;
+	CL_ClearPredict ();
 }
 
 /*
diff --git a/qw/source/cl_ents.c b/qw/source/cl_ents.c
index 7421e962f..f151eb0ef 100644
--- a/qw/source/cl_ents.c
+++ b/qw/source/cl_ents.c
@@ -301,7 +301,8 @@ CL_ParsePacketEntities (qboolean delta)
 		from = MSG_ReadByte (net_message);
 
 		oldpacket = cl.frames[newpacket].delta_sequence;
-
+		if (cls.demoplayback2)
+			from = oldpacket = (cls.netchan.incoming_sequence - 1);
 		if ((from & UPDATE_MASK) != (oldpacket & UPDATE_MASK))
 			Con_DPrintf ("WARNING: from mismatch\n");
 	} else
@@ -564,10 +565,10 @@ CL_ClearProjectiles (void)
 	Nails are passed as efficient temporary entities
 */
 void
-CL_ParseProjectiles (void)
+CL_ParseProjectiles (qboolean nail2)
 {
 	byte		bits[6];
-	int			i, c, d, j;
+	int			i, c, d, j, num;
 	entity_t   *pr;
 
 	c = MSG_ReadByte (net_message);
@@ -578,6 +579,11 @@ CL_ParseProjectiles (void)
 		d = c;
 
 	for (i = 0; i < d; i++) {
+		if (nail2)
+			num = MSG_ReadByte (net_message);
+		else
+			num = 0;
+
 		for (j = 0; j < 6; j++)
 			bits[j] = MSG_ReadByte (net_message);
 
@@ -595,8 +601,11 @@ CL_ParseProjectiles (void)
 
 	if (d < c) {
 		c = (c - d) * 6;
-		for (i = 0; i < c; i++)
+		for (i = 0; i < c; i++) {
+			if (nail2)
+				MSG_ReadByte (net_message);
 			MSG_ReadByte (net_message);
+		}
 	}
 }
 
@@ -618,19 +627,98 @@ CL_LinkProjectiles (void)
 	}
 }
 
+#define DF_ORIGIN   1
+#define DF_ANGLES   (1<<3)
+#define DF_EFFECTS  (1<<6)
+#define DF_SKINNUM  (1<<7)
+#define DF_DEAD     (1<<8)
+#define DF_GIB      (1<<9)
+#define DF_WEAPONFRAME (1<<10)
+#define DF_MODEL    (1<<11)
+
+int
+TranslateFlags (int src)
+{
+	int         dst = 0;
+
+	if (src & DF_EFFECTS)
+		dst |= PF_EFFECTS;
+	if (src & DF_SKINNUM)
+		dst |= PF_SKINNUM;
+	if (src & DF_DEAD)
+		dst |= PF_DEAD;
+	if (src & DF_GIB)
+		dst |= PF_GIB;
+	if (src & DF_WEAPONFRAME)
+		dst |= PF_WEAPONFRAME;
+	if (src & DF_MODEL)
+		dst |= PF_MODEL;
+
+	return dst;
+}
+
 void
 CL_ParsePlayerinfo (void)
 {
-	int				flags, msec, num, i;
-	player_state_t *state;
+	int            flags, msec, num, i;
+	player_info_t *info;
+	player_state_t *state, *prevstate;
+	static player_state_t dummy;
 
 	num = MSG_ReadByte (net_message);
 	if (num > MAX_CLIENTS)
 		Host_Error ("CL_ParsePlayerinfo: bad num");
 
+	info = &cl.players[num];
 	state = &cl.frames[parsecountmod].playerstate[num];
 
 	state->number = num;
+
+	if (cls.demoplayback2) {
+		if (info->prevcount > cl.parsecount || !cl.parsecount) {
+			prevstate = &dummy;
+		} else {
+			if (cl.parsecount - info->prevcount >= UPDATE_BACKUP-1)
+				prevstate = &dummy;
+			else
+				prevstate = &cl.frames[info->prevcount
+									   & UPDATE_MASK].playerstate[num];
+		}
+		info->prevcount = cl.parsecount;
+
+		if (cls.findtrack && info->stats[STAT_HEALTH] != 0) {
+			autocam = CAM_TRACK;
+			Cam_Lock (num);
+			ideal_track = num;
+			cls.findtrack = false;
+		}
+
+		memcpy(state, prevstate, sizeof(player_state_t));
+
+		flags = MSG_ReadShort (net_message);
+		state->flags = TranslateFlags(flags);
+		state->messagenum = cl.parsecount;
+		state->command.msec = 0;
+		state->frame = MSG_ReadByte (net_message);
+		state->state_time = parsecounttime;
+		for (i=0; i <3; i++)
+			if (flags & (DF_ORIGIN << i))
+				state->origin[i] = MSG_ReadCoord (net_message);
+		for (i=0; i <3; i++)
+			if (flags & (DF_ANGLES << i))
+				state->command.angles[i] = MSG_ReadAngle16 (net_message);
+		if (flags & DF_MODEL)
+			state->modelindex = MSG_ReadByte (net_message);
+		if (flags & DF_SKINNUM)
+			state->skinnum = MSG_ReadByte (net_message);
+		if (flags & DF_EFFECTS)
+			state->effects = MSG_ReadByte (net_message);
+		if (flags & DF_WEAPONFRAME)
+			state->weaponframe = MSG_ReadByte (net_message);
+		VectorCopy (state->command.angles, state->viewangles);
+		return;
+	}
+
 	flags = state->flags = MSG_ReadShort (net_message);
 
 	state->messagenum = cl.parsecount;
@@ -830,7 +918,7 @@ CL_LinkPlayers (void)
 
 		// only predict half the move to minimize overruns
 		msec = 500 * (playertime - state->state_time);
-		if (msec <= 0 || (!cl_predict_players->int_val)) {
+		if (msec <= 0 || (!cl_predict_players->int_val) || cls.demoplayback2) {
 			VectorCopy (state->origin, ent->origin);
 		} else {									// predict players movement
 			state->command.msec = msec = min (msec, 255);
@@ -927,6 +1015,13 @@ CL_SetSolidEntities (void)
 	}
 }
 
+void
+CL_ClearPredict (void)
+{
+	memset (predicted_players, 0, sizeof (predicted_players));
+	//fixangle = 0;
+}
+
 /*
 	Calculate the new position of players, without other player clipping
 
diff --git a/qw/source/cl_input.c b/qw/source/cl_input.c
index 3f7774d17..4c9355343 100644
--- a/qw/source/cl_input.c
+++ b/qw/source/cl_input.c
@@ -670,7 +670,7 @@ CL_SendCmd (void)
 	sizebuf_t		buf;
 	usercmd_t	   *cmd, *oldcmd;
 
-	if (cls.demoplayback)
+	if (cls.demoplayback && !cls.demoplayback2)
 		return;							// sendcmds come from the demo
 
 	// save this command off for prediction
@@ -684,6 +684,11 @@ CL_SendCmd (void)
 
 	build_cmd (cmd);
 
+	if (cls.demoplayback2) {
+		cls.netchan.outgoing_sequence++;
+		return;
+	}
+
 	// send this and the previous cmds in the message, so
 	// if the last packet was dropped, it can be recovered
 	buf.maxsize = 128;
diff --git a/qw/source/cl_main.c b/qw/source/cl_main.c
index 6fc0d20a5..a7f4b0d7b 100644
--- a/qw/source/cl_main.c
+++ b/qw/source/cl_main.c
@@ -471,6 +471,7 @@ CL_Disconnect (void)
 		CL_SetState (ca_disconnected);
 
 		cls.demoplayback = cls.demorecording = cls.timedemo = false;
+		cls.demoplayback2 = false;
 
 		CL_RemoveQFInfoKeys ();
 	}
@@ -1049,7 +1050,7 @@ CL_ReadPackets (void)
 			SL_CheckPing (NET_AdrToString (net_from));
 			continue;
 		}
-		if (net_message->message->cursize < 8) {
+		if (net_message->message->cursize < 8 && !cls.demoplayback2) {
 			Con_Printf ("%s: Runt packet\n", NET_AdrToString (net_from));
 			continue;
 		}
@@ -1061,14 +1062,18 @@ CL_ReadPackets (void)
 						 NET_AdrToString (net_from));
 			continue;
 		}
-		if (!Netchan_Process (&cls.netchan))
-			continue;						// wasn't accepted for some reason
+		if (!cls.demoplayback2) {
+			if (!Netchan_Process (&cls.netchan))
+				continue;					// wasn't accepted for some reason
+		} else {
+			MSG_BeginReading (net_message);
+		}
 		if (cls.state != ca_disconnected)
 			CL_ParseServerMessage ();
 	}
 
 	// check timeout
-	if (cls.state >= ca_connected
+	if (!cls.demoplayback && cls.state >= ca_connected
 		&& realtime - cls.netchan.last_received > cl_timeout->value) {
 		Con_Printf ("\nServer connection timed out.\n");
 		CL_Disconnect ();
@@ -1543,6 +1548,23 @@ Host_Frame (float time)
 	// fetch results from server
 	CL_ReadPackets ();
 
+	if (cls.demoplayback2) {
+		player_state_t *self, *oldself;
+
+		self = &cl.frames[cl.parsecount
+						  & UPDATE_MASK].playerstate[cl.playernum];
+		oldself = &cl.frames[(cls.netchan.outgoing_sequence - 1)
+							 & UPDATE_MASK].playerstate[cl.playernum];
+		self->messagenum = cl.parsecount;
+		VectorCopy (oldself->origin, self->origin);
+		VectorCopy (oldself->velocity, self->velocity);
+		VectorCopy (oldself->viewangles, self->viewangles);
+
+		CL_ParseClientdata ();
+
+		cls.netchan.outgoing_sequence = cl.parsecount + 1;
+	}
+
 	// send intentions now
 	// resend a connection request if necessary
 	if (cls.state == ca_disconnected) {
diff --git a/qw/source/cl_parse.c b/qw/source/cl_parse.c
index 0b4e50683..d30ed2b66 100644
--- a/qw/source/cl_parse.c
+++ b/qw/source/cl_parse.c
@@ -60,6 +60,7 @@ static const char rcsid[] =
 #include "QF/gib_thread.h"
 
 #include "bothdefs.h"
+#include "cl_cam.h"
 #include "cl_ents.h"
 #include "cl_input.h"
 #include "cl_main.h"
@@ -138,7 +139,7 @@ char       *svc_strings[] = {
 	"svc_setinfo",
 	"svc_serverinfo",
 	"svc_updatepl",
-	"NEW PROTOCOL",
+	"svc_nails2",						// FIXME from qwex
 	"NEW PROTOCOL",
 	"NEW PROTOCOL",
 	"NEW PROTOCOL",
@@ -658,11 +659,18 @@ CL_ParseServerData (void)
 		snprintf (fn, sizeof (fn), "cmd_warncmd %d\n", cmd_warncmd_val);
 		Cbuf_AddText (cl_cbuf, fn);
 	}
-	// parse player slot, high bit means spectator
-	cl.playernum = MSG_ReadByte (net_message);
-	if (cl.playernum & 128) {
+
+	if (cls.demoplayback2) {
+		realtime = cls.basetime = MSG_ReadFloat (net_message);
+		cl.playernum = 31;
 		cl.spectator = true;
-		cl.playernum &= ~128;
+	} else {
+		// parse player slot, high bit means spectator
+		cl.playernum = MSG_ReadByte (net_message);
+		if (cl.playernum & 128) {
+			cl.spectator = true;
+			cl.playernum &= ~128;
+		}
 	}
 
 // FIXME: evil hack so NQ and QW can share sound code
@@ -915,10 +923,13 @@ CL_ParseClientdata (void)
 	oldparsecountmod = parsecountmod;
 
 	i = cls.netchan.incoming_acknowledged;
+
 	cl.parsecount = i;
 	i &= UPDATE_MASK;
 	parsecountmod = i;
 	frame = &cl.frames[i];
+	if (cls.demoplayback2)
+		frame->senttime = realtime - host_frametime;//realtime;
 	parsecounttime = cl.frames[i].senttime;
 
 	frame->receivedtime = realtime;
@@ -1064,6 +1075,12 @@ CL_SetStat (int stat, int value)
 	if (stat < 0 || stat >= MAX_CL_STATS)
 		Host_Error ("CL_SetStat: %i is invalid", stat);
 
+	if (cls.demoplayback2) {
+		cl.players[cls.lastto].stats[stat] = value;
+		if (Cam_TrackNum () != cls.lastto)
+			return;
+	}
+
 	Sbar_Changed ();
 
 	switch (stat) {
@@ -1211,7 +1228,8 @@ CL_ParseServerMessage (void)
 		// other commands
 		switch (cmd) {
 			default:
-				Host_Error ("CL_ParseServerMessage: Illegible server message");
+				Host_Error ("CL_ParseServerMessage: Illegible server "
+							"message: %d\n", cmd);
 				break;
 
 			case svc_nop:
@@ -1287,7 +1305,19 @@ CL_ParseServerMessage (void)
 				break;
 
 			case svc_setangle:
-				MSG_ReadAngleV (net_message, cl.viewangles);
+				if (!cls.demoplayback2) {
+					MSG_ReadAngleV (net_message, cl.viewangles);
+				} else {
+					j = MSG_ReadByte(net_message);
+					//fixangle |= 1 << j;
+					if (j != Cam_TrackNum()) {
+						MSG_ReadAngle (net_message);
+						MSG_ReadAngle (net_message);
+						MSG_ReadAngle (net_message);
+					} else {
+						MSG_ReadAngleV (net_message, cl.viewangles);
+					}
+				}
 // FIXME		cl.viewangles[PITCH] = cl.viewangles[ROLL] = 0;
 				break;
 
@@ -1453,7 +1483,11 @@ CL_ParseServerMessage (void)
 				break;
 
 			case svc_nails:
-				CL_ParseProjectiles ();
+				CL_ParseProjectiles (false);
+				break;
+
+			case svc_nails2:			// FIXME from qwex
+				CL_ParseProjectiles (true);
 				break;
 
 			case svc_chokecount:		// some preceding packets were choked