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