summaryrefslogtreecommitdiff
path: root/game/server/nav_entities.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/nav_entities.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/nav_entities.cpp')
-rw-r--r--game/server/nav_entities.cpp711
1 files changed, 711 insertions, 0 deletions
diff --git a/game/server/nav_entities.cpp b/game/server/nav_entities.cpp
new file mode 100644
index 0000000..2638db0
--- /dev/null
+++ b/game/server/nav_entities.cpp
@@ -0,0 +1,711 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_entities.cpp
+// AI Navigation entities
+// Author: Michael S. Booth ([email protected]), January 2003
+
+#include "cbase.h"
+
+#include "nav_mesh.h"
+#include "nav_node.h"
+#include "nav_pathfind.h"
+#include "nav_colors.h"
+#include "fmtstr.h"
+#include "props_shared.h"
+#include "func_breakablesurf.h"
+
+#ifdef TERROR
+#include "func_elevator.h"
+#include "AmbientLight.h"
+#endif
+
+#ifdef TF_DLL
+#include "tf_player.h"
+#include "bot/tf_bot.h"
+#endif
+
+#include "Color.h"
+#include "collisionutils.h"
+#include "functorutils.h"
+#include "team.h"
+#include "nav_entities.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+//--------------------------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------------------------
+BEGIN_DATADESC( CFuncNavCost )
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+ DEFINE_KEYFIELD( m_iszTags, FIELD_STRING, "tags" ),
+ DEFINE_KEYFIELD( m_team, FIELD_INTEGER, "team" ),
+ DEFINE_KEYFIELD( m_isDisabled, FIELD_BOOLEAN, "start_disabled" ),
+
+ DEFINE_THINKFUNC( CostThink ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( func_nav_avoid, CFuncNavAvoid );
+LINK_ENTITY_TO_CLASS( func_nav_prefer, CFuncNavPrefer );
+
+CUtlVector< CHandle< CFuncNavCost > > CFuncNavCost::gm_masterCostVector;
+CountdownTimer CFuncNavCost::gm_dirtyTimer;
+
+#define UPDATE_DIRTY_TIME 0.2f
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavCost::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ gm_masterCostVector.AddToTail( this );
+ gm_dirtyTimer.Start( UPDATE_DIRTY_TIME );
+
+ SetSolid( SOLID_BSP );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ SetMoveType( MOVETYPE_NONE );
+ SetModel( STRING( GetModelName() ) );
+ AddEffects( EF_NODRAW );
+ SetCollisionGroup( COLLISION_GROUP_NONE );
+
+ VPhysicsInitShadow( false, false );
+
+ SetThink( &CFuncNavCost::CostThink );
+ SetNextThink( gpGlobals->curtime + UPDATE_DIRTY_TIME );
+
+ m_tags.RemoveAll();
+
+ const char *tags = STRING( m_iszTags );
+
+ // chop space-delimited string into individual tokens
+ if ( tags )
+ {
+ char *buffer = V_strdup ( tags );
+
+ for( char *token = strtok( buffer, " " ); token; token = strtok( NULL, " " ) )
+ {
+ m_tags.AddToTail( CFmtStr( "%s", token ) );
+ }
+
+ delete [] buffer;
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavCost::UpdateOnRemove( void )
+{
+ gm_masterCostVector.FindAndFastRemove( this );
+ BaseClass::UpdateOnRemove();
+
+ gm_dirtyTimer.Start( UPDATE_DIRTY_TIME );
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavCost::InputEnable( inputdata_t &inputdata )
+{
+ m_isDisabled = false;
+ gm_dirtyTimer.Start( UPDATE_DIRTY_TIME );
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavCost::InputDisable( inputdata_t &inputdata )
+{
+ m_isDisabled = true;
+ gm_dirtyTimer.Start( UPDATE_DIRTY_TIME );
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavCost::CostThink( void )
+{
+ SetNextThink( gpGlobals->curtime + UPDATE_DIRTY_TIME );
+
+ if ( gm_dirtyTimer.HasStarted() && gm_dirtyTimer.IsElapsed() )
+ {
+ // one or more avoid entities have changed - update nav decoration
+ gm_dirtyTimer.Invalidate();
+
+ UpdateAllNavCostDecoration();
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+bool CFuncNavCost::HasTag( const char *groupname ) const
+{
+ for( int i=0; i<m_tags.Count(); ++i )
+ {
+ if ( FStrEq( m_tags[i], groupname ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+// Return true if this cost applies to the given actor
+bool CFuncNavCost::IsApplicableTo( CBaseCombatCharacter *who ) const
+{
+ if ( !who )
+ {
+ return false;
+ }
+
+ if ( m_team > 0 )
+ {
+ if ( who->GetTeamNumber() != m_team )
+ {
+ return false;
+ }
+ }
+
+#ifdef TF_DLL
+ // TODO: Make group comparison efficient and move to base combat character
+ CTFBot *bot = ToTFBot( who );
+ if ( bot )
+ {
+ if ( bot->HasTheFlag() )
+ {
+ if ( HasTag( "bomb_carrier" ) )
+ {
+ return true;
+ }
+
+ // check custom bomb_carrier tags for this bot
+ for( int i=0; i<m_tags.Count(); ++i )
+ {
+ const char* pszTag = m_tags[i];
+ if ( V_stristr( pszTag, "bomb_carrier" ) )
+ {
+ if ( bot->HasTag( pszTag ) )
+ {
+ return true;
+ }
+ }
+ }
+
+ // the bomb carrier only pays attention to bomb_carrier costs
+ return false;
+ }
+
+ if ( bot->HasMission( CTFBot::MISSION_DESTROY_SENTRIES ) )
+ {
+ if ( HasTag( "mission_sentry_buster" ) )
+ {
+ return true;
+ }
+ }
+
+ if ( bot->HasMission( CTFBot::MISSION_SNIPER ) )
+ {
+ if ( HasTag( "mission_sniper" ) )
+ {
+ return true;
+ }
+ }
+
+ if ( bot->HasMission( CTFBot::MISSION_SPY ) )
+ {
+ if ( HasTag( "mission_spy" ) )
+ {
+ return true;
+ }
+ }
+
+ if ( bot->HasMission( CTFBot::MISSION_REPROGRAMMED ) )
+ {
+ return false;
+ }
+
+ if ( !bot->IsOnAnyMission() )
+ {
+ if ( HasTag( "common" ) )
+ {
+ return true;
+ }
+ }
+
+ if ( HasTag( bot->GetPlayerClass()->GetName() ) )
+ {
+ return true;
+ }
+
+ // check custom tags for this bot
+ for( int i=0; i<m_tags.Count(); ++i )
+ {
+ if ( bot->HasTag( m_tags[i] ) )
+ {
+ return true;
+ }
+ }
+
+ // this cost doesn't apply to me
+ return false;
+ }
+#endif
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+// Reevaluate all func_nav_cost entities and update the nav decoration accordingly.
+// This is required to handle overlapping func_nav_cost entities.
+void CFuncNavCost::UpdateAllNavCostDecoration( void )
+{
+ int i, j;
+
+ // first, clear all avoid decoration from the mesh
+ for( i=0; i<TheNavAreas.Count(); ++i )
+ {
+ TheNavAreas[i]->ClearAllNavCostEntities();
+ }
+
+ // now, mark all areas with active cost entities overlapping them
+ for( i=0; i<gm_masterCostVector.Count(); ++i )
+ {
+ CFuncNavCost *cost = gm_masterCostVector[i];
+
+ if ( !cost || !cost->IsEnabled() )
+ {
+ continue;
+ }
+
+ Extent extent;
+ extent.Init( cost );
+
+ CUtlVector< CNavArea * > overlapVector;
+ TheNavMesh->CollectAreasOverlappingExtent( extent, &overlapVector );
+
+ Ray_t ray;
+ trace_t tr;
+ ICollideable *pCollide = cost->CollisionProp();
+
+ for( j=0; j<overlapVector.Count(); ++j )
+ {
+ ray.Init( overlapVector[j]->GetCenter(), overlapVector[j]->GetCenter() );
+
+ enginetrace->ClipRayToCollideable( ray, MASK_ALL, pCollide, &tr );
+
+ if ( tr.startsolid )
+ {
+ overlapVector[j]->AddFuncNavCostEntity( cost );
+ }
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------------------------
+// Return pathfind cost multiplier for the given actor
+float CFuncNavAvoid::GetCostMultiplier( CBaseCombatCharacter *who ) const
+{
+ if ( IsApplicableTo( who ) )
+ {
+ return 25.0f;
+ }
+
+ return 1.0f;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------------------------
+// Return pathfind cost multiplier for the given actor
+float CFuncNavPrefer::GetCostMultiplier( CBaseCombatCharacter *who ) const
+{
+ if ( IsApplicableTo( who ) )
+ {
+ return 0.04f; // 1/25th
+ }
+
+ return 1.0f;
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------------------------
+BEGIN_DATADESC( CFuncNavBlocker )
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "BlockNav", InputBlockNav ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "UnblockNav", InputUnblockNav ),
+ DEFINE_KEYFIELD( m_blockedTeamNumber, FIELD_INTEGER, "teamToBlock" ),
+ DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
+
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS( func_nav_blocker, CFuncNavBlocker );
+
+
+CUtlLinkedList<CFuncNavBlocker *> CFuncNavBlocker::gm_NavBlockers;
+
+//-----------------------------------------------------------------------------------------------------
+int CFuncNavBlocker::DrawDebugTextOverlays( void )
+{
+ int offset = BaseClass::DrawDebugTextOverlays();
+
+ if (m_debugOverlays & OVERLAY_TEXT_BIT)
+ {
+ CFmtStr str;
+
+ // FIRST_GAME_TEAM skips TEAM_SPECTATOR and TEAM_UNASSIGNED, so we can print
+ // useful team names in a non-game-specific fashion.
+ for ( int i=FIRST_GAME_TEAM; i<FIRST_GAME_TEAM + MAX_NAV_TEAMS; ++i )
+ {
+ if ( IsBlockingNav( i ) )
+ {
+ CTeam *team = GetGlobalTeam( i );
+ if ( team )
+ {
+ EntityText( offset++, str.sprintf( "blocking team %s", team->GetName() ), 0 );
+ }
+ else
+ {
+ EntityText( offset++, str.sprintf( "blocking team %d", i ), 0 );
+ }
+ }
+ }
+
+ NavAreaCollector collector( true );
+ Extent extent;
+ extent.Init( this );
+ TheNavMesh->ForAllAreasOverlappingExtent( collector, extent );
+
+ for ( int i=0; i<collector.m_area.Count(); ++i )
+ {
+ CNavArea *area = collector.m_area[i];
+ Extent areaExtent;
+ area->GetExtent( &areaExtent );
+ if ( debugoverlay )
+ {
+ debugoverlay->AddBoxOverlay( vec3_origin, areaExtent.lo, areaExtent.hi, vec3_angle, 0, 255, 0, 10, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ }
+ }
+ }
+
+ return offset;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavBlocker::UpdateBlocked()
+{
+ NavAreaCollector collector( true );
+ Extent extent;
+ extent.Init( this );
+ TheNavMesh->ForAllAreasOverlappingExtent( collector, extent );
+
+ for ( int i=0; i<collector.m_area.Count(); ++i )
+ {
+ CNavArea *area = collector.m_area[i];
+ area->UpdateBlocked( true );
+ }
+
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+// Forces nav areas to unblock when the nav blocker is deleted (round restart) so flow can compute properly
+void CFuncNavBlocker::UpdateOnRemove( void )
+{
+ UnblockNav();
+
+ gm_NavBlockers.FindAndRemove( this );
+
+ BaseClass::UpdateOnRemove();
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavBlocker::Spawn( void )
+{
+ gm_NavBlockers.AddToTail( this );
+
+ if ( !m_blockedTeamNumber )
+ m_blockedTeamNumber = TEAM_ANY;
+
+ SetMoveType( MOVETYPE_NONE );
+ SetModel( STRING( GetModelName() ) );
+ AddEffects( EF_NODRAW );
+ SetCollisionGroup( COLLISION_GROUP_NONE );
+ SetSolid( SOLID_NONE );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ CollisionProp()->WorldSpaceAABB( &m_CachedMins, &m_CachedMaxs );
+
+ if ( m_bDisabled )
+ {
+ UnblockNav();
+ }
+ else
+ {
+ BlockNav();
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavBlocker::InputBlockNav( inputdata_t &inputdata )
+{
+ BlockNav();
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavBlocker::InputUnblockNav( inputdata_t &inputdata )
+{
+ UnblockNav();
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavBlocker::BlockNav( void )
+{
+ if ( m_blockedTeamNumber == TEAM_ANY )
+ {
+ for ( int i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ m_isBlockingNav[ i ] = true;
+ }
+ }
+ else
+ {
+ int teamNumber = m_blockedTeamNumber % MAX_NAV_TEAMS;
+ m_isBlockingNav[ teamNumber ] = true;
+ }
+
+ Extent extent;
+ extent.Init( this );
+ TheNavMesh->ForAllAreasOverlappingExtent( *this, extent );
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavBlocker::UnblockNav( void )
+{
+ if ( m_blockedTeamNumber == TEAM_ANY )
+ {
+ for ( int i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ m_isBlockingNav[ i ] = false;
+ }
+ }
+ else
+ {
+ int teamNumber = m_blockedTeamNumber % MAX_NAV_TEAMS;
+ m_isBlockingNav[ teamNumber ] = false;
+ }
+
+ UpdateBlocked();
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+// functor that blocks areas in our extent
+bool CFuncNavBlocker::operator()( CNavArea *area )
+{
+ area->MarkAsBlocked( m_blockedTeamNumber, this );
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+bool CFuncNavBlocker::CalculateBlocked( bool *pResultByTeam, const Vector &vecMins, const Vector &vecMaxs )
+{
+ int nTeamsBlocked = 0;
+ int i;
+ bool bBlocked = false;
+ for ( i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ pResultByTeam[i] = false;
+ }
+
+ FOR_EACH_LL( gm_NavBlockers, iBlocker )
+ {
+ CFuncNavBlocker *pBlocker = gm_NavBlockers[iBlocker];
+ bool bIsIntersecting = false;
+
+ for ( i=0; i<MAX_NAV_TEAMS; ++i )
+ {
+ if ( pBlocker->m_isBlockingNav[i] )
+ {
+ if ( !pResultByTeam[i] )
+ {
+ if ( bIsIntersecting || ( bIsIntersecting = IsBoxIntersectingBox( pBlocker->m_CachedMins, pBlocker->m_CachedMaxs, vecMins, vecMaxs ) ) != false )
+ {
+ bBlocked = true;
+ pResultByTeam[i] = true;
+ nTeamsBlocked++;
+ }
+ else
+ {
+ continue;
+ }
+ }
+ }
+ }
+
+ if ( nTeamsBlocked == MAX_NAV_TEAMS )
+ {
+ break;
+ }
+ }
+ return bBlocked;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * An entity that can obstruct nav areas. This is meant for semi-transient areas that obstruct
+ * pathfinding but can be ignored for longer-term queries like computing L4D flow distances and
+ * escape routes.
+ */
+class CFuncNavObstruction : public CBaseEntity, public INavAvoidanceObstacle
+{
+ DECLARE_DATADESC();
+ DECLARE_CLASS( CFuncNavObstruction, CBaseEntity );
+
+public:
+ void Spawn();
+ virtual void UpdateOnRemove( void );
+
+ void InputEnable( inputdata_t &inputdata );
+ void InputDisable( inputdata_t &inputdata );
+
+ virtual bool IsPotentiallyAbleToObstructNavAreas( void ) const { return true; } // could we at some future time obstruct nav?
+ virtual float GetNavObstructionHeight( void ) const { return JumpCrouchHeight; } // height at which to obstruct nav areas
+ virtual bool CanObstructNavAreas( void ) const { return !m_bDisabled; } // can we obstruct nav right this instant?
+ virtual CBaseEntity *GetObstructingEntity( void ) { return this; }
+ virtual void OnNavMeshLoaded( void )
+ {
+ if ( !m_bDisabled )
+ {
+ ObstructNavAreas();
+ }
+ }
+
+ int DrawDebugTextOverlays( void );
+
+ bool operator()( CNavArea *area ); // functor that obstructs areas in our extent
+
+private:
+
+ void ObstructNavAreas( void );
+ bool m_bDisabled;
+};
+
+
+
+//--------------------------------------------------------------------------------------------------------
+BEGIN_DATADESC( CFuncNavObstruction )
+ DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS( func_nav_avoidance_obstacle, CFuncNavObstruction );
+
+
+//-----------------------------------------------------------------------------------------------------
+int CFuncNavObstruction::DrawDebugTextOverlays( void )
+{
+ int offset = BaseClass::DrawDebugTextOverlays();
+
+ if (m_debugOverlays & OVERLAY_TEXT_BIT)
+ {
+ if ( CanObstructNavAreas() )
+ {
+ EntityText( offset++, "Obstructing nav", NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ }
+ else
+ {
+ EntityText( offset++, "Not obstructing nav", NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ }
+ }
+
+ return offset;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavObstruction::UpdateOnRemove( void )
+{
+ TheNavMesh->UnregisterAvoidanceObstacle( this );
+
+ BaseClass::UpdateOnRemove();
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavObstruction::Spawn( void )
+{
+ SetMoveType( MOVETYPE_NONE );
+ SetModel( STRING( GetModelName() ) );
+ AddEffects( EF_NODRAW );
+ SetCollisionGroup( COLLISION_GROUP_NONE );
+ SetSolid( SOLID_NONE );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ if ( !m_bDisabled )
+ {
+ ObstructNavAreas();
+ TheNavMesh->RegisterAvoidanceObstacle( this );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavObstruction::InputEnable( inputdata_t &inputdata )
+{
+ m_bDisabled = false;
+ ObstructNavAreas();
+ TheNavMesh->RegisterAvoidanceObstacle( this );
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavObstruction::InputDisable( inputdata_t &inputdata )
+{
+ m_bDisabled = true;
+ TheNavMesh->UnregisterAvoidanceObstacle( this );
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CFuncNavObstruction::ObstructNavAreas( void )
+{
+ Extent extent;
+ extent.Init( this );
+ TheNavMesh->ForAllAreasOverlappingExtent( *this, extent );
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+// functor that blocks areas in our extent
+bool CFuncNavObstruction::operator()( CNavArea *area )
+{
+ area->MarkObstacleToAvoid( GetNavObstructionHeight() );
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------