// Copyright (C) 2001-2002 Raven Software.
//
// BG_Player.c

#include "q_shared.h"
#include "bg_public.h"
#include "bg_local.h"

#include "..\ghoul2\g2.h"

#include "../cgame/animtable.h"

#ifdef QAGAME
#include "g_local.h"
#endif

#ifdef UI_EXPORTS
#include "../ui/ui_local.h"
#endif

#ifndef UI_EXPORTS
#ifndef QAGAME
#include "../cgame/cg_local.h"
#endif
#endif

TCharacterTemplate		*bg_characterTemplates = NULL;
TItemTemplate			*bg_itemTemplates	   = NULL;
int						bg_identityCount       = 0;
TIdentity				bg_identities[MAX_IDENTITIES];

goutfitting_t			bg_outfittings[MAX_OUTFITTINGS];
int						bg_outfittingCount     = 0;

char					bg_availableOutfitting[WP_NUM_WEAPONS] = {-1};

int bg_outfittingGroups[OUTFITTING_GROUP_MAX][MAX_OUTFITTING_GROUPITEM] = 
{
	{ MODELINDEX_WEAPON_AK74,		MODELINDEX_WEAPON_M4,		MODELINDEX_WEAPON_USAS12, MODELINDEX_WEAPON_MSG90A1,	MODELINDEX_WEAPON_M60,	MODELINDEX_WEAPON_MP5,	MODELINDEX_WEAPON_RPG7,	 MODELINDEX_WEAPON_MM1, -1, -1 },
	{ MODELINDEX_WEAPON_M590,		MODELINDEX_WEAPON_MICROUZI,	MODELINDEX_WEAPON_M3A1,		-1, -1, -1, -1, -1, -1, - 1 },
	{ MODELINDEX_WEAPON_M19,		MODELINDEX_WEAPON_SOCOM,	-1,							-1,							-1, -1, -1, -1, -1, -1 },
	{ MODELINDEX_WEAPON_SMOHG92,	MODELINDEX_WEAPON_M84,		MODELINDEX_WEAPON_M15,		MODELINDEX_WEAPON_ANM14,	-1, -1, -1, -1, -1, -1 },
	{ MODELINDEX_ARMOR,				MODELINDEX_NIGHTVISION,		MODELINDEX_THERMAL,			-1,							-1, -1, -1, -1, -1, -1 },
};

/*
===================
PM_StartLegsAnim

Starts a new leg animation for the given playerstate
===================
*/
static void PM_StartLegsAnim( playerState_t* ps, int anim ) 
{
	if ( ps->pm_type >= PM_DEAD ) 
	{
		return;
	}

	ps->legsAnim = ( ( ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
}

/*
===================
PM_ContinueLegsAnim

Continues running the given leg animation, if its a new animation then it is started
===================
*/
void PM_ContinueLegsAnim( playerState_t* ps, int anim ) 
{
	if ( ( ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim ) 
	{
		return;
	}
	
	PM_StartLegsAnim( ps, anim );
}

/*
===================
PM_ForceLegsAnim
===================
*/
void PM_ForceLegsAnim( playerState_t* ps, int anim) 
{
	PM_StartLegsAnim( ps, anim );
}

/*
===================
PM_StartTorsoAnim
===================
*/
void PM_StartTorsoAnim( playerState_t* ps, int anim, int time ) 
{
	if ( anim == -1 )
	{
		return;
	}

	if ( ps->pm_type >= PM_DEAD ) 
	{
		return;
	}
	
	ps->torsoAnim  = ( ( ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
	ps->torsoTimer = time;
}

/*
===================
PM_ContinueTorsoAnim

Continues running the given torso animation, if its a new animation then it is started
===================
*/
static void PM_ContinueTorsoAnim( playerState_t* ps, int anim ) 
{
	if ( anim == -1 )
	{
		return;
	}

	if ( ( ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) 
	{
		return;
	}
	
	PM_StartTorsoAnim( ps, anim, 0 );
}

/*
==============
PM_TorsoAnimation

Sets the current torso animation based on the given playerstate
==============
*/
void PM_TorsoAnimation( playerState_t* ps ) 
{
	switch ( ps->weaponstate ) 
	{
		case WEAPON_SPAWNING:
		case WEAPON_READY:

			if ( ps->stats[STAT_USEWEAPONDROP] )
			{
				PM_ContinueTorsoAnim ( ps, TORSO_USE );
			}
			else if ( (ps->pm_flags & PMF_ZOOMED) && weaponData[ps->weapon].animIdleZoomed )
			{
				PM_ContinueTorsoAnim ( ps, weaponData[ps->weapon].animIdleZoomed );
			}
			else
			{
				PM_ContinueTorsoAnim ( ps, weaponData[ps->weapon].animIdle );
			}

			break;
	}
}

/*
=================
BG_ParseInventory

Parses the inventory items indicated in the given group and returns
a linked list containing the results
=================
*/
TInventoryTemplate *BG_ParseInventory ( TGPGroup group )
{
	TInventoryTemplate	*top, *inv;
	TGPGroup			subGroup;
	char				temp[1024];

	// Convienience for handling no items
	if (!group)
	{
		return 0;
	}

	top = NULL;

	// Parse each of the inventory items
	subGroup = trap_GPG_GetSubGroups( group );
	while(subGroup)
	{
		trap_GPG_GetName ( subGroup, temp );

		// If the group name isnt item then 'Item' then its not an inventory item
		if ( Q_stricmp ( temp, "Item") == 0)
		{
			inv = (TInventoryTemplate *)trap_VM_LocalAlloc ( sizeof(*inv));

			// Name of the item
			trap_GPG_FindPairValue ( subGroup, "Name||Name1", "", temp );
			inv->mName = trap_VM_LocalStringAlloc ( temp );

			// Bolt for the item
			trap_GPG_FindPairValue ( subGroup, "Bolt", "", temp );
			inv->mBolt = trap_VM_LocalStringAlloc ( temp );

			trap_GPG_FindPairValue ( subGroup, "mp_onback||onback", "no", temp );
			if ( !Q_stricmp ( temp, "yes" ) )
			{
				inv->mOnBack = qtrue;
			}

			inv->mNext = top;
			top = inv;
		}

		// Move to the next group	
		subGroup = trap_GPG_GetNext ( subGroup );
	}
	
	return top;
}

/*
=================
BG_ParseSkins

Parses the skins contained in the given group and returns a linked
list with the results
=================
*/
TSkinTemplate *BG_ParseSkins( TCharacterTemplate* character, TGPGroup group )
{
	TSkinTemplate	*skin;
	TIdentity		*identity;
	TGPGroup		subGroup;
	char			temp[1024];
	fileHandle_t	f;
#ifndef SPECIAL_PRE_CACHE
	int				len;
#endif
	qboolean		validSkin;

	character->mSkins = NULL;

	// Parse the skin file first
	trap_GPG_FindPairValue ( group, "SkinFile", "", temp );
	if ( temp[0] )
	{
		skin = (TSkinTemplate *) trap_VM_LocalAlloc ( sizeof(*skin) );

		skin->mSkin = trap_VM_LocalStringAlloc ( temp );
		skin->mNext = character->mSkins;
		character->mSkins = skin;
	}

	// Now parse all the skin groups
	subGroup = trap_GPG_GetSubGroups ( group );
	while(subGroup)
	{
		trap_GPG_GetName ( subGroup, temp );

		// If the groups name isnt 'Skin' then skip it
		if ( Q_stricmp( temp, "Skin") == 0 )
		{
			// Allocate memory for the skin
			skin = (TSkinTemplate *) trap_VM_LocalAlloc ( sizeof(*skin) );

			// Grab the skin filename
			trap_GPG_FindPairValue ( subGroup, "File", "", temp );
			skin->mSkin = trap_VM_LocalStringAlloc ( temp );

#ifdef SPECIAL_PRE_CACHE
			f = 0;
			validSkin = qtrue;
#else
			Com_sprintf( temp, sizeof(temp), "models/characters/skins/%s.g2skin", skin->mSkin );
			len = trap_FS_FOpenFile( temp, &f, FS_READ );
			if (f != 0) 
			{
				trap_FS_FCloseFile(f);
				validSkin = qtrue;
			}
			else
			{
				validSkin = qfalse;
			}
#endif

			// Parse the inventory for the skin
			skin->mInventory = BG_ParseInventory( trap_GPG_FindSubGroup ( subGroup, "Inventory") );

			// Link the skin into the skin linked list
			skin->mNext = character->mSkins;
			character->mSkins = skin;

			// If the character isnt deathmatch then dont add it to the 
			// identity list
			if ( character->mDeathmatch && validSkin)
			{
				// Allocate a new identity
				identity = &bg_identities[bg_identityCount++];

				identity->mCharacter = character;
				identity->mSkin      = skin;

				trap_GPG_FindPairValue ( subGroup, "mp_identity", "", temp );
				if ( !temp[0] )
				{
					identity->mName = trap_VM_LocalStringAlloc ( va("%s/%s", character->mName, skin->mSkin ) );
				}
				else
				{
					identity->mName = trap_VM_LocalStringAlloc ( temp );
				}

				// Team name?
				trap_GPG_FindPairValue ( subGroup, "mp_team", "", temp );
				if ( temp[0] )
				{
					identity->mTeam = trap_VM_LocalStringAlloc ( temp );
				}
				else
				{
					identity->mTeam = "";
				}
			}
		}

		// Move to the next sub group in the parsers list
		subGroup = trap_GPG_GetNext ( subGroup );
	}
	
	return character->mSkins;
}

/*
=================
BG_ParseModelSounds

Parses the model sounds for the given group and returns a linked
list with the results
=================
*/
TModelSounds *BG_ParseModelSounds( TGPGroup group )
{
	TModelSounds    *top;
	TModelSounds	*sounds;
	TGPGroup		pairs;
	char			temp[1024];

	// Convienience
	if ( !group )
	{
		return NULL;
	}

	top = NULL;

	// Now parse all the skin groups
	pairs = trap_GPG_GetPairs ( group );
	while(pairs)
	{
		// Allocate memory for the sounds
		sounds = (TModelSounds*) trap_VM_LocalAlloc ( sizeof(*sounds) );
		sounds->mNext = top;
		top = sounds;

		// Grab the sounds name
		trap_GPV_GetName ( pairs, temp );
		sounds->mName = trap_VM_LocalStringAlloc ( temp );

		// Start with no sounds
		sounds->mCount = 0;

		// Should be a list
		if ( trap_GPV_IsList ( pairs ) )
		{
			TGPValue list;
			
			// Run through the list
			list = trap_GPV_GetList ( pairs );
			while ( list && sounds->mCount < MAX_MODEL_SOUNDS )
			{
				// Add the sound to the list
				trap_GPV_GetName ( list, temp );
				sounds->mSounds[sounds->mCount++] = trap_VM_LocalStringAlloc ( temp );

				list = trap_GPV_GetNext ( list );
			}
		}

		// Move to the next sound set in the parsers list
		pairs = trap_GPV_GetNext ( pairs );
	}

	return top;
}

/*
=================
BG_ParseItemFile

Parses the item file.  The item file contains a list of all of the 
items that can be used as inventory items for a given skin
=================
*/
qboolean BG_ParseItemFile ( void )
{
	TGPGroup		*baseGroup, *subGroup;
	TGPValue		*pairs;
	TItemTemplate	*item;
	TSurfaceList	*surf;
	char			temp[1024];
	TGenericParser2	ItemFile;

	// Create the generic parser so the item file can be parsed
	ItemFile = trap_GP_ParseFile( "ext_data/sof2.item", qtrue, qfalse );
	if ( !ItemFile )
	{
		return qfalse;
	}

	baseGroup = trap_GP_GetBaseParseGroup ( ItemFile );
	subGroup = trap_GPG_GetSubGroups ( baseGroup );
	while(subGroup)
	{
		trap_GPG_GetName ( subGroup, temp );

		if (Q_stricmp( temp, "item") == 0)
		{
			// Is this item used for deathmatch?
			trap_GPG_FindPairValue ( subGroup, "Deathmatch", "yes", temp );
			if (Q_stricmp( temp, "no") == 0)
			{
				subGroup = trap_GPG_GetNext ( subGroup );
				continue;
			}

			// Allocate the item template and link it up to the item list
			item = (TItemTemplate *) trap_VM_LocalAlloc ( sizeof(*item) );
			item->mNext   = bg_itemTemplates;
			bg_itemTemplates = item;

			// Name of the item
			trap_GPG_FindPairValue ( subGroup, "Name", "", temp );
			item->mName = trap_VM_LocalStringAlloc ( temp );

			// Model for the item
			trap_GPG_FindPairValue ( subGroup, "Model", "", temp );
			if ( temp[0] )
			{
				item->mModel = trap_VM_LocalStringAlloc ( temp );
			}

			pairs = trap_GPG_GetPairs ( subGroup );
			while(pairs)
			{
				trap_GPV_GetName ( pairs, temp );

				// Surface off?
				if ( Q_stricmpn ( temp, "offsurf", 7) == 0)
				{
					// Allocate the surface structure and link it into the list
					surf = (TSurfaceList *) trap_VM_LocalAlloc ( sizeof(*surf) );
					surf->mNext = item->mOffList;
					item->mOffList = surf;

					// Name of the surface to turn off
					trap_GPV_GetTopValue ( pairs, temp );
					surf->mName = trap_VM_LocalStringAlloc ( temp );
				}
				// Surface on?
				else if ( Q_stricmpn( temp, "onsurf", 6) == 0)
				{
					// Allocate the surface structure and link it into the list
					surf = (TSurfaceList *) trap_VM_LocalAlloc(sizeof(*surf));
					surf->mNext = item->mOnList;
					item->mOnList = surf;

					// Name of the surface to turn off
					trap_GPV_GetTopValue ( pairs, temp );
					surf->mName = trap_VM_LocalStringAlloc ( temp );
				}

				// Next pairs
				pairs = trap_GPV_GetNext ( pairs );
			}
		}

		// Next group
		subGroup = trap_GPG_GetNext ( subGroup );
	}

	trap_GP_Delete(&ItemFile);

	return qtrue;
}

/*
=================
BG_FindCharacterTemplate

Finds a character template the matches the given name or NULL if
a match could not be found
=================
*/
TCharacterTemplate *BG_FindCharacterTemplate (const char *name)
{
	TCharacterTemplate	*current;

	// Convienience
	if (!name)
	{
		return NULL;
	}

	// Linear search through all of the parsed templates
	current = bg_characterTemplates;
	while(current)
	{
		if (Q_stricmp(name, current->mName) == 0)
		{
			return current;
		}

		current = current->mNext;
	}

	// None found
	return NULL;
}

/*
=================
BG_FindItemTemplate

Finds an item template the matches the given name or NULL if a match
could not be found
=================
*/
TItemTemplate *BG_FindItemTemplate(const char *name)
{
	TItemTemplate		*current;

	// Convienience
	if (!name)
	{
		return NULL;
	}

	// Linear search through all of the parsed items
	current = bg_itemTemplates;
	while(current)
	{
		if (Q_stricmp(name, current->mName) == 0)
		{
			return current;
		}

		current = current->mNext;
	}

	return NULL;
}

/*
=================
BG_LinkTemplates

Cross links the various templates 
=================
*/
static void BG_LinkTemplates(void)
{
	TCharacterTemplate	*current;
	TInventoryTemplate	*inv;
	TSkinTemplate		*skin;

	current = bg_characterTemplates;
	while(current)
	{
		// If this template has a parent then find it and link it up as 
		// its parent.  Ensure that the parent doesnt link back to itself
		current->mParent = BG_FindCharacterTemplate(current->mParentName);
		if (current->mParent == current)
		{
			current->mParent = NULL;
		}
		// Bring over any parent items
		else if ( current->mParent )
		{
			// No model, bring over the parents.
			if ( !current->mModel )
			{
				current->mModel = current->mParent->mModel;
			}
		}

		// Link up all the inventory for this character
		inv = current->mInventory;
		while(inv)
		{
			inv->mItem = BG_FindItemTemplate(inv->mName);		
			inv = inv->mNext;
		}

		// Link up all the skins for this character
		skin = current->mSkins;
		while(skin)
		{
			// Link up all the inventory items specific to the skins
			inv = skin->mInventory;
			while(inv)
			{
				inv->mItem = BG_FindItemTemplate(inv->mName);

				inv = inv->mNext;
			}

			skin = skin->mNext;
		}

		// Move on to the next character template
		current = current->mNext;
	}
}

/*
=================
BG_ParseNPCFiles

Parses all the the .npc files in the npc directory and
stores their info into global lists.  
=================
*/
qboolean BG_ParseNPCFiles ( void )
{
	int					i, numNPCFiles, filelen;
	TGPGroup			baseGroup, subGroup;
	TGenericParser2		NPCFile;
	const char			*currentParent = 0;
	TCharacterTemplate	*newTemplate;
	char				fileName[MAX_QPATH];
	char				NPCFiles[4096];
	char				temp[1024];
	char				*fileptr;

	// Clear the current list 
	bg_characterTemplates = NULL;

	// Grab the list of NPC files
	numNPCFiles = trap_FS_GetFileList("NPCs", ".npc", NPCFiles, 4096 );
	if ( !numNPCFiles )
	{
		return qfalse;
	}

	// Parse each of the NPC files
	fileptr = NPCFiles;
	for( i=0; i<numNPCFiles; i++, fileptr += filelen+1 )
	{
		// Grab the length so we can skip this file later
		filelen = strlen(fileptr);

		Com_sprintf(fileName, sizeof(fileName),"NPCs/%s", fileptr );

		// Create the generic parser so the item file can be parsed
		NPCFile = trap_GP_ParseFile( fileName, qtrue, qfalse );
		if ( !NPCFile )
		{
			continue;
		}

		baseGroup = trap_GP_GetBaseParseGroup ( NPCFile );
		subGroup = trap_GPG_GetSubGroups ( baseGroup );

		while(subGroup)
		{
			trap_GPG_GetName ( subGroup, temp );

			// Look for a parent template if this is the group info
			if ( Q_stricmp( temp, "GroupInfo") == 0)
			{
				currentParent = NULL;

				// Is there a parent template?
				trap_GPG_FindPairValue ( subGroup, "ParentTemplate", "", temp );
				if ( temp[0] )
				{					
					currentParent = trap_VM_LocalStringAlloc ( temp );
				}
			}
			// A new character template
			else if ( Q_stricmp( temp, "CharacterTemplate") == 0)
			{
				// Allocate the new template and link it into the global list.
				newTemplate = (TCharacterTemplate *)trap_VM_LocalAlloc (sizeof(*newTemplate));
				newTemplate->mNext = bg_characterTemplates;
				bg_characterTemplates = newTemplate;

				// Exclude from deathmatch?
				trap_GPG_FindPairValue ( subGroup, "DeathMatch", "yes", temp );
				if ( Q_stricmp( temp, "no") == 0)
				{
					newTemplate->mDeathmatch = qfalse;
				}
				else
				{
					newTemplate->mDeathmatch = qtrue;
				}

				// Template name
				trap_GPG_FindPairValue ( subGroup, "Name", "", temp );
				if ( temp[0] )
				{
					newTemplate->mName = trap_VM_LocalStringAlloc ( temp );
				}

				// Template formal name
				trap_GPG_FindPairValue ( subGroup, "FormalName", "", temp );
				if ( temp[0] )
				{
					newTemplate->mFormalName = trap_VM_LocalStringAlloc ( temp );
				}

				// Template model 
				trap_GPG_FindPairValue ( subGroup, "Model", "", temp );
				if ( temp[0] )
				{
					newTemplate->mModel = trap_VM_LocalStringAlloc ( temp );
				}

				// Use the current parent
				newTemplate->mParentName = currentParent;

				// Parse inventory for this character template
				newTemplate->mInventory = BG_ParseInventory( trap_GPG_FindSubGroup( subGroup, "Inventory" ));

				// Parse the skins for this character template
				BG_ParseSkins( newTemplate, subGroup);

				// Parse the sounds for this character template
				newTemplate->mSounds = BG_ParseModelSounds ( trap_GPG_FindSubGroup ( subGroup, "MPSounds" ) );
			}

			// Move to the next group
			subGroup = trap_GPG_GetNext ( subGroup );
		}

		trap_GP_Delete(&NPCFile);
	}

	// Parse the item file
	BG_ParseItemFile();

	// Link up all the templates
	BG_LinkTemplates();
	
	return qtrue;
}

/*
=================
BG_GetModelSoundsGroup

Returns the group of sounds for the given model and sound group combination, if the
sound group couldnt be found NULL is returned
=================
*/
TModelSounds* BG_GetModelSoundsGroup ( const char* Identity, const char* SoundGroup )
{
	TIdentity			*identity;
	TCharacterTemplate	*character;
	TModelSounds		*sounds;
	
	// Grab the identity in question
	identity = BG_FindIdentity (Identity );
	if ( !identity )
	{
		return NULL;
	}

	character = identity->mCharacter;

	while ( character )
	{
		// Run through the sounds and look for the match
		sounds = character->mSounds;
		while ( sounds )
		{
			// Match?
			if ( !Q_stricmp ( sounds->mName, SoundGroup ) )
			{
				return sounds;
			}

			sounds = sounds->mNext;
		}

		character = character->mParent;
	}

	// Not found
	return NULL;
}

/*
=================
BG_GetModelSoundCount

Return the number of sounds for the given model
=================
*/
int BG_GetModelSoundCount ( const char *Identity, const char *SoundGroup )
{
	TModelSounds* sounds;

	// Grab the sounds
	sounds = BG_GetModelSoundsGroup( Identity, SoundGroup );
	if ( !sounds )
	{
		return 0;
	}

	// Return the sound count
	return sounds->mCount;
}

/*
=================
BG_GetModelSound

Returns the model sound for the given sound group and index.  If the sound
could not be found then NULL is returned.
=================
*/
const char *BG_GetModelSound ( const char *Identity, const char *SoundGroup, int index )
{
	TModelSounds	*sounds;

	// Grab the sounds
	sounds = BG_GetModelSoundsGroup( Identity, SoundGroup );
	if ( !sounds )
	{		
		return NULL;
	}

	// Invalid index?
	if ( index >= sounds->mCount )
	{
		return "";
	}

	// Run through the sounds and look for the match
	return sounds->mSounds[index];
}

/*
=================
BG_FindIdentity

Returns the identity with the given name
=================
*/
TIdentity *BG_FindIdentity ( const char *identityName )
{
	int i;

	// Convienience
	if (!identityName)
	{
		return NULL;
	}

	// Linear search through all of the parsed identities
	for ( i = 0; i < bg_identityCount; i ++ )
	{
		if (Q_stricmp(identityName, bg_identities[i].mName) == 0)
		{
			return &bg_identities[i];
		}
	}

	// None found
	return NULL;
}

/*
=================
BG_FindTeamIdentity

returns the first identity which matches the given team name
=================
*/
#define MAX_TEAM_IDENTS	5
TIdentity* BG_FindTeamIdentity ( const char* teamName, int index )
{
	TIdentity* idents[MAX_TEAM_IDENTS];
	int		   count;
	int		   i;

	// Convienience
	if ( !teamName )
	{
		return NULL;
	}

	// Linear search through all of the parsed identities
	for ( i = 0, count = 0; i < bg_identityCount && count < MAX_TEAM_IDENTS; i ++ )
	{
		if (Q_stricmp(teamName, bg_identities[i].mTeam) == 0)
		{
			idents[count++] = &bg_identities[i];
		}
	}

	if ( !count )
	{
		return NULL;
	}
	
	if ( index != -1 )
	{
		if ( index >= count )
		{
			index = count - 1;
		}

		return idents[index];
	}

	// None found
	return idents[rand()%count];
}


/*
=================
BG_ParseSkin

Reads a skin file into a null terminated list and returns the number found
=================
*/
int BG_ParseSkin ( const char* filename, char* pairs, int pairsSize )
{
	TGenericParser2 skinFile;
	TGPGroup		*basegroup;
	TGPGroup		*group;
	TGPGroup		*sub;
	char			name[MAX_QPATH];
	char*			end;
	int				numPairs;

	// Open the skin file
	skinFile = trap_GP_ParseFile( (char*)filename, qtrue, qfalse );
	if ( !skinFile )
	{
		return 0;
	}

	numPairs  = 0;
	end		  = pairs;
	*end	  = 0;
	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])
			{
				int size;

				size = Com_sprintf(end, pairsSize, "%s %s ", matName, shaderName);
				end += size;
				pairsSize -= size;
				numPairs++;
			}
		}

		group = trap_GPG_GetNext ( group );
	}

	trap_GP_Delete(&skinFile);

	return numPairs;
}

/*
==================
BG_SwingAngles
==================
*/
static void BG_SwingAngles ( 
	float		destination, 
	float		swingTolerance, 
	float		clampTolerance,
	float		speed, 
	float		*angle, 
	qboolean	*swinging, 
	int			frameTime 
	) 
{
	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 = frameTime * scale * speed;
		if ( move >= swing ) 
		{
			move = swing;
			*swinging = qfalse;
		}
		*angle = AngleMod( *angle + move );
	} 
	else if ( swing < 0 ) 
	{
		move = 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) );
	}
}

/*
=================
CG_AddPainTwitch
=================
*/
#define	PAIN_TWITCH_TIME	200
static void BG_AddPainTwitch( int painTime, int painDirection, int currentTime,  vec3_t torsoAngles ) {
	int		t;
	float	f;

	t = currentTime - painTime;
	if ( t >= PAIN_TWITCH_TIME ) {
		return;
	}

	f = 1.0 - (float)t / PAIN_TWITCH_TIME;

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

/*
=================
BG_CalculateLeanOffset
=================
*/
float BG_CalculateLeanOffset ( int leanTime )
{
	return ((float)(leanTime - LEAN_TIME) / LEAN_TIME * LEAN_OFFSET);
}

/*
=================
BG_PlayerAngles
=================
*/
void BG_PlayerAngles ( 

	vec3_t		startAngles, 
	vec3_t		legs[3], 

	vec3_t		legsAngles,				// out
	vec3_t		lowerTorsoAngles,
	vec3_t		upperTorsoAngles,
	vec3_t		headAngles,

	int			leanOffset,

	int			painTime, 
	int			painDirection, 
	int			currentTime,

	animInfo_t*	torsoInfo,
	animInfo_t*	legsInfo,

	int			frameTime, 
	vec3_t		realvelocity,
	qboolean	dead,
	float		movementDir,
	void*		ghoul2
	)
{
	float		dest;
	static	int	movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 };
	float		speed;
	int			dir;
	vec3_t		velocity;

	VectorCopy( startAngles, headAngles );
	VectorCopy ( realvelocity, velocity );
	headAngles[YAW] = AngleMod( headAngles[YAW] );
	VectorClear( legsAngles );
	VectorClear( lowerTorsoAngles );	
	VectorClear( upperTorsoAngles );

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

	// allow yaw to drift a bit
	if ( ( legsInfo->anim & ~ANIM_TOGGLEBIT ) != TORSO_IDLE_PISTOL  ) 
	{
		// if not standing still, always point all in the same direction
		torsoInfo->yawing   = qtrue;
		torsoInfo->pitching = qtrue;
		legsInfo->yawing    = qtrue;
	}

	speed = VectorNormalize( velocity );

	// adjust legs for movement dir
	if (dead) 
	{
		dir = 0;
	} 
	else 
	{
		dir = movementDir;

		if ( leanOffset && !speed )
		{
			dir = 0;
		}
	}
	
//	legsAngles[YAW]   = headAngles[YAW] + 2 * movementOffsets[ dir ];
//	torsoAngles[YAW]  = headAngles[YAW] + 2 * movementOffsets[ dir ];
	legsAngles[YAW]   = headAngles[YAW] + 2 * movementOffsets[ dir ];
	lowerTorsoAngles[YAW]  = headAngles[YAW] + 2 * movementOffsets[ dir ];

	// torso
	BG_SwingAngles( lowerTorsoAngles[YAW], 25, 90, 0.3f, &torsoInfo->yawAngle, &torsoInfo->yawing, frameTime );
	BG_SwingAngles( legsAngles[YAW],  40, 90, 0.3f, &legsInfo->yawAngle,  &legsInfo->yawing,  frameTime );

	if ( leanOffset )
	{
		legsAngles[YAW] = headAngles[YAW];
	}
	else
	{
		legsAngles[YAW] = legsInfo->yawAngle;
	}

	lowerTorsoAngles[YAW] = Com_Clampf ( -90, 90, AngleDelta (headAngles[YAW], legsAngles[YAW] ) );
	upperTorsoAngles[YAW] = lowerTorsoAngles[YAW] / 2;
	lowerTorsoAngles[YAW]  = legsAngles[YAW] + lowerTorsoAngles[YAW] / 2;

	headAngles[YAW] -= upperTorsoAngles[YAW];


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

	lowerTorsoAngles[PITCH] = dest;

	// --------- roll -------------

	// lean towards the direction of travel

	// Add in leanoffset
	if ( leanOffset )
	{
		lowerTorsoAngles[ROLL] -= ((float)leanOffset * 1.25f);
		lowerTorsoAngles[YAW] -= 1.25f * ((float)leanOffset/LEAN_OFFSET) * dest;
		headAngles[YAW] -= ((float)leanOffset/LEAN_OFFSET) * dest;
		headAngles[ROLL] -= ((float)leanOffset * 1.25f);
	}
	else if ( speed ) 
	{
		vec3_t	axis[3];
		float	side;

		speed *= 0.025;

		AnglesToAxis( legsAngles, axis );
		side = speed * DotProduct( velocity, axis[1] );
		legsAngles[ROLL] -= side;
	}

	// pain twitch
	BG_AddPainTwitch( painTime, painDirection, currentTime, lowerTorsoAngles );

	// pull the angles back out of the hierarchial chain
	AnglesSubtract( headAngles, lowerTorsoAngles, headAngles );
	AnglesSubtract( lowerTorsoAngles, legsAngles, lowerTorsoAngles );

	if ( legs && ghoul2 )
	{
		AnglesToAxis( legsAngles, legs );

		// Apply the rotations
		trap_G2API_SetBoneAngles(ghoul2, 0, "upper_lumbar", upperTorsoAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, currentTime); 

		trap_G2API_SetBoneAngles(ghoul2, 0, "lower_lumbar", lowerTorsoAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, currentTime); 
		trap_G2API_SetBoneAngles(ghoul2, 0, "cranium", headAngles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, 0,0, currentTime); 
	}
}

/*
======================
BG_ParseAnimationFile

Read a configuration file containing animation counts and rates
models/players/visor/animation.cfg, etc
======================
*/
qboolean BG_ParseAnimationFile ( const char *filename, animation_t* animations ) 
{
	const char		*text_p;
	int				len;
	int				i;
	char			*token;
	float			fps;
	int				skip;
	char			text[20000];
	fileHandle_t	f;
	int				animNum;

	// load the file
	len = trap_FS_FOpenFile( filename, &f, FS_READ );
	if ( len <= 0 || len >= sizeof( text ) - 1 ) 
	{
		return qfalse;
	}

	trap_FS_Read( text, len, f );
	trap_FS_FCloseFile( f );

	// parse the text
	text[len] = 0;
	text_p    = text;
	skip      = 0;

	//initialize anim array so that from 0 to MAX_ANIMATIONS, set default values of 0 1 0 100
	for(i = 0; i < MAX_ANIMATIONS; i++)
	{
		animations[i].firstFrame = 0;
		animations[i].numFrames = 0;
		animations[i].loopFrames = -1;
		animations[i].frameLerp = 100;
		animations[i].initialLerp = 100;
	}

	// read information for each frame
	while(1) 
	{
		token = COM_Parse( &text_p );

		if ( !token || !token[0]) 
		{
			break;
		}

		animNum = GetIDForString(bg_animTable, token);
		if(animNum == -1)
		{
//#ifndef FINAL_BUILD
#ifdef _DEBUG
			Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, filename);
#endif
			continue;
		}

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

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

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

		token = COM_Parse( &text_p );
		if ( !token ) 
		{
			break;
		}
		fps = atof( token );
		if ( fps == 0 ) 
		{
			fps = 1;//Don't allow divide by zero error
		}
		if ( fps < 0 )
		{//backwards
			animations[animNum].frameLerp = floor(1000.0f / fps);
		}
		else
		{
			animations[animNum].frameLerp = ceil(1000.0f / fps);
		}

		animations[animNum].initialLerp = ceil(1000.0f / fabs(fps));
	}

	return qtrue;
}

/*
========================
BG_SetAvailableOutfitting

Set the current availability table
========================
*/
void BG_SetAvailableOutfitting ( const char* available )
{
	int		len;

	len = strlen ( available );
	if ( len > WP_NUM_WEAPONS )
	{
		len = WP_NUM_WEAPONS;
	}

	// IF the availability has changed force a reload of the outfitting groups
	if ( Q_strncmp ( available, bg_availableOutfitting, min(sizeof(bg_availableOutfitting),len) ) )
	{
		bg_outfittingCount = 0;
	}

	// Initialize it to all on.
	memset ( &bg_availableOutfitting[0], '2', sizeof(bg_availableOutfitting) );
	memcpy ( &bg_availableOutfitting[0], available, len );
}

/*
========================
BG_IsWeaponAvailableForOutfitting

Is the given weapon available for outfitting?
========================
*/
qboolean BG_IsWeaponAvailableForOutfitting ( weapon_t weapon, int level )
{
	if ( bg_availableOutfitting[0] == -1 )
	{
		return qtrue;
	}
	
	if ( bg_availableOutfitting[weapon-1] - '0' >= level )
	{
		return qtrue;
	}

	return qfalse;
}

/*
========================
BG_DecompressOutfitting

Decompresses the given outfitting string into the outfitting structure
========================
*/
void BG_DecompressOutfitting ( const char* compressed, goutfitting_t* outfitting)
{
	int	group;
	int origitem;

	memset ( outfitting->items, 0, sizeof(outfitting->items) );

	for ( group = 0; group < OUTFITTING_GROUP_MAX; group ++ )
	{
		int item;

		if ( !*compressed )
		{
			item = -1;
		}
		else
		{
			item = ((*compressed++) - 'A');
		}

		// Valid item number?
		if ( item < 0 || item >= 10 )
		{
			item = 0;
		}

		// Valid slot for the group ?
		if ( bg_outfittingGroups[group][item] == -1 )
		{
			continue;
		}			

		// Ok to set the item now	
		outfitting->items[group] = item;
							
		// No initialized
		if ( bg_availableOutfitting[0] == -1 )
		{
			continue;
		}

		// Is it available?
		if ( bg_itemlist[bg_outfittingGroups[group][item]].giType == IT_WEAPON )
		{
			origitem = item;
			while ( !BG_IsWeaponAvailableForOutfitting ( bg_itemlist[bg_outfittingGroups[group][item]].giTag, 2 ) )
			{
				item++;
				if ( bg_outfittingGroups[group][item] == -1 )
				{
					item = 0;
				}

				if ( item == origitem )
				{
					//Com_Error ( ERR_FATAL, "ERROR: There must be at least one weapon available in each category" );
					item = -1;
					break;
				}
			}
		}

		// Ok to set the item now	
		outfitting->items[group] = item;
	}
}

/*
========================
BG_CompressOutfitting

Compresses the given outfitting structure into the given string
========================
*/
void BG_CompressOutfitting ( goutfitting_t* outfitting, char* compressed, int size )
{
	int i;

	for ( i = 0; i < OUTFITTING_GROUP_MAX && size; i ++, size-- )
	{
		*compressed++ = outfitting->items[i] + 'A';
	}

	*compressed = '\0';
}

/*
========================
BG_FindOutfitting

Finds the real outfitting that matches the given outfitting
========================
*/
int BG_FindOutfitting ( goutfitting_t* outfitting )
{
	int i;
	int l;

	// Loop through all the outfittings linearly
	for ( i = 0; i < bg_outfittingCount; i ++ )
	{
		for ( l = 0; l < OUTFITTING_GROUP_MAX; l ++ )
		{
			if ( bg_outfittings[i].items[l] != outfitting->items[l] )
			{
				break;
			}
		}

		// Both iterators at the end signifies a match
		if ( l == OUTFITTING_GROUP_MAX )
		{
			return i;
		}
	}

	return -1;
}

/*
========================
BG_ParseOutfittingTemplate

Parses a single outfitting template
========================
*/
qboolean BG_ParseOutfittingTemplate ( const char* fileName, goutfitting_t* outfitting )
{
	TGPGroup			baseGroup;
	TGPGroup			subGroup;
	TGenericParser2		file;
	TGPGroup			pairs;
	char				temp[MAX_OUTFITTING_NAME];

	// Initialize the outfitting
	memset ( outfitting, 0, sizeof(goutfitting_t) );

	// Create the generic parser so the item file can be parsed
	file = trap_GP_ParseFile( (char*)fileName, qtrue, qfalse );
	if ( !file )
	{
		return qfalse;
	}

	// Start at the top with the "outfitting" group
	baseGroup = trap_GP_GetBaseParseGroup ( file );
	subGroup  = trap_GPG_FindSubGroup ( baseGroup, "outfitting" );
	if ( !subGroup )
	{
		trap_GP_Delete(&file);
		return qfalse;
	}

	// Get the name of the template
	trap_GPG_FindPairValue ( subGroup, "displayName", fileName, outfitting->name );

	// Sub group named "items"
	pairs = trap_GPG_FindPair ( subGroup, "items" );
	if ( pairs )
	{
		TGPValue list;
		
		// Run through the list
		list = trap_GPV_GetList ( pairs );
		while ( list )
		{
			gitem_t*	item;

			trap_GPV_GetName ( list, temp );

			item = BG_FindItem ( temp );
			if ( item )
			{
				int index;
				for ( index=0; bg_outfittingGroups[item->outfittingGroup][index] != -1; index ++ )
				{
					if ( bg_outfittingGroups[item->outfittingGroup][index] == (item - &bg_itemlist[0]) )
					{
						outfitting->items[item->outfittingGroup] = index;
						break;
					}
				}
			}

			list = trap_GPV_GetNext ( list );
		}
	}

	// Sub group named "weapons"
	pairs = trap_GPG_FindPair ( subGroup, "weapons" );

	// Run through the weapons
	if ( pairs )
	{
		TGPValue list;
		
		// Run through the list
		list = trap_GPV_GetList ( pairs );
		while ( list )
		{
			gitem_t*	item;
			int			i;

			trap_GPV_GetName ( list, temp );

			// Lookup the weapon number
			for( i = WP_NONE + 1, item=NULL; i < WP_NUM_WEAPONS; i++ )
			{
				if ( Q_stricmp(bg_weaponNames[i], temp ) == 0)
				{					
					// translate the weapon index into an item index.
					item = BG_FindWeaponItem ( (weapon_t) i );					
					break;
				}
			}			

			// If the weapon translated into an item ok then drop it
			// in its appropriate slot.
			if ( item )
			{
				int index;
	
				// Make sure outfitting groups that have weapons that are not available
				// do not show up
				if ( !BG_IsWeaponAvailableForOutfitting ( item->giTag, 2 ) )
				{
					trap_GP_Delete(&file);
					return qfalse;
				}

				for ( index=0; bg_outfittingGroups[item->outfittingGroup][index] != -1; index ++ )
				{
					if ( bg_outfittingGroups[item->outfittingGroup][index] == (item - &bg_itemlist[0]) )
					{
						outfitting->items[item->outfittingGroup] = index;
						break;
					}
				}
			}

			list = trap_GPV_GetNext ( list );
		}
	}

	trap_GP_Delete(&file);

	return qtrue;
}

/*
========================
BG_ParseOutfittingTemplates

Parses the available outfitting templates
========================
*/
int BG_ParseOutfittingTemplates ( qboolean force )
{
	int		i;
	int		numOutfittingFiles;
	int		filelen;
	char	fileName[MAX_QPATH];
	char	outfittingFiles[4096];
	char	*fileptr;

	// Dont reload unless forced
	if ( bg_outfittingCount && !force )
	{
		return bg_outfittingCount;
	}

	// Clear the current list 
	bg_outfittingCount = 1;
	strcpy ( bg_outfittings[0].name, "CUSTOM" );

	// Grab the list of NPC files
	numOutfittingFiles = trap_FS_GetFileList("scripts", ".outfitting", outfittingFiles, 4096 );
	if ( !numOutfittingFiles )
	{
		return 0;
	}

	// Parse each of the NPC files
	fileptr = outfittingFiles;
	for( i = 0; i < numOutfittingFiles; i++, fileptr += filelen+1 )
	{
		// Grab the length so we can skip this file later
		filelen = strlen(fileptr);

		Com_sprintf ( fileName, sizeof(fileName), "scripts/%s", fileptr );

		// Parse the outfitting template
		if ( BG_ParseOutfittingTemplate ( fileName, &bg_outfittings[bg_outfittingCount]	) )
		{
			bg_outfittingCount++;
		}
	}

	return bg_outfittingCount;
}


