source-engine/game/server/hl2/antlion_maker.cpp

1754 lines
49 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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 );
}
}
}
}