Home | History | Annotate | Download | only in client
      1 /*
      2 Copyright (C) 1996-1997 Id Software, Inc.
      3 
      4 This program is free software; you can redistribute it and/or
      5 modify it under the terms of the GNU General Public License
      6 as published by the Free Software Foundation; either version 2
      7 of the License, or (at your option) any later version.
      8 
      9 This program is distributed in the hope that it will be useful,
     10 but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     12 
     13 See the included (GNU.txt) GNU General Public License for more details.
     14 
     15 You should have received a copy of the GNU General Public License
     16 along with this program; if not, write to the Free Software
     17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     18 
     19 */
     20 
     21 #include "quakedef.h"
     22 
     23 void CL_FinishTimeDemo (void);
     24 
     25 /*
     26 ==============================================================================
     27 
     28 DEMO CODE
     29 
     30 When a demo is playing back, all NET_SendMessages are skipped, and
     31 NET_GetMessages are read from the demo file.
     32 
     33 Whenever cl.time gets past the last received message, another message is
     34 read from the demo file.
     35 ==============================================================================
     36 */
     37 
     38 /*
     39 ==============
     40 CL_StopPlayback
     41 
     42 Called when a demo file runs out, or the user starts a game
     43 ==============
     44 */
     45 void CL_StopPlayback (void)
     46 {
     47 	if (!cls.demoplayback)
     48 		return;
     49 
     50 	fclose (cls.demofile);
     51 	cls.demofile = NULL;
     52 	cls.state = ca_disconnected;
     53 	cls.demoplayback = 0;
     54 
     55 	if (cls.timedemo)
     56 		CL_FinishTimeDemo ();
     57 }
     58 
     59 #define dem_cmd		0
     60 #define dem_read	1
     61 #define dem_set		2
     62 
     63 /*
     64 ====================
     65 CL_WriteDemoCmd
     66 
     67 Writes the current user cmd
     68 ====================
     69 */
     70 void CL_WriteDemoCmd (usercmd_t *pcmd)
     71 {
     72 	int		i;
     73 	float	fl;
     74 	byte	c;
     75 	usercmd_t cmd;
     76 
     77 //Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, realtime);
     78 
     79 	fl = LittleFloat((float)realtime);
     80 	fwrite (&fl, sizeof(fl), 1, cls.demofile);
     81 
     82 	c = dem_cmd;
     83 	fwrite (&c, sizeof(c), 1, cls.demofile);
     84 
     85 	// correct for byte order, bytes don't matter
     86 	cmd = *pcmd;
     87 
     88 	for (i = 0; i < 3; i++)
     89 		cmd.angles[i] = LittleFloat(cmd.angles[i]);
     90 	cmd.forwardmove = LittleShort(cmd.forwardmove);
     91 	cmd.sidemove    = LittleShort(cmd.sidemove);
     92 	cmd.upmove      = LittleShort(cmd.upmove);
     93 
     94 	fwrite(&cmd, sizeof(cmd), 1, cls.demofile);
     95 
     96 	for (i=0 ; i<3 ; i++)
     97 	{
     98 		fl = LittleFloat (cl.viewangles[i]);
     99 		fwrite (&fl, 4, 1, cls.demofile);
    100 	}
    101 
    102 	fflush (cls.demofile);
    103 }
    104 
    105 /*
    106 ====================
    107 CL_WriteDemoMessage
    108 
    109 Dumps the current net message, prefixed by the length and view angles
    110 ====================
    111 */
    112 void CL_WriteDemoMessage (sizebuf_t *msg)
    113 {
    114 	int		len;
    115 	float	fl;
    116 	byte	c;
    117 
    118 //Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, realtime);
    119 
    120 	if (!cls.demorecording)
    121 		return;
    122 
    123 	fl = LittleFloat((float)realtime);
    124 	fwrite (&fl, sizeof(fl), 1, cls.demofile);
    125 
    126 	c = dem_read;
    127 	fwrite (&c, sizeof(c), 1, cls.demofile);
    128 
    129 	len = LittleLong (msg->cursize);
    130 	fwrite (&len, 4, 1, cls.demofile);
    131 	fwrite (msg->data, msg->cursize, 1, cls.demofile);
    132 
    133 	fflush (cls.demofile);
    134 }
    135 
    136 /*
    137 ====================
    138 CL_GetDemoMessage
    139 
    140   FIXME...
    141 ====================
    142 */
    143 qboolean CL_GetDemoMessage (void)
    144 {
    145 	int		r, i, j;
    146 	float	f;
    147 	float	demotime;
    148 	byte	c;
    149 	usercmd_t *pcmd;
    150 
    151 	// read the time from the packet
    152 	fread(&demotime, sizeof(demotime), 1, cls.demofile);
    153 	demotime = LittleFloat(demotime);
    154 
    155 // decide if it is time to grab the next message
    156 	if (cls.timedemo) {
    157 		if (cls.td_lastframe < 0)
    158 			cls.td_lastframe = demotime;
    159 		else if (demotime > cls.td_lastframe) {
    160 			cls.td_lastframe = demotime;
    161 			// rewind back to time
    162 			fseek(cls.demofile, ftell(cls.demofile) - sizeof(demotime),
    163 					SEEK_SET);
    164 			return 0;		// allready read this frame's message
    165 		}
    166 		if (!cls.td_starttime && cls.state == ca_active) {
    167 			cls.td_starttime = Sys_DoubleTime();
    168 			cls.td_startframe = host_framecount;
    169 		}
    170 		realtime = demotime; // warp
    171 	} else if (!cl.paused && cls.state >= ca_onserver) {	// allways grab until fully connected
    172 		if (realtime + 1.0 < demotime) {
    173 			// too far back
    174 			realtime = demotime - 1.0;
    175 			// rewind back to time
    176 			fseek(cls.demofile, ftell(cls.demofile) - sizeof(demotime),
    177 					SEEK_SET);
    178 			return 0;
    179 		} else if (realtime < demotime) {
    180 			// rewind back to time
    181 			fseek(cls.demofile, ftell(cls.demofile) - sizeof(demotime),
    182 					SEEK_SET);
    183 			return 0;		// don't need another message yet
    184 		}
    185 	} else
    186 		realtime = demotime; // we're warping
    187 
    188 	if (cls.state < ca_demostart)
    189 		Host_Error ("CL_GetDemoMessage: cls.state != ca_active");
    190 
    191 	// get the msg type
    192 	fread (&c, sizeof(c), 1, cls.demofile);
    193 
    194 	switch (c) {
    195 	case dem_cmd :
    196 		// user sent input
    197 		i = cls.netchan.outgoing_sequence & UPDATE_MASK;
    198 		pcmd = &cl.frames[i].cmd;
    199 		r = fread (pcmd, sizeof(*pcmd), 1, cls.demofile);
    200 		if (r != 1)
    201 		{
    202 			CL_StopPlayback ();
    203 			return 0;
    204 		}
    205 		// byte order stuff
    206 		for (j = 0; j < 3; j++)
    207 			pcmd->angles[j] = LittleFloat(pcmd->angles[j]);
    208 		pcmd->forwardmove = LittleShort(pcmd->forwardmove);
    209 		pcmd->sidemove    = LittleShort(pcmd->sidemove);
    210 		pcmd->upmove      = LittleShort(pcmd->upmove);
    211 		cl.frames[i].senttime = demotime;
    212 		cl.frames[i].receivedtime = -1;		// we haven't gotten a reply yet
    213 		cls.netchan.outgoing_sequence++;
    214 		for (i=0 ; i<3 ; i++)
    215 		{
    216 			r = fread (&f, 4, 1, cls.demofile);
    217 			cl.viewangles[i] = LittleFloat (f);
    218 		}
    219 		break;
    220 
    221 	case dem_read:
    222 		// get the next message
    223 		fread (&net_message.cursize, 4, 1, cls.demofile);
    224 		net_message.cursize = LittleLong (net_message.cursize);
    225 	//Con_Printf("read: %ld bytes\n", net_message.cursize);
    226 		if (net_message.cursize > MAX_MSGLEN)
    227 			Sys_Error ("Demo message > MAX_MSGLEN");
    228 		r = fread (net_message.data, net_message.cursize, 1, cls.demofile);
    229 		if (r != 1)
    230 		{
    231 			CL_StopPlayback ();
    232 			return 0;
    233 		}
    234 		break;
    235 
    236 	case dem_set :
    237 		fread (&i, 4, 1, cls.demofile);
    238 		cls.netchan.outgoing_sequence = LittleLong(i);
    239 		fread (&i, 4, 1, cls.demofile);
    240 		cls.netchan.incoming_sequence = LittleLong(i);
    241 		break;
    242 
    243 	default :
    244 		Con_Printf("Corrupted demo.\n");
    245 		CL_StopPlayback ();
    246 		return 0;
    247 	}
    248 
    249 	return 1;
    250 }
    251 
    252 /*
    253 ====================
    254 CL_GetMessage
    255 
    256 Handles recording and playback of demos, on top of NET_ code
    257 ====================
    258 */
    259 qboolean CL_GetMessage (void)
    260 {
    261 	if	(cls.demoplayback)
    262 		return CL_GetDemoMessage ();
    263 
    264 	if (!NET_GetPacket ())
    265 		return false;
    266 
    267 	CL_WriteDemoMessage (&net_message);
    268 
    269 	return true;
    270 }
    271 
    272 
    273 /*
    274 ====================
    275 CL_Stop_f
    276 
    277 stop recording a demo
    278 ====================
    279 */
    280 void CL_Stop_f (void)
    281 {
    282 	if (!cls.demorecording)
    283 	{
    284 		Con_Printf ("Not recording a demo.\n");
    285 		return;
    286 	}
    287 
    288 // write a disconnect message to the demo file
    289 	SZ_Clear (&net_message);
    290 	MSG_WriteLong (&net_message, -1);	// -1 sequence means out of band
    291 	MSG_WriteByte (&net_message, svc_disconnect);
    292 	MSG_WriteString (&net_message, "EndOfDemo");
    293 	CL_WriteDemoMessage (&net_message);
    294 
    295 // finish up
    296 	fclose (cls.demofile);
    297 	cls.demofile = NULL;
    298 	cls.demorecording = false;
    299 	Con_Printf ("Completed demo\n");
    300 }
    301 
    302 
    303 /*
    304 ====================
    305 CL_WriteDemoMessage
    306 
    307 Dumps the current net message, prefixed by the length and view angles
    308 ====================
    309 */
    310 void CL_WriteRecordDemoMessage (sizebuf_t *msg, int seq)
    311 {
    312 	int		len;
    313 	int		i;
    314 	float	fl;
    315 	byte	c;
    316 
    317 //Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, realtime);
    318 
    319 	if (!cls.demorecording)
    320 		return;
    321 
    322 	fl = LittleFloat((float)realtime);
    323 	fwrite (&fl, sizeof(fl), 1, cls.demofile);
    324 
    325 	c = dem_read;
    326 	fwrite (&c, sizeof(c), 1, cls.demofile);
    327 
    328 	len = LittleLong (msg->cursize + 8);
    329 	fwrite (&len, 4, 1, cls.demofile);
    330 
    331 	i = LittleLong(seq);
    332 	fwrite (&i, 4, 1, cls.demofile);
    333 	fwrite (&i, 4, 1, cls.demofile);
    334 
    335 	fwrite (msg->data, msg->cursize, 1, cls.demofile);
    336 
    337 	fflush (cls.demofile);
    338 }
    339 
    340 
    341 void CL_WriteSetDemoMessage (void)
    342 {
    343 	int		len;
    344 	float	fl;
    345 	byte	c;
    346 
    347 //Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, realtime);
    348 
    349 	if (!cls.demorecording)
    350 		return;
    351 
    352 	fl = LittleFloat((float)realtime);
    353 	fwrite (&fl, sizeof(fl), 1, cls.demofile);
    354 
    355 	c = dem_set;
    356 	fwrite (&c, sizeof(c), 1, cls.demofile);
    357 
    358 	len = LittleLong(cls.netchan.outgoing_sequence);
    359 	fwrite (&len, 4, 1, cls.demofile);
    360 	len = LittleLong(cls.netchan.incoming_sequence);
    361 	fwrite (&len, 4, 1, cls.demofile);
    362 
    363 	fflush (cls.demofile);
    364 }
    365 
    366 
    367 
    368 
    369 /*
    370 ====================
    371 CL_Record_f
    372 
    373 record <demoname> <server>
    374 ====================
    375 */
    376 void CL_Record_f (void)
    377 {
    378 	int		c;
    379 	char	name[MAX_OSPATH];
    380 	sizebuf_t	buf;
    381 	char	buf_data[MAX_MSGLEN];
    382 	int n, i, j;
    383 	char *s;
    384 	entity_t *ent;
    385 	entity_state_t *es, blankes;
    386 	player_info_t *player;
    387 	extern	char gamedirfile[];
    388 	int seq = 1;
    389 
    390 	c = Cmd_Argc();
    391 	if (c != 2)
    392 	{
    393 		Con_Printf ("record <demoname>\n");
    394 		return;
    395 	}
    396 
    397 	if (cls.state != ca_active) {
    398 		Con_Printf ("You must be connected to record.\n");
    399 		return;
    400 	}
    401 
    402 	if (cls.demorecording)
    403 		CL_Stop_f();
    404 
    405 	sprintf (name, "%s/%s", com_gamedir, Cmd_Argv(1));
    406 
    407 //
    408 // open the demo file
    409 //
    410 	COM_DefaultExtension (name, ".qwd");
    411 
    412 	cls.demofile = fopen (name, "wb");
    413 	if (!cls.demofile)
    414 	{
    415 		Con_Printf ("ERROR: couldn't open.\n");
    416 		return;
    417 	}
    418 
    419 	Con_Printf ("recording to %s.\n", name);
    420 	cls.demorecording = true;
    421 
    422 /*-------------------------------------------------*/
    423 
    424 // serverdata
    425 	// send the info about the new client to all connected clients
    426 	memset(&buf, 0, sizeof(buf));
    427 	buf.data = (byte*) buf_data;
    428 	buf.maxsize = sizeof(buf_data);
    429 
    430 // send the serverdata
    431 	MSG_WriteByte (&buf, svc_serverdata);
    432 	MSG_WriteLong (&buf, PROTOCOL_VERSION);
    433 	MSG_WriteLong (&buf, cl.servercount);
    434 	MSG_WriteString (&buf, gamedirfile);
    435 
    436 	if (cl.spectator)
    437 		MSG_WriteByte (&buf, cl.playernum | 128);
    438 	else
    439 		MSG_WriteByte (&buf, cl.playernum);
    440 
    441 	// send full levelname
    442 	MSG_WriteString (&buf, cl.levelname);
    443 
    444 	// send the movevars
    445 	MSG_WriteFloat(&buf, movevars.gravity);
    446 	MSG_WriteFloat(&buf, movevars.stopspeed);
    447 	MSG_WriteFloat(&buf, movevars.maxspeed);
    448 	MSG_WriteFloat(&buf, movevars.spectatormaxspeed);
    449 	MSG_WriteFloat(&buf, movevars.accelerate);
    450 	MSG_WriteFloat(&buf, movevars.airaccelerate);
    451 	MSG_WriteFloat(&buf, movevars.wateraccelerate);
    452 	MSG_WriteFloat(&buf, movevars.friction);
    453 	MSG_WriteFloat(&buf, movevars.waterfriction);
    454 	MSG_WriteFloat(&buf, movevars.entgravity);
    455 
    456 	// send music
    457 	MSG_WriteByte (&buf, svc_cdtrack);
    458 	MSG_WriteByte (&buf, 0); // none in demos
    459 
    460 	// send server info string
    461 	MSG_WriteByte (&buf, svc_stufftext);
    462 	MSG_WriteString (&buf, va("fullserverinfo \"%s\"\n", cl.serverinfo) );
    463 
    464 	// flush packet
    465 	CL_WriteRecordDemoMessage (&buf, seq++);
    466 	SZ_Clear (&buf);
    467 
    468 // soundlist
    469 	MSG_WriteByte (&buf, svc_soundlist);
    470 	MSG_WriteByte (&buf, 0);
    471 
    472 	n = 0;
    473 	s = cl.sound_name[n+1];
    474 	while (*s) {
    475 		MSG_WriteString (&buf, s);
    476 		if (buf.cursize > MAX_MSGLEN/2) {
    477 			MSG_WriteByte (&buf, 0);
    478 			MSG_WriteByte (&buf, n);
    479 			CL_WriteRecordDemoMessage (&buf, seq++);
    480 			SZ_Clear (&buf);
    481 			MSG_WriteByte (&buf, svc_soundlist);
    482 			MSG_WriteByte (&buf, n + 1);
    483 		}
    484 		n++;
    485 		s = cl.sound_name[n+1];
    486 	}
    487 	if (buf.cursize) {
    488 		MSG_WriteByte (&buf, 0);
    489 		MSG_WriteByte (&buf, 0);
    490 		CL_WriteRecordDemoMessage (&buf, seq++);
    491 		SZ_Clear (&buf);
    492 	}
    493 
    494 // modellist
    495 	MSG_WriteByte (&buf, svc_modellist);
    496 	MSG_WriteByte (&buf, 0);
    497 
    498 	n = 0;
    499 	s = cl.model_name[n+1];
    500 	while (*s) {
    501 		MSG_WriteString (&buf, s);
    502 		if (buf.cursize > MAX_MSGLEN/2) {
    503 			MSG_WriteByte (&buf, 0);
    504 			MSG_WriteByte (&buf, n);
    505 			CL_WriteRecordDemoMessage (&buf, seq++);
    506 			SZ_Clear (&buf);
    507 			MSG_WriteByte (&buf, svc_modellist);
    508 			MSG_WriteByte (&buf, n + 1);
    509 		}
    510 		n++;
    511 		s = cl.model_name[n+1];
    512 	}
    513 	if (buf.cursize) {
    514 		MSG_WriteByte (&buf, 0);
    515 		MSG_WriteByte (&buf, 0);
    516 		CL_WriteRecordDemoMessage (&buf, seq++);
    517 		SZ_Clear (&buf);
    518 	}
    519 
    520 // spawnstatic
    521 
    522 	for (i = 0; i < cl.num_statics; i++) {
    523 		ent = cl_static_entities + i;
    524 
    525 		MSG_WriteByte (&buf, svc_spawnstatic);
    526 
    527 		for (j = 1; j < MAX_MODELS; j++)
    528 			if (ent->model == cl.model_precache[j])
    529 				break;
    530 		if (j == MAX_MODELS)
    531 			MSG_WriteByte (&buf, 0);
    532 		else
    533 			MSG_WriteByte (&buf, j);
    534 
    535 		MSG_WriteByte (&buf, ent->frame);
    536 		MSG_WriteByte (&buf, 0);
    537 		MSG_WriteByte (&buf, ent->skinnum);
    538 		for (j=0 ; j<3 ; j++)
    539 		{
    540 			MSG_WriteCoord (&buf, ent->origin[j]);
    541 			MSG_WriteAngle (&buf, ent->angles[j]);
    542 		}
    543 
    544 		if (buf.cursize > MAX_MSGLEN/2) {
    545 			CL_WriteRecordDemoMessage (&buf, seq++);
    546 			SZ_Clear (&buf);
    547 		}
    548 	}
    549 
    550 // spawnstaticsound
    551 	// static sounds are skipped in demos, life is hard
    552 
    553 // baselines
    554 
    555 	memset(&blankes, 0, sizeof(blankes));
    556 	for (i = 0; i < MAX_EDICTS; i++) {
    557 		es = cl_baselines + i;
    558 
    559 		if (memcmp(es, &blankes, sizeof(blankes))) {
    560 			MSG_WriteByte (&buf,svc_spawnbaseline);
    561 			MSG_WriteShort (&buf, i);
    562 
    563 			MSG_WriteByte (&buf, es->modelindex);
    564 			MSG_WriteByte (&buf, es->frame);
    565 			MSG_WriteByte (&buf, es->colormap);
    566 			MSG_WriteByte (&buf, es->skinnum);
    567 			for (j=0 ; j<3 ; j++)
    568 			{
    569 				MSG_WriteCoord(&buf, es->origin[j]);
    570 				MSG_WriteAngle(&buf, es->angles[j]);
    571 			}
    572 
    573 			if (buf.cursize > MAX_MSGLEN/2) {
    574 				CL_WriteRecordDemoMessage (&buf, seq++);
    575 				SZ_Clear (&buf);
    576 			}
    577 		}
    578 	}
    579 
    580 	MSG_WriteByte (&buf, svc_stufftext);
    581 	MSG_WriteString (&buf, va("cmd spawn %i 0\n", cl.servercount) );
    582 
    583 	if (buf.cursize) {
    584 		CL_WriteRecordDemoMessage (&buf, seq++);
    585 		SZ_Clear (&buf);
    586 	}
    587 
    588 // send current status of all other players
    589 
    590 	for (i = 0; i < MAX_CLIENTS; i++) {
    591 		player = cl.players + i;
    592 
    593 		MSG_WriteByte (&buf, svc_updatefrags);
    594 		MSG_WriteByte (&buf, i);
    595 		MSG_WriteShort (&buf, player->frags);
    596 
    597 		MSG_WriteByte (&buf, svc_updateping);
    598 		MSG_WriteByte (&buf, i);
    599 		MSG_WriteShort (&buf, player->ping);
    600 
    601 		MSG_WriteByte (&buf, svc_updatepl);
    602 		MSG_WriteByte (&buf, i);
    603 		MSG_WriteByte (&buf, player->pl);
    604 
    605 		MSG_WriteByte (&buf, svc_updateentertime);
    606 		MSG_WriteByte (&buf, i);
    607 		MSG_WriteFloat (&buf, player->entertime);
    608 
    609 		MSG_WriteByte (&buf, svc_updateuserinfo);
    610 		MSG_WriteByte (&buf, i);
    611 		MSG_WriteLong (&buf, player->userid);
    612 		MSG_WriteString (&buf, player->userinfo);
    613 
    614 		if (buf.cursize > MAX_MSGLEN/2) {
    615 			CL_WriteRecordDemoMessage (&buf, seq++);
    616 			SZ_Clear (&buf);
    617 		}
    618 	}
    619 
    620 // send all current light styles
    621 	for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
    622 	{
    623 		MSG_WriteByte (&buf, svc_lightstyle);
    624 		MSG_WriteByte (&buf, (char)i);
    625 		MSG_WriteString (&buf, cl_lightstyle[i].map);
    626 	}
    627 
    628 	for (i = 0; i < MAX_CL_STATS; i++) {
    629 		MSG_WriteByte (&buf, svc_updatestatlong);
    630 		MSG_WriteByte (&buf, i);
    631 		MSG_WriteLong (&buf, cl.stats[i]);
    632 		if (buf.cursize > MAX_MSGLEN/2) {
    633 			CL_WriteRecordDemoMessage (&buf, seq++);
    634 			SZ_Clear (&buf);
    635 		}
    636 	}
    637 
    638 #if 0
    639 	MSG_WriteByte (&buf, svc_updatestatlong);
    640 	MSG_WriteByte (&buf, STAT_TOTALMONSTERS);
    641 	MSG_WriteLong (&buf, cl.stats[STAT_TOTALMONSTERS]);
    642 
    643 	MSG_WriteByte (&buf, svc_updatestatlong);
    644 	MSG_WriteByte (&buf, STAT_SECRETS);
    645 	MSG_WriteLong (&buf, cl.stats[STAT_SECRETS]);
    646 
    647 	MSG_WriteByte (&buf, svc_updatestatlong);
    648 	MSG_WriteByte (&buf, STAT_MONSTERS);
    649 	MSG_WriteLong (&buf, cl.stats[STAT_MONSTERS]);
    650 #endif
    651 
    652 	// get the client to check and download skins
    653 	// when that is completed, a begin command will be issued
    654 	MSG_WriteByte (&buf, svc_stufftext);
    655 	MSG_WriteString (&buf, va("skins\n") );
    656 
    657 	CL_WriteRecordDemoMessage (&buf, seq++);
    658 
    659 	CL_WriteSetDemoMessage();
    660 
    661 	// done
    662 }
    663 
    664 /*
    665 ====================
    666 CL_ReRecord_f
    667 
    668 record <demoname>
    669 ====================
    670 */
    671 void CL_ReRecord_f (void)
    672 {
    673 	int		c;
    674 	char	name[MAX_OSPATH];
    675 
    676 	c = Cmd_Argc();
    677 	if (c != 2)
    678 	{
    679 		Con_Printf ("rerecord <demoname>\n");
    680 		return;
    681 	}
    682 
    683 	if (!*cls.servername) {
    684 		Con_Printf("No server to reconnect to...\n");
    685 		return;
    686 	}
    687 
    688 	if (cls.demorecording)
    689 		CL_Stop_f();
    690 
    691 	sprintf (name, "%s/%s", com_gamedir, Cmd_Argv(1));
    692 
    693 //
    694 // open the demo file
    695 //
    696 	COM_DefaultExtension (name, ".qwd");
    697 
    698 	cls.demofile = fopen (name, "wb");
    699 	if (!cls.demofile)
    700 	{
    701 		Con_Printf ("ERROR: couldn't open.\n");
    702 		return;
    703 	}
    704 
    705 	Con_Printf ("recording to %s.\n", name);
    706 	cls.demorecording = true;
    707 
    708 	CL_Disconnect();
    709 	CL_BeginServerConnect();
    710 }
    711 
    712 
    713 /*
    714 ====================
    715 CL_PlayDemo_f
    716 
    717 play [demoname]
    718 ====================
    719 */
    720 void CL_PlayDemo_f (void)
    721 {
    722 	char	name[256];
    723 
    724 	if (Cmd_Argc() != 2)
    725 	{
    726 		Con_Printf ("play <demoname> : plays a demo\n");
    727 		return;
    728 	}
    729 
    730 //
    731 // disconnect from server
    732 //
    733 	CL_Disconnect ();
    734 
    735 //
    736 // open the demo file
    737 //
    738 	strcpy (name, Cmd_Argv(1));
    739 	COM_DefaultExtension (name, ".qwd");
    740 
    741 	Con_Printf ("Playing demo from %s.\n", name);
    742 	COM_FOpenFile (name, &cls.demofile);
    743 	if (!cls.demofile)
    744 	{
    745 		Con_Printf ("ERROR: couldn't open.\n");
    746 		cls.demonum = -1;		// stop demo loop
    747 		return;
    748 	}
    749 
    750 	cls.demoplayback = true;
    751 	cls.state = ca_demostart;
    752 	Netchan_Setup (&cls.netchan, net_from, 0);
    753 	realtime = 0;
    754 }
    755 
    756 /*
    757 ====================
    758 CL_FinishTimeDemo
    759 
    760 ====================
    761 */
    762 void CL_FinishTimeDemo (void)
    763 {
    764 	int		frames;
    765 	float	time;
    766 
    767 	cls.timedemo = false;
    768 
    769 // the first frame didn't count
    770 	frames = (host_framecount - cls.td_startframe) - 1;
    771 	time = Sys_DoubleTime() - cls.td_starttime;
    772 	if (!time)
    773 		time = 1;
    774 	Con_Printf ("%i frames %5.1f seconds %5.1f fps\n", frames, time, frames/time);
    775 }
    776 
    777 /*
    778 ====================
    779 CL_TimeDemo_f
    780 
    781 timedemo [demoname]
    782 ====================
    783 */
    784 void CL_TimeDemo_f (void)
    785 {
    786 	if (Cmd_Argc() != 2)
    787 	{
    788 		Con_Printf ("timedemo <demoname> : gets demo speeds\n");
    789 		return;
    790 	}
    791 
    792 	CL_PlayDemo_f ();
    793 
    794 	if (cls.state != ca_demostart)
    795 		return;
    796 
    797 // cls.td_starttime will be grabbed at the second frame of the demo, so
    798 // all the loading time doesn't get counted
    799 
    800 	cls.timedemo = true;
    801 	cls.td_starttime = 0;
    802 	cls.td_startframe = host_framecount;
    803 	cls.td_lastframe = -1;		// get a new message this frame
    804 }
    805 
    806