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;
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_client)->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;
412 void CL_PlayDemo_f(cmd_state_t *cmd)
414 char name[MAX_QPATH];
419 if (Cmd_Argc(cmd) != 2)
421 Con_Print("playdemo <demoname> : plays a demo\n");
425 // open the demo file
426 strlcpy (name, Cmd_Argv(cmd, 1), sizeof (name));
427 FS_DefaultExtension (name, ".dem", sizeof (name));
428 f = FS_OpenVirtualFile(name, false);
431 Con_Printf(CON_ERROR "ERROR: couldn't open %s.\n", name);
432 cls.demonum = -1; // stop demo loop
436 cls.demostarting = true;
438 // disconnect from server
439 if(cls.state == ca_connected)
444 // update networking ports (this is mainly just needed at startup)
445 NetConn_UpdateSockets();
447 cls.protocol = PROTOCOL_QUAKE;
449 Con_Printf("Playing demo %s.\n", name);
451 strlcpy(cls.demoname, name, sizeof(cls.demoname));
453 cls.demoplayback = true;
454 cls.state = ca_connected;
457 while ((c = FS_Getc (cls.demofile)) != '\n')
461 cls.forcetrack = cls.forcetrack * 10 + (c - '0');
464 cls.forcetrack = -cls.forcetrack;
466 cls.demostarting = false;
472 double time, totalfpsavg;
473 double fpsmin, fpsavg, fpsmax;
476 static size_t doublecmp_offset;
477 static int doublecmp_withoffset(const void *a_, const void *b_)
479 const double *a = (const double *) ((const char *) a_ + doublecmp_offset);
480 const double *b = (const double *) ((const char *) b_ + doublecmp_offset);
494 static void CL_FinishTimeDemo (void)
498 double time, totalfpsavg;
499 double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
500 static int benchmark_runs = 0;
503 cls.timedemo = host.restless = false;
505 frames = cls.td_frames;
506 time = host.realtime - cls.td_starttime;
507 totalfpsavg = time > 0 ? frames / time : 0;
508 fpsmin = cls.td_onesecondminfps;
509 fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
510 fpsmax = cls.td_onesecondmaxfps;
511 // LadyHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
512 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);
513 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);
514 if (Sys_CheckParm("-benchmark"))
517 i = Sys_CheckParm("-benchmarkruns");
518 if(i && i + 1 < sys.argc)
520 static benchmarkhistory_t *history = NULL;
522 history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(sys.argv[i + 1]));
524 history[benchmark_runs - 1].frames = frames;
525 history[benchmark_runs - 1].time = time;
526 history[benchmark_runs - 1].totalfpsavg = totalfpsavg;
527 history[benchmark_runs - 1].fpsmin = fpsmin;
528 history[benchmark_runs - 1].fpsavg = fpsavg;
529 history[benchmark_runs - 1].fpsmax = fpsmax;
531 if(atoi(sys.argv[i + 1]) > benchmark_runs)
533 // restart the benchmark
534 Cbuf_AddText(cmd_client, va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname));
535 // cannot execute here
540 int first = Sys_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0;
541 if(benchmark_runs > first)
544 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f
547 for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f
550 doublecmp_offset = (char *)&history->f - (char *)history; \
551 qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \
552 if((first + benchmark_runs) & 1) \
553 f = history[(first + benchmark_runs - 1) / 2].f; \
555 f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2
563 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);
571 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);
579 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);
583 host.state = host_shutdown;
587 host.state = host_shutdown;
598 void CL_TimeDemo_f(cmd_state_t *cmd)
600 if (Cmd_Argc(cmd) != 2)
602 Con_Print("timedemo <demoname> : gets demo speeds\n");
606 srand(0); // predictable random sequence for benchmarking
610 // cls.td_starttime will be grabbed at the second frame of the demo, so
611 // all the loading time doesn't get counted
613 // instantly hide console and deactivate it
615 key_consoleactive = 0;
618 cls.timedemo = host.restless = true;
619 cls.td_frames = -2; // skip the first frame
620 cls.demonum = -1; // stop demo loop
624 ===============================================================================
628 ===============================================================================
637 static void CL_Startdemos_f(cmd_state_t *cmd)
641 if (cls.state == ca_dedicated || Sys_CheckParm("-listen") || Sys_CheckParm("-benchmark") || Sys_CheckParm("-demo") || Sys_CheckParm("-capturedemo"))
644 c = Cmd_Argc(cmd) - 1;
647 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
650 Con_DPrintf("%i demo(s) in loop\n", c);
652 for (i=1 ; i<c+1 ; i++)
653 strlcpy (cls.demos[i-1], Cmd_Argv(cmd, i), sizeof (cls.demos[i-1]));
655 // LadyHavoc: clear the remaining slots
656 for (;i <= MAX_DEMOS;i++)
657 cls.demos[i-1][0] = 0;
659 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
673 Return to looping demos
676 static void CL_Demos_f(cmd_state_t *cmd)
678 if (cls.state == ca_dedicated)
680 if (cls.demonum == -1)
682 CL_Disconnect_f (cmd);
690 Return to looping demos
693 static void CL_Stopdemo_f(cmd_state_t *cmd)
695 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);