// Copyright (C) 2001-2002 Raven Software, Inc.
//
// cg_players.c -- handle the media and animation for player entities

#include "cg_local.h"
#include "..\ghoul2\g2.h"
#include "..\game\inv.h"

typedef struct SCustomSound
{
	const char	*mGroup;
	const int	mIndex;
} TCustomSound;

TCustomSound CustomSounds[MAX_CUSTOM_SOUNDS] = 
{
	{ "Death", 0 },
	{ "Death", 1 },
	{ "Death", 2 },
	{ "Pain", 0 },
	{ "Pain", 1 },
	{ "Pain", 2 },
};

/*
================
CG_CustomSound
================
*/
sfxHandle_t	CG_CustomSound(int clientNum, const char *soundName) 
{
	return trap_S_RegisterSound( soundName );
}

/*
================
CG_CustomPlayerSound
================
*/
sfxHandle_t	CG_CustomPlayerSound(int clientNum, ECustomSounds sound)
{
	clientInfo_t	*ci;

	if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) 
	{
		clientNum = 0;
	}
	ci = &cgs.clientinfo[ clientNum ];

	return ci->sounds[sound];
}


/*
==========================
CG_RegisterClientIdentity
==========================
*/
static qboolean CG_RegisterClientIdentity ( clientInfo_t *ci, const char *identityName ) 
{
	char		afilename[MAX_QPATH];
	TIdentity*	identity;

	// Find the identity in question
	identity = BG_FindIdentity( identityName );
	if (!identity )
	{
		return qfalse;
	}

	// Register the identity
	ci->ghoul2Model = CG_RegisterIdentity( identity, afilename, &ci->gender );
	if (!ci->ghoul2Model)
	{
		return qfalse;
	}

	// find the bolt point for hanging world models of weapons on
	ci->boltWorldWeapon = trap_G2API_AddBolt(ci->ghoul2Model, 0, "rhang_tag_bone");	

	if ( !BG_ParseAnimationFile( afilename, ci->animations ) ) 
	{
		Com_Printf( "Failed to load animation file %s\n", afilename );
		trap_G2API_RemoveGhoul2Model(&ci->ghoul2Model, 0);
		return qfalse;
	}

	// Flag model as male or female.
	assert(afilename);
	ci->isMale=(strstr(afilename,"female"))?qfalse:qtrue;
	ci->identity = identity;

	return qtrue;
}

/*
====================
CG_ColorFromString
====================
*/
static void CG_ColorFromString( const char *v, vec3_t color ) {
	int val;

	VectorClear( color );

	val = atoi( v );

	if ( val < 1 || val > 7 ) {
		VectorSet( color, 1, 1, 1 );
		return;
	}

	if ( val & 1 ) {
		color[2] = 1.0f;
	}
	if ( val & 2 ) {
		color[1] = 1.0f;
	}
	if ( val & 4 ) {
		color[0] = 1.0f;
	}
}

/*
===================
CG_LoadClientInfo

Load it now, taking the disk hits.
This will usually be deferred to a safe time
===================
*/
static void CG_LoadClientInfo( clientInfo_t *ci ) 
{
	int		i;
	int		modelloaded;
	int		clientNum;

	modelloaded = qtrue;

	// Initialize all bolt positions
	ci->boltWorldWeapon = -1;
	ci->boltNightvision = -1;
	for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ )
	{
		ci->boltGametypeItems[i] = -1;
	}

	// Register the client identity
	if ( !CG_RegisterClientIdentity ( ci, ci->identityName ) )
	{
		if ( !CG_RegisterClientIdentity ( ci, DEFAULT_IDENTITY ) )
		{
			Com_Error( ERR_FATAL, "DEFAULT_IDENTITY (%s) failed to register", DEFAULT_IDENTITY );
		}
	}

	// sounds
	for( i=0; i< MAX_CUSTOM_SOUNDS; i++ )
	{
		const char* sound;

		ci->sounds[i] = 0;
		sound = NULL;

		sound = BG_GetModelSound ( ci->identityName, CustomSounds[i].mGroup, CustomSounds[i].mIndex );

		// If there is a valid sound to load then load it
		if ( sound && sound[0] )
		{
			ci->sounds[i] = trap_S_RegisterSound( sound );
		}
	}

	ci->deferred = qfalse;

	// reset any existing players and bodies, because they might be in bad
	// frames for this new model
	clientNum = ci - cgs.clientinfo;
	for ( i = 0 ; i < MAX_GENTITIES ; i++ )
	{
		centity_t* cent;

		cent = CG_GetEntity ( i );

		if ( cent->currentState.clientNum == clientNum && 
			 cent->currentState.eType == ET_PLAYER ) 
		{
			CG_ResetPlayerEntity( cent );
		}
	}
}

/*
======================
CG_CopyClientInfoModel
======================
*/
static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) 
{
	int i;

	to->gender = from->gender;

	to->boltWorldWeapon = from->boltWorldWeapon;

	for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ )
	{
		to->boltGametypeItems[i] = from->boltGametypeItems[i];
	}

	to->boltNightvision = from->boltNightvision;

	// Copy all the ghoul2 information too
	if (from->ghoul2Model)
	{
		trap_G2API_DuplicateGhoul2Instance( from->ghoul2Model, &to->ghoul2Model );
	}
	else
	{
		assert(0);
		Com_Error(ERR_DROP, "CG_CopyClientInfoModel invalid g2 pointer\n");
	}

	memcpy( to->animations, from->animations, sizeof( to->animations ) );
	memcpy( to->sounds, from->sounds, sizeof( to->sounds ) );
}

/*
======================
CG_ScanForExistingClientInfo
======================
*/
static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) 
{
	int				i;
	clientInfo_t	*match;

	for ( i = 0 ; i < cgs.maxclients ; i++ ) 
	{
		match = &cgs.clientinfo[ i ];
		
		if ( !match->infoValid ) 
		{
			continue;
		}
		if ( match->deferred ) 
		{
			continue;
		}

		// Identity name doesnt match
		if ( Q_stricmp ( ci->identityName, match->identityName ) )
		{
			continue;
		}

		if ( ci->team == TEAM_FREE      ||
		     ci->team == TEAM_SPECTATOR ||
			 ci->team == match->team        )
		{
			// this clientinfo is identical, so use it's handles
			ci->deferred = qfalse;

			CG_CopyClientInfoModel( match, ci );

			return qtrue;
		}
	}

	// nothing matches, so defer the load
	return qfalse;
}

/*
======================
CG_SetDeferredClientInfo

We aren't going to load it now, so grab some other
client's info to use until we have some spare time.
======================
*/
static void CG_SetDeferredClientInfo( clientInfo_t *ci ) 
{
	int				i;
	clientInfo_t	*match;

	// spectators can use any skin 
	if ( ci->team == TEAM_SPECTATOR )
	{
		for ( i = 0; i < cgs.maxclients; i ++ )
		{
			match = &cgs.clientinfo[ i ];
			if ( !match->infoValid ) 
			{
				continue;
			}

			ci->deferred = qtrue;
			CG_CopyClientInfoModel( match, ci );
			return;
		}
	}

	// Try to use any skin from the team they are joining
	if ( cgs.gametypeData->teams )
	{
		for ( i = 0; i < cgs.maxclients; i ++ )
		{
			match = &cgs.clientinfo[ i ];
			if ( !match->infoValid ) 
			{
				continue;
			}

			// Must be on the same team
			if ( match->team != ci->team )
			{
				continue;
			}

			ci->deferred = qtrue;
			CG_CopyClientInfoModel( match, ci );
			return;
		}
	}
	// find the first valid clientinfo and grab its stuff
	else
	{
		for ( i = 0 ; i < cgs.maxclients ; i++ ) 
		{
			match = &cgs.clientinfo[ i ];
			if ( !match->infoValid ) 
			{
				continue;
			}

			ci->deferred = qtrue;
			CG_CopyClientInfoModel( match, ci );
			return;
		}
	}

	CG_LoadClientInfo( ci );
}

/*
======================
CG_ResetClientIdentity
======================
*/
void CG_ResetClientIdentity ( int clientNum )
{
	clientInfo_t *ci;
	centity_t	 *cent;
	int			 i;

	ci = &cgs.clientinfo[clientNum];
	cent = CG_GetEntity ( clientNum );	

	// Clean up the raw ghoul model
	if ( ci->ghoul2Model )
	{
		trap_G2API_CleanGhoul2Models ( &ci->ghoul2Model );
		ci->ghoul2Model = NULL;
	}

	// Clean up the active ghoul model
	if ( cent->ghoul2 )
	{
		trap_G2API_CleanGhoul2Models ( &cent->ghoul2 );
		cent->pe.weaponModelSpot = 0;
		cent->pe.weapon = 0;
		cent->ghoul2 = NULL;
	}

	// Reset all bolt position
	ci->boltWorldWeapon = -1;
	ci->boltNightvision = -1;
	for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ )
	{
		ci->boltGametypeItems[i] = -1;
	}
}

/*
======================
CG_NewClientInfo
======================
*/
void CG_NewClientInfo( int clientNum ) 
{
	clientInfo_t *ci;
	clientInfo_t newInfo;
	centity_t*	 cent;
	const char	*configstring;
	const char	*v;

	ci = &cgs.clientinfo[clientNum];

	cent = CG_GetEntity ( clientNum );	

	configstring = CG_ConfigString( clientNum + CS_PLAYERS );
	if ( !configstring[0] ) 
	{
		memset( ci, 0, sizeof( *ci ) );
		return;
	}

	// Clear the client models / etc
	CG_ResetClientIdentity ( clientNum );

	ci->infoValid = qfalse;

	// build into a temp buffer so the defer checks can use
	// the old value
	memset( &newInfo, 0, sizeof( newInfo ) );

	// isolate the player's name
	v = Info_ValueForKey(configstring, "n");
	Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) );

	// bot skill
	v = Info_ValueForKey( configstring, "skill" );
	newInfo.botSkill = atoi( v );

	// handicap
	v = Info_ValueForKey( configstring, "hc" );
	newInfo.handicap = atoi( v );

	// wins
	v = Info_ValueForKey( configstring, "w" );
	newInfo.wins = atoi( v );

	// losses
	v = Info_ValueForKey( configstring, "l" );
	newInfo.losses = atoi( v );

	// team
	v = Info_ValueForKey( configstring, "t" );
	newInfo.team = atoi( v );

	// team task
	v = Info_ValueForKey( configstring, "tt" );
	newInfo.teamTask = atoi(v);

	// team leader
	v = Info_ValueForKey( configstring, "tl" );
	newInfo.teamLeader = atoi(v);

	// identity
	v = Info_ValueForKey( configstring, "identity" );
	Q_strncpyz ( newInfo.identityName, v, sizeof(newInfo.identityName) );

	// Inform the ui of the team switch
	if ( clientNum == cg.clientNum )
	{
		char		temp[MAX_QPATH];
		const char* identityCvar;

		trap_Cvar_Set ( "ui_info_team", va("%i",newInfo.team ) );

		if (cgs.gametypeData->teams )
		{
			identityCvar = "team_identity";
		}
		else
		{
			identityCvar = "identity";
		}

		trap_Cvar_VariableStringBuffer ( identityCvar, temp, sizeof(temp) );

		if ( Q_stricmp ( temp, newInfo.identityName ) )
		{
			trap_Cvar_Set ( identityCvar, newInfo.identityName );
		}
	}

	// scan for an existing clientinfo that matches this modelname
	// so we can avoid loading checks if possible
	if ( !CG_ScanForExistingClientInfo( &newInfo ) ) 
	{
		qboolean	forceDefer;

		forceDefer = trap_MemoryRemaining() < 4000000;

		// if we are defering loads, just have it pick the first valid
		if ( forceDefer || (cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading ) ) 
		{
			// keep whatever they had if it won't violate team skins
			CG_SetDeferredClientInfo( &newInfo );

			// if we are low on memory, leave them with this model
			if ( forceDefer ) 
			{
				Com_Printf( "Memory is low.  Using deferred model.\n" );
				newInfo.deferred = qfalse;
			}
		} 
		else 
		{
			CG_LoadClientInfo( &newInfo );
		}
	}

	// replace whatever was there with the new one
	newInfo.infoValid = qtrue;
	*ci = newInfo;
}

/*
======================
CG_LoadDeferredPlayers

Called each frame when a player is dead
and the scoreboard is up
so deferred players can be loaded
======================
*/
void CG_LoadDeferredPlayers( void ) 
{
	int				i;
	clientInfo_t	*ci;

	// scan for a deferred player to load
	for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) 
	{
		if ( ci->infoValid && ci->deferred ) 
		{
			// if we are low on memory, leave it deferred
			if ( trap_MemoryRemaining() < 4000000 ) 
			{
				Com_Printf( "Memory is low.  Using deferred model.\n" );
				ci->deferred = qfalse;
				continue;
			}

			// Clear the client models / etc
			CG_ResetClientIdentity ( i );

			CG_LoadClientInfo( ci );
		}
	}
}

/*
=================
CG_AddPainTwitch
=================
*/
static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) 
{
	int		t;
	float	f;

	t = cg.time - cent->pe.painTime;
	if ( t >= PAIN_TWITCH_TIME ) {
		return;
	}

	f = 1.0 - (float)t / PAIN_TWITCH_TIME;

	if ( cent->pe.painDirection ) {
		torsoAngles[ROLL] += 20 * f;
	} else {
		torsoAngles[ROLL] -= 20 * f;
	}
}

/*
===============
CG_PlayerFloatSprite

Float a sprite over the player's head
===============
*/
static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) 
{
	int				rf;
	refEntity_t		ent;

	if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) 
	{
		// only show in mirrors
		rf = RF_THIRD_PERSON;		
	} 
	else 
	{
		rf = 0;
	}

	memset( &ent, 0, sizeof( ent ) );
	VectorCopy( cent->lerpOrigin, ent.origin );
	ent.origin[2] += 58;
	ent.reType = RT_SPRITE;
	ent.customShader = shader;
	ent.radius = 10;
	ent.renderfx = rf;
	ent.shaderRGBA[0] = 255;
	ent.shaderRGBA[1] = 255;
	ent.shaderRGBA[2] = 255;
	ent.shaderRGBA[3] = 255;
	trap_R_AddRefEntityToScene( &ent );
}

/*
===============
CG_PlayerSprites

Float sprites over the player's head
===============
*/
static void CG_PlayerSprites( centity_t *cent ) 
{
	int		team;

	team = cgs.clientinfo[ cent->currentState.clientNum ].team;
	if ( !(cent->currentState.number == cg.snap->ps.clientNum) &&
	     !(cent->currentState.eFlags & EF_DEAD)     && 
		  cg.snap->ps.persistant[PERS_TEAM] == team &&
		  cgs.gametypeData->teams ) 
	{
		if (cg_drawFriend.integer) 
		{
			CG_PlayerFloatSprite( cent, team==TEAM_RED?cgs.media.redFriendShader:cgs.media.blueFriendShader );
		}

		return;
	}
}

/*
===============
CG_PlayerShadow

Returns the Z component of the surface being shadowed

  should it return a full plane instead of a Z?
===============
*/
#define	SHADOW_DISTANCE		256
qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) 
{
	vec3_t		end, mins = {-15, -15, 0}, maxs = {15, 15, 2};
	trace_t		trace;
	float		alpha;

	*shadowPlane = 0;

	if ( cg_shadows.integer == 0 ) {
		return qfalse;
	}

	// send a trace down from the player to the ground
	VectorCopy( cent->lerpOrigin, end );
	end[2] -= SHADOW_DISTANCE;

	trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID );

	// no shadow if too high
	if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) {
		return qfalse;
	}

	*shadowPlane = trace.endpos[2] + 1;

	if ( cg_shadows.integer != 1 ) {	// no mark for stencil or projection shadows
		return qtrue;
	}

	// fade the shadow out with height
	alpha = 1.0 - trace.fraction;
	alpha = 1.0;
	trap_R_AddDecalToScene ( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, 
							 cent->currentState.apos.trBase[YAW], 1.0,0.0,1.0,1.0, qfalse, 24, qtrue );

	return qtrue;
}


/*
===============
CG_PlayerSplash

Draw a mark at the water surface
===============
*/
static void CG_PlayerSplash( centity_t *cent ) {
	vec3_t		start, end;
	trace_t		trace;
	int			contents;
	polyVert_t	verts[4];

	if ( !cg_shadows.integer ) {
		return;
	}

	VectorCopy( cent->lerpOrigin, end );
	end[2] -= 24;

	// if the feet aren't in liquid, don't make a mark
	// this won't handle moving water brushes, but they wouldn't draw right anyway...
	contents = trap_CM_PointContents( end, 0 );
	if ( !( contents & ( CONTENTS_WATER | CONTENTS_LAVA ) ) ) {
		return;
	}

	VectorCopy( cent->lerpOrigin, start );
	start[2] += 32;

	// if the head isn't out of liquid, don't make a mark
	contents = trap_CM_PointContents( start, 0 );
	if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_LAVA ) ) {
		return;
	}

	// trace down to find the surface
	trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_LAVA ) );

	if ( trace.fraction == 1.0 ) {
		return;
	}

	// create a mark polygon
	VectorCopy( trace.endpos, verts[0].xyz );
	verts[0].xyz[0] -= 32;
	verts[0].xyz[1] -= 32;
	verts[0].st[0] = 0;
	verts[0].st[1] = 0;
	verts[0].modulate[0] = 255;
	verts[0].modulate[1] = 255;
	verts[0].modulate[2] = 255;
	verts[0].modulate[3] = 255;

	VectorCopy( trace.endpos, verts[1].xyz );
	verts[1].xyz[0] -= 32;
	verts[1].xyz[1] += 32;
	verts[1].st[0] = 0;
	verts[1].st[1] = 1;
	verts[1].modulate[0] = 255;
	verts[1].modulate[1] = 255;
	verts[1].modulate[2] = 255;
	verts[1].modulate[3] = 255;

	VectorCopy( trace.endpos, verts[2].xyz );
	verts[2].xyz[0] += 32;
	verts[2].xyz[1] += 32;
	verts[2].st[0] = 1;
	verts[2].st[1] = 1;
	verts[2].modulate[0] = 255;
	verts[2].modulate[1] = 255;
	verts[2].modulate[2] = 255;
	verts[2].modulate[3] = 255;

	VectorCopy( trace.endpos, verts[3].xyz );
	verts[3].xyz[0] += 32;
	verts[3].xyz[1] -= 32;
	verts[3].st[0] = 1;
	verts[3].st[1] = 0;
	verts[3].modulate[0] = 255;
	verts[3].modulate[1] = 255;
	verts[3].modulate[2] = 255;
	verts[3].modulate[3] = 255;

	trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts );
}

/*
===============
CG_UpdatePlayerAnimations
===============
*/
static void CG_UpdatePlayerAnimations (	centity_t* cent	) 
{
	clientInfo_t *ci;

	// Make sure its a player
	assert ( cent->currentState.eType == ET_PLAYER );

	// Retrieve the client info pointer for the given client
	ci = &cgs.clientinfo[ cent->currentState.clientNum ];

	// Update the leg animation
	if ( cent->pe.legs.anim != cent->currentState.legsAnim && cent->currentState.legsAnim != -1 )
	{
		animation_t	*anim;
		int			flags;
		float		speed;

		anim = &ci->animations[ (cent->currentState.legsAnim & (~ANIM_TOGGLEBIT)) ];

		if ( anim->loopFrames == -1 )
		{
			flags = BONE_ANIM_OVERRIDE|BONE_ANIM_OVERRIDE_FREEZE;
		}
		else
		{
			flags = BONE_ANIM_OVERRIDE_LOOP;
		}

		if (cg_animBlend.integer)
		{
			flags |= BONE_ANIM_BLEND;
		}

		speed = 1.0f;

		trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, 50.0f / anim->frameLerp * speed, cg.time, -1, 150);

		cent->pe.legs.anim = cent->currentState.legsAnim;
		cent->pe.legs.animTime = cg.time;
	}

	// Update the torso animation
	if ( cent->pe.torso.anim != cent->currentState.torsoAnim && cent->currentState.torsoAnim != -1 )
	{
		animation_t	*anim;
		int			flags;
		float		time;
		anim = &ci->animations[ (cent->currentState.torsoAnim & (~ANIM_TOGGLEBIT)) ];

		if ( anim->loopFrames == -1 )
		{
			flags = BONE_ANIM_OVERRIDE|BONE_ANIM_OVERRIDE_FREEZE;
		}
		else
		{
			flags = BONE_ANIM_OVERRIDE_LOOP;
		}

		if (cg_animBlend.integer)
		{
			flags |= BONE_ANIM_BLEND;
		}

		if ( cent->currentState.torsoTimer > 0 )
		{
			time = cent->currentState.torsoTimer;
			time /= anim->numFrames;
		}
		else
		{
			time = anim->frameLerp;
		}

		// Default to 20 FPS.
		if ( time <= 0 )
		{
			time = 1000.0f / 20.0f;
		}
		
		trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, 50.0f / time, cg.time, -1, 150);

		cent->pe.torso.anim = cent->currentState.torsoAnim ;
		cent->pe.torso.animTime = cg.time;
	}
}

/*
===============
CG_PlayerGametypeItems
===============
*/
void CG_PlayerGametypeItems ( centity_t* cent, vec3_t angles )
{
	clientInfo_t	*ci = &cgs.clientinfo[cent->currentState.clientNum];
	int				i;
	int				rf;

	if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) 
	{
		// only show in mirrors
		rf = RF_THIRD_PERSON;		
	} 
	else 
	{
		rf = 0;
	}

	// If the player is wearing goggles then draw them on their head
	if ( cent->currentState.eFlags & EF_GOGGLES )
	{
		refEntity_t	item;

		if ( ci->boltNightvision == -1 )
		{
			ci->boltNightvision = trap_G2API_AddBolt( cent->ghoul2, 0, "*head_t" );
		}

		memset ( &item, 0, sizeof(item) );
		item.renderfx = rf | RF_MINLIGHT;
		item.ghoul2 = cgs.media.nightVisionModel;
		G2_PositionEntityOnBolt ( &item, cent->ghoul2, 0, ci->boltNightvision, cent->lerpOrigin, angles, cent->modelScale );
		trap_R_AddRefEntityToScene(&item);
	}
		

	// Loop through the gametye items  and handle each separately
	for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ )
	{
		// If they dont have the item then skip it 
		if ( !(cent->currentState.gametypeitems & (1<<i) ))
		{
			continue;
		}

		// Need to add bolt to model
		if ( cg_items[ MODELINDEX_GAMETYPE_ITEM + i ].boltModel )
		{
			refEntity_t	item;

			if ( ci->boltGametypeItems[i] == -1 )
			{
				ci->boltGametypeItems[i] = trap_G2API_AddBolt( cent->ghoul2, 0, "*back" );	
				if ( ci->boltGametypeItems[i] == -1 )
				{
					continue;
				}

				// Remove all identity items on the back
				CG_RemoveIdentityItemsOnBack ( cent );
			}

			memset ( &item, 0, sizeof(item) );
			item.renderfx = rf | RF_MINLIGHT;
			item.ghoul2 = cg_items[ MODELINDEX_GAMETYPE_ITEM + i ].boltModel;
			G2_PositionEntityOnBolt ( &item, cent->ghoul2, 0, ci->boltGametypeItems[i], cent->lerpOrigin, angles, cent->modelScale );
			trap_R_AddRefEntityToScene(&item);
		}
	}	
}

/*
===============
CG_UpdatePlayerWeaponModel
===============
*/
void CG_UpdatePlayerWeaponModel ( centity_t* cent, qboolean force )
{
	clientInfo_t *ci = &cgs.clientinfo[cent->currentState.clientNum];
	void         *model;
	
	model = NULL;
	if ( (cent->pe.torso.anim&~ANIM_TOGGLEBIT) == TORSO_USE )
	{
		int item;

		cent->pe.weapon = -1;

		for ( item = 0; item < MAX_GAMETYPE_ITEMS; item ++ )
		{
			if ( cent->currentState.gametypeitems & (1<<item) )
			{
				model = cg_items[MODELINDEX_GAMETYPE_ITEM+item].useModel;
				break;
			}
		}
	}
	// If the weapon model hasnt changed then dont update it
	else if ( !force && cent->currentState.weapon == cent->pe.weapon )
	{
		return;
	}
	else
	{
		// Ensure the weapon is registered
		CG_RegisterWeapon ( cent->currentState.weapon );

		model = cg_weapons[cent->currentState.weapon].weaponG2Model;

		cent->pe.weapon = cent->currentState.weapon;
	}

	// Get rid of whats in their hand
	if ( cent->pe.weaponModelSpot )
	{
		trap_G2API_DetachG2Model ( cent->ghoul2, cent->pe.weaponModelSpot );
		trap_G2API_RemoveGhoul2Model( &cent->ghoul2, cent->pe.weaponModelSpot );
		cent->pe.weaponModelSpot = 0;
	}

	// If we have a ghoul model then we can attach it to the right hand of the player model
	if (model)
	{
		cent->pe.weaponModelSpot = trap_G2API_CopySpecificGhoul2Model(model, 0, cent->ghoul2, -1 ); 
		trap_G2API_SetBoltInfo(cent->ghoul2, cent->pe.weaponModelSpot, ci->boltWorldWeapon );
		trap_G2API_AttachG2Model(cent->ghoul2, cent->pe.weaponModelSpot, cent->ghoul2, ci->boltWorldWeapon, 0);

		// Special case to make sure the bayonet shows up for the ak74
		switch ( cent->currentState.weapon )
		{
			case WP_AK74_ASSAULT_RIFLE:
				trap_G2API_SetSurfaceOnOff( cent->ghoul2, cent->pe.weaponModelSpot, "bayonet_off", 0 );
				break;

			case WP_M4_ASSAULT_RIFLE:
				trap_G2API_SetSurfaceOnOff( cent->ghoul2, cent->pe.weaponModelSpot, "m203_off", 0 );
				break;
		}
	}
}

/*
===============
CG_UpdatePlayerModel
===============
*/
void CG_UpdatePlayerModel ( centity_t* cent )
{
	if ((cent->ghoul2 == NULL) && trap_G2_HaveWeGhoul2Models(cgs.clientinfo[cent->currentState.clientNum].ghoul2Model))
	{
		CG_ResetPlayerEntity ( cent );

		if (!cgs.clientinfo[cent->currentState.clientNum].ghoul2Model)
		{
			Com_Error(ERR_DROP, "CG_UpdatePlayerModel invalid g2 pointer for client\n");
		}
		trap_G2API_DuplicateGhoul2Instance(cgs.clientinfo[cent->currentState.clientNum].ghoul2Model, &cent->ghoul2);
	
		// Force the players weapon model to be updated since their main model changed
		CG_UpdatePlayerWeaponModel ( cent, qtrue );
	}
}

/*
===============
CG_Player
===============
*/
void CG_Player( centity_t *cent ) 
{
	clientInfo_t	*ci;
	refEntity_t		player;
	int				clientNum;
	int				renderfx;
	qboolean		shadow;
	float			shadowPlane;
	int				iwantout = 0;
	vec3_t			save;

	// Dont draw any player models when the round has ended
	if ( cg.snap->ps.pm_type == PM_INTERMISSION )
	{
		return;
	}

	// the client number is stored in clientNum.  It can't be derived
	// from the entity number, because a single client may have
	// multiple corpses on the level using the same clientinfo
	clientNum = cent->currentState.clientNum;
	if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) 
	{
		Com_Error( ERR_FATAL, "Bad clientNum on player entity");
	}

	ci = &cgs.clientinfo[ clientNum ];

	// it is possible to see corpses from disconnected players that may
	// not have valid clientinfo
	if ( !ci->infoValid ) 
	{
		return;
	}

	// don't draw the death sequences, as the body queue should be doing it for us
	if ( cent->currentState.eFlags & EF_DEAD )
	{	
		return;
	}

	// Add the player to the radar if on the same team and its a team game
	if ( cgs.gametypeData->teams )
	{
		if ( cg.snap->ps.clientNum != cent->currentState.number && ci->team == cgs.clientinfo[ cg.snap->ps.clientNum ].team )
		{
			cg.radarEntities[cg.radarEntityCount++] = cent;
		}
	}

	// get the player model information
	renderfx = 0;
	if ( cent->currentState.number == cg.snap->ps.clientNum) 
	{
		// If rendering third person then we shouldnt draw the player if 
		// the view origin is too close to the player (ie, inside it)
		if ( cg.renderingThirdPerson )
		{
			vec3_t diff;
			vec3_t a;
			vec3_t b;
			float f;

			VectorCopy ( cg.refdef.vieworg, a );
			VectorCopy ( cent->lerpOrigin, b );
//			a[2] = 0;
//			b[2] = 0;

			VectorSubtract ( a, b, diff );

			f = VectorLengthSquared ( diff );
			if ( VectorLengthSquared ( diff ) < 1800 )
			{
				renderfx = RF_THIRD_PERSON;
			}
		}
		// only draw in mirrors
		else
		{
			renderfx = RF_THIRD_PERSON;			
		} 
	}

	// Initialize the ref entity
	memset (&player, 0, sizeof(player));

	// NOTENOTE Temporary
	VectorSet(player.modelScale, 1,1,1);
	player.radius = 64;
	VectorClear(player.angles);

	// add the shadow
	shadow = CG_PlayerShadow( cent, &shadowPlane );

	if (iwantout)
	{
		return;
	}

	// add a water splash if partially in and out of water
//	CG_PlayerSplash( cent );

	if ( cg_shadows.integer == 3 && shadow ) 
	{
		renderfx |= RF_SHADOW_PLANE;
	}

	// use the same origin for all
	renderfx |= RF_LIGHTING_ORIGIN | RF_MINLIGHT;			

	VectorCopy( cent->lerpOrigin, player.origin );
	VectorCopy( cent->lerpOrigin, player.lightingOrigin );

	player.shadowPlane = shadowPlane;
	player.renderfx = renderfx;
	
	// don't positionally lerp at all
	VectorCopy (player.origin, player.oldorigin);	

	// get the animation state (after rotation, to allow feet shuffle)
	CG_UpdatePlayerModel ( cent );
	CG_UpdatePlayerAnimations ( cent );
	CG_UpdatePlayerWeaponModel ( cent, qfalse );

	if ( (cent->currentState.eFlags & EF_DEAD ) )
	{
		AnglesToAxis( cent->lerpAngles, player.axis );
	}
	else
	{
		// Force the legs and torso to stay aligned for now to ensure the client
		// and server are in sync with the angles.  
		// TODO: Come up with a way to keep these in sync on both client and server
		cent->pe.torso.yawing = qtrue;
		cent->pe.torso.pitching = qtrue;
		cent->pe.legs.yawing= qtrue;

		BG_PlayerAngles( cent->lerpAngles, 
 					     player.axis, 
						 
						 cent->pe.ghoulLegsAngles, 
						 cent->pe.ghoulLowerTorsoAngles,
						 cent->pe.ghoulUpperTorsoAngles,
						 cent->pe.ghoulHeadAngles,

						 cent->lerpLeanOffset,
	
						 cent->pe.painTime, 
						 cent->pe.painDirection, 
						 cg.time,

						 &cent->pe.torso,
						 &cent->pe.legs,

					     cg.frametime, 
						 cent->lerpVelocity,
						 (cent->currentState.eFlags & EF_DEAD), 
						 cent->currentState.angles2[YAW],
						 cent->ghoul2 );
	}

	VectorCopy ( cent->lerpOrigin, save );
	VectorCopy ( player.origin, cent->lerpOrigin );

	CG_ScaleModelAxis(&player);

	// Copy the ghoul 2 info into the ref entity
	CG_SetGhoul2Info(&player, cent);

	// Now add the player to the scene
	trap_R_AddRefEntityToScene(&player);
	
	//
	// add the gun / barrel / flash
	//
	// need the angle AFTER the lean is added
//	vectoangles( player.axis[0], cent->pe.ghoulRootAngles ); 	

	// Render any weapon related effects
	CG_PlayerWeaponEffects ( &player, cent, ci->team, cent->pe.ghoulLegsAngles ); 
	
	// Render any of the floating sprites above the players head
	CG_PlayerSprites ( cent );

	CG_PlayerGametypeItems ( cent, cent->pe.ghoulLegsAngles );

	VectorCopy ( save, cent->lerpOrigin );
}

/*
===============
CG_ResetPlayerEntity

A player just came into view or teleported, so reset all animation info
===============
*/
void CG_ResetPlayerEntity( centity_t *cent ) 
{
	cent->errorTime = -99999;		// guarantee no error decay added
	cent->extrapolated = qfalse;	

	BG_EvaluateTrajectory( &cent->currentState.pos, cg.time, cent->lerpOrigin );
	BG_EvaluateTrajectory( &cent->currentState.apos, cg.time, cent->lerpAngles );

	VectorCopy( cent->lerpOrigin, cent->rawOrigin );
	VectorCopy( cent->lerpAngles, cent->rawAngles );

	cent->pe.legs.anim = -1;
	cent->pe.torso.anim = -1;

	memset( &cent->pe.legs, 0, sizeof( cent->pe.legs ) );
	cent->pe.legs.yawAngle = cent->rawAngles[YAW];
	cent->pe.legs.yawing = qfalse;
	cent->pe.legs.pitchAngle = 0;
	cent->pe.legs.pitching = qfalse;

	memset( &cent->pe.torso, 0, sizeof( cent->pe.legs ) );
	cent->pe.torso.yawAngle = cent->rawAngles[YAW];
	cent->pe.torso.yawing = qfalse;
	cent->pe.torso.pitchAngle = cent->rawAngles[PITCH];
	cent->pe.torso.pitching = qfalse;
}

/*
=================
CG_ProcessIdentityItems

Attaches all the items for a character skin
=================
*/
static void CG_ProcessIdentityItems ( TGhoul2 ghoul2, TInventoryTemplate *items )
{
	TSurfaceList	*surf;

	while ( items )
	{
		if ( items->mItem )
		{
			if ( items->mItem->mModel && items->mBolt )
			{
				items->mModelIndex = trap_G2API_InitGhoul2Model(&ghoul2, items->mItem->mModel, 0, 0, 0, 0, 0);
				if (items->mModelIndex != -1)
				{
					items->mBoltIndex = trap_G2API_AddBolt ( ghoul2, 0, items->mBolt);
					if (items->mBoltIndex != -1)
					{
						trap_G2API_AttachG2Model( ghoul2, items->mModelIndex, ghoul2, items->mBoltIndex, 0);
					}
				}
			}

			surf = items->mItem->mOffList;
			while(surf)
			{
				trap_G2API_SetSurfaceOnOff( ghoul2, 0, surf->mName, G2SURFACEFLAG_OFF);

				surf = surf->mNext;
			}

			surf = items->mItem->mOnList;
			while(surf)
			{
				trap_G2API_SetSurfaceOnOff( ghoul2, 0, surf->mName, 0);

				surf = surf->mNext;
			}
		}

		items = items->mNext;
	}
}

/*
=================
CG_RemoveIdentityItemsOnBack

Removes all identity items on the back of the given entity. There is
currently no way to get the identity items back after removing them
without killing off the player.
=================
*/
void CG_RemoveIdentityItemsOnBack ( centity_t* cent )
{
	TInventoryTemplate*	items[2];
	TIdentity*			identity;
	int					pass;

	// Grab the identity structure for this entity
	identity = cgs.clientinfo[cent->currentState.number].identity;
	if ( !identity )
	{
		return;
	}

	// Takes two passes to remove em all since the item lists are in two places
	items[0] = identity->mCharacter->mInventory;
	items[1] = identity->mSkin->mInventory;

	for ( pass = 0; pass < 2; pass ++ )
	{
		while ( items[pass] )
		{
			if ( items[pass]->mOnBack && items[pass]->mItem )
			{
				TSurfaceList	*surf;

				// Bolt on?
				if ( items[pass]->mBolt && *items[pass]->mBolt)
				{
					trap_G2API_DetachG2Model ( cent->ghoul2, items[pass]->mModelIndex );
					trap_G2API_RemoveGhoul2Model ( &cent->ghoul2, items[pass]->mModelIndex );
				}

				// Surface list?  Reversed from what process does
				surf = items[pass]->mItem->mOffList;
				while(surf)
				{
					trap_G2API_SetSurfaceOnOff( cent->ghoul2, 0, surf->mName, 0);

					surf = surf->mNext;
				}

				surf = items[pass]->mItem->mOnList;
				while(surf)
				{
					trap_G2API_SetSurfaceOnOff( cent->ghoul2, 0, surf->mName, G2SURFACEFLAG_OFF);

					surf = surf->mNext;
				}
			}

			items[pass] = items[pass]->mNext;
		}
	}
}

/*
=================
CG_RegisterIdentity

Registers an identity
=================
*/
TGhoul2 CG_RegisterIdentity ( TIdentity* identity, char *animationFile, gender_t* gender )
{
	char				name[MAX_QPATH];
	TGenericParser2		skinFile;
	TGPGroup			*basegroup, *group, *sub;
	char				temp[20480], *end;
	int					numPairs;
	TGhoul2				ghoul2Ptr;

	numPairs   = 0;
	end		   = temp;
	*end	   = 0;
	ghoul2Ptr  = NULL;

	*animationFile = 0;

	if (!identity )
	{
		return NULL;
	}

	if (trap_G2API_InitGhoul2Model( &ghoul2Ptr, identity->mCharacter->mModel, 0, 0, 0, (1<<4), 0) == -1)
	{
		return NULL;
	}
	
	trap_G2API_GetAnimFileNameIndex( ghoul2Ptr, 0, name );
	Com_sprintf(animationFile, MAX_QPATH, "%s_mp.cfg", name );

	if ( identity->mCharacter->mParent)
	{
		CG_ProcessIdentityItems( ghoul2Ptr, identity->mCharacter->mParent->mInventory );
	}

	CG_ProcessIdentityItems( ghoul2Ptr, identity->mCharacter->mInventory);
	CG_ProcessIdentityItems( ghoul2Ptr, identity->mSkin->mInventory);

	// don't need the mouth
	trap_G2API_SetSurfaceOnOff( ghoul2Ptr, 0, "mouth_r", G2SURFACEFLAG_OFF|G2SURFACEFLAG_NODESCENDANTS);
	trap_G2API_SetSurfaceOnOff( ghoul2Ptr, 0, "mouth_l", G2SURFACEFLAG_OFF|G2SURFACEFLAG_NODESCENDANTS);

	// Parse the g2skin file
	Com_sprintf( name, sizeof(name), "models/characters/skins/%s.g2skin", identity->mSkin->mSkin );
	skinFile = trap_GP_ParseFile( name, qtrue, qfalse );
	if ( !skinFile )
	{
		trap_G2API_CleanGhoul2Models( &ghoul2Ptr);
		return NULL;
	}

	basegroup = trap_GP_GetBaseParseGroup ( skinFile );
	group = trap_GPG_GetSubGroups ( basegroup );

	while(group)
	{
		trap_GPG_GetName ( group, name );

		// Parse the material
		if ( Q_stricmp ( name, "material") == 0)
		{
			char	matName[MAX_QPATH];
			char	shaderName[MAX_QPATH];

			trap_GPG_FindPairValue ( group, "name", "", matName );

			sub = trap_GPG_FindSubGroup ( group, "group");
			if (sub)
			{
				trap_GPG_FindPairValue ( sub, "shader1", "", shaderName );
				if (!shaderName[0])
				{
					trap_GPG_FindPairValue ( sub, "texture1", "", shaderName );
				}
			}

			if (matName[0] && shaderName[0])
			{
				end += Com_sprintf(end, sizeof(temp) - (end-temp+1), "%s %s ", matName, shaderName);
				numPairs++;
			}
		}

		group = trap_GPG_GetNext ( group );
	}

	trap_GP_Delete(&skinFile);

	if (numPairs)
	{
		qhandle_t	handle;

		handle = trap_G2API_RegisterSkin( identity->mName, numPairs, temp);
		trap_G2API_SetSkin( ghoul2Ptr, 0, handle);
	}

	// If the gender was requested then look for female in the model
	// name as the indicator
	if ( gender )
	{
		Q_strlwr ( (char*)identity->mCharacter->mModel );
		
		*gender = GENDER_MALE;
		if ( strstr ( identity->mCharacter->mModel, "female" ) )
		{
			*gender = GENDER_FEMALE;
		}
	}

	return ghoul2Ptr;
}
