780b1f1c9b5355cd3a3170eed4405fba941ab2fd
[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_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 //      SCR_BeginLoadingPlaque ();
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_Printf ("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         Qclose (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         Qwrite (cls.demofile, &len, 4);
109         for (i=0 ; i<3 ; i++)
110         {
111                 f = LittleFloat (cl.viewangles[i]);
112                 Qwrite (cls.demofile, &f, 4);
113         }
114         Qwrite (cls.demofile, net_message.data, net_message.cursize);
115         Qflush (cls.demofile);
116 }
117
118 /*
119 ====================
120 CL_GetMessage
121
122 Handles recording and playback of demos, on top of NET_ code
123 ====================
124 */
125 int CL_GetMessage (void)
126 {
127         int             r, i;
128         float   f;
129         
130         if      (cls.demoplayback)
131         {
132                 if (cls.demopaused) // LordHavoc: pausedemo
133                         return 0;
134
135         // decide if it is time to grab the next message                
136                 if (cls.signon == SIGNONS)      // always grab until fully connected
137                 {
138                         if (cls.timedemo)
139                         {
140                                 if (host_framecount == cls.td_lastframe)
141                                         return 0;               // already read this frame's message
142                                 cls.td_lastframe = host_framecount;
143                         // if this is the second frame, grab the real td_starttime
144                         // so the bogus time on the first frame doesn't count
145                                 if (host_framecount == cls.td_startframe + 1)
146                                         cls.td_starttime = realtime;
147                         }
148                         else if ( /* cl.time > 0 && */ cl.time <= cl.mtime[0])
149                         {
150                                         return 0;               // don't need another message yet
151                         }
152                 }
153                 
154         // get the next message
155                 Qread (cls.demofile, &net_message.cursize, 4);
156                 VectorCopy (cl.mviewangles[0], cl.mviewangles[1]);
157                 for (i=0 ; i<3 ; i++)
158                 {
159                         r = Qread (cls.demofile, &f, 4);
160                         cl.mviewangles[0][i] = LittleFloat (f);
161                 }
162                 
163                 net_message.cursize = LittleLong (net_message.cursize);
164                 if (net_message.cursize > MAX_MSGLEN)
165                         Host_Error ("Demo message > MAX_MSGLEN");
166                 r = Qread (cls.demofile, net_message.data, net_message.cursize);
167                 if (r != net_message.cursize)
168                 {
169                         CL_Disconnect ();
170                         return 0;
171                 }
172         
173                 return 1;
174         }
175
176         while (1)
177         {
178                 r = NET_GetMessage (cls.netcon);
179                 
180                 if (r != 1 && r != 2)
181                         return r;
182         
183         // discard nop keepalive message
184                 if (net_message.cursize == 1 && net_message.data[0] == svc_nop)
185                         Con_Printf ("<-- server to client keepalive\n");
186                 else
187                         break;
188         }
189
190         if (cls.demorecording)
191                 CL_WriteDemoMessage ();
192         
193         return r;
194 }
195
196
197 /*
198 ====================
199 CL_Stop_f
200
201 stop recording a demo
202 ====================
203 */
204 void CL_Stop_f (void)
205 {
206         if (cmd_source != src_command)
207                 return;
208
209         if (!cls.demorecording)
210         {
211                 Con_Printf ("Not recording a demo.\n");
212                 return;
213         }
214
215 // write a disconnect message to the demo file
216         SZ_Clear (&net_message);
217         MSG_WriteByte (&net_message, svc_disconnect);
218         CL_WriteDemoMessage ();
219
220 // finish up
221         Qclose (cls.demofile);
222         cls.demofile = NULL;
223         cls.demorecording = false;
224         Con_Printf ("Completed demo\n");
225 }
226
227 /*
228 ====================
229 CL_Record_f
230
231 record <demoname> <map> [cd track]
232 ====================
233 */
234 void CL_Record_f (void)
235 {
236         int             c;
237         char    name[MAX_OSPATH];
238         int             track;
239
240         if (cmd_source != src_command)
241                 return;
242
243         c = Cmd_Argc();
244         if (c != 2 && c != 3 && c != 4)
245         {
246                 Con_Printf ("record <demoname> [<map> [cd track]]\n");
247                 return;
248         }
249
250         if (strstr(Cmd_Argv(1), ".."))
251         {
252                 Con_Printf ("Relative pathnames are not allowed.\n");
253                 return;
254         }
255
256         if (c == 2 && cls.state == ca_connected)
257         {
258                 Con_Printf("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
259                 return;
260         }
261
262 // write the forced cd track number, or -1
263         if (c == 4)
264         {
265                 track = atoi(Cmd_Argv(3));
266                 Con_Printf ("Forcing CD track to %i\n", cls.forcetrack);
267         }
268         else
269                 track = -1;     
270
271         sprintf (name, "%s/%s", com_gamedir, Cmd_Argv(1));
272         
273 //
274 // start the map up
275 //
276         if (c > 2)
277                 Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command);
278         
279 //
280 // open the demo file
281 //
282         COM_DefaultExtension (name, ".dem");
283
284         Con_Printf ("recording to %s.\n", name);
285         cls.demofile = Qopen (name, "wb");
286         if (!cls.demofile)
287         {
288                 Con_Printf ("ERROR: couldn't open.\n");
289                 return;
290         }
291
292         cls.forcetrack = track;
293         Qprintf (cls.demofile, "%i\n", cls.forcetrack);
294         
295         cls.demorecording = true;
296 }
297
298
299 /*
300 ====================
301 CL_PlayDemo_f
302
303 play [demoname]
304 ====================
305 */
306 void CL_PlayDemo_f (void)
307 {
308         char    name[256];
309         int c;
310         qboolean neg = false;
311
312         if (cmd_source != src_command)
313                 return;
314
315         if (Cmd_Argc() != 2)
316         {
317                 Con_Printf ("play <demoname> : plays a demo\n");
318                 return;
319         }
320
321 //      SCR_BeginLoadingPlaque();
322
323 //
324 // disconnect from server
325 //
326         CL_Disconnect ();
327         
328 //
329 // open the demo file
330 //
331         strcpy (name, Cmd_Argv(1));
332         COM_DefaultExtension (name, ".dem");
333
334         Con_Printf ("Playing demo from %s.\n", name);
335         COM_FOpenFile (name, &cls.demofile, false, true);
336         if (!cls.demofile)
337         {
338                 Con_Printf ("ERROR: couldn't open.\n");
339                 cls.demonum = -1;               // stop demo loop
340                 return;
341         }
342
343         cls.demoplayback = true;
344         cls.state = ca_connected;
345         cls.forcetrack = 0;
346
347         while ((c = Qgetc(cls.demofile)) != '\n')
348                 if (c == '-')
349                         neg = true;
350                 else
351                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
352
353         if (neg)
354                 cls.forcetrack = -cls.forcetrack;
355 // ZOID, fscanf is evil
356 //      fscanf (cls.demofile, "%i\n", &cls.forcetrack);
357 }
358
359 /*
360 ====================
361 CL_FinishTimeDemo
362
363 ====================
364 */
365 void CL_FinishTimeDemo (void)
366 {
367         int             frames;
368         double  time; // LordHavoc: changed timedemo accuracy to double
369         
370         cls.timedemo = false;
371         
372 // the first frame didn't count
373         frames = (host_framecount - cls.td_startframe) - 1;
374         time = realtime - cls.td_starttime;
375         if (!time)
376                 time = 1;
377         // LordHavoc: timedemo now prints out 7 digits of fraction
378         Con_Printf ("%i frames %5.7f seconds %5.7f fps\n", frames, time, frames/time);
379 }
380
381 /*
382 ====================
383 CL_TimeDemo_f
384
385 timedemo [demoname]
386 ====================
387 */
388 void CL_TimeDemo_f (void)
389 {
390         if (cmd_source != src_command)
391                 return;
392
393         if (Cmd_Argc() != 2)
394         {
395                 Con_Printf ("timedemo <demoname> : gets demo speeds\n");
396                 return;
397         }
398
399         CL_PlayDemo_f ();
400
401 // cls.td_starttime will be grabbed at the second frame of the demo, so
402 // all the loading time doesn't get counted
403
404         // instantly hide console and deactivate it
405         key_dest = key_game;
406         scr_conlines = 0;
407         scr_con_current = 0;
408
409         cls.timedemo = true;
410         cls.td_startframe = host_framecount;
411         cls.td_lastframe = -1;          // get a new message this frame
412 }
413