9d8e4a0056c8fab3ed84402109746ad8f19e2eda
[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         sprintf (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_ReadDemoMessage
125
126 Handles playback of demos
127 ====================
128 */
129 void CL_ReadDemoMessage(void)
130 {
131         int r, i;
132         float f;
133
134         if (!cls.demoplayback)
135                 return;
136
137         // LordHavoc: pausedemo
138         if (cls.demopaused)
139                 return;
140
141         for (;;)
142         {
143                 // decide if it is time to grab the next message
144                 // always grab until fully connected
145                 if (cls.signon == SIGNONS)
146                 {
147                         if (cls.timedemo)
148                         {
149                                 cls.td_frames++;
150                                 cls.td_onesecondframes++;
151                                 // if this is the first official frame we can now grab the real
152                                 // td_starttime so the bogus time on the first frame doesn't
153                                 // count against the final report
154                                 if (cls.td_frames == 0)
155                                 {
156                                         cls.td_starttime = realtime;
157                                         cls.td_onesecondnexttime = cl.time + 1;
158                                         cls.td_onesecondframes = 0;
159                                         cls.td_onesecondminframes = 0;
160                                         cls.td_onesecondmaxframes = 0;
161                                         cls.td_onesecondavgframes = 0;
162                                         cls.td_onesecondavgcount = 0;
163                                 }
164                                 if (cl.time >= cls.td_onesecondnexttime)
165                                 {
166                                         if (cls.td_onesecondavgcount == 0)
167                                         {
168                                                 cls.td_onesecondminframes = cls.td_onesecondframes;
169                                                 cls.td_onesecondmaxframes = cls.td_onesecondframes;
170                                         }
171                                         cls.td_onesecondminframes = min(cls.td_onesecondminframes, cls.td_onesecondframes);
172                                         cls.td_onesecondmaxframes = max(cls.td_onesecondmaxframes, cls.td_onesecondframes);
173                                         cls.td_onesecondavgframes += cls.td_onesecondframes;
174                                         cls.td_onesecondavgcount++;
175                                         cls.td_onesecondframes = 0;
176                                         cls.td_onesecondnexttime++;
177                                 }
178                         }
179                         else if (cl.time <= cl.mtime[0])
180                         {
181                                 // don't need another message yet
182                                 return;
183                         }
184                 }
185
186                 // get the next message
187                 FS_Read(cls.demofile, &net_message.cursize, 4);
188                 net_message.cursize = LittleLong(net_message.cursize);
189                 if (net_message.cursize > net_message.maxsize)
190                         Host_Error("Demo message (%i) > net_message.maxsize (%i)", net_message.cursize, net_message.maxsize);
191                 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
192                 for (i = 0;i < 3;i++)
193                 {
194                         r = (int)FS_Read(cls.demofile, &f, 4);
195                         cl.mviewangles[0][i] = LittleFloat(f);
196                 }
197
198                 if (FS_Read(cls.demofile, net_message.data, net_message.cursize) == net_message.cursize)
199                 {
200                         MSG_BeginReading();
201                         CL_ParseServerMessage();
202
203                         // In case the demo contains a "svc_disconnect" message
204                         if (!cls.demoplayback)
205                                 return;
206
207                         if (cls.timedemo)
208                                 return;
209                 }
210                 else
211                 {
212                         CL_Disconnect();
213                         return;
214                 }
215         }
216 }
217
218
219 /*
220 ====================
221 CL_Stop_f
222
223 stop recording a demo
224 ====================
225 */
226 void CL_Stop_f (void)
227 {
228         sizebuf_t buf;
229         unsigned char bufdata[64];
230
231         if (!cls.demorecording)
232         {
233                 Con_Print("Not recording a demo.\n");
234                 return;
235         }
236
237 // write a disconnect message to the demo file
238         // LordHavoc: don't replace the net_message when doing this
239         buf.data = bufdata;
240         buf.maxsize = sizeof(bufdata);
241         SZ_Clear(&buf);
242         MSG_WriteByte(&buf, svc_disconnect);
243         CL_WriteDemoMessage(&buf);
244
245 // finish up
246         FS_Close (cls.demofile);
247         cls.demofile = NULL;
248         cls.demorecording = false;
249         Con_Print("Completed demo\n");
250 }
251
252 /*
253 ====================
254 CL_Record_f
255
256 record <demoname> <map> [cd track]
257 ====================
258 */
259 void CL_Record_f (void)
260 {
261         int c, track;
262         char name[MAX_OSPATH];
263
264         c = Cmd_Argc();
265         if (c != 2 && c != 3 && c != 4)
266         {
267                 Con_Print("record <demoname> [<map> [cd track]]\n");
268                 return;
269         }
270
271         if (strstr(Cmd_Argv(1), ".."))
272         {
273                 Con_Print("Relative pathnames are not allowed.\n");
274                 return;
275         }
276
277         if (c == 2 && cls.state == ca_connected)
278         {
279                 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
280                 return;
281         }
282
283         if (cls.state == ca_connected)
284                 CL_Disconnect();
285
286         // write the forced cd track number, or -1
287         if (c == 4)
288         {
289                 track = atoi(Cmd_Argv(3));
290                 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
291         }
292         else
293                 track = -1;
294
295         // get the demo name
296         strlcpy (name, Cmd_Argv(1), sizeof (name));
297         FS_DefaultExtension (name, ".dem", sizeof (name));
298
299         // start the map up
300         if (c > 2)
301                 Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command);
302
303         // open the demo file
304         Con_Printf("recording to %s.\n", name);
305         cls.demofile = FS_Open (name, "wb", false, false);
306         if (!cls.demofile)
307         {
308                 Con_Print("ERROR: couldn't open.\n");
309                 return;
310         }
311
312         cls.forcetrack = track;
313         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
314
315         cls.demorecording = true;
316 }
317
318
319 /*
320 ====================
321 CL_PlayDemo_f
322
323 play [demoname]
324 ====================
325 */
326 void CL_PlayDemo_f (void)
327 {
328         char    name[MAX_QPATH];
329         int c;
330         qboolean neg = false;
331
332         if (Cmd_Argc() != 2)
333         {
334                 Con_Print("play <demoname> : plays a demo\n");
335                 return;
336         }
337
338         // disconnect from server
339         CL_Disconnect ();
340         Host_ShutdownServer ();
341
342         // update networking ports (this is mainly just needed at startup)
343         NetConn_UpdateSockets();
344
345         // open the demo file
346         strlcpy (name, Cmd_Argv(1), sizeof (name));
347         FS_DefaultExtension (name, ".dem", sizeof (name));
348         cls.protocol = PROTOCOL_QUAKE;
349
350         Con_Printf("Playing demo from %s.\n", name);
351         cls.demofile = FS_Open (name, "rb", false, false);
352         if (!cls.demofile)
353         {
354                 Con_Print("ERROR: couldn't open.\n");
355                 cls.demonum = -1;               // stop demo loop
356                 return;
357         }
358
359         strlcpy(cls.demoname, name, sizeof(cls.demoname));
360         cls.demoplayback = true;
361         cls.state = ca_connected;
362         cls.forcetrack = 0;
363
364         while ((c = FS_Getc (cls.demofile)) != '\n')
365                 if (c == '-')
366                         neg = true;
367                 else
368                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
369
370         if (neg)
371                 cls.forcetrack = -cls.forcetrack;
372 }
373
374 /*
375 ====================
376 CL_FinishTimeDemo
377
378 ====================
379 */
380 void CL_FinishTimeDemo (void)
381 {
382         int frames;
383         double time, totalfpsavg;
384         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
385
386         cls.timedemo = false;
387
388         frames = cls.td_frames;
389         time = realtime - cls.td_starttime;
390         totalfpsavg = time > 0 ? frames / time : 0;
391         fpsmin = cls.td_onesecondminframes;
392         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgframes / cls.td_onesecondavgcount : 0;
393         fpsmax = cls.td_onesecondmaxframes;
394         // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
395         Con_Printf("%i frames %5.7f seconds %5.7f fps, one-second min/avg/max: %.0f %.0f %.0f\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax);
396         Log_Printf("benchmark.log", "date %s | enginedate %s | demo %s | commandline %s | result %i frames %5.7f seconds %5.7f fps, one-second min/avg/max: %.0f %.0f %.0f\n", Sys_TimeString("%Y-%m-%d %H:%M:%S"), buildstring, cls.demoname, cmdline.string, frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax);
397         if (COM_CheckParm("-benchmark"))
398                 Host_Quit_f();
399 }
400
401 /*
402 ====================
403 CL_TimeDemo_f
404
405 timedemo [demoname]
406 ====================
407 */
408 void CL_TimeDemo_f (void)
409 {
410         if (Cmd_Argc() != 2)
411         {
412                 Con_Print("timedemo <demoname> : gets demo speeds\n");
413                 return;
414         }
415
416         srand(0); // predictable random sequence for benchmarking
417
418         CL_PlayDemo_f ();
419
420 // cls.td_starttime will be grabbed at the second frame of the demo, so
421 // all the loading time doesn't get counted
422
423         // instantly hide console and deactivate it
424         key_dest = key_game;
425         key_consoleactive = 0;
426         scr_con_current = 0;
427
428         cls.timedemo = true;
429         cls.td_frames = -2;             // skip the first frame
430         cls.demonum = -1;               // stop demo loop
431         cls.demonum = -1;               // stop demo loop
432 }
433