// Copyright (C) 1999-2000 Id Software, Inc.
//
// ui_players.c

#include "ui_local.h"


#define UI_TIMER_GESTURE		2300
#define UI_TIMER_JUMP			1000
#define UI_TIMER_LAND			130
#define UI_TIMER_WEAPON_SWITCH	300
#define UI_TIMER_ATTACK			500
#define	UI_TIMER_MUZZLE_FLASH	20
#define	UI_TIMER_WEAPON_DELAY	250

#define JUMP_HEIGHT				56

#define SWINGSPEED				0.2  //TiM - 0.3

#define SPIN_SPEED				0.9
#define COAST_TIME				1000


static int			dp_realtime;
static float		jumpHeight;

//TiM : Bolton Table
stringID_table_t BoltonTable[BOLTON_MAX + 1] =
{
	{ ENUM2STRING(BOLTON_HEAD) },
	{ ENUM2STRING(BOLTON_TORSO) },
	{ ENUM2STRING(BOLTON_LEGS) },
	{ NULL, -1 }
};

/*
===============
UI_PlayerInfo_SetWeapon
===============
*/
static void UI_PlayerInfo_SetWeapon( playerInfo_t *pi, weapon_t weaponNum ) {
	gitem_t *	item;
	char		path[MAX_QPATH];

	pi->currentWeapon = weaponNum;
tryagain:
	pi->realWeapon = weaponNum;
	pi->weaponModel = 0;
	pi->barrelModel = 0;
	pi->flashModel = 0;

	if ( weaponNum == WP_NONE ) {
		return;
	}

	for ( item = bg_itemlist + 1; item->classname ; item++ ) {
		if ( item->giType != IT_WEAPON ) {
			continue;
		}
		if ( item->giTag == weaponNum ) {
			break;
		}
	}

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

	if( pi->weaponModel == 0 )
	{
		if( weaponNum == WP_PHASER )
		{

			weaponNum = WP_NONE;
			goto tryagain;
		}
		weaponNum = WP_PHASER;
		goto tryagain;
	}

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

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

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

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

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

	case WP_NULL_HAND:
		//MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 );
		break;

	case WP_COMPRESSION_RIFLE:
		MAKERGB( pi->flashDlightColor, 0.16, 0.16, 1 );
		break;

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

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

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

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


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

	if ( anim == BOTH_JUMP1 ) {
		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 ) {
		pi->torsoAnimationTimer = UI_TIMER_GESTURE;
	}*/

	if ( anim == UI_GetAnim( ANIM_ATTACK, pi->currentWeapon, qtrue ) ) { //BOTH_ATTACK1 //Hack ROFL. Code can't see the ANIM defines from here. ANIM_ATTACK = 22
		pi->torsoAnimationTimer = UI_TIMER_ATTACK;
	}
}


/*
===============
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 && !pi->upperEmoting ) {
		if ( currentAnim != TORSO_DROPWEAP1 ) {
			pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
			UI_ForceTorsoAnim( pi, TORSO_DROPWEAP1 );
		}
	}

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

	/*if( currentAnim == TORSO_GESTURE ) {
		UI_SetTorsoAnim( pi, TORSO_STAND );
		return;
	}*/

	if( currentAnim == UI_GetAnim( ANIM_ATTACK, pi->currentWeapon, qtrue ) ) {  //BOTH_ATTACK1 22 = ANIM_ATTACK
		//if ( pi->currentWeapon == WP_NONE || pi->currentWeapon == WP_PHASER  ) 
		if ( pi->currentWeapon != WP_COMPRESSION_RIFLE 
			&& pi->currentWeapon != WP_TR116
			&& pi->currentWeapon != WP_GRENADE_LAUNCHER
			&& pi->currentWeapon != WP_QUANTUM_BURST )
		{
			UI_SetTorsoAnim( pi, BOTH_STAND1 ); //TORSO_STAND
		}
		else 
		{
			UI_SetTorsoAnim( pi, BOTH_STAND4 ); //TORSO_STAND
		}

		return;
	}

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

	if ( currentAnim == TORSO_RAISEWEAP1 ) {
		UI_SetTorsoAnim( pi, BOTH_STAND1 ); //STAND2
		return;
	}

	//TiM: Was playing a non-loop emote, so go back to default now
	if ( pi->upperEmoting ) {
		if ( !pi->upperLoopEmote )
		{
			UI_SetTorsoAnim( pi, BOTH_STAND1 );
			pi->upperEmoting = qfalse;
		}
		else
		{
			UI_SetTorsoAnim( pi, pi->upperLoopEmote );
			pi->upperEmoting = qfalse;
		}
		return;
	}
}


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

	currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;

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

	if ( currentAnim == BOTH_JUMP1) {
		UI_ForceLegsAnim( pi, BOTH_LAND1 );
		pi->legsAnimationTimer = UI_TIMER_LAND;
		jumpHeight = 0;
		return;
	}

	if ( currentAnim == BOTH_LAND1 ) {
		UI_SetLegsAnim( pi, BOTH_STAND1 );
		return;
	}

	//TiM: Was playing a non-loop emote, so go back to default now
	if ( pi->lowerEmoting ) {
		if ( !pi->lowerLoopEmote )
		{
			UI_SetLegsAnim( pi, BOTH_STAND1 );
			pi->lowerEmoting = qfalse;
		}
		else
		{
			UI_SetLegsAnim( pi, pi->lowerLoopEmote );
			pi->lowerEmoting = qfalse;
		}
		return;
	}
}


/*
======================
UI_PositionEntityOnTag
======================
*/
static void UI_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, 
							clipHandle_t parentModel, char *tagName ) {
	int				i;
	orientation_t	lerped;
	
	// lerp the tag
	trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
		1.0 - parent->backlerp, tagName );

	// 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;
}


/*
======================
UI_PositionRotatedEntityOnTag
======================
*/
static void UI_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, 
							clipHandle_t parentModel, char *tagName ) {
	int				i;
	orientation_t	lerped;
	vec3_t			tempAxis[3];

	// lerp the tag
	trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
		1.0 - parent->backlerp, tagName );

	// 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 );
}


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

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

	if ( newAnimation < 0 || newAnimation >= MAX_ANIMATIONS ) {
		trap_Error( va("Bad animation number: %i", newAnimation) );
	}

	anim = &ci->animations[ newAnimation ];

	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 ( anim->numFrames < 0 ) {
			UI_SetLerpFrameAnimation( ci, lf, BOTH_STAND1 );
		}*/

		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 );
	}
}


/*
===============
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 -= uis.frametime;
	if ( pi->legsAnimationTimer < 0 ) {
		pi->legsAnimationTimer = 0;
	}

	UI_LegsSequencing( pi );

	if ( pi->legs.yawing && ( ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == BOTH_STAND1 
		|| ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == BOTH_STAND2
		|| ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == BOTH_STAND4 ) ) 
	{
		UI_RunLerpFrame( pi, &pi->legs, LEGS_TURN1 );
	} else {
		UI_RunLerpFrame( pi, &pi->legs, pi->legsAnim );
	}
	*legsOld = pi->legs.oldFrame;
	*legs = pi->legs.frame;
	*legsBackLerp = pi->legs.backlerp;

	// torso animation
	pi->torsoAnimationTimer -= uis.frametime;
	if ( pi->torsoAnimationTimer < 0 ) {
		pi->torsoAnimationTimer = 0;
	}

	UI_TorsoSequencing( pi );

	UI_RunLerpFrame( pi, &pi->torso, pi->torsoAnim );
	*torsoOld = pi->torso.oldFrame;
	*torso = pi->torso.frame;
	*torsoBackLerp = pi->torso.backlerp;
}


/*
==================
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 = uis.frametime * scale * speed;
		if ( move >= swing ) {
			move = swing;
			*swinging = qfalse;
		}
		*angle = AngleMod( *angle + move );
	} else if ( swing < 0 ) {
		move = uis.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) );
	}
}


/*
======================
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
	if ( ( pi->legsAnim & ~ANIM_TOGGLEBIT ) != UI_GetAnim( ANIM_IDLE, pi->currentWeapon, qfalse ) //TORSO_STAND2 
		|| ( pi->torsoAnim & ~ANIM_TOGGLEBIT ) != UI_GetAnim( ANIM_IDLE, pi->currentWeapon, qtrue ) ) {
		// 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
	if ( !uis.spinView ) {
		//adjust = UI_MovedirAdjustment( pi ); //TiM: Do we really need this?
		adjust = 0;
		legsAngles[YAW] = headAngles[YAW] + adjust;
		torsoAngles[YAW] = headAngles[YAW] + /*0.25 **/ adjust;

		// torso
		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 );
	}
	else {
		pi->torso.yawAngle = headAngles[YAW];
		pi->legs.yawAngle = headAngles[YAW];
	}

	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;
	}
	UI_SwingAngles( dest, 15, 30, 0.1, &pi->torso.pitchAngle, &pi->torso.pitching );
	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.data.sprite.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;
	if( torsoAnim == TORSO_ATTACK2 ) {
		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, vec3_t pOrigin, playerInfo_t *pi, int time  ) { //RPG-X : TiM- Origin added
	refdef_t		refdef;
	refEntity_t		legs;
	refEntity_t		torso;
	refEntity_t		head;
	refEntity_t		gun;
	refEntity_t		flash;
	vec3_t			origin;
	int				renderfx;
	vec3_t			mins = {-16, -24, -24};
	vec3_t			maxs = {16, 16, 32};
	float			len;
	float			xx;
	int				anim;

	if ( !pi->legsModel || !pi->torsoModel || !pi->headModel || !pi->animations[0].numFrames ) {
		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;
		/*if( pi->currentWeapon != pi->weapon ) {
			trap_S_StartLocalSound( trap_S_RegisterSound( "sound/weapons/change.wav" ), CHAN_LOCAL );
		}*/
	}

	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;

	refdef.fov_x = (int)((float)refdef.width / 640.0f * 10.0f); //RPG-X : TiM- 90.0f //Anyone else noticed how the high FOV value distorted the model horribly in the menus? O_o
	xx = refdef.width / tan( refdef.fov_x / 360 * M_PI );
	refdef.fov_y = atan2( refdef.height, xx );
	refdef.fov_y *= ( 360 / M_PI );

	// calculate distance so the player nearly fills the box
	//len = 0.7f * ( 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] );

	len = 0.35f * ( maxs[2] - mins[2] );		//TiM: 0.35f
	origin[0] = (len / tan( DEG2RAD(refdef.fov_x) * 0.5 )) + pOrigin[0]; //0.5 //Z scale - Conventional 3d, not Q3 ;)
	origin[1] = 0.5 * ( mins[1] + maxs[1] ) + pOrigin[1]; //Xscale, adding numbers pushes the model to the left
	origin[2] = -0.5 * ( mins[2] + maxs[2] ) + pOrigin[2]; //yScale, adding numbers pushes up

	refdef.time = dp_realtime;

	trap_R_ClearScene();

	//spinView 
	if ( uis.spinView ) 
	{
		pi->viewAngles[YAW] = AngleNormalize360( ( uis.cursorx - uis.cursorpx) + uis.lastYaw );

		if ( !trap_Key_IsDown( K_MOUSE1 ) ) 
		{
			uis.spinView = qfalse;
			uis.lastYaw = pi->viewAngles[YAW];
		}
	}

	//TiM: random emote functionality :)
	//first init the timer so this will start a minute after loading the menu
	if ( pi->randomEmote && pi->nextEmoteTime == 0 ) {
		pi->nextEmoteTime = uis.realtime + ( irandom( 15, 20 ) * 1000 );
	}

	//whup, time to play a random emote
	if ( pi->randomEmote && uis.realtime > pi->nextEmoteTime ) {
		//randomly pick an anim
		anim = irandom( BOTH_STAND1_RANDOM2, BOTH_STAND1_RANDOM11 );

		//make sure we can play this emote
		if ( pi->animations[anim].numFrames > 0 ) {
			
			UI_ForceLegsAnim( pi, anim );
			UI_ForceTorsoAnim( pi, anim );			
			
			//play lower
			pi->legsAnimationTimer = pi->animations[ anim ].numFrames * pi->animations[ anim ].frameLerp * ((float)uis.realtime/(float)dp_realtime);
			pi->lowerEmoting = qtrue;

			pi->torsoAnimationTimer = pi->animations[ anim ].numFrames * pi->animations[ anim ].frameLerp * ((float)uis.realtime/(float)dp_realtime);
			pi->upperEmoting = qtrue;

			pi->nextEmoteTime = uis.realtime + ( irandom( 10, 20 ) * 1000 ) + pi->legsAnimationTimer; 
		}
		else {
			pi->nextEmoteTime = uis.realtime + ( irandom( 3, 8 ) * 1000 );
		}
	}

	// 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;

	//
	// 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);

	VectorScale( legs.axis[0], pi->height, legs.axis[0]);
	VectorScale( legs.axis[1], (pi->height * pi->weight), legs.axis[1]); //weight... i think
	VectorScale( legs.axis[2], pi->height, legs.axis[2]);
	legs.origin[2] = legs.origin[2] - (24.0f * (1.0f - pi->height));

	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, "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, "tag_head");

	head.renderfx = renderfx;

	trap_R_AddRefEntityToScene( &head );

	//
	// add the gun
	//
	if ( pi->currentWeapon != WP_NONE ) {
		memset( &gun, 0, sizeof(gun) );
		gun.hModel = pi->weaponModel;
		VectorCopy( origin, gun.lightingOrigin );
		UI_PositionEntityOnTag( &gun, &torso, pi->torsoModel, "tag_weapon");
		gun.renderfx = renderfx;
		trap_R_AddRefEntityToScene( &gun );
	}

	//
	// add muzzle flash
	//
	if ( dp_realtime <= pi->muzzleFlashTime ) {
		if ( pi->flashModel ) {
			memset( &flash, 0, sizeof(flash) );
			flash.hModel = pi->flashModel;
			VectorCopy( origin, flash.lightingOrigin );
			UI_PositionEntityOnTag( &flash, &gun, pi->weaponModel, "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] );
		}
	}

	//
	// add the chat icon
	//
	if ( pi->chat ) {
		UI_PlayerFloatSprite( pi, origin, trap_R_RegisterShaderNoMip( "sprites/chat" ) );
	}

	//
	// add an accent light
	// TiM: Holy Hell.  This explains why the models are washed out when overBrightBits is active. O_o
	// 500 is WAY too high
	//
	origin[0] -= 100;	// + = behind, - = in front
	origin[1] += 100;	// + = left, - = right
	origin[2] += 100;	// + = above, - = below
	trap_R_AddLightToScene( origin, 100, 1.0, 1.0, 1.0 ); //500

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

	trap_R_RenderScene( &refdef );
}


/*
==========================
UI_RegisterClientSkin
==========================
*/
/*extern char* BG_RegisterRace( const char *name );
static qboolean UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, const char *skinName ) {
	char		filename[MAX_QPATH];

	Com_sprintf( filename, sizeof( filename ), "models/players2/%s/lower_%s.skin", modelName, skinName );
	pi->legsSkin = trap_R_RegisterSkin( filename );

	Com_sprintf( filename, sizeof( filename ), "models/players2/%s/upper_%s.skin", modelName, skinName );
	pi->torsoSkin = trap_R_RegisterSkin( filename );

	Com_sprintf( filename, sizeof( filename ), "models/players2/%s/head_%s.skin", modelName, skinName );
	pi->headSkin = trap_R_RegisterSkin( filename );

	Com_sprintf( filename, sizeof( filename ), "models/players2/%s/groups.cfg", modelName);
	strcpy(pi->race, BG_RegisterRace( filename ));

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

	return qtrue;
}*/


/*
======================
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 );
		return qfalse;
	}
	trap_FS_Read( text, len, f );
	text[len] = 0;
	trap_FS_FCloseFile( f );

	// 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[0] ) {
			break;
		}
		if ( !Q_stricmp( token, "footsteps" ) ) {
			token = COM_Parse( &text_p );
			if ( !token[0] ) {
				break;
			}
			continue;
		} else if ( !Q_stricmp( token, "headoffset" ) ) {
			for ( i = 0 ; i < 3 ; i++ ) {
				token = COM_Parse( &text_p );
				if ( !token[0] ) {
					break;
				}
			}
			continue;
		} else if ( !Q_stricmp( token, "sex" ) ) {
			token = COM_Parse( &text_p );
			if ( !token[0] ) {
				break;
			}
			continue;
		} else if ( !Q_stricmp( token, "soundpath" ) ) {
			token = COM_Parse( &text_p );
			if ( !token[0] ) {
				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[0] ) {
			break;
		}
		animations[i].firstFrame = atoi( token );
		// leg only frames are adjusted to not count the upper body only frames
		if ( i == LEGS_KNEEL1 ) {
			skip = animations[LEGS_KNEEL1].firstFrame - animations[TORSO_ACTIVATEMEDKIT1].firstFrame;
		}
		if ( i >= LEGS_KNEEL1) {
			animations[i].firstFrame -= skip;
		}

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

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

		token = COM_Parse( &text_p );
		if ( !token[0] ) {
			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", filename );
		return qfalse;
	}

	return qtrue;
}

/*
======================
UI_InitModelData
by TiM

Initialize default values 
in case the crazy modder 
left out some of the keys.

In most cases, the fields 
will just be left blank.
No point in using extra 
resources if they weren't 
specified.
======================
*/

static void UI_InitModelData( playerInfo_t *pi ) {
	pi->hasRanks = qfalse;

	//initialize all model + skin data as 0, so it can be told if they don't get
	//values assigned in the script parser, in which case we exit.
	pi->headModel = 0;
	pi->torsoModel = 0;
	pi->legsModel = 0;

	pi->headSkin = 0;
	pi->headSkinBlink = 0; //doesn't matter if left 0; won't end the parser
	pi->torsoSkin = 0;
	pi->legsSkin = 0;

	//doesn't matter if left 0
	pi->headBlinkTime.minSeconds = 0;
	pi->headBlinkTime.maxSeconds = 0;
	
	pi->nextTalkTime = 0;
	pi->currentTalkSkin = 0;

	pi->headSkinTalk[0] = 0;
	pi->headSkinTalk[1] = 0;
	pi->headSkinTalk[2] = 0;
	pi->headSkinTalk[3] = 0;

	memset( &pi->boltonTags, 0, sizeof(pi->boltonTags));
}

/*
=====================
UI_ParseSkinSetDataFile
by TiM

Parses a separate.skinset
file to get the skin data 
for this model.
======================
*/

static qboolean UI_ParseSkinSetDataFile( playerInfo_t *pi, const char *skinSetFrame, const char *charName, const char *skinName )
{
	char*			skinStar;
	char			skinSetName[MAX_QPATH];
	char			skinSetRoute[MAX_QPATH];
	char*			token;
	char*			textPtr;
	char			buffer[5000];
	int				len;
	fileHandle_t	f;
	int				n, i;
	int				noBlinking = trap_Cvar_VariableValue( "cg_noBlinkingHeads" );

	if ( ( skinStar = strstr( skinSetFrame, "*" ) ) == NULL )
	{
		Com_Printf( S_COLOR_RED "ERROR: No '*' specified in model skin set!\n" );
		return qfalse;
	}
	else
	{
		//star is at front
		if ( skinStar == skinSetFrame )
		{
			skinStar++;
			Com_sprintf( skinSetName, sizeof( skinSetName ), "%s%s", skinName, skinStar );
		}
		//star is at end
		else if ((int)(skinStar - skinSetFrame)+1 == (int)strlen(skinSetFrame) )
		{
			Q_strncpyz( skinSetName, skinSetFrame, strlen( skinSetFrame ) );
			Q_strcat( skinSetName, sizeof( skinSetName ), skinName );
		}
		else
		{
			Com_Printf( "ERROR: The '*' in %s must be on either the start or end, not the middle.\n", skinSetFrame );
			return qfalse;
		}
	}

	//Com_Printf( S_COLOR_RED "DEBUG: skinSetName = %s \n", skinSetName );

	Com_sprintf( skinSetRoute, sizeof( skinSetRoute ), "models/players_rpgx/%s/%s.skinset", charName, skinSetName );

	len = trap_FS_FOpenFile( skinSetRoute, &f, FS_READ );

	if ( len <= 0 )
	{
		Com_Printf( S_COLOR_RED "ERROR: Could not open file: %s\n", skinSetRoute );
		return qfalse;
	}

	if ( len > sizeof( buffer) - 1 )
	{
		Com_Printf( S_COLOR_RED "ERROR: Imported file is too big for buffer: %s. Len is %i\n", skinSetRoute, len );
		return qfalse;
	}

	trap_FS_Read( buffer, len, f );

	trap_FS_FCloseFile( f );

	if ( !buffer[0] )
	{
		Com_Printf( S_COLOR_RED "ERROR: Could not import data from %s\n", skinSetRoute );
		return qfalse;
	}

	buffer[len] = '\0';

	textPtr = buffer;

	token = COM_Parse( &textPtr );

	if ( Q_stricmp( token, "{" ) )
	{
		Com_Printf( S_COLOR_RED "ERROR: Skinset %s did not start with a '{'\n", skinSetRoute );
		return qfalse;
	}
	else
	{
		while ( 1 ) 
		{ //while we don't hit the closing brace
			
			token = COM_Parse( &textPtr ); //parse
			if ( !token[0] ) { //error check
				break;
			}

			//head skin when blinking
			//must be before headskin, or the damn thing will think the two are the same :P
			if ( !Q_stricmpn( token, "headSkinBlink", 13 ) ) {
				if ( COM_ParseString( &textPtr, &token ) ) {
					continue;
				}

				if ( !noBlinking ) {
					pi->headSkinBlink = trap_R_RegisterSkin( token );
				}

				if ( !noBlinking && !pi->headSkinBlink ) {
					//We'll alert them, but not cancel the loop
					Com_Printf( S_COLOR_RED "WARNING: Couldn't load headSkinBlink: %s\n", token);
				}
				continue;
			}

			//head blink time
			else if ( !Q_stricmpn( token, "headBlinkTime", 13 ) ) 
			{
				//Done this way so we know we got two valid args b4 proceeding
				if ( COM_ParseInt( &textPtr, &n ) ) { //first arg
					SkipRestOfLine( &textPtr );
					continue;
				}

				if ( COM_ParseInt( &textPtr, &i ) ) { //2nd arg
					SkipRestOfLine( &textPtr );
					continue;
				}				
				
				//Bug: if the stupid n00b of a modder made 
				//the minimum time larger than the max time >.<
				if ( n > i ) 
				{
					Com_Printf( S_COLOR_RED "ERROR: Minimum blink time was larger than maximum blink time.\n" );
					continue;
				}

				if ( !noBlinking ) {
					pi->headBlinkTime.minSeconds = n;
					pi->headBlinkTime.maxSeconds = i;
				}
				continue;
			}

			else if ( !Q_stricmpn( token, "torsoSkin", 9 ) ) {
				if (COM_ParseString( &textPtr, &token ) ) {
					continue;
				}

				pi->torsoSkin = trap_R_RegisterSkin( token );
				if (!pi->torsoSkin ) {
					Com_Printf( S_COLOR_RED "ERROR: Couldn't load torsoSkin: %s\n", token);
				}
				continue;
			}

			else if ( !Q_stricmpn( token, "legsSkin", 8 ) ) {
				if (COM_ParseString( &textPtr, &token ) ) {
					continue;
				}

				pi->legsSkin = trap_R_RegisterSkin( token );
				if (!pi->legsSkin ) {
					Com_Printf( S_COLOR_RED "ERROR: Couldn't load legsSkin: %s\n", token);
				}
				continue;
			}

			else if ( !Q_stricmpn( token, "headSkinTalk", 12 ) )
			{
				SkipBracedSection( &textPtr );
				continue;
			}

			//head skin
			else if ( !Q_stricmp( token, "headSkin" ) ) {
				if ( COM_ParseString( &textPtr, &token ) ) {
					continue;
				}

				pi->headSkin = trap_R_RegisterSkin( token );
				if ( !pi->headSkin ) {
					Com_Printf( S_COLOR_RED "ERROR: Couldn't load headSkin: %s\n", token );
					return qfalse;
				}
				continue;
			}

			if ( !Q_stricmpn( token, "}", 1) ) {
				break;
			}
		}
	}

	return qtrue;
}


/*
======================
UI_ParseModelDataFile
by TiM

Reads in the .model file 
needed to put together 
a character model.
======================
*/

qboolean UI_ParseModelDataFile( playerInfo_t *pi, const char *charName, 
										const char *modelName, const char *skinName ) {
	fileHandle_t	file;
	int				file_len;
	char			charText[20000];
	char			*textPtr, *prevValue;
	char			fileName[MAX_QPATH];
	//char			animPath[MAX_QPATH];
	int				i, n;
	char			*token;
	char			legsFileRoute[MAX_QPATH];
	qboolean		didAnims = qfalse;
	qboolean		skinSetFound=qfalse;
	int				noBlinking;

	noBlinking = trap_Cvar_VariableValue( "cg_noBlinkingHeads" );
	//size_t			strLen;

	//create the file route
	Com_sprintf( fileName, sizeof(fileName), "models/players_rpgx/%s/%s.model", charName, modelName);

	//Okay... gotta get the hang of ANSI C text parsing >.<
	//first... I guess load the file
	file_len = trap_FS_FOpenFile( fileName, &file, FS_READ );
	//Error handle
	//if length was 0, ie file not found or was empty
	if (file_len <= 0 ) {
		return qfalse;
	}
	//Another error... if text is WAY bigger than our available buffer O_O
	if ( file_len >= sizeof( charText ) - 1 ) {
		Com_Printf( S_COLOR_RED "Model Data File %s too long... WAY too long\n", fileName );
		return qfalse;
	}

	//initialize the buffer
	memset( charText, 0, sizeof( charText ) );

	//read data into char array
	//i guess we use a char array so we can actually specify size/width.
	trap_FS_Read( charText, file_len, file );
	//I guess this is needed to mark the EOF.
	charText[file_len] = 0;
	//Free memory. Close Files
	trap_FS_FCloseFile( file );

	//default values if needed
	UI_InitModelData( pi );
	
	//Used to just clear any previous parse temp data
	COM_BeginParseSession();

	//transfer our data from a char array to a char ptr.
	//needed for the parsing func methinks
	textPtr = charText;

	token = COM_Parse( &textPtr ); //COM_Parse seems to work by splitting up each line of text by the spaces, 
									//and then removes that chunk from the original
	//Okay, we should have the beginning variable first... which should be a '{'

	//from the looks of this, I think we have to do this after
	//every parse call. O_O
	if ( !token[0] ) {
		Com_Printf( S_COLOR_RED "No data found in model data buffer!\n");
		return qfalse;
	}

	if ( Q_stricmp(token, "{" ) ) {
		Com_Printf(S_COLOR_RED "Missing { in %s\n", fileName);
		return qfalse;
	}

	while ( 1 ) {
		prevValue = textPtr; //set a backup
		token = COM_Parse( &textPtr );

		if (!token[0] || !token ) { //we've hit the end of the file. w00t! exit!
			break;
		}
		
		//if we randomly find a brace in here (ie a sub-struct that may have no header)
		//just skip it. :P
		if ( !Q_stricmpn( token, "{", 1 ) ) {
			SkipBracedSection ( &textPtr );
		}

		if ( !Q_stricmpn( token, "animsConfig", 11 ) ) {
			if( COM_ParseString( &textPtr, &token ) ) {
				continue;
			}

			//no valid anim file found.  Don't give up hope though.
			//We have a backup resort at the end if need be. :)
			if ( ( didAnims = UI_ParseAnimationFile( token, pi->animations ) ) == qfalse ) {
				Com_Printf( S_COLOR_RED "WARNING: Was unable to load file %s.\n", token );
			}
			continue;
		}

		//playermodel gender
		else if ( !Q_stricmpn( token, "sex", 3 ) ) {
			if (COM_ParseString( &textPtr, &token ) ) {
				continue;
			}
			if ( token[0] == 'f' || token[0] == 'F' ) {
				pi->gender = GENDER_FEMALE;
			} else if ( token[0] == 'n' || token[0] == 'N' ) {
				pi->gender = GENDER_NEUTER;
			} else {
				pi->gender = GENDER_MALE;
			}
			continue;
		} 

		//character's legs model
		else if ( !Q_stricmpn( token, "legsModel", 9 ) ) {
			
			if( COM_ParseString( &textPtr, &token ) ) {
				continue;
			}

			pi->legsModel = trap_R_RegisterModel( token );
			if (!pi->legsModel) {
				Com_Printf( S_COLOR_RED "ERROR: Unable to load legs model: %s\n", token);
				return qfalse;
			}

			//if loaded no anims yet, copy the legs route to this variable,
			//and we'll try again at the end of the function
			//if ( ci->animIndex == -1 ) {
			Q_strncpyz( legsFileRoute, token, sizeof( legsFileRoute ) );
			//} Actually. just copy it regardless. Just in case

			continue;
		}

		//character's torso model
		else if ( !Q_stricmpn( token, "torsoModel", 10 ) ) {
			if( COM_ParseString( &textPtr, &token ) ) {
				continue;
			}
			pi->torsoModel = trap_R_RegisterModel( token );
			//Com_Printf("Torsomodel passed as %s, %i\n", token, (int)ci->torsoModel);

			if (!pi->torsoModel) {
				Com_Printf( S_COLOR_RED "ERROR: Unable to load torso model: %s\n", token);
				return qfalse;
			}
			continue;
		}

		//character's headmodel
		else if ( !Q_stricmpn( token, "headModel", 9 ) ) {

			//return true = no extra text found on this line - bad! O_O!
			if( COM_ParseString( &textPtr, &token ) ) {
				continue;
			}

			pi->headModel = trap_R_RegisterModel( token );
			if (!pi->headModel) {
				Com_Printf( S_COLOR_RED "ERROR: Unable to load head model: %s\n", token);
				return qfalse;
			}
			continue;
		}

		// Custom bolton models... oi O_o
		else if ( !Q_stricmpn( token, "boltonModels", 12 ) ) {
			//needed coz '{' could also be on next line
			token = COM_Parse( &textPtr );
			if ( !token[0] ) { //if that was it
				break;
			} else { //else, if next character is '{'
				if ( !Q_stricmpn( token, "{", 1 ) ) {
					token = COM_Parse( &textPtr );
					if ( !token[0] ) { 
						break; 
					}	
					//loop till we hit the end of the brackets
					i = 0;

					while ( Q_stricmp( token, "}" ) ) {
						if ( !Q_stricmpn( token, "BOLTON_", 7 ) ) {
							
							pi->boltonTags[i].modelBase = GetIDForString( BoltonTable, token );

							if( COM_ParseString( &textPtr, &token ) ) {
								continue;
							}

							if (!Q_stricmpn( token, "tag_", 4 ) ) {
								Q_strncpyz(pi->boltonTags[i].tagName, token, sizeof (pi->boltonTags[i].tagName) );
							
								if( COM_ParseString( &textPtr, &token ) ) {
									continue;
								}
								pi->boltonTags[i].tagModel = trap_R_RegisterModel( token );
								
								if (!pi->boltonTags[i].tagModel) {
									Com_Printf( S_COLOR_RED "WARNING: Unable to load bolton model: %s\n", token);
								}

								i++;

								if (i > MAX_BOLTONS -1) {
									break;
								}
							}
						}

						//Com_Printf("Index: %i, Name: %s, Handle: %i\n", ci->boltonTags[ci->numBoltOns].modelBase, ci->boltonTags[ci->numBoltOns].tagName, ci->boltonTags[ci->numBoltOns].tagModel  );
						token = COM_Parse( &textPtr );
						if ( !token[0] ) { 
							break; 
						}	
					}
				}
			}
		}

		//whether char is allowed to wear ranks
		else if ( !Q_stricmpn( token, "hasRanks", 8 ) ) {
			if (COM_ParseInt(&textPtr, &n ) ) {
				continue;
			}
			pi->hasRanks = n;
			continue;
		}

		//TiM - The skinset is defined
		else if ( !Q_stricmpn( token, "skinSet", 7 ) ) {
			if ( COM_ParseString( &textPtr, &token ) ) {
				continue;
			}
			
			if ( !UI_ParseSkinSetDataFile( pi, token, charName, skinName ) )
			{
				Com_Printf( S_COLOR_RED "WARNING: Could not load data from specified skin set in char: %s. Attempting to load default.\n", charName );
			}
			else
			{
				skinSetFound = qtrue;
			}
			
			continue;
		}
	}

	if ( !skinSetFound )
	{
		if ( !UI_ParseSkinSetDataFile( pi, va("%s_*", modelName, skinName ), charName, skinName ) )
		{
			Com_Printf( S_COLOR_RED "ERROR: Tried loading default skin set, however it failed.\n");
		}	
	}

	//if any of the models or skins were left blank, then output false. Coz we need them. :P
	if (!pi->headModel || !pi->torsoModel || !pi->legsModel ) {
		Com_Printf( S_COLOR_RED "One or more necessary model files weren't loaded from %s\n", fileName );
		return qfalse;
	}

	if (!pi->headSkin || !pi->torsoSkin || !pi->legsSkin ) {
		Com_Printf( S_COLOR_RED "One or more necessary skin files weren't loaded from %s\n", fileName );
		return qfalse;
	}

	//if modder specified no animations file route, or they did, and it sucked (ie -1 ),
	//Then try looking for one in the same directory as the lower.mdr file

	//k... the goal of this is to take a string like
	//models/players_rpgx/crewman_male/lower.mdr
	//and turn it into
	//models/players_rpgx/crewman_male/animation.cfg

	if ( !didAnims && strlen( legsFileRoute ) > 0 ) {
		//get length of file route
		i = strlen(legsFileRoute);

		while( 1 ) {
			//if we looped all the way to the end.... ie BAD
			if (i <= 0) {
				//we obviously have no animation directory :(
				Com_Printf(S_COLOR_RED "ERROR: Was unable to calculate location of animation.cfg for %s\n", fileName);
				return qfalse;
			}

			//if this is the first '/' we come across from going from the end to the start
			if (legsFileRoute[i] == '/' ) {
				//copy i bytes of data from token to animpath (effectively giving us the route, with no file)
				Q_strncpyz(legsFileRoute, legsFileRoute, (i = i + 2 )); //+2 for the null char these things auto assign at the end... i think
				break;										//won't work without it anyway :P
			}
			i--;
		}

		//add animation.cfg to the end of the string
		Q_strcat(legsFileRoute, sizeof(legsFileRoute), "animation.cfg");

		//Com_Printf( S_COLOR_RED "WARNING: Failed to load animation file specified in model config, attempting to load %s\n", legsFileRoute );

		//Parse it
		if ( !UI_ParseAnimationFile( legsFileRoute, pi->animations) ) {
			Com_Printf( "Tried loading anim data from location %s, however nothing was valid.\n", legsFileRoute );
			return qfalse;
		}
	}
	else {
		if ( !legsFileRoute[0] ) {
			Com_Printf( S_COLOR_RED "Couldn't load/locate any player animation data for player: %s.\n", charName );
			return qfalse;
		}
	}

	//holy fudgenuggets.  after all that checking, we actually made it to the end and have a valid freaking
	//model! OWNED!
	return qtrue;
}


/*
==========================
UI_RegisterClientModelname
==========================
*/
qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName ) {
	char		charName[MAX_QPATH];
	char		modelName[MAX_QPATH];
	char		skinName[MAX_QPATH];
	//char		filename[MAX_QPATH];
	char		*model, *skin;
	//char		*slash;
	int			len;

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

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

	Q_strncpyz( charName, modelSkinName, sizeof( charName ) );

	/*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 ) );
		// truncate modelName
		*slash = 0;
	}*/

	//step 1, take the first bit of the string and put it in the charName var.
	if ( ( model = strchr( charName, '/') ) == NULL ) { //if there's no slash
		Q_strncpyz( charName, modelSkinName, sizeof( charName ) ); //just set it
	} else { //otherwise, isolate the first bit, and copy that
		len = strlen( modelSkinName );
		Q_strncpyz( charName, modelSkinName, ((int)len - (int)strlen(model)) + 1 );
	}
	//Com_Printf("%s\n", newInfo.charName);

	//slash = strchr( newInfo.modelName, '/' );
	if ( !model || !model[1] ) {
		// modelName didn not include a skin name
		//Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) );
		Q_strncpyz( modelName, "main", sizeof( modelName ) );
		Q_strncpyz( skinName, "default", sizeof( skinName ) );

		if ( model && !model[1] )
		{//if we had a slash, but nothing after, clear it
			*model = 0;
		}
	} else {
		//*model++; //bypass the slash
		model++;
		len = strlen(model);
		skin = strchr( model, '/' );

		//if there was a model defined, but no skin
		if ( !skin || !skin[1] ) {
			//no skin, but I'm guessing we gotz a model at least
			if ( !skin ) {
				Q_strncpyz( modelName, model, sizeof( modelName ) );
			}
			else {
				if ( !skin[1] ) {
					Q_strncpyz( modelName, model, (int)strlen(model) );
				}
			}

			Q_strncpyz( skinName, "default", sizeof( skinName ) );

			if ( skin && !skin[1] ) {
				*skin = 0;
			}
		} else {
			//*skin++;
			skin++;
			Q_strncpyz( modelName, model, ((int)len - (int)strlen(skin)) );
			Q_strncpyz( skinName, skin, sizeof( skinName ) );
		}

		//Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) );
		// truncate modelName
		*model = 0;
	}

	// load cmodels before models so filecache works
	//try loading the main model
	if ( !UI_ParseModelDataFile( pi, charName, modelName, skinName ) ) 
	{
		Com_Printf( S_COLOR_RED "Was unable to parse model file for character: %s/%s/%s\n", charName, modelName, skinName );
		//if that fails, try and load the model's default data at least
		if ( !UI_ParseModelDataFile( pi, charName, DEFAULT_MODEL, DEFAULT_SKIN ) ) 
		{
			//if THAT fails, try loading our defualt char, with the specfied char's model and skin parms
			if ( !UI_ParseModelDataFile( pi, ui_defaultChar.string, modelName, skinName ) )
			{
				if ( !UI_ParseModelDataFile( pi, DEFAULT_CHAR, DEFAULT_MODEL, DEFAULT_SKIN ) )
				{
					//if all else fails, try and load the normal default model
					return qfalse;
				}
			}
		}
	}

	/*Com_sprintf( filename, sizeof( filename ), "models/players2/%s/lower.mdr", modelName );
	pi->legsModel = trap_R_RegisterModel( filename );
	if ( !pi->legsModel )
	{
		Com_sprintf( filename, sizeof( filename ), "models/players2/%s/lower.md3", modelName );
		pi->legsModel = trap_R_RegisterModel( filename );
		if ( !pi->legsModel )
		{
			Com_Printf( S_COLOR_RED"Failed to load model file %s\n", filename );
			return qfalse;
		}
	}

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

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

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

	// load the animations
	Com_sprintf( filename, sizeof( filename ), "models/players2/%s/animation.cfg", modelName );
	if ( !UI_ParseAnimationFile( filename, pi->animations ) ) {
		Com_Printf( "Failed to load animation file %s\n", filename );
		return qfalse;
	}*/

	return qtrue;
}


/*
===============
UI_PlayerInfo_SetModel
===============
*/
void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model ) {
	memset( pi, 0, sizeof(*pi) );
	UI_RegisterClientModelname( pi, model );
	Q_strncpyz( pi->modelName, model, sizeof( pi->modelName ) );
	pi->weapon = WP_NONE;
	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, float height, float weight, qboolean chat ) {
	int			currentAnim;
	weapon_t	weaponNum;

	pi->chat = chat;

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

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

	//TiM : Clamp weight and height
	pi->height = height;
	pi->weight = weight;

	if ( !pi->weight )
		pi->weight = 1.0f;
	if ( !pi->height )
		pi->height = 1.0f;

	pi->height = Com_Clamp( 0.9f, 1.15f, pi->height );
	pi->weight = Com_Clamp( 0.9f, 1.1f, pi->weight );

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

		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
	currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;
	if ( legsAnim != BOTH_JUMP1 && ( currentAnim == BOTH_JUMP1 || currentAnim == BOTH_LAND1 ) ) {
		pi->pendingLegsAnim = legsAnim;
	}
	else if ( legsAnim != currentAnim ) {
		jumpHeight = 0;
		pi->pendingLegsAnim = 0;
		UI_ForceLegsAnim( pi, legsAnim );
	}

	// torso animation
	//if ( torsoAnim == TORSO_STAND || torsoAnim == TORSO_STAND2) //TiM: TORSO_STAND2
	if ( torsoAnim == UI_GetAnim( ANIM_IDLE, pi->currentWeapon, qtrue ) )
	{	
		/*if ( weaponNum == WP_NONE || weaponNum == WP_PHASER ) 
		{
			torsoAnim = TORSO_STAND2;
		}
		else 
		{
			torsoAnim = TORSO_STAND2;
		}*/

		/*if ( weaponNum == WP_COMPRESSION_RIFLE || weaponNum == WP_TR116 ) 
		{
			torsoAnim = TORSO_STAND;
		}
		else 
		{
			torsoAnim = TORSO_STAND2;
		}*/
		torsoAnim = UI_GetAnim( ANIM_IDLE, pi->currentWeapon, qtrue );

	}

	//if ( torsoAnim == TORSO_ATTACK || torsoAnim == TORSO_ATTACK2 )
	if ( torsoAnim == UI_GetAnim( ANIM_ATTACK, pi->currentWeapon, qtrue ) )
	{
		/*if ( weaponNum == WP_NONE || weaponNum == WP_PHASER ) 
		{
			torsoAnim = TORSO_ATTACK2;
		}
		else 
		{
			torsoAnim = TORSO_ATTACK;
		}*/

		/*if ( weaponNum == WP_COMPRESSION_RIFLE || weaponNum == WP_TR116 ) 
		{
			torsoAnim = TORSO_ATTACK;
		}
		else 
		{
			torsoAnim = TORSO_ATTACK2;
		}*/

		torsoAnim = UI_GetAnim( ANIM_ATTACK, pi->currentWeapon, qtrue );

		pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH;
		//FIXME play firing sound here
	}

	currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;

	if ( weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISEWEAP1 || currentAnim == TORSO_DROPWEAP1 ) {
		pi->pendingTorsoAnim = torsoAnim;
	}
	else if ( ( /*currentAnim == TORSO_GESTURE ||*/ currentAnim == UI_GetAnim(ANIM_ATTACK, pi->currentWeapon, qtrue) ) && ( torsoAnim != currentAnim ) ) {
		pi->pendingTorsoAnim = torsoAnim;
	}
	else if ( torsoAnim != currentAnim ) {
		pi->pendingTorsoAnim = 0;
		UI_ForceTorsoAnim( pi, torsoAnim );
	}
}