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
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; |
|
} |
|
|
|
|
|
|