//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// // NavMesh.cpp // Implementation of Navigation Mesh interface // Author: Michael S. Booth, 2003-2004 #include "cbase.h" #include "filesystem.h" #include "cs_nav_mesh.h" #include "cs_nav_node.h" #include "cs_nav_area.h" #include "cs_nav_pathfind.h" #include "cs_shareddefs.h" #include "cs_gamerules.h" #include "fmtstr.h" #include "utlbuffer.h" #include "bot_util.h" #include "tier0/vprof.h" #include "cs_bot_manager.h" #ifndef CLIENT_DLL #include "mapinfo.h" #endif class CFuncBlockDMSpawns : public CFuncBrush { DECLARE_CLASS( CFuncBlockDMSpawns, CFuncBrush ); void Spawn( void ) { SetSolid( SOLID_BSP ); AddSolidFlags( FSOLID_NOT_SOLID ); SetModel( STRING( GetModelName() ) ); // set size and link into world extern ConVar showtriggers; if ( !showtriggers.GetInt() ) { AddEffects( EF_NODRAW ); } } }; LINK_ENTITY_TO_CLASS( func_block_dm_spawns, CFuncBlockDMSpawns ); extern ConVar mp_randomspawn; ConVar mp_guardian_target_site( "mp_guardian_target_site", "-1", FCVAR_RELEASE | FCVAR_GAMEDLL, "If set to the index of a bombsite, will cause random spawns to be only created near that site." ); //-------------------------------------------------------------------------------------------------------------- CSNavMesh::CSNavMesh( void ) { m_desiredChickenCount = 0; } //-------------------------------------------------------------------------------------------------------------- CSNavMesh::~CSNavMesh() { } CNavArea * CSNavMesh::CreateArea( void ) const { return new CCSNavArea; } //------------------------------------------------------------------------- void CSNavMesh::BeginCustomAnalysis( bool bIncremental ) { } //------------------------------------------------------------------------- // invoked when custom analysis step is complete void CSNavMesh::PostCustomAnalysis( void ) { } //------------------------------------------------------------------------- void CSNavMesh::EndCustomAnalysis() { } //------------------------------------------------------------------------- /** * Returns sub-version number of data format used by derived classes */ unsigned int CSNavMesh::GetSubVersionNumber( void ) const { // 1: initial implementation - added ApproachArea data return 1; } //------------------------------------------------------------------------- /** * Store custom mesh data for derived classes */ void CSNavMesh::SaveCustomData( CUtlBuffer &fileBuffer ) const { } //------------------------------------------------------------------------- /** * Load custom mesh data for derived classes */ void CSNavMesh::LoadCustomData( CUtlBuffer &fileBuffer, unsigned int subVersion ) { } //-------------------------------------------------------------------------------------------------------------- /** * Reset the Navigation Mesh to initial values */ void CSNavMesh::Reset( void ) { m_refreshChickenTimer.Start( 5.0f ); m_refreshDMSpawnTimer.Start( 1.0f ); CNavMesh::Reset(); } //-------------------------------------------------------------------------------------------------------------- /** * Zero player counts in all areas */ void CSNavMesh::ClearPlayerCounts( void ) { FOR_EACH_VEC( TheNavAreas, it ) { CCSNavArea *area = (CCSNavArea*)TheNavAreas[ it ]; area->ClearPlayerCount(); } } //-------------------------------------------------------------------------------------------------------------- // Keep desired number of chickens alive in map void CSNavMesh::MaintainChickenPopulation( void ) { if ( m_desiredChickenCount > 0 && m_refreshChickenTimer.IsElapsed() ) { m_refreshChickenTimer.Start( RandomFloat( 10.0f, 20.0f ) ); int actualCount = 0; for( int i=0; iGetSizeX() > tooSmall && testArea->GetSizeY() > tooSmall ) { if ( !UTIL_IsVisibleToTeam( testArea->GetCenter(), TEAM_CT ) && !UTIL_IsVisibleToTeam( testArea->GetCenter(), TEAM_TERRORIST ) ) { // don't spawn a chicken on top of another chicken int n; for( n=0; nGetAbsOrigin() - testArea->GetCenter(); if ( between.IsLengthLessThan( tooClose ) ) break; } if ( n >= m_chickenVector.Count() ) { // found a good spot - spawn a chicken here chicken = CreateEntityByName( "chicken" ); if ( chicken ) { chicken->SetAbsOrigin( testArea->GetCenter() ); DispatchSpawn( chicken ); m_chickenVector.AddToTail( chicken ); } break; } } } } if ( !chicken ) { // couldn't spawn a chicken - try again later return; } } } } } //-------------------------------------------------------------------------------------------------------------- void CSNavMesh::Update( void ) { MaintainChickenPopulation(); MaintainDMSpawnPopulation(); CNavMesh::Update(); } NavErrorType CSNavMesh::Load( void ) { return CNavMesh::Load(); } bool CSNavMesh::Save( void ) const { return CNavMesh::Save(); } NavErrorType CSNavMesh::PostLoad( unsigned int version ) { if ( CSGameRules()->IsPlayingGunGameDeathmatch() ) m_desiredChickenCount = 10; else m_desiredChickenCount = g_pMapInfo ? g_pMapInfo->m_iPetPopulation : 0; m_chickenVector.RemoveAll(); if ( CSGameRules() && CSGameRules()->IsPlayingCooperativeGametype() ) { // in Guardian mode, remove all of the mapper placed DM spawn points CBaseEntity *pEntity = NULL; while ( ( pEntity = gEntList.FindEntityByClassname( pEntity, "info_deathmatch_spawn" ) ) != NULL ) { UTIL_Remove( pEntity ); } } ResetDMSpawns(); return CNavMesh::PostLoad(version); } CON_COMMAND( dm_reset_spawns, "" ) { ( dynamic_cast< CSNavMesh* >( TheNavMesh ) )->ResetDMSpawns(); DevMsg( "Manually resetting deathmatch spawns.\n"); } void CSNavMesh::ResetDMSpawns( void ) { FOR_EACH_VEC( m_DMSpawnVector, i ) { UTIL_Remove( m_DMSpawnVector[i] ); } m_desiredDMSpawns = 50; m_consecutiveFailedAttempts = 0; m_DMSpawnVector.RemoveAll(); MaintainDMSpawnPopulation(); } void CSNavMesh::MaintainDMSpawnPopulation( void ) { if ( mp_randomspawn.GetInt() == 0 ) return; bool bDisableAutoGeneratedDMSpawns = ( g_pMapInfo ? g_pMapInfo->m_bDisableAutoGeneratedDMSpawns : false ); // HACK: Force this to run in guardian, even if the map explicitly placed dm spawns if ( bDisableAutoGeneratedDMSpawns && !CSGameRules()->IsPlayingCoopGuardian() ) return; if ( m_desiredDMSpawns > 0 && m_refreshDMSpawnTimer.IsElapsed() ) { m_refreshDMSpawnTimer.Start( 1.0f ); int actualCount = 0; for( int i=0; i= 100 ) { m_desiredDMSpawns = actualCount; m_consecutiveFailedAttempts = 0; Warning( "Giving up attempts to make more random spawn points. Current count: %i.\n", actualCount ); } if ( actualCount < m_desiredDMSpawns ) { // float calctime = Plat_FloatTime(); MEASURE PERF 1/2 int need = m_desiredDMSpawns - actualCount; int spawnsToTry = max( 10, need ); for( int k=0; k < spawnsToTry; k++ ) { // find a good spot to spawn a Deathmatch spawnpoint CBaseEntity *DMSpawn = NULL; m_consecutiveFailedAttempts++; for( int attempts = 0; attempts < 10; attempts++ ) { CNavArea *testArea = NULL; if ( mp_guardian_target_site.GetInt() >= 0 ) { const CCSBotManager::Zone *zone = TheCSBots()->GetZone( mp_guardian_target_site.GetInt() ); if ( zone ) { testArea = zone->m_area[ RandomInt( 0, zone->m_areaCount - 1 ) ]; } } else { testArea = TheNavAreas[ RandomInt( 0, TheNavAreas.Count() - 1 ) ]; } const float tooSmall = ( mp_guardian_target_site.GetInt() >= 0 ) ? 35.0f : 100.0f; if ( testArea && testArea->GetSizeX() > tooSmall && testArea->GetSizeY() > tooSmall ) { bool bBadNavArea = false; // don't spawn a DMSpawn too close to another DMSpawn for( int n = 0; n < m_DMSpawnVector.Count(); n++ ) { if ( m_DMSpawnVector[n] == NULL ) continue; float tooClose = ( mp_guardian_target_site.GetInt() >= 0 ) ? 50.0f : 300.0f; Vector between = m_DMSpawnVector[n]->GetAbsOrigin() - testArea->GetCenter(); if ( between.IsLengthLessThan( tooClose ) ) { bBadNavArea = true; break; } } if ( bBadNavArea ) continue; if ( IsSpawnBlockedByTrigger( testArea->GetCenter() + Vector( 0, 0, 16 ) ) ) continue; // check that we can path from the nav area to a ct spawner to confirm it isn't orphaned. CBaseEntity *CTSpawn = gEntList.FindEntityByClassname( NULL, "info_player_counterterrorist" ); if ( CTSpawn ) { CNavArea *CTSpawnArea = GetNearestNavArea( CTSpawn->GetAbsOrigin() ); ShortestPathCost cost; bool bNotOrphaned = NavAreaBuildPath( testArea, CTSpawnArea, NULL, cost ); if ( !bNotOrphaned ) { const char* szTSpawnEntName = "info_player_terrorist"; if ( CSGameRules()->IsPlayingCoopMission() ) szTSpawnEntName = "info_enemy_terrorist_spawn"; // double check that we can path from the nav area to a t spawner to confirm it isn't orphaned. CBaseEntity *TSpawn = gEntList.FindEntityByClassname( NULL, szTSpawnEntName ); if ( TSpawn ) { CNavArea *TSpawnArea = GetNearestNavArea( TSpawn->GetAbsOrigin() ); ShortestPathCost cost2; bNotOrphaned = NavAreaBuildPath( testArea, TSpawnArea, NULL, cost2 ); if ( !bNotOrphaned ) continue; } } } // found a good spot - spawn a DMSpawn here DMSpawn = CreateEntityByName( "info_deathmatch_spawn" ); if ( DMSpawn ) { DMSpawn->SetAbsOrigin( testArea->GetCenter() + Vector( 0, 0, 16 )); DispatchSpawn( DMSpawn ); m_DMSpawnVector.AddToTail( DMSpawn ); m_consecutiveFailedAttempts = 0; // Msg( " %f :CREATED DM SPAWN %i.\n", gpGlobals->curtime, m_DMSpawnVector.Count() ); } break; } } } // MEASURE PERF 2/2 // calctime = Plat_FloatTime() - calctime; // Msg( "DM SPAWN SEARCH CALC TIME: %f\n", calctime ); } } } bool CSNavMesh::IsSpawnBlockedByTrigger( Vector pos ) { CBaseEntity *list[32]; Ray_t ray; ray.Init( pos, pos + Vector( 1,1,1 ) ); int nCount = AllEdictsAlongRay( list, 1024, ray, 0 ); for ( int i = 0; i < nCount; i++ ) { if ( FClassnameIs( list[i], "func_block_dm_spawns" ) ) return true; } return false; } int CSNavMesh::AllEdictsAlongRay( CBaseEntity **pList, int listMax, const Ray_t &ray, int flagMask ) { CFlaggedEntitiesEnum rayEnum( pList, listMax, flagMask ); ::partition->EnumerateElementsAlongRay( PARTITION_ENGINE_NON_STATIC_EDICTS, ray, false, &rayEnum ); return rayEnum.GetCount(); }