]> git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_demo.c
5be4e8080d8ad24998f38ff50f7db9bc1918de1c
[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 (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_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 (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 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         cls.demo_lastcsprogssize = -1;
381         cls.demo_lastcsprogscrc = -1;
382 }
383
384
385 /*
386 ====================
387 CL_PlayDemo_f
388
389 play [demoname]
390 ====================
391 */
392 void CL_PlayDemo_f (void)
393 {
394         char    name[MAX_QPATH];
395         int c;
396         qboolean neg = false;
397
398         if (Cmd_Argc() != 2)
399         {
400                 Con_Print("play <demoname> : plays a demo\n");
401                 return;
402         }
403
404         // disconnect from server
405         CL_Disconnect ();
406         Host_ShutdownServer ();
407
408         // update networking ports (this is mainly just needed at startup)
409         NetConn_UpdateSockets();
410
411         // open the demo file
412         strlcpy (name, Cmd_Argv(1), sizeof (name));
413         FS_DefaultExtension (name, ".dem", sizeof (name));
414         cls.protocol = PROTOCOL_QUAKE;
415
416         Con_Printf("Playing demo %s.\n", name);
417         cls.demofile = FS_Open (name, "rb", false, false);
418         if (!cls.demofile)
419         {
420                 Con_Print("ERROR: couldn't open.\n");
421                 cls.demonum = -1;               // stop demo loop
422                 return;
423         }
424         strlcpy(cls.demoname, name, sizeof(cls.demoname));
425
426         cls.demoplayback = true;
427         cls.state = ca_connected;
428         cls.forcetrack = 0;
429
430         while ((c = FS_Getc (cls.demofile)) != '\n')
431                 if (c == '-')
432                         neg = true;
433                 else
434                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
435
436         if (neg)
437                 cls.forcetrack = -cls.forcetrack;
438 }
439
440 /*
441 ====================
442 CL_FinishTimeDemo
443
444 ====================
445 */
446 void CL_FinishTimeDemo (void)
447 {
448         int frames;
449         double time, totalfpsavg;
450         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
451
452         cls.timedemo = false;
453
454         frames = cls.td_frames;
455         time = realtime - cls.td_starttime;
456         totalfpsavg = time > 0 ? frames / time : 0;
457         fpsmin = cls.td_onesecondminfps;
458         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
459         fpsmax = cls.td_onesecondmaxfps;
460         // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
461         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);
462         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);
463         if (COM_CheckParm("-benchmark"))
464                 Host_Quit_f();
465 }
466
467 /*
468 ====================
469 CL_TimeDemo_f
470
471 timedemo [demoname]
472 ====================
473 */
474 void CL_TimeDemo_f (void)
475 {
476         if (Cmd_Argc() != 2)
477         {
478                 Con_Print("timedemo <demoname> : gets demo speeds\n");
479                 return;
480         }
481
482         srand(0); // predictable random sequence for benchmarking
483
484         CL_PlayDemo_f ();
485
486 // cls.td_starttime will be grabbed at the second frame of the demo, so
487 // all the loading time doesn't get counted
488
489         // instantly hide console and deactivate it
490         key_dest = key_game;
491         key_consoleactive = 0;
492         scr_con_current = 0;
493
494         cls.timedemo = true;
495         cls.td_frames = -2;             // skip the first frame
496         cls.demonum = -1;               // stop demo loop
497         cls.demonum = -1;               // stop demo loop
498 }
499