newtree/source/cl_demo.c
Bill Currie 24a76ef439 don't seek while backing up over demotime. gzipped demos no longer slow down as
the demo progresses (this will probably also be good for eventual dzip
support).
2000-12-19 20:33:06 +00:00

833 lines
18 KiB
C

/*
cl_demo.c
(description)
Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
$Id$
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "host.h"
#include "sys.h"
#include "cvar.h"
#include "sizebuf.h"
#include "msg.h"
#include "client.h"
#include "commdef.h"
#include "cmd.h"
#include "console.h"
#include "qendian.h"
#include "va.h"
#include "quakefs.h"
#include "pmove.h"
#include "cl_main.h"
void CL_FinishTimeDemo (void);
/*
==============================================================================
DEMO CODE
When a demo is playing back, all NET_SendMessages are skipped, and
NET_GetMessages are read from the demo file.
Whenever cl.time gets past the last received message, another message is
read from the demo file.
==============================================================================
*/
/*
==============
CL_StopPlayback
Called when a demo file runs out, or the user starts a game
==============
*/
void
CL_StopPlayback (void)
{
if (!cls.demoplayback)
return;
Qclose (cls.demofile);
cls.demofile = NULL;
cls.state = ca_disconnected;
cls.demoplayback = 0;
if (cls.timedemo)
CL_FinishTimeDemo ();
}
#define dem_cmd 0
#define dem_read 1
#define dem_set 2
/*
====================
CL_WriteDemoCmd
Writes the current user cmd
====================
*/
void
CL_WriteDemoCmd (usercmd_t *pcmd)
{
int i;
float fl;
byte c;
usercmd_t cmd;
//Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, realtime);
fl = LittleFloat ((float) realtime);
Qwrite (cls.demofile, &fl, sizeof (fl));
c = dem_cmd;
Qwrite (cls.demofile, &c, sizeof (c));
// correct for byte order, bytes don't matter
cmd = *pcmd;
for (i = 0; i < 3; i++)
cmd.angles[i] = LittleFloat (cmd.angles[i]);
cmd.forwardmove = LittleShort (cmd.forwardmove);
cmd.sidemove = LittleShort (cmd.sidemove);
cmd.upmove = LittleShort (cmd.upmove);
Qwrite (cls.demofile, &cmd, sizeof (cmd));
for (i = 0; i < 3; i++) {
fl = LittleFloat (cl.viewangles[i]);
Qwrite (cls.demofile, &fl, 4);
}
Qflush (cls.demofile);
}
/*
====================
CL_WriteDemoMessage
Dumps the current net message, prefixed by the length and view angles
====================
*/
void
CL_WriteDemoMessage (sizebuf_t *msg)
{
int len;
float fl;
byte c;
//Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, realtime);
if (!cls.demorecording)
return;
fl = LittleFloat ((float) realtime);
Qwrite (cls.demofile, &fl, sizeof (fl));
c = dem_read;
Qwrite (cls.demofile, &c, sizeof (c));
len = LittleLong (msg->cursize);
Qwrite (cls.demofile, &len, 4);
Qwrite (cls.demofile, msg->data, msg->cursize);
Qflush (cls.demofile);
}
/*
====================
CL_GetDemoMessage
FIXME...
====================
*/
qboolean
CL_GetDemoMessage (void)
{
int r, i, j;
float f;
float demotime;
byte c;
usercmd_t *pcmd;
static int demotime_cached;
static float cached_demotime;
// read the time from the packet
if (demotime_cached) {
demotime = cached_demotime;
demotime_cached = 0;
} else {
Qread (cls.demofile, &demotime, sizeof (demotime));
demotime = LittleFloat (demotime);
}
// decide if it is time to grab the next message
if (cls.timedemo) {
if (cls.td_lastframe < 0)
cls.td_lastframe = demotime;
else if (demotime > cls.td_lastframe) {
cls.td_lastframe = demotime;
// rewind back to time
demotime_cached = 1;
cached_demotime = demotime;
return 0; // allready read this frame's message
}
if (!cls.td_starttime && cls.state == ca_active) {
cls.td_starttime = Sys_DoubleTime ();
cls.td_startframe = host_framecount;
}
realtime = demotime; // warp
} else if (!cl.paused && cls.state >= ca_onserver) { // allways grab
// until fully
// connected
if (realtime + 1.0 < demotime) {
// too far back
realtime = demotime - 1.0;
// rewind back to time
demotime_cached = 1;
cached_demotime = demotime;
return 0;
} else if (realtime < demotime) {
// rewind back to time
demotime_cached = 1;
cached_demotime = demotime;
return 0; // don't need another message yet
}
} else
realtime = demotime; // we're warping
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) {
case dem_cmd:
// user sent input
i = cls.netchan.outgoing_sequence & UPDATE_MASK;
pcmd = &cl.frames[i].cmd;
r = Qread (cls.demofile, pcmd, sizeof (*pcmd));
if (r != sizeof (*pcmd)) {
CL_StopPlayback ();
return 0;
}
// byte order stuff
for (j = 0; j < 3; j++)
pcmd->angles[j] = LittleFloat (pcmd->angles[j]);
pcmd->forwardmove = LittleShort (pcmd->forwardmove);
pcmd->sidemove = LittleShort (pcmd->sidemove);
pcmd->upmove = LittleShort (pcmd->upmove);
cl.frames[i].senttime = demotime;
cl.frames[i].receivedtime = -1; // we haven't gotten a reply yet
cls.netchan.outgoing_sequence++;
for (i = 0; i < 3; i++) {
Qread (cls.demofile, &f, 4);
cl.viewangles[i] = LittleFloat (f);
}
break;
case dem_read:
// get the next message
Qread (cls.demofile, &net_message.cursize, 4);
net_message.cursize = LittleLong (net_message.cursize);
// Con_Printf("read: %ld bytes\n", net_message.cursize);
if (net_message.cursize > MAX_MSGLEN)
// Sys_Error ("Demo message > MAX_MSGLEN");
Host_EndGame ("Demo message > MAX_MSGLEN");
r = Qread (cls.demofile, net_message.data, net_message.cursize);
if (r != net_message.cursize) {
CL_StopPlayback ();
return 0;
}
break;
case dem_set:
Qread (cls.demofile, &i, 4);
cls.netchan.outgoing_sequence = LittleLong (i);
Qread (cls.demofile, &i, 4);
cls.netchan.incoming_sequence = LittleLong (i);
break;
default:
Con_Printf ("Corrupted demo.\n");
CL_StopPlayback ();
return 0;
}
return 1;
}
/*
====================
CL_GetMessage
Handles recording and playback of demos, on top of NET_ code
====================
*/
qboolean
CL_GetMessage (void)
{
if (cls.demoplayback)
return CL_GetDemoMessage ();
if (!NET_GetPacket ())
return false;
CL_WriteDemoMessage (&net_message);
return true;
}
/*
====================
CL_Stop_f
stop recording a demo
====================
*/
void
CL_Stop_f (void)
{
if (!cls.demorecording) {
Con_Printf ("Not recording a demo.\n");
return;
}
// write a disconnect message to the demo file
SZ_Clear (&net_message);
MSG_WriteLong (&net_message, -1); // -1 sequence means out of band
MSG_WriteByte (&net_message, svc_disconnect);
MSG_WriteString (&net_message, "EndOfDemo");
CL_WriteDemoMessage (&net_message);
// finish up
Qclose (cls.demofile);
cls.demofile = NULL;
cls.demorecording = false;
Con_Printf ("Completed demo\n");
}
/*
====================
CL_WriteDemoMessage
Dumps the current net message, prefixed by the length and view angles
====================
*/
void
CL_WriteRecordDemoMessage (sizebuf_t *msg, int seq)
{
int len;
int i;
float fl;
byte c;
//Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, realtime);
if (!cls.demorecording)
return;
fl = LittleFloat ((float) realtime);
Qwrite (cls.demofile, &fl, sizeof (fl));
c = dem_read;
Qwrite (cls.demofile, &c, sizeof (c));
len = LittleLong (msg->cursize + 8);
Qwrite (cls.demofile, &len, 4);
i = LittleLong (seq);
Qwrite (cls.demofile, &i, 4);
Qwrite (cls.demofile, &i, 4);
Qwrite (cls.demofile, msg->data, msg->cursize);
Qflush (cls.demofile);
}
void
CL_WriteSetDemoMessage (void)
{
int len;
float fl;
byte c;
//Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, realtime);
if (!cls.demorecording)
return;
fl = LittleFloat ((float) realtime);
Qwrite (cls.demofile, &fl, sizeof (fl));
c = dem_set;
Qwrite (cls.demofile, &c, sizeof (c));
len = LittleLong (cls.netchan.outgoing_sequence);
Qwrite (cls.demofile, &len, 4);
len = LittleLong (cls.netchan.incoming_sequence);
Qwrite (cls.demofile, &len, 4);
Qflush (cls.demofile);
}
/*
====================
CL_Record_f
record <demoname> <server>
====================
*/
void
CL_Record_f (void)
{
int c;
char name[MAX_OSPATH];
sizebuf_t buf;
char buf_data[MAX_MSGLEN];
int n, i, j;
char *s;
entity_t *ent;
entity_state_t *es, blankes;
player_info_t *player;
extern char gamedirfile[];
int seq = 1;
c = Cmd_Argc ();
if (c != 2) {
Con_Printf ("record <demoname>\n");
return;
}
if (cls.state != ca_active) {
Con_Printf ("You must be connected to record.\n");
return;
}
if (cls.demorecording)
CL_Stop_f ();
snprintf (name, sizeof (name), "%s/%s", com_gamedir, Cmd_Argv (1));
//
// open the demo file
//
COM_DefaultExtension (name, ".qwd");
cls.demofile = Qopen (name, "wb");
if (!cls.demofile) {
Con_Printf ("ERROR: couldn't open.\n");
return;
}
Con_Printf ("recording to %s.\n", name);
cls.demorecording = true;
/*-------------------------------------------------*/
// serverdata
// send the info about the new client to all connected clients
memset (&buf, 0, sizeof (buf));
buf.data = buf_data;
buf.maxsize = sizeof (buf_data);
// send the serverdata
MSG_WriteByte (&buf, svc_serverdata);
MSG_WriteLong (&buf, PROTOCOL_VERSION);
MSG_WriteLong (&buf, cl.servercount);
MSG_WriteString (&buf, gamedirfile);
if (cl.spectator)
MSG_WriteByte (&buf, cl.playernum | 128);
else
MSG_WriteByte (&buf, cl.playernum);
// send full levelname
MSG_WriteString (&buf, cl.levelname);
// send the movevars
MSG_WriteFloat (&buf, movevars.gravity);
MSG_WriteFloat (&buf, movevars.stopspeed);
MSG_WriteFloat (&buf, movevars.maxspeed);
MSG_WriteFloat (&buf, movevars.spectatormaxspeed);
MSG_WriteFloat (&buf, movevars.accelerate);
MSG_WriteFloat (&buf, movevars.airaccelerate);
MSG_WriteFloat (&buf, movevars.wateraccelerate);
MSG_WriteFloat (&buf, movevars.friction);
MSG_WriteFloat (&buf, movevars.waterfriction);
MSG_WriteFloat (&buf, movevars.entgravity);
// send music
MSG_WriteByte (&buf, svc_cdtrack);
MSG_WriteByte (&buf, 0); // none in demos
// send server info string
MSG_WriteByte (&buf, svc_stufftext);
MSG_WriteString (&buf, va ("fullserverinfo \"%s\"\n", cl.serverinfo));
// flush packet
CL_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
// soundlist
MSG_WriteByte (&buf, svc_soundlist);
MSG_WriteByte (&buf, 0);
n = 0;
s = cl.sound_name[n + 1];
while (*s) {
MSG_WriteString (&buf, s);
if (buf.cursize > MAX_MSGLEN / 2) {
MSG_WriteByte (&buf, 0);
MSG_WriteByte (&buf, n);
CL_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
MSG_WriteByte (&buf, svc_soundlist);
MSG_WriteByte (&buf, n + 1);
}
n++;
s = cl.sound_name[n + 1];
}
if (buf.cursize) {
MSG_WriteByte (&buf, 0);
MSG_WriteByte (&buf, 0);
CL_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
}
// modellist
MSG_WriteByte (&buf, svc_modellist);
MSG_WriteByte (&buf, 0);
n = 0;
s = cl.model_name[n + 1];
while (*s) {
MSG_WriteString (&buf, s);
if (buf.cursize > MAX_MSGLEN / 2) {
MSG_WriteByte (&buf, 0);
MSG_WriteByte (&buf, n);
CL_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
MSG_WriteByte (&buf, svc_modellist);
MSG_WriteByte (&buf, n + 1);
}
n++;
s = cl.model_name[n + 1];
}
if (buf.cursize) {
MSG_WriteByte (&buf, 0);
MSG_WriteByte (&buf, 0);
CL_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
}
// spawnstatic
for (i = 0; i < cl.num_statics; i++) {
ent = cl_static_entities + i;
MSG_WriteByte (&buf, svc_spawnstatic);
for (j = 1; j < MAX_MODELS; j++)
if (ent->model == cl.model_precache[j])
break;
if (j == MAX_MODELS)
MSG_WriteByte (&buf, 0);
else
MSG_WriteByte (&buf, j);
MSG_WriteByte (&buf, ent->frame);
MSG_WriteByte (&buf, 0);
MSG_WriteByte (&buf, ent->skinnum);
for (j = 0; j < 3; j++) {
MSG_WriteCoord (&buf, ent->origin[j]);
MSG_WriteAngle (&buf, ent->angles[j]);
}
if (buf.cursize > MAX_MSGLEN / 2) {
CL_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
}
}
// spawnstaticsound
// static sounds are skipped in demos, life is hard
// baselines
memset (&blankes, 0, sizeof (blankes));
for (i = 0; i < MAX_EDICTS; i++) {
es = cl_baselines + i;
if (memcmp (es, &blankes, sizeof (blankes))) {
MSG_WriteByte (&buf, svc_spawnbaseline);
MSG_WriteShort (&buf, i);
MSG_WriteByte (&buf, es->modelindex);
MSG_WriteByte (&buf, es->frame);
MSG_WriteByte (&buf, es->colormap);
MSG_WriteByte (&buf, es->skinnum);
for (j = 0; j < 3; j++) {
MSG_WriteCoord (&buf, es->origin[j]);
MSG_WriteAngle (&buf, es->angles[j]);
}
if (buf.cursize > MAX_MSGLEN / 2) {
CL_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
}
}
}
MSG_WriteByte (&buf, svc_stufftext);
MSG_WriteString (&buf, va ("cmd spawn %i 0\n", cl.servercount));
if (buf.cursize) {
CL_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
}
// send current status of all other players
for (i = 0; i < MAX_CLIENTS; i++) {
player = cl.players + i;
MSG_WriteByte (&buf, svc_updatefrags);
MSG_WriteByte (&buf, i);
MSG_WriteShort (&buf, player->frags);
MSG_WriteByte (&buf, svc_updateping);
MSG_WriteByte (&buf, i);
MSG_WriteShort (&buf, player->ping);
MSG_WriteByte (&buf, svc_updatepl);
MSG_WriteByte (&buf, i);
MSG_WriteByte (&buf, player->pl);
MSG_WriteByte (&buf, svc_updateentertime);
MSG_WriteByte (&buf, i);
MSG_WriteFloat (&buf, player->entertime);
MSG_WriteByte (&buf, svc_updateuserinfo);
MSG_WriteByte (&buf, i);
MSG_WriteLong (&buf, player->userid);
MSG_WriteString (&buf, player->userinfo);
if (buf.cursize > MAX_MSGLEN / 2) {
CL_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
}
}
// send all current light styles
for (i = 0; i < MAX_LIGHTSTYLES; i++) {
MSG_WriteByte (&buf, svc_lightstyle);
MSG_WriteByte (&buf, (char) i);
MSG_WriteString (&buf, cl_lightstyle[i].map);
}
for (i = 0; i < MAX_CL_STATS; i++) {
MSG_WriteByte (&buf, svc_updatestatlong);
MSG_WriteByte (&buf, i);
MSG_WriteLong (&buf, cl.stats[i]);
if (buf.cursize > MAX_MSGLEN / 2) {
CL_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
}
}
#if 0
MSG_WriteByte (&buf, svc_updatestatlong);
MSG_WriteByte (&buf, STAT_TOTALMONSTERS);
MSG_WriteLong (&buf, cl.stats[STAT_TOTALMONSTERS]);
MSG_WriteByte (&buf, svc_updatestatlong);
MSG_WriteByte (&buf, STAT_SECRETS);
MSG_WriteLong (&buf, cl.stats[STAT_SECRETS]);
MSG_WriteByte (&buf, svc_updatestatlong);
MSG_WriteByte (&buf, STAT_MONSTERS);
MSG_WriteLong (&buf, cl.stats[STAT_MONSTERS]);
#endif
// get the client to check and download skins
// when that is completed, a begin command will be issued
MSG_WriteByte (&buf, svc_stufftext);
MSG_WriteString (&buf, va ("skins\n"));
CL_WriteRecordDemoMessage (&buf, seq++);
CL_WriteSetDemoMessage ();
// done
}
/*
====================
CL_ReRecord_f
record <demoname>
====================
*/
void
CL_ReRecord_f (void)
{
int c;
char name[MAX_OSPATH];
c = Cmd_Argc ();
if (c != 2) {
Con_Printf ("rerecord <demoname>\n");
return;
}
if (!*cls.servername) {
Con_Printf ("No server to reconnect to...\n");
return;
}
if (cls.demorecording)
CL_Stop_f ();
snprintf (name, sizeof (name), "%s/%s", com_gamedir, Cmd_Argv (1));
//
// open the demo file
//
COM_DefaultExtension (name, ".qwd");
cls.demofile = Qopen (name, "wb");
if (!cls.demofile) {
Con_Printf ("ERROR: couldn't open.\n");
return;
}
Con_Printf ("recording to %s.\n", name);
cls.demorecording = true;
CL_Disconnect ();
CL_BeginServerConnect ();
}
/*
====================
CL_PlayDemo_f
play [demoname]
====================
*/
void
CL_PlayDemo_f (void)
{
char name[MAX_OSPATH];
if (Cmd_Argc () != 2) {
Con_Printf ("play <demoname> : plays a demo\n");
return;
}
//
// disconnect from server
//
CL_Disconnect ();
//
// open the demo file
//
strcpy (name, Cmd_Argv (1));
COM_DefaultExtension (name, ".qwd");
Con_Printf ("Playing demo from %s.\n", name);
COM_FOpenFile (name, &cls.demofile);
if (!cls.demofile) {
Con_Printf ("ERROR: couldn't open.\n");
cls.demonum = -1; // stop demo loop
return;
}
cls.demoplayback = true;
cls.state = ca_demostart;
Netchan_Setup (&cls.netchan, net_from, 0);
realtime = 0;
}
/*
====================
CL_FinishTimeDemo
====================
*/
void
CL_FinishTimeDemo (void)
{
int frames;
float time;
cls.timedemo = false;
// the first frame didn't count
frames = (host_framecount - cls.td_startframe) - 1;
time = Sys_DoubleTime () - cls.td_starttime;
if (!time)
time = 1;
Con_Printf ("%i frames %5.1f seconds %5.1f fps\n", frames, time,
frames / time);
}
/*
====================
CL_TimeDemo_f
timedemo [demoname]
====================
*/
void
CL_TimeDemo_f (void)
{
if (Cmd_Argc () != 2) {
Con_Printf ("timedemo <demoname> : gets demo speeds\n");
return;
}
CL_PlayDemo_f ();
if (cls.state != ca_demostart)
return;
// cls.td_starttime will be grabbed at the second frame of the demo, so
// all the loading time doesn't get counted
cls.timedemo = true;
cls.td_starttime = 0;
cls.td_startframe = host_framecount;
cls.td_lastframe = -1; // get a new message this frame
}