finished a lot of little todo items, mostly regarding server list and networking...
[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         strncpy (name, Cmd_Argv(1), sizeof (name) - 1);
263         name[sizeof (name) - 1] = '\0';
264         FS_DefaultExtension (name, ".dem");
265
266         // start the map up
267         if (c > 2)
268                 Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command);
269
270         // open the demo file
271         Con_Printf ("recording to %s.\n", name);
272         cls.demofile = FS_Open (name, "wb", false);
273         if (!cls.demofile)
274         {
275                 Con_Printf ("ERROR: couldn't open.\n");
276                 return;
277         }
278
279         cls.forcetrack = track;
280         FS_Printf (cls.demofile, "%i\n", cls.forcetrack);
281
282         cls.demorecording = true;
283 }
284
285
286 /*
287 ====================
288 CL_PlayDemo_f
289
290 play [demoname]
291 ====================
292 */
293 void CL_PlayDemo_f (void)
294 {
295         char    name[256];
296         int c;
297         qboolean neg = false;
298
299         if (cmd_source != src_command)
300                 return;
301
302         if (Cmd_Argc() != 2)
303         {
304                 Con_Printf ("play <demoname> : plays a demo\n");
305                 return;
306         }
307
308         // disconnect from server
309         CL_Disconnect ();
310
311         // update networking ports (this is mainly just needed at startup)
312         NetConn_ClientFrame();
313
314         // open the demo file
315         strcpy (name, Cmd_Argv(1));
316         FS_DefaultExtension (name, ".dem");
317
318         Con_Printf ("Playing demo from %s.\n", name);
319         cls.demofile = FS_Open (name, "rb", false);
320         if (!cls.demofile)
321         {
322                 Con_Printf ("ERROR: couldn't open.\n");
323                 cls.demonum = -1;               // stop demo loop
324                 return;
325         }
326
327         SCR_BeginLoadingPlaque ();
328
329         cls.demoplayback = true;
330         cls.state = ca_connected;
331         cls.forcetrack = 0;
332
333         while ((c = FS_Getc (cls.demofile)) != '\n')
334                 if (c == '-')
335                         neg = true;
336                 else
337                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
338
339         if (neg)
340                 cls.forcetrack = -cls.forcetrack;
341 }
342
343 /*
344 ====================
345 CL_FinishTimeDemo
346
347 ====================
348 */
349 void CL_FinishTimeDemo (void)
350 {
351         int             frames;
352         double  time; // LordHavoc: changed timedemo accuracy to double
353
354         cls.timedemo = false;
355
356 // the first frame didn't count
357         frames = (host_framecount - cls.td_startframe) - 1;
358         time = realtime - cls.td_starttime;
359         if (!time)
360                 time = 1;
361         // LordHavoc: timedemo now prints out 7 digits of fraction
362         Con_Printf ("%i frames %5.7f seconds %5.7f fps\n", frames, time, frames/time);
363 }
364
365 /*
366 ====================
367 CL_TimeDemo_f
368
369 timedemo [demoname]
370 ====================
371 */
372 void CL_TimeDemo_f (void)
373 {
374         if (cmd_source != src_command)
375                 return;
376
377         if (Cmd_Argc() != 2)
378         {
379                 Con_Printf ("timedemo <demoname> : gets demo speeds\n");
380                 return;
381         }
382
383         CL_PlayDemo_f ();
384
385 // cls.td_starttime will be grabbed at the second frame of the demo, so
386 // all the loading time doesn't get counted
387
388         // instantly hide console and deactivate it
389         key_dest = key_game;
390         key_consoleactive = 0;
391         scr_conlines = 0;
392         scr_con_current = 0;
393
394         cls.timedemo = true;
395         // get first message this frame
396         cls.td_lastframe = -1;
397 }
398