void CASW_Spawn_Manager::Update()
{
	if ( m_iHordeToSpawn > 0 )
	{		
		if ( m_vecHordePosition != vec3_origin && ( !m_batchInterval.HasStarted() || m_batchInterval.IsElapsed() ) )
		{
			int iToSpawn = MIN( m_iHordeToSpawn, asw_max_alien_batch.GetInt() );
			int iSpawned = SpawnAlienBatch( asw_horde_class.GetString(), iToSpawn, m_vecHordePosition, m_angHordeAngle, 0 );
			if (asw_director_debug.GetInt() >= 4)
				Msg("spawned %d/%d %s (horde) at (%f, %f, %f)\n", iSpawned, m_iHordeToSpawn, asw_horde_class.GetString(), VectorExpand(m_vecHordePosition));
			m_iHordeToSpawn -= iSpawned;

			for (int i = 0; i < iSpawned; i++)
			{
				if (RandomFloat() < asw_horde_wanderers.GetFloat())
				{
					KeyValues *pWanderer = RandomWanderer();
					if (pWanderer)
					{
						FOR_EACH_TRUE_SUBKEY(pWanderer, pNPC)
						{
							if (V_stricmp(pNPC->GetName(), "NPC"))
							{
								Warning("Spawn Manager ignoring non-NPC key in WANDERER definition: %s\n", pNPC->GetName());
								continue;
							}

							const char *szAlienClass = pNPC->GetString("AlienClass");
							if (SpawnAlienAt(szAlienClass, m_vecHordePosition + Vector(0, 0, !V_stricmp(szAlienClass, "asw_buzzer") ? 128 : 32), m_angHordeAngle, pNPC))
							{
								if (asw_director_debug.GetInt() >= 4)
								{
									Msg("spawned %s (horde wanderer) at (%f, %f, %f)\n", szAlienClass, VectorExpand(m_vecHordePosition));
								}
							}
						}
					}
					else
					{
						const char *szAlienClass = asw_wanderer_class.GetString();
						if (SpawnAlienAt(szAlienClass, m_vecHordePosition + Vector(0, 0, !V_stricmp(szAlienClass, "asw_buzzer") ? 128 : 32), m_angHordeAngle))
						{
							if (asw_director_debug.GetInt() >= 4)
							{
								Msg("spawned %s (horde wanderer) at (%f, %f, %f)\n", szAlienClass, VectorExpand(m_vecHordePosition));
							}
						}
					}
				}
			}
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 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;
}
// spawn a group of aliens at the target point
int CASW_Spawn_Manager::SpawnAlienBatch( const char* szAlienClass, int iNumAliens, const Vector &vecPosition, const QAngle &angFacing, float flMarinesBeyondDist )
{

    int iSpawned = 0;
    bool bCheckGround = true;
    Vector vecMins = NAI_Hull::Mins(HULL_MEDIUMBIG);
    Vector vecMaxs = NAI_Hull::Maxs(HULL_MEDIUMBIG);
    GetAlienBounds( szAlienClass, vecMins, vecMaxs );

    float flAlienWidth = vecMaxs.x - vecMins.x;
    float flAlienDepth = vecMaxs.y - vecMins.y;

    // spawn one in the middle
    if ( ValidSpawnPoint( vecPosition, vecMins, vecMaxs, bCheckGround, flMarinesBeyondDist ) )
    {
        if ( SpawnAlienAt( szAlienClass, vecPosition, angFacing ) )
            iSpawned++;
    }

    // try to spawn a 5x5 grid of aliens, starting at the centre and expanding outwards
    Vector vecNewPos = vecPosition;
    for ( int i=1; i<=5 && iSpawned < iNumAliens; i++ )
    {
        QAngle angle = angFacing;
        angle[YAW] += RandomFloat( -20, 20 );
        // spawn aliens along top of box
        for ( int x=-i; x<=i && iSpawned < iNumAliens; x++ )
        {
            vecNewPos = vecPosition;
            vecNewPos.x += x * flAlienWidth;
            vecNewPos.y -= i * flAlienDepth;
            if ( !LineBlockedByGeometry( vecPosition, vecNewPos) && ValidSpawnPoint( vecNewPos, vecMins, vecMaxs, bCheckGround, flMarinesBeyondDist ) )
            {
                if ( SpawnAlienAt( szAlienClass, vecNewPos, angle ) )
                    iSpawned++;
            }
        }

        // spawn aliens along bottom of box
        for ( int x=-i; x<=i && iSpawned < iNumAliens; x++ )
        {
            vecNewPos = vecPosition;
            vecNewPos.x += x * flAlienWidth;
            vecNewPos.y += i * flAlienDepth;
            if ( !LineBlockedByGeometry( vecPosition, vecNewPos) && ValidSpawnPoint( vecNewPos, vecMins, vecMaxs, bCheckGround, flMarinesBeyondDist ) )
            {
                if ( SpawnAlienAt( szAlienClass, vecNewPos, angle ) )
                    iSpawned++;
            }
        }

        // spawn aliens along left of box
        for ( int y=-i + 1; y<i && iSpawned < iNumAliens; y++ )
        {
            vecNewPos = vecPosition;
            vecNewPos.x -= i * flAlienWidth;
            vecNewPos.y += y * flAlienDepth;
            if ( !LineBlockedByGeometry( vecPosition, vecNewPos) && ValidSpawnPoint( vecNewPos, vecMins, vecMaxs, bCheckGround, flMarinesBeyondDist ) )
            {
                if ( SpawnAlienAt( szAlienClass, vecNewPos, angle ) )
                    iSpawned++;
            }
        }

        // spawn aliens along right of box
        for ( int y=-i + 1; y<i && iSpawned < iNumAliens; y++ )
        {
            vecNewPos = vecPosition;
            vecNewPos.x += i * flAlienWidth;
            vecNewPos.y += y * flAlienDepth;
            if ( !LineBlockedByGeometry( vecPosition, vecNewPos) && ValidSpawnPoint( vecNewPos, vecMins, vecMaxs, bCheckGround, flMarinesBeyondDist ) )
            {
                if ( SpawnAlienAt( szAlienClass, vecNewPos, angle ) )
                    iSpawned++;
            }
        }
    }

    return iSpawned;
}
bool CASW_Spawn_Manager::SpawnAlientAtRandomNode()
{
    if ( spawnRandomAlienTimer.HasStarted() && !spawnRandomAlienTimer.IsElapsed() )
        return false;
    spawnRandomAlienTimer.Start( MINIMUM_SPAWN_DELAY );

    UpdateCandidateNodes();

    // decide if the alien is going to come from behind or in front
    bool bNorth = RandomFloat() < 0.7f;
    if ( m_northCandidateNodes.Count() <= 0 )
    {
        bNorth = false;
    }
    else if ( m_southCandidateNodes.Count() <= 0 )
    {
        bNorth = true;
    }

    CUtlVector<int> &candidateNodes = bNorth ? m_northCandidateNodes : m_southCandidateNodes;

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

    const char *szAlienClass = wandererQueue[wandererQueueStart];

    Vector vecMins, vecMaxs;
    GetAlienBounds( szAlienClass, vecMins, vecMaxs );

    int alienHull = getAlienHull(szAlienClass);

    int iMaxTries = 3;

    for ( int i=0 ; i<iMaxTries ; i++ )
    {
        int iChosen = RandomInt( 0, candidateNodes.Count() - 1);
        CAI_Node *pNode = GetNetwork()->GetNode( candidateNodes[iChosen] );
        if ( !pNode )
            continue;

        float flDistance = 0;
        CASW_Marine *pMarine = dynamic_cast<CASW_Marine*>(UTIL_ASW_NearestMarine( pNode->GetPosition( alienHull ), flDistance ));
        if ( !pMarine )
            return false;

        // check if there's a route from this node to the marine(s)
        AI_Waypoint_t *pRoute = ASWPathUtils()->BuildRoute( pNode->GetPosition( alienHull ), pMarine->GetAbsOrigin(), NULL, 100 );
        if ( !pRoute )
        {
            if ( asw_director_debug.GetBool() )
            {
                NDebugOverlay::Cross3D( pNode->GetOrigin(), 10.0f, 255, 128, 0, true, 20.0f );
            }
            continue;
        }

        if ( bNorth && UTIL_ASW_DoorBlockingRoute( pRoute, true ) )
        {
            DeleteRoute( pRoute );
            continue;
        }

        Vector vecSpawnPos = pNode->GetPosition( alienHull ) + Vector( 0, 0, 32 );
        bool needsGround = Q_stricmp(szAlienClass, "asw_buzzer") != 0;
        if ( ValidSpawnPoint( vecSpawnPos, vecMins, vecMaxs, needsGround, MARINE_NEAR_DISTANCE ) ) {
            float extraClearanceFactor = 1;
            //the boomer's hull seems to be too small for some reason
            if (Q_stricmp(szAlienClass, "asw_boomer") == 0) {
                extraClearanceFactor = 2;
            }

            Vector shiftSpawnBy = shiftSpawnPosition(vecSpawnPos, vecMins, vecMaxs, extraClearanceFactor);
            if (shiftSpawnBy.z == -1 ) {
                DeleteRoute( pRoute );
                continue;
            }

            Vector shiftedSpawnPos = vecSpawnPos+shiftSpawnBy;



            Vector testShift = shiftSpawnPosition(shiftedSpawnPos, vecMins, vecMaxs, extraClearanceFactor);
            if (testShift.x != 0 || testShift.y != 0 || testShift.z != 0) {
                DeleteRoute( pRoute );
                continue;
            }

            if ( SpawnAlienAt( szAlienClass, shiftedSpawnPos, vec3_angle ) ) {
                wandererQueueStart++;
                wandererQueueStart %= WANDERER_QUEUE_SIZE;
                if ( asw_director_debug.GetBool() )	{
                    NDebugOverlay::Cross3D( vecSpawnPos, 25.0f, 255, 255, 255, true, 20.0f );
                    float flDist;
                    CASW_Marine *pMarine = UTIL_ASW_NearestMarine( vecSpawnPos, flDist );
                    if ( pMarine ) {
                        NDebugOverlay::Line( pMarine->GetAbsOrigin(), vecSpawnPos, 64, 64, 64, true, 60.0f );
                    }
                }
                DeleteRoute( pRoute );
                return true;
            }
        }
        else
        {
            if ( asw_director_debug.GetBool() )
            {
                NDebugOverlay::Cross3D( vecSpawnPos, 25.0f, 255, 0, 0, true, 20.0f );
            }
        }
        DeleteRoute( pRoute );
    }
    return false;
}
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;
}
bool CASW_Spawn_Manager::SpawnAlientAtRandomNode()
{
	UpdateCandidateNodes();

	// decide if the alien is going to come from behind or in front
	bool bNorth = RandomFloat() < 0.7f;
	if ( m_northCandidateNodes.Count() <= 0 )
	{
		bNorth = false;
	}
	else if ( m_southCandidateNodes.Count() <= 0 )
	{
		bNorth = true;
	}

	CUtlVector<int> &candidateNodes = bNorth ? m_northCandidateNodes : m_southCandidateNodes;

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

	const char *szAlienClass = "asw_drone";
	Vector vecMins, vecMaxs;
	GetAlienBounds( szAlienClass, vecMins, vecMaxs );

	int iMaxTries = 1;
	for ( int i=0 ; i<iMaxTries ; i++ )
	{
		int iChosen = RandomInt( 0, candidateNodes.Count() - 1);
		CAI_Node *pNode = GetNetwork()->GetNode( candidateNodes[iChosen] );
		if ( !pNode )
			continue;

		float flDistance = 0;
		CASW_Marine *pMarine = dynamic_cast<CASW_Marine*>(UTIL_ASW_NearestMarine( pNode->GetPosition( CANDIDATE_ALIEN_HULL ), flDistance ));
		if ( !pMarine )
			return false;

		// check if there's a route from this node to the marine(s)
		AI_Waypoint_t *pRoute = ASWPathUtils()->BuildRoute( pNode->GetPosition( CANDIDATE_ALIEN_HULL ), pMarine->GetAbsOrigin(), NULL, 100 );
		if ( !pRoute )
		{
			if ( asw_director_debug.GetBool() )
			{
				NDebugOverlay::Cross3D( pNode->GetOrigin(), 10.0f, 255, 128, 0, true, 20.0f );
			}
			continue;
		}

		if ( bNorth && UTIL_ASW_DoorBlockingRoute( pRoute, true ) )
		{
			DeleteRoute( pRoute );
			continue;
		}

		// riflemod: preventing wanderers from spawning behind closed airlocks
		if (UTIL_ASW_BrushBlockingRoute(pRoute, MASK_PLAYERSOLID_BRUSHONLY, COLLISION_GROUP_PLAYER_MOVEMENT))
		{
			DeleteRoute(pRoute);
			continue;
		}
		
		Vector vecSpawnPos = pNode->GetPosition( CANDIDATE_ALIEN_HULL ) + Vector( 0, 0, 32 );
		if ( ValidSpawnPoint( vecSpawnPos, vecMins, vecMaxs, true, MARINE_NEAR_DISTANCE ) )
		{
			if ( SpawnAlienAt( szAlienClass, vecSpawnPos, vec3_angle ) )
			{
				if ( asw_director_debug.GetBool() )
				{
					NDebugOverlay::Cross3D( vecSpawnPos, 25.0f, 255, 255, 255, true, 20.0f );
					float flDist;
					CASW_Marine *pMarine = UTIL_ASW_NearestMarine( vecSpawnPos, flDist );
					if ( pMarine )
					{
						NDebugOverlay::Line( pMarine->GetAbsOrigin(), vecSpawnPos, 64, 64, 64, true, 60.0f );
					}
				}
				DeleteRoute( pRoute );
				return true;
			}
		}
		else
		{
			if ( asw_director_debug.GetBool() )
			{
				NDebugOverlay::Cross3D( vecSpawnPos, 25.0f, 255, 0, 0, true, 20.0f );
			}
		}
		DeleteRoute( pRoute );
	}
	return false;
}