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

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

#include "cbase.h"
#include "cs_bot.h"
#include "basecsgrenade_projectile.h"

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

//--------------------------------------------------------------------------------------------------------------
/**
 * Fire our active weapon towards our current enemy
 * NOTE: Aiming our weapon is handled in RunBotUpkeep()
 */
void CCSBot::FireWeaponAtEnemy( void )
{
	if (cv_bot_dont_shoot.GetBool())
	{
		return;
	}

	CBasePlayer *enemy = GetBotEnemy();
	if (enemy == NULL)
	{
		return;
	}

	Vector myOrigin = GetCentroid( this );

	if (IsUsingSniperRifle())
	{
		// if we're using a sniper rifle, don't fire until we are standing still, are zoomed in, and not rapidly moving our view
		if (!IsNotMoving() || IsWaitingForZoom() || !HasViewBeenSteady( GetProfile()->GetReactionTime() ) )
		{
			return;
		}
	}

	if (gpGlobals->curtime > m_fireWeaponTimestamp &&
		GetTimeSinceAcquiredCurrentEnemy() >= GetProfile()->GetAttackDelay() &&
		!IsSurprised())
	{
		if (!(IsRecognizedEnemyProtectedByShield() && IsPlayerFacingMe( enemy )) &&	// don't shoot at enemies behind shields
			!IsReloading() && 
			!IsActiveWeaponClipEmpty() && 
			//gpGlobals->curtime > m_reacquireTimestamp &&
			IsEnemyVisible())
		{
			// we have a clear shot - pull trigger if we are aiming at enemy
			Vector toAimSpot = m_aimSpot - EyePosition();
			float rangeToEnemy = toAimSpot.NormalizeInPlace();

			if ( IsUsingSniperRifle() )
			{
				// check our accuracy versus our target distance
				float fProjectedSpread = rangeToEnemy * GetActiveCSWeapon()->GetInaccuracy();
				float fRequiredSpread = IsUsing( WEAPON_AWP ) ? 50.0f : 25.0f;	// AWP will kill with any hit
				if ( fProjectedSpread > fRequiredSpread )
					return;
			}

			// get actual view direction vector
			Vector aimDir = GetViewVector();

			float onTarget = DotProduct( toAimSpot, aimDir );

			// aim more precisely with a sniper rifle
			// because rifles' bullets spray, don't have to be very precise
			const float halfSize = (IsUsingSniperRifle()) ? HalfHumanWidth : 2.0f * HalfHumanWidth;

			// aiming tolerance depends on how close the target is - closer targets subtend larger angles
			float aimTolerance = (float)cos( atan( halfSize / rangeToEnemy ) );

			if (onTarget > aimTolerance)
			{
				bool doAttack;

				// if friendly fire is on, don't fire if a teammate is blocking our line of fire
				if (TheCSBots()->AllowFriendlyFireDamage())
				{
					if (IsFriendInLineOfFire())
						doAttack = false;
					else
						doAttack = true;
				}
				else
				{
					// fire freely
					doAttack = true;
				}

				if (doAttack)
				{
					// if we are using a knife, only swing it if we're close
					if (IsUsingKnife())
					{
						const float knifeRange = 75.0f;		// 50
						if (rangeToEnemy < knifeRange)
						{
							// since we've given ourselves away - run!
							ForceRun( 5.0f );

							// if our prey is facing away, backstab him!
							if (!IsPlayerFacingMe( enemy ))
							{
								SecondaryAttack();
							}
							else
							{
								// randomly choose primary and secondary attacks with knife
								const float knifeStabChance = 33.3f;
								if (RandomFloat( 0, 100 ) < knifeStabChance)
									SecondaryAttack();
								else
									PrimaryAttack();
							}
						}
					}
					else
					{
						PrimaryAttack();
					}
				}

				if (IsUsingPistol())
				{
					// high-skill bots fire their pistols quickly at close range
					const float closePistolRange = 360.0f;
					if (GetProfile()->GetSkill() > 0.75f && rangeToEnemy < closePistolRange)
					{
						// fire as fast as possible
						m_fireWeaponTimestamp = 0.0f; 
					}
					else
					{
						// fire somewhat quickly
						m_fireWeaponTimestamp = RandomFloat( 0.15f, 0.4f );
					}
				}
				else	// not using a pistol
				{
					const float sprayRange = 400.0f;
					if (GetProfile()->GetSkill() < 0.5f || rangeToEnemy < sprayRange || IsUsingMachinegun())
					{
						// spray 'n pray if enemy is close, or we're not that good, or we're using the big machinegun
						m_fireWeaponTimestamp = 0.0f;
					}
					else
					{
						const float distantTargetRange = 800.0f;
						if (!IsUsingSniperRifle() && rangeToEnemy > distantTargetRange)
						{
							// if very far away, fire slowly for better accuracy
							m_fireWeaponTimestamp = RandomFloat( 0.3f, 0.7f );
						}
						else
						{
							// fire short bursts for accuracy
							m_fireWeaponTimestamp = RandomFloat( 0.15f, 0.25f );		// 0.15, 0.5
						}
					}
				}

				// subtract system latency
				m_fireWeaponTimestamp -= g_BotUpdateInterval;

				m_fireWeaponTimestamp += gpGlobals->curtime;
			}
		}
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Set the current aim offset using given accuracy (1.0 = perfect aim, 0.0f = terrible aim)
 */
void CCSBot::SetAimOffset( float accuracy )
{
	// if our accuracy is less than perfect, it will improve as we "focus in" while not rotating our view
	if (accuracy < 1.0f)
	{
		// if we moved our view, reset our "focus" mechanism
		if (IsViewMoving( 100.0f ))
			m_aimSpreadTimestamp = gpGlobals->curtime;

		// focusTime is the time it takes for a bot to "focus in" for very good aim, from 2 to 5 seconds
		const float focusTime = MAX( 5.0f * (1.0f - accuracy), 2.0f );
		float focusInterval = gpGlobals->curtime - m_aimSpreadTimestamp;

		float focusAccuracy = focusInterval / focusTime;

		// limit how much "focus" will help
		const float maxFocusAccuracy = 0.75f;
		if (focusAccuracy > maxFocusAccuracy)
			focusAccuracy = maxFocusAccuracy;

		accuracy = MAX( accuracy, focusAccuracy );
	}

	//PrintIfWatched( "Accuracy = %4.3f\n", accuracy );

	// aim error increases with distance, such that actual crosshair error stays about the same
	float range = (m_lastEnemyPosition - EyePosition()).Length();
	float maxOffset = (GetFOV()/GetDefaultFOV()) * 0.05f * range;		// 0.1
	float error = maxOffset * (1.0f - accuracy);

	m_aimOffsetGoal.x = RandomFloat( -error, error );
	m_aimOffsetGoal.y = RandomFloat( -error, error );
	m_aimOffsetGoal.z = RandomFloat( -error, error );

	// define time when aim offset will automatically be updated
	m_aimOffsetTimestamp = gpGlobals->curtime + RandomFloat( 0.25f, 1.0f ); // 0.25, 1.5f
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Wiggle aim error based on GetProfile()->GetSkill()
 */
void CCSBot::UpdateAimOffset( void )
{
	if (gpGlobals->curtime >= m_aimOffsetTimestamp)
	{
		SetAimOffset( GetProfile()->GetSkill() );
	}

	// move current offset towards goal offset
	Vector d = m_aimOffsetGoal - m_aimOffset;
	const float stiffness = 0.1f;
	m_aimOffset.x += stiffness * d.x;
	m_aimOffset.y += stiffness * d.y;
	m_aimOffset.z += stiffness * d.z;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Change our zoom level to be appropriate for the given range.
 * Return true if the zoom level changed.
 */
bool CCSBot::AdjustZoom( float range )
{
	bool adjustZoom = false;

	if (IsUsingSniperRifle())
	{
		const float sniperZoomRange = 150.0f;	// NOTE: This must be less than sniperMinRange in AttackState
		const float sniperFarZoomRange = 1500.0f;

		// if range is too close, don't zoom
		if (range <= sniperZoomRange)
		{
			// zoom out
			if (GetZoomLevel() != NO_ZOOM)
			{
				adjustZoom = true;
			}
		}
		else if (range < sniperFarZoomRange)
		{
			// maintain low zoom
			if (GetZoomLevel() != LOW_ZOOM)
			{
				adjustZoom = true;
			}
		}
		else
		{
			// maintain high zoom
			if (GetZoomLevel() != HIGH_ZOOM)
			{
				adjustZoom = true;
			}
		}
	}
	else
	{
		// zoom out
		if (GetZoomLevel() != NO_ZOOM)
		{
			adjustZoom = true;
		}
	}

	if (adjustZoom)
	{
		SecondaryAttack();

		// pause after zoom to allow "eyes" to refocus
// 		m_zoomTimer.Start( 0.25f + (1.0f - GetProfile()->GetSkill()) );
		m_zoomTimer.Start( 0.25f );
	}

	return adjustZoom;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Returns true if using the specific weapon
 */
bool CCSBot::IsUsing( CSWeaponID weaponID ) const
{
	CWeaponCSBase *weapon = GetActiveCSWeapon();

	if (weapon == NULL)
		return false;

	if (weapon->IsA( weaponID ))
		return true;

	return false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Returns true if we are using a weapon with a removable silencer
 */
bool CCSBot::DoesActiveWeaponHaveSilencer( void ) const
{
	CWeaponCSBase *weapon = GetActiveCSWeapon();

	if (weapon == NULL)
		return false;

	if (weapon->IsA( WEAPON_M4A1 ) || weapon->IsA( WEAPON_USP ))
		return true;

	return false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if we are using a sniper rifle
 */
bool CCSBot::IsUsingSniperRifle( void ) const
{
	CWeaponCSBase *weapon = GetActiveCSWeapon();

	if (weapon && IsSniperRifle( weapon ))
		return true;

	return false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if we have a sniper rifle in our inventory
 */
bool CCSBot::IsSniper( void ) const
{
	CWeaponCSBase *weapon = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );

	if (weapon && IsSniperRifle( weapon ))
		return true;

	return false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if we are actively sniping (moving to sniper spot or settled in)
 */
bool CCSBot::IsSniping( void ) const
{
	if (GetTask() == MOVE_TO_SNIPER_SPOT || GetTask() == SNIPING)
		return true;

	return false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if we are using a shotgun
 */
bool CCSBot::IsUsingShotgun( void ) const
{
	CWeaponCSBase *weapon = GetActiveCSWeapon();

	if (weapon == NULL)
		return false;

	return weapon->IsKindOf(WEAPONTYPE_SHOTGUN);
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Returns true if using the big 'ol machinegun
 */
bool CCSBot::IsUsingMachinegun( void ) const
{
	CWeaponCSBase *weapon = GetActiveCSWeapon();

	if (weapon && weapon->IsA( WEAPON_M249 ))
		return true;

	return false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if primary weapon doesn't exist or is totally out of ammo
 */
bool CCSBot::IsPrimaryWeaponEmpty( void ) const
{
	CWeaponCSBase *weapon = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );

	if (weapon == NULL)
		return true;

	// check if gun has any ammo left
	if (weapon->HasAnyAmmo())
		return false;

	return true;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if pistol doesn't exist or is totally out of ammo
 */
bool CCSBot::IsPistolEmpty( void ) const
{
	CWeaponCSBase *weapon = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_PISTOL ) );

	if (weapon == NULL)
		return true;

	// check if gun has any ammo left
	if (weapon->HasAnyAmmo())
		return false;

	return true;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Equip the given item
 */
bool CCSBot::DoEquip( CWeaponCSBase *weapon )
{
	if (weapon == NULL)
		return false;

	// check if weapon has any ammo left
	if (!weapon->HasAnyAmmo())
		return false;

	// equip it
	SelectItem( weapon->GetClassname() );
	m_equipTimer.Start();

	return true;
}


// throttle how often equipping is allowed
const float minEquipInterval = 5.0f;


//--------------------------------------------------------------------------------------------------------------
/**
 * Equip the best weapon we are carrying that has ammo
 */
void CCSBot::EquipBestWeapon( bool mustEquip )
{
	// throttle how often equipping is allowed
	if (!mustEquip && m_equipTimer.GetElapsedTime() < minEquipInterval)
		return;

	CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheBots );

	CWeaponCSBase *primary = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
	if (primary)
	{
		CSWeaponType weaponClass = primary->GetCSWpnData().m_WeaponType;

		if ((ctrl->AllowShotguns() && weaponClass == WEAPONTYPE_SHOTGUN) ||
			(ctrl->AllowMachineGuns() && weaponClass == WEAPONTYPE_MACHINEGUN) || 
			(ctrl->AllowRifles() && weaponClass == WEAPONTYPE_RIFLE) || 
			(ctrl->AllowShotguns() && weaponClass == WEAPONTYPE_SHOTGUN) ||
			(ctrl->AllowSnipers() && weaponClass == WEAPONTYPE_SNIPER_RIFLE) || 
			(ctrl->AllowSubMachineGuns() && weaponClass == WEAPONTYPE_SUBMACHINEGUN))
		{
			if (DoEquip( primary ))
				return;
		}
	}

	if (ctrl->AllowPistols())
	{
		if (DoEquip( static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_PISTOL ) ) ))
			return;
	}

	// always have a knife
	EquipKnife();
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Equip our pistol
 */
void CCSBot::EquipPistol( void )
{
	// throttle how often equipping is allowed
	if (m_equipTimer.GetElapsedTime() < minEquipInterval)
		return;

	if (TheCSBots()->AllowPistols() && !IsUsingPistol())
	{
		CWeaponCSBase *pistol = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_PISTOL ) );
		DoEquip( pistol );
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Equip the knife
 */
void CCSBot::EquipKnife( void )
{
	if (!IsUsingKnife())
	{
		SelectItem( "weapon_knife" );
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if we have a grenade in our inventory
 */
bool CCSBot::HasGrenade( void ) const
{
	CWeaponCSBase *grenade = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_GRENADES ) );
	return (grenade) ? true : false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Equip a grenade, return false if we cant
 */
bool CCSBot::EquipGrenade( bool noSmoke )
{
	// snipers don't use grenades
	if (IsSniper())
		return false;

	if (IsUsingGrenade())
		return true;

	if (HasGrenade())
	{
		CWeaponCSBase *grenade = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_GRENADES ) );

		if (noSmoke && grenade->IsA( WEAPON_SMOKEGRENADE ))
			return false;

		SelectItem( grenade->GetClassname() );

		return true;
	}

	return false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Returns true if we have knife equipped
 */
bool CCSBot::IsUsingKnife( void ) const
{
	CWeaponCSBase *weapon = GetActiveCSWeapon();

	if (weapon && weapon->IsA( WEAPON_KNIFE ))
		return true;

	return false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Returns true if we have pistol equipped
 */
bool CCSBot::IsUsingPistol( void ) const
{
	CWeaponCSBase *weapon = GetActiveCSWeapon();

	if (weapon && weapon->IsPistol())
		return true;

	return false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Returns true if we have a grenade equipped
 */
bool CCSBot::IsUsingGrenade( void ) const
{
	CWeaponCSBase *weapon = GetActiveCSWeapon();

	if (!weapon)
		return false;

	if (weapon->IsA( WEAPON_FLASHBANG ) ||
		weapon->IsA( WEAPON_SMOKEGRENADE ) ||
		weapon->IsA( WEAPON_HEGRENADE ))
		return true;

	return false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Begin the process of throwing the grenade
 */
void CCSBot::ThrowGrenade( const Vector &target )
{
	if (IsUsingGrenade() && m_grenadeTossState == NOT_THROWING && !IsOnLadder())
	{
		m_grenadeTossState = START_THROW;
		m_tossGrenadeTimer.Start( 2.0f );

		const float angleTolerance = 3.0f;
		SetLookAt( "GrenadeThrow", target, PRIORITY_UNINTERRUPTABLE, 4.0f, false, angleTolerance ); 

		Wait( RandomFloat( 2.0f, 4.0f ) );

		if (cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
		{
			NDebugOverlay::Cross3D( target, 25.0f, 255, 125, 0, true, 3.0f );
		}

		PrintIfWatched( "%3.2f: Grenade: START_THROW\n", gpGlobals->curtime );
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Returns true if our weapon can attack
 */
bool CCSBot::CanActiveWeaponFire( void ) const
{
	return ( GetActiveWeapon() && GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime );
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Find spot to throw grenade ahead of us and "around the corner" along our path
 */
bool CCSBot::FindGrenadeTossPathTarget( Vector *pos )
{
	if (!HasPath())
		return false;

	// find farthest point we can see on the path
	int i;
	for( i=m_pathIndex; i<m_pathLength; ++i )
	{
		if (!FVisible( m_path[i].pos + Vector( 0, 0, HalfHumanHeight ) ))
			break;
	}

	if (i == m_pathIndex)
		return false;

	// find exact spot where we lose sight
	Vector dir = m_path[i].pos - m_path[i-1].pos;
	float length = dir.NormalizeInPlace();

	const float inc = 25.0f;
	Vector p;
	Vector visibleSpot = m_path[i-1].pos;
	for( float t = 0.0f; t<length; t += inc )
	{
		p = m_path[i-1].pos + t * dir;
		p.z += HalfHumanHeight;
		if (!FVisible( p ))
			break;

		visibleSpot = p;
	}

	// massage the location a bit
	visibleSpot.z += 10.0f;

	const float bufferRange = 50.0f;

	trace_t result;
	Vector check;

	// check +X
	check = visibleSpot + Vector( 999.9f, 0, 0 );
	UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );

	if (result.fraction < 1.0f)
	{
		float range = result.endpos.x - visibleSpot.x;
		if (range < bufferRange)
		{
			visibleSpot.x = result.endpos.x - bufferRange;
		}
	}

	// check -X
	check = visibleSpot + Vector( -999.9f, 0, 0 );
	UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );

	if (result.fraction < 1.0f)
	{
		float range = visibleSpot.x - result.endpos.x;
		if (range < bufferRange)
		{
			visibleSpot.x = result.endpos.x + bufferRange;
		}
	}

	// check +Y
	check = visibleSpot + Vector( 0, 999.9f, 0 );
	UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );

	if (result.fraction < 1.0f)
	{
		float range = result.endpos.y - visibleSpot.y;
		if (range < bufferRange)
		{
			visibleSpot.y = result.endpos.y - bufferRange;
		}
	}

	// check -Y
	check = visibleSpot + Vector( 0, -999.9f, 0 );
	UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );

	if (result.fraction < 1.0f)
	{
		float range = visibleSpot.y - result.endpos.y;
		if (range < bufferRange)
		{
			visibleSpot.y = result.endpos.y + bufferRange;
		}
	}

	*pos = visibleSpot;
	return true;	
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Look for grenade throw targets and throw the grenade
 */
void CCSBot::LookForGrenadeTargets( void )
{
	if (!IsUsingGrenade() || IsThrowingGrenade())
	{
		return;
	}

	const CNavArea *tossArea = GetInitialEncounterArea();
	if (tossArea == NULL)
	{
		return;
	}

	int enemyTeam = OtherTeam( GetTeamNumber() );

	// check if we should put our grenade away
	if (tossArea->GetEarliestOccupyTime( enemyTeam ) > gpGlobals->curtime)
	{
		EquipBestWeapon( MUST_EQUIP );
		return;
	}

	// throw grenades at initial encounter area
	Vector tossTarget = Vector( 0, 0, 0 );
	if (!tossArea->IsVisible( EyePosition(), &tossTarget ))
	{
		return;
	}


	CWeaponCSBase *weapon = GetActiveCSWeapon();
	if (weapon && weapon->IsA( WEAPON_SMOKEGRENADE ))
	{
		// don't worry so much about smokes
		ThrowGrenade( tossTarget );
		PrintIfWatched( "Throwing smoke grenade!" );
		SetInitialEncounterArea( NULL );
		return;
	}
	else	// explosive and flashbang grenades
	{
		// initial encounter area is visible, wait to throw until timing is right

		const float leadTime = 1.5f;
		float enemyTime = tossArea->GetEarliestOccupyTime( enemyTeam );
		if (enemyTime - TheCSBots()->GetElapsedRoundTime() > leadTime)
		{
			// don't throw yet
			return;
		}


		Vector to = tossTarget - EyePosition();
		float range = to.Length();

		const float slope = 0.2f; // 0.25f;
		float tossHeight = slope * range;

		trace_t result;
		CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );

		const float heightInc = tossHeight / 10.0f;
		Vector target;
		float safeSpace = tossHeight / 2.0f;

		// Build a box to sweep along the ray when looking for obstacles
		const Vector& eyePosition = EyePosition();
		Vector mins = VEC_HULL_MIN;
		Vector maxs = VEC_HULL_MAX;
		mins.z = 0;
		maxs.z = heightInc;


		// find low and high bounds of toss window
		float low = 0.0f;
		float high = tossHeight + safeSpace;
		bool gotLow = false;
		float lastH = 0.0f;
		for( float h = 0.0f; h < 3.0f * tossHeight; h += heightInc )
		{
			target = tossTarget + Vector( 0, 0, h );

			// make sure toss line is clear

			QAngle angles( 0, 0, 0 );
			Ray_t ray;
			ray.Init( eyePosition, target, mins, maxs );
			enginetrace->TraceRay( ray, MASK_VISIBLE_AND_NPCS | CONTENTS_GRATE, &traceFilter, &result );
			if (result.fraction == 1.0f)
			{
				//NDebugOverlay::SweptBox( eyePosition, target, mins, maxs, angles, 0, 0, 255, 40, 10.0f );

				// line is clear
				if (!gotLow)
				{
					low = h;
					gotLow = true;
				}
			}
			else
			{
				//NDebugOverlay::SweptBox( eyePosition, target, mins, maxs, angles, 255, 0, 0, 5, 10.0f );

				// line is blocked
				if (gotLow)
				{
					high = lastH;
					break;
				}
			}

			lastH = h;
		}

		if (gotLow)
		{
			// throw grenade into toss window
			if (tossHeight < low)
			{
				if (low + safeSpace > high)
				{
					// narrow window
					tossHeight = (high + low)/2.0f;
				}
				else
				{
					tossHeight = low + safeSpace;
				}
			}
			else if (tossHeight > high - safeSpace)
			{
				if (high - safeSpace < low)
				{
					// narrow window
					tossHeight = (high + low)/2.0f;
				}
				else
				{
					tossHeight = high - safeSpace;
				}
			}

			ThrowGrenade( tossTarget + Vector( 0, 0, tossHeight ) );
			SetInitialEncounterArea( NULL );
			return;
		}
	}
}


//--------------------------------------------------------------------------------------------------------------
class FOVClearOfFriends
{
public:
	FOVClearOfFriends( CCSBot *me )
	{
		m_me = me;
	}

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

		if (m_me->InSameTeam( player ))
		{
			Vector to = player->EyePosition() - m_me->EyePosition();
			to.NormalizeInPlace();

			Vector forward;
			m_me->EyeVectors( &forward );

			if (DotProduct( to, forward ) > 0.95f)
			{
				if (m_me->IsVisible( (CCSPlayer *)player ))
				{
					// we see a friend in our FOV
					return false;
				}
			}
		}

		return true;
	}

	CCSBot *m_me;
};

//--------------------------------------------------------------------------------------------------------------
/**
 * Process the grenade throw state machine
 */
void CCSBot::UpdateGrenadeThrow( void )
{
	switch( m_grenadeTossState )
	{
		case START_THROW:
		{
			if (m_tossGrenadeTimer.IsElapsed())
			{
				// something prevented the throw - give up
				EquipBestWeapon( MUST_EQUIP );
				ClearLookAt();
				m_grenadeTossState = NOT_THROWING;
				PrintIfWatched( "%3.2f: Grenade: THROW FAILED\n", gpGlobals->curtime );
				return;
			}

			if (m_lookAtSpotState == LOOK_AT_SPOT)
			{
				// don't throw if there are friends ahead of us
				FOVClearOfFriends fovClear( this );
				if (ForEachPlayer( fovClear ))
				{
					m_grenadeTossState = FINISH_THROW;
					m_tossGrenadeTimer.Start( 1.0f );
					PrintIfWatched( "%3.2f: Grenade: FINISH_THROW\n", gpGlobals->curtime );
				}
				else
				{
					PrintIfWatched( "%3.2f: Grenade: Friend is in the way...\n", gpGlobals->curtime );
				}
			}

			// hold in the trigger and be ready to throw
			PrimaryAttack();

			break;
		}

		case FINISH_THROW:
		{
			// throw the grenade and hold our aiming line for a moment
			if (m_tossGrenadeTimer.IsElapsed())
			{
				ClearLookAt();

				m_grenadeTossState = NOT_THROWING;
				PrintIfWatched( "%3.2f: Grenade: THROW COMPLETE\n", gpGlobals->curtime );
			}
			break;
		}

		default:
		{
			if (IsUsingGrenade())
			{
				// pull the pin
				PrimaryAttack();
			}
			break;
		}
	}
}


//--------------------------------------------------------------------------------------------------------------
class GrenadeResponse
{
public:
	GrenadeResponse( CCSBot *me )
	{
		m_me = me;
	}

	bool operator() ( ActiveGrenade *ag ) const
	{
		const float retreatRange = 300.0f;
		const float hideTime = 1.0f;

		// do we see this grenade
		if (m_me->IsVisible( ag->GetPosition(), CHECK_FOV, (CBaseEntity *)ag->GetEntity() ))
		{
			// we see it
			if (ag->IsSmoke())
			{
				// ignore smokes
				return true;
			}

			Vector velDir = ag->GetEntity()->GetAbsVelocity();
			float grenadeSpeed = velDir.NormalizeInPlace();
			const float atRestSpeed = 50.0f;

			const float aboutToBlow = 0.5f;
			if (ag->IsFlashbang() && ag->GetEntity()->m_flDetonateTime - gpGlobals->curtime < aboutToBlow)
			{
				// turn away from flashbangs about to explode
				QAngle eyeAngles = m_me->EyeAngles();

				float yaw = RandomFloat( 100.0f, 135.0f );
				eyeAngles.y += (RandomFloat( -1.0f, 1.0f ) < 0.0f) ? (-yaw) : yaw;

				Vector forward;
				AngleVectors( eyeAngles, &forward );

				Vector away = m_me->EyePosition() - 1000.0f * forward;
				const float duration = 2.0f;

				m_me->ClearLookAt();
				m_me->SetLookAt( "Avoid Flashbang", away, PRIORITY_UNINTERRUPTABLE, duration );

				m_me->StopAiming();

				return false;
			}


			// flee from grenades if close by or thrown towards us
			const float throwDangerRange = 750.0f;
			const float nearDangerRange = 300.0f;
			Vector to = ag->GetPosition() - m_me->GetAbsOrigin();
			float range = to.NormalizeInPlace();
			if (range > throwDangerRange)
			{
				return true;
			}

			if (grenadeSpeed > atRestSpeed)
			{
				// grenade is moving
				if (DotProduct( to, velDir ) >= -0.5f)
				{
					// going away from us
					return true;
				}

				m_me->PrintIfWatched( "Retreating from a grenade thrown towards me!\n" );
			}
			else if (range < nearDangerRange)
			{
				// grenade has come to rest near us
				m_me->PrintIfWatched( "Retreating from a grenade that landed near me!\n" );
			}

			// retreat!
			m_me->TryToRetreat( retreatRange, hideTime );

			return false;
		}

		return true;
	}

	CCSBot *m_me;
};

/**
 * React to enemy grenades we see
 */
void CCSBot::AvoidEnemyGrenades( void )
{
	// low skill bots dont avoid grenades
	if (GetProfile()->GetSkill() < 0.5)
	{
		return;
	}

	if (IsAvoidingGrenade())
	{
		// already avoiding one
		return;
	}

	// low skill bots don't avoid grenades
	if (GetProfile()->GetSkill() < 0.6f)
	{
		return;
	}

	GrenadeResponse respond( this );	
	if (TheBots->ForEachGrenade( respond ) == false)
	{
		const float avoidTime = 4.0f;
		m_isAvoidingGrenade.Start( avoidTime );
	}
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Reload our weapon if we must
 */
void CCSBot::ReloadCheck( void )
{
	const float safeReloadWaitTime = 3.0f;
	const float reloadAmmoRatio = 0.6f;

	// don't bother to reload if there are no enemies left
	if (GetEnemiesRemaining() == 0)
		return;

	if (IsDefusingBomb() || IsReloading())
		return;

	if (IsActiveWeaponClipEmpty())
	{
		// high-skill players switch to pistol instead of reloading during combat
		if (GetProfile()->GetSkill() > 0.5f && IsAttacking())
		{
			if (!GetActiveCSWeapon()->IsPistol() && !IsPistolEmpty())
			{
				// switch to pistol instead of reloading
				EquipPistol();
				return;
			}
		}
	}
	else if (GetTimeSinceLastSawEnemy() > safeReloadWaitTime && GetActiveWeaponAmmoRatio() <= reloadAmmoRatio)
	{
		// high-skill players use all their ammo and switch to pistol instead of reloading during combat
		if (GetProfile()->GetSkill() > 0.5f && IsAttacking())
			return;
	}
	else
	{
		// do not need to reload
		return;
	}

	// don't reload the AWP until it is totally out of ammo
	if (IsUsing( WEAPON_AWP ) && !IsActiveWeaponClipEmpty())
		return;

	Reload();

	// move to cover to reload if there are enemies nearby
	if (GetNearbyEnemyCount())
	{
		// avoid enemies while reloading (above 0.75 skill always hide to reload)
		const float hideChance = 25.0f + 100.0f * GetProfile()->GetSkill();

		if (!IsHiding() && RandomFloat( 0, 100 ) < hideChance)
		{
			const float safeTime = 5.0f;
			if (GetTimeSinceLastSawEnemy() < safeTime)
			{
				PrintIfWatched( "Retreating to a safe spot to reload!\n" );
				const Vector *spot = FindNearbyRetreatSpot( this, 1000.0f );
				if (spot)
				{
					// ignore enemies for a second to give us time to hide
					// reaching our hiding spot clears our disposition
					IgnoreEnemies( 10.0f );

					Run();
					StandUp();
					Hide( *spot, 0.0f );
				}
			}
		}
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Silence/unsilence our weapon if we must
 */
void CCSBot::SilencerCheck( void )
{
	const float safeSilencerWaitTime = 3.5f; // longer than reload check because reloading should take precedence

	if (IsDefusingBomb() || IsReloading() || IsAttacking())
		return;

	// M4A1 and USP are the only weapons with removable silencers
	if (!DoesActiveWeaponHaveSilencer())
		return;

	if (GetTimeSinceLastSawEnemy() < safeSilencerWaitTime)
		return;

	// don't touch the silencer if there are enemies nearby
	if (GetNearbyEnemyCount() == 0)
	{
		CWeaponCSBase *weapon = GetActiveCSWeapon();
		if (weapon == NULL)
			return;

		bool isSilencerOn = weapon->IsSilenced();

		if ( weapon->m_flNextSecondaryAttack >= gpGlobals->curtime )
			return;

		// equip silencer if we want to and we don't have a shield.
		if ( isSilencerOn != (GetProfile()->PrefersSilencer() || GetProfile()->GetSkill() > 0.7f) && !HasShield() )
		{
			PrintIfWatched( "%s silencer!\n", (isSilencerOn) ? "Unequipping" : "Equipping" );
			weapon->SecondaryAttack();
		}
	}
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Invoked when in contact with a CBaseCombatWeapon
 */
bool CCSBot::BumpWeapon( CBaseCombatWeapon *pWeapon )
{
	CWeaponCSBase *droppedGun = dynamic_cast< CWeaponCSBase* >( pWeapon );

	// right now we only care about primary weapons on the ground
	if ( droppedGun && droppedGun->GetSlot() == WEAPON_SLOT_RIFLE )
	{
		CWeaponCSBase *myGun = dynamic_cast< CWeaponCSBase* >( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );

		// if the gun on the ground is the same one we have, dont bother
		if ( myGun && droppedGun->GetWeaponID() != myGun->GetWeaponID() )
		{
			// if we don't have a weapon preference, give up
			if ( GetProfile()->HasPrimaryPreference() )
			{
				// don't change weapons if we've seen enemies recently
				const float safeTime = 2.5f;
				if ( GetTimeSinceLastSawEnemy() >= safeTime )
				{
					// we have a primary weapon - drop it if the one on the ground is better
					for( int i = 0; i < GetProfile()->GetWeaponPreferenceCount(); ++i )
					{
						CSWeaponID prefID = GetProfile()->GetWeaponPreference( i );

						if (!IsPrimaryWeapon( prefID ))
							continue;

						// if the gun we are using is more desirable, give up
						if ( prefID == myGun->GetWeaponID() )
							break;

						if ( prefID == droppedGun->GetWeaponID() )
						{
							// the gun on the ground is better than the one we have - drop our gun
							DropRifle();
							break;
						}
					}
				}
			}
		}
	}

	return BaseClass::BumpWeapon( droppedGun );
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if a friend is in our weapon's way
 * @todo Check more rays for safety.
 */
bool CCSBot::IsFriendInLineOfFire( void )
{
	// compute the unit vector along our view
	Vector aimDir = GetViewVector();

	// trace the bullet's path
	trace_t result;
	UTIL_TraceLine( EyePosition(), EyePosition() + 10000.0f * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );

	if (result.DidHitNonWorldEntity())
	{
		CBaseEntity *victim = result.m_pEnt;

		if (victim && victim->IsPlayer() && victim->IsAlive())
		{
			CBasePlayer *player = static_cast<CBasePlayer *>( victim );

			if (player->InSameTeam( this ))
				return true;
		}
	}

	return false;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Return line-of-sight distance to obstacle along weapon fire ray
 * @todo Re-use this computation with IsFriendInLineOfFire()
 */
float CCSBot::ComputeWeaponSightRange( void )
{
	// compute the unit vector along our view
	Vector aimDir = GetViewVector();

	// trace the bullet's path
	trace_t result;
	UTIL_TraceLine( EyePosition(), EyePosition() + 10000.0f * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );

	return (EyePosition() - result.endpos).Length();
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if the given player just fired their weapon
 */
bool CCSBot::DidPlayerJustFireWeapon( const CCSPlayer *player ) const
{
	// if this player has just fired his weapon, we notice him
	CWeaponCSBase *weapon = player->GetActiveCSWeapon();
	return (weapon && !weapon->IsSilenced() && weapon->m_flNextPrimaryAttack > gpGlobals->curtime);
}