// Copyright (C) 2001-2002 Raven Software
//
// bg_weapons.c - weapon data loading

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

// names as they appear in the SOF2.wpn and inview files
char *bg_weaponNames[WP_NUM_WEAPONS] = 
{
	"No Weapon",					// WP_NONE,
	"Knife",						// WP_KNIFE,
	"M1911A1",						// WP_M1911A1_PISTOL,
	"US SOCOM",						// WP_US_SOCOM_PISTOL,
	"Silver Talon",					// WP_SILVER_TALON,
	"M590",							// WP_M590_SHOTGUN,
	"Micro Uzi",					// WP_MICRO_UZI_SUBMACHINEGUN,
	"M3A1",							// WP_M3A1_SUBMACHINEGUN,
	"MP5",							// WP_MP5
	"USAS-12",						// WP_USAS_12_SHOTGUN,
	"M4",							// WP_M4_ASSAULT_RIFLE,
	"AK74",							// WP_AK74_ASSAULT_RIFLE,
	"Sig 551",						// WP_SIG551
#ifdef SMKWEAPONS
	"OICW",							// WP_OICW,
#endif
	"MSG90A1",						// WP_MSG90A1_SNIPER_RIFLE,
	"M60",							// WP_M60_MACHINEGUN,
	"MM1",							// WP_MM1_GRENADE_LAUNCHER,
	"RPG7",							// WP_RPG7_LAUNCHER,
	"M84",							// WP_M84_GRENADE,
	"SMOHG92",						// WP_SMOHG92_GRENADE,
	"ANM14", 						// WP_ANM14_GRENADE,
	"M15",							// WP_M15_GRENADE,
};

weaponData_t weaponData[WP_NUM_WEAPONS];

char *ammoNames[AMMO_MAX] =
{							
	"Knife",		//	AMMO_KNIFE,
	"0.45 ACP",		//	AMMO_045,
	"5.56mm", 		//	AMMO_556,
	"9mm",			//	AMMO_9  ,
	"12 gauge",		//	AMMO_12 ,
	"7.62mm",		//	AMMO_762,
	"40mm grenade",	//	AMMO_40,
	"RPG7",			//  AMMO_RPG7
	"M15",			//	AMMO_M15,
	"M84",			//	AMMO_M84,
	"SMOHG92",		//	AMMO_SMOHG92,
	"ANM14", 		//	AMMO_ANM14,
	"7.62mm belt",	//  AMMO_762_BELT,
	"9mm|mp5",		//  AMMO_9_MP5
};

ammoData_t ammoData[AMMO_MAX];

static const char* BG_GetRealAmmoName ( ammo_t ammoNum )
{
	static char name[64] = "";
	char* or;

	or = strchr ( ammoNames[ammoNum], '|' );
	if ( or )
	{
		Q_strncpyz ( name, ammoNames[ammoNum], or - ammoNames[ammoNum] + 1);
	}
	else
	{
		strcpy ( name, ammoNames[ammoNum] );
	}

	return name;
}

static qboolean BG_ParseAmmoStats(ammo_t ammoNum, void *group)
{
	char		tmpStr[256];
	ammoData_t *ammo;		// ammo

	ammo = &ammoData[ammoNum];
	memset(ammo, 0, sizeof(ammoData_t));

	ammo->name = (char*)trap_VM_LocalStringAlloc ( BG_GetRealAmmoName ( ammoNum ) );
	Q_strlwr ( ammo->name );

	// Get the scale of the gore for this bullet
	trap_GPG_FindPairValue(group, "mp_goreScale||goreScale", "1", tmpStr );
	ammo->goreScale = atof ( tmpStr );

	// Max ammo will be filled in by the weapon parsing

	return qtrue;
}

qboolean BG_InitAmmoStats(void)
{
	void		*GP2, *topGroup;
	int			i;

	ammoData[AMMO_NONE].goreScale = 0.0f;
	ammoData[AMMO_NONE].name = "none";

	GP2 = trap_GP_ParseFile("ext_data/sof2.ammo", qtrue, qfalse);
	if (!GP2)
	{
		return qfalse;
	}

	topGroup = trap_GP_GetBaseParseGroup(GP2);
	for ( i = 0; i < AMMO_MAX; i ++ )
	{
		void*	topSubs;
		const char*	realName;

		realName = BG_GetRealAmmoName ( i );

		topSubs = trap_GPG_GetSubGroups(topGroup);
		while(topSubs)
		{
			char name[256];
			trap_GPG_GetName(topSubs, name);
			if (Q_stricmp(name, "ammo") != 0)
			{
				continue;
			}
	
			trap_GPG_FindPairValue(topSubs, "name", "", name);
			if ( !Q_stricmp ( name, realName ) )
			{
				BG_ParseAmmoStats(i, topSubs );
				break;
			}

			topSubs = trap_GPG_GetNext(topSubs);
		}

		if ( !topSubs )
		{
			Com_Printf("BG_InitAmmoStats: Unknown ammo: %s\n", BG_GetRealAmmoName ( i ) );
		}		
	}

	trap_GP_Delete(&GP2);

	return qtrue;
}

static qboolean BG_ParseAttackStats ( int weaponNum, attackData_t* attack, void *attacksub, qboolean pickupsDisabled )
{
	void*	sub;
	char	tmpStr[256];
	int		i;

	// No group is success.  This is to allow NULL to be passed 
	if ( NULL == attacksub )
	{
		return qtrue;
	}

	// Zoom information
	trap_GPG_FindPairValue(attacksub, "action", "", tmpStr);
	if ( !Q_stricmp ( tmpStr, "toggleZoom" ) )
	{
		weaponData_t *weapon;
		void		 *value;
		int			 zoomlvl;

		weapon = &weaponData[weaponNum];

		sub = trap_GPG_FindSubGroup(attacksub, "zoomFactors");
		if ( sub )
		{
			value = trap_GPG_GetPairs(sub);
			zoomlvl = 0;
			while(value)
			{
				trap_GPV_GetName ( value, weapon->zoom[zoomlvl].name );
				trap_GPV_GetTopValue(value, tmpStr );

				weapon->zoom[zoomlvl].fov = atoi ( tmpStr );					

				value = trap_GPV_GetNext ( value );
				zoomlvl ++;
			}

//			return qtrue;
		}
	}

	// Assign a melee attribute if there is one
	trap_GPG_FindPairValue(attacksub, "mp_melee||melee", "none", tmpStr );
	if ( Q_stricmp ( tmpStr, "none" ) )
	{
		Q_strlwr ( tmpStr );
		attack->melee = trap_VM_LocalStringAlloc ( tmpStr );
	}

	trap_GPG_FindPairValue(attacksub, "name", "NONE", attack->name);
	trap_GPG_FindPairValue(attacksub, "hudIcon", "NONE", attack->icon);	

	if ( pickupsDisabled )
	{
		trap_GPG_FindPairValue(attacksub, "mp_ammoType_outfitting", "", tmpStr);
		if ( !tmpStr[0] )
		{
			trap_GPG_FindPairValue(attacksub, "mp_ammoType||ammoType", "none", tmpStr);
		}
	}		
	else
	{
		trap_GPG_FindPairValue(attacksub, "mp_ammoType||ammoType", "none", tmpStr);
	}

	attack->ammoIndex = AMMO_NONE;
	for (i = 0; i < AMMO_MAX; i++)
	{
		if (0 == Q_stricmp(tmpStr, ammoNames[i]))
		{
			attack->ammoIndex = i;
			break;
		}
	}

#ifdef _DEBUG
	if (AMMO_MAX == i)
	{
		Com_Printf("BG_ParseWeaponStats: Unknown ammo: %s\n", tmpStr);
	}
#endif

	// Parse the weapon animations
	trap_GPG_FindPairValue( attacksub, "mp_animFire", "TORSO_ATTACK_PISTOL", tmpStr );
	attack->animFire = GetIDForString ( bg_animTable, tmpStr );
	trap_GPG_FindPairValue( attacksub, "mp_animFireZoomed", "", tmpStr );
	attack->animFireZoomed = GetIDForString ( bg_animTable, tmpStr );

	trap_GPG_FindPairValue(attacksub, "mp_range||range", "8192", tmpStr);
	attack->rV.range = atoi(tmpStr);
	trap_GPG_FindPairValue(attacksub, "mp_radius||radius", "0", tmpStr);
	attack->splashRadius = atoi(tmpStr);
	trap_GPG_FindPairValue(attacksub, "mp_fireDelay||fireDelay", "0", tmpStr);
	attack->fireDelay = atoi(tmpStr);
	trap_GPG_FindPairValue(attacksub, "mp_clipSize||clipSize", "0", tmpStr);
	attack->clipSize = atoi(tmpStr);
	trap_GPG_FindPairValue(attacksub, "mp_fireAmount||fireAmount", "1", tmpStr);
	attack->fireAmount = atoi(tmpStr);
	trap_GPG_FindPairValue(attacksub, "mp_fireFromClip||fireFromClip", "1", tmpStr);
	attack->fireFromClip = atoi(tmpStr);
	trap_GPG_FindPairValue(attacksub, "mp_damage||damage", "0", tmpStr);
	attack->damage = atoi(tmpStr);
	trap_GPG_FindPairValue(attacksub, "mp_inaccuracy||inaccuracy", "0", tmpStr);
	attack->inaccuracy = (int)(atof(tmpStr)*1000.0f);
	trap_GPG_FindPairValue(attacksub, "mp_zoominaccuracy", "0", tmpStr);
	attack->zoomInaccuracy = (int)(atof(tmpStr)*1000.0f);
	trap_GPG_FindPairValue(attacksub, "mp_maxInaccuracy||maxInaccuracy", "0", tmpStr);
	attack->maxInaccuracy = (int)(atof(tmpStr)*1000.0f);
	trap_GPG_FindPairValue(attacksub, "mp_gore||gore", "YES", tmpStr);
	attack->gore = (Q_stricmp ( tmpStr, "YES" )?qfalse:qtrue);

	trap_GPG_FindPairValue(attacksub,"mp_extraClips", "0", tmpStr );
	attack->extraClips = atoi ( tmpStr );

	// max ammo is the combination of all guns that share the ammo
	ammoData[attack->ammoIndex].max += attack->clipSize * attack->extraClips;

	trap_GPG_FindPairValue(attacksub,"mp_kickAngles||kickAngles", "0 0 0 0 0 0", tmpStr);
	sscanf( tmpStr, "%f %f %f %f %f %f", 
			&attack->minKickAngles[0], 
			&attack->maxKickAngles[0],
			&attack->minKickAngles[1], 
			&attack->maxKickAngles[1],
			&attack->minKickAngles[2], 
			&attack->maxKickAngles[2]  );
	
	if (0 == attack->inaccuracy)
	{
		trap_GPG_FindPairValue(attacksub, "mp_spread||spread", "0", tmpStr);
		attack->inaccuracy = atof(tmpStr);
	}
	trap_GPG_FindPairValue(attacksub, "mp_pellets||pellets", "1", tmpStr);
	attack->pellets = atof(tmpStr);
	attack->mod = (meansOfDeath_t)weaponNum; 

	trap_GPG_FindPairValue(attacksub, "mp_lockFlashToBarrel||lockFlashToBarrel", "true", tmpStr);
	if (0 == Q_stricmp(tmpStr, "false"))
	{
		attack->weaponFlags |= UNLOCK_MUZZLEFLASH;
	}
	// load effects, sounds
	trap_GPG_FindPairValue(attacksub, "muzzleFlash", "", attack->muzzleEffect);
	trap_GPG_FindPairValue(attacksub, "3rdPersonMuzzleFlash", "", attack->muzzleEffectInWorld);
	trap_GPG_FindPairValue(attacksub, "EjectBone", "", attack->ejectBone);
	trap_GPG_FindPairValue(attacksub, "ShellCasingEject", "", attack->shellEject);
	trap_GPG_FindPairValue(attacksub, "TracerEffect", "", attack->tracerEffect);

	// Some alt attacks have special bones they need their muzzle flashes attached to
	trap_GPG_FindPairValue ( attacksub, "mp_muzzleFlashBone", "", attack->muzzleEffectBone );

	sub = trap_GPG_FindSubGroup(attacksub, "fireModes");
	if (sub)
	{		
		int i;

		for ( i = 0; i < 5; i ++ )
		{
			trap_GPG_FindPairValue ( sub, va("mp_mode%i||mode%i", i, i ), "", tmpStr );
			if ( !tmpStr[0] )
			{
				continue;
			}

			if (0 == Q_stricmp("single", tmpStr))
				attack->weaponFlags |= (1<<WP_FIREMODE_SINGLE);
			else if (0 == Q_stricmp("auto", tmpStr))
				attack->weaponFlags |= (1<<WP_FIREMODE_AUTO);
			else if (0 == Q_stricmp("burst", tmpStr))
				attack->weaponFlags |= (1<<WP_FIREMODE_BURST);
#ifdef SMKWEAPONS												//
			else if (0 == Q_stricmp("20mm", tmpStr))			//added 1/12/05 by pimpy
				attack->weaponFlags |= (1<<WP_FIREMODE_20MM);	//
#endif
			else
				attack->weaponFlags |= (1<<WP_FIREMODE_SINGLE);
		}
	}
	else
	{
		attack->weaponFlags |= (1<<WP_FIREMODE_SINGLE);
	}

	sub = trap_GPG_FindSubGroup(attacksub, "projectile");
	if (sub)
	{
		attack->weaponFlags |= PROJECTILE_FIRE;

		trap_GPG_FindPairValue(sub, "gravity", "1", tmpStr);
		if (0 < atof(tmpStr))
			attack->weaponFlags |= PROJECTILE_GRAVITY;

		trap_GPG_FindPairValue(sub, "detonation", "0", tmpStr);
		if (0 == Q_stricmp(tmpStr,"timer"))
			attack->weaponFlags |= PROJECTILE_TIMED;

		trap_GPG_FindPairValue(sub, "mp_bounce||bounce", "0", tmpStr );
		attack->bounceScale = atof ( tmpStr );

		switch ( weaponNum )
		{
			case WP_ANM14_GRENADE:
				// incediary grenade
				attack->weaponFlags |= PROJECTILE_DAMAGE_AREA;
				break;

			case WP_KNIFE:
				if ( attack->weaponFlags & PROJECTILE_GRAVITY )
				{
					attack->weaponFlags &= ~PROJECTILE_GRAVITY;
					attack->weaponFlags |= PROJECTILE_LIGHTGRAVITY;
				}
				break;
		}
		trap_GPG_FindPairValue(sub, "mp_speed||speed", "0", tmpStr);
		attack->rV.velocity = atoi(tmpStr);
		trap_GPG_FindPairValue(sub, "mp_timer||timer", "10", tmpStr);
		attack->projectileLifetime = (int)(atof(tmpStr) * 1000);

		// 'trail' effect
		trap_GPG_FindPairValue(sub, "mp_effect||effect", "", attack->tracerEffect);
		trap_GPG_FindPairValue(sub, "model", "", attack->missileG2Model);
		trap_GPG_FindPairValue(sub, "mp_explosionEffect||explosionEffect", "", attack->explosionEffect);
		trap_GPG_FindPairValue(sub, "mp_explosionSound||explosionSound", "", attack->explosionSound);
	}

	return qtrue;
}

static qboolean BG_ParseWeaponStats(weapon_t weaponNum, void *group, qboolean pickupsDisabled )
{
	char		 tmpStr[256];
	weaponData_t *weapon;

	weapon = &weaponData[weaponNum];
	memset(weapon, 0, sizeof(weaponData_t));

	weapon->classname = bg_weaponNames[weaponNum];
	trap_GPG_FindPairValue(group, "category", "0", tmpStr);
	weapon->category = atoi(tmpStr);

	trap_GPG_FindPairValue(group, "safe", "false", tmpStr);
	weapon->safe = !Q_stricmp(tmpStr, "true");

	trap_GPG_FindPairValue(group, "model", "", weapon->worldModel);

	trap_GPG_FindPairValue(group, "menuImage", "", weapon->menuImage);

	// Grab the animations
	trap_GPG_FindPairValue( group, "mp_animRaise", "TORSO_RAISE", tmpStr );
	weapon->animRaise = GetIDForString ( bg_animTable, tmpStr );
	trap_GPG_FindPairValue( group, "mp_animDrop", "TORSO_DROP", tmpStr );
	weapon->animDrop = GetIDForString ( bg_animTable, tmpStr );
	trap_GPG_FindPairValue( group, "mp_animIdle", "TORSO_IDLE_PISTOL", tmpStr );
	weapon->animIdle = GetIDForString ( bg_animTable, tmpStr );
	trap_GPG_FindPairValue( group, "mp_animIdleZoomed", "", tmpStr );
	weapon->animIdleZoomed = GetIDForString ( bg_animTable, tmpStr );
	trap_GPG_FindPairValue( group, "mp_animReload", "", tmpStr );
	weapon->animReload = GetIDForString ( bg_animTable, tmpStr );
	trap_GPG_FindPairValue( group, "mp_animReloadStart", "", tmpStr );
	weapon->animReloadStart = GetIDForString ( bg_animTable, tmpStr );
	trap_GPG_FindPairValue( group, "mp_animReloadEnd", "", tmpStr );
	weapon->animReloadEnd = GetIDForString ( bg_animTable, tmpStr );

	// primary attack
	BG_ParseAttackStats ( weaponNum, &weapon->attack[ATTACK_NORMAL], trap_GPG_FindSubGroup(group, "attack"), pickupsDisabled );

	// alternate attack
	BG_ParseAttackStats ( weaponNum, &weapon->attack[ATTACK_ALTERNATE], trap_GPG_FindSubGroup(group, "altattack"), pickupsDisabled );

	return qtrue;
}

qboolean BG_InitWeaponStats( qboolean pickupsDisabled )
{
	void		*GP2, *topGroup, *topSubs;
	char		name[256];
	int			i;

	GP2 = trap_GP_ParseFile(g_weaponMod.string, qtrue, qfalse);
	if (!GP2)
	{
		GP2 = trap_GP_ParseFile("ext_data/sof2.wpn", qtrue, qfalse);
		if (!GP2)
		{
			return qfalse;
		}
	}

	topGroup = trap_GP_GetBaseParseGroup(GP2);
	topSubs = trap_GPG_GetSubGroups(topGroup);
	while(topSubs)
	{
		trap_GPG_GetName(topSubs, name);
		if (Q_stricmp(name, "weapon") == 0)
		{
			trap_GPG_FindPairValue(topSubs, "name", "", name);
			for(i=0;i<WP_NUM_WEAPONS;i++)
			{
				if (Q_stricmp(bg_weaponNames[i], name) == 0)
				{
					BG_ParseWeaponStats(i, topSubs, pickupsDisabled );
					break;
				}
			}

#ifdef _DEBUG
			if (i == WP_NUM_WEAPONS)
			{
				Com_Printf("BG_InitWeaponStats: Unknown weapon: %s\n", name);
			}
#endif
		}
		topSubs = trap_GPG_GetNext(topSubs);
	}

	trap_GP_Delete(&GP2);

	return qtrue;
}

////////////////////////////////////////////////////////////////////////////////////

TWeaponParseInfo	weaponParseInfo[WP_NUM_WEAPONS];
char				weaponLeftHand[MAX_QPATH];
char				weaponRightHand[MAX_QPATH];

static char *BG_BuildSideSurfaceList(void *group, char *pattern, char *sideSurfaces[])
{
	void		*value;
	char		*output, *data;
	char		fieldName[256], fieldValue[256];
	int			length;
	int			i;
	
	output = trap_VM_LocalAlloc(0);
	length = strlen(pattern);
	i=0;

	value = trap_GPG_GetPairs(group);
	while((value)&&(i<MAX_SIDE_SURFACES))
	{
		trap_GPV_GetName(value, fieldName);
		if (Q_stricmpn(fieldName, pattern, length) == 0)
		{
			trap_GPV_GetTopValue(value, fieldValue);
			data = trap_VM_LocalAllocUnaligned(strlen(fieldValue)+1);
			strcpy(data, fieldValue);
			sideSurfaces[i]=data;
			i++;
		}
		value = trap_GPV_GetNext(value);
	}

	data = trap_VM_LocalAllocUnaligned(1);
	*data = 0;

	return output;
}

static char *BG_BuildList(void *group, char *pattern)
{
	void		*value;
	char		*output, *data;
	char		fieldName[256], fieldValue[256];
	int			length;

	output = trap_VM_LocalAlloc(0);
	length = strlen(pattern);

	value = trap_GPG_GetPairs(group);
	while(value)
	{
		trap_GPV_GetName(value, fieldName);
		if (Q_stricmpn(fieldName, pattern, length) == 0)
		{
			trap_GPV_GetTopValue(value, fieldValue);
			data = trap_VM_LocalAllocUnaligned(strlen(fieldValue)+1);
			strcpy(data, fieldValue);
		}
		value = trap_GPV_GetNext(value);
	}

	data = trap_VM_LocalAllocUnaligned(1);
	*data = 0;

	return output;
}

#define		MAX_WEAPON_FILES	10

static void *weaponFrames[MAX_WEAPON_FILES];
static void	*frameGroup[MAX_WEAPON_FILES];
static int	numWeaponFiles = 0;
static int	numInitialFiles = 0;

static	qboolean BG_OpenWeaponFrames(const char *name)
{
	weaponFrames[numWeaponFiles] = trap_GP_ParseFile((char *)name, qtrue, qfalse);

	if (!weaponFrames)
	{
		return qfalse;
	}

	frameGroup[numWeaponFiles] = trap_GP_GetBaseParseGroup(weaponFrames[numWeaponFiles]);
	numWeaponFiles++;

	return qtrue;
}

static TNoteTrack *BG_FindNoteTracks(void *group)
{
	void		*sub;
	char		name[256];
	TNoteTrack	*head, *last, *current, *insert;

	head = last = insert = 0;

	sub = trap_GPG_GetSubGroups(group);
	while(sub)
	{
		trap_GPG_GetName(sub, name);
		if (Q_stricmp(name, "notetrack") == 0)
		{
			current = (TNoteTrack *)trap_VM_LocalAlloc(sizeof(*current));
			memset(current, 0, sizeof(*current));

			// last character is automatically 0 cuz of the memset
			trap_GPG_FindPairValue(sub, "note", "", current->mNote);
			trap_GPG_FindPairValue(sub, "frame", "-1", name);
			current->mFrame = atoi(name);

			last=insert=head;
			while(insert)
			{
				if(current->mFrame<insert->mFrame)
				{
					break;
				}
				last=insert;
				insert=insert->mNext;
			}
			if(insert==head)
			{
				head=current;
			}
			else
			{
				last->mNext=current;
			}
			current->mNext=insert;
		}

		sub = trap_GPG_GetNext(sub);
	}

	return head;
}

static void BG_FindWeaponFrames(TAnimInfoWeapon *animInfo, int choice)
{
	void	*group;
	int		i;

	if (!numWeaponFiles || !animInfo->mAnim[choice])
	{
		animInfo->mNumFrames[choice] = -1;
		return;
	}

	for(i=0;i<numWeaponFiles;i++)
	{
		char  temp[256];

		group = trap_GPG_GetSubGroups ( frameGroup[i] );
		while ( group )
		{
			char* name;

			// Get the name and break it down to just the filename without
			// and extension			
			trap_GPG_GetName ( group, temp );
			name = COM_SkipPath ( temp );
			COM_StripExtension ( name, temp );

			if ( Q_stricmp ( temp, animInfo->mAnim[choice] ) == 0 )
			{
				break;
			}

			group = trap_GPG_GetNext ( group );
		}

		if (group)
		{
			trap_GPG_FindPairValue(group, "startframe", "0", temp);
			animInfo->mStartFrame[choice] = atoi(temp);
			trap_GPG_FindPairValue(group, "duration", "0", temp);
			animInfo->mNumFrames[choice] = atoi(temp);
			trap_GPG_FindPairValue(group, "fps", "0", temp);
			animInfo->mFPS[choice] = atoi(temp);
			animInfo->mNoteTracks[choice] = BG_FindNoteTracks(group);
			return;
		}
	}

	animInfo->mNumFrames[choice] = -1;
}

static void BG_CloseWeaponFrames(int upTo)
{
	int		i;

	for(i=upTo; i < numWeaponFiles; i++)
	{
		if (weaponFrames[i])
		{
			trap_GP_Delete(&weaponFrames[i]);
		}
	}

	numWeaponFiles = upTo;
}


static qboolean BG_ParseAnimGroup(weapon_t weapon, void *animGroup)
{
	void			*sub;
	char			name[256];
	TAnimWeapon		*anim;
	TAnimInfoWeapon	*info;
	char			value[256];
	int				i;
	char			temp[256];

	anim = (TAnimWeapon *)trap_VM_LocalAlloc(sizeof(*anim));
	memset(anim, 0, sizeof(*anim));

	anim->mNext = weaponParseInfo[weapon].mAnimList;
	weaponParseInfo[weapon].mAnimList = anim;

	trap_GPG_FindPairValue(animGroup, "name", "", anim->mName);
	trap_GPG_FindPairValue(animGroup, "mp_muzzle||muzzle", "", anim->mMuzzle);

	sub = trap_GPG_GetSubGroups(animGroup);
	while(sub)
	{
		trap_GPG_GetName(sub, name);
		if (Q_stricmp(name, "info") == 0)
		{
			info = (TAnimInfoWeapon *)trap_VM_LocalAlloc(sizeof(*info));
			memset(info, 0, sizeof(*info));
			
			info->mNext = anim->mInfos;
			anim->mInfos = info;

			info->mNumChoices = 0;
			trap_GPG_FindPairValue(sub, "name", "", info->mName);
			trap_GPG_FindPairValue(sub, "type", "", info->mType);

			// Cache for later
			if ( !Q_stricmp ( info->mType, "weaponmodel" ) )
			{
				anim->mWeaponModelInfo = info;
			}

			// We first look for a multiplayer specific speed. If we don't
			// find a valid speed, use the single player speed instead.
			trap_GPG_FindPairValue(sub, "mp_speed||speed", "0", temp);
			info->mSpeed = atof(temp);
			if(!info->mSpeed)
			{
				trap_GPG_FindPairValue(sub, "mp_speed||speed", "1", temp);
				info->mSpeed = atof(temp);
			}
			trap_GPG_FindPairValue(sub, "lodbias", "0", temp);
			info->mLODBias = atoi(temp);

			for(i=0;i<=MAX_WEAPON_ANIM_CHOICES;i++)
			{
				if (i == 0)
				{
					strcpy(temp, "anim||animNoLerp");
				}
				else
				{
					Com_sprintf(temp, sizeof(temp), "anim%d||animNoLerp%d", i, i);
				}
				trap_GPG_FindPairValue(sub, temp, "", value);
				if (value[0] && info->mNumChoices < MAX_WEAPON_ANIM_CHOICES)
				{
					info->mAnim[info->mNumChoices] = (char *)trap_VM_LocalAlloc(strlen(value)+1);
					strcpy(info->mAnim[info->mNumChoices], value);

					if (i == 0)
					{
						strcpy(temp, "transition");
					}
					else
					{
						Com_sprintf(temp, sizeof(temp), "transition%d", i);
					}
					trap_GPG_FindPairValue(sub, temp, "", value);
					if (value[0])
					{
						info->mTransition[info->mNumChoices] = (char *)trap_VM_LocalAlloc(strlen(value)+1);
						strcpy(info->mTransition[info->mNumChoices], value);
					}

					if (i == 0)
					{
						strcpy(temp, "end");
					}
					else
					{
						Com_sprintf(temp, sizeof(temp), "end%d", i);
					}
					trap_GPG_FindPairValue(sub, temp, "", value);
					if (value[0])
					{
						info->mEnd[info->mNumChoices] = (char *)trap_VM_LocalAlloc(strlen(value)+1);
						strcpy(info->mEnd[info->mNumChoices], value);
					}

					info->mNumChoices++;
				}
			}
		}

		sub = trap_GPG_GetNext(sub);
	}

	return qtrue;
}

static TBoltonWeapon *BG_ParseBolton(void *boltonGroup)
{
	TBoltonWeapon	*bolton;
	void			*sub;
	char			temp[256];

	bolton = (TBoltonWeapon *)trap_VM_LocalAlloc(sizeof(*bolton));
	memset(bolton, 0, sizeof(*bolton));

	trap_GPG_FindPairValue(boltonGroup, "name", "", bolton->mName);
	trap_GPG_FindPairValue(boltonGroup, "model", "", bolton->mModel);
	trap_GPG_FindPairValue(boltonGroup, "parent", "", bolton->mParent);
	trap_GPG_FindPairValue(boltonGroup, "bolttobone", "", bolton->mBoltToBone);

	trap_GPG_FindPairValue(boltonGroup, "frames", "", temp);
	BG_OpenWeaponFrames(temp);

	sub = trap_GPG_FindSubGroup(boltonGroup, "rightside");
	if (sub)
	{
 		BG_BuildSideSurfaceList(sub, "surface", bolton->mRightSide);
	}

	sub = trap_GPG_FindSubGroup(boltonGroup, "joint");
	if (sub)
	{
		trap_GPG_FindPairValue(sub, "bone", "", bolton->mJointBone);
		trap_GPG_FindPairValue(sub, "parentBone", "", bolton->mJointParentBone);
		trap_GPG_FindPairValue(sub, "fwd", "", bolton->mJointForward);
		trap_GPG_FindPairValue(sub, "right", "", bolton->mJointRight);
		trap_GPG_FindPairValue(sub, "up", "", bolton->mJointUp);
	}

	return bolton;
}

static qboolean BG_ParseWeaponGroup(TWeaponModel *weapon, void *weaponGroup)
{
	void			*sub, *hand;
	char			name[256];
	TOptionalWeapon	*option;
	char			temp[256];

	trap_GPG_FindPairValue(weaponGroup, "name", "", weapon->mName);
	trap_GPG_FindPairValue(weaponGroup, "model", "", weapon->mModel);

	trap_GPG_FindPairValue(weaponGroup, "frames", "", temp);
	BG_OpenWeaponFrames(temp);

	sub = trap_GPG_GetSubGroups(weaponGroup);
	while(sub)
	{
		trap_GPG_GetName(sub, name);
		if (Q_stricmp(name, "buffer") == 0)
		{
			trap_GPG_FindPairValue(sub, "name", "", weapon->mBufferName);
			trap_GPG_FindPairValue(sub, "model", "", weapon->mBufferModel);
			trap_GPG_FindPairValue(sub, "bolttobone", "", weapon->mBufferBoltToBone);
			trap_GPG_FindPairValue(sub, "mp_muzzle||muzzle", "", weapon->mBufferMuzzle);
			trap_GPG_FindPairValue(sub, "mp_altmuzzle", "", weapon->mBufferAltMuzzle);
		}
		else if (Q_stricmp(name, "hands") == 0)
		{
			hand = trap_GPG_FindSubGroup(sub, "left");
			if (hand)
			{
				trap_GPG_FindPairValue(hand, "bolttobone", "", weapon->mLeftHandsBoltToBone);
			}
			hand = trap_GPG_FindSubGroup(sub, "right");
			if (hand)
			{
				trap_GPG_FindPairValue(hand, "bolttobone", "", weapon->mRightHandsBoltToBone);
			}
		}
		else if (Q_stricmp(name, "bolton") == 0)
		{
			weapon->mBolton = BG_ParseBolton(sub);
		}
		else if (Q_stricmp(name, "rightside") == 0)
		{
			BG_BuildSideSurfaceList(sub, "surface", weapon->mRightSideSurfaces);
		}
		else if (Q_stricmp(name, "leftside") == 0)
		{
			BG_BuildSideSurfaceList(sub, "surface", weapon->mLeftSideSurfaces);
		}
		else if (Q_stricmp(name, "front") == 0)
		{
			BG_BuildSideSurfaceList(sub, "surface", weapon->mFrontSurfaces);
		}
		else if (Q_stricmp(name, "optionalpart") == 0)
		{
			option = (TOptionalWeapon *)trap_VM_LocalAlloc(sizeof(*option));
			memset(option, 0, sizeof(*option));
			trap_GPG_FindPairValue(sub, "name", "", option->mName);
			trap_GPG_FindPairValue(sub, "muzzle", "", option->mMuzzle);
			BG_BuildSideSurfaceList(sub, "surface", option->mSurfaces);
			option->mNext=weapon->mOptionalList;
			weapon->mOptionalList=option;
/*
			if(weapon->mOptionalList)
			{
				option->mNext=weapon->mOptionalList;
				weapon->mOptionalList=option;
			}
			else
			{
				weapon->mOptionalList=option;
			}		*/
		}
		
		sub = trap_GPG_GetNext(sub);
	}

	return qtrue;
}

static qboolean BG_ParseWeapon(weapon_t weapon, void *group)
{
	void			*sub, *soundName, *surfaceCallbackName;
	void			*soundValue, *surfaceCallbackValue;
	char			onOffVal[256];
	char			name[256];
	int				i, j;
	TAnimWeapon		*anims;
	TAnimInfoWeapon	*infos;
	char			temp[256];

	memset(&weaponParseInfo[weapon], 0, sizeof(TWeaponParseInfo));
	weaponParseInfo[weapon].mName = bg_weaponNames[weapon];
	trap_GPG_FindPairValue(group, "foreshorten", "0.0", temp);
	weaponParseInfo[weapon].mForeshorten = atof(temp);

	sub = trap_GPG_GetSubGroups(group);
	while(sub)
	{
		trap_GPG_GetName(sub, name);

		if (Q_stricmp(name, "viewoffset") == 0)
		{
			trap_GPG_FindPairValue(sub, "forward", "0.0", temp);
			weaponParseInfo[weapon].mViewOffset[0] = atof(temp);
			trap_GPG_FindPairValue(sub, "right", "0.0", temp);
			weaponParseInfo[weapon].mViewOffset[1] = atof(temp);
			trap_GPG_FindPairValue(sub, "up", "0.0", temp);
			weaponParseInfo[weapon].mViewOffset[2] = atof(temp);
		}
		else if (Q_stricmp(name, "sounds") == 0)
		{
			soundName = trap_GPG_GetSubGroups(sub);
			for(i=0;soundName && (i < MAX_WEAPON_SOUNDS);i++)
			{
				trap_GPG_GetName(soundName, weaponParseInfo[weapon].mSoundNames[i]);
				soundValue = trap_GPG_GetPairs(soundName);
				for(j=0;soundValue && (j<MAX_WEAPON_SOUND_SLOTS);j++)
				{
					trap_GPV_GetTopValue(soundValue, weaponParseInfo[weapon].mSounds[i][j]);
					soundValue = trap_GPV_GetNext(soundValue);
				}

				soundName = trap_GPG_GetNext(soundName);
			}
		}
		else if (Q_stricmp(name, "surfaces") == 0)
		{
			surfaceCallbackName = trap_GPG_GetSubGroups(sub);
			for(i=0;surfaceCallbackName && (i < MAX_SURFACE_CALLBACKS);i++)
			{
				trap_GPG_GetName(surfaceCallbackName, weaponParseInfo[weapon].mSurfaceCallbacks[i].mName);
				surfaceCallbackValue = trap_GPG_GetPairs(surfaceCallbackName);
				for(j=0;surfaceCallbackValue && (j<MAX_CALLBACK_SURFACES);j++)
				{
					trap_GPG_GetName(surfaceCallbackValue, weaponParseInfo[weapon].mSurfaceCallbacks[i].mOnOffSurfaces[j].mName);
					trap_GPV_GetTopValue(surfaceCallbackValue, onOffVal);
					assert(onOffVal);
					weaponParseInfo[weapon].mSurfaceCallbacks[i].mOnOffSurfaces[j].mStatus=(!Q_stricmp(onOffVal,"on"))?1:0;
					surfaceCallbackValue=trap_GPV_GetNext(surfaceCallbackValue);
				}
				surfaceCallbackName=trap_GPG_GetNext(surfaceCallbackName);
			}

		}
		else if (Q_stricmp(name, "weaponmodel") == 0)
		{
			BG_ParseWeaponGroup(&weaponParseInfo[weapon].mWeaponModel, sub);
		}
		else if (Q_stricmp(name, "anim") == 0)
		{
			BG_ParseAnimGroup(weapon, sub);
		}

		sub = trap_GPG_GetNext(sub);
	}

	anims = weaponParseInfo[weapon].mAnimList;
	while(anims)
	{
		infos = anims->mInfos;
		while(infos)
		{
			for(i=0;i<infos->mNumChoices;i++)
			{
				BG_FindWeaponFrames(infos, i);
			}
			infos = infos->mNext;
		}

		anims = anims->mNext;
	}

	BG_CloseWeaponFrames(numInitialFiles);

	return qtrue;
}

qboolean BG_ParseInviewFile( qboolean pickupsDisabled )
{
	void		*GP2, *topGroup, *topSubs, *group;
	char		name[256], temp[256];
	int			i;

	GP2 = trap_GP_ParseFile("inview/sof2.inview", qtrue, qfalse);
	if (!GP2)
	{
		return qfalse;
	}

	weaponLeftHand[0] = 0;
	weaponRightHand[0] = 0;

	topGroup = trap_GP_GetBaseParseGroup(GP2);
	topSubs = trap_GPG_GetSubGroups(topGroup);
	while(topSubs)
	{
		trap_GPG_GetName(topSubs, name);
		if (Q_stricmp(name, "hands") == 0)
		{
			group = trap_GPG_FindSubGroup(topSubs, "left");
			if (group)
			{
				trap_GPG_FindPairValue(group, "model", "", weaponLeftHand);
				trap_GPG_FindPairValue(group, "frames", "", temp);
				if (BG_OpenWeaponFrames(temp))
				{
					numInitialFiles++;
				}
			}
			group = trap_GPG_FindSubGroup(topSubs, "right");
			if (group)
			{
				trap_GPG_FindPairValue(group, "model", "", weaponRightHand);
				trap_GPG_FindPairValue(group, "frames", "", temp);
				if (BG_OpenWeaponFrames(temp))
				{
					numInitialFiles++;
				}
			}
		}
		else if (Q_stricmp(name, "hud") == 0)
		{
		}
		else if (Q_stricmp(name, "weapon") == 0)
		{
			trap_GPG_FindPairValue(topSubs, "name", "", name);
			for(i=0;i<WP_NUM_WEAPONS;i++)
			{
				if (Q_stricmp(bg_weaponNames[i], name) == 0)
				{
					BG_ParseWeapon(i, topSubs);
					break;
				}
			}

#ifdef _DEBUG
			if (i == WP_NUM_WEAPONS)
			{
				Com_Printf("BG_InitWeapons: Unknown weapon: %s\n", name);
			}
#endif
		}

		topSubs = trap_GPG_GetNext(topSubs);
	}

	BG_CloseWeaponFrames(0);
	trap_GP_Delete(&GP2);

	BG_InitAmmoStats();

	return BG_InitWeaponStats( pickupsDisabled );
}

TAnimWeapon *BG_GetInviewAnim(int weaponIdx,const char *animKey,int *animIndex)
{
	TAnimWeapon			*animWeapon;

	(*animIndex)=0;
	animWeapon=weaponParseInfo[weaponIdx].mAnimList;
	while((animWeapon!=0)&&(Q_stricmp(animWeapon->mName,animKey)))
	{
		animWeapon=animWeapon->mNext;
		(*animIndex)++;
	}
	if(!animWeapon)
	{
		return(0);
	}
	return(animWeapon);
}

TAnimWeapon *BG_GetInviewAnimFromIndex(int weaponIdx,int animIndex)
{
	TAnimWeapon		*animWeapon;
	int				i=0;

	animWeapon=weaponParseInfo[weaponIdx].mAnimList;
	while((animWeapon!=0)&&(i!=animIndex))
	{
		animWeapon=animWeapon->mNext;
		i++;
	}
	if(!animWeapon)
	{
		return(0);
	}
	return(animWeapon);
}

TAnimInfoWeapon *BG_GetInviewModelAnim(int weaponIdx,const char *modelKey,const char *animKey)
{
	TAnimWeapon			*animWeapon;
	TAnimInfoWeapon		*animInfoWeapon;
	animWeapon=weaponParseInfo[weaponIdx].mAnimList;
	while((animWeapon!=0)&&(Q_stricmp(animWeapon->mName,animKey)))
	{
		animWeapon=animWeapon->mNext;
	}
	if(!animWeapon)
	{
		return(0);
	}
	animInfoWeapon=animWeapon->mInfos;
	while((animInfoWeapon!=0)&&(Q_stricmp(animInfoWeapon->mType,modelKey)))
	{
		animInfoWeapon=animInfoWeapon->mNext;
	}
	if(!animInfoWeapon)
	{
		return(0);
	}
	return(animInfoWeapon);
}

/*
===============
BG_WeaponHasAlternateAmmo

Returns qtrue if the given weapon has ammo for its alternate attack
===============
*/
qboolean BG_WeaponHasAlternateAmmo ( int weapon )
{
	// No valid ammo index means no alternate ammo
	if ( weaponData[weapon].attack[ATTACK_ALTERNATE].ammoIndex == AMMO_NONE )
	{
		return qfalse;
	}

	// If the alternate attack doesnt deplete ammo then it doesnt use it
	if ( !weaponData[weapon].attack[ATTACK_ALTERNATE].fireAmount )
	{
		return qfalse;
	}

	// If the alternate ammo is the same as the primary ammo then
	// the primary is good enough
	if ( weaponData[weapon].attack[ATTACK_ALTERNATE].ammoIndex ==
	     weaponData[weapon].attack[ATTACK_NORMAL].ammoIndex )
	{
		return qfalse;
	}

	// Yup, alternates have ammo
	return qtrue;
}

/*
===============
BG_FindFireMode

Finds the firemode for the given weapon using the given default
===============
*/
int BG_FindFireMode ( weapon_t weapon, attackType_t attack, int firemode )
{
	int i;

	if ( !weapon )
	{
		return WP_FIREMODE_NONE;
	}

	for ( i=0; i <= WP_FIREMODE_SINGLE; i++ )
	{
		if( firemode >= WP_FIREMODE_MAX )
		{
			firemode = WP_FIREMODE_NONE + 1;
		}

		if( weaponData[weapon].attack[ATTACK_NORMAL].weaponFlags&(1<<firemode))
		{
			break;
		}
		else
		{
			firemode++;
		}
	}

	assert ( firemode < WP_FIREMODE_MAX );
	
	return firemode;
}

/*
===============
BG_CalculateBulletEndpoint

Calculates the end point of a bullet based on the given inaccuracy and range
===============
*/
void BG_CalculateBulletEndpoint ( vec3_t muzzlePoint, vec3_t fireAngs, float inaccuracy, float range, vec3_t end, int *seed )
{
	float	fGaussianX = 0;
	float	fGaussianY = 0;
	vec3_t	dir;
	vec3_t  fwd;
	vec3_t	up;
	vec3_t	right;

	AngleVectors ( fireAngs, fwd, right, up );

	// No inaccuracy so just extend it forward by the range
	if ( inaccuracy <= 0.0f )
	{
		VectorMA (muzzlePoint, range, fwd, end);
		return;
	}

	// Gaussian spread should keep it a bit less random looking
	while ( 1 )
	{ 	
		float fGaussian;
		float f1;
		float f2;

		f1 = (float)((unsigned int)Q_rand ( seed ) % 15000) / 15000.0f;
		f2 = (float)((unsigned int)Q_rand ( seed ) % 15000) / 15000.0f;
		fGaussianX = (f1-0.5f) + (f2-0.5f); 

		f1 = (float)((unsigned int)Q_rand ( seed ) % 15000) / 15000.0f;
		f2 = (float)((unsigned int)Q_rand ( seed ) % 15000) / 15000.0f;
		fGaussianY = (f1-0.5f) + (f2-0.5f); 

		fGaussian = fGaussianX * fGaussianX + fGaussianY * fGaussianY;

		if ( fGaussian < 1 )
		{
			break;
		}
	} 

	VectorMA ( fwd, 0.05f * inaccuracy * fGaussianX, right, dir );
	VectorMA ( dir, 0.05f * inaccuracy * fGaussianY, up, dir );

	VectorMA (muzzlePoint, range, dir, end);
}

/*
===============
BG_GetMaxAmmo

Returns the max ammo a client can hold for the given ammo index
===============
*/
int BG_GetMaxAmmo ( const playerState_t* ps, int ammoIndex )
{
	int			ammo;
	weapon_t	weapon;

	if ( ammoIndex == AMMO_NONE )
	{
		return 0;
	}

	for ( ammo = 0, weapon = WP_KNIFE; weapon < WP_NUM_WEAPONS; weapon ++ )
	{
		if ( !(ps->stats[STAT_WEAPONS] & (1<<weapon)) )
		{
			if ( weapon != ps->stats[STAT_OUTFIT_GRENADE] )
			{
				continue;
			}
		}

		if ( weaponData[weapon].attack[ATTACK_NORMAL].ammoIndex == ammoIndex )
		{
			ammo += (weaponData[weapon].attack[ATTACK_NORMAL].extraClips + 1) * weaponData[weapon].attack[ATTACK_NORMAL].clipSize;
			ammo -= ps->clip[ATTACK_NORMAL][weapon];
		}

		if ( BG_WeaponHasAlternateAmmo ( weapon ) )
		{
			if ( weaponData[weapon].attack[ATTACK_ALTERNATE].ammoIndex == ammoIndex )
			{
				ammo += (weaponData[weapon].attack[ATTACK_ALTERNATE].extraClips + 1) * weaponData[weapon].attack[ATTACK_ALTERNATE].clipSize;
				ammo -= ps->clip[ATTACK_ALTERNATE][weapon];
			}
		}
	}

	return ammo;
}
