//========= 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( (*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( (*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( (*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; dirGetAdjacentCount( (NavDirType)dir ); for( int i=0; i( 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; dirGetIncomingConnections( (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 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; }