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