changed 1-frame warm-up period to 3 frames before time begins advancing
[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 int old_vsync = 0;
24
25 void CL_FinishTimeDemo (void);
26
27 /*
28 ==============================================================================
29
30 DEMO CODE
31
32 When a demo is playing back, all outgoing network messages are skipped, and
33 incoming messages are read from the demo file.
34
35 Whenever cl.time gets past the last received message, another message is
36 read from the demo file.
37 ==============================================================================
38 */
39
40 /*
41 =====================
42 CL_NextDemo
43
44 Called to play the next demo in the demo loop
45 =====================
46 */
47 void CL_NextDemo (void)
48 {
49         char    str[MAX_INPUTLINE];
50
51         if (cls.demonum == -1)
52                 return;         // don't play demos
53
54         if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
55         {
56                 cls.demonum = 0;
57                 if (!cls.demos[cls.demonum][0])
58                 {
59                         Con_Print("No demos listed with startdemos\n");
60                         cls.demonum = -1;
61                         return;
62                 }
63         }
64
65         sprintf (str,"playdemo %s\n", cls.demos[cls.demonum]);
66         Cbuf_InsertText (str);
67         cls.demonum++;
68 }
69
70 /*
71 ==============
72 CL_StopPlayback
73
74 Called when a demo file runs out, or the user starts a game
75 ==============
76 */
77 // LordHavoc: now called only by CL_Disconnect
78 void CL_StopPlayback (void)
79 {
80         if (!cls.demoplayback)
81                 return;
82
83         FS_Close (cls.demofile);
84         cls.demoplayback = false;
85         cls.demofile = NULL;
86
87         if (cls.timedemo)
88                 CL_FinishTimeDemo ();
89
90         if (COM_CheckParm("-demo"))
91                 Host_Quit_f();
92
93 }
94
95 /*
96 ====================
97 CL_WriteDemoMessage
98
99 Dumps the current net message, prefixed by the length and view angles
100 ====================
101 */
102 void CL_WriteDemoMessage (sizebuf_t *message)
103 {
104         int             len;
105         int             i;
106         float   f;
107
108         if (cls.demopaused) // LordHavoc: pausedemo
109                 return;
110
111         len = LittleLong (message->cursize);
112         FS_Write (cls.demofile, &len, 4);
113         for (i=0 ; i<3 ; i++)
114         {
115                 f = LittleFloat (cl.viewangles[i]);
116                 FS_Write (cls.demofile, &f, 4);
117         }
118         FS_Write (cls.demofile, message->data, message->cursize);
119 }
120
121 /*
122 ====================
123 CL_ReadDemoMessage
124
125 Handles playback of demos
126 ====================
127 */
128 void CL_ReadDemoMessage(void)
129 {
130         int r, i;
131         float f;
132
133         if (!cls.demoplayback)
134                 return;
135
136         // LordHavoc: pausedemo
137         if (cls.demopaused)
138                 return;
139
140         while (1)
141         {
142                 // decide if it is time to grab the next message
143                 // always grab until fully connected
144                 if (cls.signon == SIGNONS)
145                 {
146                         if (cls.timedemo)
147                         {
148                                 if (host_framecount == cls.td_lastframe)
149                                 {
150                                         // already read this frame's message
151                                         return;
152                                 }
153                                 if (cls.td_lastframe == -1)
154                                 {
155                                         // render a couple frames before we start counting
156                                         cls.td_startframe = host_framecount + 3;
157                                 }
158                                 cls.td_lastframe = host_framecount;
159                                 cls.td_onesecondframes++;
160                                 // don't read any new messages during the warm-up period
161                                 if (host_framecount < cls.td_startframe)
162                                         return;
163                                 // if this is the first official frame we can now grab the real
164                                 // td_starttime so the bogus time on the first frame doesn't
165                                 // count against the final report
166                                 if (host_framecount == cls.td_startframe)
167                                 {
168                                         cls.td_starttime = realtime;
169                                         cls.td_onesecondnexttime = realtime + 1;
170                                         cls.td_onesecondframes = 0;
171                                         cls.td_onesecondminframes = 0;
172                                         cls.td_onesecondmaxframes = 0;
173                                         cls.td_onesecondavgframes = 0;
174                                         cls.td_onesecondavgcount = 0;
175                                 }
176                                 if (realtime >= cls.td_onesecondnexttime)
177                                 {
178                                         if (cls.td_onesecondavgcount == 0)
179                                         {
180                                                 cls.td_onesecondminframes = cls.td_onesecondframes;
181                                                 cls.td_onesecondmaxframes = cls.td_onesecondframes;
182                                         }
183                                         cls.td_onesecondminframes = min(cls.td_onesecondminframes, cls.td_onesecondframes);
184                                         cls.td_onesecondmaxframes = max(cls.td_onesecondmaxframes, cls.td_onesecondframes);
185                                         cls.td_onesecondavgframes += cls.td_onesecondframes;
186                                         cls.td_onesecondavgcount++;
187                                         cls.td_onesecondframes = 0;
188                                         cls.td_onesecondnexttime++;
189                                 }
190                         }
191                         else if (cl.time <= cl.mtime[0])
192                         {
193                                 // don't need another message yet
194                                 return;
195                         }
196                 }
197
198                 // get the next message
199                 FS_Read(cls.demofile, &net_message.cursize, 4);
200                 net_message.cursize = LittleLong(net_message.cursize);
201                 if (net_message.cursize > net_message.maxsize)
202                         Host_Error("Demo message (%i) > net_message.maxsize (%i)", net_message.cursize, net_message.maxsize);
203                 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
204                 for (i = 0;i < 3;i++)
205                 {
206                         r = (int)FS_Read(cls.demofile, &f, 4);
207                         cl.mviewangles[0][i] = LittleFloat(f);
208                 }
209
210                 if (FS_Read(cls.demofile, net_message.data, net_message.cursize) == net_message.cursize)
211                 {
212                         MSG_BeginReading();
213                         CL_ParseServerMessage();
214
215                         // In case the demo contains a "svc_disconnect" message
216                         if (!cls.demoplayback)
217                                 return;
218                 }
219                 else
220                 {
221                         CL_Disconnect();
222                         return;
223                 }
224         }
225 }
226
227
228 /*
229 ====================
230 CL_Stop_f
231
232 stop recording a demo
233 ====================
234 */
235 void CL_Stop_f (void)
236 {
237         sizebuf_t buf;
238         unsigned char bufdata[64];
239
240         if (!cls.demorecording)
241         {
242                 Con_Print("Not recording a demo.\n");
243                 return;
244         }
245
246 // write a disconnect message to the demo file
247         // LordHavoc: don't replace the net_message when doing this
248         buf.data = bufdata;
249         buf.maxsize = sizeof(bufdata);
250         SZ_Clear(&buf);
251         MSG_WriteByte(&buf, svc_disconnect);
252         CL_WriteDemoMessage(&buf);
253
254 // finish up
255         FS_Close (cls.demofile);
256         cls.demofile = NULL;
257         cls.demorecording = false;
258         Con_Print("Completed demo\n");
259 }
260
261 /*
262 ====================
263 CL_Record_f
264
265 record <demoname> <map> [cd track]
266 ====================
267 */
268 void CL_Record_f (void)
269 {
270         int c, track;
271         char name[MAX_OSPATH];
272
273         c = Cmd_Argc();
274         if (c != 2 && c != 3 && c != 4)
275         {
276                 Con_Print("record <demoname> [<map> [cd track]]\n");
277                 return;
278         }
279
280         if (strstr(Cmd_Argv(1), ".."))
281         {
282                 Con_Print("Relative pathnames are not allowed.\n");
283                 return;
284         }
285
286         if (c == 2 && cls.state == ca_connected)
287         {
288                 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
289                 return;
290         }
291
292         if (cls.state == ca_connected)
293                 CL_Disconnect();
294
295         // write the forced cd track number, or -1
296         if (c == 4)
297         {
298                 track = atoi(Cmd_Argv(3));
299                 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
300         }
301         else
302                 track = -1;
303
304         // get the demo name
305         strlcpy (name, Cmd_Argv(1), sizeof (name));
306         FS_DefaultExtension (name, ".dem", sizeof (name));
307
308         // start the map up
309         if (c > 2)
310                 Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command);
311
312         // open the demo file
313         Con_Printf("recording to %s.\n", name);
314         cls.demofile = FS_Open (name, "wb", false, false);
315         if (!cls.demofile)
316         {
317                 Con_Print("ERROR: couldn't open.\n");
318                 return;
319         }
320
321         cls.forcetrack = track;
322         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
323
324         cls.demorecording = true;
325 }
326
327
328 /*
329 ====================
330 CL_PlayDemo_f
331
332 play [demoname]
333 ====================
334 */
335 void CL_PlayDemo_f (void)
336 {
337         char    name[MAX_QPATH];
338         int c;
339         qboolean neg = false;
340
341         if (Cmd_Argc() != 2)
342         {
343                 Con_Print("play <demoname> : plays a demo\n");
344                 return;
345         }
346
347         // disconnect from server
348         CL_Disconnect ();
349         Host_ShutdownServer ();
350
351         // update networking ports (this is mainly just needed at startup)
352         NetConn_UpdateSockets();
353
354         // open the demo file
355         strlcpy (name, Cmd_Argv(1), sizeof (name));
356         FS_DefaultExtension (name, ".dem", sizeof (name));
357         cls.protocol = PROTOCOL_QUAKE;
358
359         Con_Printf("Playing demo from %s.\n", name);
360         cls.demofile = FS_Open (name, "rb", false, false);
361         if (!cls.demofile)
362         {
363                 Con_Print("ERROR: couldn't open.\n");
364                 cls.demonum = -1;               // stop demo loop
365                 return;
366         }
367
368         strlcpy(cls.demoname, name, sizeof(cls.demoname));
369         cls.demoplayback = true;
370         cls.state = ca_connected;
371         cls.forcetrack = 0;
372
373         while ((c = FS_Getc (cls.demofile)) != '\n')
374                 if (c == '-')
375                         neg = true;
376                 else
377                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
378
379         if (neg)
380                 cls.forcetrack = -cls.forcetrack;
381 }
382
383 /*
384 ====================
385 CL_FinishTimeDemo
386
387 ====================
388 */
389 void CL_FinishTimeDemo (void)
390 {
391         int frames;
392         double time, totalfpsavg;
393         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
394
395         cls.timedemo = false;
396
397 // the first frame didn't count
398         frames = (host_framecount - cls.td_startframe) - 1;
399         time = realtime - cls.td_starttime;
400         totalfpsavg = time > 0 ? frames / time : 0;
401         fpsmin = cls.td_onesecondminframes;
402         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgframes / cls.td_onesecondavgcount : 0;
403         fpsmax = cls.td_onesecondmaxframes;
404         // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
405         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);
406         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);
407         if (COM_CheckParm("-benchmark"))
408                 Host_Quit_f();
409 }
410
411 /*
412 ====================
413 CL_TimeDemo_f
414
415 timedemo [demoname]
416 ====================
417 */
418 void CL_TimeDemo_f (void)
419 {
420         if (Cmd_Argc() != 2)
421         {
422                 Con_Print("timedemo <demoname> : gets demo speeds\n");
423                 return;
424         }
425
426         srand(0); // predictable random sequence for benchmarking
427
428         CL_PlayDemo_f ();
429
430 // cls.td_starttime will be grabbed at the second frame of the demo, so
431 // all the loading time doesn't get counted
432
433         // instantly hide console and deactivate it
434         key_dest = key_game;
435         key_consoleactive = 0;
436         scr_con_current = 0;
437
438         cls.timedemo = true;
439         // get first message this frame
440         cls.td_lastframe = -1;
441 }
442