2020-04-22 16:56:21 +00:00
|
|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
|
|
//
|
|
|
|
// Purpose: Squad classes
|
|
|
|
//
|
|
|
|
//=============================================================================//
|
|
|
|
|
|
|
|
#include "cbase.h"
|
|
|
|
#include "ai_squad.h"
|
|
|
|
#include "ai_squadslot.h"
|
|
|
|
#include "ai_basenpc.h"
|
|
|
|
#include "saverestore_bitstring.h"
|
|
|
|
#include "saverestore_utlvector.h"
|
|
|
|
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
CAI_SquadManager g_AI_SquadManager;
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// CAI_SquadManager
|
|
|
|
//
|
|
|
|
// Purpose: Manages all the squads in the system
|
|
|
|
//
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
CAI_Squad *CAI_SquadManager::FindSquad( string_t squadName )
|
|
|
|
{
|
|
|
|
CAI_Squad* pSquad = m_pSquads;
|
|
|
|
|
|
|
|
while (pSquad)
|
|
|
|
{
|
|
|
|
if (FStrEq(STRING(squadName),pSquad->GetName()))
|
|
|
|
{
|
|
|
|
return pSquad;
|
|
|
|
}
|
|
|
|
pSquad = pSquad->m_pNextSquad;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
CAI_Squad *CAI_SquadManager::CreateSquad(string_t squadName)
|
|
|
|
{
|
|
|
|
CAI_Squad *pResult = new CAI_Squad(squadName);
|
|
|
|
|
|
|
|
// ---------------------------------
|
|
|
|
// Only named squads get added to the squad list
|
|
|
|
if ( squadName != NULL_STRING )
|
|
|
|
{
|
|
|
|
pResult->m_pNextSquad = m_pSquads;
|
|
|
|
m_pSquads = pResult;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
pResult->m_pNextSquad = NULL;
|
|
|
|
return pResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
int CAI_SquadManager::NumSquads()
|
|
|
|
{
|
|
|
|
int nSquads = 0;
|
|
|
|
CAI_Squad* pSquad = m_pSquads;
|
|
|
|
|
|
|
|
while (pSquad)
|
|
|
|
{
|
|
|
|
nSquads++;
|
|
|
|
pSquad = pSquad->GetNext();
|
|
|
|
}
|
|
|
|
return nSquads;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
void CAI_SquadManager::DeleteSquad( CAI_Squad *pSquad )
|
|
|
|
{
|
|
|
|
CAI_Squad *pCurSquad = m_pSquads;
|
|
|
|
if (pCurSquad == pSquad)
|
|
|
|
{
|
|
|
|
g_AI_SquadManager.m_pSquads = pCurSquad->m_pNextSquad;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
while (pCurSquad)
|
|
|
|
{
|
|
|
|
if (pCurSquad->m_pNextSquad == pSquad)
|
|
|
|
{
|
|
|
|
pCurSquad->m_pNextSquad = pCurSquad->m_pNextSquad->m_pNextSquad;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
pCurSquad= pCurSquad->m_pNextSquad;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete pSquad;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
// Purpose: Delete all the squads (called between levels / loads)
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
void CAI_SquadManager::DeleteAllSquads(void)
|
|
|
|
{
|
|
|
|
CAI_Squad *squad = CAI_SquadManager::m_pSquads;
|
|
|
|
|
|
|
|
while (squad)
|
|
|
|
{
|
|
|
|
CAI_Squad *temp = squad->m_pNextSquad;
|
|
|
|
delete squad;
|
|
|
|
squad = temp;
|
|
|
|
}
|
|
|
|
CAI_SquadManager::m_pSquads = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// CAI_Squad
|
|
|
|
//
|
|
|
|
// Purpose: Tracks enemies, squad slots, squad members
|
|
|
|
//
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#ifdef PER_ENEMY_SQUADSLOTS
|
|
|
|
BEGIN_SIMPLE_DATADESC( AISquadEnemyInfo_t )
|
|
|
|
|
|
|
|
DEFINE_FIELD( hEnemy, FIELD_EHANDLE ),
|
|
|
|
DEFINE_BITSTRING( slots),
|
|
|
|
|
|
|
|
END_DATADESC()
|
|
|
|
#endif
|
|
|
|
|
|
|
|
BEGIN_SIMPLE_DATADESC( CAI_Squad )
|
|
|
|
|
|
|
|
// m_pNextSquad (rebuilt)
|
|
|
|
// m_Name (rebuilt)
|
|
|
|
// m_SquadMembers (rebuilt)
|
|
|
|
// m_SquadMembers.Count() (rebuilt)
|
|
|
|
DEFINE_FIELD( m_flSquadSoundWaitTime, FIELD_TIME ),
|
|
|
|
DEFINE_FIELD( m_nSquadSoundPriority, FIELD_INTEGER ),
|
|
|
|
DEFINE_FIELD( m_hSquadInflictor, FIELD_EHANDLE ),
|
|
|
|
DEFINE_AUTO_ARRAY( m_SquadData, FIELD_INTEGER ),
|
|
|
|
// m_pLastFoundEnemyInfo (think transient)
|
|
|
|
|
|
|
|
#ifdef PER_ENEMY_SQUADSLOTS
|
|
|
|
DEFINE_UTLVECTOR(m_EnemyInfos, FIELD_EMBEDDED ),
|
|
|
|
DEFINE_FIELD( m_flEnemyInfoCleanupTime, FIELD_TIME ),
|
|
|
|
#else
|
|
|
|
DEFINE_EMBEDDED( m_squadSlotsUsed ),
|
|
|
|
#endif
|
|
|
|
|
|
|
|
END_DATADESC()
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
2022-05-01 17:09:55 +00:00
|
|
|
CAI_Squad::CAI_Squad(string_t newName)
|
2020-04-22 16:56:21 +00:00
|
|
|
#ifndef PER_ENEMY_SQUADSLOTS
|
|
|
|
: m_squadSlotsUsed(MAX_SQUADSLOTS)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
Init( newName );
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
2022-05-01 17:09:55 +00:00
|
|
|
CAI_Squad::CAI_Squad()
|
2020-04-22 16:56:21 +00:00
|
|
|
#ifndef PER_ENEMY_SQUADSLOTS
|
|
|
|
: m_squadSlotsUsed(MAX_SQUADSLOTS)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
Init( NULL_STRING );
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
void CAI_Squad::Init(string_t newName)
|
|
|
|
{
|
2022-05-01 17:09:55 +00:00
|
|
|
m_Name = newName;
|
2020-04-22 16:56:21 +00:00
|
|
|
m_pNextSquad = NULL;
|
|
|
|
m_flSquadSoundWaitTime = 0;
|
|
|
|
m_SquadMembers.RemoveAll();
|
|
|
|
|
|
|
|
m_flSquadSoundWaitTime = 0;
|
|
|
|
|
|
|
|
SetSquadInflictor( NULL );
|
|
|
|
|
|
|
|
#ifdef PER_ENEMY_SQUADSLOTS
|
|
|
|
m_flEnemyInfoCleanupTime = 0;
|
|
|
|
m_pLastFoundEnemyInfo = NULL;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
CAI_Squad::~CAI_Squad(void)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
bool CAI_Squad::IsSilentMember( const CAI_BaseNPC *pNPC )
|
|
|
|
{
|
|
|
|
if ( !pNPC || ( pNPC->GetMoveType() == MOVETYPE_NONE && pNPC->GetSolid() == SOLID_NONE ) ) // a.k.a., enemy finder
|
|
|
|
return true;
|
|
|
|
return pNPC->IsSilentSquadMember();
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
// Purpose: Removes an NPC from a squad
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
void CAI_Squad::RemoveFromSquad( CAI_BaseNPC *pNPC, bool bDeath )
|
|
|
|
{
|
|
|
|
if ( !pNPC )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Find the index of this squad member
|
|
|
|
int member;
|
|
|
|
int myIndex = m_SquadMembers.Find(pNPC);
|
|
|
|
if (myIndex == -1)
|
|
|
|
{
|
|
|
|
DevMsg("ERROR: Attempting to remove non-existing squad membmer!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_SquadMembers.Remove(myIndex);
|
|
|
|
|
|
|
|
// Notify squad members of death
|
|
|
|
if ( bDeath )
|
|
|
|
{
|
|
|
|
for (member = 0; member < m_SquadMembers.Count(); member++)
|
|
|
|
{
|
|
|
|
CAI_BaseNPC* pSquadMem = m_SquadMembers[member];
|
|
|
|
if (pSquadMem)
|
|
|
|
{
|
|
|
|
pSquadMem->NotifyDeadFriend(pNPC);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pNPC->SetSquad(NULL);
|
|
|
|
pNPC->SetSquadName( NULL_STRING );
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
// Purpose: Addes the given NPC to the squad
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_Squad::AddToSquad(CAI_BaseNPC *pNPC)
|
|
|
|
{
|
|
|
|
if ( !pNPC || !pNPC->IsAlive() )
|
|
|
|
{
|
|
|
|
Assert(0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( pNPC->GetSquad() == this )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( pNPC->GetSquad() )
|
|
|
|
{
|
|
|
|
pNPC->GetSquad()->RemoveFromSquad(pNPC);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_SquadMembers.Count() == MAX_SQUAD_MEMBERS)
|
|
|
|
{
|
|
|
|
DevMsg("Error!! Squad %s is too big!!! Replacing last member\n", STRING( this->m_Name ));
|
|
|
|
m_SquadMembers.Remove(m_SquadMembers.Count()-1);
|
|
|
|
}
|
|
|
|
m_SquadMembers.AddToTail(pNPC);
|
|
|
|
pNPC->SetSquad( this );
|
|
|
|
pNPC->SetSquadName( m_Name );
|
|
|
|
|
|
|
|
if ( m_SquadMembers.Count() > 1 )
|
|
|
|
{
|
|
|
|
CAI_BaseNPC *pCopyFrom = m_SquadMembers[0];
|
|
|
|
CAI_Enemies *pEnemies = pCopyFrom->GetEnemies();
|
|
|
|
AIEnemiesIter_t iter;
|
|
|
|
AI_EnemyInfo_t *pInfo = pEnemies->GetFirst( &iter );
|
|
|
|
while ( pInfo )
|
|
|
|
{
|
|
|
|
pNPC->UpdateEnemyMemory( pInfo->hEnemy, pInfo->vLastKnownLocation, pCopyFrom );
|
|
|
|
pInfo = pEnemies->GetNext( &iter );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
CAI_BaseNPC *CAI_Squad::SquadMemberInRange( const Vector &vecLocation, float flDist )
|
|
|
|
{
|
|
|
|
for (int i = 0; i < m_SquadMembers.Count(); i++)
|
|
|
|
{
|
|
|
|
if (m_SquadMembers[i] != NULL && (vecLocation - m_SquadMembers[i]->GetAbsOrigin() ).Length2D() <= flDist)
|
|
|
|
return m_SquadMembers[i];
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
// Purpose: Returns the nearest squad member to the given squad member
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
CAI_BaseNPC *CAI_Squad::NearestSquadMember( CAI_BaseNPC *pMember )
|
|
|
|
{
|
|
|
|
float fBestDist = MAX_COORD_RANGE;
|
|
|
|
CAI_BaseNPC *fNearestEnt = NULL;
|
|
|
|
Vector fStartLoc = pMember->GetAbsOrigin();
|
|
|
|
for (int i = 0; i < m_SquadMembers.Count(); i++)
|
|
|
|
{
|
|
|
|
if (m_SquadMembers[i] != NULL)
|
|
|
|
{
|
|
|
|
float fDist = (fStartLoc - m_SquadMembers[i]->GetAbsOrigin()).Length();
|
|
|
|
if (m_SquadMembers[i] != pMember &&
|
|
|
|
fDist < fBestDist )
|
|
|
|
{
|
|
|
|
fBestDist = fDist;
|
|
|
|
fNearestEnt = m_SquadMembers[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fNearestEnt;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
// Purpose: Return the number of squad members visible to the specified member
|
|
|
|
//-------------------------------------
|
|
|
|
int CAI_Squad::GetVisibleSquadMembers( CAI_BaseNPC *pMember )
|
|
|
|
{
|
|
|
|
int iCount = 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < m_SquadMembers.Count(); i++)
|
|
|
|
{
|
|
|
|
// Make sure it's not the specified member
|
|
|
|
if ( m_SquadMembers[i] != NULL && pMember != m_SquadMembers[i] )
|
|
|
|
{
|
|
|
|
if ( pMember->FVisible( m_SquadMembers[i] ) )
|
|
|
|
{
|
|
|
|
iCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return iCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
//
|
|
|
|
//-------------------------------------
|
|
|
|
CAI_BaseNPC *CAI_Squad::GetSquadMemberNearestTo( const Vector &vecLocation )
|
|
|
|
{
|
|
|
|
CAI_BaseNPC *pNearest = NULL;
|
|
|
|
float flNearest = FLT_MAX;
|
|
|
|
|
|
|
|
for ( int i = 0; i < m_SquadMembers.Count(); i++ )
|
|
|
|
{
|
|
|
|
float flDist;
|
|
|
|
flDist = m_SquadMembers[i]->GetAbsOrigin().DistToSqr( vecLocation );
|
|
|
|
|
|
|
|
if( flDist < flNearest )
|
|
|
|
{
|
|
|
|
flNearest = flDist;
|
|
|
|
pNearest = m_SquadMembers[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Assert( pNearest != NULL );
|
|
|
|
return pNearest;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
// Purpose: Returns true if given entity is in the squad
|
|
|
|
//-------------------------------------
|
|
|
|
bool CAI_Squad::SquadIsMember( CBaseEntity *pMember )
|
|
|
|
{
|
|
|
|
CAI_BaseNPC *pNPC = pMember->MyNPCPointer();
|
|
|
|
if ( pNPC && pNPC->GetSquad() == this )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
bool CAI_Squad::IsLeader( CAI_BaseNPC *pNPC )
|
|
|
|
{
|
|
|
|
if ( IsSilentMember( pNPC ) )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if ( !pNPC )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if ( GetLeader() == pNPC )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
CAI_BaseNPC *CAI_Squad::GetLeader( void )
|
|
|
|
{
|
|
|
|
CAI_BaseNPC *pLeader = NULL;
|
|
|
|
int nSilentMembers = 0;
|
|
|
|
for ( int i = 0; i < m_SquadMembers.Count(); i++ )
|
|
|
|
{
|
|
|
|
if ( !IsSilentMember( m_SquadMembers[i] ) )
|
|
|
|
{
|
|
|
|
if ( !pLeader )
|
|
|
|
pLeader = m_SquadMembers[i];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
nSilentMembers++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ( m_SquadMembers.Count() - nSilentMembers > 1) ? pLeader : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CAI_BaseNPC *CAI_Squad::GetFirstMember( AISquadIter_t *pIter, bool bIgnoreSilentMembers )
|
|
|
|
{
|
2022-06-15 18:59:06 +00:00
|
|
|
intp i = 0;
|
2020-04-22 16:56:21 +00:00
|
|
|
if ( bIgnoreSilentMembers )
|
|
|
|
{
|
|
|
|
for ( ; i < m_SquadMembers.Count(); i++ )
|
|
|
|
{
|
|
|
|
if ( !IsSilentMember( m_SquadMembers[i] ) )
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( pIter )
|
|
|
|
*pIter = (AISquadIter_t)i;
|
|
|
|
if ( i >= m_SquadMembers.Count() )
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return m_SquadMembers[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
CAI_BaseNPC *CAI_Squad::GetNextMember( AISquadIter_t *pIter, bool bIgnoreSilentMembers )
|
|
|
|
{
|
|
|
|
int &i = (int &)*pIter;
|
|
|
|
i++;
|
|
|
|
if ( bIgnoreSilentMembers )
|
|
|
|
{
|
|
|
|
for ( ; i < m_SquadMembers.Count(); i++ )
|
|
|
|
{
|
|
|
|
if ( !IsSilentMember( m_SquadMembers[i] ) )
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( i >= m_SquadMembers.Count() )
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return m_SquadMembers[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
// Purpose: Alert everyone in the squad to the presence of a new enmey
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
int CAI_Squad::NumMembers( bool bIgnoreSilentMembers )
|
|
|
|
{
|
|
|
|
int nSilentMembers = 0;
|
|
|
|
if ( bIgnoreSilentMembers )
|
|
|
|
{
|
|
|
|
for ( int i = 0; i < m_SquadMembers.Count(); i++ )
|
|
|
|
{
|
|
|
|
if ( IsSilentMember( m_SquadMembers[i] ) )
|
|
|
|
nSilentMembers++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ( m_SquadMembers.Count() - nSilentMembers );
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
// Purpose: Alert everyone in the squad to the presence of a new enmey
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
void CAI_Squad::SquadNewEnemy( CBaseEntity *pEnemy )
|
|
|
|
{
|
|
|
|
if ( !pEnemy )
|
|
|
|
{
|
|
|
|
DevMsg( "ERROR: SquadNewEnemy() - pEnemy is NULL!\n" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < m_SquadMembers.Count(); i++)
|
|
|
|
{
|
|
|
|
CAI_BaseNPC *pMember = m_SquadMembers[i];
|
|
|
|
if (pMember)
|
|
|
|
{
|
|
|
|
// reset members who aren't activly engaged in fighting (only do this if the NPC's using the squad memory, or it'll fail)
|
|
|
|
if ( !pMember->GetEnemy() ||
|
|
|
|
( pMember->GetEnemy() != pEnemy &&
|
|
|
|
!pMember->HasCondition( COND_SEE_ENEMY) &&
|
|
|
|
gpGlobals->curtime - pMember->GetEnemyLastTimeSeen() > 3.0 ) )
|
|
|
|
{
|
|
|
|
// give them a new enemy
|
|
|
|
if( !hl2_episodic.GetBool() || pMember->IsValidEnemy(pEnemy) )
|
|
|
|
{
|
|
|
|
pMember->SetEnemy( pEnemy );
|
|
|
|
}
|
|
|
|
// pMember->SetLastAttackTime( 0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
// Purpose: Broadcast a message to all squad members
|
|
|
|
// Input: messageID - generic message handle
|
|
|
|
// data - generic data handle
|
|
|
|
// sender - who sent the message (NULL by default, if not, will not resend to the sender)
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
int CAI_Squad::BroadcastInteraction( int interactionType, void *data, CBaseCombatCharacter *sender )
|
|
|
|
{
|
|
|
|
//Must have a squad
|
|
|
|
if ( m_SquadMembers.Count() == 0 )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
//Broadcast to all members of the squad
|
|
|
|
for ( int i = 0; i < m_SquadMembers.Count(); i++ )
|
|
|
|
{
|
|
|
|
CAI_BaseNPC *pMember = m_SquadMembers[i]->MyNPCPointer();
|
|
|
|
|
|
|
|
//Validate and don't send again to the sender
|
|
|
|
if ( ( pMember != NULL) && ( pMember != sender ) )
|
|
|
|
{
|
|
|
|
//Send it
|
|
|
|
pMember->DispatchInteraction( interactionType, data, sender );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: is it ok to make a sound of the given priority? Check for conflicts
|
|
|
|
// Input : soundPriority -
|
|
|
|
// Output : Returns true on success, false on failure.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CAI_Squad::FOkToMakeSound( int soundPriority )
|
|
|
|
{
|
|
|
|
if (gpGlobals->curtime <= m_flSquadSoundWaitTime)
|
|
|
|
{
|
|
|
|
if ( soundPriority <= m_nSquadSoundPriority )
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: A squad member made an exclusive sound. Keep track so other squad
|
|
|
|
// members don't talk over it
|
|
|
|
// Input : soundPriority - for sorting
|
|
|
|
// time -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_Squad::JustMadeSound( int soundPriority, float time )
|
|
|
|
{
|
|
|
|
m_flSquadSoundWaitTime = time;
|
|
|
|
m_nSquadSoundPriority = soundPriority;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Try to get one of a contiguous range of slots
|
|
|
|
// Input : slotIDStart - start of slot range
|
|
|
|
// slotIDEnd - end of slot range
|
|
|
|
// hEnemy - enemy this slot is for
|
|
|
|
// Output : Returns true on success, false on failure.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CAI_Squad::OccupyStrategySlotRange( CBaseEntity *pEnemy, int slotIDStart, int slotIDEnd, int *pSlot )
|
|
|
|
{
|
|
|
|
#ifndef PER_ENEMY_SQUADSLOTS
|
|
|
|
// FIXME: combat slots need to be per enemy, not per squad.
|
|
|
|
// As it is, once a squad is occupied it stops making even simple attacks to other things nearby.
|
|
|
|
// This code may make soldiers too aggressive
|
|
|
|
if (GetLeader() && pEnemy != GetLeader()->GetEnemy())
|
|
|
|
{
|
|
|
|
*pSlot = SQUAD_SLOT_NONE;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// If I'm already occupying this slot
|
|
|
|
if ( *pSlot >= slotIDStart && *pSlot <= slotIDEnd)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
for ( int i = slotIDStart; i <= slotIDEnd; i++ )
|
|
|
|
{
|
|
|
|
// Check enemy to see if slot already occupied
|
|
|
|
if (!IsSlotOccupied(pEnemy, i))
|
|
|
|
{
|
|
|
|
// Clear any previous spot;
|
|
|
|
if (*pSlot != SQUAD_SLOT_NONE)
|
|
|
|
{
|
|
|
|
// As a debug measure check to see if slot was filled
|
|
|
|
if (!IsSlotOccupied(pEnemy, *pSlot))
|
|
|
|
{
|
|
|
|
DevMsg( "ERROR! Vacating an empty slot!\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Free the slot
|
|
|
|
VacateSlot(pEnemy, *pSlot);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fill the slot
|
|
|
|
OccupySlot(pEnemy, i);
|
|
|
|
*pSlot = i;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
bool CAI_Squad::IsStrategySlotRangeOccupied( CBaseEntity *pEnemy, int slotIDStart, int slotIDEnd )
|
|
|
|
{
|
|
|
|
for ( int i = slotIDStart; i <= slotIDEnd; i++ )
|
|
|
|
{
|
|
|
|
if (!IsSlotOccupied(pEnemy, i))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void CAI_Squad::VacateStrategySlot( CBaseEntity *pEnemy, int slot)
|
|
|
|
{
|
|
|
|
// If I wasn't taking up a squad slot I'm done
|
|
|
|
if (slot == SQUAD_SLOT_NONE)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// As a debug measure check to see if slot was filled
|
|
|
|
if (!IsSlotOccupied(pEnemy, slot))
|
|
|
|
{
|
|
|
|
DevMsg( "ERROR! Vacating an empty slot!\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Free the slot
|
|
|
|
VacateSlot(pEnemy, slot);
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void CAI_Squad::UpdateEnemyMemory( CAI_BaseNPC *pUpdater, CBaseEntity *pEnemy, const Vector &position )
|
|
|
|
{
|
|
|
|
//Broadcast to all members of the squad
|
|
|
|
for ( int i = 0; i < m_SquadMembers.Count(); i++ )
|
|
|
|
{
|
|
|
|
if ( m_SquadMembers[i] != pUpdater )
|
|
|
|
{
|
|
|
|
m_SquadMembers[i]->UpdateEnemyMemory( pEnemy, position, pUpdater );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#ifdef PER_ENEMY_SQUADSLOTS
|
|
|
|
|
|
|
|
AISquadEnemyInfo_t *CAI_Squad::FindEnemyInfo( CBaseEntity *pEnemy )
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
if ( gpGlobals->curtime > m_flEnemyInfoCleanupTime )
|
|
|
|
{
|
|
|
|
if ( m_EnemyInfos.Count() )
|
|
|
|
{
|
|
|
|
m_pLastFoundEnemyInfo = NULL;
|
|
|
|
CUtlRBTree<CBaseEntity *> activeEnemies;
|
|
|
|
SetDefLessFunc( activeEnemies );
|
|
|
|
|
|
|
|
// Gather up the set of active enemies
|
|
|
|
for ( i = 0; i < m_SquadMembers.Count(); i++ )
|
|
|
|
{
|
|
|
|
CBaseEntity *pMemberEnemy = m_SquadMembers[i]->GetEnemy();
|
|
|
|
if ( pMemberEnemy && activeEnemies.Find( pMemberEnemy ) == activeEnemies.InvalidIndex() )
|
|
|
|
{
|
|
|
|
activeEnemies.Insert( pMemberEnemy );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the records for deleted or unused enemies
|
|
|
|
for ( i = m_EnemyInfos.Count() - 1; i >= 0; --i )
|
|
|
|
{
|
|
|
|
if ( m_EnemyInfos[i].hEnemy == NULL || activeEnemies.Find( m_EnemyInfos[i].hEnemy ) == activeEnemies.InvalidIndex() )
|
|
|
|
{
|
|
|
|
m_EnemyInfos.FastRemove( i );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_flEnemyInfoCleanupTime = gpGlobals->curtime + 30;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( m_pLastFoundEnemyInfo && m_pLastFoundEnemyInfo->hEnemy == pEnemy )
|
|
|
|
return m_pLastFoundEnemyInfo;
|
|
|
|
|
|
|
|
for ( i = 0; i < m_EnemyInfos.Count(); i++ )
|
|
|
|
{
|
|
|
|
if ( m_EnemyInfos[i].hEnemy == pEnemy )
|
|
|
|
{
|
|
|
|
m_pLastFoundEnemyInfo = &m_EnemyInfos[i];
|
|
|
|
return &m_EnemyInfos[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_pLastFoundEnemyInfo = NULL;
|
|
|
|
i = m_EnemyInfos.AddToTail();
|
|
|
|
m_EnemyInfos[i].hEnemy = pEnemy;
|
|
|
|
|
|
|
|
m_pLastFoundEnemyInfo = &m_EnemyInfos[i];
|
|
|
|
return &m_EnemyInfos[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void CAI_Squad::OccupySlot( CBaseEntity *pEnemy, int i )
|
|
|
|
{
|
|
|
|
#ifdef PER_ENEMY_SQUADSLOTS
|
|
|
|
AISquadEnemyInfo_t *pInfo = FindEnemyInfo( pEnemy );
|
|
|
|
pInfo->slots.Set(i);
|
|
|
|
#else
|
|
|
|
m_squadSlotsUsed.Set(i);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void CAI_Squad::VacateSlot( CBaseEntity *pEnemy, int i )
|
|
|
|
{
|
|
|
|
#ifdef PER_ENEMY_SQUADSLOTS
|
|
|
|
AISquadEnemyInfo_t *pInfo = FindEnemyInfo( pEnemy );
|
|
|
|
pInfo->slots.Clear(i);
|
|
|
|
#else
|
|
|
|
m_squadSlotsUsed.Clear(i);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
bool CAI_Squad::IsSlotOccupied( CBaseEntity *pEnemy, int i ) const
|
|
|
|
{
|
|
|
|
#ifdef PER_ENEMY_SQUADSLOTS
|
|
|
|
const AISquadEnemyInfo_t *pInfo = FindEnemyInfo( pEnemy );
|
|
|
|
return pInfo->slots.IsBitSet(i);
|
|
|
|
#else
|
|
|
|
return m_squadSlotsUsed.IsBitSet(i);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void CAI_Squad::SquadRemember( int iMemory )
|
|
|
|
{
|
|
|
|
for (int i = 0; i < m_SquadMembers.Count(); i++)
|
|
|
|
{
|
|
|
|
if (m_SquadMembers[i] != NULL )
|
|
|
|
{
|
|
|
|
m_SquadMembers[i]->Remember( iMemory );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void CAI_Squad::SetSquadInflictor( CBaseEntity *pInflictor )
|
|
|
|
{
|
|
|
|
m_hSquadInflictor.Set(pInflictor);
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
bool CAI_Squad::IsSquadInflictor( CBaseEntity *pInflictor )
|
|
|
|
{
|
|
|
|
return (m_hSquadInflictor.Get() == pInflictor);
|
|
|
|
}
|
|
|
|
|
|
|
|
//=============================================================================
|
|
|
|
|