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.
2451 lines
71 KiB
2451 lines
71 KiB
5 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
// tf_nav_mesh.cpp
|
||
|
// TF specific nav mesh
|
||
|
// Michael Booth, February 2009
|
||
|
|
||
|
#include "cbase.h"
|
||
|
#include "tf_nav_mesh.h"
|
||
|
#include "bot/tf_bot.h"
|
||
|
#include "bot/tf_bot_manager.h"
|
||
|
#include "tf_obj.h"
|
||
|
#include "tf_obj_sentrygun.h"
|
||
|
#include "team_control_point_master.h"
|
||
|
#include "team_train_watcher.h"
|
||
|
#include "tf_gamerules.h"
|
||
|
#include "func_respawnroom.h"
|
||
|
#include "doors.h"
|
||
|
#include "props.h"
|
||
|
#include "filters.h"
|
||
|
#include "NextBotUtil.h"
|
||
|
|
||
|
// NOTE: nav_debug_blocked ConVar is also use for debugging NAV_MESH_NAV_BLOCKER and TF_NAV_BLOCKED...
|
||
|
|
||
|
ConVar tf_show_in_combat_areas( "tf_show_in_combat_areas", "0", FCVAR_CHEAT );
|
||
|
ConVar tf_show_enemy_invasion_areas( "tf_show_enemy_invasion_areas", "0", FCVAR_CHEAT, "Highlight areas where the enemy team enters the visible environment of the local player" );
|
||
|
ConVar tf_show_blocked_areas( "tf_show_blocked_areas", "0", FCVAR_CHEAT, "Highlight areas that are considered blocked for TF-specific reasons" );
|
||
|
ConVar tf_show_incursion_flow( "tf_show_incursion_flow", "0", FCVAR_CHEAT );
|
||
|
ConVar tf_show_incursion_flow_range( "tf_show_incursion_flow_range", "150", FCVAR_CHEAT, "1 = red, 2 = blue" );
|
||
|
ConVar tf_show_incursion_flow_gradient( "tf_show_incursion_flow_gradient", "0", FCVAR_CHEAT, "1 = red, 2 = blue" );
|
||
|
ConVar tf_show_mesh_decoration( "tf_show_mesh_decoration", "0", FCVAR_CHEAT, "Highlight special areas" );
|
||
|
ConVar tf_show_mesh_decoration_manual( "tf_show_mesh_decoration_manual", "0", FCVAR_CHEAT, "Highlight special areas marked by hand" );
|
||
|
// Method 1 & 2 should be exactly the same for tf_show_sentry_danger.
|
||
|
ConVar tf_show_sentry_danger( "tf_show_sentry_danger", "0", FCVAR_CHEAT, "Show sentry danger areas. 1:Use m_sentryAreas. 2:Check all nav areas." );
|
||
|
ConVar tf_show_actor_potential_visibility( "tf_show_actor_potential_visibility", "0", FCVAR_CHEAT );
|
||
|
ConVar tf_show_control_points( "tf_show_control_points", "0", FCVAR_CHEAT );
|
||
|
ConVar tf_show_bomb_drop_areas( "tf_show_bomb_drop_areas", "0", FCVAR_CHEAT );
|
||
|
|
||
|
ConVar tf_bot_min_setup_gate_defend_range( "tf_bot_min_setup_gate_defend_range", "750", FCVAR_CHEAT, "How close from the setup gate(s) defending bots can take up positions. Areas closer than this will be in cover to ambush." );
|
||
|
ConVar tf_bot_max_setup_gate_defend_range( "tf_bot_max_setup_gate_defend_range", "2000", FCVAR_CHEAT, "How far from the setup gate(s) defending bots can take up positions" );
|
||
|
ConVar tf_bot_min_setup_gate_sniper_defend_range( "tf_bot_min_setup_gate_sniper_defend_range", "1500", FCVAR_CHEAT, "How far from the setup gate(s) a defending sniper will take up position" );
|
||
|
ConVar tf_show_gate_defense_areas( "tf_show_gate_defense_areas", "0", FCVAR_CHEAT );
|
||
|
ConVar tf_show_point_defense_areas( "tf_show_point_defense_areas", "0", FCVAR_CHEAT );
|
||
|
|
||
|
|
||
|
extern ConVar tf_bot_debug_select_defense_area;
|
||
|
extern ConVar tf_nav_in_combat_duration;
|
||
|
extern ConVar mp_teams_unbalance_limit;
|
||
|
extern ConVar mp_autoteambalance;
|
||
|
extern ConVar sv_alltalk;
|
||
|
extern ConVar mp_timelimit;
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------------------
|
||
|
ConVar tf_select_ambush_areas_radius( "tf_select_ambush_areas_radius", "750", FCVAR_CHEAT );
|
||
|
ConVar tf_select_ambush_areas_close_range( "tf_select_ambush_areas_close_range", "300", FCVAR_CHEAT );
|
||
|
ConVar tf_select_ambush_areas_max_enemy_exposure_area( "tf_select_ambush_areas_max_enemy_exposure_area", "500000", FCVAR_CHEAT );
|
||
|
|
||
|
class ScanSelectAmbushAreas
|
||
|
{
|
||
|
public:
|
||
|
ScanSelectAmbushAreas( CUtlVector< CTFNavArea * > *ambushAreaVector, int teamToAmbush, float enemyIncursionLimit )
|
||
|
{
|
||
|
m_ambushAreaVector = ambushAreaVector;
|
||
|
m_teamToAmbush = teamToAmbush;
|
||
|
m_enemyIncursionLimit = enemyIncursionLimit;
|
||
|
}
|
||
|
|
||
|
bool operator() ( CNavArea *baseArea )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( baseArea );
|
||
|
|
||
|
// no drop-downs or jumps
|
||
|
if ( area->GetParent() && !area->GetParent()->IsContiguous( area ) )
|
||
|
return false;
|
||
|
|
||
|
float enemyIncursionDistanceAtArea = area->GetIncursionDistance( m_teamToAmbush );
|
||
|
|
||
|
if ( enemyIncursionDistanceAtArea > m_enemyIncursionLimit )
|
||
|
return false;
|
||
|
|
||
|
int wallCount = 0;
|
||
|
int dir;
|
||
|
for( dir=0; dir<NUM_DIRECTIONS; ++dir )
|
||
|
{
|
||
|
if ( area->GetAdjacentCount( (NavDirType)dir ) == 0 )
|
||
|
{
|
||
|
// wall (or dropoff) on this side
|
||
|
++wallCount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( wallCount >= 1 )
|
||
|
{
|
||
|
// good cover, are we also right next to enemy incursion areas?
|
||
|
const CUtlVector< CTFNavArea * > &invasionVector = area->GetEnemyInvasionAreaVector( GetEnemyTeam( m_teamToAmbush ) );
|
||
|
|
||
|
// don't use areas that are in plain sight of large amounts of incoming enemy space
|
||
|
NavAreaCollector collector( true );
|
||
|
area->ForAllPotentiallyVisibleAreas( collector );
|
||
|
|
||
|
float totalVisibleThreatArea = 0.0f;
|
||
|
FOR_EACH_VEC( collector.m_area, it )
|
||
|
{
|
||
|
CTFNavArea *visArea = static_cast< CTFNavArea * >( collector.m_area[ it ] );
|
||
|
|
||
|
if ( visArea->GetIncursionDistance( m_teamToAmbush ) < enemyIncursionDistanceAtArea )
|
||
|
{
|
||
|
totalVisibleThreatArea += visArea->GetSizeX() * visArea->GetSizeY();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( totalVisibleThreatArea > tf_select_ambush_areas_max_enemy_exposure_area.GetFloat() )
|
||
|
{
|
||
|
// too exposed
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
float nearRangeSq = tf_select_ambush_areas_close_range.GetFloat();
|
||
|
nearRangeSq *= nearRangeSq;
|
||
|
|
||
|
FOR_EACH_VEC( invasionVector, it )
|
||
|
{
|
||
|
CTFNavArea *invasionArea = invasionVector[ it ];
|
||
|
|
||
|
if ( invasionArea->GetIncursionDistance( m_teamToAmbush ) < enemyIncursionDistanceAtArea )
|
||
|
{
|
||
|
// the enemy will go through invasionArea before they reach the candidate area
|
||
|
float rangeSq = ( invasionArea->GetCenter() - area->GetCenter() ).LengthSqr();
|
||
|
if ( rangeSq < nearRangeSq )
|
||
|
{
|
||
|
// there is at least one nearby invasion area
|
||
|
m_ambushAreaVector->AddToTail( area );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
int m_teamToAmbush;
|
||
|
float m_enemyIncursionLimit;
|
||
|
CUtlVector< CTFNavArea * > *m_ambushAreaVector;
|
||
|
};
|
||
|
|
||
|
void CMD_SelectAmbushAreas( void )
|
||
|
{
|
||
|
CBasePlayer *player = UTIL_GetListenServerHost();
|
||
|
if ( player == NULL )
|
||
|
return;
|
||
|
|
||
|
CTFNavArea *searchSourceArea = static_cast< CTFNavArea * >( player->GetLastKnownArea() );
|
||
|
|
||
|
int teamToAmbush = GetEnemyTeam( player->GetTeamNumber() );
|
||
|
|
||
|
CUtlVector< CTFNavArea * > ambushAreaVector;
|
||
|
ScanSelectAmbushAreas selector( &ambushAreaVector, teamToAmbush, searchSourceArea->GetIncursionDistance( teamToAmbush ) + 300.0f );
|
||
|
SearchSurroundingAreas( searchSourceArea, searchSourceArea->GetCenter(), selector, tf_select_ambush_areas_radius.GetFloat() );
|
||
|
|
||
|
FOR_EACH_VEC( ambushAreaVector, it )
|
||
|
{
|
||
|
TheNavMesh->AddToSelectedSet( ambushAreaVector[ it ] );
|
||
|
}
|
||
|
}
|
||
|
static ConCommand tf_select_ambush_areas( "tf_select_ambush_areas", CMD_SelectAmbushAreas, "Add good ambush spots to the selected set. For debugging.", FCVAR_GAMEDLL | FCVAR_CHEAT );
|
||
|
|
||
|
|
||
|
#ifdef SKIPME
|
||
|
//-------------------------------------------------------------------------
|
||
|
void CMD_SelectIncursionZone( void )
|
||
|
{
|
||
|
CBasePlayer *player = UTIL_GetListenServerHost();
|
||
|
if ( player == NULL )
|
||
|
return;
|
||
|
|
||
|
const CUtlVector< CTFNavArea * > *pointAreaVector = TheTFNavMesh()->GetControlPointAreas();
|
||
|
if ( !pointAreaVector )
|
||
|
return;
|
||
|
|
||
|
int i;
|
||
|
float incursionAtPoint = 0.0f;
|
||
|
float maxInvaderTravelDistance = 2000.0f;
|
||
|
|
||
|
for( i=0; i<pointAreaVector->Count(); ++i )
|
||
|
{
|
||
|
if ( pointAreaVector->Element(i)->GetIncursionDistance( TF_TEAM_BLUE ) > incursionAtPoint )
|
||
|
{
|
||
|
incursionAtPoint = pointAreaVector->Element(i)->GetIncursionDistance( TF_TEAM_BLUE );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for( i=0; i<TheNavAreas.Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ i ] );
|
||
|
|
||
|
float inc = area->GetIncursionDistance( TF_TEAM_BLUE );
|
||
|
if ( inc > 0.0f && inc < incursionAtPoint && inc > incursionAtPoint - maxInvaderTravelDistance )
|
||
|
{
|
||
|
NDebugOverlay::Cross3D( area->GetCenter(), 5.0f, 255, 255, 0, true, 99999.9f );
|
||
|
//TheNavMesh->AddToSelectedSet( area );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
static ConCommand tf_select_incursion_zone( "tf_select_incursion_zone", CMD_SelectIncursionZone, "Select areas where invading team approaches the objective. For debugging.", FCVAR_GAMEDLL | FCVAR_CHEAT );
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
void CMD_SelectControlPointIncursionAreas( void )
|
||
|
{
|
||
|
CBasePlayer *player = UTIL_GetListenServerHost();
|
||
|
if ( player == NULL )
|
||
|
return;
|
||
|
|
||
|
const CUtlVector< CTFNavArea * > *pointAreaVector = TheTFNavMesh()->GetControlPointAreas();
|
||
|
|
||
|
for( int i=0; i<pointAreaVector->Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *pointArea = (CTFNavArea *)pointAreaVector->Element(i);
|
||
|
|
||
|
for( i=0; i<TheNavAreas.Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ i ] );
|
||
|
|
||
|
if ( area->GetIncursionDistance( TF_TEAM_BLUE ) > pointArea->GetIncursionDistance( TF_TEAM_BLUE ) )
|
||
|
continue;
|
||
|
|
||
|
if ( pointArea->IsPotentiallyVisible( area ) )
|
||
|
{
|
||
|
// the point is visible from this area
|
||
|
|
||
|
// if no prior areas can see the point, we have a point incursion area
|
||
|
CUtlVector< CTFNavArea * > priorVector;
|
||
|
area->CollectPriorIncursionAreas( TF_TEAM_BLUE, &priorVector );
|
||
|
|
||
|
int j;
|
||
|
for( j=0; j<priorVector.Count(); ++j )
|
||
|
{
|
||
|
if ( pointArea->IsPotentiallyVisible( priorVector[j] ) )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( j == priorVector.Count() && j > 0 )
|
||
|
{
|
||
|
// no prior areas can see the point
|
||
|
TheNavMesh->AddToSelectedSet( area );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
static ConCommand tf_select_control_point_incursion_areas( "tf_select_control_point_incursion_areas", CMD_SelectControlPointIncursionAreas, "Select areas where invading team leaves cover near the objective. For debugging.", FCVAR_GAMEDLL | FCVAR_CHEAT );
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
CON_COMMAND_F( tf_assign_territory, "Divvy up the mesh into red and blue territories. For debugging.", FCVAR_GAMEDLL )
|
||
|
{
|
||
|
// Listenserver host or rcon access only!
|
||
|
if ( !UTIL_IsCommandIssuedByServerAdmin() )
|
||
|
return;
|
||
|
|
||
|
int i;
|
||
|
|
||
|
// clear all territory markings
|
||
|
for( i=0; i<TheNavAreas.Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ i ] );
|
||
|
area->ClearAttributeTF( TF_NAV_RED_TERRITORY | TF_NAV_BLUE_TERRITORY );
|
||
|
area->SetParent( NULL );
|
||
|
}
|
||
|
|
||
|
const CUtlVector< CTFNavArea * > *pointAreaVector = TheTFNavMesh()->GetControlPointAreas();
|
||
|
|
||
|
if ( !pointAreaVector || pointAreaVector->Count() <= 0 )
|
||
|
return;
|
||
|
|
||
|
// find centermost point area, and mark all contested point areas as owned by red
|
||
|
Vector center = vec3_origin;
|
||
|
for( i=0; i<pointAreaVector->Count(); ++i )
|
||
|
{
|
||
|
center += pointAreaVector->Element(i)->GetCenter();
|
||
|
pointAreaVector->Element(i)->SetAttributeTF( TF_NAV_RED_TERRITORY );
|
||
|
}
|
||
|
center /= pointAreaVector->Count();
|
||
|
|
||
|
CTFNavArea *pointArea = pointAreaVector->Element(0);
|
||
|
for( i=0; i<pointAreaVector->Count(); ++i )
|
||
|
{
|
||
|
if ( pointAreaVector->Element(i)->IsOverlapping( center ) )
|
||
|
{
|
||
|
pointArea = pointAreaVector->Element(i);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// spread red's territory to surround the contested area a bit
|
||
|
const float surroundRange = 1000.0f;
|
||
|
CUtlVector< CNavArea * > surroundingVector;
|
||
|
CollectSurroundingAreas( &surroundingVector, pointArea, surroundRange );
|
||
|
|
||
|
for( int t=0; t<surroundingVector.Count(); ++t )
|
||
|
{
|
||
|
CTFNavArea *area = (CTFNavArea *)surroundingVector[t];
|
||
|
area->ClearAttributeTF( TF_NAV_BLUE_TERRITORY );
|
||
|
area->SetAttributeTF( TF_NAV_RED_TERRITORY );
|
||
|
}
|
||
|
|
||
|
|
||
|
// do a breadth first search out from control point center
|
||
|
// when a spawn room is reached, mark it and all its parent areas as belonging to the team of the spawn room
|
||
|
CNavArea::ClearSearchLists();
|
||
|
|
||
|
pointArea->AddToOpenList();
|
||
|
pointArea->Mark();
|
||
|
pointArea->SetParent( NULL );
|
||
|
|
||
|
CUtlVectorFixedGrowable< const NavConnect *, 64 > adjAreaVector;
|
||
|
|
||
|
while( !CNavArea::IsOpenListEmpty() )
|
||
|
{
|
||
|
// get next area to check
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( CNavArea::PopOpenList() );
|
||
|
|
||
|
// ignore setup gates, since they will be open after the setup time
|
||
|
if ( !area->HasAttributeTF( TF_NAV_BLUE_SETUP_GATE | TF_NAV_RED_SETUP_GATE ) && ( area->IsBlocked( TF_TEAM_RED ) || area->IsBlocked( TF_TEAM_BLUE ) ) )
|
||
|
{
|
||
|
// don't pass through blocked areas
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// explore adjacent floor areas
|
||
|
adjAreaVector.RemoveAll();
|
||
|
|
||
|
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
|
||
|
{
|
||
|
// collect all OUTGOING links from this area to adjacent areas
|
||
|
const NavConnectVector *adjVector = area->GetAdjacentAreas( (NavDirType)dir );
|
||
|
FOR_EACH_VEC( (*adjVector), bit )
|
||
|
{
|
||
|
adjAreaVector.AddToTail( &(*adjVector)[ bit ] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FOR_EACH_VEC( adjAreaVector, vit )
|
||
|
{
|
||
|
const NavConnect *connect = adjAreaVector[ vit ];
|
||
|
CTFNavArea *adjArea = static_cast< CTFNavArea * >( connect->area );
|
||
|
|
||
|
if ( adjArea->ComputeAdjacentConnectionHeightChange( area ) > TF_PLAYER_JUMP_HEIGHT ||
|
||
|
area->ComputeAdjacentConnectionHeightChange( adjArea ) > TF_PLAYER_JUMP_HEIGHT )
|
||
|
{
|
||
|
// don't go up ledges too high to jump
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( !adjArea->IsMarked() )
|
||
|
{
|
||
|
adjArea->Mark();
|
||
|
adjArea->SetParent( area );
|
||
|
|
||
|
// if this area is in a spawn room, mark path we took to get here as the appropriate team's territory
|
||
|
if ( adjArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_RED ) )
|
||
|
{
|
||
|
for( CTFNavArea *pathArea = adjArea; pathArea; pathArea = (CTFNavArea *)pathArea->GetParent() )
|
||
|
{
|
||
|
pathArea->SetAttributeTF( TF_NAV_RED_TERRITORY );
|
||
|
}
|
||
|
}
|
||
|
else if ( adjArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ) )
|
||
|
{
|
||
|
for( CTFNavArea *pathArea = adjArea; pathArea; pathArea = (CTFNavArea *)pathArea->GetParent() )
|
||
|
{
|
||
|
pathArea->SetAttributeTF( TF_NAV_BLUE_TERRITORY );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
adjArea->AddToOpenListTail();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( args.ArgC() == 1 )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// iterate over all areas, spreading territory out from found routes into unclaimed areas
|
||
|
CUtlVector< CTFNavArea * > spreadVector;
|
||
|
|
||
|
while( true )
|
||
|
{
|
||
|
spreadVector.RemoveAll();
|
||
|
|
||
|
for( int i=0; i<TheNavAreas.Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ i ] );
|
||
|
CTFNavArea *parent = (CTFNavArea *)area->GetParent();
|
||
|
|
||
|
// if this area has no territory affiliation but its parent does, inherit it and iterate again
|
||
|
if ( !area->HasAttributeTF( TF_NAV_RED_TERRITORY | TF_NAV_BLUE_TERRITORY ) && parent && parent->HasAttributeTF( TF_NAV_RED_TERRITORY | TF_NAV_BLUE_TERRITORY ) )
|
||
|
{
|
||
|
spreadVector.AddToTail( area );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( spreadVector.Count() == 0 )
|
||
|
{
|
||
|
// finished spreading
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// spread the territory influence one step out
|
||
|
for( int j=0; j<spreadVector.Count(); ++j )
|
||
|
{
|
||
|
CTFNavArea *area = spreadVector[j];
|
||
|
CTFNavArea *parent = (CTFNavArea *)area->GetParent();
|
||
|
|
||
|
if ( parent->HasAttributeTF( TF_NAV_RED_TERRITORY ) )
|
||
|
{
|
||
|
area->SetAttributeTF( TF_NAV_RED_TERRITORY );
|
||
|
}
|
||
|
|
||
|
if ( parent->HasAttributeTF( TF_NAV_BLUE_TERRITORY ) )
|
||
|
{
|
||
|
area->SetAttributeTF( TF_NAV_BLUE_TERRITORY );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif // SKIPME
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
CTFNavMesh::CTFNavMesh( void )
|
||
|
{
|
||
|
for( int j=0; j<MAX_CONTROL_POINTS; ++j )
|
||
|
{
|
||
|
m_controlPointAreaVector[j].RemoveAll();
|
||
|
m_controlPointCenterAreaVector[j] = NULL;
|
||
|
}
|
||
|
|
||
|
ListenForGameEvent( "teamplay_setup_finished" );
|
||
|
ListenForGameEvent( "teamplay_point_captured" );
|
||
|
ListenForGameEvent( "teamplay_point_unlocked" );
|
||
|
|
||
|
ListenForGameEvent( "player_builtobject" );
|
||
|
ListenForGameEvent( "player_dropobject" );
|
||
|
ListenForGameEvent( "player_carryobject" );
|
||
|
ListenForGameEvent( "object_detonated" );
|
||
|
ListenForGameEvent( "object_destroyed" );
|
||
|
|
||
|
m_priorBotCount = 0;
|
||
|
|
||
|
m_recomputeInternalDataTimer.Invalidate();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
CTFNavArea *CTFNavMesh::CreateArea( void ) const
|
||
|
{
|
||
|
return new CTFNavArea;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
/**
|
||
|
* Invoked on each game frame
|
||
|
*/
|
||
|
void CTFNavMesh::Update( void )
|
||
|
{
|
||
|
CNavMesh::Update();
|
||
|
|
||
|
if ( !TheNavAreas.Count() )
|
||
|
return;
|
||
|
|
||
|
UpdateDebugDisplay();
|
||
|
|
||
|
if ( TheNextBots().GetNextBotCount() > 0 )
|
||
|
{
|
||
|
if ( m_priorBotCount == 0 )
|
||
|
{
|
||
|
// the first bot was just added
|
||
|
ScheduleRecomputationOfInternalData( RESET );
|
||
|
}
|
||
|
|
||
|
// we use a timer here to give the map logic a few moments to settle out before inspecting it
|
||
|
if ( m_recomputeInternalDataTimer.HasStarted() && m_recomputeInternalDataTimer.IsElapsed() )
|
||
|
{
|
||
|
m_recomputeInternalDataTimer.Invalidate();
|
||
|
RecomputeInternalData();
|
||
|
}
|
||
|
|
||
|
if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT && m_watchCartTimer.IsElapsed() )
|
||
|
{
|
||
|
// the cart may have moved, recompute new sniper spots
|
||
|
m_watchCartTimer.Start( 3.0f );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_priorBotCount = TheNextBots().GetNextBotCount();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
/**
|
||
|
* (EXTEND) invoked when server loads a new map
|
||
|
*/
|
||
|
void CTFNavMesh::OnServerActivate( void )
|
||
|
{
|
||
|
CNavMesh::OnServerActivate();
|
||
|
|
||
|
m_sentryAreas.RemoveAll();
|
||
|
|
||
|
ResetMeshAttributes( true );
|
||
|
m_priorBotCount = 0;
|
||
|
|
||
|
m_setupGateDefenseAreaVector.RemoveAll();
|
||
|
|
||
|
m_redSpawnRoomAreaVector.RemoveAll();
|
||
|
m_blueSpawnRoomAreaVector.RemoveAll();
|
||
|
|
||
|
m_redSpawnRoomExitAreaVector.RemoveAll();
|
||
|
m_blueSpawnRoomExitAreaVector.RemoveAll();
|
||
|
for( int i=0; i<MAX_CONTROL_POINTS; ++i )
|
||
|
{
|
||
|
m_controlPointAreaVector[i].RemoveAll();
|
||
|
m_controlPointCenterAreaVector[i] = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
/**
|
||
|
* Invoked when a game round restarts
|
||
|
*/
|
||
|
void CTFNavMesh::OnRoundRestart( void )
|
||
|
{
|
||
|
CNavMesh::OnRoundRestart();
|
||
|
|
||
|
ResetMeshAttributes( true );
|
||
|
|
||
|
// nasty hack
|
||
|
TheTFBots().OnRoundRestart();
|
||
|
|
||
|
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
|
||
|
{
|
||
|
RecomputeInternalData();
|
||
|
}
|
||
|
|
||
|
DevMsg( "CTFNavMesh: %d nav areas in mesh.\n", GetNavAreaCount() );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
/**
|
||
|
* One or more areas may have become blocked or are no longer blocked.
|
||
|
* Recompute dependent mesh data.
|
||
|
*/
|
||
|
void CTFNavMesh::OnBlockedAreasChanged( void )
|
||
|
{
|
||
|
VPROF_BUDGET( "CTFNavMesh::OnBlockedAreasChanged", "NextBot" );
|
||
|
|
||
|
if ( TheNextBots().GetNextBotCount() == 0 )
|
||
|
return;
|
||
|
|
||
|
ScheduleRecomputationOfInternalData( BLOCKED_STATUS_CHANGED );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
void TestAndBlockOverlappingAreas( CBaseEntity *entity )
|
||
|
{
|
||
|
Ray_t ray;
|
||
|
trace_t trace;
|
||
|
NextBotTraceFilterIgnoreActors filter( NULL, COLLISION_GROUP_NONE );
|
||
|
|
||
|
const float crouchHeight = 30.0f;
|
||
|
Vector hullMin, hullMax;
|
||
|
Vector traceFrom, traceTo;
|
||
|
|
||
|
Extent extent;
|
||
|
extent.Init( entity );
|
||
|
|
||
|
CUtlVector< CNavArea * > overlapVector;
|
||
|
TheNavMesh->CollectAreasOverlappingExtent( extent, &overlapVector );
|
||
|
|
||
|
for( int i=0; i<overlapVector.Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = (CTFNavArea *)overlapVector[i];
|
||
|
|
||
|
const float tolerance = 1.0f;
|
||
|
if ( fabs( area->GetCorner( NORTH_WEST ).z - area->GetCorner( NORTH_EAST ).z ) < tolerance )
|
||
|
{
|
||
|
// flat along X, potentially varies along Y
|
||
|
hullMin.x = 0.0f;
|
||
|
hullMin.y = 0.0f;
|
||
|
hullMin.z = StepHeight;
|
||
|
|
||
|
hullMax.x = area->GetSizeX();
|
||
|
hullMax.y = 0.0f;
|
||
|
hullMax.z = crouchHeight;
|
||
|
|
||
|
traceFrom = area->GetCorner( NORTH_WEST );
|
||
|
traceTo = area->GetCorner( SOUTH_WEST );
|
||
|
}
|
||
|
else if ( fabs( area->GetCorner( NORTH_WEST ).z - area->GetCorner( SOUTH_WEST ).z ) < tolerance )
|
||
|
{
|
||
|
// flat along Y, potentially varies along X
|
||
|
hullMin.x = 0.0f;
|
||
|
hullMin.y = 0.0f;
|
||
|
hullMin.z = StepHeight;
|
||
|
|
||
|
hullMax.x = 0.0f;
|
||
|
hullMax.y = area->GetSizeY();
|
||
|
hullMax.z = crouchHeight;
|
||
|
|
||
|
traceFrom = area->GetCorner( NORTH_WEST );
|
||
|
traceTo = area->GetCorner( NORTH_EAST );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// varies along both X and Y
|
||
|
hullMin.x = 0.0f;
|
||
|
hullMin.y = 0.0f;
|
||
|
hullMin.z = StepHeight;
|
||
|
|
||
|
hullMax.x = 1.0f;
|
||
|
hullMax.y = 1.0f;
|
||
|
hullMax.z = crouchHeight;
|
||
|
|
||
|
traceFrom = area->GetCorner( NORTH_WEST );
|
||
|
traceTo = area->GetCorner( SOUTH_EAST );
|
||
|
}
|
||
|
|
||
|
// need to trace from high to low to avoid interpenetration
|
||
|
if ( traceFrom.z < traceTo.z )
|
||
|
{
|
||
|
Vector tmp = traceFrom;
|
||
|
traceFrom = traceTo;
|
||
|
traceTo = tmp;
|
||
|
}
|
||
|
|
||
|
ray.Init( traceFrom, traceTo, hullMin, hullMax );
|
||
|
enginetrace->TraceRay( ray, MASK_PLAYERSOLID, &filter, &trace );
|
||
|
|
||
|
// NDebugOverlay::SweptBox( traceFrom, traceTo, hullMin, hullMax, vec3_angle, 255, 255, 0, 255, 99999.9f );
|
||
|
|
||
|
if ( trace.DidHit() )
|
||
|
{
|
||
|
if ( trace.m_pEnt && trace.m_pEnt->ShouldBlockNav() )
|
||
|
{
|
||
|
area->MarkAsBlocked( TEAM_ANY, entity );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
void CTFNavMesh::ComputeBlockedAreas( void )
|
||
|
{
|
||
|
// clear all blocked state
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
area->UnblockArea();
|
||
|
}
|
||
|
|
||
|
#ifdef TF_CREEP_MODE
|
||
|
if ( TFGameRules()->IsCreepWaveMode() )
|
||
|
{
|
||
|
// no blocking for creeps
|
||
|
return;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// block mesh under solid brushes
|
||
|
CFuncBrush *brush = NULL;
|
||
|
while( ( brush = (CFuncBrush *)gEntList.FindEntityByClassname( brush, "func_brush" ) ) != NULL )
|
||
|
{
|
||
|
if ( brush->IsSolid() ) // && !brush->m_iDisabled ) // "disabled" seems to be overridden by solidity
|
||
|
{
|
||
|
// this brush is potentially blocking navigation
|
||
|
TestAndBlockOverlappingAreas( brush );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Find all func_doors in the map. If a func_door is surrounded by a trigger_multiple,
|
||
|
// the trigger controls access to the door. If the func_door is bare, the door itself
|
||
|
// determines access.
|
||
|
CBaseDoor *door = NULL;
|
||
|
while( ( door = (CBaseDoor *)gEntList.FindEntityByClassname( door, "func_door*" ) ) != NULL )
|
||
|
{
|
||
|
// if a closed door is not controlled by a trigger assume it doesn't open at all until the scenario changes and map logic opens it
|
||
|
bool isDoorClosed = ( door->m_toggle_state == TS_AT_BOTTOM || door->m_toggle_state == TS_GOING_DOWN );
|
||
|
|
||
|
int doorOwnedByTeam = TEAM_UNASSIGNED;
|
||
|
|
||
|
bool isDoorTriggerControlled = false;
|
||
|
|
||
|
Extent triggerExtent, doorExtent;
|
||
|
doorExtent.Init( door );
|
||
|
|
||
|
CTriggerMultiple *trigger = NULL;
|
||
|
while( ( trigger = (CTriggerMultiple *)gEntList.FindEntityByClassname( trigger, "trigger_multiple" ) ) != NULL )
|
||
|
{
|
||
|
triggerExtent.Init( trigger );
|
||
|
|
||
|
// just check overlapping, not encompassing, since some door triggers only are player height tall (cp_gravelpit)
|
||
|
if ( triggerExtent.IsOverlapping( doorExtent ) )
|
||
|
{
|
||
|
if ( !trigger->m_bDisabled )
|
||
|
{
|
||
|
// this trigger contains this door, and thus controls it
|
||
|
isDoorTriggerControlled = true;
|
||
|
|
||
|
// look for a filter attached to this trigger that limits access to one team
|
||
|
if ( trigger->m_hFilter != NULL && FClassnameIs( trigger->m_hFilter, "filter_activator_tfteam" ) )
|
||
|
{
|
||
|
doorOwnedByTeam = trigger->m_hFilter->GetTeamNumber();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// is this door acting like a wall?
|
||
|
bool isDoorWall = isDoorTriggerControlled ? false : isDoorClosed;
|
||
|
|
||
|
// set the blocked status of all areas overlapping this door
|
||
|
NavAreaCollector doorAreas;
|
||
|
TheNavMesh->ForAllAreasOverlappingExtent( doorAreas, doorExtent );
|
||
|
|
||
|
int blockedTeam = ( doorOwnedByTeam == TEAM_UNASSIGNED ) ? TEAM_ANY : ( ( doorOwnedByTeam == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
|
||
|
|
||
|
for( int i=0; i<doorAreas.m_area.Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = (CTFNavArea *)doorAreas.m_area[i];
|
||
|
|
||
|
bool isDoorBlocking;
|
||
|
if ( area->HasAttributeTF( TF_NAV_DOOR_ALWAYS_BLOCKS ) )
|
||
|
{
|
||
|
// closed doors always block
|
||
|
isDoorBlocking = isDoorClosed;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// untriggered closed doors, or team-owned doors block
|
||
|
isDoorBlocking = ( isDoorWall || doorOwnedByTeam != TEAM_UNASSIGNED );
|
||
|
}
|
||
|
|
||
|
if ( isDoorBlocking )
|
||
|
{
|
||
|
// this door is blocking navigation for at least one team
|
||
|
if ( !area->HasAttributeTF( TF_NAV_DOOR_NEVER_BLOCKS ) )
|
||
|
{
|
||
|
area->MarkAsBlocked( blockedTeam, door );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// we need to UN-block these areas to account for legacy func_brushes
|
||
|
// used inside of cosmetic doors as a collision proxy that have marked
|
||
|
// these areas as blocked
|
||
|
area->UnblockArea( blockedTeam );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef DONT_USE_BLOCKS_TOO_MUCH
|
||
|
// Find all prop_dynamic entities in the map and block areas they overlap
|
||
|
CDynamicProp *prop = NULL;
|
||
|
while( ( prop = (CDynamicProp *)gEntList.FindEntityByClassname( prop, "prop_dynamic" ) ) != NULL )
|
||
|
{
|
||
|
if ( prop->IsSolid() )
|
||
|
{
|
||
|
// if this prop is parented to a door, ignore it - it has already been handled by the door code above
|
||
|
CBaseDoor *parentDoor = dynamic_cast< CBaseDoor * >( prop->GetParent() );
|
||
|
if ( !parentDoor )
|
||
|
{
|
||
|
// this prop is potentially blocking navigation
|
||
|
TestAndBlockOverlappingAreas( prop );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif // DONT_USE_BLOCKS_TOO_MUCH
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
void CTFNavMesh::CollectControlPointAreas( void )
|
||
|
{
|
||
|
for( int i=0; i<MAX_CONTROL_POINTS; ++i )
|
||
|
{
|
||
|
m_controlPointAreaVector[i].RemoveAll();
|
||
|
m_controlPointCenterAreaVector[i] = NULL;
|
||
|
}
|
||
|
|
||
|
CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
|
||
|
if ( pMaster )
|
||
|
{
|
||
|
CBaseEntity *trigger = NULL;
|
||
|
while( ( trigger = gEntList.FindEntityByClassname( trigger, "trigger_capture_area*" ) ) != NULL )
|
||
|
{
|
||
|
CTeamControlPoint *point = ((CTriggerAreaCapture *)trigger)->GetControlPoint();
|
||
|
|
||
|
if ( point )
|
||
|
{
|
||
|
Extent extent;
|
||
|
extent.Init( trigger );
|
||
|
|
||
|
// expand extent a bit to make sure it intersects ground below (koth_viaduct)
|
||
|
extent.lo.z -= HalfHumanHeight;
|
||
|
extent.hi.z += HalfHumanHeight;
|
||
|
|
||
|
CUtlVector< CTFNavArea * > *pointAreaVector = &m_controlPointAreaVector[ point->GetPointIndex() ];
|
||
|
TheNavMesh->CollectAreasOverlappingExtent< CTFNavArea >( extent, pointAreaVector );
|
||
|
|
||
|
// find area closest to the control point's center
|
||
|
m_controlPointCenterAreaVector[ point->GetPointIndex() ] = NULL;
|
||
|
float closeRangeSq = FLT_MAX;
|
||
|
|
||
|
for( int i=0; i<pointAreaVector->Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = pointAreaVector->Element(i);
|
||
|
|
||
|
float rangeSq = ( area->GetCenter() - trigger->WorldSpaceCenter() ).Length2DSqr();
|
||
|
if ( rangeSq < closeRangeSq )
|
||
|
{
|
||
|
m_controlPointCenterAreaVector[ point->GetPointIndex() ] = area;
|
||
|
closeRangeSq = rangeSq;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// For MvM mode. Mark all nav areas where the bomb can drop and the invaders can reach it.
|
||
|
void CTFNavMesh::ComputeLegalBombDropAreas( void )
|
||
|
{
|
||
|
if ( !TFGameRules()->IsMannVsMachineMode() )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CTFNavArea *startArea = NULL;
|
||
|
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ) )
|
||
|
{
|
||
|
startArea = area;
|
||
|
}
|
||
|
|
||
|
area->ClearAttributeTF( TF_NAV_BOMB_CAN_DROP_HERE );
|
||
|
}
|
||
|
|
||
|
if ( startArea == NULL )
|
||
|
{
|
||
|
Warning( "Can't find blue spawn room nav areas. No legal bomb drop areas are marked" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CNavArea::ClearSearchLists();
|
||
|
|
||
|
startArea->AddToOpenList();
|
||
|
startArea->Mark();
|
||
|
startArea->SetParent( NULL );
|
||
|
|
||
|
CUtlVectorFixedGrowable< const NavConnect *, 64 > adjAreaVector;
|
||
|
|
||
|
while( !CNavArea::IsOpenListEmpty() )
|
||
|
{
|
||
|
// get next area to check
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( CNavArea::PopOpenList() );
|
||
|
|
||
|
// explore adjacent floor areas
|
||
|
adjAreaVector.RemoveAll();
|
||
|
|
||
|
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
|
||
|
{
|
||
|
// collect all OUTGOING links from this area to adjacent areas
|
||
|
const NavConnectVector *adjVector = area->GetAdjacentAreas( (NavDirType)dir );
|
||
|
FOR_EACH_VEC( (*adjVector), bit )
|
||
|
{
|
||
|
adjAreaVector.AddToTail( &(*adjVector)[ bit ] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FOR_EACH_VEC( adjAreaVector, vit )
|
||
|
{
|
||
|
const NavConnect *connect = adjAreaVector[ vit ];
|
||
|
CTFNavArea *adjArea = static_cast< CTFNavArea * >( connect->area );
|
||
|
|
||
|
if ( adjArea->IsMarked() )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( area->ComputeAdjacentConnectionHeightChange( adjArea ) > StepHeight )
|
||
|
{
|
||
|
// don't go up ledges higher than a legal step
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( !adjArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) )
|
||
|
{
|
||
|
// this area can be reached by walking from the spawn, so it's legal to drop the bomb here
|
||
|
adjArea->SetAttributeTF( TF_NAV_BOMB_CAN_DROP_HERE );
|
||
|
}
|
||
|
|
||
|
adjArea->Mark();
|
||
|
adjArea->SetParent( area );
|
||
|
|
||
|
if ( !adjArea->IsOpen() )
|
||
|
{
|
||
|
// Since we're doing a breadth-first search, this area will end up at the end of the list.
|
||
|
// Adding it to the tail explicitly saves us a bunch of list traversals.
|
||
|
adjArea->AddToOpenListTail();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// For MvM mode. Mark all nav areas where the bomb can drop and the invaders can reach it.
|
||
|
void CTFNavMesh::ComputeBombTargetDistance()
|
||
|
{
|
||
|
if ( !TFGameRules()->IsMannVsMachineMode() )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CCaptureZone *zone = NULL;
|
||
|
for( int i=0; i<ICaptureZoneAutoList::AutoList().Count(); ++i )
|
||
|
{
|
||
|
zone = static_cast< CCaptureZone* >( ICaptureZoneAutoList::AutoList()[i] );
|
||
|
if ( zone->GetTeamNumber() == TF_TEAM_PVE_INVADERS )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( zone == NULL )
|
||
|
{
|
||
|
Warning( "Can't find bomb delivery zone." );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CTFNavArea *zoneArea = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( zone->WorldSpaceCenter(), false, 500.0f, true );
|
||
|
if ( !zoneArea )
|
||
|
{
|
||
|
Warning( "No nav area for bomb delivery zone." );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// invalidate all travel distances
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
area->m_distanceToBombTarget = -1.0f;
|
||
|
}
|
||
|
|
||
|
CNavArea::ClearSearchLists();
|
||
|
|
||
|
zoneArea->AddToOpenList();
|
||
|
zoneArea->Mark();
|
||
|
zoneArea->SetParent( NULL );
|
||
|
zoneArea->m_distanceToBombTarget = 0.0f;
|
||
|
|
||
|
CUtlVectorFixedGrowable< const NavConnect *, 64 > adjAreaVector;
|
||
|
|
||
|
while( !CNavArea::IsOpenListEmpty() )
|
||
|
{
|
||
|
// get next area to check
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( CNavArea::PopOpenList() );
|
||
|
|
||
|
// explore adjacent floor areas
|
||
|
adjAreaVector.RemoveAll();
|
||
|
|
||
|
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
|
||
|
{
|
||
|
// collect all OUTGOING links from this area to adjacent areas
|
||
|
const NavConnectVector *adjVector = area->GetAdjacentAreas( (NavDirType)dir );
|
||
|
FOR_EACH_VEC( (*adjVector), bit )
|
||
|
{
|
||
|
adjAreaVector.AddToTail( &(*adjVector)[ bit ] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FOR_EACH_VEC( adjAreaVector, vit )
|
||
|
{
|
||
|
const NavConnect *connect = adjAreaVector[ vit ];
|
||
|
CTFNavArea *adjArea = static_cast< CTFNavArea * >( connect->area );
|
||
|
|
||
|
if ( area->ComputeAdjacentConnectionHeightChange( adjArea ) > TF_PLAYER_JUMP_HEIGHT )
|
||
|
{
|
||
|
// don't go up ledges too high to jump
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// compute travel distance
|
||
|
float newTravelDistance = 0.0f;
|
||
|
|
||
|
float between = connect->length;
|
||
|
newTravelDistance = area->m_distanceToBombTarget + between;
|
||
|
|
||
|
float adjacentTravelDistance = adjArea->m_distanceToBombTarget;
|
||
|
|
||
|
// Found a shortcut to our neighbor passing through this area?
|
||
|
// Use a tolernace. Without it, floating point math can make this loop go on forever,
|
||
|
// because intermediate results are stored at a different precision
|
||
|
float flTol = .001f;
|
||
|
if ( adjacentTravelDistance < 0.0f || adjacentTravelDistance > newTravelDistance + flTol )
|
||
|
{
|
||
|
adjArea->m_distanceToBombTarget = newTravelDistance;
|
||
|
adjArea->Mark();
|
||
|
adjArea->SetParent( area );
|
||
|
|
||
|
if ( !adjArea->IsOpen() )
|
||
|
{
|
||
|
// Since we're doing a breadth-first search, this area will end up at the end of the list.
|
||
|
// Adding it to the tail explicitly saves us a bunch of list traversals.
|
||
|
adjArea->AddToOpenListTail();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Found a shortcut this area that passes through the neighbor?
|
||
|
float newTravelDistanceFromAdjacent = adjacentTravelDistance + between;
|
||
|
if ( newTravelDistanceFromAdjacent + flTol < area->m_distanceToBombTarget )
|
||
|
{
|
||
|
// check if the reverse direction is cheaper (for the case of jumping off edges)
|
||
|
area->m_distanceToBombTarget = newTravelDistanceFromAdjacent;
|
||
|
area->Mark();
|
||
|
area->SetParent( adjArea );
|
||
|
if ( !area->IsOpen() )
|
||
|
{
|
||
|
// found a cheaper path, try to traverse backward
|
||
|
area->AddToOpenListTail();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
void CTFNavMesh::RecomputeInternalData( void )
|
||
|
{
|
||
|
CollectControlPointAreas();
|
||
|
RemoveAllMeshDecoration();
|
||
|
DecorateMesh();
|
||
|
ComputeBlockedAreas(); // relies on DecorateMesh() being complete
|
||
|
ComputeIncursionDistances();
|
||
|
ComputeInvasionAreas();
|
||
|
ComputeLegalBombDropAreas();
|
||
|
ComputeBombTargetDistance(); // for MvM
|
||
|
|
||
|
if ( m_recomputeReason == RESET || m_recomputeReason == SETUP_FINISHED )
|
||
|
{
|
||
|
// update point-conditionally blocked areas
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_BLOCKED_UNTIL_POINT_CAPTURE ) )
|
||
|
{
|
||
|
area->SetAttributeTF( TF_NAV_BLOCKED );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_recomputeReason == POINT_CAPTURED )
|
||
|
{
|
||
|
// update point-conditionally blocked areas
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_BLOCKED_UNTIL_POINT_CAPTURE ) )
|
||
|
{
|
||
|
// which point unblocks us?
|
||
|
|
||
|
// if no modifier given, unblock after first capture
|
||
|
bool isUnblocked = true;
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_WITH_SECOND_POINT ) )
|
||
|
{
|
||
|
isUnblocked = (m_recomputeReasonWhichPoint >= 1);
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_WITH_THIRD_POINT ) )
|
||
|
{
|
||
|
isUnblocked = (m_recomputeReasonWhichPoint >= 2);
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_WITH_FOURTH_POINT ) )
|
||
|
{
|
||
|
isUnblocked = (m_recomputeReasonWhichPoint >= 3);
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_WITH_FIFTH_POINT ) )
|
||
|
{
|
||
|
isUnblocked = (m_recomputeReasonWhichPoint >= 4);
|
||
|
}
|
||
|
|
||
|
if ( isUnblocked )
|
||
|
{
|
||
|
area->ClearAttributeTF( TF_NAV_BLOCKED );
|
||
|
}
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_BLOCKED_AFTER_POINT_CAPTURE ) )
|
||
|
{
|
||
|
// which point blocks us?
|
||
|
|
||
|
// if no modifier given, block after first capture
|
||
|
bool isBlocked = true;
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_WITH_SECOND_POINT ) )
|
||
|
{
|
||
|
isBlocked = ( m_recomputeReasonWhichPoint >= 1 );
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_WITH_THIRD_POINT ) )
|
||
|
{
|
||
|
isBlocked = ( m_recomputeReasonWhichPoint >= 2 );
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_WITH_FOURTH_POINT ) )
|
||
|
{
|
||
|
isBlocked = ( m_recomputeReasonWhichPoint >= 3 );
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_WITH_FIFTH_POINT ) )
|
||
|
{
|
||
|
isBlocked = ( m_recomputeReasonWhichPoint >= 4 );
|
||
|
}
|
||
|
|
||
|
if ( isBlocked )
|
||
|
{
|
||
|
area->SetAttributeTF( TF_NAV_BLOCKED );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_recomputeInternalDataTimer.Invalidate();
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// Re-calculate sentry danger attributes.
|
||
|
void CTFNavMesh::OnObjectChanged()
|
||
|
{
|
||
|
// Clear all sentry danger attributes.
|
||
|
ResetMeshAttributes( false );
|
||
|
|
||
|
CUtlVector< CBaseObject * > ActiveSentries;
|
||
|
ActiveSentries.EnsureCapacity( 16 );
|
||
|
|
||
|
// Get a list of all sentries that aren't being carried or dying.
|
||
|
for ( int oit = 0; oit < IBaseObjectAutoList::AutoList().Count(); ++oit )
|
||
|
{
|
||
|
CBaseObject* obj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[ oit ] );
|
||
|
|
||
|
if ( obj->ObjectType() == OBJ_SENTRYGUN )
|
||
|
{
|
||
|
if ( !obj->IsDying() && !obj->IsCarried() )
|
||
|
ActiveSentries.AddToTail( obj );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Only go through the NavAreas if we found some live sentries. Hopefully some of these
|
||
|
// sentries will be able to shoot some spies in the face.
|
||
|
if ( ActiveSentries.Count() )
|
||
|
{
|
||
|
// We must iterate all of the nav areas because we're testing visibility
|
||
|
// and arbitrary switchback routes make the use of SearchSurroundingAreas
|
||
|
// not useful.
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea *>( TheNavAreas[ it ] );
|
||
|
|
||
|
// Check all active sentries against this area.
|
||
|
FOR_EACH_VEC( ActiveSentries, oit )
|
||
|
{
|
||
|
const CBaseObject* obj = ActiveSentries[ oit ];
|
||
|
|
||
|
// If this area in range of this sentry?
|
||
|
Vector close;
|
||
|
area->GetClosestPointOnArea( obj->GetAbsOrigin(), &close );
|
||
|
|
||
|
if ( ( obj->GetAbsOrigin() - close ).IsLengthLessThan( SENTRY_MAX_RANGE ) )
|
||
|
{
|
||
|
// Can this sentry reach this area?
|
||
|
if ( area->IsPartiallyVisible( obj->GetAbsOrigin() + Vector( 0, 0, 30.0f ), obj ) )
|
||
|
{
|
||
|
// If this area wasn't already added to m_sentryAreas, do it now.
|
||
|
if ( !area->HasAttributeTF( TF_NAV_BLUE_SENTRY_DANGER | TF_NAV_RED_SENTRY_DANGER ) )
|
||
|
m_sentryAreas.AddToTail( area );
|
||
|
|
||
|
// Mark this area as being potentially dangerous.
|
||
|
area->SetAttributeTF( ( obj->GetTeamNumber() == TF_TEAM_RED ) ? TF_NAV_RED_SENTRY_DANGER : TF_NAV_BLUE_SENTRY_DANGER );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( tf_show_sentry_danger.GetBool() )
|
||
|
DevMsg( "%s: sentries:%d areas count:%d\n", __FUNCTION__, ActiveSentries.Count(), m_sentryAreas.Count() );
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------------
|
||
|
/**
|
||
|
* Return true if a Sentry Gun has been built in the given area
|
||
|
*/
|
||
|
bool CTFNavMesh::IsSentryGunHere( CTFNavArea *area ) const
|
||
|
{
|
||
|
// Check to see if the area is on the highway to the danger zone.
|
||
|
// If it isn't then there shouldn't be a sentry gun here.
|
||
|
if ( area->HasAttributeTF( TF_NAV_BLUE_SENTRY_DANGER | TF_NAV_RED_SENTRY_DANGER ) )
|
||
|
{
|
||
|
// Walk through all the objects built by players
|
||
|
for ( int oit = 0; oit < IBaseObjectAutoList::AutoList().Count(); ++oit )
|
||
|
{
|
||
|
CBaseObject* obj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[ oit ] );
|
||
|
|
||
|
if ( obj->ObjectType() == OBJ_SENTRYGUN )
|
||
|
{
|
||
|
// If this object is a sentry gun, and it's in this nav area, return true.
|
||
|
if ( GetNearestNavArea( obj ) == area )
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// Fill given vector will all objects on the given team
|
||
|
void CTFNavMesh::CollectBuiltObjects( CUtlVector< CBaseObject * > *collectionVector, int team )
|
||
|
{
|
||
|
collectionVector->RemoveAll();
|
||
|
|
||
|
// check all active sentries against this area
|
||
|
for ( int oit = 0; oit < IBaseObjectAutoList::AutoList().Count(); ++oit )
|
||
|
{
|
||
|
CBaseObject* obj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[ oit ] );
|
||
|
|
||
|
if ( team == TEAM_ANY || obj->GetTeamNumber() == team )
|
||
|
{
|
||
|
collectionVector->AddToTail( obj );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
void CTFNavMesh::FireGameEvent( IGameEvent *event )
|
||
|
{
|
||
|
CNavMesh::FireGameEvent( event );
|
||
|
|
||
|
const CUtlString eventName( event->GetName() );
|
||
|
|
||
|
if ( eventName == "teamplay_point_captured" )
|
||
|
{
|
||
|
int whichPoint = event->GetInt( "cp" );
|
||
|
|
||
|
ScheduleRecomputationOfInternalData( POINT_CAPTURED, whichPoint );
|
||
|
}
|
||
|
else if ( eventName == "teamplay_setup_finished" )
|
||
|
{
|
||
|
ScheduleRecomputationOfInternalData( SETUP_FINISHED );
|
||
|
}
|
||
|
else if ( eventName == "teamplay_point_unlocked" )
|
||
|
{
|
||
|
// recompute since doors may have opened/etc (koth_nucleus)
|
||
|
int whichPoint = event->GetInt( "cp" );
|
||
|
|
||
|
ScheduleRecomputationOfInternalData( POINT_UNLOCKED, whichPoint );
|
||
|
}
|
||
|
else if ( eventName == "player_builtobject" ||
|
||
|
eventName == "player_carryobject" ||
|
||
|
eventName == "object_detonated" ||
|
||
|
eventName == "object_destroyed" )
|
||
|
{
|
||
|
// We don't need "player_dropobject" as "player_builtobject" is sent right after.
|
||
|
// Some message have "object", some have "objectid" - use the one that is set.
|
||
|
int objecttype = !event->IsEmpty( "objecttype" ) ? event->GetInt( "objecttype" ) : event->GetInt( "object" );
|
||
|
if ( objecttype == OBJ_SENTRYGUN )
|
||
|
{
|
||
|
if ( tf_show_sentry_danger.GetBool() )
|
||
|
DevMsg( "%s: Got sentrygun %s event\n", __FUNCTION__, eventName.Get() );
|
||
|
|
||
|
OnObjectChanged();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
void CTFNavMesh::BeginCustomAnalysis( bool bIncremental )
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// invoked when custom analysis step is complete
|
||
|
void CTFNavMesh::PostCustomAnalysis( void )
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
void CTFNavMesh::EndCustomAnalysis()
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
/**
|
||
|
* Returns sub-version number of data format used by derived classes
|
||
|
*/
|
||
|
unsigned int CTFNavMesh::GetSubVersionNumber( void ) const
|
||
|
{
|
||
|
// 1: initial implementation
|
||
|
// 2: added TF-specific attribute flags
|
||
|
return 2;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
/**
|
||
|
* Store custom mesh data for derived classes
|
||
|
*/
|
||
|
void CTFNavMesh::SaveCustomData( CUtlBuffer &fileBuffer ) const
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
/**
|
||
|
* Load custom mesh data for derived classes
|
||
|
*/
|
||
|
void CTFNavMesh::LoadCustomData( CUtlBuffer &fileBuffer, unsigned int subVersion )
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
/**
|
||
|
* Recompute travel distance from each team's spawn room for each nav area
|
||
|
*/
|
||
|
void CTFNavMesh::ComputeIncursionDistances( void )
|
||
|
{
|
||
|
VPROF_BUDGET( "CTFNavMesh::ComputeIncursionDistances", "NextBot" );
|
||
|
|
||
|
// invalidate all travel distances
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
|
||
|
for( int i=0; i<TF_TEAM_COUNT; ++i )
|
||
|
{
|
||
|
area->m_distanceFromSpawnRoom[i] = -1.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool isRedComputed = false;
|
||
|
bool isBlueComputed = false;
|
||
|
for ( int i=0; i<IFuncRespawnRoomAutoList::AutoList().Count(); ++i )
|
||
|
{
|
||
|
CFuncRespawnRoom *spawnRoom = static_cast< CFuncRespawnRoom* >( IFuncRespawnRoomAutoList::AutoList()[i] );
|
||
|
|
||
|
if ( !spawnRoom->GetActive() )
|
||
|
continue;
|
||
|
|
||
|
if ( spawnRoom->m_bDisabled )
|
||
|
continue;
|
||
|
|
||
|
// find a spawn point inside this room
|
||
|
for ( int i=0; i<ITFTeamSpawnAutoList::AutoList().Count(); ++i )
|
||
|
{
|
||
|
CTFTeamSpawn *spawnSpot = static_cast< CTFTeamSpawn* >( ITFTeamSpawnAutoList::AutoList()[i] );
|
||
|
|
||
|
if ( !spawnSpot->IsTriggered( NULL ) )
|
||
|
continue;
|
||
|
|
||
|
if ( spawnSpot->IsDisabled() )
|
||
|
continue;
|
||
|
|
||
|
if ( spawnSpot->GetTeamNumber() == TF_TEAM_RED && isRedComputed )
|
||
|
continue;
|
||
|
|
||
|
if ( spawnSpot->GetTeamNumber() == TF_TEAM_BLUE && isBlueComputed )
|
||
|
continue;
|
||
|
|
||
|
if ( spawnRoom->PointIsWithin( spawnSpot->GetAbsOrigin() ) )
|
||
|
{
|
||
|
// found a valid spawn spot in an active spawn room, compute travel distances throughout the nav mesh
|
||
|
CTFNavArea *spawnArea = static_cast< CTFNavArea * >( TheTFNavMesh()->GetNearestNavArea( spawnSpot ) );
|
||
|
if ( spawnArea )
|
||
|
{
|
||
|
ComputeIncursionDistances( spawnArea, spawnSpot->GetTeamNumber() );
|
||
|
|
||
|
if ( spawnSpot->GetTeamNumber() == TF_TEAM_RED )
|
||
|
{
|
||
|
isRedComputed = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
isBlueComputed = true;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !isRedComputed )
|
||
|
{
|
||
|
Warning( "Can't compute incursion distances from the Red spawn room(s). Bots will perform poorly. This is caused by either a missing func_respawnroom, or missing info_player_teamspawn entities within the func_respawnroom.\n" );
|
||
|
}
|
||
|
|
||
|
if ( !isBlueComputed )
|
||
|
{
|
||
|
Warning( "Can't compute incursion distances from the Blue spawn room(s). Bots will perform poorly. This is caused by either a missing func_respawnroom, or missing info_player_teamspawn entities within the func_respawnroom.\n" );
|
||
|
}
|
||
|
|
||
|
if ( !TFGameRules()->IsMannVsMachineMode() )
|
||
|
{
|
||
|
// In Raid mode, the Red (bot) team has no spawn room.
|
||
|
// So, we'll assume the Red incursion distance is the inverse of the Blue incursion distance for now.
|
||
|
// @TODO: Use the Boss battle room as the anchor for computing Red incursion distances
|
||
|
float maxBlueIncursionDistance = 0.0f;
|
||
|
|
||
|
for( int i=0; i<TheNavAreas.Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ i ] );
|
||
|
|
||
|
if ( area->GetIncursionDistance( TF_TEAM_BLUE ) > maxBlueIncursionDistance )
|
||
|
{
|
||
|
maxBlueIncursionDistance = area->GetIncursionDistance( TF_TEAM_BLUE );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for( int i=0; i<TheNavAreas.Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ i ] );
|
||
|
|
||
|
if ( area->GetIncursionDistance( TF_TEAM_BLUE ) >= 0.0f )
|
||
|
{
|
||
|
area->m_distanceFromSpawnRoom[ TF_TEAM_RED ] = maxBlueIncursionDistance - area->GetIncursionDistance( TF_TEAM_BLUE );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------------
|
||
|
/**
|
||
|
* Flood-fill outwards, marking flow distance as we go.
|
||
|
* When we reach an area, stop if it already has a lesser travel distance
|
||
|
*/
|
||
|
void CTFNavMesh::ComputeIncursionDistances( CTFNavArea *spawnArea, int team )
|
||
|
{
|
||
|
if ( spawnArea == NULL || team < 0 || team >= TF_TEAM_COUNT )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CNavArea::ClearSearchLists();
|
||
|
|
||
|
spawnArea->m_distanceFromSpawnRoom[ team ] = 0.0f;
|
||
|
spawnArea->AddToOpenList();
|
||
|
spawnArea->Mark();
|
||
|
spawnArea->SetParent( NULL );
|
||
|
|
||
|
CUtlVectorFixedGrowable< const NavConnect *, 64 > adjAreaVector;
|
||
|
//TFNavAttributeType teamSpawnRoom = ( team == TF_TEAM_RED ) ? TF_NAV_SPAWN_ROOM_RED : TF_NAV_SPAWN_ROOM_BLUE;
|
||
|
|
||
|
while( !CNavArea::IsOpenListEmpty() )
|
||
|
{
|
||
|
// get next area to check
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( CNavArea::PopOpenList() );
|
||
|
|
||
|
bool bIgnoreBlockedAreas = false;
|
||
|
|
||
|
#ifdef TF_RAID_MODE
|
||
|
// TODO: Raid mode ignores blocked areas for now (cap gates break this)
|
||
|
if ( TFGameRules()->IsRaidMode() )
|
||
|
{
|
||
|
bIgnoreBlockedAreas = true;
|
||
|
}
|
||
|
#endif // TF_RAID_MODE
|
||
|
|
||
|
// TODO: Ditto for Mann Vs Machine mode
|
||
|
if ( TFGameRules()->IsMannVsMachineMode() )
|
||
|
{
|
||
|
bIgnoreBlockedAreas = true;
|
||
|
}
|
||
|
|
||
|
if ( !bIgnoreBlockedAreas )
|
||
|
{
|
||
|
// ignore spawn room exits, since they presumably will be open
|
||
|
// ignore setup gates, since they will be open after the setup time
|
||
|
if ( !area->HasAttributeTF( TF_NAV_SPAWN_ROOM_EXIT | TF_NAV_BLUE_SETUP_GATE | TF_NAV_RED_SETUP_GATE ) && area->IsBlocked( team ) )
|
||
|
{
|
||
|
// don't pass through blocked areas
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// explore adjacent floor areas
|
||
|
adjAreaVector.RemoveAll();
|
||
|
|
||
|
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
|
||
|
{
|
||
|
// collect all OUTGOING links from this area to adjacent areas
|
||
|
const NavConnectVector *adjVector = area->GetAdjacentAreas( (NavDirType)dir );
|
||
|
FOR_EACH_VEC( (*adjVector), bit )
|
||
|
{
|
||
|
adjAreaVector.AddToTail( &(*adjVector)[ bit ] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FOR_EACH_VEC( adjAreaVector, vit )
|
||
|
{
|
||
|
const NavConnect *connect = adjAreaVector[ vit ];
|
||
|
CTFNavArea *adjArea = static_cast< CTFNavArea * >( connect->area );
|
||
|
|
||
|
if ( area->ComputeAdjacentConnectionHeightChange( adjArea ) > TF_PLAYER_JUMP_HEIGHT )
|
||
|
{
|
||
|
// don't go up ledges too high to jump
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// compute travel distance
|
||
|
float newTravelDistance = 0.0f;
|
||
|
|
||
|
// travel distance is zero in all areas of our spawn room
|
||
|
// if ( !adjArea->HasAttributeTF( teamSpawnRoom ) )
|
||
|
{
|
||
|
float between = connect->length;
|
||
|
newTravelDistance = area->m_distanceFromSpawnRoom[ team ] + between;
|
||
|
}
|
||
|
|
||
|
float adjacentTravelDistance = adjArea->m_distanceFromSpawnRoom[ team ];
|
||
|
|
||
|
if ( adjacentTravelDistance < 0.0f || adjacentTravelDistance > newTravelDistance )
|
||
|
{
|
||
|
adjArea->m_distanceFromSpawnRoom[ team ] = newTravelDistance;
|
||
|
adjArea->Mark();
|
||
|
adjArea->SetParent( area );
|
||
|
|
||
|
if ( !adjArea->IsOpen() )
|
||
|
{
|
||
|
// Since we're doing a breadth-first search, this area will end up at the end of the list.
|
||
|
// Adding it to the tail explicitly saves us a bunch of list traversals.
|
||
|
adjArea->AddToOpenListTail();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------------
|
||
|
void CTFNavMesh::ComputeInvasionAreas( void )
|
||
|
{
|
||
|
VPROF_BUDGET( "CTFNavMesh::ComputeInvasionAreas", "NextBot" );
|
||
|
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
|
||
|
area->ComputeInvasionAreaVectors();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------------
|
||
|
class CCollectAndLabelSpawnRoomAreas
|
||
|
{
|
||
|
public:
|
||
|
CCollectAndLabelSpawnRoomAreas( void )
|
||
|
{
|
||
|
m_room = NULL;
|
||
|
}
|
||
|
|
||
|
void Init( CFuncRespawnRoom *room, int team, CUtlVector< CTFNavArea * > *areaVector )
|
||
|
{
|
||
|
m_room = room;
|
||
|
m_team = team;
|
||
|
m_areaVector = areaVector;
|
||
|
}
|
||
|
|
||
|
bool operator() ( CNavArea *baseArea )
|
||
|
{
|
||
|
static Vector stepHeight( 0.0f, 0.0f, 18.0f );
|
||
|
|
||
|
if ( !m_room )
|
||
|
return true;
|
||
|
|
||
|
if ( m_room->PointIsWithin( baseArea->GetCenter() + stepHeight ) ||
|
||
|
m_room->PointIsWithin( baseArea->GetCorner( NORTH_WEST ) + stepHeight ) ||
|
||
|
m_room->PointIsWithin( baseArea->GetCorner( NORTH_EAST ) + stepHeight ) ||
|
||
|
m_room->PointIsWithin( baseArea->GetCorner( SOUTH_WEST ) + stepHeight ) ||
|
||
|
m_room->PointIsWithin( baseArea->GetCorner( SOUTH_EAST ) + stepHeight ) )
|
||
|
{
|
||
|
CTFNavArea *area = (CTFNavArea *)baseArea;
|
||
|
|
||
|
area->SetAttributeTF( ( m_team == TF_TEAM_RED ) ? TF_NAV_SPAWN_ROOM_RED : TF_NAV_SPAWN_ROOM_BLUE );
|
||
|
|
||
|
m_areaVector->AddToTail( area );
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
CFuncRespawnRoom *m_room;
|
||
|
int m_team;
|
||
|
CUtlVector< CTFNavArea * > *m_areaVector;
|
||
|
};
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------------
|
||
|
void CTFNavMesh::CollectAndMarkSpawnRoomExits( CTFNavArea *area, CUtlVector< CTFNavArea * > *exitAreaVector )
|
||
|
{
|
||
|
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
|
||
|
{
|
||
|
const NavConnectVector *connect = area->GetAdjacentAreas( (NavDirType)dir );
|
||
|
if ( connect )
|
||
|
{
|
||
|
FOR_EACH_VEC( (*connect), cit )
|
||
|
{
|
||
|
CTFNavArea *adjArea = (CTFNavArea *)connect->Element(cit).area;
|
||
|
|
||
|
if ( !adjArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) )
|
||
|
{
|
||
|
// adjacent area leads out of spawn room - this is an exit
|
||
|
area->SetAttributeTF( TF_NAV_SPAWN_ROOM_EXIT );
|
||
|
exitAreaVector->AddToTail( area );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------------
|
||
|
void CTFNavMesh::DecorateMesh( void )
|
||
|
{
|
||
|
VPROF_BUDGET( "CTFNavMesh::DecorateMesh", "NextBot" );
|
||
|
|
||
|
CBaseEntity *entity = NULL;
|
||
|
CCollectAndLabelSpawnRoomAreas collectAndLabel;
|
||
|
Extent extent;
|
||
|
|
||
|
// mark spawn rooms
|
||
|
m_redSpawnRoomAreaVector.RemoveAll();
|
||
|
m_blueSpawnRoomAreaVector.RemoveAll();
|
||
|
|
||
|
for ( int iFuncRespawnRoom=0; iFuncRespawnRoom<IFuncRespawnRoomAutoList::AutoList().Count(); ++iFuncRespawnRoom )
|
||
|
{
|
||
|
CFuncRespawnRoom *respawnRoom = static_cast< CFuncRespawnRoom* >( IFuncRespawnRoomAutoList::AutoList()[iFuncRespawnRoom] );
|
||
|
|
||
|
if ( !respawnRoom->GetActive() )
|
||
|
continue;
|
||
|
|
||
|
if ( respawnRoom->m_bDisabled )
|
||
|
continue;
|
||
|
|
||
|
// func_respawn rooms only enforce spawn room rules. We need to search for enabled
|
||
|
// info_player_teamspawn entities contained within an active func_respawnroom in
|
||
|
// order to locate the current set of active spawn rooms
|
||
|
// find a spawn point inside this room
|
||
|
for ( int iTFTeamSpawn=0; iTFTeamSpawn<ITFTeamSpawnAutoList::AutoList().Count(); ++iTFTeamSpawn )
|
||
|
{
|
||
|
CTFTeamSpawn *spawnSpot = static_cast< CTFTeamSpawn* >( ITFTeamSpawnAutoList::AutoList()[iTFTeamSpawn] );
|
||
|
|
||
|
if ( !spawnSpot->IsTriggered( NULL ) )
|
||
|
continue;
|
||
|
|
||
|
if ( spawnSpot->IsDisabled() )
|
||
|
continue;
|
||
|
|
||
|
if ( respawnRoom->PointIsWithin( spawnSpot->GetAbsOrigin() ) )
|
||
|
{
|
||
|
// found a valid spawn spot in an active spawn room
|
||
|
collectAndLabel.Init( respawnRoom, spawnSpot->GetTeamNumber(), spawnSpot->GetTeamNumber() == TF_TEAM_RED ? &m_redSpawnRoomAreaVector : &m_blueSpawnRoomAreaVector );
|
||
|
extent.Init( respawnRoom );
|
||
|
|
||
|
TheNavMesh->ForAllAreasOverlappingExtent( collectAndLabel, extent );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// mark each spawn room area adjacent to a non-spawn room area as an exit
|
||
|
m_redSpawnRoomExitAreaVector.RemoveAll();
|
||
|
m_blueSpawnRoomExitAreaVector.RemoveAll();
|
||
|
|
||
|
FOR_EACH_VEC( m_redSpawnRoomAreaVector, rit )
|
||
|
{
|
||
|
CollectAndMarkSpawnRoomExits( m_redSpawnRoomAreaVector[ rit ], &m_redSpawnRoomExitAreaVector );
|
||
|
}
|
||
|
|
||
|
FOR_EACH_VEC( m_blueSpawnRoomAreaVector, bit )
|
||
|
{
|
||
|
CollectAndMarkSpawnRoomExits( m_blueSpawnRoomAreaVector[ bit ], &m_blueSpawnRoomExitAreaVector );
|
||
|
}
|
||
|
|
||
|
|
||
|
// mark ammo areas
|
||
|
entity = NULL;
|
||
|
while( ( entity = gEntList.FindEntityByClassname( entity, "item_ammopack*" ) ) != NULL )
|
||
|
{
|
||
|
CTFNavArea *area = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( entity->GetAbsOrigin() );
|
||
|
if ( area )
|
||
|
{
|
||
|
area->SetAttributeTF( TF_NAV_HAS_AMMO );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// mark health areas
|
||
|
entity = NULL;
|
||
|
while( ( entity = gEntList.FindEntityByClassname( entity, "item_healthkit*" ) ) != NULL )
|
||
|
{
|
||
|
CTFNavArea *area = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( entity->GetAbsOrigin() );
|
||
|
if ( area )
|
||
|
{
|
||
|
area->SetAttributeTF( TF_NAV_HAS_HEALTH );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// mark control points
|
||
|
for( int p=0; p<MAX_CONTROL_POINTS; ++p )
|
||
|
{
|
||
|
CUtlVector< CTFNavArea * > *pointAreaVector = &m_controlPointAreaVector[ p ];
|
||
|
|
||
|
for( int i=0; i<pointAreaVector->Count(); ++i )
|
||
|
{
|
||
|
pointAreaVector->Element(i)->SetAttributeTF( TF_NAV_CONTROL_POINT );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------------
|
||
|
void CTFNavMesh::RemoveAllMeshDecoration( void )
|
||
|
{
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
|
||
|
// wipe all non-persistent attributes
|
||
|
area->ClearAttributeTF( (TFNavAttributeType)( ~TF_NAV_PERSISTENT_ATTRIBUTES ) );
|
||
|
}
|
||
|
|
||
|
// We just cleared all our SENTRY_DANGER attributes. Wipe m_sentryAreas and recompute.
|
||
|
m_sentryAreas.RemoveAll();
|
||
|
OnObjectChanged();
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------------
|
||
|
void CTFNavMesh::ResetMeshAttributes( bool bScheduleRecomputation )
|
||
|
{
|
||
|
// Clear all sentry danger attributes.
|
||
|
FOR_EACH_VEC( m_sentryAreas, nit )
|
||
|
{
|
||
|
// One of the sentry danger attributes should be set.
|
||
|
Assert( bScheduleRecomputation || m_sentryAreas[ nit ]->HasAttributeTF( TF_NAV_BLUE_SENTRY_DANGER | TF_NAV_RED_SENTRY_DANGER ) );
|
||
|
m_sentryAreas[ nit ]->ClearAttributeTF( TF_NAV_BLUE_SENTRY_DANGER | TF_NAV_RED_SENTRY_DANGER );
|
||
|
}
|
||
|
m_sentryAreas.RemoveAll();
|
||
|
|
||
|
#ifdef DBGFLAG_ASSERT
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
// Sentry danger attributes should not be set anywhere.
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
Assert( !area->HasAttributeTF( TF_NAV_BLUE_SENTRY_DANGER | TF_NAV_RED_SENTRY_DANGER ) );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if ( bScheduleRecomputation )
|
||
|
{
|
||
|
ScheduleRecomputationOfInternalData( RESET );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------------
|
||
|
class DrawIncursionFlow
|
||
|
{
|
||
|
public:
|
||
|
bool operator() ( CNavArea *baseArea )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( baseArea );
|
||
|
|
||
|
int team = ( tf_show_incursion_flow.GetInt() == 1 ) ? TF_TEAM_RED : TF_TEAM_BLUE;
|
||
|
|
||
|
const float cycleRange = 2500.0f;
|
||
|
const float cycleRate = 0.333f; // cycles/sec
|
||
|
|
||
|
float baseFlow = area->GetIncursionDistance( team );
|
||
|
|
||
|
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
|
||
|
{
|
||
|
const NavConnectVector *adjVector = area->GetAdjacentAreas( (NavDirType)dir );
|
||
|
FOR_EACH_VEC( (*adjVector), bit )
|
||
|
{
|
||
|
CTFNavArea *adjArea = static_cast< CTFNavArea * >( (*adjVector)[ bit ].area );
|
||
|
|
||
|
if ( area->ComputeAdjacentConnectionHeightChange( adjArea ) > TF_PLAYER_JUMP_HEIGHT )
|
||
|
{
|
||
|
// don't go up ledges too high to jump
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
float adjFlow = adjArea->GetIncursionDistance( team );
|
||
|
|
||
|
if ( adjFlow > baseFlow )
|
||
|
{
|
||
|
float cycle = fmod( adjFlow - ( gpGlobals->curtime * cycleRate * cycleRange ), cycleRange );
|
||
|
float t = 2.0f * cycle / cycleRange;
|
||
|
if ( t > 1.0f )
|
||
|
{
|
||
|
t = 2.0f - t;
|
||
|
}
|
||
|
|
||
|
int r, g, b;
|
||
|
if ( team == TF_TEAM_RED )
|
||
|
{
|
||
|
r = 255 * t;
|
||
|
g = 0;
|
||
|
b = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
r = 0;
|
||
|
g = 0;
|
||
|
b = 255 * t;
|
||
|
}
|
||
|
|
||
|
NDebugOverlay::HorzArrow( area->GetCenter(), adjArea->GetCenter(), 5.0f, r, g, b, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
void CTFNavMesh::UpdateDebugDisplay( void ) const
|
||
|
{
|
||
|
// avoid Warning() spam from UTIL_GetListenServerHost when on a dedicated server
|
||
|
if ( engine->IsDedicatedServer() )
|
||
|
return;
|
||
|
|
||
|
CBasePlayer *player = UTIL_GetListenServerHost();
|
||
|
if ( player == NULL )
|
||
|
return;
|
||
|
|
||
|
|
||
|
if ( tf_show_in_combat_areas.GetBool() )
|
||
|
{
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
|
||
|
if ( area->IsInCombat() )
|
||
|
{
|
||
|
float t = area->GetCombatIntensity();
|
||
|
area->DrawFilled( t * 255, 0, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( tf_show_enemy_invasion_areas.GetBool() )
|
||
|
{
|
||
|
CTFNavArea *myArea = static_cast< CTFNavArea * >( player->GetLastKnownArea() );
|
||
|
|
||
|
if ( myArea )
|
||
|
{
|
||
|
const CUtlVector< CTFNavArea * > &invasionAreaVector = myArea->GetEnemyInvasionAreaVector( player->GetTeamNumber() );
|
||
|
|
||
|
FOR_EACH_VEC( invasionAreaVector, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( invasionAreaVector[ it ] );
|
||
|
|
||
|
area->DrawFilled( 255, 0, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( tf_show_bomb_drop_areas.GetBool() )
|
||
|
{
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_BOMB_CAN_DROP_HERE ) )
|
||
|
{
|
||
|
area->DrawFilled( 0, 255, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( tf_show_blocked_areas.GetBool() )
|
||
|
{
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
const char *describe = "";
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_BLOCKED ) )
|
||
|
{
|
||
|
area->DrawFilled( 255, 0, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true, 0.0f );
|
||
|
}
|
||
|
|
||
|
if ( area->IsBlocked( TF_TEAM_RED ) )
|
||
|
{
|
||
|
if ( area->IsBlocked( TF_TEAM_BLUE ) )
|
||
|
{
|
||
|
area->DrawFilled( 100, 0, 100, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Blocked for All";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
area->DrawFilled( 100, 0, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Blocked for Red";
|
||
|
}
|
||
|
}
|
||
|
else if ( area->IsBlocked( TF_TEAM_BLUE ) )
|
||
|
{
|
||
|
area->DrawFilled( 0, 0, 100, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Blocked for Blue";
|
||
|
}
|
||
|
|
||
|
if ( describe && TheNavMesh->GetSelectedArea() == area )
|
||
|
{
|
||
|
NDebugOverlay::Text( area->GetCenter(), describe, false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( tf_show_incursion_flow.GetInt() > 0 || tf_show_incursion_flow_gradient.GetInt() > 0 )
|
||
|
{
|
||
|
Vector forward;
|
||
|
AngleVectors( player->EyeAngles() + player->GetPunchAngle(), &forward );
|
||
|
|
||
|
float maxRange = 2000.0f;
|
||
|
Vector to = player->EyePosition() + maxRange * forward;
|
||
|
|
||
|
trace_t result;
|
||
|
CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
|
||
|
UTIL_TraceLine( player->EyePosition(), to, MASK_NPCSOLID, &filter, &result );
|
||
|
|
||
|
CTFNavArea *selectedArea = static_cast< CTFNavArea * >( TheNavMesh->GetNearestNavArea( result.endpos, false, 500.0f ) );
|
||
|
|
||
|
if ( selectedArea )
|
||
|
{
|
||
|
if ( tf_show_incursion_flow.GetInt() > 0 )
|
||
|
{
|
||
|
DrawIncursionFlow draw;
|
||
|
SearchSurroundingAreas( selectedArea, selectedArea->GetCenter(), draw, tf_show_incursion_flow_range.GetFloat() );
|
||
|
}
|
||
|
else if ( tf_show_incursion_flow_gradient.GetInt() > 0 )
|
||
|
{
|
||
|
int myTeam;
|
||
|
int r,g,b;
|
||
|
if ( tf_show_incursion_flow_gradient.GetInt() == 1 )
|
||
|
{
|
||
|
myTeam = TF_TEAM_RED;
|
||
|
r = 255;
|
||
|
g = 0;
|
||
|
b = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
myTeam = TF_TEAM_BLUE;
|
||
|
r = 0;
|
||
|
g = 0;
|
||
|
b = 255;
|
||
|
}
|
||
|
|
||
|
selectedArea->DrawFilled( r, g, b, 255 );
|
||
|
|
||
|
CUtlVector< CTFNavArea * > areaVector;
|
||
|
selectedArea->CollectPriorIncursionAreas( myTeam, &areaVector );
|
||
|
FOR_EACH_VEC( areaVector, p )
|
||
|
{
|
||
|
areaVector[p]->DrawFilled( r/2, g/2, b/2, 255 );
|
||
|
}
|
||
|
|
||
|
selectedArea->CollectNextIncursionAreas( myTeam, &areaVector );
|
||
|
FOR_EACH_VEC( areaVector, n )
|
||
|
{
|
||
|
areaVector[n]->DrawFilled( MIN( r+100, 255 ), MIN( g+100, 255 ), MIN( b+100, 255 ), 255 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( tf_show_mesh_decoration.GetBool() && !tf_show_mesh_decoration_manual.GetBool() )
|
||
|
{
|
||
|
// render these from cached vectors to verify their data
|
||
|
int i;
|
||
|
const CUtlVector< CTFNavArea * > *areaVector;
|
||
|
|
||
|
areaVector = GetSpawnRoomAreas( TF_TEAM_BLUE );
|
||
|
if ( areaVector )
|
||
|
{
|
||
|
for( i=0; i<areaVector->Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = areaVector->Element(i);
|
||
|
|
||
|
if ( !area->HasAttributeTF( TF_NAV_SPAWN_ROOM_EXIT ) )
|
||
|
{
|
||
|
area->DrawFilled( 0, 0, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
|
||
|
if ( TheNavMesh->GetSelectedArea() == area )
|
||
|
{
|
||
|
NDebugOverlay::Text( area->GetCenter(), "Blue Spawn Room", false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
areaVector = GetSpawnRoomExitAreas( TF_TEAM_BLUE );
|
||
|
if ( areaVector )
|
||
|
{
|
||
|
for( i=0; i<areaVector->Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = areaVector->Element(i);
|
||
|
|
||
|
area->DrawFilled( 150, 150, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
|
||
|
if ( TheNavMesh->GetSelectedArea() == area )
|
||
|
{
|
||
|
NDebugOverlay::Text( area->GetCenter(), "Blue Spawn Exit", false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
areaVector = GetSpawnRoomAreas( TF_TEAM_RED );
|
||
|
if ( areaVector )
|
||
|
{
|
||
|
for( i=0; i<areaVector->Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = areaVector->Element(i);
|
||
|
|
||
|
if ( !area->HasAttributeTF( TF_NAV_SPAWN_ROOM_EXIT ) )
|
||
|
{
|
||
|
area->DrawFilled( 255, 0, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
|
||
|
if ( TheNavMesh->GetSelectedArea() == area )
|
||
|
{
|
||
|
NDebugOverlay::Text( area->GetCenter(), "Red Spawn Room", false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
areaVector = GetSpawnRoomExitAreas( TF_TEAM_RED );
|
||
|
if ( areaVector )
|
||
|
{
|
||
|
for( i=0; i<areaVector->Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = areaVector->Element(i);
|
||
|
|
||
|
area->DrawFilled( 255, 150, 150, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
|
||
|
if ( TheNavMesh->GetSelectedArea() == area )
|
||
|
{
|
||
|
NDebugOverlay::Text( area->GetCenter(), "Red Spawn Exit", false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( tf_show_mesh_decoration.GetBool() || tf_show_mesh_decoration_manual.GetBool() )
|
||
|
{
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
const char *describe = "";
|
||
|
|
||
|
if ( !tf_show_mesh_decoration_manual.GetBool() )
|
||
|
{
|
||
|
if ( area->HasAttributeTF( TF_NAV_HAS_AMMO ) && area->HasAttributeTF( TF_NAV_HAS_HEALTH ) )
|
||
|
{
|
||
|
area->DrawFilled( 255, 0, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Health & Ammo";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( area->HasAttributeTF( TF_NAV_HAS_AMMO ) )
|
||
|
{
|
||
|
area->DrawFilled( 100, 100, 100, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Ammo";
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_HAS_HEALTH ) )
|
||
|
{
|
||
|
area->DrawFilled( 255, 150, 150, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Health";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_CONTROL_POINT ) )
|
||
|
{
|
||
|
area->DrawFilled( 0, 255, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Control Point";
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_BLUE_ONE_WAY_DOOR ) )
|
||
|
{
|
||
|
area->DrawFilled( 100, 100, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_RED_ONE_WAY_DOOR ) )
|
||
|
{
|
||
|
area->DrawFilled( 255, 100, 100, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_SNIPER_SPOT ) )
|
||
|
{
|
||
|
area->DrawFilled( 255, 255, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Sniper Spot";
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_SENTRY_SPOT ) )
|
||
|
{
|
||
|
area->DrawFilled( 255, 100, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Sentry Spot";
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_NO_SPAWNING ) )
|
||
|
{
|
||
|
area->DrawFilled( 100, 100, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "No Spawning";
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_RESCUE_CLOSET ) )
|
||
|
{
|
||
|
area->DrawFilled( 0, 255, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Rescue Closet";
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_BLOCKED_UNTIL_POINT_CAPTURE ) )
|
||
|
{
|
||
|
area->DrawFilled( 0, 255, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_WITH_SECOND_POINT ) )
|
||
|
{
|
||
|
describe = "Blocked Until Second Point Captured";
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_WITH_THIRD_POINT ) )
|
||
|
{
|
||
|
describe = "Blocked Until Third Point Captured";
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_WITH_FOURTH_POINT ) )
|
||
|
{
|
||
|
describe = "Blocked Until Fourth Point Captured";
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_WITH_FIFTH_POINT ) )
|
||
|
{
|
||
|
describe = "Blocked Until Fifth Point Captured";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
describe = "Blocked Until First Point Captured";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_BLOCKED_AFTER_POINT_CAPTURE ) )
|
||
|
{
|
||
|
area->DrawFilled( 255, 255, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_WITH_SECOND_POINT ) )
|
||
|
{
|
||
|
describe = "Blocked After Second Point Captured";
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_WITH_THIRD_POINT ) )
|
||
|
{
|
||
|
describe = "Blocked After Third Point Captured";
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_WITH_FOURTH_POINT ) )
|
||
|
{
|
||
|
describe = "Blocked After Fourth Point Captured";
|
||
|
}
|
||
|
else if ( area->HasAttributeTF( TF_NAV_WITH_FIFTH_POINT ) )
|
||
|
{
|
||
|
describe = "Blocked After Fifth Point Captured";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
describe = "Blocked After First Point Captured";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_BLUE_SETUP_GATE ) )
|
||
|
{
|
||
|
area->DrawFilled( 0, 0, 100, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Blue Setup Gate";
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_RED_SETUP_GATE ) )
|
||
|
{
|
||
|
area->DrawFilled( 100, 0, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Red Setup Gate";
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_DOOR_ALWAYS_BLOCKS ) )
|
||
|
{
|
||
|
area->DrawFilled( 100, 0, 100, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Door Always Blocks";
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_DOOR_NEVER_BLOCKS ) )
|
||
|
{
|
||
|
area->DrawFilled( 0, 100, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Door Never Blocks";
|
||
|
}
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_UNBLOCKABLE ) )
|
||
|
{
|
||
|
area->DrawFilled( 0, 200, 100, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
describe = "Unblockable";
|
||
|
}
|
||
|
|
||
|
if ( describe && TheNavMesh->GetSelectedArea() == area )
|
||
|
{
|
||
|
NDebugOverlay::Text( area->GetCenter(), describe, false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( tf_show_sentry_danger.GetBool() )
|
||
|
{
|
||
|
if ( tf_show_sentry_danger.GetInt() == 2 )
|
||
|
{
|
||
|
// Walk all TheNavAreas entries. Left this code in to help debug in case
|
||
|
// TheNavAreas is never not _exactly_ the same as m_sentryAreas.
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
const CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
int r = area->HasAttributeTF( TF_NAV_RED_SENTRY_DANGER ) * 255;
|
||
|
int b = area->HasAttributeTF( TF_NAV_BLUE_SENTRY_DANGER ) * 255;
|
||
|
|
||
|
if ( r || b )
|
||
|
{
|
||
|
area->DrawFilled( r, 0, b, 80, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Only go through the m_SentryAreas entries. Should be the same as walking the
|
||
|
// entire TheNavAreas, but a lot faster.
|
||
|
FOR_EACH_VEC( m_sentryAreas, nit )
|
||
|
{
|
||
|
const CTFNavArea *area = m_sentryAreas[ nit ];
|
||
|
int r = area->HasAttributeTF( TF_NAV_RED_SENTRY_DANGER ) * 255;
|
||
|
int b = area->HasAttributeTF( TF_NAV_BLUE_SENTRY_DANGER ) * 255;
|
||
|
|
||
|
if ( r || b )
|
||
|
{
|
||
|
area->DrawFilled( r, 0, b, 80, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( tf_show_actor_potential_visibility.GetBool() )
|
||
|
{
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
|
||
|
if ( area->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) )
|
||
|
{
|
||
|
if ( area->IsPotentiallyVisibleToTeam( TF_TEAM_RED ) )
|
||
|
{
|
||
|
area->DrawFilled( 255, 0, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
area->DrawFilled( 0, 0, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
}
|
||
|
}
|
||
|
else if ( area->IsPotentiallyVisibleToTeam( TF_TEAM_RED ) )
|
||
|
{
|
||
|
area->DrawFilled( 255, 0, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
if ( tf_show_gate_defense_areas.GetBool() )
|
||
|
{
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_DEFEND_SETUP_GATES ) )
|
||
|
{
|
||
|
if ( area->HasAttributeTF( TF_NAV_DEFEND_VIA_SNIPING ) )
|
||
|
area->DrawFilled( 0, 255, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
else if ( area->HasAttributeTF( TF_NAV_DEFEND_VIA_AMBUSH ) )
|
||
|
area->DrawFilled( 255, 0, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
else
|
||
|
area->DrawFilled( 0, 0, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( tf_show_point_defense_areas.GetBool() )
|
||
|
{
|
||
|
FOR_EACH_VEC( TheNavAreas, it )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ it ] );
|
||
|
|
||
|
if ( area->HasAttributeTF( TF_NAV_DEFEND_POINT ) )
|
||
|
{
|
||
|
if ( area->HasAttributeTF( TF_NAV_DEFEND_VIA_SNIPING ) )
|
||
|
area->DrawFilled( 0, 255, 100, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
else if ( area->HasAttributeTF( TF_NAV_DEFEND_VIA_AMBUSH ) )
|
||
|
area->DrawFilled( 255, 150, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
else
|
||
|
area->DrawFilled( 0, 150, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER, true );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
if ( tf_show_control_points.GetBool() )
|
||
|
{
|
||
|
for( int which=0; which<MAX_CONTROL_POINTS; ++which )
|
||
|
{
|
||
|
for( int i=0; i<m_controlPointAreaVector[ which ].Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = m_controlPointAreaVector[ which ][ i ];
|
||
|
|
||
|
if ( m_controlPointCenterAreaVector[ which ] == area )
|
||
|
{
|
||
|
area->DrawFilled( 255, 255, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
area->DrawFilled( 255, 150, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------------
|
||
|
/**
|
||
|
* Populate the given "ambushVector" with good areas to lurk in ambush for the invading enemy team
|
||
|
*/
|
||
|
void CTFNavMesh::CollectAmbushAreas( CUtlVector< CTFNavArea * > *ambushVector, CTFNavArea *startArea, int teamToAmbush, float searchRadius, float incursionTolerance ) const
|
||
|
{
|
||
|
ScanSelectAmbushAreas selector( ambushVector, teamToAmbush, startArea->GetIncursionDistance( teamToAmbush ) + incursionTolerance );
|
||
|
SearchSurroundingAreas( startArea, startArea->GetCenter(), selector, searchRadius );
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------------
|
||
|
/**
|
||
|
* Populate the given vector with areas that are just outside of the given team's spawn room(s)
|
||
|
*/
|
||
|
void CTFNavMesh::CollectSpawnRoomThresholdAreas( CUtlVector< CTFNavArea * > *spawnExitAreaVector, int team ) const
|
||
|
{
|
||
|
const CUtlVector< CTFNavArea * > *exitAreaVector = GetSpawnRoomExitAreas( team );
|
||
|
|
||
|
if ( !exitAreaVector )
|
||
|
return;
|
||
|
|
||
|
for( int i=0; i<exitAreaVector->Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ i ] );
|
||
|
|
||
|
// find largest non-spawn-room area connected to this exit
|
||
|
CTFNavArea *exitArea = NULL;
|
||
|
float exitAreaSize = 0.0f;
|
||
|
|
||
|
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
|
||
|
{
|
||
|
const NavConnectVector *adjConnect = area->GetAdjacentAreas( (NavDirType)dir );
|
||
|
|
||
|
for( int j=0; j<adjConnect->Count(); ++j )
|
||
|
{
|
||
|
CTFNavArea *adjArea = (CTFNavArea *)adjConnect->Element(j).area;
|
||
|
|
||
|
if ( !adjArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_RED | TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_EXIT ) )
|
||
|
{
|
||
|
// this area is outside of the spawn room
|
||
|
float size = adjArea->GetSizeX() * adjArea->GetSizeY();
|
||
|
if ( size > exitAreaSize )
|
||
|
{
|
||
|
exitArea = adjArea;
|
||
|
exitAreaSize = size;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( exitArea )
|
||
|
{
|
||
|
spawnExitAreaVector->AddToTail( exitArea );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------------
|
||
|
// Populate the given vector with areas that have a bomb travel distance within the given range
|
||
|
void CTFNavMesh::CollectAreaWithinBombTravelRange( CUtlVector< CTFNavArea * > *spawnExitAreaVector, float minTravel, float maxTravel ) const
|
||
|
{
|
||
|
for( int i=0; i<TheNavAreas.Count(); ++i )
|
||
|
{
|
||
|
CTFNavArea *area = static_cast< CTFNavArea * >( TheNavAreas[ i ] );
|
||
|
|
||
|
float travelDistance = area->GetTravelDistanceToBombTarget();
|
||
|
|
||
|
if ( travelDistance >= minTravel && travelDistance <= maxTravel )
|
||
|
{
|
||
|
spawnExitAreaVector->AddToTail( area );
|
||
|
}
|
||
|
}
|
||
|
}
|