コード例 #1
0
//-----------------------------------------------------------------------------
// CMaterialFileChangeWatcher implementation.
//-----------------------------------------------------------------------------
void CMaterialFileChangeWatcher::Init( CTextureSystem *pSystem, int context )
{
	m_pTextureSystem = pSystem;
	m_Context = context;

	m_Watcher.Init( this );
	
	char searchPaths[1024 * 16];
	if ( g_pFullFileSystem->GetSearchPath( "GAME", false, searchPaths, sizeof( searchPaths ) ) > 0 )
	{
		CUtlVector<char*> searchPathList;
		V_SplitString( searchPaths, ";", searchPathList );

		for ( int i=0; i < searchPathList.Count(); i++ )
		{
			m_Watcher.AddDirectory( searchPathList[i], "materials", true );
		}
		
		searchPathList.PurgeAndDeleteElements();
	}
	else
	{
		Warning( "Error in GetSearchPath. Dynamic material list updating will not be available." );
	}
}
コード例 #2
0
void CGEStats::SetAwardsInEvent( IGameEvent *pEvent )
{   
	// Ignore awards when only 1 person is playing
	if ( GEMPRules()->GetNumActivePlayers() < 2 )
		return;

	CUtlVector<GEStatSort*> vAwards;
	GEStatSort *award;
	int i;

	// Prevent divide by zero
	if ( m_iRoundCount == 0 )
		m_iRoundCount = 1;

	for ( i=0; i < GE_AWARD_GIVEMAX; i++ )
	{
		// Check for valid award
		if ( AwardIDToIdent(i) )
		{
			award = new GEStatSort;
			// See if we are going to give this one out, if so store it, if not erase the dummy var
			if ( GetAwardWinner(i, *award) )
				vAwards.AddToTail( award );
			else
				delete award;
		}
	}

	// Sort our ratios from High to Low
	vAwards.Sort( &CGEStats::StatSortHigh );

	char eventid[16], eventwinner[16];
	CBaseEntity *pPlayer;
	for ( i=0; i < vAwards.Count(); i++ )
	{
		// Never give out more than 6 awards
		if ( i == 6 )
			break;

		pPlayer = m_pPlayerStats[ vAwards[i]->idx ]->GetPlayer();
		if ( !pPlayer )
			continue;

		Q_snprintf( eventid, 16, "award%i_id", i+1 );
		Q_snprintf( eventwinner, 16, "award%i_winner", i+1 );

		pEvent->SetInt( eventid, vAwards[i]->m_iAward );
		pEvent->SetInt( eventwinner, pPlayer->entindex() );
	}

	vAwards.PurgeAndDeleteElements();
}
コード例 #3
0
//-----------------------------------------------------------------------------
// Pre-init
//-----------------------------------------------------------------------------
void CVConfigApp::PostShutdown()
{
	// Stop our message window
	ShutdownMessageWindow();

	// Clear our configs
	g_Configs.PurgeAndDeleteElements();

	// Stop file notifications
	UpdateConfigsStatus_Shutdown();

	BaseClass::PostShutdown();
}
コード例 #4
0
//-----------------------------------------------------------------------------
// Purpose: Searches for GameStartup*.mp3 files in the sound/ui folder and plays one
//-----------------------------------------------------------------------------
void CGameUI::PlayGameStartupSound()
{
#if defined( LEFT4DEAD )
	// L4D not using this path, L4D UI now handling with background menu movies
	return;
#endif

	if ( IsX360() )
		return;

	if ( CommandLine()->FindParm( "-nostartupsound" ) )
		return;

	FileFindHandle_t fh;

	CUtlVector<char *> fileNames;

	char path[ 512 ];
	Q_snprintf( path, sizeof( path ), "sound/ui/gamestartup*.mp3" );
	Q_FixSlashes( path );

	char const *fn = g_pFullFileSystem->FindFirstEx( path, "MOD", &fh );
	if ( fn )
	{
		do
		{
			char ext[ 10 ];
			Q_ExtractFileExtension( fn, ext, sizeof( ext ) );

			if ( !Q_stricmp( ext, "mp3" ) )
			{
				char temp[ 512 ];
				Q_snprintf( temp, sizeof( temp ), "ui/%s", fn );

				char *found = new char[ strlen( temp ) + 1 ];
				Q_strncpy( found, temp, strlen( temp ) + 1 );

				Q_FixSlashes( found );
				fileNames.AddToTail( found );
			}
	
			fn = g_pFullFileSystem->FindNext( fh );

		} while ( fn );

		g_pFullFileSystem->FindClose( fh );
	}

	// did we find any?
	if ( fileNames.Count() > 0 )
	{
		SYSTEMTIME SystemTime;
		GetSystemTime( &SystemTime );
		int index = SystemTime.wMilliseconds % fileNames.Count();

		if ( fileNames.IsValidIndex( index ) && fileNames[index] )
		{
			char found[ 512 ];

			// escape chars "*#" make it stream, and be affected by snd_musicvolume
			Q_snprintf( found, sizeof( found ), "play *#%s", fileNames[index] );

			engine->ClientCmd_Unrestricted( found );
		}

		fileNames.PurgeAndDeleteElements();
	}
}
コード例 #5
0
void CGECreateServer::OnCommand( const char *command )
{
	if ( !Q_stricmp(command, "play") )
	{
		CUtlVector<char*> commands;

		// Pull the values from our controls and apply them to commands and save off the choices
		for ( KeyValues *kv = m_kvCmdMap->GetFirstTrueSubKey(); kv; kv = kv->GetNextTrueSubKey() )
		{
			KeyValues *kv_value = m_kvCmdValues->FindKey( kv->GetName(), true );
			char *cmd = new char[128];

			try {
				if ( !Q_stricmp(kv->GetString("type"), "CHOICE") )
				{
					ComboBox *panel = dynamic_cast<ComboBox*>( FindChildByName(kv->GetName()) );

					const char *cmd_value = panel->GetActiveItemUserData()->GetName();
					kv_value->SetStringValue( cmd_value );

					if ( !Q_stricmp(cmd_value, RANDOM_VALUE) )
					{
						int idx = GERandom<int>( panel->GetItemCount()-1 ) + 1;
						idx = panel->GetItemIDFromRow( idx );
						cmd_value = panel->GetItemUserData( idx )->GetName();
					}

					Q_snprintf( cmd, 128, "%s \"%s\"", kv->GetString("cmd"), cmd_value );
					commands.AddToTail( cmd );
				}
				else if ( !Q_stricmp(kv->GetString("type"), "TEXT") )
				{
					char cmd_value[64];
					TextEntry *panel = dynamic_cast<TextEntry*>( FindChildByName(kv->GetName()) );
					panel->GetText( cmd_value, 64 );

					// We don't allow blank values... use default instead
					if ( !cmd_value[0] )
						Q_strncpy( cmd_value, kv->GetString("default",""), 64 );

					kv_value->SetStringValue( cmd_value );

					Q_snprintf( cmd, 128, "%s \"%s\"", kv->GetString("cmd"), cmd_value );
					commands.AddToTail( cmd );
				}
				else if ( !Q_stricmp(kv->GetString("type"), "BOOL") )
				{
					CheckButton *panel = dynamic_cast<CheckButton*>( FindChildByName(kv->GetName()) );
					
					if ( panel->IsSelected() ) {
						kv_value->SetStringValue( "1" );
						Q_snprintf( cmd, 128, "%s \"%s\"", kv->GetString("cmd"), kv->GetString("on_val","1") );
					} else {
						kv_value->SetStringValue( "0" );
						Q_snprintf( cmd, 128, "%s \"%s\"", kv->GetString("cmd"), kv->GetString("off_val","0") );
					}
				
					commands.AddToTail( cmd );
				}
				else
				{
					delete [] cmd;
				}
			} catch (...) {
				delete [] cmd;
			}
		}

		// Apply the commands
		for ( int i=0; i < commands.Count(); i++ )
			engine->ClientCmd_Unrestricted( commands[i] );
		
		// Save our last used settings to our custom file
		m_kvCmdValues->SaveToFile( filesystem, COMMAND_MAP_VAL, "MOD" );

		// Cleanup
		commands.PurgeAndDeleteElements();
	}

	SetVisible( false );
}
コード例 #6
0
void CGEObjectives::ClearAll( void )
{
	m_vObjectives.PurgeAndDeleteElements();
}
コード例 #7
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;
}
コード例 #8
0
//-----------------------------------------------------------------------------
// Purpose: Reload and re-parse our configuration data
//-----------------------------------------------------------------------------
void ReloadConfigs( bool bNoWarning /*= false*/ )
{
	g_Configs.PurgeAndDeleteElements();
	ParseConfigs();
	g_pMainFrame->PopulateConfigList( bNoWarning );
}
コード例 #9
0
void HandlePacket_LOOKING_FOR_WORKERS( bf_read &buf, const CIPAddr &ipFrom )
{
	// If we're downloading files for a job request, don't process any more "looking for workers" packets.
	if ( g_Waiting_hProcess )
		return;
	
	// This will be a nonzero-length string if patching.
	char versionString[512];
	buf.ReadString( versionString, sizeof( versionString ) );

	int iPort = buf.ReadShort();
	int iPriority = buf.ReadShort();

	// Make sure we don't run the same job more than once.
	if ( !CheckJobID( buf, g_CurJobID ) )
		return;

	CUtlVector<char*> newArgv;
	GetArgsFromBuffer( buf, newArgv, &g_Waiting_bShowAppWindow );

	bool bForcePatch = false;
	if ( buf.GetNumBytesLeft() >= 1 )
		bForcePatch = (buf.ReadByte() != 0);

	int iDownloaderPort = iPort;
	if  ( buf.GetNumBytesLeft() >= 2 )
		iDownloaderPort = buf.ReadShort();

	// Add these arguments after the executable filename to tell the program
	// that it's an MPI worker and who to connect to. 
	char strDownloaderIP[128], strMainIP[128];
	V_snprintf( strDownloaderIP, sizeof( strDownloaderIP ), "%d.%d.%d.%d:%d", ipFrom.ip[0], ipFrom.ip[1], ipFrom.ip[2], ipFrom.ip[3], iDownloaderPort );
	V_snprintf( strMainIP, sizeof( strMainIP ), "%d.%d.%d.%d:%d", ipFrom.ip[0], ipFrom.ip[1], ipFrom.ip[2], ipFrom.ip[3], iPort );

	// (-mpi is already on the command line of whoever ran the app).
	// AppendArg( commandLine, sizeof( commandLine ), "-mpi" );
	newArgv.InsertAfter( 0, CopyString( "-mpi_worker" ) );
	newArgv.InsertAfter( 1, CopyString( strDownloaderIP ) );


	// If the version string is set, then this is a patch.
	bool bPatching = false;
	if ( versionString[0] != 0 )
	{
		bPatching = true;
		
		// Check that we haven't applied this patch version yet. This case usually happens right after we've applied a patch
		// and we're restarting. The vmpi_transfer master is still pinging us telling us to patch, but we don't want to
		// reapply this patch.
		if ( atof( versionString ) <= atof( g_VersionString ) && !bForcePatch )
		{
			newArgv.PurgeAndDeleteElements();
			return;
		}
		
		// Ok, it's a new version. Get rid of whatever was running before.
		KillRunningProcess( "Starting a patch..", true );
	}
								 
	// If there's already a job running, only interrupt it if this new one has a higher priority.
	if ( WaitForProcessToExit() )
	{
		if ( iPriority > g_CurJobPriority )
		{
			KillRunningProcess( "Interrupted by a higher priority process", true );
		}
		else
		{
			// This means we're already running a job with equal to or greater priority than
			// the one that has been requested. We're going to ignore this request.
			newArgv.PurgeAndDeleteElements();
			return;
		}
	}

	// Responses go here.
	g_CurRespondAddr = ipFrom;
	
	// Also look for -mpi_ShowAppWindow in the args to the service.
	if ( !g_Waiting_bShowAppWindow && FindArg( __argc, __argv, "-mpi_ShowAppWindow" ) )
		g_Waiting_bShowAppWindow = true;

	// Copy all the files from the master and put them in our cache dir to run with.
	char cacheDir[MAX_PATH];
	if ( StartDownloadingAppFiles( newArgv, cacheDir, sizeof( cacheDir ), g_Waiting_bShowAppWindow, &g_Waiting_hProcess, bPatching ) )
	{
		// After it's downloaded, we want it to switch to the main connection port.
		if ( newArgv.Count() >= 3 && V_stricmp( newArgv[2], strDownloaderIP ) == 0 )
		{
			delete newArgv[2];
			newArgv[2] = CopyString( strMainIP );
		}
		
		g_Waiting_StartTime = Plat_FloatTime();
		g_Waiting_Argv.PurgeAndDeleteElements();
		g_Waiting_Argv = newArgv;
		g_Waiting_Priority = iPriority;
		g_Waiting_bPatching = bPatching;
		newArgv.Purge();
	}
	else
	{
		newArgv.PurgeAndDeleteElements();
	}

	// Remember that we tried to run this job so we don't try to run it again.
	AddJobMemory( g_CurJobID );
	
	SendStateToServicesBrowsers();
}
コード例 #10
0
// Returns true if the service was just patched and should exit.
bool CheckDownloaderFinished()
{
	if ( !g_Waiting_hProcess )
		return false;
	
	// Check if the downloader has timed out and kill it if necessary.
	if ( Plat_FloatTime() - g_Waiting_StartTime > MAX_DOWNLOADER_TIME_ALLOWED )
	{
		TerminateProcess( g_Waiting_hProcess, 1 );
		CloseHandle( g_Waiting_hProcess );
		g_Waiting_hProcess = NULL;
		return false;
	}

	// Check if it's done.
	if ( WaitForSingleObject( g_Waiting_hProcess, 0 ) != WAIT_OBJECT_0 )
		return false;

	CloseHandle( g_Waiting_hProcess );
	g_Waiting_hProcess = NULL;

	// Ok, it's done. Did it finish successfully?
	char testFilename[MAX_PATH];
	V_ComposeFileName( g_FileCachePath, "ReadyToGo.txt", testFilename, sizeof( testFilename ) );
	if ( _access( testFilename, 0 ) != 0 )
		return false;
		
	// Ok, the downloader finished successfully. Run the worker app.
	if ( g_bSuperDebugMode )
		AdjustSuperDebugArgs( g_Waiting_Argv );

	// Figure out the name of the master machine.
	V_strncpy( g_CurMasterName, "<unknown>", sizeof( g_CurMasterName ) );
	for ( int iArg=1; iArg < g_Waiting_Argv.Count()-1; iArg++ )
	{
		if ( stricmp( g_Waiting_Argv[iArg], "-mpi_MasterName" ) == 0 )
		{
			Q_strncpy( g_CurMasterName, g_Waiting_Argv[iArg+1], sizeof( g_CurMasterName ) );
		}
	}

	char DLLFilename[MAX_PATH];
	if ( FindArg( __argc, __argv, "-TryDLLMode" ) && 
		g_RunMode == RUNMODE_CONSOLE && 
		GetDLLFilename( g_Waiting_Argv, DLLFilename ) &&
		!g_Waiting_bPatching )
	{
		// This is just a helper for debugging. If it's VRAD, we can run it
		// in-process as a DLL instead of running it as a separate EXE.
		RunInDLL( DLLFilename, g_Waiting_Argv );
	}
	else
	{
		// Run the (hopefully!) MPI app they specified.
		RunProcessAtCommandLine( g_Waiting_Argv, g_Waiting_bShowAppWindow, g_Waiting_bPatching, g_Waiting_Priority );
		
		if ( g_Waiting_bPatching )
		{														
			// Tell any currently-running UI apps to patch themselves and quit ASAP so the installer can finish.
			SendPatchCommandToUIs( g_dwRunningProcessId );

			ResumeThread( g_hRunningThread ); // We started the installer suspended so we could make sure we'd send out the patch command.
			
			// We just ran the installer, but let's forget about it, otherwise we'll kill its process when we exit here.
			CloseHandle( g_hRunningProcess );
			CloseHandle( g_hRunningThread ) ;
			g_hRunningProcess = g_hRunningThread = NULL;
			g_RunningProcess_ExeName[0] = 0;
			g_RunningProcess_MapName[0] = 0;

			ServiceHelpers_ExitEarly();
			return true;
		}
	}
	
	g_Waiting_Argv.PurgeAndDeleteElements();
	return false;
}
コード例 #11
0
bool CGEStats::GetFavoriteWeapon( int iPlayer, GEWeaponSort &fav )
{
	if ( iPlayer < 0 || iPlayer >= m_pPlayerStats.Count() )
		return false;

	GEPlayerStats *stats = m_pPlayerStats[iPlayer];
	CBasePlayer *pPlayer = ToBasePlayer( m_pPlayerStats[iPlayer]->GetPlayer() );

	if ( !pPlayer || !stats )
		return false;

	// We want raw kills, NOT score
	int playerFrags = pPlayer->FragCount();
	float roundtime = gpGlobals->curtime - m_flRoundStartTime;
	// Avoid divide by zero!
	if ( roundtime == 0.0f )
		roundtime = 0.5f;

	CUtlVector<GEWeaponSort*> weapons;
	float killPercent;

	for ( int i=WEAPON_NONE+1; i < WEAPON_RANDOM; i++ ) // Go up to WEAPON_RANDOM to exclude tokens.
	{
		GEWeaponSort *entry = new GEWeaponSort;
		if ( GEGameplay()->IsInFinalIntermission() )
		{
			entry->m_iPercentage = stats->m_iMatchWeapons[i];
		}
		else
		{
			// Avoid divide by zero!
			if ( playerFrags == 0 )
				killPercent = 0.0f;
			else
				killPercent = (float)stats->m_iWeaponsKills[i] / (float)playerFrags;

			if ( roundtime < 1.0f )
				entry->m_iPercentage = 0;
			else
				entry->m_iPercentage = 100 * ( ((float)stats->m_iWeaponsHeldTime[i] / roundtime) + killPercent );
		}
		
		entry->m_iWeaponID = i;
		weapons.AddToTail( entry );
	}

	weapons.Sort( &CGEStats::WeaponSort );

	// Only give them a fav weapon if they have a percentage
	fav.m_iPercentage = weapons[0]->m_iPercentage;
	if ( fav.m_iPercentage )
		fav.m_iWeaponID = weapons[0]->m_iWeaponID;
	else
		fav.m_iWeaponID = WEAPON_NONE;

#ifdef _DEBUG
	DevMsg( "%s's weapon stats:\n", pPlayer->GetPlayerName() );
	DevMsg( "Frags: %i, Roundtime: %0.2f\n", playerFrags, roundtime );
	for ( int i=0; i < WEAPON_MAX-2; i++ )
	{
		if ( weapons[i]->m_iPercentage )
			DevMsg( "%s : %i\n", WeaponIDToAlias( weapons[i]->m_iWeaponID ), weapons[i]->m_iPercentage );
	}
	DevMsg( "Favorite Weapon: %s\n", WeaponIDToAlias( fav.m_iWeaponID ) );
#endif

	weapons.PurgeAndDeleteElements();

	return true;
}
コード例 #12
0
bool CGEStats::GetAwardWinner( int iAward, GEStatSort &winner )
{
	CUtlVector<GEStatSort*> stats;
	if ( GEMPRules()->GetNumActivePlayers() < 2 )
		return false;

	// Reset any previous inclinations
	winner.m_iAward = -1;
	winner.m_iStat = 0;

	CGEPlayer *pPlayer = NULL;
	// First parse out all the award to player information
	for ( int i=0; i < m_pPlayerStats.Count(); i++ )
	{
		// Don't try to give awards to invalid players
		pPlayer = m_pPlayerStats[i]->GetPlayer();
		if ( !pPlayer )
			continue;
		if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR )
			continue;

		GEStatSort *entry = new GEStatSort;
		entry->idx = i;

		// If this is the last report, then take the match stats, average them over the rounds, and use that as our stat
		if ( GEGameplay()->IsInFinalIntermission() )
			entry->m_iStat = m_pPlayerStats[i]->GetMatchStat( iAward ) / m_iRoundCount;
		else
			entry->m_iStat = m_pPlayerStats[i]->GetRoundStat( iAward );

		stats.AddToTail( entry );
	}

	// Make sure after our checks we have at least two players to do awards for
	if ( stats.Count() < 2 )
		return false;

	float playerrat = (float)(stats.Count() + GE_STATS_PLAYERRATIO) / (float)stats.Count();

	if ( GetAwardSort(iAward) == GE_SORT_HIGH )
		stats.Sort( &CGEStats::StatSortHigh );
	else
	{
		stats.Sort( &CGEStats::StatSortLow );
		// Since we are sorting low we have to invert our player ratio
		playerrat = 1 / playerrat;
	}


	// Prevent divide by zero and inflation
	if ( stats[1]->m_iStat == 0 )
		stats[1]->m_iStat = 1;

	float statrat = (float)stats[0]->m_iStat / (float)stats[1]->m_iStat;
	
	// Low sort should have a ratio less than the inverted playerrat
	if ( GetAwardSort(iAward) == GE_SORT_HIGH ? statrat > playerrat : statrat < playerrat )
	{
		// Prevent divide by zero
		if ( statrat == 0 )
			statrat = 1;

		winner.idx = stats[0]->idx;
		winner.m_iStat = (GetAwardSort(iAward) == GE_SORT_HIGH ? statrat : 1/statrat) * 1000;  // 3 decimal precision
		winner.m_iAward = iAward;

		stats.PurgeAndDeleteElements();
		return true;
	}

	stats.PurgeAndDeleteElements();
	return false;
}
コード例 #13
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;
}