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 Con_Printf("Demo message (%i) > cl_message.maxsize (%i)", cl_message.cursize, cl_message.maxsize);
260 cl_message.cursize = 0;
264 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
265 for (i = 0;i < 3;i++)
267 FS_Read(cls.demofile, &f, 4);
268 cl.mviewangles[0][i] = LittleFloat(f);
271 if (FS_Read(cls.demofile, cl_message.data, cl_message.cursize) == cl_message.cursize)
273 MSG_BeginReading(&cl_message);
274 CL_ParseServerMessage();
276 if (cls.signon != SIGNONS)
277 Cbuf_Execute((cmd_local)->cbuf); // immediately execute svc_stufftext if in the demo before connect!
279 // In case the demo contains a "svc_disconnect" message
280 if (!cls.demoplayback)
299 stop recording a demo
302 void CL_Stop_f(cmd_state_t *cmd)
305 unsigned char bufdata[64];
307 if (!cls.demorecording)
309 Con_Print("Not recording a demo.\n");
313 // write a disconnect message to the demo file
314 // LadyHavoc: don't replace the cl_message when doing this
316 buf.maxsize = sizeof(bufdata);
318 MSG_WriteByte(&buf, svc_disconnect);
319 CL_WriteDemoMessage(&buf);
322 if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1))
324 FS_RemoveOnClose(cls.demofile);
325 Con_Print("Completed and deleted demo\n");
328 Con_Print("Completed demo\n");
329 FS_Close (cls.demofile);
331 cls.demorecording = false;
338 record <demoname> <map> [cd track]
341 void CL_Record_f(cmd_state_t *cmd)
344 char name[MAX_OSPATH];
348 if (c != 2 && c != 3 && c != 4)
350 Con_Print("record <demoname> [<map> [cd track]]\n");
354 if (strstr(Cmd_Argv(cmd, 1), ".."))
356 Con_Print("Relative pathnames are not allowed.\n");
360 if (c == 2 && cls.state == ca_connected)
362 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
366 if (cls.state == ca_connected)
369 // write the forced cd track number, or -1
372 track = atoi(Cmd_Argv(cmd, 3));
373 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
379 strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
380 FS_DefaultExtension (name, ".dem", sizeof (name));
384 Cmd_ExecuteString ( cmd, va(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(cmd, 2)), src_local, false);
386 // open the demo file
387 Con_Printf("recording to %s.\n", name);
388 cls.demofile = FS_OpenRealFile(name, "wb", false);
391 Con_Print(CON_ERROR "ERROR: couldn't open.\n");
394 strlcpy(cls.demoname, name, sizeof(cls.demoname));
396 cls.forcetrack = track;
397 FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
399 cls.demorecording = true;
400 cls.demo_lastcsprogssize = -1;
401 cls.demo_lastcsprogscrc = -1;
404 void CL_PlayDemo(const char *demo)
406 char name[MAX_QPATH];
411 // open the demo file
412 strlcpy (name, demo, sizeof (name));
413 FS_DefaultExtension (name, ".dem", sizeof (name));
414 f = FS_OpenVirtualFile(name, false);
417 Con_Printf(CON_ERROR "ERROR: couldn't open %s.\n", name);
418 cls.demonum = -1; // stop demo loop
422 cls.demostarting = true;
424 // disconnect from server
425 if(cls.state == ca_connected)
430 // update networking ports (this is mainly just needed at startup)
431 NetConn_UpdateSockets();
433 cls.protocol = PROTOCOL_QUAKE;
435 Con_Printf("Playing demo %s.\n", name);
437 strlcpy(cls.demoname, name, sizeof(cls.demoname));
439 cls.demoplayback = true;
440 cls.state = ca_connected;
443 while ((c = FS_Getc (cls.demofile)) != '\n')
447 cls.forcetrack = cls.forcetrack * 10 + (c - '0');
450 cls.forcetrack = -cls.forcetrack;
452 cls.demostarting = false;
462 void CL_PlayDemo_f(cmd_state_t *cmd)
464 if (Cmd_Argc(cmd) != 2)
466 Con_Print("playdemo <demoname> : plays a demo\n");
470 CL_PlayDemo(Cmd_Argv(cmd, 1));
476 double time, totalfpsavg;
477 double fpsmin, fpsavg, fpsmax;
480 static size_t doublecmp_offset;
481 static int doublecmp_withoffset(const void *a_, const void *b_)
483 const double *a = (const double *) ((const char *) a_ + doublecmp_offset);
484 const double *b = (const double *) ((const char *) b_ + doublecmp_offset);
498 static void CL_FinishTimeDemo (void)
502 double time, totalfpsavg;
503 double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
504 static int benchmark_runs = 0;
507 cls.timedemo = host.restless = false;
509 frames = cls.td_frames;
510 time = host.realtime - cls.td_starttime;
511 totalfpsavg = time > 0 ? frames / time : 0;
512 fpsmin = cls.td_onesecondminfps;
513 fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
514 fpsmax = cls.td_onesecondmaxfps;
515 // LadyHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
516 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);
517 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);
518 if (Sys_CheckParm("-benchmark"))
521 i = Sys_CheckParm("-benchmarkruns");
522 if(i && i + 1 < sys.argc)
524 static benchmarkhistory_t *history = NULL;
526 history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(sys.argv[i + 1]));
528 history[benchmark_runs - 1].frames = frames;
529 history[benchmark_runs - 1].time = time;
530 history[benchmark_runs - 1].totalfpsavg = totalfpsavg;
531 history[benchmark_runs - 1].fpsmin = fpsmin;
532 history[benchmark_runs - 1].fpsavg = fpsavg;
533 history[benchmark_runs - 1].fpsmax = fpsmax;
535 if(atoi(sys.argv[i + 1]) > benchmark_runs)
537 // restart the benchmark
538 Cbuf_AddText(cmd_local, va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname));
539 // cannot execute here
544 int first = Sys_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0;
545 if(benchmark_runs > first)
548 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f
551 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f
554 doublecmp_offset = (char *)&history->f - (char *)history; \
555 qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \
556 if((first + benchmark_runs) & 1) \
557 f = history[(first + benchmark_runs - 1) / 2].f; \
559 f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2
567 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);
575 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);
583 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);
587 host.state = host_shutdown;
591 host.state = host_shutdown;
602 void CL_TimeDemo_f(cmd_state_t *cmd)
604 if (Cmd_Argc(cmd) != 2)
606 Con_Print("timedemo <demoname> : gets demo speeds\n");
610 srand(0); // predictable random sequence for benchmarking
612 CL_PlayDemo(Cmd_Argv(cmd, 1));
614 // cls.td_starttime will be grabbed at the second frame of the demo, so
615 // all the loading time doesn't get counted
617 // instantly hide console and deactivate it
619 key_consoleactive = 0;
622 cls.timedemo = host.restless = true;
623 cls.td_frames = -2; // skip the first frame
624 cls.demonum = -1; // stop demo loop
628 ===============================================================================
632 ===============================================================================
641 static void CL_Startdemos_f(cmd_state_t *cmd)
645 if (cls.state == ca_dedicated || Sys_CheckParm("-listen") || Sys_CheckParm("-benchmark") || Sys_CheckParm("-demo") || Sys_CheckParm("-capturedemo"))
648 c = Cmd_Argc(cmd) - 1;
651 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
654 Con_DPrintf("%i demo(s) in loop\n", c);
656 for (i=1 ; i<c+1 ; i++)
657 strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
659 // LadyHavoc: clear the remaining slots
660 for (;i <= MAX_DEMOS;i++)
661 cls.demos[i-1][0] = 0;
663 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
677 Return to looping demos
680 static void CL_Demos_f(cmd_state_t *cmd)
682 if (cls.state == ca_dedicated)
684 if (cls.demonum == -1)
686 CL_Disconnect_f (cmd);
694 Return to looping demos
697 static void CL_Stopdemo_f(cmd_state_t *cmd)
699 if (!cls.demoplayback)
704 // LadyHavoc: pausedemo command
705 static void CL_PauseDemo_f(cmd_state_t *cmd)
707 cls.demopaused = !cls.demopaused;
709 Con_Print("Demo paused\n");
711 Con_Print("Demo unpaused\n");
714 void CL_Demo_Init(void)
716 Cmd_AddCommand(CF_CLIENT, "record", CL_Record_f, "record a demo");
717 Cmd_AddCommand(CF_CLIENT, "stop", CL_Stop_f, "stop recording or playing a demo");
718 Cmd_AddCommand(CF_CLIENT, "playdemo", CL_PlayDemo_f, "watch a demo file");
719 Cmd_AddCommand(CF_CLIENT, "timedemo", CL_TimeDemo_f, "play back a demo as fast as possible and save statistics to benchmark.log");
720 Cmd_AddCommand(CF_CLIENT, "startdemos", CL_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
721 Cmd_AddCommand(CF_CLIENT, "demos", CL_Demos_f, "restart looping demos defined by the last startdemos command");
722 Cmd_AddCommand(CF_CLIENT, "stopdemo", CL_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
723 // LadyHavoc: added pausedemo
724 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)");
725 Cvar_RegisterVariable (&cl_autodemo);
726 Cvar_RegisterVariable (&cl_autodemo_nameformat);
727 Cvar_RegisterVariable (&cl_autodemo_delete);