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_Abort("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];
354 if (c != 2 && c != 3 && c != 4)
356 Con_Print("record <demoname> [<map> [cd track]]\n");
360 if (strstr(Cmd_Argv(cmd, 1), ".."))
362 Con_Print("Relative pathnames are not allowed.\n");
366 if (c == 2 && cls.state == ca_connected)
368 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
372 if (cls.state == ca_connected)
375 // write the forced cd track number, or -1
378 track = atoi(Cmd_Argv(cmd, 3));
379 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
385 dp_strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
386 FS_DefaultExtension (name, ".dem", sizeof (name));
391 vabuf_len = dpsnprintf(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(cmd, 2));
392 Cmd_ExecuteString(cmd, vabuf, vabuf_len, src_local, false);
395 // open the demo file
396 Con_Printf("recording to %s.\n", name);
397 cls.demofile = FS_OpenRealFile(name, "wb", false);
400 Con_Print(CON_ERROR "ERROR: couldn't open.\n");
403 dp_strlcpy(cls.demoname, name, sizeof(cls.demoname));
405 cls.forcetrack = track;
406 FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
408 cls.demorecording = true;
409 cls.demo_lastcsprogssize = -1;
410 cls.demo_lastcsprogscrc = -1;
413 void CL_PlayDemo(const char *demo)
415 char name[MAX_QPATH];
420 // open the demo file
421 dp_strlcpy (name, demo, sizeof (name));
422 FS_DefaultExtension (name, ".dem", sizeof (name));
423 f = FS_OpenVirtualFile(name, false);
426 Con_Printf(CON_ERROR "ERROR: couldn't open %s.\n", name);
427 cls.demonum = -1; // stop demo loop
431 cls.demostarting = true;
433 // disconnect from server
436 // update networking ports (this is mainly just needed at startup)
437 NetConn_UpdateSockets();
439 cls.protocol = PROTOCOL_QUAKE;
441 Con_Printf("Playing demo %s.\n", name);
443 dp_strlcpy(cls.demoname, name, sizeof(cls.demoname));
445 cls.demoplayback = true;
446 cls.state = ca_connected;
449 while ((c = FS_Getc (cls.demofile)) != '\n')
453 cls.forcetrack = cls.forcetrack * 10 + (c - '0');
456 cls.forcetrack = -cls.forcetrack;
458 cls.demostarting = false;
468 void CL_PlayDemo_f(cmd_state_t *cmd)
470 if (Cmd_Argc(cmd) != 2)
472 Con_Print("playdemo <demoname> : plays a demo\n");
476 CL_PlayDemo(Cmd_Argv(cmd, 1));
482 double time, totalfpsavg;
483 double fpsmin, fpsavg, fpsmax;
486 static size_t doublecmp_offset;
487 static int doublecmp_withoffset(const void *a_, const void *b_)
489 const double *a = (const double *) ((const char *) a_ + doublecmp_offset);
490 const double *b = (const double *) ((const char *) b_ + doublecmp_offset);
504 static void CL_FinishTimeDemo (void)
508 double time, totalfpsavg;
509 double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
510 static int benchmark_runs = 0;
513 cls.timedemo = host.restless = false;
515 frames = cls.td_frames;
516 time = host.realtime - cls.td_starttime;
517 totalfpsavg = time > 0 ? frames / time : 0;
518 fpsmin = cls.td_onesecondminfps;
519 fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
520 fpsmax = cls.td_onesecondmaxfps;
521 // LadyHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
522 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);
523 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);
524 if (Sys_CheckParm("-benchmark"))
527 i = Sys_CheckParm("-benchmarkruns");
528 if(i && i + 1 < sys.argc)
530 static benchmarkhistory_t *history = NULL;
532 history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(sys.argv[i + 1]));
534 history[benchmark_runs - 1].frames = frames;
535 history[benchmark_runs - 1].time = time;
536 history[benchmark_runs - 1].totalfpsavg = totalfpsavg;
537 history[benchmark_runs - 1].fpsmin = fpsmin;
538 history[benchmark_runs - 1].fpsavg = fpsavg;
539 history[benchmark_runs - 1].fpsmax = fpsmax;
541 if(atoi(sys.argv[i + 1]) > benchmark_runs)
543 // restart the benchmark
544 Cbuf_AddText(cmd_local, va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname));
545 // cannot execute here
550 int first = Sys_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0;
551 if(benchmark_runs > first)
554 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f
557 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f
560 doublecmp_offset = (char *)&history->f - (char *)history; \
561 qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \
562 if((first + benchmark_runs) & 1) \
563 f = history[(first + benchmark_runs - 1) / 2].f; \
565 f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2
573 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);
581 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);
589 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);
593 host.state = host_shutdown;
597 host.state = host_shutdown;
600 // Might need to re-enable vsync
601 Cvar_Callback(&vid_vsync);
611 void CL_TimeDemo_f(cmd_state_t *cmd)
613 if (Cmd_Argc(cmd) != 2)
615 Con_Print("timedemo <demoname> : gets demo speeds\n");
619 srand(0); // predictable random sequence for benchmarking
621 CL_PlayDemo(Cmd_Argv(cmd, 1));
623 // cls.td_starttime will be grabbed at the second frame of the demo, so
624 // all the loading time doesn't get counted
626 // instantly hide console and deactivate it
628 key_consoleactive = 0;
631 cls.timedemo = host.restless = true;
632 cls.td_frames = -2; // skip the first frame
633 cls.demonum = -1; // stop demo loop
635 // Might need to disable vsync
636 Cvar_Callback(&vid_vsync);
640 ===============================================================================
644 ===============================================================================
653 static void CL_Startdemos_f(cmd_state_t *cmd)
657 if (cls.state == ca_dedicated || Sys_CheckParm("-listen") || Sys_CheckParm("-benchmark") || Sys_CheckParm("-demo") || Sys_CheckParm("-capturedemo"))
660 c = Cmd_Argc(cmd) - 1;
663 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
666 Con_DPrintf("%i demo(s) in loop\n", c);
668 for (i=1 ; i<c+1 ; i++)
669 dp_strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
671 // LadyHavoc: clear the remaining slots
672 for (;i <= MAX_DEMOS;i++)
673 cls.demos[i-1][0] = 0;
675 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
689 Return to looping demos
692 static void CL_Demos_f(cmd_state_t *cmd)
694 if (cls.state == ca_dedicated)
696 if (cls.demonum == -1)
706 Return to looping demos
709 static void CL_Stopdemo_f(cmd_state_t *cmd)
711 if (!cls.demoplayback)
716 // LadyHavoc: pausedemo command
717 static void CL_PauseDemo_f(cmd_state_t *cmd)
719 cls.demopaused = !cls.demopaused;
721 Con_Print("Demo paused\n");
723 Con_Print("Demo unpaused\n");
726 void CL_Demo_Init(void)
728 Cmd_AddCommand(CF_CLIENT, "record", CL_Record_f, "record a demo");
729 Cmd_AddCommand(CF_CLIENT, "stop", CL_Stop_f, "stop recording or playing a demo");
730 Cmd_AddCommand(CF_CLIENT, "playdemo", CL_PlayDemo_f, "watch a demo file");
731 Cmd_AddCommand(CF_CLIENT, "timedemo", CL_TimeDemo_f, "play back a demo as fast as possible and save statistics to benchmark.log");
732 Cmd_AddCommand(CF_CLIENT, "startdemos", CL_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
733 Cmd_AddCommand(CF_CLIENT, "demos", CL_Demos_f, "restart looping demos defined by the last startdemos command");
734 Cmd_AddCommand(CF_CLIENT, "stopdemo", CL_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
735 // LadyHavoc: added pausedemo
736 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)");
737 Cvar_RegisterVariable (&cl_autodemo);
738 Cvar_RegisterVariable (&cl_autodemo_nameformat);
739 Cvar_RegisterVariable (&cl_autodemo_delete);