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