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.
1753 lines
49 KiB
1753 lines
49 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "npc_antlion.h" |
|
#include "antlion_maker.h" |
|
#include "saverestore_utlvector.h" |
|
#include "mapentities.h" |
|
#include "decals.h" |
|
#include "iservervehicle.h" |
|
#include "antlion_dust.h" |
|
#include "smoke_trail.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
CAntlionMakerManager g_AntlionMakerManager( "CAntlionMakerManager" ); |
|
|
|
static const char *s_pPoolThinkContext = "PoolThinkContext"; |
|
static const char *s_pBlockedEffectsThinkContext = "BlockedEffectsThinkContext"; |
|
static const char *s_pBlockedCheckContext = "BlockedCheckContext"; |
|
|
|
ConVar g_debug_antlionmaker( "g_debug_antlionmaker", "0", FCVAR_CHEAT ); |
|
|
|
|
|
#define ANTLION_MAKER_PLAYER_DETECT_RADIUS 512 |
|
#define ANTLION_MAKER_BLOCKED_MASS 250.0f // half the weight of a car |
|
#define ANTLION_MAKE_SPORE_SPAWNRATE 25.0f |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &vFightGoal - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionMakerManager::BroadcastFightGoal( const Vector &vFightGoal ) |
|
{ |
|
CAntlionTemplateMaker *pMaker; |
|
|
|
for ( int i=0; i < m_Makers.Count(); i++ ) |
|
{ |
|
pMaker = m_Makers[i]; |
|
|
|
if ( pMaker && pMaker->ShouldHearBugbait() ) |
|
{ |
|
pMaker->SetFightTarget( vFightGoal ); |
|
pMaker->SetChildMoveState( ANTLION_MOVE_FIGHT_TO_GOAL ); |
|
pMaker->UpdateChildren(); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pFightGoal - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionMakerManager::BroadcastFightGoal( CBaseEntity *pFightGoal ) |
|
{ |
|
CAntlionTemplateMaker *pMaker; |
|
|
|
for ( int i=0; i < m_Makers.Count(); i++ ) |
|
{ |
|
pMaker = m_Makers[i]; |
|
|
|
if ( pMaker && pMaker->ShouldHearBugbait() ) |
|
{ |
|
pMaker->SetFightTarget( pFightGoal ); |
|
pMaker->SetChildMoveState( ANTLION_MOVE_FIGHT_TO_GOAL ); |
|
pMaker->UpdateChildren(); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pFightGoal - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionMakerManager::BroadcastFollowGoal( CBaseEntity *pFollowGoal ) |
|
{ |
|
CAntlionTemplateMaker *pMaker; |
|
|
|
for ( int i=0; i < m_Makers.Count(); i++ ) |
|
{ |
|
pMaker = m_Makers[i]; |
|
|
|
if ( pMaker && pMaker->ShouldHearBugbait() ) |
|
{ |
|
//pMaker->SetFightTarget( NULL ); |
|
pMaker->SetFollowTarget( pFollowGoal ); |
|
pMaker->SetChildMoveState( ANTLION_MOVE_FOLLOW ); |
|
pMaker->UpdateChildren(); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionMakerManager::GatherMakers( void ) |
|
{ |
|
CBaseEntity *pSearch = NULL; |
|
CAntlionTemplateMaker *pMaker; |
|
|
|
m_Makers.Purge(); |
|
|
|
// Find these all once |
|
while ( ( pSearch = gEntList.FindEntityByClassname( pSearch, "npc_antlion_template_maker" ) ) != NULL ) |
|
{ |
|
pMaker = static_cast<CAntlionTemplateMaker *>(pSearch); |
|
|
|
m_Makers.AddToTail( pMaker ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionMakerManager::LevelInitPostEntity( void ) |
|
{ |
|
//Find all antlion makers |
|
GatherMakers(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Antlion template maker |
|
//----------------------------------------------------------------------------- |
|
|
|
LINK_ENTITY_TO_CLASS( npc_antlion_template_maker, CAntlionTemplateMaker ); |
|
|
|
//DT Definition |
|
BEGIN_DATADESC( CAntlionTemplateMaker ) |
|
|
|
DEFINE_KEYFIELD( m_strSpawnGroup, FIELD_STRING, "spawngroup" ), |
|
DEFINE_KEYFIELD( m_strSpawnTarget, FIELD_STRING, "spawntarget" ), |
|
DEFINE_KEYFIELD( m_flSpawnRadius, FIELD_FLOAT, "spawnradius" ), |
|
DEFINE_KEYFIELD( m_strFightTarget, FIELD_STRING, "fighttarget" ), |
|
DEFINE_KEYFIELD( m_strFollowTarget, FIELD_STRING, "followtarget" ), |
|
DEFINE_KEYFIELD( m_bIgnoreBugbait, FIELD_BOOLEAN, "ignorebugbait" ), |
|
DEFINE_KEYFIELD( m_flVehicleSpawnDistance, FIELD_FLOAT, "vehicledistance" ), |
|
DEFINE_KEYFIELD( m_flWorkerSpawnRate, FIELD_FLOAT, "workerspawnrate" ), |
|
|
|
DEFINE_FIELD( m_nChildMoveState, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_hFightTarget, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hProxyTarget, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hFollowTarget, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_iSkinCount, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_flBlockedBumpTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_bBlocked, FIELD_BOOLEAN ), |
|
|
|
DEFINE_UTLVECTOR( m_Children, FIELD_EHANDLE ), |
|
|
|
DEFINE_KEYFIELD( m_iPool, FIELD_INTEGER, "pool_start" ), |
|
DEFINE_KEYFIELD( m_iMaxPool, FIELD_INTEGER, "pool_max" ), |
|
DEFINE_KEYFIELD( m_iPoolRegenAmount,FIELD_INTEGER, "pool_regen_amount" ), |
|
DEFINE_KEYFIELD( m_flPoolRegenTime, FIELD_FLOAT, "pool_regen_time" ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetFightTarget", InputSetFightTarget ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetFollowTarget", InputSetFollowTarget ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "ClearFollowTarget", InputClearFollowTarget ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "ClearFightTarget", InputClearFightTarget ), |
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpawnRadius", InputSetSpawnRadius ), |
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "AddToPool", InputAddToPool ), |
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxPool", InputSetMaxPool ), |
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetPoolRegenAmount", InputSetPoolRegenAmount ), |
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPoolRegenTime", InputSetPoolRegenTime ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "ChangeDestinationGroup", InputChangeDestinationGroup ), |
|
DEFINE_OUTPUT( m_OnAllBlocked, "OnAllBlocked" ), |
|
|
|
DEFINE_KEYFIELD( m_bCreateSpores, FIELD_BOOLEAN, "createspores" ), |
|
|
|
DEFINE_THINKFUNC( PoolRegenThink ), |
|
DEFINE_THINKFUNC( FindNodesCloseToPlayer ), |
|
DEFINE_THINKFUNC( BlockedCheckFunc ), |
|
|
|
END_DATADESC() |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CAntlionTemplateMaker::CAntlionTemplateMaker( void ) |
|
{ |
|
m_hFightTarget = NULL; |
|
m_hProxyTarget = NULL; |
|
m_hFollowTarget = NULL; |
|
m_nChildMoveState = ANTLION_MOVE_FREE; |
|
m_iSkinCount = 0; |
|
m_flBlockedBumpTime = 0.0f; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CAntlionTemplateMaker::~CAntlionTemplateMaker( void ) |
|
{ |
|
DestroyProxyTarget(); |
|
m_Children.Purge(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pAnt - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::AddChild( CNPC_Antlion *pAnt ) |
|
{ |
|
m_Children.AddToTail( pAnt ); |
|
m_nLiveChildren = m_Children.Count(); |
|
|
|
pAnt->SetOwnerEntity( this ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pAnt - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::RemoveChild( CNPC_Antlion *pAnt ) |
|
{ |
|
m_Children.FindAndRemove( pAnt ); |
|
m_nLiveChildren = m_Children.Count(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::FixupOrphans( void ) |
|
{ |
|
CBaseEntity *pSearch = NULL; |
|
CNPC_Antlion *pAntlion = NULL; |
|
|
|
// Iterate through all antlions and see if there are any orphans |
|
while ( ( pSearch = gEntList.FindEntityByClassname( pSearch, "npc_antlion" ) ) != NULL ) |
|
{ |
|
pAntlion = dynamic_cast<CNPC_Antlion *>(pSearch); |
|
|
|
// See if it's a live orphan |
|
if ( pAntlion && pAntlion->GetOwnerEntity() == NULL && pAntlion->IsAlive() ) |
|
{ |
|
// See if its parent was named the same as we are |
|
if ( stricmp( pAntlion->GetParentSpawnerName(), STRING( GetEntityName() ) ) == 0 ) |
|
{ |
|
// Relink us to this antlion, he's come through a transition and was orphaned |
|
AddChild( pAntlion ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::PrecacheTemplateEntity( CBaseEntity *pEntity ) |
|
{ |
|
BaseClass::PrecacheTemplateEntity( pEntity ); |
|
|
|
// If we can spawn workers, precache the worker as well. |
|
if ( m_flWorkerSpawnRate != 0 ) |
|
{ |
|
pEntity->AddSpawnFlags( SF_ANTLION_WORKER ); |
|
pEntity->Precache(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::Activate( void ) |
|
{ |
|
FixupOrphans(); |
|
|
|
BaseClass::Activate(); |
|
|
|
// Are we using the pool behavior for coast? |
|
if ( m_iMaxPool ) |
|
{ |
|
if ( !m_flPoolRegenTime ) |
|
{ |
|
Msg("%s using pool behavior without a specified pool regen time.\n", GetClassname() ); |
|
m_flPoolRegenTime = 0.1; |
|
} |
|
|
|
// Start up our think cycle unless we're reloading this map (which would reset it) |
|
if ( m_bDisabled == false && gpGlobals->eLoadType != MapLoad_LoadGame ) |
|
{ |
|
// Start our pool regeneration cycle |
|
SetContextThink( &CAntlionTemplateMaker::PoolRegenThink, gpGlobals->curtime + m_flPoolRegenTime, s_pPoolThinkContext ); |
|
|
|
// Start our blocked effects cycle |
|
if ( hl2_episodic.GetBool() == true && HasSpawnFlags( SF_ANTLIONMAKER_DO_BLOCKEDEFFECTS ) ) |
|
{ |
|
SetContextThink( &CAntlionTemplateMaker::FindNodesCloseToPlayer, gpGlobals->curtime + 1.0f, s_pBlockedEffectsThinkContext ); |
|
} |
|
} |
|
} |
|
|
|
ActivateAllSpores(); |
|
} |
|
|
|
void CAntlionTemplateMaker::ActivateSpore( const char* sporename, Vector vOrigin ) |
|
{ |
|
if ( m_bCreateSpores == false ) |
|
return; |
|
|
|
char szName[64]; |
|
Q_snprintf( szName, sizeof( szName ), "%s_spore", sporename ); |
|
|
|
SporeExplosion *pSpore = (SporeExplosion*)gEntList.FindEntityByName( NULL, szName ); |
|
|
|
//One already exists... |
|
if ( pSpore ) |
|
{ |
|
if ( pSpore->m_bDisabled == true ) |
|
{ |
|
inputdata_t inputdata; |
|
pSpore->InputEnable( inputdata ); |
|
} |
|
|
|
return; |
|
} |
|
|
|
CBaseEntity *pEnt = CreateEntityByName( "env_sporeexplosion" ); |
|
|
|
if ( pEnt ) |
|
{ |
|
pSpore = dynamic_cast<SporeExplosion*>(pEnt); |
|
|
|
if ( pSpore ) |
|
{ |
|
pSpore->SetAbsOrigin( vOrigin ); |
|
pSpore->SetName( AllocPooledString( szName ) ); |
|
pSpore->m_flSpawnRate = ANTLION_MAKE_SPORE_SPAWNRATE; |
|
} |
|
} |
|
} |
|
|
|
void CAntlionTemplateMaker::DisableSpore( const char* sporename ) |
|
{ |
|
if ( m_bCreateSpores == false ) |
|
return; |
|
|
|
char szName[64]; |
|
Q_snprintf( szName, sizeof( szName ), "%s_spore", sporename ); |
|
|
|
SporeExplosion *pSpore = (SporeExplosion*)gEntList.FindEntityByName( NULL, szName ); |
|
|
|
if ( pSpore && pSpore->m_bDisabled == false ) |
|
{ |
|
inputdata_t inputdata; |
|
pSpore->InputDisable( inputdata ); |
|
return; |
|
} |
|
} |
|
|
|
void CAntlionTemplateMaker::ActivateAllSpores( void ) |
|
{ |
|
if ( m_bDisabled == true ) |
|
return; |
|
|
|
if ( m_bCreateSpores == false ) |
|
return; |
|
|
|
CHintCriteria hintCriteria; |
|
|
|
hintCriteria.SetGroup( m_strSpawnGroup ); |
|
hintCriteria.SetHintType( HINT_ANTLION_BURROW_POINT ); |
|
|
|
CUtlVector<CAI_Hint *> hintList; |
|
CAI_HintManager::FindAllHints( vec3_origin, hintCriteria, &hintList ); |
|
|
|
for ( int i = 0; i < hintList.Count(); i++ ) |
|
{ |
|
CAI_Hint *pTestHint = hintList[i]; |
|
|
|
if ( pTestHint ) |
|
{ |
|
bool bBlank; |
|
if ( !AllHintsFromClusterBlocked( pTestHint, bBlank ) ) |
|
{ |
|
ActivateSpore( STRING( pTestHint->GetEntityName() ), pTestHint->GetAbsOrigin() ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CAntlionTemplateMaker::DisableAllSpores( void ) |
|
{ |
|
CHintCriteria hintCriteria; |
|
|
|
hintCriteria.SetGroup( m_strSpawnGroup ); |
|
hintCriteria.SetHintType( HINT_ANTLION_BURROW_POINT ); |
|
|
|
CUtlVector<CAI_Hint *> hintList; |
|
CAI_HintManager::FindAllHints( vec3_origin, hintCriteria, &hintList ); |
|
|
|
for ( int i = 0; i < hintList.Count(); i++ ) |
|
{ |
|
CAI_Hint *pTestHint = hintList[i]; |
|
|
|
if ( pTestHint ) |
|
{ |
|
DisableSpore( STRING( pTestHint->GetEntityName() ) ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : CBaseEntity |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CAntlionTemplateMaker::GetFightTarget( void ) |
|
{ |
|
if ( m_hFightTarget != NULL ) |
|
return m_hFightTarget; |
|
|
|
return m_hProxyTarget; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : CBaseEntity |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CAntlionTemplateMaker::GetFollowTarget( void ) |
|
{ |
|
return m_hFollowTarget; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::UpdateChildren( void ) |
|
{ |
|
//Update all children |
|
CNPC_Antlion *pAntlion = NULL; |
|
|
|
// Move through our child list |
|
int i=0; |
|
for ( ; i < m_Children.Count(); i++ ) |
|
{ |
|
pAntlion = m_Children[i]; |
|
|
|
//HACKHACK |
|
//Let's just fix this up. |
|
//This guy might have been killed in another level and we just came back. |
|
if ( pAntlion == NULL ) |
|
{ |
|
m_Children.Remove( i ); |
|
i--; |
|
continue; |
|
} |
|
|
|
if ( pAntlion->m_lifeState != LIFE_ALIVE ) |
|
continue; |
|
|
|
pAntlion->SetFightTarget( GetFightTarget() ); |
|
pAntlion->SetFollowTarget( GetFollowTarget() ); |
|
pAntlion->SetMoveState( m_nChildMoveState ); |
|
} |
|
|
|
m_nLiveChildren = i; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : strTarget - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::SetFightTarget( string_t strTarget, CBaseEntity *pActivator, CBaseEntity *pCaller ) |
|
{ |
|
if ( HasSpawnFlags( SF_ANTLIONMAKER_RANDOM_FIGHT_TARGET ) ) |
|
{ |
|
CBaseEntity *pSearch = m_hFightTarget; |
|
|
|
for ( int i = random->RandomInt(1,5); i > 0; i-- ) |
|
pSearch = gEntList.FindEntityByName( pSearch, strTarget, this, pActivator, pCaller ); |
|
|
|
if ( pSearch != NULL ) |
|
{ |
|
SetFightTarget( pSearch ); |
|
} |
|
else |
|
{ |
|
SetFightTarget( gEntList.FindEntityByName( NULL, strTarget, this, pActivator, pCaller ) ); |
|
} |
|
} |
|
else |
|
{ |
|
SetFightTarget( gEntList.FindEntityByName( NULL, strTarget, this, pActivator, pCaller ) ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pEntity - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::SetFightTarget( CBaseEntity *pEntity ) |
|
{ |
|
m_hFightTarget = pEntity; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &position - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::SetFightTarget( const Vector &position ) |
|
{ |
|
CreateProxyTarget( position ); |
|
|
|
m_hFightTarget = NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pTarget - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::SetFollowTarget( CBaseEntity *pTarget ) |
|
{ |
|
m_hFollowTarget = pTarget; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pTarget - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::SetFollowTarget( string_t strTarget, CBaseEntity *pActivator, CBaseEntity *pCaller ) |
|
{ |
|
CBaseEntity *pSearch = gEntList.FindEntityByName( NULL, strTarget, NULL, pActivator, pCaller ); |
|
|
|
if ( pSearch != NULL ) |
|
{ |
|
SetFollowTarget( pSearch ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : state - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::SetChildMoveState( AntlionMoveState_e state ) |
|
{ |
|
m_nChildMoveState = state; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &position - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::CreateProxyTarget( const Vector &position ) |
|
{ |
|
// Create if we don't have one |
|
if ( m_hProxyTarget == NULL ) |
|
{ |
|
m_hProxyTarget = CreateEntityByName( "info_target" ); |
|
} |
|
|
|
// Update if we do |
|
if ( m_hProxyTarget != NULL ) |
|
{ |
|
m_hProxyTarget->SetAbsOrigin( position ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::DestroyProxyTarget( void ) |
|
{ |
|
if ( m_hProxyTarget ) |
|
{ |
|
UTIL_Remove( m_hProxyTarget ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : bIgnoreSolidEntities - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAntlionTemplateMaker::CanMakeNPC( bool bIgnoreSolidEntities ) |
|
{ |
|
if ( m_nMaxLiveChildren == 0 ) |
|
return false; |
|
|
|
if ( !HasSpawnFlags( SF_ANTLIONMAKER_SPAWN_CLOSE_TO_TARGET ) ) |
|
{ |
|
if ( m_strSpawnGroup == NULL_STRING ) |
|
return BaseClass::CanMakeNPC( bIgnoreSolidEntities ); |
|
} |
|
|
|
if ( m_nMaxLiveChildren > 0 && m_nLiveChildren >= m_nMaxLiveChildren ) |
|
return false; |
|
|
|
// If we're spawning from a pool, ensure the pool has an antlion in it |
|
if ( m_iMaxPool && !m_iPool ) |
|
return false; |
|
|
|
if ( (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) == bits_debugDisableAI ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
void CAntlionTemplateMaker::Enable( void ) |
|
{ |
|
BaseClass::Enable(); |
|
|
|
if ( m_iMaxPool ) |
|
{ |
|
SetContextThink( &CAntlionTemplateMaker::PoolRegenThink, gpGlobals->curtime + m_flPoolRegenTime, s_pPoolThinkContext ); |
|
} |
|
|
|
if ( hl2_episodic.GetBool() == true && HasSpawnFlags( SF_ANTLIONMAKER_DO_BLOCKEDEFFECTS ) ) |
|
{ |
|
SetContextThink( &CAntlionTemplateMaker::FindNodesCloseToPlayer, gpGlobals->curtime + 1.0f, s_pBlockedEffectsThinkContext ); |
|
} |
|
|
|
ActivateAllSpores(); |
|
} |
|
|
|
void CAntlionTemplateMaker::Disable( void ) |
|
{ |
|
BaseClass::Disable(); |
|
|
|
SetContextThink( NULL, gpGlobals->curtime, s_pPoolThinkContext ); |
|
SetContextThink( NULL, gpGlobals->curtime, s_pBlockedEffectsThinkContext ); |
|
|
|
DisableAllSpores(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Randomly turn it into an antlion worker if that is enabled for this maker. |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::ChildPreSpawn( CAI_BaseNPC *pChild ) |
|
{ |
|
BaseClass::ChildPreSpawn( pChild ); |
|
|
|
if ( ( m_flWorkerSpawnRate > 0 ) && ( random->RandomFloat( 0, 1 ) < m_flWorkerSpawnRate ) ) |
|
{ |
|
pChild->AddSpawnFlags( SF_ANTLION_WORKER ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::MakeNPC( void ) |
|
{ |
|
// If we're not restricting to hint groups, spawn as normal |
|
if ( !HasSpawnFlags( SF_ANTLIONMAKER_SPAWN_CLOSE_TO_TARGET ) ) |
|
{ |
|
if ( m_strSpawnGroup == NULL_STRING ) |
|
{ |
|
BaseClass::MakeNPC(); |
|
return; |
|
} |
|
} |
|
|
|
if ( CanMakeNPC( true ) == false ) |
|
return; |
|
|
|
// Set our defaults |
|
Vector targetOrigin = GetAbsOrigin(); |
|
QAngle targetAngles = GetAbsAngles(); |
|
|
|
// Look for our target entity |
|
CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_strSpawnTarget, this ); |
|
|
|
// Take its position if it exists |
|
if ( pTarget != NULL ) |
|
{ |
|
UTIL_PredictedPosition( pTarget, 1.5f, &targetOrigin ); |
|
} |
|
|
|
Vector spawnOrigin = vec3_origin; |
|
|
|
CAI_Hint *pNode = NULL; |
|
|
|
bool bRandom = HasSpawnFlags( SF_ANTLIONMAKER_RANDOM_SPAWN_NODE ); |
|
|
|
if ( HasSpawnFlags( SF_ANTLIONMAKER_SPAWN_CLOSE_TO_TARGET ) ) |
|
{ |
|
if ( FindNearTargetSpawnPosition( spawnOrigin, m_flSpawnRadius, pTarget ) == false ) |
|
return; |
|
} |
|
else |
|
{ |
|
// If we can't find a spawn position, then we can't spawn this time |
|
if ( FindHintSpawnPosition( targetOrigin, m_flSpawnRadius, m_strSpawnGroup, &pNode, bRandom ) == false ) |
|
return; |
|
|
|
pNode->GetPosition( HULL_MEDIUM, &spawnOrigin ); |
|
} |
|
|
|
// Point at the current position of the enemy |
|
if ( pTarget != NULL ) |
|
{ |
|
targetOrigin = pTarget->GetAbsOrigin(); |
|
} |
|
|
|
// Create the entity via a template |
|
CAI_BaseNPC *pent = NULL; |
|
CBaseEntity *pEntity = NULL; |
|
MapEntity_ParseEntity( pEntity, STRING(m_iszTemplateData), NULL ); |
|
|
|
if ( pEntity != NULL ) |
|
{ |
|
pent = (CAI_BaseNPC *) pEntity; |
|
} |
|
|
|
if ( pent == NULL ) |
|
{ |
|
Warning("NULL Ent in NPCMaker!\n" ); |
|
return; |
|
} |
|
|
|
if ( !HasSpawnFlags( SF_ANTLIONMAKER_SPAWN_CLOSE_TO_TARGET ) ) |
|
{ |
|
// Lock this hint node |
|
pNode->Lock( pEntity ); |
|
|
|
// Unlock it in two seconds, this forces subsequent antlions to |
|
// reject this point as a spawn point to spread them out a bit |
|
pNode->Unlock( 2.0f ); |
|
} |
|
|
|
m_OnSpawnNPC.Set( pEntity, pEntity, this ); |
|
|
|
pent->AddSpawnFlags( SF_NPC_FALL_TO_GROUND ); |
|
|
|
ChildPreSpawn( pent ); |
|
|
|
// Put us at the desired location |
|
pent->SetLocalOrigin( spawnOrigin ); |
|
|
|
QAngle spawnAngles; |
|
|
|
if ( pTarget ) |
|
{ |
|
// Face our spawning direction |
|
Vector spawnDir = ( targetOrigin - spawnOrigin ); |
|
VectorNormalize( spawnDir ); |
|
|
|
VectorAngles( spawnDir, spawnAngles ); |
|
spawnAngles[PITCH] = 0.0f; |
|
spawnAngles[ROLL] = 0.0f; |
|
} |
|
else if ( pNode ) |
|
{ |
|
spawnAngles = QAngle( 0, pNode->Yaw(), 0 ); |
|
} |
|
|
|
pent->SetLocalAngles( spawnAngles ); |
|
DispatchSpawn( pent ); |
|
|
|
pent->Activate(); |
|
|
|
m_iSkinCount = ( m_iSkinCount + 1 ) % ANTLION_SKIN_COUNT; |
|
pent->m_nSkin = m_iSkinCount; |
|
|
|
ChildPostSpawn( pent ); |
|
|
|
// Hold onto the child |
|
CNPC_Antlion *pAntlion = dynamic_cast<CNPC_Antlion *>(pent); |
|
|
|
AddChild( pAntlion ); |
|
|
|
m_bBlocked = false; |
|
SetContextThink( NULL, -1, s_pBlockedCheckContext ); |
|
|
|
pAntlion->ClearBurrowPoint( spawnOrigin ); |
|
|
|
if (!(m_spawnflags & SF_NPCMAKER_INF_CHILD)) |
|
{ |
|
if ( m_iMaxPool ) |
|
{ |
|
m_iPool--; |
|
|
|
if ( g_debug_antlionmaker.GetInt() == 2 ) |
|
{ |
|
Msg("SPAWNED: Pool: %d (max %d) (Regenerating %d every %f)\n", m_iPool, m_iMaxPool, m_iPoolRegenAmount, m_flPoolRegenTime ); |
|
} |
|
} |
|
else |
|
{ |
|
m_nMaxNumNPCs--; |
|
} |
|
|
|
if ( IsDepleted() ) |
|
{ |
|
m_OnAllSpawned.FireOutput( this, this ); |
|
|
|
// Disable this forever. Don't kill it because it still gets death notices |
|
SetThink( NULL ); |
|
SetUse( NULL ); |
|
} |
|
} |
|
} |
|
|
|
bool CAntlionTemplateMaker::FindPositionOnFoot( Vector &origin, float radius, CBaseEntity *pTarget ) |
|
{ |
|
int iMaxTries = 10; |
|
Vector vSpawnOrigin = pTarget->GetAbsOrigin(); |
|
|
|
while ( iMaxTries > 0 ) |
|
{ |
|
vSpawnOrigin.x += random->RandomFloat( -radius, radius ); |
|
vSpawnOrigin.y += random->RandomFloat( -radius, radius ); |
|
vSpawnOrigin.z += 96; |
|
|
|
if ( ValidateSpawnPosition( vSpawnOrigin, pTarget ) == false ) |
|
{ |
|
iMaxTries--; |
|
continue; |
|
} |
|
|
|
origin = vSpawnOrigin; |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool CAntlionTemplateMaker::FindPositionOnVehicle( Vector &origin, float radius, CBaseEntity *pTarget ) |
|
{ |
|
int iMaxTries = 10; |
|
Vector vSpawnOrigin = pTarget->GetAbsOrigin(); |
|
vSpawnOrigin.z += 96; |
|
|
|
if ( pTarget == NULL ) |
|
return false; |
|
|
|
while ( iMaxTries > 0 ) |
|
{ |
|
Vector vForward, vRight; |
|
|
|
pTarget->GetVectors( &vForward, &vRight, NULL ); |
|
|
|
float flSpeed = (pTarget->GetSmoothedVelocity().Length() * m_flVehicleSpawnDistance) * random->RandomFloat( 1.0f, 1.5f ); |
|
|
|
vSpawnOrigin = vSpawnOrigin + (vForward * flSpeed) + vRight * random->RandomFloat( -radius, radius ); |
|
|
|
if ( ValidateSpawnPosition( vSpawnOrigin, pTarget ) == false ) |
|
{ |
|
iMaxTries--; |
|
continue; |
|
} |
|
|
|
origin = vSpawnOrigin; |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool CAntlionTemplateMaker::ValidateSpawnPosition( Vector &vOrigin, CBaseEntity *pTarget ) |
|
{ |
|
trace_t tr; |
|
UTIL_TraceLine( vOrigin, vOrigin - Vector( 0, 0, 1024 ), MASK_BLOCKLOS | CONTENTS_WATER, NULL, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( g_debug_antlionmaker.GetInt() == 1 ) |
|
NDebugOverlay::Line( vOrigin, tr.endpos, 0, 255, 0, false, 5 ); |
|
|
|
// Make sure this point is clear |
|
if ( tr.fraction != 1.0 ) |
|
{ |
|
if ( tr.contents & ( CONTENTS_WATER ) ) |
|
return false; |
|
|
|
const surfacedata_t *psurf = physprops->GetSurfaceData( tr.surface.surfaceProps ); |
|
|
|
if ( psurf ) |
|
{ |
|
if ( g_debug_antlionmaker.GetInt() == 1 ) |
|
{ |
|
char szText[16]; |
|
|
|
Q_snprintf( szText, 16, "Material %c", psurf->game.material ); |
|
NDebugOverlay::Text( vOrigin, szText, true, 5 ); |
|
} |
|
|
|
if ( psurf->game.material != CHAR_TEX_SAND ) |
|
return false; |
|
} |
|
|
|
if ( CAntlionRepellant::IsPositionRepellantFree( tr.endpos ) == false ) |
|
return false; |
|
|
|
trace_t trCheck; |
|
UTIL_TraceHull( tr.endpos, tr.endpos + Vector(0,0,5), NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), MASK_NPCSOLID, NULL, COLLISION_GROUP_NONE, &trCheck ); |
|
|
|
if ( trCheck.DidHit() == false ) |
|
{ |
|
if ( g_debug_antlionmaker.GetInt() == 1 ) |
|
NDebugOverlay::Box( tr.endpos + Vector(0,0,5), NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), 0, 255, 0, 128, 5 ); |
|
|
|
if ( pTarget ) |
|
{ |
|
if ( pTarget->IsPlayer() ) |
|
{ |
|
CBaseEntity *pVehicle = NULL; |
|
CBasePlayer *pPlayer = dynamic_cast < CBasePlayer *> ( pTarget ); |
|
|
|
if ( pPlayer && pPlayer->GetVehicle() ) |
|
pVehicle = ((CBasePlayer *)pTarget)->GetVehicle()->GetVehicleEnt(); |
|
|
|
CTraceFilterSkipTwoEntities traceFilter( pPlayer, pVehicle, COLLISION_GROUP_NONE ); |
|
|
|
trace_t trVerify; |
|
|
|
Vector vVerifyOrigin = pPlayer->GetAbsOrigin() + pPlayer->GetViewOffset(); |
|
float flZOffset = NAI_Hull::Maxs( HULL_MEDIUM ).z; |
|
UTIL_TraceLine( vVerifyOrigin, tr.endpos + Vector( 0, 0, flZOffset ), MASK_BLOCKLOS | CONTENTS_WATER, &traceFilter, &trVerify ); |
|
|
|
if ( trVerify.fraction != 1.0f ) |
|
{ |
|
const surfacedata_t *psurf = physprops->GetSurfaceData( trVerify.surface.surfaceProps ); |
|
|
|
if ( psurf ) |
|
{ |
|
if ( psurf->game.material == CHAR_TEX_DIRT ) |
|
{ |
|
if ( g_debug_antlionmaker.GetInt() == 1 ) |
|
{ |
|
NDebugOverlay::Line( vVerifyOrigin, trVerify.endpos, 255, 0, 0, false, 5 ); |
|
} |
|
|
|
return false; |
|
} |
|
} |
|
} |
|
|
|
if ( g_debug_antlionmaker.GetInt() == 1 ) |
|
{ |
|
NDebugOverlay::Line( vVerifyOrigin, trVerify.endpos, 0, 255, 0, false, 5 ); |
|
} |
|
} |
|
} |
|
|
|
|
|
vOrigin = trCheck.endpos + Vector(0,0,5); |
|
return true; |
|
} |
|
else |
|
{ |
|
if ( g_debug_antlionmaker.GetInt() == 1 ) |
|
NDebugOverlay::Box( tr.endpos + Vector(0,0,5), NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), 255, 0, 0, 128, 5 ); |
|
|
|
return false; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find a position near the player to spawn the new antlion at |
|
// Input : &origin - search origin |
|
// radius - search radius |
|
// *retOrigin - found origin (if any) |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAntlionTemplateMaker::FindNearTargetSpawnPosition( Vector &origin, float radius, CBaseEntity *pTarget ) |
|
{ |
|
if ( pTarget ) |
|
{ |
|
CBaseEntity *pVehicle = NULL; |
|
|
|
if ( pTarget->IsPlayer() ) |
|
{ |
|
CBasePlayer *pPlayer = ((CBasePlayer *)pTarget); |
|
|
|
if ( pPlayer->GetVehicle() ) |
|
pVehicle = ((CBasePlayer *)pTarget)->GetVehicle()->GetVehicleEnt(); |
|
} |
|
|
|
if ( pVehicle ) |
|
return FindPositionOnVehicle( origin, radius, pVehicle ); |
|
else |
|
return FindPositionOnFoot( origin, radius, pTarget ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find a hint position to spawn the new antlion at |
|
// Input : &origin - search origin |
|
// radius - search radius |
|
// hintGroupName - search hint group name |
|
// *retOrigin - found origin (if any) |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAntlionTemplateMaker::FindHintSpawnPosition( const Vector &origin, float radius, string_t hintGroupName, CAI_Hint **pHint, bool bRandom ) |
|
{ |
|
CAI_Hint *pChosenHint = NULL; |
|
|
|
CHintCriteria hintCriteria; |
|
|
|
hintCriteria.SetGroup( hintGroupName ); |
|
hintCriteria.SetHintType( HINT_ANTLION_BURROW_POINT ); |
|
|
|
if ( bRandom ) |
|
{ |
|
hintCriteria.SetFlag( bits_HINT_NODE_RANDOM ); |
|
} |
|
else |
|
{ |
|
hintCriteria.SetFlag( bits_HINT_NODE_NEAREST ); |
|
} |
|
|
|
// If requested, deny nodes that can be seen by the player |
|
if ( m_spawnflags & SF_NPCMAKER_HIDEFROMPLAYER ) |
|
{ |
|
hintCriteria.SetFlag( bits_HINT_NODE_NOT_VISIBLE_TO_PLAYER ); |
|
} |
|
|
|
hintCriteria.AddIncludePosition( origin, radius ); |
|
|
|
if ( bRandom == true ) |
|
{ |
|
pChosenHint = CAI_HintManager::FindHintRandom( NULL, origin, hintCriteria ); |
|
} |
|
else |
|
{ |
|
pChosenHint = CAI_HintManager::FindHint( origin, hintCriteria ); |
|
} |
|
|
|
if ( pChosenHint != NULL ) |
|
{ |
|
bool bChosenHintBlocked = false; |
|
|
|
if ( AllHintsFromClusterBlocked( pChosenHint, bChosenHintBlocked ) ) |
|
{ |
|
if ( ( GetIndexForThinkContext( s_pBlockedCheckContext ) == NO_THINK_CONTEXT ) || |
|
( GetNextThinkTick( s_pBlockedCheckContext ) == TICK_NEVER_THINK ) ) |
|
{ |
|
SetContextThink( &CAntlionTemplateMaker::BlockedCheckFunc, gpGlobals->curtime + 2.0f, s_pBlockedCheckContext ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
if ( bChosenHintBlocked == true ) |
|
{ |
|
return false; |
|
} |
|
|
|
*pHint = pChosenHint; |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
void CAntlionTemplateMaker::DoBlockedEffects( CBaseEntity *pBlocker, Vector vOrigin ) |
|
{ |
|
// If the object blocking the hole is a physics object, wobble it a bit. |
|
if( pBlocker ) |
|
{ |
|
IPhysicsObject *pPhysObj = pBlocker->VPhysicsGetObject(); |
|
|
|
if( pPhysObj && pPhysObj->IsAsleep() ) |
|
{ |
|
// Don't bonk the object unless it is at rest. |
|
float x = RandomFloat( -5000, 5000 ); |
|
float y = RandomFloat( -5000, 5000 ); |
|
|
|
Vector vecForce = Vector( x, y, RandomFloat(10000, 15000) ); |
|
pPhysObj->ApplyForceCenter( vecForce ); |
|
|
|
UTIL_CreateAntlionDust( vOrigin, vec3_angle, true ); |
|
pBlocker->EmitSound( "NPC_Antlion.MeleeAttackSingle_Muffled" ); |
|
pBlocker->EmitSound( "NPC_Antlion.TrappedMetal" ); |
|
|
|
|
|
m_flBlockedBumpTime = gpGlobals->curtime + random->RandomFloat( 1.75, 2.75 ); |
|
} |
|
} |
|
} |
|
|
|
CBaseEntity *CAntlionTemplateMaker::AllHintsFromClusterBlocked( CAI_Hint *pNode, bool &bChosenHintBlocked ) |
|
{ |
|
// Only do this for episodic content! |
|
if ( hl2_episodic.GetBool() == false ) |
|
return NULL; |
|
|
|
CBaseEntity *pBlocker = NULL; |
|
|
|
if ( pNode != NULL ) |
|
{ |
|
int iNumBlocked = 0; |
|
int iNumNodes = 0; |
|
|
|
CHintCriteria hintCriteria; |
|
|
|
hintCriteria.SetGroup( m_strSpawnGroup ); |
|
hintCriteria.SetHintType( HINT_ANTLION_BURROW_POINT ); |
|
|
|
CUtlVector<CAI_Hint *> hintList; |
|
CAI_HintManager::FindAllHints( vec3_origin, hintCriteria, &hintList ); |
|
|
|
for ( int i = 0; i < hintList.Count(); i++ ) |
|
{ |
|
CAI_Hint *pTestHint = hintList[i]; |
|
|
|
if ( pTestHint ) |
|
{ |
|
if ( pTestHint->NameMatches( pNode->GetEntityName() ) ) |
|
{ |
|
bool bBlocked; |
|
|
|
iNumNodes++; |
|
|
|
Vector spawnOrigin; |
|
pTestHint->GetPosition( HULL_MEDIUM, &spawnOrigin ); |
|
|
|
bBlocked = false; |
|
|
|
CBaseEntity* pList[20]; |
|
|
|
int count = UTIL_EntitiesInBox( pList, 20, spawnOrigin + NAI_Hull::Mins( HULL_MEDIUM ), spawnOrigin + NAI_Hull::Maxs( HULL_MEDIUM ), 0 ); |
|
|
|
//Iterate over all the possible targets |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
if ( pList[i]->GetMoveType() != MOVETYPE_VPHYSICS ) |
|
continue; |
|
|
|
if ( PhysGetEntityMass( pList[i] ) > ANTLION_MAKER_BLOCKED_MASS ) |
|
{ |
|
bBlocked = true; |
|
iNumBlocked++; |
|
pBlocker = pList[i]; |
|
|
|
if ( pTestHint == pNode ) |
|
{ |
|
bChosenHintBlocked = true; |
|
} |
|
|
|
break; |
|
} |
|
} |
|
|
|
if ( g_debug_antlionmaker.GetInt() == 1 ) |
|
{ |
|
if ( bBlocked ) |
|
{ |
|
NDebugOverlay::Box( spawnOrigin + Vector(0,0,5), NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), 255, 0, 0, 128, 0.25f ); |
|
} |
|
else |
|
{ |
|
NDebugOverlay::Box( spawnOrigin + Vector(0,0,5), NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), 0, 255, 0, 128, 0.25f ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//All the nodes from this cluster are blocked so start playing the effects. |
|
if ( iNumNodes > 0 && iNumBlocked == iNumNodes ) |
|
{ |
|
return pBlocker; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
void CAntlionTemplateMaker::FindNodesCloseToPlayer( void ) |
|
{ |
|
SetContextThink( &CAntlionTemplateMaker::FindNodesCloseToPlayer, gpGlobals->curtime + random->RandomFloat( 0.75, 1.75 ), s_pBlockedEffectsThinkContext ); |
|
|
|
CBasePlayer *pPlayer = AI_GetSinglePlayer(); |
|
|
|
if ( pPlayer == NULL ) |
|
return; |
|
|
|
CHintCriteria hintCriteria; |
|
|
|
float flRadius = ANTLION_MAKER_PLAYER_DETECT_RADIUS; |
|
|
|
hintCriteria.SetGroup( m_strSpawnGroup ); |
|
hintCriteria.SetHintType( HINT_ANTLION_BURROW_POINT ); |
|
hintCriteria.AddIncludePosition( pPlayer->GetAbsOrigin(), ANTLION_MAKER_PLAYER_DETECT_RADIUS ); |
|
|
|
CUtlVector<CAI_Hint *> hintList; |
|
|
|
if ( CAI_HintManager::FindAllHints( vec3_origin, hintCriteria, &hintList ) <= 0 ) |
|
return; |
|
|
|
CUtlVector<string_t> m_BlockedNames; |
|
|
|
//---- |
|
//What we do here is find all hints of the same name (cluster name) and figure out if all of them are blocked. |
|
//If they are then we only need to play the blocked effects once |
|
//--- |
|
for ( int i = 0; i < hintList.Count(); i++ ) |
|
{ |
|
CAI_Hint *pNode = hintList[i]; |
|
|
|
if ( pNode && pNode->HintMatchesCriteria( NULL, hintCriteria, pPlayer->GetAbsOrigin(), &flRadius ) ) |
|
{ |
|
bool bClusterAlreadyBlocked = false; |
|
|
|
//Have one of the nodes from this cluster been checked for blockage? If so then there's no need to do block checks again for this cluster. |
|
for ( int iStringCount = 0; iStringCount < m_BlockedNames.Count(); iStringCount++ ) |
|
{ |
|
if ( pNode->NameMatches( m_BlockedNames[iStringCount] ) ) |
|
{ |
|
bClusterAlreadyBlocked = true; |
|
break; |
|
} |
|
} |
|
|
|
if ( bClusterAlreadyBlocked == true ) |
|
continue; |
|
|
|
Vector vHintPos; |
|
pNode->GetPosition( HULL_MEDIUM, &vHintPos ); |
|
|
|
bool bBlank; |
|
if ( CBaseEntity *pBlocker = AllHintsFromClusterBlocked( pNode, bBlank ) ) |
|
{ |
|
DisableSpore( STRING( pNode->GetEntityName() ) ); |
|
DoBlockedEffects( pBlocker, vHintPos ); |
|
m_BlockedNames.AddToTail( pNode->GetEntityName() ); |
|
} |
|
else |
|
{ |
|
ActivateSpore( STRING( pNode->GetEntityName() ), pNode->GetAbsOrigin() ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CAntlionTemplateMaker::BlockedCheckFunc( void ) |
|
{ |
|
SetContextThink( &CAntlionTemplateMaker::BlockedCheckFunc, -1, s_pBlockedCheckContext ); |
|
|
|
if ( m_bBlocked == true ) |
|
return; |
|
|
|
CUtlVector<CAI_Hint *> hintList; |
|
int iBlocked = 0; |
|
|
|
CHintCriteria hintCriteria; |
|
|
|
hintCriteria.SetGroup( m_strSpawnGroup ); |
|
hintCriteria.SetHintType( HINT_ANTLION_BURROW_POINT ); |
|
|
|
if ( CAI_HintManager::FindAllHints( vec3_origin, hintCriteria, &hintList ) > 0 ) |
|
{ |
|
for ( int i = 0; i < hintList.Count(); i++ ) |
|
{ |
|
CAI_Hint *pNode = hintList[i]; |
|
|
|
if ( pNode ) |
|
{ |
|
Vector vHintPos; |
|
pNode->GetPosition( AI_GetSinglePlayer(), &vHintPos ); |
|
|
|
CBaseEntity* pList[20]; |
|
int count = UTIL_EntitiesInBox( pList, 20, vHintPos + NAI_Hull::Mins( HULL_MEDIUM ), vHintPos + NAI_Hull::Maxs( HULL_MEDIUM ), 0 ); |
|
|
|
//Iterate over all the possible targets |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
if ( pList[i]->GetMoveType() != MOVETYPE_VPHYSICS ) |
|
continue; |
|
|
|
if ( PhysGetEntityMass( pList[i] ) > ANTLION_MAKER_BLOCKED_MASS ) |
|
{ |
|
iBlocked++; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ( iBlocked > 0 && hintList.Count() == iBlocked ) |
|
{ |
|
m_bBlocked = true; |
|
m_OnAllBlocked.FireOutput( this, this ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Makes the antlion immediatley unburrow if it started burrowed |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::ChildPostSpawn( CAI_BaseNPC *pChild ) |
|
{ |
|
CNPC_Antlion *pAntlion = static_cast<CNPC_Antlion*>(pChild); |
|
|
|
if ( pAntlion == NULL ) |
|
return; |
|
|
|
// Unburrow the spawned antlion immediately |
|
if ( pAntlion->m_bStartBurrowed ) |
|
{ |
|
pAntlion->BurrowUse( this, this, USE_ON, 0.0f ); |
|
} |
|
|
|
// Set us to a follow target, if we have one |
|
if ( GetFollowTarget() ) |
|
{ |
|
pAntlion->SetFollowTarget( GetFollowTarget() ); |
|
} |
|
else if ( ( m_strFollowTarget != NULL_STRING ) ) |
|
{ |
|
// If we don't already have a fight target, set it up |
|
SetFollowTarget( m_strFollowTarget ); |
|
|
|
if ( GetFightTarget() == NULL ) |
|
{ |
|
SetChildMoveState( ANTLION_MOVE_FOLLOW ); |
|
|
|
// If it's valid, fight there |
|
if ( GetFollowTarget() != NULL ) |
|
{ |
|
pAntlion->SetFollowTarget( GetFollowTarget() ); |
|
} |
|
} |
|
} |
|
// See if we need to send them on their way to a fight goal |
|
if ( GetFightTarget() && !HasSpawnFlags( SF_ANTLIONMAKER_RANDOM_FIGHT_TARGET ) ) |
|
{ |
|
pAntlion->SetFightTarget( GetFightTarget() ); |
|
} |
|
else if ( m_strFightTarget != NULL_STRING ) |
|
{ |
|
// If we don't already have a fight target, set it up |
|
SetFightTarget( m_strFightTarget ); |
|
SetChildMoveState( ANTLION_MOVE_FIGHT_TO_GOAL ); |
|
|
|
// If it's valid, fight there |
|
if ( GetFightTarget() != NULL ) |
|
{ |
|
pAntlion->SetFightTarget( GetFightTarget() ); |
|
} |
|
} |
|
|
|
// Set us to the desired movement state |
|
pAntlion->SetMoveState( m_nChildMoveState ); |
|
|
|
// Save our name for level transitions |
|
pAntlion->SetParentSpawnerName( STRING( GetEntityName() ) ); |
|
|
|
if ( m_hIgnoreEntity != NULL ) |
|
{ |
|
pChild->SetOwnerEntity( m_hIgnoreEntity ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::InputSetFightTarget( inputdata_t &inputdata ) |
|
{ |
|
// Set our new goal |
|
m_strFightTarget = MAKE_STRING( inputdata.value.String() ); |
|
|
|
SetFightTarget( m_strFightTarget, inputdata.pActivator, inputdata.pCaller ); |
|
SetChildMoveState( ANTLION_MOVE_FIGHT_TO_GOAL ); |
|
|
|
UpdateChildren(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::InputSetFollowTarget( inputdata_t &inputdata ) |
|
{ |
|
// Set our new goal |
|
m_strFollowTarget = MAKE_STRING( inputdata.value.String() ); |
|
|
|
SetFollowTarget( m_strFollowTarget, inputdata.pActivator, inputdata.pCaller ); |
|
SetChildMoveState( ANTLION_MOVE_FOLLOW ); |
|
|
|
UpdateChildren(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::InputClearFightTarget( inputdata_t &inputdata ) |
|
{ |
|
SetFightTarget( NULL ); |
|
SetChildMoveState( ANTLION_MOVE_FOLLOW ); |
|
|
|
UpdateChildren(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::InputClearFollowTarget( inputdata_t &inputdata ) |
|
{ |
|
SetFollowTarget( NULL ); |
|
SetChildMoveState( ANTLION_MOVE_FIGHT_TO_GOAL ); |
|
|
|
UpdateChildren(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::InputSetSpawnRadius( inputdata_t &inputdata ) |
|
{ |
|
m_flSpawnRadius = inputdata.value.Float(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::InputAddToPool( inputdata_t &inputdata ) |
|
{ |
|
PoolAdd( inputdata.value.Int() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::InputSetMaxPool( inputdata_t &inputdata ) |
|
{ |
|
m_iMaxPool = inputdata.value.Int(); |
|
if ( m_iPool > m_iMaxPool ) |
|
{ |
|
m_iPool = m_iMaxPool; |
|
} |
|
|
|
// Stop regenerating if we're supposed to stop using the pool |
|
if ( !m_iMaxPool ) |
|
{ |
|
SetContextThink( NULL, gpGlobals->curtime, s_pPoolThinkContext ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::InputSetPoolRegenAmount( inputdata_t &inputdata ) |
|
{ |
|
m_iPoolRegenAmount = inputdata.value.Int(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::InputSetPoolRegenTime( inputdata_t &inputdata ) |
|
{ |
|
m_flPoolRegenTime = inputdata.value.Float(); |
|
|
|
if ( m_flPoolRegenTime != 0.0f ) |
|
{ |
|
SetContextThink( &CAntlionTemplateMaker::PoolRegenThink, gpGlobals->curtime + m_flPoolRegenTime, s_pPoolThinkContext ); |
|
} |
|
else |
|
{ |
|
SetContextThink( NULL, gpGlobals->curtime, s_pPoolThinkContext ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Pool behavior for coast |
|
// Input : iNumToAdd - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::PoolAdd( int iNumToAdd ) |
|
{ |
|
m_iPool = clamp( m_iPool + iNumToAdd, 0, m_iMaxPool ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Regenerate the pool |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::PoolRegenThink( void ) |
|
{ |
|
if ( m_iPool < m_iMaxPool ) |
|
{ |
|
m_iPool = clamp( m_iPool + m_iPoolRegenAmount, 0, m_iMaxPool ); |
|
|
|
if ( g_debug_antlionmaker.GetInt() == 2 ) |
|
{ |
|
Msg("REGENERATED: Pool: %d (max %d) (Regenerating %d every %f)\n", m_iPool, m_iMaxPool, m_iPoolRegenAmount, m_flPoolRegenTime ); |
|
} |
|
} |
|
|
|
SetContextThink( &CAntlionTemplateMaker::PoolRegenThink, gpGlobals->curtime + m_flPoolRegenTime, s_pPoolThinkContext ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pVictim - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::DeathNotice( CBaseEntity *pVictim ) |
|
{ |
|
CNPC_Antlion *pAnt = dynamic_cast<CNPC_Antlion *>(pVictim); |
|
if ( pAnt == NULL ) |
|
return; |
|
|
|
// Take it out of our list |
|
RemoveChild( pAnt ); |
|
|
|
// Check if all live children are now dead |
|
if ( m_nLiveChildren <= 0 ) |
|
{ |
|
// Fire the output for this case |
|
m_OnAllLiveChildrenDead.FireOutput( this, this ); |
|
|
|
bool bPoolDepleted = ( m_iMaxPool != 0 && m_iPool == 0 ); |
|
if ( bPoolDepleted || IsDepleted() ) |
|
{ |
|
// Signal that all our children have been spawned and are now dead |
|
m_OnAllSpawnedDead.FireOutput( this, this ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: If this had a finite number of children, return true if they've all |
|
// been created. |
|
//----------------------------------------------------------------------------- |
|
bool CAntlionTemplateMaker::IsDepleted( void ) |
|
{ |
|
// If we're running pool behavior, we're never depleted |
|
if ( m_iMaxPool ) |
|
return false; |
|
|
|
return BaseClass::IsDepleted(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Change the spawn group the maker is using |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::InputChangeDestinationGroup( inputdata_t &inputdata ) |
|
{ |
|
// FIXME: This function is redundant to the base class version, remove the m_strSpawnGroup |
|
m_strSpawnGroup = inputdata.value.StringID(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Draw debugging text for the spawner |
|
//----------------------------------------------------------------------------- |
|
int CAntlionTemplateMaker::DrawDebugTextOverlays( void ) |
|
{ |
|
// We don't want the base class info, it's not useful to us |
|
int text_offset = BaseClass::DrawDebugTextOverlays(); |
|
|
|
if ( m_debugOverlays & OVERLAY_TEXT_BIT ) |
|
{ |
|
char tempstr[255]; |
|
|
|
// Print the state of the spawner |
|
if ( m_bDisabled ) |
|
{ |
|
Q_strncpy( tempstr, "State: Disabled\n", sizeof(tempstr) ); |
|
} |
|
else |
|
{ |
|
Q_strncpy( tempstr, "State: Enabled\n", sizeof(tempstr) ); |
|
} |
|
|
|
EntityText( text_offset, tempstr, 0 ); |
|
text_offset++; |
|
|
|
// Print follow information |
|
if ( m_strFollowTarget != NULL_STRING ) |
|
{ |
|
Q_snprintf( tempstr, sizeof(tempstr), "Follow Target: %s\n", STRING( m_strFollowTarget ) ); |
|
} |
|
else |
|
{ |
|
Q_strncpy( tempstr, "Follow Target : NONE\n", sizeof(tempstr) ); |
|
} |
|
|
|
EntityText( text_offset, tempstr, 0 ); |
|
text_offset++; |
|
|
|
// Print fight information |
|
if ( m_strFightTarget != NULL_STRING ) |
|
{ |
|
Q_snprintf( tempstr, sizeof(tempstr), "Fight Target: %s\n", STRING( m_strFightTarget ) ); |
|
} |
|
else |
|
{ |
|
Q_strncpy( tempstr, "Fight Target : NONE\n", sizeof(tempstr) ); |
|
} |
|
|
|
EntityText( text_offset, tempstr, 0 ); |
|
text_offset++; |
|
|
|
// Print spawning criteria information |
|
if ( m_strSpawnTarget != NULL_STRING ) |
|
{ |
|
Q_snprintf( tempstr, sizeof(tempstr), "Spawn Target: %s\n", STRING( m_strSpawnTarget ) ); |
|
} |
|
else |
|
{ |
|
Q_strncpy( tempstr, "Spawn Target : NONE\n", sizeof(tempstr) ); |
|
} |
|
|
|
EntityText( text_offset, tempstr, 0 ); |
|
text_offset++; |
|
|
|
// Print the chilrens' state |
|
Q_snprintf( tempstr, sizeof(tempstr), "Spawn Frequency: %f\n", m_flSpawnFrequency ); |
|
EntityText( text_offset, tempstr, 0 ); |
|
text_offset++; |
|
|
|
// Print the spawn radius |
|
Q_snprintf( tempstr, sizeof(tempstr), "Spawn Radius: %.02f units\n", m_flSpawnRadius ); |
|
EntityText( text_offset, tempstr, 0 ); |
|
text_offset++; |
|
|
|
// Print the spawn group we're using |
|
if ( m_strSpawnGroup != NULL_STRING ) |
|
{ |
|
Q_snprintf( tempstr, sizeof(tempstr), "Spawn Group: %s\n", STRING( m_strSpawnGroup ) ); |
|
EntityText( text_offset, tempstr, 0 ); |
|
text_offset++; |
|
} |
|
|
|
// Print the chilrens' state |
|
Q_snprintf( tempstr, sizeof(tempstr), "Live Children: (%d/%d)\n", m_nLiveChildren, m_nMaxLiveChildren ); |
|
EntityText( text_offset, tempstr, 0 ); |
|
text_offset++; |
|
|
|
// Print pool information |
|
if ( m_iMaxPool ) |
|
{ |
|
// Print the pool's state |
|
Q_snprintf( tempstr, sizeof(tempstr), "Pool: (%d/%d) (%d per regen)\n", m_iPool, m_iMaxPool, m_iPoolRegenAmount ); |
|
EntityText( text_offset, tempstr, 0 ); |
|
text_offset++; |
|
|
|
float flTimeRemaining = GetNextThink( s_pPoolThinkContext ) - gpGlobals->curtime; |
|
|
|
if ( flTimeRemaining < 0.0f ) |
|
{ |
|
flTimeRemaining = 0.0f; |
|
} |
|
|
|
// Print the pool's regeneration state |
|
Q_snprintf( tempstr, sizeof(tempstr), "Pool Regen Time: %.02f sec. (%.02f remaining)\n", m_flPoolRegenTime, flTimeRemaining ); |
|
EntityText( text_offset, tempstr, 0 ); |
|
text_offset++; |
|
} |
|
} |
|
|
|
return text_offset; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Draw debugging overlays for the spawner |
|
//----------------------------------------------------------------------------- |
|
void CAntlionTemplateMaker::DrawDebugGeometryOverlays( void ) |
|
{ |
|
BaseClass::DrawDebugGeometryOverlays(); |
|
|
|
if ( m_debugOverlays & OVERLAY_TEXT_BIT ) |
|
{ |
|
float r, g, b; |
|
|
|
// Color by active state |
|
if ( m_bDisabled ) |
|
{ |
|
r = 255.0f; |
|
g = 0.0f; |
|
b = 0.0f; |
|
} |
|
else |
|
{ |
|
r = 0.0f; |
|
g = 255.0f; |
|
b = 0.0f; |
|
} |
|
|
|
// Draw ourself |
|
NDebugOverlay::Box( GetAbsOrigin(), -Vector(8,8,8), Vector(8,8,8), r, g, b, true, 0.05f ); |
|
|
|
// Draw lines to our spawngroup hints |
|
if ( m_strSpawnGroup != NULL_STRING ) |
|
{ |
|
// Draw lines to our active hint groups |
|
AIHintIter_t iter; |
|
CAI_Hint *pHint = CAI_HintManager::GetFirstHint( &iter ); |
|
while ( pHint != NULL ) |
|
{ |
|
// Must be of the hint group we care about |
|
if ( pHint->GetGroup() != m_strSpawnGroup ) |
|
{ |
|
pHint = CAI_HintManager::GetNextHint( &iter ); |
|
continue; |
|
} |
|
|
|
// Draw an arrow to the spot |
|
NDebugOverlay::VertArrow( GetAbsOrigin(), pHint->GetAbsOrigin() + Vector( 0, 0, 32 ), 8.0f, r, g, b, 0, true, 0.05f ); |
|
|
|
// Draw a box to represent where it's sitting |
|
Vector vecForward; |
|
AngleVectors( pHint->GetAbsAngles(), &vecForward ); |
|
NDebugOverlay::BoxDirection( pHint->GetAbsOrigin(), -Vector(32,32,0), Vector(32,32,16), vecForward, r, g, b, true, 0.05f ); |
|
|
|
// Move to the next |
|
pHint = CAI_HintManager::GetNextHint( &iter ); |
|
} |
|
} |
|
|
|
// Draw a line to the spawn target (if it exists) |
|
if ( m_strSpawnTarget != NULL_STRING ) |
|
{ |
|
// Find all the possible targets |
|
CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_strSpawnTarget ); |
|
if ( pTarget != NULL ) |
|
{ |
|
NDebugOverlay::VertArrow( GetAbsOrigin(), pTarget->WorldSpaceCenter(), 4.0f, 255, 255, 255, 0, true, 0.05f ); |
|
} |
|
} |
|
|
|
// Draw a line to the follow target (if it exists) |
|
if ( m_strFollowTarget != NULL_STRING ) |
|
{ |
|
// Find all the possible targets |
|
CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_strFollowTarget ); |
|
if ( pTarget != NULL ) |
|
{ |
|
NDebugOverlay::VertArrow( GetAbsOrigin(), pTarget->WorldSpaceCenter(), 4.0f, 255, 255, 0, 0, true, 0.05f ); |
|
} |
|
} |
|
|
|
// Draw a line to the fight target (if it exists) |
|
if ( m_strFightTarget != NULL_STRING ) |
|
{ |
|
// Find all the possible targets |
|
CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_strFightTarget ); |
|
if ( pTarget != NULL ) |
|
{ |
|
NDebugOverlay::VertArrow( GetAbsOrigin(), pTarget->WorldSpaceCenter(), 4.0f, 255, 0, 0, 0, true, 0.05f ); |
|
} |
|
} |
|
} |
|
}
|
|
|