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.state = host_shutdown;
111 Dumps the current net message, prefixed by the length and view angles
112 #====================
114 void CL_WriteDemoMessage (sizebuf_t *message)
120 if (cls.demopaused) // LadyHavoc: pausedemo
123 len = LittleLong (message->cursize);
124 FS_Write (cls.demofile, &len, 4);
125 for (i=0 ; i<3 ; i++)
127 f = LittleFloat (cl.viewangles[i]);
128 FS_Write (cls.demofile, &f, 4);
130 FS_Write (cls.demofile, message->data, message->cursize);
137 Dumps the current demo to a buffer, and resets the demo to its starting point.
138 Used to insert csprogs.dat files as a download to the beginning of a demo file.
141 void CL_CutDemo (unsigned char **buf, fs_offset_t *filesize)
146 FS_Close(cls.demofile);
147 *buf = FS_LoadFile(cls.demoname, tempmempool, false, filesize);
149 // restart the demo recording
150 cls.demofile = FS_OpenRealFile(cls.demoname, "wb", false);
152 Sys_Error("failed to reopen the demo file");
153 FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
160 Adds the cut stuff back to the demo. Also frees the buffer.
161 Used to insert csprogs.dat files as a download to the beginning of a demo file.
164 void CL_PasteDemo (unsigned char **buf, fs_offset_t *filesize)
166 fs_offset_t startoffset = 0;
172 while(startoffset < *filesize && ((char *)(*buf))[startoffset] != '\n')
174 if(startoffset < *filesize)
177 FS_Write(cls.demofile, *buf + startoffset, *filesize - startoffset);
188 Handles playback of demos
191 void CL_ReadDemoMessage(void)
196 if (!cls.demoplayback)
199 // LadyHavoc: pausedemo
205 // decide if it is time to grab the next message
206 // always grab until fully connected
207 if (cls.signon == SIGNONS)
212 cls.td_onesecondframes++;
213 // if this is the first official frame we can now grab the real
214 // td_starttime so the bogus time on the first frame doesn't
215 // count against the final report
216 if (cls.td_frames == 0)
218 cls.td_starttime = host.realtime;
219 cls.td_onesecondnexttime = cl.time + 1;
220 cls.td_onesecondrealtime = host.realtime;
221 cls.td_onesecondframes = 0;
222 cls.td_onesecondminfps = 0;
223 cls.td_onesecondmaxfps = 0;
224 cls.td_onesecondavgfps = 0;
225 cls.td_onesecondavgcount = 0;
227 if (cl.time >= cls.td_onesecondnexttime)
229 double fps = cls.td_onesecondframes / (host.realtime - cls.td_onesecondrealtime);
230 if (cls.td_onesecondavgcount == 0)
232 cls.td_onesecondminfps = fps;
233 cls.td_onesecondmaxfps = fps;
235 cls.td_onesecondrealtime = host.realtime;
236 cls.td_onesecondminfps = min(cls.td_onesecondminfps, fps);
237 cls.td_onesecondmaxfps = max(cls.td_onesecondmaxfps, fps);
238 cls.td_onesecondavgfps += fps;
239 cls.td_onesecondavgcount++;
240 cls.td_onesecondframes = 0;
241 cls.td_onesecondnexttime++;
244 else if (cl.time < cl.mtime[0])
246 // don't need another message yet
251 // get the next message
252 FS_Read(cls.demofile, &cl_message.cursize, 4);
253 cl_message.cursize = LittleLong(cl_message.cursize);
254 if(cl_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
256 // skip over demo packet
257 FS_Seek(cls.demofile, 12 + (cl_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
260 if (cl_message.cursize > cl_message.maxsize)
262 Con_Printf("Demo message (%i) > cl_message.maxsize (%i)", cl_message.cursize, cl_message.maxsize);
263 cl_message.cursize = 0;
267 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
268 for (i = 0;i < 3;i++)
270 FS_Read(cls.demofile, &f, 4);
271 cl.mviewangles[0][i] = LittleFloat(f);
274 if (FS_Read(cls.demofile, cl_message.data, cl_message.cursize) == cl_message.cursize)
276 MSG_BeginReading(&cl_message);
277 CL_ParseServerMessage();
279 if (cls.signon != SIGNONS)
280 Cbuf_Execute((&cmd_client)->cbuf); // immediately execute svc_stufftext if in the demo before connect!
282 // In case the demo contains a "svc_disconnect" message
283 if (!cls.demoplayback)
302 stop recording a demo
305 void CL_Stop_f(cmd_state_t *cmd)
308 unsigned char bufdata[64];
310 if (!cls.demorecording)
312 Con_Print("Not recording a demo.\n");
316 // write a disconnect message to the demo file
317 // LadyHavoc: don't replace the cl_message when doing this
319 buf.maxsize = sizeof(bufdata);
321 MSG_WriteByte(&buf, svc_disconnect);
322 CL_WriteDemoMessage(&buf);
325 if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1))
327 FS_RemoveOnClose(cls.demofile);
328 Con_Print("Completed and deleted demo\n");
331 Con_Print("Completed demo\n");
332 FS_Close (cls.demofile);
334 cls.demorecording = false;
341 record <demoname> <map> [cd track]
344 void CL_Record_f(cmd_state_t *cmd)
347 char name[MAX_OSPATH];
351 if (c != 2 && c != 3 && c != 4)
353 Con_Print("record <demoname> [<map> [cd track]]\n");
357 if (strstr(Cmd_Argv(cmd, 1), ".."))
359 Con_Print("Relative pathnames are not allowed.\n");
363 if (c == 2 && cls.state == ca_connected)
365 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
369 if (cls.state == ca_connected)
372 // write the forced cd track number, or -1
375 track = atoi(Cmd_Argv(cmd, 3));
376 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
382 strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
383 FS_DefaultExtension (name, ".dem", sizeof (name));
387 Cmd_ExecuteString ( cmd, va(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(cmd, 2)), src_local, false);
389 // open the demo file
390 Con_Printf("recording to %s.\n", name);
391 cls.demofile = FS_OpenRealFile(name, "wb", false);
394 Con_Print(CON_ERROR "ERROR: couldn't open.\n");
397 strlcpy(cls.demoname, name, sizeof(cls.demoname));
399 cls.forcetrack = track;
400 FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
402 cls.demorecording = true;
403 cls.demo_lastcsprogssize = -1;
404 cls.demo_lastcsprogscrc = -1;
415 void CL_PlayDemo_f(cmd_state_t *cmd)
417 char name[MAX_QPATH];
422 if (Cmd_Argc(cmd) != 2)
424 Con_Print("play <demoname> : plays a demo\n");
428 // open the demo file
429 strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
430 FS_DefaultExtension (name, ".dem", sizeof (name));
431 f = FS_OpenVirtualFile(name, false);
434 Con_Printf(CON_ERROR "ERROR: couldn't open %s.\n", name);
435 cls.demonum = -1; // stop demo loop
439 cls.demostarting = true;
441 // disconnect from server
445 // update networking ports (this is mainly just needed at startup)
446 NetConn_UpdateSockets();
448 cls.protocol = PROTOCOL_QUAKE;
450 Con_Printf("Playing demo %s.\n", name);
452 strlcpy(cls.demoname, name, sizeof(cls.demoname));
454 cls.demoplayback = true;
455 cls.state = ca_connected;
458 while ((c = FS_Getc (cls.demofile)) != '\n')
462 cls.forcetrack = cls.forcetrack * 10 + (c - '0');
465 cls.forcetrack = -cls.forcetrack;
467 cls.demostarting = false;
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_client, 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
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)
683 CL_Disconnect_f (cmd);
691 Return to looping demos
694 static void CL_Stopdemo_f(cmd_state_t *cmd)
696 if (!cls.demoplayback)
702 // LadyHavoc: pausedemo command
703 static void CL_PauseDemo_f(cmd_state_t *cmd)
705 cls.demopaused = !cls.demopaused;
707 Con_Print("Demo paused\n");
709 Con_Print("Demo unpaused\n");
712 void CL_Demo_Init(void)
714 Cmd_AddCommand(CF_CLIENT, "record", CL_Record_f, "record a demo");
715 Cmd_AddCommand(CF_CLIENT, "stop", CL_Stop_f, "stop recording or playing a demo");
716 Cmd_AddCommand(CF_CLIENT, "playdemo", CL_PlayDemo_f, "watch a demo file");
717 Cmd_AddCommand(CF_CLIENT, "timedemo", CL_TimeDemo_f, "play back a demo as fast as possible and save statistics to benchmark.log");
718 Cmd_AddCommand(CF_CLIENT, "startdemos", CL_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
719 Cmd_AddCommand(CF_CLIENT, "demos", CL_Demos_f, "restart looping demos defined by the last startdemos command");
720 Cmd_AddCommand(CF_CLIENT, "stopdemo", CL_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
721 // LadyHavoc: added pausedemo
722 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)");
723 Cvar_RegisterVariable (&cl_autodemo);
724 Cvar_RegisterVariable (&cl_autodemo_nameformat);
725 Cvar_RegisterVariable (&cl_autodemo_delete);