Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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