//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003

#include "cbase.h"
#include "cs_bot.h"
#include "datacache/imdlcache.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//--------------------------------------------------------------------------------------------------------------
/**
 * Used to update view angles to stay on a ladder
 */
inline float StayOnLadderLine( CCSBot *me, const CNavLadder *ladder )
{
	// determine our facing
	NavDirType faceDir = AngleToDirection( me->EyeAngles().y );

	const float stiffness = 1.0f;

	// move toward ladder mount point
	switch( faceDir )
	{
		case NORTH:
			return stiffness * (ladder->m_top.x - me->GetAbsOrigin().x);

		case SOUTH:
			return -stiffness * (ladder->m_top.x - me->GetAbsOrigin().x);

		case WEST:
			return -stiffness * (ladder->m_top.y - me->GetAbsOrigin().y);

		case EAST:
			return stiffness * (ladder->m_top.y - me->GetAbsOrigin().y);
	}

	return 0.0f;
}

//--------------------------------------------------------------------------------------------------------------
void CCSBot::ComputeLadderAngles( float *yaw, float *pitch )
{
	if ( !yaw || !pitch )
		return;

	Vector myOrigin = GetCentroid( this );

	// set yaw to aim at ladder
	Vector to = m_pathLadder->GetPosAtHeight(myOrigin.z) - myOrigin;
	float idealYaw = UTIL_VecToYaw( to );

	Vector faceDir = (m_pathLadderFaceIn) ? -m_pathLadder->GetNormal() : m_pathLadder->GetNormal();
	QAngle faceAngles;
	VectorAngles( faceDir, faceAngles );

	const float lookAlongLadderRange = 50.0f;
	const float ladderPitchUpApproach = -30.0f;
	const float ladderPitchUpTraverse = -60.0f;		// -80
	const float ladderPitchDownApproach = 0.0f;
	const float ladderPitchDownTraverse = 80.0f;

	// adjust pitch to look up/down ladder as we ascend/descend
	switch( m_pathLadderState )
	{
		case APPROACH_ASCENDING_LADDER:
		{
			Vector to = m_goalPosition - myOrigin;
			*yaw = idealYaw;

			if (to.IsLengthLessThan( lookAlongLadderRange ))
				*pitch = ladderPitchUpApproach;
			break;
		}

		case APPROACH_DESCENDING_LADDER:
		{
			Vector to = m_goalPosition - myOrigin;
			*yaw = idealYaw;

			if (to.IsLengthLessThan( lookAlongLadderRange ))
				*pitch = ladderPitchDownApproach;
			break;
		}

		case FACE_ASCENDING_LADDER:
			if ( m_pathLadderDismountDir == LEFT )
			{
				*yaw = AngleNormalizePositive( idealYaw + 90.0f );
			}
			else if ( m_pathLadderDismountDir == RIGHT )
			{
				*yaw = AngleNormalizePositive( idealYaw - 90.0f );
			}
			else
			{
				*yaw = idealYaw;
			}
			*pitch = ladderPitchUpApproach;
			break;

		case FACE_DESCENDING_LADDER:
			*yaw = idealYaw;
			*pitch = ladderPitchDownApproach;
			break;

		case MOUNT_ASCENDING_LADDER:
		case ASCEND_LADDER:
			if ( m_pathLadderDismountDir == LEFT )
			{
				*yaw = AngleNormalizePositive( idealYaw + 90.0f );
			}
			else if ( m_pathLadderDismountDir == RIGHT )
			{
				*yaw = AngleNormalizePositive( idealYaw - 90.0f );
			}
			else
			{
				*yaw = faceAngles[ YAW ] + StayOnLadderLine( this, m_pathLadder );
			}
			*pitch = ( m_pathLadderState == ASCEND_LADDER ) ? ladderPitchUpTraverse : ladderPitchUpApproach;
			break;

		case MOUNT_DESCENDING_LADDER:
		case DESCEND_LADDER:
			*yaw = faceAngles[ YAW ] + StayOnLadderLine( this, m_pathLadder );
			*pitch = ( m_pathLadderState == DESCEND_LADDER ) ? ladderPitchDownTraverse : ladderPitchDownApproach;
			break;

		case DISMOUNT_ASCENDING_LADDER:
			if ( m_pathLadderDismountDir == LEFT )
			{
				*yaw = AngleNormalizePositive( faceAngles[ YAW ] + 90.0f );
			}
			else if ( m_pathLadderDismountDir == RIGHT )
			{
				*yaw = AngleNormalizePositive( faceAngles[ YAW ] - 90.0f );
			}
			else
			{
				*yaw = faceAngles[ YAW ];
			}
			break;

		case DISMOUNT_DESCENDING_LADDER:
			*yaw = faceAngles[ YAW ];
			break;
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Move actual view angles towards desired ones.
 * This is the only place v_angle is altered.
 * @todo Make stiffness and turn rate constants timestep invariant.
 */
void CCSBot::UpdateLookAngles( void )
{
	VPROF_BUDGET( "CCSBot::UpdateLookAngles", VPROF_BUDGETGROUP_NPCS );

	const float deltaT = g_BotUpkeepInterval;
	float maxAccel;
	float stiffness;
	float damping;

	// If mimicing the player, don't modify the view angles.
	if ( bot_mimic.GetInt() )
		return;

	// springs are stiffer when attacking, so we can track and move between targets better
	if (IsAttacking())
	{
		stiffness = 300.0f;
		damping = 30.0f;			// 20
		maxAccel = 3000.0f;	// 4000
	}
	else
	{
		stiffness = 200.0f;
		damping = 25.0f;
		maxAccel = 3000.0f;
	}

	// these may be overridden by ladder logic
	float useYaw = m_lookYaw;
	float usePitch = m_lookPitch;

	//
	// Ladders require precise movement, therefore we need to look at the 
	// ladder as we approach and ascend/descend it.
	// If we are on a ladder, we need to look up or down to traverse it - override pitch in this case.
	//
	// If we're trying to break something, though, we actually need to look at it before we can
	// look at the ladder
	//
	if ( IsUsingLadder() && !(IsLookingAtSpot( PRIORITY_HIGH ) && m_lookAtSpotAttack) )
	{
		ComputeLadderAngles( &useYaw, &usePitch );
	}

	// get current view angles
	QAngle viewAngles = EyeAngles();

	//
	// Yaw
	//
	float angleDiff = AngleNormalize( useYaw - viewAngles.y );

	/*
	 * m_forwardAngle is unreliable. Need to simulate mouse sliding & centering
	if (!IsAttacking())
	{
		// do not allow rotation through our reverse facing angle - go the "long" way instead
		float toCurrent = AngleNormalize( pev->v_angle.y - m_forwardAngle );
		float toDesired = AngleNormalize( useYaw - m_forwardAngle );

		// if angle differences are different signs, they cross the forward facing
		if (toCurrent * toDesired < 0.0f)
		{
			// if the sum of the angles is greater than 180, turn the "long" way around
			if (abs( toCurrent - toDesired ) >= 180.0f)
			{
				if (angleDiff > 0.0f)
					angleDiff -= 360.0f;
				else
					angleDiff += 360.0f;
			}
		}
	}
	*/

	// if almost at target angle, snap to it
	const float onTargetTolerance = 1.0f;		// 3
	if (angleDiff < onTargetTolerance && angleDiff > -onTargetTolerance)
	{
		m_lookYawVel = 0.0f;
		viewAngles.y = useYaw;
	}
	else
	{
		// simple angular spring/damper
		float accel = stiffness * angleDiff - damping * m_lookYawVel;

		// limit rate
		if (accel > maxAccel)
			accel = maxAccel;
		else if (accel < -maxAccel)
			accel = -maxAccel;

		m_lookYawVel += deltaT * accel;
		viewAngles.y += deltaT * m_lookYawVel;

		// keep track of how long our view remains steady
		const float steadyYaw = 1000.0f;
		if (fabs( accel ) > steadyYaw)
		{
			m_viewSteadyTimer.Start();
		}
	}

	//
	// Pitch
	// Actually, this is negative pitch.
	//
	angleDiff = usePitch - viewAngles.x;

	angleDiff = AngleNormalize( angleDiff );


	if (false && angleDiff < onTargetTolerance && angleDiff > -onTargetTolerance)
	{
		m_lookPitchVel = 0.0f;
		viewAngles.x = usePitch;
	}
	else
	{
		// simple angular spring/damper
		// double the stiffness since pitch is only +/- 90 and yaw is +/- 180
		float accel = 2.0f * stiffness * angleDiff - damping * m_lookPitchVel;

		// limit rate
		if (accel > maxAccel)
			accel = maxAccel;
		else if (accel < -maxAccel)
			accel = -maxAccel;

		m_lookPitchVel += deltaT * accel;
		viewAngles.x += deltaT * m_lookPitchVel;

		// keep track of how long our view remains steady
		const float steadyPitch = 1000.0f;
		if (fabs( accel ) > steadyPitch)
		{
			m_viewSteadyTimer.Start();
		}
	}

	//PrintIfWatched( "yawVel = %g, pitchVel = %g\n", m_lookYawVel, m_lookPitchVel );

	// limit range - avoid gimbal lock
	if (viewAngles.x < -89.0f)
		viewAngles.x = -89.0f;
	else if (viewAngles.x > 89.0f)
		viewAngles.x = 89.0f;

	// update view angles
	SnapEyeAngles( viewAngles );

	// if our weapon is zooming, our view is not steady
	if (IsWaitingForZoom())
	{
		m_viewSteadyTimer.Start();
	}
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if we can see the point
 */
bool CCSBot::IsVisible( const Vector &pos, bool testFOV, const CBaseEntity *ignore ) const
{
	VPROF_BUDGET( "CCSBot::IsVisible( pos )", VPROF_BUDGETGROUP_NPCS );

	// we can't see anything if we're blind
	if (IsBlind())
		return false;

	// is it in my general viewcone?
	if (testFOV && !(const_cast<CCSBot *>(this)->FInViewCone( pos )))
		return false;

	// check line of sight against smoke
	if (TheCSBots()->IsLineBlockedBySmoke( EyePositionConst(), pos ))
		return false;

	// check line of sight
	// Must include CONTENTS_MONSTER to pick up all non-brush objects like barrels
	trace_t result;
	CTraceFilterNoNPCsOrPlayer traceFilter( ignore, COLLISION_GROUP_NONE );
	UTIL_TraceLine( EyePositionConst(), pos, MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
	if (result.fraction != 1.0f)
		return false;

	return true;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if we can see any part of the player
 * Check parts in order of importance. Return the first part seen in "visPart" if it is non-NULL.
 */
bool CCSBot::IsVisible( CCSPlayer *player, bool testFOV, unsigned char *visParts ) const
{
	VPROF_BUDGET( "CCSBot::IsVisible( player )", VPROF_BUDGETGROUP_NPCS );

	// optimization - assume if center is not in FOV, nothing is
	// we're using WorldSpaceCenter instead of GUT so we can skip GetPartPosition below - that's
	// the most expensive part of this, and if we can skip it, so much the better.
	if (testFOV && !(const_cast<CCSBot *>(this)->FInViewCone( player->WorldSpaceCenter() )))
	{
		return false;
	}

	unsigned char testVisParts = NONE;

	// check gut
	Vector partPos = GetPartPosition( player, GUT );

	// finish gut check
	if (IsVisible( partPos, testFOV ))
	{
		if (visParts == NULL)
			return true;

		testVisParts |= GUT;
	}


	// check top of head
	partPos = GetPartPosition( player, HEAD );
	if (IsVisible( partPos, testFOV ))
	{
		if (visParts == NULL)
			return true;

		testVisParts |= HEAD;
	}

	// check feet
	partPos = GetPartPosition( player, FEET );
	if (IsVisible( partPos, testFOV ))
	{
		if (visParts == NULL)
			return true;

		testVisParts |= FEET;
	}

	// check "edges"
	partPos = GetPartPosition( player, LEFT_SIDE );
	if (IsVisible( partPos, testFOV ))
	{
		if (visParts == NULL)
			return true;

		testVisParts |= LEFT_SIDE;
	}

	partPos = GetPartPosition( player, RIGHT_SIDE );
	if (IsVisible( partPos, testFOV ))
	{
		if (visParts == NULL)
			return true;

		testVisParts |= RIGHT_SIDE;
	}

	if (visParts)
		*visParts = testVisParts;

	if (testVisParts)
		return true;

	return false;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Interesting part positions
 */
CCSBot::PartInfo CCSBot::m_partInfo[ MAX_PLAYERS ];

//--------------------------------------------------------------------------------------------------------------
/**
 * Compute part positions from bone location.
 */
void CCSBot::ComputePartPositions( CCSPlayer *player )
{
	const int headBox = 12;
	const int gutBox = 9;
	const int leftElbowBox = 14;
	const int rightElbowBox = 17;
	//const int hipBox = 0;
	//const int leftFootBox = 4;
	//const int rightFootBox = 8;
	const int maxBoxIndex = rightElbowBox;

	VPROF_BUDGET( "CCSBot::ComputePartPositions", VPROF_BUDGETGROUP_NPCS );

	// which PartInfo corresponds to the given player
	PartInfo *info = &m_partInfo[ player->entindex() % MAX_PLAYERS ];

	// always compute feet, since it doesn't rely on bones
	info->m_feetPos = player->GetAbsOrigin();
	info->m_feetPos.z += 5.0f;

	// get bone positions for interesting points on the player
	MDLCACHE_CRITICAL_SECTION();
	CStudioHdr *studioHdr = player->GetModelPtr();
	if (studioHdr)
	{
		mstudiohitboxset_t *set = studioHdr->pHitboxSet( player->GetHitboxSet() );
		if (set && maxBoxIndex < set->numhitboxes)
		{
			QAngle angles;
			mstudiobbox_t *box;

			// gut
			box = set->pHitbox( gutBox );
			player->GetBonePosition( box->bone, info->m_gutPos, angles );	

			// head
			box = set->pHitbox( headBox );
			player->GetBonePosition( box->bone, info->m_headPos, angles );

			Vector forward, right;
			AngleVectors( angles, &forward, &right, NULL );

			// in local bone space
			const float headForwardOffset = 4.0f;
			const float headRightOffset = 2.0f;
			info->m_headPos += headForwardOffset * forward + headRightOffset * right;

			/// @todo Fix this hack - lower the head target because it's a bit too high for the current T model
			info->m_headPos.z -= 2.0f;


			// left side
			box = set->pHitbox( leftElbowBox );
			player->GetBonePosition( box->bone, info->m_leftSidePos, angles );	

			// right side
			box = set->pHitbox( rightElbowBox );
			player->GetBonePosition( box->bone, info->m_rightSidePos, angles );	

			return;
		}
	}


	// default values if bones are not available
	info->m_headPos = GetCentroid( player );
	info->m_gutPos = info->m_headPos;
	info->m_leftSidePos = info->m_headPos;
	info->m_rightSidePos = info->m_headPos;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Return world space position of given part on player.
 * Uses hitboxes to get accurate positions.
 * @todo Optimize by computing once for each player and storing.
 */
const Vector &CCSBot::GetPartPosition( CCSPlayer *player, VisiblePartType part ) const
{
	VPROF_BUDGET( "CCSBot::GetPartPosition", VPROF_BUDGETGROUP_NPCS );

	// which PartInfo corresponds to the given player
	PartInfo *info = &m_partInfo[ player->entindex() % MAX_PLAYERS ];

	if (gpGlobals->framecount > info->m_validFrame)
	{
		// update part positions
		const_cast< CCSBot * >( this )->ComputePartPositions( player );
		info->m_validFrame = gpGlobals->framecount;
	}

	// return requested part position
	switch( part )
	{
		default:
		{
			AssertMsg( false, "GetPartPosition: Invalid part" );
			// fall thru to GUT
		}

		case GUT:
			return info->m_gutPos;

		case HEAD:
			return info->m_headPos;
			
		case FEET:
			return info->m_feetPos;

		case LEFT_SIDE:
			return info->m_leftSidePos;
			
		case RIGHT_SIDE:
			return info->m_rightSidePos;
	}
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Update desired view angles to point towards m_lookAtSpot
 */
void CCSBot::UpdateLookAt( void )
{
	Vector to = m_lookAtSpot - EyePositionConst();

	QAngle idealAngle;
	VectorAngles( to, idealAngle );

	//Vector idealAngle = UTIL_VecToAngles( to );
	//idealAngle.x = 360.0f - idealAngle.x;

	SetLookAngles( idealAngle.y, idealAngle.x );
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Look at the given point in space for the given duration (-1 means forever)
 */
void CCSBot::SetLookAt( const char *desc, const Vector &pos, PriorityType pri, float duration, bool clearIfClose, float angleTolerance, bool attack )
{
	if (IsBlind())
		return;

	// if currently looking at a point in space with higher priority, ignore this request
	if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && m_lookAtSpotPriority > pri)
		return;

	// if already looking at this spot, just extend the time
	const float tolerance = 10.0f;
	if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && VectorsAreEqual( pos, m_lookAtSpot, tolerance ))
	{
		m_lookAtSpotDuration = duration;

		if (m_lookAtSpotPriority < pri)
			m_lookAtSpotPriority = pri;
	}
	else
	{
		// look at new spot
		m_lookAtSpot = pos; 
		m_lookAtSpotState = LOOK_TOWARDS_SPOT;
		m_lookAtSpotDuration = duration;
		m_lookAtSpotPriority = pri;
	}

	m_lookAtSpotAngleTolerance = angleTolerance;
	m_lookAtSpotClearIfClose = clearIfClose;
	m_lookAtDesc = desc;
	m_lookAtSpotAttack = attack;

	PrintIfWatched( "%3.1f SetLookAt( %s ), duration = %f\n", gpGlobals->curtime, desc, duration );
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Block all "look at" and "look around" behavior for given duration - just look ahead
 */
void CCSBot::InhibitLookAround( float duration )
{
	m_inhibitLookAroundTimestamp = gpGlobals->curtime + duration;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Update enounter spot timestamps, etc
 */
void CCSBot::UpdatePeripheralVision()
{
	VPROF_BUDGET( "CCSBot::UpdatePeripheralVision", VPROF_BUDGETGROUP_NPCS );

	const float peripheralUpdateInterval = 0.29f;		// if we update at 10Hz, this ensures we test once every three
	if (gpGlobals->curtime - m_peripheralTimestamp < peripheralUpdateInterval)
		return;

	m_peripheralTimestamp = gpGlobals->curtime;

	if (m_spotEncounter)
	{
		// check LOS to all spots in case we see them with our "peripheral vision"
		const SpotOrder *spotOrder;
		Vector pos;

		FOR_EACH_VEC( m_spotEncounter->spots, it )
		{
			spotOrder = &m_spotEncounter->spots[ it ];

			const Vector &spotPos = spotOrder->spot->GetPosition();

			pos.x = spotPos.x;
			pos.y = spotPos.y;
			pos.z = spotPos.z + HalfHumanHeight;

			if (!IsVisible( pos, CHECK_FOV ))
				continue;

			// can see hiding spot, remember when we saw it last
			SetHidingSpotCheckTimestamp( spotOrder->spot );
		}
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Update the "looking around" behavior.
 */
void CCSBot::UpdateLookAround( bool updateNow )
{
	VPROF_BUDGET( "CCSBot::UpdateLookAround", VPROF_BUDGETGROUP_NPCS );

	//
	// If we recently saw an enemy, look towards where we last saw them
	// Unless we can hear them moving, in which case look towards the noise
	//
	const float closeRange = 500.0f;
	if (!IsNoiseHeard() || GetNoiseRange() > closeRange)
	{
		const float recentThreatTime = 1.0f; // 0.25f;
		if (!IsLookingAtSpot( PRIORITY_MEDIUM ) && gpGlobals->curtime - m_lastSawEnemyTimestamp < recentThreatTime)
		{
			ClearLookAt();

			Vector spot = m_lastEnemyPosition;

			// find enemy position on the ground
			if (TheNavMesh->GetSimpleGroundHeight( m_lastEnemyPosition, &spot.z ))
			{
				spot.z += HalfHumanHeight;
				SetLookAt( "Last Enemy Position", spot, PRIORITY_MEDIUM, RandomFloat( 2.0f, 3.0f ), true );
				return;
			}
		}
	}

	//
	// Look at nearby enemy noises
	//
	if (UpdateLookAtNoise())
		return;


	// check if looking around has been inhibited
	// Moved inhibit to allow high priority enemy lookats to still occur
	if (gpGlobals->curtime < m_inhibitLookAroundTimestamp)
		return;

	//
	// If we are hiding (or otherwise standing still), watch all approach points leading into this region
	//
	const float minStillTime = 2.0f;
	if (IsAtHidingSpot() || IsNotMoving( minStillTime ))
	{
		// update approach points
		const float recomputeApproachPointTolerance = 50.0f;
		if ((m_approachPointViewPosition - GetAbsOrigin()).IsLengthGreaterThan( recomputeApproachPointTolerance ))
		{
			ComputeApproachPoints();
			m_approachPointViewPosition = GetAbsOrigin();
		}

		// if we're sniping, zoom in to watch our approach points
		if (IsUsingSniperRifle())
		{
			// low skill bots don't pre-zoom
			if (GetProfile()->GetSkill() > 0.4f)
			{
				if (!IsViewMoving())
				{
					float range = ComputeWeaponSightRange();
					AdjustZoom( range );
				}
				else
				{
					// zoom out
					if (GetZoomLevel() != NO_ZOOM)
						SecondaryAttack();
				}
			}
		}

		if (m_lastKnownArea == NULL)
			return;

		if (gpGlobals->curtime < m_lookAroundStateTimestamp)
			return;

		// if we're sniping, switch look-at spots less often
		if (IsUsingSniperRifle())
			m_lookAroundStateTimestamp = gpGlobals->curtime + RandomFloat( 5.0f, 10.0f );
		else
			m_lookAroundStateTimestamp = gpGlobals->curtime + RandomFloat( 1.0f, 2.0f );	// 0.5, 1.0


		#define MAX_APPROACHES 16
		Vector validSpot[ MAX_APPROACHES ];
		int validSpotCount = 0;

		Vector *earlySpot = NULL;
		float earliest = 999999.9f;

		for( int i=0; i<m_approachPointCount; ++i )
		{
			float spotTime = m_approachPoint[i].m_area->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) );

			// ignore approach areas the enemy could not have possibly reached yet
			if (TheCSBots()->GetElapsedRoundTime() >= spotTime)
			{
				validSpot[ validSpotCount++ ] = m_approachPoint[i].m_pos;
			}
			else
			{
				// keep track of earliest spot we can see in case we get there very early
				if (spotTime < earliest)
				{
					earlySpot = &m_approachPoint[i].m_pos;
					earliest = spotTime;
				}
			}
		}

		Vector spot;

		if (validSpotCount)
		{
			int which = RandomInt( 0, validSpotCount-1 );
			spot = validSpot[ which ];
		}
		else if (earlySpot)
		{
			// all of the spots we can see can't be reached yet by the enemy - look at the earliest spot
			spot = *earlySpot;
		}
		else
		{
			return;
		}

		// don't look at the floor, look roughly at chest level
		/// @todo If this approach point is very near, this will cause us to aim up in the air if were crouching
		spot.z += HalfHumanHeight;

		SetLookAt( "Approach Point (Hiding)", spot, PRIORITY_LOW );

		return;
	}

	//
	// Glance at "encouter spots" as we move past them
	//
	if (m_spotEncounter)
	{
		//
		// Check encounter spots
		//
		if (!IsSafe() && !IsLookingAtSpot( PRIORITY_LOW ))
		{
			// allow a short time to look where we're going
			if (gpGlobals->curtime < m_spotCheckTimestamp)
				return;

			/// @todo Use skill parameter instead of accuracy

			// lower skills have exponentially longer delays
			float asleep = (1.0f - GetProfile()->GetSkill());
			asleep *= asleep;
			asleep *= asleep;

			m_spotCheckTimestamp = gpGlobals->curtime + asleep * RandomFloat( 10.0f, 30.0f );


			// figure out how far along the path segment we are
			Vector delta = m_spotEncounter->path.to - m_spotEncounter->path.from;
			float length = delta.Length();
			float adx = (float)fabs(delta.x);
			float ady = (float)fabs(delta.y);
			float t;
			Vector myOrigin = GetCentroid( this );

			if (adx > ady)
				t = (myOrigin.x - m_spotEncounter->path.from.x) / delta.x;
			else
				t = (myOrigin.y - m_spotEncounter->path.from.y) / delta.y;

			// advance parameter a bit so we "lead" our checks
			const float leadCheckRange = 50.0f;
			t += leadCheckRange / length;

			if (t < 0.0f)
				t = 0.0f;
			else if (t > 1.0f)
				t = 1.0f;

			// collect the unchecked spots so far
			#define MAX_DANGER_SPOTS 16
			HidingSpot *dangerSpot[MAX_DANGER_SPOTS];
			int dangerSpotCount = 0;
			int dangerIndex = 0;

			const float checkTime = 10.0f;
			const SpotOrder *spotOrder;
			FOR_EACH_VEC( m_spotEncounter->spots, it )
			{
				spotOrder = &(m_spotEncounter->spots[ it ]);

				// if we have seen this spot recently, we don't need to look at it
				if (gpGlobals->curtime - GetHidingSpotCheckTimestamp( spotOrder->spot ) <= checkTime)
					continue;

				if (spotOrder->t > t)
					break;

				// ignore spots the enemy could not have possibly reached yet
				if (spotOrder->spot->GetArea())
				{
					if (TheCSBots()->GetElapsedRoundTime() < spotOrder->spot->GetArea()->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) ))
					{
						continue;
					}
				}

				dangerSpot[ dangerIndex++ ] = spotOrder->spot;
				if (dangerIndex >= MAX_DANGER_SPOTS)
					dangerIndex = 0;
				if (dangerSpotCount < MAX_DANGER_SPOTS)
					++dangerSpotCount;
			}

			if (dangerSpotCount)
			{
				// pick one of the spots at random
				int which = RandomInt( 0, dangerSpotCount-1 );

				// glance at the spot for minimum time 
				SetLookAt( "Encounter Spot", dangerSpot[which]->GetPosition() + Vector( 0, 0, HalfHumanHeight ), PRIORITY_LOW, 0.2f, true, 10.0f );

				// immediately mark it as "checked", so we don't check it again
				// if we get distracted before we check it - that's the way it goes
				SetHidingSpotCheckTimestamp( dangerSpot[which] );
			}
		}
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
 * "Bend" our line of sight around corners until we can "see" the point. 
 */
bool CCSBot::BendLineOfSight( const Vector &eye, const Vector &target, Vector *bend, float angleLimit ) const
{
	VPROF_BUDGET( "CCSBot::BendLineOfSight", VPROF_BUDGETGROUP_NPCS );

	bool doDebug = false;
	const float debugDuration = 0.04f;
	if (doDebug && cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
		NDebugOverlay::Line( eye, target, 255, 255, 255, true, debugDuration );

	// if we can directly see the point, use it
	trace_t result;
	CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
	UTIL_TraceLine( eye, target, MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
	if (result.fraction == 1.0f && !result.startsolid)
	{
		// can directly see point, no bending needed
		*bend = target;
		return true;
	}

	// "bend" our line of sight until we can see the approach point
	Vector to = target - eye;
	float startAngle = UTIL_VecToYaw( to );
	float length = to.Length2D();
	to.NormalizeInPlace();

	struct Color3
	{
		int r, g, b;
	};
	const int colorCount = 6;
	Color3 colorSet[ colorCount ] =
	{
		{ 255,   0,   0 },
		{   0, 255,   0 },
		{   0,   0, 255 },
		{ 255, 255,   0 },
		{   0, 255, 255 },
		{ 255,   0, 255 },
	};

	int color = 0;

	// optiming assumption - previous rays cast "shadow" on subsequent rays since they already
	// enumerated visible space along their length.
	// We should do a dot product and compute the exact length, but since the angular changes
	// are incremental, using the direct length should be close enough.
	float priorVisibleLength[2] = { 0.0f, 0.0f };

	float angleInc = 5.0f; 
	for( float angle = angleInc; angle <= angleLimit; angle += angleInc )
	{
		// check both sides at this angle offset
		for( int side=0; side<2; ++side )
		{
			float actualAngle = (side) ? (startAngle + angle) : (startAngle - angle);

			float dx = cos( 3.141592f * actualAngle / 180.0f );
			float dy = sin( 3.141592f * actualAngle / 180.0f );

			// compute rotated point ray endpoint
			Vector rotPoint( eye.x + length * dx, eye.y + length * dy, target.z );

			// check LOS to find length to test along ray
			UTIL_TraceLine( eye, rotPoint, MASK_VISIBLE_AND_NPCS, &traceFilter, &result );

			// if this ray started in an obstacle, skip it
			if (result.startsolid)
			{
				continue;
			}

			Vector ray = rotPoint - eye;
			float rayLength = ray.NormalizeInPlace();
			float visibleLength = rayLength * result.fraction;

			if (doDebug && cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
			{
				NDebugOverlay::Line( eye, eye + visibleLength * ray, colorSet[color].r, colorSet[color].g, colorSet[color].b, true, debugDuration );
			}

			// step along ray, checking if point is visible from ray point
			const float bendStepSize = 50.0f; 

			// start from point that prior rays couldn't see
			float startLength = priorVisibleLength[ side ];

			for( float bendLength=startLength; bendLength <= visibleLength; bendLength += bendStepSize )
			{
				// compute point along ray
				Vector bendPoint = eye + bendLength * ray;

				// check if we can see approach point from this bend point
				UTIL_TraceLine( bendPoint, target, MASK_VISIBLE_AND_NPCS, &traceFilter, &result );

				if (doDebug && cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
				{
					NDebugOverlay::Line( bendPoint, result.endpos, colorSet[color].r/2, colorSet[color].g/2, colorSet[color].b/2, true, debugDuration );
				}

				if (result.fraction == 1.0f && !result.startsolid)
				{
					// target is visible from this bend point on the ray - use this point on the ray as our point

					// keep "bent" point at correct height along line of sight
					bendPoint.z = eye.z + bendLength * to.z;

					*bend = bendPoint;

					return true;
				}	
			}

			priorVisibleLength[ side ] = visibleLength;

			++color;
			if (color >= colorCount)
			{
				color = 0;
			}
		} // side
	}

	// bending rays didn't help - still can't see the point
	return false;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if we "notice" given player
 * @todo Increase chance if player is rotating
 * @todo Decrease chance as nears edge of FOV
 */
bool CCSBot::IsNoticable( const CCSPlayer *player, unsigned char visParts ) const
{
	// if this player has just fired his weapon, we notice him
	if (DidPlayerJustFireWeapon( player ))
	{
		return true;
	}

	float deltaT = m_attentionInterval.GetElapsedTime();

	// all chances are specified in terms of a standard "quantum" of time
	// in which a normal person would notice something
	const float noticeQuantum = 0.25f;

	// determine percentage of player that is visible
	float coverRatio = 0.0f;

	if (visParts & GUT)
	{
		const float chance = 40.0f;
		coverRatio += chance;
	}

	if (visParts & HEAD)
	{
		const float chance = 10.0f;
		coverRatio += chance;
	}

	if (visParts & LEFT_SIDE)
	{
		const float chance = 20.0f;
		coverRatio += chance;
	}

	if (visParts & RIGHT_SIDE)
	{
		const float chance = 20.0f;
		coverRatio += chance;
	}

	if (visParts & FEET)
	{
		const float chance = 10.0f;
		coverRatio += chance;
	}


	// compute range modifier - farther away players are harder to notice, depeding on what they are doing
	float range = (player->GetAbsOrigin() - GetAbsOrigin()).Length();
	const float closeRange = 300.0f;
	const float farRange = 1000.0f;

	float rangeModifier;
	if (range < closeRange)
	{
		rangeModifier = 0.0f;
	}
	else if (range > farRange)
	{
		rangeModifier = 1.0f;
	}
	else
	{
		rangeModifier = (range - closeRange)/(farRange - closeRange);
	}


	// harder to notice when crouched
	bool isCrouching = (player->GetFlags() & FL_DUCKING);


	// moving players are easier to spot
	float playerSpeedSq = player->GetAbsVelocity().LengthSqr();
	const float runSpeed = 200.0f;
	const float walkSpeed = 30.0f;
	float farChance, closeChance;
	if (playerSpeedSq > runSpeed * runSpeed)
	{
		// running players are always easy to spot (must be standing to run)
		return true;
	}
	else if (playerSpeedSq > walkSpeed * walkSpeed)
	{
		// walking players are less noticable far away
		if (isCrouching)
		{
			closeChance = 90.0f;
			farChance = 60.0f;
		}
		else // standing
		{
			closeChance = 100.0f;
			farChance = 75.0f;
		}
	}
	else
	{
		// motionless players are hard to notice
		if (isCrouching)
		{
			// crouching and motionless - very tough to notice
			closeChance = 80.0f;
			farChance = 5.0f;		// takes about three seconds to notice (50% chance)
		}
		else // standing
		{
			closeChance = 100.0f;
			farChance = 10.0f;
		}
	}

	// combine posture, speed, and range chances
	float dispositionChance = closeChance + (farChance - closeChance) * rangeModifier;

	// determine actual chance of noticing player
	float noticeChance = dispositionChance * coverRatio/100.0f;

	// scale by skill level
	noticeChance *= (0.5f + 0.5f * GetProfile()->GetSkill());

	// if we are alert, our chance of noticing is much higher
	if (IsAlert())
	{
		const float alertBonus = 50.0f;
		noticeChance += alertBonus;
	}

	// scale by time quantum
	noticeChance *= deltaT / noticeQuantum;

	// there must always be a chance of detecting the enemy
	const float minChance = 0.1f;
	if (noticeChance < minChance)
	{
		noticeChance = minChance;
	}

	//PrintIfWatched( "Notice chance = %3.2f\n", noticeChance );

	return (RandomFloat( 0.0f, 100.0f ) < noticeChance);
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Return most dangerous threat in my field of view (feeds into reaction time queue).
 * @todo Account for lighting levels, cover, and distance to see if we notice enemy
 */
CCSPlayer *CCSBot::FindMostDangerousThreat( void )
{
	VPROF_BUDGET( "CCSBot::FindMostDangerousThreat", VPROF_BUDGETGROUP_NPCS );

	if (IsBlind())
	{
		return NULL;
	}

	enum { MAX_THREATS = 16 };		// maximum number of simulataneously attendable threats	
	struct CloseInfo
	{
		CCSPlayer *enemy;
		float range;
	}
	threat[ MAX_THREATS ];
	int threatCount = 0;

	int prevIndex = m_enemyQueueIndex - 1;
	if ( prevIndex < 0 )
		prevIndex = MAX_ENEMY_QUEUE - 1;
	CCSPlayer *currentThreat = m_enemyQueue[ prevIndex ].player;

	m_bomber = NULL;
	m_isEnemySniperVisible = false;

	m_closestVisibleFriend = NULL;
	float closeFriendRange = 99999999999.9f;

	m_closestVisibleHumanFriend = NULL;
	float closeHumanFriendRange = 99999999999.9f;

	CCSPlayer *sniperThreat = NULL;
	float sniperThreatRange = 99999999999.9f;
	bool sniperThreatIsFacingMe = false;

	const float lookingAtMeTolerance = 0.7071f;

	int i;

	{
		VPROF_BUDGET( "CCSBot::Collect Threats", VPROF_BUDGETGROUP_NPCS );

		for( i = 1; i <= gpGlobals->maxClients; ++i )
		{
			CBaseEntity *entity = UTIL_PlayerByIndex( i );

			if (entity == NULL)
				continue;

			// is it a player?
			if (!entity->IsPlayer())
				continue;

			CCSPlayer *player = static_cast<CCSPlayer *>( entity );

			// ignore self
			if (player->entindex() == entindex())
				continue;

			// is it alive?
			if (!player->IsAlive())
				continue;

			// is it an enemy?
			if (player->InSameTeam( this ))
			{
				// keep track of nearby friends - use less exact visibility check
				if (IsVisible( entity->WorldSpaceCenter(), false, this ))
				{
					// update watch timestamp
					int idx = player->entindex();
					m_watchInfo[idx].timestamp = gpGlobals->curtime;
					m_watchInfo[idx].isEnemy = false;

					// keep track of our closest friend 
					Vector to = GetAbsOrigin() - player->GetAbsOrigin();
					float rangeSq = to.LengthSqr();
					if (rangeSq < closeFriendRange)
					{
						m_closestVisibleFriend = player;
						closeFriendRange = rangeSq;
					}

					// keep track of our closest human friend 
					if (!player->IsBot() && rangeSq < closeHumanFriendRange)
					{
						m_closestVisibleHumanFriend = player;
						closeHumanFriendRange = rangeSq;
					}
				}

				continue;
			}

			// check if this enemy is fully or partially visible
			unsigned char visParts;
			if (!IsVisible( player, CHECK_FOV, &visParts ))
				continue;

			// do we notice this enemy? (always notice current enemy)
			if (player != currentThreat)
			{
				if (!IsNoticable( player, visParts ))
				{
					continue;
				}
			}

			// update watch timestamp
			int idx = player->entindex();
			m_watchInfo[idx].timestamp = gpGlobals->curtime;
			m_watchInfo[idx].isEnemy = true;

			// note if we see the bomber
			if (player->HasC4())
			{
				m_bomber = player;
			}

			// keep track of all visible threats
			Vector d = GetAbsOrigin() - player->GetAbsOrigin();
			float distSq = d.LengthSqr();

			// track enemy sniper threats
			if (IsSniperRifle( player->GetActiveCSWeapon() ))
			{
				m_isEnemySniperVisible = true;

				// keep track of the most dangerous sniper we see
				if (sniperThreat)
				{
					if (IsPlayerLookingAtMe( player, lookingAtMeTolerance ))
					{
						if (sniperThreatIsFacingMe)
						{
							// several snipers are facing us - keep closest
							if (distSq < sniperThreatRange)
							{
								sniperThreat = player;
								sniperThreatRange = distSq;
								sniperThreatIsFacingMe = true;
							}
						}
						else
						{
							// even if this sniper is farther away, keep it because he's aiming at us
							sniperThreat = player;
							sniperThreatRange = distSq;
							sniperThreatIsFacingMe = true;
						}
					}
					else
					{
						// this sniper is not looking at us, only consider it if we dont have a sniper facing us
						if (!sniperThreatIsFacingMe && distSq < sniperThreatRange)
						{
							sniperThreat = player;
							sniperThreatRange = distSq;
						}
					}
				}
				else
				{
					// first sniper we see
					sniperThreat = player;
					sniperThreatRange = distSq;
					sniperThreatIsFacingMe = IsPlayerLookingAtMe( player, lookingAtMeTolerance );
				}
			}


			{
				VPROF_BUDGET( "CCSBot::Sort Threats", VPROF_BUDGETGROUP_NPCS );

				// maintain set of visible threats, sorted by increasing distance
				if (threatCount == 0)
				{
					threat[0].enemy = player;
					threat[0].range = distSq;
					threatCount = 1;
				}
				else
				{
					// find insertion point
					int j;
					for( j=0; j<threatCount; ++j )
					{
						if (distSq < threat[j].range)
							break;
					}

					// shift lower half down a notch
					for( int k=threatCount-1; k>=j; --k )
						threat[k+1] = threat[k];

					// insert threat into sorted list
					threat[j].enemy = player;
					threat[j].range = distSq;

					if (threatCount < MAX_THREATS)
						++threatCount;
				}
			}
		}
	}


	{
		VPROF_BUDGET( "CCSBot::Count nearby Friends & Enemies", VPROF_BUDGETGROUP_NPCS );

		// track the maximum enemy and friend counts we've seen recently
		int prevEnemies = m_nearbyEnemyCount;
		m_nearbyEnemyCount = 0;
		m_nearbyFriendCount = 0;
		for( i=0; i<MAX_PLAYERS; ++i )
		{
			if (m_watchInfo[i].timestamp <= 0.0f)
				continue;

			const float recentTime = 3.0f;
			if (gpGlobals->curtime - m_watchInfo[i].timestamp < recentTime)
			{
				if (m_watchInfo[i].isEnemy)
					++m_nearbyEnemyCount;
				else
					++m_nearbyFriendCount;
			}
		}

		// note when we saw this batch of enemies
		if (prevEnemies == 0 && m_nearbyEnemyCount > 0)
		{
			m_firstSawEnemyTimestamp = gpGlobals->curtime;
		}
	}


	{
		VPROF_BUDGET( "CCSBot::Track enemy Place", VPROF_BUDGETGROUP_NPCS );

		//
		// Track the place where we saw most of our enemies
		//
		struct PlaceRank
		{
			unsigned int place;
			int count;
		};
		static PlaceRank placeRank[ MAX_PLACES_PER_MAP ];
		int locCount = 0;

		PlaceRank common;
		common.place = 0;
		common.count = 0;

		for( i=0; i<threatCount; ++i )
		{
			// find the area the player/bot is standing on
			CNavArea *area;
			CCSBot *bot = dynamic_cast<CCSBot *>(threat[i].enemy);
			if (bot && bot->IsBot())
			{
				area = bot->GetLastKnownArea();
			}
			else
			{
				Vector enemyOrigin = GetCentroid( threat[i].enemy );
				area = TheNavMesh->GetNearestNavArea( enemyOrigin );
			}

			if (area == NULL)
				continue;

			unsigned int threatLoc = area->GetPlace();
			if (!threatLoc)
				continue;

			// if place is already in set, increment count
			int j;
			for( j=0; j<locCount; ++j )
				if (placeRank[j].place == threatLoc)
					break;

			if (j == locCount)
			{
				// new place
				if (locCount < MAX_PLACES_PER_MAP)
				{
					placeRank[ locCount ].place = threatLoc;
					placeRank[ locCount ].count = 1;

					if (common.count == 0)
						common = placeRank[locCount];

					++locCount;
				}
			}
			else
			{
				// others are in that place, increment
				++placeRank[j].count;

				// keep track of the most common place
				if (placeRank[j].count > common.count)
					common = placeRank[j];
			}
		}

		// remember most common place
		m_enemyPlace = common.place;
	}


	{
		VPROF_BUDGET( "CCSBot::Select Threat", VPROF_BUDGETGROUP_NPCS );

		if (threatCount == 0)
			return NULL;

		// if we can still see our current threat, keep it
		// unless a new one is much closer
		bool sawCloserThreat = false;
		bool sawCurrentThreat = false;
		int t;
		for( t=0; t<threatCount; ++t )
		{
			if ( threat[t].enemy == currentThreat )
			{
				sawCurrentThreat = true;
			}
			else if ( threat[t].enemy != currentThreat &&
				IsSignificantlyCloser( threat[t].enemy, currentThreat ) )
			{
				sawCloserThreat = true;
			}
		}

		if ( sawCurrentThreat && !sawCloserThreat )
		{
			return currentThreat;
		}

		// if we are a sniper and we see a sniper threat, attack it unless 
		// there are other close enemies facing me
		if (IsSniper() && sniperThreat)
		{
			const float closeCombatRange = 500.0f;

			for( t=0; t<threatCount; ++t )
			{
				if (threat[t].range < closeCombatRange && IsPlayerLookingAtMe( threat[t].enemy, lookingAtMeTolerance ))
				{
					return threat[t].enemy;
				}
			}

			return sniperThreat;
		}
		
		// otherwise, find the closest threat that is looking at me
		for( t=0; t<threatCount; ++t )
		{
			if (IsPlayerLookingAtMe( threat[t].enemy, lookingAtMeTolerance ))
			{
				return threat[t].enemy;
			}
		}
	}


	// return closest threat
	return threat[0].enemy;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Update our reaction time queue
 */
void CCSBot::UpdateReactionQueue( void )
{
	VPROF_BUDGET( "CCSBot::UpdateReactionQueue", VPROF_BUDGETGROUP_NPCS );

	// zombies dont see any threats
	if (cv_bot_zombie.GetBool())
		return;

	// find biggest threat at this instant
	CCSPlayer *threat = FindMostDangerousThreat();

	// reset timer
	m_attentionInterval.Start();


	int now = m_enemyQueueIndex;

	// store a snapshot of its state at the end of the reaction time queue
	if (threat)
	{
		m_enemyQueue[ now ].player = threat;
		m_enemyQueue[ now ].isReloading = threat->IsReloading();
		m_enemyQueue[ now ].isProtectedByShield = threat->IsProtectedByShield();
	}
	else
	{
		m_enemyQueue[ now ].player = NULL;
		m_enemyQueue[ now ].isReloading = false;
		m_enemyQueue[ now ].isProtectedByShield = false;
	}

	// queue is round-robin
	++m_enemyQueueIndex;
	if (m_enemyQueueIndex >= MAX_ENEMY_QUEUE)
		m_enemyQueueIndex = 0;
	
	if (m_enemyQueueCount < MAX_ENEMY_QUEUE)
		++m_enemyQueueCount;

	// clamp reaction time to enemy queue size
	float reactionTime = GetProfile()->GetReactionTime() - g_BotUpdateInterval;
	float maxReactionTime = (MAX_ENEMY_QUEUE * g_BotUpdateInterval) - 0.01f;
	if (reactionTime > maxReactionTime)
		reactionTime = maxReactionTime;

	// "rewind" time back to our reaction time
	int reactionTimeSteps = (int)((reactionTime / g_BotUpdateInterval) + 0.5f);

	int i = now - reactionTimeSteps;
	if (i < 0)
		i += MAX_ENEMY_QUEUE;

	m_enemyQueueAttendIndex = (unsigned char)i;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return the most dangerous threat we are "conscious" of
 */
CCSPlayer *CCSBot::GetRecognizedEnemy( void )
{
	if (m_enemyQueueAttendIndex >= m_enemyQueueCount || IsBlind())
	{
		return NULL;
	}

	return m_enemyQueue[ m_enemyQueueAttendIndex ].player;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if the enemy we are "conscious" of is reloading
 */
bool CCSBot::IsRecognizedEnemyReloading( void )
{
	if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
		return false;

	return m_enemyQueue[ m_enemyQueueAttendIndex ].isReloading;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if the enemy we are "conscious" of is hiding behind a shield
 */
bool CCSBot::IsRecognizedEnemyProtectedByShield( void )
{
	if (m_enemyQueueAttendIndex >= m_enemyQueueCount)
		return false;

	return m_enemyQueue[ m_enemyQueueAttendIndex ].isProtectedByShield;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return distance to closest enemy we are "conscious" of
 */
float CCSBot::GetRangeToNearestRecognizedEnemy( void )
{
	const CCSPlayer *enemy = GetRecognizedEnemy();

	if (enemy)
		return (GetAbsOrigin() - enemy->GetAbsOrigin()).Length();

	return 99999999.9f;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Blind the bot for the given duration
 */
void CCSBot::Blind( float holdTime, float fadeTime, float startingAlpha )
{
	PrintIfWatched( "Blinded: holdTime = %3.2f, fadeTime = %3.2f, alpha = %3.2f\n", holdTime, fadeTime, startingAlpha );

	// if we were only blinded a little bit, shake it off
	const float mildBlindTime = 3.0f;
	if (holdTime < mildBlindTime)
	{
		Wait( 0.75f * holdTime );
		BecomeAlert();
		BaseClass::Blind( holdTime, fadeTime, startingAlpha );
		return;
	}


	// if blinded while in combat - then spray and pray!
	m_blindFire = IsAttacking();

	// retreat
	// do this first, so spot selection happens before IsBlind() is set
	const float hideRange = 400.0f;
	TryToRetreat( hideRange );

	PrintIfWatched( "I'm blind!\n" );

	if (RandomFloat( 0.0f, 100.0f ) < 33.3f)
	{
		GetChatter()->Say( "Blinded", 1.0f );
	}

	// no longer safe
	AdjustSafeTime();

	// decide which way to move while blind
	m_blindMoveDir = static_cast<NavRelativeDirType>( RandomInt( 1, NUM_RELATIVE_DIRECTIONS-1 ) );

	// if we're defusing, don't give up
	if (IsDefusingBomb())
	{
		return;
	}

	// can't see to aim at enemy
	StopAiming();

	// dont override "facing away" behavior unless we are going to spray and pray
	if (m_blindFire)
	{
		ClearLookAt();

		// just look straight ahead while blind
		Vector forward;
		EyeVectors( &forward );
		SetLookAt( "Blind", EyePosition() + 10000.0f * forward, PRIORITY_UNINTERRUPTABLE, holdTime + 0.5f * fadeTime );
	}
	
	StopWaiting();
	BecomeAlert();

	BaseClass::Blind( holdTime, fadeTime, startingAlpha );
}


//--------------------------------------------------------------------------------------------------------------
class CheckLookAt
{
public:
	CheckLookAt( const CCSBot *me, bool testFOV )
	{
		m_me = me;
		m_testFOV = testFOV;
	}

	bool operator() ( CBasePlayer *player )
	{
		if (!m_me->IsEnemy( player ))
			return true;

		if (m_testFOV && !(const_cast< CCSBot * >(m_me)->FInViewCone( player->WorldSpaceCenter() )))
			return true;

		if (!m_me->IsPlayerLookingAtMe( player ))
			return true;
			
		if (m_me->IsVisible( (CCSPlayer *)player ))
			return false;

		return true;
	}

	const CCSBot *m_me;
	bool m_testFOV;
};

/**
 * Return true if any enemy I have LOS to is looking directly at me
 * @todo Use reaction time pipeline
 */
bool CCSBot::IsAnyVisibleEnemyLookingAtMe( bool testFOV ) const
{
	CheckLookAt checkLookAt( this, testFOV );
	return (ForEachPlayer( checkLookAt ) == false) ? true : false;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Do panic behavior
 */
void CCSBot::UpdatePanicLookAround( void )
{
	if (m_panicTimer.IsElapsed())
	{
		return;
	}

	if (IsEnemyVisible())
	{
		StopPanicking();
		return;
	}

	if (HasLookAtTarget())
	{
		// wait until we finish our current look at
		return;
	}

	// select a spot somewhere behind us to look at as we search for our attacker
	const QAngle &eyeAngles = EyeAngles();

	QAngle newAngles;
	newAngles.x = RandomFloat( -30.0f, 30.0f );

	// Look directly behind at a random offset in a 90 window.
	float yaw = RandomFloat( 135.0f, 225.0f );
	newAngles.y = eyeAngles.y + yaw;
	newAngles.z = 0.0f;

	Vector forward;
	AngleVectors( newAngles, &forward );

	Vector spot;
	spot = EyePosition() + 1000.0f * forward;

	SetLookAt( "Panic", spot, PRIORITY_HIGH, 0.0f );
	PrintIfWatched( "Panic yaw angle = %3.2f\n", newAngles.y );
}