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