//NPC_utils.cpp

// leave this line at the top for all NPC_xxxx.cpp files...
#include "g_headers.h"




#include "b_local.h"
#include "Q3_Interface.h"

extern Vehicle_t *G_IsRidingVehicle( gentity_t *pEnt );

int	teamNumbers[TEAM_NUM_TEAMS];
int	teamStrength[TEAM_NUM_TEAMS];
int	teamCounter[TEAM_NUM_TEAMS];

#define	VALID_ATTACK_CONE	2.0f	//Degrees
void GetAnglesForDirection( const vec3_t p1, const vec3_t p2, vec3_t out );

/*
void CalcEntitySpot ( gentity_t *ent, spot_t spot, vec3_t point ) 

Added: Uses shootAngles if a NPC has them

*/
extern void ViewHeightFix(const gentity_t *const ent);
extern void AddLeanOfs(const gentity_t *const ent, vec3_t point);
extern void SubtractLeanOfs(const gentity_t *const ent, vec3_t point);
void CalcEntitySpot ( const gentity_t *ent, const spot_t spot, vec3_t point ) 
{
	vec3_t	forward, up, right;
	vec3_t	start, end;
	trace_t	tr;

	if ( !ent )
	{
		return;
	}
	ViewHeightFix(ent);
	switch ( spot ) 
	{
	case SPOT_ORIGIN:
		if(VectorCompare(ent->currentOrigin, vec3_origin))
		{//brush
			VectorSubtract(ent->absmax, ent->absmin, point);//size
			VectorMA(ent->absmin, 0.5, point, point);
		}
		else
		{
			VectorCopy ( ent->currentOrigin, point );
		}
		break;

	case SPOT_CHEST:
	case SPOT_HEAD:
		if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) && (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD) )
		{//Actual tag_head eyespot!
			//FIXME: Stasis aliens may have a problem here...
			VectorCopy( ent->client->renderInfo.eyePoint, point );
			if ( ent->client->NPC_class == CLASS_ATST )
			{//adjust up some
				point[2] += 28;//magic number :)
			}
			if ( ent->NPC )
			{//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards
				point[0] = ent->currentOrigin[0];
				point[1] = ent->currentOrigin[1];
			}
			else if ( !ent->s.number )
			{
				SubtractLeanOfs( ent, point );
			}
		}
		else
		{
			VectorCopy ( ent->currentOrigin, point );
			if ( ent->client ) 
			{
				point[2] += ent->client->ps.viewheight;
			}
		}
		if ( spot == SPOT_CHEST && ent->client )
		{
			if ( ent->client->NPC_class != CLASS_ATST )
			{//adjust up some
				point[2] -= ent->maxs[2]*0.2f;
			}
		}
		break;

	case SPOT_HEAD_LEAN:
		if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) && (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD) )
		{//Actual tag_head eyespot!
			//FIXME: Stasis aliens may have a problem here...
			VectorCopy( ent->client->renderInfo.eyePoint, point );
			if ( ent->client->NPC_class == CLASS_ATST )
			{//adjust up some
				point[2] += 28;//magic number :)
			}
			if ( ent->NPC )
			{//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards
				point[0] = ent->currentOrigin[0];
				point[1] = ent->currentOrigin[1];
			}
			else if ( !ent->s.number )
			{
				SubtractLeanOfs( ent, point );
			}
			//NOTE: automatically takes leaning into account!
		}
		else
		{
			VectorCopy ( ent->currentOrigin, point );
			if ( ent->client ) 
			{
				point[2] += ent->client->ps.viewheight;
			}
			//AddLeanOfs ( ent, point );
		}
		break;

	//FIXME: implement...
	//case SPOT_CHEST:
		//Returns point 3/4 from tag_torso to tag_head?
		//break;

	case SPOT_LEGS:
		VectorCopy ( ent->currentOrigin, point );
		point[2] += (ent->mins[2] * 0.5);
		break;

	case SPOT_WEAPON:
		if( ent->NPC && !VectorCompare( ent->NPC->shootAngles, vec3_origin ) && !VectorCompare( ent->NPC->shootAngles, ent->client->ps.viewangles ))
		{
			AngleVectors( ent->NPC->shootAngles, forward, right, up );
		}
		else
		{
			AngleVectors( ent->client->ps.viewangles, forward, right, up );
		}
		CalcMuzzlePoint( (gentity_t*)ent, forward, right, up, point, 0 );
		//NOTE: automatically takes leaning into account!
		break;

	case SPOT_GROUND:
		// if entity is on the ground, just use it's absmin
		if ( ent->s.groundEntityNum != -1 ) 
		{
			VectorCopy( ent->currentOrigin, point );
			point[2] = ent->absmin[2];
			break;
		}

		// if it is reasonably close to the ground, give the point underneath of it
		VectorCopy( ent->currentOrigin, start );
		start[2] = ent->absmin[2];
		VectorCopy( start, end );
		end[2] -= 64;
		gi.trace( &tr, start, ent->mins, ent->maxs, end, ent->s.number, MASK_PLAYERSOLID );
		if ( tr.fraction < 1.0 ) 
		{
			VectorCopy( tr.endpos, point);
			break;
		}

		// otherwise just use the origin
		VectorCopy( ent->currentOrigin, point );
		break;

	default:
		VectorCopy ( ent->currentOrigin, point );
		break;
	}
}


//===================================================================================

/*
qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) 

Added: option to do just pitch or just yaw

Does not include "aim" in it's calculations

FIXME: stop compressing angles into shorts!!!!
*/
extern cvar_t		*g_timescale;
extern bool NPC_IsTrooper( gentity_t *ent );
qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) 
{
#if 1

	float		error;
	float		decay;
	float		targetPitch = 0;
	float		targetYaw = 0;
	float		yawSpeed;
	qboolean	exact = qtrue;

	// if angle changes are locked; just keep the current angles
	// aimTime isn't even set anymore... so this code was never reached, but I need a way to lock NPC's yaw, so instead of making a new SCF_ flag, just use the existing render flag... - dmv
	if ( !NPC->enemy && ( (level.time < NPCInfo->aimTime) || NPC->client->renderInfo.renderFlags & RF_LOCKEDANGLE) ) 
	{
		if(doPitch)
			targetPitch = NPCInfo->lockedDesiredPitch;

		if(doYaw)
			targetYaw = NPCInfo->lockedDesiredYaw;
	}
	else 
	{
		// we're changing the lockedDesired Pitch/Yaw below so it's lost it's original meaning, get rid of the lock flag
		NPC->client->renderInfo.renderFlags &= ~RF_LOCKEDANGLE;

		if(doPitch)
		{
			targetPitch = NPCInfo->desiredPitch;
			NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch;
		}

		if(doYaw)
		{
			targetYaw = NPCInfo->desiredYaw;
			NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw;
		}			
	}

	if ( NPC->s.weapon == WP_EMPLACED_GUN )
	{
		// FIXME: this seems to do nothing, actually...
		yawSpeed = 20;
	}
	else
	{
		if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER 
			&& !NPC->enemy )
		{//just slowly lookin' around
			yawSpeed = 1;
		}
		else
		{
			yawSpeed = NPCInfo->stats.yawSpeed;
		}
	}

	if ( NPC->s.weapon == WP_SABER && NPC->client->ps.forcePowersActive&(1<<FP_SPEED) )
	{
		yawSpeed *= 1.0f/g_timescale->value;
	}

	if (!NPC_IsTrooper(NPC) 
		&& NPC->enemy
		&& !G_IsRidingVehicle( NPC ) 
		&& NPC->client->NPC_class != CLASS_VEHICLE )
	{
		if (NPC->s.weapon==WP_BLASTER_PISTOL || 
			NPC->s.weapon==WP_BLASTER || 
			NPC->s.weapon==WP_BOWCASTER ||
			NPC->s.weapon==WP_REPEATER ||
			NPC->s.weapon==WP_FLECHETTE ||
			NPC->s.weapon==WP_BRYAR_PISTOL ||
			NPC->s.weapon==WP_NOGHRI_STICK)
		{
			yawSpeed *= 10.0f;
		}
	}
	
	if( doYaw )
	{
		// decay yaw error
		error = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw );
		if( fabs(error) > MIN_ANGLE_ERROR )
		{
			if ( error ) 
			{
				exact = qfalse;

				decay = 60.0 + yawSpeed * 3;
				decay *= 50.0f / 1000.0f;//msec

				if ( error < 0.0 ) 
				{
					error += decay;
					if ( error > 0.0 ) 
					{
						error = 0.0;
					}
				}
				else 
				{
					error -= decay;
					if ( error < 0.0 ) 
					{
						error = 0.0;
					}
				}
			}
		}
		
		ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW];
	}

	//FIXME: have a pitchSpeed?
	if( doPitch )
	{
		// decay pitch error
		error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch );
		if ( fabs(error) > MIN_ANGLE_ERROR )
		{
			if ( error ) 
			{
				exact = qfalse;

				decay = 60.0 + yawSpeed * 3;
				decay *= 50.0f / 1000.0f;//msec

				if ( error < 0.0 ) 
				{
					error += decay;
					if ( error > 0.0 ) 
					{
						error = 0.0;
					}
				}
				else 
				{
					error -= decay;
					if ( error < 0.0 ) 
					{
						error = 0.0;
					}
				}
			}
		}

		ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH];
	}

	ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL];

	if ( exact && Q3_TaskIDPending( NPC, TID_ANGLE_FACE ) )
	{
		Q3_TaskIDComplete( NPC, TID_ANGLE_FACE );
	}
	return exact;

#else

	float		error;
	float		decay;
	float		targetPitch = 0;
	float		targetYaw = 0;
	float		yawSpeed;
	//float		runningMod = NPCInfo->currentSpeed/100.0f;
	qboolean	exact = qtrue;
	qboolean	doSound = qfalse;

	// if angle changes are locked; just keep the current angles
	if ( level.time < NPCInfo->aimTime ) 
	{
		if(doPitch)
			targetPitch = NPCInfo->lockedDesiredPitch;
		if(doYaw)
			targetYaw = NPCInfo->lockedDesiredYaw;
	}
	else 
	{
		if(doPitch)
			targetPitch = NPCInfo->desiredPitch;
		if(doYaw)
			targetYaw = NPCInfo->desiredYaw;

//		NPCInfo->aimTime = level.time + 250;
		if(doPitch)
			NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch;
		if(doYaw)
			NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw;
	}

	yawSpeed = NPCInfo->stats.yawSpeed;

	if(doYaw)
	{
		// decay yaw error
		error = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw );
		if( fabs(error) > MIN_ANGLE_ERROR )
		{
			/*
			if(NPC->client->playerTeam == TEAM_BORG&&
				NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE)
			{//HACK - borg turn more jittery
				if ( error ) 
				{
					exact = qfalse;

					decay = 60.0 + yawSpeed * 3;
					decay *= 50.0 / 1000.0;//msec
					//Snap to
					if(fabs(error) > 10)
					{
						if(random() > 0.6)
						{
							doSound = qtrue;
						}
					}

					if ( error < 0.0)//-10.0 ) 
					{
						error += decay;
						if ( error > 0.0 ) 
						{
							error = 0.0;
						}
					}
					else if ( error > 0.0)//10.0 ) 
					{
						error -= decay;
						if ( error < 0.0 ) 
						{
							error = 0.0;
						}
					}
				}
			}
			else*/
			
			if ( error ) 
			{
				exact = qfalse;

				decay = 60.0 + yawSpeed * 3;
				decay *= 50.0 / 1000.0;//msec

				if ( error < 0.0 ) 
				{
					error += decay;
					if ( error > 0.0 ) 
					{
						error = 0.0;
					}
				}
				else 
				{
					error -= decay;
					if ( error < 0.0 ) 
					{
						error = 0.0;
					}
				}
			}
		}
		ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW];
	}

	//FIXME: have a pitchSpeed?
	if(doPitch)
	{
		// decay pitch error
		error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch );
		if ( fabs(error) > MIN_ANGLE_ERROR )
		{
			/*
			if(NPC->client->playerTeam == TEAM_BORG&&
				NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE)
			{//HACK - borg turn more jittery
				if ( error ) 
				{
					exact = qfalse;

					decay = 60.0 + yawSpeed * 3;
					decay *= 50.0 / 1000.0;//msec
					//Snap to
					if(fabs(error) > 10)
					{
						if(random() > 0.6)
						{
							doSound = qtrue;
						}
					}

					if ( error < 0.0)//-10.0 ) 
					{
						error += decay;
						if ( error > 0.0 ) 
						{
							error = 0.0;
						}
					}
					else if ( error > 0.0)//10.0 ) 
					{
						error -= decay;
						if ( error < 0.0 ) 
						{
							error = 0.0;
						}
					}
				}
			}
			else*/
			
			if ( error ) 
			{
				exact = qfalse;

				decay = 60.0 + yawSpeed * 3;
				decay *= 50.0 / 1000.0;//msec

				if ( error < 0.0 ) 
				{
					error += decay;
					if ( error > 0.0 ) 
					{
						error = 0.0;
					}
				}
				else 
				{
					error -= decay;
					if ( error < 0.0 ) 
					{
						error = 0.0;
					}
				}
			}
		}
		ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH];
	}

	ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL];

	/*
	if(doSound)
	{
		G_Sound(NPC, G_SoundIndex(va("sound/enemies/borg/borgservo%d.wav", Q_irand(1, 8))));
	}
	*/

	return exact;

#endif

}

void NPC_AimWiggle( vec3_t enemy_org )
{
	//shoot for somewhere between the head and torso
	//NOTE: yes, I know this looks weird, but it works
	if ( NPCInfo->aimErrorDebounceTime < level.time )
	{
		NPCInfo->aimOfs[0] = 0.3*Q_flrand(NPC->enemy->mins[0], NPC->enemy->maxs[0]);
		NPCInfo->aimOfs[1] = 0.3*Q_flrand(NPC->enemy->mins[1], NPC->enemy->maxs[1]);
		if ( NPC->enemy->maxs[2] > 0 )
		{
			NPCInfo->aimOfs[2] = NPC->enemy->maxs[2]*Q_flrand(0.0f, -1.0f);
		}
	}
	VectorAdd( enemy_org, NPCInfo->aimOfs, enemy_org );
}

/*
qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) 

  Includes aim when determining angles - so they don't always hit...
  */
qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) 
{

#if 0

	float		diff;
	float		error;
	float		targetPitch = 0;
	float		targetYaw = 0;
	qboolean	exact = qtrue;

	if ( level.time < NPCInfo->aimTime ) 
	{
		if( doPitch )
			targetPitch = NPCInfo->lockedDesiredPitch;

		if( doYaw )
			targetYaw = NPCInfo->lockedDesiredYaw;
	}
	else 
	{
		if( doPitch )
		{
			targetPitch = NPCInfo->desiredPitch;
			NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch;
		}

		if( doYaw )
		{
			targetYaw = NPCInfo->desiredYaw;
			NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw;
		}			
	}

	if( doYaw )
	{
		// add yaw error based on NPCInfo->aim value
		error = ((float)(6 - NPCInfo->stats.aim)) * Q_flrand(-1, 1);

		if(Q_irand(0, 1))
			error *= -1;

		diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw );

		if ( diff )
			exact = qfalse;
		
		ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW];
	}

	if( doPitch )
	{
		// add pitch error based on NPCInfo->aim value
		error = ((float)(6 - NPCInfo->stats.aim)) * Q_flrand(-1, 1);

		diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch );

		if ( diff )
			exact = qfalse;
		
		ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH];
	}

	ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL];

	return exact;
	
#else

	float		error, diff;
	float		decay;
	float		targetPitch = 0;
	float		targetYaw = 0;
	qboolean	exact = qtrue;

	// if angle changes are locked; just keep the current angles
	if ( level.time < NPCInfo->aimTime ) 
	{
		if(doPitch)
			targetPitch = NPCInfo->lockedDesiredPitch;
		if(doYaw)
			targetYaw = NPCInfo->lockedDesiredYaw;
	}
	else 
	{
		if(doPitch)
			targetPitch = NPCInfo->desiredPitch;
		if(doYaw)
			targetYaw = NPCInfo->desiredYaw;

//		NPCInfo->aimTime = level.time + 250;
		if(doPitch)
			NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch;
		if(doYaw)
			NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw;
	}

	if ( NPCInfo->aimErrorDebounceTime < level.time )
	{
		if ( Q_irand(0, 1 ) )
		{
			NPCInfo->lastAimErrorYaw = ((float)(6 - NPCInfo->stats.aim)) * Q_flrand(-1, 1);
		}
		if ( Q_irand(0, 1 ) )
		{
			NPCInfo->lastAimErrorPitch = ((float)(6 - NPCInfo->stats.aim)) * Q_flrand(-1, 1);
		}
		NPCInfo->aimErrorDebounceTime = level.time + Q_irand(250, 2000);
	}

	if(doYaw)
	{
		// decay yaw diff
		diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw );
		
		if ( diff) 
		{
			exact = qfalse;

			decay = 60.0 + 80.0;
			decay *= 50.0f / 1000.0f;//msec
			if ( diff < 0.0 ) 
			{
				diff += decay;
				if ( diff > 0.0 ) 
				{
					diff = 0.0;
				}
			}
			else 
			{
				diff -= decay;
				if ( diff < 0.0 ) 
				{
					diff = 0.0;
				}
			}
		}
				
		// add yaw error based on NPCInfo->aim value
		error = NPCInfo->lastAimErrorYaw;

		/*
		if(Q_irand(0, 1))
		{
			error *= -1;
		}
		*/

		ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW];
	}

	if(doPitch)
	{
		// decay pitch diff
		diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch );
		if ( diff) 
		{
			exact = qfalse;

			decay = 60.0 + 80.0;
			decay *= 50.0f / 1000.0f;//msec
			if ( diff < 0.0 ) 
			{
				diff += decay;
				if ( diff > 0.0 ) 
				{
					diff = 0.0;
				}
			}
			else 
			{
				diff -= decay;
				if ( diff < 0.0 ) 
				{
					diff = 0.0;
				}
			}
		}
		
		error = NPCInfo->lastAimErrorPitch;

		ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH];
	}

	ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL];

	return exact;

#endif

}
//===================================================================================

/*
static void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ) 

Does update angles on shootAngles
*/

void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ) 
{//FIXME: shoot angles either not set right or not used!
	float		error;
	float		decay;
	float		targetPitch = 0;
	float		targetYaw = 0;

	if(doPitch)
		targetPitch = angles[PITCH];
	if(doYaw)
		targetYaw = angles[YAW];


	if(doYaw)
	{
		// decay yaw error
		error = AngleDelta ( NPCInfo->shootAngles[YAW], targetYaw );
		if ( error ) 
		{
			decay = 60.0 + 80.0 * NPCInfo->stats.aim;
			decay *= 100.0f / 1000.0f;//msec
			if ( error < 0.0 ) 
			{
				error += decay;
				if ( error > 0.0 ) 
				{
					error = 0.0;
				}
			}
			else 
			{
				error -= decay;
				if ( error < 0.0 ) 
				{
					error = 0.0;
				}
			}
		}
		NPCInfo->shootAngles[YAW] = targetYaw + error;
	}

	if(doPitch)
	{
		// decay pitch error
		error = AngleDelta ( NPCInfo->shootAngles[PITCH], targetPitch );
		if ( error ) 
		{
			decay = 60.0 + 80.0 * NPCInfo->stats.aim;
			decay *= 100.0f / 1000.0f;//msec
			if ( error < 0.0 ) 
			{
				error += decay;
				if ( error > 0.0 ) 
				{
					error = 0.0;
				}
			}
			else 
			{
				error -= decay;
				if ( error < 0.0 ) 
				{
					error = 0.0;
				}
			}
		}
		NPCInfo->shootAngles[PITCH] = targetPitch + error;
	}
}

/*
void SetTeamNumbers (void)

Sets the number of living clients on each team

FIXME: Does not account for non-respawned players!
FIXME: Don't include medics?
*/
void SetTeamNumbers (void)
{
	gentity_t	*found;
	int			i;

	for( i = 0; i < TEAM_NUM_TEAMS; i++ )
	{
		teamNumbers[i] = 0;
		teamStrength[i] = 0;
	}

	for( i = 0; i < 1 ; i++ )
	{
		found = &g_entities[i];

		if( found->client )
		{
			if( found->health > 0 )//FIXME: or if a player!
			{
				teamNumbers[found->client->playerTeam]++;
				teamStrength[found->client->playerTeam] += found->health;
			}
		}
	}

	for( i = 0; i < TEAM_NUM_TEAMS; i++ )
	{//Get the average health
		teamStrength[i] = floor( ((float)(teamStrength[i])) / ((float)(teamNumbers[i])) );
	}
}

extern stringID_table_t BSTable[];
extern stringID_table_t BSETTable[];
qboolean G_ActivateBehavior (gentity_t *self, int bset )
{
	bState_t	bSID = (bState_t)-1;
	char *bs_name = NULL;

	if ( !self )
	{
		return qfalse;
	}

	bs_name = self->behaviorSet[bset];
	
	if( !(VALIDSTRING( bs_name )) )
	{
		return qfalse;
	}

	if ( self->NPC )
	{
		bSID = (bState_t)(GetIDForString( BSTable, bs_name ));
	}

	if(bSID > -1)
	{
		self->NPC->tempBehavior = BS_DEFAULT;
		self->NPC->behaviorState = bSID;
		if ( bSID == BS_SEARCH || bSID == BS_WANDER )
		{
			//FIXME: Reimplement?
			if( self->waypoint != WAYPOINT_NONE )
			{
				NPC_BSSearchStart( self->waypoint, bSID );
			}
			else
			{
				self->waypoint = NAV::GetNearestNode(self);
				if( self->waypoint != WAYPOINT_NONE )
				{
					NPC_BSSearchStart( self->waypoint, bSID );
				}
			}
		}
	}
	else
	{
		Quake3Game()->DebugPrint( IGameInterface::WL_VERBOSE, "%s attempting to run bSet %s (%s)\n", self->targetname, GetStringForID( BSETTable, bset ), bs_name );
		Quake3Game()->RunScript( self, bs_name );
	}
	return qtrue;
}


/*
=============================================================================

	Extended Functions

=============================================================================
*/

/*
-------------------------
NPC_ValidEnemy
-------------------------
*/

qboolean G_ValidEnemy( gentity_t *self, gentity_t *enemy )
{
	//Must be a valid pointer
	if ( enemy == NULL )
		return qfalse;

	//Must not be me
	if ( enemy == self )
		return qfalse;

	//Must not be deleted
	if ( enemy->inuse == qfalse )
		return qfalse;

	//Must be alive
	if ( enemy->health <= 0 )
		return qfalse;

	//In case they're in notarget mode
	if ( enemy->flags & FL_NOTARGET )
		return qfalse;

	//Must be an NPC
	if ( enemy->client == NULL )
	{
		if ( enemy->svFlags&SVF_NONNPC_ENEMY )
		{//still potentially valid
			if (self->client)
			{
				if ( enemy->noDamageTeam == self->client->playerTeam )
				{
					return qfalse;
				}
				else
				{
					return qtrue;
				}
			}
			else
			{
				if ( enemy->noDamageTeam == self->noDamageTeam )
				{
					return qfalse;
				}
				else
				{
					return qtrue;
				}
			}
		}
		else
		{
			return qfalse;
		}
	}

	if ( enemy->client->playerTeam == TEAM_FREE && enemy->s.number < MAX_CLIENTS )
	{//An evil player, everyone attacks him
		return qtrue;
	}

	//Can't be on the same team
	if ( enemy->client->playerTeam == self->client->playerTeam )
	{
		return qfalse;
	}

	//if haven't seen him in a while, give up
	//if ( NPCInfo->enemyLastSeenTime != 0 && level.time - NPCInfo->enemyLastSeenTime > 7000 )//FIXME: make a stat?
	//	return qfalse;

	if ( enemy->client->playerTeam == self->client->enemyTeam //simplest case: they're on my enemy team
		|| (self->client->enemyTeam == TEAM_FREE && enemy->client->NPC_class != self->client->NPC_class )//I get mad at anyone and this guy isn't the same class as me
		|| (enemy->client->NPC_class == CLASS_WAMPA && enemy->enemy )//a rampaging wampa
		|| (enemy->client->NPC_class == CLASS_RANCOR && enemy->enemy )//a rampaging rancor
		|| (enemy->client->playerTeam == TEAM_FREE && enemy->client->enemyTeam == TEAM_FREE && enemy->enemy && enemy->enemy->client && (enemy->enemy->client->playerTeam == self->client->playerTeam||(enemy->enemy->client->playerTeam != TEAM_ENEMY&&self->client->playerTeam==TEAM_PLAYER))) //enemy is a rampaging non-aligned creature who is attacking someone on our team or a non-enemy (this last condition is used only if we're a good guy - in effect, we protect the innocent)
		)
	{
		return qtrue;
	}
	//all other cases = false?
	return qfalse;
}

qboolean NPC_ValidEnemy( gentity_t *ent )
{
	return G_ValidEnemy( NPC, ent );
}

/*
-------------------------
NPC_TargetVisible
-------------------------
*/

qboolean NPC_TargetVisible( gentity_t *ent )
{
	//Make sure we're in a valid range
	if ( DistanceSquared( ent->currentOrigin, NPC->currentOrigin ) > ( NPCInfo->stats.visrange * NPCInfo->stats.visrange ) )
		return qfalse;

	//Check our FOV
	if ( InFOV( ent, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse )
		return qfalse;

	//Check for sight
	if ( NPC_ClearLOS( ent ) == qfalse )
		return qfalse;

	return qtrue;
}

/*
-------------------------
NPC_GetCheckDelta
-------------------------
*/

/*
#define	CHECK_TIME_BASE				250
#define CHECK_TIME_BASE_SQUARED		( CHECK_TIME_BASE * CHECK_TIME_BASE )

static int NPC_GetCheckDelta( void )
{
	if ( NPC_ValidEnemy( NPC->enemy ) == qfalse )
	{
		int distance = DistanceSquared( NPC->currentOrigin, g_entities[0].currentOrigin );

		distance /= CHECK_TIME_BASE_SQUARED;

		return ( CHECK_TIME_BASE * distance );
	}

	return 0;
}
*/

/*
-------------------------
NPC_FindNearestEnemy
-------------------------
*/

#define	MAX_RADIUS_ENTS			256	//NOTE: This can cause entities to be lost
#define NEAR_DEFAULT_RADIUS		256
extern gentity_t *G_CheckControlledTurretEnemy(gentity_t *self,  gentity_t *enemy, qboolean validate );

int NPC_FindNearestEnemy( gentity_t *ent )
{
	gentity_t	*radiusEnts[ MAX_RADIUS_ENTS ];
	gentity_t	*nearest;
	vec3_t		mins, maxs;
	int			nearestEntID = -1;
	float		nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE;
	float		distance;
	int			numEnts, numChecks = 0;

	//Setup the bbox to search in
	for ( int i = 0; i < 3; i++ )
	{
		mins[i] = ent->currentOrigin[i] - NPCInfo->stats.visrange;
		maxs[i] = ent->currentOrigin[i] + NPCInfo->stats.visrange;
	}

	//Get a number of entities in a given space
	numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS );

	for ( i = 0; i < numEnts; i++ )
	{
		nearest = G_CheckControlledTurretEnemy(ent, radiusEnts[i], qtrue);

		//Don't consider self
		if ( nearest == ent )
			continue;

		//Must be valid
		if ( NPC_ValidEnemy( nearest ) == qfalse )
			continue;

		numChecks++;
		//Must be visible
		if ( NPC_TargetVisible( nearest ) == qfalse )
			continue;

		distance = DistanceSquared( ent->currentOrigin, nearest->currentOrigin );

		//Found one closer to us
		if ( distance < nearestDist )
		{
			nearestEntID = nearest->s.number;
			nearestDist = distance;
		}
	}

	return nearestEntID;
}

/*
-------------------------
NPC_PickEnemyExt
-------------------------
*/

gentity_t *NPC_PickEnemyExt( qboolean checkAlerts = qfalse )
{
	//Check for Hazard Team status and remove this check
	/*
	if ( NPC->client->playerTeam != TEAM_STARFLEET )
	{
		//If we've found the player, return it
		if ( NPC_FindPlayer() )
			return &g_entities[0];
	}
	*/

	//If we've asked for the closest enemy
	int entID = NPC_FindNearestEnemy( NPC );

	//If we have a valid enemy, use it
	if ( entID >= 0 )
		return &g_entities[entID];

	if ( checkAlerts )
	{
		int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qtrue, AEL_DISCOVERED );

		//There is an event to look at
		if ( alertEvent >= 0 )
		{
			alertEvent_t *event = &level.alertEvents[alertEvent];

			//Don't pay attention to our own alerts
			if ( event->owner == NPC )
				return NULL;

			if ( event->level >= AEL_DISCOVERED )
			{
				//If it's the player, attack him
				if ( event->owner == &g_entities[0] )
					return event->owner;

				//If it's on our team, then take its enemy as well
				if ( ( event->owner->client ) && ( event->owner->client->playerTeam == NPC->client->playerTeam ) )
					return event->owner->enemy;
			}
		}
	}

	return NULL;
}

/*
-------------------------
NPC_FindPlayer
-------------------------
*/

qboolean NPC_FindPlayer( void )
{
	return NPC_TargetVisible( &g_entities[0] );
}

/*
-------------------------
NPC_CheckPlayerDistance
-------------------------
*/

static qboolean NPC_CheckPlayerDistance( void )
{
	//Make sure we have an enemy
	if ( NPC->enemy == NULL )
		return qfalse;

	//Only do this for non-players
	if ( NPC->enemy->s.number == 0 )
		return qfalse;

	//must be set up to get mad at player
	if ( !NPC->client || NPC->client->enemyTeam != TEAM_PLAYER )
		return qfalse;

	//Must be within our FOV
	if ( InFOV( &g_entities[0], NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse )
		return qfalse;

	float	distance = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );

	if ( distance > DistanceSquared( NPC->currentOrigin, g_entities[0].currentOrigin ) )
	{
		G_SetEnemy( NPC, &g_entities[0] );
		return qtrue;
	}

	return qfalse;
}

/*
-------------------------
NPC_FindEnemy
-------------------------
*/

qboolean NPC_FindEnemy( qboolean checkAlerts = qfalse )
{
	//We're ignoring all enemies for now
	if( NPC->svFlags & SVF_IGNORE_ENEMIES )
	{
		G_ClearEnemy( NPC );
		return qfalse;
	}

	//we can't pick up any enemies for now
	if( NPCInfo->confusionTime > level.time )
	{
		G_ClearEnemy( NPC );
		return qfalse;
	}

	//Don't want a new enemy
	if ( ( NPC_ValidEnemy( NPC->enemy ) ) && ( NPC->svFlags & SVF_LOCKEDENEMY ) )
		return qtrue;

	//See if the player is closer than our current enemy
	if ( NPC->client->NPC_class != CLASS_RANCOR 
		&& NPC->client->NPC_class != CLASS_WAMPA
		&& NPC->client->NPC_class != CLASS_SAND_CREATURE
		&& NPC_CheckPlayerDistance() )
	{//rancors, wampas & sand creatures don't care if player is closer, they always go with closest
		return qtrue;
	}

	//Otherwise, turn off the flag
	NPC->svFlags &= ~SVF_LOCKEDENEMY;

	//If we've gotten here alright, then our target it still valid
	if ( NPC_ValidEnemy( NPC->enemy ) )
		return qtrue;

	gentity_t *newenemy = NPC_PickEnemyExt( checkAlerts );

	//if we found one, take it as the enemy
	if( NPC_ValidEnemy( newenemy ) )
	{
		G_SetEnemy( NPC, newenemy );
		return qtrue;
	}

	G_ClearEnemy( NPC );
	return qfalse;
}

/*
-------------------------
NPC_CheckEnemyExt
-------------------------
*/

qboolean NPC_CheckEnemyExt( qboolean checkAlerts )
{
	//Make sure we're ready to think again
/*
	if ( NPCInfo->enemyCheckDebounceTime > level.time )
		return qfalse;

	//Get our next think time
	NPCInfo->enemyCheckDebounceTime = level.time + NPC_GetCheckDelta();

	//Attempt to find an enemy
	return NPC_FindEnemy();
*/
	return NPC_FindEnemy( checkAlerts );
}

/*
-------------------------
NPC_FacePosition
-------------------------
*/

qboolean NPC_FacePosition( vec3_t position, qboolean doPitch )
{
	vec3_t		muzzle;
	qboolean	facing = qtrue;

	//Get the positions
	if ( NPC->client && (NPC->client->NPC_class == CLASS_RANCOR || NPC->client->NPC_class == CLASS_WAMPA || NPC->client->NPC_class == CLASS_SAND_CREATURE) )
	{
		CalcEntitySpot( NPC, SPOT_ORIGIN, muzzle );
		muzzle[2] += NPC->maxs[2] * 0.75f;
	}
	else if ( NPC->client && NPC->client->NPC_class == CLASS_GALAKMECH )
	{
		CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
	}
	else
	{
		CalcEntitySpot( NPC, SPOT_HEAD_LEAN, muzzle );//SPOT_HEAD
		if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER )
		{//*sigh*, look down more
			position[2] -= 32;
		}
	}

	//Find the desired angles
	vec3_t	angles;

	GetAnglesForDirection( muzzle, position, angles );

	NPCInfo->desiredYaw		= AngleNormalize360( angles[YAW] );
	NPCInfo->desiredPitch	= AngleNormalize360( angles[PITCH] );

	if ( NPC->enemy && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_ATST )
	{
		// FIXME: this is kind of dumb, but it was the easiest way to get it to look sort of ok
		NPCInfo->desiredYaw	+= Q_flrand( -5, 5 ) + sin( level.time * 0.004f ) * 7;
		NPCInfo->desiredPitch += Q_flrand( -2, 2 );
	}
	//Face that yaw
	NPC_UpdateAngles( qtrue, qtrue );

	//Find the delta between our goal and our current facing
	float yawDelta = AngleNormalize360( NPCInfo->desiredYaw - ( SHORT2ANGLE( ucmd.angles[YAW] + client->ps.delta_angles[YAW] ) ) );
	
	//See if we are facing properly
	if ( fabs( yawDelta ) > VALID_ATTACK_CONE )
		facing = qfalse;

	if ( doPitch )
	{
		//Find the delta between our goal and our current facing
		float currentAngles = ( SHORT2ANGLE( ucmd.angles[PITCH] + client->ps.delta_angles[PITCH] ) );
		float pitchDelta = NPCInfo->desiredPitch - currentAngles;
		
		//See if we are facing properly
		if ( fabs( pitchDelta ) > VALID_ATTACK_CONE )
			facing = qfalse;
	}

	return facing;
}

/*
-------------------------
NPC_FaceEntity
-------------------------
*/

qboolean NPC_FaceEntity( gentity_t *ent, qboolean doPitch )
{
	vec3_t		entPos;

	//Get the positions
	CalcEntitySpot( ent, SPOT_HEAD_LEAN, entPos );

	return NPC_FacePosition( entPos, doPitch );
}

/*
-------------------------
NPC_FaceEnemy
-------------------------
*/

qboolean NPC_FaceEnemy( qboolean doPitch )
{
	if ( NPC == NULL )
		return qfalse;

	if ( NPC->enemy == NULL )
		return qfalse;

	return NPC_FaceEntity( NPC->enemy, doPitch );
}

/*
-------------------------
NPC_CheckCanAttackExt
-------------------------
*/

qboolean NPC_CheckCanAttackExt( void )
{
	//We don't want them to shoot
	if( NPCInfo->scriptFlags & SCF_DONT_FIRE )
		return qfalse;

	//Turn to face
	if ( NPC_FaceEnemy( qtrue ) == qfalse )
		return qfalse;

	//Must have a clear line of sight to the target
	if ( NPC_ClearShot( NPC->enemy ) == qfalse )
		return qfalse;

	return qtrue;
}

/*
-------------------------
NPC_ClearLookTarget
-------------------------
*/

void NPC_ClearLookTarget( gentity_t *self )
{
	if ( !self->client )
	{
		return;
	}

	self->client->renderInfo.lookTarget = ENTITYNUM_NONE;//ENTITYNUM_WORLD;
	self->client->renderInfo.lookTargetClearTime = 0;
}

/*
-------------------------
NPC_SetLookTarget
-------------------------
*/
void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime )
{
	if ( !self->client )
	{
		return;
	}

	self->client->renderInfo.lookTarget = entNum;
	self->client->renderInfo.lookTargetClearTime = clearTime;
}

/*
-------------------------
NPC_CheckLookTarget
-------------------------
*/
qboolean NPC_CheckLookTarget( gentity_t *self )
{
	if ( self->client )
	{
		if ( self->client->renderInfo.lookTarget >= 0 && self->client->renderInfo.lookTarget < ENTITYNUM_WORLD )
		{//within valid range
			if ( (&g_entities[self->client->renderInfo.lookTarget] == NULL) || !g_entities[self->client->renderInfo.lookTarget].inuse )
			{//lookTarget not inuse or not valid anymore
				NPC_ClearLookTarget( self );
			}
			else if ( self->client->renderInfo.lookTargetClearTime && self->client->renderInfo.lookTargetClearTime < level.time )
			{//Time to clear lookTarget
				NPC_ClearLookTarget( self );
			}
			else if ( g_entities[self->client->renderInfo.lookTarget].client && self->enemy && (&g_entities[self->client->renderInfo.lookTarget] != self->enemy) )
			{//should always look at current enemy if engaged in battle... FIXME: this could override certain scripted lookTargets...???
				NPC_ClearLookTarget( self );
			}
			else
			{
				return qtrue;
			}
		}
	}

	return qfalse;
}

/*
-------------------------
NPC_CheckCharmed
-------------------------
*/
extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
void G_CheckCharmed( gentity_t *self )
{
	if ( self
		&& self->client
		&& self->client->playerTeam == TEAM_PLAYER 
		&& self->NPC
		&& self->NPC->charmedTime 
		&& (self->NPC->charmedTime < level.time ||self->health <= 0) )
	{//we were charmed, set us back!
		//NOTE: presumptions here...
		team_t	savTeam = self->client->enemyTeam;
		self->client->enemyTeam = self->client->playerTeam;
		self->client->playerTeam = savTeam;
		self->client->leader = NULL;
		self->NPC->charmedTime = 0;
		if ( self->health > 0 )
		{
			if ( self->NPC->tempBehavior == BS_FOLLOW_LEADER )
			{
				self->NPC->tempBehavior = BS_DEFAULT;
			}
			G_ClearEnemy( self );
			//say something to let player know you've snapped out of it
			G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 );
		}
	}

}

void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex = 0 )
{
	if ( !self || !self->ghoul2.size() )
	{
		return;
	}
	mdxaBone_t	boltMatrix;
	vec3_t		result, angles={0,self->currentAngles[YAW],0};

	gi.G2API_GetBoltMatrix( self->ghoul2, modelIndex, 
				boltIndex,
				&boltMatrix, angles, self->currentOrigin, (cg.time?cg.time:level.time),
				NULL, self->s.modelScale );
	if ( pos )
	{
        gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, result );
		VectorCopy( result, pos );
	}
}

float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex )
{
	vec3_t	org;

	if ( !targEnt )
	{
		return Q3_INFINITE;
	}

	G_GetBoltPosition( NPC, boltIndex, org );

	return (Distance( targEnt->currentOrigin, org ));
}

float NPC_EnemyRangeFromBolt( int boltIndex )
{
	return (NPC_EntRangeFromBolt( NPC->enemy, boltIndex ));
}

int G_GetEntsNearBolt( gentity_t *self, gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg )
{
	vec3_t		mins, maxs;
	int			i;

	//get my handRBolt's position
	vec3_t	org;

	G_GetBoltPosition( self, boltIndex, org );

	VectorCopy( org, boltOrg );

	//Setup the bbox to search in
	for ( i = 0; i < 3; i++ )
	{
		mins[i] = boltOrg[i] - radius;
		maxs[i] = boltOrg[i] + radius;
	}

	//Get the number of entities in a given space
	return (gi.EntitiesInBox( mins, maxs, radiusEnts, 128 ));
}

int NPC_GetEntsNearBolt( gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg )
{
	return (G_GetEntsNearBolt( NPC, radiusEnts, radius, boltIndex, boltOrg ));
}

extern qboolean RT_Flying( gentity_t *self );
extern void RT_FlyStart( gentity_t *self );
extern void RT_FlyStop( gentity_t *self );
extern qboolean Boba_Flying( gentity_t *self );
extern void Boba_FlyStart( gentity_t *self );
extern void Boba_FlyStop( gentity_t *self );

qboolean JET_Flying( gentity_t *self )
{
	if ( !self || !self->client )
	{
		return qfalse;
	}
	if ( self->client->NPC_class == CLASS_BOBAFETT )
	{
		return (Boba_Flying(self));
	}
	else if ( self->client->NPC_class == CLASS_ROCKETTROOPER )
	{
		return (RT_Flying(self));
	}
	else
	{
		return qfalse;
	}
}

void JET_FlyStart( gentity_t *self )
{
	if ( !self || !self->client )
	{
		return;
	}
	self->lastInAirTime = level.time;
	if ( self->client->NPC_class == CLASS_BOBAFETT )
	{
		Boba_FlyStart( self );
	}
	else if ( self->client->NPC_class == CLASS_ROCKETTROOPER )
	{
		RT_FlyStart( self );
	}
}

void JET_FlyStop( gentity_t *self )
{
	if ( !self || !self->client )
	{
		return;
	}
	if ( self->client->NPC_class == CLASS_BOBAFETT )
	{
		Boba_FlyStop( self );
	}
	else if ( self->client->NPC_class == CLASS_ROCKETTROOPER )
	{
		RT_FlyStop( self );
	}
}
