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.
This commit is contained in:
Bill Currie 2002-10-02 21:56:45 +00:00
parent 533a74aa75
commit 2383340031
11 changed files with 355 additions and 33 deletions

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 ();
}
/*

View file

@ -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

View file

@ -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;

View file

@ -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) {

View file

@ -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