demo recording: stuff csprogs.dat files into .dem files so demos can always be played...
[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 (void **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_Open(cls.demoname, "wb", false, 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 (void **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 r, 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                         r = (int)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         FS_Close (cls.demofile);
310         cls.demofile = NULL;
311         cls.demorecording = false;
312         Con_Print("Completed demo\n");
313 }
314
315 /*
316 ====================
317 CL_Record_f
318
319 record <demoname> <map> [cd track]
320 ====================
321 */
322 void CL_Record_f (void)
323 {
324         int c, track;
325         char name[MAX_OSPATH];
326
327         c = Cmd_Argc();
328         if (c != 2 && c != 3 && c != 4)
329         {
330                 Con_Print("record <demoname> [<map> [cd track]]\n");
331                 return;
332         }
333
334         if (strstr(Cmd_Argv(1), ".."))
335         {
336                 Con_Print("Relative pathnames are not allowed.\n");
337                 return;
338         }
339
340         if (c == 2 && cls.state == ca_connected)
341         {
342                 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
343                 return;
344         }
345
346         if (cls.state == ca_connected)
347                 CL_Disconnect();
348
349         // write the forced cd track number, or -1
350         if (c == 4)
351         {
352                 track = atoi(Cmd_Argv(3));
353                 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
354         }
355         else
356                 track = -1;
357
358         // get the demo name
359         strlcpy (name, Cmd_Argv(1), sizeof (name));
360         FS_DefaultExtension (name, ".dem", sizeof (name));
361
362         // start the map up
363         if (c > 2)
364                 Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command);
365
366         // open the demo file
367         Con_Printf("recording to %s.\n", name);
368         cls.demofile = FS_Open (name, "wb", false, false);
369         if (!cls.demofile)
370         {
371                 Con_Print("ERROR: couldn't open.\n");
372                 return;
373         }
374         strlcpy(cls.demoname, name, sizeof(cls.demoname));
375
376         cls.forcetrack = track;
377         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
378
379         cls.demorecording = true;
380 }
381
382
383 /*
384 ====================
385 CL_PlayDemo_f
386
387 play [demoname]
388 ====================
389 */
390 void CL_PlayDemo_f (void)
391 {
392         char    name[MAX_QPATH];
393         int c;
394         qboolean neg = false;
395
396         if (Cmd_Argc() != 2)
397         {
398                 Con_Print("play <demoname> : plays a demo\n");
399                 return;
400         }
401
402         // disconnect from server
403         CL_Disconnect ();
404         Host_ShutdownServer ();
405
406         // update networking ports (this is mainly just needed at startup)
407         NetConn_UpdateSockets();
408
409         // open the demo file
410         strlcpy (name, Cmd_Argv(1), sizeof (name));
411         FS_DefaultExtension (name, ".dem", sizeof (name));
412         cls.protocol = PROTOCOL_QUAKE;
413
414         Con_Printf("Playing demo %s.\n", name);
415         cls.demofile = FS_Open (name, "rb", false, false);
416         if (!cls.demofile)
417         {
418                 Con_Print("ERROR: couldn't open.\n");
419                 cls.demonum = -1;               // stop demo loop
420                 return;
421         }
422         strlcpy(cls.demoname, name, sizeof(cls.demoname));
423
424         cls.demoplayback = true;
425         cls.state = ca_connected;
426         cls.forcetrack = 0;
427
428         while ((c = FS_Getc (cls.demofile)) != '\n')
429                 if (c == '-')
430                         neg = true;
431                 else
432                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
433
434         if (neg)
435                 cls.forcetrack = -cls.forcetrack;
436 }
437
438 /*
439 ====================
440 CL_FinishTimeDemo
441
442 ====================
443 */
444 void CL_FinishTimeDemo (void)
445 {
446         int frames;
447         double time, totalfpsavg;
448         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
449
450         cls.timedemo = false;
451
452         frames = cls.td_frames;
453         time = realtime - cls.td_starttime;
454         totalfpsavg = time > 0 ? frames / time : 0;
455         fpsmin = cls.td_onesecondminfps;
456         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
457         fpsmax = cls.td_onesecondmaxfps;
458         // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
459         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);
460         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);
461         if (COM_CheckParm("-benchmark"))
462                 Host_Quit_f();
463 }
464
465 /*
466 ====================
467 CL_TimeDemo_f
468
469 timedemo [demoname]
470 ====================
471 */
472 void CL_TimeDemo_f (void)
473 {
474         if (Cmd_Argc() != 2)
475         {
476                 Con_Print("timedemo <demoname> : gets demo speeds\n");
477                 return;
478         }
479
480         srand(0); // predictable random sequence for benchmarking
481
482         CL_PlayDemo_f ();
483
484 // cls.td_starttime will be grabbed at the second frame of the demo, so
485 // all the loading time doesn't get counted
486
487         // instantly hide console and deactivate it
488         key_dest = key_game;
489         key_consoleactive = 0;
490         scr_con_current = 0;
491
492         cls.timedemo = true;
493         cls.td_frames = -2;             // skip the first frame
494         cls.demonum = -1;               // stop demo loop
495         cls.demonum = -1;               // stop demo loop
496 }
497