]> git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_demo.c
note to myself: skipping over data works better if you actually do it
[xonotic/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 extern cvar_t cl_capturevideo;
24 int old_vsync = 0;
25
26 void CL_FinishTimeDemo (void);
27
28 /*
29 ==============================================================================
30
31 DEMO CODE
32
33 When a demo is playing back, all outgoing network messages are skipped, and
34 incoming messages are read from the demo file.
35
36 Whenever cl.time gets past the last received message, another message is
37 read from the demo file.
38 ==============================================================================
39 */
40
41 /*
42 =====================
43 CL_NextDemo
44
45 Called to play the next demo in the demo loop
46 =====================
47 */
48 void CL_NextDemo (void)
49 {
50         char    str[MAX_INPUTLINE];
51
52         if (cls.demonum == -1)
53                 return;         // don't play demos
54
55         if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
56         {
57                 cls.demonum = 0;
58                 if (!cls.demos[cls.demonum][0])
59                 {
60                         Con_Print("No demos listed with startdemos\n");
61                         cls.demonum = -1;
62                         return;
63                 }
64         }
65
66         sprintf (str,"playdemo %s\n", cls.demos[cls.demonum]);
67         Cbuf_InsertText (str);
68         cls.demonum++;
69 }
70
71 /*
72 ==============
73 CL_StopPlayback
74
75 Called when a demo file runs out, or the user starts a game
76 ==============
77 */
78 // LordHavoc: now called only by CL_Disconnect
79 void CL_StopPlayback (void)
80 {
81         if (!cls.demoplayback)
82                 return;
83
84         FS_Close (cls.demofile);
85         cls.demoplayback = false;
86         cls.demofile = NULL;
87
88         if (cls.timedemo)
89                 CL_FinishTimeDemo ();
90
91         if (COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
92                 Host_Quit_f();
93
94 }
95
96 /*
97 ====================
98 CL_WriteDemoMessage
99
100 Dumps the current net message, prefixed by the length and view angles
101 ====================
102 */
103 void CL_WriteDemoMessage (sizebuf_t *message)
104 {
105         int             len;
106         int             i;
107         float   f;
108
109         if (cls.demopaused) // LordHavoc: pausedemo
110                 return;
111
112         len = LittleLong (message->cursize);
113         FS_Write (cls.demofile, &len, 4);
114         for (i=0 ; i<3 ; i++)
115         {
116                 f = LittleFloat (cl.viewangles[i]);
117                 FS_Write (cls.demofile, &f, 4);
118         }
119         FS_Write (cls.demofile, message->data, message->cursize);
120 }
121
122 /*
123 ====================
124 CL_ReadDemoMessage
125
126 Handles playback of demos
127 ====================
128 */
129 void CL_ReadDemoMessage(void)
130 {
131         int r, i;
132         float f;
133
134         if (!cls.demoplayback)
135                 return;
136
137         // LordHavoc: pausedemo
138         if (cls.demopaused)
139                 return;
140
141         for (;;)
142         {
143                 // decide if it is time to grab the next message
144                 // always grab until fully connected
145                 if (cls.signon == SIGNONS)
146                 {
147                         if (cls.timedemo)
148                         {
149                                 cls.td_frames++;
150                                 cls.td_onesecondframes++;
151                                 // if this is the first official frame we can now grab the real
152                                 // td_starttime so the bogus time on the first frame doesn't
153                                 // count against the final report
154                                 if (cls.td_frames == 0)
155                                 {
156                                         cls.td_starttime = realtime;
157                                         cls.td_onesecondnexttime = cl.time + 1;
158                                         cls.td_onesecondframes = 0;
159                                         cls.td_onesecondminframes = 0;
160                                         cls.td_onesecondmaxframes = 0;
161                                         cls.td_onesecondavgframes = 0;
162                                         cls.td_onesecondavgcount = 0;
163                                 }
164                                 if (cl.time >= cls.td_onesecondnexttime)
165                                 {
166                                         if (cls.td_onesecondavgcount == 0)
167                                         {
168                                                 cls.td_onesecondminframes = cls.td_onesecondframes;
169                                                 cls.td_onesecondmaxframes = cls.td_onesecondframes;
170                                         }
171                                         cls.td_onesecondminframes = min(cls.td_onesecondminframes, cls.td_onesecondframes);
172                                         cls.td_onesecondmaxframes = max(cls.td_onesecondmaxframes, cls.td_onesecondframes);
173                                         cls.td_onesecondavgframes += cls.td_onesecondframes;
174                                         cls.td_onesecondavgcount++;
175                                         cls.td_onesecondframes = 0;
176                                         cls.td_onesecondnexttime++;
177                                 }
178                         }
179                         else if (cl.time <= cl.mtime[0])
180                         {
181                                 // don't need another message yet
182                                 return;
183                         }
184                 }
185
186                 // get the next message
187                 FS_Read(cls.demofile, &net_message.cursize, 4);
188                 net_message.cursize = LittleLong(net_message.cursize);
189                 if(net_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
190                 {
191                         // skip over demo packet
192                         FS_Seek(cls.demofile, 12 + (net_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
193                         continue;
194                 }
195                 if (net_message.cursize > net_message.maxsize)
196                         Host_Error("Demo message (%i) > net_message.maxsize (%i)", net_message.cursize, net_message.maxsize);
197                 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
198                 for (i = 0;i < 3;i++)
199                 {
200                         r = (int)FS_Read(cls.demofile, &f, 4);
201                         cl.mviewangles[0][i] = LittleFloat(f);
202                 }
203
204                 if (FS_Read(cls.demofile, net_message.data, net_message.cursize) == net_message.cursize)
205                 {
206                         MSG_BeginReading();
207                         CL_ParseServerMessage();
208
209                         // In case the demo contains a "svc_disconnect" message
210                         if (!cls.demoplayback)
211                                 return;
212
213                         if (cls.timedemo)
214                                 return;
215                 }
216                 else
217                 {
218                         CL_Disconnect();
219                         return;
220                 }
221         }
222 }
223
224
225 /*
226 ====================
227 CL_Stop_f
228
229 stop recording a demo
230 ====================
231 */
232 void CL_Stop_f (void)
233 {
234         sizebuf_t buf;
235         unsigned char bufdata[64];
236
237         if (!cls.demorecording)
238         {
239                 Con_Print("Not recording a demo.\n");
240                 return;
241         }
242
243 // write a disconnect message to the demo file
244         // LordHavoc: don't replace the net_message when doing this
245         buf.data = bufdata;
246         buf.maxsize = sizeof(bufdata);
247         SZ_Clear(&buf);
248         MSG_WriteByte(&buf, svc_disconnect);
249         CL_WriteDemoMessage(&buf);
250
251 // finish up
252         FS_Close (cls.demofile);
253         cls.demofile = NULL;
254         cls.demorecording = false;
255         Con_Print("Completed demo\n");
256 }
257
258 /*
259 ====================
260 CL_Record_f
261
262 record <demoname> <map> [cd track]
263 ====================
264 */
265 void CL_Record_f (void)
266 {
267         int c, track;
268         char name[MAX_OSPATH];
269
270         c = Cmd_Argc();
271         if (c != 2 && c != 3 && c != 4)
272         {
273                 Con_Print("record <demoname> [<map> [cd track]]\n");
274                 return;
275         }
276
277         if (strstr(Cmd_Argv(1), ".."))
278         {
279                 Con_Print("Relative pathnames are not allowed.\n");
280                 return;
281         }
282
283         if (c == 2 && cls.state == ca_connected)
284         {
285                 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
286                 return;
287         }
288
289         if (cls.state == ca_connected)
290                 CL_Disconnect();
291
292         // write the forced cd track number, or -1
293         if (c == 4)
294         {
295                 track = atoi(Cmd_Argv(3));
296                 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
297         }
298         else
299                 track = -1;
300
301         // get the demo name
302         strlcpy (name, Cmd_Argv(1), sizeof (name));
303         FS_DefaultExtension (name, ".dem", sizeof (name));
304
305         // start the map up
306         if (c > 2)
307                 Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command);
308
309         // open the demo file
310         Con_Printf("recording to %s.\n", name);
311         cls.demofile = FS_Open (name, "wb", false, false);
312         if (!cls.demofile)
313         {
314                 Con_Print("ERROR: couldn't open.\n");
315                 return;
316         }
317
318         cls.forcetrack = track;
319         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
320
321         cls.demorecording = true;
322 }
323
324
325 /*
326 ====================
327 CL_PlayDemo_f
328
329 play [demoname]
330 ====================
331 */
332 void CL_PlayDemo_f (void)
333 {
334         char    name[MAX_QPATH];
335         int c;
336         qboolean neg = false;
337
338         if (Cmd_Argc() != 2)
339         {
340                 Con_Print("play <demoname> : plays a demo\n");
341                 return;
342         }
343
344         // disconnect from server
345         CL_Disconnect ();
346         Host_ShutdownServer ();
347
348         // update networking ports (this is mainly just needed at startup)
349         NetConn_UpdateSockets();
350
351         // open the demo file
352         strlcpy (name, Cmd_Argv(1), sizeof (name));
353         FS_DefaultExtension (name, ".dem", sizeof (name));
354         cls.protocol = PROTOCOL_QUAKE;
355
356         Con_Printf("Playing demo from %s.\n", name);
357         cls.demofile = FS_Open (name, "rb", false, false);
358         if (!cls.demofile)
359         {
360                 Con_Print("ERROR: couldn't open.\n");
361                 cls.demonum = -1;               // stop demo loop
362                 return;
363         }
364
365         strlcpy(cls.demoname, name, sizeof(cls.demoname));
366         cls.demoplayback = true;
367         cls.state = ca_connected;
368         cls.forcetrack = 0;
369
370         while ((c = FS_Getc (cls.demofile)) != '\n')
371                 if (c == '-')
372                         neg = true;
373                 else
374                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
375
376         if (neg)
377                 cls.forcetrack = -cls.forcetrack;
378 }
379
380 /*
381 ====================
382 CL_FinishTimeDemo
383
384 ====================
385 */
386 void CL_FinishTimeDemo (void)
387 {
388         int frames;
389         double time, totalfpsavg;
390         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
391
392         cls.timedemo = false;
393
394         frames = cls.td_frames;
395         time = realtime - cls.td_starttime;
396         totalfpsavg = time > 0 ? frames / time : 0;
397         fpsmin = cls.td_onesecondminframes;
398         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgframes / cls.td_onesecondavgcount : 0;
399         fpsmax = cls.td_onesecondmaxframes;
400         // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
401         Con_Printf("%i frames %5.7f seconds %5.7f fps, one-second min/avg/max: %.0f %.0f %.0f\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax);
402         Log_Printf("benchmark.log", "date %s | enginedate %s | demo %s | commandline %s | result %i frames %5.7f seconds %5.7f fps, one-second min/avg/max: %.0f %.0f %.0f\n", Sys_TimeString("%Y-%m-%d %H:%M:%S"), buildstring, cls.demoname, cmdline.string, frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax);
403         if (COM_CheckParm("-benchmark"))
404                 Host_Quit_f();
405 }
406
407 /*
408 ====================
409 CL_TimeDemo_f
410
411 timedemo [demoname]
412 ====================
413 */
414 void CL_TimeDemo_f (void)
415 {
416         if (Cmd_Argc() != 2)
417         {
418                 Con_Print("timedemo <demoname> : gets demo speeds\n");
419                 return;
420         }
421
422         srand(0); // predictable random sequence for benchmarking
423
424         CL_PlayDemo_f ();
425
426 // cls.td_starttime will be grabbed at the second frame of the demo, so
427 // all the loading time doesn't get counted
428
429         // instantly hide console and deactivate it
430         key_dest = key_game;
431         key_consoleactive = 0;
432         scr_con_current = 0;
433
434         cls.timedemo = true;
435         cls.td_frames = -2;             // skip the first frame
436         cls.demonum = -1;               // stop demo loop
437         cls.demonum = -1;               // stop demo loop
438 }
439