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.
518 lines
16 KiB
518 lines
16 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Entity which alters the relationships between entities via entity I/O |
|
// |
|
//=====================================================================================// |
|
|
|
#include "cbase.h" |
|
#include "ndebugoverlay.h" |
|
#include "ai_basenpc.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
#define SF_RELATIONSHIP_NOTIFY_SUBJECT (1<<0) // Alert the subject of the change and give them a memory of the target entity |
|
#define SF_RELATIONSHIP_NOTIFY_TARGET (1<<1) // Alert the target of the change and give them a memory of the subject entity |
|
|
|
enum |
|
{ |
|
NOT_REVERTING, |
|
REVERTING_TO_PREV, |
|
REVERTING_TO_DEFAULT, |
|
}; |
|
|
|
//========================================================= |
|
//========================================================= |
|
class CAI_Relationship : public CBaseEntity, public IEntityListener |
|
{ |
|
DECLARE_CLASS( CAI_Relationship, CBaseEntity ); |
|
|
|
public: |
|
CAI_Relationship() : m_iPreviousDisposition( -1 ) { } |
|
|
|
void Spawn(); |
|
void Activate(); |
|
|
|
void SetActive( bool bActive ); |
|
void ChangeRelationships( int disposition, int iReverting, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); |
|
|
|
void ApplyRelationship( CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); |
|
void RevertRelationship( CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); |
|
void RevertToDefaultRelationship( CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); |
|
|
|
void UpdateOnRemove(); |
|
void OnRestore(); |
|
|
|
bool IsASubject( CBaseEntity *pEntity ); |
|
bool IsATarget( CBaseEntity *pEntity ); |
|
|
|
void OnEntitySpawned( CBaseEntity *pEntity ); |
|
void OnEntityDeleted( CBaseEntity *pEntity ); |
|
|
|
private: |
|
|
|
void ApplyRelationshipThink( void ); |
|
CBaseEntity *FindEntityForProceduralName( string_t iszName, CBaseEntity *pActivator, CBaseEntity *pCaller ); |
|
void DiscloseNPCLocation( CBaseCombatCharacter *pSubject, CBaseCombatCharacter *pTarget ); |
|
|
|
string_t m_iszSubject; |
|
string_t m_iszSubjectClass; |
|
string_t m_iszTargetClass; |
|
int m_iDisposition; |
|
int m_iRank; |
|
bool m_fStartActive; |
|
bool m_bIsActive; |
|
int m_iPreviousDisposition; |
|
float m_flRadius; |
|
int m_iPreviousRank; |
|
bool m_bReciprocal; |
|
|
|
public: |
|
// Input functions |
|
void InputApplyRelationship( inputdata_t &inputdata ); |
|
void InputRevertRelationship( inputdata_t &inputdata ); |
|
void InputRevertToDefaultRelationship( inputdata_t &inputdata ); |
|
|
|
DECLARE_DATADESC(); |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( ai_relationship, CAI_Relationship ); |
|
|
|
BEGIN_DATADESC( CAI_Relationship ) |
|
DEFINE_THINKFUNC( ApplyRelationshipThink ), |
|
|
|
DEFINE_KEYFIELD( m_iszSubject, FIELD_STRING, "subject" ), |
|
DEFINE_KEYFIELD( m_iszSubjectClass, FIELD_STRING, "subjectclass" ), |
|
DEFINE_KEYFIELD( m_iszTargetClass, FIELD_STRING, "targetclass" ), |
|
DEFINE_KEYFIELD( m_iDisposition, FIELD_INTEGER, "disposition" ), |
|
DEFINE_KEYFIELD( m_iRank, FIELD_INTEGER, "rank" ), |
|
DEFINE_KEYFIELD( m_fStartActive, FIELD_BOOLEAN, "StartActive" ), |
|
DEFINE_FIELD( m_bIsActive, FIELD_BOOLEAN ), |
|
DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ), |
|
DEFINE_FIELD( m_iPreviousDisposition, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iPreviousRank, FIELD_INTEGER ), |
|
DEFINE_KEYFIELD( m_bReciprocal, FIELD_BOOLEAN, "reciprocal" ), |
|
|
|
// Inputs |
|
DEFINE_INPUTFUNC( FIELD_VOID, "ApplyRelationship", InputApplyRelationship ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "RevertRelationship", InputRevertRelationship ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "RevertToDefaultRelationship", InputRevertToDefaultRelationship ), |
|
END_DATADESC() |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAI_Relationship::Spawn() |
|
{ |
|
m_bIsActive = false; |
|
|
|
if ( m_iszSubject == NULL_STRING && m_iszSubjectClass == NULL_STRING ) |
|
{ |
|
DevWarning("ai_relationship '%s' with no subject specified, removing.\n", GetDebugName()); |
|
UTIL_Remove(this); |
|
} |
|
else if ( m_target == NULL_STRING && m_iszTargetClass == NULL_STRING ) |
|
{ |
|
DevWarning("ai_relationship '%s' with no target specified, removing.\n", GetDebugName()); |
|
UTIL_Remove(this); |
|
} |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CAI_Relationship::Activate() |
|
{ |
|
if ( m_fStartActive ) |
|
{ |
|
ApplyRelationship(); |
|
|
|
// Clear this flag so that nothing happens when the level is loaded (which calls activate again) |
|
m_fStartActive = false; |
|
} |
|
|
|
BaseClass::Activate(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : bActive - |
|
//----------------------------------------------------------------------------- |
|
void CAI_Relationship::SetActive( bool bActive ) |
|
{ |
|
if ( bActive && !m_bIsActive ) |
|
{ |
|
// Start getting entity updates! |
|
gEntList.AddListenerEntity( this ); |
|
} |
|
else if ( !bActive && m_bIsActive ) |
|
{ |
|
// Stop getting entity updates! |
|
gEntList.RemoveListenerEntity( this ); |
|
} |
|
|
|
m_bIsActive = bActive; |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CAI_Relationship::InputApplyRelationship( inputdata_t &inputdata ) |
|
{ |
|
ApplyRelationship( inputdata.pActivator, inputdata.pCaller ); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CAI_Relationship::InputRevertRelationship( inputdata_t &inputdata ) |
|
{ |
|
RevertRelationship( inputdata.pActivator, inputdata.pCaller ); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CAI_Relationship::InputRevertToDefaultRelationship( inputdata_t &inputdata ) |
|
{ |
|
RevertToDefaultRelationship( inputdata.pActivator, inputdata.pCaller ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This think function is used to wait until the player has properly |
|
// spawned, after all the NPCs have spawned. Once that occurs, this |
|
// function terminates. |
|
//----------------------------------------------------------------------------- |
|
void CAI_Relationship::ApplyRelationshipThink( void ) |
|
{ |
|
// Call down to the base until the player has properly spawned |
|
ApplyRelationship(); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
// Purpose: Applies the desired relationships to an entity |
|
//--------------------------------------------------------- |
|
void CAI_Relationship::ApplyRelationship( CBaseEntity *pActivator, CBaseEntity *pCaller ) |
|
{ |
|
// @TODO (toml 10-22-04): sort out MP relationships |
|
|
|
// The player spawns slightly after the NPCs, meaning that if we don't wait, the |
|
// player will miss any relationships placed on them. |
|
if ( AI_IsSinglePlayer() && !UTIL_GetLocalPlayer() ) |
|
{ |
|
SetThink( &CAI_Relationship::ApplyRelationshipThink ); |
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
|
|
if ( !m_bIsActive ) |
|
{ |
|
SetActive( true ); |
|
} |
|
|
|
ChangeRelationships( m_iDisposition, NOT_REVERTING, pActivator, pCaller ); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CAI_Relationship::RevertRelationship( CBaseEntity *pActivator, CBaseEntity *pCaller ) |
|
{ |
|
if ( m_bIsActive ) |
|
{ |
|
ChangeRelationships( m_iPreviousDisposition, REVERTING_TO_PREV, pActivator, pCaller ); |
|
SetActive( false ); |
|
} |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CAI_Relationship::RevertToDefaultRelationship( CBaseEntity *pActivator, CBaseEntity *pCaller ) |
|
{ |
|
if ( m_bIsActive ) |
|
{ |
|
ChangeRelationships( -1, REVERTING_TO_DEFAULT, pActivator, pCaller ); |
|
SetActive( false ); |
|
} |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CAI_Relationship::UpdateOnRemove() |
|
{ |
|
gEntList.RemoveListenerEntity( this ); |
|
// @TODO (toml 07-21-04): Should this actually revert on kill? |
|
// RevertRelationship(); |
|
BaseClass::UpdateOnRemove(); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CAI_Relationship::OnRestore() |
|
{ |
|
BaseClass::OnRestore(); |
|
if ( m_bIsActive ) |
|
{ |
|
gEntList.AddListenerEntity( this ); |
|
} |
|
} |
|
|
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
bool CAI_Relationship::IsASubject( CBaseEntity *pEntity ) |
|
{ |
|
if ( m_iszSubject != NULL_STRING ) |
|
{ |
|
if ( pEntity->NameMatches( m_iszSubject ) ) |
|
return true; |
|
|
|
if ( pEntity->ClassMatches( m_iszSubject ) ) |
|
return true; |
|
} |
|
|
|
if ( m_iszSubjectClass != NULL_STRING && pEntity->ClassMatches( m_iszSubjectClass ) ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
bool CAI_Relationship::IsATarget( CBaseEntity *pEntity ) |
|
{ |
|
if ( m_target != NULL_STRING ) |
|
{ |
|
if ( pEntity->NameMatches( m_target ) ) |
|
return true; |
|
|
|
if ( pEntity->ClassMatches( m_target ) ) |
|
return true; |
|
} |
|
|
|
if ( m_iszTargetClass != NULL_STRING && pEntity->ClassMatches( m_iszTargetClass ) ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CAI_Relationship::OnEntitySpawned( CBaseEntity *pEntity ) |
|
{ |
|
// NOTE: This cannot use the procedural entity finding code since that only occurs on |
|
// inputs and not passively. |
|
|
|
if ( IsATarget( pEntity ) || IsASubject( pEntity ) ) |
|
{ |
|
ApplyRelationship(); |
|
} |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CAI_Relationship::OnEntityDeleted( CBaseEntity *pEntity ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Translate special tokens for inputs |
|
// Input : iszName - Name to check |
|
// *pActivator - Activator |
|
// *pCaller - Caller |
|
// Output : CBaseEntity - Entity that matches (NULL if none) |
|
//----------------------------------------------------------------------------- |
|
#define ACTIVATOR_KEYNAME "!activator" |
|
#define CALLER_KEYNAME "!caller" |
|
|
|
CBaseEntity *CAI_Relationship::FindEntityForProceduralName( string_t iszName, CBaseEntity *pActivator, CBaseEntity *pCaller ) |
|
{ |
|
// Handle the activator token |
|
if ( iszName == AllocPooledString( ACTIVATOR_KEYNAME ) ) |
|
return pActivator; |
|
|
|
// Handle the caller token |
|
if ( iszName == AllocPooledString( CALLER_KEYNAME ) ) |
|
return pCaller; |
|
|
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Disclose the location of the target entity to the subject via a memory |
|
// Input : *pSubject - Entity to gain the memory of the target's location |
|
// *pTarget - Entity who's location will be disclosed |
|
//----------------------------------------------------------------------------- |
|
void CAI_Relationship::DiscloseNPCLocation( CBaseCombatCharacter *pSubject, CBaseCombatCharacter *pTarget ) |
|
{ |
|
if ( pSubject == NULL || pTarget == NULL ) |
|
return; |
|
|
|
CAI_BaseNPC *pNPC = pSubject->MyNPCPointer(); |
|
if ( pNPC != NULL ) |
|
{ |
|
pNPC->UpdateEnemyMemory( pTarget, pTarget->GetAbsOrigin() ); |
|
} |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CAI_Relationship::ChangeRelationships( int disposition, int iReverting, CBaseEntity *pActivator, CBaseEntity *pCaller ) |
|
{ |
|
if( iReverting != NOT_REVERTING && m_iPreviousDisposition == -1 ) |
|
{ |
|
// Trying to revert without having ever set the relationships! |
|
DevMsg( 2, "ai_relationship cannot revert changes before they are applied!\n"); |
|
return; |
|
} |
|
|
|
const int MAX_HANDLED = 512; |
|
CUtlVectorFixed<CBaseCombatCharacter *, MAX_HANDLED> subjectList; |
|
CUtlVectorFixed<CBaseCombatCharacter *, MAX_HANDLED> targetList; |
|
|
|
// Add any special subjects we found |
|
CBaseEntity *pSpecialSubject = FindEntityForProceduralName( m_iszSubject, pActivator, pCaller ); |
|
if ( pSpecialSubject && pSpecialSubject->MyCombatCharacterPointer() ) |
|
{ |
|
subjectList.AddToTail( pSpecialSubject->MyCombatCharacterPointer() ); |
|
} |
|
|
|
// Add any special targets we found |
|
CBaseEntity *pSpecialTarget = FindEntityForProceduralName( m_target, pActivator, pCaller ); |
|
if ( pSpecialTarget && pSpecialTarget->MyCombatCharacterPointer() ) |
|
{ |
|
targetList.AddToTail( pSpecialTarget->MyCombatCharacterPointer() ); |
|
} |
|
|
|
// ------------------------------- |
|
// Search for targets and subjects |
|
// ------------------------------- |
|
|
|
float radiusSq = Square( m_flRadius ); |
|
|
|
// Search players first |
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
if ( subjectList.Count() == MAX_HANDLED || targetList.Count() == MAX_HANDLED ) |
|
{ |
|
DevMsg( "Too many entities handled by ai_relationship %s\n", GetDebugName() ); |
|
break; |
|
} |
|
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); |
|
if ( pPlayer ) |
|
{ |
|
if( IsASubject( pPlayer ) ) |
|
{ |
|
if ( m_flRadius == 0.0 || GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() ) <= radiusSq ) |
|
subjectList.AddToTail( pPlayer ); |
|
} |
|
else if( IsATarget( pPlayer ) ) |
|
{ |
|
targetList.AddToTail( pPlayer ); |
|
} |
|
} |
|
} |
|
|
|
// Search NPCs |
|
for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) |
|
{ |
|
if ( subjectList.Count() == MAX_HANDLED || targetList.Count() == MAX_HANDLED ) |
|
{ |
|
DevMsg( "Too many entities handled by ai_relationship %s\n", GetDebugName() ); |
|
break; |
|
} |
|
|
|
CAI_BaseNPC *pNPC = (g_AI_Manager.AccessAIs())[i]; |
|
if ( pNPC ) |
|
{ |
|
if( IsASubject( pNPC ) ) |
|
{ |
|
if ( m_flRadius == 0.0 || GetAbsOrigin().DistToSqr( pNPC->GetAbsOrigin() ) <= radiusSq ) |
|
subjectList.AddToTail( pNPC ); |
|
} |
|
else if( IsATarget( pNPC ) ) |
|
{ |
|
targetList.AddToTail( pNPC ); |
|
} |
|
} |
|
} |
|
|
|
// If either list is still empty, we have a problem. |
|
if( subjectList.Count() == 0 ) |
|
{ |
|
DevMsg( 2, "ai_relationship '%s' finds no subject(s) called: %s or with class %s\n", |
|
GetDebugName(), |
|
m_iszSubject == NULL_STRING ? "NULL" : STRING( m_iszSubject ), |
|
m_iszSubjectClass == NULL_STRING ? "NULL" : STRING( m_iszSubjectClass ) ); |
|
return; |
|
} |
|
else if ( targetList.Count() == 0 ) |
|
{ |
|
DevMsg( 2, "ai_relationship '%s' finds no target(s) called: %s or with class %s\n", |
|
GetDebugName(), |
|
m_target == NULL_STRING ? "NULL" : STRING( m_target ), |
|
m_iszTargetClass == NULL_STRING ? "NULL" : STRING( m_iszTargetClass ) ); |
|
return; |
|
} |
|
|
|
// Ok, lists are populated. Apply all relationships. |
|
for ( int i = 0 ; i < subjectList.Count(); i++ ) |
|
{ |
|
CBaseCombatCharacter *pSubject = subjectList[ i ]; |
|
|
|
for ( int j = 0 ; j < targetList.Count(); j++ ) |
|
{ |
|
CBaseCombatCharacter *pTarget = targetList[ j ]; |
|
|
|
if ( m_iPreviousDisposition == -1 && iReverting == NOT_REVERTING ) |
|
{ |
|
// Set previous disposition. |
|
m_iPreviousDisposition = pSubject->IRelationType( pTarget ); |
|
m_iPreviousRank = pSubject->IRelationPriority( pTarget ); |
|
} |
|
|
|
if ( iReverting == REVERTING_TO_PREV ) |
|
{ |
|
pSubject->AddEntityRelationship( pTarget, (Disposition_t)m_iPreviousDisposition, m_iPreviousRank ); |
|
|
|
if( m_bReciprocal ) |
|
{ |
|
pTarget->AddEntityRelationship( pSubject, (Disposition_t)m_iPreviousDisposition, m_iPreviousRank ); |
|
} |
|
} |
|
else if ( iReverting == REVERTING_TO_DEFAULT ) |
|
{ |
|
pSubject->RemoveEntityRelationship( pTarget ); |
|
|
|
if( m_bReciprocal ) |
|
{ |
|
pTarget->RemoveEntityRelationship( pSubject ); |
|
} |
|
} |
|
else if( pSubject->IRelationType(pTarget) != disposition || |
|
pSubject->IRelationPriority(pTarget) != m_iRank || |
|
HasSpawnFlags( SF_RELATIONSHIP_NOTIFY_SUBJECT ) || |
|
HasSpawnFlags( SF_RELATIONSHIP_NOTIFY_TARGET ) ) |
|
{ |
|
// Apply the relationship to the subject |
|
pSubject->AddEntityRelationship( pTarget, (Disposition_t)disposition, m_iRank ); |
|
|
|
// Make the subject aware of the target |
|
if ( HasSpawnFlags( SF_RELATIONSHIP_NOTIFY_SUBJECT ) ) |
|
{ |
|
DiscloseNPCLocation( pSubject, pTarget ); |
|
} |
|
|
|
// Make the target aware of the subject |
|
if ( HasSpawnFlags( SF_RELATIONSHIP_NOTIFY_TARGET ) ) |
|
{ |
|
DiscloseNPCLocation( pTarget, pSubject ); |
|
} |
|
|
|
// This relationship is applied to target and subject alike |
|
if ( m_bReciprocal ) |
|
{ |
|
// Apply the relationship to the target |
|
pTarget->AddEntityRelationship( pSubject, (Disposition_t)disposition, m_iRank ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
|