Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

672 lines
20 KiB

//========= 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;
}
}