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 included (GNU.txt) 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.demofile = NULL; 52 cls.state = ca_disconnected; 53 cls.demoplayback = 0; 54 55 if (cls.timedemo) 56 CL_FinishTimeDemo (); 57 } 58 59 #define dem_cmd 0 60 #define dem_read 1 61 #define dem_set 2 62 63 /* 64 ==================== 65 CL_WriteDemoCmd 66 67 Writes the current user cmd 68 ==================== 69 */ 70 void CL_WriteDemoCmd (usercmd_t *pcmd) 71 { 72 int i; 73 float fl; 74 byte c; 75 usercmd_t cmd; 76 77 //Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, realtime); 78 79 fl = LittleFloat((float)realtime); 80 fwrite (&fl, sizeof(fl), 1, cls.demofile); 81 82 c = dem_cmd; 83 fwrite (&c, sizeof(c), 1, cls.demofile); 84 85 // correct for byte order, bytes don't matter 86 cmd = *pcmd; 87 88 for (i = 0; i < 3; i++) 89 cmd.angles[i] = LittleFloat(cmd.angles[i]); 90 cmd.forwardmove = LittleShort(cmd.forwardmove); 91 cmd.sidemove = LittleShort(cmd.sidemove); 92 cmd.upmove = LittleShort(cmd.upmove); 93 94 fwrite(&cmd, sizeof(cmd), 1, cls.demofile); 95 96 for (i=0 ; i<3 ; i++) 97 { 98 fl = LittleFloat (cl.viewangles[i]); 99 fwrite (&fl, 4, 1, cls.demofile); 100 } 101 102 fflush (cls.demofile); 103 } 104 105 /* 106 ==================== 107 CL_WriteDemoMessage 108 109 Dumps the current net message, prefixed by the length and view angles 110 ==================== 111 */ 112 void CL_WriteDemoMessage (sizebuf_t *msg) 113 { 114 int len; 115 float fl; 116 byte c; 117 118 //Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, realtime); 119 120 if (!cls.demorecording) 121 return; 122 123 fl = LittleFloat((float)realtime); 124 fwrite (&fl, sizeof(fl), 1, cls.demofile); 125 126 c = dem_read; 127 fwrite (&c, sizeof(c), 1, cls.demofile); 128 129 len = LittleLong (msg->cursize); 130 fwrite (&len, 4, 1, cls.demofile); 131 fwrite (msg->data, msg->cursize, 1, cls.demofile); 132 133 fflush (cls.demofile); 134 } 135 136 /* 137 ==================== 138 CL_GetDemoMessage 139 140 FIXME... 141 ==================== 142 */ 143 qboolean CL_GetDemoMessage (void) 144 { 145 int r, i, j; 146 float f; 147 float demotime; 148 byte c; 149 usercmd_t *pcmd; 150 151 // read the time from the packet 152 fread(&demotime, sizeof(demotime), 1, cls.demofile); 153 demotime = LittleFloat(demotime); 154 155 // decide if it is time to grab the next message 156 if (cls.timedemo) { 157 if (cls.td_lastframe < 0) 158 cls.td_lastframe = demotime; 159 else if (demotime > cls.td_lastframe) { 160 cls.td_lastframe = demotime; 161 // rewind back to time 162 fseek(cls.demofile, ftell(cls.demofile) - sizeof(demotime), 163 SEEK_SET); 164 return 0; // allready read this frame's message 165 } 166 if (!cls.td_starttime && cls.state == ca_active) { 167 cls.td_starttime = Sys_DoubleTime(); 168 cls.td_startframe = host_framecount; 169 } 170 realtime = demotime; // warp 171 } else if (!cl.paused && cls.state >= ca_onserver) { // allways grab until fully connected 172 if (realtime + 1.0 < demotime) { 173 // too far back 174 realtime = demotime - 1.0; 175 // rewind back to time 176 fseek(cls.demofile, ftell(cls.demofile) - sizeof(demotime), 177 SEEK_SET); 178 return 0; 179 } else if (realtime < demotime) { 180 // rewind back to time 181 fseek(cls.demofile, ftell(cls.demofile) - sizeof(demotime), 182 SEEK_SET); 183 return 0; // don't need another message yet 184 } 185 } else 186 realtime = demotime; // we're warping 187 188 if (cls.state < ca_demostart) 189 Host_Error ("CL_GetDemoMessage: cls.state != ca_active"); 190 191 // get the msg type 192 fread (&c, sizeof(c), 1, cls.demofile); 193 194 switch (c) { 195 case dem_cmd : 196 // user sent input 197 i = cls.netchan.outgoing_sequence & UPDATE_MASK; 198 pcmd = &cl.frames[i].cmd; 199 r = fread (pcmd, sizeof(*pcmd), 1, cls.demofile); 200 if (r != 1) 201 { 202 CL_StopPlayback (); 203 return 0; 204 } 205 // byte order stuff 206 for (j = 0; j < 3; j++) 207 pcmd->angles[j] = LittleFloat(pcmd->angles[j]); 208 pcmd->forwardmove = LittleShort(pcmd->forwardmove); 209 pcmd->sidemove = LittleShort(pcmd->sidemove); 210 pcmd->upmove = LittleShort(pcmd->upmove); 211 cl.frames[i].senttime = demotime; 212 cl.frames[i].receivedtime = -1; // we haven't gotten a reply yet 213 cls.netchan.outgoing_sequence++; 214 for (i=0 ; i<3 ; i++) 215 { 216 r = fread (&f, 4, 1, cls.demofile); 217 cl.viewangles[i] = LittleFloat (f); 218 } 219 break; 220 221 case dem_read: 222 // get the next message 223 fread (&net_message.cursize, 4, 1, cls.demofile); 224 net_message.cursize = LittleLong (net_message.cursize); 225 //Con_Printf("read: %ld bytes\n", net_message.cursize); 226 if (net_message.cursize > MAX_MSGLEN) 227 Sys_Error ("Demo message > MAX_MSGLEN"); 228 r = fread (net_message.data, net_message.cursize, 1, cls.demofile); 229 if (r != 1) 230 { 231 CL_StopPlayback (); 232 return 0; 233 } 234 break; 235 236 case dem_set : 237 fread (&i, 4, 1, cls.demofile); 238 cls.netchan.outgoing_sequence = LittleLong(i); 239 fread (&i, 4, 1, cls.demofile); 240 cls.netchan.incoming_sequence = LittleLong(i); 241 break; 242 243 default : 244 Con_Printf("Corrupted demo.\n"); 245 CL_StopPlayback (); 246 return 0; 247 } 248 249 return 1; 250 } 251 252 /* 253 ==================== 254 CL_GetMessage 255 256 Handles recording and playback of demos, on top of NET_ code 257 ==================== 258 */ 259 qboolean CL_GetMessage (void) 260 { 261 if (cls.demoplayback) 262 return CL_GetDemoMessage (); 263 264 if (!NET_GetPacket ()) 265 return false; 266 267 CL_WriteDemoMessage (&net_message); 268 269 return true; 270 } 271 272 273 /* 274 ==================== 275 CL_Stop_f 276 277 stop recording a demo 278 ==================== 279 */ 280 void CL_Stop_f (void) 281 { 282 if (!cls.demorecording) 283 { 284 Con_Printf ("Not recording a demo.\n"); 285 return; 286 } 287 288 // write a disconnect message to the demo file 289 SZ_Clear (&net_message); 290 MSG_WriteLong (&net_message, -1); // -1 sequence means out of band 291 MSG_WriteByte (&net_message, svc_disconnect); 292 MSG_WriteString (&net_message, "EndOfDemo"); 293 CL_WriteDemoMessage (&net_message); 294 295 // finish up 296 fclose (cls.demofile); 297 cls.demofile = NULL; 298 cls.demorecording = false; 299 Con_Printf ("Completed demo\n"); 300 } 301 302 303 /* 304 ==================== 305 CL_WriteDemoMessage 306 307 Dumps the current net message, prefixed by the length and view angles 308 ==================== 309 */ 310 void CL_WriteRecordDemoMessage (sizebuf_t *msg, int seq) 311 { 312 int len; 313 int i; 314 float fl; 315 byte c; 316 317 //Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, realtime); 318 319 if (!cls.demorecording) 320 return; 321 322 fl = LittleFloat((float)realtime); 323 fwrite (&fl, sizeof(fl), 1, cls.demofile); 324 325 c = dem_read; 326 fwrite (&c, sizeof(c), 1, cls.demofile); 327 328 len = LittleLong (msg->cursize + 8); 329 fwrite (&len, 4, 1, cls.demofile); 330 331 i = LittleLong(seq); 332 fwrite (&i, 4, 1, cls.demofile); 333 fwrite (&i, 4, 1, cls.demofile); 334 335 fwrite (msg->data, msg->cursize, 1, cls.demofile); 336 337 fflush (cls.demofile); 338 } 339 340 341 void CL_WriteSetDemoMessage (void) 342 { 343 int len; 344 float fl; 345 byte c; 346 347 //Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, realtime); 348 349 if (!cls.demorecording) 350 return; 351 352 fl = LittleFloat((float)realtime); 353 fwrite (&fl, sizeof(fl), 1, cls.demofile); 354 355 c = dem_set; 356 fwrite (&c, sizeof(c), 1, cls.demofile); 357 358 len = LittleLong(cls.netchan.outgoing_sequence); 359 fwrite (&len, 4, 1, cls.demofile); 360 len = LittleLong(cls.netchan.incoming_sequence); 361 fwrite (&len, 4, 1, cls.demofile); 362 363 fflush (cls.demofile); 364 } 365 366 367 368 369 /* 370 ==================== 371 CL_Record_f 372 373 record <demoname> <server> 374 ==================== 375 */ 376 void CL_Record_f (void) 377 { 378 int c; 379 char name[MAX_OSPATH]; 380 sizebuf_t buf; 381 char buf_data[MAX_MSGLEN]; 382 int n, i, j; 383 char *s; 384 entity_t *ent; 385 entity_state_t *es, blankes; 386 player_info_t *player; 387 extern char gamedirfile[]; 388 int seq = 1; 389 390 c = Cmd_Argc(); 391 if (c != 2) 392 { 393 Con_Printf ("record <demoname>\n"); 394 return; 395 } 396 397 if (cls.state != ca_active) { 398 Con_Printf ("You must be connected to record.\n"); 399 return; 400 } 401 402 if (cls.demorecording) 403 CL_Stop_f(); 404 405 sprintf (name, "%s/%s", com_gamedir, Cmd_Argv(1)); 406 407 // 408 // open the demo file 409 // 410 COM_DefaultExtension (name, ".qwd"); 411 412 cls.demofile = fopen (name, "wb"); 413 if (!cls.demofile) 414 { 415 Con_Printf ("ERROR: couldn't open.\n"); 416 return; 417 } 418 419 Con_Printf ("recording to %s.\n", name); 420 cls.demorecording = true; 421 422 /*-------------------------------------------------*/ 423 424 // serverdata 425 // send the info about the new client to all connected clients 426 memset(&buf, 0, sizeof(buf)); 427 buf.data = (byte*) buf_data; 428 buf.maxsize = sizeof(buf_data); 429 430 // send the serverdata 431 MSG_WriteByte (&buf, svc_serverdata); 432 MSG_WriteLong (&buf, PROTOCOL_VERSION); 433 MSG_WriteLong (&buf, cl.servercount); 434 MSG_WriteString (&buf, gamedirfile); 435 436 if (cl.spectator) 437 MSG_WriteByte (&buf, cl.playernum | 128); 438 else 439 MSG_WriteByte (&buf, cl.playernum); 440 441 // send full levelname 442 MSG_WriteString (&buf, cl.levelname); 443 444 // send the movevars 445 MSG_WriteFloat(&buf, movevars.gravity); 446 MSG_WriteFloat(&buf, movevars.stopspeed); 447 MSG_WriteFloat(&buf, movevars.maxspeed); 448 MSG_WriteFloat(&buf, movevars.spectatormaxspeed); 449 MSG_WriteFloat(&buf, movevars.accelerate); 450 MSG_WriteFloat(&buf, movevars.airaccelerate); 451 MSG_WriteFloat(&buf, movevars.wateraccelerate); 452 MSG_WriteFloat(&buf, movevars.friction); 453 MSG_WriteFloat(&buf, movevars.waterfriction); 454 MSG_WriteFloat(&buf, movevars.entgravity); 455 456 // send music 457 MSG_WriteByte (&buf, svc_cdtrack); 458 MSG_WriteByte (&buf, 0); // none in demos 459 460 // send server info string 461 MSG_WriteByte (&buf, svc_stufftext); 462 MSG_WriteString (&buf, va("fullserverinfo \"%s\"\n", cl.serverinfo) ); 463 464 // flush packet 465 CL_WriteRecordDemoMessage (&buf, seq++); 466 SZ_Clear (&buf); 467 468 // soundlist 469 MSG_WriteByte (&buf, svc_soundlist); 470 MSG_WriteByte (&buf, 0); 471 472 n = 0; 473 s = cl.sound_name[n+1]; 474 while (*s) { 475 MSG_WriteString (&buf, s); 476 if (buf.cursize > MAX_MSGLEN/2) { 477 MSG_WriteByte (&buf, 0); 478 MSG_WriteByte (&buf, n); 479 CL_WriteRecordDemoMessage (&buf, seq++); 480 SZ_Clear (&buf); 481 MSG_WriteByte (&buf, svc_soundlist); 482 MSG_WriteByte (&buf, n + 1); 483 } 484 n++; 485 s = cl.sound_name[n+1]; 486 } 487 if (buf.cursize) { 488 MSG_WriteByte (&buf, 0); 489 MSG_WriteByte (&buf, 0); 490 CL_WriteRecordDemoMessage (&buf, seq++); 491 SZ_Clear (&buf); 492 } 493 494 // modellist 495 MSG_WriteByte (&buf, svc_modellist); 496 MSG_WriteByte (&buf, 0); 497 498 n = 0; 499 s = cl.model_name[n+1]; 500 while (*s) { 501 MSG_WriteString (&buf, s); 502 if (buf.cursize > MAX_MSGLEN/2) { 503 MSG_WriteByte (&buf, 0); 504 MSG_WriteByte (&buf, n); 505 CL_WriteRecordDemoMessage (&buf, seq++); 506 SZ_Clear (&buf); 507 MSG_WriteByte (&buf, svc_modellist); 508 MSG_WriteByte (&buf, n + 1); 509 } 510 n++; 511 s = cl.model_name[n+1]; 512 } 513 if (buf.cursize) { 514 MSG_WriteByte (&buf, 0); 515 MSG_WriteByte (&buf, 0); 516 CL_WriteRecordDemoMessage (&buf, seq++); 517 SZ_Clear (&buf); 518 } 519 520 // spawnstatic 521 522 for (i = 0; i < cl.num_statics; i++) { 523 ent = cl_static_entities + i; 524 525 MSG_WriteByte (&buf, svc_spawnstatic); 526 527 for (j = 1; j < MAX_MODELS; j++) 528 if (ent->model == cl.model_precache[j]) 529 break; 530 if (j == MAX_MODELS) 531 MSG_WriteByte (&buf, 0); 532 else 533 MSG_WriteByte (&buf, j); 534 535 MSG_WriteByte (&buf, ent->frame); 536 MSG_WriteByte (&buf, 0); 537 MSG_WriteByte (&buf, ent->skinnum); 538 for (j=0 ; j<3 ; j++) 539 { 540 MSG_WriteCoord (&buf, ent->origin[j]); 541 MSG_WriteAngle (&buf, ent->angles[j]); 542 } 543 544 if (buf.cursize > MAX_MSGLEN/2) { 545 CL_WriteRecordDemoMessage (&buf, seq++); 546 SZ_Clear (&buf); 547 } 548 } 549 550 // spawnstaticsound 551 // static sounds are skipped in demos, life is hard 552 553 // baselines 554 555 memset(&blankes, 0, sizeof(blankes)); 556 for (i = 0; i < MAX_EDICTS; i++) { 557 es = cl_baselines + i; 558 559 if (memcmp(es, &blankes, sizeof(blankes))) { 560 MSG_WriteByte (&buf,svc_spawnbaseline); 561 MSG_WriteShort (&buf, i); 562 563 MSG_WriteByte (&buf, es->modelindex); 564 MSG_WriteByte (&buf, es->frame); 565 MSG_WriteByte (&buf, es->colormap); 566 MSG_WriteByte (&buf, es->skinnum); 567 for (j=0 ; j<3 ; j++) 568 { 569 MSG_WriteCoord(&buf, es->origin[j]); 570 MSG_WriteAngle(&buf, es->angles[j]); 571 } 572 573 if (buf.cursize > MAX_MSGLEN/2) { 574 CL_WriteRecordDemoMessage (&buf, seq++); 575 SZ_Clear (&buf); 576 } 577 } 578 } 579 580 MSG_WriteByte (&buf, svc_stufftext); 581 MSG_WriteString (&buf, va("cmd spawn %i 0\n", cl.servercount) ); 582 583 if (buf.cursize) { 584 CL_WriteRecordDemoMessage (&buf, seq++); 585 SZ_Clear (&buf); 586 } 587 588 // send current status of all other players 589 590 for (i = 0; i < MAX_CLIENTS; i++) { 591 player = cl.players + i; 592 593 MSG_WriteByte (&buf, svc_updatefrags); 594 MSG_WriteByte (&buf, i); 595 MSG_WriteShort (&buf, player->frags); 596 597 MSG_WriteByte (&buf, svc_updateping); 598 MSG_WriteByte (&buf, i); 599 MSG_WriteShort (&buf, player->ping); 600 601 MSG_WriteByte (&buf, svc_updatepl); 602 MSG_WriteByte (&buf, i); 603 MSG_WriteByte (&buf, player->pl); 604 605 MSG_WriteByte (&buf, svc_updateentertime); 606 MSG_WriteByte (&buf, i); 607 MSG_WriteFloat (&buf, player->entertime); 608 609 MSG_WriteByte (&buf, svc_updateuserinfo); 610 MSG_WriteByte (&buf, i); 611 MSG_WriteLong (&buf, player->userid); 612 MSG_WriteString (&buf, player->userinfo); 613 614 if (buf.cursize > MAX_MSGLEN/2) { 615 CL_WriteRecordDemoMessage (&buf, seq++); 616 SZ_Clear (&buf); 617 } 618 } 619 620 // send all current light styles 621 for (i=0 ; i<MAX_LIGHTSTYLES ; i++) 622 { 623 MSG_WriteByte (&buf, svc_lightstyle); 624 MSG_WriteByte (&buf, (char)i); 625 MSG_WriteString (&buf, cl_lightstyle[i].map); 626 } 627 628 for (i = 0; i < MAX_CL_STATS; i++) { 629 MSG_WriteByte (&buf, svc_updatestatlong); 630 MSG_WriteByte (&buf, i); 631 MSG_WriteLong (&buf, cl.stats[i]); 632 if (buf.cursize > MAX_MSGLEN/2) { 633 CL_WriteRecordDemoMessage (&buf, seq++); 634 SZ_Clear (&buf); 635 } 636 } 637 638 #if 0 639 MSG_WriteByte (&buf, svc_updatestatlong); 640 MSG_WriteByte (&buf, STAT_TOTALMONSTERS); 641 MSG_WriteLong (&buf, cl.stats[STAT_TOTALMONSTERS]); 642 643 MSG_WriteByte (&buf, svc_updatestatlong); 644 MSG_WriteByte (&buf, STAT_SECRETS); 645 MSG_WriteLong (&buf, cl.stats[STAT_SECRETS]); 646 647 MSG_WriteByte (&buf, svc_updatestatlong); 648 MSG_WriteByte (&buf, STAT_MONSTERS); 649 MSG_WriteLong (&buf, cl.stats[STAT_MONSTERS]); 650 #endif 651 652 // get the client to check and download skins 653 // when that is completed, a begin command will be issued 654 MSG_WriteByte (&buf, svc_stufftext); 655 MSG_WriteString (&buf, va("skins\n") ); 656 657 CL_WriteRecordDemoMessage (&buf, seq++); 658 659 CL_WriteSetDemoMessage(); 660 661 // done 662 } 663 664 /* 665 ==================== 666 CL_ReRecord_f 667 668 record <demoname> 669 ==================== 670 */ 671 void CL_ReRecord_f (void) 672 { 673 int c; 674 char name[MAX_OSPATH]; 675 676 c = Cmd_Argc(); 677 if (c != 2) 678 { 679 Con_Printf ("rerecord <demoname>\n"); 680 return; 681 } 682 683 if (!*cls.servername) { 684 Con_Printf("No server to reconnect to...\n"); 685 return; 686 } 687 688 if (cls.demorecording) 689 CL_Stop_f(); 690 691 sprintf (name, "%s/%s", com_gamedir, Cmd_Argv(1)); 692 693 // 694 // open the demo file 695 // 696 COM_DefaultExtension (name, ".qwd"); 697 698 cls.demofile = fopen (name, "wb"); 699 if (!cls.demofile) 700 { 701 Con_Printf ("ERROR: couldn't open.\n"); 702 return; 703 } 704 705 Con_Printf ("recording to %s.\n", name); 706 cls.demorecording = true; 707 708 CL_Disconnect(); 709 CL_BeginServerConnect(); 710 } 711 712 713 /* 714 ==================== 715 CL_PlayDemo_f 716 717 play [demoname] 718 ==================== 719 */ 720 void CL_PlayDemo_f (void) 721 { 722 char name[256]; 723 724 if (Cmd_Argc() != 2) 725 { 726 Con_Printf ("play <demoname> : plays a demo\n"); 727 return; 728 } 729 730 // 731 // disconnect from server 732 // 733 CL_Disconnect (); 734 735 // 736 // open the demo file 737 // 738 strcpy (name, Cmd_Argv(1)); 739 COM_DefaultExtension (name, ".qwd"); 740 741 Con_Printf ("Playing demo from %s.\n", name); 742 COM_FOpenFile (name, &cls.demofile); 743 if (!cls.demofile) 744 { 745 Con_Printf ("ERROR: couldn't open.\n"); 746 cls.demonum = -1; // stop demo loop 747 return; 748 } 749 750 cls.demoplayback = true; 751 cls.state = ca_demostart; 752 Netchan_Setup (&cls.netchan, net_from, 0); 753 realtime = 0; 754 } 755 756 /* 757 ==================== 758 CL_FinishTimeDemo 759 760 ==================== 761 */ 762 void CL_FinishTimeDemo (void) 763 { 764 int frames; 765 float time; 766 767 cls.timedemo = false; 768 769 // the first frame didn't count 770 frames = (host_framecount - cls.td_startframe) - 1; 771 time = Sys_DoubleTime() - cls.td_starttime; 772 if (!time) 773 time = 1; 774 Con_Printf ("%i frames %5.1f seconds %5.1f fps\n", frames, time, frames/time); 775 } 776 777 /* 778 ==================== 779 CL_TimeDemo_f 780 781 timedemo [demoname] 782 ==================== 783 */ 784 void CL_TimeDemo_f (void) 785 { 786 if (Cmd_Argc() != 2) 787 { 788 Con_Printf ("timedemo <demoname> : gets demo speeds\n"); 789 return; 790 } 791 792 CL_PlayDemo_f (); 793 794 if (cls.state != ca_demostart) 795 return; 796 797 // cls.td_starttime will be grabbed at the second frame of the demo, so 798 // all the loading time doesn't get counted 799 800 cls.timedemo = true; 801 cls.td_starttime = 0; 802 cls.td_startframe = host_framecount; 803 cls.td_lastframe = -1; // get a new message this frame 804 } 805 806