diff options
Diffstat (limited to 'game/server/ai_senses.cpp')
| -rw-r--r-- | game/server/ai_senses.cpp | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/game/server/ai_senses.cpp b/game/server/ai_senses.cpp new file mode 100644 index 0000000..45e23e0 --- /dev/null +++ b/game/server/ai_senses.cpp @@ -0,0 +1,752 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "ai_senses.h" + +#include "soundent.h" +#include "team.h" +#include "ai_basenpc.h" +#include "saverestore_utlvector.h" + +#ifdef PORTAL + #include "portal_util_shared.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Use this to disable caching and other optimizations in senses +#define DEBUG_SENSES 1 + +#ifdef DEBUG_SENSES +#define AI_PROFILE_SENSES(tag) AI_PROFILE_SCOPE(tag) +#else +#define AI_PROFILE_SENSES(tag) ((void)0) +#endif + +const float AI_STANDARD_NPC_SEARCH_TIME = .25; +const float AI_EFFICIENT_NPC_SEARCH_TIME = .35; +const float AI_HIGH_PRIORITY_SEARCH_TIME = 0.15; +const float AI_MISC_SEARCH_TIME = 0.45; + +//----------------------------------------------------------------------------- + +CAI_SensedObjectsManager g_AI_SensedObjectsManager; + +//----------------------------------------------------------------------------- + +#pragma pack(push) +#pragma pack(1) + +struct AISightIterVal_t +{ + char array; + short iNext; + char SeenArray; +}; + +#pragma pack(pop) + + +//============================================================================= +// +// CAI_Senses +// +//============================================================================= + +BEGIN_SIMPLE_DATADESC( CAI_Senses ) + + DEFINE_FIELD( m_LookDist, FIELD_FLOAT ), + DEFINE_FIELD( m_LastLookDist, FIELD_FLOAT ), + DEFINE_FIELD( m_TimeLastLook, FIELD_TIME ), + DEFINE_FIELD( m_iSensingFlags, FIELD_INTEGER ), + // m_iAudibleList (no way to save?) + DEFINE_UTLVECTOR(m_SeenHighPriority, FIELD_EHANDLE ), + DEFINE_UTLVECTOR(m_SeenNPCs, FIELD_EHANDLE ), + DEFINE_UTLVECTOR(m_SeenMisc, FIELD_EHANDLE ), + // m_SeenArrays (not saved, rebuilt) + + // Could fold these three and above timer into one concept, but would invalidate savegames + DEFINE_FIELD( m_TimeLastLookHighPriority, FIELD_TIME ), + DEFINE_FIELD( m_TimeLastLookNPCs, FIELD_TIME ), + DEFINE_FIELD( m_TimeLastLookMisc, FIELD_TIME ), + +END_DATADESC() + +//----------------------------------------------------------------------------- + +bool CAI_Senses::CanHearSound( CSound *pSound ) +{ + if ( pSound->m_hOwner.Get() == GetOuter() ) + return false; + + if( GetOuter()->GetState() == NPC_STATE_SCRIPT && pSound->IsSoundType( SOUND_DANGER ) ) + { + // For now, don't hear danger in scripted sequences. This probably isn't a + // good long term solution, but it makes the Bank Exterior work better. + return false; + } + + if ( GetOuter()->IsInAScript() ) + return false; + + // @TODO (toml 10-18-02): what about player sounds and notarget? + float flHearDistanceSq = pSound->Volume() * GetOuter()->HearingSensitivity(); + flHearDistanceSq *= flHearDistanceSq; + if( pSound->GetSoundOrigin().DistToSqr( GetOuter()->EarPosition() ) <= flHearDistanceSq ) + { + return GetOuter()->QueryHearSound( pSound ); + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Listen - npcs dig through the active sound list for +// any sounds that may interest them. (smells, too!) + +void CAI_Senses::Listen( void ) +{ + m_iAudibleList = SOUNDLIST_EMPTY; + + int iSoundMask = GetOuter()->GetSoundInterests(); + + if ( iSoundMask != SOUND_NONE && !(GetOuter()->HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN)) ) + { + int iSound = CSoundEnt::ActiveList(); + + while ( iSound != SOUNDLIST_EMPTY ) + { + CSound *pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound ); + + if ( pCurrentSound && (iSoundMask & pCurrentSound->SoundType()) && CanHearSound( pCurrentSound ) ) + { + // the npc cares about this sound, and it's close enough to hear. + pCurrentSound->m_iNextAudible = m_iAudibleList; + m_iAudibleList = iSound; + } + + iSound = pCurrentSound->NextSound(); + } + } + + GetOuter()->OnListened(); +} + +//----------------------------------------------------------------------------- + +bool CAI_Senses::ShouldSeeEntity( CBaseEntity *pSightEnt ) +{ + if ( pSightEnt == GetOuter() || !pSightEnt->IsAlive() ) + return false; + + if ( pSightEnt->IsPlayer() && ( pSightEnt->GetFlags() & FL_NOTARGET ) ) + return false; + + // don't notice anyone waiting to be seen by the player + if ( pSightEnt->m_spawnflags & SF_NPC_WAIT_TILL_SEEN ) + return false; + + if ( !pSightEnt->CanBeSeenBy( GetOuter() ) ) + return false; + + if ( !GetOuter()->QuerySeeEntity( pSightEnt, true ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- + +bool CAI_Senses::CanSeeEntity( CBaseEntity *pSightEnt ) +{ + return ( GetOuter()->FInViewCone( pSightEnt ) && GetOuter()->FVisible( pSightEnt ) ); +} + +#ifdef PORTAL +bool CAI_Senses::CanSeeEntityThroughPortal( const CProp_Portal *pPortal, CBaseEntity *pSightEnt ) +{ + return GetOuter()->FVisibleThroughPortal( pPortal, pSightEnt ); +} +#endif + +//----------------------------------------------------------------------------- + +bool CAI_Senses::DidSeeEntity( CBaseEntity *pSightEnt ) const +{ + AISightIter_t iter; + CBaseEntity *pTestEnt; + + pTestEnt = GetFirstSeenEntity( &iter ); + + while( pTestEnt ) + { + if ( pSightEnt == pTestEnt ) + return true; + pTestEnt = GetNextSeenEntity( &iter ); + } + return false; +} + +//----------------------------------------------------------------------------- + +void CAI_Senses::NoteSeenEntity( CBaseEntity *pSightEnt ) +{ + pSightEnt->m_pLink = GetOuter()->m_pLink; + GetOuter()->m_pLink = pSightEnt; +} + +//----------------------------------------------------------------------------- + +bool CAI_Senses::WaitingUntilSeen( CBaseEntity *pSightEnt ) +{ + if ( GetOuter()->m_spawnflags & SF_NPC_WAIT_TILL_SEEN ) + { + if ( pSightEnt->IsPlayer() ) + { + CBasePlayer *pPlayer = ToBasePlayer( pSightEnt ); + Vector zero = Vector(0,0,0); + // don't link this client in the list if the npc is wait till seen and the player isn't facing the npc + if ( pPlayer + // && pPlayer->FVisible( GetOuter() ) + && pPlayer->FInViewCone( GetOuter() ) + && FBoxVisible( pSightEnt, static_cast<CBaseEntity*>(GetOuter()), zero ) ) + { + // player sees us, become normal now. + GetOuter()->m_spawnflags &= ~SF_NPC_WAIT_TILL_SEEN; + return false; + } + } + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- + +bool CAI_Senses::SeeEntity( CBaseEntity *pSightEnt ) +{ + GetOuter()->OnSeeEntity( pSightEnt ); + + // insert at the head of my sight list + NoteSeenEntity( pSightEnt ); + + return true; +} + +//----------------------------------------------------------------------------- + +CBaseEntity *CAI_Senses::GetFirstSeenEntity( AISightIter_t *pIter, seentype_t iSeenType ) const +{ + COMPILE_TIME_ASSERT( sizeof( AISightIter_t ) == sizeof( AISightIterVal_t ) ); + + AISightIterVal_t *pIterVal = (AISightIterVal_t *)pIter; + + // If we're searching for a specific type, start in that array + pIterVal->SeenArray = (char)iSeenType; + int iFirstArray = ( iSeenType == SEEN_ALL ) ? 0 : iSeenType; + + for ( int i = iFirstArray; i < ARRAYSIZE( m_SeenArrays ); i++ ) + { + if ( m_SeenArrays[i]->Count() != 0 ) + { + pIterVal->array = i; + pIterVal->iNext = 1; + return (*m_SeenArrays[i])[0]; + } + } + + (*pIter) = (AISightIter_t)(-1); + return NULL; +} + +//----------------------------------------------------------------------------- + +CBaseEntity *CAI_Senses::GetNextSeenEntity( AISightIter_t *pIter ) const +{ + if ( ((int)*pIter) != -1 ) + { + AISightIterVal_t *pIterVal = (AISightIterVal_t *)pIter; + + for ( int i = pIterVal->array; i < ARRAYSIZE( m_SeenArrays ); i++ ) + { + for ( int j = pIterVal->iNext; j < m_SeenArrays[i]->Count(); j++ ) + { + if ( (*m_SeenArrays[i])[j].Get() != NULL ) + { + pIterVal->array = i; + pIterVal->iNext = j+1; + return (*m_SeenArrays[i])[j]; + } + } + pIterVal->iNext = 0; + + // If we're searching for a specific type, don't move to the next array + if ( pIterVal->SeenArray != SEEN_ALL ) + break; + } + (*pIter) = (AISightIter_t)(-1); + } + return NULL; +} + +//----------------------------------------------------------------------------- + +void CAI_Senses::BeginGather() +{ + // clear my sight list + GetOuter()->m_pLink = NULL; +} + +//----------------------------------------------------------------------------- + +void CAI_Senses::EndGather( int nSeen, CUtlVector<EHANDLE> *pResult ) +{ + pResult->SetCount( nSeen ); + if ( nSeen ) + { + CBaseEntity *pCurrent = GetOuter()->m_pLink; + for (int i = 0; i < nSeen; i++ ) + { + Assert( pCurrent ); + (*pResult)[i].Set( pCurrent ); + pCurrent = pCurrent->m_pLink; + } + GetOuter()->m_pLink = NULL; + } +} + +//----------------------------------------------------------------------------- +// Look - Base class npc function to find enemies or +// food by sight. iDistance is distance ( in units ) that the +// npc can see. +// +// Sets the sight bits of the m_afConditions mask to indicate +// which types of entities were sighted. +// Function also sets the Looker's m_pLink +// to the head of a link list that contains all visible ents. +// (linked via each ent's m_pLink field) +// + +void CAI_Senses::Look( int iDistance ) +{ + if ( m_TimeLastLook != gpGlobals->curtime || m_LastLookDist != iDistance ) + { + //----------------------------- + + LookForHighPriorityEntities( iDistance ); + LookForNPCs( iDistance); + LookForObjects( iDistance ); + + //----------------------------- + + m_LastLookDist = iDistance; + m_TimeLastLook = gpGlobals->curtime; + } + + GetOuter()->OnLooked( iDistance ); +} + +//----------------------------------------------------------------------------- + +bool CAI_Senses::Look( CBaseEntity *pSightEnt ) +{ + if ( WaitingUntilSeen( pSightEnt ) ) + return false; + + if ( ShouldSeeEntity( pSightEnt ) && CanSeeEntity( pSightEnt ) ) + { + return SeeEntity( pSightEnt ); + } + return false; +} + +#ifdef PORTAL +bool CAI_Senses::LookThroughPortal( const CProp_Portal *pPortal, CBaseEntity *pSightEnt ) +{ + if ( WaitingUntilSeen( pSightEnt ) ) + return false; + + if ( ShouldSeeEntity( pSightEnt ) && CanSeeEntityThroughPortal( pPortal, pSightEnt ) ) + { + return SeeEntity( pSightEnt ); + } + return false; +} +#endif + +//----------------------------------------------------------------------------- + +int CAI_Senses::LookForHighPriorityEntities( int iDistance ) +{ + int nSeen = 0; + if ( gpGlobals->curtime - m_TimeLastLookHighPriority > AI_HIGH_PRIORITY_SEARCH_TIME ) + { + AI_PROFILE_SENSES(CAI_Senses_LookForHighPriorityEntities); + m_TimeLastLookHighPriority = gpGlobals->curtime; + + BeginGather(); + + float distSq = ( iDistance * iDistance ); + const Vector &origin = GetAbsOrigin(); + + // Players + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer ) + { + if ( origin.DistToSqr(pPlayer->GetAbsOrigin()) < distSq && Look( pPlayer ) ) + { + nSeen++; + } +#ifdef PORTAL + else + { + CProp_Portal *pPortal = GetOuter()->FInViewConeThroughPortal( pPlayer ); + if ( pPortal && UTIL_Portal_DistanceThroughPortalSqr( pPortal, origin, pPlayer->GetAbsOrigin() ) < distSq && LookThroughPortal( pPortal, pPlayer ) ) + { + nSeen++; + } + } +#endif + } + } + + EndGather( nSeen, &m_SeenHighPriority ); + } + else + { + for ( int i = m_SeenHighPriority.Count() - 1; i >= 0; --i ) + { + if ( m_SeenHighPriority[i].Get() == NULL ) + m_SeenHighPriority.FastRemove( i ); + } + nSeen = m_SeenHighPriority.Count(); + } + + return nSeen; +} + +//----------------------------------------------------------------------------- + +int CAI_Senses::LookForNPCs( int iDistance ) +{ + bool bRemoveStaleFromCache = false; + float distSq = ( iDistance * iDistance ); + const Vector &origin = GetAbsOrigin(); + AI_Efficiency_t efficiency = GetOuter()->GetEfficiency(); + float timeNPCs = ( efficiency < AIE_VERY_EFFICIENT ) ? AI_STANDARD_NPC_SEARCH_TIME : AI_EFFICIENT_NPC_SEARCH_TIME; + if ( gpGlobals->curtime - m_TimeLastLookNPCs > timeNPCs ) + { + AI_PROFILE_SENSES(CAI_Senses_LookForNPCs); + + m_TimeLastLookNPCs = gpGlobals->curtime; + + if ( efficiency < AIE_SUPER_EFFICIENT ) + { + int i, nSeen = 0; + + BeginGather(); + + CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); + + for ( i = 0; i < g_AI_Manager.NumAIs(); i++ ) + { + if ( ppAIs[i] != GetOuter() && ( ppAIs[i]->ShouldNotDistanceCull() || origin.DistToSqr(ppAIs[i]->GetAbsOrigin()) < distSq ) ) + { + if ( Look( ppAIs[i] ) ) + { + nSeen++; + } + } + } + + EndGather( nSeen, &m_SeenNPCs ); + + return nSeen; + } + + bRemoveStaleFromCache = true; + // Fall through + } + + for ( int i = m_SeenNPCs.Count() - 1; i >= 0; --i ) + { + if ( m_SeenNPCs[i].Get() == NULL ) + { + m_SeenNPCs.FastRemove( i ); + } + else if ( bRemoveStaleFromCache ) + { + if ( ( !((CAI_BaseNPC *)m_SeenNPCs[i].Get())->ShouldNotDistanceCull() && + origin.DistToSqr(m_SeenNPCs[i]->GetAbsOrigin()) > distSq ) || + !Look( m_SeenNPCs[i] ) ) + { + m_SeenNPCs.FastRemove( i ); + } + } + } + + return m_SeenNPCs.Count(); +} + +//----------------------------------------------------------------------------- + +int CAI_Senses::LookForObjects( int iDistance ) +{ + const int BOX_QUERY_MASK = FL_OBJECT; + int nSeen = 0; + + if ( gpGlobals->curtime - m_TimeLastLookMisc > AI_MISC_SEARCH_TIME ) + { + AI_PROFILE_SENSES(CAI_Senses_LookForObjects); + m_TimeLastLookMisc = gpGlobals->curtime; + + BeginGather(); + + float distSq = ( iDistance * iDistance ); + const Vector &origin = GetAbsOrigin(); + int iter; + CBaseEntity *pEnt = g_AI_SensedObjectsManager.GetFirst( &iter ); + while ( pEnt ) + { + if ( pEnt->GetFlags() & BOX_QUERY_MASK ) + { + if ( origin.DistToSqr(pEnt->GetAbsOrigin()) < distSq && Look( pEnt) ) + { + nSeen++; + } + } + pEnt = g_AI_SensedObjectsManager.GetNext( &iter ); + } + + EndGather( nSeen, &m_SeenMisc ); + } + else + { + for ( int i = m_SeenMisc.Count() - 1; i >= 0; --i ) + { + if ( m_SeenMisc[i].Get() == NULL ) + m_SeenMisc.FastRemove( i ); + } + nSeen = m_SeenMisc.Count(); + } + + return nSeen; +} + +//----------------------------------------------------------------------------- + +float CAI_Senses::GetTimeLastUpdate( CBaseEntity *pEntity ) +{ + if ( !pEntity ) + return 0; + if ( pEntity->IsPlayer() ) + return m_TimeLastLookHighPriority; + if ( pEntity->IsNPC() ) + return m_TimeLastLookNPCs; + return m_TimeLastLookMisc; +} + +//----------------------------------------------------------------------------- + +CSound* CAI_Senses::GetFirstHeardSound( AISoundIter_t *pIter ) +{ + int iFirst = GetAudibleList(); + + if ( iFirst == SOUNDLIST_EMPTY ) + { + *pIter = NULL; + return NULL; + } + + *pIter = (AISoundIter_t)iFirst; + return CSoundEnt::SoundPointerForIndex( iFirst ); +} + +//----------------------------------------------------------------------------- + +CSound* CAI_Senses::GetNextHeardSound( AISoundIter_t *pIter ) +{ + if ( !*pIter ) + return NULL; + + int iCurrent = (int)*pIter; + + Assert( iCurrent != SOUNDLIST_EMPTY ); + if ( iCurrent == SOUNDLIST_EMPTY ) + { + *pIter = NULL; + return NULL; + } + + iCurrent = CSoundEnt::SoundPointerForIndex( iCurrent )->m_iNextAudible; + if ( iCurrent == SOUNDLIST_EMPTY ) + { + *pIter = NULL; + return NULL; + } + + *pIter = (AISoundIter_t)iCurrent; + return CSoundEnt::SoundPointerForIndex( iCurrent ); +} + +//----------------------------------------------------------------------------- + +CSound *CAI_Senses::GetClosestSound( bool fScent, int validTypes, bool bUsePriority ) +{ + float flBestDist = MAX_COORD_RANGE*MAX_COORD_RANGE;// so first nearby sound will become best so far. + float flDist; + int iBestPriority = SOUND_PRIORITY_VERY_LOW; + + AISoundIter_t iter; + + CSound *pResult = NULL; + CSound *pCurrent = GetFirstHeardSound( &iter ); + + Vector earPosition = GetOuter()->EarPosition(); + + while ( pCurrent ) + { + if ( ( !fScent && pCurrent->FIsSound() ) || + ( fScent && pCurrent->FIsScent() ) ) + { + if( pCurrent->IsSoundType( validTypes ) && !GetOuter()->ShouldIgnoreSound( pCurrent ) ) + { + if( !bUsePriority || GetOuter()->GetSoundPriority(pCurrent) >= iBestPriority ) + { + flDist = ( pCurrent->GetSoundOrigin() - earPosition ).LengthSqr(); + + if ( flDist < flBestDist ) + { + pResult = pCurrent; + flBestDist = flDist; + + iBestPriority = GetOuter()->GetSoundPriority(pCurrent); + } + } + } + } + + pCurrent = GetNextHeardSound( &iter ); + } + + return pResult; +} + +//----------------------------------------------------------------------------- + +void CAI_Senses::PerformSensing( void ) +{ + AI_PROFILE_SCOPE (CAI_BaseNPC_PerformSensing); + + // ----------------- + // Look + // ----------------- + if( !HasSensingFlags(SENSING_FLAGS_DONT_LOOK) ) + Look( m_LookDist ); + + // ------------------ + // Listen + // ------------------ + if( !HasSensingFlags(SENSING_FLAGS_DONT_LISTEN) ) + Listen(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void CAI_SensedObjectsManager::Init() +{ + CBaseEntity *pEnt = NULL; + while ( ( pEnt = gEntList.NextEnt( pEnt ) ) != NULL ) + { + OnEntitySpawned( pEnt ); + } + + gEntList.AddListenerEntity( this ); +} + +//----------------------------------------------------------------------------- + +void CAI_SensedObjectsManager::Term() +{ + gEntList.RemoveListenerEntity( this ); + m_SensedObjects.RemoveAll(); +} + +//----------------------------------------------------------------------------- + +CBaseEntity *CAI_SensedObjectsManager::GetFirst( int *pIter ) +{ + if ( m_SensedObjects.Count() ) + { + *pIter = 1; + return m_SensedObjects[0]; + } + + *pIter = 0; + return NULL; +} + +//----------------------------------------------------------------------------- + +CBaseEntity *CAI_SensedObjectsManager::GetNext( int *pIter ) +{ + int i = *pIter; + if ( i && i < m_SensedObjects.Count() ) + { + (*pIter)++; + return m_SensedObjects[i]; + } + + *pIter = 0; + return NULL; +} + +//----------------------------------------------------------------------------- + +void CAI_SensedObjectsManager::OnEntitySpawned( CBaseEntity *pEntity ) +{ + if ( ( pEntity->GetFlags() & FL_OBJECT ) && !pEntity->IsPlayer() && !pEntity->IsNPC() ) + { + m_SensedObjects.AddToTail( pEntity ); + } +} + +//----------------------------------------------------------------------------- + +void CAI_SensedObjectsManager::OnEntityDeleted( CBaseEntity *pEntity ) +{ + if ( ( pEntity->GetFlags() & FL_OBJECT ) && !pEntity->IsPlayer() && !pEntity->IsNPC() ) + { + int i = m_SensedObjects.Find( pEntity ); + if ( i != m_SensedObjects.InvalidIndex() ) + m_SensedObjects.FastRemove( i ); + } +} + +void CAI_SensedObjectsManager::AddEntity( CBaseEntity *pEntity ) +{ + if ( m_SensedObjects.Find( pEntity ) != m_SensedObjects.InvalidIndex() ) + return; + + // We shouldn't be adding players or NPCs to this list + Assert( !pEntity->IsPlayer() && !pEntity->IsNPC() ); + + // Add the object flag so it gets removed when it dies + pEntity->AddFlag( FL_OBJECT ); + m_SensedObjects.AddToTail( pEntity ); +} + +//============================================================================= |