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