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