//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot.cpp
// Team Fortress NextBot
// Michael Booth, February 2009

#include "cbase.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_obj_sentrygun.h"
#include "team_control_point_master.h"
#include "tf_weapon_pipebomblauncher.h"
#include "team_train_watcher.h"
#include "tf_bot.h"
#include "tf_bot_manager.h"
#include "tf_bot_vision.h"
#include "tf_team.h"
#include "bot/map_entities/tf_bot_generator.h"
#include "trigger_area_capture.h"
#include "GameEventListener.h"
#include "NextBotUtil.h"
#include "tier3/tier3.h"
#include "vgui/ILocalize.h"
#include "econ_item_system.h"
#include "bot/behavior/tf_bot_use_item.h"
#include "tf_wearable_item_demoshield.h"
#include "tf_weapon_buff_item.h"
#include "tf_weapon_lunchbox.h"
#include "func_respawnroom.h"
#include "soundenvelope.h"

#include "econ_entity_creation.h"

#include "player_vs_environment/tf_population_manager.h"

#include "bot/behavior/tf_bot_behavior.h"
#include "bot/map_entities/tf_bot_generator.h"
#include "bot/map_entities/tf_bot_hint_entity.h"

ConVar tf_bot_force_class( "tf_bot_force_class", "", FCVAR_GAMEDLL, "If set to a class name, all TFBots will respawn as that class" );

ConVar tf_bot_notice_gunfire_range( "tf_bot_notice_gunfire_range", "3000", FCVAR_GAMEDLL );
ConVar tf_bot_notice_quiet_gunfire_range( "tf_bot_notice_quiet_gunfire_range", "500", FCVAR_GAMEDLL );
ConVar tf_bot_sniper_personal_space_range( "tf_bot_sniper_personal_space_range", "1000", FCVAR_CHEAT, "Enemies beyond this range don't worry the Sniper" );
ConVar tf_bot_pyro_deflect_tolerance( "tf_bot_pyro_deflect_tolerance", "0.5", FCVAR_CHEAT );
ConVar tf_bot_keep_class_after_death( "tf_bot_keep_class_after_death", "0", FCVAR_GAMEDLL );
ConVar tf_bot_prefix_name_with_difficulty( "tf_bot_prefix_name_with_difficulty", "0", FCVAR_GAMEDLL, "Append the skill level of the bot to the bot's name" );
ConVar tf_bot_near_point_travel_distance( "tf_bot_near_point_travel_distance", "750", FCVAR_CHEAT, "If within this travel distance to the current point, bot is 'near' it" );
ConVar tf_bot_pyro_shove_away_range( "tf_bot_pyro_shove_away_range", "250", FCVAR_CHEAT, "If a Pyro bot's target is closer than this, compression blast them away" );
ConVar tf_bot_pyro_always_reflect( "tf_bot_pyro_always_reflect", "0", FCVAR_CHEAT, "Pyro bots will always reflect projectiles fired at them. For tesing/debugging purposes." );

ConVar tf_bot_sniper_spot_min_range( "tf_bot_sniper_spot_min_range", "1000", FCVAR_CHEAT );
ConVar tf_bot_sniper_spot_max_count( "tf_bot_sniper_spot_max_count", "10", FCVAR_CHEAT, "Stop searching for sniper spots when each side has found this many" );
ConVar tf_bot_sniper_spot_search_count( "tf_bot_sniper_spot_search_count", "10", FCVAR_CHEAT, "Search this many times per behavior update frame" );
ConVar tf_bot_sniper_spot_point_tolerance( "tf_bot_sniper_spot_point_tolerance", "750", FCVAR_CHEAT );
ConVar tf_bot_sniper_spot_epsilon( "tf_bot_sniper_spot_epsilon", "100", FCVAR_CHEAT );

ConVar tf_bot_sniper_goal_entity_move_tolerance( "tf_bot_sniper_goal_entity_move_tolerance", "500", FCVAR_CHEAT );

ConVar tf_bot_suspect_spy_touch_interval( "tf_bot_suspect_spy_touch_interval", "5", FCVAR_CHEAT, "How many seconds back to look for touches against suspicious spies" );
ConVar tf_bot_suspect_spy_forget_cooldown( "tf_bot_suspect_spy_forget_cooldown", "5", FCVAR_CHEAT, "How long to consider a suspicious spy as suspicious" );

ConVar tf_bot_debug_tags( "tf_bot_debug_tags", "0", FCVAR_CHEAT, "ent_text will only show tags on bots" );

extern ConVar tf_bot_sniper_spot_max_count;
extern ConVar tf_bot_fire_weapon_min_time;
extern ConVar tf_bot_sniper_misfire_chance;
extern ConVar tf_bot_difficulty;
extern ConVar tf_bot_farthest_visible_theater_sample_count;
extern ConVar tf_bot_sniper_spot_min_range;
extern ConVar tf_bot_sniper_spot_epsilon;
extern ConVar tf_mvm_miniboss_min_health;
extern ConVar tf_bot_path_lookahead_range;

extern ConVar tf_mvm_miniboss_scale;


//-----------------------------------------------------------------------------------------------------
bool IsPlayerClassname( const char *string )
{
	for ( int i = TF_CLASS_SCOUT; i < TF_CLASS_COUNT_ALL; ++i )
	{
		if ( !stricmp( string, GetPlayerClassData( i )->m_szClassName ) )
		{
			return true;
		}
	}

	return false;
}


//-----------------------------------------------------------------------------------------------------
bool IsTeamName( const char *string )
{
	if ( !stricmp( string, "red" ) )
		return true;

	if ( !stricmp( string, "blue" ) )
		return true;

	return false;
}


//-----------------------------------------------------------------------------------------------------
CTFBot::DifficultyType StringToDifficultyLevel( const char *string )
{
	if ( !stricmp( string, "easy" ) )
		return CTFBot::EASY;

	if ( !stricmp( string, "normal" ) )
		return CTFBot::NORMAL;

	if ( !stricmp( string, "hard" ) )
		return CTFBot::HARD;

	if ( !stricmp( string, "expert" ) )
		return CTFBot::EXPERT;

	return CTFBot::UNDEFINED;
}


//-----------------------------------------------------------------------------------------------------
const char *DifficultyLevelToString( CTFBot::DifficultyType skill )
{
	switch( skill )
	{
	case CTFBot::EASY:		return "Easy ";
	case CTFBot::NORMAL:	return "Normal ";
	case CTFBot::HARD:		return "Hard ";
	case CTFBot::EXPERT:	return "Expert ";
	}

	return "Undefined ";
}


//-----------------------------------------------------------------------------------------------------
const char *GetRandomBotName( void )
{
	static const char *nameList[] =
	{
		"Chucklenuts",
		"CryBaby",
		"WITCH",
		"ThatGuy",
		"Still Alive",
		"Hat-Wearing MAN",
		"Me",
		"Numnutz",
		"H@XX0RZ",
		"The G-Man",
		"Chell",
		"The Combine",
		"Totally Not A Bot",
		"Pow!",
		"Zepheniah Mann",
		"THEM",
		"LOS LOS LOS",
		"10001011101",
		"DeadHead",
		"ZAWMBEEZ",
		"MindlessElectrons",
		"TAAAAANK!",
		"The Freeman",
		"Black Mesa",
		"Soulless",
		"CEDA",
		"BeepBeepBoop",
		"NotMe",
		"CreditToTeam",
		"BoomerBile",
		"Someone Else",
		"Mann Co.",
		"Dog",
		"Kaboom!",
		"AmNot",
		"0xDEADBEEF",
		"HI THERE",
		"SomeDude",
		"GLaDOS",
		"Hostage",
		"Headful of Eyeballs",
		"CrySomeMore",
		"Aperture Science Prototype XR7",
		"Humans Are Weak",
		"AimBot",
		"C++",
		"GutsAndGlory!",
		"Nobody",
		"Saxton Hale",
		"RageQuit",
		"Screamin' Eagles",

		"Ze Ubermensch",
		"Maggot",
		"CRITRAWKETS",
		"Herr Doktor",
		"Gentlemanne of Leisure",
		"Companion Cube",
		"Target Practice",
		"One-Man Cheeseburger Apocalypse",
		"Crowbar",
		"Delicious Cake",
		"IvanTheSpaceBiker",
		"I LIVE!",
		"Cannon Fodder",

		"trigger_hurt",
		"Nom Nom Nom",
		"Divide by Zero",
		"GENTLE MANNE of LEISURE",
		"MoreGun",
		"Tiny Baby Man",
		"Big Mean Muther Hubbard",
		"Force of Nature",

		"Crazed Gunman",
		"Grim Bloody Fable",
		"Poopy Joe",
		"A Professional With Standards",
		"Freakin' Unbelievable",
		"SMELLY UNFORTUNATE",
		"The Administrator",
		"Mentlegen",

		"Archimedes!",
		"Ribs Grow Back",
		"It's Filthy in There!",
		"Mega Baboon",
		"Kill Me",
		"Glorified Toaster with Legs",

#ifdef STAGING_ONLY
		"John Spartan",
		"Leeloo Dallas Multipass",
		"Sho'nuff",
		"Bruce Leroy",
		"CAN YOUUUUUUUUU DIG IT?!?!?!?!",
		"Big Gulp, Huh?",
		"Stupid Hot Dog",
		"I'm your huckleberry",
		"The Crocketeer",
#endif
		NULL
	};
	static int nameCount = 0;
	static int nameIndex = 0;

	if ( nameCount == 0 )
	{
		for( ; nameList[ nameCount ]; ++nameCount );

		// randomize the initial index
		nameIndex = RandomInt( 0, nameCount-1 );
	}

	const char *name = nameList[ nameIndex++ ];

	if ( nameIndex >= nameCount )
		nameIndex = 0;

	return name;
}


//-----------------------------------------------------------------------------------------------------
void CreateBotName( int iTeam, int iClassIndex, CTFBot::DifficultyType skill, char* pBuffer, int iBufferSize )
{
	char szBotNameBuffer[256];
	char szEnemyOrFriendlyString[256];

	const char *pBotName = "";
	const char *pFriendlyOrEnemyTitle = "";

	// @note (Tom Bui): it is okay to get localized name in training, since we should be on a listen server
	if ( TFGameRules()->IsInTraining() )
	{
		// get the friendly/enemy title
		const char *pBotTitle = NULL;
		if ( iTeam != TEAM_UNASSIGNED )
		{
			int iHumanTeam = TFGameRules()->GetAssignedHumanTeam();
			if ( iHumanTeam != TEAM_ANY )
			{
				if ( iHumanTeam == iTeam )
				{
					pBotTitle = "#TF_Bot_Title_Friendly";
				}
				else
				{
					pBotTitle = "#TF_Bot_Title_Enemy";
				}
			}
		}
		wchar_t *pLocalizedTitle = pBotTitle ? g_pVGuiLocalize->Find( pBotTitle ) : NULL;
		if ( pLocalizedTitle )
		{
			g_pVGuiLocalize->ConvertUnicodeToANSI( pLocalizedTitle, szEnemyOrFriendlyString, sizeof( szEnemyOrFriendlyString ) );
			pFriendlyOrEnemyTitle = szEnemyOrFriendlyString;
		}

		// get the class name
		wchar_t *pLocalizedName = NULL;
		if ( iClassIndex >= TF_FIRST_NORMAL_CLASS && iClassIndex < TF_LAST_NORMAL_CLASS )
		{
			pLocalizedName = g_pVGuiLocalize->Find( g_aPlayerClassNames[ iClassIndex ] );
		}
		else
		{
			pLocalizedName = g_pVGuiLocalize->Find( "#TF_Bot_Generic_ClassName" );
		}
		g_pVGuiLocalize->ConvertUnicodeToANSI( pLocalizedName, szBotNameBuffer, sizeof( szBotNameBuffer ) );
		pBotName = szBotNameBuffer;
	}
	else
	{
		pBotName = GetRandomBotName();
	}
	
	const char *pDifficultyString = tf_bot_prefix_name_with_difficulty.GetBool() ? DifficultyLevelToString( skill ) : "";

	// we use this as our formatting, because we don't know the language of the downstream clients
	CFmtStr name( "%s%s%s", 
				  pDifficultyString, pFriendlyOrEnemyTitle, pBotName );
	Q_strncpy( pBuffer, name.Access(), iBufferSize );
}


//-----------------------------------------------------------------------------------------------------
CON_COMMAND_F( tf_bot_add, "Add a bot.", FCVAR_GAMEDLL )
{
	// Listenserver host or rcon access only!
	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return;

	bool bQuotaManaged = true;
	int botCount = 1;
	const char *classname = NULL;
	const char *teamname = "auto";
	const char *pszBotNameViaArg = NULL;
	CTFBot::DifficultyType skill = clamp( (CTFBot::DifficultyType)tf_bot_difficulty.GetInt(), CTFBot::EASY, CTFBot::EXPERT );

	int i;
	for( i=1; i<args.ArgC(); ++i )
	{
		CTFBot::DifficultyType trySkill = StringToDifficultyLevel( args.Arg(i) );
		int nArgAsInteger = atoi( args.Arg(i) );

		// each argument could be a classname, a team, a difficulty level, a count, or a name
		if ( IsPlayerClassname( args.Arg(i) ) )
		{
			classname = args.Arg(i);
		}
		else if ( IsTeamName( args.Arg(i) ) )
		{
			teamname = args.Arg(i);
		}
		else if ( !stricmp( args.Arg( i ), "noquota" ) )
		{
			bQuotaManaged = false;
		}
		else if ( trySkill != CTFBot::UNDEFINED )
		{
			skill = trySkill;
		}
		else if ( nArgAsInteger > 0 )
		{
			botCount = nArgAsInteger;
			pszBotNameViaArg = NULL; // can't have a custom name if spawning multiple bots
		}
		else if ( botCount == 1 )
		{
			pszBotNameViaArg = args.Arg( i );
		}
		else
		{
			Warning( "Invalid argument '%s'\n", args.Arg(i) );
		}
	}

	// cvar can override classname
	classname = FStrEq( tf_bot_force_class.GetString(), "" ) ? classname : tf_bot_force_class.GetString();
	int iClassIndex = classname ? GetClassIndexFromString( classname ) : TF_CLASS_UNDEFINED;

	int iTeam = TEAM_UNASSIGNED;
	if ( FStrEq( teamname, "red" ) )
	{
		iTeam = TF_TEAM_RED;
	}
	else if ( FStrEq( teamname, "blue" ) )
	{
		iTeam = TF_TEAM_BLUE;
	}

	if ( TFGameRules()->IsInTraining() )
	{
		skill = CTFBot::EASY;
	}
	
	char name[256];
	int iNumAdded = 0;
	for( i=0; i<botCount; ++i )
	{
		CTFBot *pBot = NULL;
		const char *pszBotName = NULL;

		if ( !pszBotNameViaArg )
		{
			CreateBotName( iTeam, iClassIndex, skill, name, sizeof(name) );
			pszBotName = name;
		}
		else
		{
			pszBotName = pszBotNameViaArg;
		}

		pBot = NextBotCreatePlayerBot< CTFBot >( pszBotName );

		if ( pBot ) 
		{
			if ( bQuotaManaged )
			{
				pBot->SetAttribute( CTFBot::QUOTA_MANANGED );
			}

			pBot->HandleCommand_JoinTeam( teamname );

			pBot->SetDifficulty( skill );

			// if no class is set, auto-select one
			const char *thisClassname = classname ? classname : pBot->GetNextSpawnClassname();
			pBot->HandleCommand_JoinClass( thisClassname );

			// set up a proper name now that we are in training
			if ( TFGameRules()->IsInTraining() )
			{
				CreateBotName( pBot->GetTeamNumber(), pBot->GetPlayerClass()->GetClassIndex(), skill, name, sizeof(name) );
				engine->SetFakeClientConVarValue( pBot->edict(), "name", name );
			}

			++iNumAdded;
		}
	}

	if ( bQuotaManaged )
	{
		TheTFBots().OnForceAddedBots( iNumAdded );
	}
}


//-----------------------------------------------------------------------------------------------------
CON_COMMAND_F( tf_bot_kick, "Remove a TFBot by name, or all bots (\"all\").", FCVAR_GAMEDLL )
{
	// Listenserver host or rcon access only!
	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return;

	if ( args.ArgC() < 2 )
	{
		DevMsg( "%s <bot name>, \"red\", \"blue\", or \"all\"> <optional: \"moveToSpectatorTeam\"> \n", args.Arg(0) );
		return;
	}

	bool bMoveToSpectatorTeam = false;
	int iTeam = TEAM_UNASSIGNED;
	int i;
	const char *pPlayerName = "";
	for( i=1; i<args.ArgC(); ++i )
	{
		// each argument could be a classname, a team, or a count
		if ( FStrEq( args.Arg(i), "red" ) )
		{
			iTeam = TF_TEAM_RED;
		}
		else if ( FStrEq( args.Arg(i), "blue" ) )
		{
			iTeam = TF_TEAM_BLUE;
		}
		else if ( FStrEq( args.Arg(i), "all" ) )
		{
			iTeam = TEAM_ANY;
		}
		else if ( FStrEq( args.Arg(i), "moveToSpectatorTeam" ) )
		{
			bMoveToSpectatorTeam = true;
		}
		else 
		{
			pPlayerName = args.Arg(i);
		}
	}

	int iNumKicked = 0;
	for( int i=1; i<=gpGlobals->maxClients; ++i )
	{
		CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );

		if ( !player )
			continue;

		if ( FNullEnt( player->edict() ) )
			continue;

		if ( player->MyNextBotPointer() )
		{
			if ( iTeam == TEAM_ANY ||
				 FStrEq( pPlayerName, player->GetPlayerName() ) ||
				 ( player->GetTeamNumber() == iTeam ) ||
				 ( player->GetTeamNumber() == iTeam ) )
			{
				if ( bMoveToSpectatorTeam )
				{
					player->ChangeTeam( TEAM_SPECTATOR, false, true );
				}
				else
				{
					engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", player->GetUserID() ) );
				}
				CTFBot* pBot = dynamic_cast< CTFBot* >( player );
				if ( pBot && pBot->HasAttribute( CTFBot::QUOTA_MANANGED ) )
				{
					++iNumKicked;
				}				
			}
		}
	}
	TheTFBots().OnForceKickedBots( iNumKicked );
}


//-----------------------------------------------------------------------------------------------------
CON_COMMAND_F( tf_bot_kill, "Kill a TFBot by name, or all bots (\"all\").", FCVAR_GAMEDLL )
{
	// Listenserver host or rcon access only!
	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return;

	if ( args.ArgC() < 2 )
	{
		DevMsg( "%s <bot name>, \"red\", \"blue\", or \"all\"> <optional: \"moveToSpectatorTeam\"> \n", args.Arg(0) );
		return;
	}

	int iTeam = TEAM_UNASSIGNED;
	int i;
	const char *pPlayerName = "";
	for( i=1; i<args.ArgC(); ++i )
	{
		// each argument could be a classname, a team, or a count
		if ( FStrEq( args.Arg(i), "red" ) )
		{
			iTeam = TF_TEAM_RED;
		}
		else if ( FStrEq( args.Arg(i), "blue" ) )
		{
			iTeam = TF_TEAM_BLUE;
		}
		else if ( FStrEq( args.Arg(i), "all" ) )
		{
			iTeam = TEAM_ANY;
		}
		else if ( FStrEq( args.Arg(i), "moveToSpectatorTeam" ) )
		{
			// bMoveToSpectatorTeam = true;
		}
		else 
		{
			pPlayerName = args.Arg(i);
		}
	}

	for( int i=1; i<=gpGlobals->maxClients; ++i )
	{
		CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );

		if ( !player )
			continue;

		if ( FNullEnt( player->edict() ) )
			continue;

		if ( player->MyNextBotPointer() )
		{
			if ( iTeam == TEAM_ANY ||
				FStrEq( pPlayerName, player->GetPlayerName() ) ||
				( player->GetTeamNumber() == iTeam ) ||
				( player->GetTeamNumber() == iTeam ) )
			{
				CTakeDamageInfo info( player, player, 9999999.9f, DMG_ENERGYBEAM, TF_DMG_CUSTOM_NONE );
				player->TakeDamage( info );
			}
		}
	}
}

//-----------------------------------------------------------------------------------------------------
void CMD_BotWarpTeamToMe( void )
{
	CBasePlayer *player = UTIL_GetListenServerHost();
	if ( !player )
		return;

	CTeam *myTeam = player->GetTeam();
	for( int i=0; i<myTeam->GetNumPlayers(); ++i )
	{
		if ( !myTeam->GetPlayer(i)->IsAlive() )
			continue;

		myTeam->GetPlayer(i)->SetAbsOrigin( player->GetAbsOrigin() );
	}
}
static ConCommand tf_bot_warp_team_to_me( "tf_bot_warp_team_to_me", CMD_BotWarpTeamToMe, "", FCVAR_GAMEDLL | FCVAR_CHEAT );


//-----------------------------------------------------------------------------------------------------
IMPLEMENT_INTENTION_INTERFACE( CTFBot, CTFBotMainAction );


//-----------------------------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( tf_bot, CTFBot );


//-----------------------------------------------------------------------------------------------------
/**
 * Allocate a bot and bind it to the edict
 */
CBasePlayer *CTFBot::AllocatePlayerEntity( edict_t *edict, const char *playerName )
{
	CBasePlayer::s_PlayerEdict = edict;
	return static_cast< CBasePlayer * >( CreateEntityByName( "tf_bot" ) );
}


//-----------------------------------------------------------------------------------------------------
void CTFBot::PressFireButton( float duration )
{
	// can't fire if stunned
	// @todo Tom Bui: Eventually, we'll probably want to check the actual weapon for supress fire
	if ( m_Shared.IsControlStunned() || m_Shared.IsLoserStateStunned() || HasAttribute( CTFBot::SUPPRESS_FIRE ) )
	{
		ReleaseFireButton();
		return;
	}

	BaseClass::PressFireButton( duration );
}


//-----------------------------------------------------------------------------------------------------
void CTFBot::PressAltFireButton( float duration )
{
	// can't fire if stunned
	// @todo Tom Bui: Eventually, we'll probably want to check the actual weapon for supress fire
	if ( m_Shared.IsControlStunned() || m_Shared.IsLoserStateStunned() || HasAttribute( CTFBot::SUPPRESS_FIRE ) )
	{
		ReleaseAltFireButton();
		return;
	}

	BaseClass::PressAltFireButton( duration );
}


//-----------------------------------------------------------------------------------------------------
void CTFBot::PressSpecialFireButton( float duration )
{
	// can't fire if stunned
	// @todo Tom Bui: Eventually, we'll probably want to check the actual weapon for supress fire
	if ( m_Shared.IsControlStunned() || m_Shared.IsLoserStateStunned() || HasAttribute( CTFBot::SUPPRESS_FIRE ) )
	{
		ReleaseAltFireButton();
		return;
	}

	BaseClass::PressSpecialFireButton( duration );
}


//-----------------------------------------------------------------------------------------------------
class CCountClassMembers
{
public:
	CCountClassMembers( const CTFBot *me, int teamID )
	{
		m_me = me;
		m_myTeam = teamID;
		m_teamSize = 0;
		
		for( int i=0; i<TF_LAST_NORMAL_CLASS; ++i )
			m_count[i] = 0;
	}

	bool operator() ( CBasePlayer *basePlayer )
	{
		CTFPlayer *player = (CTFPlayer *)basePlayer;

		if ( player->GetTeamNumber() != m_myTeam )
			return true;

		++m_teamSize;

		if ( m_me->IsSelf( player ) )
			return true;

		++m_count[ player->GetDesiredPlayerClassIndex() ];

		return true;
	}

	const CTFBot *m_me;
	int m_myTeam;
	int m_count[ TF_LAST_NORMAL_CLASS+1 ];
	int m_teamSize;
};


//-----------------------------------------------------------------------------------------------------
/**
 * NOTE: Assumes bot's difficulty has been set, and the bot is on a team.
 */
const char *CTFBot::GetNextSpawnClassname( void ) const
{
	struct ClassSelectionInfo
	{
		int m_class;
		int m_minTeamSizeToSelect;					// team must have this many members to choose this class
		int m_countPerTeamSize;						// must have 1 Medic for each 4 team members, for example
		int m_minLimit;								// minimum that must be present (once other constraints are met)
		int m_maxLimit[ NUM_DIFFICULTY_LEVELS ];	// maximum that can be present (-1 for infinite)
	};

	const int NoLimit = -1;

	static ClassSelectionInfo defenseRoster[] = 
	{
		{ TF_CLASS_ENGINEER,		0, 4, 1, { 1, 2, 3, 3 } },
		{ TF_CLASS_SOLDIER,			0, 0, 0, { NoLimit, NoLimit, NoLimit, NoLimit } },
		{ TF_CLASS_DEMOMAN,			0, 0, 0, { 2, 3, 3, 3 } },
		{ TF_CLASS_PYRO,			3, 0, 0, { NoLimit, NoLimit, NoLimit, NoLimit } },
		{ TF_CLASS_HEAVYWEAPONS,	3, 0, 0, { 1, 1, 2, 2 } },
		{ TF_CLASS_MEDIC,			4, 4, 1, { 1, 1, 2, 2 } },
		{ TF_CLASS_SNIPER,			5, 0, 0, { 0, 1, 1, 1 } },
		{ TF_CLASS_SPY,				5, 0, 0, { 0, 1, 2, 2 } },

		{ TF_CLASS_UNDEFINED,		0, -1 },
	};

	static ClassSelectionInfo offenseRoster[] = 
	{
		{ TF_CLASS_SCOUT,			0, 0, 1, { 3, 3, 3, 3 } },
		{ TF_CLASS_SOLDIER,			0, 0, 0, { NoLimit, NoLimit, NoLimit, NoLimit } },
		{ TF_CLASS_DEMOMAN,			0, 0, 0, { 2, 3, 3, 3 } },							// must limit demomen, or the whole team will go demo to take out tough sentryguns
		{ TF_CLASS_PYRO,			3, 0, 0, { NoLimit, NoLimit, NoLimit, NoLimit } },
		{ TF_CLASS_HEAVYWEAPONS,	3, 0, 0, { 1, 1, 2, 2 } },
		{ TF_CLASS_MEDIC,			4, 4, 1, { 1, 1, 2, 2 } },
		{ TF_CLASS_SNIPER,			5, 0, 0, { 0, 1, 1, 1 } },
		{ TF_CLASS_SPY,				5, 0, 0, { 0, 1, 2, 2 } },
		{ TF_CLASS_ENGINEER,		5, 0, 0, { 1, 1, 1, 1 } },

		{ TF_CLASS_UNDEFINED,		0, -1 },
	};

	static ClassSelectionInfo compRoster[] =
	{
		{ TF_CLASS_SCOUT,			0, 0, 0, { 0, 0, 2, 2 } },
		{ TF_CLASS_SOLDIER,			0, 0, 0, { 0, 0, NoLimit, NoLimit } },
		{ TF_CLASS_DEMOMAN,			0, 0, 0, { 0, 0, 2, 2 } },							// must limit demomen, or the whole team will go demo to take out tough sentryguns
		{ TF_CLASS_PYRO,			0, -1 },
		{ TF_CLASS_HEAVYWEAPONS,	3, 0, 0, { 0, 0, 2, 2 } },
		{ TF_CLASS_MEDIC,			1, 0, 1, { 0, 0, 1, 1 } },
		{ TF_CLASS_SNIPER,			0, -1 },
		{ TF_CLASS_SPY,				0, -1 },
		{ TF_CLASS_ENGINEER,		0, -1 },

		{ TF_CLASS_UNDEFINED,		0, -1 },
	};

	// if we are an engineer with an active sentry or teleporters, don't switch
	if ( IsPlayerClass( TF_CLASS_ENGINEER ) )
	{
		if ( const_cast< CTFBot * >( this )->GetObjectOfType( OBJ_SENTRYGUN ) ||
			 const_cast< CTFBot * >( this )->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT ) )
		{
			return "engineer";
		}
	}

	// count classes in use by my team, not including me
	CCountClassMembers currentRoster( this, GetTeamNumber() );
	ForEachPlayer( currentRoster );

	// assume offense
	ClassSelectionInfo *desiredRoster = offenseRoster;
	
	if ( TFGameRules()->IsMatchTypeCompetitive() )
	{
		desiredRoster = compRoster;
	}
	else if ( TFGameRules()->IsInKothMode() )
	{
		CTeamControlPoint *point = GetMyControlPoint();
		if ( point )
		{
			if ( GetTeamNumber() == ObjectiveResource()->GetOwningTeam( point->GetPointIndex() ) )
			{
				// defend our point
				desiredRoster = defenseRoster;
			}
		}
	}
	else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP )
	{
		CUtlVector< CTeamControlPoint * > captureVector;
		TFGameRules()->CollectCapturePoints( const_cast< CTFBot * >( this ), &captureVector );

		CUtlVector< CTeamControlPoint * > defendVector;
		TFGameRules()->CollectDefendPoints( const_cast< CTFBot * >( this ), &defendVector );

		// if we have any points we can capture, try to do so
		if ( captureVector.Count() > 0 || defendVector.Count() == 0 )
		{
			desiredRoster = offenseRoster;
		}
		else
		{
			desiredRoster = defenseRoster;
		}
	}
	else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT )
	{
		if ( GetTeamNumber() == TF_TEAM_RED )
		{
			desiredRoster = defenseRoster;
		}
	}

	// build vector of classes we can pick from
	CUtlVector< int > desiredClassVector;
	CUtlVector< int > allowedClassForBotRosterVector;

	for( int i=0; desiredRoster[ i ].m_class != TF_CLASS_UNDEFINED; ++i )
	{
		ClassSelectionInfo *desiredClassInfo = &desiredRoster[ i ];

		if ( TFGameRules()->CanBotChooseClass( const_cast< CTFBot * >( this ), desiredClassInfo->m_class ) == false )
		{
			// not allowed to use this class
			continue;
		}
		// just in case we hit the class limits, we want to make sure we select a class that is allowed
		allowedClassForBotRosterVector.AddToTail( desiredClassInfo->m_class );

		if ( currentRoster.m_teamSize < desiredClassInfo->m_minTeamSizeToSelect )
		{
			// team is too small to choose this class
			continue;
		}

		// check limits
		if ( currentRoster.m_count[ desiredClassInfo->m_class ] < desiredClassInfo->m_minLimit )
		{
			// below required limit - choose only this class
			desiredClassVector.RemoveAll();
			desiredClassVector.AddToTail( desiredClassInfo->m_class );
			break;
		}

		int maxLimit = desiredClassInfo->m_maxLimit[ (int)clamp( GetDifficulty(), CTFBot::EASY, CTFBot::EXPERT ) ];

		if ( maxLimit > NoLimit && currentRoster.m_count[ desiredClassInfo->m_class ] >= maxLimit )
		{
			// at or above limit for this class
			continue;
		}

		if ( desiredClassInfo->m_countPerTeamSize > 0 )
		{
			// how many of this class should there be at the given "per" count
			int maxCountPer = currentRoster.m_teamSize / desiredClassInfo->m_countPerTeamSize;
			if ( currentRoster.m_count[ desiredClassInfo->m_class ] - desiredClassInfo->m_minTeamSizeToSelect < maxCountPer )
			{
				// below required limit - choose only this class
				desiredClassVector.RemoveAll();
				desiredClassVector.AddToTail( desiredClassInfo->m_class );
				break;
			}
		}

		// valid class to choose
		desiredClassVector.AddToTail( desiredClassInfo->m_class );
	}

	if ( desiredClassVector.Count() == 0 )
	{
		if ( allowedClassForBotRosterVector.Count() == 0 )
		{
			// nothing available
			Warning( "TFBot unable to choose a class, defaulting to 'auto'\n" );
			return "auto";
		}
		else
		{
			desiredClassVector = allowedClassForBotRosterVector;
		}
	}

	int which = RandomInt( 0, desiredClassVector.Count()-1 );

	// if we need to destroy a sentry, pick a class that can do so
	if ( GetEnemySentry() ) 
	{
		// best sentry demolitions
		int demoman = desiredClassVector.Find( TF_CLASS_DEMOMAN );
		if ( demoman >= 0 )
		{
			which = demoman;
		}
		else
		{
			// next best sentry demolitions
			int spy = desiredClassVector.Find( TF_CLASS_SPY );
			if ( spy >= 0 )
			{
				which = spy;
			}
			else
			{
				// good sentry demolitions
				int soldier = desiredClassVector.Find( TF_CLASS_SOLDIER );
				if ( soldier >= 0 )
				{
					which = soldier;
				}
			}
		}
	}

	TFPlayerClassData_t *classData = GetPlayerClassData( desiredClassVector[ which ] );
	if ( classData )
	{
		return classData->m_szClassName;
	}

	Warning( "TFBot unable to get data for desired class, defaulting to 'auto'\n" );
	return "auto";
}


//-----------------------------------------------------------------------------------------------------
CTFBot::CTFBot()
{
	m_body = new CTFBotBody( this );
	m_locomotor = new CTFBotLocomotion( this );
	m_vision = new CTFBotVision( this );
	ALLOCATE_INTENTION_INTERFACE( CTFBot );

	m_spawnArea = NULL;
	m_weaponRestrictionFlags = 0;
	m_attributeFlags = 0;
	m_homeArea = NULL;
	m_squad = NULL;
	m_didReselectClass = false;
	m_enemySentry = NULL;
	m_spotWhereEnemySentryLastInjuredMe = vec3_origin;
	m_isLookingAroundForEnemies = true;
	m_behaviorFlags = 0;
	m_attentionFocusEntity = NULL;
	m_noisyTimer.Invalidate();

	if ( TFGameRules()->IsInTraining() )
	{
		m_difficulty = CTFBot::EASY;
	}
	else
	{
		m_difficulty = clamp( (CTFBot::DifficultyType)tf_bot_difficulty.GetInt(), CTFBot::EASY, CTFBot::EXPERT );
	}

	m_actionPoint = NULL;
	m_proxy = NULL;
	m_spawner = NULL;

	m_myControlPoint = NULL;

	SetMission( NO_MISSION, MISSION_DOESNT_RESET_BEHAVIOR_SYSTEM );
	SetMissionTarget( NULL );
	m_missionString.Clear();

	m_fModelScaleOverride = -1.0f;
	m_maxVisionRangeOverride = -1.0f;
	m_squadFormationError = 0.0f;

	m_hFollowingFlagTarget = NULL;

	SetShouldQuickBuild( false );
	SetAutoJump( 0.f, 0.f );

	ClearSniperSpots();

	ListenForGameEvent( "teamplay_point_startcapture" );
	ListenForGameEvent( "teamplay_point_captured" );
	ListenForGameEvent( "teamplay_round_win" );
	ListenForGameEvent( "teamplay_flag_event" );
}


//-----------------------------------------------------------------------------------------------------
CTFBot::~CTFBot()
{
	// delete Intention first, since destruction of Actions may access other components
	DEALLOCATE_INTENTION_INTERFACE;

	if ( m_body )
		delete m_body;

	if ( m_locomotor )
		delete m_locomotor;

	if ( m_vision )
		delete m_vision;

	m_suspectedSpyVector.PurgeAndDeleteElements();
}


//-----------------------------------------------------------------------------------------------------
void CTFBot::Spawn()
{
	BaseClass::Spawn();

	m_spawnArea = NULL;
	m_justLostPointTimer.Invalidate();
	m_squad = NULL;
	m_didReselectClass = false;
	m_isLookingAroundForEnemies = true;
	m_attentionFocusEntity = NULL;

	m_suspectedSpyVector.PurgeAndDeleteElements();
	m_knownSpyVector.RemoveAll();
	m_delayedNoticeVector.RemoveAll();

	m_myControlPoint = NULL;
	ClearSniperSpots();
	ClearTags();

	m_hFollowingFlagTarget = NULL;

	m_requiredWeaponStack.Clear();
	SetShouldQuickBuild( false );

	SetSquadFormationError( 0.0f );
	SetBrokenFormation( false );

	GetVisionInterface()->ForgetAllKnownEntities();
}


//-----------------------------------------------------------------------------------------------------
void CTFBot::SetMission( MissionType mission, bool resetBehaviorSystem )
{
	SetPrevMission( m_mission );
	m_mission = mission;

	if ( resetBehaviorSystem )
	{
		// reset the behavior system to start the given mission
		GetIntentionInterface()->Reset();
	}

	// Temp hack - some missions play an idle loop
	if ( m_mission > NO_MISSION )
	{
		StartIdleSound();
	}
}


//-----------------------------------------------------------------------------------------------------
void CTFBot::PhysicsSimulate( void )
{
	BaseClass::PhysicsSimulate();

	if ( m_spawnArea == NULL )
	{
		m_spawnArea = GetLastKnownArea();
	}

	if ( HasAttribute( CTFBot::ALWAYS_CRIT ) && !m_Shared.InCond( TF_COND_CRITBOOSTED_USER_BUFF ) )
	{
		m_Shared.AddCond( TF_COND_CRITBOOSTED_USER_BUFF );
	}

	// force my speed to be recalculated to keep squad together and restore speed afterwards
	TeamFortress_SetSpeed();

	if ( IsInASquad() )
	{
		if ( GetSquad()->GetMemberCount() <= 1 || GetSquad()->GetLeader() == NULL )
		{
			// squad has collapsed - disband it
			LeaveSquad();
		}
	}


	// If we're dead, choose a new class.
	// We need to do this outside of the behavior system, since changing class can
	// sometimes force an immediate respawn, which will destroy the bot's existing actions out from under it.
	if ( !IsAlive() && !m_didReselectClass && tf_bot_keep_class_after_death.GetBool() == false && TFGameRules()->CanBotChangeClass( this ) )
	{
		if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
			return;

		const char *classname = FStrEq( tf_bot_force_class.GetString(), "" ) ? GetNextSpawnClassname() : tf_bot_force_class.GetString();

		HandleCommand_JoinClass( classname );

		m_didReselectClass = true;
	}
}


//-----------------------------------------------------------------------------------------------------
void CTFBot::Touch( CBaseEntity *pOther )
{
	BaseClass::Touch( pOther );

	CTFPlayer *them = ToTFPlayer( pOther );
	if ( them && IsEnemy( them ) )
	{
		if ( them->m_Shared.IsStealthed() || them->m_Shared.InCond( TF_COND_DISGUISED ) )
		{
			// bumped a spy - they are discovered!
			if ( TFGameRules()->IsMannVsMachineMode() )	// we have to build up to knowing that they are a spy in MvM
			{
				SuspectSpy( them );
			}
			else
			{
				RealizeSpy( them );
			}
		}

		// always notice if we bump an enemy
		TheNextBots().OnWeaponFired( them, them->GetActiveTFWeapon() );
	}
}


//-----------------------------------------------------------------------------------------------------
// Avoid penetrating teammates
void CTFBot::AvoidPlayers( CUserCmd *pCmd )
{
	// Turn off the avoid player code.
	if ( !tf_avoidteammates.GetBool() || !tf_avoidteammates_pushaway.GetBool() )
		return;

	Vector forward, right;
	EyeVectors( &forward, &right );

	CUtlVector< CTFPlayer * > playerVector;
	CollectPlayers( &playerVector, GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS );

	Vector avoidVector = vec3_origin;

	float tooClose = 50.0f;
	if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
	{
		// bots stay farther apart in MvM mode
		tooClose = 150.0f;
	}

	for( int i=0; i<playerVector.Count(); ++i )
	{
		CTFPlayer *them = playerVector[i];

		if ( IsSelf( them ) )
		{
			continue;
		}

		if ( HasTheFlag() )
		{
			// Don't push around the flag (bomb) carrier.
			// We need this for MvM mode so friendly bots don't
			// move the bomb jumper and cause him to restart.
			continue;
		}

		if ( IsPlayerClass( TF_CLASS_MEDIC ) )
		{
			if ( !them->IsPlayerClass( TF_CLASS_MEDIC ) )
			{
				// medics only avoid other medics, so they stay with their patient
				continue;
			}
		}
		else if ( IsInASquad() )
		{
			// if I'm a non-Medic in a Squad, I'm part of a formation
			continue;
		}

		Vector between = GetAbsOrigin() - them->GetAbsOrigin();
		if ( between.IsLengthLessThan( tooClose ) )
		{
			float range = between.NormalizeInPlace();

			avoidVector += ( 1.0f - ( range / tooClose ) ) * between;
		}
	}

	if ( avoidVector.IsZero() )
	{
		m_Shared.SetSeparation( false );
		m_Shared.SetSeparationVelocity( vec3_origin );
		return;
	}

	avoidVector.NormalizeInPlace();

	m_Shared.SetSeparation( true );

	const float maxSpeed = 50.0f;
	m_Shared.SetSeparationVelocity( avoidVector * maxSpeed );

	float ahead = maxSpeed * DotProduct( forward, avoidVector );
	float side = maxSpeed * DotProduct( right, avoidVector );

	pCmd->forwardmove	+= ahead;
	pCmd->sidemove		+= side;
}


//-----------------------------------------------------------------------------------------------------
void CTFBot::UpdateOnRemove( void )
{
	StopIdleSound();

	BaseClass::UpdateOnRemove();
}


//-----------------------------------------------------------------------------------------------------
int CTFBot::ShouldTransmit( const CCheckTransmitInfo *pInfo )
{
	if ( HasAttribute( USE_BOSS_HEALTH_BAR ) )
	{
		return FL_EDICT_ALWAYS;
	}

	return BaseClass::ShouldTransmit( pInfo );
}


//-----------------------------------------------------------------------------------------------------
void CTFBot::ChangeTeam( int iTeamNum, bool bAutoTeam, bool bSilent, bool bAutoBalance /*= false*/  )
{
	BaseClass::ChangeTeam( iTeamNum, bAutoTeam, bSilent, bAutoBalance );
	
	if ( TFGameRules()->IsMannVsMachineMode() )
	{
		SetPrevMission( CTFBot::NO_MISSION );
		ClearAllAttributes();
		// Clear Sound
		StopIdleSound();
	}
}


//-----------------------------------------------------------------------------------------------------
bool CTFBot::ShouldGib( const CTakeDamageInfo &info )
{
	// only gib giant/miniboss
	if ( TFGameRules()->IsMannVsMachineMode() && ( IsMiniBoss() || GetModelScale() > 1.f ) )
	{
		return true;
	}

	return BaseClass::ShouldGib( info );
}


//-----------------------------------------------------------------------------------------------------
bool CTFBot::IsAllowedToPickUpFlag( void ) const
{
	if ( !BaseClass::IsAllowedToPickUpFlag() )
	{
		return false;
	}

	// only the leader of a squad can pick up the flag
	if ( IsInASquad() && !GetSquad()->IsLeader( const_cast< CTFBot * >( this ) ) )
		return false;

	// mission bots can't pick up the flag
	return !IsOnAnyMission();
}


//-----------------------------------------------------------------------------------------------------
void CTFBot::InitClass( void )
{
	BaseClass::InitClass();
}

void CTFBot::ModifyMaxHealth( int nNewMaxHealth, bool bSetCurrentHealth /*= true*/, bool bAllowModelScaling /*= true*/ )
{
	if ( GetMaxHealth() != nNewMaxHealth )
	{
		static CSchemaAttributeDefHandle pAttrDef_HiddenMaxHealthNonBuffed( "hidden maxhealth non buffed" );
		if ( !pAttrDef_HiddenMaxHealthNonBuffed )
		{
			Warning( "TFBotSpawner: Invalid attribute 'hidden maxhealth non buffed'\n" );
		}
		else
		{
			CAttributeList *pAttrList = GetAttributeList();
			if ( pAttrList )
			{
				pAttrList->SetRuntimeAttributeValue( pAttrDef_HiddenMaxHealthNonBuffed, nNewMaxHealth - GetMaxHealth() );
			}
		}
	}

	if ( bSetCurrentHealth )
	{
		SetHealth( nNewMaxHealth );
	}

	if ( bAllowModelScaling && IsMiniBoss() )
	{
		SetModelScale( m_fModelScaleOverride > 0.0f ? m_fModelScaleOverride : tf_mvm_miniboss_scale.GetFloat() );		
	}
}

//-----------------------------------------------------------------------------------------------------
/**
 * Invoked when a game event occurs
 */
void CTFBot::FireGameEvent( IGameEvent *event )
{
	const char *eventName = event->GetName();

	if ( FStrEq( eventName, "teamplay_point_captured" ) )
	{
		ClearMyControlPoint();

		int whoCapped = event->GetInt( "team" );
		int pointID = event->GetInt( "cp" );

		if ( whoCapped == GetTeamNumber() )
		{
			OnTerritoryCaptured( pointID );
		}
		else
		{
			OnTerritoryLost( pointID );

			m_justLostPointTimer.Start( RandomFloat( 10.0f, 20.0f ) );
		}
	}
	else if ( FStrEq( eventName, "teamplay_point_startcapture" ) )
	{
		int pointID = event->GetInt( "cp" );

		OnTerritoryContested( pointID );
	}
	else if ( FStrEq( eventName, "teamplay_flag_event" ) )
	{
		if ( event->GetInt( "eventtype" ) == TF_FLAGEVENT_PICKUP )
		{
			int iPlayer = event->GetInt( "player" );
			if ( iPlayer == entindex() )
			{
				// I just picked up the flag
				OnPickUp( NULL, NULL );
			}
		}
	}
}

	
//-----------------------------------------------------------------------------------------------------
void CTFBot::Event_Killed( const CTakeDamageInfo &info )
{
	BaseClass::Event_Killed( info );

	if ( HasProxy() )
	{
		GetProxy()->OnKilled();
	}

	// announce Spies
	if ( TFGameRules()->IsMannVsMachineMode() )
	{
		if ( IsPlayerClass( TF_CLASS_SPY ) )
		{
			CUtlVector< CTFPlayer * > playerVector;
			CollectPlayers( &playerVector, TF_TEAM_PVE_INVADERS, COLLECT_ONLY_LIVING_PLAYERS );

			int spyCount = 0;
			for( int i=0; i<playerVector.Count(); ++i )
			{
				if ( playerVector[i]->IsPlayerClass( TF_CLASS_SPY ) )
				{
					++spyCount;
				}
			}

			IGameEvent *event = gameeventmanager->CreateEvent( "mvm_mission_update" );
			if ( event )
			{
				event->SetInt( "class", TF_CLASS_SPY );
				event->SetInt( "count", spyCount );
				gameeventmanager->FireEvent( event );
			}
		}
		else if ( IsPlayerClass( TF_CLASS_ENGINEER ) )
		{
			// in MVM, when an engineer dies, we need to decouple his objects so they stay alive when his bot slot gets recycled
			while ( GetObjectCount() > 0 )
			{
				// set to not have owner
				CBaseObject *pObject = GetObject( 0 );
				if ( pObject )
				{
					pObject->SetOwnerEntity( NULL );
					pObject->SetBuilder( NULL );
				}
				RemoveObject( pObject );
			}

			// unown engineer nest if owned any
			for ( int i=0; i<ITFBotHintEntityAutoList::AutoList().Count(); ++i )
			{
				CBaseTFBotHintEntity* pHint = static_cast< CBaseTFBotHintEntity* >( ITFBotHintEntityAutoList::AutoList()[i] );
				if ( pHint->GetOwnerEntity() == this )
				{
					pHint->SetOwnerEntity( NULL );
				}
			}

			CUtlVector< CTFPlayer* > playerVector;
			CollectPlayers( &playerVector, TF_TEAM_PVE_INVADERS, COLLECT_ONLY_LIVING_PLAYERS );
			bool bShouldAnnounceLastEngineerBotDeath = HasAttribute( CTFBot::TELEPORT_TO_HINT );
			if ( bShouldAnnounceLastEngineerBotDeath )
			{
				for ( int i=0; i<playerVector.Count(); ++i )
				{
					if ( playerVector[i] != this && playerVector[i]->IsPlayerClass( TF_CLASS_ENGINEER ) )
					{
						bShouldAnnounceLastEngineerBotDeath = false;
						break;
					}
				}
			}

			if ( bShouldAnnounceLastEngineerBotDeath )
			{
				bool bEngineerTeleporterInTheWorld = false;
				for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i )
				{
					CBaseObject* pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] );
					if ( pObj->GetType() == OBJ_TELEPORTER && pObj->GetTeamNumber() == TF_TEAM_PVE_INVADERS )
					{
						bEngineerTeleporterInTheWorld = true;
					}
				}

				if ( bEngineerTeleporterInTheWorld )
				{
					TFGameRules()->BroadcastSound( 255, "Announcer.MVM_An_Engineer_Bot_Is_Dead_But_Not_Teleporter" );
				}
				else
				{
					TFGameRules()->BroadcastSound( 255, "Announcer.MVM_An_Engineer_Bot_Is_Dead" );
				}
			}
		}

		// remove this bot from following flag
		for ( int i=0; i<ICaptureFlagAutoList::AutoList().Count(); ++i )
		{
			for ( int i=0; i<ICaptureFlagAutoList::AutoList().Count(); ++i )
			{
				CCaptureFlag *flag = static_cast< CCaptureFlag* >( ICaptureFlagAutoList::AutoList()[i] );
				flag->RemoveFollower( this );
			}
		}
	} // MvM

	if ( HasSpawner() )
	{
		GetSpawner()->OnBotKilled( this );
	}

	if ( IsInASquad() )
	{
		LeaveSquad();
	}

	CTFNavArea *lastArea = (CTFNavArea *)GetLastKnownArea();
	if ( lastArea )
	{
		// remove us from old visible set
		NavAreaCollector wasVisible;
		lastArea->ForAllPotentiallyVisibleAreas( wasVisible );

		int i;
		for( i=0; i<wasVisible.m_area.Count(); ++i )
		{
			CTFNavArea *area = (CTFNavArea *)wasVisible.m_area[i];
			area->RemovePotentiallyVisibleActor( this );
		}
	}


	if ( info.GetInflictor() && info.GetInflictor()->GetTeamNumber() != GetTeamNumber() )
	{
		CObjectSentrygun *sentrygun = dynamic_cast< CObjectSentrygun * >( info.GetInflictor() );

		if ( sentrygun )
		{
			// we were killed by an enemy sentry - remember it
			RememberEnemySentry( sentrygun, GetAbsOrigin() );
		}
	}

	StopIdleSound();
}


//-----------------------------------------------------------------------------------------------------
CTeamControlPoint *CTFBot::SelectPointToCapture( CUtlVector< CTeamControlPoint * > *captureVector ) const
{
	if ( !captureVector || captureVector->Count() == 0 )
	{
		return NULL;
	}

	if ( captureVector->Count() == 1 )
	{
		// only one choice
		return captureVector->Element(0);
	}

	// if we're capturing a point, stay on it
	if ( const_cast< CTFBot * >( this )->IsCapturingPoint() )
	{
		CTriggerAreaCapture *trigger = const_cast< CTFBot * >( this )->GetControlPointStandingOn();
		if ( trigger )
		{
			return trigger->GetControlPoint();
		}
	}

	// if we're near a point that is being captured, go help (in the event multiple points are being simultaneously captured)
	CTeamControlPoint *closestPoint = SelectClosestControlPointByTravelDistance( captureVector );
	if ( closestPoint )
	{
		bool alwaysUseClosest = false;

#ifdef STAGING_ONLY
		alwaysUseClosest = TFGameRules() && TFGameRules()->IsBountyMode();
#endif // STAGING_ONLY

		if ( IsPointBeingCaptured( closestPoint ) || alwaysUseClosest )
		{
			return closestPoint;
		}
	}

	// if any point is being captured by our team, go help
	for( int i=0; i<captureVector->Count(); ++i )
	{
		CTeamControlPoint *point = captureVector->Element(i);

		if ( IsPointBeingCaptured( point ) )
		{
			return point;
		}
	}

	// no points are currently being captured - pick the point with the least combat
	CTeamControlPoint *safestPoint = NULL;
	float safestPointCombat = FLT_MAX;
	bool areAllPointsCombatFree = true;

	for( int i=0; i<captureVector->Count(); ++i )
	{
		CTeamControlPoint *point = captureVector->Element(i);
		CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( point->GetPointIndex() );

		if ( !pointArea )
		{
			continue;
		}

		float combat = pointArea->GetCombatIntensity();

		const float minCombat = 0.1f;
		if ( combat > minCombat )
		{
			areAllPointsCombatFree = false;
		}

		if ( combat < safestPointCombat )
		{
			safestPoint = point;
			safestPointCombat = combat;
		}
	}

	// if no points are in combat, pick a random point
	if ( areAllPointsCombatFree )
	{
		const float decisionPeriod = 60.0f;
		int which = captureVector->Count() * TransientlyConsistentRandomValue( decisionPeriod );
		which = clamp( which, 0, captureVector->Count()-1 );

		return captureVector->Element( which );
	}

	// choose the point with the least combat
	return safestPoint;
}


//---------------------------------------------------------------------------------------------
CTeamControlPoint *CTFBot::SelectPointToDefend( CUtlVector< CTeamControlPoint * > *defendVector ) const
{
	if ( defendVector && defendVector->Count() > 0 )
	{
		if ( HasAttribute( CTFBot::PRIORITIZE_DEFENSE ) )
		{
			return SelectClosestControlPointByTravelDistance( defendVector );
		}

		return defendVector->Element( RandomInt( 0, defendVector->Count()-1 ) );
	}

	return NULL;
}


//-----------------------------------------------------------------------------------------------------
/**
 * Return the point we have decided to capture or defend
 */
CTeamControlPoint *CTFBot::GetMyControlPoint( void ) const
{
	if ( m_myControlPoint != NULL && !m_evaluateControlPointTimer.IsElapsed() )
	{
		return m_myControlPoint;
	}

	m_evaluateControlPointTimer.Start( RandomFloat( 1.0f, 2.0f ) );


	CUtlVector< CTeamControlPoint * > captureVector;
	TFGameRules()->CollectCapturePoints( const_cast< CTFBot * >( this ), &captureVector );

	CUtlVector< CTeamControlPoint * > defendVector;
	TFGameRules()->CollectDefendPoints( const_cast< CTFBot * >( this ), &defendVector );

	if ( IsPlayerClass( TF_CLASS_ENGINEER ) || IsPlayerClass( TF_CLASS_SNIPER ) || HasAttribute( CTFBot::PRIORITIZE_DEFENSE ) )
	{
		// engineers always try to defend first
		if ( defendVector.Count() > 0 )
		{
			m_myControlPoint = SelectPointToDefend( &defendVector );
			return m_myControlPoint;
		}
	}

	// if we have a point we can capture - do it
	m_myControlPoint = SelectPointToCapture( &captureVector );

	if ( m_myControlPoint == NULL )
	{
		// otherwise, defend our point(s) from capture
		m_myControlPoint = SelectPointToDefend( &defendVector );
	}

	return m_myControlPoint;
}


//-----------------------------------------------------------------------------------------------------
// Return flag we want to fetch
CCaptureFlag *CTFBot::GetFlagToFetch( void ) const
{
	CUtlVector<CCaptureFlag *> flagsVector;
	int nCarriedFlags = 0;

	// MvM Engineer bot never pick up a flag
	if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
	{
		if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS && IsPlayerClass( TF_CLASS_ENGINEER ) )
		{
			return NULL;
		}

		if( HasAttribute( CTFBot::IGNORE_FLAG ) )
		{
			return NULL;
		}

		if ( TFGameRules()->IsMannVsMachineMode() && HasFlagTaget() )
		{
			return GetFlagTarget();
		}
	}

	// Collect flags
	for ( int i=0; i<ICaptureFlagAutoList::AutoList().Count(); ++i )
	{
		CCaptureFlag *flag = static_cast< CCaptureFlag* >( ICaptureFlagAutoList::AutoList()[i] );

		if ( flag->IsDisabled() )
			continue;

		// If I'm carrying a flag, look for mine and early-out
		if ( HasTheFlag() )
		{
			if ( flag->GetOwnerEntity() == this )
			{
				return flag;
			}
		}

		switch( flag->GetType() )
		{
		case TF_FLAGTYPE_CTF:
			if ( flag->GetTeamNumber() == GetEnemyTeam( GetTeamNumber() ) )
			{
				// we want to steal the other team's flag
				flagsVector.AddToTail( flag );
			}
			break;

		case TF_FLAGTYPE_ATTACK_DEFEND:
		case TF_FLAGTYPE_TERRITORY_CONTROL:
		case TF_FLAGTYPE_INVADE:
			if ( flag->GetTeamNumber() != GetEnemyTeam( GetTeamNumber() ) )
			{
				// we want to move our team's flag or a neutral flag
				flagsVector.AddToTail( flag );
			}
			break;
		}

		if ( flag->IsStolen() )
		{
			nCarriedFlags++;
		}
	}

	CCaptureFlag *pClosestFlag = NULL;
	float flClosestFlagDist = FLT_MAX;
	CCaptureFlag *pClosestUncarriedFlag = NULL;
	float flClosestUncarriedFlagDist = FLT_MAX;

	if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
	{
		int nMinFollower = INT_MAX;

		FOR_EACH_VEC( flagsVector, i )
		{
			CCaptureFlag *pFlag = flagsVector[i];
			if ( pFlag )
			{
				// find the one which needs the most love
				if ( pFlag->GetNumFollowers() < nMinFollower )
				{
					nMinFollower = pFlag->GetNumFollowers();

					pClosestFlag = NULL;
					flClosestFlagDist = FLT_MAX;
					pClosestUncarriedFlag = NULL;
					flClosestUncarriedFlagDist = FLT_MAX;
				}
				
				if ( pFlag->GetNumFollowers() == nMinFollower )
				{
					// Find the closest
					float flDist = ( pFlag->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
					if ( flDist < flClosestFlagDist )
					{
						pClosestFlag = pFlag;
						flClosestFlagDist = flDist;
					}

					// Find the closest uncarried
					if ( nCarriedFlags < flagsVector.Count() && !pFlag->IsStolen() )
					{
						if ( flDist < flClosestUncarriedFlagDist )
						{
							pClosestUncarriedFlag = flagsVector[i];
							flClosestUncarriedFlagDist = flDist;
						}
					}
				}
			}
		}
	}
	else
	{
		FOR_EACH_VEC( flagsVector, i )
		{
			if ( flagsVector[i] )
			{
				// Find the closest
				float flDist = ( flagsVector[i]->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
				if ( flDist < flClosestFlagDist )
				{
					pClosestFlag = flagsVector[i];
					flClosestFlagDist = flDist;
				}

				// Find the closest uncarried
				if ( nCarriedFlags < flagsVector.Count() && !flagsVector[i]->IsStolen() )
				{
					if ( flDist < flClosestUncarriedFlagDist )
					{
						pClosestUncarriedFlag = flagsVector[i];
						flClosestUncarriedFlagDist = flDist;
					}
				}
			}
		}
	}

	// If we have an uncarried flag, prioritize
	if ( pClosestUncarriedFlag )
		return pClosestUncarriedFlag;

	return pClosestFlag;
}


//-----------------------------------------------------------------------------------------------------
// Return capture zone for our flag(s)
CCaptureZone *CTFBot::GetFlagCaptureZone( void ) const
{
	for( int i=0; i<ICaptureZoneAutoList::AutoList().Count(); ++i )
	{
		CCaptureZone *zone = static_cast< CCaptureZone* >( ICaptureZoneAutoList::AutoList()[i] );
		if ( zone->GetTeamNumber() == GetTeamNumber() )
		{
			return zone;
		}
	}

	return NULL;
}



//-----------------------------------------------------------------------------------------------------
void CTFBot::ClearMyControlPoint( void )
{
	m_myControlPoint = NULL;
	m_evaluateControlPointTimer.Invalidate();
}


//-----------------------------------------------------------------------------------------------------
/**
 * Return true if no enemy has contested any point yet
 */
bool CTFBot::AreAllPointsUncontestedSoFar( void ) const
{
	CTeamControlPointMaster *master = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
	if ( master )
	{
		for( int i=0; i<master->GetNumPoints(); ++i )
		{
			CTeamControlPoint *point = master->GetControlPoint( i );
			
			if ( point && point->HasBeenContested() )
				return false;
		}
	}

	return true;
}


//-----------------------------------------------------------------------------------------------------
// Return true if the given point is being captured
bool CTFBot::IsPointBeingCaptured( CTeamControlPoint *point ) const
{
	if ( point == NULL )
		return false;

	if ( point->LastContestedAt() > 0.0f && ( gpGlobals->curtime - point->LastContestedAt() ) < 5.0f )
	{
		// the point is, or was very recently, contested
		return true;
	}

	return false;
}


//---------------------------------------------------------------------------------------------
// Return true if any point is being captured
bool CTFBot::IsAnyPointBeingCaptured( void ) const
{
	CTeamControlPointMaster *master = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
	if ( master )
	{
		for( int i=0; i<master->GetNumPoints(); ++i )
		{
			CTeamControlPoint *point = master->GetControlPoint( i );

			if ( IsPointBeingCaptured( point ) )
				return true;
		}
	}

	return false;
}


//---------------------------------------------------------------------------------------------
// Return true if we are within a short travel distance of the current point
bool CTFBot::IsNearPoint( CTeamControlPoint *point ) const
{
	CTFNavArea *myArea = GetLastKnownArea();

	if ( !myArea || !point )
	{
		return false;
	}

	CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( point->GetPointIndex() );

	if ( !pointArea )
	{
		return false;
	}

	float travelToPoint = fabs( myArea->GetIncursionDistance( GetTeamNumber() ) - pointArea->GetIncursionDistance( GetTeamNumber() ) );

	return travelToPoint < tf_bot_near_point_travel_distance.GetFloat();
}


//---------------------------------------------------------------------------------------------
// Return time left to capture the point before we lose the game
float CTFBot::GetTimeLeftToCapture( void ) const
{
	if ( TFGameRules()->IsInKothMode() )
	{
		if ( TFGameRules()->GetKothTeamTimer( GetEnemyTeam( GetTeamNumber() ) ) )
		{
			return TFGameRules()->GetKothTeamTimer( GetEnemyTeam( GetTeamNumber() ) )->GetTimeRemaining();
		}
	}
	else if ( TFGameRules()->GetActiveRoundTimer() )
	{
		return TFGameRules()->GetActiveRoundTimer()->GetTimeRemaining();
	}

	return 0.0f;
}


//-----------------------------------------------------------------------------------------------------
// Do internal setup when control point changes
void CTFBot::SetupSniperSpotAccumulation( void )
{
	VPROF_BUDGET( "CTFBot::SetupSniperSpotAccumulation", "NextBot" );

	CBaseEntity *goalEntity = NULL;

	if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT )
	{
		// try to find a payload cart to guard
		CTeamTrainWatcher *trainWatcher = TFGameRules()->GetPayloadToPush( GetTeamNumber() );

		if ( !trainWatcher )
		{
			trainWatcher = TFGameRules()->GetPayloadToBlock( GetTeamNumber() );
		}

		if ( trainWatcher )
		{
			goalEntity = trainWatcher->GetTrainEntity();
		}
	}
	else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP )
	{
		goalEntity = GetMyControlPoint();
	}

	if ( !goalEntity )
	{
		ClearSniperSpots();
		return;
	}

	if ( goalEntity == m_snipingGoalEntity )
	{
		// if goal has moved too much (ie: payload cart), recompute our spots
		Vector toGoal = m_snipingGoalEntity->WorldSpaceCenter() - m_lastSnipingGoalEntityPosition;

		if ( toGoal.IsLengthLessThan( tf_bot_sniper_goal_entity_move_tolerance.GetFloat() ) )
		{
			// already set up
			return;
		}
	}

	ClearSniperSpots();

	int myTeam = GetTeamNumber();
	int enemyTeam = ( myTeam == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE;

	bool isDefendingPoint = false;
	CTFNavArea *goalEntityArea = NULL;

	if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT )
	{
		// the cart is owned by the invaders
		isDefendingPoint = ( goalEntity->GetTeamNumber() != myTeam );
		goalEntityArea = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( goalEntity->WorldSpaceCenter(), GETNAVAREA_CHECK_GROUND, 500.0f );
	}
	else
	{
		isDefendingPoint = ( GetMyControlPoint()->GetOwner() == myTeam );
		goalEntityArea = TheTFNavMesh()->GetControlPointCenterArea( GetMyControlPoint()->GetPointIndex() );
	}

	// we are sniping a different control point - setup for new point accumulation
	m_sniperVantageAreaVector.RemoveAll();
	m_sniperTheaterAreaVector.RemoveAll();

	if ( !goalEntityArea )
	{
		return;
	}

	for( int i=0; i<TheNavAreas.Count(); ++i )
	{
		CTFNavArea *area = (CTFNavArea *)TheNavAreas[i];

		if ( !area->IsReachableByTeam( myTeam ) || !area->IsReachableByTeam( enemyTeam ) )
		{
			continue;
		}

		if ( area->GetIncursionDistance( enemyTeam ) <= goalEntityArea->GetIncursionDistance( enemyTeam ) )
		{
			m_sniperTheaterAreaVector.AddToTail( area );
		}

		// if this is my point, I can stand on it, or go a bit beyond it
		float myIncursionTolerance = tf_bot_sniper_spot_point_tolerance.GetFloat();

		if ( !isDefendingPoint )
		{
			// not my point, keep back from it a bit
			myIncursionTolerance *= -1.0f;
		}
		
		if ( area->GetIncursionDistance( myTeam ) <= goalEntityArea->GetIncursionDistance( myTeam ) + myIncursionTolerance )
		{
			m_sniperVantageAreaVector.AddToTail( area );
		}
	}

	m_snipingGoalEntity = goalEntity;
	m_lastSnipingGoalEntityPosition = goalEntity->WorldSpaceCenter();
}


//-----------------------------------------------------------------------------------------------------
// Randomly sample points within candidate areas to find good sniping positions
void CTFBot::AccumulateSniperSpots( void )
{
	VPROF_BUDGET( "CTFBot::AccumulateSniperSpots", "NextBot" );

	SetupSniperSpotAccumulation();

	if ( m_sniperVantageAreaVector.Count() == 0 || m_sniperTheaterAreaVector.Count() == 0 )
	{
		// retry every so often to catch cases where the incursion data is invalid during setup time
		// due to blocked/closed off areas, etc.
		if ( m_retrySniperSpotSetupTimer.IsElapsed() )
		{
			// retry
			ClearSniperSpots();
		}

		return;
	}

	SniperSpotInfo info;

	for( int count=0; count<tf_bot_sniper_spot_search_count.GetInt(); ++count )
	{
		// pick a random vantage area to sample
		int which = RandomInt( 0, m_sniperVantageAreaVector.Count()-1 );
		info.m_vantageArea = m_sniperVantageAreaVector[ which ];
		info.m_vantageSpot = info.m_vantageArea->GetRandomPoint();

		// pick a random theater area to sample
		which = RandomInt( 0, m_sniperTheaterAreaVector.Count()-1 );
		info.m_theaterArea = m_sniperTheaterAreaVector[ which ];
		info.m_theaterSpot = info.m_theaterArea->GetRandomPoint();

		info.m_range = ( info.m_vantageSpot - info.m_theaterSpot ).Length();
		if ( info.m_range < tf_bot_sniper_spot_min_range.GetFloat() )
		{
			// not long enough sightline
			continue;
		}

		for( int i=0; i<m_sniperSpotVector.Count(); ++i )
		{
			if ( ( info.m_vantageSpot - m_sniperSpotVector[i].m_vantageSpot ).IsLengthLessThan( tf_bot_sniper_spot_epsilon.GetFloat() ) )
			{
				// too close to existing spot
				continue;
			}
		}

		Vector eyeOffset( 0, 0, 60.0f );
		if ( IsLineOfFireClear( info.m_vantageSpot + eyeOffset, info.m_theaterSpot + eyeOffset ) )
		{
			// valid spot

			// maximize the time it takes the enemy to get to us
			info.m_advantage = info.m_vantageArea->GetIncursionDistance( GetEnemyTeam( GetTeamNumber() ) ) - info.m_theaterArea->GetIncursionDistance( GetEnemyTeam( GetTeamNumber() ) );

			// if we have already maxxed out our sniper spots, replace the worst one if this is better
			if ( m_sniperSpotVector.Count() >= tf_bot_sniper_spot_max_count.GetInt() )
			{
				int worst = -1;

				for( int i=0; i<m_sniperSpotVector.Count(); ++i )
				{
					if ( worst < 0 || m_sniperSpotVector[i].m_advantage < m_sniperSpotVector[ worst ].m_advantage )
					{
						worst = i;
					}
				}

				// if our new spot is better, replace it
				if ( info.m_advantage > m_sniperSpotVector[ worst ].m_advantage )
				{
					m_sniperSpotVector[ worst ] = info;
				}
			}
			else
			{
				m_sniperSpotVector.AddToTail( info );
			}
		}
	}

	if ( IsDebugging( NEXTBOT_BEHAVIOR ) )
	{
		for( int i=0; i<m_sniperSpotVector.Count(); ++i )
		{
			NDebugOverlay::Cross3D( m_sniperSpotVector[i].m_vantageSpot, 5.0f, 255, 0, 255, true, 0.1f );
			NDebugOverlay::Line( m_sniperSpotVector[i].m_vantageSpot, m_sniperSpotVector[i].m_theaterSpot, 0, 200, 0, true, 0.1f );
		}
	}
}



//-----------------------------------------------------------------------------------------------------
void CTFBot::ClearSniperSpots( void )
{
	m_sniperSpotVector.RemoveAll();
	m_sniperVantageAreaVector.RemoveAll();
	m_sniperTheaterAreaVector.RemoveAll();
	m_snipingGoalEntity = NULL;
	m_retrySniperSpotSetupTimer.Start( RandomFloat( 5.0f, 10.0f ) );
}



//---------------------------------------------------------------------------------------------
class CCollectReachableObjects : public ISearchSurroundingAreasFunctor
{
public:
	CCollectReachableObjects( const CTFBot *me, float maxRange, const CUtlVector< CHandle< CBaseEntity > > &potentialVector, CUtlVector< CHandle< CBaseEntity > > *collectionVector ) : m_potentialVector( potentialVector )
	{
		m_me = me;
		m_maxRange = maxRange;
		m_collectionVector = collectionVector;
	}

	virtual bool operator() ( CNavArea *area, CNavArea *priorArea, float travelDistanceSoFar )
	{
		// do any of the potential objects overlap this area?
		FOR_EACH_VEC( m_potentialVector, it )
		{
			CBaseEntity *obj = m_potentialVector[ it ];

			if ( obj && area->Contains( obj->WorldSpaceCenter() ) )
			{
				// reachable - keep it
				if ( !m_collectionVector->HasElement( obj ) )
				{
					m_collectionVector->AddToTail( obj );
				}
			}
		}
		return true;
	}

	virtual bool ShouldSearch( CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar )
	{
		if ( adjArea->IsBlocked( m_me->GetTeamNumber() ) )
		{
			return false;
		}

		if ( travelDistanceSoFar > m_maxRange )
		{
			// too far away
			return false;
		}

		return currentArea->IsContiguous( adjArea );
	}

	const CTFBot *m_me;
	float m_maxRange;
	const CUtlVector< CHandle< CBaseEntity > > &m_potentialVector;
	CUtlVector< CHandle< CBaseEntity > > *m_collectionVector;
};


//
// Search outwards from startSearchArea and collect all reachable objects from the given list that pass the given filter
// Items in selectedObjectVector will be approximately sorted in nearest-to-farthest order (because of SearchSurroundingAreas)
//
void CTFBot::SelectReachableObjects( const CUtlVector< CHandle< CBaseEntity > > &candidateObjectVector, 
									 CUtlVector< CHandle< CBaseEntity > > *selectedObjectVector, 
									 const INextBotFilter &filter, 
									 CNavArea *startSearchArea, 
									 float maxRange ) const
{
	if ( startSearchArea == NULL || selectedObjectVector == NULL )
		return;

	selectedObjectVector->RemoveAll();

	// filter candidate objects
	CUtlVector< CHandle< CBaseEntity > > filteredObjectVector;
	for( int i=0; i<candidateObjectVector.Count(); ++i )
	{
		if ( filter.IsSelected( candidateObjectVector[i] ) )
		{
			filteredObjectVector.AddToTail( candidateObjectVector[i] );
		}
	}

	// only keep those that are reachable by us
	CCollectReachableObjects collector( this, maxRange, filteredObjectVector, selectedObjectVector );
	SearchSurroundingAreas( startSearchArea, collector );
}


//---------------------------------------------------------------------------------------------
bool CTFBot::IsAmmoLow( void ) const
{
	CTFWeaponBase *myWeapon = m_Shared.GetActiveTFWeapon();
	if ( myWeapon )
	{
		if ( myWeapon->GetWeaponID() == TF_WEAPON_WRENCH )
		{
			// wrench is special. it's a melee weapon that wants ammo - metal
			return ( GetAmmoCount( TF_AMMO_METAL ) <= 0 );
		}

		if ( myWeapon->IsMeleeWeapon() )
		{
			// we never run out of ammo with a melee weapon
			return false;
		}

		// no projectile, no ammo needed
		const char *weaponAlias = WeaponIdToAlias( myWeapon->GetWeaponID() );
		if ( weaponAlias )
		{
			WEAPON_FILE_INFO_HANDLE	weaponInfoHandle = LookupWeaponInfoSlot( weaponAlias );
			if ( weaponInfoHandle != GetInvalidWeaponInfoHandle() )
			{
				CTFWeaponInfo *weaponInfo = static_cast< CTFWeaponInfo * >( GetFileWeaponInfoFromHandle( weaponInfoHandle ) );
				if ( weaponInfo && weaponInfo->GetWeaponData( TF_WEAPON_PRIMARY_MODE ).m_iProjectile == TF_PROJECTILE_NONE )
				{
					// we don't shoot anything, so we don't need ammo
					return false;
				}
			}
		}

		float ratio = (float)GetAmmoCount( TF_AMMO_PRIMARY ) / (float)( const_cast< CTFBot * >( this )->GetMaxAmmo( TF_AMMO_PRIMARY ) );

		if ( ratio < 0.2f )
		{
			return true;
		}
		//if ( !myWeapon->HasPrimaryAmmo() && myWeapon->GetWeaponID() != TF_WEAPON_BUILDER && myWeapon->GetWeaponID() != TF_WEAPON_MEDIGUN )
	}

	return false;
}


//-----------------------------------------------------------------------------------------------------
bool CTFBot::IsAmmoFull( void ) const
{
	bool isPrimaryFull = GetAmmoCount( TF_AMMO_PRIMARY ) >= const_cast< CTFBot * >( this )->GetMaxAmmo( TF_AMMO_PRIMARY );
	bool isSecondaryFull = GetAmmoCount( TF_AMMO_SECONDARY ) >= const_cast< CTFBot * >( this )->GetMaxAmmo( TF_AMMO_SECONDARY );

	if ( IsPlayerClass( TF_CLASS_ENGINEER ) )
	{
		// wrench is special. it's a melee weapon that wants ammo - metal
		return ( GetAmmoCount( TF_AMMO_METAL ) >= 200 ) && isPrimaryFull && isSecondaryFull;
	}

	return isPrimaryFull && isSecondaryFull;

/*
	CTFWeaponBase *myWeapon = m_Shared.GetActiveTFWeapon();
	if ( myWeapon )
	{
		if ( IsPlayerClass( TF_CLASS_ENGINEER ) )
		{
			// wrench is special. it's a melee weapon that wants ammo - metal
			return ( GetAmmoCount( TF_AMMO_METAL ) >= 200 );
		}

		if ( myWeapon->IsMeleeWeapon() )
		{
			// we never run out of ammo with a melee weapon
			return true;
		}

		// no projectile, no ammo needed
		const char *weaponAlias = WeaponIdToAlias( myWeapon->GetWeaponID() );
		if ( weaponAlias )
		{
			WEAPON_FILE_INFO_HANDLE	weaponInfoHandle = LookupWeaponInfoSlot( weaponAlias );
			if ( weaponInfoHandle != GetInvalidWeaponInfoHandle() )
			{
				CTFWeaponInfo *weaponInfo = static_cast< CTFWeaponInfo * >( GetFileWeaponInfoFromHandle( weaponInfoHandle ) );
				if ( weaponInfo && weaponInfo->GetWeaponData( TF_WEAPON_PRIMARY_MODE ).m_iProjectile == TF_PROJECTILE_NONE )
				{
					// we don't shoot anything, so we don't need ammo
					return true;
				}
			}
		}

		bool isPrimaryFull = GetAmmoCount( TF_AMMO_PRIMARY ) >= const_cast< CTFBot * >( this )->GetMaxAmmo( TF_AMMO_PRIMARY );
		bool isSecondaryFull = GetAmmoCount( TF_AMMO_SECONDARY ) >= const_cast< CTFBot * >( this )->GetMaxAmmo( TF_AMMO_SECONDARY );

		return isPrimaryFull && isSecondaryFull;
	}

	return false;
*/
}


bool CTFBot::IsDormantWhenDead( void ) const
{
	return false;
}


//-----------------------------------------------------------------------------------------------------
/**
 * When someone fires their weapon
 */
void CTFBot::OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon )
{
	VPROF_BUDGET( "CTFBot::OnWeaponFired", "NextBot" );

	BaseClass::OnWeaponFired( whoFired, weapon );

	if ( !whoFired || !whoFired->IsAlive() )
		return;

	if ( IsRangeGreaterThan( whoFired, tf_bot_notice_gunfire_range.GetFloat() ) )
		return;

	int noticeChance = 100;

	if ( IsQuietWeapon( (CTFWeaponBase *)weapon ) )
	{
		if ( IsRangeGreaterThan( whoFired, tf_bot_notice_quiet_gunfire_range.GetFloat() ) )
		{
			// too far away to hear in any event
			return;
		}

		switch( GetDifficulty() )
		{
		case EASY:
			noticeChance = 10;
			break;

		case NORMAL:
			noticeChance = 30;
			break;

		case HARD:
			noticeChance = 60;
			break;

		default:
		case EXPERT:
			noticeChance = 90;
			break;
		}

		if ( IsEnvironmentNoisy() )
		{
			// less likely to notice with all the noise
			noticeChance /= 2;
		}
	}
	else if ( IsRangeLessThan( whoFired, 1000.0f ) )
	{
		// loud gunfire in our area - it's now "noisy" for a bit
		m_noisyTimer.Start( 3.0f );
	}

	if ( RandomInt( 1, 100 ) > noticeChance )
	{
		return;
	}

	// notice the gunfire
	GetVisionInterface()->AddKnownEntity( whoFired );
}


//-----------------------------------------------------------------------------------------------------
// Return true if we match the given debug symbol
bool CTFBot::IsDebugFilterMatch( const char *name ) const
{
	// player classname
	if ( !Q_strnicmp( name, const_cast< CTFBot * >( this )->GetPlayerClass()->GetName(), Q_strlen( name ) ) )
	{
		return true;
	}

	return BaseClass::IsDebugFilterMatch( name );
}


//-----------------------------------------------------------------------------------------------------
class CFindClosestPotentiallyVisibleAreaToPos
{
public:
	CFindClosestPotentiallyVisibleAreaToPos( const Vector &pos )
	{
		m_pos = pos;
		m_closeArea = NULL;
		m_closeRangeSq = FLT_MAX;
	}

	bool operator() ( CNavArea *baseArea )
	{
		CTFNavArea *area = (CTFNavArea *)baseArea;

		Vector close;
		area->GetClosestPointOnArea( m_pos, &close );

		float rangeSq = ( close - m_pos ).LengthSqr();
		if ( rangeSq < m_closeRangeSq )
		{
			m_closeArea = area;
			m_closeRangeSq = rangeSq;
		}

		return true;
	}

	Vector m_pos;
	CTFNavArea *m_closeArea;
	float m_closeRangeSq;
};


//-----------------------------------------------------------------------------------------------------
// Update our view to watch where members of the given team will be coming from
void CTFBot::UpdateLookingAroundForIncomingPlayers( bool lookForEnemies )
{
	if ( !m_lookAtEnemyInvasionAreasTimer.IsElapsed() )
		return;

	const float maxLookInterval = 1.0f;
	m_lookAtEnemyInvasionAreasTimer.Start( RandomFloat( 0.333f, maxLookInterval ) );

	float minGazeRange = m_Shared.InCond( TF_COND_ZOOMED ) ? 750.0f : 150.0f;

	CTFNavArea *myArea = GetLastKnownArea();
	if ( myArea )
	{
		int team = GetTeamNumber();

		// if we want to look where teammates come from, we need to pass in
		// the *enemy* team, since the method collects *enemy* invasion areas
		if ( !lookForEnemies )
		{
			team = GetEnemyTeam( team );
		}

		const CUtlVector< CTFNavArea * > &invasionAreaVector = myArea->GetEnemyInvasionAreaVector( team );

		if ( invasionAreaVector.Count() > 0 )
		{
			// try to not look directly at walls
			const int retryCount = 20.0f;
			for( int r=0; r<retryCount; ++r )
			{
				int which = RandomInt( 0, invasionAreaVector.Count()-1 );
				Vector gazeSpot = invasionAreaVector[ which ]->GetRandomPoint() + Vector( 0, 0, 0.75f * HumanHeight );

				if ( IsRangeGreaterThan( gazeSpot, minGazeRange ) && GetVisionInterface()->IsLineOfSightClear( gazeSpot ) )
				{
					// use maxLookInterval so these looks override body aiming from path following
					GetBodyInterface()->AimHeadTowards( gazeSpot, IBody::INTERESTING, maxLookInterval, NULL, "Looking toward enemy invasion areas" );
					break;
				}
			}
		}
	}
}


//-----------------------------------------------------------------------------------------------------
/**
 * Update our view to keep an eye on areas where the enemy will be coming from
 */
void CTFBot::UpdateLookingAroundForEnemies( void )
{
	if ( !m_isLookingAroundForEnemies )
		return;

	if ( HasAttribute( CTFBot::IGNORE_ENEMIES ) )
		return;

	if ( m_Shared.IsControlStunned() )
		return;

	const float maxLookInterval = 1.0f;

	const CKnownEntity *known = GetVisionInterface()->GetPrimaryKnownThreat();

	if ( known )
	{
		if ( known->IsVisibleInFOVNow() )
		{
			if ( IsPlayerClass( TF_CLASS_SPY ) && 
				 GetDifficulty() >= CTFBot::HARD &&
				 m_Shared.InCond( TF_COND_DISGUISED ) &&
				 !m_Shared.IsStealthed() )
			{
				// smart Spies don't look at their victims until it's too late...
				// look around at where *teammates* will be coming from to fool the enemy
				UpdateLookingAroundForIncomingPlayers( LOOK_FOR_FRIENDS );
				return;
			}

			// I see you!
			GetBodyInterface()->AimHeadTowards( known->GetEntity(), IBody::CRITICAL, 1.0f, NULL, "Aiming at a visible threat" );
			return;
		}

/* apparently sounds update last known position...
		if ( known->WasEverVisible() && known->GetTimeSinceLastSeen() < 3.0f )
		{
			// I saw you just a moment ago...
			GetBodyInterface()->AimHeadTowards( known->GetLastKnownPosition() + GetClassEyeHeight(), IBody::IMPORTANT, 1.0f, NULL, "Aiming at a last known threat position" );
			return;
		}
*/

		// known but not currently visible (I know you're around here somewhere)

		// if there is unobstructed space between us, turn around
		if ( IsLineOfSightClear( known->GetEntity(), IGNORE_ACTORS ) )
		{
			Vector toThreat = known->GetEntity()->GetAbsOrigin() - GetAbsOrigin();
			float threatRange = toThreat.NormalizeInPlace();

			float aimError = M_PI/6.0f;

			float s, c;
			FastSinCos( aimError, &s, &c );

			float error = threatRange * s;
			Vector imperfectAimSpot = known->GetEntity()->WorldSpaceCenter();
			imperfectAimSpot.x += RandomFloat( -error, error );
			imperfectAimSpot.y += RandomFloat( -error, error );

			GetBodyInterface()->AimHeadTowards( imperfectAimSpot, IBody::IMPORTANT, 1.0f, NULL, "Turning around to find threat out of our FOV" );
			return;
		}
			
		if ( !IsPlayerClass( TF_CLASS_SNIPER ) )
		{
			// look toward potentially visible area nearest the last known position
			CTFNavArea *myArea = GetLastKnownArea();
			if ( myArea )
			{
				const CTFNavArea *closeArea = NULL;
				CFindClosestPotentiallyVisibleAreaToPos find( known->GetLastKnownPosition() );
				myArea->ForAllPotentiallyVisibleAreas( find );

				closeArea = find.m_closeArea;

				if ( closeArea )
				{
					// try to not look directly at walls
					const int retryCount = 10.0f;
					for( int r=0; r<retryCount; ++r )
					{
						Vector gazeSpot = closeArea->GetRandomPoint() + Vector( 0, 0, 0.75f * HumanHeight );

						if ( GetVisionInterface()->IsLineOfSightClear( gazeSpot ) )
						{
							// use maxLookInterval so these looks override body aiming from path following
							GetBodyInterface()->AimHeadTowards( gazeSpot, IBody::IMPORTANT, maxLookInterval, NULL, "Looking toward potentially visible area near known but hidden threat" );
							return;
						}
					}					

					// can't find a clear line to look along
					if ( IsDebugging( NEXTBOT_VISION | NEXTBOT_ERRORS ) )
					{
						ConColorMsg( Color( 255, 255, 0, 255 ), "%3.2f: %s can't find clear line to look at potentially visible near known but hidden entity %s(#%d)\n", 
										gpGlobals->curtime,
										GetDebugIdentifier(),
										known->GetEntity()->GetClassname(),
										known->GetEntity()->entindex() );
					}
				}
				else if ( IsDebugging( NEXTBOT_VISION | NEXTBOT_ERRORS ) )
				{
					ConColorMsg( Color( 255, 255, 0, 255 ), "%3.2f: %s no potentially visible area to look toward known but hidden entity %s(#%d)\n", 
									gpGlobals->curtime,
									GetDebugIdentifier(),
									known->GetEntity()->GetClassname(),
									known->GetEntity()->entindex() );
				}
			}

			return;
		}
	}

	// no known threat - look toward where enemies will come from
	UpdateLookingAroundForIncomingPlayers( LOOK_FOR_ENEMIES );
}


//---------------------------------------------------------------------------------------------
class CFindVantagePoint : public ISearchSurroundingAreasFunctor
{
public:
	CFindVantagePoint( int enemyTeamIndex )
	{
		m_enemyTeamIndex = enemyTeamIndex;
		m_vantageArea = NULL;
	}

	virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
	{
		CTFNavArea *area = (CTFNavArea *)baseArea;

		CTeam *enemyTeam = GetGlobalTeam( m_enemyTeamIndex );
		for( int i=0; i<enemyTeam->GetNumPlayers(); ++i )
		{
			CTFPlayer *enemy = (CTFPlayer *)enemyTeam->GetPlayer(i);

			if ( !enemy->IsAlive() || !enemy->GetLastKnownArea() )
				continue;

			CTFNavArea *enemyArea = (CTFNavArea *)enemy->GetLastKnownArea();
			if ( enemyArea->IsCompletelyVisible( area ) )
			{
				// nearby area from which we can see the enemy team
				m_vantageArea = area;
				return false;
			}
		}

		return true;
	}

	int m_enemyTeamIndex;
	CTFNavArea *m_vantageArea;
};


//-----------------------------------------------------------------------------------------------------
// Return a nearby area where we can see a member of the enemy team
CTFNavArea *CTFBot::FindVantagePoint( float maxTravelDistance ) const
{
	CFindVantagePoint find( GetTeamNumber() == TF_TEAM_BLUE ? TF_TEAM_RED : TF_TEAM_BLUE );
	SearchSurroundingAreas( GetLastKnownArea(), find, maxTravelDistance );
	return find.m_vantageArea;
}


//-----------------------------------------------------------------------------------------------------
/**
 * Return perceived danger of threat (0=none, 1=immediate deadly danger)
 * @todo: Move this to contextual query
 * @todo: Differentiate between potential threats (that sentry up ahead along our route) and immediate threats (the sentry I'm in range of)
 */
float CTFBot::GetThreatDanger( CBaseCombatCharacter *who ) const
{
	if ( who == NULL )
		return 0.0f;

	if ( IsPlayerClass( TF_CLASS_SNIPER ) )
	{
		if ( IsRangeGreaterThan( who, tf_bot_sniper_personal_space_range.GetFloat() ) )
		{
			// far away enemies are no threat to a Sniper
			return 0.0f;
		}
	}

	if ( who->IsPlayer() )
	{
		CTFPlayer *player = ToTFPlayer( who );

		// ubers are scary
		if ( player->m_Shared.IsInvulnerable() )
			return 1.0f;

		switch( player->GetPlayerClass()->GetClassIndex() )
		{
		case TF_CLASS_MEDIC:
			return 0.2f;		// 1/5

		case TF_CLASS_ENGINEER:
		case TF_CLASS_SNIPER:
			return 0.4f;		// 2/5

		case TF_CLASS_SCOUT:
		case TF_CLASS_SPY:
		case TF_CLASS_DEMOMAN:
			return 0.6f;		// 3/5

		case TF_CLASS_SOLDIER:
		case TF_CLASS_HEAVYWEAPONS:
			return 0.8f;		// 4/5

		case TF_CLASS_PYRO:
			return 1.0f;		// 5/5
		}

	}
	else
	{
		// sentry gun
		CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( who );
		if ( sentry )
		{
			if ( !sentry->IsAlive() || sentry->IsPlacing() || sentry->HasSapper() || sentry->IsPlasmaDisabled() || sentry->IsUpgrading() || sentry->IsBuilding() )
				return 0.0f;

			switch( sentry->GetUpgradeLevel() )
			{
			case 3:		return 1.0f;
			case 2:		return 0.8f;
			default:	return 0.6f;
			}
		}
	}

	return 0.0f;
}


//-----------------------------------------------------------------------------------------------------
/**
 * Return the max range at which we can effectively attack
 */
float CTFBot::GetMaxAttackRange( void ) const
{
	CTFWeaponBase *myWeapon = m_Shared.GetActiveTFWeapon();
	if ( !myWeapon )
		return 0.0f;

	if ( myWeapon->IsMeleeWeapon() )
	{
		return 100.0f;
	}
	
	if ( myWeapon->IsWeapon( TF_WEAPON_FLAMETHROWER ) )
	{
		if ( TFGameRules()->IsMannVsMachineMode() )
		{
			const float flameRange = 350.0f;

			static CSchemaItemDefHandle pItemDef_GiantFlamethrower( "MVM Giant Flamethrower" );

			if ( IsActiveTFWeapon( pItemDef_GiantFlamethrower ) )
			{
				return 2.5f * flameRange;
			}

			return flameRange;
		}

		return 250.0f;
	}

	if ( WeaponID_IsSniperRifle( myWeapon->GetWeaponID() ) )
	{
		// infinite
		return FLT_MAX;
	}

	if ( myWeapon->IsWeapon( TF_WEAPON_ROCKETLAUNCHER ) )
	{
		return 3000.0f;
	}

	// bullet spray weapons, grenades, etc
	// for now, default to infinite so bot always returns fire and doesn't look dumb
	return FLT_MAX;
}


//-----------------------------------------------------------------------------------------------------
/**
 * Return the ideal range at which we can effectively attack
 */
float CTFBot::GetDesiredAttackRange( void ) const
{
	CTFWeaponBase *myWeapon = m_Shared.GetActiveTFWeapon();
	if ( !myWeapon )
		return 0.0f;

	if ( myWeapon->IsWeapon( TF_WEAPON_KNIFE ) )
	{
		// get very close and stab
		return 70.0f;	// 60
	}

	if ( myWeapon->IsMeleeWeapon() )
	{
		return 100.0f;
	}
	
	if ( myWeapon->IsWeapon( TF_WEAPON_FLAMETHROWER ) )
	{
		return 100.0f;
	}

	if ( WeaponID_IsSniperRifle( myWeapon->GetWeaponID() ) )
	{
		// infinite
		return FLT_MAX;
	}

	if ( myWeapon->IsWeapon( TF_WEAPON_ROCKETLAUNCHER ) && !TFGameRules()->IsMannVsMachineMode() )
	{
		return 1250.0f;
	}

	// bullet spray weapons, grenades, etc
	return 500.0f;
}


//-----------------------------------------------------------------------------------------------------
// If we're required to equip a specific weapon, do it.
bool CTFBot::EquipRequiredWeapon( void )
{
	// if we have a required weapon on our stack, it takes precedence (items, etc)
	if ( m_requiredWeaponStack.Count() )
	{
		CBaseCombatWeapon *pWeapon = m_requiredWeaponStack.Top().Get();
		return Weapon_Switch( pWeapon );
	}

	if ( TheTFBots().IsMeleeOnly() || TFGameRules()->IsInMedievalMode() || HasWeaponRestriction( MELEE_ONLY ) )
	{
		// force use of melee weapons
		Weapon_Switch( Weapon_GetSlot( TF_WPN_TYPE_MELEE ) );
		return true;
	}

	if ( HasWeaponRestriction( PRIMARY_ONLY ) )
	{
		Weapon_Switch( Weapon_GetSlot( TF_WPN_TYPE_PRIMARY ) );
		return true;
	}

	if ( HasWeaponRestriction( SECONDARY_ONLY ) )
	{
		Weapon_Switch( Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
		return true;
	}

	return false;
}


//-----------------------------------------------------------------------------------------------------
// Equip the best weapon we have to attack the given threat
void CTFBot::EquipBestWeaponForThreat( const CKnownEntity *threat )
{
	if ( EquipRequiredWeapon() )
		return;

#ifdef TF_RAID_MODE
	if ( TFGameRules()->IsRaidMode() )
	{
		if ( HasAttribute( CTFBot::AGGRESSIVE ) )
		{
			// mobs never equip other weapons
			return;
		}

		if ( GetPlayerClass()->GetClassIndex() == TF_CLASS_DEMOMAN && !IsInASquad() )
		{
			// wandering demomen use stickies only
			Weapon_Switch( Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
			return;
		}
	}
#endif // TF_RAID_MODE
	 
	CTFWeaponBase *primary = dynamic_cast< CTFWeaponBase *>( Weapon_GetSlot( TF_WPN_TYPE_PRIMARY ) );
	if ( !IsCombatWeapon( primary ) )
	{
		primary = NULL;
	}

	CTFWeaponBase *secondary = dynamic_cast< CTFWeaponBase *>( Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
	if ( !IsCombatWeapon( secondary ) )
	{
		secondary = NULL;
	}

	// no secondary weapons in MvM
	if ( TFGameRules()->IsMannVsMachineMode() )
	{
		if ( IsPlayerClass( TF_CLASS_MEDIC ) && IsInASquad() && GetSquad() && !GetSquad()->IsLeader( this ) )
		{
			// always try to heal leader
			Weapon_Switch( Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
			return;
		}

		secondary = NULL;
	}

	CTFWeaponBase *melee = dynamic_cast< CTFWeaponBase *>( Weapon_GetSlot( TF_WPN_TYPE_MELEE ) );
	if ( !IsCombatWeapon( melee ) )
	{
		melee = NULL;
	}

	CTFWeaponBase *gun = NULL;
	if ( primary )
	{
		gun = primary;
	}
	else if ( secondary )
	{
		gun = secondary;
	}
	else
	{
		gun = melee;
	}

	if ( IsDifficulty( CTFBot::EASY ) )
	{
		// easy bots always use their primary weapon if they have one
		if ( gun )
		{
			Weapon_Switch( gun );
		}

		return;
	}

	if ( !threat || !threat->WasEverVisible() || threat->GetTimeSinceLastSeen() > 5.0f )
	{
		// no threat - go back to primary weapon so it has a chance to reload
		if ( gun )
		{
			Weapon_Switch( gun );
		}

		return;
	}

	// now filter weapons by available ammo
	if ( GetAmmoCount( TF_AMMO_PRIMARY ) <= 0 )
	{
		primary = NULL;
	}

	if ( GetAmmoCount( TF_WPN_TYPE_SECONDARY ) <= 0 )
	{
		secondary = NULL;
	}

	// modify our gun choice based on threat situation (range, etc)
	switch( GetPlayerClass()->GetClassIndex() )
	{
	case TF_CLASS_DEMOMAN:
	case TF_CLASS_HEAVYWEAPONS:
	case TF_CLASS_SPY:
	case TF_CLASS_MEDIC:
	case TF_CLASS_ENGINEER:
		// primary
		break;

	case TF_CLASS_SCOUT:
		{
			if ( secondary )
			{
				if ( gun && !gun->Clip1() )
				{
					gun = secondary;
				}
			}
		}
		break;

	case TF_CLASS_SOLDIER:
		{
			// if we've emptied our rocket launcher clip and are fighting a nearby threat, switch to our secondary if it is ready to fire
			if ( gun && !gun->Clip1() )
			{
				if ( secondary && secondary->Clip1() )
				{
					const float closeSoldierRange = 500.0f;
					if ( IsRangeLessThan( threat->GetLastKnownPosition(), closeSoldierRange ) )
					{
						gun = secondary;
					}
				}
			}
		}
		break;

	case TF_CLASS_SNIPER:
		{
			const float closeSniperRange = 750.0f;
			if ( secondary && IsRangeLessThan( threat->GetLastKnownPosition(), closeSniperRange ) )
				gun = secondary;
		}
		break;

	case TF_CLASS_PYRO:
		{
			const float flameRange = 750.0f;
			if ( secondary && IsRangeGreaterThan( threat->GetLastKnownPosition(), flameRange ) )
			{
				gun = secondary;
			}

			// keep flamethrower out to reflect projectiles
			if ( threat->GetEntity() && threat->GetEntity()->IsPlayer() )
			{
				CTFPlayer *enemy = ToTFPlayer( threat->GetEntity() );

				if ( enemy->IsPlayerClass( TF_CLASS_SOLDIER ) || enemy->IsPlayerClass( TF_CLASS_DEMOMAN ) )
				{
					gun = primary;
				}
			}
		}
		break;
	}

	if ( gun )
	{
		Weapon_Switch( gun );
	}
}


//-----------------------------------------------------------------------------------------------------
// NOTE: This assumes default weapon loadouts
bool CTFBot::EquipLongRangeWeapon( void )
{
	// no secondary weapons in MvM
	if ( TFGameRules()->IsMannVsMachineMode() )
		return false;

	if ( IsPlayerClass( TF_CLASS_SOLDIER ) || 
		 IsPlayerClass( TF_CLASS_DEMOMAN ) ||
		 IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ||
		 IsPlayerClass( TF_CLASS_SNIPER ) )
	{
		CBaseCombatWeapon *primary = Weapon_GetSlot( TF_WPN_TYPE_PRIMARY );
		if ( primary )
		{
			if ( GetAmmoCount( TF_AMMO_PRIMARY ) > 0 )
			{
				Weapon_Switch( primary );
				return true;
			}
		}
	}

	// fall back to our secondary (or go right to it if its the only thing we have that has reach)
	CBaseCombatWeapon *secondary = Weapon_GetSlot( TF_WPN_TYPE_SECONDARY );
	if ( secondary )
	{
		if ( GetAmmoCount( TF_AMMO_SECONDARY ) > 0 )
		{
			Weapon_Switch( secondary );
			return true;
		}
	}

	return false;
}


//-----------------------------------------------------------------------------------------------------
// Force us to equip and use this weapon until popped off the required stack
void CTFBot::PushRequiredWeapon( CTFWeaponBase *weapon )
{
	m_requiredWeaponStack.Push( weapon );
}


//-----------------------------------------------------------------------------------------------------
// Pop top required weapon off of stack and discard
void CTFBot::PopRequiredWeapon( void )
{
	m_requiredWeaponStack.Pop();
}


//-----------------------------------------------------------------------------------------------------
// return true if given weapon can be used to attack
bool CTFBot::IsCombatWeapon( CTFWeaponBase *weapon ) const
{
	if ( weapon == MY_CURRENT_GUN )		// MY_CURRENT_GUN == NULL
	{
		weapon = m_Shared.GetActiveTFWeapon();
	}

	if ( weapon )
	{
		switch ( weapon->GetWeaponID() )
		{
		case TF_WEAPON_MEDIGUN:
		case TF_WEAPON_PDA:
		case TF_WEAPON_PDA_ENGINEER_BUILD:
		case TF_WEAPON_PDA_ENGINEER_DESTROY:
		case TF_WEAPON_PDA_SPY:
		case TF_WEAPON_BUILDER:
		case TF_WEAPON_DISPENSER:
		case TF_WEAPON_INVIS:
		case TF_WEAPON_LUNCHBOX:
		case TF_WEAPON_BUFF_ITEM:
		case TF_WEAPON_PUMPKIN_BOMB:
			return false;
		};
	}

	return true;
}


//-----------------------------------------------------------------------------------------------------
// return true if given weapon is a "hitscan" weapon
bool CTFBot::IsHitScanWeapon( CTFWeaponBase *weapon ) const
{
	if ( weapon == MY_CURRENT_GUN )		// MY_CURRENT_GUN == NULL
	{
		weapon = m_Shared.GetActiveTFWeapon();
	}

	if ( weapon )
	{
		switch ( weapon->GetWeaponID() )
		{
		case TF_WEAPON_SHOTGUN_PRIMARY:
		case TF_WEAPON_SHOTGUN_SOLDIER:
		case TF_WEAPON_SHOTGUN_HWG:
		case TF_WEAPON_SHOTGUN_PYRO:
		case TF_WEAPON_SCATTERGUN:
		case TF_WEAPON_SNIPERRIFLE:
		case TF_WEAPON_MINIGUN:
		case TF_WEAPON_SMG:
		case TF_WEAPON_CHARGED_SMG:
		case TF_WEAPON_PISTOL:
		case TF_WEAPON_PISTOL_SCOUT:
		case TF_WEAPON_REVOLVER:
		case TF_WEAPON_SENTRY_BULLET:
		case TF_WEAPON_SENTRY_ROCKET:
		case TF_WEAPON_SENTRY_REVENGE:
		case TF_WEAPON_HANDGUN_SCOUT_PRIMARY:
		case TF_WEAPON_HANDGUN_SCOUT_SECONDARY:
		case TF_WEAPON_SODA_POPPER:
		case TF_WEAPON_SNIPERRIFLE_DECAP:
		case TF_WEAPON_PEP_BRAWLER_BLASTER:
		case TF_WEAPON_SNIPERRIFLE_CLASSIC:
			return true;
		};
	}

	return false;
}


//-----------------------------------------------------------------------------------------------------
// return true if given weapon "sprays" bullets/fire/etc continuously (ie: not individual rockets/etc)
bool CTFBot::IsContinuousFireWeapon( CTFWeaponBase *weapon ) const
{
	if ( weapon == MY_CURRENT_GUN )
	{
		weapon = m_Shared.GetActiveTFWeapon();
	}

	if ( !IsCombatWeapon( weapon ) )
		return false;

	if ( weapon )
	{
		switch ( weapon->GetWeaponID() )
		{
		case TF_WEAPON_ROCKETLAUNCHER:
		case TF_WEAPON_ROCKETLAUNCHER_DIRECTHIT:
		case TF_WEAPON_GRENADELAUNCHER:
		case TF_WEAPON_PIPEBOMBLAUNCHER:
		case TF_WEAPON_PISTOL:
		case TF_WEAPON_PISTOL_SCOUT:
		case TF_WEAPON_FLAREGUN:
		case TF_WEAPON_JAR:
		case TF_WEAPON_COMPOUND_BOW:
			return false;
		};
	}

	return true;

}


//-----------------------------------------------------------------------------------------------------
// return true if given weapon launches explosive projectiles with splash damage
bool CTFBot::IsExplosiveProjectileWeapon( CTFWeaponBase *weapon ) const
{
	if ( weapon == MY_CURRENT_GUN )
	{
		weapon = m_Shared.GetActiveTFWeapon();
	}

	if ( weapon )
	{
		switch ( weapon->GetWeaponID() )
		{
		case TF_WEAPON_ROCKETLAUNCHER:
		case TF_WEAPON_ROCKETLAUNCHER_DIRECTHIT:
		case TF_WEAPON_GRENADELAUNCHER:
		case TF_WEAPON_PIPEBOMBLAUNCHER:
		case TF_WEAPON_JAR:
			return true;
		};
	}

	return false;
}


//-----------------------------------------------------------------------------------------------------
// return true if given weapon has small clip and long reload cost (ie: rocket launcher, etc)
bool CTFBot::IsBarrageAndReloadWeapon( CTFWeaponBase *weapon ) const
{
	if ( weapon == MY_CURRENT_GUN )
	{
		weapon = m_Shared.GetActiveTFWeapon();
	}

	if ( weapon ) 
	{
		switch ( weapon->GetWeaponID() )
		{
		case TF_WEAPON_ROCKETLAUNCHER:
		case TF_WEAPON_ROCKETLAUNCHER_DIRECTHIT:
		case TF_WEAPON_GRENADELAUNCHER:
		case TF_WEAPON_PIPEBOMBLAUNCHER:
		case TF_WEAPON_SCATTERGUN:
			return true;
		};
	}

	return false;
}


//-----------------------------------------------------------------------------------------------------
// Return true if given weapon doesn't make much sound when used (ie: spy knife, etc)
bool CTFBot::IsQuietWeapon( CTFWeaponBase *weapon ) const
{
	if ( weapon == MY_CURRENT_GUN )
	{
		weapon = m_Shared.GetActiveTFWeapon();
	}

	if ( weapon ) 
	{
		switch ( weapon->GetWeaponID() )
		{
		case TF_WEAPON_KNIFE:
		case TF_WEAPON_FISTS:
		case TF_WEAPON_PDA:
		case TF_WEAPON_PDA_ENGINEER_BUILD:
		case TF_WEAPON_PDA_ENGINEER_DESTROY:
		case TF_WEAPON_PDA_SPY:
		case TF_WEAPON_BUILDER:
		case TF_WEAPON_MEDIGUN:
		case TF_WEAPON_DISPENSER:
		case TF_WEAPON_INVIS:
		case TF_WEAPON_FLAREGUN:
		case TF_WEAPON_LUNCHBOX:
		case TF_WEAPON_JAR:
		case TF_WEAPON_COMPOUND_BOW:
		case TF_WEAPON_SWORD:
		case TF_WEAPON_CROSSBOW:
			return true;
		};
	}

	return false;
}


//-----------------------------------------------------------------------------------------------------
// Return true if a weapon has no obstructions along the line between the given points
bool CTFBot::IsLineOfFireClear( const Vector &from, const Vector &to ) const
{
	trace_t trace;
	NextBotTraceFilterIgnoreActors botFilter( NULL, COLLISION_GROUP_NONE );
	CTraceFilterIgnoreFriendlyCombatItems ignoreFriendlyCombatFilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
	CTraceFilterChain filter( &botFilter, &ignoreFriendlyCombatFilter );

	UTIL_TraceLine( from, to, MASK_SOLID_BRUSHONLY, &filter, &trace );

	return !trace.DidHit();
}


//-----------------------------------------------------------------------------------------------------
// Return true if a weapon has no obstructions along the line from our eye to the given position
bool CTFBot::IsLineOfFireClear( const Vector &where ) const
{
	return IsLineOfFireClear( const_cast< CTFBot * >( this )->EyePosition(), where );
}


//-----------------------------------------------------------------------------------------------------
// Return true if a weapon has no obstructions along the line between the given point and entity
bool CTFBot::IsLineOfFireClear( const Vector &from, CBaseEntity *who ) const
{
	trace_t trace;
	NextBotTraceFilterIgnoreActors botFilter( NULL, COLLISION_GROUP_NONE );
	CTraceFilterIgnoreFriendlyCombatItems ignoreFriendlyCombatFilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
	CTraceFilterChain filter( &botFilter, &ignoreFriendlyCombatFilter );

	UTIL_TraceLine( from, who->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, &filter, &trace );

	return !trace.DidHit() || trace.m_pEnt == who;
}


//-----------------------------------------------------------------------------------------------------
// Return true if a weapon has no obstructions along the line from our eye to the given entity
bool CTFBot::IsLineOfFireClear( CBaseEntity *who ) const
{
	return IsLineOfFireClear( const_cast< CTFBot * >( this )->EyePosition(), who );
}


//-----------------------------------------------------------------------------------------------------
bool CTFBot::IsEntityBetweenTargetAndSelf( CBaseEntity *other, CBaseEntity *target )
{
	Vector toTarget = target->GetAbsOrigin() - GetAbsOrigin();
	float rangeToTarget = toTarget.NormalizeInPlace();

	Vector toOther = other->GetAbsOrigin() - GetAbsOrigin();
	float rangeToOther = toOther.NormalizeInPlace();

	return rangeToOther < rangeToTarget && DotProduct( toTarget, toOther ) > 0.7071f;
}


//-----------------------------------------------------------------------------------------------------
// Return true if we are sure this player actually is an enemy spy
bool CTFBot::IsKnownSpy( CTFPlayer *player ) const
{
	for( int i=0; i<m_knownSpyVector.Count(); ++i )
	{
		CTFPlayer *spy = m_knownSpyVector[i];
		if ( spy && player->entindex() == spy->entindex() )
		{
			return true;
		}
	}

	return false;
}


//-----------------------------------------------------------------------------------------------------
// Return true if we suspect this player might be an enemy spy
CTFBot::SuspectedSpyInfo_t* CTFBot::IsSuspectedSpy( CTFPlayer *pPlayer )
{
	for( int i=0; i<m_suspectedSpyVector.Count(); ++i )
	{
		SuspectedSpyInfo_t* pSpyInfo = m_suspectedSpyVector[i];
		CTFPlayer* pSpy = pSpyInfo->m_suspectedSpy;
		if ( pSpy && pPlayer->entindex() == pSpy->entindex() )
		{
			return pSpyInfo;
		}
	}

	return NULL;
}


//-----------------------------------------------------------------------------------------------------
// Note that this player might be a spy
void CTFBot::SuspectSpy( CTFPlayer *pPlayer )
{
	SuspectedSpyInfo_t* pSpyInfo = IsSuspectedSpy( pPlayer );

	// Start suspecting this spy if we're not aware of them until now
	if( pSpyInfo == NULL )
	{
		// add to head for LRU effect
		pSpyInfo = new SuspectedSpyInfo_t;
		pSpyInfo->m_suspectedSpy = pPlayer;
		m_suspectedSpyVector.AddToHead( pSpyInfo );
	}
	
	// Suspicious!
	pSpyInfo->Suspect();

	// Too suspicious?
	if( pSpyInfo->TestForRealizing() )
	{
		RealizeSpy( pPlayer );
	}
}

void CTFBot::SuspectedSpyInfo_t::Suspect()
{
	int nCurTime = floor(gpGlobals->curtime);

	// Add our new entry
	m_touchTimes.AddToHead( nCurTime );
}

bool CTFBot::SuspectedSpyInfo_t::TestForRealizing()
{
	// Remove any old entries
	int nCurTime = floor(gpGlobals->curtime);
	int nCutoffTime = nCurTime - tf_bot_suspect_spy_touch_interval.GetInt();

	FOR_EACH_VEC_BACK( m_touchTimes, i )
	{
		if( m_touchTimes[i] <= nCutoffTime )
			m_touchTimes.Remove( i );
	}

	// Add our new entry
	m_touchTimes.AddToHead( nCurTime );
	
	// Setup an array of bools representing the past few seconds that we want
	// to look for suspicious activity
	CUtlVector<bool> vecSeconds;
	vecSeconds.SetSize( tf_bot_suspect_spy_touch_interval.GetInt() );
	FOR_EACH_VEC( vecSeconds, i )
	{
		vecSeconds[i] = false;
	}

	// Go through each time chunk and mark if there was suspicious activity
	FOR_EACH_VEC( m_touchTimes, i )
	{
		int nTouchTime = m_touchTimes[i];
		int nTimeSlot = nCurTime - nTouchTime;

		if( nTimeSlot >= 0 && nTimeSlot < vecSeconds.Count() )
		{
			vecSeconds[nTimeSlot] = true;
		}
	}

	// If all are true, then the spy has been suspicious enough to warrant being realized
	FOR_EACH_VEC( vecSeconds, i )
	{
		if( vecSeconds[i] == false )
		{
			return false;
		}
	}

	return true;
}


bool CTFBot::SuspectedSpyInfo_t::IsCurrentlySuspected()
{
	float flCutoffTime = gpGlobals->curtime - tf_bot_suspect_spy_forget_cooldown.GetFloat();
	if( m_touchTimes.Count() && m_touchTimes.Head() > flCutoffTime )
	{
		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------------------------------
// Note that this player *IS* a spy
void CTFBot::RealizeSpy( CTFPlayer *pPlayer )
{
	// We already know about this spy
	if ( IsKnownSpy( pPlayer ) )
		return;

	// add to head for LRU effect
	m_knownSpyVector.AddToHead( pPlayer );

	// inform my teammates
	SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_CLOAKEDSPY );

	// If I am suspicious of this spy, make everyone around me know that
	// they should be suspicious too
	SuspectedSpyInfo_t* pSuspectInfo = IsSuspectedSpy( pPlayer );
	if( pSuspectInfo && pSuspectInfo->IsCurrentlySuspected() )
	{
		// Tell others around us we've realized there's a spy
		CUtlVector< CTFPlayer * > playerVector;
		CollectPlayers( &playerVector, GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS );
		FOR_EACH_VEC( playerVector, i )
		{
			CTFPlayer* pOther = playerVector[i];

			if( !pOther->IsBot() )
				continue;

			//Make sure they're close by
			Vector vecBetween = EyePosition() - pOther->EyePosition();
			if( vecBetween.IsLengthLessThan( 512.f ) )
			{
				// If they dont know about this spy
				CTFBot* pOtherBot = static_cast<CTFBot*>( pOther );
				if( !pOtherBot->IsKnownSpy( pPlayer ) )
				{
					// I was suspicious that they were a spy, make my friend suspicious as well.
					// This will cause them to attack a disguised spy in MvM for a bit.
					pOtherBot->SuspectSpy( pPlayer );

					// Tell them about it
					pOtherBot->RealizeSpy( pPlayer );
				}
			}
		}
	}
	
}


//-----------------------------------------------------------------------------------------------------
// Remove player from spy suspect system
void CTFBot::ForgetSpy( CTFPlayer *pPlayer )
{
	StopSuspectingSpy( pPlayer );
	m_knownSpyVector.FindAndFastRemove( pPlayer );
}

void CTFBot::StopSuspectingSpy( CTFPlayer *pPlayer )
{
	// Find the entry matching this spy
	for( int i=0; i<m_suspectedSpyVector.Count(); ++i )
	{
		SuspectedSpyInfo_t* pSpyInfo = m_suspectedSpyVector[i];
		CTFPlayer* pSpy = pSpyInfo->m_suspectedSpy;
		if ( pSpy && pPlayer->entindex() == pSpy->entindex() )
		{
			delete pSpyInfo;
			m_suspectedSpyVector.Remove(i);
			break;
		}
	}
}


//-----------------------------------------------------------------------------------------------------
// Return the nearest human player on the given team who is looking directly at me
CTFPlayer *CTFBot::GetClosestHumanLookingAtMe( int team ) const
{
	CUtlVector< CTFPlayer * > otherVector;
	CollectPlayers( &otherVector, team, COLLECT_ONLY_LIVING_PLAYERS );

	float closeRange = FLT_MAX;
	CTFPlayer *close = NULL;

	for( int i=0; i<otherVector.Count(); ++i )
	{
		CTFPlayer *other = otherVector[i];

		if ( other->IsBot() )
			continue;

		Vector otherEye, otherForward;
		other->EyePositionAndVectors( &otherEye, &otherForward, NULL, NULL );

		Vector toMe = const_cast< CTFBot * >( this )->EyePosition() - otherEye;
		float range = toMe.NormalizeInPlace();

		if ( range < closeRange )
		{
			const float cosTolerance = 0.98f;
			if ( DotProduct( toMe, otherForward ) > cosTolerance )
			{
				// a human is looking toward me - check LOS
				if ( IsLineOfSightClear( otherEye, IGNORE_NOTHING, other ) )
				{
					close = other;
					closeRange = range;
				}
			}
		}
	}

	return close;
}


//-----------------------------------------------------------------------------------------------------
// become a member of the given squad
void CTFBot::JoinSquad( CTFBotSquad *squad )
{
	if ( squad )
	{
		squad->Join( this );
		m_squad = squad;
	}
}


//-----------------------------------------------------------------------------------------------------
// leave our current squad
void CTFBot::LeaveSquad( void )
{
	if ( m_squad )
	{
		m_squad->Leave( this );
		m_squad = NULL;
	}
}

//-----------------------------------------------------------------------------------------------------
// leave our current squad
void CTFBot::DeleteSquad( void )
{
	if ( m_squad )
	{
		m_squad = NULL;
	}
}

//---------------------------------------------------------------------------------------------
bool CTFBot::IsWeaponRestricted( CTFWeaponBase *weapon ) const
{
	if ( !weapon )
	{
		return false;
	}

	// Get the weapon's loadout slot
	CEconItemView *pEconItemView = weapon->GetAttributeContainer()->GetItem();
	if ( !pEconItemView )
		return false;
	CTFItemDefinition *pItemDef = pEconItemView->GetStaticData();
	if ( !pItemDef )
		return false;
	int iLoadoutSlot = pItemDef->GetLoadoutSlot( GetPlayerClass()->GetClassIndex() );

	if ( HasWeaponRestriction( MELEE_ONLY ) )
	{
		return (iLoadoutSlot != LOADOUT_POSITION_MELEE);
	}

	if ( HasWeaponRestriction( PRIMARY_ONLY ) )
	{
		return (iLoadoutSlot != LOADOUT_POSITION_PRIMARY);
	}

	if ( HasWeaponRestriction( SECONDARY_ONLY ) )
	{
		return (iLoadoutSlot != LOADOUT_POSITION_SECONDARY);
	}

	return false;
}


//---------------------------------------------------------------------------------------------
//
// Return true if there is something we want to reflect directly ahead of us
//
bool CTFBot::ShouldFireCompressionBlast( void )
{
	if ( TFGameRules()->IsInTraining() )
	{
		// no reflection in training mode
		return false;
	}

	if ( !tf_bot_pyro_always_reflect.GetBool() )
	{
		if ( IsDifficulty( CTFBot::EASY ) )
		{
			// easy bots can't reflect at all
			return false;
		}

		if ( IsDifficulty( CTFBot::NORMAL ) )
		{
			// normal bots reflect some of the time
			if ( TransientlyConsistentRandomValue( 1.0f ) < 0.5f )
			{
				return false;
			}
		}

		if ( IsDifficulty( CTFBot::HARD ) )
		{
			// hard bots reflect most of the time
			if ( TransientlyConsistentRandomValue( 1.0f ) < 0.1f )
			{
				return false;
			}
		}
	}

	bool shouldPushPlayers = !TFGameRules()->IsMannVsMachineMode();

	if ( shouldPushPlayers )
	{
		const CKnownEntity *threat = GetVisionInterface()->GetPrimaryKnownThreat( true );
		if ( threat && threat->GetEntity() && threat->GetEntity()->IsPlayer() )
		{
			CTFPlayer *pushVictim = ToTFPlayer( threat->GetEntity() );

			if ( IsRangeLessThan( pushVictim, tf_bot_pyro_shove_away_range.GetFloat() ) )
			{
				// our threat is very close - shove them!

				// always shove ubers
				if ( pushVictim && pushVictim->m_Shared.IsInvulnerable() )
				{
					return true;
				}

				if ( pushVictim->GetGroundEntity() == NULL )
				{
					// they are in the air - juggle them some of the time
					return ( TransientlyConsistentRandomValue( 0.5f ) < 0.5f );
				}

				if ( pushVictim->IsCapturingPoint() )
				{
					// push them off the point!
					return true;
				}

				// be pushy sometimes
				if ( TransientlyConsistentRandomValue( 3.0f ) < 0.5f )
				{
					return true;
				}
			}
		}
	}


	Vector vecEye = EyePosition();
	Vector vecForward, vecRight, vecUp;

	AngleVectors( EyeAngles(), &vecForward, &vecRight, &vecUp );

	Vector vecCenter = vecEye + vecForward * 128;
	Vector vecSize = Vector( 128, 128, 64 );

	const int maxCollectedEntities = 128;
	CBaseEntity	*pObjects[ maxCollectedEntities ];
	int count = UTIL_EntitiesInBox( pObjects, maxCollectedEntities, vecCenter - vecSize, vecCenter + vecSize, FL_CLIENT | FL_GRENADE );

	for ( int i = 0; i < count; i++ )
	{
		CBaseEntity *pObject = pObjects[i];
		if ( pObject == this )
			continue;

		if ( pObject->GetTeamNumber() == GetTeamNumber() )
			continue;

		// should air blast player logic is already done before this loop
		if ( pObject->IsPlayer() )
			continue;

		// is this something I want to deflect?
		if ( !pObject->IsDeflectable() )
			continue;

		if ( FClassnameIs( pObject, "tf_projectile_rocket" ) || FClassnameIs( pObject, "tf_projectile_energy_ball" ) )
		{
			// is it headed right for me?
			Vector vecThemUnitVel = pObject->GetAbsVelocity();
			vecThemUnitVel.z = 0.0f;
			vecThemUnitVel.NormalizeInPlace();

			Vector horzForward( vecForward.x, vecForward.y, 0.0f );
			horzForward.NormalizeInPlace();

			if ( DotProduct( horzForward, vecThemUnitVel ) > -tf_bot_pyro_deflect_tolerance.GetFloat() )
				continue;
		}

		// can I see it?
		if ( !GetVisionInterface()->IsLineOfSightClear( pObject->WorldSpaceCenter() ) )
			continue;

		// bounce it!
		return true;
	}

	return false;
}


//---------------------------------------------------------------------------------------------
// Compute a pseudo random value (0-1) that stays consistent for the 
// given period of time, but changes unpredictably each period.
float CTFBot::TransientlyConsistentRandomValue( float period, int seedValue ) const
{
	CNavArea *area = GetLastKnownArea();
	if ( !area )
	{
		return 0.0f;
	}

	// this term stays stable for 'period' seconds, then changes in an unpredictable way
	int timeMod = (int)( gpGlobals->curtime / period ) + 1;
	return fabs( FastCos( (float)( seedValue + ( entindex() * area->GetID() * timeMod ) ) ) );
}


//---------------------------------------------------------------------------------------------
// Given a target entity, find a target within 'maxSplashRadius' that has clear line of fire
// to both the target entity and to me.
bool CTFBot::FindSplashTarget( CBaseEntity *target, float maxSplashRadius, Vector *splashTarget ) const
{
	if ( !target || !splashTarget )
		return false;

	*splashTarget = target->WorldSpaceCenter();

	const int retryCount = 50;
	for( int i=0; i<retryCount; ++i )
	{
		Vector probe = target->WorldSpaceCenter() + RandomVector( -maxSplashRadius, maxSplashRadius );

		trace_t trace;
		NextBotTraceFilterIgnoreActors filter( NULL, COLLISION_GROUP_NONE );

		UTIL_TraceLine( target->WorldSpaceCenter(), probe, MASK_SOLID_BRUSHONLY, &filter, &trace );
		if ( trace.DidHitWorld() )
		{
			// can we shoot this spot?
			if ( IsLineOfFireClear( trace.endpos ) )
			{
				// yes, found a corner-sticky target
				*splashTarget = trace.endpos;

				NDebugOverlay::Line( target->WorldSpaceCenter(), trace.endpos, 255, 0, 0, true, 60.0f );
				NDebugOverlay::Cross3D( trace.endpos, 5.0f, 255, 255, 0, true, 60.0f );

				return true;
			}
		}
	}

	return false;
}


//---------------------------------------------------------------------------------------------
// Restrict bot's attention to only this entity (or radius around this entity) to the exclusion of everything else
void CTFBot::SetAttentionFocus( CBaseEntity *focusOn )
{
	m_attentionFocusEntity = focusOn;
}


//---------------------------------------------------------------------------------------------
// Remove attention focus restrictions
void CTFBot::ClearAttentionFocus( void )
{
	m_attentionFocusEntity = NULL;
}


//---------------------------------------------------------------------------------------------
bool CTFBot::IsAttentionFocused( void ) const
{
	return m_attentionFocusEntity != NULL;
}


//---------------------------------------------------------------------------------------------
bool CTFBot::IsAttentionFocusedOn( CBaseEntity *who ) const
{
	if ( m_attentionFocusEntity == NULL || who == NULL )
	{
		return false;
	}

	if ( m_attentionFocusEntity->entindex() == who->entindex() )
	{
		// specifically focused on this entity
		return true;
	}

	CTFBotActionPoint *actionPoint = dynamic_cast< CTFBotActionPoint * >( m_attentionFocusEntity.Get() );
	if ( actionPoint )
	{
		// we attend to everything within the action point's radius
		return actionPoint->IsWithinRange( who );
	}

	return false;
}


//---------------------------------------------------------------------------------------------
// Notice the given threat after the given number of seconds have elapsed
void CTFBot::DelayedThreatNotice( CHandle< CBaseEntity > who, float noticeDelay )
{
	float when = gpGlobals->curtime + noticeDelay;

	// if we already have a delayed notice for this threat, ignore the new one unless the delay is less
	for( int i=0; i<m_delayedNoticeVector.Count(); ++i )
	{
		if ( m_delayedNoticeVector[i].m_who == who )
		{
			if ( m_delayedNoticeVector[i].m_when > when )
			{
				// update delay to shorter time
				m_delayedNoticeVector[i].m_when = when;
			}
			return;
		}
	}

	// new notice
	DelayedNoticeInfo delay;
	delay.m_who = who;
	delay.m_when = when;
	m_delayedNoticeVector.AddToTail( delay );
}


//---------------------------------------------------------------------------------------------
void CTFBot::UpdateDelayedThreatNotices( void )
{
	for( int i=0; i<m_delayedNoticeVector.Count(); ++i )
	{
		if ( m_delayedNoticeVector[i].m_when <= gpGlobals->curtime )
		{
			// delay is up - notice this threat
			CBaseEntity *who = m_delayedNoticeVector[i].m_who;

			if ( who )
			{
				if ( who->IsPlayer() )
				{
					CTFPlayer *player = ToTFPlayer( who );
					if ( player->IsPlayerClass( TF_CLASS_SPY ) )
					{
						RealizeSpy( player );
					}
				}

				GetVisionInterface()->AddKnownEntity( who );
			}

			m_delayedNoticeVector.Remove( i );
			--i;
		}
	}
}


//---------------------------------------------------------------------------------------------
void CTFBot::GiveRandomItem( loadout_positions_t loadoutPosition )
{
	CUtlVector< const CEconItemDefinition * > itemVector;

	const CEconItemSchema::ItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetItemDefinitionMap();
	FOR_EACH_MAP_FAST( mapItemDefs, i )
	{
		const CTFItemDefinition *pItemDef = dynamic_cast< const CTFItemDefinition * >( mapItemDefs[i] );

		if ( pItemDef && pItemDef->GetLoadoutSlot( GetPlayerClass()->GetClassIndex() ) == loadoutPosition )
		{
			itemVector.AddToTail( pItemDef );
		}
	}

	if ( itemVector.Count() > 0 )
	{
		int which = RandomInt( 0, itemVector.Count()-1 );

/*
		CBaseCombatWeapon *myMelee = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
		me->Weapon_Detach( myMelee );
		UTIL_Remove( myMelee );
*/

		const char *itemName = itemVector[ which ]->GetDefinitionName();
		BotGenerateAndWearItem( this, itemName );
	}
}


//---------------------------------------------------------------------------------------------
bool CTFBot::IsSquadmate( CTFPlayer *who ) const
{
	if ( !m_squad || !who || !who->IsBotOfType( TF_BOT_TYPE ) )
		return false;

	return GetSquad() == ToTFBot( who )->GetSquad();
}


//---------------------------------------------------------------------------------------------
// Set Spy disguise to be a class that someone on the enemy team is actually using
void CTFBot::DisguiseAsMemberOfEnemyTeam( void )
{
	CUtlVector< CTFPlayer * > enemyVector;
	CollectPlayers( &enemyVector, GetEnemyTeam( GetTeamNumber() ) );

	int disguise = RandomInt( TF_FIRST_NORMAL_CLASS, TF_LAST_NORMAL_CLASS-1 );

	if ( enemyVector.Count() > 0 )
	{
		disguise = enemyVector[ RandomInt( 0, enemyVector.Count()-1 ) ]->GetPlayerClass()->GetClassIndex();
	}

	m_Shared.Disguise( GetEnemyTeam( GetTeamNumber() ), disguise );
}


//---------------------------------------------------------------------------------------------
void CTFBot::ClearTags( void )
{
	m_tags.RemoveAll();
}


//---------------------------------------------------------------------------------------------
void CTFBot::AddTag( const char *tag )
{
	if ( !HasTag( tag ) )
	{
		m_tags.AddToTail( CFmtStr( "%s", tag ) );
	}
}


//---------------------------------------------------------------------------------------------
void CTFBot::RemoveTag( const char *tag )
{
	for ( int i=0; i<m_tags.Count(); ++i )
	{
		if ( FStrEq( tag, m_tags[i] ) )
		{
			m_tags.Remove(i);
			return;
		}
	}
}


//---------------------------------------------------------------------------------------------
// TODO: Make this an efficient lookup/match
bool CTFBot::HasTag( const char *tag )
{
	for( int i=0; i<m_tags.Count(); ++i )
	{
		if ( FStrEq( tag, m_tags[i] ) )
		{
			return true;
		}
	}

	return false;
}


//---------------------------------------------------------------------------------------------
CBaseObject *CTFBot::GetNearestKnownSappableTarget( void )
{
	CUtlVector< CKnownEntity > knownVector;
	GetVisionInterface()->CollectKnownEntities( &knownVector );

	CBaseObject *closeObject = NULL;
	float closeObjectRangeSq = 500.0f * 500.0f;

	for( int i=0; i<knownVector.Count(); ++i )
	{
		CBaseObject *enemyObject = dynamic_cast< CBaseObject * >( knownVector[i].GetEntity() );
		if ( enemyObject && !enemyObject->HasSapper() && IsEnemy( enemyObject ) )
		{
			float rangeSq = GetRangeSquaredTo( enemyObject );
			if ( rangeSq < closeObjectRangeSq )
			{
				closeObjectRangeSq = rangeSq;
				closeObject = enemyObject;
			}		
		}
	}

	return closeObject;
}


//-----------------------------------------------------------------------------------------
Action< CTFBot > *CTFBot::OpportunisticallyUseWeaponAbilities( void )
{
	if ( !m_opportunisticTimer.IsElapsed() )
	{
		return NULL;
	}

	m_opportunisticTimer.Start( RandomFloat( 0.1f, 0.2f ) );


	// if I'm wearing a charge shield, use it!
	if ( IsPlayerClass( TF_CLASS_DEMOMAN ) && m_Shared.IsShieldEquipped() )
	{
		Vector forward;
		EyeVectors( &forward );
		bool bShouldCharge = GetLocomotionInterface()->IsPotentiallyTraversable( GetAbsOrigin(), GetAbsOrigin() + 100.0f * forward, ILocomotion::IMMEDIATELY );
		if ( HasAttribute( CTFBot::AIR_CHARGE_ONLY ) && ( GetGroundEntity() || GetAbsVelocity().z > 0 ) )
		{
			bShouldCharge = false;
		}

		if ( bShouldCharge )
		{
			PressAltFireButton();
		}
	}
	// if I'm wearing parachute, check if I should activate my parachute
	else if ( m_Shared.IsParachuteEquipped() )
	{
		bool bIsBurning = m_Shared.InCond( TF_COND_BURNING );
		float flHealthPercent = (float)GetHealth() / GetMaxHealth();
		const float flHealthThreshold = 0.5f;
		// should I activate parachute?
		if ( !m_Shared.InCond( TF_COND_PARACHUTE_DEPLOYED ) )
		{
			float flMinParachuteGroundDistance = 300.f;
			// check if I'm falling, high enough off the ground to deploy parachute, and not burning
			if ( flHealthPercent >= flHealthThreshold && !bIsBurning && GetAbsVelocity().z < 0 && GetLocomotionInterface()->IsPotentiallyTraversable( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, -flMinParachuteGroundDistance ), ILocomotion::IMMEDIATELY ) )
			{
				PressJumpButton();
			}
		}
		// should I deactivate parachute?
		else
		{
			float flCancelParachuteDistance = 150.f;
			// if I'm burning or close enough to landing, deactivate the parachute or health less than some threshold
			if ( flHealthPercent < flHealthThreshold || bIsBurning || !GetLocomotionInterface()->IsPotentiallyTraversable( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, -flCancelParachuteDistance ), ILocomotion::IMMEDIATELY ) )
			{
				PressJumpButton();
			}
		}
	}

	// don't use items if we have the flag, since most of them are unusable (unless we're a bomb carrier in MvM)
	if ( HasTheFlag() && !TFGameRules()->IsMannVsMachineMode() )
	{
		return NULL;
	}

	for ( int w=0; w<MAX_WEAPONS; ++w )
	{
		CTFWeaponBase *weapon = ( CTFWeaponBase * )GetWeapon( w );
		if ( !weapon )
			continue;

		// if I have some kind of buff banner - use it!
		if ( weapon->GetWeaponID() == TF_WEAPON_BUFF_ITEM )
		{
			CTFBuffItem *buff = (CTFBuffItem *)weapon;
			if ( buff->IsFull() )
			{
				return new CTFBotUseItem( buff );
			}
		}
		else if ( weapon->GetWeaponID() == TF_WEAPON_LUNCHBOX )
		{
			// if we have an eatable (drink, sandvich, etc) - eat it!
			CTFLunchBox *lunchbox = (CTFLunchBox *)weapon;
			if ( lunchbox->HasAmmo() )
			{
				// scout lunchboxes are also gated by their energy drink meter
				if ( !IsPlayerClass( TF_CLASS_SCOUT ) || m_Shared.GetScoutEnergyDrinkMeter() >= 100 )
				{
					return new CTFBotUseItem( lunchbox );
				}
			}
		}
		else if ( weapon->GetWeaponID() == TF_WEAPON_BAT_WOOD )
		{
			// sandman
			if ( GetAmmoCount( TF_AMMO_GRENADES1 ) > 0 )
			{
				const CKnownEntity *threat = GetVisionInterface()->GetPrimaryKnownThreat();
				if ( threat && threat->IsVisibleInFOVNow() )
				{
					// hit a stunball
					PressAltFireButton();			
				}
			}
		}
	}

	return NULL;
}


//-----------------------------------------------------------------------------------------
// mostly for MvM - pick a random enemy player that is not in their spawn room
CTFPlayer *CTFBot::SelectRandomReachableEnemy( void )
{
	CUtlVector< CTFPlayer * > livePlayerVector;
	CollectPlayers( &livePlayerVector, GetEnemyTeam( GetTeamNumber() ), COLLECT_ONLY_LIVING_PLAYERS );

	// only consider players who have left their spawn
	CUtlVector< CTFPlayer * > playerVector;
	for( int i=0; i<livePlayerVector.Count(); ++i )
	{
		CTFPlayer *player = livePlayerVector[i];
		if ( !PointInRespawnRoom( player, player->WorldSpaceCenter() ) )
		{
			playerVector.AddToTail( player );
		}
	}

	if ( playerVector.Count() > 0 )
	{
		return playerVector[ RandomInt( 0, playerVector.Count()-1 ) ];
	}

	return NULL;
}


//-----------------------------------------------------------------------------------------
// Different sized bots used different lookahead distances
float CTFBot::GetDesiredPathLookAheadRange( void ) const
{
	return tf_bot_path_lookahead_range.GetFloat() * GetModelScale();
}

//-----------------------------------------------------------------------------------------
// Hack to apply idle loop sounds in MvM
void CTFBot::StartIdleSound( void )
{
	StopIdleSound();

	if ( TFGameRules() && !TFGameRules()->IsMannVsMachineMode() )
		return;

	// SHIELD YOUR EYES MIKEB!!!
	if ( IsMiniBoss() )
	{
		const char *pszSoundName = NULL;

		int iClass = GetPlayerClass()->GetClassIndex();
		switch ( iClass )
		{
		case TF_CLASS_HEAVYWEAPONS:
			{
				pszSoundName = "MVM.GiantHeavyLoop";
				break;
			}
		case TF_CLASS_SOLDIER:
			{
				pszSoundName = "MVM.GiantSoldierLoop";
				break;
			}
		case TF_CLASS_DEMOMAN:
			{
				if ( m_mission == MISSION_DESTROY_SENTRIES )
				{
					pszSoundName = "MVM.SentryBusterLoop";
				}
				else
				{
					pszSoundName = "MVM.GiantDemomanLoop";
				}
				break;
			}
		case TF_CLASS_SCOUT:
			{
				pszSoundName = "MVM.GiantScoutLoop";
				break;
			}
		case TF_CLASS_PYRO:
			{
				pszSoundName = "MVM.GiantPyroLoop";
				break;
			}
		}

		if ( pszSoundName )
		{
			CReliableBroadcastRecipientFilter filter;
			CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
			m_pIdleSound = controller.SoundCreate( filter, entindex(), pszSoundName );
			controller.Play( m_pIdleSound, 1.0, 100 );
		}
	}
}

//-----------------------------------------------------------------------------------------
void CTFBot::StopIdleSound( void )
{
	if ( m_pIdleSound )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pIdleSound );
		m_pIdleSound = NULL;
	}
}

bool CTFBot::ShouldAutoJump()
{
	if ( !HasAttribute( CTFBot::AUTO_JUMP ) )
		return false;

	if ( !m_autoJumpTimer.HasStarted() )
	{
		m_autoJumpTimer.Start( RandomFloat( m_flAutoJumpMin, m_flAutoJumpMax ) );
		return true;
	}
	else if ( m_autoJumpTimer.IsElapsed() )
	{
		m_autoJumpTimer.Start( RandomFloat( m_flAutoJumpMin, m_flAutoJumpMax ) );
		return true;
	}
	
	return false;
}


void CTFBot::SetFlagTarget( CCaptureFlag* pFlag )
{
	if ( m_hFollowingFlagTarget != pFlag )
	{
		if ( m_hFollowingFlagTarget )
		{
			m_hFollowingFlagTarget->RemoveFollower( this );
		}

		m_hFollowingFlagTarget = pFlag;
		if ( m_hFollowingFlagTarget )
		{
			m_hFollowingFlagTarget->AddFollower( this );
		}
	}
}


int CTFBot::DrawDebugTextOverlays(void)
{
	int offset = tf_bot_debug_tags.GetBool() ? 1 : BaseClass::DrawDebugTextOverlays();

	CUtlString strTags = "Tags : ";
	for( int i=0; i<m_tags.Count(); ++i )
	{
		strTags.Append( m_tags[i] );
		strTags.Append( " " );
	}

	EntityText( offset, strTags.Get(), 0 );
	offset++;

	return offset;
}


void CTFBot::AddEventChangeAttributes( const CTFBot::EventChangeAttributes_t* newEvent )
{
	m_eventChangeAttributes.AddToTail( newEvent );
}


const CTFBot::EventChangeAttributes_t* CTFBot::GetEventChangeAttributes( const char* pszEventName ) const
{
	for ( int i=0; i<m_eventChangeAttributes.Count(); ++i )
	{
		if ( FStrEq( m_eventChangeAttributes[i]->m_eventName, pszEventName ) )
		{
			return m_eventChangeAttributes[i];
		}
	}
	return NULL;
}


void CTFBot::OnEventChangeAttributes( const CTFBot::EventChangeAttributes_t* pEvent )
{
	if ( pEvent )
	{
		SetDifficulty( pEvent->m_skill );

		ClearWeaponRestrictions();
		SetWeaponRestriction( pEvent->m_weaponRestriction );

		SetMission( pEvent->m_mission );

		ClearAllAttributes();
		SetAttribute( pEvent->m_attributeFlags );

		SetMaxVisionRangeOverride( pEvent->m_maxVisionRange );

		if ( TFGameRules()->IsMannVsMachineMode() )
		{
			SetAttribute( CTFBot::BECOME_SPECTATOR_ON_DEATH );
			SetAttribute( CTFBot::RETAIN_BUILDINGS );
		}

		// cache off health value before we clear attribute because ModifyMaxHealth adds new attribute and reset the health
		int nHealth = GetHealth();
		int nMaxHealth = GetMaxHealth();

		// remove any player attributes
		RemovePlayerAttributes( false );
		// and add ones that we want specifically
		FOR_EACH_VEC( pEvent->m_characterAttributes, i )
		{
			const CEconItemAttributeDefinition *pDef = pEvent->m_characterAttributes[i].GetAttributeDefinition();
			if ( pDef )
			{
				Assert( GetAttributeList() );
				GetAttributeList()->SetRuntimeAttributeValue( pDef, pEvent->m_characterAttributes[i].m_value.asFloat );
			}
		}
		NetworkStateChanged();

		// set health back to what it was before we clear bot's attributes
		ModifyMaxHealth( nMaxHealth );
		SetHealth( nHealth );

		// give items to bot before apply attribute changes
		FOR_EACH_VEC( pEvent->m_items, i )
		{
			AddItem( pEvent->m_items[i] );
		}

		// add attributes to equipped items
		FOR_EACH_VEC( pEvent->m_itemsAttributes, i )
		{
			const CTFBot::EventChangeAttributes_t::item_attributes_t& itemAttributes = pEvent->m_itemsAttributes[i];
			CSchemaItemDefHandle itemDef( itemAttributes.m_itemName );
			if ( !itemDef )
			{
				Warning( "Unable to find item %s to update attribute.\n", itemAttributes.m_itemName.Get() ); 
			}

			for ( int iItemSlot = LOADOUT_POSITION_PRIMARY ; iItemSlot < CLASS_LOADOUT_POSITION_COUNT ; iItemSlot++ )
			{
				CEconEntity* pEntity = NULL;
				CEconItemView *pCurItemData = CTFPlayerSharedUtils::GetEconItemViewByLoadoutSlot( this, iItemSlot, &pEntity );
				if ( pCurItemData && itemDef && ( pCurItemData->GetItemDefIndex() == itemDef->GetDefinitionIndex() ) )
				{
					for ( int iAtt=0; iAtt<itemAttributes.m_attributes.Count(); ++iAtt )
					{
						const static_attrib_t& attrib = itemAttributes.m_attributes[iAtt];
						CAttributeList *pAttribList = pCurItemData->GetAttributeList();
						if ( pAttribList )
						{
							pAttribList->SetRuntimeAttributeValue( attrib.GetAttributeDefinition(), attrib.m_value.asFloat );
						}
					}

					if ( pEntity )
					{
						// update model incase we change style
						pEntity->UpdateModelToClass();
					}

					// move on to the next set of attributes
					break;
				}
			} // for each slot
		} // for each set of attributes

		// tags
		ClearTags();
		for( int g=0; g<pEvent->m_tags.Count(); ++g )
		{
			AddTag( pEvent->m_tags[g] );
		}
	}
}


void CTFBot::AddItem( const char* pszItemName )
{
	CItemSelectionCriteria criteria;
	criteria.SetQuality( AE_USE_SCRIPT_VALUE );
	criteria.BAddCondition( "name", k_EOperator_String_EQ, pszItemName, true );

	CBaseEntity *pItem = ItemGeneration()->GenerateRandomItem( &criteria, WorldSpaceCenter(), vec3_angle );
	if ( pItem )
	{
		CEconItemView *pScriptItem = static_cast< CBaseCombatWeapon * >( pItem )->GetAttributeContainer()->GetItem();

		// If we already have an item in that slot, remove it
		int iClass = GetPlayerClass()->GetClassIndex();
		int iSlot = pScriptItem->GetStaticData()->GetLoadoutSlot( iClass );
		equip_region_mask_t unNewItemRegionMask = pScriptItem->GetItemDefinition() ? pScriptItem->GetItemDefinition()->GetEquipRegionConflictMask() : 0;

		if ( IsWearableSlot( iSlot ) )
		{
			// Remove any wearable that has a conflicting equip_region
			for ( int wbl = 0; wbl < GetNumWearables(); wbl++ )
			{
				CEconWearable *pWearable = GetWearable( wbl );
				if ( !pWearable )
					continue;

				equip_region_mask_t unWearableRegionMask = 0;
				if ( pWearable->GetAttributeContainer()->GetItem() )
				{
					unWearableRegionMask = pWearable->GetAttributeContainer()->GetItem()->GetItemDefinition()->GetEquipRegionConflictMask();
				}

				if ( unWearableRegionMask & unNewItemRegionMask )
				{
					RemoveWearable( pWearable );
				}
			}
		}
		else
		{
			CBaseEntity	*pEntity = GetEntityForLoadoutSlot( iSlot );
			if ( pEntity )
			{
				CBaseCombatWeapon *pWpn = dynamic_cast< CBaseCombatWeapon * >( pEntity );
				Weapon_Detach( pWpn );
				UTIL_Remove( pEntity );
			}
		}

		// Fake global id
		pScriptItem->SetItemID( 1 );

		DispatchSpawn( pItem );

		CEconEntity *pNewItem = assert_cast<CEconEntity*>( pItem );
		if ( pNewItem )
		{
			pNewItem->GiveTo( this );
		}

		PostInventoryApplication();
	}
	else
	{
		if ( pszItemName && pszItemName[0] )
		{
			DevMsg( "CTFBotSpawner::AddItemToBot: Invalid item %s.\n", pszItemName );
		}
	}
}


int CTFBot::GetUberHealthThreshold()
{
	int iUberHealthThreshold = 0;
	CALL_ATTRIB_HOOK_INT( iUberHealthThreshold, bot_medic_uber_health_threshold );
	if ( iUberHealthThreshold > 0 )
	{
		return iUberHealthThreshold;
	}

	return 50;
}


float CTFBot::GetUberDeployDelayDuration()
{
	float flDelayUberDuration = 0;
	CALL_ATTRIB_HOOK_INT( flDelayUberDuration, bot_medic_uber_deploy_delay_duration );
	if ( flDelayUberDuration > 0 )
	{
		return flDelayUberDuration;
	}
	
	return -1.f;
}