//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "filters.h" #include "entitylist.h" #include "ai_squad.h" #include "ai_basenpc.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" // ################################################################### // > BaseFilter // ################################################################### LINK_ENTITY_TO_CLASS(filter_base, CBaseFilter); BEGIN_DATADESC( CBaseFilter ) DEFINE_KEYFIELD(m_bNegated, FIELD_BOOLEAN, "Negated"), // Inputs DEFINE_INPUTFUNC( FIELD_INPUT, "TestActivator", InputTestActivator ), // Outputs DEFINE_OUTPUT( m_OnPass, "OnPass"), DEFINE_OUTPUT( m_OnFail, "OnFail"), END_DATADESC() //----------------------------------------------------------------------------- bool CBaseFilter::PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) { return true; } bool CBaseFilter::PassesFilter( CBaseEntity *pCaller, CBaseEntity *pEntity ) { bool baseResult = PassesFilterImpl( pCaller, pEntity ); return (m_bNegated) ? !baseResult : baseResult; } bool CBaseFilter::PassesDamageFilter(const CTakeDamageInfo &info) { bool baseResult = PassesDamageFilterImpl(info); return (m_bNegated) ? !baseResult : baseResult; } bool CBaseFilter::PassesDamageFilterImpl( const CTakeDamageInfo &info ) { return PassesFilterImpl( NULL, info.GetAttacker() ); } //----------------------------------------------------------------------------- // Purpose: Input handler for testing the activator. If the activator passes the // filter test, the OnPass output is fired. If not, the OnFail output is fired. //----------------------------------------------------------------------------- void CBaseFilter::InputTestActivator( inputdata_t &inputdata ) { if ( PassesFilter( inputdata.pCaller, inputdata.pActivator ) ) { m_OnPass.FireOutput( inputdata.pActivator, this ); } else { m_OnFail.FireOutput( inputdata.pActivator, this ); } } // ################################################################### // > FilterMultiple // // Allows one to filter through mutiple filters // ################################################################### #define MAX_FILTERS 5 enum filter_t { FILTER_AND, FILTER_OR, }; class CFilterMultiple : public CBaseFilter { DECLARE_CLASS( CFilterMultiple, CBaseFilter ); DECLARE_DATADESC(); filter_t m_nFilterType; string_t m_iFilterName[MAX_FILTERS]; EHANDLE m_hFilter[MAX_FILTERS]; bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ); bool PassesDamageFilterImpl(const CTakeDamageInfo &info); void Activate(void); }; LINK_ENTITY_TO_CLASS(filter_multi, CFilterMultiple); BEGIN_DATADESC( CFilterMultiple ) // Keys DEFINE_KEYFIELD(m_nFilterType, FIELD_INTEGER, "FilterType"), // Silence, Classcheck! // DEFINE_ARRAY( m_iFilterName, FIELD_STRING, MAX_FILTERS ), DEFINE_KEYFIELD(m_iFilterName[0], FIELD_STRING, "Filter01"), DEFINE_KEYFIELD(m_iFilterName[1], FIELD_STRING, "Filter02"), DEFINE_KEYFIELD(m_iFilterName[2], FIELD_STRING, "Filter03"), DEFINE_KEYFIELD(m_iFilterName[3], FIELD_STRING, "Filter04"), DEFINE_KEYFIELD(m_iFilterName[4], FIELD_STRING, "Filter05"), DEFINE_ARRAY( m_hFilter, FIELD_EHANDLE, MAX_FILTERS ), END_DATADESC() //------------------------------------------------------------------------------ // Purpose : Called after all entities have been loaded //------------------------------------------------------------------------------ void CFilterMultiple::Activate( void ) { BaseClass::Activate(); // We may reject an entity specified in the array of names, but we want the array of valid filters to be contiguous! int nNextFilter = 0; // Get handles to my filter entities for ( int i = 0; i < MAX_FILTERS; i++ ) { if ( m_iFilterName[i] != NULL_STRING ) { CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, m_iFilterName[i] ); CBaseFilter *pFilter = dynamic_cast(pEntity); if ( pFilter == NULL ) { Warning("filter_multi: Tried to add entity (%s) which is not a filter entity!\n", STRING( m_iFilterName[i] ) ); continue; } // Take this entity and increment out array pointer m_hFilter[nNextFilter] = pFilter; nNextFilter++; } } } //----------------------------------------------------------------------------- // Purpose: Returns true if the entity passes our filter, false if not. // Input : pEntity - Entity to test. //----------------------------------------------------------------------------- bool CFilterMultiple::PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) { // Test against each filter if (m_nFilterType == FILTER_AND) { for (int i=0;iPassesFilter( pCaller, pEntity ) ) { return false; } } } return true; } else // m_nFilterType == FILTER_OR { for (int i=0;iPassesFilter( pCaller, pEntity ) ) { return true; } } } return false; } } //----------------------------------------------------------------------------- // Purpose: Returns true if the entity passes our filter, false if not. // Input : pEntity - Entity to test. //----------------------------------------------------------------------------- bool CFilterMultiple::PassesDamageFilterImpl(const CTakeDamageInfo &info) { // Test against each filter if (m_nFilterType == FILTER_AND) { for (int i=0;iPassesDamageFilter(info)) { return false; } } } return true; } else // m_nFilterType == FILTER_OR { for (int i=0;iPassesDamageFilter(info)) { return true; } } } return false; } } // ################################################################### // > FilterName // ################################################################### class CFilterName : public CBaseFilter { DECLARE_CLASS( CFilterName, CBaseFilter ); DECLARE_DATADESC(); public: string_t m_iFilterName; bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) { // special check for !player as GetEntityName for player won't return "!player" as a name if (FStrEq(STRING(m_iFilterName), "!player")) { return pEntity->IsPlayer(); } else { return pEntity->NameMatches( STRING(m_iFilterName) ); } } }; LINK_ENTITY_TO_CLASS( filter_activator_name, CFilterName ); BEGIN_DATADESC( CFilterName ) // Keyfields DEFINE_KEYFIELD( m_iFilterName, FIELD_STRING, "filtername" ), END_DATADESC() // ################################################################### // > FilterClass // ################################################################### class CFilterClass : public CBaseFilter { DECLARE_CLASS( CFilterClass, CBaseFilter ); DECLARE_DATADESC(); public: string_t m_iFilterClass; bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) { return pEntity->ClassMatches( STRING(m_iFilterClass) ); } }; LINK_ENTITY_TO_CLASS( filter_activator_class, CFilterClass ); BEGIN_DATADESC( CFilterClass ) // Keyfields DEFINE_KEYFIELD( m_iFilterClass, FIELD_STRING, "filterclass" ), END_DATADESC() // ################################################################### // > FilterTeam // ################################################################### class FilterTeam : public CBaseFilter { DECLARE_CLASS( FilterTeam, CBaseFilter ); DECLARE_DATADESC(); public: int m_iFilterTeam; bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) { return ( pEntity->GetTeamNumber() == m_iFilterTeam ); } }; LINK_ENTITY_TO_CLASS( filter_activator_team, FilterTeam ); BEGIN_DATADESC( FilterTeam ) // Keyfields DEFINE_KEYFIELD( m_iFilterTeam, FIELD_INTEGER, "filterteam" ), END_DATADESC() // ################################################################### // > FilterMassGreater // ################################################################### class CFilterMassGreater : public CBaseFilter { DECLARE_CLASS( CFilterMassGreater, CBaseFilter ); DECLARE_DATADESC(); public: float m_fFilterMass; bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) { if ( pEntity->VPhysicsGetObject() == NULL ) return false; return ( pEntity->VPhysicsGetObject()->GetMass() > m_fFilterMass ); } }; LINK_ENTITY_TO_CLASS( filter_activator_mass_greater, CFilterMassGreater ); BEGIN_DATADESC( CFilterMassGreater ) // Keyfields DEFINE_KEYFIELD( m_fFilterMass, FIELD_FLOAT, "filtermass" ), END_DATADESC() // ################################################################### // > FilterDamageType // ################################################################### class FilterDamageType : public CBaseFilter { DECLARE_CLASS( FilterDamageType, CBaseFilter ); DECLARE_DATADESC(); protected: bool PassesFilterImpl(CBaseEntity *pCaller, CBaseEntity *pEntity ) { ASSERT( false ); return true; } bool PassesDamageFilterImpl(const CTakeDamageInfo &info) { return info.GetDamageType() == m_iDamageType; } int m_iDamageType; }; LINK_ENTITY_TO_CLASS( filter_damage_type, FilterDamageType ); BEGIN_DATADESC( FilterDamageType ) // Keyfields DEFINE_KEYFIELD( m_iDamageType, FIELD_INTEGER, "damagetype" ), END_DATADESC() // ################################################################### // > CFilterEnemy // ################################################################### #define SF_FILTER_ENEMY_NO_LOSE_AQUIRED (1<<0) class CFilterEnemy : public CBaseFilter { DECLARE_CLASS( CFilterEnemy, CBaseFilter ); // NOT SAVED // m_iszPlayerName DECLARE_DATADESC(); public: virtual bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ); virtual bool PassesDamageFilterImpl( const CTakeDamageInfo &info ); private: bool PassesNameFilter( CBaseEntity *pCaller ); bool PassesProximityFilter( CBaseEntity *pCaller, CBaseEntity *pEnemy ); bool PassesMobbedFilter( CBaseEntity *pCaller, CBaseEntity *pEnemy ); string_t m_iszEnemyName; // Name or classname float m_flRadius; // Radius (enemies are acquired at this range) float m_flOuterRadius; // Outer radius (enemies are LOST at this range) int m_nMaxSquadmatesPerEnemy; // Maximum number of squadmates who may share the same enemy string_t m_iszPlayerName; // "!player" }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CFilterEnemy::PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) { if ( pCaller == NULL || pEntity == NULL ) return false; // If asked to, we'll never fail to pass an already acquired enemy // This allows us to use test criteria to initially pick an enemy, then disregard the test until a new enemy comes along if ( HasSpawnFlags( SF_FILTER_ENEMY_NO_LOSE_AQUIRED ) && ( pEntity == pCaller->GetEnemy() ) ) return true; // This is a little weird, but it's saying that if we're not the entity we're excluding the filter to, then just pass it throughZ if ( PassesNameFilter( pEntity ) == false ) return true; if ( PassesProximityFilter( pCaller, pEntity ) == false ) return false; // NOTE: This can result in some weird NPC behavior if used improperly if ( PassesMobbedFilter( pCaller, pEntity ) == false ) return false; // The filter has been passed, meaning: // - If we wanted all criteria to fail, they have // - If we wanted all criteria to succeed, they have return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CFilterEnemy::PassesDamageFilterImpl( const CTakeDamageInfo &info ) { // NOTE: This function has no meaning to this implementation of the filter class! Assert( 0 ); return false; } //----------------------------------------------------------------------------- // Purpose: Tests the enemy's name or classname // Input : *pEnemy - Entity being assessed // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CFilterEnemy::PassesNameFilter( CBaseEntity *pEnemy ) { // If there is no name specified, we're not using it if ( m_iszEnemyName == NULL_STRING ) return true; // Cache off the special case player name if ( m_iszPlayerName == NULL_STRING ) { m_iszPlayerName = FindPooledString( "!player" ); } if ( m_iszEnemyName == m_iszPlayerName ) { if ( pEnemy->IsPlayer() ) { if ( m_bNegated ) return false; return true; } } // May be either a targetname or classname bool bNameOrClassnameMatches = ( m_iszEnemyName == pEnemy->GetEntityName() || m_iszEnemyName == pEnemy->m_iClassname ); // We only leave this code block in a state meaning we've "succeeded" in any context if ( m_bNegated ) { // We wanted the names to not match, but they did if ( bNameOrClassnameMatches ) return false; } else { // We wanted them to be the same, but they weren't if ( bNameOrClassnameMatches == false ) return false; } return true; } //----------------------------------------------------------------------------- // Purpose: Tests the enemy's proximity to the caller's position // Input : *pCaller - Entity assessing the target // *pEnemy - Entity being assessed // Output : Returns true if potential enemy passes this filter stage //----------------------------------------------------------------------------- bool CFilterEnemy::PassesProximityFilter( CBaseEntity *pCaller, CBaseEntity *pEnemy ) { // If there is no radius specified, we're not testing it if ( m_flRadius <= 0.0f ) return true; // We test the proximity differently when we've already picked up this enemy before bool bAlreadyEnemy = ( pCaller->GetEnemy() == pEnemy ); // Get our squared length to the enemy from the caller float flDistToEnemySqr = ( pCaller->GetAbsOrigin() - pEnemy->GetAbsOrigin() ).LengthSqr(); // Two radii are used to control oscillation between true/false cases // The larger radius is either specified or defaulted to be double or half the size of the inner radius float flLargerRadius = m_flOuterRadius; if ( flLargerRadius == 0 ) { flLargerRadius = ( m_bNegated ) ? (m_flRadius*0.5f) : (m_flRadius*2.0f); } float flSmallerRadius = m_flRadius; if ( flSmallerRadius > flLargerRadius ) { ::V_swap( flLargerRadius, flSmallerRadius ); } float flDist; if ( bAlreadyEnemy ) { flDist = ( m_bNegated ) ? flSmallerRadius : flLargerRadius; } else { flDist = ( m_bNegated ) ? flLargerRadius : flSmallerRadius; } // Test for success if ( flDistToEnemySqr <= (flDist*flDist) ) { // We wanted to fail but didn't if ( m_bNegated ) return false; return true; } // We wanted to succeed but didn't if ( m_bNegated == false ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: Attempt to govern how many squad members can target any given entity // Input : *pCaller - Entity assessing the target // *pEnemy - Entity being assessed // Output : Returns true if potential enemy passes this filter stage //----------------------------------------------------------------------------- bool CFilterEnemy::PassesMobbedFilter( CBaseEntity *pCaller, CBaseEntity *pEnemy ) { // Must be a valid candidate CAI_BaseNPC *pNPC = pCaller->MyNPCPointer(); if ( pNPC == NULL || pNPC->GetSquad() == NULL ) return true; // Make sure we're checking for this if ( m_nMaxSquadmatesPerEnemy <= 0 ) return true; AISquadIter_t iter; int nNumMatchingSquadmates = 0; // Look through our squad members to see how many of them are already mobbing this entity for ( CAI_BaseNPC *pSquadMember = pNPC->GetSquad()->GetFirstMember( &iter ); pSquadMember != NULL; pSquadMember = pNPC->GetSquad()->GetNextMember( &iter ) ) { // Disregard ourself if ( pSquadMember == pNPC ) continue; // If the enemies match, count it if ( pSquadMember->GetEnemy() == pEnemy ) { nNumMatchingSquadmates++; // If we're at or passed the max we stop if ( nNumMatchingSquadmates >= m_nMaxSquadmatesPerEnemy ) { // We wanted to find more than allowed and we did if ( m_bNegated ) return true; // We wanted to be less but we're not return false; } } } // We wanted to find more than the allowed amount but we didn't if ( m_bNegated ) return false; return true; } LINK_ENTITY_TO_CLASS( filter_enemy, CFilterEnemy ); BEGIN_DATADESC( CFilterEnemy ) DEFINE_KEYFIELD( m_iszEnemyName, FIELD_STRING, "filtername" ), DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "filter_radius" ), DEFINE_KEYFIELD( m_flOuterRadius, FIELD_FLOAT, "filter_outer_radius" ), DEFINE_KEYFIELD( m_nMaxSquadmatesPerEnemy, FIELD_INTEGER, "filter_max_per_enemy" ), DEFINE_FIELD( m_iszPlayerName, FIELD_STRING ), END_DATADESC()