text: //========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============
  //
  // Purpose: 
  //
  // $NoKeywords: $
  //=============================================================================
  
  // Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
  
  #pragma warning( disable : 4530 )					// STL uses exceptions, but we are not compiling with them - ignore warning
  
  #define DEFINE_EVENT_NAMES
  
  #include "extdll.h"
  #include "util.h"
  #include "cbase.h"
  #include "weapons.h"
  #include "soundent.h"
  #include "gamerules.h"
  #include "player.h"
  #include "client.h"
  #include "perf_counter.h"
  
  #include "bot.h"
  #include "bot_manager.h"
  #include "nav_area.h"
  #include "bot_util.h"
  #include "hostage.h"
  
  #include "tutor.h"
  
  const float smokeRadius = 115.0f;		///< for smoke grenades
  
  
  //#define CHECK_PERFORMANCE
  #ifdef CHECK_PERFORMANCE
  	// crude performance timing
  	static CPerformanceCounter perfCounter;
  
  	struct PerfInfo
  	{
  		float frameTime;
  		float botThinkTime;
  	};
  
  	#define MAX_PERF_DATA 50000
  	static PerfInfo perfData[ MAX_PERF_DATA ];
  	static int perfDataCount = 0;
  	static int perfFileIndex = 0;
  #endif
  
  
  
  /**
   * Convert name to GameEventType
   * @todo Find more appropriate place for this function
   */
  GameEventType NameToGameEvent( const char *name )
  {
  	for( int i=0; GameEventName[i]; ++i )
  		if (!stricmp( GameEventName[i], name ))
  			return static_cast<GameEventType>( i );
  
  	return EVENT_INVALID;
  }
  
  
  //--------------------------------------------------------------------------------------------------------------
  CBotManager::CBotManager()
  {
  	InitBotTrig();
  }
  
  //--------------------------------------------------------------------------------------------------------------
  /**
   * Invoked when the round is restarting
   */
  void CBotManager::RestartRound( void )
  {
  #ifdef CHECK_PERFORMANCE
  	// dump previous round's performance
  	char filename[80];
  	sprintf( filename, "perfdata%02X.txt", perfFileIndex++ );
  	FILE *fp = fopen( filename, "w" );
  
  	if (fp)
  	{
  		for( int p=0; p<perfDataCount; ++p )
  			fprintf( fp, "%f\t%f\n", perfData[p].frameTime, perfData[p].botThinkTime );
  
  		fclose( fp );
  	}
  		
  	perfDataCount = 0;
  #endif
  
  	DestroyAllGrenades();
  }
  
  //--------------------------------------------------------------------------------------------------------------
  /**
   * Invoked at the start of each frame
   */
  void CBotManager::StartFrame( void )
  {
  	// debug smoke grenade visualization
  	if (cv_bot_debug.value == 5)
  	{
  		Vector edge, lastEdge;
  
  		ActiveGrenadeList::iterator iter = m_activeGrenadeList.begin();
  		while( iter != m_activeGrenadeList.end() )
  		{
  			ActiveGrenade *ag = *iter;
  
  			// lazy validation
  			if (!ag->IsValid())
  			{
  				delete ag;
  				iter = m_activeGrenadeList.erase( iter );
  				continue;
  			}
  			else
  			{
  				++iter;
  			}
  
  			const Vector *pos = ag->GetDetonationPosition();
  
  			UTIL_DrawBeamPoints( *pos, *pos + Vector( 0, 0, 50 ), 1, 255, 100, 0 );
  
  			lastEdge = Vector( smokeRadius + pos->x, pos->y, pos->z );
  			float angle;
  			for( angle=0.0f; angle <= 180.0f; angle += 22.5f )
  			{
  				edge.x = smokeRadius * BotCOS( angle ) + pos->x;
  				edge.y = pos->y;
  				edge.z = smokeRadius * BotSIN( angle ) + pos->z;
  
  				UTIL_DrawBeamPoints( edge, lastEdge, 1, 255, 50, 0 );
  
  				lastEdge = edge;
  			}
  
  			lastEdge = Vector( pos->x, smokeRadius + pos->y, pos->z );
  			for( angle=0.0f; angle <= 180.0f; angle += 22.5f )
  			{
  				edge.x = pos->x;
  				edge.y = smokeRadius * BotCOS( angle ) + pos->y;
  				edge.z = smokeRadius * BotSIN( angle ) + pos->z;
  
  				UTIL_DrawBeamPoints( edge, lastEdge, 1, 255, 50, 0 );
  
  				lastEdge = edge;
  			}
  		}
  	}
  
  
  	//
  	// Process each active bot
  	//
  
  #ifdef CHECK_PERFORMANCE
  	static double lastTime = 0.0f;
  	double startTime = perfCounter.GetCurTime();
  #endif
  
  	for( int i = 1; i <= gpGlobals->maxClients; ++i )
  	{
  		CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i );
  
  		if (!pPlayer)
  			continue;
  
  		if (pPlayer->IsBot() && IsEntityValid( pPlayer ))
  		{
  			CBot *pBot = static_cast<CBot *>( pPlayer );
  
  			pBot->BotThink();
  		}
  	}
  
  #ifdef CHECK_PERFORMANCE
  	if (perfDataCount < MAX_PERF_DATA)
  	{
  		if (lastTime > 0.0f)
  		{
  			double endTime = perfCounter.GetCurTime();
  
  			perfData[ perfDataCount ].frameTime = (float)(startTime - lastTime);
  			perfData[ perfDataCount ].botThinkTime = (float)(endTime - startTime);
  			++perfDataCount;
  		}
  
  		lastTime = startTime;
  	}
  #endif
  }
  
  //--------------------------------------------------------------------------------------------------------------
  /**
   * Return the filename for this map's "nav map" file
   */
  const char *CBotManager::GetNavMapFilename( void ) const
  {
  	static char filename[256];
  	sprintf( filename, "maps\\%s.nav", STRING( gpGlobals->mapname ) );
  	return filename;
  }
  
  //--------------------------------------------------------------------------------------------------------------
  /**
   * Invoked when given player does given event (some events have NULL player).
   * Events are propogated to all bots.
   *
   * @todo This has become the game-wide event dispatcher. We should restructure this.
   */
  void CBotManager::OnEvent( GameEventType event, CBaseEntity *entity, CBaseEntity *other )
  {
  	// propogate event to all bots
  	for ( int i=1; i <= gpGlobals->maxClients; ++i )
  	{
  		CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  
  		if (player == NULL)
  			continue;
  
  		if (FNullEnt( player->pev ))
  			continue;
  
  		if (FStrEq( STRING( player->pev->netname ), "" ))
  			continue;
  
  		if (!player->IsBot())
  			continue;
  
  		// do not send self-generated event
  		if (entity == player)
  			continue;
  
  		CBot *bot = static_cast<CBot *>( player );
  		bot->OnEvent( event, entity, other );
  	}
  
  	if (TheTutor)
  		TheTutor->OnEvent( event, entity, other );
  
  	if (g_pHostages)
  		g_pHostages->OnEvent( event, entity, other );
  }
  
  //--------------------------------------------------------------------------------------------------------------
  /**
   * Add an active grenade to the bot's awareness
   */
  void CBotManager::AddGrenade( int type, CGrenade *grenade )
  {
  	ActiveGrenade *ag = new ActiveGrenade( type, grenade );
  	m_activeGrenadeList.push_back( ag );
  }
  
  //--------------------------------------------------------------------------------------------------------------
  /**
   * The grenade entity in the world is going away
   */
  void CBotManager::RemoveGrenade( CGrenade *grenade )
  {
  	for( ActiveGrenadeList::iterator iter = m_activeGrenadeList.begin(); iter != m_activeGrenadeList.end(); ++iter )
  	{
  		ActiveGrenade *ag = *iter;
  
  		if (ag->IsEntity( grenade ))
  		{
  			ag->OnEntityGone();
  			return;
  		}
  	}
  }
  
  //--------------------------------------------------------------------------------------------------------------
  /**
   * Destroy any invalid active grenades
   */
  void CBotManager::ValidateActiveGrenades( void )
  {
  	ActiveGrenadeList::iterator iter = m_activeGrenadeList.begin();
  	while( iter != m_activeGrenadeList.end() )
  	{
  		ActiveGrenade *ag = *iter;
  
  		if (!ag->IsValid())
  		{
  			delete ag;
  			iter = m_activeGrenadeList.erase( iter );
  		}
  		else
  		{
  			++iter;
  		}
  	}
  }
  
  //--------------------------------------------------------------------------------------------------------------
  void CBotManager::DestroyAllGrenades( void )
  {
  	for( ActiveGrenadeList::iterator iter = m_activeGrenadeList.begin(); iter != m_activeGrenadeList.end(); ++iter )
  		delete *iter;
  
  	m_activeGrenadeList.clear();
  }
  
  //--------------------------------------------------------------------------------------------------------------
  /**
   * Return true if position is inside a smoke cloud
   */
  bool CBotManager::IsInsideSmokeCloud( const Vector *pos )
  {
  	ActiveGrenadeList::iterator iter = m_activeGrenadeList.begin();
  	while( iter != m_activeGrenadeList.end() )
  	{
  		ActiveGrenade *ag = *iter;
  
  		// lazy validation
  		if (!ag->IsValid())
  		{
  			delete ag;
  			iter = m_activeGrenadeList.erase( iter );
  			continue;
  		}
  		else
  		{
  			++iter;
  		}
  
  		if (ag->GetID() == WEAPON_SMOKEGRENADE)
  		{
  			const Vector *smokeOrigin = ag->GetDetonationPosition();
  
  			if ((*smokeOrigin - *pos).IsLengthLessThan( smokeRadius ))
  				return true;			
  		}
  	}
  
  	return false;
  }
  
  //--------------------------------------------------------------------------------------------------------------
  /**
   * Return true if line intersects smoke volume
   * Determine the length of the line of sight covered by each smoke cloud, 
   * and sum them (overlap is additive for obstruction).
   * If the overlap exceeds the threshold, the bot can't see through.
   */
  bool CBotManager::IsLineBlockedBySmoke( const Vector *from, const Vector *to )
  {
  	const float smokeRadiusSq = smokeRadius * smokeRadius;
  	float totalSmokedLength = 0.0f;	// distance along line of sight covered by smoke
  
  	// compute unit vector and length of line of sight segment
  	Vector sightDir = *to - *from;
  	float sightLength = sightDir.NormalizeInPlace();
  
  
  	ActiveGrenadeList::iterator iter = m_activeGrenadeList.begin();
  	while( iter != m_activeGrenadeList.end() )
  	{
  		ActiveGrenade *ag = *iter;
  
  		// lazy validation
  		if (!ag->IsValid())
  		{
  			delete ag;
  			iter = m_activeGrenadeList.erase( iter );
  			continue;
  		}
  		else
  		{
  			++iter;
  		}
  
  		if (ag->GetID() == WEAPON_SMOKEGRENADE)
  		{
  			const Vector *smokeOrigin = ag->GetDetonationPosition();
  
  			Vector toGrenade = *smokeOrigin - *from;
  
  			float alongDist = DotProduct( toGrenade, sightDir );
  
  			// compute closest point to grenade along line of sight ray
  			Vector close;
  
  			// constrain closest point to line segment
  			if (alongDist < 0.0f)
  				close = *from;
  			else if (alongDist >= sightLength)
  				close = *to;
  			else
  				close = *from + sightDir * alongDist;
  
  			// if closest point is within smoke radius, the line overlaps the smoke cloud
  			Vector toClose = close - *smokeOrigin;
  			float lengthSq = toClose.LengthSquared();
  
  			if (lengthSq < smokeRadiusSq)
  			{
  				// some portion of the ray intersects the cloud
  
  				float fromSq = toGrenade.LengthSquared();
  				float toSq = (*smokeOrigin - *to).LengthSquared();
  
  				if (fromSq < smokeRadiusSq)
  				{
  					if (toSq < smokeRadiusSq)
  					{
  						// both 'from' and 'to' lie within the cloud
  						// entire length is smoked
  						totalSmokedLength += (*to - *from).Length();
  					}
  					else
  					{
  						// 'from' is inside the cloud, 'to' is outside
  						// compute half of total smoked length as if ray crosses entire cloud chord
  						float halfSmokedLength = sqrt( smokeRadiusSq - lengthSq );
  
  						if (alongDist > 0.0f)
  						{
  							// ray goes thru 'close'
  							totalSmokedLength += halfSmokedLength + (close - *from).Length();						
  						}
  						else
  						{
  							// ray starts after 'close'
  							totalSmokedLength += halfSmokedLength - (close - *from).Length();						
  						}
  
  					}
  				}
  				else if (toSq < smokeRadiusSq)
  				{
  					// 'from' is outside the cloud, 'to' is inside
  					// compute half of total smoked length as if ray crosses entire cloud chord
  					float halfSmokedLength = sqrt( smokeRadiusSq - lengthSq );
  
  					Vector v = *to - *smokeOrigin;
  					if (DotProduct( v, sightDir ) > 0.0f)
  					{
  						// ray goes thru 'close'
  						totalSmokedLength += halfSmokedLength + (close - *to).Length();					
  					}
  					else
  					{
  						// ray ends before 'close'
  						totalSmokedLength += halfSmokedLength - (close - *to).Length();
  					}
  				}
  				else
  				{			
  					// 'from' and 'to' lie outside of the cloud - the line of sight completely crosses it
  					// determine the length of the chord that crosses the cloud
  					float smokedLength = 2.0f * sqrt( smokeRadiusSq - lengthSq );
  
  					totalSmokedLength += smokedLength;
  				}
  			}
  		}
  	}
  
  	// define how much smoke a bot can see thru
  	const float maxSmokedLength = 0.7f * smokeRadius;
  
  	// return true if the total length of smoke-covered line-of-sight is too much
  	return (totalSmokedLength > maxSmokedLength);
  }
  