You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
473 lines
12 KiB
473 lines
12 KiB
//========= 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; i<m_chickenVector.Count(); ++i )
|
|
{
|
|
if ( m_chickenVector[i] != NULL )
|
|
{
|
|
++actualCount;
|
|
}
|
|
}
|
|
|
|
if ( actualCount < m_desiredChickenCount )
|
|
{
|
|
int need = m_desiredChickenCount - actualCount;
|
|
|
|
for( int k=0; k<need; ++k )
|
|
{
|
|
// find a good spot to spawn a chicken
|
|
CBaseEntity *chicken = NULL;
|
|
|
|
for( int attempts=0; attempts<10; ++attempts )
|
|
{
|
|
int which = RandomInt( 0, TheNavAreas.Count()-1 );
|
|
|
|
CNavArea *testArea = TheNavAreas[ which ];
|
|
|
|
const float tooSmall = 50.0f;
|
|
|
|
if ( testArea && testArea->GetSizeX() > 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; n<m_chickenVector.Count(); ++n )
|
|
{
|
|
if ( m_chickenVector[n] == NULL )
|
|
continue;
|
|
|
|
const float tooClose = 50.0f;
|
|
Vector between = m_chickenVector[n]->GetAbsOrigin() - 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<m_DMSpawnVector.Count(); ++i )
|
|
{
|
|
if ( m_DMSpawnVector[i] != NULL )
|
|
{
|
|
++actualCount;
|
|
}
|
|
}
|
|
|
|
// If the map isn't large enough to support as many spawns as m_desiredDMSpawns then shrink m_desiredDMSpawns to whatever we can get.
|
|
if ( m_consecutiveFailedAttempts >= 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();
|
|
}
|