• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
CL_StopPlayback(void)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 */
CL_WriteDemoCmd(usercmd_t * pcmd)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 */
CL_WriteDemoMessage(sizebuf_t * msg)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 */
CL_GetDemoMessage(void)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 */
CL_GetMessage(void)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 */
CL_Stop_f(void)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 */
CL_WriteRecordDemoMessage(sizebuf_t * msg,int seq)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 
CL_WriteSetDemoMessage(void)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 */
CL_Record_f(void)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 */
CL_ReRecord_f(void)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 */
CL_PlayDemo_f(void)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 */
CL_FinishTimeDemo(void)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 */
CL_TimeDemo_f(void)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