ui_players.c (34466B)
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 // ui_players.c 24 25 #include "ui_local.h" 26 27 28 #define UI_TIMER_GESTURE 2300 29 #define UI_TIMER_JUMP 1000 30 #define UI_TIMER_LAND 130 31 #define UI_TIMER_WEAPON_SWITCH 300 32 #define UI_TIMER_ATTACK 500 33 #define UI_TIMER_MUZZLE_FLASH 20 34 #define UI_TIMER_WEAPON_DELAY 250 35 36 #define JUMP_HEIGHT 56 37 38 #define SWINGSPEED 0.3f 39 40 #define SPIN_SPEED 0.9f 41 #define COAST_TIME 1000 42 43 44 static int dp_realtime; 45 static float jumpHeight; 46 sfxHandle_t weaponChangeSound; 47 48 49 /* 50 =============== 51 UI_PlayerInfo_SetWeapon 52 =============== 53 */ 54 static void UI_PlayerInfo_SetWeapon( playerInfo_t *pi, weapon_t weaponNum ) { 55 gitem_t * item; 56 char path[MAX_QPATH]; 57 58 pi->currentWeapon = weaponNum; 59 tryagain: 60 pi->realWeapon = weaponNum; 61 pi->weaponModel = 0; 62 pi->barrelModel = 0; 63 pi->flashModel = 0; 64 65 if ( weaponNum == WP_NONE ) { 66 return; 67 } 68 69 for ( item = bg_itemlist + 1; item->classname ; item++ ) { 70 if ( item->giType != IT_WEAPON ) { 71 continue; 72 } 73 if ( item->giTag == weaponNum ) { 74 break; 75 } 76 } 77 78 if ( item->classname ) { 79 pi->weaponModel = trap_R_RegisterModel( item->world_model[0] ); 80 } 81 82 if( pi->weaponModel == 0 ) { 83 if( weaponNum == WP_MACHINEGUN ) { 84 weaponNum = WP_NONE; 85 goto tryagain; 86 } 87 weaponNum = WP_MACHINEGUN; 88 goto tryagain; 89 } 90 91 if ( weaponNum == WP_MACHINEGUN || weaponNum == WP_GAUNTLET || weaponNum == WP_BFG ) { 92 strcpy( path, item->world_model[0] ); 93 COM_StripExtension( path, path ); 94 strcat( path, "_barrel.md3" ); 95 pi->barrelModel = trap_R_RegisterModel( path ); 96 } 97 98 strcpy( path, item->world_model[0] ); 99 COM_StripExtension( path, path ); 100 strcat( path, "_flash.md3" ); 101 pi->flashModel = trap_R_RegisterModel( path ); 102 103 switch( weaponNum ) { 104 case WP_GAUNTLET: 105 MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 ); 106 break; 107 108 case WP_MACHINEGUN: 109 MAKERGB( pi->flashDlightColor, 1, 1, 0 ); 110 break; 111 112 case WP_SHOTGUN: 113 MAKERGB( pi->flashDlightColor, 1, 1, 0 ); 114 break; 115 116 case WP_GRENADE_LAUNCHER: 117 MAKERGB( pi->flashDlightColor, 1, 0.7f, 0.5f ); 118 break; 119 120 case WP_ROCKET_LAUNCHER: 121 MAKERGB( pi->flashDlightColor, 1, 0.75f, 0 ); 122 break; 123 124 case WP_LIGHTNING: 125 MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 ); 126 break; 127 128 case WP_RAILGUN: 129 MAKERGB( pi->flashDlightColor, 1, 0.5f, 0 ); 130 break; 131 132 case WP_PLASMAGUN: 133 MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 ); 134 break; 135 136 case WP_BFG: 137 MAKERGB( pi->flashDlightColor, 1, 0.7f, 1 ); 138 break; 139 140 case WP_GRAPPLING_HOOK: 141 MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 ); 142 break; 143 144 default: 145 MAKERGB( pi->flashDlightColor, 1, 1, 1 ); 146 break; 147 } 148 } 149 150 151 /* 152 =============== 153 UI_ForceLegsAnim 154 =============== 155 */ 156 static void UI_ForceLegsAnim( playerInfo_t *pi, int anim ) { 157 pi->legsAnim = ( ( pi->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; 158 159 if ( anim == LEGS_JUMP ) { 160 pi->legsAnimationTimer = UI_TIMER_JUMP; 161 } 162 } 163 164 165 /* 166 =============== 167 UI_SetLegsAnim 168 =============== 169 */ 170 static void UI_SetLegsAnim( playerInfo_t *pi, int anim ) { 171 if ( pi->pendingLegsAnim ) { 172 anim = pi->pendingLegsAnim; 173 pi->pendingLegsAnim = 0; 174 } 175 UI_ForceLegsAnim( pi, anim ); 176 } 177 178 179 /* 180 =============== 181 UI_ForceTorsoAnim 182 =============== 183 */ 184 static void UI_ForceTorsoAnim( playerInfo_t *pi, int anim ) { 185 pi->torsoAnim = ( ( pi->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; 186 187 if ( anim == TORSO_GESTURE ) { 188 pi->torsoAnimationTimer = UI_TIMER_GESTURE; 189 } 190 191 if ( anim == TORSO_ATTACK || anim == TORSO_ATTACK2 ) { 192 pi->torsoAnimationTimer = UI_TIMER_ATTACK; 193 } 194 } 195 196 197 /* 198 =============== 199 UI_SetTorsoAnim 200 =============== 201 */ 202 static void UI_SetTorsoAnim( playerInfo_t *pi, int anim ) { 203 if ( pi->pendingTorsoAnim ) { 204 anim = pi->pendingTorsoAnim; 205 pi->pendingTorsoAnim = 0; 206 } 207 208 UI_ForceTorsoAnim( pi, anim ); 209 } 210 211 212 /* 213 =============== 214 UI_TorsoSequencing 215 =============== 216 */ 217 static void UI_TorsoSequencing( playerInfo_t *pi ) { 218 int currentAnim; 219 220 currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; 221 222 if ( pi->weapon != pi->currentWeapon ) { 223 if ( currentAnim != TORSO_DROP ) { 224 pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH; 225 UI_ForceTorsoAnim( pi, TORSO_DROP ); 226 } 227 } 228 229 if ( pi->torsoAnimationTimer > 0 ) { 230 return; 231 } 232 233 if( currentAnim == TORSO_GESTURE ) { 234 UI_SetTorsoAnim( pi, TORSO_STAND ); 235 return; 236 } 237 238 if( currentAnim == TORSO_ATTACK || currentAnim == TORSO_ATTACK2 ) { 239 UI_SetTorsoAnim( pi, TORSO_STAND ); 240 return; 241 } 242 243 if ( currentAnim == TORSO_DROP ) { 244 UI_PlayerInfo_SetWeapon( pi, pi->weapon ); 245 pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH; 246 UI_ForceTorsoAnim( pi, TORSO_RAISE ); 247 return; 248 } 249 250 if ( currentAnim == TORSO_RAISE ) { 251 UI_SetTorsoAnim( pi, TORSO_STAND ); 252 return; 253 } 254 } 255 256 257 /* 258 =============== 259 UI_LegsSequencing 260 =============== 261 */ 262 static void UI_LegsSequencing( playerInfo_t *pi ) { 263 int currentAnim; 264 265 currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT; 266 267 if ( pi->legsAnimationTimer > 0 ) { 268 if ( currentAnim == LEGS_JUMP ) { 269 jumpHeight = JUMP_HEIGHT * sin( M_PI * ( UI_TIMER_JUMP - pi->legsAnimationTimer ) / UI_TIMER_JUMP ); 270 } 271 return; 272 } 273 274 if ( currentAnim == LEGS_JUMP ) { 275 UI_ForceLegsAnim( pi, LEGS_LAND ); 276 pi->legsAnimationTimer = UI_TIMER_LAND; 277 jumpHeight = 0; 278 return; 279 } 280 281 if ( currentAnim == LEGS_LAND ) { 282 UI_SetLegsAnim( pi, LEGS_IDLE ); 283 return; 284 } 285 } 286 287 288 /* 289 ====================== 290 UI_PositionEntityOnTag 291 ====================== 292 */ 293 static void UI_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, 294 clipHandle_t parentModel, char *tagName ) { 295 int i; 296 orientation_t lerped; 297 298 // lerp the tag 299 trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, 300 1.0 - parent->backlerp, tagName ); 301 302 // FIXME: allow origin offsets along tag? 303 VectorCopy( parent->origin, entity->origin ); 304 for ( i = 0 ; i < 3 ; i++ ) { 305 VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); 306 } 307 308 // cast away const because of compiler problems 309 MatrixMultiply( lerped.axis, ((refEntity_t*)parent)->axis, entity->axis ); 310 entity->backlerp = parent->backlerp; 311 } 312 313 314 /* 315 ====================== 316 UI_PositionRotatedEntityOnTag 317 ====================== 318 */ 319 static void UI_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, 320 clipHandle_t parentModel, char *tagName ) { 321 int i; 322 orientation_t lerped; 323 vec3_t tempAxis[3]; 324 325 // lerp the tag 326 trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, 327 1.0 - parent->backlerp, tagName ); 328 329 // FIXME: allow origin offsets along tag? 330 VectorCopy( parent->origin, entity->origin ); 331 for ( i = 0 ; i < 3 ; i++ ) { 332 VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); 333 } 334 335 // cast away const because of compiler problems 336 MatrixMultiply( entity->axis, ((refEntity_t *)parent)->axis, tempAxis ); 337 MatrixMultiply( lerped.axis, tempAxis, entity->axis ); 338 } 339 340 341 /* 342 =============== 343 UI_SetLerpFrameAnimation 344 =============== 345 */ 346 static void UI_SetLerpFrameAnimation( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { 347 animation_t *anim; 348 349 lf->animationNumber = newAnimation; 350 newAnimation &= ~ANIM_TOGGLEBIT; 351 352 if ( newAnimation < 0 || newAnimation >= MAX_ANIMATIONS ) { 353 trap_Error( va("Bad animation number: %i", newAnimation) ); 354 } 355 356 anim = &ci->animations[ newAnimation ]; 357 358 lf->animation = anim; 359 lf->animationTime = lf->frameTime + anim->initialLerp; 360 } 361 362 363 /* 364 =============== 365 UI_RunLerpFrame 366 =============== 367 */ 368 static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { 369 int f; 370 animation_t *anim; 371 372 // see if the animation sequence is switching 373 if ( newAnimation != lf->animationNumber || !lf->animation ) { 374 UI_SetLerpFrameAnimation( ci, lf, newAnimation ); 375 } 376 377 // if we have passed the current frame, move it to 378 // oldFrame and calculate a new frame 379 if ( dp_realtime >= lf->frameTime ) { 380 lf->oldFrame = lf->frame; 381 lf->oldFrameTime = lf->frameTime; 382 383 // get the next frame based on the animation 384 anim = lf->animation; 385 if ( dp_realtime < lf->animationTime ) { 386 lf->frameTime = lf->animationTime; // initial lerp 387 } else { 388 lf->frameTime = lf->oldFrameTime + anim->frameLerp; 389 } 390 f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; 391 if ( f >= anim->numFrames ) { 392 f -= anim->numFrames; 393 if ( anim->loopFrames ) { 394 f %= anim->loopFrames; 395 f += anim->numFrames - anim->loopFrames; 396 } else { 397 f = anim->numFrames - 1; 398 // the animation is stuck at the end, so it 399 // can immediately transition to another sequence 400 lf->frameTime = dp_realtime; 401 } 402 } 403 lf->frame = anim->firstFrame + f; 404 if ( dp_realtime > lf->frameTime ) { 405 lf->frameTime = dp_realtime; 406 } 407 } 408 409 if ( lf->frameTime > dp_realtime + 200 ) { 410 lf->frameTime = dp_realtime; 411 } 412 413 if ( lf->oldFrameTime > dp_realtime ) { 414 lf->oldFrameTime = dp_realtime; 415 } 416 // calculate current lerp value 417 if ( lf->frameTime == lf->oldFrameTime ) { 418 lf->backlerp = 0; 419 } else { 420 lf->backlerp = 1.0 - (float)( dp_realtime - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); 421 } 422 } 423 424 425 /* 426 =============== 427 UI_PlayerAnimation 428 =============== 429 */ 430 static void UI_PlayerAnimation( playerInfo_t *pi, int *legsOld, int *legs, float *legsBackLerp, 431 int *torsoOld, int *torso, float *torsoBackLerp ) { 432 433 // legs animation 434 pi->legsAnimationTimer -= uiInfo.uiDC.frameTime; 435 if ( pi->legsAnimationTimer < 0 ) { 436 pi->legsAnimationTimer = 0; 437 } 438 439 UI_LegsSequencing( pi ); 440 441 if ( pi->legs.yawing && ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) { 442 UI_RunLerpFrame( pi, &pi->legs, LEGS_TURN ); 443 } else { 444 UI_RunLerpFrame( pi, &pi->legs, pi->legsAnim ); 445 } 446 *legsOld = pi->legs.oldFrame; 447 *legs = pi->legs.frame; 448 *legsBackLerp = pi->legs.backlerp; 449 450 // torso animation 451 pi->torsoAnimationTimer -= uiInfo.uiDC.frameTime; 452 if ( pi->torsoAnimationTimer < 0 ) { 453 pi->torsoAnimationTimer = 0; 454 } 455 456 UI_TorsoSequencing( pi ); 457 458 UI_RunLerpFrame( pi, &pi->torso, pi->torsoAnim ); 459 *torsoOld = pi->torso.oldFrame; 460 *torso = pi->torso.frame; 461 *torsoBackLerp = pi->torso.backlerp; 462 } 463 464 465 /* 466 ================== 467 UI_SwingAngles 468 ================== 469 */ 470 static void UI_SwingAngles( float destination, float swingTolerance, float clampTolerance, 471 float speed, float *angle, qboolean *swinging ) { 472 float swing; 473 float move; 474 float scale; 475 476 if ( !*swinging ) { 477 // see if a swing should be started 478 swing = AngleSubtract( *angle, destination ); 479 if ( swing > swingTolerance || swing < -swingTolerance ) { 480 *swinging = qtrue; 481 } 482 } 483 484 if ( !*swinging ) { 485 return; 486 } 487 488 // modify the speed depending on the delta 489 // so it doesn't seem so linear 490 swing = AngleSubtract( destination, *angle ); 491 scale = fabs( swing ); 492 if ( scale < swingTolerance * 0.5 ) { 493 scale = 0.5; 494 } else if ( scale < swingTolerance ) { 495 scale = 1.0; 496 } else { 497 scale = 2.0; 498 } 499 500 // swing towards the destination angle 501 if ( swing >= 0 ) { 502 move = uiInfo.uiDC.frameTime * scale * speed; 503 if ( move >= swing ) { 504 move = swing; 505 *swinging = qfalse; 506 } 507 *angle = AngleMod( *angle + move ); 508 } else if ( swing < 0 ) { 509 move = uiInfo.uiDC.frameTime * scale * -speed; 510 if ( move <= swing ) { 511 move = swing; 512 *swinging = qfalse; 513 } 514 *angle = AngleMod( *angle + move ); 515 } 516 517 // clamp to no more than tolerance 518 swing = AngleSubtract( destination, *angle ); 519 if ( swing > clampTolerance ) { 520 *angle = AngleMod( destination - (clampTolerance - 1) ); 521 } else if ( swing < -clampTolerance ) { 522 *angle = AngleMod( destination + (clampTolerance - 1) ); 523 } 524 } 525 526 527 /* 528 ====================== 529 UI_MovedirAdjustment 530 ====================== 531 */ 532 static float UI_MovedirAdjustment( playerInfo_t *pi ) { 533 vec3_t relativeAngles; 534 vec3_t moveVector; 535 536 VectorSubtract( pi->viewAngles, pi->moveAngles, relativeAngles ); 537 AngleVectors( relativeAngles, moveVector, NULL, NULL ); 538 if ( Q_fabs( moveVector[0] ) < 0.01 ) { 539 moveVector[0] = 0.0; 540 } 541 if ( Q_fabs( moveVector[1] ) < 0.01 ) { 542 moveVector[1] = 0.0; 543 } 544 545 if ( moveVector[1] == 0 && moveVector[0] > 0 ) { 546 return 0; 547 } 548 if ( moveVector[1] < 0 && moveVector[0] > 0 ) { 549 return 22; 550 } 551 if ( moveVector[1] < 0 && moveVector[0] == 0 ) { 552 return 45; 553 } 554 if ( moveVector[1] < 0 && moveVector[0] < 0 ) { 555 return -22; 556 } 557 if ( moveVector[1] == 0 && moveVector[0] < 0 ) { 558 return 0; 559 } 560 if ( moveVector[1] > 0 && moveVector[0] < 0 ) { 561 return 22; 562 } 563 if ( moveVector[1] > 0 && moveVector[0] == 0 ) { 564 return -45; 565 } 566 567 return -22; 568 } 569 570 571 /* 572 =============== 573 UI_PlayerAngles 574 =============== 575 */ 576 static void UI_PlayerAngles( playerInfo_t *pi, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { 577 vec3_t legsAngles, torsoAngles, headAngles; 578 float dest; 579 float adjust; 580 581 VectorCopy( pi->viewAngles, headAngles ); 582 headAngles[YAW] = AngleMod( headAngles[YAW] ); 583 VectorClear( legsAngles ); 584 VectorClear( torsoAngles ); 585 586 // --------- yaw ------------- 587 588 // allow yaw to drift a bit 589 if ( ( pi->legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE 590 || ( pi->torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) { 591 // if not standing still, always point all in the same direction 592 pi->torso.yawing = qtrue; // always center 593 pi->torso.pitching = qtrue; // always center 594 pi->legs.yawing = qtrue; // always center 595 } 596 597 // adjust legs for movement dir 598 adjust = UI_MovedirAdjustment( pi ); 599 legsAngles[YAW] = headAngles[YAW] + adjust; 600 torsoAngles[YAW] = headAngles[YAW] + 0.25 * adjust; 601 602 603 // torso 604 UI_SwingAngles( torsoAngles[YAW], 25, 90, SWINGSPEED, &pi->torso.yawAngle, &pi->torso.yawing ); 605 UI_SwingAngles( legsAngles[YAW], 40, 90, SWINGSPEED, &pi->legs.yawAngle, &pi->legs.yawing ); 606 607 torsoAngles[YAW] = pi->torso.yawAngle; 608 legsAngles[YAW] = pi->legs.yawAngle; 609 610 // --------- pitch ------------- 611 612 // only show a fraction of the pitch angle in the torso 613 if ( headAngles[PITCH] > 180 ) { 614 dest = (-360 + headAngles[PITCH]) * 0.75; 615 } else { 616 dest = headAngles[PITCH] * 0.75; 617 } 618 UI_SwingAngles( dest, 15, 30, 0.1f, &pi->torso.pitchAngle, &pi->torso.pitching ); 619 torsoAngles[PITCH] = pi->torso.pitchAngle; 620 621 // pull the angles back out of the hierarchial chain 622 AnglesSubtract( headAngles, torsoAngles, headAngles ); 623 AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); 624 AnglesToAxis( legsAngles, legs ); 625 AnglesToAxis( torsoAngles, torso ); 626 AnglesToAxis( headAngles, head ); 627 } 628 629 630 /* 631 =============== 632 UI_PlayerFloatSprite 633 =============== 634 */ 635 static void UI_PlayerFloatSprite( playerInfo_t *pi, vec3_t origin, qhandle_t shader ) { 636 refEntity_t ent; 637 638 memset( &ent, 0, sizeof( ent ) ); 639 VectorCopy( origin, ent.origin ); 640 ent.origin[2] += 48; 641 ent.reType = RT_SPRITE; 642 ent.customShader = shader; 643 ent.radius = 10; 644 ent.renderfx = 0; 645 trap_R_AddRefEntityToScene( &ent ); 646 } 647 648 649 /* 650 ====================== 651 UI_MachinegunSpinAngle 652 ====================== 653 */ 654 float UI_MachinegunSpinAngle( playerInfo_t *pi ) { 655 int delta; 656 float angle; 657 float speed; 658 int torsoAnim; 659 660 delta = dp_realtime - pi->barrelTime; 661 if ( pi->barrelSpinning ) { 662 angle = pi->barrelAngle + delta * SPIN_SPEED; 663 } else { 664 if ( delta > COAST_TIME ) { 665 delta = COAST_TIME; 666 } 667 668 speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); 669 angle = pi->barrelAngle + delta * speed; 670 } 671 672 torsoAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; 673 if( torsoAnim == TORSO_ATTACK2 ) { 674 torsoAnim = TORSO_ATTACK; 675 } 676 if ( pi->barrelSpinning == !(torsoAnim == TORSO_ATTACK) ) { 677 pi->barrelTime = dp_realtime; 678 pi->barrelAngle = AngleMod( angle ); 679 pi->barrelSpinning = !!(torsoAnim == TORSO_ATTACK); 680 } 681 682 return angle; 683 } 684 685 686 /* 687 =============== 688 UI_DrawPlayer 689 =============== 690 */ 691 void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ) { 692 refdef_t refdef; 693 refEntity_t legs; 694 refEntity_t torso; 695 refEntity_t head; 696 refEntity_t gun; 697 refEntity_t barrel; 698 refEntity_t flash; 699 vec3_t origin; 700 int renderfx; 701 vec3_t mins = {-16, -16, -24}; 702 vec3_t maxs = {16, 16, 32}; 703 float len; 704 float xx; 705 706 if ( !pi->legsModel || !pi->torsoModel || !pi->headModel || !pi->animations[0].numFrames ) { 707 return; 708 } 709 710 // this allows the ui to cache the player model on the main menu 711 if (w == 0 || h == 0) { 712 return; 713 } 714 715 dp_realtime = time; 716 717 if ( pi->pendingWeapon != -1 && dp_realtime > pi->weaponTimer ) { 718 pi->weapon = pi->pendingWeapon; 719 pi->lastWeapon = pi->pendingWeapon; 720 pi->pendingWeapon = -1; 721 pi->weaponTimer = 0; 722 if( pi->currentWeapon != pi->weapon ) { 723 trap_S_StartLocalSound( weaponChangeSound, CHAN_LOCAL ); 724 } 725 } 726 727 UI_AdjustFrom640( &x, &y, &w, &h ); 728 729 y -= jumpHeight; 730 731 memset( &refdef, 0, sizeof( refdef ) ); 732 memset( &legs, 0, sizeof(legs) ); 733 memset( &torso, 0, sizeof(torso) ); 734 memset( &head, 0, sizeof(head) ); 735 736 refdef.rdflags = RDF_NOWORLDMODEL; 737 738 AxisClear( refdef.viewaxis ); 739 740 refdef.x = x; 741 refdef.y = y; 742 refdef.width = w; 743 refdef.height = h; 744 745 refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f); 746 xx = refdef.width / tan( refdef.fov_x / 360 * M_PI ); 747 refdef.fov_y = atan2( refdef.height, xx ); 748 refdef.fov_y *= ( 360 / (float)M_PI ); 749 750 // calculate distance so the player nearly fills the box 751 len = 0.7 * ( maxs[2] - mins[2] ); 752 origin[0] = len / tan( DEG2RAD(refdef.fov_x) * 0.5 ); 753 origin[1] = 0.5 * ( mins[1] + maxs[1] ); 754 origin[2] = -0.5 * ( mins[2] + maxs[2] ); 755 756 refdef.time = dp_realtime; 757 758 trap_R_ClearScene(); 759 760 // get the rotation information 761 UI_PlayerAngles( pi, legs.axis, torso.axis, head.axis ); 762 763 // get the animation state (after rotation, to allow feet shuffle) 764 UI_PlayerAnimation( pi, &legs.oldframe, &legs.frame, &legs.backlerp, 765 &torso.oldframe, &torso.frame, &torso.backlerp ); 766 767 renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW; 768 769 // 770 // add the legs 771 // 772 legs.hModel = pi->legsModel; 773 legs.customSkin = pi->legsSkin; 774 775 VectorCopy( origin, legs.origin ); 776 777 VectorCopy( origin, legs.lightingOrigin ); 778 legs.renderfx = renderfx; 779 VectorCopy (legs.origin, legs.oldorigin); 780 781 trap_R_AddRefEntityToScene( &legs ); 782 783 if (!legs.hModel) { 784 return; 785 } 786 787 // 788 // add the torso 789 // 790 torso.hModel = pi->torsoModel; 791 if (!torso.hModel) { 792 return; 793 } 794 795 torso.customSkin = pi->torsoSkin; 796 797 VectorCopy( origin, torso.lightingOrigin ); 798 799 UI_PositionRotatedEntityOnTag( &torso, &legs, pi->legsModel, "tag_torso"); 800 801 torso.renderfx = renderfx; 802 803 trap_R_AddRefEntityToScene( &torso ); 804 805 // 806 // add the head 807 // 808 head.hModel = pi->headModel; 809 if (!head.hModel) { 810 return; 811 } 812 head.customSkin = pi->headSkin; 813 814 VectorCopy( origin, head.lightingOrigin ); 815 816 UI_PositionRotatedEntityOnTag( &head, &torso, pi->torsoModel, "tag_head"); 817 818 head.renderfx = renderfx; 819 820 trap_R_AddRefEntityToScene( &head ); 821 822 // 823 // add the gun 824 // 825 if ( pi->currentWeapon != WP_NONE ) { 826 memset( &gun, 0, sizeof(gun) ); 827 gun.hModel = pi->weaponModel; 828 VectorCopy( origin, gun.lightingOrigin ); 829 UI_PositionEntityOnTag( &gun, &torso, pi->torsoModel, "tag_weapon"); 830 gun.renderfx = renderfx; 831 trap_R_AddRefEntityToScene( &gun ); 832 } 833 834 // 835 // add the spinning barrel 836 // 837 if ( pi->realWeapon == WP_MACHINEGUN || pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) { 838 vec3_t angles; 839 840 memset( &barrel, 0, sizeof(barrel) ); 841 VectorCopy( origin, barrel.lightingOrigin ); 842 barrel.renderfx = renderfx; 843 844 barrel.hModel = pi->barrelModel; 845 angles[YAW] = 0; 846 angles[PITCH] = 0; 847 angles[ROLL] = UI_MachinegunSpinAngle( pi ); 848 if( pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) { 849 angles[PITCH] = angles[ROLL]; 850 angles[ROLL] = 0; 851 } 852 AnglesToAxis( angles, barrel.axis ); 853 854 UI_PositionRotatedEntityOnTag( &barrel, &gun, pi->weaponModel, "tag_barrel"); 855 856 trap_R_AddRefEntityToScene( &barrel ); 857 } 858 859 // 860 // add muzzle flash 861 // 862 if ( dp_realtime <= pi->muzzleFlashTime ) { 863 if ( pi->flashModel ) { 864 memset( &flash, 0, sizeof(flash) ); 865 flash.hModel = pi->flashModel; 866 VectorCopy( origin, flash.lightingOrigin ); 867 UI_PositionEntityOnTag( &flash, &gun, pi->weaponModel, "tag_flash"); 868 flash.renderfx = renderfx; 869 trap_R_AddRefEntityToScene( &flash ); 870 } 871 872 // make a dlight for the flash 873 if ( pi->flashDlightColor[0] || pi->flashDlightColor[1] || pi->flashDlightColor[2] ) { 874 trap_R_AddLightToScene( flash.origin, 200 + (rand()&31), pi->flashDlightColor[0], 875 pi->flashDlightColor[1], pi->flashDlightColor[2] ); 876 } 877 } 878 879 // 880 // add the chat icon 881 // 882 if ( pi->chat ) { 883 UI_PlayerFloatSprite( pi, origin, trap_R_RegisterShaderNoMip( "sprites/balloon3" ) ); 884 } 885 886 // 887 // add an accent light 888 // 889 origin[0] -= 100; // + = behind, - = in front 890 origin[1] += 100; // + = left, - = right 891 origin[2] += 100; // + = above, - = below 892 trap_R_AddLightToScene( origin, 500, 1.0, 1.0, 1.0 ); 893 894 origin[0] -= 100; 895 origin[1] -= 100; 896 origin[2] -= 100; 897 trap_R_AddLightToScene( origin, 500, 1.0, 0.0, 0.0 ); 898 899 trap_R_RenderScene( &refdef ); 900 } 901 902 /* 903 ========================== 904 UI_FileExists 905 ========================== 906 */ 907 static qboolean UI_FileExists(const char *filename) { 908 int len; 909 910 len = trap_FS_FOpenFile( filename, 0, FS_READ ); 911 if (len>0) { 912 return qtrue; 913 } 914 return qfalse; 915 } 916 917 /* 918 ========================== 919 UI_FindClientHeadFile 920 ========================== 921 */ 922 static qboolean UI_FindClientHeadFile( char *filename, int length, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) { 923 char *team, *headsFolder; 924 int i; 925 926 team = "default"; 927 928 if ( headModelName[0] == '*' ) { 929 headsFolder = "heads/"; 930 headModelName++; 931 } 932 else { 933 headsFolder = ""; 934 } 935 while(1) { 936 for ( i = 0; i < 2; i++ ) { 937 if ( i == 0 && teamName && *teamName ) { 938 Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext ); 939 } 940 else { 941 Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext ); 942 } 943 if ( UI_FileExists( filename ) ) { 944 return qtrue; 945 } 946 if ( i == 0 && teamName && *teamName ) { 947 Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext ); 948 } 949 else { 950 Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext ); 951 } 952 if ( UI_FileExists( filename ) ) { 953 return qtrue; 954 } 955 if ( !teamName || !*teamName ) { 956 break; 957 } 958 } 959 // if tried the heads folder first 960 if ( headsFolder[0] ) { 961 break; 962 } 963 headsFolder = "heads/"; 964 } 965 966 return qfalse; 967 } 968 969 /* 970 ========================== 971 UI_RegisterClientSkin 972 ========================== 973 */ 974 static qboolean UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName , const char *teamName) { 975 char filename[MAX_QPATH*2]; 976 977 if (teamName && *teamName) { 978 Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/lower_%s.skin", modelName, teamName, skinName ); 979 } else { 980 Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName ); 981 } 982 pi->legsSkin = trap_R_RegisterSkin( filename ); 983 if (!pi->legsSkin) { 984 if (teamName && *teamName) { 985 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/lower_%s.skin", modelName, teamName, skinName ); 986 } else { 987 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower_%s.skin", modelName, skinName ); 988 } 989 pi->legsSkin = trap_R_RegisterSkin( filename ); 990 } 991 992 if (teamName && *teamName) { 993 Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/upper_%s.skin", modelName, teamName, skinName ); 994 } else { 995 Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName ); 996 } 997 pi->torsoSkin = trap_R_RegisterSkin( filename ); 998 if (!pi->torsoSkin) { 999 if (teamName && *teamName) { 1000 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/upper_%s.skin", modelName, teamName, skinName ); 1001 } else { 1002 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper_%s.skin", modelName, skinName ); 1003 } 1004 pi->torsoSkin = trap_R_RegisterSkin( filename ); 1005 } 1006 1007 if ( UI_FindClientHeadFile( filename, sizeof(filename), teamName, headModelName, headSkinName, "head", "skin" ) ) { 1008 pi->headSkin = trap_R_RegisterSkin( filename ); 1009 } 1010 1011 if ( !pi->legsSkin || !pi->torsoSkin || !pi->headSkin ) { 1012 return qfalse; 1013 } 1014 1015 return qtrue; 1016 } 1017 1018 1019 /* 1020 ====================== 1021 UI_ParseAnimationFile 1022 ====================== 1023 */ 1024 static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animations ) { 1025 char *text_p, *prev; 1026 int len; 1027 int i; 1028 char *token; 1029 float fps; 1030 int skip; 1031 char text[20000]; 1032 fileHandle_t f; 1033 1034 memset( animations, 0, sizeof( animation_t ) * MAX_ANIMATIONS ); 1035 1036 // load the file 1037 len = trap_FS_FOpenFile( filename, &f, FS_READ ); 1038 if ( len <= 0 ) { 1039 return qfalse; 1040 } 1041 if ( len >= ( sizeof( text ) - 1 ) ) { 1042 Com_Printf( "File %s too long\n", filename ); 1043 return qfalse; 1044 } 1045 trap_FS_Read( text, len, f ); 1046 text[len] = 0; 1047 trap_FS_FCloseFile( f ); 1048 1049 COM_Compress(text); 1050 1051 // parse the text 1052 text_p = text; 1053 skip = 0; // quite the compiler warning 1054 1055 // read optional parameters 1056 while ( 1 ) { 1057 prev = text_p; // so we can unget 1058 token = COM_Parse( &text_p ); 1059 if ( !token ) { 1060 break; 1061 } 1062 if ( !Q_stricmp( token, "footsteps" ) ) { 1063 token = COM_Parse( &text_p ); 1064 if ( !token ) { 1065 break; 1066 } 1067 continue; 1068 } else if ( !Q_stricmp( token, "headoffset" ) ) { 1069 for ( i = 0 ; i < 3 ; i++ ) { 1070 token = COM_Parse( &text_p ); 1071 if ( !token ) { 1072 break; 1073 } 1074 } 1075 continue; 1076 } else if ( !Q_stricmp( token, "sex" ) ) { 1077 token = COM_Parse( &text_p ); 1078 if ( !token ) { 1079 break; 1080 } 1081 continue; 1082 } 1083 1084 // if it is a number, start parsing animations 1085 if ( token[0] >= '0' && token[0] <= '9' ) { 1086 text_p = prev; // unget the token 1087 break; 1088 } 1089 1090 Com_Printf( "unknown token '%s' is %s\n", token, filename ); 1091 } 1092 1093 // read information for each frame 1094 for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { 1095 1096 token = COM_Parse( &text_p ); 1097 if ( !token ) { 1098 break; 1099 } 1100 animations[i].firstFrame = atoi( token ); 1101 // leg only frames are adjusted to not count the upper body only frames 1102 if ( i == LEGS_WALKCR ) { 1103 skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; 1104 } 1105 if ( i >= LEGS_WALKCR ) { 1106 animations[i].firstFrame -= skip; 1107 } 1108 1109 token = COM_Parse( &text_p ); 1110 if ( !token ) { 1111 break; 1112 } 1113 animations[i].numFrames = atoi( token ); 1114 1115 token = COM_Parse( &text_p ); 1116 if ( !token ) { 1117 break; 1118 } 1119 animations[i].loopFrames = atoi( token ); 1120 1121 token = COM_Parse( &text_p ); 1122 if ( !token ) { 1123 break; 1124 } 1125 fps = atof( token ); 1126 if ( fps == 0 ) { 1127 fps = 1; 1128 } 1129 animations[i].frameLerp = 1000 / fps; 1130 animations[i].initialLerp = 1000 / fps; 1131 } 1132 1133 if ( i != MAX_ANIMATIONS ) { 1134 Com_Printf( "Error parsing animation file: %s", filename ); 1135 return qfalse; 1136 } 1137 1138 return qtrue; 1139 } 1140 1141 /* 1142 ========================== 1143 UI_RegisterClientModelname 1144 ========================== 1145 */ 1146 qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName, const char *headModelSkinName, const char *teamName ) { 1147 char modelName[MAX_QPATH]; 1148 char skinName[MAX_QPATH]; 1149 char headModelName[MAX_QPATH]; 1150 char headSkinName[MAX_QPATH]; 1151 char filename[MAX_QPATH]; 1152 char *slash; 1153 1154 pi->torsoModel = 0; 1155 pi->headModel = 0; 1156 1157 if ( !modelSkinName[0] ) { 1158 return qfalse; 1159 } 1160 1161 Q_strncpyz( modelName, modelSkinName, sizeof( modelName ) ); 1162 1163 slash = strchr( modelName, '/' ); 1164 if ( !slash ) { 1165 // modelName did not include a skin name 1166 Q_strncpyz( skinName, "default", sizeof( skinName ) ); 1167 } else { 1168 Q_strncpyz( skinName, slash + 1, sizeof( skinName ) ); 1169 *slash = '\0'; 1170 } 1171 1172 Q_strncpyz( headModelName, headModelSkinName, sizeof( headModelName ) ); 1173 slash = strchr( headModelName, '/' ); 1174 if ( !slash ) { 1175 // modelName did not include a skin name 1176 Q_strncpyz( headSkinName, "default", sizeof( skinName ) ); 1177 } else { 1178 Q_strncpyz( headSkinName, slash + 1, sizeof( skinName ) ); 1179 *slash = '\0'; 1180 } 1181 1182 // load cmodels before models so filecache works 1183 1184 Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); 1185 pi->legsModel = trap_R_RegisterModel( filename ); 1186 if ( !pi->legsModel ) { 1187 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName ); 1188 pi->legsModel = trap_R_RegisterModel( filename ); 1189 if ( !pi->legsModel ) { 1190 Com_Printf( "Failed to load model file %s\n", filename ); 1191 return qfalse; 1192 } 1193 } 1194 1195 Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); 1196 pi->torsoModel = trap_R_RegisterModel( filename ); 1197 if ( !pi->torsoModel ) { 1198 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName ); 1199 pi->torsoModel = trap_R_RegisterModel( filename ); 1200 if ( !pi->torsoModel ) { 1201 Com_Printf( "Failed to load model file %s\n", filename ); 1202 return qfalse; 1203 } 1204 } 1205 1206 if (headModelName && headModelName[0] == '*' ) { 1207 Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] ); 1208 } 1209 else { 1210 Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headModelName ); 1211 } 1212 pi->headModel = trap_R_RegisterModel( filename ); 1213 if ( !pi->headModel && headModelName[0] != '*') { 1214 Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName ); 1215 pi->headModel = trap_R_RegisterModel( filename ); 1216 } 1217 1218 if (!pi->headModel) { 1219 Com_Printf( "Failed to load model file %s\n", filename ); 1220 return qfalse; 1221 } 1222 1223 // if any skins failed to load, fall back to default 1224 if ( !UI_RegisterClientSkin( pi, modelName, skinName, headModelName, headSkinName, teamName) ) { 1225 if ( !UI_RegisterClientSkin( pi, modelName, "default", headModelName, "default", teamName ) ) { 1226 Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName ); 1227 return qfalse; 1228 } 1229 } 1230 1231 // load the animations 1232 Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); 1233 if ( !UI_ParseAnimationFile( filename, pi->animations ) ) { 1234 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName ); 1235 if ( !UI_ParseAnimationFile( filename, pi->animations ) ) { 1236 Com_Printf( "Failed to load animation file %s\n", filename ); 1237 return qfalse; 1238 } 1239 } 1240 1241 return qtrue; 1242 } 1243 1244 1245 /* 1246 =============== 1247 UI_PlayerInfo_SetModel 1248 =============== 1249 */ 1250 void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model, const char *headmodel, char *teamName ) { 1251 memset( pi, 0, sizeof(*pi) ); 1252 UI_RegisterClientModelname( pi, model, headmodel, teamName ); 1253 pi->weapon = WP_MACHINEGUN; 1254 pi->currentWeapon = pi->weapon; 1255 pi->lastWeapon = pi->weapon; 1256 pi->pendingWeapon = -1; 1257 pi->weaponTimer = 0; 1258 pi->chat = qfalse; 1259 pi->newModel = qtrue; 1260 UI_PlayerInfo_SetWeapon( pi, pi->weapon ); 1261 } 1262 1263 1264 /* 1265 =============== 1266 UI_PlayerInfo_SetInfo 1267 =============== 1268 */ 1269 void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNumber, qboolean chat ) { 1270 int currentAnim; 1271 weapon_t weaponNum; 1272 1273 pi->chat = chat; 1274 1275 // view angles 1276 VectorCopy( viewAngles, pi->viewAngles ); 1277 1278 // move angles 1279 VectorCopy( moveAngles, pi->moveAngles ); 1280 1281 if ( pi->newModel ) { 1282 pi->newModel = qfalse; 1283 1284 jumpHeight = 0; 1285 pi->pendingLegsAnim = 0; 1286 UI_ForceLegsAnim( pi, legsAnim ); 1287 pi->legs.yawAngle = viewAngles[YAW]; 1288 pi->legs.yawing = qfalse; 1289 1290 pi->pendingTorsoAnim = 0; 1291 UI_ForceTorsoAnim( pi, torsoAnim ); 1292 pi->torso.yawAngle = viewAngles[YAW]; 1293 pi->torso.yawing = qfalse; 1294 1295 if ( weaponNumber != -1 ) { 1296 pi->weapon = weaponNumber; 1297 pi->currentWeapon = weaponNumber; 1298 pi->lastWeapon = weaponNumber; 1299 pi->pendingWeapon = -1; 1300 pi->weaponTimer = 0; 1301 UI_PlayerInfo_SetWeapon( pi, pi->weapon ); 1302 } 1303 1304 return; 1305 } 1306 1307 // weapon 1308 if ( weaponNumber == -1 ) { 1309 pi->pendingWeapon = -1; 1310 pi->weaponTimer = 0; 1311 } 1312 else if ( weaponNumber != WP_NONE ) { 1313 pi->pendingWeapon = weaponNumber; 1314 pi->weaponTimer = dp_realtime + UI_TIMER_WEAPON_DELAY; 1315 } 1316 weaponNum = pi->lastWeapon; 1317 pi->weapon = weaponNum; 1318 1319 if ( torsoAnim == BOTH_DEATH1 || legsAnim == BOTH_DEATH1 ) { 1320 torsoAnim = legsAnim = BOTH_DEATH1; 1321 pi->weapon = pi->currentWeapon = WP_NONE; 1322 UI_PlayerInfo_SetWeapon( pi, pi->weapon ); 1323 1324 jumpHeight = 0; 1325 pi->pendingLegsAnim = 0; 1326 UI_ForceLegsAnim( pi, legsAnim ); 1327 1328 pi->pendingTorsoAnim = 0; 1329 UI_ForceTorsoAnim( pi, torsoAnim ); 1330 1331 return; 1332 } 1333 1334 // leg animation 1335 currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT; 1336 if ( legsAnim != LEGS_JUMP && ( currentAnim == LEGS_JUMP || currentAnim == LEGS_LAND ) ) { 1337 pi->pendingLegsAnim = legsAnim; 1338 } 1339 else if ( legsAnim != currentAnim ) { 1340 jumpHeight = 0; 1341 pi->pendingLegsAnim = 0; 1342 UI_ForceLegsAnim( pi, legsAnim ); 1343 } 1344 1345 // torso animation 1346 if ( torsoAnim == TORSO_STAND || torsoAnim == TORSO_STAND2 ) { 1347 if ( weaponNum == WP_NONE || weaponNum == WP_GAUNTLET ) { 1348 torsoAnim = TORSO_STAND2; 1349 } 1350 else { 1351 torsoAnim = TORSO_STAND; 1352 } 1353 } 1354 1355 if ( torsoAnim == TORSO_ATTACK || torsoAnim == TORSO_ATTACK2 ) { 1356 if ( weaponNum == WP_NONE || weaponNum == WP_GAUNTLET ) { 1357 torsoAnim = TORSO_ATTACK2; 1358 } 1359 else { 1360 torsoAnim = TORSO_ATTACK; 1361 } 1362 pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH; 1363 //FIXME play firing sound here 1364 } 1365 1366 currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; 1367 1368 if ( weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISE || currentAnim == TORSO_DROP ) { 1369 pi->pendingTorsoAnim = torsoAnim; 1370 } 1371 else if ( ( currentAnim == TORSO_GESTURE || currentAnim == TORSO_ATTACK ) && ( torsoAnim != currentAnim ) ) { 1372 pi->pendingTorsoAnim = torsoAnim; 1373 } 1374 else if ( torsoAnim != currentAnim ) { 1375 pi->pendingTorsoAnim = 0; 1376 UI_ForceTorsoAnim( pi, torsoAnim ); 1377 } 1378 }