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

#include "cbase.h"

#include "IEffects.h"
#include "collisionutils.h"

#include "ai_basenpc.h"
#include "ai_scriptconditions.h"
#include "saverestore_utlvector.h"

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

#define SF_ACTOR_AS_ACTIVATOR	( 1 << 0 )

ConVar debugscriptconditions( "ai_debugscriptconditions", "0" );

#define ScrCondDbgMsg( msg ) \
	do \
{ \
	if ( debugscriptconditions.GetBool() ) \
{ \
	DevMsg msg; \
} \
} \
	while (0)


//=============================================================================
//
// CAI_ScriptConditions
//
//=============================================================================

LINK_ENTITY_TO_CLASS(ai_script_conditions, CAI_ScriptConditions);

BEGIN_DATADESC( CAI_ScriptConditions )

	DEFINE_THINKFUNC( EvaluationThink ),

	DEFINE_OUTPUT( m_OnConditionsSatisfied, "OnConditionsSatisfied" ),
	DEFINE_OUTPUT( m_OnConditionsTimeout, "OnConditionsTimeout" ),
	DEFINE_OUTPUT( m_NoValidActors, "NoValidActors" ),

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

	DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),

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

	// Inputs
	DEFINE_KEYFIELD(m_fDisabled, 					FIELD_BOOLEAN, 	"StartDisabled" 			),

	DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ),
	DEFINE_KEYFIELD(m_Actor,						FIELD_STRING,	"Actor"						),

	DEFINE_KEYFIELD(m_flRequiredTime, 				FIELD_FLOAT, 	"RequiredTime" 				),

#ifndef HL2_EPISODIC
	DEFINE_FIELD( m_hActor, FIELD_EHANDLE ),
	DEFINE_EMBEDDED(m_Timer ),
	DEFINE_EMBEDDED(m_Timeout ),
#endif

	DEFINE_KEYFIELD(m_fMinState, 					FIELD_INTEGER,	"MinimumState" 				),
	DEFINE_KEYFIELD(m_fMaxState, 					FIELD_INTEGER,	"MaximumState" 				),

	DEFINE_KEYFIELD(m_fScriptStatus, 				FIELD_INTEGER,	"ScriptStatus" 				),
	DEFINE_KEYFIELD(m_fActorSeePlayer,				FIELD_INTEGER,	"ActorSeePlayer"			),


	DEFINE_KEYFIELD(m_flPlayerActorProximity,		FIELD_FLOAT, 	"PlayerActorProximity" 		),
	DEFINE_EMBEDDED(m_PlayerActorProxTester),

	DEFINE_KEYFIELD(m_flPlayerActorFOV, 			FIELD_FLOAT, 	"PlayerActorFOV" 			),
	DEFINE_KEYFIELD(m_bPlayerActorFOVTrueCone,		FIELD_BOOLEAN,	"PlayerActorFOVTrueCone"	),

	DEFINE_KEYFIELD(m_fPlayerActorLOS, 				FIELD_INTEGER, 	"PlayerActorLOS" 			),
	DEFINE_KEYFIELD(m_fActorSeeTarget,				FIELD_INTEGER,	"ActorSeeTarget" 			),

	DEFINE_KEYFIELD(m_flActorTargetProximity,		FIELD_FLOAT, 	"ActorTargetProximity" 		),
	DEFINE_EMBEDDED(m_ActorTargetProxTester),

	DEFINE_KEYFIELD(m_flPlayerTargetProximity, 		FIELD_FLOAT, 	"PlayerTargetProximity"		),
	DEFINE_EMBEDDED(m_PlayerTargetProxTester),

	DEFINE_KEYFIELD(m_flPlayerTargetFOV, 			FIELD_FLOAT,	"PlayerTargetFOV"			),
	DEFINE_KEYFIELD(m_bPlayerTargetFOVTrueCone,		FIELD_BOOLEAN,	"PlayerTargetFOVTrueCone"	),

	DEFINE_KEYFIELD(m_fPlayerTargetLOS, 			FIELD_INTEGER,	"PlayerTargetLOS"			),
	DEFINE_KEYFIELD(m_fPlayerBlockingActor,			FIELD_INTEGER,  "PlayerBlockingActor"		),

	DEFINE_KEYFIELD(m_flMinTimeout, 				FIELD_FLOAT,	"MinTimeout"				),
	DEFINE_KEYFIELD(m_flMaxTimeout, 				FIELD_FLOAT,	"MaxTimeout"				),

	DEFINE_KEYFIELD(m_fActorInPVS,					FIELD_INTEGER,  "ActorInPVS"		),

	DEFINE_KEYFIELD(m_fActorInVehicle,				FIELD_INTEGER,	 "ActorInVehicle" ),
	DEFINE_KEYFIELD(m_fPlayerInVehicle,				FIELD_INTEGER,	 "PlayerInVehicle" ),

	DEFINE_UTLVECTOR( m_ElementList,				FIELD_EMBEDDED ),
	DEFINE_FIELD( m_bLeaveAsleep,					FIELD_BOOLEAN ),

END_DATADESC()

BEGIN_SIMPLE_DATADESC( CAI_ProxTester )
	DEFINE_FIELD( m_distSq, FIELD_FLOAT ),
	DEFINE_FIELD( m_fInside, FIELD_BOOLEAN ),
END_DATADESC()

BEGIN_SIMPLE_DATADESC( CAI_ScriptConditionsElement )
	DEFINE_FIELD( m_hActor, FIELD_EHANDLE ),
	DEFINE_EMBEDDED(m_Timer ),
	DEFINE_EMBEDDED(m_Timeout ),
END_DATADESC()


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

#define EVALUATOR( name ) { &CAI_ScriptConditions::Eval##name, #name }

CAI_ScriptConditions::EvaluatorInfo_t CAI_ScriptConditions::gm_Evaluators[] =
{
	EVALUATOR( ActorSeePlayer ),
		EVALUATOR( State ),
		EVALUATOR( PlayerActorProximity ),
		EVALUATOR( PlayerTargetProximity ),
		EVALUATOR( ActorTargetProximity ),
		EVALUATOR( PlayerBlockingActor ),
		EVALUATOR( PlayerActorLook ),
		EVALUATOR( PlayerTargetLook ),
		EVALUATOR( ActorSeeTarget),
		EVALUATOR( PlayerActorLOS ),
		EVALUATOR( PlayerTargetLOS ),

#ifdef HL2_EPISODIC
		EVALUATOR( ActorInPVS ),
		EVALUATOR( PlayerInVehicle ),
		EVALUATOR( ActorInVehicle ),
#endif

};

void CAI_ScriptConditions::OnRestore( void )
{
	BaseClass::OnRestore();

#ifndef HL2_EPISODIC
	//Old HL2 save game! Fix up to new system.
	if ( m_hActor )
	{
		CAI_ScriptConditionsElement conditionactor;

		conditionactor.SetActor( m_hActor );
		conditionactor.SetTimeOut( m_Timeout );
		conditionactor.SetTimer( m_Timer );
 
		m_ElementList.AddToTail( conditionactor );

		m_hActor = NULL;
	}

	if ( m_ElementList.Count() == 0 && m_Actor == NULL_STRING && m_fDisabled == false )
	{
		AddNewElement( NULL );
	}
#endif
}

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

bool CAI_ScriptConditions::EvalState( const EvalArgs_t &args )
{
	if ( !args.pActor )
		return true;

	CAI_BaseNPC *pNpc = args.pActor->MyNPCPointer();

	// !!!LATER - fix this code, we shouldn't need the table anymore
	// now that we've placed the NPC state defs in a logical order (sjb)
	static int stateVals[] = 
	{
		-1, // NPC_STATE_NONE
			0, // NPC_STATE_IDLE
			1, // NPC_STATE_ALERT
			2, // NPC_STATE_COMBAT
			-1, // NPC_STATE_SCRIPT
			-1, // NPC_STATE_PLAYDEAD
			-1, // NPC_STATE_PRONE
			-1, // NPC_STATE_DEAD
	};

	int valState = stateVals[pNpc->m_NPCState];

	if ( valState < 0 )
	{
		if ( pNpc->m_NPCState == NPC_STATE_SCRIPT && m_fScriptStatus >= TRS_TRUE )
			return true;

		return false;
	}

	const int valLow  = stateVals[m_fMinState];
	const int valHigh = stateVals[m_fMaxState];

	if ( valLow > valHigh )
	{
		DevMsg( "Script condition warning: Invalid setting for Maximum/Minimum state\n" );
		Disable();
		return false;
	}

	return ( valState >= valLow && valState <= valHigh );
}

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

bool CAI_ScriptConditions::EvalActorSeePlayer( const EvalArgs_t &args )
{
	if( m_fActorSeePlayer == TRS_NONE )
	{
		// Don't care, so don't do any work.
		return true;
	}

	if ( !args.pActor )
		return true;

	bool fCanSeePlayer = args.pActor->MyNPCPointer()->HasCondition( COND_SEE_PLAYER );
	return ( (int)m_fActorSeePlayer == (int)fCanSeePlayer );
}

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

bool CAI_ScriptConditions::EvalActorSeeTarget( const EvalArgs_t &args )
{
	if( m_fActorSeeTarget == TRS_NONE )
	{
		// Don't care, so don't do any work.
		return true;
	}

	if ( args.pTarget )
	{
		if ( !args.pActor )
			return true;

		CAI_BaseNPC *pNPCActor = args.pActor->MyNPCPointer();

#ifdef HL2_EPISODIC
		// This is the code we want to have written for HL2, but HL2 shipped without the QuerySeeEntity() call. This #ifdef really wants to be
		// something like #ifndef HL2_RETAIL, since this change does want to be in any products that are built henceforth. (sjb)
		bool fSee = pNPCActor->FInViewCone( args.pTarget ) && pNPCActor->FVisible( args.pTarget ) && pNPCActor->QuerySeeEntity( args.pTarget );
#else
		bool fSee = pNPCActor->FInViewCone( args.pTarget ) && pNPCActor->FVisible( args.pTarget );
#endif//HL2_EPISODIC

		if( fSee )
		{
			if( m_fActorSeeTarget == TRS_TRUE )
			{
				return true;
			}

			return false;
		}
		else
		{
			if( m_fActorSeeTarget == TRS_FALSE )
			{
				return true;
			}

			return false;
		}
	}

	return true;
}


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

bool CAI_ScriptConditions::EvalPlayerActorProximity( const EvalArgs_t &args )
{
	return ( !args.pActor || m_PlayerActorProxTester.Check( args.pPlayer, args.pActor ) );
}

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

bool CAI_ScriptConditions::EvalPlayerTargetProximity( const EvalArgs_t &args )
{
	return ( !args.pTarget || 
		m_PlayerTargetProxTester.Check( args.pPlayer, args.pTarget ) );
}


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

bool CAI_ScriptConditions::EvalActorTargetProximity( const EvalArgs_t &args )
{
	return ( !args.pTarget || !args.pActor ||
		m_ActorTargetProxTester.Check( args.pActor, args.pTarget ) );
}

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

bool CAI_ScriptConditions::EvalPlayerActorLook( const EvalArgs_t &args )
{
	return ( !args.pActor || 
		IsInFOV( args.pPlayer, args.pActor, m_flPlayerActorFOV, m_bPlayerActorFOVTrueCone ) );
}

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

bool CAI_ScriptConditions::EvalPlayerTargetLook( const EvalArgs_t &args )
{
	return ( !args.pTarget || IsInFOV( args.pPlayer, args.pTarget, m_flPlayerTargetFOV, m_bPlayerTargetFOVTrueCone ) );
}

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

bool CAI_ScriptConditions::EvalPlayerActorLOS( const EvalArgs_t &args )
{
	if( m_fPlayerActorLOS == TRS_NONE )
	{
		// Don't execute expensive code if we don't care.
		return true;
	}

	return ( !args.pActor || PlayerHasLineOfSight( args.pPlayer, args.pActor, m_fPlayerActorLOS == TRS_FALSE ) );
}

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

bool CAI_ScriptConditions::EvalPlayerTargetLOS( const EvalArgs_t &args )
{
	if( m_fPlayerTargetLOS == TRS_NONE )
	{
		// Don't execute expensive code if we don't care.
		return true;
	}

	return ( !args.pTarget || PlayerHasLineOfSight( args.pPlayer, args.pTarget, m_fPlayerTargetLOS == TRS_FALSE ) );
}

bool CAI_ScriptConditions::EvalActorInPVS( const EvalArgs_t &args )
{
	if( m_fActorInPVS == TRS_NONE )
	{
		// Don't execute expensive code if we don't care.
		return true;
	}

	return ( !args.pActor || ActorInPlayersPVS( args.pActor, m_fActorInPVS == TRS_FALSE ) );
}

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

bool CAI_ScriptConditions::EvalPlayerBlockingActor( const EvalArgs_t &args )
{
	if ( m_fPlayerBlockingActor == TRS_NONE )
		return true;

#if 0
	CAI_BaseNPC *pNpc = args.pActor->MyNPCPointer();

	const float testDist = 30.0;

	Vector origin = args.pActor->WorldSpaceCenter();
	Vector delta  = UTIL_YawToVector( args.pActor->GetAngles().y ) * testDist;

	Vector vecAbsMins, vecAbsMaxs;
	args.pActor->CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );
	bool intersect = IsBoxIntersectingRay( vecAbsMins, vecAbsMaxs, origin, delta );
#endif

	if ( m_fPlayerBlockingActor == TRS_FALSE )
		return true;

	return false; // for now, never say player is blocking
}

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

bool CAI_ScriptConditions::EvalPlayerInVehicle( const EvalArgs_t &args )
{
	// We don't care
	if ( m_fPlayerInVehicle == TRS_NONE )
		return true;

	// Need a player to test
	if ( args.pPlayer == NULL )
		return false;

	// Desired states must match
	return ( !!args.pPlayer->IsInAVehicle() == m_fPlayerInVehicle );
}

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

bool CAI_ScriptConditions::EvalActorInVehicle( const EvalArgs_t &args )
{
	// We don't care
	if ( m_fActorInVehicle == TRS_NONE )
		return true;

	if ( !args.pActor )
		return true;

	// Must be able to be in a vehicle at all
	CBaseCombatCharacter *pBCC = args.pActor->MyCombatCharacterPointer();
	if ( pBCC == NULL )
		return false;

	// Desired states must match
	return ( !!pBCC->IsInAVehicle() == m_fActorInVehicle );
}

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

void CAI_ScriptConditions::Spawn()
{
	Assert( ( m_fMinState == NPC_STATE_IDLE || m_fMinState == NPC_STATE_COMBAT || m_fMinState == NPC_STATE_ALERT ) &&
		( m_fMaxState == NPC_STATE_IDLE || m_fMaxState == NPC_STATE_COMBAT || m_fMaxState == NPC_STATE_ALERT ) );

	m_PlayerActorProxTester.Init( m_flPlayerActorProximity );
	m_PlayerTargetProxTester.Init( m_flPlayerTargetProximity );
	m_ActorTargetProxTester.Init( m_flActorTargetProximity );

	m_bLeaveAsleep = m_fDisabled;
}

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

void CAI_ScriptConditions::Activate()
{
	BaseClass::Activate();

	// When we spawn, m_fDisabled is initial state as given by worldcraft.
	// following that, we keep it updated and it reflects current state.
	if( !m_fDisabled )
		Enable();

#ifdef HL2_EPISODIC
	gEntList.AddListenerEntity( this );
#endif
}

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

void CAI_ScriptConditions::UpdateOnRemove( void )
{
	gEntList.RemoveListenerEntity( this );
	BaseClass::UpdateOnRemove();

	m_ElementList.Purge();
}

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

void CAI_ScriptConditions::EvaluationThink()
{
	if ( m_fDisabled == true )
		return;

	int iActorsDone = 0;

#ifdef HL2_DLL
	if( AI_GetSinglePlayer()->GetFlags() & FL_NOTARGET )
	{
		ScrCondDbgMsg( ("%s WARNING: Player is NOTARGET. This will affect all LOS conditiosn involving the player!\n", GetDebugName()) );
	}
#endif


	for ( int i = 0; i < m_ElementList.Count(); )
	{
		CAI_ScriptConditionsElement *pConditionElement = &m_ElementList[i];

		if ( pConditionElement == NULL )
		{
			i++;
			continue;
		}

		CBaseEntity *pActor = pConditionElement->GetActor();
		CBaseEntity *pActivator = this;

#ifdef HL2_EPISODIC
		if ( pActor && HasSpawnFlags( SF_ACTOR_AS_ACTIVATOR ) )
		{
			pActivator = pActor;
		}
#endif

		AssertMsg( !m_fDisabled, ("Violated invariant between CAI_ScriptConditions disabled state and think func setting") );

		if ( m_Actor != NULL_STRING && !pActor )
		{
			if ( m_ElementList.Count() == 1 )
			{
				DevMsg( "Warning: Active AI script conditions associated with an non-existant or destroyed NPC\n" );
				m_NoValidActors.FireOutput( this, this, 0 );
			}

			iActorsDone++;
			m_ElementList.Remove( i );
			continue;
		}

		i++;

		if( m_flMinTimeout > 0 && pConditionElement->GetTimeOut()->Expired() )
		{
			ScrCondDbgMsg( ( "%s firing output OnConditionsTimeout (%f seconds)\n", STRING( GetEntityName() ), pConditionElement->GetTimeOut()->GetInterval() ) );

			iActorsDone++;
			m_OnConditionsTimeout.FireOutput( pActivator, this );
			continue;
		}

		bool      result = true;
		const int nEvaluators = sizeof( gm_Evaluators ) / sizeof( gm_Evaluators[0] );

		EvalArgs_t args =
		{
			pActor,
			GetPlayer(),
			m_hTarget.Get()
		};

		for ( int i = 0; i < nEvaluators; ++i )
		{
			if ( !(this->*gm_Evaluators[i].pfnEvaluator)( args ) )
			{
				pConditionElement->GetTimer()->Reset();
				result = false;

				ScrCondDbgMsg( ( "%s failed on: %s\n", GetDebugName(), gm_Evaluators[ i ].pszName ) );

				break;
			}
		}

		if ( result )
		{
			ScrCondDbgMsg( ( "%s waiting... %f\n", GetDebugName(), pConditionElement->GetTimer()->GetRemaining() ) );
		}

		if ( result && pConditionElement->GetTimer()->Expired() )
		{
			ScrCondDbgMsg( ( "%s firing output OnConditionsSatisfied\n", GetDebugName() ) );

			// Default behavior for now, provide worldcraft option later.
			iActorsDone++;
			m_OnConditionsSatisfied.FireOutput( pActivator, this );
		}
	}

	//All done!
	if ( iActorsDone == m_ElementList.Count() )
	{
		Disable();
		m_ElementList.Purge();
	}

	SetThinkTime();
}

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

int CAI_ScriptConditions::AddNewElement( CBaseEntity *pActor )
{
	CAI_ScriptConditionsElement conditionelement;
	conditionelement.SetActor( pActor );

	if( m_flMaxTimeout > 0 )
	{
		conditionelement.GetTimeOut()->Set( random->RandomFloat( m_flMinTimeout, m_flMaxTimeout ), false );
	}
	else
	{
		conditionelement.GetTimeOut()->Set( m_flMinTimeout, false );
	}

	conditionelement.GetTimer()->Set( m_flRequiredTime );

	if ( m_flRequiredTime > 0 )
	{
		conditionelement.GetTimer()->Reset();
	}

	return m_ElementList.AddToTail( conditionelement );
}

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

void CAI_ScriptConditions::Enable( void )
{
	m_hTarget = gEntList.FindEntityByName( NULL, m_target );

	CBaseEntity *pActor = gEntList.FindEntityByName( NULL, m_Actor );
	if ( m_ElementList.Count() == 0 )
	{
		if ( m_Actor != NULL_STRING && pActor == NULL )
		{
			DevMsg( "Warning: Spawning AI script conditions (%s) associated with an non-existant NPC\n", GetDebugName() );
			m_NoValidActors.FireOutput( this, this, 0 );
			Disable();
			return;
		}

		if ( pActor && pActor->MyNPCPointer() == NULL )
		{
			Warning( "Script condition warning: warning actor is not an NPC\n" );
			Disable();
			return;
		}
	}

	while( pActor != NULL )
	{
		if( !ActorInList(pActor) )
		{
			AddNewElement( pActor );
		}

		pActor = gEntList.FindEntityByName( pActor, m_Actor );
	}

	//If we are hitting this it means we are using a Target->Player condition
	if ( m_Actor == NULL_STRING )
	{
		if( !ActorInList(pActor) )
		{
			AddNewElement( NULL );
		}
	}

	m_fDisabled = false;

	SetThink( &CAI_ScriptConditions::EvaluationThink );
	SetThinkTime();
}

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

void CAI_ScriptConditions::Disable( void )
{
	SetThink( NULL );

	m_fDisabled = true;
}

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

void CAI_ScriptConditions::InputEnable( inputdata_t &inputdata )
{
	m_bLeaveAsleep = false;
	Enable();
}

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

void CAI_ScriptConditions::InputDisable( inputdata_t &inputdata )
{
	m_bLeaveAsleep = true;
	Disable();
}

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

bool CAI_ScriptConditions::IsInFOV( CBaseEntity *pViewer, CBaseEntity *pViewed, float fov, bool bTrueCone )
{
	CBaseCombatCharacter *pCombatantViewer = (pViewer) ? pViewer->MyCombatCharacterPointer() : NULL;

	if ( fov < 360 && pCombatantViewer /*&& pViewed*/ )
	{
		Vector vLookDir;
		Vector vActorDir;

		// Halve the fov. As expressed here, fov is the full size of the viewcone.
		float flFovDotResult;
		flFovDotResult = cos( DEG2RAD( fov / 2 ) );
		float fDotPr = 1;

		if( bTrueCone )
		{
			// 3D Check
			vLookDir = pCombatantViewer->EyeDirection3D( );
			vActorDir = pViewed->EyePosition() - pViewer->EyePosition();
			vActorDir.NormalizeInPlace();
			fDotPr = vLookDir.Dot(vActorDir);
		}
		else
		{
			// 2D Check
			vLookDir = pCombatantViewer->EyeDirection2D( );
			vActorDir = pViewed->EyePosition() - pViewer->EyePosition();
			vActorDir.z = 0.0;
			vActorDir.AsVector2D().NormalizeInPlace();
			fDotPr = vLookDir.AsVector2D().Dot(vActorDir.AsVector2D());
		}

		if ( fDotPr < flFovDotResult )
		{
			if( fov < 0 )
			{
				// Designer has requested that the player
				// NOT be looking at this place.
				return true;
			}

			return false;
		}
	}

	if( fov < 0 )
	{
		return false;
	}

	return true;
}

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

bool CAI_ScriptConditions::PlayerHasLineOfSight( CBaseEntity *pViewer, CBaseEntity *pViewed, bool fNot )
{
	CBaseCombatCharacter *pCombatantViewer = pViewer->MyCombatCharacterPointer();

	if( pCombatantViewer )
	{
		// We always trace towards the player, so we handle players-in-vehicles
		if ( pViewed->FVisible( pCombatantViewer ) )
		{
			// Line of sight exists.
			if( fNot )
			{
				return false;
			}
			else
			{
				return true;
			}
		}
		else
		{
			// No line of sight.
			if( fNot )
			{
				return true;
			}
			else
			{
				return false;
			}
		}
	}

	return true;
}

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

bool CAI_ScriptConditions::ActorInPlayersPVS( CBaseEntity *pActor, bool bNot )
{
	if ( pActor == NULL )
		return true;

	bool bInPVS = !!UTIL_FindClientInPVS( pActor->edict());

	if ( bInPVS )
	{
		if( bNot )
		{
			return false;
		}
		else
		{
			return true;
		}
	}
	else
	{
		if( bNot )
		{
			return true;
		}
		else
		{
			return false;
		}
	}
}

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

bool CAI_ScriptConditions::ActorInList( CBaseEntity *pActor )
{
	for ( int i = 0; i < m_ElementList.Count(); i++ )
	{
		if ( m_ElementList[i].GetActor() == pActor )
			 return true;
	}

	return false;
}

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

void CAI_ScriptConditions::OnEntitySpawned( CBaseEntity *pEntity )
{
	if( m_fDisabled && m_bLeaveAsleep )
	{
		// Don't add elements if we're not currently running and don't want to automatically wake up.
		// Any spawning NPC's we miss during this time will be found and added when manually Enabled().
		return;
	}

	if ( pEntity->MyNPCPointer() == NULL )
		 return;

	if ( pEntity->NameMatches( m_Actor ) )
	{
		if ( ActorInList( pEntity ) == false )
		{
			AddNewElement( pEntity );

			if ( m_fDisabled == true && m_bLeaveAsleep == false )
			{
				Enable();
			}
		}
	}
}

//=============================================================================
