]> git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_demo.c
Use proper built-in function for getting fraction part
[xonotic/darkplaces.git] / cl_demo.c
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 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 #ifdef CONFIG_VIDEO_CAPTURE
24 extern cvar_t cl_capturevideo;
25 extern cvar_t cl_capturevideo_demo_stop;
26 #endif
27
28 static void CL_FinishTimeDemo (void);
29
30 /*
31 ==============================================================================
32
33 DEMO CODE
34
35 When a demo is playing back, all outgoing network messages are skipped, and
36 incoming messages are read from the demo file.
37
38 Whenever cl.time gets past the last received message, another message is
39 read from the demo file.
40 ==============================================================================
41 */
42
43 /*
44 =====================
45 CL_NextDemo
46
47 Called to play the next demo in the demo loop
48 =====================
49 */
50 void CL_NextDemo (void)
51 {
52         char    str[MAX_INPUTLINE];
53
54         if (cls.demonum == -1)
55                 return;         // don't play demos
56
57         if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
58         {
59                 cls.demonum = 0;
60                 if (!cls.demos[cls.demonum][0])
61                 {
62                         Con_Print("No demos listed with startdemos\n");
63                         cls.demonum = -1;
64                         return;
65                 }
66         }
67
68         dpsnprintf (str, sizeof(str), "playdemo %s\n", cls.demos[cls.demonum]);
69         Cbuf_InsertText(cmd_local, str);
70         cls.demonum++;
71 }
72
73 /*
74 ==============
75 CL_StopPlayback
76
77 Called when a demo file runs out, or the user starts a game
78 ==============
79 */
80 // LadyHavoc: now called only by CL_Disconnect
81 void CL_StopPlayback (void)
82 {
83 #ifdef CONFIG_VIDEO_CAPTURE
84         if (cl_capturevideo_demo_stop.integer)
85                 Cvar_Set(&cvars_all, "cl_capturevideo", "0");
86 #endif
87
88         if (!cls.demoplayback)
89                 return;
90
91         FS_Close (cls.demofile);
92         cls.demoplayback = false;
93         cls.demofile = NULL;
94
95         if (cls.timedemo)
96                 CL_FinishTimeDemo ();
97
98         if (!cls.demostarting) // only quit if not starting another demo
99                 if (Sys_CheckParm("-demo") || Sys_CheckParm("-capturedemo"))
100                         host.state = host_shutdown;
101 }
102
103 /*
104 ====================
105 CL_WriteDemoMessage
106
107 Dumps the current net message, prefixed by the length and view angles
108 #====================
109 */
110 void CL_WriteDemoMessage (sizebuf_t *message)
111 {
112         int             len;
113         int             i;
114         float   f;
115
116         if (cls.demopaused) // LadyHavoc: pausedemo
117                 return;
118
119         len = LittleLong (message->cursize);
120         FS_Write (cls.demofile, &len, 4);
121         for (i=0 ; i<3 ; i++)
122         {
123                 f = LittleFloat (cl.viewangles[i]);
124                 FS_Write (cls.demofile, &f, 4);
125         }
126         FS_Write (cls.demofile, message->data, message->cursize);
127 }
128
129 /*
130 ====================
131 CL_CutDemo
132
133 Dumps the current demo to a buffer, and resets the demo to its starting point.
134 Used to insert csprogs.dat files as a download to the beginning of a demo file.
135 ====================
136 */
137 void CL_CutDemo (unsigned char **buf, fs_offset_t *filesize)
138 {
139         *buf = NULL;
140         *filesize = 0;
141
142         FS_Close(cls.demofile);
143         *buf = FS_LoadFile(cls.demoname, tempmempool, false, filesize);
144
145         // restart the demo recording
146         cls.demofile = FS_OpenRealFile(cls.demoname, "wb", false);
147         if(!cls.demofile)
148                 Sys_Error("failed to reopen the demo file");
149         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
150 }
151
152 /*
153 ====================
154 CL_PasteDemo
155
156 Adds the cut stuff back to the demo. Also frees the buffer.
157 Used to insert csprogs.dat files as a download to the beginning of a demo file.
158 ====================
159 */
160 void CL_PasteDemo (unsigned char **buf, fs_offset_t *filesize)
161 {
162         fs_offset_t startoffset = 0;
163
164         if(!*buf)
165                 return;
166
167         // skip cdtrack
168         while(startoffset < *filesize && ((char *)(*buf))[startoffset] != '\n')
169                 ++startoffset;
170         if(startoffset < *filesize)
171                 ++startoffset;
172
173         FS_Write(cls.demofile, *buf + startoffset, *filesize - startoffset);
174
175         Mem_Free(*buf);
176         *buf = NULL;
177         *filesize = 0;
178 }
179
180 /*
181 ====================
182 CL_ReadDemoMessage
183
184 Handles playback of demos
185 ====================
186 */
187 void CL_ReadDemoMessage(void)
188 {
189         int i;
190         float f;
191
192         if (!cls.demoplayback)
193                 return;
194
195         // LadyHavoc: pausedemo
196         if (cls.demopaused)
197                 return;
198
199         for (;;)
200         {
201                 // decide if it is time to grab the next message
202                 // always grab until fully connected
203                 if (cls.signon == SIGNONS)
204                 {
205                         if (cls.timedemo)
206                         {
207                                 cls.td_frames++;
208                                 cls.td_onesecondframes++;
209                                 // if this is the first official frame we can now grab the real
210                                 // td_starttime so the bogus time on the first frame doesn't
211                                 // count against the final report
212                                 if (cls.td_frames == 0)
213                                 {
214                                         cls.td_starttime = host.realtime;
215                                         cls.td_onesecondnexttime = cl.time + 1;
216                                         cls.td_onesecondrealtime = host.realtime;
217                                         cls.td_onesecondframes = 0;
218                                         cls.td_onesecondminfps = 0;
219                                         cls.td_onesecondmaxfps = 0;
220                                         cls.td_onesecondavgfps = 0;
221                                         cls.td_onesecondavgcount = 0;
222                                 }
223                                 if (cl.time >= cls.td_onesecondnexttime)
224                                 {
225                                         double fps = cls.td_onesecondframes / (host.realtime - cls.td_onesecondrealtime);
226                                         if (cls.td_onesecondavgcount == 0)
227                                         {
228                                                 cls.td_onesecondminfps = fps;
229                                                 cls.td_onesecondmaxfps = fps;
230                                         }
231                                         cls.td_onesecondrealtime = host.realtime;
232                                         cls.td_onesecondminfps = min(cls.td_onesecondminfps, fps);
233                                         cls.td_onesecondmaxfps = max(cls.td_onesecondmaxfps, fps);
234                                         cls.td_onesecondavgfps += fps;
235                                         cls.td_onesecondavgcount++;
236                                         cls.td_onesecondframes = 0;
237                                         cls.td_onesecondnexttime++;
238                                 }
239                         }
240                         else if (cl.time < cl.mtime[0])
241                         {
242                                 // don't need another message yet
243                                 return;
244                         }
245                 }
246
247                 /* At signon 1 the cl_begindownloads command starts the world and, if applicable,
248                  * boots up CSQC which may be required to parse the next message.
249                  * That will be delayed if curl must first (down)load the map.
250                  */
251                 if (cls.signon == 1 && cl.loadcsqc) // waiting for CL_VM_Init() to be called
252                         return;
253
254                 // get the next message
255                 FS_Read(cls.demofile, &cl_message.cursize, 4);
256                 cl_message.cursize = LittleLong(cl_message.cursize);
257                 if(cl_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
258                 {
259                         // skip over demo packet
260                         FS_Seek(cls.demofile, 12 + (cl_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
261                         continue;
262                 }
263                 if (cl_message.cursize > cl_message.maxsize)
264                 {
265                         CL_DisconnectEx(false, "Demo message (%i) > cl_message.maxsize (%i)", cl_message.cursize, cl_message.maxsize);
266                         cl_message.cursize = 0;
267                         return;
268                 }
269                 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
270                 for (i = 0;i < 3;i++)
271                 {
272                         FS_Read(cls.demofile, &f, 4);
273                         cl.mviewangles[0][i] = LittleFloat(f);
274                 }
275
276                 if (FS_Read(cls.demofile, cl_message.data, cl_message.cursize) == cl_message.cursize)
277                 {
278                         MSG_BeginReading(&cl_message);
279                         CL_ParseServerMessage();
280
281                         if (cls.signon != SIGNONS)
282                                 Cbuf_Execute((cmd_local)->cbuf); // immediately execute svc_stufftext if in the demo before connect!
283
284                         // In case the demo contains a "svc_disconnect" message
285                         if (!cls.demoplayback)
286                                 return;
287
288                         if (cls.timedemo)
289                                 return;
290                 }
291                 else
292                 {
293                         CL_Disconnect();
294                         return;
295                 }
296         }
297 }
298
299
300 /*
301 ====================
302 CL_Stop_f
303
304 stop recording a demo
305 ====================
306 */
307 void CL_Stop_f(cmd_state_t *cmd)
308 {
309         sizebuf_t buf;
310         unsigned char bufdata[64];
311
312         if (!cls.demorecording)
313         {
314                 Con_Print("Not recording a demo.\n");
315                 return;
316         }
317
318 // write a disconnect message to the demo file
319         // LadyHavoc: don't replace the cl_message when doing this
320         buf.data = bufdata;
321         buf.maxsize = sizeof(bufdata);
322         SZ_Clear(&buf);
323         MSG_WriteByte(&buf, svc_disconnect);
324         CL_WriteDemoMessage(&buf);
325
326 // finish up
327         if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1))
328         {
329                 FS_RemoveOnClose(cls.demofile);
330                 Con_Print("Completed and deleted demo\n");
331         }
332         else
333                 Con_Print("Completed demo\n");
334         FS_Close (cls.demofile);
335         cls.demofile = NULL;
336         cls.demorecording = false;
337 }
338
339 /*
340 ====================
341 CL_Record_f
342
343 record <demoname> <map> [cd track]
344 ====================
345 */
346 void CL_Record_f(cmd_state_t *cmd)
347 {
348         int c, track;
349         char name[MAX_OSPATH];
350         char vabuf[1024];
351
352         c = Cmd_Argc(cmd);
353         if (c != 2 && c != 3 && c != 4)
354         {
355                 Con_Print("record <demoname> [<map> [cd track]]\n");
356                 return;
357         }
358
359         if (strstr(Cmd_Argv(cmd, 1), ".."))
360         {
361                 Con_Print("Relative pathnames are not allowed.\n");
362                 return;
363         }
364
365         if (c == 2 && cls.state == ca_connected)
366         {
367                 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
368                 return;
369         }
370
371         if (cls.state == ca_connected)
372                 CL_Disconnect();
373
374         // write the forced cd track number, or -1
375         if (c == 4)
376         {
377                 track = atoi(Cmd_Argv(cmd, 3));
378                 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
379         }
380         else
381                 track = -1;
382
383         // get the demo name
384         strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
385         FS_DefaultExtension (name, ".dem", sizeof (name));
386
387         // start the map up
388         if (c > 2)
389                 Cmd_ExecuteString ( cmd, va(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(cmd, 2)), src_local, false);
390
391         // open the demo file
392         Con_Printf("recording to %s.\n", name);
393         cls.demofile = FS_OpenRealFile(name, "wb", false);
394         if (!cls.demofile)
395         {
396                 Con_Print(CON_ERROR "ERROR: couldn't open.\n");
397                 return;
398         }
399         strlcpy(cls.demoname, name, sizeof(cls.demoname));
400
401         cls.forcetrack = track;
402         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
403
404         cls.demorecording = true;
405         cls.demo_lastcsprogssize = -1;
406         cls.demo_lastcsprogscrc = -1;
407 }
408
409 void CL_PlayDemo(const char *demo)
410 {
411         char name[MAX_QPATH];
412         int c;
413         qbool neg = false;
414         qfile_t *f;
415
416         // open the demo file
417         strlcpy (name, demo, sizeof (name));
418         FS_DefaultExtension (name, ".dem", sizeof (name));
419         f = FS_OpenVirtualFile(name, false);
420         if (!f)
421         {
422                 Con_Printf(CON_ERROR "ERROR: couldn't open %s.\n", name);
423                 cls.demonum = -1;               // stop demo loop
424                 return;
425         }
426
427         cls.demostarting = true;
428
429         // disconnect from server
430         CL_Disconnect();
431
432         // update networking ports (this is mainly just needed at startup)
433         NetConn_UpdateSockets();
434
435         cls.protocol = PROTOCOL_QUAKE;
436
437         Con_Printf("Playing demo %s.\n", name);
438         cls.demofile = f;
439         strlcpy(cls.demoname, name, sizeof(cls.demoname));
440
441         cls.demoplayback = true;
442         cls.state = ca_connected;
443         cls.forcetrack = 0;
444
445         while ((c = FS_Getc (cls.demofile)) != '\n')
446                 if (c == '-')
447                         neg = true;
448                 else
449                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
450
451         if (neg)
452                 cls.forcetrack = -cls.forcetrack;
453
454         cls.demostarting = false;
455 }
456
457 /*
458 ====================
459 CL_PlayDemo_f
460
461 playdemo [demoname]
462 ====================
463 */
464 void CL_PlayDemo_f(cmd_state_t *cmd)
465 {
466         if (Cmd_Argc(cmd) != 2)
467         {
468                 Con_Print("playdemo <demoname> : plays a demo\n");
469                 return;
470         }
471
472         CL_PlayDemo(Cmd_Argv(cmd, 1));
473 }
474
475 typedef struct
476 {
477         int frames;
478         double time, totalfpsavg;
479         double fpsmin, fpsavg, fpsmax;
480 }
481 benchmarkhistory_t;
482 static size_t doublecmp_offset;
483 static int doublecmp_withoffset(const void *a_, const void *b_)
484 {
485         const double *a = (const double *) ((const char *) a_ + doublecmp_offset);
486         const double *b = (const double *) ((const char *) b_ + doublecmp_offset);
487         if(*a > *b)
488                 return +1;
489         if(*a < *b)
490                 return -1;
491         return 0;
492 }
493
494 /*
495 ====================
496 CL_FinishTimeDemo
497
498 ====================
499 */
500 static void CL_FinishTimeDemo (void)
501 {
502         int frames;
503         int i;
504         double time, totalfpsavg;
505         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
506         static int benchmark_runs = 0;
507         char vabuf[1024];
508
509         cls.timedemo = host.restless = false;
510
511         frames = cls.td_frames;
512         time = host.realtime - cls.td_starttime;
513         totalfpsavg = time > 0 ? frames / time : 0;
514         fpsmin = cls.td_onesecondminfps;
515         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
516         fpsmax = cls.td_onesecondmaxfps;
517         // LadyHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
518         Con_Printf("%i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
519         Log_Printf("benchmark.log", "date %s | enginedate %s | demo %s | commandline %s | run %d | result %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", Sys_TimeString("%Y-%m-%d %H:%M:%S"), engineversion, cls.demoname, cmdline.string, benchmark_runs + 1, frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
520         if (Sys_CheckParm("-benchmark"))
521         {
522                 ++benchmark_runs;
523                 i = Sys_CheckParm("-benchmarkruns");
524                 if(i && i + 1 < sys.argc)
525                 {
526                         static benchmarkhistory_t *history = NULL;
527                         if(!history)
528                                 history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(sys.argv[i + 1]));
529
530                         history[benchmark_runs - 1].frames = frames;
531                         history[benchmark_runs - 1].time = time;
532                         history[benchmark_runs - 1].totalfpsavg = totalfpsavg;
533                         history[benchmark_runs - 1].fpsmin = fpsmin;
534                         history[benchmark_runs - 1].fpsavg = fpsavg;
535                         history[benchmark_runs - 1].fpsmax = fpsmax;
536
537                         if(atoi(sys.argv[i + 1]) > benchmark_runs)
538                         {
539                                 // restart the benchmark
540                                 Cbuf_AddText(cmd_local, va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname));
541                                 // cannot execute here
542                         }
543                         else
544                         {
545                                 // print statistics
546                                 int first = Sys_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0;
547                                 if(benchmark_runs > first)
548                                 {
549 #define DO_MIN(f) \
550                                         for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f
551
552 #define DO_MAX(f) \
553                                         for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f
554
555 #define DO_MED(f) \
556                                         doublecmp_offset = (char *)&history->f - (char *)history; \
557                                         qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \
558                                         if((first + benchmark_runs) & 1) \
559                                                 f = history[(first + benchmark_runs - 1) / 2].f; \
560                                         else \
561                                                 f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2
562
563                                         DO_MIN(frames);
564                                         DO_MAX(time);
565                                         DO_MIN(totalfpsavg);
566                                         DO_MIN(fpsmin);
567                                         DO_MIN(fpsavg);
568                                         DO_MIN(fpsmax);
569                                         Con_Printf("MIN: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
570
571                                         DO_MED(frames);
572                                         DO_MED(time);
573                                         DO_MED(totalfpsavg);
574                                         DO_MED(fpsmin);
575                                         DO_MED(fpsavg);
576                                         DO_MED(fpsmax);
577                                         Con_Printf("MED: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
578
579                                         DO_MAX(frames);
580                                         DO_MIN(time);
581                                         DO_MAX(totalfpsavg);
582                                         DO_MAX(fpsmin);
583                                         DO_MAX(fpsavg);
584                                         DO_MAX(fpsmax);
585                                         Con_Printf("MAX: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
586                                 }
587                                 Z_Free(history);
588                                 history = NULL;
589                                 host.state = host_shutdown;
590                         }
591                 }
592                 else
593                         host.state = host_shutdown;
594         }
595
596         // Might need to re-enable vsync
597         Cvar_Callback(&vid_vsync);
598 }
599
600 /*
601 ====================
602 CL_TimeDemo_f
603
604 timedemo [demoname]
605 ====================
606 */
607 void CL_TimeDemo_f(cmd_state_t *cmd)
608 {
609         if (Cmd_Argc(cmd) != 2)
610         {
611                 Con_Print("timedemo <demoname> : gets demo speeds\n");
612                 return;
613         }
614
615         srand(0); // predictable random sequence for benchmarking
616
617         CL_PlayDemo(Cmd_Argv(cmd, 1));
618
619 // cls.td_starttime will be grabbed at the second frame of the demo, so
620 // all the loading time doesn't get counted
621
622         // instantly hide console and deactivate it
623         key_dest = key_game;
624         key_consoleactive = 0;
625         scr_con_current = 0;
626
627         cls.timedemo = host.restless = true;
628         cls.td_frames = -2;             // skip the first frame
629         cls.demonum = -1;               // stop demo loop
630
631         // Might need to disable vsync
632         Cvar_Callback(&vid_vsync);
633 }
634
635 /*
636 ===============================================================================
637
638 DEMO LOOP CONTROL
639
640 ===============================================================================
641 */
642
643
644 /*
645 ==================
646 CL_Startdemos_f
647 ==================
648 */
649 static void CL_Startdemos_f(cmd_state_t *cmd)
650 {
651         int             i, c;
652
653         if (cls.state == ca_dedicated || Sys_CheckParm("-listen") || Sys_CheckParm("-benchmark") || Sys_CheckParm("-demo") || Sys_CheckParm("-capturedemo"))
654                 return;
655
656         c = Cmd_Argc(cmd) - 1;
657         if (c > MAX_DEMOS)
658         {
659                 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
660                 c = MAX_DEMOS;
661         }
662         Con_DPrintf("%i demo(s) in loop\n", c);
663
664         for (i=1 ; i<c+1 ; i++)
665                 strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
666
667         // LadyHavoc: clear the remaining slots
668         for (;i <= MAX_DEMOS;i++)
669                 cls.demos[i-1][0] = 0;
670
671         if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
672         {
673                 cls.demonum = 0;
674                 CL_NextDemo ();
675         }
676         else
677                 cls.demonum = -1;
678 }
679
680
681 /*
682 ==================
683 CL_Demos_f
684
685 Return to looping demos
686 ==================
687 */
688 static void CL_Demos_f(cmd_state_t *cmd)
689 {
690         if (cls.state == ca_dedicated)
691                 return;
692         if (cls.demonum == -1)
693                 cls.demonum = 1;
694         CL_Disconnect();
695         CL_NextDemo();
696 }
697
698 /*
699 ==================
700 CL_Stopdemo_f
701
702 Return to looping demos
703 ==================
704 */
705 static void CL_Stopdemo_f(cmd_state_t *cmd)
706 {
707         if (!cls.demoplayback)
708                 return;
709         CL_Disconnect();
710 }
711
712 // LadyHavoc: pausedemo command
713 static void CL_PauseDemo_f(cmd_state_t *cmd)
714 {
715         cls.demopaused = !cls.demopaused;
716         if (cls.demopaused)
717                 Con_Print("Demo paused\n");
718         else
719                 Con_Print("Demo unpaused\n");
720 }
721
722 void CL_Demo_Init(void)
723 {
724         Cmd_AddCommand(CF_CLIENT, "record", CL_Record_f, "record a demo");
725         Cmd_AddCommand(CF_CLIENT, "stop", CL_Stop_f, "stop recording or playing a demo");
726         Cmd_AddCommand(CF_CLIENT, "playdemo", CL_PlayDemo_f, "watch a demo file");
727         Cmd_AddCommand(CF_CLIENT, "timedemo", CL_TimeDemo_f, "play back a demo as fast as possible and save statistics to benchmark.log");
728         Cmd_AddCommand(CF_CLIENT, "startdemos", CL_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
729         Cmd_AddCommand(CF_CLIENT, "demos", CL_Demos_f, "restart looping demos defined by the last startdemos command");
730         Cmd_AddCommand(CF_CLIENT, "stopdemo", CL_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
731         // LadyHavoc: added pausedemo
732         Cmd_AddCommand(CF_CLIENT, "pausedemo", CL_PauseDemo_f, "pause demo playback (can also safely pause demo recording if using QUAKE, QUAKEDP or NEHAHRAMOVIE protocol, useful for making movies)");
733         Cvar_RegisterVariable (&cl_autodemo);
734         Cvar_RegisterVariable (&cl_autodemo_nameformat);
735         Cvar_RegisterVariable (&cl_autodemo_delete);
736 }