quakeforge/qw/source/sv_demo.c
2003-03-21 21:25:44 +00:00

1672 lines
38 KiB
C

/*
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 included (GNU.txt) 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 the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
static __attribute__ ((unused)) const char rcsid[] =
"$Id$";
#ifdef HAVE_STRING_H
# include "string.h"
#endif
#ifdef HAVE_STRINGS_H
# include "strings.h"
#endif
#ifdef HAVE_UNISTD_H
# include "unistd.h"
#endif
#include <sys/time.h>
#include <time.h>
#include "QF/cmd.h"
#include "QF/cvar.h"
#include "QF/dstring.h"
#include "QF/info.h"
#include "QF/msg.h"
#include "QF/qargs.h"
#include "QF/qendian.h"
#include "QF/quakefs.h"
#include "QF/sys.h"
#include "QF/va.h"
#include "pmove.h"
#include "server.h"
#include "sv_demo.h"
#include "sv_progs.h"
demo_t demo;
#define MIN_DEMO_MEMORY 0x100000
#define USACACHE (sv_demoUseCache->int_val && svs.demomemsize)
#define DWRITE(a,b,d) dwrite((QFile *) d, a, b)
#define MAXSIZE (demobuffer->end < demobuffer->last ? \
demobuffer->start - demobuffer->end : \
demobuffer->maxsize - demobuffer->end)
static int demo_max_size;
static int demo_size;
cvar_t *sv_demoUseCache;
cvar_t *sv_demoCacheSize;
cvar_t *sv_demoMaxDirSize;
cvar_t *sv_demoDir;
cvar_t *sv_demofps;
cvar_t *sv_demoPings;
cvar_t *sv_demoNoVis;
cvar_t *sv_demoMaxSize;
cvar_t *sv_demoPrefix;
cvar_t *sv_demoSuffix;
cvar_t *sv_onrecordfinish;
cvar_t *sv_ondemoremove;
cvar_t *sv_demotxt;
cvar_t *serverdemo;
int (*dwrite) (QFile * file, const void *buf, int count);
static dbuffer_t *demobuffer;
static int header = (int) &((header_t *) 0)->data;
entity_state_t demo_entities[UPDATE_MASK + 1][MAX_DEMO_PACKET_ENTITIES];
void
SV_DemoPings (void)
{
client_t *client;
int j;
for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) {
if (client->state != cs_spawned)
continue;
DemoWrite_Begin (dem_all, 0, 7);
MSG_WriteByte ((sizebuf_t *) demo.dbuf, svc_updateping);
MSG_WriteByte ((sizebuf_t *) demo.dbuf, j);
MSG_WriteShort ((sizebuf_t *) demo.dbuf, SV_CalcPing (client));
MSG_WriteByte ((sizebuf_t *) demo.dbuf, svc_updatepl);
MSG_WriteByte ((sizebuf_t *) demo.dbuf, j);
MSG_WriteByte ((sizebuf_t *) demo.dbuf, client->lossage);
}
}
static void
DemoBuffer_Init (dbuffer_t * dbuffer, byte * buf, size_t size)
{
demobuffer = dbuffer;
demobuffer->data = buf;
demobuffer->maxsize = size;
demobuffer->start = 0;
demobuffer->end = 0;
demobuffer->last = 0;
}
/*
Demo_SetMsgBuf
Sets the frame message buffer
*/
void
DemoSetMsgBuf (demobuf_t * prev, demobuf_t * cur)
{
// fix the maxsize of previous msg buffer,
// we won't be able to write there anymore
if (prev != NULL)
prev->sz.maxsize = prev->bufsize;
demo.dbuf = cur;
memset (demo.dbuf, 0, sizeof (*demo.dbuf));
demo.dbuf->sz.data = demobuffer->data + demobuffer->end;
demo.dbuf->sz.maxsize = MAXSIZE;
}
/*
DemoWriteToDisk
Writes to disk a message meant for specifc client
or all messages if type == 0
Message is cleared from demobuf after that
*/
static void
SV_DemoWriteToDisk (int type, int to, float time)
{
int pos = 0, oldm, oldd;
header_t *p;
int size;
sizebuf_t msg;
(byte *) p = demo.dbuf->sz.data;
demo.dbuf->h = NULL;
oldm = demo.dbuf->bufsize;
oldd = demobuffer->start;
while (pos < demo.dbuf->bufsize) {
size = p->size;
pos += header + size;
// no type means we are writing to disk everything
if (!type || (p->type == type && p->to == to)) {
if (size) {
msg.data = p->data;
msg.cursize = size;
SV_WriteDemoMessage (&msg, p->type, p->to, time);
}
// data is written so it need to be cleard from demobuf
if (demo.dbuf->sz.data != (byte *) p)
memmove (demo.dbuf->sz.data + size + header,
demo.dbuf->sz.data, (byte *) p - demo.dbuf->sz.data);
demo.dbuf->bufsize -= size + header;
demo.dbuf->sz.data += size + header;
pos -= size + header;
demo.dbuf->sz.maxsize -= size + header;
demobuffer->start += size + header;
}
// move along
(byte *) p = p->data + size;
}
if (demobuffer->start == demobuffer->last) {
if (demobuffer->start == demobuffer->end) {
demobuffer->end = 0; // demobuffer is empty
demo.dbuf->sz.data = demobuffer->data;
}
// go back to begining of the buffer
demobuffer->last = demobuffer->end;
demobuffer->start = 0;
}
}
/*
DemoSetBuf
Sets position in the buf for writing to specific client
*/
static void
DemoSetBuf (byte type, int to)
{
header_t *p;
int pos = 0;
(byte *) p = demo.dbuf->sz.data;
while (pos < demo.dbuf->bufsize) {
pos += header + p->size;
if (type == p->type && to == p->to && !p->full) {
demo.dbuf->sz.cursize = pos;
demo.dbuf->h = p;
return;
}
(byte *) p = p->data + p->size;
}
// type&&to not exist in the buf, so add it
p->type = type;
p->to = to;
p->size = 0;
p->full = 0;
demo.dbuf->bufsize += header;
demo.dbuf->sz.cursize = demo.dbuf->bufsize;
demobuffer->end += header;
demo.dbuf->h = p;
}
static void
DemoMoveBuf (void)
{
// set the last message mark to the previous frame (i/e begining of this
// one)
demobuffer->last = demobuffer->end - demo.dbuf->bufsize;
// move buffer to the begining of demo buffer
memmove (demobuffer->data, demo.dbuf->sz.data, demo.dbuf->bufsize);
demo.dbuf->sz.data = demobuffer->data;
demobuffer->end = demo.dbuf->bufsize;
demo.dbuf->h = NULL; // it will be setup again
demo.dbuf->sz.maxsize = MAXSIZE + demo.dbuf->bufsize;
}
void
DemoWrite_Begin (byte type, int to, int size)
{
byte *p;
qboolean move = false;
// will it fit?
while (demo.dbuf->bufsize + size + header > demo.dbuf->sz.maxsize) {
// if we reached the end of buffer move msgbuf to the begining
if (!move && demobuffer->end > demobuffer->start)
move = true;
SV_DemoWritePackets (1);
if (move && demobuffer->start > demo.dbuf->bufsize + header + size)
DemoMoveBuf ();
}
if (demo.dbuf->h == NULL || demo.dbuf->h->type != type
|| demo.dbuf->h->to != to || demo.dbuf->h->full) {
DemoSetBuf (type, to);
}
if (demo.dbuf->h->size + size > MAX_MSGLEN) {
demo.dbuf->h->full = 1;
DemoSetBuf (type, to);
}
// we have to make room for new data
if (demo.dbuf->sz.cursize != demo.dbuf->bufsize) {
p = demo.dbuf->sz.data + demo.dbuf->sz.cursize;
memmove (p + size, p, demo.dbuf->bufsize - demo.dbuf->sz.cursize);
}
demo.dbuf->bufsize += size;
demo.dbuf->h->size += size;
if ((demobuffer->end += size) > demobuffer->last)
demobuffer->last = demobuffer->end;
}
/*
SV_WriteDemoMessage
Dumps the current net message, prefixed by the length and view angles
*/
void
SV_WriteDemoMessage (sizebuf_t *msg, int type, int to, float time)
{
int len, i, msec;
byte c;
static double prevtime;
if (!sv.demorecording)
return;
msec = (time - prevtime) * 1000;
prevtime += msec * 0.001;
if (msec > 255)
msec = 255;
if (msec < 2)
msec = 0;
c = msec;
demo.size += DWRITE (&c, sizeof (c), demo.dest);
if (demo.lasttype != type || demo.lastto != to) {
demo.lasttype = type;
demo.lastto = to;
switch (demo.lasttype) {
case dem_all:
c = dem_all;
demo.size += DWRITE (&c, sizeof (c), demo.dest);
break;
case dem_multiple:
c = dem_multiple;
demo.size += DWRITE (&c, sizeof (c), demo.dest);
i = LittleLong (demo.lastto);
demo.size += DWRITE (&i, sizeof (i), demo.dest);
break;
case dem_single:
case dem_stats:
c = demo.lasttype + (demo.lastto << 3);
demo.size += DWRITE (&c, sizeof (c), demo.dest);
break;
default:
SV_Stop_f ();
Con_Printf ("bad demo message type:%d", type);
return;
}
} else {
c = dem_read;
demo.size += DWRITE (&c, sizeof (c), demo.dest);
}
len = LittleLong (msg->cursize);
demo.size += DWRITE (&len, 4, demo.dest);
demo.size += DWRITE (msg->data, msg->cursize, demo.dest);
if (demo.disk)
Qflush (demo.file);
else if (demo.size - demo_size > demo_max_size) {
demo_size = demo.size;
demo.mfile -= 0x80000;
Qwrite (demo.file, svs.demomem, 0x80000);
Qflush (demo.file);
memmove (svs.demomem, svs.demomem + 0x80000, demo.size - 0x80000);
}
}
/*
SV_DemoWritePackets
Interpolates to get exact players position for current frame
and writes packets to the disk/memory
*/
static float
adjustangle (float current, float ideal, float fraction)
{
float move;
move = ideal - current;
if (ideal > current) {
if (move >= 180)
move = move - 360;
} else {
if (move <= -180)
move = move + 360;
}
move *= fraction;
return (current + move);
}
void
SV_DemoWritePackets (int num)
{
demo_frame_t *frame, *nextframe;
demo_client_t *cl, *nextcl = 0;
int i, j, flags;
qboolean valid;
double time, playertime, nexttime;
float f;
vec3_t origin, angles;
sizebuf_t msg;
byte msg_buf[MAX_MSGLEN];
demoinfo_t *demoinfo;
if (!sv.demorecording)
return;
msg.data = msg_buf;
msg.maxsize = sizeof (msg_buf);
if (num > demo.parsecount - demo.lastwritten + 1)
num = demo.parsecount - demo.lastwritten + 1;
// 'num' frames to write
for (; num; num--, demo.lastwritten++) {
frame = &demo.frames[demo.lastwritten & DEMO_FRAMES_MASK];
time = frame->time;
nextframe = frame;
msg.cursize = 0;
demo.dbuf = &frame->buf;
// find two frames
// one before the exact time (time - msec) and one after,
// then we can interpolte exact position for current frame
for (i = 0, cl = frame->clients, demoinfo = demo.info; i < MAX_CLIENTS;
i++, cl++, demoinfo++) {
if (cl->parsecount != demo.lastwritten)
continue; // not valid
nexttime = playertime = time - cl->sec;
for (j = demo.lastwritten + 1, valid = false;
nexttime < time && j < demo.parsecount; j++) {
nextframe = &demo.frames[j & DEMO_FRAMES_MASK];
nextcl = &nextframe->clients[i];
if (nextcl->parsecount != j)
break; // disconnected?
if (nextcl->fixangle)
break; // respawned, or walked into
// teleport, do not interpolate!
if (!(nextcl->flags & DF_DEAD) && (cl->flags & DF_DEAD))
break; // respawned, do not interpolate
nexttime = nextframe->time - nextcl->sec;
if (nexttime >= time) {
// good, found what we were looking for
valid = true;
break;
}
}
if (valid) {
f = (time - nexttime) / (nexttime - playertime);
for (j = 0; j < 3; j++) {
angles[j] = adjustangle (cl->info.angles[j],
nextcl->info.angles[j], 1.0 + f);
origin[j] = (nextcl->info.origin[j]
+ f * (nextcl->info.origin[j]
- cl->info.origin[j]));
}
} else {
VectorCopy (cl->info.origin, origin);
VectorCopy (cl->info.angles, angles);
}
// now write it to buf
flags = cl->flags;
if (cl->fixangle) {
demo.fixangletime[i] = cl->cmdtime;
}
for (j = 0; j < 3; j++)
if (origin[j] != demoinfo->origin[j])
flags |= DF_ORIGIN << j;
if (cl->fixangle || demo.fixangletime[i] != cl->cmdtime) {
for (j = 0; j < 3; j++)
if (angles[j] != demoinfo->angles[j])
flags |= DF_ANGLES << j;
}
if (cl->info.model != demoinfo->model)
flags |= DF_MODEL;
if (cl->info.effects != demoinfo->effects)
flags |= DF_EFFECTS;
if (cl->info.skinnum != demoinfo->skinnum)
flags |= DF_SKINNUM;
if (cl->info.weaponframe != demoinfo->weaponframe)
flags |= DF_WEAPONFRAME;
MSG_WriteByte (&msg, svc_playerinfo);
MSG_WriteByte (&msg, i);
MSG_WriteShort (&msg, flags);
MSG_WriteByte (&msg, cl->frame);
for (j = 0; j < 3; j++)
if (flags & (DF_ORIGIN << j))
MSG_WriteCoord (&msg, origin[j]);
for (j = 0; j < 3; j++)
if (flags & (DF_ANGLES << j))
MSG_WriteAngle16 (&msg, angles[j]);
if (flags & DF_MODEL)
MSG_WriteByte (&msg, cl->info.model);
if (flags & DF_SKINNUM)
MSG_WriteByte (&msg, cl->info.skinnum);
if (flags & DF_EFFECTS)
MSG_WriteByte (&msg, cl->info.effects);
if (flags & DF_WEAPONFRAME)
MSG_WriteByte (&msg, cl->info.weaponframe);
VectorCopy (cl->info.origin, demoinfo->origin);
VectorCopy (cl->info.angles, demoinfo->angles);
demoinfo->skinnum = cl->info.skinnum;
demoinfo->effects = cl->info.effects;
demoinfo->weaponframe = cl->info.weaponframe;
demoinfo->model = cl->info.model;
}
// this goes first to reduce demo size a bit
SV_DemoWriteToDisk (demo.lasttype, demo.lastto, (float) time);
SV_DemoWriteToDisk (0, 0, (float) time); // now goes the rest
if (msg.cursize)
SV_WriteDemoMessage (&msg, dem_all, 0, (float) time);
}
if (demo.lastwritten > demo.parsecount)
demo.lastwritten = demo.parsecount;
demo.dbuf = &demo.frames[demo.parsecount & DEMO_FRAMES_MASK].buf;
demo.dbuf->sz.maxsize = MAXSIZE + demo.dbuf->bufsize;
}
static int
memwrite (QFile * _mem, const void *buffer, int size)
{
int i;
byte **mem = (byte **) _mem;
const byte *buf;
for (i = size, buf = buffer; i; i--)
*(*mem)++ = *buf++;
return size;
}
static qboolean
SV_InitRecord (void)
{
if (!USACACHE) {
dwrite = &Qwrite;
demo.dest = demo.file;
demo.disk = true;
} else {
dwrite = &memwrite;
demo.mfile = svs.demomem;
demo.dest = &demo.mfile;
}
demo_size = 0;
return true;
}
/*
SV_Stop
stop recording a demo
*/
void
SV_Stop (int reason)
{
if (!sv.demorecording) {
Con_Printf ("Not recording a demo.\n");
return;
}
if (reason == 2) {
char path[MAX_OSPATH];
// stop and remove
if (demo.disk)
Qclose (demo.file);
sprintf (path, "%s/%s/%s/%s", fs_userpath->string, qfs_gamedir->dir.def, demo.path->str, demo.name->str);
unlink (path);
strcpy (path + strlen (path) - 3, "txt");
unlink (path);
demo.file = NULL;
sv.demorecording = false;
SV_BroadcastPrintf (PRINT_CHAT,
"Server recording canceled, demo removed\n");
Cvar_Set (serverdemo, "");
return;
}
// write a disconnect message to the demo file
// clearup to be sure message will fit
demo.dbuf->sz.cursize = 0;
demo.dbuf->h = NULL;
demo.dbuf->bufsize = 0;
DemoWrite_Begin (dem_all, 0, 2 + strlen ("EndOfDemo"));
MSG_WriteByte ((sizebuf_t *) demo.dbuf, svc_disconnect);
MSG_WriteString ((sizebuf_t *) demo.dbuf, "EndOfDemo");
SV_DemoWritePackets (demo.parsecount - demo.lastwritten + 1);
// finish up
if (!demo.disk) {
Qwrite (demo.file, svs.demomem, demo.size - demo_size);
Qflush (demo.file);
}
Qclose (demo.file);
demo.file = NULL;
sv.demorecording = false;
if (!reason)
SV_BroadcastPrintf (PRINT_CHAT, "Server recording completed\n");
else
SV_BroadcastPrintf (PRINT_CHAT,
"Server recording stoped\nMax demo size exceeded\n");
/*
if (sv_onrecordfinish->string[0]) {
extern redirect_t sv_redirected;
int old = sv_redirected;
char path[MAX_OSPATH];
char *p;
if ((p = strstr (sv_onrecordfinish->string, " ")) != NULL)
*p = 0; // strip parameters
strcpy (path, demo.name->str);
strcpy (path + strlen (demo.name->str) - 3, "txt");
sv_redirected = RD_NONE; // onrecord script is called always
// from the console
Cmd_TokenizeString (va ("script %s \"%s\" \"%s\" \"%s\" %s",
sv_onrecordfinish->string, demo.path->str,
serverdemo->string,
path, p != NULL ? p + 1 : ""));
if (p)
*p = ' ';
SV_Script_f ();
sv_redirected = old;
}
*/
Cvar_Set (serverdemo, "");
}
void
SV_Stop_f (void)
{
SV_Stop (0);
}
/*
SV_Cancel_f
Stops recording, and removes the demo
*/
static void
SV_Cancel_f (void)
{
SV_Stop (2);
}
/*
SV_WriteDemoMessage
Dumps the current net message, prefixed by the length and view angles
*/
static void
SV_WriteRecordDemoMessage (sizebuf_t *msg, int seq)
{
int len;
byte c;
if (!sv.demorecording)
return;
c = 0;
demo.size += DWRITE (&c, sizeof (c), demo.dest);
c = dem_read;
demo.size += DWRITE (&c, sizeof (c), demo.dest);
len = LittleLong (msg->cursize);
demo.size += DWRITE (&len, 4, demo.dest);
demo.size += DWRITE (msg->data, msg->cursize, demo.dest);
if (demo.disk)
Qflush (demo.file);
}
static void
SV_WriteSetDemoMessage (void)
{
int len;
byte c;
// Con_Printf ("write: %ld bytes, %4.4f\n", msg->cursize, realtime);
if (!sv.demorecording)
return;
c = 0;
demo.size += DWRITE (&c, sizeof (c), demo.dest);
c = dem_set;
demo.size += DWRITE (&c, sizeof (c), demo.dest);
len = LittleLong (0);
demo.size += DWRITE (&len, 4, demo.dest);
len = LittleLong (0);
demo.size += DWRITE (&len, 4, demo.dest);
if (demo.disk)
Qflush (demo.file);
}
static const char *
SV_PrintTeams (void)
{
char teams[MAX_CLIENTS][128];
char *p;
int i, j, numcl = 0, numt = 0;
client_t *clients[MAX_CLIENTS];
char buf[2048] = { 0 };
const char *team;
// count teams and players
for (i = 0; i < MAX_CLIENTS; i++) {
if (svs.clients[i].state != cs_spawned)
continue;
if (svs.clients[i].spectator)
continue;
team = Info_ValueForKey (svs.clients[i].userinfo, "team");
clients[numcl++] = &svs.clients[i];
for (j = 0; j < numt; j++)
if (!strcmp (team, teams[j]))
break;
if (j != numt)
continue;
strcpy (teams[numt++], team);
}
// create output
if (numcl == 2) // duel
{
sprintf (buf, "team1 %s\nteam2 %s\n", clients[0]->name,
clients[1]->name);
} else if (!teamplay->int_val) // ffa
{
sprintf (buf, "players:\n");
for (i = 0; i < numcl; i++)
sprintf (buf + strlen (buf), " %s\n", clients[i]->name);
} else { // teamplay
for (j = 0; j < numt; j++) {
sprintf (buf + strlen (buf), "team %s:\n", teams[j]);
for (i = 0; i < numcl; i++) {
team = Info_ValueForKey (svs.clients[i].userinfo, "team");
if (!strcmp (team, teams[j]))
sprintf (buf + strlen (buf), " %s\n", clients[i]->name);
}
}
}
if (!numcl)
return "\n";
for (p = buf; *p; p++)
*p = sys_char_map[(byte) * p];
return va ("%s", buf);
}
static int
make_info_string_filter (const char *key)
{
return *key == '_' || !Info_FilterForKey (key, client_info_filters);
}
static void
SV_Record (char *name)
{
sizebuf_t buf;
char buf_data[MAX_MSGLEN];
int n, i;
char path[MAX_OSPATH];
const char *info;
dstring_t *tn = demo.name, *tp = demo.path;
client_t *player;
const char *gamedir, *s;
int seq = 1;
memset (&demo, 0, sizeof (demo));
for (i = 0; i < UPDATE_BACKUP; i++)
demo.recorder.frames[i].entities.entities = demo_entities[i];
dstring_clearstr (demo.name = tn);
dstring_clearstr (demo.path = tp);
DemoBuffer_Init (&demo.dbuffer, demo.buffer, sizeof (demo.buffer));
DemoSetMsgBuf (NULL, &demo.frames[0].buf);
demo.datagram.maxsize = sizeof (demo.datagram_data);
demo.datagram.data = demo.datagram_data;
demo.file = Qopen (name, "wb");
if (!demo.file) {
Con_Printf ("ERROR: couldn't open %s\n", name);
return;
}
SV_InitRecord ();
s = name + strlen (name);
while (*s != '/')
s--;
dstring_clearstr (demo.name);
dstring_clearstr (demo.path);
dstring_appendstr (demo.name, s + 1);
dstring_appendstr (demo.path, sv_demoDir->string);
if (demo.path->size < 2)
dstring_appendstr (demo.path, ".");
SV_BroadcastPrintf (PRINT_CHAT, "Server starts recording (%s):\n%s\n",
demo.disk ? "disk" : "memory", demo.name->str);
Cvar_Set (serverdemo, demo.name->str);
strcpy (path, name);
strcpy (path + strlen (path) - 3, "txt");
if (sv_demotxt->int_val) {
QFile *f;
f = Qopen (path, "w+t");
if (f != NULL) {
char buf[2000];
char date[20];
time_t tim;
time (&tim);
strftime (date, 19, "%Y-%m-%d-%H-%M", localtime (&tim));
sprintf (buf, "date %s\nmap %s\nteamplay %d\ndeathmatch %d\n"
"timelimit %d\n%s",
date, sv.name, teamplay->int_val,
deathmatch->int_val, timelimit->int_val, SV_PrintTeams ());
Qwrite (f, buf, strlen (buf));
Qflush (f);
Qclose (f);
}
} else
unlink (path);
sv.demorecording = true;
demo.pingtime = demo.time = sv.time;
/*-------------------------------------------------*/
// 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
gamedir = Info_ValueForKey (svs.info, "*gamedir");
if (!gamedir[0])
gamedir = "qw";
MSG_WriteByte (&buf, svc_serverdata);
MSG_WriteLong (&buf, PROTOCOL_VERSION);
MSG_WriteLong (&buf, svs.spawncount);
MSG_WriteString (&buf, gamedir);
MSG_WriteFloat (&buf, sv.time);
// send full levelname
MSG_WriteString (&buf, PR_GetString (&sv_pr_state,
SVstring (sv.edicts, message)));
// 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",
Info_MakeString (svs.info, 0)));
// flush packet
SV_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
// soundlist
MSG_WriteByte (&buf, svc_soundlist);
MSG_WriteByte (&buf, 0);
n = 0;
s = sv.sound_precache[n + 1];
while (s) {
MSG_WriteString (&buf, s);
if (buf.cursize > MAX_MSGLEN / 2) {
MSG_WriteByte (&buf, 0);
MSG_WriteByte (&buf, n);
SV_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
MSG_WriteByte (&buf, svc_soundlist);
MSG_WriteByte (&buf, n + 1);
}
n++;
s = sv.sound_precache[n + 1];
}
if (buf.cursize) {
MSG_WriteByte (&buf, 0);
MSG_WriteByte (&buf, 0);
SV_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
}
// modellist
MSG_WriteByte (&buf, svc_modellist);
MSG_WriteByte (&buf, 0);
n = 0;
s = sv.model_precache[n + 1];
while (s) {
MSG_WriteString (&buf, s);
if (buf.cursize > MAX_MSGLEN / 2) {
MSG_WriteByte (&buf, 0);
MSG_WriteByte (&buf, n);
SV_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
MSG_WriteByte (&buf, svc_modellist);
MSG_WriteByte (&buf, n + 1);
}
n++;
s = sv.model_precache[n + 1];
}
if (buf.cursize) {
MSG_WriteByte (&buf, 0);
MSG_WriteByte (&buf, 0);
SV_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
}
// prespawn
for (n = 0; n < sv.num_signon_buffers; n++) {
SZ_Write (&buf, sv.signon_buffers[n], sv.signon_buffer_size[n]);
if (buf.cursize > MAX_MSGLEN / 2) {
SV_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
}
}
MSG_WriteByte (&buf, svc_stufftext);
MSG_WriteString (&buf, va ("cmd spawn %i 0\n", svs.spawncount));
if (buf.cursize) {
SV_WriteRecordDemoMessage (&buf, seq++);
SZ_Clear (&buf);
}
// send current status of all other players
for (i = 0; i < MAX_CLIENTS; i++) {
player = svs.clients + i;
MSG_WriteByte (&buf, svc_updatefrags);
MSG_WriteByte (&buf, i);
MSG_WriteShort (&buf, player->old_frags);
MSG_WriteByte (&buf, svc_updateping);
MSG_WriteByte (&buf, i);
MSG_WriteShort (&buf, SV_CalcPing (player));
MSG_WriteByte (&buf, svc_updatepl);
MSG_WriteByte (&buf, i);
MSG_WriteByte (&buf, player->lossage);
MSG_WriteByte (&buf, svc_updateentertime);
MSG_WriteByte (&buf, i);
MSG_WriteFloat (&buf, realtime - player->connection_started);
info = player->userinfo ? Info_MakeString (player->userinfo,
make_info_string_filter)
: "";
MSG_WriteByte (&buf, svc_updateuserinfo);
MSG_WriteByte (&buf, i);
MSG_WriteLong (&buf, player->userid);
MSG_WriteString (&buf, info);
if (buf.cursize > MAX_MSGLEN / 2) {
SV_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, sv.lightstyles[i]);
}
// 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"));
SV_WriteRecordDemoMessage (&buf, seq++);
SV_WriteSetDemoMessage ();
// done
}
/*
SV_CleanName
Cleans the demo name, removes restricted chars, makes name lowercase
*/
static char *
SV_CleanName (const unsigned char *name)
{
static char text[1024];
char *out = text;
*out = sys_char_map[*name++];
while (*name)
if (*out == '_' && sys_char_map[*name] == '_')
name++;
else
*++out = sys_char_map[*name++];
*++out = 0;
return text;
}
#define MAX_DEMO_NAME 64 // FIXME
/*
SV_Record_f
record <demoname>
*/
static void
SV_Record_f (void)
{
dstring_t *name = dstring_newstr ();
if (Cmd_Argc () != 2) {
Con_Printf ("record <demoname>\n");
return;
}
if (sv.state != ss_active) {
Con_Printf ("Not active yet.\n");
return;
}
if (sv.demorecording)
SV_Stop_f ();
dsprintf (name, "%s/%s/%s/%s%s%s", fs_userpath->string, qfs_gamedir->dir.def, sv_demoDir->string,
sv_demoPrefix->string, SV_CleanName (Cmd_Argv (1)),
sv_demoSuffix->string);
QFS_CreatePath (name->str);
// open the demo file
name->size += 4;
dstring_adjust (name);
QFS_DefaultExtension (name->str, ".mvd");
SV_Record (name->str);
dstring_delete (name);
}
/*
SV_EasyRecord_f
easyrecord [demoname]
*/
static int
Dem_CountPlayers (void)
{
int i, count;
count = 0;
for (i = 0; i < MAX_CLIENTS; i++) {
if (svs.clients[i].name[0] && !svs.clients[i].spectator)
count++;
}
return count;
}
static const char *
Dem_Team (int num)
{
int i;
static const char *lastteam[2];
qboolean first = true;
client_t *client;
static int index = 0;
const char *team;
index = 1 - index;
for (i = 0, client = svs.clients; num && i < MAX_CLIENTS; i++, client++) {
if (!client->name[0] || client->spectator)
continue;
team = Info_ValueForKey (svs.clients[i].userinfo, "team");
if (first || strcmp (lastteam[index], team)) {
first = false;
num--;
lastteam[index] = team;
}
}
if (num)
return "";
return lastteam[index];
}
static const char *
Dem_PlayerName (int num)
{
int i;
client_t *client;
for (i = 0, client = svs.clients; i < MAX_CLIENTS; i++, client++) {
if (!client->name[0] || client->spectator)
continue;
if (!--num)
return client->name;
}
return "";
}
static void
SV_EasyRecord_f (void)
{
dstring_t *name = dstring_newstr ();
dstring_t *name2 = dstring_newstr ();
int i;
QFile *f;
if (Cmd_Argc () > 2) {
Con_Printf ("easyrecord [demoname]\n");
return;
}
if (sv.demorecording)
SV_Stop_f ();
if (Cmd_Argc () == 2)
dsprintf (name, "%s", Cmd_Argv (1));
else {
// guess game type and write demo name
i = Dem_CountPlayers ();
if (teamplay->int_val && i > 2) {
// Teamplay
dsprintf (name, "team_%s_vs_%s_%s",
Dem_Team (1), Dem_Team (2), sv.name);
} else {
if (i == 2) {
// Duel
dsprintf (name, "duel_%s_vs_%s_%s",
Dem_PlayerName (1), Dem_PlayerName (2), sv.name);
} else {
// FFA
dsprintf (name, "ffa_%s (%d)", sv.name, i);
}
}
}
// Make sure the filename doesn't contain illegal characters
dsprintf (name2, "%s/%s/%s/%s%s%s", fs_userpath->string,
qfs_gamedir->dir.def, sv_demoDir->string,
sv_demoPrefix->string, SV_CleanName (name->str),
sv_demoSuffix->string);
QFS_CreatePath (name2->str);
// find a filename that doesn't exist yet
dsprintf (name, "%s", name2->str);
name->size += 4;
dstring_adjust (name);
QFS_DefaultExtension (name->str, ".mvd");
if ((f = Qopen (name->str, "rb")) == 0)
f = Qopen (va ("%s.gz", name->str), "rb");
if (f) {
i = 1;
do {
Qclose (f);
dsprintf (name, "%s_%02i", name2->str, i);
name->size += 4;
dstring_adjust (name);
QFS_DefaultExtension (name->str, ".mvd");
if ((f = Qopen (name->str, "rb")) == 0)
f = Qopen (va ("%s.gz", name->str), "rb");
i++;
} while (f);
}
SV_Record (name->str);
dstring_delete (name);
dstring_delete (name2);
}
static void
SV_DemoList_f (void)
{
/*
float f;
int i, j, show;
dir_t dir;
file_t *list;
Con_Printf ("content of %s/%s/%s/ *.mvd\n", fs_userpath->string, qfs_gamedir->dir.def, sv_demoDir->string);
dir = Sys_listdir (va ("%s/%s/%s", fs_userpath->string, qfs_gamedir->dir.def, sv_demoDir->string), ".mvd");
list = dir.files;
if (!list->name[0]) {
Con_Printf ("no demos\n");
}
for (i = 1; list->name[0]; i++, list++) {
for (j = 1; j < Cmd_Argc (); j++)
if (strstr (list->name, Cmd_Argv (j)) == NULL)
break;
show = Cmd_Argc () == j;
if (show) {
if (sv.demorecording && !strcmp (list->name, demo.name->str))
Con_Printf ("*%d: %s %dk\n", i, list->name, demo.size / 1024);
else
Con_Printf ("%d: %s %dk\n", i, list->name, list->size / 1024);
}
}
if (sv.demorecording)
dir.size += demo.size;
Con_Printf ("\ndirectory size: %.1fMB\n", (float) dir.size / (1024 * 1024));
if (sv_demoMaxDirSize->int_val) {
f = (sv_demoMaxDirSize->int_val * 1024 - dir.size) / (1024 * 1024);
if (f < 0)
f = 0;
Con_Printf ("space available: %.1fMB\n", f);
}
*/
}
static char *
SV_DemoNum (int num)
{
/*
file_t *list;
dir_t dir;
dir = Sys_listdir (va ("%s/%s/%s", fs_userpath->string, qfs_gamedir->dir.def, sv_demoDir->string), ".mvd");
list = dir.files;
if (num <= 0)
return NULL;
num--;
while (list->name[0] && num) {
list++;
num--;
};
if (list->name[0])
return list->name;
*/
return NULL;
}
static char *
SV_DemoName2Txt (char *name)
{
char s[MAX_OSPATH];
if (!name)
return NULL;
strcpy (s, name);
if (strstr (s, ".mvd.gz") != NULL)
strcpy (s + strlen (s) - 6, "txt");
else
strcpy (s + strlen (s) - 3, "txt");
return va ("%s", s);
}
static char *
SV_DemoTxTNum (int num)
{
return SV_DemoName2Txt (SV_DemoNum (num));
}
static void
SV_DemoRemove_f (void)
{
dstring_t *name = dstring_newstr ();
const char *ptr;
char path[MAX_OSPATH];
if (Cmd_Argc () != 2) {
Con_Printf ("rmdemo <demoname> - removes the demo\n"
"rmdemo *<token> - removes demo with <token> in "
"the name\n"
"rmdemo * - removes all demos\n");
return;
}
ptr = Cmd_Argv (1);
/*
if (*ptr == '*') {
dir_t dir;
file_t *list;
int i;
// remove all demos with specified token
ptr++;
dir =
Sys_listdir (va ("%s/%s/%s", fs_userpath->string, qfs_gamedir->dir.def, sv_demoDir->string), ".mvd");
list = dir.files;
for (i = 0; list->name[0]; list++) {
if (strstr (list->name, ptr)) {
if (sv.demorecording && !strcmp (list->name, demo.name->str))
SV_Stop_f ();
// stop recording first;
sprintf (path, "%s/%s/%s/%s", fs_userpath->string, qfs_gamedir->dir.def, sv_demoDir->string,
list->name);
if (!unlink (path)) {
Con_Printf ("removing %s...\n", list->name);
i++;
}
unlink (SV_DemoName2Txt (path));
}
}
if (i) {
Con_Printf ("%d demos removed\n", i);
} else {
Con_Printf ("no matching found\n");
}
return;
}
*/
dsprintf (name, "%s", Cmd_Argv (1));
QFS_DefaultExtension (name->str, ".mvd");
sprintf (path, "%s/%s/%s/%s", fs_userpath->string, qfs_gamedir->dir.def, sv_demoDir->string, name->str);
if (sv.demorecording && !strcmp (name->str, demo.name->str))
SV_Stop_f ();
if (!unlink (path)) {
Con_Printf ("demo %s succesfully removed\n", name->str);
/*
if (*sv_ondemoremove->string) {
extern redirect_t sv_redirected;
int old = sv_redirected;
// this script is called always from the console
sv_redirected = RD_NONE;
Cmd_TokenizeString (va ("script %s \"%s\" \"%s\"",
sv_ondemoremove->string,
sv_demoDir->string, name->str));
SV_Script_f ();
sv_redirected = old;
}
*/
} else
Con_Printf ("unable to remove demo %s\n", name->str);
unlink (SV_DemoName2Txt (path));
}
static void
SV_DemoRemoveNum_f (void)
{
int num;
const char *val, *name;
char path[MAX_OSPATH];
if (Cmd_Argc () != 2) {
Con_Printf ("rmdemonum <#>\n");
return;
}
val = Cmd_Argv (1);
if ((num = atoi (val)) == 0 && val[0] != '0') {
Con_Printf ("rmdemonum <#>\n");
return;
}
name = SV_DemoNum (num);
if (name != NULL) {
if (sv.demorecording && !strcmp (name, demo.name->str))
SV_Stop_f ();
sprintf (path, "%s/%s/%s/%s", fs_userpath->string, qfs_gamedir->dir.def, sv_demoDir->string, name);
if (!unlink (path)) {
Con_Printf ("demo %s succesfully removed\n", name);
/*
if (*sv_ondemoremove->string) {
extern redirect_t sv_redirected;
int old = sv_redirected;
// this script is called always from the console
sv_redirected = RD_NONE;
Cmd_TokenizeString (va ("script %s \"%s\" \"%s\"",
sv_ondemoremove->string,
sv_demoDir->string, name));
SV_Script_f ();
sv_redirected = old;
}
*/
} else
Con_Printf ("unable to remove demo %s\n", name);
unlink (SV_DemoName2Txt (path));
} else
Con_Printf ("invalid demo num\n");
}
static void
SV_DemoInfoAdd_f (void)
{
const char *name = 0, *args;
char path[MAX_OSPATH];
QFile *f;
if (Cmd_Argc () < 3) {
Con_Printf ("usage:demoInfoAdd <demonum> <info string>\n"
"<demonum> = * for currently recorded demo\n");
return;
}
if (!strcmp (Cmd_Argv (1), "*")) {
if (!sv.demorecording) {
Con_Printf ("Not recording demo!\n");
return;
}
sprintf (path, "%s/%s/%s/%s", fs_userpath->string, qfs_gamedir->dir.def, demo.path->str,
SV_DemoName2Txt (demo.name->str));
} else {
name = SV_DemoTxTNum (atoi (Cmd_Argv (1)));
if (!name) {
Con_Printf ("invalid demo num\n");
return;
}
sprintf (path, "%s/%s/%s/%s", fs_userpath->string, qfs_gamedir->dir.def, sv_demoDir->string, name);
}
if ((f = Qopen (path, "a+t")) == NULL) {
Con_Printf ("faild to open the file\n");
return;
}
// skip demonum
args = Cmd_Args (1);
while (*args > 32)
args++;
while (*args && *args <= 32)
args++;
Qwrite (f, args, strlen (args));
Qwrite (f, "\n", 1);
Qflush (f);
Qclose (f);
}
static void
SV_DemoInfoRemove_f (void)
{
char *name = 0, path[MAX_OSPATH];
if (Cmd_Argc () < 2) {
Con_Printf ("usage:demoInfoRemove <demonum>\n"
"<demonum> = * for currently recorded demo\n");
return;
}
if (!strcmp (Cmd_Argv (1), "*")) {
if (!sv.demorecording) {
Con_Printf ("Not recording demo!\n");
return;
}
sprintf (path, "%s/%s/%s/%s", fs_userpath->string, qfs_gamedir->dir.def, demo.path->str,
SV_DemoName2Txt (demo.name->str));
} else {
name = SV_DemoTxTNum (atoi (Cmd_Argv (1)));
if (!name) {
Con_Printf ("invalid demo num\n");
return;
}
sprintf (path, "%s/%s/%s/%s", fs_userpath->string, qfs_gamedir->dir.def, sv_demoDir->string, name);
}
if (unlink (path))
Con_Printf ("failed to remove the file\n");
else
Con_Printf ("file removed\n");
}
static void
SV_DemoInfo_f (void)
{
const char *buf;
QFile *f = NULL;
char *name = 0, path[MAX_OSPATH];
if (Cmd_Argc () < 2) {
Con_Printf ("usage:demoinfo <demonum>\n"
"<demonum> = * for currently recorded demo\n");
return;
}
if (!strcmp (Cmd_Argv (1), "*")) {
if (!sv.demorecording) {
Con_Printf ("Not recording demo!\n");
return;
}
sprintf (path, "%s/%s/%s/%s", fs_userpath->string, qfs_gamedir->dir.def, demo.path->str,
SV_DemoName2Txt (demo.name->str));
} else {
name = SV_DemoTxTNum (atoi (Cmd_Argv (1)));
if (!name) {
Con_Printf ("invalid demo num\n");
return;
}
sprintf (path, "%s/%s/%s/%s", fs_userpath->string, qfs_gamedir->dir.def, sv_demoDir->string, name);
}
if ((f = Qopen (path, "rt")) == NULL) {
Con_Printf ("(empty)\n");
return;
}
while ((buf = Qgetline (f))) {
Con_Printf ("%s", buf);
}
Qclose (f);
}
void
Demo_Init (void)
{
int p, size = MIN_DEMO_MEMORY;
p = COM_CheckParm ("-democache");
if (p) {
if (p < com_argc - 1)
size = atoi (com_argv[p + 1]) * 1024;
else
Sys_Error ("Memory_Init: you must specify a size in KB after "
"-democache");
}
if (size < MIN_DEMO_MEMORY) {
Con_Printf ("Minimum memory size for demo cache is %dk\n",
MIN_DEMO_MEMORY / 1024);
size = MIN_DEMO_MEMORY;
}
demo.name = dstring_newstr ();
demo.path = dstring_newstr ();
svs.demomem = Hunk_AllocName (size, "demo");
svs.demomemsize = size;
demo_max_size = size - 0x80000;
serverdemo = Cvar_Get ("serverdemo", "", CVAR_SERVERINFO, Cvar_Info,
"FIXME");
sv_demofps = Cvar_Get ("sv_demofps", "20", CVAR_NONE, 0, "FIXME");
sv_demoPings = Cvar_Get ("sv_demoPings", "3", CVAR_NONE, 0, "FIXME");
sv_demoNoVis = Cvar_Get ("sv_demoNoVis", "1", CVAR_NONE, 0, "FIXME");
sv_demoUseCache = Cvar_Get ("sv_demoUseCache", "0", CVAR_NONE, 0, "FIXME");
sv_demoCacheSize = Cvar_Get ("sv_demoCacheSize", va ("%d", size / 1024),
CVAR_ROM, 0, "FIXME");
sv_demoMaxSize = Cvar_Get ("sv_demoMaxSize", "20480", CVAR_NONE, 0,
"FIXME");
sv_demoMaxDirSize = Cvar_Get ("sv_demoMaxDirSize", "102400", CVAR_NONE, 0,
"FIXME");
sv_demoDir = Cvar_Get ("sv_demoDir", "demos", CVAR_NONE, 0, "FIXME");
sv_demoPrefix = Cvar_Get ("sv_demoPrefix", "", CVAR_NONE, 0, "FIXME");
sv_demoSuffix = Cvar_Get ("sv_demoSuffix", "", CVAR_NONE, 0, "FIXME");
sv_onrecordfinish = Cvar_Get ("sv_onrecordfinish", "", CVAR_NONE, 0, "FIXME");
sv_ondemoremove = Cvar_Get ("sv_ondemoremove", "", CVAR_NONE, 0, "FIXME");
sv_demotxt = Cvar_Get ("sv_demotxt", "1", CVAR_NONE, 0, "FIXME");
Cmd_AddCommand ("record", SV_Record_f, "FIXME");
Cmd_AddCommand ("easyrecord", SV_EasyRecord_f, "FIXME");
Cmd_AddCommand ("stop", SV_Stop_f, "FIXME");
Cmd_AddCommand ("cancel", SV_Cancel_f, "FIXME");
Cmd_AddCommand ("demolist", SV_DemoList_f, "FIXME");
Cmd_AddCommand ("rmdemo", SV_DemoRemove_f, "FIXME");
Cmd_AddCommand ("rmdemonum", SV_DemoRemoveNum_f, "FIXME");
Cmd_AddCommand ("demoInfoAdd", SV_DemoInfoAdd_f, "FIXME");
Cmd_AddCommand ("demoInfoRemove", SV_DemoInfoRemove_f, "FIXME");
Cmd_AddCommand ("demoInfo", SV_DemoInfo_f, "FIXME");
}