cg_players.c (70197B)
1 /* 2 =========================================================================== 3 Copyright (C) 1999-2005 Id Software, Inc. 4 5 This file is part of Quake III Arena source code. 6 7 Quake III Arena source code is free software; you can redistribute it 8 and/or modify it under the terms of the GNU General Public License as 9 published by the Free Software Foundation; either version 2 of the License, 10 or (at your option) any later version. 11 12 Quake III Arena source code is distributed in the hope that it will be 13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with Foobar; if not, write to the Free Software 19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 =========================================================================== 21 */ 22 // 23 // cg_players.c -- handle the media and animation for player entities 24 #include "cg_local.h" 25 26 char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { 27 "*death1.wav", 28 "*death2.wav", 29 "*death3.wav", 30 "*jump1.wav", 31 "*pain25_1.wav", 32 "*pain50_1.wav", 33 "*pain75_1.wav", 34 "*pain100_1.wav", 35 "*falling1.wav", 36 "*gasp.wav", 37 "*drown.wav", 38 "*fall1.wav", 39 "*taunt.wav" 40 }; 41 42 43 /* 44 ================ 45 CG_CustomSound 46 47 ================ 48 */ 49 sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) { 50 clientInfo_t *ci; 51 int i; 52 53 if ( soundName[0] != '*' ) { 54 return trap_S_RegisterSound( soundName, qfalse ); 55 } 56 57 if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { 58 clientNum = 0; 59 } 60 ci = &cgs.clientinfo[ clientNum ]; 61 62 for ( i = 0 ; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[i] ; i++ ) { 63 if ( !strcmp( soundName, cg_customSoundNames[i] ) ) { 64 return ci->sounds[i]; 65 } 66 } 67 68 CG_Error( "Unknown custom sound: %s", soundName ); 69 return 0; 70 } 71 72 73 74 /* 75 ============================================================================= 76 77 CLIENT INFO 78 79 ============================================================================= 80 */ 81 82 /* 83 ====================== 84 CG_ParseAnimationFile 85 86 Read a configuration file containing animation coutns and rates 87 models/players/visor/animation.cfg, etc 88 ====================== 89 */ 90 static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) { 91 char *text_p, *prev; 92 int len; 93 int i; 94 char *token; 95 float fps; 96 int skip; 97 char text[20000]; 98 fileHandle_t f; 99 animation_t *animations; 100 101 animations = ci->animations; 102 103 // load the file 104 len = trap_FS_FOpenFile( filename, &f, FS_READ ); 105 if ( len <= 0 ) { 106 return qfalse; 107 } 108 if ( len >= sizeof( text ) - 1 ) { 109 CG_Printf( "File %s too long\n", filename ); 110 return qfalse; 111 } 112 trap_FS_Read( text, len, f ); 113 text[len] = 0; 114 trap_FS_FCloseFile( f ); 115 116 // parse the text 117 text_p = text; 118 skip = 0; // quite the compiler warning 119 120 ci->footsteps = FOOTSTEP_NORMAL; 121 VectorClear( ci->headOffset ); 122 ci->gender = GENDER_MALE; 123 ci->fixedlegs = qfalse; 124 ci->fixedtorso = qfalse; 125 126 // read optional parameters 127 while ( 1 ) { 128 prev = text_p; // so we can unget 129 token = COM_Parse( &text_p ); 130 if ( !token ) { 131 break; 132 } 133 if ( !Q_stricmp( token, "footsteps" ) ) { 134 token = COM_Parse( &text_p ); 135 if ( !token ) { 136 break; 137 } 138 if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { 139 ci->footsteps = FOOTSTEP_NORMAL; 140 } else if ( !Q_stricmp( token, "boot" ) ) { 141 ci->footsteps = FOOTSTEP_BOOT; 142 } else if ( !Q_stricmp( token, "flesh" ) ) { 143 ci->footsteps = FOOTSTEP_FLESH; 144 } else if ( !Q_stricmp( token, "mech" ) ) { 145 ci->footsteps = FOOTSTEP_MECH; 146 } else if ( !Q_stricmp( token, "energy" ) ) { 147 ci->footsteps = FOOTSTEP_ENERGY; 148 } else { 149 CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token ); 150 } 151 continue; 152 } else if ( !Q_stricmp( token, "headoffset" ) ) { 153 for ( i = 0 ; i < 3 ; i++ ) { 154 token = COM_Parse( &text_p ); 155 if ( !token ) { 156 break; 157 } 158 ci->headOffset[i] = atof( token ); 159 } 160 continue; 161 } else if ( !Q_stricmp( token, "sex" ) ) { 162 token = COM_Parse( &text_p ); 163 if ( !token ) { 164 break; 165 } 166 if ( token[0] == 'f' || token[0] == 'F' ) { 167 ci->gender = GENDER_FEMALE; 168 } else if ( token[0] == 'n' || token[0] == 'N' ) { 169 ci->gender = GENDER_NEUTER; 170 } else { 171 ci->gender = GENDER_MALE; 172 } 173 continue; 174 } else if ( !Q_stricmp( token, "fixedlegs" ) ) { 175 ci->fixedlegs = qtrue; 176 continue; 177 } else if ( !Q_stricmp( token, "fixedtorso" ) ) { 178 ci->fixedtorso = qtrue; 179 continue; 180 } 181 182 // if it is a number, start parsing animations 183 if ( token[0] >= '0' && token[0] <= '9' ) { 184 text_p = prev; // unget the token 185 break; 186 } 187 Com_Printf( "unknown token '%s' is %s\n", token, filename ); 188 } 189 190 // read information for each frame 191 for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { 192 193 token = COM_Parse( &text_p ); 194 if ( !*token ) { 195 if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) { 196 animations[i].firstFrame = animations[TORSO_GESTURE].firstFrame; 197 animations[i].frameLerp = animations[TORSO_GESTURE].frameLerp; 198 animations[i].initialLerp = animations[TORSO_GESTURE].initialLerp; 199 animations[i].loopFrames = animations[TORSO_GESTURE].loopFrames; 200 animations[i].numFrames = animations[TORSO_GESTURE].numFrames; 201 animations[i].reversed = qfalse; 202 animations[i].flipflop = qfalse; 203 continue; 204 } 205 break; 206 } 207 animations[i].firstFrame = atoi( token ); 208 // leg only frames are adjusted to not count the upper body only frames 209 if ( i == LEGS_WALKCR ) { 210 skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; 211 } 212 if ( i >= LEGS_WALKCR && i<TORSO_GETFLAG) { 213 animations[i].firstFrame -= skip; 214 } 215 216 token = COM_Parse( &text_p ); 217 if ( !*token ) { 218 break; 219 } 220 animations[i].numFrames = atoi( token ); 221 222 animations[i].reversed = qfalse; 223 animations[i].flipflop = qfalse; 224 // if numFrames is negative the animation is reversed 225 if (animations[i].numFrames < 0) { 226 animations[i].numFrames = -animations[i].numFrames; 227 animations[i].reversed = qtrue; 228 } 229 230 token = COM_Parse( &text_p ); 231 if ( !*token ) { 232 break; 233 } 234 animations[i].loopFrames = atoi( token ); 235 236 token = COM_Parse( &text_p ); 237 if ( !*token ) { 238 break; 239 } 240 fps = atof( token ); 241 if ( fps == 0 ) { 242 fps = 1; 243 } 244 animations[i].frameLerp = 1000 / fps; 245 animations[i].initialLerp = 1000 / fps; 246 } 247 248 if ( i != MAX_ANIMATIONS ) { 249 CG_Printf( "Error parsing animation file: %s", filename ); 250 return qfalse; 251 } 252 253 // crouch backward animation 254 memcpy(&animations[LEGS_BACKCR], &animations[LEGS_WALKCR], sizeof(animation_t)); 255 animations[LEGS_BACKCR].reversed = qtrue; 256 // walk backward animation 257 memcpy(&animations[LEGS_BACKWALK], &animations[LEGS_WALK], sizeof(animation_t)); 258 animations[LEGS_BACKWALK].reversed = qtrue; 259 // flag moving fast 260 animations[FLAG_RUN].firstFrame = 0; 261 animations[FLAG_RUN].numFrames = 16; 262 animations[FLAG_RUN].loopFrames = 16; 263 animations[FLAG_RUN].frameLerp = 1000 / 15; 264 animations[FLAG_RUN].initialLerp = 1000 / 15; 265 animations[FLAG_RUN].reversed = qfalse; 266 // flag not moving or moving slowly 267 animations[FLAG_STAND].firstFrame = 16; 268 animations[FLAG_STAND].numFrames = 5; 269 animations[FLAG_STAND].loopFrames = 0; 270 animations[FLAG_STAND].frameLerp = 1000 / 20; 271 animations[FLAG_STAND].initialLerp = 1000 / 20; 272 animations[FLAG_STAND].reversed = qfalse; 273 // flag speeding up 274 animations[FLAG_STAND2RUN].firstFrame = 16; 275 animations[FLAG_STAND2RUN].numFrames = 5; 276 animations[FLAG_STAND2RUN].loopFrames = 1; 277 animations[FLAG_STAND2RUN].frameLerp = 1000 / 15; 278 animations[FLAG_STAND2RUN].initialLerp = 1000 / 15; 279 animations[FLAG_STAND2RUN].reversed = qtrue; 280 // 281 // new anims changes 282 // 283 // animations[TORSO_GETFLAG].flipflop = qtrue; 284 // animations[TORSO_GUARDBASE].flipflop = qtrue; 285 // animations[TORSO_PATROL].flipflop = qtrue; 286 // animations[TORSO_AFFIRMATIVE].flipflop = qtrue; 287 // animations[TORSO_NEGATIVE].flipflop = qtrue; 288 // 289 return qtrue; 290 } 291 292 /* 293 ========================== 294 CG_FileExists 295 ========================== 296 */ 297 static qboolean CG_FileExists(const char *filename) { 298 int len; 299 300 len = trap_FS_FOpenFile( filename, 0, FS_READ ); 301 if (len>0) { 302 return qtrue; 303 } 304 return qfalse; 305 } 306 307 /* 308 ========================== 309 CG_FindClientModelFile 310 ========================== 311 */ 312 static qboolean CG_FindClientModelFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *base, const char *ext ) { 313 char *team, *charactersFolder; 314 int i; 315 316 if ( cgs.gametype >= GT_TEAM ) { 317 switch ( ci->team ) { 318 case TEAM_BLUE: { 319 team = "blue"; 320 break; 321 } 322 default: { 323 team = "red"; 324 break; 325 } 326 } 327 } 328 else { 329 team = "default"; 330 } 331 charactersFolder = ""; 332 while(1) { 333 for ( i = 0; i < 2; i++ ) { 334 if ( i == 0 && teamName && *teamName ) { 335 // "models/players/characters/james/stroggs/lower_lily_red.skin" 336 Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, team, ext ); 337 } 338 else { 339 // "models/players/characters/james/lower_lily_red.skin" 340 Com_sprintf( filename, length, "models/players/%s%s/%s_%s_%s.%s", charactersFolder, modelName, base, skinName, team, ext ); 341 } 342 if ( CG_FileExists( filename ) ) { 343 return qtrue; 344 } 345 if ( cgs.gametype >= GT_TEAM ) { 346 if ( i == 0 && teamName && *teamName ) { 347 // "models/players/characters/james/stroggs/lower_red.skin" 348 Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, team, ext ); 349 } 350 else { 351 // "models/players/characters/james/lower_red.skin" 352 Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, team, ext ); 353 } 354 } 355 else { 356 if ( i == 0 && teamName && *teamName ) { 357 // "models/players/characters/james/stroggs/lower_lily.skin" 358 Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, ext ); 359 } 360 else { 361 // "models/players/characters/james/lower_lily.skin" 362 Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, skinName, ext ); 363 } 364 } 365 if ( CG_FileExists( filename ) ) { 366 return qtrue; 367 } 368 if ( !teamName || !*teamName ) { 369 break; 370 } 371 } 372 // if tried the heads folder first 373 if ( charactersFolder[0] ) { 374 break; 375 } 376 charactersFolder = "characters/"; 377 } 378 379 return qfalse; 380 } 381 382 /* 383 ========================== 384 CG_FindClientHeadFile 385 ========================== 386 */ 387 static qboolean CG_FindClientHeadFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) { 388 char *team, *headsFolder; 389 int i; 390 391 if ( cgs.gametype >= GT_TEAM ) { 392 switch ( ci->team ) { 393 case TEAM_BLUE: { 394 team = "blue"; 395 break; 396 } 397 default: { 398 team = "red"; 399 break; 400 } 401 } 402 } 403 else { 404 team = "default"; 405 } 406 407 if ( headModelName[0] == '*' ) { 408 headsFolder = "heads/"; 409 headModelName++; 410 } 411 else { 412 headsFolder = ""; 413 } 414 while(1) { 415 for ( i = 0; i < 2; i++ ) { 416 if ( i == 0 && teamName && *teamName ) { 417 Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext ); 418 } 419 else { 420 Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext ); 421 } 422 if ( CG_FileExists( filename ) ) { 423 return qtrue; 424 } 425 if ( cgs.gametype >= GT_TEAM ) { 426 if ( i == 0 && teamName && *teamName ) { 427 Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, team, ext ); 428 } 429 else { 430 Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, team, ext ); 431 } 432 } 433 else { 434 if ( i == 0 && teamName && *teamName ) { 435 Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext ); 436 } 437 else { 438 Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext ); 439 } 440 } 441 if ( CG_FileExists( filename ) ) { 442 return qtrue; 443 } 444 if ( !teamName || !*teamName ) { 445 break; 446 } 447 } 448 // if tried the heads folder first 449 if ( headsFolder[0] ) { 450 break; 451 } 452 headsFolder = "heads/"; 453 } 454 455 return qfalse; 456 } 457 458 /* 459 ========================== 460 CG_RegisterClientSkin 461 ========================== 462 */ 463 static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName ) { 464 char filename[MAX_QPATH]; 465 466 /* 467 Com_sprintf( filename, sizeof( filename ), "models/players/%s/%slower_%s.skin", modelName, teamName, skinName ); 468 ci->legsSkin = trap_R_RegisterSkin( filename ); 469 if (!ci->legsSkin) { 470 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%slower_%s.skin", modelName, teamName, skinName ); 471 ci->legsSkin = trap_R_RegisterSkin( filename ); 472 if (!ci->legsSkin) { 473 Com_Printf( "Leg skin load failure: %s\n", filename ); 474 } 475 } 476 477 478 Com_sprintf( filename, sizeof( filename ), "models/players/%s/%supper_%s.skin", modelName, teamName, skinName ); 479 ci->torsoSkin = trap_R_RegisterSkin( filename ); 480 if (!ci->torsoSkin) { 481 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%supper_%s.skin", modelName, teamName, skinName ); 482 ci->torsoSkin = trap_R_RegisterSkin( filename ); 483 if (!ci->torsoSkin) { 484 Com_Printf( "Torso skin load failure: %s\n", filename ); 485 } 486 } 487 */ 488 if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "lower", "skin" ) ) { 489 ci->legsSkin = trap_R_RegisterSkin( filename ); 490 } 491 if (!ci->legsSkin) { 492 Com_Printf( "Leg skin load failure: %s\n", filename ); 493 } 494 495 if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "upper", "skin" ) ) { 496 ci->torsoSkin = trap_R_RegisterSkin( filename ); 497 } 498 if (!ci->torsoSkin) { 499 Com_Printf( "Torso skin load failure: %s\n", filename ); 500 } 501 502 if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headModelName, headSkinName, "head", "skin" ) ) { 503 ci->headSkin = trap_R_RegisterSkin( filename ); 504 } 505 if (!ci->headSkin) { 506 Com_Printf( "Head skin load failure: %s\n", filename ); 507 } 508 509 // if any skins failed to load 510 if ( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) { 511 return qfalse; 512 } 513 return qtrue; 514 } 515 516 /* 517 ========================== 518 CG_RegisterClientModelname 519 ========================== 520 */ 521 static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName, const char *teamName ) { 522 char filename[MAX_QPATH*2]; 523 const char *headName; 524 char newTeamName[MAX_QPATH*2]; 525 526 if ( headModelName[0] == '\0' ) { 527 headName = modelName; 528 } 529 else { 530 headName = headModelName; 531 } 532 Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); 533 ci->legsModel = trap_R_RegisterModel( filename ); 534 if ( !ci->legsModel ) { 535 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName ); 536 ci->legsModel = trap_R_RegisterModel( filename ); 537 if ( !ci->legsModel ) { 538 Com_Printf( "Failed to load model file %s\n", filename ); 539 return qfalse; 540 } 541 } 542 543 Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); 544 ci->torsoModel = trap_R_RegisterModel( filename ); 545 if ( !ci->torsoModel ) { 546 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName ); 547 ci->torsoModel = trap_R_RegisterModel( filename ); 548 if ( !ci->torsoModel ) { 549 Com_Printf( "Failed to load model file %s\n", filename ); 550 return qfalse; 551 } 552 } 553 554 if( headName[0] == '*' ) { 555 Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] ); 556 } 557 else { 558 Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headName ); 559 } 560 ci->headModel = trap_R_RegisterModel( filename ); 561 // if the head model could not be found and we didn't load from the heads folder try to load from there 562 if ( !ci->headModel && headName[0] != '*' ) { 563 Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName ); 564 ci->headModel = trap_R_RegisterModel( filename ); 565 } 566 if ( !ci->headModel ) { 567 Com_Printf( "Failed to load model file %s\n", filename ); 568 return qfalse; 569 } 570 571 // if any skins failed to load, return failure 572 if ( !CG_RegisterClientSkin( ci, teamName, modelName, skinName, headName, headSkinName ) ) { 573 if ( teamName && *teamName) { 574 Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", teamName, modelName, skinName, headName, headSkinName ); 575 if( ci->team == TEAM_BLUE ) { 576 Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_BLUETEAM_NAME); 577 } 578 else { 579 Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_REDTEAM_NAME); 580 } 581 if ( !CG_RegisterClientSkin( ci, newTeamName, modelName, skinName, headName, headSkinName ) ) { 582 Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", newTeamName, modelName, skinName, headName, headSkinName ); 583 return qfalse; 584 } 585 } else { 586 Com_Printf( "Failed to load skin file: %s : %s, %s : %s\n", modelName, skinName, headName, headSkinName ); 587 return qfalse; 588 } 589 } 590 591 // load the animations 592 Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); 593 if ( !CG_ParseAnimationFile( filename, ci ) ) { 594 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName ); 595 if ( !CG_ParseAnimationFile( filename, ci ) ) { 596 Com_Printf( "Failed to load animation file %s\n", filename ); 597 return qfalse; 598 } 599 } 600 601 if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "skin" ) ) { 602 ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); 603 } 604 else if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "tga" ) ) { 605 ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); 606 } 607 608 if ( !ci->modelIcon ) { 609 return qfalse; 610 } 611 612 return qtrue; 613 } 614 615 /* 616 ==================== 617 CG_ColorFromString 618 ==================== 619 */ 620 static void CG_ColorFromString( const char *v, vec3_t color ) { 621 int val; 622 623 VectorClear( color ); 624 625 val = atoi( v ); 626 627 if ( val < 1 || val > 7 ) { 628 VectorSet( color, 1, 1, 1 ); 629 return; 630 } 631 632 if ( val & 1 ) { 633 color[2] = 1.0f; 634 } 635 if ( val & 2 ) { 636 color[1] = 1.0f; 637 } 638 if ( val & 4 ) { 639 color[0] = 1.0f; 640 } 641 } 642 643 /* 644 =================== 645 CG_LoadClientInfo 646 647 Load it now, taking the disk hits. 648 This will usually be deferred to a safe time 649 =================== 650 */ 651 static void CG_LoadClientInfo( clientInfo_t *ci ) { 652 const char *dir, *fallback; 653 int i, modelloaded; 654 const char *s; 655 int clientNum; 656 char teamname[MAX_QPATH]; 657 658 teamname[0] = 0; 659 #ifdef MISSIONPACK 660 if( cgs.gametype >= GT_TEAM) { 661 if( ci->team == TEAM_BLUE ) { 662 Q_strncpyz(teamname, cg_blueTeamName.string, sizeof(teamname) ); 663 } else { 664 Q_strncpyz(teamname, cg_redTeamName.string, sizeof(teamname) ); 665 } 666 } 667 if( teamname[0] ) { 668 strcat( teamname, "/" ); 669 } 670 #endif 671 modelloaded = qtrue; 672 if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ) ) { 673 if ( cg_buildScript.integer ) { 674 CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ); 675 } 676 677 // fall back to default team name 678 if( cgs.gametype >= GT_TEAM) { 679 // keep skin name 680 if( ci->team == TEAM_BLUE ) { 681 Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME, sizeof(teamname) ); 682 } else { 683 Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME, sizeof(teamname) ); 684 } 685 if ( !CG_RegisterClientModelname( ci, DEFAULT_TEAM_MODEL, ci->skinName, DEFAULT_TEAM_HEAD, ci->skinName, teamname ) ) { 686 CG_Error( "DEFAULT_TEAM_MODEL / skin (%s/%s) failed to register", DEFAULT_TEAM_MODEL, ci->skinName ); 687 } 688 } else { 689 if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", DEFAULT_MODEL, "default", teamname ) ) { 690 CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); 691 } 692 } 693 modelloaded = qfalse; 694 } 695 696 ci->newAnims = qfalse; 697 if ( ci->torsoModel ) { 698 orientation_t tag; 699 // if the torso model has the "tag_flag" 700 if ( trap_R_LerpTag( &tag, ci->torsoModel, 0, 0, 1, "tag_flag" ) ) { 701 ci->newAnims = qtrue; 702 } 703 } 704 705 // sounds 706 dir = ci->modelName; 707 fallback = (cgs.gametype >= GT_TEAM) ? DEFAULT_TEAM_MODEL : DEFAULT_MODEL; 708 709 for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) { 710 s = cg_customSoundNames[i]; 711 if ( !s ) { 712 break; 713 } 714 ci->sounds[i] = 0; 715 // if the model didn't load use the sounds of the default model 716 if (modelloaded) { 717 ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", dir, s + 1), qfalse ); 718 } 719 if ( !ci->sounds[i] ) { 720 ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", fallback, s + 1), qfalse ); 721 } 722 } 723 724 ci->deferred = qfalse; 725 726 // reset any existing players and bodies, because they might be in bad 727 // frames for this new model 728 clientNum = ci - cgs.clientinfo; 729 for ( i = 0 ; i < MAX_GENTITIES ; i++ ) { 730 if ( cg_entities[i].currentState.clientNum == clientNum 731 && cg_entities[i].currentState.eType == ET_PLAYER ) { 732 CG_ResetPlayerEntity( &cg_entities[i] ); 733 } 734 } 735 } 736 737 /* 738 ====================== 739 CG_CopyClientInfoModel 740 ====================== 741 */ 742 static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) { 743 VectorCopy( from->headOffset, to->headOffset ); 744 to->footsteps = from->footsteps; 745 to->gender = from->gender; 746 747 to->legsModel = from->legsModel; 748 to->legsSkin = from->legsSkin; 749 to->torsoModel = from->torsoModel; 750 to->torsoSkin = from->torsoSkin; 751 to->headModel = from->headModel; 752 to->headSkin = from->headSkin; 753 to->modelIcon = from->modelIcon; 754 755 to->newAnims = from->newAnims; 756 757 memcpy( to->animations, from->animations, sizeof( to->animations ) ); 758 memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); 759 } 760 761 /* 762 ====================== 763 CG_ScanForExistingClientInfo 764 ====================== 765 */ 766 static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) { 767 int i; 768 clientInfo_t *match; 769 770 for ( i = 0 ; i < cgs.maxclients ; i++ ) { 771 match = &cgs.clientinfo[ i ]; 772 if ( !match->infoValid ) { 773 continue; 774 } 775 if ( match->deferred ) { 776 continue; 777 } 778 if ( !Q_stricmp( ci->modelName, match->modelName ) 779 && !Q_stricmp( ci->skinName, match->skinName ) 780 && !Q_stricmp( ci->headModelName, match->headModelName ) 781 && !Q_stricmp( ci->headSkinName, match->headSkinName ) 782 && !Q_stricmp( ci->blueTeam, match->blueTeam ) 783 && !Q_stricmp( ci->redTeam, match->redTeam ) 784 && (cgs.gametype < GT_TEAM || ci->team == match->team) ) { 785 // this clientinfo is identical, so use it's handles 786 787 ci->deferred = qfalse; 788 789 CG_CopyClientInfoModel( match, ci ); 790 791 return qtrue; 792 } 793 } 794 795 // nothing matches, so defer the load 796 return qfalse; 797 } 798 799 /* 800 ====================== 801 CG_SetDeferredClientInfo 802 803 We aren't going to load it now, so grab some other 804 client's info to use until we have some spare time. 805 ====================== 806 */ 807 static void CG_SetDeferredClientInfo( clientInfo_t *ci ) { 808 int i; 809 clientInfo_t *match; 810 811 // if someone else is already the same models and skins we 812 // can just load the client info 813 for ( i = 0 ; i < cgs.maxclients ; i++ ) { 814 match = &cgs.clientinfo[ i ]; 815 if ( !match->infoValid || match->deferred ) { 816 continue; 817 } 818 if ( Q_stricmp( ci->skinName, match->skinName ) || 819 Q_stricmp( ci->modelName, match->modelName ) || 820 // Q_stricmp( ci->headModelName, match->headModelName ) || 821 // Q_stricmp( ci->headSkinName, match->headSkinName ) || 822 (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { 823 continue; 824 } 825 // just load the real info cause it uses the same models and skins 826 CG_LoadClientInfo( ci ); 827 return; 828 } 829 830 // if we are in teamplay, only grab a model if the skin is correct 831 if ( cgs.gametype >= GT_TEAM ) { 832 for ( i = 0 ; i < cgs.maxclients ; i++ ) { 833 match = &cgs.clientinfo[ i ]; 834 if ( !match->infoValid || match->deferred ) { 835 continue; 836 } 837 if ( Q_stricmp( ci->skinName, match->skinName ) || 838 (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { 839 continue; 840 } 841 ci->deferred = qtrue; 842 CG_CopyClientInfoModel( match, ci ); 843 return; 844 } 845 // load the full model, because we don't ever want to show 846 // an improper team skin. This will cause a hitch for the first 847 // player, when the second enters. Combat shouldn't be going on 848 // yet, so it shouldn't matter 849 CG_LoadClientInfo( ci ); 850 return; 851 } 852 853 // find the first valid clientinfo and grab its stuff 854 for ( i = 0 ; i < cgs.maxclients ; i++ ) { 855 match = &cgs.clientinfo[ i ]; 856 if ( !match->infoValid ) { 857 continue; 858 } 859 860 ci->deferred = qtrue; 861 CG_CopyClientInfoModel( match, ci ); 862 return; 863 } 864 865 // we should never get here... 866 CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" ); 867 868 CG_LoadClientInfo( ci ); 869 } 870 871 872 /* 873 ====================== 874 CG_NewClientInfo 875 ====================== 876 */ 877 void CG_NewClientInfo( int clientNum ) { 878 clientInfo_t *ci; 879 clientInfo_t newInfo; 880 const char *configstring; 881 const char *v; 882 char *slash; 883 884 ci = &cgs.clientinfo[clientNum]; 885 886 configstring = CG_ConfigString( clientNum + CS_PLAYERS ); 887 if ( !configstring[0] ) { 888 memset( ci, 0, sizeof( *ci ) ); 889 return; // player just left 890 } 891 892 // build into a temp buffer so the defer checks can use 893 // the old value 894 memset( &newInfo, 0, sizeof( newInfo ) ); 895 896 // isolate the player's name 897 v = Info_ValueForKey(configstring, "n"); 898 Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); 899 900 // colors 901 v = Info_ValueForKey( configstring, "c1" ); 902 CG_ColorFromString( v, newInfo.color1 ); 903 904 v = Info_ValueForKey( configstring, "c2" ); 905 CG_ColorFromString( v, newInfo.color2 ); 906 907 // bot skill 908 v = Info_ValueForKey( configstring, "skill" ); 909 newInfo.botSkill = atoi( v ); 910 911 // handicap 912 v = Info_ValueForKey( configstring, "hc" ); 913 newInfo.handicap = atoi( v ); 914 915 // wins 916 v = Info_ValueForKey( configstring, "w" ); 917 newInfo.wins = atoi( v ); 918 919 // losses 920 v = Info_ValueForKey( configstring, "l" ); 921 newInfo.losses = atoi( v ); 922 923 // team 924 v = Info_ValueForKey( configstring, "t" ); 925 newInfo.team = atoi( v ); 926 927 // team task 928 v = Info_ValueForKey( configstring, "tt" ); 929 newInfo.teamTask = atoi(v); 930 931 // team leader 932 v = Info_ValueForKey( configstring, "tl" ); 933 newInfo.teamLeader = atoi(v); 934 935 v = Info_ValueForKey( configstring, "g_redteam" ); 936 Q_strncpyz(newInfo.redTeam, v, MAX_TEAMNAME); 937 938 v = Info_ValueForKey( configstring, "g_blueteam" ); 939 Q_strncpyz(newInfo.blueTeam, v, MAX_TEAMNAME); 940 941 // model 942 v = Info_ValueForKey( configstring, "model" ); 943 if ( cg_forceModel.integer ) { 944 // forcemodel makes everyone use a single model 945 // to prevent load hitches 946 char modelStr[MAX_QPATH]; 947 char *skin; 948 949 if( cgs.gametype >= GT_TEAM ) { 950 Q_strncpyz( newInfo.modelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.modelName ) ); 951 Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); 952 } else { 953 trap_Cvar_VariableStringBuffer( "model", modelStr, sizeof( modelStr ) ); 954 if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { 955 skin = "default"; 956 } else { 957 *skin++ = 0; 958 } 959 960 Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); 961 Q_strncpyz( newInfo.modelName, modelStr, sizeof( newInfo.modelName ) ); 962 } 963 964 if ( cgs.gametype >= GT_TEAM ) { 965 // keep skin name 966 slash = strchr( v, '/' ); 967 if ( slash ) { 968 Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); 969 } 970 } 971 } else { 972 Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); 973 974 slash = strchr( newInfo.modelName, '/' ); 975 if ( !slash ) { 976 // modelName didn not include a skin name 977 Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); 978 } else { 979 Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); 980 // truncate modelName 981 *slash = 0; 982 } 983 } 984 985 // head model 986 v = Info_ValueForKey( configstring, "hmodel" ); 987 if ( cg_forceModel.integer ) { 988 // forcemodel makes everyone use a single model 989 // to prevent load hitches 990 char modelStr[MAX_QPATH]; 991 char *skin; 992 993 if( cgs.gametype >= GT_TEAM ) { 994 Q_strncpyz( newInfo.headModelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.headModelName ) ); 995 Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); 996 } else { 997 trap_Cvar_VariableStringBuffer( "headmodel", modelStr, sizeof( modelStr ) ); 998 if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { 999 skin = "default"; 1000 } else { 1001 *skin++ = 0; 1002 } 1003 1004 Q_strncpyz( newInfo.headSkinName, skin, sizeof( newInfo.headSkinName ) ); 1005 Q_strncpyz( newInfo.headModelName, modelStr, sizeof( newInfo.headModelName ) ); 1006 } 1007 1008 if ( cgs.gametype >= GT_TEAM ) { 1009 // keep skin name 1010 slash = strchr( v, '/' ); 1011 if ( slash ) { 1012 Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); 1013 } 1014 } 1015 } else { 1016 Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) ); 1017 1018 slash = strchr( newInfo.headModelName, '/' ); 1019 if ( !slash ) { 1020 // modelName didn not include a skin name 1021 Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); 1022 } else { 1023 Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); 1024 // truncate modelName 1025 *slash = 0; 1026 } 1027 } 1028 1029 // scan for an existing clientinfo that matches this modelname 1030 // so we can avoid loading checks if possible 1031 if ( !CG_ScanForExistingClientInfo( &newInfo ) ) { 1032 qboolean forceDefer; 1033 1034 forceDefer = trap_MemoryRemaining() < 4000000; 1035 1036 // if we are defering loads, just have it pick the first valid 1037 if ( forceDefer || (cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading ) ) { 1038 // keep whatever they had if it won't violate team skins 1039 CG_SetDeferredClientInfo( &newInfo ); 1040 // if we are low on memory, leave them with this model 1041 if ( forceDefer ) { 1042 CG_Printf( "Memory is low. Using deferred model.\n" ); 1043 newInfo.deferred = qfalse; 1044 } 1045 } else { 1046 CG_LoadClientInfo( &newInfo ); 1047 } 1048 } 1049 1050 // replace whatever was there with the new one 1051 newInfo.infoValid = qtrue; 1052 *ci = newInfo; 1053 } 1054 1055 1056 1057 /* 1058 ====================== 1059 CG_LoadDeferredPlayers 1060 1061 Called each frame when a player is dead 1062 and the scoreboard is up 1063 so deferred players can be loaded 1064 ====================== 1065 */ 1066 void CG_LoadDeferredPlayers( void ) { 1067 int i; 1068 clientInfo_t *ci; 1069 1070 // scan for a deferred player to load 1071 for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) { 1072 if ( ci->infoValid && ci->deferred ) { 1073 // if we are low on memory, leave it deferred 1074 if ( trap_MemoryRemaining() < 4000000 ) { 1075 CG_Printf( "Memory is low. Using deferred model.\n" ); 1076 ci->deferred = qfalse; 1077 continue; 1078 } 1079 CG_LoadClientInfo( ci ); 1080 // break; 1081 } 1082 } 1083 } 1084 1085 /* 1086 ============================================================================= 1087 1088 PLAYER ANIMATION 1089 1090 ============================================================================= 1091 */ 1092 1093 1094 /* 1095 =============== 1096 CG_SetLerpFrameAnimation 1097 1098 may include ANIM_TOGGLEBIT 1099 =============== 1100 */ 1101 static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { 1102 animation_t *anim; 1103 1104 lf->animationNumber = newAnimation; 1105 newAnimation &= ~ANIM_TOGGLEBIT; 1106 1107 if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) { 1108 CG_Error( "Bad animation number: %i", newAnimation ); 1109 } 1110 1111 anim = &ci->animations[ newAnimation ]; 1112 1113 lf->animation = anim; 1114 lf->animationTime = lf->frameTime + anim->initialLerp; 1115 1116 if ( cg_debugAnim.integer ) { 1117 CG_Printf( "Anim: %i\n", newAnimation ); 1118 } 1119 } 1120 1121 /* 1122 =============== 1123 CG_RunLerpFrame 1124 1125 Sets cg.snap, cg.oldFrame, and cg.backlerp 1126 cg.time should be between oldFrameTime and frameTime after exit 1127 =============== 1128 */ 1129 static void CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) { 1130 int f, numFrames; 1131 animation_t *anim; 1132 1133 // debugging tool to get no animations 1134 if ( cg_animSpeed.integer == 0 ) { 1135 lf->oldFrame = lf->frame = lf->backlerp = 0; 1136 return; 1137 } 1138 1139 // see if the animation sequence is switching 1140 if ( newAnimation != lf->animationNumber || !lf->animation ) { 1141 CG_SetLerpFrameAnimation( ci, lf, newAnimation ); 1142 } 1143 1144 // if we have passed the current frame, move it to 1145 // oldFrame and calculate a new frame 1146 if ( cg.time >= lf->frameTime ) { 1147 lf->oldFrame = lf->frame; 1148 lf->oldFrameTime = lf->frameTime; 1149 1150 // get the next frame based on the animation 1151 anim = lf->animation; 1152 if ( !anim->frameLerp ) { 1153 return; // shouldn't happen 1154 } 1155 if ( cg.time < lf->animationTime ) { 1156 lf->frameTime = lf->animationTime; // initial lerp 1157 } else { 1158 lf->frameTime = lf->oldFrameTime + anim->frameLerp; 1159 } 1160 f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; 1161 f *= speedScale; // adjust for haste, etc 1162 1163 numFrames = anim->numFrames; 1164 if (anim->flipflop) { 1165 numFrames *= 2; 1166 } 1167 if ( f >= numFrames ) { 1168 f -= numFrames; 1169 if ( anim->loopFrames ) { 1170 f %= anim->loopFrames; 1171 f += anim->numFrames - anim->loopFrames; 1172 } else { 1173 f = numFrames - 1; 1174 // the animation is stuck at the end, so it 1175 // can immediately transition to another sequence 1176 lf->frameTime = cg.time; 1177 } 1178 } 1179 if ( anim->reversed ) { 1180 lf->frame = anim->firstFrame + anim->numFrames - 1 - f; 1181 } 1182 else if (anim->flipflop && f>=anim->numFrames) { 1183 lf->frame = anim->firstFrame + anim->numFrames - 1 - (f%anim->numFrames); 1184 } 1185 else { 1186 lf->frame = anim->firstFrame + f; 1187 } 1188 if ( cg.time > lf->frameTime ) { 1189 lf->frameTime = cg.time; 1190 if ( cg_debugAnim.integer ) { 1191 CG_Printf( "Clamp lf->frameTime\n"); 1192 } 1193 } 1194 } 1195 1196 if ( lf->frameTime > cg.time + 200 ) { 1197 lf->frameTime = cg.time; 1198 } 1199 1200 if ( lf->oldFrameTime > cg.time ) { 1201 lf->oldFrameTime = cg.time; 1202 } 1203 // calculate current lerp value 1204 if ( lf->frameTime == lf->oldFrameTime ) { 1205 lf->backlerp = 0; 1206 } else { 1207 lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); 1208 } 1209 } 1210 1211 1212 /* 1213 =============== 1214 CG_ClearLerpFrame 1215 =============== 1216 */ 1217 static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) { 1218 lf->frameTime = lf->oldFrameTime = cg.time; 1219 CG_SetLerpFrameAnimation( ci, lf, animationNumber ); 1220 lf->oldFrame = lf->frame = lf->animation->firstFrame; 1221 } 1222 1223 1224 /* 1225 =============== 1226 CG_PlayerAnimation 1227 =============== 1228 */ 1229 static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp, 1230 int *torsoOld, int *torso, float *torsoBackLerp ) { 1231 clientInfo_t *ci; 1232 int clientNum; 1233 float speedScale; 1234 1235 clientNum = cent->currentState.clientNum; 1236 1237 if ( cg_noPlayerAnims.integer ) { 1238 *legsOld = *legs = *torsoOld = *torso = 0; 1239 return; 1240 } 1241 1242 if ( cent->currentState.powerups & ( 1 << PW_HASTE ) ) { 1243 speedScale = 1.5; 1244 } else { 1245 speedScale = 1; 1246 } 1247 1248 ci = &cgs.clientinfo[ clientNum ]; 1249 1250 // do the shuffle turn frames locally 1251 if ( cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) { 1252 CG_RunLerpFrame( ci, ¢->pe.legs, LEGS_TURN, speedScale ); 1253 } else { 1254 CG_RunLerpFrame( ci, ¢->pe.legs, cent->currentState.legsAnim, speedScale ); 1255 } 1256 1257 *legsOld = cent->pe.legs.oldFrame; 1258 *legs = cent->pe.legs.frame; 1259 *legsBackLerp = cent->pe.legs.backlerp; 1260 1261 CG_RunLerpFrame( ci, ¢->pe.torso, cent->currentState.torsoAnim, speedScale ); 1262 1263 *torsoOld = cent->pe.torso.oldFrame; 1264 *torso = cent->pe.torso.frame; 1265 *torsoBackLerp = cent->pe.torso.backlerp; 1266 } 1267 1268 /* 1269 ============================================================================= 1270 1271 PLAYER ANGLES 1272 1273 ============================================================================= 1274 */ 1275 1276 /* 1277 ================== 1278 CG_SwingAngles 1279 ================== 1280 */ 1281 static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance, 1282 float speed, float *angle, qboolean *swinging ) { 1283 float swing; 1284 float move; 1285 float scale; 1286 1287 if ( !*swinging ) { 1288 // see if a swing should be started 1289 swing = AngleSubtract( *angle, destination ); 1290 if ( swing > swingTolerance || swing < -swingTolerance ) { 1291 *swinging = qtrue; 1292 } 1293 } 1294 1295 if ( !*swinging ) { 1296 return; 1297 } 1298 1299 // modify the speed depending on the delta 1300 // so it doesn't seem so linear 1301 swing = AngleSubtract( destination, *angle ); 1302 scale = fabs( swing ); 1303 if ( scale < swingTolerance * 0.5 ) { 1304 scale = 0.5; 1305 } else if ( scale < swingTolerance ) { 1306 scale = 1.0; 1307 } else { 1308 scale = 2.0; 1309 } 1310 1311 // swing towards the destination angle 1312 if ( swing >= 0 ) { 1313 move = cg.frametime * scale * speed; 1314 if ( move >= swing ) { 1315 move = swing; 1316 *swinging = qfalse; 1317 } 1318 *angle = AngleMod( *angle + move ); 1319 } else if ( swing < 0 ) { 1320 move = cg.frametime * scale * -speed; 1321 if ( move <= swing ) { 1322 move = swing; 1323 *swinging = qfalse; 1324 } 1325 *angle = AngleMod( *angle + move ); 1326 } 1327 1328 // clamp to no more than tolerance 1329 swing = AngleSubtract( destination, *angle ); 1330 if ( swing > clampTolerance ) { 1331 *angle = AngleMod( destination - (clampTolerance - 1) ); 1332 } else if ( swing < -clampTolerance ) { 1333 *angle = AngleMod( destination + (clampTolerance - 1) ); 1334 } 1335 } 1336 1337 /* 1338 ================= 1339 CG_AddPainTwitch 1340 ================= 1341 */ 1342 static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) { 1343 int t; 1344 float f; 1345 1346 t = cg.time - cent->pe.painTime; 1347 if ( t >= PAIN_TWITCH_TIME ) { 1348 return; 1349 } 1350 1351 f = 1.0 - (float)t / PAIN_TWITCH_TIME; 1352 1353 if ( cent->pe.painDirection ) { 1354 torsoAngles[ROLL] += 20 * f; 1355 } else { 1356 torsoAngles[ROLL] -= 20 * f; 1357 } 1358 } 1359 1360 1361 /* 1362 =============== 1363 CG_PlayerAngles 1364 1365 Handles seperate torso motion 1366 1367 legs pivot based on direction of movement 1368 1369 head always looks exactly at cent->lerpAngles 1370 1371 if motion < 20 degrees, show in head only 1372 if < 45 degrees, also show in torso 1373 =============== 1374 */ 1375 static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { 1376 vec3_t legsAngles, torsoAngles, headAngles; 1377 float dest; 1378 static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 }; 1379 vec3_t velocity; 1380 float speed; 1381 int dir, clientNum; 1382 clientInfo_t *ci; 1383 1384 VectorCopy( cent->lerpAngles, headAngles ); 1385 headAngles[YAW] = AngleMod( headAngles[YAW] ); 1386 VectorClear( legsAngles ); 1387 VectorClear( torsoAngles ); 1388 1389 // --------- yaw ------------- 1390 1391 // allow yaw to drift a bit 1392 if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE 1393 || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) { 1394 // if not standing still, always point all in the same direction 1395 cent->pe.torso.yawing = qtrue; // always center 1396 cent->pe.torso.pitching = qtrue; // always center 1397 cent->pe.legs.yawing = qtrue; // always center 1398 } 1399 1400 // adjust legs for movement dir 1401 if ( cent->currentState.eFlags & EF_DEAD ) { 1402 // don't let dead bodies twitch 1403 dir = 0; 1404 } else { 1405 dir = cent->currentState.angles2[YAW]; 1406 if ( dir < 0 || dir > 7 ) { 1407 CG_Error( "Bad player movement angle" ); 1408 } 1409 } 1410 legsAngles[YAW] = headAngles[YAW] + movementOffsets[ dir ]; 1411 torsoAngles[YAW] = headAngles[YAW] + 0.25 * movementOffsets[ dir ]; 1412 1413 // torso 1414 CG_SwingAngles( torsoAngles[YAW], 25, 90, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); 1415 CG_SwingAngles( legsAngles[YAW], 40, 90, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); 1416 1417 torsoAngles[YAW] = cent->pe.torso.yawAngle; 1418 legsAngles[YAW] = cent->pe.legs.yawAngle; 1419 1420 1421 // --------- pitch ------------- 1422 1423 // only show a fraction of the pitch angle in the torso 1424 if ( headAngles[PITCH] > 180 ) { 1425 dest = (-360 + headAngles[PITCH]) * 0.75f; 1426 } else { 1427 dest = headAngles[PITCH] * 0.75f; 1428 } 1429 CG_SwingAngles( dest, 15, 30, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching ); 1430 torsoAngles[PITCH] = cent->pe.torso.pitchAngle; 1431 1432 // 1433 clientNum = cent->currentState.clientNum; 1434 if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { 1435 ci = &cgs.clientinfo[ clientNum ]; 1436 if ( ci->fixedtorso ) { 1437 torsoAngles[PITCH] = 0.0f; 1438 } 1439 } 1440 1441 // --------- roll ------------- 1442 1443 1444 // lean towards the direction of travel 1445 VectorCopy( cent->currentState.pos.trDelta, velocity ); 1446 speed = VectorNormalize( velocity ); 1447 if ( speed ) { 1448 vec3_t axis[3]; 1449 float side; 1450 1451 speed *= 0.05f; 1452 1453 AnglesToAxis( legsAngles, axis ); 1454 side = speed * DotProduct( velocity, axis[1] ); 1455 legsAngles[ROLL] -= side; 1456 1457 side = speed * DotProduct( velocity, axis[0] ); 1458 legsAngles[PITCH] += side; 1459 } 1460 1461 // 1462 clientNum = cent->currentState.clientNum; 1463 if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { 1464 ci = &cgs.clientinfo[ clientNum ]; 1465 if ( ci->fixedlegs ) { 1466 legsAngles[YAW] = torsoAngles[YAW]; 1467 legsAngles[PITCH] = 0.0f; 1468 legsAngles[ROLL] = 0.0f; 1469 } 1470 } 1471 1472 // pain twitch 1473 CG_AddPainTwitch( cent, torsoAngles ); 1474 1475 // pull the angles back out of the hierarchial chain 1476 AnglesSubtract( headAngles, torsoAngles, headAngles ); 1477 AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); 1478 AnglesToAxis( legsAngles, legs ); 1479 AnglesToAxis( torsoAngles, torso ); 1480 AnglesToAxis( headAngles, head ); 1481 } 1482 1483 1484 //========================================================================== 1485 1486 /* 1487 =============== 1488 CG_HasteTrail 1489 =============== 1490 */ 1491 static void CG_HasteTrail( centity_t *cent ) { 1492 localEntity_t *smoke; 1493 vec3_t origin; 1494 int anim; 1495 1496 if ( cent->trailTime > cg.time ) { 1497 return; 1498 } 1499 anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; 1500 if ( anim != LEGS_RUN && anim != LEGS_BACK ) { 1501 return; 1502 } 1503 1504 cent->trailTime += 100; 1505 if ( cent->trailTime < cg.time ) { 1506 cent->trailTime = cg.time; 1507 } 1508 1509 VectorCopy( cent->lerpOrigin, origin ); 1510 origin[2] -= 16; 1511 1512 smoke = CG_SmokePuff( origin, vec3_origin, 1513 8, 1514 1, 1, 1, 1, 1515 500, 1516 cg.time, 1517 0, 1518 0, 1519 cgs.media.hastePuffShader ); 1520 1521 // use the optimized local entity add 1522 smoke->leType = LE_SCALE_FADE; 1523 } 1524 1525 #ifdef MISSIONPACK 1526 /* 1527 =============== 1528 CG_BreathPuffs 1529 =============== 1530 */ 1531 static void CG_BreathPuffs( centity_t *cent, refEntity_t *head) { 1532 clientInfo_t *ci; 1533 vec3_t up, origin; 1534 int contents; 1535 1536 ci = &cgs.clientinfo[ cent->currentState.number ]; 1537 1538 if (!cg_enableBreath.integer) { 1539 return; 1540 } 1541 if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson) { 1542 return; 1543 } 1544 if ( cent->currentState.eFlags & EF_DEAD ) { 1545 return; 1546 } 1547 contents = trap_CM_PointContents( head->origin, 0 ); 1548 if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { 1549 return; 1550 } 1551 if ( ci->breathPuffTime > cg.time ) { 1552 return; 1553 } 1554 1555 VectorSet( up, 0, 0, 8 ); 1556 VectorMA(head->origin, 8, head->axis[0], origin); 1557 VectorMA(origin, -4, head->axis[2], origin); 1558 CG_SmokePuff( origin, up, 16, 1, 1, 1, 0.66f, 1500, cg.time, cg.time + 400, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); 1559 ci->breathPuffTime = cg.time + 2000; 1560 } 1561 1562 /* 1563 =============== 1564 CG_DustTrail 1565 =============== 1566 */ 1567 static void CG_DustTrail( centity_t *cent ) { 1568 int anim; 1569 localEntity_t *dust; 1570 vec3_t end, vel; 1571 trace_t tr; 1572 1573 if (!cg_enableDust.integer) 1574 return; 1575 1576 if ( cent->dustTrailTime > cg.time ) { 1577 return; 1578 } 1579 1580 anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; 1581 if ( anim != LEGS_LANDB && anim != LEGS_LAND ) { 1582 return; 1583 } 1584 1585 cent->dustTrailTime += 40; 1586 if ( cent->dustTrailTime < cg.time ) { 1587 cent->dustTrailTime = cg.time; 1588 } 1589 1590 VectorCopy(cent->currentState.pos.trBase, end); 1591 end[2] -= 64; 1592 CG_Trace( &tr, cent->currentState.pos.trBase, NULL, NULL, end, cent->currentState.number, MASK_PLAYERSOLID ); 1593 1594 if ( !(tr.surfaceFlags & SURF_DUST) ) 1595 return; 1596 1597 VectorCopy( cent->currentState.pos.trBase, end ); 1598 end[2] -= 16; 1599 1600 VectorSet(vel, 0, 0, -30); 1601 dust = CG_SmokePuff( end, vel, 1602 24, 1603 .8f, .8f, 0.7f, 0.33f, 1604 500, 1605 cg.time, 1606 0, 1607 0, 1608 cgs.media.dustPuffShader ); 1609 } 1610 1611 #endif 1612 1613 /* 1614 =============== 1615 CG_TrailItem 1616 =============== 1617 */ 1618 static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) { 1619 refEntity_t ent; 1620 vec3_t angles; 1621 vec3_t axis[3]; 1622 1623 VectorCopy( cent->lerpAngles, angles ); 1624 angles[PITCH] = 0; 1625 angles[ROLL] = 0; 1626 AnglesToAxis( angles, axis ); 1627 1628 memset( &ent, 0, sizeof( ent ) ); 1629 VectorMA( cent->lerpOrigin, -16, axis[0], ent.origin ); 1630 ent.origin[2] += 16; 1631 angles[YAW] += 90; 1632 AnglesToAxis( angles, ent.axis ); 1633 1634 ent.hModel = hModel; 1635 trap_R_AddRefEntityToScene( &ent ); 1636 } 1637 1638 1639 /* 1640 =============== 1641 CG_PlayerFlag 1642 =============== 1643 */ 1644 static void CG_PlayerFlag( centity_t *cent, qhandle_t hSkin, refEntity_t *torso ) { 1645 clientInfo_t *ci; 1646 refEntity_t pole; 1647 refEntity_t flag; 1648 vec3_t angles, dir; 1649 int legsAnim, flagAnim, updateangles; 1650 float angle, d; 1651 1652 // show the flag pole model 1653 memset( &pole, 0, sizeof(pole) ); 1654 pole.hModel = cgs.media.flagPoleModel; 1655 VectorCopy( torso->lightingOrigin, pole.lightingOrigin ); 1656 pole.shadowPlane = torso->shadowPlane; 1657 pole.renderfx = torso->renderfx; 1658 CG_PositionEntityOnTag( &pole, torso, torso->hModel, "tag_flag" ); 1659 trap_R_AddRefEntityToScene( &pole ); 1660 1661 // show the flag model 1662 memset( &flag, 0, sizeof(flag) ); 1663 flag.hModel = cgs.media.flagFlapModel; 1664 flag.customSkin = hSkin; 1665 VectorCopy( torso->lightingOrigin, flag.lightingOrigin ); 1666 flag.shadowPlane = torso->shadowPlane; 1667 flag.renderfx = torso->renderfx; 1668 1669 VectorClear(angles); 1670 1671 updateangles = qfalse; 1672 legsAnim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; 1673 if( legsAnim == LEGS_IDLE || legsAnim == LEGS_IDLECR ) { 1674 flagAnim = FLAG_STAND; 1675 } else if ( legsAnim == LEGS_WALK || legsAnim == LEGS_WALKCR ) { 1676 flagAnim = FLAG_STAND; 1677 updateangles = qtrue; 1678 } else { 1679 flagAnim = FLAG_RUN; 1680 updateangles = qtrue; 1681 } 1682 1683 if ( updateangles ) { 1684 1685 VectorCopy( cent->currentState.pos.trDelta, dir ); 1686 // add gravity 1687 dir[2] += 100; 1688 VectorNormalize( dir ); 1689 d = DotProduct(pole.axis[2], dir); 1690 // if there is anough movement orthogonal to the flag pole 1691 if (fabs(d) < 0.9) { 1692 // 1693 d = DotProduct(pole.axis[0], dir); 1694 if (d > 1.0f) { 1695 d = 1.0f; 1696 } 1697 else if (d < -1.0f) { 1698 d = -1.0f; 1699 } 1700 angle = acos(d); 1701 1702 d = DotProduct(pole.axis[1], dir); 1703 if (d < 0) { 1704 angles[YAW] = 360 - angle * 180 / M_PI; 1705 } 1706 else { 1707 angles[YAW] = angle * 180 / M_PI; 1708 } 1709 if (angles[YAW] < 0) 1710 angles[YAW] += 360; 1711 if (angles[YAW] > 360) 1712 angles[YAW] -= 360; 1713 1714 //vectoangles( cent->currentState.pos.trDelta, tmpangles ); 1715 //angles[YAW] = tmpangles[YAW] + 45 - cent->pe.torso.yawAngle; 1716 // change the yaw angle 1717 CG_SwingAngles( angles[YAW], 25, 90, 0.15f, ¢->pe.flag.yawAngle, ¢->pe.flag.yawing ); 1718 } 1719 1720 /* 1721 d = DotProduct(pole.axis[2], dir); 1722 angle = Q_acos(d); 1723 1724 d = DotProduct(pole.axis[1], dir); 1725 if (d < 0) { 1726 angle = 360 - angle * 180 / M_PI; 1727 } 1728 else { 1729 angle = angle * 180 / M_PI; 1730 } 1731 if (angle > 340 && angle < 20) { 1732 flagAnim = FLAG_RUNUP; 1733 } 1734 if (angle > 160 && angle < 200) { 1735 flagAnim = FLAG_RUNDOWN; 1736 } 1737 */ 1738 } 1739 1740 // set the yaw angle 1741 angles[YAW] = cent->pe.flag.yawAngle; 1742 // lerp the flag animation frames 1743 ci = &cgs.clientinfo[ cent->currentState.clientNum ]; 1744 CG_RunLerpFrame( ci, ¢->pe.flag, flagAnim, 1 ); 1745 flag.oldframe = cent->pe.flag.oldFrame; 1746 flag.frame = cent->pe.flag.frame; 1747 flag.backlerp = cent->pe.flag.backlerp; 1748 1749 AnglesToAxis( angles, flag.axis ); 1750 CG_PositionRotatedEntityOnTag( &flag, &pole, pole.hModel, "tag_flag" ); 1751 1752 trap_R_AddRefEntityToScene( &flag ); 1753 } 1754 1755 1756 #ifdef MISSIONPACK // bk001204 1757 /* 1758 =============== 1759 CG_PlayerTokens 1760 =============== 1761 */ 1762 static void CG_PlayerTokens( centity_t *cent, int renderfx ) { 1763 int tokens, i, j; 1764 float angle; 1765 refEntity_t ent; 1766 vec3_t dir, origin; 1767 skulltrail_t *trail; 1768 trail = &cg.skulltrails[cent->currentState.number]; 1769 tokens = cent->currentState.generic1; 1770 if ( !tokens ) { 1771 trail->numpositions = 0; 1772 return; 1773 } 1774 1775 if ( tokens > MAX_SKULLTRAIL ) { 1776 tokens = MAX_SKULLTRAIL; 1777 } 1778 1779 // add skulls if there are more than last time 1780 for (i = 0; i < tokens - trail->numpositions; i++) { 1781 for (j = trail->numpositions; j > 0; j--) { 1782 VectorCopy(trail->positions[j-1], trail->positions[j]); 1783 } 1784 VectorCopy(cent->lerpOrigin, trail->positions[0]); 1785 } 1786 trail->numpositions = tokens; 1787 1788 // move all the skulls along the trail 1789 VectorCopy(cent->lerpOrigin, origin); 1790 for (i = 0; i < trail->numpositions; i++) { 1791 VectorSubtract(trail->positions[i], origin, dir); 1792 if (VectorNormalize(dir) > 30) { 1793 VectorMA(origin, 30, dir, trail->positions[i]); 1794 } 1795 VectorCopy(trail->positions[i], origin); 1796 } 1797 1798 memset( &ent, 0, sizeof( ent ) ); 1799 if( cgs.clientinfo[ cent->currentState.clientNum ].team == TEAM_BLUE ) { 1800 ent.hModel = cgs.media.redCubeModel; 1801 } else { 1802 ent.hModel = cgs.media.blueCubeModel; 1803 } 1804 ent.renderfx = renderfx; 1805 1806 VectorCopy(cent->lerpOrigin, origin); 1807 for (i = 0; i < trail->numpositions; i++) { 1808 VectorSubtract(origin, trail->positions[i], ent.axis[0]); 1809 ent.axis[0][2] = 0; 1810 VectorNormalize(ent.axis[0]); 1811 VectorSet(ent.axis[2], 0, 0, 1); 1812 CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]); 1813 1814 VectorCopy(trail->positions[i], ent.origin); 1815 angle = (((cg.time + 500 * MAX_SKULLTRAIL - 500 * i) / 16) & 255) * (M_PI * 2) / 255; 1816 ent.origin[2] += sin(angle) * 10; 1817 trap_R_AddRefEntityToScene( &ent ); 1818 VectorCopy(trail->positions[i], origin); 1819 } 1820 } 1821 #endif 1822 1823 1824 /* 1825 =============== 1826 CG_PlayerPowerups 1827 =============== 1828 */ 1829 static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) { 1830 int powerups; 1831 clientInfo_t *ci; 1832 1833 powerups = cent->currentState.powerups; 1834 if ( !powerups ) { 1835 return; 1836 } 1837 1838 // quad gives a dlight 1839 if ( powerups & ( 1 << PW_QUAD ) ) { 1840 trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1 ); 1841 } 1842 1843 // flight plays a looped sound 1844 if ( powerups & ( 1 << PW_FLIGHT ) ) { 1845 trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flightSound ); 1846 } 1847 1848 ci = &cgs.clientinfo[ cent->currentState.clientNum ]; 1849 // redflag 1850 if ( powerups & ( 1 << PW_REDFLAG ) ) { 1851 if (ci->newAnims) { 1852 CG_PlayerFlag( cent, cgs.media.redFlagFlapSkin, torso ); 1853 } 1854 else { 1855 CG_TrailItem( cent, cgs.media.redFlagModel ); 1856 } 1857 trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f ); 1858 } 1859 1860 // blueflag 1861 if ( powerups & ( 1 << PW_BLUEFLAG ) ) { 1862 if (ci->newAnims){ 1863 CG_PlayerFlag( cent, cgs.media.blueFlagFlapSkin, torso ); 1864 } 1865 else { 1866 CG_TrailItem( cent, cgs.media.blueFlagModel ); 1867 } 1868 trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 ); 1869 } 1870 1871 // neutralflag 1872 if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) { 1873 if (ci->newAnims) { 1874 CG_PlayerFlag( cent, cgs.media.neutralFlagFlapSkin, torso ); 1875 } 1876 else { 1877 CG_TrailItem( cent, cgs.media.neutralFlagModel ); 1878 } 1879 trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 1.0, 1.0 ); 1880 } 1881 1882 // haste leaves smoke trails 1883 if ( powerups & ( 1 << PW_HASTE ) ) { 1884 CG_HasteTrail( cent ); 1885 } 1886 } 1887 1888 1889 /* 1890 =============== 1891 CG_PlayerFloatSprite 1892 1893 Float a sprite over the player's head 1894 =============== 1895 */ 1896 static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) { 1897 int rf; 1898 refEntity_t ent; 1899 1900 if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { 1901 rf = RF_THIRD_PERSON; // only show in mirrors 1902 } else { 1903 rf = 0; 1904 } 1905 1906 memset( &ent, 0, sizeof( ent ) ); 1907 VectorCopy( cent->lerpOrigin, ent.origin ); 1908 ent.origin[2] += 48; 1909 ent.reType = RT_SPRITE; 1910 ent.customShader = shader; 1911 ent.radius = 10; 1912 ent.renderfx = rf; 1913 ent.shaderRGBA[0] = 255; 1914 ent.shaderRGBA[1] = 255; 1915 ent.shaderRGBA[2] = 255; 1916 ent.shaderRGBA[3] = 255; 1917 trap_R_AddRefEntityToScene( &ent ); 1918 } 1919 1920 1921 1922 /* 1923 =============== 1924 CG_PlayerSprites 1925 1926 Float sprites over the player's head 1927 =============== 1928 */ 1929 static void CG_PlayerSprites( centity_t *cent ) { 1930 int team; 1931 1932 if ( cent->currentState.eFlags & EF_CONNECTION ) { 1933 CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); 1934 return; 1935 } 1936 1937 if ( cent->currentState.eFlags & EF_TALK ) { 1938 CG_PlayerFloatSprite( cent, cgs.media.balloonShader ); 1939 return; 1940 } 1941 1942 if ( cent->currentState.eFlags & EF_AWARD_IMPRESSIVE ) { 1943 CG_PlayerFloatSprite( cent, cgs.media.medalImpressive ); 1944 return; 1945 } 1946 1947 if ( cent->currentState.eFlags & EF_AWARD_EXCELLENT ) { 1948 CG_PlayerFloatSprite( cent, cgs.media.medalExcellent ); 1949 return; 1950 } 1951 1952 if ( cent->currentState.eFlags & EF_AWARD_GAUNTLET ) { 1953 CG_PlayerFloatSprite( cent, cgs.media.medalGauntlet ); 1954 return; 1955 } 1956 1957 if ( cent->currentState.eFlags & EF_AWARD_DEFEND ) { 1958 CG_PlayerFloatSprite( cent, cgs.media.medalDefend ); 1959 return; 1960 } 1961 1962 if ( cent->currentState.eFlags & EF_AWARD_ASSIST ) { 1963 CG_PlayerFloatSprite( cent, cgs.media.medalAssist ); 1964 return; 1965 } 1966 1967 if ( cent->currentState.eFlags & EF_AWARD_CAP ) { 1968 CG_PlayerFloatSprite( cent, cgs.media.medalCapture ); 1969 return; 1970 } 1971 1972 team = cgs.clientinfo[ cent->currentState.clientNum ].team; 1973 if ( !(cent->currentState.eFlags & EF_DEAD) && 1974 cg.snap->ps.persistant[PERS_TEAM] == team && 1975 cgs.gametype >= GT_TEAM) { 1976 if (cg_drawFriend.integer) { 1977 CG_PlayerFloatSprite( cent, cgs.media.friendShader ); 1978 } 1979 return; 1980 } 1981 } 1982 1983 /* 1984 =============== 1985 CG_PlayerShadow 1986 1987 Returns the Z component of the surface being shadowed 1988 1989 should it return a full plane instead of a Z? 1990 =============== 1991 */ 1992 #define SHADOW_DISTANCE 128 1993 static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) { 1994 vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2}; 1995 trace_t trace; 1996 float alpha; 1997 1998 *shadowPlane = 0; 1999 2000 if ( cg_shadows.integer == 0 ) { 2001 return qfalse; 2002 } 2003 2004 // no shadows when invisible 2005 if ( cent->currentState.powerups & ( 1 << PW_INVIS ) ) { 2006 return qfalse; 2007 } 2008 2009 // send a trace down from the player to the ground 2010 VectorCopy( cent->lerpOrigin, end ); 2011 end[2] -= SHADOW_DISTANCE; 2012 2013 trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); 2014 2015 // no shadow if too high 2016 if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) { 2017 return qfalse; 2018 } 2019 2020 *shadowPlane = trace.endpos[2] + 1; 2021 2022 if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows 2023 return qtrue; 2024 } 2025 2026 // fade the shadow out with height 2027 alpha = 1.0 - trace.fraction; 2028 2029 // bk0101022 - hack / FPE - bogus planes? 2030 //assert( DotProduct( trace.plane.normal, trace.plane.normal ) != 0.0f ) 2031 2032 // add the mark as a temporary, so it goes directly to the renderer 2033 // without taking a spot in the cg_marks array 2034 CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, 2035 cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, 24, qtrue ); 2036 2037 return qtrue; 2038 } 2039 2040 2041 /* 2042 =============== 2043 CG_PlayerSplash 2044 2045 Draw a mark at the water surface 2046 =============== 2047 */ 2048 static void CG_PlayerSplash( centity_t *cent ) { 2049 vec3_t start, end; 2050 trace_t trace; 2051 int contents; 2052 polyVert_t verts[4]; 2053 2054 if ( !cg_shadows.integer ) { 2055 return; 2056 } 2057 2058 VectorCopy( cent->lerpOrigin, end ); 2059 end[2] -= 24; 2060 2061 // if the feet aren't in liquid, don't make a mark 2062 // this won't handle moving water brushes, but they wouldn't draw right anyway... 2063 contents = trap_CM_PointContents( end, 0 ); 2064 if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { 2065 return; 2066 } 2067 2068 VectorCopy( cent->lerpOrigin, start ); 2069 start[2] += 32; 2070 2071 // if the head isn't out of liquid, don't make a mark 2072 contents = trap_CM_PointContents( start, 0 ); 2073 if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { 2074 return; 2075 } 2076 2077 // trace down to find the surface 2078 trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ); 2079 2080 if ( trace.fraction == 1.0 ) { 2081 return; 2082 } 2083 2084 // create a mark polygon 2085 VectorCopy( trace.endpos, verts[0].xyz ); 2086 verts[0].xyz[0] -= 32; 2087 verts[0].xyz[1] -= 32; 2088 verts[0].st[0] = 0; 2089 verts[0].st[1] = 0; 2090 verts[0].modulate[0] = 255; 2091 verts[0].modulate[1] = 255; 2092 verts[0].modulate[2] = 255; 2093 verts[0].modulate[3] = 255; 2094 2095 VectorCopy( trace.endpos, verts[1].xyz ); 2096 verts[1].xyz[0] -= 32; 2097 verts[1].xyz[1] += 32; 2098 verts[1].st[0] = 0; 2099 verts[1].st[1] = 1; 2100 verts[1].modulate[0] = 255; 2101 verts[1].modulate[1] = 255; 2102 verts[1].modulate[2] = 255; 2103 verts[1].modulate[3] = 255; 2104 2105 VectorCopy( trace.endpos, verts[2].xyz ); 2106 verts[2].xyz[0] += 32; 2107 verts[2].xyz[1] += 32; 2108 verts[2].st[0] = 1; 2109 verts[2].st[1] = 1; 2110 verts[2].modulate[0] = 255; 2111 verts[2].modulate[1] = 255; 2112 verts[2].modulate[2] = 255; 2113 verts[2].modulate[3] = 255; 2114 2115 VectorCopy( trace.endpos, verts[3].xyz ); 2116 verts[3].xyz[0] += 32; 2117 verts[3].xyz[1] -= 32; 2118 verts[3].st[0] = 1; 2119 verts[3].st[1] = 0; 2120 verts[3].modulate[0] = 255; 2121 verts[3].modulate[1] = 255; 2122 verts[3].modulate[2] = 255; 2123 verts[3].modulate[3] = 255; 2124 2125 trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts ); 2126 } 2127 2128 2129 2130 /* 2131 =============== 2132 CG_AddRefEntityWithPowerups 2133 2134 Adds a piece with modifications or duplications for powerups 2135 Also called by CG_Missile for quad rockets, but nobody can tell... 2136 =============== 2137 */ 2138 void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ) { 2139 2140 if ( state->powerups & ( 1 << PW_INVIS ) ) { 2141 ent->customShader = cgs.media.invisShader; 2142 trap_R_AddRefEntityToScene( ent ); 2143 } else { 2144 /* 2145 if ( state->eFlags & EF_KAMIKAZE ) { 2146 if (team == TEAM_BLUE) 2147 ent->customShader = cgs.media.blueKamikazeShader; 2148 else 2149 ent->customShader = cgs.media.redKamikazeShader; 2150 trap_R_AddRefEntityToScene( ent ); 2151 } 2152 else {*/ 2153 trap_R_AddRefEntityToScene( ent ); 2154 //} 2155 2156 if ( state->powerups & ( 1 << PW_QUAD ) ) 2157 { 2158 if (team == TEAM_RED) 2159 ent->customShader = cgs.media.redQuadShader; 2160 else 2161 ent->customShader = cgs.media.quadShader; 2162 trap_R_AddRefEntityToScene( ent ); 2163 } 2164 if ( state->powerups & ( 1 << PW_REGEN ) ) { 2165 if ( ( ( cg.time / 100 ) % 10 ) == 1 ) { 2166 ent->customShader = cgs.media.regenShader; 2167 trap_R_AddRefEntityToScene( ent ); 2168 } 2169 } 2170 if ( state->powerups & ( 1 << PW_BATTLESUIT ) ) { 2171 ent->customShader = cgs.media.battleSuitShader; 2172 trap_R_AddRefEntityToScene( ent ); 2173 } 2174 } 2175 } 2176 2177 /* 2178 ================= 2179 CG_LightVerts 2180 ================= 2181 */ 2182 int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts ) 2183 { 2184 int i, j; 2185 float incoming; 2186 vec3_t ambientLight; 2187 vec3_t lightDir; 2188 vec3_t directedLight; 2189 2190 trap_R_LightForPoint( verts[0].xyz, ambientLight, directedLight, lightDir ); 2191 2192 for (i = 0; i < numVerts; i++) { 2193 incoming = DotProduct (normal, lightDir); 2194 if ( incoming <= 0 ) { 2195 verts[i].modulate[0] = ambientLight[0]; 2196 verts[i].modulate[1] = ambientLight[1]; 2197 verts[i].modulate[2] = ambientLight[2]; 2198 verts[i].modulate[3] = 255; 2199 continue; 2200 } 2201 j = ( ambientLight[0] + incoming * directedLight[0] ); 2202 if ( j > 255 ) { 2203 j = 255; 2204 } 2205 verts[i].modulate[0] = j; 2206 2207 j = ( ambientLight[1] + incoming * directedLight[1] ); 2208 if ( j > 255 ) { 2209 j = 255; 2210 } 2211 verts[i].modulate[1] = j; 2212 2213 j = ( ambientLight[2] + incoming * directedLight[2] ); 2214 if ( j > 255 ) { 2215 j = 255; 2216 } 2217 verts[i].modulate[2] = j; 2218 2219 verts[i].modulate[3] = 255; 2220 } 2221 return qtrue; 2222 } 2223 2224 /* 2225 =============== 2226 CG_Player 2227 =============== 2228 */ 2229 void CG_Player( centity_t *cent ) { 2230 clientInfo_t *ci; 2231 refEntity_t legs; 2232 refEntity_t torso; 2233 refEntity_t head; 2234 int clientNum; 2235 int renderfx; 2236 qboolean shadow; 2237 float shadowPlane; 2238 #ifdef MISSIONPACK 2239 refEntity_t skull; 2240 refEntity_t powerup; 2241 int t; 2242 float c; 2243 float angle; 2244 vec3_t dir, angles; 2245 #endif 2246 2247 // the client number is stored in clientNum. It can't be derived 2248 // from the entity number, because a single client may have 2249 // multiple corpses on the level using the same clientinfo 2250 clientNum = cent->currentState.clientNum; 2251 if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { 2252 CG_Error( "Bad clientNum on player entity"); 2253 } 2254 ci = &cgs.clientinfo[ clientNum ]; 2255 2256 // it is possible to see corpses from disconnected players that may 2257 // not have valid clientinfo 2258 if ( !ci->infoValid ) { 2259 return; 2260 } 2261 2262 // get the player model information 2263 renderfx = 0; 2264 if ( cent->currentState.number == cg.snap->ps.clientNum) { 2265 if (!cg.renderingThirdPerson) { 2266 renderfx = RF_THIRD_PERSON; // only draw in mirrors 2267 } else { 2268 if (cg_cameraMode.integer) { 2269 return; 2270 } 2271 } 2272 } 2273 2274 2275 memset( &legs, 0, sizeof(legs) ); 2276 memset( &torso, 0, sizeof(torso) ); 2277 memset( &head, 0, sizeof(head) ); 2278 2279 // get the rotation information 2280 CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis ); 2281 2282 // get the animation state (after rotation, to allow feet shuffle) 2283 CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, 2284 &torso.oldframe, &torso.frame, &torso.backlerp ); 2285 2286 // add the talk baloon or disconnect icon 2287 CG_PlayerSprites( cent ); 2288 2289 // add the shadow 2290 shadow = CG_PlayerShadow( cent, &shadowPlane ); 2291 2292 // add a water splash if partially in and out of water 2293 CG_PlayerSplash( cent ); 2294 2295 if ( cg_shadows.integer == 3 && shadow ) { 2296 renderfx |= RF_SHADOW_PLANE; 2297 } 2298 renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all 2299 #ifdef MISSIONPACK 2300 if( cgs.gametype == GT_HARVESTER ) { 2301 CG_PlayerTokens( cent, renderfx ); 2302 } 2303 #endif 2304 // 2305 // add the legs 2306 // 2307 legs.hModel = ci->legsModel; 2308 legs.customSkin = ci->legsSkin; 2309 2310 VectorCopy( cent->lerpOrigin, legs.origin ); 2311 2312 VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); 2313 legs.shadowPlane = shadowPlane; 2314 legs.renderfx = renderfx; 2315 VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all 2316 2317 CG_AddRefEntityWithPowerups( &legs, ¢->currentState, ci->team ); 2318 2319 // if the model failed, allow the default nullmodel to be displayed 2320 if (!legs.hModel) { 2321 return; 2322 } 2323 2324 // 2325 // add the torso 2326 // 2327 torso.hModel = ci->torsoModel; 2328 if (!torso.hModel) { 2329 return; 2330 } 2331 2332 torso.customSkin = ci->torsoSkin; 2333 2334 VectorCopy( cent->lerpOrigin, torso.lightingOrigin ); 2335 2336 CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso"); 2337 2338 torso.shadowPlane = shadowPlane; 2339 torso.renderfx = renderfx; 2340 2341 CG_AddRefEntityWithPowerups( &torso, ¢->currentState, ci->team ); 2342 2343 #ifdef MISSIONPACK 2344 if ( cent->currentState.eFlags & EF_KAMIKAZE ) { 2345 2346 memset( &skull, 0, sizeof(skull) ); 2347 2348 VectorCopy( cent->lerpOrigin, skull.lightingOrigin ); 2349 skull.shadowPlane = shadowPlane; 2350 skull.renderfx = renderfx; 2351 2352 if ( cent->currentState.eFlags & EF_DEAD ) { 2353 // one skull bobbing above the dead body 2354 angle = ((cg.time / 7) & 255) * (M_PI * 2) / 255; 2355 if (angle > M_PI * 2) 2356 angle -= (float)M_PI * 2; 2357 dir[0] = sin(angle) * 20; 2358 dir[1] = cos(angle) * 20; 2359 angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; 2360 dir[2] = 15 + sin(angle) * 8; 2361 VectorAdd(torso.origin, dir, skull.origin); 2362 2363 dir[2] = 0; 2364 VectorCopy(dir, skull.axis[1]); 2365 VectorNormalize(skull.axis[1]); 2366 VectorSet(skull.axis[2], 0, 0, 1); 2367 CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); 2368 2369 skull.hModel = cgs.media.kamikazeHeadModel; 2370 trap_R_AddRefEntityToScene( &skull ); 2371 skull.hModel = cgs.media.kamikazeHeadTrail; 2372 trap_R_AddRefEntityToScene( &skull ); 2373 } 2374 else { 2375 // three skulls spinning around the player 2376 angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; 2377 dir[0] = cos(angle) * 20; 2378 dir[1] = sin(angle) * 20; 2379 dir[2] = cos(angle) * 20; 2380 VectorAdd(torso.origin, dir, skull.origin); 2381 2382 angles[0] = sin(angle) * 30; 2383 angles[1] = (angle * 180 / M_PI) + 90; 2384 if (angles[1] > 360) 2385 angles[1] -= 360; 2386 angles[2] = 0; 2387 AnglesToAxis( angles, skull.axis ); 2388 2389 /* 2390 dir[2] = 0; 2391 VectorInverse(dir); 2392 VectorCopy(dir, skull.axis[1]); 2393 VectorNormalize(skull.axis[1]); 2394 VectorSet(skull.axis[2], 0, 0, 1); 2395 CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); 2396 */ 2397 2398 skull.hModel = cgs.media.kamikazeHeadModel; 2399 trap_R_AddRefEntityToScene( &skull ); 2400 // flip the trail because this skull is spinning in the other direction 2401 VectorInverse(skull.axis[1]); 2402 skull.hModel = cgs.media.kamikazeHeadTrail; 2403 trap_R_AddRefEntityToScene( &skull ); 2404 2405 angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255 + M_PI; 2406 if (angle > M_PI * 2) 2407 angle -= (float)M_PI * 2; 2408 dir[0] = sin(angle) * 20; 2409 dir[1] = cos(angle) * 20; 2410 dir[2] = cos(angle) * 20; 2411 VectorAdd(torso.origin, dir, skull.origin); 2412 2413 angles[0] = cos(angle - 0.5 * M_PI) * 30; 2414 angles[1] = 360 - (angle * 180 / M_PI); 2415 if (angles[1] > 360) 2416 angles[1] -= 360; 2417 angles[2] = 0; 2418 AnglesToAxis( angles, skull.axis ); 2419 2420 /* 2421 dir[2] = 0; 2422 VectorCopy(dir, skull.axis[1]); 2423 VectorNormalize(skull.axis[1]); 2424 VectorSet(skull.axis[2], 0, 0, 1); 2425 CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); 2426 */ 2427 2428 skull.hModel = cgs.media.kamikazeHeadModel; 2429 trap_R_AddRefEntityToScene( &skull ); 2430 skull.hModel = cgs.media.kamikazeHeadTrail; 2431 trap_R_AddRefEntityToScene( &skull ); 2432 2433 angle = ((cg.time / 3) & 255) * (M_PI * 2) / 255 + 0.5 * M_PI; 2434 if (angle > M_PI * 2) 2435 angle -= (float)M_PI * 2; 2436 dir[0] = sin(angle) * 20; 2437 dir[1] = cos(angle) * 20; 2438 dir[2] = 0; 2439 VectorAdd(torso.origin, dir, skull.origin); 2440 2441 VectorCopy(dir, skull.axis[1]); 2442 VectorNormalize(skull.axis[1]); 2443 VectorSet(skull.axis[2], 0, 0, 1); 2444 CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); 2445 2446 skull.hModel = cgs.media.kamikazeHeadModel; 2447 trap_R_AddRefEntityToScene( &skull ); 2448 skull.hModel = cgs.media.kamikazeHeadTrail; 2449 trap_R_AddRefEntityToScene( &skull ); 2450 } 2451 } 2452 2453 if ( cent->currentState.powerups & ( 1 << PW_GUARD ) ) { 2454 memcpy(&powerup, &torso, sizeof(torso)); 2455 powerup.hModel = cgs.media.guardPowerupModel; 2456 powerup.frame = 0; 2457 powerup.oldframe = 0; 2458 powerup.customSkin = 0; 2459 trap_R_AddRefEntityToScene( &powerup ); 2460 } 2461 if ( cent->currentState.powerups & ( 1 << PW_SCOUT ) ) { 2462 memcpy(&powerup, &torso, sizeof(torso)); 2463 powerup.hModel = cgs.media.scoutPowerupModel; 2464 powerup.frame = 0; 2465 powerup.oldframe = 0; 2466 powerup.customSkin = 0; 2467 trap_R_AddRefEntityToScene( &powerup ); 2468 } 2469 if ( cent->currentState.powerups & ( 1 << PW_DOUBLER ) ) { 2470 memcpy(&powerup, &torso, sizeof(torso)); 2471 powerup.hModel = cgs.media.doublerPowerupModel; 2472 powerup.frame = 0; 2473 powerup.oldframe = 0; 2474 powerup.customSkin = 0; 2475 trap_R_AddRefEntityToScene( &powerup ); 2476 } 2477 if ( cent->currentState.powerups & ( 1 << PW_AMMOREGEN ) ) { 2478 memcpy(&powerup, &torso, sizeof(torso)); 2479 powerup.hModel = cgs.media.ammoRegenPowerupModel; 2480 powerup.frame = 0; 2481 powerup.oldframe = 0; 2482 powerup.customSkin = 0; 2483 trap_R_AddRefEntityToScene( &powerup ); 2484 } 2485 if ( cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) { 2486 if ( !ci->invulnerabilityStartTime ) { 2487 ci->invulnerabilityStartTime = cg.time; 2488 } 2489 ci->invulnerabilityStopTime = cg.time; 2490 } 2491 else { 2492 ci->invulnerabilityStartTime = 0; 2493 } 2494 if ( (cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) || 2495 cg.time - ci->invulnerabilityStopTime < 250 ) { 2496 2497 memcpy(&powerup, &torso, sizeof(torso)); 2498 powerup.hModel = cgs.media.invulnerabilityPowerupModel; 2499 powerup.customSkin = 0; 2500 // always draw 2501 powerup.renderfx &= ~RF_THIRD_PERSON; 2502 VectorCopy(cent->lerpOrigin, powerup.origin); 2503 2504 if ( cg.time - ci->invulnerabilityStartTime < 250 ) { 2505 c = (float) (cg.time - ci->invulnerabilityStartTime) / 250; 2506 } 2507 else if (cg.time - ci->invulnerabilityStopTime < 250 ) { 2508 c = (float) (250 - (cg.time - ci->invulnerabilityStopTime)) / 250; 2509 } 2510 else { 2511 c = 1; 2512 } 2513 VectorSet( powerup.axis[0], c, 0, 0 ); 2514 VectorSet( powerup.axis[1], 0, c, 0 ); 2515 VectorSet( powerup.axis[2], 0, 0, c ); 2516 trap_R_AddRefEntityToScene( &powerup ); 2517 } 2518 2519 t = cg.time - ci->medkitUsageTime; 2520 if ( ci->medkitUsageTime && t < 500 ) { 2521 memcpy(&powerup, &torso, sizeof(torso)); 2522 powerup.hModel = cgs.media.medkitUsageModel; 2523 powerup.customSkin = 0; 2524 // always draw 2525 powerup.renderfx &= ~RF_THIRD_PERSON; 2526 VectorClear(angles); 2527 AnglesToAxis(angles, powerup.axis); 2528 VectorCopy(cent->lerpOrigin, powerup.origin); 2529 powerup.origin[2] += -24 + (float) t * 80 / 500; 2530 if ( t > 400 ) { 2531 c = (float) (t - 1000) * 0xff / 100; 2532 powerup.shaderRGBA[0] = 0xff - c; 2533 powerup.shaderRGBA[1] = 0xff - c; 2534 powerup.shaderRGBA[2] = 0xff - c; 2535 powerup.shaderRGBA[3] = 0xff - c; 2536 } 2537 else { 2538 powerup.shaderRGBA[0] = 0xff; 2539 powerup.shaderRGBA[1] = 0xff; 2540 powerup.shaderRGBA[2] = 0xff; 2541 powerup.shaderRGBA[3] = 0xff; 2542 } 2543 trap_R_AddRefEntityToScene( &powerup ); 2544 } 2545 #endif // MISSIONPACK 2546 2547 // 2548 // add the head 2549 // 2550 head.hModel = ci->headModel; 2551 if (!head.hModel) { 2552 return; 2553 } 2554 head.customSkin = ci->headSkin; 2555 2556 VectorCopy( cent->lerpOrigin, head.lightingOrigin ); 2557 2558 CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head"); 2559 2560 head.shadowPlane = shadowPlane; 2561 head.renderfx = renderfx; 2562 2563 CG_AddRefEntityWithPowerups( &head, ¢->currentState, ci->team ); 2564 2565 #ifdef MISSIONPACK 2566 CG_BreathPuffs(cent, &head); 2567 2568 CG_DustTrail(cent); 2569 #endif 2570 2571 // 2572 // add the gun / barrel / flash 2573 // 2574 CG_AddPlayerWeapon( &torso, NULL, cent, ci->team ); 2575 2576 // add powerups floating behind the player 2577 CG_PlayerPowerups( cent, &torso ); 2578 } 2579 2580 2581 //===================================================================== 2582 2583 /* 2584 =============== 2585 CG_ResetPlayerEntity 2586 2587 A player just came into view or teleported, so reset all animation info 2588 =============== 2589 */ 2590 void CG_ResetPlayerEntity( centity_t *cent ) { 2591 cent->errorTime = -99999; // guarantee no error decay added 2592 cent->extrapolated = qfalse; 2593 2594 CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim ); 2595 CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim ); 2596 2597 BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); 2598 BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); 2599 2600 VectorCopy( cent->lerpOrigin, cent->rawOrigin ); 2601 VectorCopy( cent->lerpAngles, cent->rawAngles ); 2602 2603 memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) ); 2604 cent->pe.legs.yawAngle = cent->rawAngles[YAW]; 2605 cent->pe.legs.yawing = qfalse; 2606 cent->pe.legs.pitchAngle = 0; 2607 cent->pe.legs.pitching = qfalse; 2608 2609 memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) ); 2610 cent->pe.torso.yawAngle = cent->rawAngles[YAW]; 2611 cent->pe.torso.yawing = qfalse; 2612 cent->pe.torso.pitchAngle = cent->rawAngles[PITCH]; 2613 cent->pe.torso.pitching = qfalse; 2614 2615 if ( cg_debugPosition.integer ) { 2616 CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); 2617 } 2618 } 2619