add a DEMOMSG_CLIENT_TO_SERVER flag to demos (0x80000000) in the size field; packets...
[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 & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
190                         continue;
191                 if (net_message.cursize > net_message.maxsize)
192                         Host_Error("Demo message (%i) > net_message.maxsize (%i)", net_message.cursize, net_message.maxsize);
193                 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
194                 for (i = 0;i < 3;i++)
195                 {
196                         r = (int)FS_Read(cls.demofile, &f, 4);
197                         cl.mviewangles[0][i] = LittleFloat(f);
198                 }
199
200                 if (FS_Read(cls.demofile, net_message.data, net_message.cursize) == net_message.cursize)
201                 {
202                         MSG_BeginReading();
203                         CL_ParseServerMessage();
204
205                         // In case the demo contains a "svc_disconnect" message
206                         if (!cls.demoplayback)
207                                 return;
208
209                         if (cls.timedemo)
210                                 return;
211                 }
212                 else
213                 {
214                         CL_Disconnect();
215                         return;
216                 }
217         }
218 }
219
220
221 /*
222 ====================
223 CL_Stop_f
224
225 stop recording a demo
226 ====================
227 */
228 void CL_Stop_f (void)
229 {
230         sizebuf_t buf;
231         unsigned char bufdata[64];
232
233         if (!cls.demorecording)
234         {
235                 Con_Print("Not recording a demo.\n");
236                 return;
237         }
238
239 // write a disconnect message to the demo file
240         // LordHavoc: don't replace the net_message when doing this
241         buf.data = bufdata;
242         buf.maxsize = sizeof(bufdata);
243         SZ_Clear(&buf);
244         MSG_WriteByte(&buf, svc_disconnect);
245         CL_WriteDemoMessage(&buf);
246
247 // finish up
248         FS_Close (cls.demofile);
249         cls.demofile = NULL;
250         cls.demorecording = false;
251         Con_Print("Completed demo\n");
252 }
253
254 /*
255 ====================
256 CL_Record_f
257
258 record <demoname> <map> [cd track]
259 ====================
260 */
261 void CL_Record_f (void)
262 {
263         int c, track;
264         char name[MAX_OSPATH];
265
266         c = Cmd_Argc();
267         if (c != 2 && c != 3 && c != 4)
268         {
269                 Con_Print("record <demoname> [<map> [cd track]]\n");
270                 return;
271         }
272
273         if (strstr(Cmd_Argv(1), ".."))
274         {
275                 Con_Print("Relative pathnames are not allowed.\n");
276                 return;
277         }
278
279         if (c == 2 && cls.state == ca_connected)
280         {
281                 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
282                 return;
283         }
284
285         if (cls.state == ca_connected)
286                 CL_Disconnect();
287
288         // write the forced cd track number, or -1
289         if (c == 4)
290         {
291                 track = atoi(Cmd_Argv(3));
292                 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
293         }
294         else
295                 track = -1;
296
297         // get the demo name
298         strlcpy (name, Cmd_Argv(1), sizeof (name));
299         FS_DefaultExtension (name, ".dem", sizeof (name));
300
301         // start the map up
302         if (c > 2)
303                 Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command);
304
305         // open the demo file
306         Con_Printf("recording to %s.\n", name);
307         cls.demofile = FS_Open (name, "wb", false, false);
308         if (!cls.demofile)
309         {
310                 Con_Print("ERROR: couldn't open.\n");
311                 return;
312         }
313
314         cls.forcetrack = track;
315         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
316
317         cls.demorecording = true;
318 }
319
320
321 /*
322 ====================
323 CL_PlayDemo_f
324
325 play [demoname]
326 ====================
327 */
328 void CL_PlayDemo_f (void)
329 {
330         char    name[MAX_QPATH];
331         int c;
332         qboolean neg = false;
333
334         if (Cmd_Argc() != 2)
335         {
336                 Con_Print("play <demoname> : plays a demo\n");
337                 return;
338         }
339
340         // disconnect from server
341         CL_Disconnect ();
342         Host_ShutdownServer ();
343
344         // update networking ports (this is mainly just needed at startup)
345         NetConn_UpdateSockets();
346
347         // open the demo file
348         strlcpy (name, Cmd_Argv(1), sizeof (name));
349         FS_DefaultExtension (name, ".dem", sizeof (name));
350         cls.protocol = PROTOCOL_QUAKE;
351
352         Con_Printf("Playing demo from %s.\n", name);
353         cls.demofile = FS_Open (name, "rb", false, false);
354         if (!cls.demofile)
355         {
356                 Con_Print("ERROR: couldn't open.\n");
357                 cls.demonum = -1;               // stop demo loop
358                 return;
359         }
360
361         strlcpy(cls.demoname, name, sizeof(cls.demoname));
362         cls.demoplayback = true;
363         cls.state = ca_connected;
364         cls.forcetrack = 0;
365
366         while ((c = FS_Getc (cls.demofile)) != '\n')
367                 if (c == '-')
368                         neg = true;
369                 else
370                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
371
372         if (neg)
373                 cls.forcetrack = -cls.forcetrack;
374 }
375
376 /*
377 ====================
378 CL_FinishTimeDemo
379
380 ====================
381 */
382 void CL_FinishTimeDemo (void)
383 {
384         int frames;
385         double time, totalfpsavg;
386         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
387
388         cls.timedemo = false;
389
390         frames = cls.td_frames;
391         time = realtime - cls.td_starttime;
392         totalfpsavg = time > 0 ? frames / time : 0;
393         fpsmin = cls.td_onesecondminframes;
394         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgframes / cls.td_onesecondavgcount : 0;
395         fpsmax = cls.td_onesecondmaxframes;
396         // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
397         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);
398         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);
399         if (COM_CheckParm("-benchmark"))
400                 Host_Quit_f();
401 }
402
403 /*
404 ====================
405 CL_TimeDemo_f
406
407 timedemo [demoname]
408 ====================
409 */
410 void CL_TimeDemo_f (void)
411 {
412         if (Cmd_Argc() != 2)
413         {
414                 Con_Print("timedemo <demoname> : gets demo speeds\n");
415                 return;
416         }
417
418         srand(0); // predictable random sequence for benchmarking
419
420         CL_PlayDemo_f ();
421
422 // cls.td_starttime will be grabbed at the second frame of the demo, so
423 // all the loading time doesn't get counted
424
425         // instantly hide console and deactivate it
426         key_dest = key_game;
427         key_consoleactive = 0;
428         scr_con_current = 0;
429
430         cls.timedemo = true;
431         cls.td_frames = -2;             // skip the first frame
432         cls.demonum = -1;               // stop demo loop
433         cls.demonum = -1;               // stop demo loop
434 }
435