rewrote RecursiveHullCheck, no longer gets stuck on angle changes, and is generally...
[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 NET_SendMessages are skipped, and
31 NET_GetMessages 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_StopPlayback
41
42 Called when a demo file runs out, or the user starts a game
43 ==============
44 */
45 void CL_StopPlayback (void)
46 {
47         if (!cls.demoplayback)
48                 return;
49
50         Qclose (cls.demofile);
51         cls.demoplayback = false;
52         cls.demofile = NULL;
53         cls.state = ca_disconnected;
54
55         if (cls.timedemo)
56                 CL_FinishTimeDemo ();
57 }
58
59 /*
60 ====================
61 CL_WriteDemoMessage
62
63 Dumps the current net message, prefixed by the length and view angles
64 ====================
65 */
66 void CL_WriteDemoMessage (void)
67 {
68         int             len;
69         int             i;
70         float   f;
71
72         if (cls.demopaused) // LordHavoc: pausedemo
73                 return;
74
75         len = LittleLong (net_message.cursize);
76         Qwrite (cls.demofile, &len, 4);
77         for (i=0 ; i<3 ; i++)
78         {
79                 f = LittleFloat (cl.viewangles[i]);
80                 Qwrite (cls.demofile, &f, 4);
81         }
82         Qwrite (cls.demofile, net_message.data, net_message.cursize);
83         Qflush (cls.demofile);
84 }
85
86 /*
87 ====================
88 CL_GetMessage
89
90 Handles recording and playback of demos, on top of NET_ code
91 ====================
92 */
93 int CL_GetMessage (void)
94 {
95         int             r, i;
96         float   f;
97         
98         if      (cls.demoplayback)
99         {
100                 if (cls.demopaused) // LordHavoc: pausedemo
101                         return 0;
102
103         // decide if it is time to grab the next message                
104                 if (cls.signon == SIGNONS)      // always grab until fully connected
105                 {
106                         if (cls.timedemo)
107                         {
108                                 if (host_framecount == cls.td_lastframe)
109                                         return 0;               // already read this frame's message
110                                 cls.td_lastframe = host_framecount;
111                         // if this is the second frame, grab the real td_starttime
112                         // so the bogus time on the first frame doesn't count
113                                 if (host_framecount == cls.td_startframe + 1)
114                                         cls.td_starttime = realtime;
115                         }
116                         else if ( /* cl.time > 0 && */ cl.time <= cl.mtime[0])
117                         {
118                                         return 0;               // don't need another message yet
119                         }
120                 }
121                 
122         // get the next message
123                 Qread (cls.demofile, &net_message.cursize, 4);
124                 VectorCopy (cl.mviewangles[0], cl.mviewangles[1]);
125                 for (i=0 ; i<3 ; i++)
126                 {
127                         r = Qread (cls.demofile, &f, 4);
128                         cl.mviewangles[0][i] = LittleFloat (f);
129                 }
130                 
131                 net_message.cursize = LittleLong (net_message.cursize);
132                 if (net_message.cursize > MAX_MSGLEN)
133                         Host_Error ("Demo message > MAX_MSGLEN");
134                 r = Qread (cls.demofile, net_message.data, net_message.cursize);
135                 if (r != net_message.cursize)
136                 {
137                         CL_StopPlayback ();
138                         return 0;
139                 }
140         
141                 return 1;
142         }
143
144         while (1)
145         {
146                 r = NET_GetMessage (cls.netcon);
147                 
148                 if (r != 1 && r != 2)
149                         return r;
150         
151         // discard nop keepalive message
152                 if (net_message.cursize == 1 && net_message.data[0] == svc_nop)
153                         Con_Printf ("<-- server to client keepalive\n");
154                 else
155                         break;
156         }
157
158         if (cls.demorecording)
159                 CL_WriteDemoMessage ();
160         
161         return r;
162 }
163
164
165 /*
166 ====================
167 CL_Stop_f
168
169 stop recording a demo
170 ====================
171 */
172 void CL_Stop_f (void)
173 {
174         if (cmd_source != src_command)
175                 return;
176
177         if (!cls.demorecording)
178         {
179                 Con_Printf ("Not recording a demo.\n");
180                 return;
181         }
182
183 // write a disconnect message to the demo file
184         SZ_Clear (&net_message);
185         MSG_WriteByte (&net_message, svc_disconnect);
186         CL_WriteDemoMessage ();
187
188 // finish up
189         Qclose (cls.demofile);
190         cls.demofile = NULL;
191         cls.demorecording = false;
192         Con_Printf ("Completed demo\n");
193 }
194
195 /*
196 ====================
197 CL_Record_f
198
199 record <demoname> <map> [cd track]
200 ====================
201 */
202 void CL_Record_f (void)
203 {
204         int             c;
205         char    name[MAX_OSPATH];
206         int             track;
207
208         if (cmd_source != src_command)
209                 return;
210
211         c = Cmd_Argc();
212         if (c != 2 && c != 3 && c != 4)
213         {
214                 Con_Printf ("record <demoname> [<map> [cd track]]\n");
215                 return;
216         }
217
218         if (strstr(Cmd_Argv(1), ".."))
219         {
220                 Con_Printf ("Relative pathnames are not allowed.\n");
221                 return;
222         }
223
224         if (c == 2 && cls.state == ca_connected)
225         {
226                 Con_Printf("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
227                 return;
228         }
229
230 // write the forced cd track number, or -1
231         if (c == 4)
232         {
233                 track = atoi(Cmd_Argv(3));
234                 Con_Printf ("Forcing CD track to %i\n", cls.forcetrack);
235         }
236         else
237                 track = -1;     
238
239         sprintf (name, "%s/%s", com_gamedir, Cmd_Argv(1));
240         
241 //
242 // start the map up
243 //
244         if (c > 2)
245                 Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command);
246         
247 //
248 // open the demo file
249 //
250         COM_DefaultExtension (name, ".dem");
251
252         Con_Printf ("recording to %s.\n", name);
253         cls.demofile = Qopen (name, "wb");
254         if (!cls.demofile)
255         {
256                 Con_Printf ("ERROR: couldn't open.\n");
257                 return;
258         }
259
260         cls.forcetrack = track;
261         Qprintf (cls.demofile, "%i\n", cls.forcetrack);
262         
263         cls.demorecording = true;
264 }
265
266
267 /*
268 ====================
269 CL_PlayDemo_f
270
271 play [demoname]
272 ====================
273 */
274 void CL_PlayDemo_f (void)
275 {
276         char    name[256];
277         int c;
278         qboolean neg = false;
279
280         if (cmd_source != src_command)
281                 return;
282
283         if (Cmd_Argc() != 2)
284         {
285                 Con_Printf ("play <demoname> : plays a demo\n");
286                 return;
287         }
288
289 //      SCR_BeginLoadingPlaque();
290
291 //
292 // disconnect from server
293 //
294         CL_Disconnect ();
295         
296 //
297 // open the demo file
298 //
299         strcpy (name, Cmd_Argv(1));
300         COM_DefaultExtension (name, ".dem");
301
302         Con_Printf ("Playing demo from %s.\n", name);
303         COM_FOpenFile (name, &cls.demofile, false, true);
304         if (!cls.demofile)
305         {
306                 Con_Printf ("ERROR: couldn't open.\n");
307                 cls.demonum = -1;               // stop demo loop
308                 return;
309         }
310
311         cls.demoplayback = true;
312         cls.state = ca_connected;
313         cls.forcetrack = 0;
314
315         while ((c = Qgetc(cls.demofile)) != '\n')
316                 if (c == '-')
317                         neg = true;
318                 else
319                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
320
321         if (neg)
322                 cls.forcetrack = -cls.forcetrack;
323 // ZOID, fscanf is evil
324 //      fscanf (cls.demofile, "%i\n", &cls.forcetrack);
325 }
326
327 /*
328 ====================
329 CL_FinishTimeDemo
330
331 ====================
332 */
333 void CL_FinishTimeDemo (void)
334 {
335         int             frames;
336         double  time; // LordHavoc: changed timedemo accuracy to double
337         
338         cls.timedemo = false;
339         
340 // the first frame didn't count
341         frames = (host_framecount - cls.td_startframe) - 1;
342         time = realtime - cls.td_starttime;
343         if (!time)
344                 time = 1;
345         // LordHavoc: timedemo now prints out 7 digits of fraction
346         Con_Printf ("%i frames %5.7f seconds %5.7f fps\n", frames, time, frames/time);
347 }
348
349 /*
350 ====================
351 CL_TimeDemo_f
352
353 timedemo [demoname]
354 ====================
355 */
356 void CL_TimeDemo_f (void)
357 {
358         if (cmd_source != src_command)
359                 return;
360
361         if (Cmd_Argc() != 2)
362         {
363                 Con_Printf ("timedemo <demoname> : gets demo speeds\n");
364                 return;
365         }
366
367         CL_PlayDemo_f ();
368
369 // cls.td_starttime will be grabbed at the second frame of the demo, so
370 // all the loading time doesn't get counted
371
372         // instantly hide console and deactivate it
373         key_dest = key_game;
374         scr_conlines = 0;
375         scr_con_current = 0;
376
377         cls.timedemo = true;
378         cls.td_startframe = host_framecount;
379         cls.td_lastframe = -1;          // get a new message this frame
380 }
381