File: client\cl_parse.c

    1 /*
    2 Copyright (C) 1997-2001 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 // cl_parse.c  -- parse a message received from the server
   21 
   22 #include "client.h"
   23 
   24 char *svc_strings[256] =
   25 {
   26         "svc_bad",
   27 
   28         "svc_muzzleflash",
   29         "svc_muzzlflash2",
   30         "svc_temp_entity",
   31         "svc_layout",
   32         "svc_inventory",
   33 
   34         "svc_nop",
   35         "svc_disconnect",
   36         "svc_reconnect",
   37         "svc_sound",
   38         "svc_print",
   39         "svc_stufftext",
   40         "svc_serverdata",
   41         "svc_configstring",
   42         "svc_spawnbaseline",    
   43         "svc_centerprint",
   44         "svc_download",
   45         "svc_playerinfo",
   46         "svc_packetentities",
   47         "svc_deltapacketentities",
   48         "svc_frame"
   49 };
   50 
   51 //=============================================================================
   52 
   53 void CL_DownloadFileName(char *dest, int destlen, char *fn)
   54 {
   55         if (strncmp(fn, "players", 7) == 0)
   56                 Com_sprintf (dest, destlen, "%s/%s", BASEDIRNAME, fn);
   57         else
   58                 Com_sprintf (dest, destlen, "%s/%s", FS_Gamedir(), fn);
   59 }
   60 
   61 /*
   62 ===============
   63 CL_CheckOrDownloadFile
   64 
   65 Returns true if the file exists, otherwise it attempts
   66 to start a download from the server.
   67 ===============
   68 */
   69 qboolean        CL_CheckOrDownloadFile (char *filename)
   70 {
   71         FILE *fp;
   72         char    name[MAX_OSPATH];
   73 
   74         if (strstr (filename, ".."))
   75         {
   76                 Com_Printf ("Refusing to download a path with ..\n");
   77                 return true;
   78         }
   79 
   80         if (FS_LoadFile (filename, NULL) != -1)
   81         {       // it exists, no need to download
   82                 return true;
   83         }
   84 
   85         strcpy (cls.downloadname, filename);
   86 
   87         // download to a temp name, and only rename
   88         // to the real name when done, so if interrupted
   89         // a runt file wont be left
   90         COM_StripExtension (cls.downloadname, cls.downloadtempname);
   91         strcat (cls.downloadtempname, ".tmp");
   92 
   93 //ZOID
   94         // check to see if we already have a tmp for this file, if so, try to resume
   95         // open the file if not opened yet
   96         CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);
   97 
   98 //      FS_CreatePath (name);
   99 
  100         fp = fopen (name, "r+b");
  101         if (fp) { // it exists
  102                 int len;
  103                 fseek(fp, 0, SEEK_END);
  104                 len = ftell(fp);
  105 
  106                 cls.download = fp;
  107 
  108                 // give the server an offset to start the download
  109                 Com_Printf ("Resuming %s\n", cls.downloadname);
  110                 MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
  111                 MSG_WriteString (&cls.netchan.message,
  112                         va("download %s %i", cls.downloadname, len));
  113         } else {
  114                 Com_Printf ("Downloading %s\n", cls.downloadname);
  115                 MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
  116                 MSG_WriteString (&cls.netchan.message,
  117                         va("download %s", cls.downloadname));
  118         }
  119 
  120         cls.downloadnumber++;
  121 
  122         return false;
  123 }
  124 
  125 /*
  126 ===============
  127 CL_Download_f
  128 
  129 Request a download from the server
  130 ===============
  131 */
  132 void    CL_Download_f (void)
  133 {
  134         char filename[MAX_OSPATH];
  135 
  136         if (Cmd_Argc() != 2) {
  137                 Com_Printf("Usage: download <filename>\n");
  138                 return;
  139         }
  140 
  141         Com_sprintf(filename, sizeof(filename), "%s", Cmd_Argv(1));
  142 
  143         if (strstr (filename, ".."))
  144         {
  145                 Com_Printf ("Refusing to download a path with ..\n");
  146                 return;
  147         }
  148 
  149         if (FS_LoadFile (filename, NULL) != -1)
  150         {       // it exists, no need to download
  151                 Com_Printf("File already exists.\n");
  152                 return;
  153         }
  154 
  155         strcpy (cls.downloadname, filename);
  156         Com_Printf ("Downloading %s\n", cls.downloadname);
  157 
  158         // download to a temp name, and only rename
  159         // to the real name when done, so if interrupted
  160         // a runt file wont be left
  161         COM_StripExtension (cls.downloadname, cls.downloadtempname);
  162         strcat (cls.downloadtempname, ".tmp");
  163 
  164         MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
  165         MSG_WriteString (&cls.netchan.message,
  166                 va("download %s", cls.downloadname));
  167 
  168         cls.downloadnumber++;
  169 }
  170 
  171 /*
  172 ======================
  173 CL_RegisterSounds
  174 ======================
  175 */
  176 void CL_RegisterSounds (void)
  177 {
  178         int             i;
  179 
  180         S_BeginRegistration ();
  181         CL_RegisterTEntSounds ();
  182         for (i=1 ; i<MAX_SOUNDS ; i++)
  183         {
  184                 if (!cl.configstrings[CS_SOUNDS+i][0])
  185                         break;
  186                 cl.sound_precache[i] = S_RegisterSound (cl.configstrings[CS_SOUNDS+i]);
  187                 Sys_SendKeyEvents ();   // pump message loop
  188         }
  189         S_EndRegistration ();
  190 }
  191 
  192 
  193 /*
  194 =====================
  195 CL_ParseDownload
  196 
  197 A download message has been received from the server
  198 =====================
  199 */
  200 void CL_ParseDownload (void)
  201 {
  202         int             size, percent;
  203         char    name[MAX_OSPATH];
  204         int             r;
  205 
  206         // read the data
  207         size = MSG_ReadShort (&net_message);
  208         percent = MSG_ReadByte (&net_message);
  209         if (size == -1)
  210         {
  211                 Com_Printf ("Server does not have this file.\n");
  212                 if (cls.download)
  213                 {
  214                         // if here, we tried to resume a file but the server said no
  215                         fclose (cls.download);
  216                         cls.download = NULL;
  217                 }
  218                 CL_RequestNextDownload ();
  219                 return;
  220         }
  221 
  222         // open the file if not opened yet
  223         if (!cls.download)
  224         {
  225                 CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);
  226 
  227                 FS_CreatePath (name);
  228 
  229                 cls.download = fopen (name, "wb");
  230                 if (!cls.download)
  231                 {
  232                         net_message.readcount += size;
  233                         Com_Printf ("Failed to open %s\n", cls.downloadtempname);
  234                         CL_RequestNextDownload ();
  235                         return;
  236                 }
  237         }
  238 
  239         fwrite (net_message.data + net_message.readcount, 1, size, cls.download);
  240         net_message.readcount += size;
  241 
  242         if (percent != 100)
  243         {
  244                 // request next block
  245 // change display routines by zoid
  246 #if 0
247 Com_Printf ("."); 248 if (10*(percent/10) != cls.downloadpercent) 249 { 250 cls.downloadpercent = 10*(percent/10); 251 Com_Printf ("%i%%", cls.downloadpercent); 252 }
253 #endif 254 cls.downloadpercent = percent; 255 256 MSG_WriteByte (&cls.netchan.message, clc_stringcmd); 257 SZ_Print (&cls.netchan.message, "nextdl"); 258 } 259 else 260 { 261 char oldn[MAX_OSPATH]; 262 char newn[MAX_OSPATH]; 263 264 // Com_Printf ("100%%\n"); 265 266 fclose (cls.download); 267 268 // rename the temp file to it's final name 269 CL_DownloadFileName(oldn, sizeof(oldn), cls.downloadtempname); 270 CL_DownloadFileName(newn, sizeof(newn), cls.downloadname); 271 r = rename (oldn, newn); 272 if (r) 273 Com_Printf ("failed to rename.\n"); 274 275 cls.download = NULL; 276 cls.downloadpercent = 0; 277 278 // get another file if needed 279 280 CL_RequestNextDownload (); 281 } 282 } 283 284 285 /* 286 ===================================================================== 287 288 SERVER CONNECTING MESSAGES 289 290 ===================================================================== 291 */ 292 293 /* 294 ================== 295 CL_ParseServerData 296 ================== 297 */ 298 void CL_ParseServerData (void) 299 { 300 extern cvar_t *fs_gamedirvar; 301 char *str; 302 int i; 303 304 Com_DPrintf ("Serverdata packet received.\n"); 305 // 306 // wipe the client_state_t struct 307 // 308 CL_ClearState (); 309 cls.state = ca_connected; 310 311 // parse protocol version number 312 i = MSG_ReadLong (&net_message); 313 cls.serverProtocol = i; 314 315 // BIG HACK to let demos from release work with the 3.0x patch!!! 316 if (Com_ServerState() && PROTOCOL_VERSION == 34) 317 { 318 } 319 else if (i != PROTOCOL_VERSION) 320 Com_Error (ERR_DROP,"Server returned version %i, not %i", i, PROTOCOL_VERSION); 321 322 cl.servercount = MSG_ReadLong (&net_message); 323 cl.attractloop = MSG_ReadByte (&net_message); 324 325 // game directory 326 str = MSG_ReadString (&net_message); 327 strncpy (cl.gamedir, str, sizeof(cl.gamedir)-1); 328 329 // set gamedir 330 if ((*str && (!fs_gamedirvar->string || !*fs_gamedirvar->string || strcmp(fs_gamedirvar->string, str))) || (!*str && (fs_gamedirvar->string || *fs_gamedirvar->string))) 331 Cvar_Set("game", str); 332 333 // parse player entity number 334 cl.playernum = MSG_ReadShort (&net_message); 335 336 // get the full level name 337 str = MSG_ReadString (&net_message); 338 339 if (cl.playernum == -1) 340 { // playing a cinematic or showing a pic, not a level 341 SCR_PlayCinematic (str); 342 } 343 else 344 { 345 // seperate the printfs so the server message can have a color 346 Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n"); 347 Com_Printf ("%c%s\n", 2, str); 348 349 // need to prep refresh at next oportunity 350 cl.refresh_prepped = false; 351 } 352 } 353 354 /* 355 ================== 356 CL_ParseBaseline 357 ================== 358 */ 359 void CL_ParseBaseline (void) 360 { 361 entity_state_t *es; 362 int bits; 363 int newnum; 364 entity_state_t nullstate; 365 366 memset (&nullstate, 0, sizeof(nullstate)); 367 368 newnum = CL_ParseEntityBits (&bits); 369 es = &cl_entities[newnum].baseline; 370 CL_ParseDelta (&nullstate, es, newnum, bits); 371 } 372 373 374 /* 375 ================ 376 CL_LoadClientinfo 377 378 ================ 379 */ 380 void CL_LoadClientinfo (clientinfo_t *ci, char *s) 381 { 382 int i; 383 char *t; 384 char model_name[MAX_QPATH]; 385 char skin_name[MAX_QPATH]; 386 char model_filename[MAX_QPATH]; 387 char skin_filename[MAX_QPATH]; 388 char weapon_filename[MAX_QPATH]; 389 390 strncpy(ci->cinfo, s, sizeof(ci->cinfo)); 391 ci->cinfo[sizeof(ci->cinfo)-1] = 0; 392 393 // isolate the player's name 394 strncpy(ci->name, s, sizeof(ci->name)); 395 ci->name[sizeof(ci->name)-1] = 0; 396 t = strstr (s, "\\"); 397 if (t) 398 { 399 ci->name[t-s] = 0; 400 s = t+1; 401 } 402 403 if (cl_noskins->value || *s == 0) 404 { 405 Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2"); 406 Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/male/weapon.md2"); 407 Com_sprintf (skin_filename, sizeof(skin_filename), "players/male/grunt.pcx"); 408 Com_sprintf (ci->iconname, sizeof(ci->iconname), "/players/male/grunt_i.pcx"); 409 ci->model = re.RegisterModel (model_filename); 410 memset(ci->weaponmodel, 0, sizeof(ci->weaponmodel)); 411 ci->weaponmodel[0] = re.RegisterModel (weapon_filename); 412 ci->skin = re.RegisterSkin (skin_filename); 413 ci->icon = re.RegisterPic (ci->iconname); 414 } 415 else 416 { 417 // isolate the model name 418 strcpy (model_name, s); 419 t = strstr(model_name, "/"); 420 if (!t) 421 t = strstr(model_name, "\\"); 422 if (!t) 423 t = model_name; 424 *t = 0; 425 426 // isolate the skin name 427 strcpy (skin_name, s + strlen(model_name) + 1); 428 429 // model file 430 Com_sprintf (model_filename, sizeof(model_filename), "players/%s/tris.md2", model_name); 431 ci->model = re.RegisterModel (model_filename); 432 if (!ci->model) 433 { 434 strcpy(model_name, "male"); 435 Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2"); 436 ci->model = re.RegisterModel (model_filename); 437 } 438 439 // skin file 440 Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", model_name, skin_name); 441 ci->skin = re.RegisterSkin (skin_filename); 442 443 // if we don't have the skin and the model wasn't male, 444 // see if the male has it (this is for CTF's skins) 445 if (!ci->skin && Q_stricmp(model_name, "male")) 446 { 447 // change model to male 448 strcpy(model_name, "male"); 449 Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2"); 450 ci->model = re.RegisterModel (model_filename); 451 452 // see if the skin exists for the male model 453 Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", model_name, skin_name); 454 ci->skin = re.RegisterSkin (skin_filename); 455 } 456 457 // if we still don't have a skin, it means that the male model didn't have 458 // it, so default to grunt 459 if (!ci->skin) { 460 // see if the skin exists for the male model 461 Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/grunt.pcx", model_name, skin_name); 462 ci->skin = re.RegisterSkin (skin_filename); 463 } 464 465 // weapon file 466 for (i = 0; i < num_cl_weaponmodels; i++) { 467 Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/%s/%s", model_name, cl_weaponmodels[i]); 468 ci->weaponmodel[i] = re.RegisterModel(weapon_filename); 469 if (!ci->weaponmodel[i] && strcmp(model_name, "cyborg") == 0) { 470 // try male 471 Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/male/%s", cl_weaponmodels[i]); 472 ci->weaponmodel[i] = re.RegisterModel(weapon_filename); 473 } 474 if (!cl_vwep->value) 475 break; // only one when vwep is off 476 } 477 478 // icon file 479 Com_sprintf (ci->iconname, sizeof(ci->iconname), "/players/%s/%s_i.pcx", model_name, skin_name); 480 ci->icon = re.RegisterPic (ci->iconname); 481 } 482 483 // must have loaded all data types to be valud 484 if (!ci->skin || !ci->icon || !ci->model || !ci->weaponmodel[0]) 485 { 486 ci->skin = NULL; 487 ci->icon = NULL; 488 ci->model = NULL; 489 ci->weaponmodel[0] = NULL; 490 return; 491 } 492 } 493 494 /* 495 ================ 496 CL_ParseClientinfo 497 498 Load the skin, icon, and model for a client 499 ================ 500 */ 501 void CL_ParseClientinfo (int player) 502 { 503 char *s; 504 clientinfo_t *ci; 505 506 s = cl.configstrings[player+CS_PLAYERSKINS]; 507 508 ci = &cl.clientinfo[player]; 509 510 CL_LoadClientinfo (ci, s); 511 } 512 513 514 /* 515 ================ 516 CL_ParseConfigString 517 ================ 518 */ 519 void CL_ParseConfigString (void) 520 { 521 int i; 522 char *s; 523 char olds[MAX_QPATH]; 524 525 i = MSG_ReadShort (&net_message); 526 if (i < 0 || i >= MAX_CONFIGSTRINGS) 527 Com_Error (ERR_DROP, "configstring > MAX_CONFIGSTRINGS"); 528 s = MSG_ReadString(&net_message); 529 530 strncpy (olds, cl.configstrings[i], sizeof(olds)); 531 olds[sizeof(olds) - 1] = 0; 532 533 strcpy (cl.configstrings[i], s); 534 535 // do something apropriate 536 537 if (i >= CS_LIGHTS && i < CS_LIGHTS+MAX_LIGHTSTYLES) 538 CL_SetLightstyle (i - CS_LIGHTS); 539 else if (i == CS_CDTRACK) 540 { 541 if (cl.refresh_prepped) 542 CDAudio_Play (atoi(cl.configstrings[CS_CDTRACK]), true); 543 } 544 else if (i >= CS_MODELS && i < CS_MODELS+MAX_MODELS) 545 { 546 if (cl.refresh_prepped) 547 { 548 cl.model_draw[i-CS_MODELS] = re.RegisterModel (cl.configstrings[i]); 549 if (cl.configstrings[i][0] == '*') 550 cl.model_clip[i-CS_MODELS] = CM_InlineModel (cl.configstrings[i]); 551 else 552 cl.model_clip[i-CS_MODELS] = NULL; 553 } 554 } 555 else if (i >= CS_SOUNDS && i < CS_SOUNDS+MAX_MODELS) 556 { 557 if (cl.refresh_prepped) 558 cl.sound_precache[i-CS_SOUNDS] = S_RegisterSound (cl.configstrings[i]); 559 } 560 else if (i >= CS_IMAGES && i < CS_IMAGES+MAX_MODELS) 561 { 562 if (cl.refresh_prepped) 563 cl.image_precache[i-CS_IMAGES] = re.RegisterPic (cl.configstrings[i]); 564 } 565 else if (i >= CS_PLAYERSKINS && i < CS_PLAYERSKINS+MAX_CLIENTS) 566 { 567 if (cl.refresh_prepped && strcmp(olds, s)) 568 CL_ParseClientinfo (i-CS_PLAYERSKINS); 569 } 570 } 571 572 573 /* 574 ===================================================================== 575 576 ACTION MESSAGES 577 578 ===================================================================== 579 */ 580 581 /* 582 ================== 583 CL_ParseStartSoundPacket 584 ================== 585 */ 586 void CL_ParseStartSoundPacket(void) 587 { 588 vec3_t pos_v; 589 float *pos; 590 int channel, ent; 591 int sound_num; 592 float volume; 593 float attenuation; 594 int flags; 595 float ofs; 596 597 flags = MSG_ReadByte (&net_message); 598 sound_num = MSG_ReadByte (&net_message); 599 600 if (flags & SND_VOLUME) 601 volume = MSG_ReadByte (&net_message) / 255.0; 602 else 603 volume = DEFAULT_SOUND_PACKET_VOLUME; 604 605 if (flags & SND_ATTENUATION) 606 attenuation = MSG_ReadByte (&net_message) / 64.0; 607 else 608 attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; 609 610 if (flags & SND_OFFSET) 611 ofs = MSG_ReadByte (&net_message) / 1000.0; 612 else 613 ofs = 0; 614 615 if (flags & SND_ENT) 616 { // entity reletive 617 channel = MSG_ReadShort(&net_message); 618 ent = channel>>3; 619 if (ent > MAX_EDICTS) 620 Com_Error (ERR_DROP,"CL_ParseStartSoundPacket: ent = %i", ent); 621 622 channel &= 7; 623 } 624 else 625 { 626 ent = 0; 627 channel = 0; 628 } 629 630 if (flags & SND_POS) 631 { // positioned in space 632 MSG_ReadPos (&net_message, pos_v); 633 634 pos = pos_v; 635 } 636 else // use entity number 637 pos = NULL; 638 639 if (!cl.sound_precache[sound_num]) 640 return; 641 642 S_StartSound (pos, ent, channel, cl.sound_precache[sound_num], volume, attenuation, ofs); 643 } 644 645 646 void SHOWNET(char *s) 647 { 648 if (cl_shownet->value>=2) 649 Com_Printf ("%3i:%s\n", net_message.readcount-1, s); 650 } 651 652 /* 653 ===================== 654 CL_ParseServerMessage 655 ===================== 656 */ 657 void CL_ParseServerMessage (void) 658 { 659 int cmd; 660 char *s; 661 int i; 662 663 // 664 // if recording demos, copy the message out 665 // 666 if (cl_shownet->value == 1) 667 Com_Printf ("%i ",net_message.cursize); 668 else if (cl_shownet->value >= 2) 669 Com_Printf ("------------------\n"); 670 671 672 // 673 // parse the message 674 // 675 while (1) 676 { 677 if (net_message.readcount > net_message.cursize) 678 { 679 Com_Error (ERR_DROP,"CL_ParseServerMessage: Bad server message"); 680 break; 681 } 682 683 cmd = MSG_ReadByte (&net_message); 684 685 if (cmd == -1) 686 { 687 SHOWNET("END OF MESSAGE"); 688 break; 689 } 690 691 if (cl_shownet->value>=2) 692 { 693 if (!svc_strings[cmd]) 694 Com_Printf ("%3i:BAD CMD %i\n", net_message.readcount-1,cmd); 695 else 696 SHOWNET(svc_strings[cmd]); 697 } 698 699 // other commands 700 switch (cmd) 701 { 702 default: 703 Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message\n"); 704 break; 705 706 case svc_nop: 707 // Com_Printf ("svc_nop\n"); 708 break; 709 710 case svc_disconnect: 711 Com_Error (ERR_DISCONNECT,"Server disconnected\n"); 712 break; 713 714 case svc_reconnect: 715 Com_Printf ("Server disconnected, reconnecting\n"); 716 if (cls.download) { 717 //ZOID, close download 718 fclose (cls.download); 719 cls.download = NULL; 720 } 721 cls.state = ca_connecting; 722 cls.connect_time = -99999; // CL_CheckForResend() will fire immediately 723 break; 724 725 case svc_print: 726 i = MSG_ReadByte (&net_message); 727 if (i == PRINT_CHAT) 728 { 729 S_StartLocalSound ("misc/talk.wav"); 730 con.ormask = 128; 731 } 732 Com_Printf ("%s", MSG_ReadString (&net_message)); 733 con.ormask = 0; 734 break; 735 736 case svc_centerprint: 737 SCR_CenterPrint (MSG_ReadString (&net_message)); 738 break; 739 740 case svc_stufftext: 741 s = MSG_ReadString (&net_message); 742 Com_DPrintf ("stufftext: %s\n", s); 743 Cbuf_AddText (s); 744 break; 745 746 case svc_serverdata: 747 Cbuf_Execute (); // make sure any stuffed commands are done 748 CL_ParseServerData (); 749 break; 750 751 case svc_configstring: 752 CL_ParseConfigString (); 753 break; 754 755 case svc_sound: 756 CL_ParseStartSoundPacket(); 757 break; 758 759 case svc_spawnbaseline: 760 CL_ParseBaseline (); 761 break; 762 763 case svc_temp_entity: 764 CL_ParseTEnt (); 765 break; 766 767 case svc_muzzleflash: 768 CL_ParseMuzzleFlash (); 769 break; 770 771 case svc_muzzleflash2: 772 CL_ParseMuzzleFlash2 (); 773 break; 774 775 case svc_download: 776 CL_ParseDownload (); 777 break; 778 779 case svc_frame: 780 CL_ParseFrame (); 781 break; 782 783 case svc_inventory: 784 CL_ParseInventory (); 785 break; 786 787 case svc_layout: 788 s = MSG_ReadString (&net_message); 789 strncpy (cl.layout, s, sizeof(cl.layout)-1); 790 break; 791 792 case svc_playerinfo: 793 case svc_packetentities: 794 case svc_deltapacketentities: 795 Com_Error (ERR_DROP, "Out of place frame data"); 796 break; 797 } 798 } 799 800 CL_AddNetgraph (); 801 802 // 803 // we don't know if it is ok to save a demo message until 804 // after we have parsed the frame 805 // 806 if (cls.demorecording && !cls.demowaiting) 807 CL_WriteDemoMessage (); 808 809 } 810 811 812