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