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_StopPlayback
41
42 Called when a demo file runs out, or the user starts a game
43 ==============
44 */
CL_StopPlayback(void)45 void CL_StopPlayback (void)
46 {
47 if (!cls.demoplayback)
48 return;
49
50 fclose (cls.demofile);
51 cls.demoplayback = false;
52 cls.demofile = NULL;
53 cls.state = ca_disconnected;
54
55 if (cls.timedemo)
56 CL_FinishTimeDemo ();
57 }
58
59 /*
60 ====================
61 CL_WriteDemoMessage
62
63 Dumps the current net message, prefixed by the length and view angles
64 ====================
65 */
CL_WriteDemoMessage(void)66 void CL_WriteDemoMessage (void)
67 {
68 int len;
69 int i;
70 float f;
71
72 len = LittleLong (net_message.cursize);
73 fwrite (&len, 4, 1, cls.demofile);
74 for (i=0 ; i<3 ; i++)
75 {
76 f = LittleFloat (cl.viewangles[i]);
77 fwrite (&f, 4, 1, cls.demofile);
78 }
79 fwrite (net_message.data, net_message.cursize, 1, cls.demofile);
80 fflush (cls.demofile);
81 }
82
83 /*
84 ====================
85 CL_GetMessage
86
87 Handles recording and playback of demos, on top of NET_ code
88 ====================
89 */
CL_GetMessage(void)90 int CL_GetMessage (void)
91 {
92 int r, i;
93 float f;
94
95 if (cls.demoplayback)
96 {
97 // decide if it is time to grab the next message
98 if (cls.signon == SIGNONS) // allways grab until fully connected
99 {
100 if (cls.timedemo)
101 {
102 if (host_framecount == cls.td_lastframe)
103 return 0; // allready read this frame's message
104 cls.td_lastframe = host_framecount;
105 // if this is the second frame, grab the real td_starttime
106 // so the bogus time on the first frame doesn't count
107 if (host_framecount == cls.td_startframe + 1)
108 cls.td_starttime = realtime;
109 }
110 else if ( /* cl.time > 0 && */ cl.time <= cl.mtime[0])
111 {
112 return 0; // don't need another message yet
113 }
114 }
115
116 // get the next message
117 fread (&net_message.cursize, 4, 1, cls.demofile);
118 VectorCopy (cl.mviewangles[0], cl.mviewangles[1]);
119 for (i=0 ; i<3 ; i++)
120 {
121 r = fread (&f, 4, 1, cls.demofile);
122 cl.mviewangles[0][i] = LittleFloat (f);
123 }
124
125 net_message.cursize = LittleLong (net_message.cursize);
126 if (net_message.cursize > MAX_MSGLEN)
127 Sys_Error ("Demo message > MAX_MSGLEN");
128 r = fread (net_message.data, net_message.cursize, 1, cls.demofile);
129 if (r != 1)
130 {
131 CL_StopPlayback ();
132 return 0;
133 }
134
135 return 1;
136 }
137
138 while (1)
139 {
140 r = NET_GetMessage (cls.netcon);
141
142 if (r != 1 && r != 2)
143 return r;
144
145 // discard nop keepalive message
146 if (net_message.cursize == 1 && net_message.data[0] == svc_nop)
147 Con_Printf ("<-- server to client keepalive\n");
148 else
149 break;
150 }
151
152 if (cls.demorecording)
153 CL_WriteDemoMessage ();
154
155 return r;
156 }
157
158
159 /*
160 ====================
161 CL_Stop_f
162
163 stop recording a demo
164 ====================
165 */
CL_Stop_f(void)166 void CL_Stop_f (void)
167 {
168 if (cmd_source != src_command)
169 return;
170
171 if (!cls.demorecording)
172 {
173 Con_Printf ("Not recording a demo.\n");
174 return;
175 }
176
177 // write a disconnect message to the demo file
178 SZ_Clear (&net_message);
179 MSG_WriteByte (&net_message, svc_disconnect);
180 CL_WriteDemoMessage ();
181
182 // finish up
183 fclose (cls.demofile);
184 cls.demofile = NULL;
185 cls.demorecording = false;
186 Con_Printf ("Completed demo\n");
187 }
188
189 /*
190 ====================
191 CL_Record_f
192
193 record <demoname> <map> [cd track]
194 ====================
195 */
CL_Record_f(void)196 void CL_Record_f (void)
197 {
198 int c;
199 char name[MAX_OSPATH];
200 int track;
201
202 if (cmd_source != src_command)
203 return;
204
205 c = Cmd_Argc();
206 if (c != 2 && c != 3 && c != 4)
207 {
208 Con_Printf ("record <demoname> [<map> [cd track]]\n");
209 return;
210 }
211
212 if (strstr(Cmd_Argv(1), ".."))
213 {
214 Con_Printf ("Relative pathnames are not allowed.\n");
215 return;
216 }
217
218 if (c == 2 && cls.state == ca_connected)
219 {
220 Con_Printf("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
221 return;
222 }
223
224 // write the forced cd track number, or -1
225 if (c == 4)
226 {
227 track = atoi(Cmd_Argv(3));
228 Con_Printf ("Forcing CD track to %i\n", cls.forcetrack);
229 }
230 else
231 track = -1;
232
233 sprintf (name, "%s/%s", com_gamedir, Cmd_Argv(1));
234
235 //
236 // start the map up
237 //
238 if (c > 2)
239 Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command);
240
241 //
242 // open the demo file
243 //
244 COM_DefaultExtension (name, ".dem");
245
246 Con_Printf ("recording to %s.\n", name);
247 cls.demofile = fopen (name, "wb");
248 if (!cls.demofile)
249 {
250 Con_Printf ("ERROR: couldn't open.\n");
251 return;
252 }
253
254 cls.forcetrack = track;
255 fprintf (cls.demofile, "%i\n", cls.forcetrack);
256
257 cls.demorecording = true;
258 }
259
260
261 /*
262 ====================
263 CL_PlayDemo_f
264
265 play [demoname]
266 ====================
267 */
CL_PlayDemo_f(void)268 void CL_PlayDemo_f (void)
269 {
270 char name[256];
271 int c;
272 qboolean neg = false;
273
274 if (cmd_source != src_command)
275 return;
276
277 if (Cmd_Argc() > 2)
278 {
279 Con_Printf ("play <demoname> : plays a demo\n");
280 return;
281 }
282
283 //
284 // disconnect from server
285 //
286 CL_Disconnect ();
287
288 //
289 // open the demo file
290 //
291 const char* cmdName = "demo1";
292 if (Cmd_Argc() == 2) {
293 cmdName = Cmd_Argv(1);
294 }
295 strcpy (name, cmdName);
296 COM_DefaultExtension (name, ".dem");
297
298 Con_Printf ("Playing demo from %s.\n", name);
299 COM_FOpenFile (name, &cls.demofile);
300 if (!cls.demofile)
301 {
302 Con_Printf ("ERROR: couldn't open.\n");
303 cls.demonum = -1; // stop demo loop
304 return;
305 }
306
307 cls.demoplayback = true;
308 cls.state = ca_connected;
309 cls.forcetrack = 0;
310
311 while ((c = getc(cls.demofile)) != '\n')
312 if (c == '-')
313 neg = true;
314 else
315 cls.forcetrack = cls.forcetrack * 10 + (c - '0');
316
317 if (neg)
318 cls.forcetrack = -cls.forcetrack;
319 // ZOID, fscanf is evil
320 // fscanf (cls.demofile, "%i\n", &cls.forcetrack);
321 }
322
323 // The timedemo numbers are very important to testing, so log them even if normal console printing is disabled.
324
325 #define LOGANDPRINT(ARGS) Con_Printf ARGS ; PMPLOG(ARGS)
326
327 /*
328 ====================
329 CL_FinishTimeDemo
330
331 ====================
332 */
CL_FinishTimeDemo(void)333 void CL_FinishTimeDemo (void)
334 {
335 int frames;
336 float time;
337
338 cls.timedemo = false;
339
340 // the first frame didn't count
341 frames = (host_framecount - cls.td_startframe) - 1;
342 time = realtime - cls.td_starttime;
343 if (!time)
344 time = 1;
345 LOGANDPRINT(("%i frames %5.3f seconds %5.3f fps\n", frames, time, frames/time));
346 if (frames > 0)
347 {
348 LOGANDPRINT(("Fastest: %5.1f ms on frame %d\n", fastestFrame.time * 1000.0, fastestFrame.frame));
349 LOGANDPRINT(("Average: %5.1f ms\n", (time / frames) * 1000.0));
350 LOGANDPRINT(("Slowest: %5.1f ms on frame %d\n", slowestFrame.time * 1000.0, slowestFrame.frame));
351 }
352 }
353
354 /*
355 ====================
356 CL_TimeDemo_f
357
358 timedemo [demoname]
359 ====================
360 */
CL_TimeDemo_f(void)361 void CL_TimeDemo_f (void)
362 {
363 if (cmd_source != src_command)
364 return;
365
366 if (Cmd_Argc() > 2)
367 {
368 Con_Printf ("timedemo <demoname> : gets demo speeds\n");
369 return;
370 }
371
372 CL_PlayDemo_f ();
373
374 // cls.td_starttime will be grabbed at the second frame of the demo, so
375 // all the loading time doesn't get counted
376
377 cls.timedemo = true;
378 cls.td_startframe = host_framecount;
379 cls.td_lastframe = -1; // get a new message this frame
380
381 InitFrameTimes();
382 }
383
384