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;
28 static void CL_FinishTimeDemo (void);
31 ==============================================================================
35 When a demo is playing back, all outgoing network messages are skipped, and
36 incoming messages are read from the demo file.
38 Whenever cl.time gets past the last received message, another message is
39 read from the demo file.
40 ==============================================================================
47 Called to play the next demo in the demo loop
50 void CL_NextDemo (void)
52 char str[MAX_INPUTLINE];
54 if (cls.demonum == -1)
55 return; // don't play demos
57 if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
60 if (!cls.demos[cls.demonum][0])
62 Con_Print("No demos listed with startdemos\n");
68 dpsnprintf (str, sizeof(str), "playdemo %s\n", cls.demos[cls.demonum]);
69 Cbuf_InsertText(cmd_local, str);
77 Called when a demo file runs out, or the user starts a game
80 // LadyHavoc: now called only by CL_Disconnect
81 void CL_StopPlayback (void)
83 #ifdef CONFIG_VIDEO_CAPTURE
84 if (cl_capturevideo_demo_stop.integer)
85 Cvar_Set(&cvars_all, "cl_capturevideo", "0");
88 if (!cls.demoplayback)
91 FS_Close (cls.demofile);
92 cls.demoplayback = false;
98 if (!cls.demostarting) // only quit if not starting another demo
99 if (Sys_CheckParm("-demo") || Sys_CheckParm("-capturedemo"))
100 host.state = host_shutdown;
107 Dumps the current net message, prefixed by the length and view angles
108 #====================
110 void CL_WriteDemoMessage (sizebuf_t *message)
116 if (cls.demopaused) // LadyHavoc: pausedemo
119 len = LittleLong (message->cursize);
120 FS_Write (cls.demofile, &len, 4);
121 for (i=0 ; i<3 ; i++)
123 f = LittleFloat (cl.viewangles[i]);
124 FS_Write (cls.demofile, &f, 4);
126 FS_Write (cls.demofile, message->data, message->cursize);
133 Dumps the current demo to a buffer, and resets the demo to its starting point.
134 Used to insert csprogs.dat files as a download to the beginning of a demo file.
137 void CL_CutDemo (unsigned char **buf, fs_offset_t *filesize)
142 FS_Close(cls.demofile);
143 *buf = FS_LoadFile(cls.demoname, tempmempool, false, filesize);
145 // restart the demo recording
146 cls.demofile = FS_OpenRealFile(cls.demoname, "wb", false);
148 Sys_Error("failed to reopen the demo file");
149 FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
156 Adds the cut stuff back to the demo. Also frees the buffer.
157 Used to insert csprogs.dat files as a download to the beginning of a demo file.
160 void CL_PasteDemo (unsigned char **buf, fs_offset_t *filesize)
162 fs_offset_t startoffset = 0;
168 while(startoffset < *filesize && ((char *)(*buf))[startoffset] != '\n')
170 if(startoffset < *filesize)
173 FS_Write(cls.demofile, *buf + startoffset, *filesize - startoffset);
184 Handles playback of demos
187 void CL_ReadDemoMessage(void)
192 if (!cls.demoplayback)
195 // LadyHavoc: pausedemo
201 // decide if it is time to grab the next message
202 // always grab until fully connected
203 if (cls.signon == SIGNONS)
208 cls.td_onesecondframes++;
209 // if this is the first official frame we can now grab the real
210 // td_starttime so the bogus time on the first frame doesn't
211 // count against the final report
212 if (cls.td_frames == 0)
214 cls.td_starttime = host.realtime;
215 cls.td_onesecondnexttime = cl.time + 1;
216 cls.td_onesecondrealtime = host.realtime;
217 cls.td_onesecondframes = 0;
218 cls.td_onesecondminfps = 0;
219 cls.td_onesecondmaxfps = 0;
220 cls.td_onesecondavgfps = 0;
221 cls.td_onesecondavgcount = 0;
223 if (cl.time >= cls.td_onesecondnexttime)
225 double fps = cls.td_onesecondframes / (host.realtime - cls.td_onesecondrealtime);
226 if (cls.td_onesecondavgcount == 0)
228 cls.td_onesecondminfps = fps;
229 cls.td_onesecondmaxfps = fps;
231 cls.td_onesecondrealtime = host.realtime;
232 cls.td_onesecondminfps = min(cls.td_onesecondminfps, fps);
233 cls.td_onesecondmaxfps = max(cls.td_onesecondmaxfps, fps);
234 cls.td_onesecondavgfps += fps;
235 cls.td_onesecondavgcount++;
236 cls.td_onesecondframes = 0;
237 cls.td_onesecondnexttime++;
240 else if (cl.time < cl.mtime[0])
242 // don't need another message yet
247 /* At signon 1 the cl_begindownloads command starts the world and, if applicable,
248 * boots up CSQC which may be required to parse the next message.
249 * That will be delayed if curl must first (down)load the map.
251 if (cls.signon == 1 && cl.loadcsqc) // waiting for CL_VM_Init() to be called
254 // get the next message
255 FS_Read(cls.demofile, &cl_message.cursize, 4);
256 cl_message.cursize = LittleLong(cl_message.cursize);
257 if(cl_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
259 // skip over demo packet
260 FS_Seek(cls.demofile, 12 + (cl_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
263 if (cl_message.cursize > cl_message.maxsize)
265 CL_DisconnectEx(false, "Demo message (%i) > cl_message.maxsize (%i)", cl_message.cursize, cl_message.maxsize);
266 cl_message.cursize = 0;
269 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
270 for (i = 0;i < 3;i++)
272 FS_Read(cls.demofile, &f, 4);
273 cl.mviewangles[0][i] = LittleFloat(f);
276 if (FS_Read(cls.demofile, cl_message.data, cl_message.cursize) == cl_message.cursize)
278 MSG_BeginReading(&cl_message);
279 CL_ParseServerMessage();
281 if (cls.signon != SIGNONS)
282 Cbuf_Execute((cmd_local)->cbuf); // immediately execute svc_stufftext if in the demo before connect!
284 // In case the demo contains a "svc_disconnect" message
285 if (!cls.demoplayback)
304 stop recording a demo
307 void CL_Stop_f(cmd_state_t *cmd)
310 unsigned char bufdata[64];
312 if (!cls.demorecording)
314 Con_Print("Not recording a demo.\n");
318 // write a disconnect message to the demo file
319 // LadyHavoc: don't replace the cl_message when doing this
321 buf.maxsize = sizeof(bufdata);
323 MSG_WriteByte(&buf, svc_disconnect);
324 CL_WriteDemoMessage(&buf);
327 if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1))
329 FS_RemoveOnClose(cls.demofile);
330 Con_Print("Completed and deleted demo\n");
333 Con_Print("Completed demo\n");
334 FS_Close (cls.demofile);
336 cls.demorecording = false;
343 record <demoname> <map> [cd track]
346 void CL_Record_f(cmd_state_t *cmd)
349 char name[MAX_OSPATH];
353 if (c != 2 && c != 3 && c != 4)
355 Con_Print("record <demoname> [<map> [cd track]]\n");
359 if (strstr(Cmd_Argv(cmd, 1), ".."))
361 Con_Print("Relative pathnames are not allowed.\n");
365 if (c == 2 && cls.state == ca_connected)
367 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
371 if (cls.state == ca_connected)
374 // write the forced cd track number, or -1
377 track = atoi(Cmd_Argv(cmd, 3));
378 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
384 dp_strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
385 FS_DefaultExtension (name, ".dem", sizeof (name));
389 Cmd_ExecuteString ( cmd, va(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(cmd, 2)), src_local, false);
391 // open the demo file
392 Con_Printf("recording to %s.\n", name);
393 cls.demofile = FS_OpenRealFile(name, "wb", false);
396 Con_Print(CON_ERROR "ERROR: couldn't open.\n");
399 dp_strlcpy(cls.demoname, name, sizeof(cls.demoname));
401 cls.forcetrack = track;
402 FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
404 cls.demorecording = true;
405 cls.demo_lastcsprogssize = -1;
406 cls.demo_lastcsprogscrc = -1;
409 void CL_PlayDemo(const char *demo)
411 char name[MAX_QPATH];
416 // open the demo file
417 dp_strlcpy (name, demo, sizeof (name));
418 FS_DefaultExtension (name, ".dem", sizeof (name));
419 f = FS_OpenVirtualFile(name, false);
422 Con_Printf(CON_ERROR "ERROR: couldn't open %s.\n", name);
423 cls.demonum = -1; // stop demo loop
427 cls.demostarting = true;
429 // disconnect from server
432 // update networking ports (this is mainly just needed at startup)
433 NetConn_UpdateSockets();
435 cls.protocol = PROTOCOL_QUAKE;
437 Con_Printf("Playing demo %s.\n", name);
439 dp_strlcpy(cls.demoname, name, sizeof(cls.demoname));
441 cls.demoplayback = true;
442 cls.state = ca_connected;
445 while ((c = FS_Getc (cls.demofile)) != '\n')
449 cls.forcetrack = cls.forcetrack * 10 + (c - '0');
452 cls.forcetrack = -cls.forcetrack;
454 cls.demostarting = false;
464 void CL_PlayDemo_f(cmd_state_t *cmd)
466 if (Cmd_Argc(cmd) != 2)
468 Con_Print("playdemo <demoname> : plays a demo\n");
472 CL_PlayDemo(Cmd_Argv(cmd, 1));
478 double time, totalfpsavg;
479 double fpsmin, fpsavg, fpsmax;
482 static size_t doublecmp_offset;
483 static int doublecmp_withoffset(const void *a_, const void *b_)
485 const double *a = (const double *) ((const char *) a_ + doublecmp_offset);
486 const double *b = (const double *) ((const char *) b_ + doublecmp_offset);
500 static void CL_FinishTimeDemo (void)
504 double time, totalfpsavg;
505 double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
506 static int benchmark_runs = 0;
509 cls.timedemo = host.restless = false;
511 frames = cls.td_frames;
512 time = host.realtime - cls.td_starttime;
513 totalfpsavg = time > 0 ? frames / time : 0;
514 fpsmin = cls.td_onesecondminfps;
515 fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
516 fpsmax = cls.td_onesecondmaxfps;
517 // LadyHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
518 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);
519 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"), engineversion, cls.demoname, cmdline.string, benchmark_runs + 1, frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
520 if (Sys_CheckParm("-benchmark"))
523 i = Sys_CheckParm("-benchmarkruns");
524 if(i && i + 1 < sys.argc)
526 static benchmarkhistory_t *history = NULL;
528 history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(sys.argv[i + 1]));
530 history[benchmark_runs - 1].frames = frames;
531 history[benchmark_runs - 1].time = time;
532 history[benchmark_runs - 1].totalfpsavg = totalfpsavg;
533 history[benchmark_runs - 1].fpsmin = fpsmin;
534 history[benchmark_runs - 1].fpsavg = fpsavg;
535 history[benchmark_runs - 1].fpsmax = fpsmax;
537 if(atoi(sys.argv[i + 1]) > benchmark_runs)
539 // restart the benchmark
540 Cbuf_AddText(cmd_local, va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname));
541 // cannot execute here
546 int first = Sys_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0;
547 if(benchmark_runs > first)
550 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f
553 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f
556 doublecmp_offset = (char *)&history->f - (char *)history; \
557 qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \
558 if((first + benchmark_runs) & 1) \
559 f = history[(first + benchmark_runs - 1) / 2].f; \
561 f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2
569 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);
577 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);
585 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);
589 host.state = host_shutdown;
593 host.state = host_shutdown;
596 // Might need to re-enable vsync
597 Cvar_Callback(&vid_vsync);
607 void CL_TimeDemo_f(cmd_state_t *cmd)
609 if (Cmd_Argc(cmd) != 2)
611 Con_Print("timedemo <demoname> : gets demo speeds\n");
615 srand(0); // predictable random sequence for benchmarking
617 CL_PlayDemo(Cmd_Argv(cmd, 1));
619 // cls.td_starttime will be grabbed at the second frame of the demo, so
620 // all the loading time doesn't get counted
622 // instantly hide console and deactivate it
624 key_consoleactive = 0;
627 cls.timedemo = host.restless = true;
628 cls.td_frames = -2; // skip the first frame
629 cls.demonum = -1; // stop demo loop
631 // Might need to disable vsync
632 Cvar_Callback(&vid_vsync);
636 ===============================================================================
640 ===============================================================================
649 static void CL_Startdemos_f(cmd_state_t *cmd)
653 if (cls.state == ca_dedicated || Sys_CheckParm("-listen") || Sys_CheckParm("-benchmark") || Sys_CheckParm("-demo") || Sys_CheckParm("-capturedemo"))
656 c = Cmd_Argc(cmd) - 1;
659 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
662 Con_DPrintf("%i demo(s) in loop\n", c);
664 for (i=1 ; i<c+1 ; i++)
665 dp_strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
667 // LadyHavoc: clear the remaining slots
668 for (;i <= MAX_DEMOS;i++)
669 cls.demos[i-1][0] = 0;
671 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
685 Return to looping demos
688 static void CL_Demos_f(cmd_state_t *cmd)
690 if (cls.state == ca_dedicated)
692 if (cls.demonum == -1)
702 Return to looping demos
705 static void CL_Stopdemo_f(cmd_state_t *cmd)
707 if (!cls.demoplayback)
712 // LadyHavoc: pausedemo command
713 static void CL_PauseDemo_f(cmd_state_t *cmd)
715 cls.demopaused = !cls.demopaused;
717 Con_Print("Demo paused\n");
719 Con_Print("Demo unpaused\n");
722 void CL_Demo_Init(void)
724 Cmd_AddCommand(CF_CLIENT, "record", CL_Record_f, "record a demo");
725 Cmd_AddCommand(CF_CLIENT, "stop", CL_Stop_f, "stop recording or playing a demo");
726 Cmd_AddCommand(CF_CLIENT, "playdemo", CL_PlayDemo_f, "watch a demo file");
727 Cmd_AddCommand(CF_CLIENT, "timedemo", CL_TimeDemo_f, "play back a demo as fast as possible and save statistics to benchmark.log");
728 Cmd_AddCommand(CF_CLIENT, "startdemos", CL_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
729 Cmd_AddCommand(CF_CLIENT, "demos", CL_Demos_f, "restart looping demos defined by the last startdemos command");
730 Cmd_AddCommand(CF_CLIENT, "stopdemo", CL_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
731 // LadyHavoc: added pausedemo
732 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)");
733 Cvar_RegisterVariable (&cl_autodemo);
734 Cvar_RegisterVariable (&cl_autodemo_nameformat);
735 Cvar_RegisterVariable (&cl_autodemo_delete);