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