Remove the CONFIG_CD macro, and enable faketracks unconditionally.
[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 int old_vsync = 0;
28
29 static void CL_FinishTimeDemo (void);
30
31 /*
32 ==============================================================================
33
34 DEMO CODE
35
36 When a demo is playing back, all outgoing network messages are skipped, and
37 incoming messages are read from the demo file.
38
39 Whenever cl.time gets past the last received message, another message is
40 read from the demo file.
41 ==============================================================================
42 */
43
44 /*
45 =====================
46 CL_NextDemo
47
48 Called to play the next demo in the demo loop
49 =====================
50 */
51 void CL_NextDemo (void)
52 {
53         char    str[MAX_INPUTLINE];
54
55         if (cls.demonum == -1)
56                 return;         // don't play demos
57
58         if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
59         {
60                 cls.demonum = 0;
61                 if (!cls.demos[cls.demonum][0])
62                 {
63                         Con_Print("No demos listed with startdemos\n");
64                         cls.demonum = -1;
65                         return;
66                 }
67         }
68
69         dpsnprintf (str, sizeof(str), "playdemo %s\n", cls.demos[cls.demonum]);
70         Cbuf_InsertText (str);
71         cls.demonum++;
72 }
73
74 /*
75 ==============
76 CL_StopPlayback
77
78 Called when a demo file runs out, or the user starts a game
79 ==============
80 */
81 // LordHavoc: now called only by CL_Disconnect
82 void CL_StopPlayback (void)
83 {
84 #ifdef CONFIG_VIDEO_CAPTURE
85         if (cl_capturevideo_demo_stop.integer)
86                 Cvar_Set("cl_capturevideo", "0");
87 #endif
88
89         if (!cls.demoplayback)
90                 return;
91
92         FS_Close (cls.demofile);
93         cls.demoplayback = false;
94         cls.demofile = NULL;
95
96         if (cls.timedemo)
97                 CL_FinishTimeDemo ();
98
99         if (!cls.demostarting) // only quit if not starting another demo
100                 if (COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
101                         Host_Quit_f();
102
103 }
104
105 /*
106 ====================
107 CL_WriteDemoMessage
108
109 Dumps the current net message, prefixed by the length and view angles
110 #====================
111 */
112 void CL_WriteDemoMessage (sizebuf_t *message)
113 {
114         int             len;
115         int             i;
116         float   f;
117
118         if (cls.demopaused) // LordHavoc: pausedemo
119                 return;
120
121         len = LittleLong (message->cursize);
122         FS_Write (cls.demofile, &len, 4);
123         for (i=0 ; i<3 ; i++)
124         {
125                 f = LittleFloat (cl.viewangles[i]);
126                 FS_Write (cls.demofile, &f, 4);
127         }
128         FS_Write (cls.demofile, message->data, message->cursize);
129 }
130
131 /*
132 ====================
133 CL_CutDemo
134
135 Dumps the current demo to a buffer, and resets the demo to its starting point.
136 Used to insert csprogs.dat files as a download to the beginning of a demo file.
137 ====================
138 */
139 void CL_CutDemo (unsigned char **buf, fs_offset_t *filesize)
140 {
141         *buf = NULL;
142         *filesize = 0;
143
144         FS_Close(cls.demofile);
145         *buf = FS_LoadFile(cls.demoname, tempmempool, false, filesize);
146
147         // restart the demo recording
148         cls.demofile = FS_OpenRealFile(cls.demoname, "wb", false);
149         if(!cls.demofile)
150                 Sys_Error("failed to reopen the demo file");
151         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
152 }
153
154 /*
155 ====================
156 CL_PasteDemo
157
158 Adds the cut stuff back to the demo. Also frees the buffer.
159 Used to insert csprogs.dat files as a download to the beginning of a demo file.
160 ====================
161 */
162 void CL_PasteDemo (unsigned char **buf, fs_offset_t *filesize)
163 {
164         fs_offset_t startoffset = 0;
165
166         if(!*buf)
167                 return;
168
169         // skip cdtrack
170         while(startoffset < *filesize && ((char *)(*buf))[startoffset] != '\n')
171                 ++startoffset;
172         if(startoffset < *filesize)
173                 ++startoffset;
174
175         FS_Write(cls.demofile, *buf + startoffset, *filesize - startoffset);
176
177         Mem_Free(*buf);
178         *buf = NULL;
179         *filesize = 0;
180 }
181
182 /*
183 ====================
184 CL_ReadDemoMessage
185
186 Handles playback of demos
187 ====================
188 */
189 void CL_ReadDemoMessage(void)
190 {
191         int i;
192         float f;
193
194         if (!cls.demoplayback)
195                 return;
196
197         // LordHavoc: pausedemo
198         if (cls.demopaused)
199                 return;
200
201         for (;;)
202         {
203                 // decide if it is time to grab the next message
204                 // always grab until fully connected
205                 if (cls.signon == SIGNONS)
206                 {
207                         if (cls.timedemo)
208                         {
209                                 cls.td_frames++;
210                                 cls.td_onesecondframes++;
211                                 // if this is the first official frame we can now grab the real
212                                 // td_starttime so the bogus time on the first frame doesn't
213                                 // count against the final report
214                                 if (cls.td_frames == 0)
215                                 {
216                                         cls.td_starttime = realtime;
217                                         cls.td_onesecondnexttime = cl.time + 1;
218                                         cls.td_onesecondrealtime = realtime;
219                                         cls.td_onesecondframes = 0;
220                                         cls.td_onesecondminfps = 0;
221                                         cls.td_onesecondmaxfps = 0;
222                                         cls.td_onesecondavgfps = 0;
223                                         cls.td_onesecondavgcount = 0;
224                                 }
225                                 if (cl.time >= cls.td_onesecondnexttime)
226                                 {
227                                         double fps = cls.td_onesecondframes / (realtime - cls.td_onesecondrealtime);
228                                         if (cls.td_onesecondavgcount == 0)
229                                         {
230                                                 cls.td_onesecondminfps = fps;
231                                                 cls.td_onesecondmaxfps = fps;
232                                         }
233                                         cls.td_onesecondrealtime = realtime;
234                                         cls.td_onesecondminfps = min(cls.td_onesecondminfps, fps);
235                                         cls.td_onesecondmaxfps = max(cls.td_onesecondmaxfps, fps);
236                                         cls.td_onesecondavgfps += fps;
237                                         cls.td_onesecondavgcount++;
238                                         cls.td_onesecondframes = 0;
239                                         cls.td_onesecondnexttime++;
240                                 }
241                         }
242                         else if (cl.time < cl.mtime[0])
243                         {
244                                 // don't need another message yet
245                                 return;
246                         }
247                 }
248
249                 // get the next message
250                 FS_Read(cls.demofile, &cl_message.cursize, 4);
251                 cl_message.cursize = LittleLong(cl_message.cursize);
252                 if(cl_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
253                 {
254                         // skip over demo packet
255                         FS_Seek(cls.demofile, 12 + (cl_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
256                         continue;
257                 }
258                 if (cl_message.cursize > cl_message.maxsize)
259                 {
260                         Con_Printf("Demo message (%i) > cl_message.maxsize (%i)", cl_message.cursize, cl_message.maxsize);
261                         cl_message.cursize = 0;
262                         CL_Disconnect();
263                         return;
264                 }
265                 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
266                 for (i = 0;i < 3;i++)
267                 {
268                         FS_Read(cls.demofile, &f, 4);
269                         cl.mviewangles[0][i] = LittleFloat(f);
270                 }
271
272                 if (FS_Read(cls.demofile, cl_message.data, cl_message.cursize) == cl_message.cursize)
273                 {
274                         MSG_BeginReading(&cl_message);
275                         CL_ParseServerMessage();
276
277                         if (cls.signon != SIGNONS)
278                                 Cbuf_Execute(); // immediately execute svc_stufftext if in the demo before connect!
279
280                         // In case the demo contains a "svc_disconnect" message
281                         if (!cls.demoplayback)
282                                 return;
283
284                         if (cls.timedemo)
285                                 return;
286                 }
287                 else
288                 {
289                         CL_Disconnect();
290                         return;
291                 }
292         }
293 }
294
295
296 /*
297 ====================
298 CL_Stop_f
299
300 stop recording a demo
301 ====================
302 */
303 void CL_Stop_f (void)
304 {
305         sizebuf_t buf;
306         unsigned char bufdata[64];
307
308         if (!cls.demorecording)
309         {
310                 Con_Print("Not recording a demo.\n");
311                 return;
312         }
313
314 // write a disconnect message to the demo file
315         // LordHavoc: don't replace the cl_message when doing this
316         buf.data = bufdata;
317         buf.maxsize = sizeof(bufdata);
318         SZ_Clear(&buf);
319         MSG_WriteByte(&buf, svc_disconnect);
320         CL_WriteDemoMessage(&buf);
321
322 // finish up
323         if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1))
324         {
325                 FS_RemoveOnClose(cls.demofile);
326                 Con_Print("Completed and deleted demo\n");
327         }
328         else
329                 Con_Print("Completed demo\n");
330         FS_Close (cls.demofile);
331         cls.demofile = NULL;
332         cls.demorecording = false;
333 }
334
335 /*
336 ====================
337 CL_Record_f
338
339 record <demoname> <map> [cd track]
340 ====================
341 */
342 void CL_Record_f (void)
343 {
344         int c, track;
345         char name[MAX_OSPATH];
346         char vabuf[1024];
347
348         c = Cmd_Argc();
349         if (c != 2 && c != 3 && c != 4)
350         {
351                 Con_Print("record <demoname> [<map> [cd track]]\n");
352                 return;
353         }
354
355         if (strstr(Cmd_Argv(1), ".."))
356         {
357                 Con_Print("Relative pathnames are not allowed.\n");
358                 return;
359         }
360
361         if (c == 2 && cls.state == ca_connected)
362         {
363                 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
364                 return;
365         }
366
367         if (cls.state == ca_connected)
368                 CL_Disconnect();
369
370         // write the forced cd track number, or -1
371         if (c == 4)
372         {
373                 track = atoi(Cmd_Argv(3));
374                 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
375         }
376         else
377                 track = -1;
378
379         // get the demo name
380         strlcpy (name, Cmd_Argv(1), sizeof (name));
381         FS_DefaultExtension (name, ".dem", sizeof (name));
382
383         // start the map up
384         if (c > 2)
385                 Cmd_ExecuteString ( va(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(2)), src_command, false);
386
387         // open the demo file
388         Con_Printf("recording to %s.\n", name);
389         cls.demofile = FS_OpenRealFile(name, "wb", false);
390         if (!cls.demofile)
391         {
392                 Con_Print("ERROR: couldn't open.\n");
393                 return;
394         }
395         strlcpy(cls.demoname, name, sizeof(cls.demoname));
396
397         cls.forcetrack = track;
398         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
399
400         cls.demorecording = true;
401         cls.demo_lastcsprogssize = -1;
402         cls.demo_lastcsprogscrc = -1;
403 }
404
405
406 /*
407 ====================
408 CL_PlayDemo_f
409
410 play [demoname]
411 ====================
412 */
413 void CL_PlayDemo_f (void)
414 {
415         char    name[MAX_QPATH];
416         int c;
417         qboolean neg = false;
418         qfile_t *f;
419
420         if (Cmd_Argc() != 2)
421         {
422                 Con_Print("play <demoname> : plays a demo\n");
423                 return;
424         }
425
426         // open the demo file
427         strlcpy (name, Cmd_Argv(1), sizeof (name));
428         FS_DefaultExtension (name, ".dem", sizeof (name));
429         f = FS_OpenVirtualFile(name, false);
430         if (!f)
431         {
432                 Con_Printf("ERROR: couldn't open %s.\n", name);
433                 cls.demonum = -1;               // stop demo loop
434                 return;
435         }
436
437         cls.demostarting = true;
438
439         // disconnect from server
440         CL_Disconnect ();
441         Host_ShutdownServer ();
442
443         // update networking ports (this is mainly just needed at startup)
444         NetConn_UpdateSockets();
445
446         cls.protocol = PROTOCOL_QUAKE;
447
448         Con_Printf("Playing demo %s.\n", name);
449         cls.demofile = f;
450         strlcpy(cls.demoname, name, sizeof(cls.demoname));
451
452         cls.demoplayback = true;
453         cls.state = ca_connected;
454         cls.forcetrack = 0;
455
456         while ((c = FS_Getc (cls.demofile)) != '\n')
457                 if (c == '-')
458                         neg = true;
459                 else
460                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
461
462         if (neg)
463                 cls.forcetrack = -cls.forcetrack;
464
465         cls.demostarting = false;
466 }
467
468 typedef struct
469 {
470         int frames;
471         double time, totalfpsavg;
472         double fpsmin, fpsavg, fpsmax;
473 }
474 benchmarkhistory_t;
475 static size_t doublecmp_offset;
476 static int doublecmp_withoffset(const void *a_, const void *b_)
477 {
478         const double *a = (const double *) ((const char *) a_ + doublecmp_offset);
479         const double *b = (const double *) ((const char *) b_ + doublecmp_offset);
480         if(*a > *b)
481                 return +1;
482         if(*a < *b)
483                 return -1;
484         return 0;
485 }
486
487 /*
488 ====================
489 CL_FinishTimeDemo
490
491 ====================
492 */
493 static void CL_FinishTimeDemo (void)
494 {
495         int frames;
496         int i;
497         double time, totalfpsavg;
498         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
499         static int benchmark_runs = 0;
500         char vabuf[1024];
501
502         cls.timedemo = false;
503
504         frames = cls.td_frames;
505         time = realtime - cls.td_starttime;
506         totalfpsavg = time > 0 ? frames / time : 0;
507         fpsmin = cls.td_onesecondminfps;
508         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
509         fpsmax = cls.td_onesecondmaxfps;
510         // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
511         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);
512         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"), buildstring, cls.demoname, cmdline.string, benchmark_runs + 1, frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
513         if (COM_CheckParm("-benchmark"))
514         {
515                 ++benchmark_runs;
516                 i = COM_CheckParm("-benchmarkruns");
517                 if(i && i + 1 < com_argc)
518                 {
519                         static benchmarkhistory_t *history = NULL;
520                         if(!history)
521                                 history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(com_argv[i + 1]));
522
523                         history[benchmark_runs - 1].frames = frames;
524                         history[benchmark_runs - 1].time = time;
525                         history[benchmark_runs - 1].totalfpsavg = totalfpsavg;
526                         history[benchmark_runs - 1].fpsmin = fpsmin;
527                         history[benchmark_runs - 1].fpsavg = fpsavg;
528                         history[benchmark_runs - 1].fpsmax = fpsmax;
529
530                         if(atoi(com_argv[i + 1]) > benchmark_runs)
531                         {
532                                 // restart the benchmark
533                                 Cbuf_AddText(va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname));
534                                 // cannot execute here
535                         }
536                         else
537                         {
538                                 // print statistics
539                                 int first = COM_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0;
540                                 if(benchmark_runs > first)
541                                 {
542 #define DO_MIN(f) \
543                                         for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f
544
545 #define DO_MAX(f) \
546                                         for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f
547
548 #define DO_MED(f) \
549                                         doublecmp_offset = (char *)&history->f - (char *)history; \
550                                         qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \
551                                         if((first + benchmark_runs) & 1) \
552                                                 f = history[(first + benchmark_runs - 1) / 2].f; \
553                                         else \
554                                                 f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2
555
556                                         DO_MIN(frames);
557                                         DO_MAX(time);
558                                         DO_MIN(totalfpsavg);
559                                         DO_MIN(fpsmin);
560                                         DO_MIN(fpsavg);
561                                         DO_MIN(fpsmax);
562                                         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);
563
564                                         DO_MED(frames);
565                                         DO_MED(time);
566                                         DO_MED(totalfpsavg);
567                                         DO_MED(fpsmin);
568                                         DO_MED(fpsavg);
569                                         DO_MED(fpsmax);
570                                         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);
571
572                                         DO_MAX(frames);
573                                         DO_MIN(time);
574                                         DO_MAX(totalfpsavg);
575                                         DO_MAX(fpsmin);
576                                         DO_MAX(fpsavg);
577                                         DO_MAX(fpsmax);
578                                         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);
579                                 }
580                                 Z_Free(history);
581                                 history = NULL;
582                                 Host_Quit_f();
583                         }
584                 }
585                 else
586                         Host_Quit_f();
587         }
588 }
589
590 /*
591 ====================
592 CL_TimeDemo_f
593
594 timedemo [demoname]
595 ====================
596 */
597 void CL_TimeDemo_f (void)
598 {
599         if (Cmd_Argc() != 2)
600         {
601                 Con_Print("timedemo <demoname> : gets demo speeds\n");
602                 return;
603         }
604
605         srand(0); // predictable random sequence for benchmarking
606
607         CL_PlayDemo_f ();
608
609 // cls.td_starttime will be grabbed at the second frame of the demo, so
610 // all the loading time doesn't get counted
611
612         // instantly hide console and deactivate it
613         key_dest = key_game;
614         key_consoleactive = 0;
615         scr_con_current = 0;
616
617         cls.timedemo = true;
618         cls.td_frames = -2;             // skip the first frame
619         cls.demonum = -1;               // stop demo loop
620 }
621