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_Error("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 // get the next message
248 FS_Read(cls.demofile, &cl_message.cursize, 4);
249 cl_message.cursize = LittleLong(cl_message.cursize);
250 if(cl_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
252 // skip over demo packet
253 FS_Seek(cls.demofile, 12 + (cl_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
256 if (cl_message.cursize > cl_message.maxsize)
258 CL_DisconnectEx(false, "Demo message (%i) > cl_message.maxsize (%i)", cl_message.cursize, cl_message.maxsize);
259 cl_message.cursize = 0;
262 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
263 for (i = 0;i < 3;i++)
265 FS_Read(cls.demofile, &f, 4);
266 cl.mviewangles[0][i] = LittleFloat(f);
269 if (FS_Read(cls.demofile, cl_message.data, cl_message.cursize) == cl_message.cursize)
271 MSG_BeginReading(&cl_message);
272 CL_ParseServerMessage();
274 if (cls.signon != SIGNONS)
275 Cbuf_Execute((cmd_local)->cbuf); // immediately execute svc_stufftext if in the demo before connect!
277 // In case the demo contains a "svc_disconnect" message
278 if (!cls.demoplayback)
297 stop recording a demo
300 void CL_Stop_f(cmd_state_t *cmd)
303 unsigned char bufdata[64];
305 if (!cls.demorecording)
307 Con_Print("Not recording a demo.\n");
311 // write a disconnect message to the demo file
312 // LadyHavoc: don't replace the cl_message when doing this
314 buf.maxsize = sizeof(bufdata);
316 MSG_WriteByte(&buf, svc_disconnect);
317 CL_WriteDemoMessage(&buf);
320 if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1))
322 FS_RemoveOnClose(cls.demofile);
323 Con_Print("Completed and deleted demo\n");
326 Con_Print("Completed demo\n");
327 FS_Close (cls.demofile);
329 cls.demorecording = false;
336 record <demoname> <map> [cd track]
339 void CL_Record_f(cmd_state_t *cmd)
342 char name[MAX_OSPATH];
346 if (c != 2 && c != 3 && c != 4)
348 Con_Print("record <demoname> [<map> [cd track]]\n");
352 if (strstr(Cmd_Argv(cmd, 1), ".."))
354 Con_Print("Relative pathnames are not allowed.\n");
358 if (c == 2 && cls.state == ca_connected)
360 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
364 if (cls.state == ca_connected)
367 // write the forced cd track number, or -1
370 track = atoi(Cmd_Argv(cmd, 3));
371 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
377 strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
378 FS_DefaultExtension (name, ".dem", sizeof (name));
382 Cmd_ExecuteString ( cmd, va(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(cmd, 2)), src_local, false);
384 // open the demo file
385 Con_Printf("recording to %s.\n", name);
386 cls.demofile = FS_OpenRealFile(name, "wb", false);
389 Con_Print(CON_ERROR "ERROR: couldn't open.\n");
392 strlcpy(cls.demoname, name, sizeof(cls.demoname));
394 cls.forcetrack = track;
395 FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
397 cls.demorecording = true;
398 cls.demo_lastcsprogssize = -1;
399 cls.demo_lastcsprogscrc = -1;
402 void CL_PlayDemo(const char *demo)
404 char name[MAX_QPATH];
409 // open the demo file
410 strlcpy (name, demo, sizeof (name));
411 FS_DefaultExtension (name, ".dem", sizeof (name));
412 f = FS_OpenVirtualFile(name, false);
415 Con_Printf(CON_ERROR "ERROR: couldn't open %s.\n", name);
416 cls.demonum = -1; // stop demo loop
420 cls.demostarting = true;
422 // disconnect from server
425 // update networking ports (this is mainly just needed at startup)
426 NetConn_UpdateSockets();
428 cls.protocol = PROTOCOL_QUAKE;
430 Con_Printf("Playing demo %s.\n", name);
432 strlcpy(cls.demoname, name, sizeof(cls.demoname));
434 cls.demoplayback = true;
435 cls.state = ca_connected;
438 while ((c = FS_Getc (cls.demofile)) != '\n')
442 cls.forcetrack = cls.forcetrack * 10 + (c - '0');
445 cls.forcetrack = -cls.forcetrack;
447 cls.demostarting = false;
457 void CL_PlayDemo_f(cmd_state_t *cmd)
459 if (Cmd_Argc(cmd) != 2)
461 Con_Print("playdemo <demoname> : plays a demo\n");
465 CL_PlayDemo(Cmd_Argv(cmd, 1));
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_local, 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.state = host_shutdown;
586 host.state = host_shutdown;
589 // Might need to re-enable vsync
590 Cvar_Callback(&vid_vsync);
600 void CL_TimeDemo_f(cmd_state_t *cmd)
602 if (Cmd_Argc(cmd) != 2)
604 Con_Print("timedemo <demoname> : gets demo speeds\n");
608 srand(0); // predictable random sequence for benchmarking
610 CL_PlayDemo(Cmd_Argv(cmd, 1));
612 // cls.td_starttime will be grabbed at the second frame of the demo, so
613 // all the loading time doesn't get counted
615 // instantly hide console and deactivate it
617 key_consoleactive = 0;
620 cls.timedemo = host.restless = true;
621 cls.td_frames = -2; // skip the first frame
622 cls.demonum = -1; // stop demo loop
624 // Might need to disable vsync
625 Cvar_Callback(&vid_vsync);
629 ===============================================================================
633 ===============================================================================
642 static void CL_Startdemos_f(cmd_state_t *cmd)
646 if (cls.state == ca_dedicated || Sys_CheckParm("-listen") || Sys_CheckParm("-benchmark") || Sys_CheckParm("-demo") || Sys_CheckParm("-capturedemo"))
649 c = Cmd_Argc(cmd) - 1;
652 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
655 Con_DPrintf("%i demo(s) in loop\n", c);
657 for (i=1 ; i<c+1 ; i++)
658 strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
660 // LadyHavoc: clear the remaining slots
661 for (;i <= MAX_DEMOS;i++)
662 cls.demos[i-1][0] = 0;
664 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
678 Return to looping demos
681 static void CL_Demos_f(cmd_state_t *cmd)
683 if (cls.state == ca_dedicated)
685 if (cls.demonum == -1)
695 Return to looping demos
698 static void CL_Stopdemo_f(cmd_state_t *cmd)
700 if (!cls.demoplayback)
705 // LadyHavoc: pausedemo command
706 static void CL_PauseDemo_f(cmd_state_t *cmd)
708 cls.demopaused = !cls.demopaused;
710 Con_Print("Demo paused\n");
712 Con_Print("Demo unpaused\n");
715 void CL_Demo_Init(void)
717 Cmd_AddCommand(CF_CLIENT, "record", CL_Record_f, "record a demo");
718 Cmd_AddCommand(CF_CLIENT, "stop", CL_Stop_f, "stop recording or playing a demo");
719 Cmd_AddCommand(CF_CLIENT, "playdemo", CL_PlayDemo_f, "watch a demo file");
720 Cmd_AddCommand(CF_CLIENT, "timedemo", CL_TimeDemo_f, "play back a demo as fast as possible and save statistics to benchmark.log");
721 Cmd_AddCommand(CF_CLIENT, "startdemos", CL_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
722 Cmd_AddCommand(CF_CLIENT, "demos", CL_Demos_f, "restart looping demos defined by the last startdemos command");
723 Cmd_AddCommand(CF_CLIENT, "stopdemo", CL_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
724 // LadyHavoc: added pausedemo
725 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)");
726 Cvar_RegisterVariable (&cl_autodemo);
727 Cvar_RegisterVariable (&cl_autodemo_nameformat);
728 Cvar_RegisterVariable (&cl_autodemo_delete);