2 Copyright (C) 1996-1997 Id Software, Inc.
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.
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.
13 See the GNU General Public License for more details.
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.
23 #ifdef CONFIG_VIDEO_CAPTURE
24 extern cvar_t cl_capturevideo;
25 extern cvar_t cl_capturevideo_demo_stop;
29 static void CL_FinishTimeDemo (void);
32 ==============================================================================
36 When a demo is playing back, all outgoing network messages are skipped, and
37 incoming messages are read from the demo file.
39 Whenever cl.time gets past the last received message, another message is
40 read from the demo file.
41 ==============================================================================
48 Called to play the next demo in the demo loop
51 void CL_NextDemo (void)
53 char str[MAX_INPUTLINE];
55 if (cls.demonum == -1)
56 return; // don't play demos
58 if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
61 if (!cls.demos[cls.demonum][0])
63 Con_Print("No demos listed with startdemos\n");
69 dpsnprintf (str, sizeof(str), "playdemo %s\n", cls.demos[cls.demonum]);
70 Cbuf_InsertText(cmd_local, str);
78 Called when a demo file runs out, or the user starts a game
81 // LadyHavoc: now called only by CL_Disconnect
82 void CL_StopPlayback (void)
84 #ifdef CONFIG_VIDEO_CAPTURE
85 if (cl_capturevideo_demo_stop.integer)
86 Cvar_Set(&cvars_all, "cl_capturevideo", "0");
89 if (!cls.demoplayback)
92 FS_Close (cls.demofile);
93 cls.demoplayback = false;
99 if (!cls.demostarting) // only quit if not starting another demo
100 if (Sys_CheckParm("-demo") || Sys_CheckParm("-capturedemo"))
101 host.state = host_shutdown;
108 Dumps the current net message, prefixed by the length and view angles
109 #====================
111 void CL_WriteDemoMessage (sizebuf_t *message)
117 if (cls.demopaused) // LadyHavoc: pausedemo
120 len = LittleLong (message->cursize);
121 FS_Write (cls.demofile, &len, 4);
122 for (i=0 ; i<3 ; i++)
124 f = LittleFloat (cl.viewangles[i]);
125 FS_Write (cls.demofile, &f, 4);
127 FS_Write (cls.demofile, message->data, message->cursize);
134 Dumps the current demo to a buffer, and resets the demo to its starting point.
135 Used to insert csprogs.dat files as a download to the beginning of a demo file.
138 void CL_CutDemo (unsigned char **buf, fs_offset_t *filesize)
143 FS_Close(cls.demofile);
144 *buf = FS_LoadFile(cls.demoname, tempmempool, false, filesize);
146 // restart the demo recording
147 cls.demofile = FS_OpenRealFile(cls.demoname, "wb", false);
149 Sys_Error("failed to reopen the demo file");
150 FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
157 Adds the cut stuff back to the demo. Also frees the buffer.
158 Used to insert csprogs.dat files as a download to the beginning of a demo file.
161 void CL_PasteDemo (unsigned char **buf, fs_offset_t *filesize)
163 fs_offset_t startoffset = 0;
169 while(startoffset < *filesize && ((char *)(*buf))[startoffset] != '\n')
171 if(startoffset < *filesize)
174 FS_Write(cls.demofile, *buf + startoffset, *filesize - startoffset);
185 Handles playback of demos
188 void CL_ReadDemoMessage(void)
193 if (!cls.demoplayback)
196 // LadyHavoc: pausedemo
202 // decide if it is time to grab the next message
203 // always grab until fully connected
204 if (cls.signon == SIGNONS)
209 cls.td_onesecondframes++;
210 // if this is the first official frame we can now grab the real
211 // td_starttime so the bogus time on the first frame doesn't
212 // count against the final report
213 if (cls.td_frames == 0)
215 cls.td_starttime = host.realtime;
216 cls.td_onesecondnexttime = cl.time + 1;
217 cls.td_onesecondrealtime = host.realtime;
218 cls.td_onesecondframes = 0;
219 cls.td_onesecondminfps = 0;
220 cls.td_onesecondmaxfps = 0;
221 cls.td_onesecondavgfps = 0;
222 cls.td_onesecondavgcount = 0;
224 if (cl.time >= cls.td_onesecondnexttime)
226 double fps = cls.td_onesecondframes / (host.realtime - cls.td_onesecondrealtime);
227 if (cls.td_onesecondavgcount == 0)
229 cls.td_onesecondminfps = fps;
230 cls.td_onesecondmaxfps = fps;
232 cls.td_onesecondrealtime = host.realtime;
233 cls.td_onesecondminfps = min(cls.td_onesecondminfps, fps);
234 cls.td_onesecondmaxfps = max(cls.td_onesecondmaxfps, fps);
235 cls.td_onesecondavgfps += fps;
236 cls.td_onesecondavgcount++;
237 cls.td_onesecondframes = 0;
238 cls.td_onesecondnexttime++;
241 else if (cl.time < cl.mtime[0])
243 // don't need another message yet
248 // get the next message
249 FS_Read(cls.demofile, &cl_message.cursize, 4);
250 cl_message.cursize = LittleLong(cl_message.cursize);
251 if(cl_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
253 // skip over demo packet
254 FS_Seek(cls.demofile, 12 + (cl_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
257 if (cl_message.cursize > cl_message.maxsize)
259 CL_DisconnectEx(false, "Demo message (%i) > cl_message.maxsize (%i)", cl_message.cursize, cl_message.maxsize);
260 cl_message.cursize = 0;
263 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
264 for (i = 0;i < 3;i++)
266 FS_Read(cls.demofile, &f, 4);
267 cl.mviewangles[0][i] = LittleFloat(f);
270 if (FS_Read(cls.demofile, cl_message.data, cl_message.cursize) == cl_message.cursize)
272 MSG_BeginReading(&cl_message);
273 CL_ParseServerMessage();
275 if (cls.signon != SIGNONS)
276 Cbuf_Execute((cmd_local)->cbuf); // immediately execute svc_stufftext if in the demo before connect!
278 // In case the demo contains a "svc_disconnect" message
279 if (!cls.demoplayback)
298 stop recording a demo
301 void CL_Stop_f(cmd_state_t *cmd)
304 unsigned char bufdata[64];
306 if (!cls.demorecording)
308 Con_Print("Not recording a demo.\n");
312 // write a disconnect message to the demo file
313 // LadyHavoc: don't replace the cl_message when doing this
315 buf.maxsize = sizeof(bufdata);
317 MSG_WriteByte(&buf, svc_disconnect);
318 CL_WriteDemoMessage(&buf);
321 if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1))
323 FS_RemoveOnClose(cls.demofile);
324 Con_Print("Completed and deleted demo\n");
327 Con_Print("Completed demo\n");
328 FS_Close (cls.demofile);
330 cls.demorecording = false;
337 record <demoname> <map> [cd track]
340 void CL_Record_f(cmd_state_t *cmd)
343 char name[MAX_OSPATH];
347 if (c != 2 && c != 3 && c != 4)
349 Con_Print("record <demoname> [<map> [cd track]]\n");
353 if (strstr(Cmd_Argv(cmd, 1), ".."))
355 Con_Print("Relative pathnames are not allowed.\n");
359 if (c == 2 && cls.state == ca_connected)
361 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
365 if (cls.state == ca_connected)
368 // write the forced cd track number, or -1
371 track = atoi(Cmd_Argv(cmd, 3));
372 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
378 strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
379 FS_DefaultExtension (name, ".dem", sizeof (name));
383 Cmd_ExecuteString ( cmd, va(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(cmd, 2)), src_local, false);
385 // open the demo file
386 Con_Printf("recording to %s.\n", name);
387 cls.demofile = FS_OpenRealFile(name, "wb", false);
390 Con_Print(CON_ERROR "ERROR: couldn't open.\n");
393 strlcpy(cls.demoname, name, sizeof(cls.demoname));
395 cls.forcetrack = track;
396 FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
398 cls.demorecording = true;
399 cls.demo_lastcsprogssize = -1;
400 cls.demo_lastcsprogscrc = -1;
403 void CL_PlayDemo(const char *demo)
405 char name[MAX_QPATH];
410 // open the demo file
411 strlcpy (name, demo, sizeof (name));
412 FS_DefaultExtension (name, ".dem", sizeof (name));
413 f = FS_OpenVirtualFile(name, false);
416 Con_Printf(CON_ERROR "ERROR: couldn't open %s.\n", name);
417 cls.demonum = -1; // stop demo loop
421 cls.demostarting = true;
423 // disconnect from server
426 // update networking ports (this is mainly just needed at startup)
427 NetConn_UpdateSockets();
429 cls.protocol = PROTOCOL_QUAKE;
431 Con_Printf("Playing demo %s.\n", name);
433 strlcpy(cls.demoname, name, sizeof(cls.demoname));
435 cls.demoplayback = true;
436 cls.state = ca_connected;
439 while ((c = FS_Getc (cls.demofile)) != '\n')
443 cls.forcetrack = cls.forcetrack * 10 + (c - '0');
446 cls.forcetrack = -cls.forcetrack;
448 cls.demostarting = false;
458 void CL_PlayDemo_f(cmd_state_t *cmd)
460 if (Cmd_Argc(cmd) != 2)
462 Con_Print("playdemo <demoname> : plays a demo\n");
466 CL_PlayDemo(Cmd_Argv(cmd, 1));
472 double time, totalfpsavg;
473 double fpsmin, fpsavg, fpsmax;
476 static size_t doublecmp_offset;
477 static int doublecmp_withoffset(const void *a_, const void *b_)
479 const double *a = (const double *) ((const char *) a_ + doublecmp_offset);
480 const double *b = (const double *) ((const char *) b_ + doublecmp_offset);
494 static void CL_FinishTimeDemo (void)
498 double time, totalfpsavg;
499 double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
500 static int benchmark_runs = 0;
503 cls.timedemo = host.restless = false;
505 frames = cls.td_frames;
506 time = host.realtime - cls.td_starttime;
507 totalfpsavg = time > 0 ? frames / time : 0;
508 fpsmin = cls.td_onesecondminfps;
509 fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
510 fpsmax = cls.td_onesecondmaxfps;
511 // LadyHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
512 Con_Printf("%i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
513 Log_Printf("benchmark.log", "date %s | enginedate %s | demo %s | commandline %s | run %d | result %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", Sys_TimeString("%Y-%m-%d %H:%M:%S"), buildstring, cls.demoname, cmdline.string, benchmark_runs + 1, frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
514 if (Sys_CheckParm("-benchmark"))
517 i = Sys_CheckParm("-benchmarkruns");
518 if(i && i + 1 < sys.argc)
520 static benchmarkhistory_t *history = NULL;
522 history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(sys.argv[i + 1]));
524 history[benchmark_runs - 1].frames = frames;
525 history[benchmark_runs - 1].time = time;
526 history[benchmark_runs - 1].totalfpsavg = totalfpsavg;
527 history[benchmark_runs - 1].fpsmin = fpsmin;
528 history[benchmark_runs - 1].fpsavg = fpsavg;
529 history[benchmark_runs - 1].fpsmax = fpsmax;
531 if(atoi(sys.argv[i + 1]) > benchmark_runs)
533 // restart the benchmark
534 Cbuf_AddText(cmd_local, va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname));
535 // cannot execute here
540 int first = Sys_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0;
541 if(benchmark_runs > first)
544 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f
547 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f
550 doublecmp_offset = (char *)&history->f - (char *)history; \
551 qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \
552 if((first + benchmark_runs) & 1) \
553 f = history[(first + benchmark_runs - 1) / 2].f; \
555 f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2
563 Con_Printf("MIN: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
571 Con_Printf("MED: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
579 Con_Printf("MAX: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
583 host.state = host_shutdown;
587 host.state = host_shutdown;
598 void CL_TimeDemo_f(cmd_state_t *cmd)
600 if (Cmd_Argc(cmd) != 2)
602 Con_Print("timedemo <demoname> : gets demo speeds\n");
606 srand(0); // predictable random sequence for benchmarking
608 CL_PlayDemo(Cmd_Argv(cmd, 1));
610 // cls.td_starttime will be grabbed at the second frame of the demo, so
611 // all the loading time doesn't get counted
613 // instantly hide console and deactivate it
615 key_consoleactive = 0;
618 cls.timedemo = host.restless = true;
619 cls.td_frames = -2; // skip the first frame
620 cls.demonum = -1; // stop demo loop
624 ===============================================================================
628 ===============================================================================
637 static void CL_Startdemos_f(cmd_state_t *cmd)
641 if (cls.state == ca_dedicated || Sys_CheckParm("-listen") || Sys_CheckParm("-benchmark") || Sys_CheckParm("-demo") || Sys_CheckParm("-capturedemo"))
644 c = Cmd_Argc(cmd) - 1;
647 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
650 Con_DPrintf("%i demo(s) in loop\n", c);
652 for (i=1 ; i<c+1 ; i++)
653 strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
655 // LadyHavoc: clear the remaining slots
656 for (;i <= MAX_DEMOS;i++)
657 cls.demos[i-1][0] = 0;
659 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
673 Return to looping demos
676 static void CL_Demos_f(cmd_state_t *cmd)
678 if (cls.state == ca_dedicated)
680 if (cls.demonum == -1)
690 Return to looping demos
693 static void CL_Stopdemo_f(cmd_state_t *cmd)
695 if (!cls.demoplayback)
700 // LadyHavoc: pausedemo command
701 static void CL_PauseDemo_f(cmd_state_t *cmd)
703 cls.demopaused = !cls.demopaused;
705 Con_Print("Demo paused\n");
707 Con_Print("Demo unpaused\n");
710 void CL_Demo_Init(void)
712 Cmd_AddCommand(CF_CLIENT, "record", CL_Record_f, "record a demo");
713 Cmd_AddCommand(CF_CLIENT, "stop", CL_Stop_f, "stop recording or playing a demo");
714 Cmd_AddCommand(CF_CLIENT, "playdemo", CL_PlayDemo_f, "watch a demo file");
715 Cmd_AddCommand(CF_CLIENT, "timedemo", CL_TimeDemo_f, "play back a demo as fast as possible and save statistics to benchmark.log");
716 Cmd_AddCommand(CF_CLIENT, "startdemos", CL_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
717 Cmd_AddCommand(CF_CLIENT, "demos", CL_Demos_f, "restart looping demos defined by the last startdemos command");
718 Cmd_AddCommand(CF_CLIENT, "stopdemo", CL_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
719 // LadyHavoc: added pausedemo
720 Cmd_AddCommand(CF_CLIENT, "pausedemo", CL_PauseDemo_f, "pause demo playback (can also safely pause demo recording if using QUAKE, QUAKEDP or NEHAHRAMOVIE protocol, useful for making movies)");
721 Cvar_RegisterVariable (&cl_autodemo);
722 Cvar_RegisterVariable (&cl_autodemo_nameformat);
723 Cvar_RegisterVariable (&cl_autodemo_delete);