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_client, 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_Quit_f(&cmd_client);
109 Dumps the current net message, prefixed by the length and view angles
110 #====================
112 void CL_WriteDemoMessage (sizebuf_t *message)
118 if (cls.demopaused) // LadyHavoc: pausedemo
121 len = LittleLong (message->cursize);
122 FS_Write (cls.demofile, &len, 4);
123 for (i=0 ; i<3 ; i++)
125 f = LittleFloat (cl.viewangles[i]);
126 FS_Write (cls.demofile, &f, 4);
128 FS_Write (cls.demofile, message->data, message->cursize);
135 Dumps the current demo to a buffer, and resets the demo to its starting point.
136 Used to insert csprogs.dat files as a download to the beginning of a demo file.
139 void CL_CutDemo (unsigned char **buf, fs_offset_t *filesize)
144 FS_Close(cls.demofile);
145 *buf = FS_LoadFile(cls.demoname, tempmempool, false, filesize);
147 // restart the demo recording
148 cls.demofile = FS_OpenRealFile(cls.demoname, "wb", false);
150 Sys_Error("failed to reopen the demo file");
151 FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
158 Adds the cut stuff back to the demo. Also frees the buffer.
159 Used to insert csprogs.dat files as a download to the beginning of a demo file.
162 void CL_PasteDemo (unsigned char **buf, fs_offset_t *filesize)
164 fs_offset_t startoffset = 0;
170 while(startoffset < *filesize && ((char *)(*buf))[startoffset] != '\n')
172 if(startoffset < *filesize)
175 FS_Write(cls.demofile, *buf + startoffset, *filesize - startoffset);
186 Handles playback of demos
189 void CL_ReadDemoMessage(void)
194 if (!cls.demoplayback)
197 // LadyHavoc: pausedemo
203 // decide if it is time to grab the next message
204 // always grab until fully connected
205 if (cls.signon == SIGNONS)
210 cls.td_onesecondframes++;
211 // if this is the first official frame we can now grab the real
212 // td_starttime so the bogus time on the first frame doesn't
213 // count against the final report
214 if (cls.td_frames == 0)
216 cls.td_starttime = host.realtime;
217 cls.td_onesecondnexttime = cl.time + 1;
218 cls.td_onesecondrealtime = host.realtime;
219 cls.td_onesecondframes = 0;
220 cls.td_onesecondminfps = 0;
221 cls.td_onesecondmaxfps = 0;
222 cls.td_onesecondavgfps = 0;
223 cls.td_onesecondavgcount = 0;
225 if (cl.time >= cls.td_onesecondnexttime)
227 double fps = cls.td_onesecondframes / (host.realtime - cls.td_onesecondrealtime);
228 if (cls.td_onesecondavgcount == 0)
230 cls.td_onesecondminfps = fps;
231 cls.td_onesecondmaxfps = fps;
233 cls.td_onesecondrealtime = host.realtime;
234 cls.td_onesecondminfps = min(cls.td_onesecondminfps, fps);
235 cls.td_onesecondmaxfps = max(cls.td_onesecondmaxfps, fps);
236 cls.td_onesecondavgfps += fps;
237 cls.td_onesecondavgcount++;
238 cls.td_onesecondframes = 0;
239 cls.td_onesecondnexttime++;
242 else if (cl.time < cl.mtime[0])
244 // don't need another message yet
249 // get the next message
250 FS_Read(cls.demofile, &cl_message.cursize, 4);
251 cl_message.cursize = LittleLong(cl_message.cursize);
252 if(cl_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
254 // skip over demo packet
255 FS_Seek(cls.demofile, 12 + (cl_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
258 if (cl_message.cursize > cl_message.maxsize)
260 Con_Printf("Demo message (%i) > cl_message.maxsize (%i)", cl_message.cursize, cl_message.maxsize);
261 cl_message.cursize = 0;
265 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
266 for (i = 0;i < 3;i++)
268 FS_Read(cls.demofile, &f, 4);
269 cl.mviewangles[0][i] = LittleFloat(f);
272 if (FS_Read(cls.demofile, cl_message.data, cl_message.cursize) == cl_message.cursize)
274 MSG_BeginReading(&cl_message);
275 CL_ParseServerMessage();
277 if (cls.signon != SIGNONS)
278 Cbuf_Execute((&cmd_client)->cbuf); // immediately execute svc_stufftext if in the demo before connect!
280 // In case the demo contains a "svc_disconnect" message
281 if (!cls.demoplayback)
300 stop recording a demo
303 void CL_Stop_f(cmd_state_t *cmd)
306 unsigned char bufdata[64];
308 if (!cls.demorecording)
310 Con_Print("Not recording a demo.\n");
314 // write a disconnect message to the demo file
315 // LadyHavoc: don't replace the cl_message when doing this
317 buf.maxsize = sizeof(bufdata);
319 MSG_WriteByte(&buf, svc_disconnect);
320 CL_WriteDemoMessage(&buf);
323 if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1))
325 FS_RemoveOnClose(cls.demofile);
326 Con_Print("Completed and deleted demo\n");
329 Con_Print("Completed demo\n");
330 FS_Close (cls.demofile);
332 cls.demorecording = false;
339 record <demoname> <map> [cd track]
342 void CL_Record_f(cmd_state_t *cmd)
345 char name[MAX_OSPATH];
349 if (c != 2 && c != 3 && c != 4)
351 Con_Print("record <demoname> [<map> [cd track]]\n");
355 if (strstr(Cmd_Argv(cmd, 1), ".."))
357 Con_Print("Relative pathnames are not allowed.\n");
361 if (c == 2 && cls.state == ca_connected)
363 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
367 if (cls.state == ca_connected)
370 // write the forced cd track number, or -1
373 track = atoi(Cmd_Argv(cmd, 3));
374 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
380 strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
381 FS_DefaultExtension (name, ".dem", sizeof (name));
385 Cmd_ExecuteString ( cmd, va(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(cmd, 2)), src_local, false);
387 // open the demo file
388 Con_Printf("recording to %s.\n", name);
389 cls.demofile = FS_OpenRealFile(name, "wb", false);
392 Con_Print(CON_ERROR "ERROR: couldn't open.\n");
395 strlcpy(cls.demoname, name, sizeof(cls.demoname));
397 cls.forcetrack = track;
398 FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
400 cls.demorecording = true;
401 cls.demo_lastcsprogssize = -1;
402 cls.demo_lastcsprogscrc = -1;
413 void CL_PlayDemo_f(cmd_state_t *cmd)
415 char name[MAX_QPATH];
420 if (Cmd_Argc(cmd) != 2)
422 Con_Print("play <demoname> : plays a demo\n");
426 // open the demo file
427 strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
428 FS_DefaultExtension (name, ".dem", sizeof (name));
429 f = FS_OpenVirtualFile(name, false);
432 Con_Printf(CON_ERROR "ERROR: couldn't open %s.\n", name);
433 cls.demonum = -1; // stop demo loop
437 cls.demostarting = true;
439 // disconnect from server
443 // update networking ports (this is mainly just needed at startup)
444 NetConn_UpdateSockets();
446 cls.protocol = PROTOCOL_QUAKE;
448 Con_Printf("Playing demo %s.\n", name);
450 strlcpy(cls.demoname, name, sizeof(cls.demoname));
452 cls.demoplayback = true;
453 cls.state = ca_connected;
456 while ((c = FS_Getc (cls.demofile)) != '\n')
460 cls.forcetrack = cls.forcetrack * 10 + (c - '0');
463 cls.forcetrack = -cls.forcetrack;
465 cls.demostarting = false;
471 double time, totalfpsavg;
472 double fpsmin, fpsavg, fpsmax;
475 static size_t doublecmp_offset;
476 static int doublecmp_withoffset(const void *a_, const void *b_)
478 const double *a = (const double *) ((const char *) a_ + doublecmp_offset);
479 const double *b = (const double *) ((const char *) b_ + doublecmp_offset);
493 static void CL_FinishTimeDemo (void)
497 double time, totalfpsavg;
498 double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
499 static int benchmark_runs = 0;
502 cls.timedemo = host.restless = false;
504 frames = cls.td_frames;
505 time = host.realtime - cls.td_starttime;
506 totalfpsavg = time > 0 ? frames / time : 0;
507 fpsmin = cls.td_onesecondminfps;
508 fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
509 fpsmax = cls.td_onesecondmaxfps;
510 // LadyHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
511 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);
512 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);
513 if (Sys_CheckParm("-benchmark"))
516 i = Sys_CheckParm("-benchmarkruns");
517 if(i && i + 1 < sys.argc)
519 static benchmarkhistory_t *history = NULL;
521 history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(sys.argv[i + 1]));
523 history[benchmark_runs - 1].frames = frames;
524 history[benchmark_runs - 1].time = time;
525 history[benchmark_runs - 1].totalfpsavg = totalfpsavg;
526 history[benchmark_runs - 1].fpsmin = fpsmin;
527 history[benchmark_runs - 1].fpsavg = fpsavg;
528 history[benchmark_runs - 1].fpsmax = fpsmax;
530 if(atoi(sys.argv[i + 1]) > benchmark_runs)
532 // restart the benchmark
533 Cbuf_AddText(&cmd_client, va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname));
534 // cannot execute here
539 int first = Sys_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0;
540 if(benchmark_runs > first)
543 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f
546 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f
549 doublecmp_offset = (char *)&history->f - (char *)history; \
550 qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \
551 if((first + benchmark_runs) & 1) \
552 f = history[(first + benchmark_runs - 1) / 2].f; \
554 f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2
562 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);
570 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);
578 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);
582 Host_Quit_f(&cmd_client);
586 Host_Quit_f(&cmd_client);
597 void CL_TimeDemo_f(cmd_state_t *cmd)
599 if (Cmd_Argc(cmd) != 2)
601 Con_Print("timedemo <demoname> : gets demo speeds\n");
605 srand(0); // predictable random sequence for benchmarking
609 // cls.td_starttime will be grabbed at the second frame of the demo, so
610 // all the loading time doesn't get counted
612 // instantly hide console and deactivate it
614 key_consoleactive = 0;
617 cls.timedemo = host.restless = true;
618 cls.td_frames = -2; // skip the first frame
619 cls.demonum = -1; // stop demo loop
623 ===============================================================================
627 ===============================================================================
636 static void CL_Startdemos_f(cmd_state_t *cmd)
640 if (cls.state == ca_dedicated || Sys_CheckParm("-listen") || Sys_CheckParm("-benchmark") || Sys_CheckParm("-demo") || Sys_CheckParm("-capturedemo"))
643 c = Cmd_Argc(cmd) - 1;
646 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
649 Con_DPrintf("%i demo(s) in loop\n", c);
651 for (i=1 ; i<c+1 ; i++)
652 strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
654 // LadyHavoc: clear the remaining slots
655 for (;i <= MAX_DEMOS;i++)
656 cls.demos[i-1][0] = 0;
658 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
672 Return to looping demos
675 static void CL_Demos_f(cmd_state_t *cmd)
677 if (cls.state == ca_dedicated)
679 if (cls.demonum == -1)
681 CL_Disconnect_f (cmd);
689 Return to looping demos
692 static void CL_Stopdemo_f(cmd_state_t *cmd)
694 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);