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