//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// // nav_entities.cpp // AI Navigation entities // Author: Michael S. Booth (mike@turtlerockstudios.com), 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 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; iHasTag( 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; iHasTag( 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; iClearAllNavCostEntities(); } // now, mark all areas with active cost entities overlapping them for( i=0; iIsEnabled() ) { 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; jGetCenter(), 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::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; iGetName() ), 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; iGetExtent( &areaExtent ); 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; iUpdateBlocked( 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; iForAllAreasOverlappingExtent( *this, extent ); } //-------------------------------------------------------------------------------------------------------- void CFuncNavBlocker::UnblockNav( void ) { if ( m_blockedTeamNumber == TEAM_ANY ) { for ( int i=0; iMarkAsBlocked( 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; im_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; } //--------------------------------------------------------------------------------------------------------------