Пример #1
0
void CASW_Spawn_Manager::PrespawnAlienAtRandomNode(const char *szAlienClass, const int iNumAliens, const int iHull, const Vector &playerStartPos, const int iNumNodes)
{
	for (int i = 0; i < iNumAliens; ++i)
	{
		CAI_Node *pNode = NULL;
		for (int k = 0; k < 30; ++k)
		{
			int node_id = RandomInt(0, iNumNodes - 1);
			pNode = g_pBigAINet->GetNode(node_id);
			if (!pNode || pNode->GetType() != NODE_GROUND)
				continue;
			else if (pNode->GetOrigin().DistToSqr(playerStartPos) < 1000 * 1000)
			{
				continue;
			}

			if (ValidSpawnPoint(pNode->GetPosition(iHull), NAI_Hull::Mins(iHull), NAI_Hull::Maxs(iHull), true, false))
			{
				// Raise the end position a little up off the floor, place the npc and drop him down
				CBaseEntity *pAlien = SpawnAlienAt(szAlienClass, pNode->GetPosition(iHull) + Vector(0.f, 0.f, 12.f), RandomAngle(0, 360));
				IASW_Spawnable_NPC *pSpawnable = dynamic_cast<IASW_Spawnable_NPC*>(pAlien);
				if (pSpawnable)
				{
					pSpawnable->SetAlienOrders(AOT_SpreadThenHibernate, vec3_origin, NULL);
				}
				if (asw_director_debug.GetBool() && pAlien)
				{
					Msg("Spawned alien at %f %f %f\n", pAlien->GetAbsOrigin());
					NDebugOverlay::Cross3D(pAlien->GetAbsOrigin(), 8.0f, 255, 0, 0, true, 20.0f);
				}
				if (pAlien)
					break;
			}
		}
	}
}
bool CAI_ASW_PrepareToEngageBehavior::GetPrepareToAttackPath(const Vector &vecThreat )
{
    if ( !CAI_NetworkManager::NetworksLoaded() )
    {
        DevWarning( 2, "Graph not ready for GetPrepareToAttackPath!\n" );
        return false;
    }

    Vector vecToThreat = vecThreat - GetAbsOrigin();
    float flDistToThreat = vecToThreat.NormalizeInPlace();
    /*
    	int iMyNode			= GetOuter()->GetPathfinder()->NearestNodeToNPC();
    	int iThreatNode		= GetOuter()->GetPathfinder()->NearestNodeToPoint( vecThreat );

    	if ( iMyNode == NO_NODE )
    	{
    		DevWarning( 2, "FindPrepareToAttackNode() - %s has no nearest node!\n", GetEntClassname());
    		return false;
    	}
    	if ( iThreatNode == NO_NODE )
    	{
    		// DevWarning( 2, "FindBackAwayNode() - Threat has no nearest node!\n" );
    		iThreatNode = iMyNode;
    		// return false;
    	}

    	// A vector pointing to the threat.
    	Vector vecToThreat;
    	vecToThreat = vecThreat - GetLocalOrigin();

    	// Get my current distance from the threat
    	float flCurDist = VectorNormalize( vecToThreat );

    	// find all nodes within the radius of our target
    	m_flPrepareRadius;
    */
    int iNumNodes = g_pBigAINet->NumNodes();
    CUtlVector<int> candidateNodes;
    for ( int i = 0; i < iNumNodes; i++ )
    {
        CAI_Node *pNode = g_pBigAINet->GetNode( i );
        if ( !pNode || pNode->GetType() != NODE_GROUND )
            continue;

        Vector vecPos = pNode->GetPosition( GetOuter()->GetHullType() );
        Vector vecDir = vecPos - vecThreat;
        float flDist = vecDir.NormalizeInPlace();
        if ( flDist > m_flPrepareRadiusMax || flDist < m_flPrepareRadiusMin )
            continue;

        // Make sure this node doesn't take me past the enemy's position.
        Vector vecToNode = vecPos - GetAbsOrigin();
        float flDistToNode = vecToNode.NormalizeInPlace();

        if( DotProduct( vecToNode, vecToThreat ) > 0.0 && flDistToNode > flDistToThreat )
            continue;

        candidateNodes.AddToTail( i );
    }

    if ( candidateNodes.Count() <= 0 )
        return false;

    int iOffset = RandomInt( 0, candidateNodes.Count() - 1 );
    int iNumCandidateNodes = candidateNodes.Count();
    int iMaxTries = 4;
    for ( int i = 0; i < iNumCandidateNodes && iMaxTries > 0; i++ )
    {
        CAI_Node *pNode = g_pBigAINet->GetNode( candidateNodes[ i + iOffset ] );
        if ( !pNode || pNode->GetType() != NODE_GROUND )
            continue;

        // see if we can reach it
        Vector vecPos = pNode->GetPosition( GetOuter()->GetHullType() );
        AI_NavGoal_t goal( vecPos );

        if ( GetNavigator()->SetGoal( goal, AIN_CLEAR_TARGET ) )
        {
            return true;
        }
        iMaxTries--;
    }
    return false;
}
Пример #3
0
bool CASW_Weapon_Blink::SetBlinkDestination()
{
	CASW_Player *pPlayer = GetCommander();
	if ( !pPlayer )
		return false;

	CASW_Marine *pMarine = GetMarine();
	if ( !pMarine )
		return false;

	Vector vecStart = pPlayer->GetCrosshairTracePos() + Vector( 0, 0, 30 );
	Vector vecEnd = pPlayer->GetCrosshairTracePos() - Vector( 0, 0, 30 );
	trace_t tr;
	UTIL_TraceHull( vecStart, vecEnd, pMarine->WorldAlignMins(), pMarine->WorldAlignMaxs(), MASK_PLAYERSOLID_BRUSHONLY, pMarine, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
	if ( tr.startsolid || tr.allsolid )
	{
		m_vecInvalidDestination = vecStart;
		return false;
	}

	if ( pMarine->GetAbsOrigin().DistTo( tr.endpos ) > asw_blink_range.GetFloat() )
	{
		m_vecInvalidDestination = tr.endpos;
		return false;
	}

	Vector vecDest = tr.endpos;

	// now see if we can build an AI path from the marine to this spot
	bool bValidRoute = false;

	if ( !pMarine->GetPathfinder() )
	{
		m_vecInvalidDestination = vecDest;
		return false;
	}

	AI_Waypoint_t *pRoute = pMarine->GetPathfinder()->BuildRoute( pMarine->GetAbsOrigin(), vecDest, NULL, 30, NAV_GROUND, bits_BUILD_GROUND | bits_BUILD_IGNORE_NPCS );
	if ( pRoute && !UTIL_ASW_DoorBlockingRoute( pRoute, true ) )
	{
		if ( !UTIL_ASW_BrushBlockingRoute( pRoute, MASK_PLAYERSOLID_BRUSHONLY, COLLISION_GROUP_PLAYER_MOVEMENT ) )
		{
			// if end node of the route is too Z different, then abort, to stop people jumping on top of walls
			AI_Waypoint_t *pLast = pRoute->GetLast();
			if ( pLast )
			{
				AI_Waypoint_t *pNode = pLast->GetPrev();
				if ( !pNode || fabs( pNode->GetPos().z - pLast->GetPos().z ) < 80.0f )
				{
					bValidRoute = true;
				}
			}
		}
	}
	
	if ( !bValidRoute )
	{
		// find the closest node to the dest and try to path there instead
		CAI_Network *pNetwork = pMarine->GetNavigator() ? pMarine->GetNavigator()->GetNetwork() : NULL;
		if ( pNetwork )
		{
			int nNode = pNetwork->NearestNodeToPoint( vecDest, false );
			if ( nNode != NO_NODE )
			{
				CAI_Node *pNode = pNetwork->GetNode( nNode );
				if ( pNode && pNode->GetType() == NODE_GROUND )
				{
					vecDest = pNode->GetOrigin();
					if ( pRoute )
					{
						ASWPathUtils()->DeleteRoute( pRoute );
						pRoute = NULL;
					}
					pRoute = pMarine->GetPathfinder()->BuildRoute( pMarine->GetAbsOrigin(), vecDest, NULL, 30, NAV_GROUND, bits_BUILD_GROUND | bits_BUILD_IGNORE_NPCS );
					if ( pRoute && !UTIL_ASW_DoorBlockingRoute( pRoute, true ) )
					{
						if ( !UTIL_ASW_BrushBlockingRoute( pRoute, MASK_PLAYERSOLID_BRUSHONLY, COLLISION_GROUP_PLAYER_MOVEMENT ) )
						{
							bValidRoute = true;
						}
					}
					if ( !bValidRoute )
					{
						m_vecInvalidDestination = vecDest;
					}
				}
			}
		}
	}

	if ( !bValidRoute )
	{
		if ( pRoute )
		{
			ASWPathUtils()->DeleteRoute( pRoute );
			pRoute = NULL;
		}
		return false;
	}

	if ( asw_blink_debug.GetBool() )
	{
		ASWPathUtils()->DebugDrawRoute( pMarine->GetAbsOrigin(), pRoute );
	}

	m_vecAbilityDestination = vecDest;

	if ( pRoute )
	{
		ASWPathUtils()->DeleteRoute( pRoute );
		pRoute = NULL;
	}

	return true;
}
Пример #4
0
bool CASW_Spawn_Manager::SpawnRandomShieldbug()
{
    int iNumNodes = g_pBigAINet->NumNodes();
    if ( iNumNodes < 6 )
        return false;

    int nHull = HULL_WIDE_SHORT;
    CUtlVector<CASW_Open_Area*> aAreas;
    for ( int i = 0; i < 6; i++ )
    {
        CAI_Node *pNode = NULL;
        int nTries = 0;
        while ( nTries < 5 && ( !pNode || pNode->GetType() != NODE_GROUND ) )
        {
            pNode = g_pBigAINet->GetNode( RandomInt( 0, iNumNodes ) );
            nTries++;
        }

        if ( pNode )
        {
            CASW_Open_Area *pArea = FindNearbyOpenArea( pNode->GetOrigin(), HULL_MEDIUMBIG );
            if ( pArea && pArea->m_nTotalLinks > 30 )
            {
                // test if there's room to spawn a shieldbug at that spot
                if ( ValidSpawnPoint( pArea->m_pNode->GetPosition( nHull ), NAI_Hull::Mins( nHull ), NAI_Hull::Maxs( nHull ), true, false ) )
                {
                    aAreas.AddToTail( pArea );
                }
                else
                {
                    delete pArea;
                }
            }
        }
        // stop searching once we have 3 acceptable candidates
        if ( aAreas.Count() >= 3 )
            break;
    }

    // find area with the highest connectivity
    CASW_Open_Area *pBestArea = NULL;
    for ( int i = 0; i < aAreas.Count(); i++ )
    {
        CASW_Open_Area *pArea = aAreas[i];
        if ( !pBestArea || pArea->m_nTotalLinks > pBestArea->m_nTotalLinks )
        {
            pBestArea = pArea;
        }
    }

    if ( pBestArea )
    {
        CBaseEntity *pAlien = SpawnAlienAt( "asw_shieldbug", pBestArea->m_pNode->GetPosition( nHull ), RandomAngle( 0, 360 ) );
        IASW_Spawnable_NPC *pSpawnable = dynamic_cast<IASW_Spawnable_NPC*>( pAlien );
        if ( pSpawnable )
        {
            pSpawnable->SetAlienOrders(AOT_SpreadThenHibernate, vec3_origin, NULL);
        }
        aAreas.PurgeAndDeleteElements();
        return true;
    }

    aAreas.PurgeAndDeleteElements();
    return false;
}
Пример #5
0
void CASW_Spawn_Manager::UpdateCandidateNodes()
{
    // don't update too frequently
    if ( m_CandidateUpdateTimer.HasStarted() && !m_CandidateUpdateTimer.IsElapsed() )
        return;

    m_CandidateUpdateTimer.Start( asw_candidate_interval.GetFloat() );

    if ( !GetNetwork() || !GetNetwork()->NumNodes() )
    {
        m_vecHordePosition = vec3_origin;
        if ( asw_director_debug.GetBool() )
            Msg("Error: Can't spawn hordes as this map has no node network\n");
        return;
    }

    CASW_Game_Resource *pGameResource = ASWGameResource();
    if ( !pGameResource )
        return;

    Vector vecSouthMarine = vec3_origin;
    Vector vecNorthMarine = vec3_origin;
    for ( int i=0; i<pGameResource->GetMaxMarineResources(); i++ )
    {
        CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i);
        if ( !pMR )
            continue;

        CASW_Marine *pMarine = pMR->GetMarineEntity();
        if ( !pMarine || pMarine->GetHealth() <= 0 )
            continue;

        if ( vecSouthMarine == vec3_origin || vecSouthMarine.y > pMarine->GetAbsOrigin().y )
        {
            vecSouthMarine = pMarine->GetAbsOrigin();
        }
        if ( vecNorthMarine == vec3_origin || vecNorthMarine.y < pMarine->GetAbsOrigin().y )
        {
            vecNorthMarine = pMarine->GetAbsOrigin();
        }
    }
    if ( vecSouthMarine == vec3_origin || vecNorthMarine == vec3_origin )		// no live marines
        return;

    int iNumNodes = GetNetwork()->NumNodes();
    m_northCandidateNodes.Purge();
    m_southCandidateNodes.Purge();
    for ( int i=0 ; i<iNumNodes; i++ )
    {
        CAI_Node *pNode = GetNetwork()->GetNode( i );
        if ( !pNode || pNode->GetType() != NODE_GROUND )
            continue;

        Vector vecPos = pNode->GetPosition( CANDIDATE_ALIEN_HULL );

        // find the nearest marine to this node
        float flDistance = 0;
        CASW_Marine *pMarine = dynamic_cast<CASW_Marine*>(UTIL_ASW_NearestMarine( vecPos, flDistance ));
        if ( !pMarine )
            return;

        if ( flDistance > asw_horde_max_distance.GetFloat() || flDistance < asw_horde_min_distance.GetFloat() )
            continue;

        // check node isn't in an exit trigger
        bool bInsideEscapeArea = false;
        for ( int d=0; d<m_EscapeTriggers.Count(); d++ )
        {
            if ( m_EscapeTriggers[d]->CollisionProp()->IsPointInBounds( vecPos ) )
            {
                bInsideEscapeArea = true;
                break;
            }
        }
        if ( bInsideEscapeArea )
            continue;

        if ( vecPos.y >= vecSouthMarine.y )
        {
            if ( asw_director_debug.GetInt() == 3 )
            {
                NDebugOverlay::Box( vecPos, -Vector( 5, 5, 5 ), Vector( 5, 5, 5 ), 32, 32, 128, 10, 60.0f );
            }
            m_northCandidateNodes.AddToTail( i );
        }
        if ( vecPos.y <= vecNorthMarine.y )
        {
            m_southCandidateNodes.AddToTail( i );
            if ( asw_director_debug.GetInt() == 3 )
            {
                NDebugOverlay::Box( vecPos, -Vector( 5, 5, 5 ), Vector( 5, 5, 5 ), 128, 32, 32, 10, 60.0f );
            }
        }
    }
}
Пример #6
0
// heuristic to find reasonably open space - searches for areas with high node connectivity
CASW_Open_Area* CASW_Spawn_Manager::FindNearbyOpenArea( const Vector &vecSearchOrigin, int nSearchHull )
{
    CBaseEntity *pStartEntity = gEntList.FindEntityByClassname( NULL, "info_player_start" );
    int iNumNodes = g_pBigAINet->NumNodes();
    CAI_Node *pHighestConnectivity = NULL;
    int nHighestLinks = 0;
    for ( int i=0 ; i<iNumNodes; i++ )
    {
        CAI_Node *pNode = g_pBigAINet->GetNode( i );
        if ( !pNode || pNode->GetType() != NODE_GROUND )
            continue;

        Vector vecPos = pNode->GetOrigin();
        float flDist = vecPos.DistTo( vecSearchOrigin );
        if ( flDist > 400.0f )
            continue;

        // discard if node is too near start location
        if ( pStartEntity && vecPos.DistTo( pStartEntity->GetAbsOrigin() ) < 1400.0f )  // NOTE: assumes all start points are clustered near one another
            continue;

        // discard if node is inside an escape area
        bool bInsideEscapeArea = false;
        for ( int d=0; d<m_EscapeTriggers.Count(); d++ )
        {
            if ( m_EscapeTriggers[d]->CollisionProp()->IsPointInBounds( vecPos ) )
            {
                bInsideEscapeArea = true;
                break;
            }
        }
        if ( bInsideEscapeArea )
            continue;

        // count links that drones could follow
        int nLinks = pNode->NumLinks();
        int nValidLinks = 0;
        for ( int k = 0; k < nLinks; k++ )
        {
            CAI_Link *pLink = pNode->GetLinkByIndex( k );
            if ( !pLink )
                continue;

            if ( !( pLink->m_iAcceptedMoveTypes[nSearchHull] & bits_CAP_MOVE_GROUND ) )
                continue;

            nValidLinks++;
        }
        if ( nValidLinks > nHighestLinks )
        {
            nHighestLinks = nValidLinks;
            pHighestConnectivity = pNode;
        }
        if ( asw_director_debug.GetBool() )
        {
            NDebugOverlay::Text( vecPos, UTIL_VarArgs( "%d", nValidLinks ), false, 10.0f );
        }
    }

    if ( !pHighestConnectivity )
        return NULL;

    // now, starting at the new node, find all nearby nodes with a minimum connectivity
    CASW_Open_Area *pArea = new CASW_Open_Area();
    pArea->m_vecOrigin = pHighestConnectivity->GetOrigin();
    pArea->m_pNode = pHighestConnectivity;
    int nMinLinks = nHighestLinks * 0.3f;
    nMinLinks = MAX( nMinLinks, 4 );

    pArea->m_aAreaNodes.AddToTail( pHighestConnectivity );
    if ( asw_director_debug.GetBool() )
    {
        Msg( "minLinks = %d\n", nMinLinks );
    }
    pArea->m_nTotalLinks = 0;
    for ( int i=0 ; i<iNumNodes; i++ )
    {
        CAI_Node *pNode = g_pBigAINet->GetNode( i );
        if ( !pNode || pNode->GetType() != NODE_GROUND )
            continue;

        Vector vecPos = pNode->GetOrigin();
        float flDist = vecPos.DistTo( pArea->m_vecOrigin );
        if ( flDist > 400.0f )
            continue;

        // discard if node is inside an escape area
        bool bInsideEscapeArea = false;
        for ( int d=0; d<m_EscapeTriggers.Count(); d++ )
        {
            if ( m_EscapeTriggers[d]->CollisionProp()->IsPointInBounds( vecPos ) )
            {
                bInsideEscapeArea = true;
                break;
            }
        }
        if ( bInsideEscapeArea )
            continue;

        // count links that drones could follow
        int nLinks = pNode->NumLinks();
        int nValidLinks = 0;
        for ( int k = 0; k < nLinks; k++ )
        {
            CAI_Link *pLink = pNode->GetLinkByIndex( k );
            if ( !pLink )
                continue;

            if ( !( pLink->m_iAcceptedMoveTypes[nSearchHull] & bits_CAP_MOVE_GROUND ) )
                continue;

            nValidLinks++;
        }
        if ( nValidLinks >= nMinLinks )
        {
            pArea->m_aAreaNodes.AddToTail( pNode );
            pArea->m_nTotalLinks += nValidLinks;
        }
    }
    // highlight and measure bounds
    Vector vecAreaMins = Vector( FLT_MAX, FLT_MAX, FLT_MAX );
    Vector vecAreaMaxs = Vector( -FLT_MAX, -FLT_MAX, -FLT_MAX );

    for ( int i = 0; i < pArea->m_aAreaNodes.Count(); i++ )
    {
        vecAreaMins = VectorMin( vecAreaMins, pArea->m_aAreaNodes[i]->GetOrigin() );
        vecAreaMaxs = VectorMax( vecAreaMaxs, pArea->m_aAreaNodes[i]->GetOrigin() );

        if ( asw_director_debug.GetBool() )
        {
            if ( i == 0 )
            {
                NDebugOverlay::Cross3D( pArea->m_aAreaNodes[i]->GetOrigin(), 20.0f, 255, 255, 64, true, 10.0f );
            }
            else
            {
                NDebugOverlay::Cross3D( pArea->m_aAreaNodes[i]->GetOrigin(), 10.0f, 255, 128, 0, true, 10.0f );
            }
        }
    }

    Vector vecArea = ( vecAreaMaxs - vecAreaMins );
    float flArea = vecArea.x * vecArea.y;

    if ( asw_director_debug.GetBool() )
    {
        Msg( "area mins = %f %f %f\n", VectorExpand( vecAreaMins ) );
        Msg( "area maxs = %f %f %f\n", VectorExpand( vecAreaMaxs ) );
        NDebugOverlay::Box( vec3_origin, vecAreaMins, vecAreaMaxs, 255, 128, 128, 10, 10.0f );
        Msg( "Total links = %d Area = %f\n", pArea->m_nTotalLinks, flArea );
    }

    return pArea;
}
//=========================================================
// Actualiza los mejores nodos para la creación de hijos.
//=========================================================
void CDirector_Manager::UpdateNodes()
{
	// Aún no toca actualizar.
	if ( CandidateUpdateTimer.HasStarted() && !CandidateUpdateTimer.IsElapsed() )
		return;

	// Empezar el cronometro.
	CandidateUpdateTimer.Start(director_update_nodes.GetFloat());

	// ¡Este mapa no tiene nodos!
	if ( !GetNetwork() || !GetNetwork()->NumNodes() )
	{
		ClientPrint(UTIL_InPlayerByIndex(1), HUD_PRINTCENTER, "#NoNODES");
		Warning("[NODOS] Este mapa no tiene nodos de movimiento.\r\n");
		return;
	}

	int iNumNodes = GetNetwork()->NumNodes();
	SpawnNodes.Purge(); // Limpiamos la lista de nodos.

	// Revisamos cada nodo.
	for ( int i = 0; i < iNumNodes; ++i )
	{
		CAI_Node *pNode = GetNetwork()->GetNode(i);

		// El nodo ya no existe o no es de suelo.
		if ( !pNode || pNode->GetType() != NODE_GROUND )
			continue;

		// Buscar al jugador más cercano.
		float flDistance	= 0;
		Vector vecPos		= pNode->GetPosition(HULL_HUMAN);
		CIN_Player *pPlayer = UTIL_GetNearestInPlayer(vecPos, flDistance);

		// ¡Ninguno!
		if ( !pPlayer )
			return;

		ConVarRef director_debug("director_debug");
		ConVarRef director_min_distance("director_min_distance");
		ConVarRef director_max_distance("director_max_distance");
		ConVarRef director_spawn_outview("director_spawn_outview");

		CBaseEntity *pChild = gEntList.FindEntityByNameNearest(CHILD_NAME, vecPos, 20);
		CBaseEntity *pSpawn = gEntList.FindEntityByClassname(NULL, "info_player_start");

		if ( pSpawn )
		{
			float spawnDistance = vecPos.DistTo(pSpawn->GetAbsOrigin());

			// Este nodo esta muy cerca del Spawn.
			if ( spawnDistance < SPAWN_OUT_DISTANCE )
				continue;
		}

		// Hay un hijo aquí.
		if ( pChild )
			continue;

		// Este nodo esta muy lejos o muy cerca.
		if ( flDistance > director_max_distance.GetFloat() || flDistance < director_min_distance.GetFloat() )
			continue;

		// No usar nodos que esten a la vista de los jugadores.
		if ( director_spawn_outview.GetBool() )
		{
			if ( UTIL_IsPlayersVisible(vecPos) )
				continue;
		}

		// Marcamos al nodo afortunado.
		if ( director_debug.GetBool() )
			NDebugOverlay::Box(vecPos, -Vector(5, 5, 5), Vector(5, 5, 5), 32, 32, 128, 10, 6.0f);

		// Lo agregamos a la lista.
		SpawnNodes.AddToTail(i);
	}
}
Пример #8
0
bool CASW_Spawn_Manager::SpawnRandomParasitePack( int nParasites )
{
	int iNumNodes = g_pBigAINet->NumNodes();
	if ( iNumNodes < 6 )
		return false;

	int nHull = HULL_TINY;
	CUtlVector<CASW_Open_Area*> aAreas;
	for ( int i = 0; i < 6; i++ )
	{
		CAI_Node *pNode = NULL;
		int nTries = 0;
		while ( nTries < 5 && ( !pNode || pNode->GetType() != NODE_GROUND ) )
		{
			pNode = g_pBigAINet->GetNode( RandomInt( 0, iNumNodes ) );
			nTries++;
		}

		if ( pNode )
		{
			CASW_Open_Area *pArea = FindNearbyOpenArea( pNode->GetOrigin(), HULL_MEDIUMBIG );
			if ( pArea && pArea->m_nTotalLinks > 30 )
			{
				// test if there's room to spawn a shieldbug at that spot
				if ( ValidSpawnPoint( pArea->m_pNode->GetPosition( nHull ), NAI_Hull::Mins( nHull ), NAI_Hull::Maxs( nHull ), true, false ) )
				{
					aAreas.AddToTail( pArea );
				}
				else
				{
					delete pArea;
				}
			}
		}
		// stop searching once we have 3 acceptable candidates
		if ( aAreas.Count() >= 3 )
			break;
	}

	// find area with the highest connectivity
	CASW_Open_Area *pBestArea = NULL;
	for ( int i = 0; i < aAreas.Count(); i++ )
	{
		CASW_Open_Area *pArea = aAreas[i];
		if ( !pBestArea || pArea->m_nTotalLinks > pBestArea->m_nTotalLinks )
		{
			pBestArea = pArea;
		}
	}

	if ( pBestArea )
	{
		for ( int i = 0; i < nParasites; i++ )
		{
			// raise the position by 12 units, a workaround for parasites
			// falling through displacements
			CBaseEntity *pAlien = SpawnAlienAt( "asw_parasite", pBestArea->m_pNode->GetPosition( nHull )  + Vector(0.f, 0.f, 12.f), RandomAngle( 0, 360 ) );
			IASW_Spawnable_NPC *pSpawnable = dynamic_cast<IASW_Spawnable_NPC*>( pAlien );
			if ( pSpawnable )
			{
				pSpawnable->SetAlienOrders(AOT_SpreadThenHibernate, vec3_origin, NULL);
			}
			if ( asw_director_debug.GetBool() && pAlien )
			{
				Msg( "Spawned parasite at %f %f %f\n", pAlien->GetAbsOrigin() );
				NDebugOverlay::Cross3D( pAlien->GetAbsOrigin(), 8.0f, 255, 0, 0, true, 20.0f );
			}
		}
		aAreas.PurgeAndDeleteElements();
		return true;
	}

	aAreas.PurgeAndDeleteElements();
	return false;
}