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