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