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