diff options
Diffstat (limited to 'mp/src/game/server/hl2/antlion_maker.cpp')
| -rw-r--r-- | mp/src/game/server/hl2/antlion_maker.cpp | 1753 |
1 files changed, 1753 insertions, 0 deletions
diff --git a/mp/src/game/server/hl2/antlion_maker.cpp b/mp/src/game/server/hl2/antlion_maker.cpp new file mode 100644 index 00000000..3d347eaa --- /dev/null +++ b/mp/src/game/server/hl2/antlion_maker.cpp @@ -0,0 +1,1753 @@ +//========= 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 );
+ }
+ }
+ }
+}
|