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