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
427 // update networking ports (this is mainly just needed at startup)
428 NetConn_UpdateSockets();
430 cls.protocol = PROTOCOL_QUAKE;
432 Con_Printf("Playing demo %s.\n", name);
434 strlcpy(cls.demoname, name, sizeof(cls.demoname));
436 cls.demoplayback = true;
437 cls.state = ca_connected;
440 while ((c = FS_Getc (cls.demofile)) != '\n')
444 cls.forcetrack = cls.forcetrack * 10 + (c - '0');
447 cls.forcetrack = -cls.forcetrack;
449 cls.demostarting = false;
459 void CL_PlayDemo_f(cmd_state_t *cmd)
461 if (Cmd_Argc(cmd) != 2)
463 Con_Print("playdemo <demoname> : plays a demo\n");
467 CL_PlayDemo(Cmd_Argv(cmd, 1));
473 double time, totalfpsavg;
474 double fpsmin, fpsavg, fpsmax;
477 static size_t doublecmp_offset;
478 static int doublecmp_withoffset(const void *a_, const void *b_)
480 const double *a = (const double *) ((const char *) a_ + doublecmp_offset);
481 const double *b = (const double *) ((const char *) b_ + doublecmp_offset);
495 static void CL_FinishTimeDemo (void)
499 double time, totalfpsavg;
500 double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
501 static int benchmark_runs = 0;
504 cls.timedemo = host.restless = false;
506 frames = cls.td_frames;
507 time = host.realtime - cls.td_starttime;
508 totalfpsavg = time > 0 ? frames / time : 0;
509 fpsmin = cls.td_onesecondminfps;
510 fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
511 fpsmax = cls.td_onesecondmaxfps;
512 // LadyHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
513 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);
514 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);
515 if (Sys_CheckParm("-benchmark"))
518 i = Sys_CheckParm("-benchmarkruns");
519 if(i && i + 1 < sys.argc)
521 static benchmarkhistory_t *history = NULL;
523 history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(sys.argv[i + 1]));
525 history[benchmark_runs - 1].frames = frames;
526 history[benchmark_runs - 1].time = time;
527 history[benchmark_runs - 1].totalfpsavg = totalfpsavg;
528 history[benchmark_runs - 1].fpsmin = fpsmin;
529 history[benchmark_runs - 1].fpsavg = fpsavg;
530 history[benchmark_runs - 1].fpsmax = fpsmax;
532 if(atoi(sys.argv[i + 1]) > benchmark_runs)
534 // restart the benchmark
535 Cbuf_AddText(cmd_local, va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname));
536 // cannot execute here
541 int first = Sys_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0;
542 if(benchmark_runs > first)
545 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f
548 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f
551 doublecmp_offset = (char *)&history->f - (char *)history; \
552 qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \
553 if((first + benchmark_runs) & 1) \
554 f = history[(first + benchmark_runs - 1) / 2].f; \
556 f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2
564 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);
572 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);
580 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);
584 host.state = host_shutdown;
588 host.state = host_shutdown;
599 void CL_TimeDemo_f(cmd_state_t *cmd)
601 if (Cmd_Argc(cmd) != 2)
603 Con_Print("timedemo <demoname> : gets demo speeds\n");
607 srand(0); // predictable random sequence for benchmarking
609 CL_PlayDemo(Cmd_Argv(cmd, 1));
611 // cls.td_starttime will be grabbed at the second frame of the demo, so
612 // all the loading time doesn't get counted
614 // instantly hide console and deactivate it
616 key_consoleactive = 0;
619 cls.timedemo = host.restless = true;
620 cls.td_frames = -2; // skip the first frame
621 cls.demonum = -1; // stop demo loop
625 ===============================================================================
629 ===============================================================================
638 static void CL_Startdemos_f(cmd_state_t *cmd)
642 if (cls.state == ca_dedicated || Sys_CheckParm("-listen") || Sys_CheckParm("-benchmark") || Sys_CheckParm("-demo") || Sys_CheckParm("-capturedemo"))
645 c = Cmd_Argc(cmd) - 1;
648 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
651 Con_DPrintf("%i demo(s) in loop\n", c);
653 for (i=1 ; i<c+1 ; i++)
654 strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
656 // LadyHavoc: clear the remaining slots
657 for (;i <= MAX_DEMOS;i++)
658 cls.demos[i-1][0] = 0;
660 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
674 Return to looping demos
677 static void CL_Demos_f(cmd_state_t *cmd)
679 if (cls.state == ca_dedicated)
681 if (cls.demonum == -1)
691 Return to looping demos
694 static void CL_Stopdemo_f(cmd_state_t *cmd)
696 if (!cls.demoplayback)
701 // LadyHavoc: pausedemo command
702 static void CL_PauseDemo_f(cmd_state_t *cmd)
704 cls.demopaused = !cls.demopaused;
706 Con_Print("Demo paused\n");
708 Con_Print("Demo unpaused\n");
711 void CL_Demo_Init(void)
713 Cmd_AddCommand(CF_CLIENT, "record", CL_Record_f, "record a demo");
714 Cmd_AddCommand(CF_CLIENT, "stop", CL_Stop_f, "stop recording or playing a demo");
715 Cmd_AddCommand(CF_CLIENT, "playdemo", CL_PlayDemo_f, "watch a demo file");
716 Cmd_AddCommand(CF_CLIENT, "timedemo", CL_TimeDemo_f, "play back a demo as fast as possible and save statistics to benchmark.log");
717 Cmd_AddCommand(CF_CLIENT, "startdemos", CL_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
718 Cmd_AddCommand(CF_CLIENT, "demos", CL_Demos_f, "restart looping demos defined by the last startdemos command");
719 Cmd_AddCommand(CF_CLIENT, "stopdemo", CL_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
720 // LadyHavoc: added pausedemo
721 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)");
722 Cvar_RegisterVariable (&cl_autodemo);
723 Cvar_RegisterVariable (&cl_autodemo_nameformat);
724 Cvar_RegisterVariable (&cl_autodemo_delete);