/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
//
// ui_players.c

#include "ui_local.h"


#ifndef TA_PLAYERSYS // PLAYERCFG_ANIMATION_TIMES
#define UI_TIMER_GESTURE		2300
#endif
#define UI_TIMER_JUMP			1000
#ifndef TA_PLAYERSYS // PLAYERCFG_ANIMATION_TIMES
#define UI_TIMER_LAND			130
#endif
#define UI_TIMER_WEAPON_SWITCH	300
#ifndef TA_PLAYERSYS // PLAYERCFG_ANIMATION_TIMES
#define UI_TIMER_ATTACK			500
#endif
#define	UI_TIMER_MUZZLE_FLASH	20
#define	UI_TIMER_WEAPON_DELAY	250

#define JUMP_HEIGHT				56

#ifndef IOQ3ZTM // BG_SWING_ANGLES
#define SWINGSPEED				0.3f
#endif

#define SPIN_SPEED				0.9f
#define COAST_TIME				1000


static int			dp_realtime;
static float		jumpHeight;
#ifdef TA_WEAPSYS
static int			atkAnim;
#endif
#ifndef TA_WEAPSYS_EX
sfxHandle_t weaponChangeSound;
#endif


/*
===============
UI_PlayerInfo_SetWeapon
===============
*/
static void UI_PlayerInfo_SetWeapon( playerInfo_t *pi, weapon_t weaponNum ) {
#ifdef TA_ITEMSYS
	int			i;
#endif
#ifndef TA_WEAPSYS
	gitem_t *	item;
#endif
	char		path[MAX_QPATH];

	pi->currentWeapon = weaponNum;
#ifndef TA_WEAPSYS
tryagain:
#endif
	pi->realWeapon = weaponNum;
#ifdef TA_WEAPSYS
	for (i = 0; i < MAX_HANDS; i++)
	{
		pi->weaponModel[i] = 0;
		pi->barrelModel[i] = 0;
		pi->flashModel[i] = 0;
	}
#else
	pi->weaponModel = 0;
	pi->barrelModel = 0;
	pi->flashModel = 0;
#endif

	if ( weaponNum == WP_NONE ) {
		return;
	}

#ifdef TA_WEAPSYS
	for (i = 0; i < MAX_HANDS; i++)
	{
		if (bg_weapongroupinfo[weaponNum].weapon[i]->model[0] == '\0')
			continue;

		pi->weaponModel[i] = trap_R_RegisterModel(bg_weapongroupinfo[weaponNum].weapon[i]->model);

		strcpy( path, bg_weapongroupinfo[weaponNum].weapon[i]->model );
		COM_StripExtension(path, path, sizeof(path));
		strcat( path, "_barrel.md3" );
		pi->barrelModel[i] = trap_R_RegisterModel( path );

		strcpy( path, bg_weapongroupinfo[weaponNum].weapon[i]->model );
		COM_StripExtension(path, path, sizeof(path));
		strcat( path, "_flash.md3" );
		pi->flashModel[i] = trap_R_RegisterModel( path );

		VectorCopy(bg_weapongroupinfo[weaponNum].weapon[i]->flashColor, pi->flashDlightColor[i]);
	}
#else
#ifdef TA_ITEMSYS
	item = BG_ItemForItemNum(0);
	for (i = BG_NumItems()-1; i > 0; i--)
#else
	for ( item = bg_itemlist + 1; item->classname ; item++ )
#endif
	{
#ifdef TA_ITEMSYS
		item = BG_ItemForItemNum(i);
#endif
		if ( item->giType != IT_WEAPON ) {
			continue;
		}
		if ( item->giTag == weaponNum ) {
			break;
		}
	}

	if ( item->classname ) {
		pi->weaponModel = trap_R_RegisterModel( item->world_model[0] );
	}

	if( pi->weaponModel == 0 ) {
		if( weaponNum == WP_MACHINEGUN ) {
			weaponNum = WP_NONE;
			goto tryagain;
		}
		weaponNum = WP_MACHINEGUN;
		goto tryagain;
	}

	if ( weaponNum == WP_MACHINEGUN || weaponNum == WP_GAUNTLET || weaponNum == WP_BFG )
	{
		strcpy( path, item->world_model[0] );
		COM_StripExtension(path, path, sizeof(path));
		strcat( path, "_barrel.md3" );
		pi->barrelModel = trap_R_RegisterModel( path );
	}

	strcpy( path, item->world_model[0] );
	COM_StripExtension(path, path, sizeof(path));
	strcat( path, "_flash.md3" );
	pi->flashModel = trap_R_RegisterModel( path );

	switch( weaponNum ) {
	case WP_GAUNTLET:
		MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
		break;

	case WP_MACHINEGUN:
		MAKERGB( pi->flashDlightColor, 1, 1, 0 );
		break;

	case WP_SHOTGUN:
		MAKERGB( pi->flashDlightColor, 1, 1, 0 );
		break;

	case WP_GRENADE_LAUNCHER:
		MAKERGB( pi->flashDlightColor, 1, 0.7f, 0.5f );
		break;

	case WP_ROCKET_LAUNCHER:
		MAKERGB( pi->flashDlightColor, 1, 0.75f, 0 );
		break;

	case WP_LIGHTNING:
		MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
		break;

	case WP_RAILGUN:
		MAKERGB( pi->flashDlightColor, 1, 0.5f, 0 );
		break;

	case WP_PLASMAGUN:
		MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
		break;

	case WP_BFG:
		MAKERGB( pi->flashDlightColor, 1, 0.7f, 1 );
		break;

	case WP_GRAPPLING_HOOK:
		MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
		break;

	default:
		MAKERGB( pi->flashDlightColor, 1, 1, 1 );
		break;
	}
#endif
}


/*
===============
UI_ForceLegsAnim
===============
*/
static void UI_ForceLegsAnim( playerInfo_t *pi, int anim ) {
	pi->legsAnim = ( ( pi->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;

	if ( anim == LEGS_JUMP ) {
		pi->legsAnimationTimer = UI_TIMER_JUMP;
	}
}


/*
===============
UI_SetLegsAnim
===============
*/
static void UI_SetLegsAnim( playerInfo_t *pi, int anim ) {
	if ( pi->pendingLegsAnim ) {
		anim = pi->pendingLegsAnim;
		pi->pendingLegsAnim = 0;
	}
	UI_ForceLegsAnim( pi, anim );
}


/*
===============
UI_ForceTorsoAnim
===============
*/
static void UI_ForceTorsoAnim( playerInfo_t *pi, int anim ) {
	pi->torsoAnim = ( ( pi->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;

	if ( anim == TORSO_GESTURE ) {
#ifdef TA_PLAYERSYS // PLAYERCFG_ANIMATION_TIMES
		pi->torsoAnimationTimer = BG_AnimationTime(&pi->playercfg.animations[anim]);
#else
		pi->torsoAnimationTimer = UI_TIMER_GESTURE;
#endif
	}

#ifdef TA_WEAPSYS
	if (BG_PlayerAttackAnim(anim))
#else
	if ( anim == TORSO_ATTACK || anim == TORSO_ATTACK2 )
#endif
	{
#ifdef TA_PLAYERSYS // PLAYERCFG_ANIMATION_TIMES
		pi->torsoAnimationTimer = BG_AnimationTime(&pi->playercfg.animations[anim]);
#else
		pi->torsoAnimationTimer = UI_TIMER_ATTACK;
#endif
	}
}


/*
===============
UI_SetTorsoAnim
===============
*/
static void UI_SetTorsoAnim( playerInfo_t *pi, int anim ) {
	if ( pi->pendingTorsoAnim ) {
		anim = pi->pendingTorsoAnim;
		pi->pendingTorsoAnim = 0;
	}

	UI_ForceTorsoAnim( pi, anim );
}


/*
===============
UI_TorsoSequencing
===============
*/
static void UI_TorsoSequencing( playerInfo_t *pi ) {
	int		currentAnim;

	currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;

	if ( pi->weapon != pi->currentWeapon ) {
		if ( currentAnim != TORSO_DROP ) {
			pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
			UI_ForceTorsoAnim( pi, TORSO_DROP );
		}
	}

	if ( pi->torsoAnimationTimer > 0 ) {
		return;
	}

	if( currentAnim == TORSO_GESTURE ) {
#ifdef TA_WEAPSYS
		UI_SetTorsoAnim( pi, BG_TorsoStandForWeapon(pi->realWeapon) );
#else
		UI_SetTorsoAnim( pi, TORSO_STAND );
#endif
		return;
	}

#ifdef TA_WEAPSYS
	if (BG_PlayerAttackAnim(currentAnim)) {
		UI_SetTorsoAnim( pi, BG_TorsoStandForWeapon(pi->realWeapon) );
		return;
	}
#else
	if( currentAnim == TORSO_ATTACK || currentAnim == TORSO_ATTACK2 ) {
		UI_SetTorsoAnim( pi, TORSO_STAND );
		return;
	}
#endif

	if ( currentAnim == TORSO_DROP ) {
		UI_PlayerInfo_SetWeapon( pi, pi->weapon );
		pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
		UI_ForceTorsoAnim( pi, TORSO_RAISE );
		return;
	}

	if ( currentAnim == TORSO_RAISE ) {
#ifdef TA_WEAPSYS
		UI_SetTorsoAnim( pi, BG_TorsoStandForWeapon(pi->realWeapon) );
#else
		UI_SetTorsoAnim( pi, TORSO_STAND );
#endif
		return;
	}
}


/*
===============
UI_LegsSequencing
===============
*/
static void UI_LegsSequencing( playerInfo_t *pi ) {
	int		currentAnim;

	currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;

	if ( pi->legsAnimationTimer > 0 ) {
		if ( currentAnim == LEGS_JUMP ) {
			jumpHeight = JUMP_HEIGHT * sin( M_PI * ( UI_TIMER_JUMP - pi->legsAnimationTimer ) / UI_TIMER_JUMP );
		}
		return;
	}

	if ( currentAnim == LEGS_JUMP ) {
		UI_ForceLegsAnim( pi, LEGS_LAND );
#ifdef TA_PLAYERSYS // PLAYERCFG_ANIMATION_TIMES
		pi->legsAnimationTimer = BG_AnimationTime(&pi->playercfg.animations[LEGS_LAND]);
#else
		pi->legsAnimationTimer = UI_TIMER_LAND;
#endif
		jumpHeight = 0;
		return;
	}

	if ( currentAnim == LEGS_LAND ) {
#ifdef TA_WEAPSYS
		UI_SetLegsAnim( pi, BG_LegsStandForWeapon(&pi->playercfg, pi->realWeapon) );
#else
		UI_SetLegsAnim( pi, LEGS_IDLE );
#endif
		return;
	}
}


/*
======================
UI_PositionEntityOnTag
======================
*/
#ifdef IOQ3ZTM // BONES
static qboolean UI_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
							clipHandle_t parentModel, const refSkeleton_t *parentSkeleton, char *tagName )
#else
static qboolean UI_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
							clipHandle_t parentModel, char *tagName )
#endif
{
	int				i;
	orientation_t	lerped;
	qboolean		returnValue;

#ifdef IOQ3ZTM // BONES
	if (parentSkeleton && parentSkeleton->type == ST_ABSOLUTE) {
		int joint = trap_R_JointIndexForName(parentModel, tagName);

		returnValue = (joint >= 0 && joint < parentSkeleton->numJoints);

		if (returnValue) {
			// Found joint
			memcpy(&lerped, &parentSkeleton->joints[joint], sizeof (lerped));
		} else {
			// Joint not found
			memset(&lerped, 0, sizeof (lerped));
		}
	} else {
#endif
	// lerp the tag
	returnValue = trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
		1.0 - parent->backlerp, tagName );
#ifdef IOQ3ZTM // BONES
	}
#endif

	// FIXME: allow origin offsets along tag?
	VectorCopy( parent->origin, entity->origin );
	for ( i = 0 ; i < 3 ; i++ ) {
		VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
	}

	// cast away const because of compiler problems
	MatrixMultiply( lerped.axis, ((refEntity_t*)parent)->axis, entity->axis );
	entity->backlerp = parent->backlerp;

	return returnValue;
}


/*
======================
UI_PositionRotatedEntityOnTag
======================
*/
#ifdef IOQ3ZTM // BONES
static qboolean UI_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
							clipHandle_t parentModel, const refSkeleton_t *parentSkeleton, char *tagName )
#else
static qboolean UI_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
							clipHandle_t parentModel, char *tagName )
#endif
{
	int				i;
	orientation_t	lerped;
	vec3_t			tempAxis[3];
	qboolean		returnValue;

#ifdef IOQ3ZTM // BONES
	if (parentSkeleton && parentSkeleton->type == ST_ABSOLUTE) {
		int joint = trap_R_JointIndexForName(parentModel, tagName);

		returnValue = (joint >= 0 && joint < parentSkeleton->numJoints);

		if (returnValue) {
			// Found joint
			memcpy(&lerped, &parentSkeleton->joints[joint], sizeof (lerped));
		} else {
			// Joint not found
			memset(&lerped, 0, sizeof (lerped));
		}
	} else {
#endif
	// lerp the tag
	returnValue = trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
		1.0 - parent->backlerp, tagName );
#ifdef IOQ3ZTM // BONES
	}
#endif

	// FIXME: allow origin offsets along tag?
	VectorCopy( parent->origin, entity->origin );
	for ( i = 0 ; i < 3 ; i++ ) {
		VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
	}

	// cast away const because of compiler problems
	MatrixMultiply( entity->axis, ((refEntity_t *)parent)->axis, tempAxis );
	MatrixMultiply( lerped.axis, tempAxis, entity->axis );

	return returnValue;
}


#ifndef IOQ3ZTM // LERP_FRAME_CLIENT_LESS // Moved to bg_misc.c
/*
===============
UI_SetLerpFrameAnimation
===============
*/
static void UI_SetLerpFrameAnimation( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
	animation_t	*anim;

	lf->animationNumber = newAnimation;
	newAnimation &= ~ANIM_TOGGLEBIT;

#ifdef TA_PLAYERSYS
	if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS )
#else
	if ( newAnimation < 0 || newAnimation >= MAX_ANIMATIONS )
#endif
	{
		trap_Error( va("Bad animation number: %i", newAnimation) );
	}

#ifdef TA_PLAYERSYS
	anim = &ci->playercfg.animations[ newAnimation ];
#else
	anim = &ci->animations[ newAnimation ];
#endif

	lf->animation = anim;
	lf->animationTime = lf->frameTime + anim->initialLerp;
}


/*
===============
UI_RunLerpFrame
===============
*/
static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
	int			f;
	animation_t	*anim;

	// see if the animation sequence is switching
	if ( newAnimation != lf->animationNumber || !lf->animation ) {
		UI_SetLerpFrameAnimation( ci, lf, newAnimation );
	}

	// if we have passed the current frame, move it to
	// oldFrame and calculate a new frame
	if ( dp_realtime >= lf->frameTime ) {
		lf->oldFrame = lf->frame;
		lf->oldFrameTime = lf->frameTime;

		// get the next frame based on the animation
		anim = lf->animation;
		if ( dp_realtime < lf->animationTime ) {
			lf->frameTime = lf->animationTime;		// initial lerp
		} else {
			lf->frameTime = lf->oldFrameTime + anim->frameLerp;
		}
		f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
		if ( f >= anim->numFrames ) {
			f -= anim->numFrames;
			if ( anim->loopFrames ) {
				f %= anim->loopFrames;
				f += anim->numFrames - anim->loopFrames;
			} else {
				f = anim->numFrames - 1;
				// the animation is stuck at the end, so it
				// can immediately transition to another sequence
				lf->frameTime = dp_realtime;
			}
		}
		lf->frame = anim->firstFrame + f;
		if ( dp_realtime > lf->frameTime ) {
			lf->frameTime = dp_realtime;
		}
	}

	if ( lf->frameTime > dp_realtime + 200 ) {
		lf->frameTime = dp_realtime;
	}

	if ( lf->oldFrameTime > dp_realtime ) {
		lf->oldFrameTime = dp_realtime;
	}
	// calculate current lerp value
	if ( lf->frameTime == lf->oldFrameTime ) {
		lf->backlerp = 0;
	} else {
		lf->backlerp = 1.0 - (float)( dp_realtime - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
	}
}
#endif


/*
===============
UI_PlayerAnimation
===============
*/
static void UI_PlayerAnimation( playerInfo_t *pi, int *legsOld, int *legs, float *legsBackLerp,
						int *torsoOld, int *torso, float *torsoBackLerp ) {

	// legs animation
	pi->legsAnimationTimer -= uiInfo.uiDC.frameTime;
	if ( pi->legsAnimationTimer < 0 ) {
		pi->legsAnimationTimer = 0;
	}

	UI_LegsSequencing( pi );

	if ( pi->legs.yawing &&
#ifdef TA_WEAPSYS
		BG_PlayerStandAnim(&pi->playercfg, AP_LEGS, pi->legsAnim)
#else
		( pi->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE
#endif
		)
	{
#ifdef IOQ3ZTM // LERP_FRAME_CLIENT_LESS
		BG_RunLerpFrame( &pi->legs,
#ifdef TA_PLAYERSYS
			pi->playercfg.animations,
#else
			pi->animations,
#endif
			LEGS_TURN, dp_realtime, 1.0f );
#else
		UI_RunLerpFrame( pi, &pi->legs, LEGS_TURN );
#endif
	} else {
#ifdef IOQ3ZTM // LERP_FRAME_CLIENT_LESS
		BG_RunLerpFrame( &pi->legs,
#ifdef TA_PLAYERSYS
			pi->playercfg.animations,
#else
			pi->animations,
#endif
			pi->legsAnim, dp_realtime, 1.0f );
#else
		UI_RunLerpFrame( pi, &pi->legs, pi->legsAnim );
#endif
	}
	*legsOld = pi->legs.oldFrame;
	*legs = pi->legs.frame;
	*legsBackLerp = pi->legs.backlerp;

	// torso animation
	pi->torsoAnimationTimer -= uiInfo.uiDC.frameTime;
	if ( pi->torsoAnimationTimer < 0 ) {
		pi->torsoAnimationTimer = 0;
	}

	UI_TorsoSequencing( pi );

#ifdef IOQ3ZTM // LERP_FRAME_CLIENT_LESS
	BG_RunLerpFrame( &pi->torso,
#ifdef TA_PLAYERSYS
		pi->playercfg.animations,
#else
		pi->animations,
#endif
		pi->torsoAnim, dp_realtime, 1.0f );
#else
	UI_RunLerpFrame( pi, &pi->torso, pi->torsoAnim );
#endif
	*torsoOld = pi->torso.oldFrame;
	*torso = pi->torso.frame;
	*torsoBackLerp = pi->torso.backlerp;
}

#ifdef IOQ3ZTM // BONES
/*
===============
UI_PlayerSkeleton
===============
*/
static void UI_PlayerSkeleton(playerInfo_t *pi, refEntity_t *legs, refEntity_t *torso,
							refEntity_t *head, refSkeleton_t *absSkeleton)
{
	refSkeleton_t skeleton;

	if (!pi->playerModel) {
		return;
	}

	if (trap_R_SetupPlayerSkeleton(pi->playerModel, &skeleton,
								legs->frame, legs->oldframe, legs->backlerp,
								torso->frame, torso->oldframe, torso->backlerp,
								head->frame, head->oldframe, head->backlerp))
	{
		// ZTM: TODO: Set torso and head axis in skeleton.

		trap_R_MakeSkeletonAbsolute(&skeleton, absSkeleton);
	}
}
#endif


#ifndef IOQ3ZTM // BG_SWING_ANGLES
/*
==================
UI_SwingAngles
==================
*/
static void UI_SwingAngles( float destination, float swingTolerance, float clampTolerance,
					float speed, float *angle, qboolean *swinging ) {
	float	swing;
	float	move;
	float	scale;

	if ( !*swinging ) {
		// see if a swing should be started
		swing = AngleSubtract( *angle, destination );
		if ( swing > swingTolerance || swing < -swingTolerance ) {
			*swinging = qtrue;
		}
	}

	if ( !*swinging ) {
		return;
	}
	
	// modify the speed depending on the delta
	// so it doesn't seem so linear
	swing = AngleSubtract( destination, *angle );
	scale = fabs( swing );
	if ( scale < swingTolerance * 0.5 ) {
		scale = 0.5;
	} else if ( scale < swingTolerance ) {
		scale = 1.0;
	} else {
		scale = 2.0;
	}

	// swing towards the destination angle
	if ( swing >= 0 ) {
		move = uiInfo.uiDC.frameTime * scale * speed;
		if ( move >= swing ) {
			move = swing;
			*swinging = qfalse;
		}
		*angle = AngleMod( *angle + move );
	} else if ( swing < 0 ) {
		move = uiInfo.uiDC.frameTime * scale * -speed;
		if ( move <= swing ) {
			move = swing;
			*swinging = qfalse;
		}
		*angle = AngleMod( *angle + move );
	}

	// clamp to no more than tolerance
	swing = AngleSubtract( destination, *angle );
	if ( swing > clampTolerance ) {
		*angle = AngleMod( destination - (clampTolerance - 1) );
	} else if ( swing < -clampTolerance ) {
		*angle = AngleMod( destination + (clampTolerance - 1) );
	}
}
#endif


/*
======================
UI_MovedirAdjustment
======================
*/
static float UI_MovedirAdjustment( playerInfo_t *pi ) {
	vec3_t		relativeAngles;
	vec3_t		moveVector;

	VectorSubtract( pi->viewAngles, pi->moveAngles, relativeAngles );
	AngleVectors( relativeAngles, moveVector, NULL, NULL );
	if ( Q_fabs( moveVector[0] ) < 0.01 ) {
		moveVector[0] = 0.0;
	}
	if ( Q_fabs( moveVector[1] ) < 0.01 ) {
		moveVector[1] = 0.0;
	}

	if ( moveVector[1] == 0 && moveVector[0] > 0 ) {
		return 0;
	}
	if ( moveVector[1] < 0 && moveVector[0] > 0 ) {
		return 22;
	}
	if ( moveVector[1] < 0 && moveVector[0] == 0 ) {
		return 45;
	}
	if ( moveVector[1] < 0 && moveVector[0] < 0 ) {
		return -22;
	}
	if ( moveVector[1] == 0 && moveVector[0] < 0 ) {
		return 0;
	}
	if ( moveVector[1] > 0 && moveVector[0] < 0 ) {
		return 22;
	}
	if ( moveVector[1] > 0 && moveVector[0] == 0 ) {
		return  -45;
	}

	return -22;
}


/*
===============
UI_PlayerAngles
===============
*/
static void UI_PlayerAngles( playerInfo_t *pi, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) {
	vec3_t		legsAngles, torsoAngles, headAngles;
	float		dest;
	float		adjust;

	VectorCopy( pi->viewAngles, headAngles );
	headAngles[YAW] = AngleMod( headAngles[YAW] );
	VectorClear( legsAngles );
	VectorClear( torsoAngles );

	// --------- yaw -------------

	// allow yaw to drift a bit
#ifdef TA_WEAPSYS
	if (!BG_PlayerStandAnim(&pi->playercfg, AP_LEGS, pi->legsAnim)
		|| !BG_PlayerStandAnim(&pi->playercfg, AP_TORSO, pi->torsoAnim))
#else
	if ( ( pi->legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE 
		|| ( pi->torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND )
#endif
	{
		// if not standing still, always point all in the same direction
		pi->torso.yawing = qtrue;	// always center
		pi->torso.pitching = qtrue;	// always center
		pi->legs.yawing = qtrue;	// always center
	}

	// adjust legs for movement dir
	adjust = UI_MovedirAdjustment( pi );
	legsAngles[YAW] = headAngles[YAW] + adjust;
	torsoAngles[YAW] = headAngles[YAW] + 0.25 * adjust;


	// torso
#ifdef IOQ3ZTM // BG_SWING_ANGLES
	BG_SwingAngles( torsoAngles[YAW], 25, 90, BG_SWINGSPEED, &pi->torso.yawAngle, &pi->torso.yawing, uiInfo.uiDC.frameTime );
	BG_SwingAngles( legsAngles[YAW], 40, 90, BG_SWINGSPEED, &pi->legs.yawAngle, &pi->legs.yawing, uiInfo.uiDC.frameTime );
#else
	UI_SwingAngles( torsoAngles[YAW], 25, 90, SWINGSPEED, &pi->torso.yawAngle, &pi->torso.yawing );
	UI_SwingAngles( legsAngles[YAW], 40, 90, SWINGSPEED, &pi->legs.yawAngle, &pi->legs.yawing );
#endif

	torsoAngles[YAW] = pi->torso.yawAngle;
	legsAngles[YAW] = pi->legs.yawAngle;

	// --------- pitch -------------

	// only show a fraction of the pitch angle in the torso
	if ( headAngles[PITCH] > 180 ) {
		dest = (-360 + headAngles[PITCH]) * 0.75;
	} else {
		dest = headAngles[PITCH] * 0.75;
	}
#ifdef IOQ3ZTM // BG_SWING_ANGLES
	BG_SwingAngles( dest, 15, 30, 0.1f, &pi->torso.pitchAngle, &pi->torso.pitching, uiInfo.uiDC.frameTime );
#else
	UI_SwingAngles( dest, 15, 30, 0.1f, &pi->torso.pitchAngle, &pi->torso.pitching );
#endif
	torsoAngles[PITCH] = pi->torso.pitchAngle;

	// pull the angles back out of the hierarchial chain
	AnglesSubtract( headAngles, torsoAngles, headAngles );
	AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
	AnglesToAxis( legsAngles, legs );
	AnglesToAxis( torsoAngles, torso );
	AnglesToAxis( headAngles, head );
}


/*
===============
UI_PlayerFloatSprite
===============
*/
static void UI_PlayerFloatSprite( playerInfo_t *pi, vec3_t origin, qhandle_t shader ) {
	refEntity_t		ent;

	memset( &ent, 0, sizeof( ent ) );
	VectorCopy( origin, ent.origin );
	ent.origin[2] += 48;
	ent.reType = RT_SPRITE;
	ent.customShader = shader;
	ent.radius = 10;
	ent.renderfx = 0;
	trap_R_AddRefEntityToScene( &ent );
}


/*
======================
UI_MachinegunSpinAngle
======================
*/
float	UI_MachinegunSpinAngle( playerInfo_t *pi ) {
	int		delta;
	float	angle;
	float	speed;
	int		torsoAnim;

	delta = dp_realtime - pi->barrelTime;
	if ( pi->barrelSpinning ) {
		angle = pi->barrelAngle + delta * SPIN_SPEED;
	} else {
		if ( delta > COAST_TIME ) {
			delta = COAST_TIME;
		}

		speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME );
		angle = pi->barrelAngle + delta * speed;
	}

	torsoAnim = pi->torsoAnim  & ~ANIM_TOGGLEBIT;
#ifdef TA_WEAPSYS
	if (BG_PlayerAttackAnim(torsoAnim))
#else
	if( torsoAnim == TORSO_ATTACK2 )
#endif
	{
		torsoAnim = TORSO_ATTACK;
	}
	if ( pi->barrelSpinning == !(torsoAnim == TORSO_ATTACK) ) {
		pi->barrelTime = dp_realtime;
		pi->barrelAngle = AngleMod( angle );
		pi->barrelSpinning = !!(torsoAnim == TORSO_ATTACK);
	}

	return angle;
}


/*
===============
UI_DrawPlayer
===============
*/
void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ) {
	refdef_t		refdef;
	refEntity_t		legs;
	refEntity_t		torso;
	refEntity_t		head;
#ifdef IOQ3ZTM // BONES
	refSkeleton_t	skeleton;
#endif
#ifdef TA_WEAPSYS
	refEntity_t		gun[MAX_HANDS];
#else
	refEntity_t		gun;
#endif
	refEntity_t		barrel;
	refEntity_t		flash;
	vec3_t			origin;
	int				renderfx;
	vec3_t			mins = {-16, -16, -24};
	vec3_t			maxs = {16, 16, 32};
	float			len;
	float			xx;
#ifdef TA_WEAPSYS
	int				i;
	vec3_t			angles;
#ifdef TURTLEARENA // PLAYERS
	char *newTagNames[3] = { "tag_hand_primary", "tag_hand_secondary", NULL };
#endif
	char *originalTagNames[3] = { "tag_weapon", "tag_flag", NULL };
#endif

#ifdef IOQ3ZTM // BONES
	if ( (!pi->playerModel && (!pi->legsModel || !pi->torsoModel || !pi->headModel))
#ifdef TA_PLAYERSYS
	|| !pi->playercfg.animations[0].numFrames ) {
#else
	|| !pi->animations[0].numFrames ) {
#endif
		return;
	}
#else
	if ( !pi->legsModel || !pi->torsoModel || !pi->headModel
#ifdef TA_PLAYERSYS
	|| !pi->playercfg.animations[0].numFrames ) {
#else
	|| !pi->animations[0].numFrames ) {
#endif
		return;
	}
#endif

	// this allows the ui to cache the player model on the main menu
	if (w == 0 || h == 0) {
		return;
	}

	dp_realtime = time;

	if ( pi->pendingWeapon != -1 && dp_realtime > pi->weaponTimer ) {
		pi->weapon = pi->pendingWeapon;
		pi->lastWeapon = pi->pendingWeapon;
		pi->pendingWeapon = -1;
		pi->weaponTimer = 0;
#ifndef TA_WEAPSYS_EX
		if( pi->currentWeapon != pi->weapon ) {
			trap_S_StartLocalSound( weaponChangeSound, CHAN_LOCAL );
		}
#endif
	}

	UI_AdjustFrom640( &x, &y, &w, &h );

	y -= jumpHeight;

	memset( &refdef, 0, sizeof( refdef ) );
	memset( &legs, 0, sizeof(legs) );
	memset( &torso, 0, sizeof(torso) );
	memset( &head, 0, sizeof(head) );

	refdef.rdflags = RDF_NOWORLDMODEL;

	AxisClear( refdef.viewaxis );

	refdef.x = x;
	refdef.y = y;
	refdef.width = w;
	refdef.height = h;

#ifdef TURTLEARENA // FOV
	refdef.fov_x = (int)((float)refdef.width / 640.0f * 70.0f);
#else
	refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f);
#endif
	xx = refdef.width / tan( refdef.fov_x / 360 * M_PI );
	refdef.fov_y = atan2( refdef.height, xx );
	refdef.fov_y *= ( 360 / (float)M_PI );

	// calculate distance so the player nearly fills the box
	len = 0.7 * ( maxs[2] - mins[2] );		
	origin[0] = len / tan( DEG2RAD(refdef.fov_x) * 0.5 );
	origin[1] = 0.5 * ( mins[1] + maxs[1] );
	origin[2] = -0.5 * ( mins[2] + maxs[2] );

	refdef.time = dp_realtime;

	trap_R_ClearScene();

	// get the rotation information
	UI_PlayerAngles( pi, legs.axis, torso.axis, head.axis );
	
	// get the animation state (after rotation, to allow feet shuffle)
	UI_PlayerAnimation( pi, &legs.oldframe, &legs.frame, &legs.backlerp,
		 &torso.oldframe, &torso.frame, &torso.backlerp );

	renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW;

#ifdef IOQ3ZTM // BONES
	if (pi->playerModel) {
		// get skeleton
		UI_PlayerSkeleton( pi, &legs, &torso, &head, &skeleton );

		//
		// add the player
		//
		legs.hModel = pi->playerModel;
		legs.customSkin = pi->playerSkin;

		VectorCopy( origin, legs.origin );

		VectorCopy( origin, legs.lightingOrigin );
		legs.renderfx = renderfx;
		VectorCopy (legs.origin, legs.oldorigin);

		trap_R_AddRefEntityToScene_CustomSkeleton( &legs, &skeleton );
	} else {
#endif
	//
	// add the legs
	//
	legs.hModel = pi->legsModel;
	legs.customSkin = pi->legsSkin;

	VectorCopy( origin, legs.origin );

	VectorCopy( origin, legs.lightingOrigin );
	legs.renderfx = renderfx;
	VectorCopy (legs.origin, legs.oldorigin);

	trap_R_AddRefEntityToScene( &legs );

	if (!legs.hModel) {
		return;
	}

	//
	// add the torso
	//
	torso.hModel = pi->torsoModel;
	if (!torso.hModel) {
		return;
	}

	torso.customSkin = pi->torsoSkin;

	VectorCopy( origin, torso.lightingOrigin );

	UI_PositionRotatedEntityOnTag( &torso, &legs, pi->legsModel, NULL, "tag_torso");

	torso.renderfx = renderfx;

	trap_R_AddRefEntityToScene( &torso );

	//
	// add the head
	//
	head.hModel = pi->headModel;
	if (!head.hModel) {
		return;
	}
	head.customSkin = pi->headSkin;

	VectorCopy( origin, head.lightingOrigin );

	UI_PositionRotatedEntityOnTag( &head, &torso, pi->torsoModel, NULL, "tag_head");

	head.renderfx = renderfx;

	trap_R_AddRefEntityToScene( &head );
#ifdef IOQ3ZTM // BONES
	}
#endif

	//
	// add the gun
	//
	if ( pi->currentWeapon != WP_NONE ) {

#ifdef TA_WEAPSYS
		// get hands from cent
		for (i = 0; i < MAX_HANDS; i++)
		{
			memset( &gun[i], 0, sizeof(gun[i]) );
			gun[i].hModel = pi->weaponModel[i];
			VectorCopy( origin, gun[i].lightingOrigin );
			gun[i].renderfx = renderfx;
			Byte4Copy( pi->c1RGBA, gun[i].shaderRGBA );

			if (!originalTagNames[i]
#ifdef TURTLEARENA // PLAYERS
				|| !newTagNames[i]
#endif
				)
			{
				break;
			}

			if (!gun[i].hModel) {
				continue;
			}

#ifdef IOQ3ZTM // BONES
			if (pi->playerModel) {
				if (
#ifdef TURTLEARENA // PLAYERS
					!UI_PositionEntityOnTag( &gun[i], &legs, pi->playerModel, &skeleton, newTagNames[i]) &&
#endif
					!UI_PositionEntityOnTag( &gun[i], &legs, pi->playerModel, &skeleton, originalTagNames[i]))
				{
					// Failed to find tag
					continue;
				}
			}
			else
#endif
			if (
#ifdef TURTLEARENA // PLAYERS
				!UI_PositionEntityOnTag( &gun[i], &torso, pi->torsoModel, NULL, newTagNames[i]) &&
#endif
				!UI_PositionEntityOnTag( &gun[i], &torso, pi->torsoModel, NULL, originalTagNames[i]))
			{
				// Failed to find tag
				continue;
			}


			trap_R_AddRefEntityToScene( &gun[i] );
		}
#else
		memset( &gun, 0, sizeof(gun) );
		gun.hModel = pi->weaponModel;
#ifdef IOQ3ZTM
		if( pi->currentWeapon == WP_RAILGUN ) {
			Byte4Copy( pi->c1RGBA, gun.shaderRGBA );
		}
		else {
			Byte4Copy( colorWhite, gun.shaderRGBA );
		}
#endif
		VectorCopy( origin, gun.lightingOrigin );
#ifdef IOQ3ZTM // BONES
		if (pi->playerModel)
			UI_PositionEntityOnTag( &gun, &legs, pi->playerModel, &skeleton, "tag_weapon");
		else
#endif
		UI_PositionEntityOnTag( &gun, &torso, pi->torsoModel, NULL, "tag_weapon");
		gun.renderfx = renderfx;
		trap_R_AddRefEntityToScene( &gun );
#endif
	}

	//
	// add the spinning barrel
	//
#ifdef TA_WEAPSYS
	for (i = 0; i < MAX_HANDS; i++)
#else
	if ( pi->realWeapon == WP_MACHINEGUN || pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG )
#endif
	{
#ifdef TA_WEAPSYS
		if (!pi->barrelModel[i])
			continue;
#else
		vec3_t	angles;
#endif

		memset( &barrel, 0, sizeof(barrel) );
		VectorCopy( origin, barrel.lightingOrigin );
		barrel.renderfx = renderfx;

#ifdef TA_WEAPSYS
		barrel.hModel = pi->barrelModel[i];
		VectorClear(angles);
		if (bg_weapongroupinfo[pi->realWeapon].weapon[0]->barrelSpin != BS_NONE)
		{
			angles[bg_weapongroupinfo[pi->realWeapon].weapon[0]->barrelSpin]
						= UI_MachinegunSpinAngle( pi );
		}
#else
		barrel.hModel = pi->barrelModel;
		angles[YAW] = 0;
		angles[PITCH] = 0;
		angles[ROLL] = UI_MachinegunSpinAngle( pi );
		if( pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) {
			angles[PITCH] = angles[ROLL];
			angles[ROLL] = 0;
		}
#endif
		AnglesToAxis( angles, barrel.axis );

#ifdef TA_WEAPSYS
		UI_PositionRotatedEntityOnTag( &barrel, &gun[i], pi->weaponModel[i], NULL, "tag_barrel");
#else
		UI_PositionRotatedEntityOnTag( &barrel, &gun, pi->weaponModel, NULL, "tag_barrel");
#endif

		trap_R_AddRefEntityToScene( &barrel );
	}

	//
	// add muzzle flash
	//
	if ( dp_realtime <= pi->muzzleFlashTime ) {
#ifdef TA_WEAPSYS
		vec3_t *flashDlightColor;

		for (i = 0; i < MAX_HANDS; i++)
		{
			memset( &flash, 0, sizeof(flash) );
			flash.hModel = pi->flashModel[i];
			flashDlightColor = &pi->flashDlightColor[i];
			Byte4Copy( pi->c1RGBA, flash.shaderRGBA );

			if (!flash.hModel)
				continue;

			VectorCopy( origin, flash.lightingOrigin );
			UI_PositionEntityOnTag( &flash, &gun[i], pi->weaponModel[i], NULL, "tag_flash");
			flash.renderfx = renderfx;
			trap_R_AddRefEntityToScene( &flash );

			// make a dlight for the flash
			if ( *flashDlightColor[0] || *flashDlightColor[1] || *flashDlightColor[2] ) {
				trap_R_AddLightToScene( flash.origin, 200 + (rand()&31), *flashDlightColor[0],
					*flashDlightColor[1], *flashDlightColor[2] );
			}
		}
#else
		if ( pi->flashModel ) {
			memset( &flash, 0, sizeof(flash) );
			flash.hModel = pi->flashModel;
#ifdef IOQ3ZTM
			if( pi->currentWeapon == WP_RAILGUN ) {
				Byte4Copy( pi->c1RGBA, flash.shaderRGBA );
			}
			else {
				Byte4Copy( colorWhite, flash.shaderRGBA );
			}
#endif
			VectorCopy( origin, flash.lightingOrigin );
			UI_PositionEntityOnTag( &flash, &gun, pi->weaponModel, NULL, "tag_flash");
			flash.renderfx = renderfx;
			trap_R_AddRefEntityToScene( &flash );
		}

		// make a dlight for the flash
		if ( pi->flashDlightColor[0] || pi->flashDlightColor[1] || pi->flashDlightColor[2] ) {
			trap_R_AddLightToScene( flash.origin, 200 + (rand()&31), pi->flashDlightColor[0],
				pi->flashDlightColor[1], pi->flashDlightColor[2] );
		}
#endif
	}

	//
	// add the chat icon
	//
	if ( pi->chat ) {
#ifdef TA_DATA // shaders
		UI_PlayerFloatSprite( pi, origin, trap_R_RegisterShaderNoMip( "sprites/talkBalloon" ) );
#else
		UI_PlayerFloatSprite( pi, origin, trap_R_RegisterShaderNoMip( "sprites/balloon3" ) );
#endif
	}

	//
	// add an accent light
	//
	origin[0] -= 100;	// + = behind, - = in front
	origin[1] += 100;	// + = left, - = right
	origin[2] += 100;	// + = above, - = below
	trap_R_AddLightToScene( origin, 500, 1.0, 1.0, 1.0 );

	origin[0] -= 100;
	origin[1] -= 100;
	origin[2] -= 100;
	trap_R_AddLightToScene( origin, 500, 1.0, 0.0, 0.0 );

	trap_R_RenderScene( &refdef );
}

/*
==========================
UI_FileExists
==========================
*/
static qboolean	UI_FileExists(const char *filename) {
	int len;

	len = trap_FS_FOpenFile( filename, NULL, FS_READ );
	if (len>0) {
		return qtrue;
	}
	return qfalse;
}

/*
==========================
UI_FindClientHeadFile
==========================
*/
static qboolean	UI_FindClientHeadFile( char *filename, int length, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) {
	char *team, *headsFolder;
	int i;

	team = "default";

	if ( headModelName[0] == '*' ) {
		headsFolder = "heads/";
		headModelName++;
	}
	else {
		headsFolder = "";
	}
	while(1) {
		for ( i = 0; i < 2; i++ ) {
			if ( i == 0 && teamName && *teamName ) {
				Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext );
			}
			else {
				Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext );
			}
			if ( UI_FileExists( filename ) ) {
				return qtrue;
			}
			if ( i == 0 && teamName && *teamName ) {
				Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext );
			}
			else {
				Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext );
			}
			if ( UI_FileExists( filename ) ) {
				return qtrue;
			}
			if ( !teamName || !*teamName ) {
				break;
			}
		}
		// if tried the heads folder first
		if ( headsFolder[0] ) {
			break;
		}
		headsFolder = "heads/";
	}

	return qfalse;
}

/*
==========================
UI_RegisterClientSkin
==========================
*/
static qboolean	UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName , const char *teamName) {
	char		filename[MAX_QPATH];

#ifdef IOQ3ZTM // BONES
	// single model player has single skin
	if (pi->playerModel) {
		if (teamName && *teamName) {
			Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/player_%s.skin", modelName, teamName, skinName );
		} else {
			Com_sprintf( filename, sizeof( filename ), "models/players/%s/player_%s.skin", modelName, skinName );
		}
		pi->playerSkin = trap_R_RegisterSkin( filename );

		if (!pi->playerSkin) {
			return qfalse;
		}

		return qtrue;
	}
#endif

	if (teamName && *teamName) {
		Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/lower_%s.skin", modelName, teamName, skinName );
	} else {
		Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName );
	}
	pi->legsSkin = trap_R_RegisterSkin( filename );

	if (teamName && *teamName) {
		Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/upper_%s.skin", modelName, teamName, skinName );
	} else {
		Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName );
	}
	pi->torsoSkin = trap_R_RegisterSkin( filename );

	if ( UI_FindClientHeadFile( filename, sizeof(filename), teamName, headModelName, headSkinName, "head", "skin" ) ) {
		pi->headSkin = trap_R_RegisterSkin( filename );
	}

	if ( !pi->legsSkin || !pi->torsoSkin || !pi->headSkin ) {
		return qfalse;
	}

	return qtrue;
}


#ifndef TA_PLAYERSYS // Moved to bg_misc.c BG_LoadPlayerCFGFile
/*
======================
UI_ParseAnimationFile
======================
*/
static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animations ) {
	char		*text_p, *prev;
	int			len;
	int			i;
	char		*token;
	float		fps;
	int			skip;
	char		text[20000];
	fileHandle_t	f;

	memset( animations, 0, sizeof( animation_t ) * MAX_ANIMATIONS );

	// load the file
	len = trap_FS_FOpenFile( filename, &f, FS_READ );
	if ( len <= 0 ) {
		return qfalse;
	}
	if ( len >= ( sizeof( text ) - 1 ) ) {
		Com_Printf( "File %s too long\n", filename );
		trap_FS_FCloseFile( f );
		return qfalse;
	}
	trap_FS_Read( text, len, f );
	text[len] = 0;
	trap_FS_FCloseFile( f );

	COM_Compress(text);

	// parse the text
	text_p = text;
	skip = 0;	// quite the compiler warning

	// read optional parameters
	while ( 1 ) {
		prev = text_p;	// so we can unget
		token = COM_Parse( &text_p );
		if ( !token ) {
			break;
		}
		if ( !Q_stricmp( token, "footsteps" ) ) {
			token = COM_Parse( &text_p );
			if ( !token ) {
				break;
			}
			continue;
		} else if ( !Q_stricmp( token, "headoffset" ) ) {
			for ( i = 0 ; i < 3 ; i++ ) {
				token = COM_Parse( &text_p );
				if ( !token ) {
					break;
				}
			}
			continue;
		} else if ( !Q_stricmp( token, "sex" ) ) {
			token = COM_Parse( &text_p );
			if ( !token ) {
				break;
			}
			continue;
		}

		// if it is a number, start parsing animations
		if ( token[0] >= '0' && token[0] <= '9' ) {
			text_p = prev;	// unget the token
			break;
		}

		Com_Printf( "unknown token '%s' is %s\n", token, filename );
	}

	// read information for each frame
	for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) {

		token = COM_Parse( &text_p );
		if ( !token ) {
			break;
		}
		animations[i].firstFrame = atoi( token );
		// leg only frames are adjusted to not count the upper body only frames
		if ( i == LEGS_WALKCR ) {
			skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame;
		}
		if ( i >= LEGS_WALKCR ) {
			animations[i].firstFrame -= skip;
		}

		token = COM_Parse( &text_p );
		if ( !token ) {
			break;
		}
		animations[i].numFrames = atoi( token );

		token = COM_Parse( &text_p );
		if ( !token ) {
			break;
		}
		animations[i].loopFrames = atoi( token );

		token = COM_Parse( &text_p );
		if ( !token ) {
			break;
		}
		fps = atof( token );
		if ( fps == 0 ) {
			fps = 1;
		}
		animations[i].frameLerp = 1000 / fps;
		animations[i].initialLerp = 1000 / fps;
	}

	if ( i != MAX_ANIMATIONS ) {
		Com_Printf( "Error parsing animation file: %s\n", filename );
		return qfalse;
	}

	return qtrue;
}
#endif // TA_PLAYERSYS

/*
==========================
UI_RegisterClientModelname
==========================
*/
qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName, const char *headModelSkinName, const char *teamName ) {
	char		modelName[MAX_QPATH];
	char		skinName[MAX_QPATH];
	char		headModelName[MAX_QPATH];
	char		headSkinName[MAX_QPATH];
	char		filename[MAX_QPATH];
	char		*slash;

	pi->torsoModel = 0;
	pi->headModel = 0;

	if ( !modelSkinName[0] ) {
		return qfalse;
	}

	Q_strncpyz( modelName, modelSkinName, sizeof( modelName ) );

	slash = strchr( modelName, '/' );
	if ( !slash ) {
		// modelName did not include a skin name
		Q_strncpyz( skinName, "default", sizeof( skinName ) );
	} else {
		Q_strncpyz( skinName, slash + 1, sizeof( skinName ) );
		*slash = '\0';
	}

	Q_strncpyz( headModelName, headModelSkinName, sizeof( headModelName ) );
	slash = strchr( headModelName, '/' );
	if ( !slash ) {
		// modelName did not include a skin name
		Q_strncpyz( headSkinName, "default", sizeof( skinName ) );
	} else {
		Q_strncpyz( headSkinName, slash + 1, sizeof( skinName ) );
		*slash = '\0';
	}

#ifdef IOQ3ZTM // BLANK_HEADMODEL
	if (!headModelName[0]) {
		Q_strncpyz( headModelName, modelName, sizeof( headModelName ) );
	}
#endif

	// load cmodels before models so filecache works

#ifdef IOQ3ZTM // BONES
	// Try loading single model player
	Com_sprintf( filename, sizeof( filename ), "models/players/%s/player.iqm", modelName );
	pi->playerModel = trap_R_RegisterModel( filename );

	// Try loading multimodel player
	if (!pi->playerModel) {
#endif
	Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName );
	pi->legsModel = trap_R_RegisterModel( filename );
	if ( !pi->legsModel ) {
		Com_Printf( "Failed to load model file %s\n", filename );
		return qfalse;
	}

	Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName );
	pi->torsoModel = trap_R_RegisterModel( filename );
	if ( !pi->torsoModel ) {
		Com_Printf( "Failed to load model file %s\n", filename );
		return qfalse;
	}

	if (headModelName[0] == '*' ) {
		Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] );
	}
	else {
		Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headModelName );
	}
	pi->headModel = trap_R_RegisterModel( filename );
	if ( !pi->headModel && headModelName[0] != '*') {
		Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName );
		pi->headModel = trap_R_RegisterModel( filename );
	}

	if (!pi->headModel) {
		Com_Printf( "Failed to load model file %s\n", filename );
		return qfalse;
	}
#ifdef IOQ3ZTM // BONES
	}
#endif

	// if any skins failed to load, fall back to default
	if ( !UI_RegisterClientSkin( pi, modelName, skinName, headModelName, headSkinName, teamName) ) {
		if ( !UI_RegisterClientSkin( pi, modelName, "default", headModelName, "default", teamName ) ) {
			Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName );
			return qfalse;
		}
	}

	// load the animations
#ifdef TA_PLAYERSYS
	return BG_LoadPlayerCFGFile(&pi->playercfg, modelName, headModelName);
#else
	Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName );
	if ( !UI_ParseAnimationFile( filename, pi->animations ) ) {
		Com_Printf( "Failed to load animation file %s\n", filename );
		return qfalse;
	}

	return qtrue;
#endif
}


/*
===============
UI_PlayerInfo_SetModel
===============
*/
void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model, const char *headmodel, char *teamName ) {
	memset( pi, 0, sizeof(*pi) );
	UI_RegisterClientModelname( pi, model, headmodel, teamName );
#if defined TA_PLAYERSYS && defined TA_WEAPSYS
	pi->weapon = pi->playercfg.default_weapon;
#else
	pi->weapon = WP_MACHINEGUN;
#endif
	pi->currentWeapon = pi->weapon;
	pi->lastWeapon = pi->weapon;
	pi->pendingWeapon = -1;
	pi->weaponTimer = 0;
	pi->chat = qfalse;
	pi->newModel = qtrue;
	UI_PlayerInfo_SetWeapon( pi, pi->weapon );
}


/*
===============
UI_PlayerInfo_SetInfo
===============
*/
void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNumber, qboolean chat ) {
	int			currentAnim;
	weapon_t	weaponNum;
#ifdef IOQ3ZTM
	int			c;
#endif

	pi->chat = chat;

#ifdef IOQ3ZTM
	c = (int)trap_Cvar_VariableValue( "color1" );
 
	VectorClear( pi->color1 );

	if( c < 1 || c > 7 ) {
		VectorSet( pi->color1, 1, 1, 1 );
	}
	else {
		if( c & 1 ) {
			pi->color1[2] = 1.0f;
		}

		if( c & 2 ) {
			pi->color1[1] = 1.0f;
		}

		if( c & 4 ) {
			pi->color1[0] = 1.0f;
		}
	}

	pi->c1RGBA[0] = 255 * pi->color1[0];
	pi->c1RGBA[1] = 255 * pi->color1[1];
	pi->c1RGBA[2] = 255 * pi->color1[2];
	pi->c1RGBA[3] = 255;
#endif

	// view angles
	VectorCopy( viewAngles, pi->viewAngles );

	// move angles
	VectorCopy( moveAngles, pi->moveAngles );

	if ( pi->newModel ) {
		pi->newModel = qfalse;

#ifdef TA_WEAPSYS
		atkAnim = 0;
#endif
		jumpHeight = 0;
		pi->pendingLegsAnim = 0;
		UI_ForceLegsAnim( pi, legsAnim );
		pi->legs.yawAngle = viewAngles[YAW];
		pi->legs.yawing = qfalse;

		pi->pendingTorsoAnim = 0;
		UI_ForceTorsoAnim( pi, torsoAnim );
		pi->torso.yawAngle = viewAngles[YAW];
		pi->torso.yawing = qfalse;

		if ( weaponNumber != -1 ) {
			pi->weapon = weaponNumber;
			pi->currentWeapon = weaponNumber;
			pi->lastWeapon = weaponNumber;
			pi->pendingWeapon = -1;
			pi->weaponTimer = 0;
			UI_PlayerInfo_SetWeapon( pi, pi->weapon );
		}

		return;
	}

	// weapon
	if ( weaponNumber == -1 ) {
		pi->pendingWeapon = -1;
		pi->weaponTimer = 0;
	}
	else if ( weaponNumber != WP_NONE ) {
		pi->pendingWeapon = weaponNumber;
		pi->weaponTimer = dp_realtime + UI_TIMER_WEAPON_DELAY;
	}
	weaponNum = pi->lastWeapon;
	pi->weapon = weaponNum;

	if ( torsoAnim == BOTH_DEATH1 || legsAnim == BOTH_DEATH1 ) {
		torsoAnim = legsAnim = BOTH_DEATH1;
		pi->weapon = pi->currentWeapon = WP_NONE;
		UI_PlayerInfo_SetWeapon( pi, pi->weapon );

		jumpHeight = 0;
		pi->pendingLegsAnim = 0;
		UI_ForceLegsAnim( pi, legsAnim );

		pi->pendingTorsoAnim = 0;
		UI_ForceTorsoAnim( pi, torsoAnim );

		return;
	}

	// leg animation
#ifdef TA_WEAPSYS
	if (BG_PlayerAttackAnim(torsoAnim))
	{
		// if torso is attacking, possibly need to animate legs too.
		currentAnim = BG_LegsAttackForWeapon(&pi->playercfg, weaponNum, atkAnim);
		if (currentAnim != -1) {
			legsAnim = currentAnim;
		}
	}
#endif

	currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;
	if ( legsAnim != LEGS_JUMP && ( currentAnim == LEGS_JUMP || currentAnim == LEGS_LAND ) ) {
		pi->pendingLegsAnim = legsAnim;
	}
	else if ( legsAnim != currentAnim ) {
		jumpHeight = 0;
		pi->pendingLegsAnim = 0;
		UI_ForceLegsAnim( pi, legsAnim );
	}

	// torso animation
#ifdef TA_WEAPSYS
	if (BG_PlayerStandAnim(&pi->playercfg, AP_TORSO, torsoAnim))
	{
		torsoAnim = BG_TorsoStandForWeapon(weaponNum);
	}
#else
	if ( torsoAnim == TORSO_STAND || torsoAnim == TORSO_STAND2 ) {
		if ( weaponNum == WP_NONE || weaponNum == WP_GAUNTLET ) {
			torsoAnim = TORSO_STAND2;
		}
		else {
			torsoAnim = TORSO_STAND;
		}
	}
#endif

#ifdef TA_WEAPSYS
	if (BG_PlayerAttackAnim(torsoAnim))
	{
		torsoAnim = BG_TorsoAttackForWeapon(weaponNum, atkAnim);
		if (!BG_WeaponHasMelee(weaponNum))
		{
			pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH;
		}
		//FIXME play firing sound here

		atkAnim++;
	}
#else
	if ( torsoAnim == TORSO_ATTACK || torsoAnim == TORSO_ATTACK2 ) {
		if ( weaponNum == WP_NONE || weaponNum == WP_GAUNTLET ) {
			torsoAnim = TORSO_ATTACK2;
		}
		else {
			torsoAnim = TORSO_ATTACK;
		}
		pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH;
		//FIXME play firing sound here
	}
#endif

	currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;

	if ( weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISE || currentAnim == TORSO_DROP ) {
		pi->pendingTorsoAnim = torsoAnim;
	}
	else if ( ( currentAnim == TORSO_GESTURE ||
#ifdef TA_WEAPSYS
		BG_PlayerAttackAnim(currentAnim)
#else
		currentAnim == TORSO_ATTACK
#endif
	) && ( torsoAnim != currentAnim ) ) {
		pi->pendingTorsoAnim = torsoAnim;
	}
	else if ( torsoAnim != currentAnim ) {
		pi->pendingTorsoAnim = 0;
		UI_ForceTorsoAnim( pi, torsoAnim );
	}
}
