]> git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_demo.c
-demo and -capturedemo: don't quit if loading another demo, only quit when no more...
[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 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                 Host_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, &net_message.cursize, 4);
243                 net_message.cursize = LittleLong(net_message.cursize);
244                 if(net_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 + (net_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
248                         continue;
249                 }
250                 if (net_message.cursize > net_message.maxsize)
251                         Host_Error("Demo message (%i) > net_message.maxsize (%i)", net_message.cursize, net_message.maxsize);
252                 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
253                 for (i = 0;i < 3;i++)
254                 {
255                         FS_Read(cls.demofile, &f, 4);
256                         cl.mviewangles[0][i] = LittleFloat(f);
257                 }
258
259                 if (FS_Read(cls.demofile, net_message.data, net_message.cursize) == net_message.cursize)
260                 {
261                         MSG_BeginReading();
262                         CL_ParseServerMessage();
263
264                         if (cls.signon != SIGNONS)
265                                 Cbuf_Execute(); // immediately execute svc_stufftext if in the demo before connect!
266
267                         // In case the demo contains a "svc_disconnect" message
268                         if (!cls.demoplayback)
269                                 return;
270
271                         if (cls.timedemo)
272                                 return;
273                 }
274                 else
275                 {
276                         CL_Disconnect();
277                         return;
278                 }
279         }
280 }
281
282
283 /*
284 ====================
285 CL_Stop_f
286
287 stop recording a demo
288 ====================
289 */
290 void CL_Stop_f (void)
291 {
292         sizebuf_t buf;
293         unsigned char bufdata[64];
294
295         if (!cls.demorecording)
296         {
297                 Con_Print("Not recording a demo.\n");
298                 return;
299         }
300
301 // write a disconnect message to the demo file
302         // LordHavoc: don't replace the net_message when doing this
303         buf.data = bufdata;
304         buf.maxsize = sizeof(bufdata);
305         SZ_Clear(&buf);
306         MSG_WriteByte(&buf, svc_disconnect);
307         CL_WriteDemoMessage(&buf);
308
309 // finish up
310         if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1))
311         {
312                 FS_RemoveOnClose(cls.demofile);
313                 Con_Print("Completed and deleted demo\n");
314         }
315         else
316                 Con_Print("Completed demo\n");
317         FS_Close (cls.demofile);
318         cls.demofile = NULL;
319         cls.demorecording = false;
320 }
321
322 /*
323 ====================
324 CL_Record_f
325
326 record <demoname> <map> [cd track]
327 ====================
328 */
329 void CL_Record_f (void)
330 {
331         int c, track;
332         char name[MAX_OSPATH];
333
334         c = Cmd_Argc();
335         if (c != 2 && c != 3 && c != 4)
336         {
337                 Con_Print("record <demoname> [<map> [cd track]]\n");
338                 return;
339         }
340
341         if (strstr(Cmd_Argv(1), ".."))
342         {
343                 Con_Print("Relative pathnames are not allowed.\n");
344                 return;
345         }
346
347         if (c == 2 && cls.state == ca_connected)
348         {
349                 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
350                 return;
351         }
352
353         if (cls.state == ca_connected)
354                 CL_Disconnect();
355
356         // write the forced cd track number, or -1
357         if (c == 4)
358         {
359                 track = atoi(Cmd_Argv(3));
360                 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
361         }
362         else
363                 track = -1;
364
365         // get the demo name
366         strlcpy (name, Cmd_Argv(1), sizeof (name));
367         FS_DefaultExtension (name, ".dem", sizeof (name));
368
369         // start the map up
370         if (c > 2)
371                 Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command);
372
373         // open the demo file
374         Con_Printf("recording to %s.\n", name);
375         cls.demofile = FS_OpenRealFile(name, "wb", false);
376         if (!cls.demofile)
377         {
378                 Con_Print("ERROR: couldn't open.\n");
379                 return;
380         }
381         strlcpy(cls.demoname, name, sizeof(cls.demoname));
382
383         cls.forcetrack = track;
384         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
385
386         cls.demorecording = true;
387         cls.demo_lastcsprogssize = -1;
388         cls.demo_lastcsprogscrc = -1;
389 }
390
391
392 /*
393 ====================
394 CL_PlayDemo_f
395
396 play [demoname]
397 ====================
398 */
399 void CL_PlayDemo_f (void)
400 {
401         char    name[MAX_QPATH];
402         int c;
403         qboolean neg = false;
404         qfile_t *f;
405
406         if (Cmd_Argc() != 2)
407         {
408                 Con_Print("play <demoname> : plays a demo\n");
409                 return;
410         }
411
412         // open the demo file
413         strlcpy (name, Cmd_Argv(1), sizeof (name));
414         FS_DefaultExtension (name, ".dem", sizeof (name));
415         f = FS_OpenVirtualFile(name, false);
416         if (!f)
417         {
418                 Con_Printf("ERROR: couldn't open %s.\n", name);
419                 cls.demonum = -1;               // stop demo loop
420                 return;
421         }
422
423         cls.demostarting = true;
424
425         // disconnect from server
426         CL_Disconnect ();
427         Host_ShutdownServer ();
428
429         // update networking ports (this is mainly just needed at startup)
430         NetConn_UpdateSockets();
431
432         cls.protocol = PROTOCOL_QUAKE;
433
434         Con_Printf("Playing demo %s.\n", name);
435         cls.demofile = f;
436         strlcpy(cls.demoname, name, sizeof(cls.demoname));
437
438         cls.demoplayback = true;
439         cls.state = ca_connected;
440         cls.forcetrack = 0;
441
442         while ((c = FS_Getc (cls.demofile)) != '\n')
443                 if (c == '-')
444                         neg = true;
445                 else
446                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
447
448         if (neg)
449                 cls.forcetrack = -cls.forcetrack;
450
451         cls.demostarting = false;
452 }
453
454 /*
455 ====================
456 CL_FinishTimeDemo
457
458 ====================
459 */
460 void CL_FinishTimeDemo (void)
461 {
462         int frames;
463         int i;
464         double time, totalfpsavg;
465         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
466         static int benchmark_runs = 0;
467
468         cls.timedemo = false;
469
470         frames = cls.td_frames;
471         time = realtime - cls.td_starttime;
472         totalfpsavg = time > 0 ? frames / time : 0;
473         fpsmin = cls.td_onesecondminfps;
474         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
475         fpsmax = cls.td_onesecondmaxfps;
476         // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
477         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);
478         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);
479         if (COM_CheckParm("-benchmark"))
480         {
481                 ++benchmark_runs;
482                 i = COM_CheckParm("-benchmarkruns");
483                 if(i && i + 1 < com_argc)
484                 {
485                         if(atoi(com_argv[i + 1]) > benchmark_runs)
486                         {
487                                 // restart the benchmark
488                                 Cbuf_AddText(va("timedemo %s\n", cls.demoname));
489                                 // cannot execute here
490                         }
491                         else
492                                 Host_Quit_f();
493                 }
494                 else
495                         Host_Quit_f();
496         }
497 }
498
499 /*
500 ====================
501 CL_TimeDemo_f
502
503 timedemo [demoname]
504 ====================
505 */
506 void CL_TimeDemo_f (void)
507 {
508         if (Cmd_Argc() != 2)
509         {
510                 Con_Print("timedemo <demoname> : gets demo speeds\n");
511                 return;
512         }
513
514         srand(0); // predictable random sequence for benchmarking
515
516         CL_PlayDemo_f ();
517
518 // cls.td_starttime will be grabbed at the second frame of the demo, so
519 // all the loading time doesn't get counted
520
521         // instantly hide console and deactivate it
522         key_dest = key_game;
523         key_consoleactive = 0;
524         scr_con_current = 0;
525
526         cls.timedemo = true;
527         cls.td_frames = -2;             // skip the first frame
528         cls.demonum = -1;               // stop demo loop
529         cls.demonum = -1;               // stop demo loop
530 }
531