Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

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 }