//========= 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(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(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(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 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 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(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 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 hintList; if ( CAI_HintManager::FindAllHints( vec3_origin, hintCriteria, &hintList ) <= 0 ) return; CUtlVector 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 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(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(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 ); } } } }