Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.
 
 
 
 
 
 

537 lines
16 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_nav_area.h
// TF specific nav area
// Michael Booth, February 2009
#include "cbase.h"
#include "tf_nav_mesh.h"
#include "tf_nav_area.h"
#include "tf_gamerules.h"
#include "bot/tf_bot.h"
#include "nav_pathfind.h"
ConVar tf_nav_show_incursion_distance( "tf_nav_show_incursion_distance", "0", FCVAR_CHEAT, "Display travel distances from current spawn room (1=red, 2=blue)" );
ConVar tf_nav_show_bomb_target_distance( "tf_nav_show_bomb_target_distance", "0", FCVAR_CHEAT, "Display travel distances to bomb target (MvM mode)" );
ConVar tf_nav_show_turf_ownership( "tf_nav_show_turf_ownership", "0", FCVAR_CHEAT, "Color nav area by smallest incursion distance" );
ConVar tf_nav_in_combat_duration( "tf_nav_in_combat_duration", "30", FCVAR_CHEAT, "How long after gunfire occurs is this area still considered to be 'in combat'" );
ConVar tf_nav_combat_build_rate( "tf_nav_combat_build_rate", "0.05", FCVAR_CHEAT, "Gunfire/second increase (combat caps at 1.0)" );
ConVar tf_nav_combat_decay_rate( "tf_nav_combat_decay_rate", "0.022", FCVAR_CHEAT, "Decay/second toward zero" );
ConVar tf_show_sniper_areas( "tf_show_sniper_areas", "0", FCVAR_CHEAT );
ConVar tf_show_sniper_areas_safety_range( "tf_show_sniper_areas_safety_range", "1000", FCVAR_CHEAT );
ConVar tf_show_incursion_range( "tf_show_incursion_range", "0", FCVAR_CHEAT, "1 = red, 2 = blue" );
ConVar tf_show_incursion_range_min( "tf_show_incursion_range_min", "0", FCVAR_CHEAT, "Highlight areas with incursion distances between min and max cvar values" );
ConVar tf_show_incursion_range_max( "tf_show_incursion_range_max", "0", FCVAR_CHEAT, "Highlight areas with incursion distances between min and max cvar values" );
//------------------------------------------------------------------------------------------------
CTFNavArea::CTFNavArea( void )
{
m_attributeFlags = 0;
m_wanderCount = 0;
m_combatIntensity = 0.0f;
m_distanceToBombTarget = 0.0f;
m_TFMark = 0;
m_invasionSearchMarker = (unsigned int)-1;
}
//------------------------------------------------------------------------------------------------
/**
* (EXTEND) invoked when map is initially loaded
*/
void CTFNavArea::OnServerActivate( void )
{
BaseClass::OnServerActivate();
ClearAllPotentiallyVisibleActors();
}
//------------------------------------------------------------------------------------------------
/**
* (EXTEND) invoked for each area when the round restarts
*/
void CTFNavArea::OnRoundRestart( void )
{
BaseClass::OnRoundRestart();
ClearAllPotentiallyVisibleActors();
m_combatIntensity = 0.0f;
}
//------------------------------------------------------------------------------------------------
/**
* For game-specific analysis
*/
void CTFNavArea::CustomAnalysis( bool isIncremental )
{
}
//------------------------------------------------------------------------------------------------
/**
* Draw area for debugging & editing
*/
void CTFNavArea::Draw( void ) const
{
CNavArea::Draw();
#ifdef TF_RAID_MODE
if ( TFGameRules()->IsRaidMode() && m_wanderCount > 0 )
{
NDebugOverlay::Text( GetCenter(), UTIL_VarArgs( "%d", m_wanderCount ), false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
#endif // TF_RAID_MODE
if ( tf_nav_show_incursion_distance.GetBool() )
{
NDebugOverlay::Text( GetCenter(), UTIL_VarArgs( "R:%3.1f B:%3.1f", GetIncursionDistance( TF_TEAM_RED ), GetIncursionDistance( TF_TEAM_BLUE ) ), false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
if ( tf_nav_show_bomb_target_distance.GetBool() )
{
NDebugOverlay::Text( GetCenter(), UTIL_VarArgs( "%3.1f", GetTravelDistanceToBombTarget() ), false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
if ( tf_show_sniper_areas.GetBool() )
{
bool redSniper = IsAwayFromInvasionAreas( TF_TEAM_RED, tf_show_sniper_areas_safety_range.GetFloat() );
bool blueSniper = IsAwayFromInvasionAreas( TF_TEAM_BLUE, tf_show_sniper_areas_safety_range.GetFloat() );
if ( blueSniper )
{
if ( redSniper )
{
// both teams like this spot?
DrawFilled( 255, 0, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
else
{
// blue sniper area
DrawFilled( 0, 0, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
}
else if ( redSniper )
{
// red sniper area
DrawFilled( 255, 0, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
}
int rangeTeam = tf_show_incursion_range.GetInt();
if ( rangeTeam > 0 )
{
rangeTeam += ( TF_TEAM_RED - 1);
float range = GetIncursionDistance( rangeTeam );
if ( range >= tf_show_incursion_range_min.GetFloat() && range <= tf_show_incursion_range_max.GetFloat() )
{
DrawFilled( 0, 255, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
}
}
//------------------------------------------------------------------------------------------------
/**
* Return adjacent area with largest increase in incursion distance
*/
CTFNavArea *CTFNavArea::GetNextIncursionArea( int team ) const
{
CTFNavArea *nextIncursionArea = NULL;
float nextIncursionDistance = GetIncursionDistance( team );
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
{
const NavConnectVector *adjVector = GetAdjacentAreas( (NavDirType)dir );
FOR_EACH_VEC( (*adjVector), bit )
{
CTFNavArea *adjArea = static_cast< CTFNavArea * >( (*adjVector)[ bit ].area );
if ( adjArea->GetIncursionDistance( team ) > nextIncursionDistance )
{
nextIncursionArea = adjArea;
nextIncursionDistance = adjArea->GetIncursionDistance( team );
}
}
}
return nextIncursionArea;
}
//-----------------------------------------------------------------------------
// Populate 'priorVector' with a collection of adjacent areas that have a lower incursion distance that this area
void CTFNavArea::CollectPriorIncursionAreas( int team, CUtlVector< CTFNavArea * > *priorVector )
{
float myIncursionDistance = GetIncursionDistance( team );
priorVector->RemoveAll();
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
{
const NavConnectVector *adjVector = GetAdjacentAreas( (NavDirType)dir );
FOR_EACH_VEC( (*adjVector), bit )
{
CTFNavArea *adjArea = static_cast< CTFNavArea * >( (*adjVector)[ bit ].area );
if ( adjArea->GetIncursionDistance( team ) < myIncursionDistance )
{
priorVector->AddToTail( adjArea );
}
}
}
}
//-----------------------------------------------------------------------------
// Populate 'priorVector' with a collection of adjacent areas that have a higher incursion distance that this area
void CTFNavArea::CollectNextIncursionAreas( int team, CUtlVector< CTFNavArea * > *priorVector )
{
float myIncursionDistance = GetIncursionDistance( team );
priorVector->RemoveAll();
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
{
const NavConnectVector *adjVector = GetAdjacentAreas( (NavDirType)dir );
FOR_EACH_VEC( (*adjVector), bit )
{
CTFNavArea *adjArea = static_cast< CTFNavArea * >( (*adjVector)[ bit ].area );
if ( adjArea->GetIncursionDistance( team ) > myIncursionDistance )
{
priorVector->AddToTail( adjArea );
}
}
}
}
//-----------------------------------------------------------------------------
/**
* Return true if this area is at least safetyRange units away from all invasion areas
*/
bool CTFNavArea::IsAwayFromInvasionAreas( int myTeam, float safetyRange ) const
{
const CUtlVector< CTFNavArea * > &invasionVector = GetEnemyInvasionAreaVector( myTeam );
FOR_EACH_VEC( invasionVector, vit )
{
CTFNavArea *invasionArea = invasionVector[ vit ];
if ( ( invasionArea->GetCenter() - GetCenter() ).IsLengthLessThan( safetyRange ) )
{
// too close to incoming enemy route to snipe
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
class MarkVisibleSet
{
public:
MarkVisibleSet( unsigned int marker )
{
m_marker = marker;
}
bool operator() ( CNavArea *baseArea )
{
CTFNavArea *area = static_cast< CTFNavArea * >( baseArea );
area->SetInvasionSearchMarker( m_marker );
return true;
}
unsigned int m_marker;
};
//-----------------------------------------------------------------------------
class CollectInvasionAreas
{
public:
CollectInvasionAreas( unsigned int marker, CTFNavArea *homeArea, CUtlVector< CTFNavArea * > *redInvasionAreaVector, CUtlVector< CTFNavArea * > *blueInvasionAreaVector )
{
m_homeArea = homeArea;
m_visibleMarker = marker;
m_redInvasionAreaVector = redInvasionAreaVector;
m_blueInvasionAreaVector = blueInvasionAreaVector;
}
void FilterArea( CTFNavArea *area, CTFNavArea *adjArea )
{
if ( adjArea->IsInvasionSearchMarked( m_visibleMarker ) )
{
// also in PVS - can't be invasion area
return;
}
const float behindTolerance = 100.0;
// adjacent area is not in PVS, test if adjacent area not penetrated as far, if so it is an invasion area
if ( area->GetIncursionDistance( TF_TEAM_BLUE ) > adjArea->GetIncursionDistance( TF_TEAM_BLUE ) )
{
if ( area->GetIncursionDistance( TF_TEAM_BLUE ) > m_homeArea->GetIncursionDistance( TF_TEAM_BLUE ) + behindTolerance )
{
// this area is farther "in" than we are - don't search further
return;
}
m_redInvasionAreaVector->AddToTail( adjArea );
}
if ( area->GetIncursionDistance( TF_TEAM_RED ) > adjArea->GetIncursionDistance( TF_TEAM_RED ) )
{
if ( area->GetIncursionDistance( TF_TEAM_RED ) > m_homeArea->GetIncursionDistance( TF_TEAM_RED ) + behindTolerance )
{
// this area is farther "in" than we are - don't search further
return;
}
m_blueInvasionAreaVector->AddToTail( adjArea );
}
}
bool operator() ( CNavArea *baseArea )
{
CTFNavArea *area = static_cast< CTFNavArea * >( baseArea );
// explore adjacent floor areas
int dir;
for( dir=0; dir<NUM_DIRECTIONS; ++dir )
{
int count = area->GetAdjacentCount( (NavDirType)dir );
for( int i=0; i<count; ++i )
{
CTFNavArea *adjArea = static_cast< CTFNavArea * >( area->GetAdjacentArea( (NavDirType)dir, i ) );
FilterArea( area, adjArea );
}
}
// include areas that connect TO this area via a one-way link, since the enemy is coming TO us
for( dir=0; dir<NUM_DIRECTIONS; ++dir )
{
const NavConnectVector *list = area->GetIncomingConnections( (NavDirType)dir );
FOR_EACH_VEC( (*list), it )
{
NavConnect connect = (*list)[ it ];
FilterArea( area, static_cast< CTFNavArea * >( connect.area ) );
}
}
return true;
}
CTFNavArea *m_homeArea;
CUtlVector< CTFNavArea * > *m_redInvasionAreaVector;
CUtlVector< CTFNavArea * > *m_blueInvasionAreaVector;
unsigned int m_visibleMarker;
};
//------------------------------------------------------------------------------------------------
/**
* Find invasion areas where enemies enter from
*/
void CTFNavArea::ComputeInvasionAreaVectors( void )
{
static unsigned int searchMarker = RandomInt( 0, 1024*1024 );
for( int i=0; i<TF_TEAM_COUNT; ++i )
{
m_invasionAreaVector[ i ].RemoveAll();
}
++searchMarker;
// mark all potentially visible areas for quick testing during the search
MarkVisibleSet marker( searchMarker );
ForAllCompletelyVisibleAreas( marker );
// search boundary of potentially visible area set for area pairs where
// the area in the PVS has a higher incursion distance than an adjacent
// area outside of the PVS - an invasion area
CollectInvasionAreas collector( searchMarker, this, &m_invasionAreaVector[ TF_TEAM_RED ], &m_invasionAreaVector[ TF_TEAM_BLUE ] );
ForAllCompletelyVisibleAreas( collector );
}
//------------------------------------------------------------------------------------------------
bool CTFNavArea::IsBlocked( int teamID, bool ignoreNavBlockers ) const
{
if ( HasAttributeTF( TF_NAV_UNBLOCKABLE ) )
return false;
if ( HasAttributeTF( TF_NAV_BLOCKED ) )
return true;
// temporary fix:
if ( teamID == TF_TEAM_RED && HasAttributeTF( TF_NAV_BLUE_ONE_WAY_DOOR ) )
return true;
if ( teamID == TF_TEAM_BLUE && HasAttributeTF( TF_NAV_RED_ONE_WAY_DOOR ) )
return true;
return CNavArea::IsBlocked( teamID, ignoreNavBlockers );
}
//------------------------------------------------------------------------------------------------
void CTFNavArea::Save( CUtlBuffer &fileBuffer, unsigned int version ) const
{
CNavArea::Save( fileBuffer, version );
// save attribute flags
unsigned int attributes = m_attributeFlags & TF_NAV_PERSISTENT_ATTRIBUTES;
fileBuffer.PutUnsignedInt( attributes );
}
//------------------------------------------------------------------------------------------------
NavErrorType CTFNavArea::Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion )
{
// load base class data
CNavArea::Load( fileBuffer, version, subVersion );
if ( subVersion > TheNavMesh->GetSubVersionNumber() )
{
Warning( "Unknown NavArea sub-version number\n" );
return NAV_INVALID_FILE;
}
else if ( subVersion <= 1 )
{
// no data
m_attributeFlags = 0;
return NAV_OK;
}
m_attributeFlags = fileBuffer.GetUnsignedInt();
if ( !fileBuffer.IsValid() )
{
Warning( "Can't read TF-specific attributes\n" );
return NAV_INVALID_FILE;
}
return NAV_OK;
}
//--------------------------------------------------------------------------------------------------------
unsigned int CTFNavArea::m_masterTFMark = 1;
//--------------------------------------------------------------------------------------------------------
void CTFNavArea::MakeNewTFMarker( void )
{
++m_masterTFMark;
}
//--------------------------------------------------------------------------------------------------------
void CTFNavArea::ResetTFMarker( void )
{
m_masterTFMark = 1;
}
//--------------------------------------------------------------------------------------------------------
bool CTFNavArea::IsTFMarked( void ) const
{
return ( m_TFMark == m_masterTFMark );
}
//--------------------------------------------------------------------------------------------------------
void CTFNavArea::TFMark( void )
{
m_TFMark = m_masterTFMark;
}
//--------------------------------------------------------------------------------------------------------
bool CTFNavArea::IsValidForWanderingPopulation( void ) const
{
if ( HasAttributeTF( TF_NAV_BLOCKED | TF_NAV_SPAWN_ROOM_RED | TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_NO_SPAWNING | TF_NAV_RESCUE_CLOSET ) )
return false;
return true;
}
//--------------------------------------------------------------------------------------------------------
void CTFNavArea::AddPotentiallyVisibleActor( CBaseCombatCharacter *who )
{
if ( who == NULL )
{
return;
}
int team = who->GetTeamNumber();
if ( team < 0 || team >= TF_TEAM_COUNT )
return;
CTFBot *bot = ToTFBot( who );
if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
return;
if ( m_potentiallyVisibleActor[ team ].Find( who ) == m_potentiallyVisibleActor[ team ].InvalidIndex() )
{
m_potentiallyVisibleActor[ team ].AddToTail( who );
}
}
//--------------------------------------------------------------------------------------------------------
float CTFNavArea::GetCombatIntensity( void ) const
{
if ( !m_combatTimer.HasStarted() )
{
return 0.0f;
}
float actualIntensity = m_combatIntensity - m_combatTimer.GetElapsedTime() * tf_nav_combat_decay_rate.GetFloat();
if ( actualIntensity < 0.0f )
{
actualIntensity = 0.0f;
}
return actualIntensity;
}
//--------------------------------------------------------------------------------------------------------
// Invoked when combat happens in/near this area
void CTFNavArea::OnCombat( void )
{
m_combatIntensity += tf_nav_combat_build_rate.GetFloat();
if ( m_combatIntensity > 1.0f )
{
m_combatIntensity = 1.0f;
}
m_combatTimer.Start();
}
//--------------------------------------------------------------------------------------------------------
bool CTFNavArea::IsInCombat( void ) const
{
return GetCombatIntensity() > 0.01f;
}