diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/ai_network.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/ai_network.cpp')
| -rw-r--r-- | mp/src/game/server/ai_network.cpp | 1288 |
1 files changed, 644 insertions, 644 deletions
diff --git a/mp/src/game/server/ai_network.cpp b/mp/src/game/server/ai_network.cpp index 2d73af24..78b7a53f 100644 --- a/mp/src/game/server/ai_network.cpp +++ b/mp/src/game/server/ai_network.cpp @@ -1,644 +1,644 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Base combat character with no AI
-//
-// $Workfile: $
-// $Date: $
-// $NoKeywords: $
-//=============================================================================//
-
-#include "cbase.h"
-
-#include "ai_network.h"
-#include "ai_node.h"
-#include "ai_basenpc.h"
-#include "ai_link.h"
-#include "ai_navigator.h"
-#include "world.h"
-#include "ai_moveprobe.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-ConVar ai_no_node_cache( "ai_no_node_cache", "0" );
-
-extern float MOVE_HEIGHT_EPSILON;
-
-//-----------------------------------------------------------------------------
-
-//-----------------------------------------------------------------------------
-// For now we just have one AINetwork called "BigNet". At some
-// later point we will probabaly have multiple AINetworkds per level
-CAI_Network* g_pBigAINet;
-
-//-----------------------------------------------------------------------------
-
-abstract_class INodeListFilter
-{
-public:
- virtual bool NodeIsValid( CAI_Node &node ) = 0;
- virtual float NodeDistanceSqr( CAI_Node &node ) = 0;
-};
-
-//-------------------------------------
-// Purpose: Filters nodes for an NPC
-//-------------------------------------
-
-class CNodeFilter : public INodeListFilter
-{
-public:
- CNodeFilter( CAI_BaseNPC *pNPC, const Vector &pos ) : m_pNPC(pNPC), m_pos(pos)
- {
- if ( m_pNPC )
- m_capabilities = m_pNPC->CapabilitiesGet();
- }
-
- CNodeFilter( const Vector &pos ) : m_pNPC(NULL), m_pos(pos)
- {
- }
-
- virtual bool NodeIsValid( CAI_Node &node )
- {
- if ( node.GetType() == NODE_DELETED )
- return false;
-
- if ( !m_pNPC )
- return true;
-
- if ( m_pNPC->GetNavType() == NAV_FLY && node.GetType() != NODE_AIR )
- return false;
-
- // Check that node is of proper type for this NPC's navigation ability
- if ((node.GetType() == NODE_AIR && !(m_capabilities & bits_CAP_MOVE_FLY)) ||
- (node.GetType() == NODE_GROUND && !(m_capabilities & bits_CAP_MOVE_GROUND)) )
- return false;
-
- if ( m_pNPC->IsUnusableNode( node.GetId(), node.GetHint() ) )
- return false;
-
- return true;
- }
-
- virtual float NodeDistanceSqr( CAI_Node &node )
- {
- // UNDONE: This call to Position() really seems excessive here. What is the real
- // error % relative to 800 units (MAX_NODE_LINK_DIST) ?
- if ( m_pNPC )
- return (node.GetPosition(m_pNPC->GetHullType()) - m_pos).LengthSqr();
- else
- return (node.GetOrigin() - m_pos).LengthSqr();
- }
-
- const Vector &m_pos;
- CAI_BaseNPC *m_pNPC;
- int m_capabilities; // cache this
-};
-
-//-----------------------------------------------------------------------------
-// CAI_Network
-//-----------------------------------------------------------------------------
-
-// for searching for the nearest node
-// PERFORMANCE: Tune this number
-#define MAX_NEAR_NODES 10 // Trace to 10 nodes at most
-
-//-----------------------------------------------------------------------------
-
-CAI_Network::CAI_Network()
-{
- m_iNumNodes = 0; // Number of nodes in this network
- m_pAInode = NULL; // Array of all nodes in this network
-
- m_iNearestCacheNext = NEARNODE_CACHE_SIZE - 1;
- // Force empty node caches to be rebuild
- for (int node=0;node<NEARNODE_CACHE_SIZE;node++)
- {
- m_NearestCache[node].hull = HULL_NONE;
- m_NearestCache[node].expiration = FLT_MIN;
- }
-
-#ifdef AI_NODE_TREE
- m_pNodeTree = NULL;
-#endif
-}
-
-//-----------------------------------------------------------------------------
-
-CAI_Network::~CAI_Network()
-{
-#ifdef AI_NODE_TREE
- if ( m_pNodeTree )
- {
- engine->DestroySpatialPartition( m_pNodeTree );
- m_pNodeTree = NULL;
- }
-#endif
- if ( m_pAInode )
- {
- for ( int node = 0; node < m_iNumNodes; node++ )
- {
- CAI_Node *pNode = m_pAInode[node];
- Assert( pNode && pNode->m_iID == node );
-
- for ( int link = 0; link < pNode->NumLinks(); link++ )
- {
- CAI_Link *pLink = pNode->m_Links[link];
- if ( pLink )
- {
- int destID = pLink->DestNodeID( node );
- Assert( destID > node && destID < m_iNumNodes );
- if ( destID > node && destID < m_iNumNodes )
- {
- CAI_Node *pDestNode = m_pAInode[destID];
- Assert( pDestNode );
-
- for ( int destLink = 0; destLink < pDestNode->NumLinks(); destLink++ )
- {
- if ( pDestNode->m_Links[destLink] == pLink )
- {
- pDestNode->m_Links[destLink] = NULL;
- }
- }
- }
- delete pLink;
- }
- }
- delete pNode;
- }
- }
- delete[] m_pAInode;
- m_pAInode = NULL;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Given an bitString and float array of size array_size, return the
-// index of the smallest number in the array whose it is set
-//-----------------------------------------------------------------------------
-
-int CAI_Network::FindBSSmallest(CVarBitVec *bitString, float *float_array, int array_size)
-{
- int winIndex = -1;
- float winSize = FLT_MAX;
- for (int i=0;i<array_size;i++)
- {
- if (bitString->IsBitSet(i) && (float_array[i]<winSize))
- {
- winIndex = i;
- winSize = float_array[i];
- }
- }
- return winIndex;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Build a list of nearby nodes sorted by distance
-// Input : &list -
-// maxListCount -
-// *pFilter -
-// Output : int - count of list
-//-----------------------------------------------------------------------------
-
-int CAI_Network::ListNodesInBox( CNodeList &list, int maxListCount, const Vector &mins, const Vector &maxs, INodeListFilter *pFilter )
-{
- CNodeList result;
-
- result.SetLessFunc( CNodeList::RevIsLowerPriority );
-
- // NOTE: maxListCount must be > 0 or this will crash
- bool full = false;
- float flClosest = 1000000.0 * 1000000;
- int closest = 0;
-
-// UNDONE: Store the nodes in a tree and query the tree instead of the entire list!!!
- for ( int node = 0; node < m_iNumNodes; node++ )
- {
- CAI_Node *pNode = m_pAInode[node];
- const Vector &origin = pNode->GetOrigin();
- // in box?
- if ( origin.x < mins.x || origin.x > maxs.x ||
- origin.y < mins.y || origin.y > maxs.y ||
- origin.z < mins.z || origin.z > maxs.z )
- continue;
-
- if ( !pFilter->NodeIsValid(*pNode) )
- continue;
-
- float flDist = pFilter->NodeDistanceSqr(*pNode);
-
- if ( flDist < flClosest )
- {
- closest = node;
- flClosest = flDist;
- }
-
- if ( !full || (flDist < result.ElementAtHead().dist) )
- {
- if ( full )
- result.RemoveAtHead();
-
- result.Insert( AI_NearNode_t(node, flDist) );
-
- full = (result.Count() == maxListCount);
- }
- }
-
- list.RemoveAll();
- while ( result.Count() )
- {
- list.Insert( result.ElementAtHead() );
- result.RemoveAtHead();
- }
-
- return list.Count();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Return ID of node nearest of vecOrigin for pNPC with the given
-// tolerance distance. If a route is required to get to the node
-// node_route is set.
-//-----------------------------------------------------------------------------
-
-int CAI_Network::NearestNodeToPoint( CAI_BaseNPC *pNPC, const Vector &vecOrigin, bool bCheckVisibility, INearestNodeFilter *pFilter )
-{
- AI_PROFILE_SCOPE( CAI_Network_NearestNodeToNPCAtPoint );
-
- // --------------------------------
- // Check if network has no nodes
- // --------------------------------
- if (m_iNumNodes == 0)
- return NO_NODE;
-
- // ----------------------------------------------------------------
- // First check cached nearest node positions
- // ----------------------------------------------------------------
- int cachePos;
- int cachedNode = GetCachedNearestNode( vecOrigin, pNPC, &cachePos );
- if ( cachedNode != NO_NODE )
- {
- if ( bCheckVisibility )
- {
- trace_t tr;
-
- Vector vTestLoc = ( pNPC ) ?
- m_pAInode[cachedNode]->GetPosition(pNPC->GetHullType()) + pNPC->GetViewOffset() :
- m_pAInode[cachedNode]->GetOrigin();
-
- CTraceFilterNav traceFilter( pNPC, true, pNPC, COLLISION_GROUP_NONE );
- AI_TraceLine ( vecOrigin, vTestLoc, MASK_NPCSOLID_BRUSHONLY, &traceFilter, &tr );
-
- if ( tr.fraction != 1.0 )
- cachedNode = NO_NODE;
- }
-
- if ( cachedNode != NO_NODE && ( !pFilter || pFilter->IsValid( m_pAInode[cachedNode] ) ) )
- {
- m_NearestCache[cachePos].expiration = gpGlobals->curtime + NEARNODE_CACHE_LIFE;
- return cachedNode;
- }
- }
-
- // ---------------------------------------------------------------
- // First get nodes distances and eliminate those that are beyond
- // the maximum allowed distance for local movements
- // ---------------------------------------------------------------
- CNodeFilter filter( pNPC, vecOrigin );
-
-#ifdef AI_PERF_MON
- m_nPerfStatNN++;
-#endif
-
- AI_NearNode_t *pBuffer = (AI_NearNode_t *)stackalloc( sizeof(AI_NearNode_t) * MAX_NEAR_NODES );
- CNodeList list( pBuffer, MAX_NEAR_NODES );
-
- // OPTIMIZE: If not flying, this box should be smaller in Z (2 * height?)
- Vector ext(MAX_NODE_LINK_DIST, MAX_NODE_LINK_DIST, MAX_NODE_LINK_DIST);
- // If the NPC can fly, check further
- if ( pNPC && ( pNPC->CapabilitiesGet() & bits_CAP_MOVE_FLY ) )
- {
- ext.Init( MAX_AIR_NODE_LINK_DIST, MAX_AIR_NODE_LINK_DIST, MAX_AIR_NODE_LINK_DIST );
- }
-
- ListNodesInBox( list, MAX_NEAR_NODES, vecOrigin - ext, vecOrigin + ext, &filter );
-
- // --------------------------------------------------------------
- // Now find a reachable node searching the close nodes first
- // --------------------------------------------------------------
- //int smallestVisibleID = NO_NODE;
-
- for( ;list.Count(); list.RemoveAtHead() )
- {
- int smallest = list.ElementAtHead().nodeIndex;
-
- // Check not already rejected above
- if ( smallest == cachedNode )
- continue;
-
- // Check that this node is usable by the current hull size
- if ( pNPC && !pNPC->GetNavigator()->CanFitAtNode(smallest))
- continue;
-
- if ( bCheckVisibility )
- {
- trace_t tr;
-
- Vector vTestLoc = ( pNPC ) ?
- m_pAInode[smallest]->GetPosition(pNPC->GetHullType()) + pNPC->GetNodeViewOffset() :
- m_pAInode[smallest]->GetOrigin();
-
- Vector vecVisOrigin = vecOrigin + Vector(0,0,1);
-
- CTraceFilterNav traceFilter( pNPC, true, pNPC, COLLISION_GROUP_NONE );
- AI_TraceLine ( vecVisOrigin, vTestLoc, MASK_NPCSOLID_BRUSHONLY, &traceFilter, &tr );
-
- if ( tr.fraction != 1.0 )
- continue;
- }
-
- if ( pFilter )
- {
- if ( !pFilter->IsValid( m_pAInode[smallest] ) )
- {
- if ( !pFilter->ShouldContinue() )
- break;
- continue;
- }
- }
-
- SetCachedNearestNode( vecOrigin, smallest, (pNPC) ? pNPC->GetHullType() : HULL_NONE );
-
- return smallest;
- }
-
- // Store inability to reach in cache for later use
- SetCachedNearestNode( vecOrigin, NO_NODE, (pNPC) ? pNPC->GetHullType() : HULL_NONE );
-
- return NO_NODE;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Find the nearest node to an entity without regard to how whether
-// the node can be reached
-//-----------------------------------------------------------------------------
-
-int CAI_Network::NearestNodeToPoint(const Vector &vPosition, bool bCheckVisibility )
-{
- return NearestNodeToPoint( NULL, vPosition, bCheckVisibility );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Check nearest node cache for checkPos and return cached nearest
-// node if it exists in the cache. Doesn't care about reachability,
-// only if the node is visible
-//-----------------------------------------------------------------------------
-int CAI_Network::GetCachedNode(const Vector &checkPos, Hull_t nHull, int *pCachePos )
-{
- if ( ai_no_node_cache.GetBool() )
- return NOT_CACHED;
-
- // Walk from newest to oldest.
- int iNewest = m_iNearestCacheNext + 1;
- for ( int i = 0; i < NEARNODE_CACHE_SIZE; i++ )
- {
- int iCurrent = ( iNewest + i ) % NEARNODE_CACHE_SIZE;
- if ( m_NearestCache[iCurrent].hull == nHull && m_NearestCache[iCurrent].expiration > gpGlobals->curtime )
- {
- if ( (m_NearestCache[iCurrent].vTestPosition - checkPos).LengthSqr() < Square(24.0) )
- {
- if ( pCachePos )
- *pCachePos = iCurrent;
- return m_NearestCache[iCurrent].node;
- }
- }
- }
-
-
- if ( pCachePos )
- *pCachePos = -1;
- return NOT_CACHED;
-}
-
-//-----------------------------------------------------------------------------
-
-int CAI_Network::GetCachedNearestNode(const Vector &checkPos, CAI_BaseNPC *pNPC, int *pCachePos )
-{
- if ( pNPC )
- {
- CNodeFilter filter( pNPC, checkPos );
-
- int nodeID = GetCachedNode( checkPos, pNPC->GetHullType(), pCachePos );
- if ( nodeID >= 0 )
- {
- if ( filter.NodeIsValid( *m_pAInode[nodeID] ) && pNPC->GetNavigator()->CanFitAtNode(nodeID) )
- return nodeID;
- }
- }
- return NO_NODE;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Update nearest node cache with new data
-// if nHull == HULL_NONE, reachability of this node wasn't checked
-//-----------------------------------------------------------------------------
-
-void CAI_Network::SetCachedNearestNode(const Vector &checkPos, int nodeID, Hull_t nHull)
-{
- if ( ai_no_node_cache.GetBool() )
- return;
-
- m_NearestCache[m_iNearestCacheNext].vTestPosition = checkPos;
- m_NearestCache[m_iNearestCacheNext].node = nodeID;
- m_NearestCache[m_iNearestCacheNext].hull = nHull;
- m_NearestCache[m_iNearestCacheNext].expiration = gpGlobals->curtime + NEARNODE_CACHE_LIFE;
-
- m_iNearestCacheNext--;
- if ( m_iNearestCacheNext < 0 )
- {
- m_iNearestCacheNext = NEARNODE_CACHE_SIZE - 1;
- }
-}
-
-//-----------------------------------------------------------------------------
-
-Vector CAI_Network::GetNodePosition( Hull_t hull, int nodeID )
-{
- if ( !m_pAInode )
- {
- Assert( 0 );
- return vec3_origin;
- }
-
- if ( ( nodeID < 0 ) || ( nodeID > m_iNumNodes ) )
- {
- Assert( 0 );
- return vec3_origin;
- }
-
- return m_pAInode[nodeID]->GetPosition( hull );
-}
-
-//-----------------------------------------------------------------------------
-
-Vector CAI_Network::GetNodePosition( CBaseCombatCharacter *pNPC, int nodeID )
-{
- if ( pNPC == NULL )
- {
- Assert( 0 );
- return vec3_origin;
- }
-
- return GetNodePosition( pNPC->GetHullType(), nodeID );
-}
-
-//-----------------------------------------------------------------------------
-
-float CAI_Network::GetNodeYaw( int nodeID )
-{
- if ( !m_pAInode )
- {
- Assert( 0 );
- return 0.0f;
- }
-
- if ( ( nodeID < 0 ) || ( nodeID > m_iNumNodes ) )
- {
- Assert( 0 );
- return 0.0f;
- }
-
- return m_pAInode[nodeID]->GetYaw();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Adds an ainode to the network
-//-----------------------------------------------------------------------------
-
-CAI_Node *CAI_Network::AddNode( const Vector &origin, float yaw )
-{
- // ---------------------------------------------------------------------
- // When loaded from a file will know the number of nodes from
- // the start. Otherwise init MAX_NODES of them if not initialized
- // ---------------------------------------------------------------------
- if (!m_pAInode || !m_pAInode[0])
- {
- if ( m_pAInode )
- delete [] m_pAInode;
- m_pAInode = new CAI_Node*[MAX_NODES];
- }
-
- if (m_iNumNodes >= MAX_NODES)
- {
- DevMsg( "ERROR: too many nodes in map, deleting last node.\n" );
- m_iNumNodes--;
- }
-
- m_pAInode[m_iNumNodes] = new CAI_Node( m_iNumNodes, origin, yaw );
-
-#ifdef AI_NODE_TREE
- if ( !m_pNodeTree )
- {
- Vector worldMins, worldMaxs;
- GetWorldEntity()->GetWorldBounds( worldMins, worldMaxs );
- m_pNodeTree = engine->CreateSpatialPartition( worldMins, worldMaxs );
- }
-
- m_pNodeTree->CreateHandle( (IHandleEntity *)m_iNumNodes, PARTITION_NODE, origin, origin ); // ignore result for now as we'll never move or remove
-#endif
-
- m_iNumNodes++;
-
- return m_pAInode[m_iNumNodes-1];
-};
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-
-CAI_Link *CAI_Network::CreateLink( int srcID, int destID, CAI_DynamicLink *pDynamicLink )
-{
- CAI_Node *pSrcNode = g_pBigAINet->GetNode( srcID );
- CAI_Node *pDestNode = g_pBigAINet->GetNode( destID );
-
- Assert( pSrcNode && pDestNode && pSrcNode != pDestNode );
-
- if ( !pSrcNode || !pDestNode )
- {
- DevMsg( "Attempted to create link to node that doesn't exist\n" );
- return NULL;
- }
-
- if ( pSrcNode == pDestNode )
- {
- DevMsg( "Attempted to link a node to itself\n" );
- return NULL;
- }
-
- if ( pSrcNode->NumLinks() == AI_MAX_NODE_LINKS )
- {
- DevMsg( "Node %d has too many links\n", srcID );
- return NULL;
- }
-
- if ( pDestNode->NumLinks() == AI_MAX_NODE_LINKS )
- {
- DevMsg( "Node %d has too many links\n", destID );
- return NULL;
- }
-
- CAI_Link *pLink = new CAI_Link;
-
- pLink->m_iSrcID = srcID;
- pLink->m_iDestID = destID;
- pLink->m_pDynamicLink = pDynamicLink;
-
- pSrcNode->AddLink(pLink);
- pDestNode->AddLink(pLink);
-
- return pLink;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns true is two nodes are connected by the network graph
-//-----------------------------------------------------------------------------
-
-bool CAI_Network::IsConnected(int srcID, int destID)
-{
- if (srcID > m_iNumNodes || destID > m_iNumNodes)
- {
- DevMsg("IsConnected called with invalid node IDs!\n");
- return false;
- }
-
- if ( srcID == destID )
- return true;
-
- int srcZone = m_pAInode[srcID]->GetZone();
- int destZone = m_pAInode[destID]->GetZone();
-
- if ( srcZone == AI_NODE_ZONE_SOLO || destZone == AI_NODE_ZONE_SOLO )
- return false;
-
- if ( srcZone == AI_NODE_ZONE_UNIVERSAL || destZone == AI_NODE_ZONE_UNIVERSAL ) // only happens in WC edit case
- return true;
-
-#ifdef DEBUG
- if ( srcZone == AI_NODE_ZONE_UNKNOWN || destZone == AI_NODE_ZONE_UNKNOWN )
- {
- DevMsg( "Warning: Node found in unknown zone\n" );
- return true;
- }
-#endif
-
- return ( srcZone == destZone );
-}
-
-//-----------------------------------------------------------------------------
-
-IterationRetval_t CAI_Network::EnumElement( IHandleEntity *pHandleEntity )
-{
-#ifdef AI_NODE_TREE
- m_GatheredNodes.AddToTail( (int)pHandleEntity );
-#endif
- return ITERATION_CONTINUE;
-}
-
-//=============================================================================
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Base combat character with no AI +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "ai_network.h" +#include "ai_node.h" +#include "ai_basenpc.h" +#include "ai_link.h" +#include "ai_navigator.h" +#include "world.h" +#include "ai_moveprobe.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar ai_no_node_cache( "ai_no_node_cache", "0" ); + +extern float MOVE_HEIGHT_EPSILON; + +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// For now we just have one AINetwork called "BigNet". At some +// later point we will probabaly have multiple AINetworkds per level +CAI_Network* g_pBigAINet; + +//----------------------------------------------------------------------------- + +abstract_class INodeListFilter +{ +public: + virtual bool NodeIsValid( CAI_Node &node ) = 0; + virtual float NodeDistanceSqr( CAI_Node &node ) = 0; +}; + +//------------------------------------- +// Purpose: Filters nodes for an NPC +//------------------------------------- + +class CNodeFilter : public INodeListFilter +{ +public: + CNodeFilter( CAI_BaseNPC *pNPC, const Vector &pos ) : m_pNPC(pNPC), m_pos(pos) + { + if ( m_pNPC ) + m_capabilities = m_pNPC->CapabilitiesGet(); + } + + CNodeFilter( const Vector &pos ) : m_pNPC(NULL), m_pos(pos) + { + } + + virtual bool NodeIsValid( CAI_Node &node ) + { + if ( node.GetType() == NODE_DELETED ) + return false; + + if ( !m_pNPC ) + return true; + + if ( m_pNPC->GetNavType() == NAV_FLY && node.GetType() != NODE_AIR ) + return false; + + // Check that node is of proper type for this NPC's navigation ability + if ((node.GetType() == NODE_AIR && !(m_capabilities & bits_CAP_MOVE_FLY)) || + (node.GetType() == NODE_GROUND && !(m_capabilities & bits_CAP_MOVE_GROUND)) ) + return false; + + if ( m_pNPC->IsUnusableNode( node.GetId(), node.GetHint() ) ) + return false; + + return true; + } + + virtual float NodeDistanceSqr( CAI_Node &node ) + { + // UNDONE: This call to Position() really seems excessive here. What is the real + // error % relative to 800 units (MAX_NODE_LINK_DIST) ? + if ( m_pNPC ) + return (node.GetPosition(m_pNPC->GetHullType()) - m_pos).LengthSqr(); + else + return (node.GetOrigin() - m_pos).LengthSqr(); + } + + const Vector &m_pos; + CAI_BaseNPC *m_pNPC; + int m_capabilities; // cache this +}; + +//----------------------------------------------------------------------------- +// CAI_Network +//----------------------------------------------------------------------------- + +// for searching for the nearest node +// PERFORMANCE: Tune this number +#define MAX_NEAR_NODES 10 // Trace to 10 nodes at most + +//----------------------------------------------------------------------------- + +CAI_Network::CAI_Network() +{ + m_iNumNodes = 0; // Number of nodes in this network + m_pAInode = NULL; // Array of all nodes in this network + + m_iNearestCacheNext = NEARNODE_CACHE_SIZE - 1; + // Force empty node caches to be rebuild + for (int node=0;node<NEARNODE_CACHE_SIZE;node++) + { + m_NearestCache[node].hull = HULL_NONE; + m_NearestCache[node].expiration = FLT_MIN; + } + +#ifdef AI_NODE_TREE + m_pNodeTree = NULL; +#endif +} + +//----------------------------------------------------------------------------- + +CAI_Network::~CAI_Network() +{ +#ifdef AI_NODE_TREE + if ( m_pNodeTree ) + { + engine->DestroySpatialPartition( m_pNodeTree ); + m_pNodeTree = NULL; + } +#endif + if ( m_pAInode ) + { + for ( int node = 0; node < m_iNumNodes; node++ ) + { + CAI_Node *pNode = m_pAInode[node]; + Assert( pNode && pNode->m_iID == node ); + + for ( int link = 0; link < pNode->NumLinks(); link++ ) + { + CAI_Link *pLink = pNode->m_Links[link]; + if ( pLink ) + { + int destID = pLink->DestNodeID( node ); + Assert( destID > node && destID < m_iNumNodes ); + if ( destID > node && destID < m_iNumNodes ) + { + CAI_Node *pDestNode = m_pAInode[destID]; + Assert( pDestNode ); + + for ( int destLink = 0; destLink < pDestNode->NumLinks(); destLink++ ) + { + if ( pDestNode->m_Links[destLink] == pLink ) + { + pDestNode->m_Links[destLink] = NULL; + } + } + } + delete pLink; + } + } + delete pNode; + } + } + delete[] m_pAInode; + m_pAInode = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Given an bitString and float array of size array_size, return the +// index of the smallest number in the array whose it is set +//----------------------------------------------------------------------------- + +int CAI_Network::FindBSSmallest(CVarBitVec *bitString, float *float_array, int array_size) +{ + int winIndex = -1; + float winSize = FLT_MAX; + for (int i=0;i<array_size;i++) + { + if (bitString->IsBitSet(i) && (float_array[i]<winSize)) + { + winIndex = i; + winSize = float_array[i]; + } + } + return winIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Build a list of nearby nodes sorted by distance +// Input : &list - +// maxListCount - +// *pFilter - +// Output : int - count of list +//----------------------------------------------------------------------------- + +int CAI_Network::ListNodesInBox( CNodeList &list, int maxListCount, const Vector &mins, const Vector &maxs, INodeListFilter *pFilter ) +{ + CNodeList result; + + result.SetLessFunc( CNodeList::RevIsLowerPriority ); + + // NOTE: maxListCount must be > 0 or this will crash + bool full = false; + float flClosest = 1000000.0 * 1000000; + int closest = 0; + +// UNDONE: Store the nodes in a tree and query the tree instead of the entire list!!! + for ( int node = 0; node < m_iNumNodes; node++ ) + { + CAI_Node *pNode = m_pAInode[node]; + const Vector &origin = pNode->GetOrigin(); + // in box? + if ( origin.x < mins.x || origin.x > maxs.x || + origin.y < mins.y || origin.y > maxs.y || + origin.z < mins.z || origin.z > maxs.z ) + continue; + + if ( !pFilter->NodeIsValid(*pNode) ) + continue; + + float flDist = pFilter->NodeDistanceSqr(*pNode); + + if ( flDist < flClosest ) + { + closest = node; + flClosest = flDist; + } + + if ( !full || (flDist < result.ElementAtHead().dist) ) + { + if ( full ) + result.RemoveAtHead(); + + result.Insert( AI_NearNode_t(node, flDist) ); + + full = (result.Count() == maxListCount); + } + } + + list.RemoveAll(); + while ( result.Count() ) + { + list.Insert( result.ElementAtHead() ); + result.RemoveAtHead(); + } + + return list.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return ID of node nearest of vecOrigin for pNPC with the given +// tolerance distance. If a route is required to get to the node +// node_route is set. +//----------------------------------------------------------------------------- + +int CAI_Network::NearestNodeToPoint( CAI_BaseNPC *pNPC, const Vector &vecOrigin, bool bCheckVisibility, INearestNodeFilter *pFilter ) +{ + AI_PROFILE_SCOPE( CAI_Network_NearestNodeToNPCAtPoint ); + + // -------------------------------- + // Check if network has no nodes + // -------------------------------- + if (m_iNumNodes == 0) + return NO_NODE; + + // ---------------------------------------------------------------- + // First check cached nearest node positions + // ---------------------------------------------------------------- + int cachePos; + int cachedNode = GetCachedNearestNode( vecOrigin, pNPC, &cachePos ); + if ( cachedNode != NO_NODE ) + { + if ( bCheckVisibility ) + { + trace_t tr; + + Vector vTestLoc = ( pNPC ) ? + m_pAInode[cachedNode]->GetPosition(pNPC->GetHullType()) + pNPC->GetViewOffset() : + m_pAInode[cachedNode]->GetOrigin(); + + CTraceFilterNav traceFilter( pNPC, true, pNPC, COLLISION_GROUP_NONE ); + AI_TraceLine ( vecOrigin, vTestLoc, MASK_NPCSOLID_BRUSHONLY, &traceFilter, &tr ); + + if ( tr.fraction != 1.0 ) + cachedNode = NO_NODE; + } + + if ( cachedNode != NO_NODE && ( !pFilter || pFilter->IsValid( m_pAInode[cachedNode] ) ) ) + { + m_NearestCache[cachePos].expiration = gpGlobals->curtime + NEARNODE_CACHE_LIFE; + return cachedNode; + } + } + + // --------------------------------------------------------------- + // First get nodes distances and eliminate those that are beyond + // the maximum allowed distance for local movements + // --------------------------------------------------------------- + CNodeFilter filter( pNPC, vecOrigin ); + +#ifdef AI_PERF_MON + m_nPerfStatNN++; +#endif + + AI_NearNode_t *pBuffer = (AI_NearNode_t *)stackalloc( sizeof(AI_NearNode_t) * MAX_NEAR_NODES ); + CNodeList list( pBuffer, MAX_NEAR_NODES ); + + // OPTIMIZE: If not flying, this box should be smaller in Z (2 * height?) + Vector ext(MAX_NODE_LINK_DIST, MAX_NODE_LINK_DIST, MAX_NODE_LINK_DIST); + // If the NPC can fly, check further + if ( pNPC && ( pNPC->CapabilitiesGet() & bits_CAP_MOVE_FLY ) ) + { + ext.Init( MAX_AIR_NODE_LINK_DIST, MAX_AIR_NODE_LINK_DIST, MAX_AIR_NODE_LINK_DIST ); + } + + ListNodesInBox( list, MAX_NEAR_NODES, vecOrigin - ext, vecOrigin + ext, &filter ); + + // -------------------------------------------------------------- + // Now find a reachable node searching the close nodes first + // -------------------------------------------------------------- + //int smallestVisibleID = NO_NODE; + + for( ;list.Count(); list.RemoveAtHead() ) + { + int smallest = list.ElementAtHead().nodeIndex; + + // Check not already rejected above + if ( smallest == cachedNode ) + continue; + + // Check that this node is usable by the current hull size + if ( pNPC && !pNPC->GetNavigator()->CanFitAtNode(smallest)) + continue; + + if ( bCheckVisibility ) + { + trace_t tr; + + Vector vTestLoc = ( pNPC ) ? + m_pAInode[smallest]->GetPosition(pNPC->GetHullType()) + pNPC->GetNodeViewOffset() : + m_pAInode[smallest]->GetOrigin(); + + Vector vecVisOrigin = vecOrigin + Vector(0,0,1); + + CTraceFilterNav traceFilter( pNPC, true, pNPC, COLLISION_GROUP_NONE ); + AI_TraceLine ( vecVisOrigin, vTestLoc, MASK_NPCSOLID_BRUSHONLY, &traceFilter, &tr ); + + if ( tr.fraction != 1.0 ) + continue; + } + + if ( pFilter ) + { + if ( !pFilter->IsValid( m_pAInode[smallest] ) ) + { + if ( !pFilter->ShouldContinue() ) + break; + continue; + } + } + + SetCachedNearestNode( vecOrigin, smallest, (pNPC) ? pNPC->GetHullType() : HULL_NONE ); + + return smallest; + } + + // Store inability to reach in cache for later use + SetCachedNearestNode( vecOrigin, NO_NODE, (pNPC) ? pNPC->GetHullType() : HULL_NONE ); + + return NO_NODE; +} + + +//----------------------------------------------------------------------------- +// Purpose: Find the nearest node to an entity without regard to how whether +// the node can be reached +//----------------------------------------------------------------------------- + +int CAI_Network::NearestNodeToPoint(const Vector &vPosition, bool bCheckVisibility ) +{ + return NearestNodeToPoint( NULL, vPosition, bCheckVisibility ); +} + +//----------------------------------------------------------------------------- +// Purpose: Check nearest node cache for checkPos and return cached nearest +// node if it exists in the cache. Doesn't care about reachability, +// only if the node is visible +//----------------------------------------------------------------------------- +int CAI_Network::GetCachedNode(const Vector &checkPos, Hull_t nHull, int *pCachePos ) +{ + if ( ai_no_node_cache.GetBool() ) + return NOT_CACHED; + + // Walk from newest to oldest. + int iNewest = m_iNearestCacheNext + 1; + for ( int i = 0; i < NEARNODE_CACHE_SIZE; i++ ) + { + int iCurrent = ( iNewest + i ) % NEARNODE_CACHE_SIZE; + if ( m_NearestCache[iCurrent].hull == nHull && m_NearestCache[iCurrent].expiration > gpGlobals->curtime ) + { + if ( (m_NearestCache[iCurrent].vTestPosition - checkPos).LengthSqr() < Square(24.0) ) + { + if ( pCachePos ) + *pCachePos = iCurrent; + return m_NearestCache[iCurrent].node; + } + } + } + + + if ( pCachePos ) + *pCachePos = -1; + return NOT_CACHED; +} + +//----------------------------------------------------------------------------- + +int CAI_Network::GetCachedNearestNode(const Vector &checkPos, CAI_BaseNPC *pNPC, int *pCachePos ) +{ + if ( pNPC ) + { + CNodeFilter filter( pNPC, checkPos ); + + int nodeID = GetCachedNode( checkPos, pNPC->GetHullType(), pCachePos ); + if ( nodeID >= 0 ) + { + if ( filter.NodeIsValid( *m_pAInode[nodeID] ) && pNPC->GetNavigator()->CanFitAtNode(nodeID) ) + return nodeID; + } + } + return NO_NODE; +} + +//----------------------------------------------------------------------------- +// Purpose: Update nearest node cache with new data +// if nHull == HULL_NONE, reachability of this node wasn't checked +//----------------------------------------------------------------------------- + +void CAI_Network::SetCachedNearestNode(const Vector &checkPos, int nodeID, Hull_t nHull) +{ + if ( ai_no_node_cache.GetBool() ) + return; + + m_NearestCache[m_iNearestCacheNext].vTestPosition = checkPos; + m_NearestCache[m_iNearestCacheNext].node = nodeID; + m_NearestCache[m_iNearestCacheNext].hull = nHull; + m_NearestCache[m_iNearestCacheNext].expiration = gpGlobals->curtime + NEARNODE_CACHE_LIFE; + + m_iNearestCacheNext--; + if ( m_iNearestCacheNext < 0 ) + { + m_iNearestCacheNext = NEARNODE_CACHE_SIZE - 1; + } +} + +//----------------------------------------------------------------------------- + +Vector CAI_Network::GetNodePosition( Hull_t hull, int nodeID ) +{ + if ( !m_pAInode ) + { + Assert( 0 ); + return vec3_origin; + } + + if ( ( nodeID < 0 ) || ( nodeID > m_iNumNodes ) ) + { + Assert( 0 ); + return vec3_origin; + } + + return m_pAInode[nodeID]->GetPosition( hull ); +} + +//----------------------------------------------------------------------------- + +Vector CAI_Network::GetNodePosition( CBaseCombatCharacter *pNPC, int nodeID ) +{ + if ( pNPC == NULL ) + { + Assert( 0 ); + return vec3_origin; + } + + return GetNodePosition( pNPC->GetHullType(), nodeID ); +} + +//----------------------------------------------------------------------------- + +float CAI_Network::GetNodeYaw( int nodeID ) +{ + if ( !m_pAInode ) + { + Assert( 0 ); + return 0.0f; + } + + if ( ( nodeID < 0 ) || ( nodeID > m_iNumNodes ) ) + { + Assert( 0 ); + return 0.0f; + } + + return m_pAInode[nodeID]->GetYaw(); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds an ainode to the network +//----------------------------------------------------------------------------- + +CAI_Node *CAI_Network::AddNode( const Vector &origin, float yaw ) +{ + // --------------------------------------------------------------------- + // When loaded from a file will know the number of nodes from + // the start. Otherwise init MAX_NODES of them if not initialized + // --------------------------------------------------------------------- + if (!m_pAInode || !m_pAInode[0]) + { + if ( m_pAInode ) + delete [] m_pAInode; + m_pAInode = new CAI_Node*[MAX_NODES]; + } + + if (m_iNumNodes >= MAX_NODES) + { + DevMsg( "ERROR: too many nodes in map, deleting last node.\n" ); + m_iNumNodes--; + } + + m_pAInode[m_iNumNodes] = new CAI_Node( m_iNumNodes, origin, yaw ); + +#ifdef AI_NODE_TREE + if ( !m_pNodeTree ) + { + Vector worldMins, worldMaxs; + GetWorldEntity()->GetWorldBounds( worldMins, worldMaxs ); + m_pNodeTree = engine->CreateSpatialPartition( worldMins, worldMaxs ); + } + + m_pNodeTree->CreateHandle( (IHandleEntity *)m_iNumNodes, PARTITION_NODE, origin, origin ); // ignore result for now as we'll never move or remove +#endif + + m_iNumNodes++; + + return m_pAInode[m_iNumNodes-1]; +}; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +CAI_Link *CAI_Network::CreateLink( int srcID, int destID, CAI_DynamicLink *pDynamicLink ) +{ + CAI_Node *pSrcNode = g_pBigAINet->GetNode( srcID ); + CAI_Node *pDestNode = g_pBigAINet->GetNode( destID ); + + Assert( pSrcNode && pDestNode && pSrcNode != pDestNode ); + + if ( !pSrcNode || !pDestNode ) + { + DevMsg( "Attempted to create link to node that doesn't exist\n" ); + return NULL; + } + + if ( pSrcNode == pDestNode ) + { + DevMsg( "Attempted to link a node to itself\n" ); + return NULL; + } + + if ( pSrcNode->NumLinks() == AI_MAX_NODE_LINKS ) + { + DevMsg( "Node %d has too many links\n", srcID ); + return NULL; + } + + if ( pDestNode->NumLinks() == AI_MAX_NODE_LINKS ) + { + DevMsg( "Node %d has too many links\n", destID ); + return NULL; + } + + CAI_Link *pLink = new CAI_Link; + + pLink->m_iSrcID = srcID; + pLink->m_iDestID = destID; + pLink->m_pDynamicLink = pDynamicLink; + + pSrcNode->AddLink(pLink); + pDestNode->AddLink(pLink); + + return pLink; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true is two nodes are connected by the network graph +//----------------------------------------------------------------------------- + +bool CAI_Network::IsConnected(int srcID, int destID) +{ + if (srcID > m_iNumNodes || destID > m_iNumNodes) + { + DevMsg("IsConnected called with invalid node IDs!\n"); + return false; + } + + if ( srcID == destID ) + return true; + + int srcZone = m_pAInode[srcID]->GetZone(); + int destZone = m_pAInode[destID]->GetZone(); + + if ( srcZone == AI_NODE_ZONE_SOLO || destZone == AI_NODE_ZONE_SOLO ) + return false; + + if ( srcZone == AI_NODE_ZONE_UNIVERSAL || destZone == AI_NODE_ZONE_UNIVERSAL ) // only happens in WC edit case + return true; + +#ifdef DEBUG + if ( srcZone == AI_NODE_ZONE_UNKNOWN || destZone == AI_NODE_ZONE_UNKNOWN ) + { + DevMsg( "Warning: Node found in unknown zone\n" ); + return true; + } +#endif + + return ( srcZone == destZone ); +} + +//----------------------------------------------------------------------------- + +IterationRetval_t CAI_Network::EnumElement( IHandleEntity *pHandleEntity ) +{ +#ifdef AI_NODE_TREE + m_GatheredNodes.AddToTail( (int)pHandleEntity ); +#endif + return ITERATION_CONTINUE; +} + +//============================================================================= |