diff options
Diffstat (limited to 'game/server/nav_entities.cpp')
| -rw-r--r-- | game/server/nav_entities.cpp | 711 |
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; +} + + +//-------------------------------------------------------------------------------------------------------------- |