Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

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 }