]> git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_demo.c
Merge PR 'Use the text from modinfo.txt as the mod menu entry'
[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         int vabuf_len;
352
353         c = Cmd_Argc(cmd);
354         if (c != 2 && c != 3 && c != 4)
355         {
356                 Con_Print("record <demoname> [<map> [cd track]]\n");
357                 return;
358         }
359
360         if (strstr(Cmd_Argv(cmd, 1), ".."))
361         {
362                 Con_Print("Relative pathnames are not allowed.\n");
363                 return;
364         }
365
366         if (c == 2 && cls.state == ca_connected)
367         {
368                 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
369                 return;
370         }
371
372         if (cls.state == ca_connected)
373                 CL_Disconnect();
374
375         // write the forced cd track number, or -1
376         if (c == 4)
377         {
378                 track = atoi(Cmd_Argv(cmd, 3));
379                 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
380         }
381         else
382                 track = -1;
383
384         // get the demo name
385         dp_strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
386         FS_DefaultExtension (name, ".dem", sizeof (name));
387
388         // start the map up
389         if (c > 2)
390         {
391                 vabuf_len = dpsnprintf(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(cmd, 2));
392                 Cmd_ExecuteString(cmd, vabuf, vabuf_len, src_local, false);
393         }
394
395         // open the demo file
396         Con_Printf("recording to %s.\n", name);
397         cls.demofile = FS_OpenRealFile(name, "wb", false);
398         if (!cls.demofile)
399         {
400                 Con_Print(CON_ERROR "ERROR: couldn't open.\n");
401                 return;
402         }
403         dp_strlcpy(cls.demoname, name, sizeof(cls.demoname));
404
405         cls.forcetrack = track;
406         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
407
408         cls.demorecording = true;
409         cls.demo_lastcsprogssize = -1;
410         cls.demo_lastcsprogscrc = -1;
411 }
412
413 void CL_PlayDemo(const char *demo)
414 {
415         char name[MAX_QPATH];
416         int c;
417         qbool neg = false;
418         qfile_t *f;
419
420         // open the demo file
421         dp_strlcpy (name, demo, sizeof (name));
422         FS_DefaultExtension (name, ".dem", sizeof (name));
423         f = FS_OpenVirtualFile(name, false);
424         if (!f)
425         {
426                 Con_Printf(CON_ERROR "ERROR: couldn't open %s.\n", name);
427                 cls.demonum = -1;               // stop demo loop
428                 return;
429         }
430
431         cls.demostarting = true;
432
433         // disconnect from server
434         CL_Disconnect();
435
436         // update networking ports (this is mainly just needed at startup)
437         NetConn_UpdateSockets();
438
439         cls.protocol = PROTOCOL_QUAKE;
440
441         Con_Printf("Playing demo %s.\n", name);
442         cls.demofile = f;
443         dp_strlcpy(cls.demoname, name, sizeof(cls.demoname));
444
445         cls.demoplayback = true;
446         cls.state = ca_connected;
447         cls.forcetrack = 0;
448
449         while ((c = FS_Getc (cls.demofile)) != '\n')
450                 if (c == '-')
451                         neg = true;
452                 else
453                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
454
455         if (neg)
456                 cls.forcetrack = -cls.forcetrack;
457
458         cls.demostarting = false;
459 }
460
461 /*
462 ====================
463 CL_PlayDemo_f
464
465 playdemo [demoname]
466 ====================
467 */
468 void CL_PlayDemo_f(cmd_state_t *cmd)
469 {
470         if (Cmd_Argc(cmd) != 2)
471         {
472                 Con_Print("playdemo <demoname> : plays a demo\n");
473                 return;
474         }
475
476         CL_PlayDemo(Cmd_Argv(cmd, 1));
477 }
478
479 typedef struct
480 {
481         int frames;
482         double time, totalfpsavg;
483         double fpsmin, fpsavg, fpsmax;
484 }
485 benchmarkhistory_t;
486 static size_t doublecmp_offset;
487 static int doublecmp_withoffset(const void *a_, const void *b_)
488 {
489         const double *a = (const double *) ((const char *) a_ + doublecmp_offset);
490         const double *b = (const double *) ((const char *) b_ + doublecmp_offset);
491         if(*a > *b)
492                 return +1;
493         if(*a < *b)
494                 return -1;
495         return 0;
496 }
497
498 /*
499 ====================
500 CL_FinishTimeDemo
501
502 ====================
503 */
504 static void CL_FinishTimeDemo (void)
505 {
506         int frames;
507         int i;
508         double time, totalfpsavg;
509         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
510         static int benchmark_runs = 0;
511         char vabuf[1024];
512
513         cls.timedemo = host.restless = false;
514
515         frames = cls.td_frames;
516         time = host.realtime - cls.td_starttime;
517         totalfpsavg = time > 0 ? frames / time : 0;
518         fpsmin = cls.td_onesecondminfps;
519         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
520         fpsmax = cls.td_onesecondmaxfps;
521         // LadyHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
522         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);
523         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);
524         if (Sys_CheckParm("-benchmark"))
525         {
526                 ++benchmark_runs;
527                 i = Sys_CheckParm("-benchmarkruns");
528                 if(i && i + 1 < sys.argc)
529                 {
530                         static benchmarkhistory_t *history = NULL;
531                         if(!history)
532                                 history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(sys.argv[i + 1]));
533
534                         history[benchmark_runs - 1].frames = frames;
535                         history[benchmark_runs - 1].time = time;
536                         history[benchmark_runs - 1].totalfpsavg = totalfpsavg;
537                         history[benchmark_runs - 1].fpsmin = fpsmin;
538                         history[benchmark_runs - 1].fpsavg = fpsavg;
539                         history[benchmark_runs - 1].fpsmax = fpsmax;
540
541                         if(atoi(sys.argv[i + 1]) > benchmark_runs)
542                         {
543                                 // restart the benchmark
544                                 Cbuf_AddText(cmd_local, va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname));
545                                 // cannot execute here
546                         }
547                         else
548                         {
549                                 // print statistics
550                                 int first = Sys_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0;
551                                 if(benchmark_runs > first)
552                                 {
553 #define DO_MIN(f) \
554                                         for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f
555
556 #define DO_MAX(f) \
557                                         for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f
558
559 #define DO_MED(f) \
560                                         doublecmp_offset = (char *)&history->f - (char *)history; \
561                                         qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \
562                                         if((first + benchmark_runs) & 1) \
563                                                 f = history[(first + benchmark_runs - 1) / 2].f; \
564                                         else \
565                                                 f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2
566
567                                         DO_MIN(frames);
568                                         DO_MAX(time);
569                                         DO_MIN(totalfpsavg);
570                                         DO_MIN(fpsmin);
571                                         DO_MIN(fpsavg);
572                                         DO_MIN(fpsmax);
573                                         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);
574
575                                         DO_MED(frames);
576                                         DO_MED(time);
577                                         DO_MED(totalfpsavg);
578                                         DO_MED(fpsmin);
579                                         DO_MED(fpsavg);
580                                         DO_MED(fpsmax);
581                                         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);
582
583                                         DO_MAX(frames);
584                                         DO_MIN(time);
585                                         DO_MAX(totalfpsavg);
586                                         DO_MAX(fpsmin);
587                                         DO_MAX(fpsavg);
588                                         DO_MAX(fpsmax);
589                                         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);
590                                 }
591                                 Z_Free(history);
592                                 history = NULL;
593                                 host.state = host_shutdown;
594                         }
595                 }
596                 else
597                         host.state = host_shutdown;
598         }
599
600         // Might need to re-enable vsync
601         Cvar_Callback(&vid_vsync);
602 }
603
604 /*
605 ====================
606 CL_TimeDemo_f
607
608 timedemo [demoname]
609 ====================
610 */
611 void CL_TimeDemo_f(cmd_state_t *cmd)
612 {
613         if (Cmd_Argc(cmd) != 2)
614         {
615                 Con_Print("timedemo <demoname> : gets demo speeds\n");
616                 return;
617         }
618
619         srand(0); // predictable random sequence for benchmarking
620
621         CL_PlayDemo(Cmd_Argv(cmd, 1));
622
623 // cls.td_starttime will be grabbed at the second frame of the demo, so
624 // all the loading time doesn't get counted
625
626         // instantly hide console and deactivate it
627         key_dest = key_game;
628         key_consoleactive = 0;
629         scr_con_current = 0;
630
631         cls.timedemo = host.restless = true;
632         cls.td_frames = -2;             // skip the first frame
633         cls.demonum = -1;               // stop demo loop
634
635         // Might need to disable vsync
636         Cvar_Callback(&vid_vsync);
637 }
638
639 /*
640 ===============================================================================
641
642 DEMO LOOP CONTROL
643
644 ===============================================================================
645 */
646
647
648 /*
649 ==================
650 CL_Startdemos_f
651 ==================
652 */
653 static void CL_Startdemos_f(cmd_state_t *cmd)
654 {
655         int             i, c;
656
657         if (cls.state == ca_dedicated || Sys_CheckParm("-listen") || Sys_CheckParm("-benchmark") || Sys_CheckParm("-demo") || Sys_CheckParm("-capturedemo"))
658                 return;
659
660         c = Cmd_Argc(cmd) - 1;
661         if (c > MAX_DEMOS)
662         {
663                 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
664                 c = MAX_DEMOS;
665         }
666         Con_DPrintf("%i demo(s) in loop\n", c);
667
668         for (i=1 ; i<c+1 ; i++)
669                 dp_strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
670
671         // LadyHavoc: clear the remaining slots
672         for (;i <= MAX_DEMOS;i++)
673                 cls.demos[i-1][0] = 0;
674
675         if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
676         {
677                 cls.demonum = 0;
678                 CL_NextDemo ();
679         }
680         else
681                 cls.demonum = -1;
682 }
683
684
685 /*
686 ==================
687 CL_Demos_f
688
689 Return to looping demos
690 ==================
691 */
692 static void CL_Demos_f(cmd_state_t *cmd)
693 {
694         if (cls.state == ca_dedicated)
695                 return;
696         if (cls.demonum == -1)
697                 cls.demonum = 1;
698         CL_Disconnect();
699         CL_NextDemo();
700 }
701
702 /*
703 ==================
704 CL_Stopdemo_f
705
706 Return to looping demos
707 ==================
708 */
709 static void CL_Stopdemo_f(cmd_state_t *cmd)
710 {
711         if (!cls.demoplayback)
712                 return;
713         CL_Disconnect();
714 }
715
716 // LadyHavoc: pausedemo command
717 static void CL_PauseDemo_f(cmd_state_t *cmd)
718 {
719         cls.demopaused = !cls.demopaused;
720         if (cls.demopaused)
721                 Con_Print("Demo paused\n");
722         else
723                 Con_Print("Demo unpaused\n");
724 }
725
726 void CL_Demo_Init(void)
727 {
728         Cmd_AddCommand(CF_CLIENT, "record", CL_Record_f, "record a demo");
729         Cmd_AddCommand(CF_CLIENT, "stop", CL_Stop_f, "stop recording or playing a demo");
730         Cmd_AddCommand(CF_CLIENT, "playdemo", CL_PlayDemo_f, "watch a demo file");
731         Cmd_AddCommand(CF_CLIENT, "timedemo", CL_TimeDemo_f, "play back a demo as fast as possible and save statistics to benchmark.log");
732         Cmd_AddCommand(CF_CLIENT, "startdemos", CL_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
733         Cmd_AddCommand(CF_CLIENT, "demos", CL_Demos_f, "restart looping demos defined by the last startdemos command");
734         Cmd_AddCommand(CF_CLIENT, "stopdemo", CL_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
735         // LadyHavoc: added pausedemo
736         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)");
737         Cvar_RegisterVariable (&cl_autodemo);
738         Cvar_RegisterVariable (&cl_autodemo_nameformat);
739         Cvar_RegisterVariable (&cl_autodemo_delete);
740 }