This commit is contained in:
FluorescentCIAAfricanAmerican
2020-04-22 12:56:21 -04:00
commit 3bf9df6b27
15370 changed files with 5489726 additions and 0 deletions
+537
View File
@@ -0,0 +1,537 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_nav_area.h
// TF specific nav area
// Michael Booth, February 2009
#include "cbase.h"
#include "tf_nav_mesh.h"
#include "tf_nav_area.h"
#include "tf_gamerules.h"
#include "bot/tf_bot.h"
#include "nav_pathfind.h"
ConVar tf_nav_show_incursion_distance( "tf_nav_show_incursion_distance", "0", FCVAR_CHEAT, "Display travel distances from current spawn room (1=red, 2=blue)" );
ConVar tf_nav_show_bomb_target_distance( "tf_nav_show_bomb_target_distance", "0", FCVAR_CHEAT, "Display travel distances to bomb target (MvM mode)" );
ConVar tf_nav_show_turf_ownership( "tf_nav_show_turf_ownership", "0", FCVAR_CHEAT, "Color nav area by smallest incursion distance" );
ConVar tf_nav_in_combat_duration( "tf_nav_in_combat_duration", "30", FCVAR_CHEAT, "How long after gunfire occurs is this area still considered to be 'in combat'" );
ConVar tf_nav_combat_build_rate( "tf_nav_combat_build_rate", "0.05", FCVAR_CHEAT, "Gunfire/second increase (combat caps at 1.0)" );
ConVar tf_nav_combat_decay_rate( "tf_nav_combat_decay_rate", "0.022", FCVAR_CHEAT, "Decay/second toward zero" );
ConVar tf_show_sniper_areas( "tf_show_sniper_areas", "0", FCVAR_CHEAT );
ConVar tf_show_sniper_areas_safety_range( "tf_show_sniper_areas_safety_range", "1000", FCVAR_CHEAT );
ConVar tf_show_incursion_range( "tf_show_incursion_range", "0", FCVAR_CHEAT, "1 = red, 2 = blue" );
ConVar tf_show_incursion_range_min( "tf_show_incursion_range_min", "0", FCVAR_CHEAT, "Highlight areas with incursion distances between min and max cvar values" );
ConVar tf_show_incursion_range_max( "tf_show_incursion_range_max", "0", FCVAR_CHEAT, "Highlight areas with incursion distances between min and max cvar values" );
//------------------------------------------------------------------------------------------------
CTFNavArea::CTFNavArea( void )
{
m_attributeFlags = 0;
m_wanderCount = 0;
m_combatIntensity = 0.0f;
m_distanceToBombTarget = 0.0f;
m_TFMark = 0;
m_invasionSearchMarker = (unsigned int)-1;
}
//------------------------------------------------------------------------------------------------
/**
* (EXTEND) invoked when map is initially loaded
*/
void CTFNavArea::OnServerActivate( void )
{
BaseClass::OnServerActivate();
ClearAllPotentiallyVisibleActors();
}
//------------------------------------------------------------------------------------------------
/**
* (EXTEND) invoked for each area when the round restarts
*/
void CTFNavArea::OnRoundRestart( void )
{
BaseClass::OnRoundRestart();
ClearAllPotentiallyVisibleActors();
m_combatIntensity = 0.0f;
}
//------------------------------------------------------------------------------------------------
/**
* For game-specific analysis
*/
void CTFNavArea::CustomAnalysis( bool isIncremental )
{
}
//------------------------------------------------------------------------------------------------
/**
* Draw area for debugging & editing
*/
void CTFNavArea::Draw( void ) const
{
CNavArea::Draw();
#ifdef TF_RAID_MODE
if ( TFGameRules()->IsRaidMode() && m_wanderCount > 0 )
{
NDebugOverlay::Text( GetCenter(), UTIL_VarArgs( "%d", m_wanderCount ), false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
#endif // TF_RAID_MODE
if ( tf_nav_show_incursion_distance.GetBool() )
{
NDebugOverlay::Text( GetCenter(), UTIL_VarArgs( "R:%3.1f B:%3.1f", GetIncursionDistance( TF_TEAM_RED ), GetIncursionDistance( TF_TEAM_BLUE ) ), false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
if ( tf_nav_show_bomb_target_distance.GetBool() )
{
NDebugOverlay::Text( GetCenter(), UTIL_VarArgs( "%3.1f", GetTravelDistanceToBombTarget() ), false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
if ( tf_show_sniper_areas.GetBool() )
{
bool redSniper = IsAwayFromInvasionAreas( TF_TEAM_RED, tf_show_sniper_areas_safety_range.GetFloat() );
bool blueSniper = IsAwayFromInvasionAreas( TF_TEAM_BLUE, tf_show_sniper_areas_safety_range.GetFloat() );
if ( blueSniper )
{
if ( redSniper )
{
// both teams like this spot?
DrawFilled( 255, 0, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
else
{
// blue sniper area
DrawFilled( 0, 0, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
}
else if ( redSniper )
{
// red sniper area
DrawFilled( 255, 0, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
}
int rangeTeam = tf_show_incursion_range.GetInt();
if ( rangeTeam > 0 )
{
rangeTeam += ( TF_TEAM_RED - 1);
float range = GetIncursionDistance( rangeTeam );
if ( range >= tf_show_incursion_range_min.GetFloat() && range <= tf_show_incursion_range_max.GetFloat() )
{
DrawFilled( 0, 255, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
}
}
//------------------------------------------------------------------------------------------------
/**
* Return adjacent area with largest increase in incursion distance
*/
CTFNavArea *CTFNavArea::GetNextIncursionArea( int team ) const
{
CTFNavArea *nextIncursionArea = NULL;
float nextIncursionDistance = GetIncursionDistance( team );
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
{
const NavConnectVector *adjVector = GetAdjacentAreas( (NavDirType)dir );
FOR_EACH_VEC( (*adjVector), bit )
{
CTFNavArea *adjArea = static_cast< CTFNavArea * >( (*adjVector)[ bit ].area );
if ( adjArea->GetIncursionDistance( team ) > nextIncursionDistance )
{
nextIncursionArea = adjArea;
nextIncursionDistance = adjArea->GetIncursionDistance( team );
}
}
}
return nextIncursionArea;
}
//-----------------------------------------------------------------------------
// Populate 'priorVector' with a collection of adjacent areas that have a lower incursion distance that this area
void CTFNavArea::CollectPriorIncursionAreas( int team, CUtlVector< CTFNavArea * > *priorVector )
{
float myIncursionDistance = GetIncursionDistance( team );
priorVector->RemoveAll();
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
{
const NavConnectVector *adjVector = GetAdjacentAreas( (NavDirType)dir );
FOR_EACH_VEC( (*adjVector), bit )
{
CTFNavArea *adjArea = static_cast< CTFNavArea * >( (*adjVector)[ bit ].area );
if ( adjArea->GetIncursionDistance( team ) < myIncursionDistance )
{
priorVector->AddToTail( adjArea );
}
}
}
}
//-----------------------------------------------------------------------------
// Populate 'priorVector' with a collection of adjacent areas that have a higher incursion distance that this area
void CTFNavArea::CollectNextIncursionAreas( int team, CUtlVector< CTFNavArea * > *priorVector )
{
float myIncursionDistance = GetIncursionDistance( team );
priorVector->RemoveAll();
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
{
const NavConnectVector *adjVector = GetAdjacentAreas( (NavDirType)dir );
FOR_EACH_VEC( (*adjVector), bit )
{
CTFNavArea *adjArea = static_cast< CTFNavArea * >( (*adjVector)[ bit ].area );
if ( adjArea->GetIncursionDistance( team ) > myIncursionDistance )
{
priorVector->AddToTail( adjArea );
}
}
}
}
//-----------------------------------------------------------------------------
/**
* Return true if this area is at least safetyRange units away from all invasion areas
*/
bool CTFNavArea::IsAwayFromInvasionAreas( int myTeam, float safetyRange ) const
{
const CUtlVector< CTFNavArea * > &invasionVector = GetEnemyInvasionAreaVector( myTeam );
FOR_EACH_VEC( invasionVector, vit )
{
CTFNavArea *invasionArea = invasionVector[ vit ];
if ( ( invasionArea->GetCenter() - GetCenter() ).IsLengthLessThan( safetyRange ) )
{
// too close to incoming enemy route to snipe
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
class MarkVisibleSet
{
public:
MarkVisibleSet( unsigned int marker )
{
m_marker = marker;
}
bool operator() ( CNavArea *baseArea )
{
CTFNavArea *area = static_cast< CTFNavArea * >( baseArea );
area->SetInvasionSearchMarker( m_marker );
return true;
}
unsigned int m_marker;
};
//-----------------------------------------------------------------------------
class CollectInvasionAreas
{
public:
CollectInvasionAreas( unsigned int marker, CTFNavArea *homeArea, CUtlVector< CTFNavArea * > *redInvasionAreaVector, CUtlVector< CTFNavArea * > *blueInvasionAreaVector )
{
m_homeArea = homeArea;
m_visibleMarker = marker;
m_redInvasionAreaVector = redInvasionAreaVector;
m_blueInvasionAreaVector = blueInvasionAreaVector;
}
void FilterArea( CTFNavArea *area, CTFNavArea *adjArea )
{
if ( adjArea->IsInvasionSearchMarked( m_visibleMarker ) )
{
// also in PVS - can't be invasion area
return;
}
const float behindTolerance = 100.0;
// adjacent area is not in PVS, test if adjacent area not penetrated as far, if so it is an invasion area
if ( area->GetIncursionDistance( TF_TEAM_BLUE ) > adjArea->GetIncursionDistance( TF_TEAM_BLUE ) )
{
if ( area->GetIncursionDistance( TF_TEAM_BLUE ) > m_homeArea->GetIncursionDistance( TF_TEAM_BLUE ) + behindTolerance )
{
// this area is farther "in" than we are - don't search further
return;
}
m_redInvasionAreaVector->AddToTail( adjArea );
}
if ( area->GetIncursionDistance( TF_TEAM_RED ) > adjArea->GetIncursionDistance( TF_TEAM_RED ) )
{
if ( area->GetIncursionDistance( TF_TEAM_RED ) > m_homeArea->GetIncursionDistance( TF_TEAM_RED ) + behindTolerance )
{
// this area is farther "in" than we are - don't search further
return;
}
m_blueInvasionAreaVector->AddToTail( adjArea );
}
}
bool operator() ( CNavArea *baseArea )
{
CTFNavArea *area = static_cast< CTFNavArea * >( baseArea );
// explore adjacent floor areas
int dir;
for( dir=0; dir<NUM_DIRECTIONS; ++dir )
{
int count = area->GetAdjacentCount( (NavDirType)dir );
for( int i=0; i<count; ++i )
{
CTFNavArea *adjArea = static_cast< CTFNavArea * >( area->GetAdjacentArea( (NavDirType)dir, i ) );
FilterArea( area, adjArea );
}
}
// include areas that connect TO this area via a one-way link, since the enemy is coming TO us
for( dir=0; dir<NUM_DIRECTIONS; ++dir )
{
const NavConnectVector *list = area->GetIncomingConnections( (NavDirType)dir );
FOR_EACH_VEC( (*list), it )
{
NavConnect connect = (*list)[ it ];
FilterArea( area, static_cast< CTFNavArea * >( connect.area ) );
}
}
return true;
}
CTFNavArea *m_homeArea;
CUtlVector< CTFNavArea * > *m_redInvasionAreaVector;
CUtlVector< CTFNavArea * > *m_blueInvasionAreaVector;
unsigned int m_visibleMarker;
};
//------------------------------------------------------------------------------------------------
/**
* Find invasion areas where enemies enter from
*/
void CTFNavArea::ComputeInvasionAreaVectors( void )
{
static unsigned int searchMarker = RandomInt( 0, 1024*1024 );
for( int i=0; i<TF_TEAM_COUNT; ++i )
{
m_invasionAreaVector[ i ].RemoveAll();
}
++searchMarker;
// mark all potentially visible areas for quick testing during the search
MarkVisibleSet marker( searchMarker );
ForAllCompletelyVisibleAreas( marker );
// search boundary of potentially visible area set for area pairs where
// the area in the PVS has a higher incursion distance than an adjacent
// area outside of the PVS - an invasion area
CollectInvasionAreas collector( searchMarker, this, &m_invasionAreaVector[ TF_TEAM_RED ], &m_invasionAreaVector[ TF_TEAM_BLUE ] );
ForAllCompletelyVisibleAreas( collector );
}
//------------------------------------------------------------------------------------------------
bool CTFNavArea::IsBlocked( int teamID, bool ignoreNavBlockers ) const
{
if ( HasAttributeTF( TF_NAV_UNBLOCKABLE ) )
return false;
if ( HasAttributeTF( TF_NAV_BLOCKED ) )
return true;
// temporary fix:
if ( teamID == TF_TEAM_RED && HasAttributeTF( TF_NAV_BLUE_ONE_WAY_DOOR ) )
return true;
if ( teamID == TF_TEAM_BLUE && HasAttributeTF( TF_NAV_RED_ONE_WAY_DOOR ) )
return true;
return CNavArea::IsBlocked( teamID, ignoreNavBlockers );
}
//------------------------------------------------------------------------------------------------
void CTFNavArea::Save( CUtlBuffer &fileBuffer, unsigned int version ) const
{
CNavArea::Save( fileBuffer, version );
// save attribute flags
unsigned int attributes = m_attributeFlags & TF_NAV_PERSISTENT_ATTRIBUTES;
fileBuffer.PutUnsignedInt( attributes );
}
//------------------------------------------------------------------------------------------------
NavErrorType CTFNavArea::Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion )
{
// load base class data
CNavArea::Load( fileBuffer, version, subVersion );
if ( subVersion > TheNavMesh->GetSubVersionNumber() )
{
Warning( "Unknown NavArea sub-version number\n" );
return NAV_INVALID_FILE;
}
else if ( subVersion <= 1 )
{
// no data
m_attributeFlags = 0;
return NAV_OK;
}
m_attributeFlags = fileBuffer.GetUnsignedInt();
if ( !fileBuffer.IsValid() )
{
Warning( "Can't read TF-specific attributes\n" );
return NAV_INVALID_FILE;
}
return NAV_OK;
}
//--------------------------------------------------------------------------------------------------------
unsigned int CTFNavArea::m_masterTFMark = 1;
//--------------------------------------------------------------------------------------------------------
void CTFNavArea::MakeNewTFMarker( void )
{
++m_masterTFMark;
}
//--------------------------------------------------------------------------------------------------------
void CTFNavArea::ResetTFMarker( void )
{
m_masterTFMark = 1;
}
//--------------------------------------------------------------------------------------------------------
bool CTFNavArea::IsTFMarked( void ) const
{
return ( m_TFMark == m_masterTFMark );
}
//--------------------------------------------------------------------------------------------------------
void CTFNavArea::TFMark( void )
{
m_TFMark = m_masterTFMark;
}
//--------------------------------------------------------------------------------------------------------
bool CTFNavArea::IsValidForWanderingPopulation( void ) const
{
if ( HasAttributeTF( TF_NAV_BLOCKED | TF_NAV_SPAWN_ROOM_RED | TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_NO_SPAWNING | TF_NAV_RESCUE_CLOSET ) )
return false;
return true;
}
//--------------------------------------------------------------------------------------------------------
void CTFNavArea::AddPotentiallyVisibleActor( CBaseCombatCharacter *who )
{
if ( who == NULL )
{
return;
}
int team = who->GetTeamNumber();
if ( team < 0 || team >= TF_TEAM_COUNT )
return;
CTFBot *bot = ToTFBot( who );
if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) )
return;
if ( m_potentiallyVisibleActor[ team ].Find( who ) == m_potentiallyVisibleActor[ team ].InvalidIndex() )
{
m_potentiallyVisibleActor[ team ].AddToTail( who );
}
}
//--------------------------------------------------------------------------------------------------------
float CTFNavArea::GetCombatIntensity( void ) const
{
if ( !m_combatTimer.HasStarted() )
{
return 0.0f;
}
float actualIntensity = m_combatIntensity - m_combatTimer.GetElapsedTime() * tf_nav_combat_decay_rate.GetFloat();
if ( actualIntensity < 0.0f )
{
actualIntensity = 0.0f;
}
return actualIntensity;
}
//--------------------------------------------------------------------------------------------------------
// Invoked when combat happens in/near this area
void CTFNavArea::OnCombat( void )
{
m_combatIntensity += tf_nav_combat_build_rate.GetFloat();
if ( m_combatIntensity > 1.0f )
{
m_combatIntensity = 1.0f;
}
m_combatTimer.Start();
}
//--------------------------------------------------------------------------------------------------------
bool CTFNavArea::IsInCombat( void ) const
{
return GetCombatIntensity() > 0.01f;
}
+299
View File
@@ -0,0 +1,299 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_nav_area.h
// TF specific nav area
// Michael Booth, February 2009
#ifndef TF_NAV_AREA_H
#define TF_NAV_AREA_H
#include "nav_area.h"
#include "tf_shareddefs.h"
enum TFNavAttributeType
{
TF_NAV_INVALID = 0x00000000,
// Also look for NAV_MESH_NAV_BLOCKER (w/ nav_debug_blocked ConVar).
TF_NAV_BLOCKED = 0x00000001, // blocked for some TF-specific reason
TF_NAV_SPAWN_ROOM_RED = 0x00000002,
TF_NAV_SPAWN_ROOM_BLUE = 0x00000004,
TF_NAV_SPAWN_ROOM_EXIT = 0x00000008,
TF_NAV_HAS_AMMO = 0x00000010,
TF_NAV_HAS_HEALTH = 0x00000020,
TF_NAV_CONTROL_POINT = 0x00000040,
TF_NAV_BLUE_SENTRY_DANGER = 0x00000080, // sentry can potentially fire upon enemies in this area
TF_NAV_RED_SENTRY_DANGER = 0x00000100,
TF_NAV_BLUE_SETUP_GATE = 0x00000800, // this area is blocked until the setup period is over
TF_NAV_RED_SETUP_GATE = 0x00001000, // this area is blocked until the setup period is over
TF_NAV_BLOCKED_AFTER_POINT_CAPTURE = 0x00002000, // this area becomes blocked after the first point is capped
TF_NAV_BLOCKED_UNTIL_POINT_CAPTURE = 0x00004000, // this area is blocked until the first point is capped, then is unblocked
TF_NAV_BLUE_ONE_WAY_DOOR = 0x00008000,
TF_NAV_RED_ONE_WAY_DOOR = 0x00010000,
TF_NAV_WITH_SECOND_POINT = 0x00020000, // modifier for BLOCKED_*_POINT_CAPTURE
TF_NAV_WITH_THIRD_POINT = 0x00040000, // modifier for BLOCKED_*_POINT_CAPTURE
TF_NAV_WITH_FOURTH_POINT = 0x00080000, // modifier for BLOCKED_*_POINT_CAPTURE
TF_NAV_WITH_FIFTH_POINT = 0x00100000, // modifier for BLOCKED_*_POINT_CAPTURE
TF_NAV_SNIPER_SPOT = 0x00200000, // this is a good place for a sniper to lurk
TF_NAV_SENTRY_SPOT = 0x00400000, // this is a good place to build a sentry
TF_NAV_ESCAPE_ROUTE = 0x00800000, // for Raid mode
TF_NAV_ESCAPE_ROUTE_VISIBLE = 0x01000000, // all areas that have visibility to the escape route
TF_NAV_NO_SPAWNING = 0x02000000, // don't spawn bots in this area
TF_NAV_RESCUE_CLOSET = 0x04000000, // for respawning friends in Raid mode
TF_NAV_BOMB_CAN_DROP_HERE = 0x08000000, // the bomb can be dropped here and reached by the invaders in MvM
TF_NAV_DOOR_NEVER_BLOCKS = 0x10000000,
TF_NAV_DOOR_ALWAYS_BLOCKS = 0x20000000,
TF_NAV_UNBLOCKABLE = 0x40000000, // this area cannot be blocked
// save/load these manually set flags, and don't clear them between rounds
TF_NAV_PERSISTENT_ATTRIBUTES = TF_NAV_SNIPER_SPOT | TF_NAV_SENTRY_SPOT | TF_NAV_NO_SPAWNING | TF_NAV_BLUE_SETUP_GATE | TF_NAV_RED_SETUP_GATE | TF_NAV_BLOCKED_AFTER_POINT_CAPTURE | TF_NAV_BLOCKED_UNTIL_POINT_CAPTURE | TF_NAV_BLUE_ONE_WAY_DOOR | TF_NAV_RED_ONE_WAY_DOOR | TF_NAV_DOOR_NEVER_BLOCKS | TF_NAV_DOOR_ALWAYS_BLOCKS | TF_NAV_UNBLOCKABLE | TF_NAV_WITH_SECOND_POINT | TF_NAV_WITH_THIRD_POINT | TF_NAV_WITH_FOURTH_POINT | TF_NAV_WITH_FIFTH_POINT | TF_NAV_RESCUE_CLOSET
};
class CTFNavArea : public CNavArea
{
public:
DECLARE_CLASS( CTFNavArea, CNavArea );
CTFNavArea( void );
virtual void OnServerActivate( void ); // (EXTEND) invoked when map is initially loaded
virtual void OnRoundRestart( void ); // (EXTEND) invoked for each area when the round restarts
virtual void CustomAnalysis( bool isIncremental = false ); // for game-specific analysis
virtual void Draw( void ) const; // draw area for debugging & editing
virtual void UpdateBlocked( bool force = false, int teamID = TEAM_ANY ) { } // we'll handle managing blocked status directly
virtual bool IsBlocked( int teamID, bool ignoreNavBlockers = false ) const;
virtual void Save( CUtlBuffer &fileBuffer, unsigned int version ) const; // (EXTEND)
virtual NavErrorType Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion ); // (EXTEND)
float GetIncursionDistance( int team ) const; // return travel distance from the team's active spawn room to this area, -1 for invalid
CTFNavArea *GetNextIncursionArea( int team ) const; // return adjacent area with largest increase in incursion distance
bool IsReachableByTeam( int team ) const; // return true if the given team can reach this area
void CollectPriorIncursionAreas( int team, CUtlVector< CTFNavArea * > *priorVector ); // populate 'priorVector' with a collection of adjacent areas that have a lower incursion distance that this area
void CollectNextIncursionAreas( int team, CUtlVector< CTFNavArea * > *priorVector ); // populate 'priorVector' with a collection of adjacent areas that have a higher incursion distance that this area
const CUtlVector< CTFNavArea * > &GetEnemyInvasionAreaVector( int myTeam ) const; // given OUR team index, return list of areas the enemy is invading from
bool IsAwayFromInvasionAreas( int myTeam, float safetyRange = 1000.0f ) const; // return true if this area is at least safetyRange units away from all invasion areas
void ComputeInvasionAreaVectors( void );
void SetInvasionSearchMarker( unsigned int marker );
bool IsInvasionSearchMarked( unsigned int marker ) const;
void SetAttributeTF( int flags );
void ClearAttributeTF( int flags );
bool HasAttributeTF( int flags ) const;
void AddPotentiallyVisibleActor( CBaseCombatCharacter *who );
void RemovePotentiallyVisibleActor( CBaseCombatCharacter *who );
void ClearAllPotentiallyVisibleActors( void );
bool IsPotentiallyVisibleToActor( CBaseCombatCharacter *who ) const; // return true if the given actor has potential visibility to this area
virtual bool IsPotentiallyVisibleToTeam( int team ) const; // return true if any portion of this area is visible to anyone on the given team (very fast)
class IForEachPotentiallyVisibleActor
{
public:
virtual bool Inspect( CBaseCombatCharacter *who ) = 0;
};
bool ForEachPotentiallyVisibleActor( IForEachPotentiallyVisibleActor &func, int team = TEAM_ANY );
void OnCombat( void ); // invoked when combat happens in/near this area
bool IsInCombat( void ) const; // return true if this area has seen combat recently
float GetCombatIntensity( void ) const; // 1 = in active combat, 0 = quiet
static void MakeNewTFMarker( void );
static void ResetTFMarker( void );
bool IsTFMarked( void ) const;
void TFMark( void );
// Raid mode -------------------------------------------------
void AddToWanderCount( int count );
void SetWanderCount( int count );
int GetWanderCount( void ) const;
bool IsValidForWanderingPopulation( void ) const;
// Raid mode -------------------------------------------------
// Distance for MvM bomb delivery
float GetTravelDistanceToBombTarget( void ) const;
private:
friend class CTFNavMesh;
float m_distanceFromSpawnRoom[ TF_TEAM_COUNT ];
CUtlVector< CTFNavArea * > m_invasionAreaVector[ TF_TEAM_COUNT ]; // use our team as index to get list of areas the enemy is invading from
unsigned int m_invasionSearchMarker;
unsigned int m_attributeFlags;
CUtlVector< CHandle< CBaseCombatCharacter > > m_potentiallyVisibleActor[ TF_TEAM_COUNT ];
float m_combatIntensity;
IntervalTimer m_combatTimer;
static unsigned int m_masterTFMark;
unsigned int m_TFMark; // this area's mark
// Raid mode -------------------------------------------------
int m_wanderCount; // how many wandering defenders to populate here
// Raid mode -------------------------------------------------
float m_distanceToBombTarget;
};
inline float CTFNavArea::GetTravelDistanceToBombTarget( void ) const
{
return m_distanceToBombTarget;
}
inline void CTFNavArea::AddToWanderCount( int count )
{
m_wanderCount += count;
}
inline void CTFNavArea::SetWanderCount( int count )
{
m_wanderCount = count;
}
inline int CTFNavArea::GetWanderCount( void ) const
{
return m_wanderCount;
}
inline bool CTFNavArea::IsPotentiallyVisibleToActor( CBaseCombatCharacter *who ) const
{
if ( who == NULL )
return false;
int team = who->GetTeamNumber();
if ( team < 0 || team >= TF_TEAM_COUNT )
return false;
return m_potentiallyVisibleActor[ team ].Find( who ) != m_potentiallyVisibleActor[ team ].InvalidIndex();
}
inline bool CTFNavArea::IsPotentiallyVisibleToTeam( int team ) const
{
return team >= 0 && team < TF_TEAM_COUNT && m_potentiallyVisibleActor[ team ].Count() > 0;
}
inline bool CTFNavArea::ForEachPotentiallyVisibleActor( CTFNavArea::IForEachPotentiallyVisibleActor &func, int team )
{
if ( team == TEAM_ANY )
{
for( int t=0; t<TF_TEAM_COUNT; ++t )
{
for( int i=0; i<m_potentiallyVisibleActor[ t ].Count(); ++i )
{
CBaseCombatCharacter *who = m_potentiallyVisibleActor[ t ][ i ];
if ( who && func.Inspect( who ) == false )
{
return false;
}
}
}
}
else if ( team >= 0 && team < TF_TEAM_COUNT )
{
for( int i=0; i<m_potentiallyVisibleActor[ team ].Count(); ++i )
{
CBaseCombatCharacter *who = m_potentiallyVisibleActor[ team ][ i ];
if ( who && func.Inspect( who ) == false )
{
return false;
}
}
}
return true;
}
inline void CTFNavArea::RemovePotentiallyVisibleActor( CBaseCombatCharacter *who )
{
for( int i=0; i<TF_TEAM_COUNT; ++i )
m_potentiallyVisibleActor[i].FindAndFastRemove( who );
}
inline void CTFNavArea::ClearAllPotentiallyVisibleActors( void )
{
for( int i=0; i<TF_TEAM_COUNT; ++i )
m_potentiallyVisibleActor[i].RemoveAll();
}
inline float CTFNavArea::GetIncursionDistance( int team ) const
{
if ( team < 0 || team >= TF_TEAM_COUNT )
{
return -1.0f;
}
return m_distanceFromSpawnRoom[ team ];
}
inline bool CTFNavArea::IsReachableByTeam( int team ) const
{
if ( team < 0 || team >= TF_TEAM_COUNT )
{
return false;
}
return m_distanceFromSpawnRoom[ team ] >= 0.0f;
}
inline const CUtlVector< CTFNavArea * > &CTFNavArea::GetEnemyInvasionAreaVector( int myTeam ) const
{
if ( myTeam < 0 || myTeam >= TF_TEAM_COUNT )
{
myTeam = 0.0f;
}
return m_invasionAreaVector[ myTeam ];
}
inline void CTFNavArea::SetInvasionSearchMarker( unsigned int marker )
{
m_invasionSearchMarker = marker;
}
inline bool CTFNavArea::IsInvasionSearchMarked( unsigned int marker ) const
{
return marker == m_invasionSearchMarker;
}
inline void CTFNavArea::SetAttributeTF( int flags )
{
m_attributeFlags |= flags;
}
inline void CTFNavArea::ClearAttributeTF( int flags )
{
m_attributeFlags &= ~flags;
}
inline bool CTFNavArea::HasAttributeTF( int flags ) const
{
return ( m_attributeFlags & flags ) ? true : false;
}
#endif // TF_NAV_AREA_H
@@ -0,0 +1,45 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements nav interface entity. Used by maps to do various things
// with the nav mesh
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "tf_nav_mesh.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
class CPointNavInterface : public CPointEntity
{
DECLARE_CLASS( CPointNavInterface, CPointEntity );
public:
// Input handlers
void RecomputeBlockers(inputdata_t &inputdata);
DECLARE_DATADESC();
};
BEGIN_DATADESC( CPointNavInterface )
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "RecomputeBlockers", RecomputeBlockers ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( tf_point_nav_interface, CPointNavInterface );
void CPointNavInterface::RecomputeBlockers( inputdata_t &inputdata )
{
CTFNavMesh* pTFNavMesh = dynamic_cast<CTFNavMesh*>( TheNavMesh );
Assert( pTFNavMesh );
if( pTFNavMesh )
{
pTFNavMesh->ScheduleRecomputationOfInternalData( CTFNavMesh::MAP_LOGIC );
}
}
File diff suppressed because it is too large Load Diff
+235
View File
@@ -0,0 +1,235 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_nav_mesh.h
// TF specific nav mesh
// Michael Booth, February 2009
#ifndef TF_NAV_MESH_H
#define TF_NAV_MESH_H
#include "nav_mesh.h"
#include "tf_nav_area.h"
#include "tf_obj_teleporter.h"
#define TF_PLAYER_JUMP_HEIGHT 45.0f // non crouch-jumping
class CBaseObject;
class CObjectTeleporter;
class CTFPlayer;
//-------------------------------------------------------------------------
// General purpose collector class for ForAllArea-style functor methods
class CTFAreaCollector
{
public:
bool operator() ( CNavArea *area )
{
m_vector.AddToTail( (CTFNavArea *)area );
return true;
}
CUtlVector< CTFNavArea * > m_vector;
};
//-------------------------------------------------------------------------
class CTFNavMesh : public CNavMesh
{
public:
CTFNavMesh( void );
virtual CTFNavArea *CreateArea( void ) const; // CNavArea factory
virtual void Update( void ); // invoked on each game frame
virtual unsigned int GetSubVersionNumber( void ) const; // returns sub-version number of data format used by derived classes
virtual void SaveCustomData( CUtlBuffer &fileBuffer ) const; // store custom mesh data for derived classes
virtual void LoadCustomData( CUtlBuffer &fileBuffer, unsigned int subVersion ); // load custom mesh data for derived classes
virtual void OnServerActivate( void ); // (EXTEND) invoked when server loads a new map
virtual void OnRoundRestart( void ); // invoked when a game round restarts
virtual void FireGameEvent( IGameEvent *event );
/**
* Return true if nav mesh can be trusted for all climbing/jumping decisions because game environment is fairly simple.
* Authoritative meshes mean path followers can skip CPU intesive realtime scanning of unpredictable geometry.
*/
virtual bool IsAuthoritative( void ) const { return true; } // TF2 has nice clean environments
virtual unsigned int GetGenerationTraceMask( void ) const; // return the mask used by traces when generating the mesh
void OnObjectChanged();
bool IsSentryGunHere( CTFNavArea *area ) const; // return true if a Sentry Gun has been built in the given area
void CollectBuiltObjects( CUtlVector< CBaseObject * > *collectionVector, int team = TEAM_ANY ); // fill given vector will all objects on the given team
struct BallisticLaunchInfo
{
Vector m_launchSpot; // where to stand
float m_aimYaw; // how to aim
float m_aimPitch; // how to aim
float m_chargeTime; // how long to charge weapon
};
// populate the given vector with ways to launch grenades to hit the given building
void CollectBallisticAttackInfo( CBaseObject *building, CUtlVector< BallisticLaunchInfo > *infoVector ) const;
void ResetMeshAttributes( bool bScheduleRecomputation );
void CollectControlPointAreas( void );
void DecorateMesh( void );
void DecorateMeshTacticalHints( void );
void RemoveAllMeshDecoration( void );
// populate the given "ambushVector" with good areas to lurk in ambush for the invading enemy team
void CollectAmbushAreas( CUtlVector< CTFNavArea * > *ambushVector, CTFNavArea *startArea, int teamToAmbush, float searchRadius, float incursionTolerance = 300.0f ) const;
// populate the given vector with areas that are just outside of the given team's spawn room(s)
void CollectSpawnRoomThresholdAreas( CUtlVector< CTFNavArea * > *spawnExitAreaVector, int team ) const;
// populate the given vector with areas that have a bomb travel distance within the given range
void CollectAreaWithinBombTravelRange( CUtlVector< CTFNavArea * > *spawnExitAreaVector, float minTravel, float maxTravel ) const;
const CUtlVector< CTFNavArea * > *GetSetupGateDefenseAreas( void ) const; // return vector of areas that are good for defending enemies coming out of the blue setup gates
const CUtlVector< CTFNavArea * > *GetControlPointAreas( int pointIndex ) const; // return vector of areas overlapping the given control point
CTFNavArea *GetControlPointCenterArea( int pointIndex ) const; // return area overlapping the center of the given control point
const CUtlVector< CTFNavArea * > *GetSpawnRoomAreas( int team ) const; // return vector of areas within the given team spawn room(s)
const CUtlVector< CTFNavArea * > *GetSpawnRoomExitAreas( int team ) const; // return vector of areas where the given team exits their spawn room(s)
enum RecomputeReasonType
{
RESET,
SETUP_FINISHED,
POINT_CAPTURED,
POINT_UNLOCKED,
BLOCKED_STATUS_CHANGED,
MAP_LOGIC
};
void ScheduleRecomputationOfInternalData( RecomputeReasonType reason, int whichPoint );
protected:
virtual void BeginCustomAnalysis( bool bIncremental );
virtual void PostCustomAnalysis( void ); // invoked when custom analysis step is complete
virtual void EndCustomAnalysis();
private:
void ComputeIncursionDistances( void ); // recompute travel distance from each team's spawn room for each nav area
void ComputeIncursionDistances( CTFNavArea *spawnArea, int team );
void ComputeInvasionAreas( void );
void ComputeLegalBombDropAreas( void );
void ComputeBombTargetDistance();
void UpdateDebugDisplay( void ) const;
void OnBlockedAreasChanged( void );
void ComputeBlockedAreas( void );
CountdownTimer m_recomputeInternalDataTimer; // if started, when counts down recompute internal data to give various map logic time to complete
RecomputeReasonType m_recomputeReason;
int m_recomputeReasonWhichPoint;
void RecomputeInternalData( void );
// Array of areas with sentry danger attributes set.
CUtlVector< CTFNavArea * > m_sentryAreas;
CUtlVector< CTFNavArea * > m_setupGateDefenseAreaVector;
CUtlVector< CTFNavArea * > m_controlPointAreaVector[ MAX_CONTROL_POINTS ];
CTFNavArea *m_controlPointCenterAreaVector[ MAX_CONTROL_POINTS ];
CUtlVector< CTFNavArea * > m_redSpawnRoomAreaVector;
CUtlVector< CTFNavArea * > m_blueSpawnRoomAreaVector;
CUtlVector< CTFNavArea * > m_redSpawnRoomExitAreaVector;
CUtlVector< CTFNavArea * > m_blueSpawnRoomExitAreaVector;
void CollectAndMarkSpawnRoomExits( CTFNavArea *area, CUtlVector< CTFNavArea * > *exitAreaVector );
CountdownTimer m_watchCartTimer;
int m_priorBotCount;
};
inline void CTFNavMesh::ScheduleRecomputationOfInternalData( CTFNavMesh::RecomputeReasonType reason, int whichPoint = 0 )
{
m_recomputeInternalDataTimer.Start( 2.0f );
m_recomputeReason = reason;
m_recomputeReasonWhichPoint = whichPoint;
}
inline const CUtlVector< CTFNavArea * > *CTFNavMesh::GetSpawnRoomAreas( int team ) const
{
if ( team == TF_TEAM_RED )
{
return &m_redSpawnRoomAreaVector;
}
if ( team == TF_TEAM_BLUE )
{
return &m_blueSpawnRoomAreaVector;
}
return NULL;
}
inline const CUtlVector< CTFNavArea * > *CTFNavMesh::GetSpawnRoomExitAreas( int team ) const
{
if ( team == TF_TEAM_RED )
{
return &m_redSpawnRoomExitAreaVector;
}
if ( team == TF_TEAM_BLUE )
{
return &m_blueSpawnRoomExitAreaVector;
}
return NULL;
}
inline const CUtlVector< CTFNavArea * > *CTFNavMesh::GetControlPointAreas( int pointIndex ) const
{
if ( pointIndex < 0 || pointIndex >= MAX_CONTROL_POINTS )
{
return NULL;
}
return &m_controlPointAreaVector[ pointIndex ];
}
inline CTFNavArea *CTFNavMesh::GetControlPointCenterArea( int pointIndex ) const
{
if ( pointIndex < 0 || pointIndex >= MAX_CONTROL_POINTS )
{
return NULL;
}
return m_controlPointCenterAreaVector[ pointIndex ];
}
inline const CUtlVector< CTFNavArea * > *CTFNavMesh::GetSetupGateDefenseAreas( void ) const
{
return &m_setupGateDefenseAreaVector;
}
inline unsigned int CTFNavMesh::GetGenerationTraceMask( void ) const
{
return MASK_PLAYERSOLID_BRUSHONLY;
}
inline CTFNavMesh *TheTFNavMesh( void )
{
return reinterpret_cast< CTFNavMesh * >( TheNavMesh );
}
extern TFNavAttributeType NameToTFAttribute( const char *name );
extern const char *TFAttributeToName( TFNavAttributeType attribute );
#endif // TF_NAV_MESH_H
@@ -0,0 +1,306 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_nav_mesh_edit.cpp
// TF specific nav mesh editing
// Michael Booth, May 2009
#include "cbase.h"
#include "tf_nav_mesh.h"
//--------------------------------------------------------------------------------------------------------
class CTFAttributeClearer
{
public:
CTFAttributeClearer( TFNavAttributeType attribute )
{
m_attribute = attribute;
}
bool operator() ( CNavArea *baseArea )
{
CTFNavArea *area = (CTFNavArea *)baseArea;
area->ClearAttributeTF( m_attribute );
return true;
}
TFNavAttributeType m_attribute;
};
void TF_EditClearAllAttributes( void )
{
CTFAttributeClearer clear( (TFNavAttributeType)0xFFFFFFFF );
TheNavMesh->ForAllSelectedAreas( clear );
TheNavMesh->ClearSelectedSet();
}
static ConCommand ClearAllAttributes( "tf_wipe_attributes", TF_EditClearAllAttributes, "Clear all TF-specific attributes of selected area.", FCVAR_CHEAT );
//--------------------------------------------------------------------------------------------------------
struct AttributeLookup
{
const char *name;
TFNavAttributeType attribute;
};
static AttributeLookup s_TFAttributeTable[] =
{
{ "BLUE_SETUP_GATE", TF_NAV_BLUE_SETUP_GATE },
{ "RED_SETUP_GATE", TF_NAV_RED_SETUP_GATE },
{ "BLOCKED_AFTER_POINT_CAPTURE", TF_NAV_BLOCKED_AFTER_POINT_CAPTURE },
{ "BLOCKED_UNTIL_POINT_CAPTURE", TF_NAV_BLOCKED_UNTIL_POINT_CAPTURE },
{ "BLUE_ONE_WAY_DOOR", TF_NAV_BLUE_ONE_WAY_DOOR },
{ "RED_ONE_WAY_DOOR", TF_NAV_RED_ONE_WAY_DOOR },
{ "SNIPER_SPOT", TF_NAV_SNIPER_SPOT },
{ "SENTRY_SPOT", TF_NAV_SENTRY_SPOT },
{ "NO_SPAWNING", TF_NAV_NO_SPAWNING },
{ "RESCUE_CLOSET", TF_NAV_RESCUE_CLOSET },
{ "DOOR_ALWAYS_BLOCKS", TF_NAV_DOOR_ALWAYS_BLOCKS },
{ "DOOR_NEVER_BLOCKS", TF_NAV_DOOR_NEVER_BLOCKS },
{ "UNBLOCKABLE", TF_NAV_UNBLOCKABLE },
{ "WITH_SECOND_POINT", TF_NAV_WITH_SECOND_POINT },
{ "WITH_THIRD_POINT", TF_NAV_WITH_THIRD_POINT },
{ "WITH_FOURTH_POINT", TF_NAV_WITH_FOURTH_POINT },
{ "WITH_FIFTH_POINT", TF_NAV_WITH_FIFTH_POINT },
{ NULL, TF_NAV_INVALID }
};
/**
* Can be used with any command that takes an attribute as its 2nd argument
*/
static int AttributeAutocomplete( const char *input, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] )
{
if ( Q_strlen( input ) >= COMMAND_COMPLETION_ITEM_LENGTH )
{
return 0;
}
char command[ COMMAND_COMPLETION_ITEM_LENGTH+1 ];
Q_strncpy( command, input, sizeof( command ) );
// skip to start of argument
char *partialArg = Q_strrchr( command, ' ' );
if ( partialArg == NULL )
{
return 0;
}
// chop command from partial argument
*partialArg = '\000';
++partialArg;
int partialArgLength = Q_strlen( partialArg );
int count = 0;
for( unsigned int i=0; s_TFAttributeTable[i].name && count < COMMAND_COMPLETION_MAXITEMS; ++i )
{
if ( !Q_strnicmp( s_TFAttributeTable[i].name, partialArg, partialArgLength ) )
{
// Add to the autocomplete array
Q_snprintf( commands[ count++ ], COMMAND_COMPLETION_ITEM_LENGTH, "%s %s", command, s_TFAttributeTable[i].name );
}
}
/* all of these are deprecated
for( unsigned int i=0; TheNavAttributeTable[i].name && count < COMMAND_COMPLETION_MAXITEMS; ++i )
{
if ( !Q_strnicmp( TheNavAttributeTable[i].name, partialArg, partialArgLength ) )
{
// Add to the autocomplete array
Q_snprintf( commands[ count++ ], COMMAND_COMPLETION_ITEM_LENGTH, "%s %s", command, TheNavAttributeTable[i].name );
}
}
*/
return count;
}
TFNavAttributeType NameToTFAttribute( const char *name )
{
for( unsigned int i=0; s_TFAttributeTable[i].name; ++i )
{
if ( !Q_stricmp( s_TFAttributeTable[i].name, name ) )
{
return s_TFAttributeTable[i].attribute;
}
}
return TF_NAV_INVALID;
}
const char *TFAttributeToName( TFNavAttributeType attribute )
{
for( unsigned int i=0; s_TFAttributeTable[i].name; ++i )
{
if ( s_TFAttributeTable[i].attribute == attribute )
{
return s_TFAttributeTable[i].name;
}
}
return NULL;
}
//--------------------------------------------------------------------------------------------------------
void TF_EditClearAttribute( const CCommand &args )
{
if ( args.ArgC() < 2 )
{
Msg( "Usage: %s <attribute1> [attribute2...]\n", args[0] );
return;
}
for ( int i = 1; i < args.ArgC(); ++i )
{
TFNavAttributeType spawnAttribute = NameToTFAttribute( args[i] );
NavAttributeType navAttribute = NameToNavAttribute( args[i] );
if ( spawnAttribute != TF_NAV_INVALID )
{
CTFAttributeClearer clear( spawnAttribute );
TheNavMesh->ForAllSelectedAreas( clear );
}
else if ( navAttribute != NAV_MESH_INVALID )
{
NavAttributeClearer clear( navAttribute );
TheNavMesh->ForAllSelectedAreas( clear );
}
else
{
Msg( "Unknown attribute '%s'", args[i] );
}
}
TheNavMesh->ClearSelectedSet();
}
static ConCommand ClearAttributeTF( "tf_clear_attribute", TF_EditClearAttribute, "Remove given attribute from all areas in the selected set.", FCVAR_CHEAT, AttributeAutocomplete );
//--------------------------------------------------------------------------------------------------------
class CTFAttributeToggler
{
public:
CTFAttributeToggler( TFNavAttributeType attribute )
{
m_attribute = attribute;
}
bool operator() ( CNavArea *baseArea )
{
CTFNavArea *area = (CTFNavArea *)baseArea;
// only toggle if dealing with a single selected area
if ( TheNavMesh->IsSelectedSetEmpty() && area->HasAttributeTF( m_attribute ) )
{
area->ClearAttributeTF( m_attribute );
}
else
{
area->SetAttributeTF( m_attribute );
}
return true;
}
TFNavAttributeType m_attribute;
};
//--------------------------------------------------------------------------------------------------------
void TF_EditMarkAttribute( const CCommand &args )
{
if ( args.ArgC() < 2 )
{
Msg( "Usage: %s <attribute> [attribute2...]\n", args[0] );
return;
}
for ( int i = 1; i < args.ArgC(); ++i )
{
TFNavAttributeType spawnAttribute = NameToTFAttribute( args[i] );
NavAttributeType navAttribute = NameToNavAttribute( args[i] );
if ( spawnAttribute != TF_NAV_INVALID )
{
CTFAttributeToggler toggle( spawnAttribute );
TheNavMesh->ForAllSelectedAreas( toggle );
}
else if ( navAttribute != NAV_MESH_INVALID )
{
NavAttributeToggler clear( navAttribute );
TheNavMesh->ForAllSelectedAreas( clear );
}
else
{
Msg( "Unknown attribute '%s'", args[i] );
}
}
TheNavMesh->ClearSelectedSet();
}
static ConCommand MarkAttribute( "tf_mark", TF_EditMarkAttribute, "Set attribute of selected area.", FCVAR_CHEAT, AttributeAutocomplete );
//--------------------------------------------------------------------------------------------------------
void TF_EditSelectWithAttribute( const CCommand &args )
{
TheNavMesh->ClearSelectedSet();
if ( args.ArgC() != 2 )
{
Msg( "Usage: %s <attribute>\n", args[0] );
return;
}
TFNavAttributeType spawnAttribute = NameToTFAttribute( args[1] );
int count = 0;
if ( spawnAttribute != TF_NAV_INVALID )
{
FOR_EACH_VEC( TheNavAreas, it )
{
CTFNavArea *area = (CTFNavArea *)TheNavAreas[ it ];
if ( area->HasAttributeTF( spawnAttribute ) )
{
TheNavMesh->AddToSelectedSet( area );
++count;
}
}
}
else
{
NavAttributeType navAttribute = NameToNavAttribute( args[1] );
if ( navAttribute != NAV_MESH_INVALID )
{
FOR_EACH_VEC( TheNavAreas, it )
{
CNavArea *area = TheNavAreas[ it ];
if ( area->GetAttributes() & navAttribute )
{
TheNavMesh->AddToSelectedSet( area );
++count;
}
}
}
else
{
Msg( "Unknown attribute '%s'", args[1] );
}
}
Msg( "%d areas added to selection\n", count );
}
static ConCommand SelectWithAttribute( "tf_select_with_attribute", TF_EditSelectWithAttribute, "Selects areas with the given attribute.", FCVAR_CHEAT, AttributeAutocomplete );
@@ -0,0 +1,11 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_nav_mesh_edit.h
// TF specific nav mesh editing
// Michael Booth, May 2009
#ifndef TF_NAV_MESH_EDIT_H
#define TF_NAV_MESH_EDIT_H
#endif // TF_NAV_MESH_EDIT_H
@@ -0,0 +1,158 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_path_follower.cpp
// Simplified path following for TF2
// Author: Michael Booth, November 2010
#include "cbase.h"
#include "NextBotManager.h"
#include "tf_path_follower.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Constructor
*/
CTFPathFollower::CTFPathFollower( void )
{
m_goal = NULL;
m_minLookAheadRange = 300.0f;
}
//--------------------------------------------------------------------------------------------------------------
CTFPathFollower::~CTFPathFollower()
{
// allow bots to detach pointer to me
CUtlVector< INextBot * > botVector;
TheNextBots().CollectAllBots( &botVector );
for( int i=0; i<botVector.Count(); ++i )
{
botVector[i]->NotifyPathDestruction( this );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* When the path is invalidated, the follower is also reset
*/
void CTFPathFollower::Invalidate( void )
{
// extend
Path::Invalidate();
m_goal = NULL;
MoveCursorToStart();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Invoked when the path is (re)computed (path is valid at the time of this call)
*/
void CTFPathFollower::OnPathChanged( INextBot *bot, Path::ResultType result )
{
// start from the beginning
m_goal = FirstSegment();
MoveCursorToStart();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move mover along path
*/
void CTFPathFollower::Update( INextBot *bot )
{
VPROF_BUDGET( "CTFPathFollower::Update", "NextBot" );
ILocomotion *mover = bot->GetLocomotionInterface();
// track most recent path followed
bot->SetCurrentPath( this );
if ( !IsValid() || m_goal == NULL )
{
return;
}
// check if we've reached the end of the path
const float nearRange = 25.0f;
if ( mover->IsOnGround() && ( GetEndPosition() - mover->GetFeet() ).AsVector2D().IsLengthLessThan( nearRange ) )
{
// the end of the path has been reached
mover->GetBot()->OnMoveToSuccess( this );
if ( bot->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "CTFPathFollower: OnMoveToSuccess\n" );
}
// don't invalidate if OnMoveToSuccess just recomputed a new path
if ( GetAge() > 0.0f )
{
Invalidate();
}
return;
}
// move along the path
MoveCursorToClosestPosition( mover->GetFeet(), SEEK_AHEAD );
float myCursorPosition = GetCursorPosition();
const Path::Data &data = GetCursorData();
if ( !data.segmentPrior )
{
// this shouldn't happen
mover->GetBot()->OnMoveToFailure( this, FAIL_STUCK );
Invalidate();
return;
}
// set goal to be just ahead of wherever we happen to be on the path
m_goal = NextSegment( data.segmentPrior );
if ( !m_goal )
{
m_goal = data.segmentPrior;
}
// find actual move-to position farther down the path
Vector moveToPos = m_goal->pos;
// follow point farther down the path to smooth out our movement
for( float ahead = m_minLookAheadRange; ahead > 0.0f; ahead -= 50.0f )
{
MoveCursor( myCursorPosition, PATH_ABSOLUTE_DISTANCE );
MoveCursor( ahead, PATH_RELATIVE_DISTANCE );
// get path data at this lookahead point
const Path::Data &data = GetCursorData();
if ( mover->IsPotentiallyTraversable( mover->GetFeet(), data.pos ) )
{
moveToPos = data.pos;
break;
}
}
// move bot along path
mover->FaceTowards( moveToPos );
mover->Approach( moveToPos );
// debug display
if ( bot->IsDebugging( NEXTBOT_PATH ) )
{
Path::Draw();
NDebugOverlay::Cross3D( moveToPos, 5.0f, 150, 150, 255, true, 0.1f );
NDebugOverlay::Line( bot->GetEntity()->WorldSpaceCenter(), moveToPos, 255, 255, 0, true, 0.1f );
}
}
@@ -0,0 +1,58 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_path_follower.h
// Simplified path following for TF2
// Author: Michael Booth, November 2010
#ifndef TF_PATH_FOLLOWER_H
#define TF_PATH_FOLLOWER_H
#include "nav_mesh.h"
#include "nav_pathfind.h"
#include "Path/NextBotPathFollow.h"
class INextBot;
class ILocomotion;
//--------------------------------------------------------------------------------------------------------
/**
* This is a simplified path follower that doesn't care about ladders, climbing, hindrances, etc.
*/
class CTFPathFollower : public PathFollower
{
public:
CTFPathFollower( void );
virtual ~CTFPathFollower();
virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
virtual void OnPathChanged( INextBot *bot, Path::ResultType result ); // invoked when the path is (re)computed (path is valid at the time of this call)
virtual void Update( INextBot *bot ); // move bot along path
virtual const Path::Segment *GetCurrentGoal( void ) const; // return current goal along the path we are trying to reach
virtual void SetMinLookAheadDistance( float value ); // minimum range movement goal must be along path
private:
const Path::Segment *m_goal; // our current goal along the path
float m_minLookAheadRange;
// bool CheckProgress( INextBot *bot );
// bool IsAtGoal( INextBot *bot ) const; // return true if reached current path goal
};
inline const Path::Segment *CTFPathFollower::GetCurrentGoal( void ) const
{
return m_goal;
}
inline void CTFPathFollower::SetMinLookAheadDistance( float value )
{
m_minLookAheadRange = value;
}
#endif // TF_PATH_FOLLOWER_H