//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: An NPC's memory of potential enemies // //=============================================================================// #include "cbase.h" #include "isaverestore.h" #include "ai_debug.h" #include "ai_memory.h" #include "ai_basenpc.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define EMEMORY_POOL_SIZE 64 #define AI_FREE_KNOWLEDGE_DURATION 1.75 //----------------------------------------------------------------------------- // AI_EnemyInfo_t // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- AI_EnemyInfo_t::AI_EnemyInfo_t(void) { hEnemy = NULL; vLastKnownLocation = vec3_origin; vLastSeenLocation = vec3_origin; timeLastSeen = 0; timeFirstSeen = 0; timeLastReacquired = 0; timeValidEnemy = 0; timeLastReceivedDamageFrom = 0; timeAtFirstHand = AI_INVALID_TIME; nFaction = FACTION_NONE; bDangerMemory = 0; bEludedMe = 0; bUnforgettable = 0; bMobbedMe = 0; } //----------------------------------------------------------------------------- // CAI_EnemiesListSaveRestoreOps // // Purpose: Handles save and load for enemy memories // //----------------------------------------------------------------------------- class CAI_EnemiesListSaveRestoreOps : public CDefSaveRestoreOps { public: CAI_EnemiesListSaveRestoreOps() { } virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) { CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField; int nMemories = pMemMap->Count(); pSave->WriteInt( &nMemories ); for ( CAI_Enemies::CMemMap::IndexType_t i = pMemMap->FirstInorder(); i != pMemMap->InvalidIndex(); i = pMemMap->NextInorder( i ) ) { pSave->WriteAll( (*pMemMap)[i] ); } } virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) { CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField; Assert( pMemMap->Count() == 0 ); int nMemories = pRestore->ReadInt(); while ( nMemories-- ) { AI_EnemyInfo_t *pAddMemory = new AI_EnemyInfo_t; pRestore->ReadAll( pAddMemory ); if ( pAddMemory->hEnemy != NULL ) { pMemMap->Insert( pAddMemory->hEnemy, pAddMemory ); } else delete pAddMemory; } } virtual void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) { CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField; for ( CAI_Enemies::CMemMap::IndexType_t i = pMemMap->FirstInorder(); i != pMemMap->InvalidIndex(); i = pMemMap->NextInorder( i ) ) { delete (*pMemMap)[i]; } pMemMap->RemoveAll(); } virtual bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) { CAI_Enemies::CMemMap *pMemMap = (CAI_Enemies::CMemMap *)fieldInfo.pField; return ( pMemMap->Count() == 0 ); } } g_AI_MemoryListSaveRestoreOps; //----------------------------------------------------------------------------- // CAI_Enemies // // Purpose: Stores a set of AI_EnemyInfo_t's // //----------------------------------------------------------------------------- BEGIN_SIMPLE_DATADESC( CAI_Enemies ) DEFINE_CUSTOM_FIELD( m_Map, &g_AI_MemoryListSaveRestoreOps ), DEFINE_FIELD( m_flFreeKnowledgeDuration, FIELD_FLOAT ), DEFINE_FIELD( m_flEnemyDiscardTime, FIELD_FLOAT ), DEFINE_FIELD( m_vecDefaultLKP, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vecDefaultLSP, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_serial, FIELD_INTEGER ), END_DATADESC() BEGIN_SIMPLE_DATADESC( AI_EnemyInfo_t ) DEFINE_FIELD( vLastKnownLocation, FIELD_POSITION_VECTOR ), DEFINE_FIELD( vLastSeenLocation, FIELD_POSITION_VECTOR ), DEFINE_FIELD( hEnemy, FIELD_EHANDLE ), DEFINE_FIELD( timeLastSeen, FIELD_TIME ), DEFINE_FIELD( timeFirstSeen, FIELD_TIME ), DEFINE_FIELD( timeLastReacquired, FIELD_TIME ), DEFINE_FIELD( timeValidEnemy, FIELD_TIME ), DEFINE_FIELD( timeLastReceivedDamageFrom, FIELD_TIME ), DEFINE_FIELD( timeAtFirstHand, FIELD_TIME ), DEFINE_FIELD( nFaction, FIELD_INTEGER ), DEFINE_FIELD( bDangerMemory, FIELD_BOOLEAN ), DEFINE_FIELD( bEludedMe, FIELD_BOOLEAN ), DEFINE_FIELD( bUnforgettable, FIELD_BOOLEAN ), DEFINE_FIELD( bMobbedMe, FIELD_BOOLEAN ), // NOT SAVED nextEMemory END_DATADESC() //----------------------------------------------------------------------------- CAI_Enemies::CAI_Enemies(void) { m_flFreeKnowledgeDuration = AI_FREE_KNOWLEDGE_DURATION; m_flEnemyDiscardTime = AI_DEF_ENEMY_DISCARD_TIME; m_vecDefaultLKP = vec3_invalid; m_vecDefaultLSP = vec3_invalid; m_serial = 0; SetDefLessFunc( m_Map ); } //----------------------------------------------------------------------------- CAI_Enemies::~CAI_Enemies() { for ( CMemMap::IndexType_t i = m_Map.FirstInorder(); i != m_Map.InvalidIndex(); i = m_Map.NextInorder( i ) ) { delete m_Map[i]; } } //----------------------------------------------------------------------------- // Purpose: Purges any dead enemies from memory //----------------------------------------------------------------------------- AI_EnemyInfo_t *CAI_Enemies::GetFirst( AIEnemiesIter_t *pIter ) { CMemMap::IndexType_t i = m_Map.FirstInorder(); *pIter = (AIEnemiesIter_t)(unsigned)i; if ( i == m_Map.InvalidIndex() ) return NULL; if ( m_Map[i]->hEnemy == NULL ) return GetNext( pIter ); return m_Map[i]; } //----------------------------------------------------------------------------- AI_EnemyInfo_t *CAI_Enemies::GetNext( AIEnemiesIter_t *pIter ) { CMemMap::IndexType_t i = (CMemMap::IndexType_t)((unsigned)(*pIter)); if ( i == m_Map.InvalidIndex() ) return NULL; i = m_Map.NextInorder( i ); *pIter = (AIEnemiesIter_t)(unsigned)i; if ( i == m_Map.InvalidIndex() ) return NULL; if ( m_Map[i]->hEnemy == NULL ) return GetNext( pIter ); return m_Map[i]; } //----------------------------------------------------------------------------- AI_EnemyInfo_t *CAI_Enemies::Find( CBaseEntity *pEntity, bool bTryDangerMemory ) { if ( pEntity == AI_UNKNOWN_ENEMY ) pEntity = NULL; CMemMap::IndexType_t i = m_Map.Find( pEntity ); if ( i == m_Map.InvalidIndex() ) { if ( !bTryDangerMemory || ( i = m_Map.Find( NULL ) ) == m_Map.InvalidIndex() ) return NULL; Assert(m_Map[i]->bDangerMemory == true); } return m_Map[i]; } //----------------------------------------------------------------------------- AI_EnemyInfo_t *CAI_Enemies::GetDangerMemory() { CMemMap::IndexType_t i = m_Map.Find( NULL ); if ( i == m_Map.InvalidIndex() ) return NULL; Assert(m_Map[i]->bDangerMemory == true); return m_Map[i]; } //----------------------------------------------------------------------------- bool CAI_Enemies::ShouldDiscardMemory( AI_EnemyInfo_t *pMemory ) { CBaseEntity *pEnemy = pMemory->hEnemy; if ( pEnemy ) { CAI_BaseNPC *pEnemyNPC = pEnemy->MyNPCPointer(); if ( pEnemyNPC ) { if ( pEnemyNPC->GetState() == NPC_STATE_DEAD ) return true; // forget about the enemy if he changes faction if ( pEnemyNPC->GetFaction() != pMemory->nFaction ) return true; } } else { if ( !pMemory->bDangerMemory ) return true; } if ( !pMemory->bUnforgettable && gpGlobals->curtime > pMemory->timeLastSeen + m_flEnemyDiscardTime ) { return true; } return false; } //----------------------------------------------------------------------------- void CAI_Enemies::RefreshMemories(void) { AI_PROFILE_SCOPE(CAI_Enemies_RefreshMemories); if ( m_flFreeKnowledgeDuration >= m_flEnemyDiscardTime ) { m_flFreeKnowledgeDuration = m_flEnemyDiscardTime - .1; } // ------------------- // Check each record // ------------------- CMemMap::IndexType_t i = m_Map.FirstInorder(); while ( i != m_Map.InvalidIndex() ) { AI_EnemyInfo_t *pMemory = m_Map[i]; CMemMap::IndexType_t iNext = m_Map.NextInorder( i ); // save so can remove if ( ShouldDiscardMemory( pMemory ) ) { delete pMemory; m_Map.RemoveAt(i); } else if ( pMemory->hEnemy ) { if ( gpGlobals->curtime <= pMemory->timeLastSeen + m_flFreeKnowledgeDuration ) { // Free knowledge is ignored if the target has notarget on if ( !(pMemory->hEnemy->GetFlags() & FL_NOTARGET) ) { pMemory->vLastKnownLocation = pMemory->hEnemy->GetAbsOrigin(); } } if ( gpGlobals->curtime <= pMemory->timeLastSeen ) { pMemory->vLastSeenLocation = pMemory->hEnemy->GetAbsOrigin(); } } i = iNext; } } //----------------------------------------------------------------------------- // Purpose: Updates information about our enemies // Output : Returns true if new enemy, false if already know of enemy //----------------------------------------------------------------------------- bool CAI_Enemies::UpdateMemory(CAI_Network* pAINet, CBaseEntity *pEnemy, const Vector &vPosition, float reactionDelay, bool firstHand ) { if ( pEnemy == AI_UNKNOWN_ENEMY ) pEnemy = NULL; const float DIST_TRIGGER_REACQUIRE_SQ = Square(20.0 * 12.0); const float TIME_TRIGGER_REACQUIRE = 4.0; const float MIN_DIST_TIME_TRIGGER_REACQUIRE_SQ = Square(4.0 * 12.0); AI_EnemyInfo_t *pMemory = Find( pEnemy ); // ------------------------------------------- // Otherwise just update my own // ------------------------------------------- // Update enemy information if ( pMemory ) { Assert(pEnemy || pMemory->bDangerMemory == true); if ( firstHand ) pMemory->timeLastSeen = gpGlobals->curtime; pMemory->bEludedMe = false; float deltaDist = (pMemory->vLastKnownLocation - vPosition).LengthSqr(); if (deltaDist>DIST_TRIGGER_REACQUIRE_SQ || ( deltaDist>MIN_DIST_TIME_TRIGGER_REACQUIRE_SQ && ( gpGlobals->curtime - pMemory->timeLastSeen ) > TIME_TRIGGER_REACQUIRE ) ) { pMemory->timeLastReacquired = gpGlobals->curtime; } // Only update if the enemy has moved if (deltaDist>Square(12.0)) { pMemory->vLastKnownLocation = vPosition; } // Update the time at which we first saw him firsthand if ( firstHand && pMemory->timeAtFirstHand == AI_INVALID_TIME ) { pMemory->timeAtFirstHand = gpGlobals->curtime; } return false; } // If not on my list of enemies add it AI_EnemyInfo_t *pAddMemory = new AI_EnemyInfo_t; pAddMemory->vLastKnownLocation = vPosition; if ( firstHand ) { pAddMemory->timeLastReacquired = pAddMemory->timeFirstSeen = pAddMemory->timeLastSeen = pAddMemory->timeAtFirstHand = gpGlobals->curtime; } else { // Block free knowledge pAddMemory->timeLastReacquired = pAddMemory->timeFirstSeen = pAddMemory->timeLastSeen = ( gpGlobals->curtime - (m_flFreeKnowledgeDuration + 0.01) ); pAddMemory->timeAtFirstHand = AI_INVALID_TIME; } if ( reactionDelay > 0.0 ) pAddMemory->timeValidEnemy = gpGlobals->curtime + reactionDelay; pAddMemory->bEludedMe = false; // I'm either remembering a position of an enemy or just a danger position pAddMemory->hEnemy = pEnemy; pAddMemory->bDangerMemory = ( pEnemy == NULL ); if ( pEnemy ) { CAI_BaseNPC *pEnemyNPC = pEnemy->MyNPCPointer(); if ( pEnemyNPC ) pAddMemory->nFaction = pEnemyNPC->GetFaction(); } // add to the list m_Map.Insert( pEnemy, pAddMemory ); m_serial++; return true; } //------------------------------------------------------------------------------ // Purpose : Returns true if this enemy is part of my memory //------------------------------------------------------------------------------ void CAI_Enemies::OnTookDamageFrom( CBaseEntity *pEnemy ) { AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); if ( pMemory ) pMemory->timeLastReceivedDamageFrom = gpGlobals->curtime; } //------------------------------------------------------------------------------ // Purpose : Returns true if this enemy is part of my memory //------------------------------------------------------------------------------ bool CAI_Enemies::HasMemory( CBaseEntity *pEnemy ) { return ( Find( pEnemy ) != NULL ); } //----------------------------------------------------------------------------- // Purpose: Clear information about our enemy //----------------------------------------------------------------------------- void CAI_Enemies::ClearMemory(CBaseEntity *pEnemy) { CMemMap::IndexType_t i = m_Map.Find( pEnemy ); if ( i != m_Map.InvalidIndex() ) { delete m_Map[i]; m_Map.RemoveAt( i ); } } //----------------------------------------------------------------------------- // Purpose: Notes that the given enemy has eluded me //----------------------------------------------------------------------------- void CAI_Enemies::MarkAsEluded( CBaseEntity *pEnemy ) { AI_EnemyInfo_t *pMemory = Find( pEnemy ); if ( pMemory ) { pMemory->bEludedMe = true; } } //----------------------------------------------------------------------------- // Purpose: Returns last known posiiton of given enemy //----------------------------------------------------------------------------- const Vector &CAI_Enemies::LastKnownPosition( CBaseEntity *pEnemy ) { AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); if ( pMemory ) { m_vecDefaultLKP = pMemory->vLastKnownLocation; } else { DevWarning( 2,"Asking LastKnownPosition for enemy that's not in my memory!!\n"); } return m_vecDefaultLKP; } //----------------------------------------------------------------------------- // Purpose: Returns the last position the enemy was SEEN at. This will always be // different than LastKnownPosition() when the enemy is out of sight, because // the last KNOWN position will be updated for a number of seconds after the // player disappears. //----------------------------------------------------------------------------- const Vector &CAI_Enemies::LastSeenPosition( CBaseEntity *pEnemy ) { AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); if ( pMemory ) { m_vecDefaultLSP = pMemory->vLastSeenLocation; } else { DevWarning( 2,"Asking LastSeenPosition for enemy that's not in my memory!!\n"); } return m_vecDefaultLSP; } float CAI_Enemies::TimeLastReacquired( CBaseEntity *pEnemy ) { // I've never seen something that doesn't exist if (!pEnemy) return 0; AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); if ( pMemory ) return pMemory->timeLastReacquired; if ( pEnemy != AI_UNKNOWN_ENEMY ) DevWarning( 2,"Asking TimeLastReacquired for enemy that's not in my memory!!\n"); return AI_INVALID_TIME; } //----------------------------------------------------------------------------- // Purpose: Sets position to the last known position of an enemy. If enemy // was not found returns last memory of danger position if it exists // Output : Returns false is no position is known //----------------------------------------------------------------------------- float CAI_Enemies::LastTimeSeen( CBaseEntity *pEnemy, bool bCheckDangerMemory /*= true*/ ) { // I've never seen something that doesn't exist if (!pEnemy) return 0; AI_EnemyInfo_t *pMemory = Find( pEnemy, bCheckDangerMemory ); if ( pMemory ) return pMemory->timeLastSeen; if ( pEnemy != AI_UNKNOWN_ENEMY ) DevWarning( 2,"Asking LastTimeSeen for enemy that's not in my memory!!\n"); return AI_INVALID_TIME; } //----------------------------------------------------------------------------- // Purpose: Get the time at which the enemy was first seen. // Output : Returns false is no position is known //----------------------------------------------------------------------------- float CAI_Enemies::FirstTimeSeen( CBaseEntity *pEnemy) { // I've never seen something that doesn't exist if (!pEnemy) return 0; AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); if ( pMemory ) return pMemory->timeFirstSeen; if ( pEnemy != AI_UNKNOWN_ENEMY ) DevWarning( 2,"Asking FirstTimeSeen for enemy that's not in my memory!!\n"); return AI_INVALID_TIME; } //----------------------------------------------------------------------------- // Purpose: // Input : *pEnemy - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CAI_Enemies::HasFreeKnowledgeOf( CBaseEntity *pEnemy ) { // I've never seen something that doesn't exist if (!pEnemy) return 0; AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); if ( pMemory ) { float flFreeKnowledgeTime = pMemory->timeLastSeen + m_flFreeKnowledgeDuration; return ( gpGlobals->curtime < flFreeKnowledgeTime ); } if ( pEnemy != AI_UNKNOWN_ENEMY ) DevWarning( 2,"Asking HasFreeKnowledgeOf for enemy that's not in my memory!!\n"); return AI_INVALID_TIME; } //----------------------------------------------------------------------------- float CAI_Enemies::LastTimeTookDamageFrom( CBaseEntity *pEnemy) { // I've never seen something that doesn't exist if (!pEnemy) return 0; AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); if ( pMemory ) return pMemory->timeLastReceivedDamageFrom; if ( pEnemy != AI_UNKNOWN_ENEMY ) DevWarning( 2,"Asking LastTimeTookDamageFrom for enemy that's not in my memory!!\n"); return AI_INVALID_TIME; } //----------------------------------------------------------------------------- // Purpose: Returns the time at which the enemy was first seen firsthand // Input : *pEnemy - // Output : float //----------------------------------------------------------------------------- float CAI_Enemies::TimeAtFirstHand( CBaseEntity *pEnemy ) { // I've never seen something that doesn't exist if (!pEnemy) return 0; AI_EnemyInfo_t *pMemory = Find( pEnemy, true ); if ( pMemory ) return pMemory->timeAtFirstHand; if ( pEnemy != AI_UNKNOWN_ENEMY ) DevWarning( 2,"Asking TimeAtFirstHand for enemy that's not in my memory!!\n"); return AI_INVALID_TIME; } //----------------------------------------------------------------------------- // Purpose: Sets position to the last known position of an enemy. If enemy // was not found returns last memory of danger position if it exists // Output : Returns false is no position is known //----------------------------------------------------------------------------- bool CAI_Enemies::HasEludedMe( CBaseEntity *pEnemy ) { AI_EnemyInfo_t *pMemory = Find( pEnemy ); if ( pMemory ) return pMemory->bEludedMe; return false; } void CAI_Enemies::SetTimeValidEnemy( CBaseEntity *pEnemy, float flTime ) { AI_EnemyInfo_t *pMemory = Find( pEnemy ); if ( pMemory ) pMemory->timeValidEnemy = flTime; } //----------------------------------------------------------------------------- void CAI_Enemies::SetUnforgettable( CBaseEntity *pEnemy, bool bUnforgettable ) { AI_EnemyInfo_t *pMemory = Find( pEnemy ); if ( pMemory ) pMemory->bUnforgettable = bUnforgettable; } //----------------------------------------------------------------------------- void CAI_Enemies::SetMobbedMe( CBaseEntity *pEnemy, bool bMobbedMe ) { AI_EnemyInfo_t *pMemory = Find( pEnemy ); if ( pMemory ) pMemory->bMobbedMe = bMobbedMe; } //----------------------------------------------------------------------------- void CAI_Enemies::SetFreeKnowledgeDuration( float flDuration ) { m_flFreeKnowledgeDuration = flDuration; if ( m_flFreeKnowledgeDuration >= m_flEnemyDiscardTime ) { // If your free knowledge time is greater than your discard time, // you'll forget about secondhand enemies passed to you by squadmates // as soon as you're given them. Assert( m_flFreeKnowledgeDuration < m_flEnemyDiscardTime ); m_flFreeKnowledgeDuration = m_flEnemyDiscardTime - .1; } } //----------------------------------------------------------------------------- void CAI_Enemies::SetEnemyDiscardTime( float flTime ) { m_flEnemyDiscardTime = flTime; if ( m_flFreeKnowledgeDuration >= m_flEnemyDiscardTime ) { // If your free knowledge time is greater than your discard time, // you'll forget about secondhand enemies passed to you by squadmates // as soon as you're given them. Assert( m_flFreeKnowledgeDuration < m_flEnemyDiscardTime ); m_flFreeKnowledgeDuration = m_flEnemyDiscardTime - .1; } }