// NextBotManager.cpp
// Author: Michael Booth, May 2006
//========= Copyright Valve Corporation, All rights reserved. ============//

#include "cbase.h"

#include "NextBotManager.h"
#include "NextBotInterface.h"

#ifdef TERROR
#include "ZombieBot/Infected/Infected.h"
#include "ZombieBot/Witch/Witch.h"
#include "ZombieManager.h"
#endif

#include "SharedFunctorUtils.h"
//#include "../../common/blackbox_helper.h"

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

extern ConVar ZombieMobMaxSize;

ConVar nb_update_frequency( "nb_update_frequency", ".1", FCVAR_CHEAT );
ConVar nb_update_framelimit( "nb_update_framelimit", ( IsDebug() ) ? "30" : "15", FCVAR_CHEAT );
ConVar nb_update_maxslide( "nb_update_maxslide", "2", FCVAR_CHEAT );
ConVar nb_update_debug( "nb_update_debug", "0", FCVAR_CHEAT );

//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
/**
 * Singleton accessor.
 * By returning a reference, we guarantee construction of the 
 * instance before its first use.
 */
NextBotManager &TheNextBots( void )
{
	if ( NextBotManager::GetInstance() )
	{
		return *NextBotManager::GetInstance();
	}
	else
	{
		static NextBotManager manager;
		NextBotManager::SetInstance( &manager );
		return manager;
	}
}

NextBotManager* NextBotManager::sInstance = NULL;

//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
static const char *debugTypeName[] =
{
	"BEHAVIOR",
	"LOOK_AT",
	"PATH",
	"ANIMATION",
	"LOCOMOTION",
	"VISION",
	"HEARING",
	"EVENTS",
	"ERRORS",
	NULL
};


static void CC_SetDebug( const CCommand &args )
{
	if ( args.ArgC() < 2 )
	{
		Msg( "Debugging stopped\n" );
		TheNextBots().SetDebugTypes( NEXTBOT_DEBUG_NONE );
		return;
	}

	int debugType = 0;

	for( int i=1; i<args.ArgC(); ++i )
	{
		int type;
		for( type = 0; debugTypeName[ type ]; ++type )
		{
			const char *token = args[i];

			// special token that means "all"
			if ( token[0] == '*' )
			{
				debugType = NEXTBOT_DEBUG_ALL;
				break;
			}

			if ( !Q_strnicmp( args[i], debugTypeName[ type ], Q_strlen( args[1] ) ) )
			{
				debugType |= ( 1 << type );
				break;
			}
		}

		if ( !debugTypeName[ type ] )
		{
			Msg( "Invalid debug type '%s'\n", args[i] );
		}
	}

	// enable debugging
	TheNextBots().SetDebugTypes( ( NextBotDebugType ) debugType );
}
static ConCommand SetDebug( "nb_debug", CC_SetDebug, "Debug NextBots.  Categories are: BEHAVIOR, LOOK_AT, PATH, ANIMATION, LOCOMOTION, VISION, HEARING, EVENTS, ERRORS.", FCVAR_CHEAT );

//---------------------------------------------------------------------------------------------
static void CC_SetDebugFilter( const CCommand &args )
{
	if ( args.ArgC() < 2 )
	{
		Msg( "Debug filter cleared.\n" );
		TheNextBots().DebugFilterClear();
		return;
	}

	for( int i=1; i<args.ArgC(); ++i )
	{
		int index = Q_atoi( args[i] );
		if ( index > 0 )
		{
			TheNextBots().DebugFilterAdd( index );
		}
		else
		{
			TheNextBots().DebugFilterAdd( args[i] );
		}
	}
}
static ConCommand SetDebugFilter( "nb_debug_filter", CC_SetDebugFilter, "Add items to the NextBot debug filter. Items can be entindexes or part of the indentifier of one or more bots.", FCVAR_CHEAT );


//---------------------------------------------------------------------------------------------
class Selector
{
public:
	Selector( CBasePlayer *player, bool useLOS )
	{
		m_player = player;
		player->EyeVectors( &m_forward );

		m_pick = NULL;
		m_pickRange = 99999999999999.9f;
		m_useLOS = useLOS;
	}

	bool operator() ( INextBot *bot )
	{
		CBaseCombatCharacter *botEntity = bot->GetEntity();
		if ( botEntity->IsAlive() )
		{
			Vector to = botEntity->WorldSpaceCenter() - m_player->EyePosition();
			float range = to.NormalizeInPlace();

			if ( DotProduct( m_forward, to ) > 0.98f && range < m_pickRange )
			{
				if ( !m_useLOS || m_player->IsAbleToSee( botEntity, CBaseCombatCharacter::DISREGARD_FOV ) )
				{
					m_pick = bot;
					m_pickRange = range;
				}
			}
		}
		return true;
	}

	CBasePlayer *m_player;
	Vector m_forward;
	INextBot *m_pick;
	float m_pickRange;
	bool m_useLOS;
};

static void CC_SelectBot( const CCommand &args )
{
	CBasePlayer *player = UTIL_GetListenServerHost();
	if ( player )
	{
		Selector select( player, false );
		TheNextBots().ForEachBot( select );

		TheNextBots().Select( select.m_pick );				

		if ( select.m_pick )
		{
			NDebugOverlay::Circle( select.m_pick->GetLocomotionInterface()->GetFeet() + Vector( 0, 0, 5 ), Vector( 1, 0, 0 ), Vector( 0, -1, 0 ), 25.0f, 0, 255, 0, 255, false, 1.0f );
		}
	}	
}
static ConCommand SelectBot( "nb_select", CC_SelectBot, "Select the bot you are aiming at for further debug operations.", FCVAR_CHEAT );


//---------------------------------------------------------------------------------------------
static void CC_ForceLookAt( const CCommand &args )
{
	CBasePlayer *player = UTIL_GetListenServerHost();
	INextBot *pick = TheNextBots().GetSelected();

	if ( player && pick )
	{
		pick->GetBodyInterface()->AimHeadTowards( player, IBody::CRITICAL, 9999999.9f, NULL, "Aim forced" );
	}
}
static ConCommand ForceLookAt( "nb_force_look_at", CC_ForceLookAt, "Force selected bot to look at the local player's position", FCVAR_CHEAT );


//--------------------------------------------------------------------------------------------------------
void CC_WarpSelectedHere( const CCommand &args )
{
	CBasePlayer *me = dynamic_cast< CBasePlayer * >( UTIL_GetCommandClient() ); 
	INextBot *pick = TheNextBots().GetSelected();

	if ( me == NULL || pick == NULL )
	{
		return;
	}

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

	trace_t result;
	UTIL_TraceLine( me->EyePosition(), me->EyePosition() + 999999.9f * forward, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, me, COLLISION_GROUP_NONE, &result );
	if ( result.DidHit() )
	{
		Vector spot = result.endpos + Vector( 0, 0, 10.0f );
		pick->GetEntity()->Teleport( &spot, &vec3_angle, &vec3_origin );
	}
}
static ConCommand WarpSelectedHere( "nb_warp_selected_here", CC_WarpSelectedHere, "Teleport the selected bot to your cursor position", FCVAR_CHEAT );


//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
NextBotManager::NextBotManager( void )
{
	m_debugType = 0;
	m_selectedBot = NULL;
	
	m_iUpdateTickrate = 0;
}

//---------------------------------------------------------------------------------------------
NextBotManager::~NextBotManager()
{
}


//---------------------------------------------------------------------------------------------
/**
 * Reset to initial state
 */
void NextBotManager::Reset( void )
{
	// remove the NextBots that should go away during a reset (they will unregister themselves as they go)
	int i = m_botList.Head();
	while ( i != m_botList.InvalidIndex() )
	{
		int iNext = m_botList.Next( i );
		if ( m_botList[i]->IsRemovedOnReset() )
		{
			UTIL_Remove( m_botList[i]->GetEntity() );
			//Assert( !m_botList.IsInList( i ) );	// UTIL_Remove() calls UpdateOnRemove, adds EFL_KILLME, but doesn't delete until the end of the frame
		}
		i = iNext;
	}

	m_selectedBot = NULL;
}


//---------------------------------------------------------------------------------------------

inline bool IsDead( INextBot *pBot )
{
	CBaseCombatCharacter *pEntity = pBot->GetEntity();
	if ( pEntity )
	{
		if ( pEntity->IsPlayer() && pEntity->m_lifeState == LIFE_DEAD )
		{
			return true;
		}
			
		if ( pEntity->IsMarkedForDeletion() )
		{
			return true;
		}

		if ( pEntity->m_pfnThink == &CBaseEntity::SUB_Remove )
		{
			return true;
		}
	}
	return false;
}

//---------------------------------------------------------------------------------------------

// Debug stats for update balancing
static int g_nRun;
static int g_nSlid;
static int g_nBlockedSlides;

void NextBotManager::Update( void )
{
	// do lightweight upkeep every tick
	for( int u=m_botList.Head(); u != m_botList.InvalidIndex(); u = m_botList.Next( u ) )
	{
		m_botList[ u ]->Upkeep();
	}

	// schedule full updates
	if ( m_botList.Count() )
	{
		static int iCurFrame = -1;
		if ( iCurFrame != gpGlobals->framecount )
		{
			iCurFrame = gpGlobals->framecount;
			m_SumFrameTime = 0;
		}
		else
		{
			// Don't run multiple ticks in a frame
			return;
		}

		int tickRate = TIME_TO_TICKS( nb_update_frequency.GetFloat() );
		if ( tickRate < 0 )
		{
			tickRate = 0;
		}

		if ( m_iUpdateTickrate != tickRate )
		{
			Msg( "NextBot tickrate changed from %d (%.3fms) to %d (%.3fms)\n", m_iUpdateTickrate, TICKS_TO_TIME( m_iUpdateTickrate ), tickRate, TICKS_TO_TIME( tickRate ) );
			m_iUpdateTickrate = tickRate;
		}

		int i = 0;
		int nScheduled = 0;
		int nNonResponsive = 0;
		int nDead = 0;
		if ( m_iUpdateTickrate > 0 )
		{
			INextBot *pBot;

			// Count dead bots, they won't update and balancing calculations should exclude them
			for( i = m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
			{
				if ( IsDead( m_botList[i] ) )
				{
					nDead++;
				}
			}


			int nTargetToRun = ceilf( (float)( m_botList.Count() - nDead ) / (float)m_iUpdateTickrate );
			int curtickcount = gpGlobals->tickcount;

			for( i = m_botList.Head(); nTargetToRun && i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
			{
				pBot = m_botList[i];
				if ( pBot->IsFlaggedForUpdate() )
				{
					// Was offered a run last tick but didn't take it, push it back
					// Leave the flag set so that bot will run right away later, but be ignored
					// until then
					nNonResponsive++;
				}
				else
				{
					if ( curtickcount - pBot->GetTickLastUpdate() < m_iUpdateTickrate )
					{
						break;
					}
					if ( !IsDead( pBot ) )
					{
						pBot->FlagForUpdate();
						nTargetToRun--;
						nScheduled++;
					}
				}
			}
		}
		else
		{
			nScheduled = m_botList.Count();
		}

		if ( nb_update_debug.GetBool() )
		{
			int nIntentionalSliders = 0;
			if ( m_iUpdateTickrate > 0 )
			{
				for( ; i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
				{
					if ( gpGlobals->tickcount - m_botList[i]->GetTickLastUpdate() >= m_iUpdateTickrate )
					{
						nIntentionalSliders++;
					}
				}
			}

			Msg( "Frame %8d/tick %8d: %3d run of %3d, %3d sliders, %3d blocked slides, scheduled %3d for next tick, %3d intentional sliders, %d nonresponsive, %d dead\n", gpGlobals->framecount - 1, gpGlobals->tickcount - 1, g_nRun, m_botList.Count() - nDead, g_nSlid, g_nBlockedSlides, nScheduled, nIntentionalSliders, nNonResponsive, nDead );
			g_nRun = g_nSlid = g_nBlockedSlides = 0;
		}

	}
}

//---------------------------------------------------------------------------------------------
bool NextBotManager::ShouldUpdate( INextBot *bot )
{
	if ( m_iUpdateTickrate < 1 )
	{
		return true;
	}

	float frameLimit = nb_update_framelimit.GetFloat();
	float sumFrameTime = 0;
	if ( bot->IsFlaggedForUpdate() )
	{
		bot->FlagForUpdate( false );
		sumFrameTime = m_SumFrameTime * 1000.0;
		if ( frameLimit > 0.0f )
		{
			if ( sumFrameTime < frameLimit )
			{
				return true;
			}
			else if ( nb_update_debug.GetBool() )
			{
				Msg( "Frame %8d/tick %8d: frame out of budget (%.2fms > %.2fms)\n", gpGlobals->framecount, gpGlobals->tickcount, sumFrameTime, frameLimit );
			}
		}
	}

	int nTicksSlid = ( gpGlobals->tickcount - bot->GetTickLastUpdate() ) - m_iUpdateTickrate;

	if ( nTicksSlid >= nb_update_maxslide.GetInt() )
	{
		if ( frameLimit == 0.0 || sumFrameTime < nb_update_framelimit.GetFloat() * 2.0 )
		{
			g_nBlockedSlides++;
			return true;
		}
	}

	if ( nb_update_debug.GetBool() )
	{
		if ( nTicksSlid > 0 )
		{
			g_nSlid++;
		}
	}

	return false;
}

//---------------------------------------------------------------------------------------------
void NextBotManager::NotifyBeginUpdate( INextBot *bot )
{
	if ( nb_update_debug.GetBool() )
	{
		g_nRun++;
	}

	m_botList.Unlink( bot->GetBotId() );
	m_botList.LinkToTail( bot->GetBotId() );
	bot->SetTickLastUpdate( gpGlobals->tickcount );

	m_CurUpdateStartTime = Plat_FloatTime();
}

//---------------------------------------------------------------------------------------------
void NextBotManager::NotifyEndUpdate( INextBot *bot )
{
	// This might be a good place to detect a particular bot had spiked [3/14/2008 tom]
	m_SumFrameTime += Plat_FloatTime() - m_CurUpdateStartTime;
}

//---------------------------------------------------------------------------------------------
/**
 * When the server has changed maps
 */
void NextBotManager::OnMapLoaded( void )
{
	Reset();
}


//---------------------------------------------------------------------------------------------
/**
 * When the scenario restarts
 */
void NextBotManager::OnRoundRestart( void )
{
	Reset();
}


//---------------------------------------------------------------------------------------------
int NextBotManager::Register( INextBot *bot )
{
	return m_botList.AddToHead( bot );
}


//---------------------------------------------------------------------------------------------
void NextBotManager::UnRegister( INextBot *bot )
{
	m_botList.Remove( bot->GetBotId() );

	if ( bot == m_selectedBot)
	{
		// we can't access virtual methods because this is called from a destructor, so just clear it
		m_selectedBot = NULL;
	}
}


//--------------------------------------------------------------------------------------------------------
void NextBotManager::OnBeginChangeLevel( void )
{
}


//----------------------------------------------------------------------------------------------------------
class NextBotKilledNotifyScan
{
public:
	NextBotKilledNotifyScan( CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
	{
		m_victim = victim;
		m_info = info;
	}

	bool operator() ( INextBot *bot )
	{
		if ( bot->GetEntity()->IsAlive() && !bot->IsSelf( m_victim ) )
		{
			bot->OnOtherKilled( m_victim, m_info );
		}
		return true;
	}

	CBaseCombatCharacter *m_victim;
	CTakeDamageInfo m_info;
};


//---------------------------------------------------------------------------------------------
/**
 * When an actor is killed.  Propagate to all NextBots.
 */
void NextBotManager::OnKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
{
	NextBotKilledNotifyScan notify( victim, info );
	TheNextBots().ForEachBot( notify );
}


//----------------------------------------------------------------------------------------------------------
class NextBotSoundNotifyScan
{
public:
	NextBotSoundNotifyScan( CBaseEntity *source, const Vector &pos, KeyValues *keys ) : m_source( source ), m_pos( pos ), m_keys( keys )
	{
	}

	bool operator() ( INextBot *bot )
	{
		if ( bot->GetEntity()->IsAlive() && !bot->IsSelf( m_source ) )
		{
			bot->OnSound( m_source, m_pos, m_keys );
		}
		return true;
	}

	CBaseEntity *m_source;
	const Vector &m_pos;
	KeyValues *m_keys;
};


//---------------------------------------------------------------------------------------------
/**
 * When an entity emits a sound
 */
void NextBotManager::OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys )
{
	NextBotSoundNotifyScan notify( source, pos, keys );
	TheNextBots().ForEachBot( notify );

	if ( source && IsDebugging( NEXTBOT_HEARING ) )
	{
		int r,g,b;
		switch( source->GetTeamNumber() )
		{
			case FIRST_GAME_TEAM:		r = 0;   g = 255; b = 0; break;
			case (FIRST_GAME_TEAM+1):	r = 255; g = 0;   b = 0; break;
			default:					r = 255; g = 255; b = 0; break;
		}
		NDebugOverlay::Circle( pos, Vector( 1, 0, 0 ), Vector( 0, -1, 0 ), 5.0f, r, g, b, 255, true, 3.0f );
	}
}


//----------------------------------------------------------------------------------------------------------
class NextBotResponseNotifyScan
{
public:
	NextBotResponseNotifyScan( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response ) : m_who( who ), m_concept( concept ), m_response( response )
	{
	}

	bool operator() ( INextBot *bot )
	{
		if ( bot->GetEntity()->IsAlive() )
		{
			bot->OnSpokeConcept( m_who, m_concept, m_response );
		}
		return true;
	}

	CBaseCombatCharacter *m_who;
	AIConcept_t m_concept;
	AI_Response *m_response;
};


//---------------------------------------------------------------------------------------------
/**
 * When an Actor speaks a concept
 */
void NextBotManager::OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response )
{
	NextBotResponseNotifyScan notify( who, concept, response );
	TheNextBots().ForEachBot( notify );

	if ( IsDebugging( NEXTBOT_HEARING ) )
	{
		// const char *who = response->GetCriteria()->GetValue( response->GetCriteria()->FindCriterionIndex( "Who" ) );

		// TODO: Need concept.GetStringConcept()
		DevMsg( "%3.2f: OnSpokeConcept( %s, %s )\n", gpGlobals->curtime, who->GetDebugName(), "concept.GetStringConcept()" );
	}
}


//----------------------------------------------------------------------------------------------------------
class NextBotWeaponFiredNotifyScan
{
public:
	NextBotWeaponFiredNotifyScan( CBaseCombatCharacter *who, CBaseCombatWeapon *weapon ) : m_who( who ), m_weapon( weapon )
	{
	}

	bool operator() ( INextBot *bot )
	{
		if ( bot->GetEntity()->IsAlive() )
		{
			bot->OnWeaponFired( m_who, m_weapon );
		}
		return true;
	}

	CBaseCombatCharacter *m_who;
	CBaseCombatWeapon *m_weapon;
};


//---------------------------------------------------------------------------------------------
/**
 * When someone fires a weapon
 */
void NextBotManager::OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon )
{
	NextBotWeaponFiredNotifyScan notify( whoFired, weapon );
	TheNextBots().ForEachBot( notify );

	if ( IsDebugging( NEXTBOT_EVENTS ) )
	{
		DevMsg( "%3.2f: OnWeaponFired( %s, %s )\n", gpGlobals->curtime, whoFired->GetDebugName(), weapon->GetName() );
	}
}


//---------------------------------------------------------------------------------------------
/**
 * Add given entindex to the debug filter
 */
void NextBotManager::DebugFilterAdd( int index )
{
	DebugFilter filter;

	filter.index = index;
	filter.name[0] = '\000';

	m_debugFilterList.AddToTail( filter );
}


//---------------------------------------------------------------------------------------------
/**
 * Add given name to the debug filter
 */
void NextBotManager::DebugFilterAdd( const char *name )
{
	DebugFilter filter;

	filter.index = -1;
	Q_strncpy( filter.name, name, DebugFilter::MAX_DEBUG_NAME_SIZE );

	m_debugFilterList.AddToTail( filter );
}


//---------------------------------------------------------------------------------------------
/**
 * Remove given entindex from the debug filter
 */
void NextBotManager::DebugFilterRemove( int index )
{
	for( int i=0; i<m_debugFilterList.Count(); ++i )
	{
		if ( m_debugFilterList[i].index == index )
		{
			m_debugFilterList.Remove( i );
			break;
		}
	}
}


//---------------------------------------------------------------------------------------------
/**
 * Remove given name from the debug filter
 */
void NextBotManager::DebugFilterRemove( const char *name )
{
	for( int i=0; i<m_debugFilterList.Count(); ++i )
	{
		if ( m_debugFilterList[i].name[0] != '\000' &&
			 !Q_strnicmp( name, m_debugFilterList[i].name, MIN( Q_strlen( name ), sizeof( m_debugFilterList[i].name ) ) ) )
		{
			m_debugFilterList.Remove( i );
			break;
		}
	}
}


//---------------------------------------------------------------------------------------------
/**
 * Clear the debug filter (remove all entries)
 */
void NextBotManager::DebugFilterClear( void )
{
	m_debugFilterList.RemoveAll();
}


//---------------------------------------------------------------------------------------------
/**
 * Return true if the given bot matches the debug filter
 */
bool NextBotManager::IsDebugFilterMatch( const INextBot *bot ) const
{
	// if the filter is empty, all bots match
	if ( m_debugFilterList.Count() == 0 )
	{
		return true;
	}

	for( int i=0; i<m_debugFilterList.Count(); ++i )
	{
		// compare entity index
		if ( m_debugFilterList[i].index == const_cast< INextBot * >( bot )->GetEntity()->entindex() )
		{
			return true;
		}

		// compare debug filter
		if ( m_debugFilterList[i].name[0] != '\000' && bot->IsDebugFilterMatch( m_debugFilterList[i].name ) )
		{
			return true;
		}

		// compare special keyword meaning local player is looking at them
		if ( !Q_strnicmp( m_debugFilterList[i].name, "lookat", Q_strlen( m_debugFilterList[i].name ) ) )
		{
			CBasePlayer *watcher = UTIL_GetListenServerHost();
			if ( watcher )
			{
				CBaseEntity *subject = watcher->GetObserverTarget();

				if ( subject && bot->IsSelf( subject ) )
				{
					return true;
				}
			}
		}

		// compare special keyword meaning NextBot is selected
		if ( !Q_strnicmp( m_debugFilterList[i].name, "selected", Q_strlen( m_debugFilterList[i].name ) ) )
		{
			INextBot *selected = GetSelected();
			if ( selected && bot->IsSelf( selected->GetEntity() ) )
			{
				return true;
			}
		}
	}

	return false;
}

//---------------------------------------------------------------------------------------------
/**
 * Get the bot under the given player's crosshair
 */
INextBot *NextBotManager::GetBotUnderCrosshair( CBasePlayer *picker )
{
	if ( !picker )
		return NULL;

	const float MaxDot = 0.7f;
	const float MaxRange = 4000.0f;
	TargetScan< CBaseCombatCharacter > scan( picker, TEAM_ANY, 1.0f - MaxDot, MaxRange );
	ForEachCombatCharacter( scan );
	CBaseCombatCharacter *target = scan.GetTarget();
	if ( target && target->MyNextBotPointer() )
		return target->MyNextBotPointer();

	return NULL;
}

#ifdef NEED_BLACK_BOX
//---------------------------------------------------------------------------------------------
CON_COMMAND( nb_dump_debug_history, "Dumps debug history for the bot under the cursor to the blackbox" )
{
	if ( !NextBotDebugHistory.GetBool() )
	{
		BlackBox_Record( "bot", "nb_debug_history 0" );
		return;
	}

	CBasePlayer *player = UTIL_GetCommandClient();
	if ( !player )
	{
		player = UTIL_GetListenServerHost();
	}
	INextBot *bot = TheNextBots().GetBotUnderCrosshair( player );
	if ( !bot )
	{
		BlackBox_Record( "bot", "no bot under crosshairs" );
		return;
	}

	CUtlVector< const INextBot::NextBotDebugLineType * > lines;
	bot->GetDebugHistory( (NEXTBOT_DEBUG_ALL & (~NEXTBOT_EVENTS)), &lines );

	for ( int i=0; i<lines.Count(); ++i )
	{
		if ( IsPC() )
		{
			BlackBox_Record( "bot", "%s", lines[i]->data );
		}
	}
}
#endif // NEED_BLACK_BOX


//---------------------------------------------------------------------------------------------
void NextBotManager::CollectAllBots( CUtlVector< INextBot * > *botVector )
{
	if ( !botVector )
		return;

	botVector->RemoveAll();

	for( int i=m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
	{
		botVector->AddToTail( m_botList[i] );
	}
}