1 /* 2 Copyright (C) 1996-1997 Id Software, Inc. 3 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. 8 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. 12 13 See the GNU General Public License for more details. 14 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. 18 19 */ 20 21 #include "quakedef.h" 22 23 void CL_FinishTimeDemo (void); 24 25 /* 26 ============================================================================== 27 28 DEMO CODE 29 30 When a demo is playing back, all NET_SendMessages are skipped, and 31 NET_GetMessages are read from the demo file. 32 33 Whenever cl.time gets past the last received message, another message is 34 read from the demo file. 35 ============================================================================== 36 */ 37 38 /* 39 ============== 40 CL_StopPlayback 41 42 Called when a demo file runs out, or the user starts a game 43 ============== 44 */ 45 void CL_StopPlayback (void) 46 { 47 if (!cls.demoplayback) 48 return; 49 50 fclose (cls.demofile); 51 cls.demoplayback = false; 52 cls.demofile = NULL; 53 cls.state = ca_disconnected; 54 55 if (cls.timedemo) 56 CL_FinishTimeDemo (); 57 } 58 59 /* 60 ==================== 61 CL_WriteDemoMessage 62 63 Dumps the current net message, prefixed by the length and view angles 64 ==================== 65 */ 66 void CL_WriteDemoMessage (void) 67 { 68 int len; 69 int i; 70 float f; 71 72 len = LittleLong (net_message.cursize); 73 fwrite (&len, 4, 1, cls.demofile); 74 for (i=0 ; i<3 ; i++) 75 { 76 f = LittleFloat (cl.viewangles[i]); 77 fwrite (&f, 4, 1, cls.demofile); 78 } 79 fwrite (net_message.data, net_message.cursize, 1, cls.demofile); 80 fflush (cls.demofile); 81 } 82 83 /* 84 ==================== 85 CL_GetMessage 86 87 Handles recording and playback of demos, on top of NET_ code 88 ==================== 89 */ 90 int CL_GetMessage (void) 91 { 92 int r, i; 93 float f; 94 95 if (cls.demoplayback) 96 { 97 // decide if it is time to grab the next message 98 if (cls.signon == SIGNONS) // allways grab until fully connected 99 { 100 if (cls.timedemo) 101 { 102 if (host_framecount == cls.td_lastframe) 103 return 0; // allready read this frame's message 104 cls.td_lastframe = host_framecount; 105 // if this is the second frame, grab the real td_starttime 106 // so the bogus time on the first frame doesn't count 107 if (host_framecount == cls.td_startframe + 1) 108 cls.td_starttime = realtime; 109 } 110 else if ( /* cl.time > 0 && */ cl.time <= cl.mtime[0]) 111 { 112 return 0; // don't need another message yet 113 } 114 } 115 116 // get the next message 117 fread (&net_message.cursize, 4, 1, cls.demofile); 118 VectorCopy (cl.mviewangles[0], cl.mviewangles[1]); 119 for (i=0 ; i<3 ; i++) 120 { 121 r = fread (&f, 4, 1, cls.demofile); 122 cl.mviewangles[0][i] = LittleFloat (f); 123 } 124 125 net_message.cursize = LittleLong (net_message.cursize); 126 if (net_message.cursize > MAX_MSGLEN) 127 Sys_Error ("Demo message > MAX_MSGLEN"); 128 r = fread (net_message.data, net_message.cursize, 1, cls.demofile); 129 if (r != 1) 130 { 131 CL_StopPlayback (); 132 return 0; 133 } 134 135 return 1; 136 } 137 138 while (1) 139 { 140 r = NET_GetMessage (cls.netcon); 141 142 if (r != 1 && r != 2) 143 return r; 144 145 // discard nop keepalive message 146 if (net_message.cursize == 1 && net_message.data[0] == svc_nop) 147 Con_Printf ("<-- server to client keepalive\n"); 148 else 149 break; 150 } 151 152 if (cls.demorecording) 153 CL_WriteDemoMessage (); 154 155 return r; 156 } 157 158 159 /* 160 ==================== 161 CL_Stop_f 162 163 stop recording a demo 164 ==================== 165 */ 166 void CL_Stop_f (void) 167 { 168 if (cmd_source != src_command) 169 return; 170 171 if (!cls.demorecording) 172 { 173 Con_Printf ("Not recording a demo.\n"); 174 return; 175 } 176 177 // write a disconnect message to the demo file 178 SZ_Clear (&net_message); 179 MSG_WriteByte (&net_message, svc_disconnect); 180 CL_WriteDemoMessage (); 181 182 // finish up 183 fclose (cls.demofile); 184 cls.demofile = NULL; 185 cls.demorecording = false; 186 Con_Printf ("Completed demo\n"); 187 } 188 189 /* 190 ==================== 191 CL_Record_f 192 193 record <demoname> <map> [cd track] 194 ==================== 195 */ 196 void CL_Record_f (void) 197 { 198 int c; 199 char name[MAX_OSPATH]; 200 int track; 201 202 if (cmd_source != src_command) 203 return; 204 205 c = Cmd_Argc(); 206 if (c != 2 && c != 3 && c != 4) 207 { 208 Con_Printf ("record <demoname> [<map> [cd track]]\n"); 209 return; 210 } 211 212 if (strstr(Cmd_Argv(1), "..")) 213 { 214 Con_Printf ("Relative pathnames are not allowed.\n"); 215 return; 216 } 217 218 if (c == 2 && cls.state == ca_connected) 219 { 220 Con_Printf("Can not record - already connected to server\nClient demo recording must be started before connecting\n"); 221 return; 222 } 223 224 // write the forced cd track number, or -1 225 if (c == 4) 226 { 227 track = atoi(Cmd_Argv(3)); 228 Con_Printf ("Forcing CD track to %i\n", cls.forcetrack); 229 } 230 else 231 track = -1; 232 233 sprintf (name, "%s/%s", com_gamedir, Cmd_Argv(1)); 234 235 // 236 // start the map up 237 // 238 if (c > 2) 239 Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command); 240 241 // 242 // open the demo file 243 // 244 COM_DefaultExtension (name, ".dem"); 245 246 Con_Printf ("recording to %s.\n", name); 247 cls.demofile = fopen (name, "wb"); 248 if (!cls.demofile) 249 { 250 Con_Printf ("ERROR: couldn't open.\n"); 251 return; 252 } 253 254 cls.forcetrack = track; 255 fprintf (cls.demofile, "%i\n", cls.forcetrack); 256 257 cls.demorecording = true; 258 } 259 260 261 /* 262 ==================== 263 CL_PlayDemo_f 264 265 play [demoname] 266 ==================== 267 */ 268 void CL_PlayDemo_f (void) 269 { 270 char name[256]; 271 int c; 272 qboolean neg = false; 273 274 if (cmd_source != src_command) 275 return; 276 277 if (Cmd_Argc() > 2) 278 { 279 Con_Printf ("play <demoname> : plays a demo\n"); 280 return; 281 } 282 283 // 284 // disconnect from server 285 // 286 CL_Disconnect (); 287 288 // 289 // open the demo file 290 // 291 const char* cmdName = "demo1"; 292 if (Cmd_Argc() == 2) { 293 cmdName = Cmd_Argv(1); 294 } 295 strcpy (name, cmdName); 296 COM_DefaultExtension (name, ".dem"); 297 298 Con_Printf ("Playing demo from %s.\n", name); 299 COM_FOpenFile (name, &cls.demofile); 300 if (!cls.demofile) 301 { 302 Con_Printf ("ERROR: couldn't open.\n"); 303 cls.demonum = -1; // stop demo loop 304 return; 305 } 306 307 cls.demoplayback = true; 308 cls.state = ca_connected; 309 cls.forcetrack = 0; 310 311 while ((c = getc(cls.demofile)) != '\n') 312 if (c == '-') 313 neg = true; 314 else 315 cls.forcetrack = cls.forcetrack * 10 + (c - '0'); 316 317 if (neg) 318 cls.forcetrack = -cls.forcetrack; 319 // ZOID, fscanf is evil 320 // fscanf (cls.demofile, "%i\n", &cls.forcetrack); 321 } 322 323 // The timedemo numbers are very important to testing, so log them even if normal console printing is disabled. 324 325 #define LOGANDPRINT(ARGS) Con_Printf ARGS ; PMPLOG(ARGS) 326 327 /* 328 ==================== 329 CL_FinishTimeDemo 330 331 ==================== 332 */ 333 void CL_FinishTimeDemo (void) 334 { 335 int frames; 336 float time; 337 338 cls.timedemo = false; 339 340 // the first frame didn't count 341 frames = (host_framecount - cls.td_startframe) - 1; 342 time = realtime - cls.td_starttime; 343 if (!time) 344 time = 1; 345 LOGANDPRINT(("%i frames %5.3f seconds %5.3f fps\n", frames, time, frames/time)); 346 if (frames > 0) 347 { 348 LOGANDPRINT(("Fastest: %5.1f ms on frame %d\n", fastestFrame.time * 1000.0, fastestFrame.frame)); 349 LOGANDPRINT(("Average: %5.1f ms\n", (time / frames) * 1000.0)); 350 LOGANDPRINT(("Slowest: %5.1f ms on frame %d\n", slowestFrame.time * 1000.0, slowestFrame.frame)); 351 } 352 } 353 354 /* 355 ==================== 356 CL_TimeDemo_f 357 358 timedemo [demoname] 359 ==================== 360 */ 361 void CL_TimeDemo_f (void) 362 { 363 if (cmd_source != src_command) 364 return; 365 366 if (Cmd_Argc() > 2) 367 { 368 Con_Printf ("timedemo <demoname> : gets demo speeds\n"); 369 return; 370 } 371 372 CL_PlayDemo_f (); 373 374 // cls.td_starttime will be grabbed at the second frame of the demo, so 375 // all the loading time doesn't get counted 376 377 cls.timedemo = true; 378 cls.td_startframe = host_framecount; 379 cls.td_lastframe = -1; // get a new message this frame 380 381 InitFrameTimes(); 382 } 383 384