aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/ai_networkmanager.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/ai_networkmanager.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/ai_networkmanager.cpp')
-rw-r--r--mp/src/game/server/ai_networkmanager.cpp6496
1 files changed, 3248 insertions, 3248 deletions
diff --git a/mp/src/game/server/ai_networkmanager.cpp b/mp/src/game/server/ai_networkmanager.cpp
index 69d6319e..7f6bea61 100644
--- a/mp/src/game/server/ai_networkmanager.cpp
+++ b/mp/src/game/server/ai_networkmanager.cpp
@@ -1,3248 +1,3248 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-
-#include "cbase.h"
-#include "fmtstr.h"
-#include "filesystem.h"
-#include "filesystem/IQueuedLoader.h"
-#include "utlbuffer.h"
-#include "utlrbtree.h"
-#include "editor_sendcommand.h"
-
-#include "ai_networkmanager.h"
-#include "ai_network.h"
-#include "ai_node.h"
-#include "ai_navigator.h"
-#include "ai_link.h"
-#include "ai_dynamiclink.h"
-#include "ai_initutils.h"
-#include "ai_moveprobe.h"
-#include "ai_hull.h"
-#include "ndebugoverlay.h"
-#include "ai_hint.h"
-#include "tier0/icommandline.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-// Increment this to force rebuilding of all networks
-#define AINET_VERSION_NUMBER 37
-
-//-----------------------------------------------------------------------------
-
-int g_DebugConnectNode1 = -1;
-int g_DebugConnectNode2 = -1;
-#define DebuggingConnect( node1, node2 ) ( ( node1 == g_DebugConnectNode1 && node2 == g_DebugConnectNode2 ) || ( node1 == g_DebugConnectNode2 && node2 == g_DebugConnectNode1 ) )
-
-inline void DebugConnectMsg( int node1, int node2, const char *pszFormat, ... )
-{
- if ( DebuggingConnect( node1, node2 ) )
- {
- char string[ 2048 ];
- va_list argptr;
- va_start( argptr, pszFormat );
- Q_vsnprintf( string, sizeof(string), pszFormat, argptr );
- va_end( argptr );
-
- DevMsg( "%s", string );
- }
-}
-
-CON_COMMAND( ai_debug_node_connect, "Debug the attempted connection between two nodes" )
-{
- g_DebugConnectNode1 = atoi( args[1] );
- g_DebugConnectNode2 = atoi( args[2] );
-
- DevMsg( "ai_debug_node_connect: debugging enbabled for %d <--> %d\n", g_DebugConnectNode1, g_DebugConnectNode2 );
-}
-
-//-----------------------------------------------------------------------------
-// This CVAR allows level designers to override the building
-// of node graphs due to date conflicts with the BSP and AIN
-// files. That way they don't have to wait for the node graph
-// to rebuild following small only-ents changes. This CVAR
-// always defaults to 0 and must be set at the command
-// line to properly override the node graph building.
-
-ConVar g_ai_norebuildgraph( "ai_norebuildgraph", "0" );
-
-
-//-----------------------------------------------------------------------------
-// CAI_NetworkManager
-//
-//-----------------------------------------------------------------------------
-
-CAI_NetworkManager *g_pAINetworkManager;
-
-//-----------------------------------------------------------------------------
-
-bool CAI_NetworkManager::gm_fNetworksLoaded;
-
-LINK_ENTITY_TO_CLASS(ai_network,CAI_NetworkManager);
-
-BEGIN_DATADESC( CAI_NetworkManager )
-
- DEFINE_FIELD( m_bNeedGraphRebuild, FIELD_BOOLEAN ),
- // m_pEditOps
- // m_pNetwork
- // DEFINE_FIELD( m_bDontSaveGraph, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_fInitalized, FIELD_BOOLEAN ),
-
- // Function pointers
- DEFINE_FUNCTION( DelayedInit ),
- DEFINE_FUNCTION( RebuildThink ),
-
-END_DATADESC()
-
-
-//-----------------------------------------------------------------------------
-
-CAI_NetworkManager::CAI_NetworkManager(void)
-{
- m_pNetwork = new CAI_Network;
- m_pEditOps = new CAI_NetworkEditTools(this);
- m_bNeedGraphRebuild = false;
- m_fInitalized = false;
- CAI_DynamicLink::gm_bInitialized = false;
-
- // ---------------------------------
- // Add to linked list of networks
- // ---------------------------------
-};
-
-//-----------------------------------------------------------------------------
-
-CAI_NetworkManager::~CAI_NetworkManager(void)
-{
- // ---------------------------------------
- // Remove from linked list of AINetworks
- // ---------------------------------------
- delete m_pEditOps;
- delete m_pNetwork;
- if ( g_pAINetworkManager == this )
- {
- g_pAINetworkManager = NULL;
- }
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose : Think function so we can put message on screen saying we are
-// going to rebuild the network, before we hang during the rebuild
-//------------------------------------------------------------------------------
-
-void CAI_NetworkManager::RebuildThink( void )
-{
- SetThink(NULL);
-
- GetEditOps()->m_debugNetOverlays &= ~bits_debugNeedRebuild;
- StartRebuild( );
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Delay function so we can put message on screen saying we are
-// going to rebuild the network, before we hang during the rebuild
-//------------------------------------------------------------------------------
-
-void CAI_NetworkManager::RebuildNetworkGraph( void )
-{
- if (m_pfnThink != (void (CBaseEntity::*)())&CAI_NetworkManager::RebuildThink)
- {
- UTIL_CenterPrintAll( "Doing partial rebuild of Node Graph...\n" );
- SetThink(&CAI_NetworkManager::RebuildThink);
- SetNextThink( gpGlobals->curtime + 0.1f );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Used for WC edit move to rebuild the network around the given
-// location. Rebuilding the entire network takes too long
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkManager::StartRebuild( void )
-{
- CAI_DynamicLink::gm_bInitialized = false;
-
- g_AINetworkBuilder.Rebuild( m_pNetwork );
-
- // ------------------------------------------------------------
- // Purge any dynamic links for links that don't exist any more
- // ------------------------------------------------------------
- CAI_DynamicLink::PurgeDynamicLinks();
-
-
- // ------------------------
- // Reset all dynamic links
- // ------------------------
- CAI_DynamicLink::ResetDynamicLinks();
-
- // --------------------------------------------------
- // Update display of usable nodes for displayed hull
- // --------------------------------------------------
- GetEditOps()->RecalcUsableNodesForHull();
-
- GetEditOps()->ClearRebuildFlags();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Called by save restore code if no valid load graph was loaded at restore time.
-// Prevents writing out of a "bogus" node graph...
-// Input : -
-//-----------------------------------------------------------------------------
-void CAI_NetworkManager::MarkDontSaveGraph()
-{
- m_bDontSaveGraph = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Only called if network has changed since last time level
-// was loaded
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkManager::SaveNetworkGraph( void )
-{
- if ( m_bDontSaveGraph )
- return;
-
- if ( !m_bNeedGraphRebuild )
- return;
-
- //if ( g_AI_Manager.NumAIs() && m_pNetwork->m_iNumNodes == 0 )
- //{
- // return;
- //}
-
- if ( !g_pGameRules->FAllowNPCs() )
- {
- return;
- }
-
- // -----------------------------
- // Make sure directories have been made
- // -----------------------------
- char szNrpFilename [MAX_PATH];// text node report filename
- Q_strncpy( szNrpFilename, "maps/graphs" ,sizeof(szNrpFilename));
-
- // Usually adding on the map filename and stripping it does nothing, but if the map is under a subdir,
- // this makes it create the correct subdir under maps/graphs.
- char tempFilename[MAX_PATH];
- Q_snprintf( tempFilename, sizeof( tempFilename ), "%s/%s", szNrpFilename, STRING( gpGlobals->mapname ) );
-
- // Remove the filename.
- int len = strlen( tempFilename );
- for ( int i=0; i < len; i++ )
- {
- if ( tempFilename[len-i-1] == '/' || tempFilename[len-i-1] == '\\' )
- {
- tempFilename[len-i-1] = 0;
- break;
- }
- }
-
- // Make sure the directories we need exist.
- filesystem->CreateDirHierarchy( tempFilename, "DEFAULT_WRITE_PATH" );
-
- // Now add the real map filename.
- Q_strncat( szNrpFilename, "/", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
- Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
- Q_strncat( szNrpFilename, IsX360() ? ".360.ain" : ".ain", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
-
- CUtlBuffer buf;
-
- // ---------------------------
- // Save the version number
- // ---------------------------
- buf.PutInt(AINET_VERSION_NUMBER);
- buf.PutInt(gpGlobals->mapversion);
-
- // -------------------------------
- // Dump all the nodes to the file
- // -------------------------------
- buf.PutInt( m_pNetwork->m_iNumNodes);
-
- int node;
- int totalNumLinks = 0;
- for ( node = 0; node < m_pNetwork->m_iNumNodes; node++)
- {
- CAI_Node *pNode = m_pNetwork->GetNode(node);
- Assert( pNode->GetZone() != AI_NODE_ZONE_UNKNOWN );
-
- buf.PutFloat( pNode->GetOrigin().x );
- buf.PutFloat( pNode->GetOrigin().y );
- buf.PutFloat( pNode->GetOrigin().z );
- buf.PutFloat( pNode->GetYaw() );
- buf.Put( pNode->m_flVOffset, sizeof( pNode->m_flVOffset ) );
- buf.PutChar( pNode->GetType() );
- if ( IsX360() )
- {
- buf.SeekPut( CUtlBuffer::SEEK_CURRENT, 3 );
- }
- buf.PutUnsignedShort( pNode->m_eNodeInfo );
- buf.PutShort( pNode->GetZone() );
-
- for (int link = 0; link < pNode->NumLinks(); link++)
- {
- // Only dump if link source
- if (node == pNode->GetLinkByIndex(link)->m_iSrcID)
- {
- totalNumLinks++;
- }
- }
- }
-
- // -------------------------------
- // Dump all the links to the file
- // -------------------------------
- buf.PutInt( totalNumLinks );
-
- for (node = 0; node < m_pNetwork->m_iNumNodes; node++)
- {
- CAI_Node *pNode = m_pNetwork->GetNode(node);
-
- for (int link = 0; link < pNode->NumLinks(); link++)
- {
- // Only dump if link source
- CAI_Link *pLink = pNode->GetLinkByIndex(link);
- if (node == pLink->m_iSrcID)
- {
- buf.PutShort( pLink->m_iSrcID );
- buf.PutShort( pLink->m_iDestID );
- buf.Put( pLink->m_iAcceptedMoveTypes, sizeof( pLink->m_iAcceptedMoveTypes) );
- }
- }
- }
-
- // -------------------------------
- // Dump WC lookup table
- // -------------------------------
- CUtlMap<int, int> wcIDs;
- SetDefLessFunc(wcIDs);
- bool bCheckForProblems = false;
- for (node = 0; node < m_pNetwork->m_iNumNodes; node++)
- {
- int iPreviousNodeBinding = wcIDs.Find( GetEditOps()->m_pNodeIndexTable[node] );
- if ( iPreviousNodeBinding != wcIDs.InvalidIndex() )
- {
- if ( !bCheckForProblems )
- {
- DevWarning( "******* MAP CONTAINS DUPLICATE HAMMER NODE IDS! CHECK FOR PROBLEMS IN HAMMER TO CORRECT *******\n" );
- bCheckForProblems = true;
- }
- DevWarning( " AI node %d is associated with Hammer node %d, but %d is already bound to node %d\n", node, GetEditOps()->m_pNodeIndexTable[node], GetEditOps()->m_pNodeIndexTable[node], wcIDs[iPreviousNodeBinding] );
- }
- else
- {
- wcIDs.Insert( GetEditOps()->m_pNodeIndexTable[node], node );
- }
- buf.PutInt( GetEditOps()->m_pNodeIndexTable[node] );
- }
-
- // -------------------------------
- // Write the file out
- // -------------------------------
-
- FileHandle_t fh = filesystem->Open( szNrpFilename, "wb" );
- if ( !fh )
- {
- DevWarning( 2, "Couldn't create %s!\n", szNrpFilename );
- return;
- }
-
- filesystem->Write( buf.Base(), buf.TellPut(), fh );
- filesystem->Close(fh);
-}
-
-/* Keep this around for debugging
-//-----------------------------------------------------------------------------
-// Purpose: Only called if network has changed since last time level
-// was loaded
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CAI_NetworkManager::SaveNetworkGraph( void )
-{
- // -----------------------------
- // Make sure directories have been made
- // -----------------------------
- char szNrpFilename [MAX_PATH];// text node report filename
- Q_strncpy( szNrpFilename, "maps" ,sizeof(szNrpFilename));
- filesystem->CreateDirHierarchy( szNrpFilename );
- Q_strncat( szNrpFilename, "/graphs", COPY_ALL_CHARACTERS );
- filesystem->CreateDirHierarchy( szNrpFilename );
-
- Q_strncat( szNrpFilename, "/", COPY_ALL_CHARACTERS );
- Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), COPY_ALL_CHARACTERS );
- Q_strncat( szNrpFilename, ".ain", COPY_ALL_CHARACTERS );
-
- FileHandle_t file = filesystem->Open ( szNrpFilename, "w+" );
-
- // -----------------------------
- // Make sure the file opened ok
- // -----------------------------
- if ( !file )
- {
- DevWarning( 2, "Couldn't create %s!\n", szNrpFilename );
- return;
- }
-
- // ---------------------------
- // Save the version number
- // ---------------------------
- filesystem->FPrintf(file,"Version %4d\n",AINET_VERSION_NUMBER);
-
- // -------------------------------
- // Dump all the nodes to the file
- // -------------------------------
- filesystem->FPrintf ( file, "NumNodes: %d\n", m_iNumNodes);
- int totalNumLinks = 0;
- for (int node = 0; node < m_iNumNodes; node++)
- {
- filesystem->FPrintf ( file, "Location %4f,%4f,%4f\n",m_pAInode[node]->GetOrigin().x, m_pAInode[node]->GetOrigin().y, m_pAInode[node]->GetOrigin().z );
- for (int hull =0;hull<NUM_HULLS;hull++)
- {
- filesystem->FPrintf ( file, "Voffset %4f\n", m_pAInode[node]->m_flVOffset[hull]);
- }
- filesystem->FPrintf ( file, "HintType: %4d\n", m_pAInode[node]->m_eHintType );
- filesystem->FPrintf ( file, "HintYaw: %4f\n", m_pAInode[node]->GetYaw() );
- filesystem->FPrintf ( file, "NodeType %4d\n",m_pAInode[node]->GetType());
- filesystem->FPrintf ( file, "NodeInfo %4d\n",m_pAInode[node]->m_eNodeInfo);
- filesystem->FPrintf ( file, "Neighbors ");
- m_pAInode[node]->m_pNeighborBS->SaveBitString(file);
- filesystem->FPrintf ( file, "Visible ");
- m_pAInode[node]->m_pVisibleBS->SaveBitString(file);
- filesystem->FPrintf ( file, "Connected ");
- m_pAInode[node]->m_pConnectedBS->SaveBitString(file);
-
- filesystem->FPrintf ( file, "NumLinks %4d\n",m_pAInode[node]->NumLinks());
-
- for (int link = 0; link < m_pAInode[node]->NumLinks(); link++)
- {
- // Only dump if link source
- if (node == m_pAInode[node]->GetLinkByIndex(link)->m_iSrcID)
- {
- totalNumLinks++;
- }
- }
- }
-
- // -------------------------------
- // Dump all the links to the file
- // -------------------------------
- filesystem->FPrintf ( file, "TotalNumLinks %4d\n",totalNumLinks);
-
- for (node = 0; node < m_iNumNodes; node++)
- {
- for (int link = 0; link < m_pAInode[node]->NumLinks(); link++)
- {
- // Only dump if link source
- if (node == m_pAInode[node]->GetLinkByIndex(link)->m_iSrcID)
- {
- filesystem->FPrintf ( file, "LinkSrcID %4d\n", m_pAInode[node]->GetLinkByIndex(link)->m_iSrcID);
- filesystem->FPrintf ( file, "LinkDestID %4d\n", m_pAInode[node]->GetLinkByIndex(link)->m_iDestID);
-
- for (int hull =0;hull<NUM_HULLS;hull++)
- {
- filesystem->FPrintf ( file, "Hulls %4d\n", m_pAInode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[hull]);
- }
- }
- }
- }
-
- // -------------------------------
- // Dump WC lookup table
- // -------------------------------
- for (node = 0; node < m_iNumNodes; node++)
- {
- filesystem->FPrintf( file, "%4d\n",m_pNodeIndexTable[node]);
- }
-
- filesystem->Close(file);
-}
-*/
-
-//-----------------------------------------------------------------------------
-// Purpose: Only called if network has changed since last time level
-// was loaded
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkManager::LoadNetworkGraph( void )
-{
- // ---------------------------------------------------
- // If I'm in edit mode don't load, always recalculate
- // ---------------------------------------------------
- DevMsg( "Loading AI graph\n" );
- if (engine->IsInEditMode())
- {
- DevMsg( "Not loading AI due to edit mode\n" );
- return;
- }
-
- if ( !g_pGameRules->FAllowNPCs() )
- {
- DevMsg( "Not loading AI due to games rules\n" );
- return;
- }
-
- DevMsg( "Step 1 loading\n" );
-
- // -----------------------------
- // Make sure directories have been made
- // -----------------------------
- char szNrpFilename[MAX_PATH];// text node report filename
- Q_strncpy( szNrpFilename, "maps" ,sizeof(szNrpFilename));
- filesystem->CreateDirHierarchy( szNrpFilename, "DEFAULT_WRITE_PATH" );
- Q_strncat( szNrpFilename, "/graphs", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
- filesystem->CreateDirHierarchy( szNrpFilename, "DEFAULT_WRITE_PATH" );
-
- Q_strncat( szNrpFilename, "/", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
- Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
- Q_strncat( szNrpFilename, IsX360() ? ".360.ain" : ".ain", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
-
- MEM_ALLOC_CREDIT();
-
- // Read the file in one gulp
- CUtlBuffer buf;
- bool bHaveAIN = false;
- if ( IsX360() && g_pQueuedLoader->IsMapLoading() )
- {
- // .ain was loaded anonymously by bsp, should be ready
- void *pData;
- int nDataSize;
- if ( g_pQueuedLoader->ClaimAnonymousJob( szNrpFilename, &pData, &nDataSize ) )
- {
- if ( nDataSize != 0 )
- {
- buf.Put( pData, nDataSize );
- bHaveAIN = true;
- }
- filesystem->FreeOptimalReadBuffer( pData );
- }
- }
-
-
-
- if ( !bHaveAIN && !filesystem->ReadFile( szNrpFilename, "game", buf ) )
- {
- DevWarning( 2, "Couldn't read %s!\n", szNrpFilename );
- return;
- }
-
- DevMsg( "Checking version\n" );
-
- // ---------------------------
- // Check the version number
- // ---------------------------
- if ( buf.GetChar() == 'V' && buf.GetChar() == 'e' && buf.GetChar() == 'r' )
- {
- DevMsg( "AI node graph %s is out of date\n", szNrpFilename );
- return;
- }
-
- DevMsg( "Passed first ver check\n" );
-
-
- buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
-
- int version = buf.GetInt();
- DevMsg( "Got version %d\n", version );
-
- if ( version != AINET_VERSION_NUMBER)
- {
- DevMsg( "AI node graph %s is out of date\n", szNrpFilename );
- return;
- }
-
- int mapversion = buf.GetInt();
- DevMsg( "Map version %d\n", mapversion );
-
- if ( mapversion != gpGlobals->mapversion && !g_ai_norebuildgraph.GetBool() )
- {
- bool bOK = false;
-
- const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" );
- char szLoweredGameDir[256];
- Q_strncpy( szLoweredGameDir, pGameDir, sizeof( szLoweredGameDir ) );
- Q_strlower( szLoweredGameDir );
-
- // hack for shipped ep1 and hl2 maps
- // they were rebuilt a week after they were actually shipped so allow the slightly
- // older node graphs to load for these maps
- if ( !V_stricmp( szLoweredGameDir, "hl2" ) || !V_stricmp( szLoweredGameDir, "episodic" ) )
- {
- bOK = true;
- }
-
- if ( !bOK )
- {
- DevMsg( "AI node graph %s is out of date (map version changed)\n", szNrpFilename );
- return;
- }
- }
-
- DevMsg( "Done version checks\n" );
-
- // ----------------------------------------
- // Get the network size and allocate space
- // ----------------------------------------
- int numNodes = buf.GetInt();
-
- if ( numNodes > MAX_NODES || numNodes < 0 )
- {
- Error( "AI node graph %s is corrupt\n", szNrpFilename );
- DevMsg( "%s", (const char *)buf.Base() );
- DevMsg( "\n" );
- Assert( 0 );
- return;
- }
-
- DevMsg( "Finishing load\n" );
-
-
- // ------------------------------------------------------------------------
- // If in wc_edit mode allocate extra space for nodes that might be created
- // ------------------------------------------------------------------------
- if ( engine->IsInEditMode() )
- {
- numNodes = MAX( numNodes, 1024 );
- }
-
- m_pNetwork->m_pAInode = new CAI_Node*[MAX( numNodes, 1 )];
- memset( m_pNetwork->m_pAInode, 0, sizeof( CAI_Node* ) * MAX( numNodes, 1 ) );
-
- // -------------------------------
- // Load all the nodes to the file
- // -------------------------------
- int node;
- for ( node = 0; node < numNodes; node++)
- {
- Vector origin;
- float yaw;
- origin.x = buf.GetFloat();
- origin.y = buf.GetFloat();
- origin.z = buf.GetFloat();
- yaw = buf.GetFloat();
-
- CAI_Node *new_node = m_pNetwork->AddNode( origin, yaw );
-
- buf.Get( new_node->m_flVOffset, sizeof(new_node->m_flVOffset) );
- new_node->m_eNodeType = (NodeType_e)buf.GetChar();
- if ( IsX360() )
- {
- buf.SeekGet( CUtlBuffer::SEEK_CURRENT, 3 );
- }
-
- new_node->m_eNodeInfo = buf.GetUnsignedShort();
- new_node->m_zone = buf.GetShort();
- }
-
- // -------------------------------
- // Load all the links to the fild
- // -------------------------------
- int totalNumLinks = buf.GetInt();
-
- for (int link = 0; link < totalNumLinks; link++)
- {
- int srcID, destID;
-
- srcID = buf.GetShort();
- destID = buf.GetShort();
-
- CAI_Link *pLink = m_pNetwork->CreateLink( srcID, destID );;
-
- byte ignored[NUM_HULLS];
- byte *pDest = ( pLink ) ? &pLink->m_iAcceptedMoveTypes[0] : &ignored[0];
- buf.Get( pDest, sizeof(ignored) );
- }
-
- // -------------------------------
- // Load WC lookup table
- // -------------------------------
- delete [] GetEditOps()->m_pNodeIndexTable;
- GetEditOps()->m_pNodeIndexTable = new int[MAX( m_pNetwork->m_iNumNodes, 1 )];
- memset( GetEditOps()->m_pNodeIndexTable, 0, sizeof( int ) *MAX( m_pNetwork->m_iNumNodes, 1 ) );
-
- for (node = 0; node < m_pNetwork->m_iNumNodes; node++)
- {
- GetEditOps()->m_pNodeIndexTable[node] = buf.GetInt();
- }
-
-
-#if 1
- CUtlRBTree<int> usedIds;
- CUtlRBTree<int> reportedIds;
- SetDefLessFunc( usedIds );
- SetDefLessFunc( reportedIds );
-
- bool printedHeader = false;
-
- for (node = 0; node < m_pNetwork->m_iNumNodes; node++)
- {
- int editorId = GetEditOps()->m_pNodeIndexTable[node];
- if ( editorId != NO_NODE )
- {
- if ( usedIds.Find( editorId ) != usedIds.InvalidIndex() )
- {
- if ( !printedHeader )
- {
- Warning( "** Duplicate Hammer Node IDs: " );
- printedHeader = true;
- }
-
- if ( reportedIds.Find( editorId ) == reportedIds.InvalidIndex() )
- {
- DevMsg( "%d, ", editorId );
- reportedIds.Insert( editorId );
- }
- }
- else
- usedIds.Insert( editorId );
- }
- }
-
- if ( printedHeader )
- DevMsg( "\n** Should run \"Check For Problems\" on the VMF then verify dynamic links\n" );
-#endif
-
- gm_fNetworksLoaded = true;
- CAI_DynamicLink::gm_bInitialized = false;
-}
-
-/* Keep this around for debugging
-//-----------------------------------------------------------------------------
-// Purpose: Only called if network has changed since last time level
-// was loaded
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CAI_NetworkManager::LoadNetworkGraph( void )
-{
- // ---------------------------------------------------
- // If I'm in edit mode don't load, always recalculate
- // ---------------------------------------------------
- if (engine->IsInEditMode())
- {
- return;
- }
-
- // -----------------------------
- // Make sure directories have been made
- // -----------------------------
- char szNrpFilename [MAX_PATH];// text node report filename
- Q_strncpy( szNrpFilename, "maps" ,sizeof(szNrpFilename));
- filesystem->CreateDirHierarchy( szNrpFilename );
- Q_strncat( szNrpFilename, "/graphs", COPY_ALL_CHARACTERS );
- filesystem->CreateDirHierarchy( szNrpFilename );
-
- Q_strncat( szNrpFilename, "/", COPY_ALL_CHARACTERS );
- Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), COPY_ALL_CHARACTERS );
- Q_strncat( szNrpFilename, ".ain", COPY_ALL_CHARACTERS );
-
- FileHandle_t file = filesystem->Open ( szNrpFilename, "r" );
-
- // -----------------------------
- // Make sure the file opened ok
- // -----------------------------
- if ( !file )
- {
- DevWarning( 2, "Couldn't create %s!\n", szNrpFilename );
- return;
- }
-
- // ---------------------------
- // Check the version number
- // ---------------------------
- char temps[256];
- int version;
- fscanf(file,"%255s",&temps);
- fscanf(file, "%i\n",&version);
- if (version!=AINET_VERSION_NUMBER)
- {
- return;
- }
-
- // ----------------------------------------
- // Get the network size and allocate space
- // ----------------------------------------
- int numNodes;
- fscanf(file,"%255s",&temps);
- fscanf ( file, "%d\n", &numNodes);
-
- // ------------------------------------------------------------------------
- // If in wc_edit mode allocate extra space for nodes that might be created
- // ------------------------------------------------------------------------
- if ( engine->IsInEditMode() )
- {
- numNodes = MAX( numNodes, 1024 );
- }
-
- m_pAInode = new CAI_Node*[numNodes];
- if ( !m_pAInode )
- {
- Warning( "LoadNetworkGraph: Not enough memory to create %i nodes\n", numNodes );
- Assert(0);
- return;
- }
-
- // -------------------------------
- // Load all the nodes to the file
- // -------------------------------
- for (int node = 0; node < numNodes; node++)
- {
- CAI_Node *new_node = AddNode();
-
- Vector origin;
- fscanf(file,"%255s",&temps);
- fscanf(file, "%f,%f,%f\n", &new_node->GetOrigin().x, &new_node->GetOrigin().y, &new_node->GetOrigin().z );
- for (int hull =0;hull<NUM_HULLS;hull++)
- {
- fscanf(file,"%255s",&temps);
- fscanf(file, "%f\n", &new_node->m_flVOffset[hull]);
- }
- fscanf(file,"%255s",&temps);
- fscanf(file, "%d\n", &new_node->m_eHintType );
- fscanf(file,"%255s",&temps);
- fscanf(file, "%f\n", &new_node->GetYaw() );
- fscanf(file,"%255s",&temps);
- fscanf(file, "%d\n",&new_node->GetType());
- fscanf(file,"%255s",&temps);
- fscanf(file, "%d\n",&new_node->m_eNodeInfo);
-
- fscanf(file,"%255s",&temps);
- new_node->m_pNeighborBS = new CVarBitVec(numNodes);
- new_node->m_pNeighborBS->LoadBitString(file);
-
- fscanf(file,"%255s",&temps);
- new_node->m_pVisibleBS = new CVarBitVec(numNodes);
- new_node->m_pVisibleBS->LoadBitString(file);
-
- fscanf(file,"%255s",&temps);
- new_node->m_pConnectedBS = new CVarBitVec(numNodes);
- new_node->m_pConnectedBS->LoadBitString(file);
-
- fscanf(file,"%255s",&temps);
- int numLinks;
- fscanf (file, "%4d",&numLinks);
-
- // ------------------------------------------------------------------------
- // If in wc_edit mode allocate extra space for nodes that might be created
- // ------------------------------------------------------------------------
- if ( engine->IsInEditMode() )
- {
- numLinks = AI_MAX_NODE_LINKS;
- }
-
- //Assert ( numLinks >= 1 );
- new_node->AllocateLinkSpace( numLinks );
- }
-
- // -------------------------------
- // Load all the links to the fild
- // -------------------------------
- int totalNumLinks;
- fscanf(file,"%255s",&temps);
- fscanf ( file, "%d\n",&totalNumLinks);
-
- for (int link = 0; link < totalNumLinks; link++)
- {
- CAI_Link *new_link = new CAI_Link;
-
- fscanf(file,"%255s",&temps);
- fscanf ( file, "%4d\n", &new_link->m_iSrcID);
-
- fscanf(file,"%255s",&temps);
- fscanf ( file, "%4d\n", &new_link->m_iDestID);
-
- for (int hull =0;hull<NUM_HULLS;hull++)
- {
- fscanf(file,"%255s",&temps);
- fscanf ( file, "%d\n", &new_link->m_iAcceptedMoveTypes[hull]);
- }
- // Now add link to source and destination nodes
- m_pAInode[new_link->m_iSrcID]->AddLink(new_link);
- m_pAInode[new_link->m_iDestID]->AddLink(new_link);
- }
-
- // -------------------------------
- // Load WC lookup table
- // -------------------------------
- m_pNodeIndexTable = new int[m_iNumNodes];
-
- for (node = 0; node < m_iNumNodes; node++)
- {
- fscanf( file, "%d\n",&m_pNodeIndexTable[node]);
- }
-
- CAI_NetworkManager::NetworksLoaded() = true;
- fclose(file);
-}
-*/
-
-//-----------------------------------------------------------------------------
-// Purpose: Deletes all AINetworks from memory
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkManager::DeleteAllAINetworks(void)
-{
- CAI_DynamicLink::gm_bInitialized = false;
- gm_fNetworksLoaded = false;
- g_pBigAINet = NULL;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Only called if network has changed since last time level
-// was loaded
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkManager::BuildNetworkGraph( void )
-{
- if ( m_bDontSaveGraph )
- return;
-
- CAI_DynamicLink::gm_bInitialized = false;
- g_AINetworkBuilder.Build( m_pNetwork );
-
- // If I'm loading for the first time save. Otherwise I'm
- // doing a wc edit and I don't want to save
- if (!CAI_NetworkManager::NetworksLoaded())
- {
- SaveNetworkGraph();
-
- gm_fNetworksLoaded = true;
- }
-}
-
-//------------------------------------------------------------------------------
-bool g_bAIDisabledByUser = false;
-
-void CAI_NetworkManager::InitializeAINetworks()
-{
- // For not just create a single AI Network called "BigNet"
- // At some later point we may have mulitple AI networks
- CAI_NetworkManager *pNetwork;
- g_pAINetworkManager = pNetwork = CREATE_ENTITY( CAI_NetworkManager, "ai_network" );
- pNetwork->AddEFlags( EFL_KEEP_ON_RECREATE_ENTITIES );
- g_pBigAINet = pNetwork->GetNetwork();
- pNetwork->SetName( AllocPooledString("BigNet") );
- pNetwork->Spawn();
- if ( engine->IsInEditMode() )
- {
- g_ai_norebuildgraph.SetValue( 0 );
- }
- if ( CAI_NetworkManager::IsAIFileCurrent( STRING( gpGlobals->mapname ) ) )
- {
- pNetwork->LoadNetworkGraph();
- if ( !g_bAIDisabledByUser )
- {
- CAI_BaseNPC::m_nDebugBits &= ~bits_debugDisableAI;
- }
- }
-
- // Reset node counter used during load
- CNodeEnt::m_nNodeCount = 0;
-
- pNetwork->SetThink( &CAI_NetworkManager::DelayedInit );
- pNetwork->SetNextThink( gpGlobals->curtime );
-}
-
-// UNDONE: Where should this be defined?
-#ifndef MAX_PATH
-#define MAX_PATH 256
-#endif
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns true if the AINetwork data files are up to date
-//-----------------------------------------------------------------------------
-
-bool CAI_NetworkManager::IsAIFileCurrent ( const char *szMapName )
-{
- char szBspFilename[MAX_PATH];
- char szGraphFilename[MAX_PATH];
-
- if ( !g_pGameRules->FAllowNPCs() )
- {
- return false;
- }
-
- if ( IsX360() && ( filesystem->GetDVDMode() == DVDMODE_STRICT ) )
- {
- // dvd build process validates and guarantees correctness, timestamps are allowed to be wrong
- return true;
- }
-
- {
- const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" );
- char szLoweredGameDir[256];
- Q_strncpy( szLoweredGameDir, pGameDir, sizeof( szLoweredGameDir ) );
- Q_strlower( szLoweredGameDir );
-
- if ( !V_stricmp( szLoweredGameDir, "hl2" ) || !V_stricmp( szLoweredGameDir, "episodic" ) || !V_stricmp( szLoweredGameDir, "ep2" ) || !V_stricmp( szLoweredGameDir, "portal" ) || !V_stricmp( szLoweredGameDir, "lostcoast" ) )
- {
- // we shipped good node graphs for our games
- return true;
- }
- }
-
- Q_snprintf( szBspFilename, sizeof( szBspFilename ), "maps/%s%s.bsp" ,szMapName, GetPlatformExt() );
- Q_snprintf( szGraphFilename, sizeof( szGraphFilename ), "maps/graphs/%s%s.ain", szMapName, GetPlatformExt() );
-
- int iCompare;
- if ( engine->CompareFileTime( szBspFilename, szGraphFilename, &iCompare ) )
- {
- if ( iCompare > 0 )
- {
- // BSP file is newer.
- if ( g_ai_norebuildgraph.GetInt() )
- {
- // The user has specified that they wish to override the
- // rebuilding of outdated nodegraphs (see top of this file)
- if ( filesystem->FileExists( szGraphFilename ) )
- {
- // Display these messages only if the graph exists, and the
- // user is asking to override the rebuilding. If the graph does
- // not exist, we're going to build it whether the user wants to or
- // not.
- DevMsg( 2, ".AIN File will *NOT* be updated. User Override.\n\n" );
- DevMsg( "\n*****Node Graph Rebuild OVERRIDDEN by user*****\n\n" );
- }
- return true;
- }
- else
- {
- // Graph is out of date. Rebuild at usual.
- DevMsg( 2, ".AIN File will be updated\n\n" );
- return false;
- }
- }
- return true;
- }
- return false;
-}
-
-//------------------------------------------------------------------------------
-
-void CAI_NetworkManager::Spawn ( void )
-{
- SetSolid( SOLID_NONE );
- SetMoveType( MOVETYPE_NONE );
-}
-
-//------------------------------------------------------------------------------
-
-void CAI_NetworkManager::DelayedInit( void )
-{
- if ( !g_pGameRules->FAllowNPCs() )
- {
- SetThink ( NULL );
- return;
- }
-
- if ( !g_ai_norebuildgraph.GetInt() )
- {
- // ----------------------------------------------------------
- // Actually enter DelayedInit twice when rebuilding the
- // node graph. The first time through we just print the
- // warning message. We only actually do the rebuild on
- // the second pass to make sure the message hits the screen
- // ----------------------------------------------------------
- if (m_bNeedGraphRebuild)
- {
- Assert( !m_bDontSaveGraph );
-
- BuildNetworkGraph(); // For now only one AI Network
-
- if (engine->IsInEditMode())
- {
- engine->ServerCommand("exec map_edit.cfg\n");
- }
-
- SetThink ( NULL );
- if ( !g_bAIDisabledByUser )
- {
- CAI_BaseNPC::m_nDebugBits &= ~bits_debugDisableAI;
- }
- }
-
-
- // --------------------------------------------
- // If I haven't loaded a network, or I'm in
- // WorldCraft edit mode rebuild the network
- // --------------------------------------------
- else if ( !m_bDontSaveGraph && ( !CAI_NetworkManager::NetworksLoaded() || engine->IsInEditMode() ) )
- {
-#ifdef _WIN32
- // --------------------------------------------------------
- // If in edit mode start WC session and make sure we are
- // running the same map in WC and the engine
- // --------------------------------------------------------
- if (engine->IsInEditMode())
- {
- int status = Editor_BeginSession(STRING(gpGlobals->mapname), gpGlobals->mapversion, false);
- if (status == Editor_NotRunning)
- {
- DevMsg("\nAborting map_edit\nWorldcraft not running...\n\n");
- UTIL_CenterPrintAll( "Worldcraft not running...\n" );
- engine->ServerCommand("disconnect\n");
- SetThink(NULL);
- return;
- }
- else if (status == Editor_BadCommand)
- {
- DevMsg("\nAborting map_edit\nWC/Engine map versions different...\n\n");
- UTIL_CenterPrintAll( "WC/Engine map versions different...\n" );
- engine->ServerCommand("disconnect\n");
- SetThink(NULL);
- return;
- }
- else
- {
- // Increment version number when session begins
- gpGlobals->mapversion++;
- }
- }
-#endif
-
- DevMsg( "Node Graph out of Date. Rebuilding... (%d, %d, %d)\n", (int)m_bDontSaveGraph, (int)!CAI_NetworkManager::NetworksLoaded(), (int) engine->IsInEditMode() );
- UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding...\n" );
- m_bNeedGraphRebuild = true;
- g_pAINetworkManager->SetNextThink( gpGlobals->curtime + 1 );
- return;
- }
-
- }
-
- // --------------------------------------------
- // Initialize any dynamic links
- // --------------------------------------------
- CAI_DynamicLink::InitDynamicLinks();
- FixupHints();
-
- GetEditOps()->OnInit();
-
- m_fInitalized = true;
-
- if ( g_AI_Manager.NumAIs() != 0 && g_pBigAINet->NumNodes() == 0 )
- DevMsg( "WARNING: Level contains NPCs but has no path nodes\n" );
-}
-
-//------------------------------------------------------------------------------
-
-void CAI_NetworkManager::FixupHints()
-{
- AIHintIter_t iter;
- CAI_Hint *pHint = CAI_HintManager::GetFirstHint( &iter );
- while ( pHint )
- {
- pHint->FixupTargetNode();
- pHint = CAI_HintManager::GetNextHint( &iter );
- }
-}
-
-//-----------------------------------------------------------------------------
-// CAI_NetworkEditTools
-//-----------------------------------------------------------------------------
-
-CAI_Node* CAI_NetworkEditTools::m_pLastDeletedNode = NULL; // For undo in wc edit mode
-int CAI_NetworkEditTools::m_iHullDrawNum = HULL_HUMAN; // Which hulls to draw
-int CAI_NetworkEditTools::m_iVisibilityNode = NO_NODE;
-int CAI_NetworkEditTools::m_iGConnectivityNode = NO_NODE;
-bool CAI_NetworkEditTools::m_bAirEditMode = false;
-bool CAI_NetworkEditTools::m_bLinkEditMode = false;
-float CAI_NetworkEditTools::m_flAirEditDistance = 300;
-
-#ifdef AI_PERF_MON
- // Performance stats (only for development)
- int CAI_NetworkEditTools::m_nPerfStatNN = 0;
- int CAI_NetworkEditTools::m_nPerfStatPB = 0;
- float CAI_NetworkEditTools::m_fNextPerfStatTime = -1;
-#endif
-
-//------------------------------------------------------------------------------
-
-void CAI_NetworkEditTools::OnInit()
-{
- // --------------------------------------------
- // If I'm not in edit mode delete WC ID table
- // --------------------------------------------
- if ( !engine->IsInEditMode() )
- {
-// delete[] m_pNodeIndexTable; // For now only one AI Network called "BigNet"
-// m_pNodeIndexTable = NULL;
- }
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Given a WorldCraft node ID, return the associated engine ID
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-int CAI_NetworkEditTools::GetNodeIdFromWCId( int nWCId )
-{
- if ( nWCId == -1 )
- return -1;
-
- if (!m_pNodeIndexTable)
- {
- DevMsg("ERROR: Trying to get WC ID with no table!\n");
- return -1;
- }
-
- if (!m_pNetwork->NumNodes())
- {
- DevMsg("ERROR: Trying to get WC ID with no network!\n");
- return -1;
- }
-
- for (int i=0;i<m_pNetwork->NumNodes();i++)
- {
- if (m_pNodeIndexTable[i] == nWCId)
- {
- return i;
- }
- }
- return -1;
-}
-
-//-----------------------------------------------------------------------------
-
-int CAI_NetworkEditTools::GetWCIdFromNodeId( int nNodeId )
-{
- if ( nNodeId == -1 || nNodeId >= m_pNetwork->NumNodes() )
- return -1;
- return m_pNodeIndexTable[nNodeId];
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Find the nearest ainode that is faced from the given location
-// and within the angular threshold (ignores worldspawn).
-// DO NOT USE FOR ANY RUN TIME RELEASE CODE
-// Used for selection of nodes in debugging display only!
-//
-//-----------------------------------------------------------------------------
-
-CAI_Node *CAI_NetworkEditTools::FindAINodeNearestFacing( const Vector &origin, const Vector &facing, float threshold, int nNodeType)
-{
- float bestDot = threshold;
- CAI_Node *best = NULL;
-
- CAI_Network* aiNet = g_pBigAINet;
-
- for (int node =0; node < aiNet->NumNodes();node++)
- {
- if (aiNet->GetNode(node)->GetType() != NODE_DELETED)
- {
- // Pick nodes that are in the current editing type
- if ( nNodeType == NODE_ANY ||
- nNodeType == aiNet->GetNode(node)->GetType() )
- {
- // Make vector to node
- Vector to_node = (aiNet->GetNode(node)->GetPosition(m_iHullDrawNum) - origin);
-
- VectorNormalize( to_node );
- float dot = DotProduct (facing , to_node );
- if (dot > bestDot)
- {
- // Make sure I have a line of sight to it
- trace_t tr;
- AI_TraceLine ( origin, aiNet->GetNode(node)->GetPosition(m_iHullDrawNum),
- MASK_BLOCKLOS, NULL, COLLISION_GROUP_NONE, &tr );
- if ( tr.fraction == 1.0 )
- {
- bestDot = dot;
- best = aiNet->GetNode(node);
- }
- }
- }
- }
- }
- return best;
-}
-
-
-Vector PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint)
-{
- Vector vEndToStart = (vEndPos - vStartPos);
- Vector vOrgToStart = (vPoint - vStartPos);
- float fNumerator = DotProduct(vEndToStart,vOrgToStart);
- float fDenominator = vEndToStart.Length() * vOrgToStart.Length();
- float fIntersectDist = vOrgToStart.Length()*(fNumerator/fDenominator);
- VectorNormalize( vEndToStart );
- Vector vIntersectPos = vStartPos + vEndToStart * fIntersectDist;
-
- return vIntersectPos;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Find the nearest ainode that is faced from the given location
-// and within the angular threshold (ignores worldspawn).
-// DO NOT USE FOR ANY RUN TIME RELEASE CODE
-// Used for selection of nodes in debugging display only!
-//-----------------------------------------------------------------------------
-
-CAI_Link *CAI_NetworkEditTools::FindAILinkNearestFacing( const Vector &vOrigin, const Vector &vFacing, float threshold)
-{
- float bestDot = threshold;
- CAI_Link *best = NULL;
-
- CAI_Network* aiNet = g_pBigAINet;
-
- for (int node =0; node < aiNet->NumNodes();node++)
- {
- if (aiNet->GetNode(node)->GetType() != NODE_DELETED)
- {
- // Pick nodes that are in the current editing type
- if (( m_bAirEditMode && aiNet->GetNode(node)->GetType() == NODE_AIR) ||
- (!m_bAirEditMode && aiNet->GetNode(node)->GetType() == NODE_GROUND))
- {
- // Go through each link
- for (int link=0; link < aiNet->GetNode(node)->NumLinks();link++)
- {
- CAI_Link *nodeLink = aiNet->GetNode(node)->GetLinkByIndex(link);
-
- // Find position on link that I am looking
- int endID = nodeLink->DestNodeID(node);
- Vector startPos = aiNet->GetNode(node)->GetPosition(m_iHullDrawNum);
- Vector endPos = aiNet->GetNode(endID)->GetPosition(m_iHullDrawNum);
- Vector vNearest = PointOnLineNearestPoint(startPos, endPos, vOrigin);
-
- // Get angle between viewing dir. and nearest point on line
- Vector vOriginToNearest = (vNearest - vOrigin);
- float fNumerator = DotProduct(vOriginToNearest,vFacing);
- float fDenominator = vOriginToNearest.Length();
- float fAngleToNearest = acos(fNumerator/fDenominator);
-
- // If not facing the line reject
- if (fAngleToNearest > 1.57)
- {
- continue;
- }
-
- // Calculate intersection of facing direction to nearest point
- float fIntersectDist = vOriginToNearest.Length() * tan(fAngleToNearest);
- Vector dir = endPos-startPos;
- float fLineLen = VectorNormalize( dir );
- Vector vIntersection = vNearest + (fIntersectDist * dir);
-
- // Reject of beyond end of line
- if (((vIntersection - startPos).Length() > fLineLen) ||
- ((vIntersection - endPos ).Length() > fLineLen) )
- {
- continue;
- }
-
- // Now test dot to the look position
- Vector toLink = vIntersection - vOrigin;
- VectorNormalize(toLink);
- float lookDot = DotProduct (vFacing , toLink);
- if (lookDot > bestDot)
- {
- // Make sure I have a line of sight to it
- trace_t tr;
- AI_TraceLine ( vOrigin, vIntersection, MASK_BLOCKLOS, NULL, COLLISION_GROUP_NONE, &tr );
- if ( tr.fraction == 1.0 )
- {
- bestDot = lookDot;
- best = nodeLink;
- }
- }
- }
- }
- }
- }
- return best;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Used for WC edit more. Marks that the network should be
-// rebuild and turns of any displays that have been invalidated
-// as the network is now out of date
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CAI_NetworkEditTools::SetRebuildFlags( void )
-{
- m_debugNetOverlays |= bits_debugNeedRebuild;
- m_debugNetOverlays &= ~bits_debugOverlayConnections;
- m_debugNetOverlays &= ~bits_debugOverlayGraphConnect;
- m_debugNetOverlays &= ~bits_debugOverlayVisibility;
- m_debugNetOverlays &= ~bits_debugOverlayHulls;
-
- // Not allowed to edit links when graph outdated
- m_bLinkEditMode = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Used for WC edit more. After node graph has been rebuild
-// marks it as so and turns connection display back on
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CAI_NetworkEditTools::ClearRebuildFlags( void )
-{
- m_debugNetOverlays |= bits_debugOverlayConnections;
-
- // ------------------------------------------
- // Clear all rebuild flags in nodes
- // ------------------------------------------
- for (int i = 0; i < m_pNetwork->NumNodes(); i++)
- {
- m_pNetwork->GetNode(i)->m_eNodeInfo &= ~bits_NODE_WC_CHANGED;
- m_pNetwork->GetNode(i)->ClearNeedsRebuild();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sets the next hull to draw, or none if at end of hulls
-//-----------------------------------------------------------------------------
-void CAI_NetworkEditTools::DrawNextHull(const char *ainet_name)
-{
- m_iHullDrawNum++;
- if (m_iHullDrawNum == NUM_HULLS)
- {
- m_iHullDrawNum = 0;
- }
-
- // Recalculate usable nodes for current hull
- g_pAINetworkManager->GetEditOps()->RecalcUsableNodesForHull();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_NetworkEditTools::DrawHull(Hull_t eHull)
-{
- m_iHullDrawNum = eHull;
- if (m_iHullDrawNum >= NUM_HULLS)
- {
- m_iHullDrawNum = 0;
- }
-
- // Recalculate usable nodes for current hull
- g_pAINetworkManager->GetEditOps()->RecalcUsableNodesForHull();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Used just for debug display, to color nodes grey that the
-// currently selected hull size can't use.
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkEditTools::RecalcUsableNodesForHull(void)
-{
- // -----------------------------------------------------
- // Use test hull to check hull sizes
- // -----------------------------------------------------
- CAI_TestHull *m_pTestHull = CAI_TestHull::GetTestHull();
- m_pTestHull->GetNavigator()->SetNetwork( g_pBigAINet );
- m_pTestHull->SetHullType((Hull_t)m_iHullDrawNum);
- m_pTestHull->SetHullSizeNormal();
-
- for (int node=0;node<m_pNetwork->NumNodes();node++)
- {
- if ( ( m_pNetwork->GetNode(node)->m_eNodeInfo & ( HullToBit( (Hull_t)m_iHullDrawNum ) << NODE_ENT_FLAGS_SHIFT ) ) ||
- m_pTestHull->GetNavigator()->CanFitAtNode(node))
- {
- m_pNetwork->GetNode(node)->m_eNodeInfo &= ~bits_NODE_WONT_FIT_HULL;
- }
- else
- {
- m_pNetwork->GetNode(node)->m_eNodeInfo |= bits_NODE_WONT_FIT_HULL;
- }
- }
- CAI_TestHull::ReturnTestHull();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sets debug bits
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkEditTools::SetDebugBits(const char *ainet_name,int debug_bit)
-{
- CAI_NetworkEditTools *pEditOps = g_pAINetworkManager->GetEditOps();
- if ( !pEditOps )
- return;
-
- if (debug_bit & bits_debugOverlayNodes)
- {
- if (pEditOps->m_debugNetOverlays & bits_debugOverlayNodesLev2)
- {
- pEditOps->m_debugNetOverlays &= ~bits_debugOverlayNodes;
- pEditOps->m_debugNetOverlays &= ~bits_debugOverlayNodesLev2;
- }
- else if (pEditOps->m_debugNetOverlays & bits_debugOverlayNodes)
- {
- pEditOps->m_debugNetOverlays |= bits_debugOverlayNodesLev2;
- }
- else
- {
- pEditOps->m_debugNetOverlays |= bits_debugOverlayNodes;
-
- // Recalculate usable nodes for current hull
- pEditOps->RecalcUsableNodesForHull();
- }
- }
- else if (pEditOps->m_debugNetOverlays & debug_bit)
- {
- pEditOps->m_debugNetOverlays &= ~debug_bit;
- }
- else
- {
- pEditOps->m_debugNetOverlays |= debug_bit;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Draws edit display info on screen
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkEditTools::DrawEditInfoOverlay(void)
-{
- hudtextparms_s tTextParam;
- tTextParam.x = 0.8;
- tTextParam.y = 0.8;
- tTextParam.effect = 0;
- tTextParam.r1 = 255;
- tTextParam.g1 = 255;
- tTextParam.b1 = 255;
- tTextParam.a1 = 255;
- tTextParam.r2 = 255;
- tTextParam.g2 = 255;
- tTextParam.b2 = 255;
- tTextParam.a2 = 255;
- tTextParam.fadeinTime = 0;
- tTextParam.fadeoutTime = 0;
- tTextParam.holdTime = 1;
- tTextParam.fxTime = 0;
- tTextParam.channel = 0;
-
- char hullTypeTxt[50];
- char nodeTypeTxt[50];
- char editTypeTxt[50];
- char outTxt[255];
-
- Q_snprintf(hullTypeTxt,sizeof(hullTypeTxt)," %s",NAI_Hull::Name(m_iHullDrawNum));
- Q_snprintf(outTxt,sizeof(outTxt),"Displaying:\n%s\n\n", hullTypeTxt);
-
- if (engine->IsInEditMode())
- {
- char outTxt2[255];
- Q_snprintf(nodeTypeTxt,sizeof(nodeTypeTxt)," %s (l)", m_bLinkEditMode ? "Links":"Nodes");
- Q_snprintf(editTypeTxt,sizeof(editTypeTxt)," %s (m)", m_bAirEditMode ? "Air":"Ground");
- Q_snprintf(outTxt2,sizeof(outTxt2),"Editing:\n%s\n%s", editTypeTxt,nodeTypeTxt);
- Q_strncat(outTxt,outTxt2,sizeof(outTxt), COPY_ALL_CHARACTERS);
-
- // Print in red if network needs rebuilding
- if (m_debugNetOverlays & bits_debugNeedRebuild)
- {
- tTextParam.g1 = 0;
- tTextParam.b1 = 0;
- }
- }
-
- UTIL_HudMessageAll( tTextParam, outTxt );
-
-
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Draws AINetwork on the screen
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkEditTools::DrawAINetworkOverlay(void)
-{
- // ------------------------------------
- // If network isn't loaded yet return
- // ------------------------------------
- if (!CAI_NetworkManager::NetworksLoaded())
- {
- return;
- }
-
- // ----------------------------------------------
- // So we don't fill up the client message queue
- // with node drawing messages, only send them
- // in chuncks
- // ----------------------------------------------
- static int startDrawNode = 0;
- static int endDrawNode = 0;
- static float flDrawDuration;
- endDrawNode = startDrawNode + 20;
- flDrawDuration = 0.1 * (m_pNetwork->NumNodes()-1)/20;
- if ( flDrawDuration < .1 )
- flDrawDuration = .1;
- if (endDrawNode > m_pNetwork->NumNodes())
- {
- endDrawNode = m_pNetwork->NumNodes();
- }
-
- // ---------------------
- // Draw grid
- // ---------------------
- if (m_debugNetOverlays & bits_debugOverlayGrid)
- {
- // Trace a line to where player is looking
- CBasePlayer* pPlayer = UTIL_PlayerByIndex(CBaseEntity::m_nDebugPlayer);
-
- if (pPlayer)
- {
- Vector vForward;
- Vector vSource = pPlayer->EyePosition();
- pPlayer->EyeVectors( &vForward );
-
- trace_t tr;
- AI_TraceLine ( vSource, vSource + vForward * 2048, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr);
-
- float dotPr = DotProduct(Vector(0,0,1),tr.plane.normal);
- if (tr.fraction != 1.0 && dotPr > 0.5)
- {
- NDebugOverlay::Grid( tr.endpos + Vector(0,0,1) );
- }
- }
- }
-
- // --------------------
- CAI_Node **pAINode = m_pNetwork->AccessNodes();
-
- // --------------------
- // Draw the graph connectivity
- // ---------------------
- if (m_debugNetOverlays & bits_debugOverlayGraphConnect)
- {
- // ---------------------------------------------------
- // If network needs rebuilding do so before display
- // --------------------------------------------------
- if (m_debugNetOverlays & bits_debugNeedRebuild)
- {
- m_pManager->RebuildNetworkGraph();
- }
- else if (m_iGConnectivityNode != NO_NODE)
- {
- for (int node=0;node<m_pNetwork->NumNodes();node++)
- {
- if ( m_pNetwork->IsConnected( m_iGConnectivityNode, node) )
- {
- Vector srcPos = pAINode[m_iGConnectivityNode]->GetPosition(m_iHullDrawNum);
- Vector desPos = pAINode[node]->GetPosition(m_iHullDrawNum);
- NDebugOverlay::Line(srcPos, desPos, 255,0,255, false,0);
- }
- }
- }
- }
-
- // --------------------
- // Draw the hulls
- // ---------------------
- if (m_debugNetOverlays & bits_debugOverlayHulls)
- {
- // ---------------------------------------------------
- // If network needs rebuilding do so before display
- // --------------------------------------------------
- if (m_debugNetOverlays & bits_debugNeedRebuild)
- {
- m_pManager->RebuildNetworkGraph();
- }
- else
- {
- for (int node=startDrawNode;node<endDrawNode;node++)
- {
- for (int link=0;link<pAINode[node]->NumLinks();link++)
- {
- // Only draw link once
- if (pAINode[node]->GetLinkByIndex(link)->DestNodeID(node) < node)
- {
-
- Vector srcPos = pAINode[pAINode[node]->GetLinkByIndex(link)->m_iSrcID]->GetPosition(m_iHullDrawNum);
- Vector desPos = pAINode[pAINode[node]->GetLinkByIndex(link)->m_iDestID]->GetPosition(m_iHullDrawNum);
- Vector direction = desPos - srcPos;
- float length = VectorNormalize(direction);
- Vector hullMins = NAI_Hull::Mins(m_iHullDrawNum);
- Vector hullMaxs = NAI_Hull::Maxs(m_iHullDrawNum);
- hullMaxs.x = length + hullMaxs.x;
-
- if (pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum] & bits_CAP_MOVE_FLY)
- {
- NDebugOverlay::BoxDirection(srcPos, hullMins, hullMaxs, direction, 100,255,255,20,flDrawDuration);
- }
-
- if (pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum] & bits_CAP_MOVE_CLIMB)
- {
- // Display as a vertical slice up the climbing surface unless dismount node
- if (pAINode[pAINode[node]->GetLinkByIndex(link)->m_iSrcID]->GetOrigin() != pAINode[pAINode[node]->GetLinkByIndex(link)->m_iDestID]->GetOrigin())
- {
- hullMaxs.x = hullMaxs.x - length;
- if (srcPos.z < desPos.z)
- {
- hullMaxs.z = length + hullMaxs.z;
- }
- else
- {
- hullMins.z = hullMins.z - length;
- }
- direction = Vector(0,1,0);
-
- }
- NDebugOverlay::BoxDirection(srcPos, hullMins, hullMaxs, direction, 255,0,255,20,flDrawDuration);
- }
-
- if (pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum] & bits_CAP_MOVE_GROUND)
- {
- NDebugOverlay::BoxDirection(srcPos, hullMins, hullMaxs, direction, 0,255,50,20,flDrawDuration);
- }
-
- else if (pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum] & bits_CAP_MOVE_JUMP)
- {
- NDebugOverlay::BoxDirection(srcPos, hullMins, hullMaxs, direction, 0,0,255,20,flDrawDuration);
- }
- }
- }
- }
- }
- }
-
- // --------------------
- // Draw the hints
- // ---------------------
- if (m_debugNetOverlays & bits_debugOverlayHints)
- {
- CAI_HintManager::DrawHintOverlays(flDrawDuration);
- }
-
- // -------------------------------
- // Draw the nodes and connections
- // -------------------------------
- if (m_debugNetOverlays & (bits_debugOverlayNodes | bits_debugOverlayConnections))
- {
- for (int node=startDrawNode;node<endDrawNode;node++) {
-
- // This gets expensive, so see if the node is visible to the client
- if (pAINode[node]->GetType() != NODE_DELETED)
- {
- // --------------------
- // Draw the connections
- // ---------------------
- if (m_debugNetOverlays & bits_debugOverlayConnections)
- {
- // ---------------------------------------------------
- // If network needs rebuilding do so before display
- // --------------------------------------------------
- if (m_debugNetOverlays & bits_debugNeedRebuild)
- {
- m_pManager->RebuildNetworkGraph();
- }
- else
- {
- for (int link=0;link<pAINode[node]->NumLinks();link++) {
-
- // Only draw link once
- if (pAINode[node]->GetLinkByIndex(link)->DestNodeID(node) < node)
- {
- int srcID = pAINode[node]->GetLinkByIndex(link)->m_iSrcID;
- int desID = pAINode[node]->GetLinkByIndex(link)->m_iDestID;
-
- Vector srcPos = pAINode[srcID]->GetPosition(m_iHullDrawNum);
- Vector desPos = pAINode[desID]->GetPosition(m_iHullDrawNum);
-
- int srcType = pAINode[pAINode[node]->GetLinkByIndex(link)->m_iSrcID]->GetType();
- int desType = pAINode[pAINode[node]->GetLinkByIndex(link)->m_iDestID]->GetType();
-
- int linkInfo = pAINode[node]->GetLinkByIndex(link)->m_LinkInfo;
- int moveTypes = pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum];
-
- // when rendering, raise NODE_GROUND off the floor slighty as they seem to clip too much
- if ( srcType == NODE_GROUND)
- {
- srcPos.z += 1.0;
- }
-
- if ( desType == NODE_GROUND)
- {
- desPos.z += 1.0;
- }
-
- // Draw in red if stale link
- if (linkInfo & bits_LINK_STALE_SUGGESTED)
- {
- NDebugOverlay::Line(srcPos, desPos, 255,0,0, false, flDrawDuration);
- }
- // Draw in grey if link turned off
- else if (linkInfo & bits_LINK_OFF)
- {
- NDebugOverlay::Line(srcPos, desPos, 100,100,100, false, flDrawDuration);
- }
- else if ((m_debugNetOverlays & bits_debugOverlayFlyConnections) && (moveTypes & bits_CAP_MOVE_FLY))
- {
- NDebugOverlay::Line(srcPos, desPos, 100,255,255, false, flDrawDuration);
- }
- else if (moveTypes & bits_CAP_MOVE_CLIMB)
- {
- NDebugOverlay::Line(srcPos, desPos, 255,0,255, false, flDrawDuration);
- }
- else if (moveTypes & bits_CAP_MOVE_GROUND)
- {
- NDebugOverlay::Line(srcPos, desPos, 0,255,50, false, flDrawDuration);
- }
- else if ((m_debugNetOverlays & bits_debugOverlayJumpConnections) && (moveTypes & bits_CAP_MOVE_JUMP) )
- {
- NDebugOverlay::Line(srcPos, desPos, 0,0,255, false, flDrawDuration);
- }
- else
- { // Dark red if this hull can't use
- bool isFly = ( srcType == NODE_AIR || desType == NODE_AIR );
- bool isJump = true;
- for ( int i = HULL_HUMAN; i < NUM_HULLS; i++ )
- {
- if ( pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[i] & ~bits_CAP_MOVE_JUMP )
- {
- isJump = false;
- break;
- }
- }
- if ( ( isFly && (m_debugNetOverlays & bits_debugOverlayFlyConnections) ) ||
- ( isJump && (m_debugNetOverlays & bits_debugOverlayJumpConnections) ) ||
- ( !isFly && !isJump ) )
- {
- NDebugOverlay::Line(srcPos, desPos, 100,25,25, false, flDrawDuration);
- }
- }
- }
- }
- }
- }
- if (m_debugNetOverlays & bits_debugOverlayNodes)
- {
- int r = 255;
- int g = 0;
- int b = 0;
-
- // If checking visibility base color off of visibility info
- if (m_debugNetOverlays & bits_debugOverlayVisibility &&
- m_iVisibilityNode != NO_NODE)
- {
- // ---------------------------------------------------
- // If network needs rebuilding do so before display
- // --------------------------------------------------
- if (m_debugNetOverlays & bits_debugNeedRebuild)
- {
- m_pManager->RebuildNetworkGraph();
- }
- }
-
- // If checking graph connectivity base color off of connectivity info
- if (m_debugNetOverlays & bits_debugOverlayGraphConnect &&
- m_iGConnectivityNode != NO_NODE)
- {
- // ---------------------------------------------------
- // If network needs rebuilding do so before display
- // --------------------------------------------------
- if (m_debugNetOverlays & bits_debugNeedRebuild)
- {
- m_pManager->RebuildNetworkGraph();
- }
- else if (m_pNetwork->IsConnected( m_iGConnectivityNode, node) )
- {
- r = 0;
- g = 0;
- b = 255;
- }
- }
- // Otherwise base color off of node type
- else
- {
- // If node is new and hasn't been rebuild yet
- if (pAINode[node]->m_eNodeInfo & bits_NODE_WC_CHANGED)
- {
- r = 200;
- g = 200;
- b = 200;
- }
-
- // If node doesn't fit the current hull size
- else if (pAINode[node]->m_eNodeInfo & bits_NODE_WONT_FIT_HULL)
- {
- r = 255;
- g = 25;
- b = 25;
- }
-
- else if (pAINode[node]->GetType() == NODE_CLIMB)
- {
- r = 255;
- g = 0;
- b = 255;
- }
- else if (pAINode[node]->GetType() == NODE_AIR)
- {
- r = 0;
- g = 255;
- b = 255;
- }
- else if (pAINode[node]->GetType() == NODE_GROUND)
- {
- r = 0;
- g = 255;
- b = 100;
- }
- }
-
-
- Vector nodePos;
-
- nodePos = pAINode[node]->GetPosition(m_iHullDrawNum);
-
- NDebugOverlay::Box(nodePos, Vector(-5,-5,-5), Vector(5,5,5), r,g,b,0,flDrawDuration);
-
- // If climb node draw line in facing direction
- if (pAINode[node]->GetType() == NODE_CLIMB)
- {
- Vector offsetDir = 12.0 * Vector(cos(DEG2RAD(pAINode[node]->GetYaw())),sin(DEG2RAD(pAINode[node]->GetYaw())),flDrawDuration);
- NDebugOverlay::Line(nodePos, nodePos+offsetDir, r,g,b,false,flDrawDuration);
- }
-
- if ( pAINode[node]->GetHint() )
- {
- NDebugOverlay::Box( nodePos, Vector(-7,-7,-7), Vector(7,7,7), 255,255,0,0,flDrawDuration);
- }
-
- if (m_debugNetOverlays & bits_debugOverlayNodesLev2)
- {
- CFmtStr msg;
-
- if ( m_pNodeIndexTable )
- msg.sprintf("%i (wc:%i; z:%i)",node,m_pNodeIndexTable[pAINode[node]->GetId()], pAINode[node]->GetZone());
- else
- msg.sprintf("%i (z:%i)",node,pAINode[node]->GetZone());
-
- Vector loc = nodePos;
- loc.x+=6;
- loc.y+=6;
- loc.z+=6;
- NDebugOverlay::Text( loc, msg, true, flDrawDuration);
-
- // Print the hintgroup if we have one
- if ( pAINode[node]->GetHint() )
- {
- msg.sprintf("%s", STRING( pAINode[node]->GetHint()->GetGroup() ));
- loc.z-=3;
- NDebugOverlay::Text( loc, msg, true, flDrawDuration);
- }
- }
- }
- }
- }
- }
-
- // -------------------------------
- // Identify hull being displayed
- // -------------------------------
- if (m_debugNetOverlays & (bits_debugOverlayNodes | bits_debugOverlayConnections | bits_debugOverlayHulls))
- {
- DrawEditInfoOverlay();
- }
-
- // ----------------------------
- // Increment node draw chunk
- // ----------------------------
- startDrawNode = endDrawNode;
- if (startDrawNode >= m_pNetwork->NumNodes())
- {
- startDrawNode = 0;
- }
-
- // ----------------------------
- // Output performance stats
- // ----------------------------
-#ifdef AI_PERF_MON
- if (m_fNextPerfStatTime < gpGlobals->curtime)
- {
- char temp[512];
- Q_snprintf(temp,sizeof(temp),"%3.2f NN/m\n%3.2f P/m\n",(m_nPerfStatNN/1.0),(m_nPerfStatPB/1.0));
- UTIL_CenterPrintAll(temp);
-
- m_fNextPerfStatTime = gpGlobals->curtime + 1;
- m_nPerfStatNN = 0;
- m_nPerfStatPB = 0;
- }
-#endif
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Constructor
-//-----------------------------------------------------------------------------
-
-CAI_NetworkEditTools::CAI_NetworkEditTools(CAI_NetworkManager *pNetworkManager)
-{
- // ----------------------------------------------------------------------------
- // If in wc_edit mode
- // ----------------------------------------------------------------------------
- if (engine->IsInEditMode())
- {
- // ----------------------------------------------------------------------------
- // Allocate extra space for storing undropped node positions
- // ----------------------------------------------------------------------------
- m_pWCPosition = new Vector[MAX_NODES];
- }
- else
- {
- m_pWCPosition = NULL;
- }
-
- m_pNodeIndexTable = NULL;
- m_debugNetOverlays = 0;
-
- // ----------------------------------------------------------------------------
- // Allocate table of WC Id's. If not in edit mode Deleted after initialization
- // ----------------------------------------------------------------------------
- m_pNodeIndexTable = new int[MAX_NODES];
- for ( int i = 0; i < MAX_NODES; i++ )
- m_pNodeIndexTable[i] = NO_NODE;
- m_nNextWCIndex = 0;
-
- m_pNetwork = pNetworkManager->GetNetwork(); // @tbd
- m_pManager = pNetworkManager;
-
-
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Destructor
-//-----------------------------------------------------------------------------
-
-CAI_NetworkEditTools::~CAI_NetworkEditTools()
-{
- // --------------------------------------------------------
- // If in edit mode tell WC I'm ending my session
- // --------------------------------------------------------
-#ifdef _WIN32
- Editor_EndSession(false);
-#endif
- delete[] m_pNodeIndexTable;
-}
-
-//-----------------------------------------------------------------------------
-// CAI_NetworkBuilder
-//
-//-----------------------------------------------------------------------------
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkBuilder::FloodFillZone( CAI_Node **ppNodes, CAI_Node *pNode, int zone )
-{
- pNode->SetZone( zone );
-
- for (int link = 0; link < pNode->NumLinks(); link++)
- {
- CAI_Link *pLink = pNode->GetLinkByIndex(link);
- CAI_Node *pLinkedNode = ( pLink->m_iDestID == pNode->GetId()) ? ppNodes[pLink->m_iSrcID] : ppNodes[pLink->m_iDestID];
- if ( pLinkedNode->GetZone() == AI_NODE_ZONE_UNKNOWN )
- FloodFillZone( ppNodes, pLinkedNode, zone );
-
- Assert( pLinkedNode->GetZone() == pNode->GetZone() && pNode->GetZone() == zone );
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkBuilder::InitZones( CAI_Network *pNetwork )
-{
- int nNodes = pNetwork->NumNodes();
- CAI_Node **ppNodes = pNetwork->AccessNodes();
-
- if ( !nNodes )
- return;
-
- int i;
-
- for (i = 0; i < nNodes; i++)
- {
- ppNodes[i]->SetZone( AI_NODE_ZONE_UNKNOWN );
- }
-
- // Mark solo nodes
- for (i = 0; i < nNodes; i++)
- {
- if ( ppNodes[i]->NumLinks() == 0 )
- ppNodes[i]->SetZone( AI_NODE_ZONE_SOLO );
- }
-
- int curZone = AI_NODE_FIRST_ZONE;
-
- for (i = 0; i < nNodes; i++)
- {
- if ( ppNodes[i]->GetZone() == AI_NODE_ZONE_UNKNOWN )
- {
- FloodFillZone( (CAI_Node **)ppNodes, ppNodes[i], curZone );
- curZone++;
- }
- }
-
-#ifdef DEBUG
- for (i = 0; i < nNodes; i++)
- {
- Assert( ppNodes[i]->GetZone() != AI_NODE_ZONE_UNKNOWN );
- }
-#endif
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Used for WC edit move to rebuild the network around the given
-// location. Rebuilding the entire network takes too long
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkBuilder::Rebuild( CAI_Network *pNetwork )
-{
- int nNodes = pNetwork->NumNodes();
- CAI_Node **ppNodes = pNetwork->AccessNodes();
-
- if ( !nNodes )
- return;
-
- BeginBuild();
-
- // ------------------------------------------------------------
- // First mark all nodes around vecPos as having to be rebuilt
- // ------------------------------------------------------------
- int i;
- for (i = 0; i < nNodes; i++)
- {
- // --------------------------------------------------------------------
- // If changed, mark all nodes that are within the max link distance to
- // the changed node as having to be rebuild
- // --------------------------------------------------------------------
- if (ppNodes[i]->m_eNodeInfo & bits_NODE_WC_CHANGED)
- {
- Vector vRebuildPos = ppNodes[i]->GetOrigin();
- ppNodes[i]->SetNeedsRebuild();
- ppNodes[i]->SetZone( AI_NODE_ZONE_UNIVERSAL );
- for (int node = 0; node < nNodes; node++)
- {
- if ( ppNodes[node]->GetType() == NODE_AIR )
- {
- if ((ppNodes[node]->GetOrigin() - vRebuildPos).LengthSqr() < MAX_AIR_NODE_LINK_DIST_SQ)
- {
- ppNodes[node]->SetNeedsRebuild();
- ppNodes[node]->SetZone( AI_NODE_ZONE_UNIVERSAL );
- }
- }
- else
- {
- if ((ppNodes[node]->GetOrigin() - vRebuildPos).LengthSqr() < MAX_NODE_LINK_DIST_SQ)
- {
- ppNodes[node]->SetNeedsRebuild();
- ppNodes[node]->SetZone( AI_NODE_ZONE_UNIVERSAL );
- }
- }
- }
- }
- }
-
- // ---------------------------
- // Initialize node positions
- // ---------------------------
- for (i = 0; i < nNodes; i++)
- {
- if (ppNodes[i]->NeedsRebuild())
- {
- InitNodePosition( pNetwork, ppNodes[i] );
- }
- }
- nNodes = pNetwork->NumNodes(); // InitNodePosition can create nodes
-
- // ---------------------------
- // Initialize node neighbors
- // ---------------------------
- m_DidSetNeighborsTable.Resize( nNodes );
- m_DidSetNeighborsTable.ClearAll();
- m_NeighborsTable.SetSize( nNodes );
- for (i = 0; i < nNodes; i++)
- {
- m_NeighborsTable[i].Resize( nNodes );
- }
- for (i = 0; i < nNodes; i++)
- {
- // If near point of change recalculate
- if (ppNodes[i]->NeedsRebuild())
- {
- InitNeighbors( pNetwork, ppNodes[i] );
- }
- }
-
- // ---------------------------
- // Force node neighbors for dynamic links
- // ---------------------------
- ForceDynamicLinkNeighbors();
-
- // ---------------------------
- // Initialize accepted hulls
- // ---------------------------
- for (i = 0; i < nNodes; i++)
- {
- if (ppNodes[i]->NeedsRebuild())
- {
- ppNodes[i]->ClearLinks();
- }
- }
- for (i = 0; i < nNodes; i++)
- {
- if (ppNodes[i]->NeedsRebuild())
- {
- InitLinks( pNetwork, ppNodes[i] );
- }
- }
-
- g_pAINetworkManager->FixupHints();
-
- EndBuild();
-}
-
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkBuilder::BeginBuild()
-{
- m_pTestHull = CAI_TestHull::GetTestHull();
-}
-
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkBuilder::EndBuild()
-{
- m_NeighborsTable.SetSize(0);
- m_DidSetNeighborsTable.Resize(0);
- CAI_TestHull::ReturnTestHull();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Only called if network has changed since last time level
-// was loaded
-//-----------------------------------------------------------------------------
-
-
-void CAI_NetworkBuilder::Build( CAI_Network *pNetwork )
-{
- int nNodes = pNetwork->NumNodes();
- CAI_Node **ppNodes = pNetwork->AccessNodes();
-
- if ( !nNodes )
- return;
-
- CAI_NetworkBuildHelper *pHelper = (CAI_NetworkBuildHelper *)CreateEntityByName( "ai_network_build_helper" );
-
- VPROF( "AINet" );
-
- BeginBuild();
-
- CFastTimer masterTimer;
- CFastTimer timer;
-
- DevMsg( "Building AI node graph...\n");
- masterTimer.Start();
-
- // ---------------------------
- // Initialize node positions
- // ---------------------------
- DevMsg( "Initializing node positions...\n" );
- timer.Start();
- int i;
- for ( i = 0; i < nNodes; i++)
- {
- InitNodePosition( pNetwork, ppNodes[i] );
- if ( pHelper )
- pHelper->PostInitNodePosition( pNetwork, ppNodes[i] );
- }
- nNodes = pNetwork->NumNodes(); // InitNodePosition can create nodes
- timer.End();
- DevMsg( "...done initializing node positions. %f seconds\n", timer.GetDuration().GetSeconds() );
-
- // ---------------------------
- // Initialize node neighbors
- // ---------------------------
- DevMsg( "Initializing node neighbors...\n" );
- timer.Start();
- m_DidSetNeighborsTable.Resize( nNodes );
- m_DidSetNeighborsTable.ClearAll();
- m_NeighborsTable.SetSize( nNodes );
- for (i = 0; i < nNodes; i++)
- {
- m_NeighborsTable[i].Resize( nNodes );
- m_NeighborsTable[i].ClearAll();
- }
- for (i = 0; i < nNodes; i++)
- {
- InitNeighbors( pNetwork, ppNodes[i] );
- }
- timer.End();
- DevMsg( "...done initializing node neighbors. %f seconds\n", timer.GetDuration().GetSeconds() );
-
- // ---------------------------
- // Force node neighbors for dynamic links
- // ---------------------------
- DevMsg( "Forcing dynamic link neighbors...\n" );
- timer.Start();
- ForceDynamicLinkNeighbors();
- timer.End();
- DevMsg( "...done forcing dynamic link neighbors. %f seconds\n", timer.GetDuration().GetSeconds() );
-
- // ---------------------------
- // Initialize accepted hulls
- // ---------------------------
- DevMsg( "Determining links...\n" );
- timer.Start();
- for (i = 0; i < nNodes; i++)
- {
- // Make sure all the links are clear
- ppNodes[i]->ClearLinks();
- }
- for (i = 0; i < nNodes; i++)
- {
- InitLinks( pNetwork, ppNodes[i] );
- }
- timer.End();
- DevMsg( "...done determining links. %f seconds\n", timer.GetDuration().GetSeconds() );
-
- // ------------------------------
- // Initialize disconnected nodes
- // ------------------------------
- DevMsg( "Determining zones...\n" );
- timer.Start();
- InitZones( pNetwork);
- timer.End();
- masterTimer.End();
- DevMsg( "...done determining zones. %f seconds\n", timer.GetDuration().GetSeconds() );
- DevMsg( "...done building AI node graph, %f seconds\n", masterTimer.GetDuration().GetSeconds() );
-
- g_pAINetworkManager->FixupHints();
-
- EndBuild();
-
- if ( pHelper )
- UTIL_Remove( pHelper );
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Forces testing of a connection between src and dest IDs for all dynamic links
-//
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CAI_NetworkBuilder::ForceDynamicLinkNeighbors(void)
-{
- if (!g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable)
- {
- DevMsg("ERROR: Trying initialize links with no WC ID table!\n");
- return;
- }
-
- CAI_DynamicLink* pDynamicLink = CAI_DynamicLink::m_pAllDynamicLinks;
-
- while (pDynamicLink)
- {
- // -------------------------------------------------------------
- // First convert this links WC IDs to engine IDs
- // -------------------------------------------------------------
- int nSrcID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( pDynamicLink->m_nSrcEditID );
- if (nSrcID == -1)
- {
- DevMsg("ERROR: Dynamic link source WC node %d not found\n", pDynamicLink->m_nSrcEditID );
- }
-
- int nDestID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( pDynamicLink->m_nDestEditID );
- if (nDestID == -1)
- {
- DevMsg("ERROR: Dynamic link dest WC node %d not found\n", pDynamicLink->m_nDestEditID );
- }
-
- if ( nSrcID != -1 && nDestID != -1 )
- {
- if ( nSrcID < g_pBigAINet->NumNodes() && nDestID < g_pBigAINet->NumNodes() )
- {
- CAI_Node *pSrcNode = g_pBigAINet->GetNode( nSrcID );
- CAI_Node *pDestNode = g_pBigAINet->GetNode( nDestID );
-
- // -------------------------------------------------------------
- // Force visibility and neighbor-ness between the nodes
- // -------------------------------------------------------------
- Assert( pSrcNode );
- Assert( pDestNode );
-
- m_NeighborsTable[pSrcNode->GetId()].Set(pDestNode->GetId());
- m_NeighborsTable[pDestNode->GetId()].Set(pSrcNode->GetId());
- }
- }
-
- // Go on to the next dynamic link
- pDynamicLink = pDynamicLink->m_pNextDynamicLink;
- }
-}
-
-CAI_NetworkBuilder g_AINetworkBuilder;
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Initializes position of climb node in the world. Climb nodes are
-// set to be just above the floor or at the same level at the
-// dismount point for the node
-//
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CAI_NetworkBuilder::InitClimbNodePosition(CAI_Network *pNetwork, CAI_Node *pNode)
-{
- AI_PROFILE_SCOPE( CAI_Node_InitClimbNodePosition );
-
- // If this is a node for mounting/dismounting the climb skip it
- if ( pNode->m_eNodeInfo & (bits_NODE_CLIMB_OFF_FORWARD | bits_NODE_CLIMB_OFF_LEFT | bits_NODE_CLIMB_OFF_RIGHT) )
- {
- return;
- }
-
- // Figure out which directions I can dismount from the climb node
-
- //float hullLength = NAI_Hull::Length(HULL_SMALL);
- //Vector offsetDir = Vector(cos(DEG2RAD(m_flYaw)),sin(DEG2RAD(m_flYaw)),0);
-
- // ----------------
- // Check position
- // ----------------
- trace_t trace;
- Vector posOnLadder = pNode->GetPosition(HULL_SMALL_CENTERED);
- AI_TraceHull( posOnLadder, posOnLadder + Vector( 0, 0, -37 ),
- NAI_Hull::Mins(HULL_SMALL_CENTERED), NAI_Hull::Maxs(HULL_SMALL_CENTERED),
- MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace );
-
- // --------------------------------------------------------------------
- // If climb node is right above the floor, we don't need any dismount
- // nodes. Accept this dropped position and note that this climb node
- // is at the bottom
- // --------------------------------------------------------------------
- if (!trace.startsolid && trace.fraction != 1)
- {
- pNode->m_eNodeInfo = bits_NODE_CLIMB_BOTTOM;
- InitGroundNodePosition( pNetwork, pNode );
- return;
- }
-
- // ---------------------------------------------------------------------
- // If network was already loaded this means we are in wc edit mode
- // so we shouldn't recreate the added climb nodes
- // ---------------------------------------------------------------------
- if (g_pAINetworkManager->NetworksLoaded())
- {
- return;
- }
-
- // ---------------------------------------------------------------------
- // Otherwise we need to create climb nodes for dismounting the climb
- // and place the height of the climb node at the dismount position
- // ---------------------------------------------------------------------
- int checkNodeTypes[3] = { bits_NODE_CLIMB_OFF_FORWARD, bits_NODE_CLIMB_OFF_LEFT, bits_NODE_CLIMB_OFF_RIGHT };
-
- int numExits = 0;
-
- // DevMsg( "testing %f %f %f\n", GetOrigin().x, GetOrigin().y, GetOrigin().z );
-
- for (int i = 0; i < 3; i++)
- {
- pNode->m_eNodeInfo = checkNodeTypes[i];
-
- Vector origin = pNode->GetPosition(HULL_SMALL_CENTERED);
-
- // DevMsg( "testing %f %f %f\n", origin.x, origin.y, origin.z );
- // ----------------
- // Check outward
- // ----------------
- AI_TraceLine ( posOnLadder,
- origin,
- MASK_NPCSOLID_BRUSHONLY,
- NULL,
- COLLISION_GROUP_NONE,
- &trace );
-
- // DevMsg( "to %f %f %f : %d %f", origin.x, origin.y, origin.z, trace.startsolid, trace.fraction );
-
- if (!trace.startsolid && trace.fraction == 1.0)
- {
- float floorZ = GetFloorZ(origin); // FIXME: don't use this
-
- if (abs(pNode->GetOrigin().z - floorZ) < 36)
- {
- CAI_Node *new_node = pNetwork->AddNode( pNode->GetOrigin(), pNode->m_flYaw );
- new_node->m_pHint = NULL;
- new_node->m_eNodeType = NODE_CLIMB;
- new_node->m_eNodeInfo = pNode->m_eNodeInfo;
- InitGroundNodePosition( pNetwork, new_node );
-
- // copy over the offsets for the first CLIMB_OFF node
- // FIXME: this method is broken for when the CLIMB_OFF nodes are at different heights
- if (numExits == 0)
- {
- for (int hull = 0; hull < NUM_HULLS; hull++)
- {
- pNode->m_flVOffset[hull] = new_node->m_flVOffset[hull];
- }
- }
- else
- {
- for (int hull = 0; hull < NUM_HULLS; hull++)
- {
- if (fabs(pNode->m_flVOffset[hull] - new_node->m_flVOffset[hull]) > 1)
- {
- DevMsg(2, "Warning: Climb Node %i has different exit heights for hull %s\n", pNode->m_iID, NAI_Hull::Name(hull));
- }
- }
- }
-
- numExits++;
- }
- }
- // DevMsg( "\n");
- }
-
- if (numExits == 0)
- {
- DevMsg("ERROR: Climb Node %i has no way off\n",pNode->m_iID);
- }
-
- // this is a node that can't get gotten to directly
- pNode->m_eNodeInfo = bits_NODE_CLIMB_ON;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Initializes position of the node sitting on the ground.
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CAI_NetworkBuilder::InitGroundNodePosition(CAI_Network *pNetwork, CAI_Node *pNode)
-{
- AI_PROFILE_SCOPE( CAI_Node_InitGroundNodePosition );
-
- if ( pNode->m_eNodeInfo & bits_DONT_DROP )
- return;
-
- // find actual floor for each hull type
- for (int hull = 0; hull < NUM_HULLS; hull++)
- {
- trace_t tr;
- Vector origin = pNode->GetOrigin();
- Vector mins, maxs;
-
- // turn hull into pancake to avoid problems with ceiling
- mins = NAI_Hull::Mins(hull);
- maxs = NAI_Hull::Maxs(hull);
- maxs.z = mins.z;
-
- // Add an epsilon for cast
- origin.z += 0.1;
-
- // shift up so bottom of box is at center of node
- origin.z -= mins.z;
-
- AI_TraceHull( origin, origin + Vector( 0, 0, -384 ), mins, maxs, MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
-
- if ( !tr.startsolid )
- pNode->m_flVOffset[hull] = tr.endpos.z - pNode->GetOrigin().z + 0.1;
- else
- pNode->m_flVOffset[hull] = -mins.z + 0.1;
- }
-}
-
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Initializes position of the node in the world. Only called if
-// the network was never initialized
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CAI_NetworkBuilder::InitNodePosition(CAI_Network *pNetwork, CAI_Node *pNode)
-{
- AI_PROFILE_SCOPE( CAI_Node_InitNodePosition );
-
- if (pNode->m_eNodeType == NODE_AIR)
- {
- return;
- }
- else if (pNode->m_eNodeType == NODE_CLIMB)
- {
- InitClimbNodePosition(pNetwork, pNode);
- return;
- }
-
- // Otherwise mark as a land node and drop to the floor
-
- else if (pNode->m_eNodeType == NODE_GROUND)
- {
- InitGroundNodePosition( pNetwork, pNode );
-
- if (pNode->m_flVOffset[HULL_SMALL_CENTERED] < -100)
- {
- Assert( pNetwork == g_pBigAINet );
- DevWarning("ERROR: Node %.0f %.0f %.0f, WC ID# %i, is either too low (fell through floor) or too high (>100 units above floor)\n",
- pNode->GetOrigin().x, pNode->GetOrigin().y, pNode->GetOrigin().z,
- g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pNode->m_iID]);
-
- pNode->m_eNodeInfo |= bits_NODE_FALLEN;
- }
- return;
- }
- /* // If under water, not that the node is in water <<TODO>> when we get water
- else if ( UTIL_PointContents(GetOrigin()) & MASK_WATER )
- {
- m_eNodeType |= NODE_WATER;
- }
- */
- else if (pNode->m_eNodeType != NODE_DELETED)
- {
- DevMsg( "Bad node type!\n" );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Set the visibility for this node. (What nodes it can see with a
-// line trace)
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-void CAI_NetworkBuilder::InitVisibility(CAI_Network *pNetwork, CAI_Node *pNode)
-{
- AI_PROFILE_SCOPE( CAI_Node_InitVisibility );
-
- // If a deleted node bail
- if (pNode->m_eNodeType == NODE_DELETED)
- {
- return;
- }
- // The actual position of some nodes may be inside geometry as they have
- // hull specific position offsets (e.g. climb nodes). Get the hull specific
- // position using the smallest hull to make sure were not in geometry
- Vector srcPos = pNode->GetPosition(HULL_SMALL_CENTERED);
-
- // Check the visibility on every other node in the network
- for (int testnode = 0; testnode < pNetwork->NumNodes(); testnode++ )
- {
- CAI_Node *testNode = pNetwork->GetNode( testnode );
-
- if ( DebuggingConnect( pNode->m_iID, testnode ) )
- {
- DevMsg( " " ); // break here..
- }
-
- // We know we can view ourself
- if (pNode->m_iID == testnode)
- {
- m_NeighborsTable[pNode->m_iID].Set(testNode->m_iID);
- continue;
- }
-
- // Remove duplicate nodes unless a climb node as they move
- if (testNode->GetOrigin() == pNode->GetOrigin() && testNode->GetType() != NODE_CLIMB)
- {
- testNode->SetType( NODE_DELETED );
- DevMsg( 2, "Probable duplicate node placed at %s\n", VecToString(testNode->GetOrigin()) );
- continue;
- }
-
- // If a deleted node we don't care about it
- if (testNode->GetType() == NODE_DELETED)
- {
- continue;
- }
-
- if ( m_DidSetNeighborsTable.IsBitSet( testNode->m_iID ) )
- {
- if ( m_NeighborsTable[testNode->m_iID].IsBitSet(pNode->m_iID))
- m_NeighborsTable[pNode->m_iID].Set(testNode->m_iID);
-
- continue;
- }
-
- float flDistToCheckNode = ( testNode->GetOrigin() - pNode->GetOrigin() ).LengthSqr();
-
- if ( testNode->GetType() == NODE_AIR )
- {
- if (flDistToCheckNode > MAX_AIR_NODE_LINK_DIST_SQ)
- continue;
- }
- else
- {
- if (flDistToCheckNode > MAX_NODE_LINK_DIST_SQ)
- continue;
- }
-
- // The actual position of some nodes may be inside geometry as they have
- // hull specific position offsets (e.g. climb nodes). Get the hull specific
- // position using the smallest hull to make sure were not in geometry
- Vector destPos = pNetwork->GetNode( testnode )->GetPosition(HULL_SMALL_CENTERED);
-
- trace_t tr;
- tr.m_pEnt = NULL;
-
- // Try several line of sight checks
-
- bool isVisible = false;
-
- // ------------------
- // Bottom to bottom
- // ------------------
- AI_TraceLine ( srcPos, destPos,MASK_NPCWORLDSTATIC,NULL,COLLISION_GROUP_NONE, &tr );
- if (!tr.startsolid && tr.fraction == 1.0)
- {
- isVisible = true;
- }
-
- // ------------------
- // Top to top
- // ------------------
- if (!isVisible)
- {
- AI_TraceLine ( srcPos + Vector( 0, 0, 70 ),destPos + Vector( 0, 0, 70 ),MASK_NPCWORLDSTATIC,NULL,COLLISION_GROUP_NONE, &tr );
- if (!tr.startsolid && tr.fraction == 1.0)
- {
- isVisible = true;
- }
- }
-
- // ------------------
- // Top to Bottom
- // ------------------
- if (!isVisible)
- {
- AI_TraceLine ( srcPos + Vector( 0, 0, 70 ),destPos,MASK_NPCWORLDSTATIC,NULL,COLLISION_GROUP_NONE, &tr );
- if (!tr.startsolid && tr.fraction == 1.0)
- {
- isVisible = true;
- }
- }
-
- // ------------------
- // Bottom to Top
- // ------------------
- if (!isVisible)
- {
- AI_TraceLine ( srcPos,destPos + Vector( 0, 0, 70 ),MASK_NPCWORLDSTATIC,NULL,COLLISION_GROUP_NONE, &tr );
- if (!tr.startsolid && tr.fraction == 1.0)
- {
- isVisible = true;
- }
- }
-
- // ------------------
- // Failure
- // ------------------
- if (!isVisible)
- {
- continue;
- }
-
- /* <<TODO>> may not apply with editable connections.......
-
- // trace hit a brush ent, trace backwards to make sure that this ent is the only thing in the way.
- if ( tr.fraction != 1.0 )
- {
- pTraceEnt = tr.u.ent;// store the ent that the trace hit, for comparison
-
- AI_TraceLine ( srcPos,
- destPos,
- MASK_NPCSOLID_BRUSHONLY,
- NULL,
- &tr );
-
-
- // there is a solid_bsp ent in the way of these two nodes, so we must record several things about in order to keep
- // track of it in the pathfinding code, as well as through save and restore of the node graph. ANY data that is manipulated
- // as part of the process of adding a LINKENT to a connection here must also be done in CGraph::SetGraphPointers, where reloaded
- // graphs are prepared for use.
- if ( tr.u.ent == pTraceEnt && !FClassnameIs( tr.u.ent, "worldspawn" ) )
- {
- // get a pointer
- pLinkPool [ cTotalLinks ].m_pLinkEnt = tr.u.ent;
-
- // record the modelname, so that we can save/load node trees
- memcpy( pLinkPool [ cTotalLinks ].m_szLinkEntModelname, STRING( tr.u.ent->model ), 4 );
-
- // set the flag for this ent that indicates that it is attached to the world graph
- // if this ent is removed from the world, it must also be removed from the connections
- // that it formerly blocked.
- CBaseEntity *e = CBaseEntity::Instance( tr.u.ent );
- if ( e )
- {
- if ( !(e->GetFlags() & FL_GRAPHED ) )
- {
- e->AddFlag( FL_GRAPHED );
- }
- }
- }
- // even if the ent wasn't there, these nodes couldn't be connected. Skip.
- else
- {
- continue;
- }
- }
-*/
- m_NeighborsTable[pNode->m_iID].Set(testNode->m_iID);
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Initializes the neighbors list
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-
-void CAI_NetworkBuilder::InitNeighbors(CAI_Network *pNetwork, CAI_Node *pNode)
-{
- m_NeighborsTable[pNode->m_iID].ClearAll();
-
- // Begin by establishing viewability to limit the number of nodes tested
- InitVisibility( pNetwork, pNode );
-
- AI_PROFILE_SCOPE_BEGIN( CAI_Node_InitNeighbors );
-
- // Now check each neighbor against all other neighbors to see if one of
- // them is a redundant connection
- for (int checknode = 0; checknode < pNetwork->NumNodes(); checknode++ )
- {
- if ( DebuggingConnect( pNode->m_iID, checknode ) )
- {
- DevMsg( " " ); // break here..
- }
-
- // I'm not a neighbor of myself
- if ( pNode->m_iID == checknode )
- {
- m_NeighborsTable[pNode->m_iID].Clear(checknode);
- continue;
- }
-
- // Only check if already on the neightbor list
- if (!m_NeighborsTable[pNode->m_iID].IsBitSet(checknode))
- {
- continue;
- }
-
- CAI_Node *pCheckNode = pNetwork->GetNode(checknode);
-
- for (int testnode = 0; testnode < pNetwork->NumNodes(); testnode++ )
- {
- // don't check against itself
- if (( testnode == checknode ) || (testnode == pNode->m_iID))
- {
- continue;
- }
-
- // Only check if already on the neightbor list
- if (!m_NeighborsTable[pNode->m_iID].IsBitSet(testnode))
- {
- continue;
- }
-
- CAI_Node *pTestNode = pNetwork->GetNode(testnode);
-
- // ----------------------------------------------------------
- // Don't check air nodes against nodes of a different types
- // ----------------------------------------------------------
- if ((pCheckNode->GetType() == NODE_AIR && pTestNode->GetType() != NODE_AIR)||
- (pCheckNode->GetType() != NODE_AIR && pTestNode->GetType() == NODE_AIR))
- {
- continue;
- }
-
- // ----------------------------------------------------------
- // If climb node pairs, don't consider redundancy
- // ----------------------------------------------------------
- if (pNode->GetType() == NODE_CLIMB &&
- (pCheckNode->GetType() == NODE_CLIMB || pTestNode->GetType() == NODE_CLIMB))
- {
- continue;
- }
-
- // ----------------------------------------------------------
- // If a climb node mounting point is involved, don't consider redundancy
- // ----------------------------------------------------------
- if ( ( pCheckNode->GetOrigin() == pNode->GetOrigin() && pNode->GetType() == NODE_CLIMB && pCheckNode->GetType() == NODE_CLIMB ) ||
- ( pTestNode->GetOrigin() == pNode->GetOrigin() && pNode->GetType() == NODE_CLIMB && pTestNode->GetType() == NODE_CLIMB ) ||
- ( pTestNode->GetOrigin() == pCheckNode->GetOrigin() && pCheckNode->GetType() == NODE_CLIMB && pTestNode->GetType() == NODE_CLIMB ) )
- {
- continue;
- }
-
- // @HACKHACK (toml 02-25-04): Ignore redundancy if both nodes are air nodes with
- // hint type "strider node". Really, really should do this in a clean manner
- bool nodeIsStrider = ( pNode->GetHint() && pNode->GetHint()->HintType() == HINT_STRIDER_NODE );
- bool other1IsStrider = ( pCheckNode->GetHint() && pCheckNode->GetHint()->HintType() == HINT_STRIDER_NODE );
- bool other2IsStrider = ( pTestNode->GetHint() && pTestNode->GetHint()->HintType() == HINT_STRIDER_NODE );
- if ( nodeIsStrider && other1IsStrider != other2IsStrider )
- {
- continue;
- }
-
- Vector vec2DirToCheckNode = pCheckNode->GetOrigin() - pNode->GetOrigin();
- float flDistToCheckNode = VectorNormalize( vec2DirToCheckNode );
-
- Vector vec2DirToTestNode = ( pTestNode->GetOrigin() - pNode->GetOrigin() );
- float flDistToTestNode = VectorNormalize( vec2DirToTestNode );
-
- float tolerance = 0.92388; // 45 degrees
-
- if ( DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) >= tolerance )
- {
- if ( flDistToTestNode < flDistToCheckNode )
- {
- DebugConnectMsg( pNode->m_iID, checknode, " Revoking neighbor status to to closer redundant link %d\n", testnode );
- m_NeighborsTable[pNode->m_iID].Clear(checknode);
- }
- else
- {
- DebugConnectMsg( pNode->m_iID, testnode, " Revoking neighbor status to to closer redundant link %d\n", checknode );
- m_NeighborsTable[pNode->m_iID].Clear(testnode);
- }
- }
- }
- }
-
- AI_PROFILE_SCOPE_END();
-
- m_DidSetNeighborsTable.Set(pNode->m_iID);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: For the current node, check its connection to all other nodes
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-
-static bool IsInLineForClimb( const Vector &srcPos, const Vector &srcFacing, const Vector &destPos, const Vector &destFacing )
-{
-#ifdef DEBUG
- Vector normSrcFacing( srcFacing ), normDestFacing( destFacing );
-
- VectorNormalize( normSrcFacing );
- VectorNormalize( normDestFacing );
-
- Assert( VectorsAreEqual( srcFacing, normSrcFacing, 0.01 ) && VectorsAreEqual( destFacing, normDestFacing, 0.01 ) );
-#endif
-
- // If they are not facing the same way...
- if ( 1 - srcFacing.Dot( destFacing ) > 0.01 )
- return false;
-
- // If they aren't in line along the facing...
- if ( CalcDistanceToLine2D( destPos.AsVector2D(), srcPos.AsVector2D(), srcPos.AsVector2D() + srcFacing.AsVector2D() ) > 0.01 )
- return false;
-
- // Check that the angle between them is either staight up, or on at angle of ladder-stairs
- Vector vecDelta = srcPos - destPos;
-
- VectorNormalize( vecDelta );
-
- float fabsCos = fabs( srcFacing.Dot( vecDelta ) );
-
- const float CosAngLadderStairs = 0.4472; // rise 2 & run 1
-
- if ( fabsCos > 0.05 && fabs( fabsCos - CosAngLadderStairs ) > 0.05 )
- return false;
-
- // *************************** --------------------------------
- return true;
-}
-
-//-------------------------------------
-
-int CAI_NetworkBuilder::ComputeConnection( CAI_Node *pSrcNode, CAI_Node *pDestNode, Hull_t hull )
-{
- int srcId = pSrcNode->m_iID;
- int destId = pDestNode->m_iID;
- int result = 0;
- trace_t tr;
-
- // Set the size of the test hull
- if ( m_pTestHull->GetHullType() != hull )
- {
- m_pTestHull->SetHullType( hull );
- m_pTestHull->SetHullSizeNormal( true );
- }
-
- if ( !( m_pTestHull->GetFlags() & FL_ONGROUND ) )
- {
- DevWarning( 2, "OFFGROUND!\n" );
- }
- m_pTestHull->AddFlag( FL_ONGROUND );
-
- // ==============================================================
- // FIRST CHECK IF HULL CAN EVEN FIT AT THESE NODES
- // ==============================================================
- // @Note (toml 02-10-03): this should be optimized, caching the results of CanFitAtNode()
- if ( !( pSrcNode->m_eNodeInfo & ( HullToBit( hull ) << NODE_ENT_FLAGS_SHIFT ) ) &&
- !m_pTestHull->GetNavigator()->CanFitAtNode(srcId,MASK_NPCWORLDSTATIC) )
- {
- DebugConnectMsg( srcId, destId, " Cannot fit at node %d\n", srcId );
- return 0;
- }
-
- if ( !( pDestNode->m_eNodeInfo & ( HullToBit( hull ) << NODE_ENT_FLAGS_SHIFT ) ) &&
- !m_pTestHull->GetNavigator()->CanFitAtNode(destId,MASK_NPCWORLDSTATIC) )
- {
- DebugConnectMsg( srcId, destId, " Cannot fit at node %d\n", destId );
- return 0;
- }
-
- // ==============================================================
- // AIR NODES (FLYING)
- // ==============================================================
- if (pSrcNode->m_eNodeType == NODE_AIR || pDestNode->GetType() == NODE_AIR)
- {
- AI_PROFILE_SCOPE( CAI_Node_InitLinks_Air );
-
- // Air nodes only connect to other air nodes and nothing else
- if (pSrcNode->m_eNodeType == NODE_AIR && pDestNode->GetType() == NODE_AIR)
- {
- AI_TraceHull( pSrcNode->GetOrigin(), pDestNode->GetOrigin(), NAI_Hull::Mins(hull),NAI_Hull::Maxs(hull), MASK_NPCWORLDSTATIC, m_pTestHull, COLLISION_GROUP_NONE, &tr );
- if (!tr.startsolid && tr.fraction == 1.0)
- {
- result |= bits_CAP_MOVE_FLY;
- DebugConnectMsg( srcId, destId, " Connect by flying\n" );
- }
- }
- }
- // =============================================================================
- // > CLIMBING
- // =============================================================================
- // If both are climb nodes just make sure they are above each other
- // and there is room for the hull to pass between them
- else if ((pSrcNode->m_eNodeType == NODE_CLIMB) && (pDestNode->GetType() == NODE_CLIMB))
- {
- AI_PROFILE_SCOPE( CAI_Node_InitLinks_Climb );
-
- Vector srcPos = pSrcNode->GetPosition(hull);
- Vector destPos = pDestNode->GetPosition(hull);
-
- // If a code genereted climb dismount node the two origins will be the same
- if (pSrcNode->GetOrigin() == pDestNode->GetOrigin())
- {
- AI_TraceHull( srcPos, destPos,
- NAI_Hull::Mins(hull),NAI_Hull::Maxs(hull),
- MASK_NPCWORLDSTATIC, m_pTestHull, COLLISION_GROUP_NONE, &tr );
- if (!tr.startsolid && tr.fraction == 1.0)
- {
- result |= bits_CAP_MOVE_CLIMB;
- DebugConnectMsg( srcId, destId, " Connect by climbing\n" );
- }
- }
- else
- {
- if ( !IsInLineForClimb(srcPos, UTIL_YawToVector( pSrcNode->m_flYaw ), destPos, UTIL_YawToVector( pDestNode->m_flYaw ) ) )
- {
- Assert( !IsInLineForClimb(destPos, UTIL_YawToVector( pDestNode->m_flYaw ), srcPos, UTIL_YawToVector( pSrcNode->m_flYaw ) ) );
- DebugConnectMsg( srcId, destId, " Not lined up for proper climbing\n" );
- return 0;
- }
-
- AI_TraceHull( srcPos, destPos, NAI_Hull::Mins(hull),NAI_Hull::Maxs(hull), MASK_NPCWORLDSTATIC, m_pTestHull, COLLISION_GROUP_NONE, &tr );
- if (!tr.startsolid && tr.fraction == 1.0)
- {
- result |= bits_CAP_MOVE_CLIMB;
- DebugConnectMsg( srcId, destId, " Connect by climbing\n" );
- }
- }
- }
- // ====================================================
- // > TWO LAND NODES
- // =====================================================
- else if ((pSrcNode->m_eNodeType == NODE_GROUND) || (pDestNode->GetType() == NODE_GROUND))
- {
- // BUG: this could use GroundMoveLimit, except there's no version of world but not brushes (doors open, etc).
-
- // ====================================================
- // > WALKING : walk the space between the nodes
- // =====================================================
-
- // in this loop we take tiny steps from the current node to the nodes that it links to, one at a time.
- bool fStandFailed = false;
- bool fWalkFailed = true;
-
- AI_PROFILE_SCOPE_BEGIN( CAI_Node_InitLinks_Ground );
-
- Vector srcPos = pSrcNode->GetPosition(hull);
- Vector destPos = pDestNode->GetPosition(hull);
-
- if (!m_pTestHull->GetMoveProbe()->CheckStandPosition( srcPos, MASK_NPCWORLDSTATIC))
- {
- DebugConnectMsg( srcId, destId, " Failed to stand at %d\n", srcId );
- fStandFailed = true;
- }
-
- if (!m_pTestHull->GetMoveProbe()->CheckStandPosition( destPos, MASK_NPCWORLDSTATIC))
- {
- DebugConnectMsg( srcId, destId, " Failed to stand at %d\n", destId );
- fStandFailed = true;
- }
-
- //if (hull == 0)
- // DevMsg("from %.1f %.1f %.1f to %.1f %.1f %.1f\n", srcPos.x, srcPos.y, srcPos.z, destPos.x, destPos.y, destPos.z );
-
- if ( !fStandFailed )
- {
- fWalkFailed = !m_pTestHull->GetMoveProbe()->TestGroundMove( srcPos, destPos, MASK_NPCWORLDSTATIC, AITGM_IGNORE_INITIAL_STAND_POS, NULL );
- if ( fWalkFailed )
- DebugConnectMsg( srcId, destId, " Failed to walk between nodes\n" );
- }
-
- // Add to our list of accepable hulls
- if (!fWalkFailed && !fStandFailed)
- {
- result |= bits_CAP_MOVE_GROUND;
- DebugConnectMsg( srcId, destId, " Nodes connect for ground movement\n" );
- }
-
- AI_PROFILE_SCOPE_END();
-
- // =============================================================================
- // > JUMPING : jump the space between the nodes, but only if walk failed
- // =============================================================================
- if (!fStandFailed && fWalkFailed && (pSrcNode->m_eNodeType == NODE_GROUND) && (pDestNode->GetType() == NODE_GROUND))
- {
- AI_PROFILE_SCOPE( CAI_Node_InitLinks_Jump );
-
- Vector srcPos = pSrcNode->GetPosition(hull);
- Vector destPos = pDestNode->GetPosition(hull);
-
- // Jumps aren't bi-directional. We can jump down further than we can jump up so
- // we have to test for either one
- bool canDestJump = m_pTestHull->IsJumpLegal(srcPos, destPos, destPos);
- bool canSrcJump = m_pTestHull->IsJumpLegal(destPos, srcPos, srcPos);
-
- if (canDestJump || canSrcJump)
- {
- CAI_MoveProbe *pMoveProbe = m_pTestHull->GetMoveProbe();
-
- bool fJumpLegal = false;
- m_pTestHull->SetGravity(1.0);
-
- AIMoveTrace_t moveTrace;
- pMoveProbe->MoveLimit( NAV_JUMP, srcPos,destPos, MASK_NPCWORLDSTATIC, NULL, &moveTrace);
- if (!IsMoveBlocked(moveTrace))
- {
- fJumpLegal = true;
- }
- pMoveProbe->MoveLimit( NAV_JUMP, destPos,srcPos, MASK_NPCWORLDSTATIC, NULL, &moveTrace);
- if (!IsMoveBlocked(moveTrace))
- {
- fJumpLegal = true;
- }
-
- // Add to our list of accepable hulls
- if (fJumpLegal)
- {
- result |= bits_CAP_MOVE_JUMP;
- DebugConnectMsg( srcId, destId, " Nodes connect for jumping\n" );
- }
- }
- }
- }
- return result;
-}
-
-
-
-//-------------------------------------
-
-void CAI_NetworkBuilder::InitLinks(CAI_Network *pNetwork, CAI_Node *pNode)
-{
- AI_PROFILE_SCOPE( CAI_Node_InitLinks );
-
- // -----------------------------------------------------
- // Get test hull
- // -----------------------------------------------------
- m_pTestHull->GetNavigator()->SetNetwork( pNetwork );
-
- // -----------------------------------------------------
- // Initialize links to every node
- // -----------------------------------------------------
- for (int i = 0; i < pNetwork->NumNodes(); i++ )
- {
- // -------------------------------------------------
- // Check for redundant link building
- // -------------------------------------------------
- DebugConnectMsg( pNode->m_iID, i, "Testing connection between %d and %d:\n", pNode->m_iID, i );
-
- if (pNode->HasLink(i))
- {
- // A link has been already created when the other node was processed...
- DebugConnectMsg( pNode->m_iID, i, " Nodes already connected\n" );
- continue;
- }
-
- // ---------------------------------------------------------------------
- // If link has been already created in other node just share it
- // ---------------------------------------------------------------------
- CAI_Node *pDestNode = pNetwork->GetNode( i );
-
- CAI_Link *pOldLink = pDestNode->HasLink(pNode->m_iID);
- if (pOldLink)
- {
- DebugConnectMsg( pNode->m_iID, i, " Sharing previously establish connection\n" );
- ((CAI_Node *)pNode)->AddLink(pOldLink);
- continue;
- }
-
- // Only check if the node is a neighbor
- if ( m_NeighborsTable[pNode->m_iID].IsBitSet(pDestNode->m_iID) )
- {
- int acceptedMotions[NUM_HULLS];
-
- bool bAllFailed = true;
-
- if ( DebuggingConnect( pNode->m_iID, i ) )
- {
- DevMsg( " " ); // break here..
- }
-
- if ( !(pNode->m_eNodeInfo & bits_NODE_FALLEN) && !(pDestNode->m_eNodeInfo & bits_NODE_FALLEN) )
- {
- for (int hull = 0 ; hull < NUM_HULLS; hull++ )
- {
- DebugConnectMsg( pNode->m_iID, i, " Testing for hull %s\n", NAI_Hull::Name( (Hull_t)hull ) );
-
- acceptedMotions[hull] = ComputeConnection( pNode, pDestNode, (Hull_t)hull );
- if ( acceptedMotions[hull] != 0 )
- bAllFailed = false;
- }
- }
- else
- DebugConnectMsg( pNode->m_iID, i, " No connection: one or both are fallen nodes\n" );
-
- // If there were any passible hulls create link
- if (!bAllFailed)
- {
- CAI_Link *pLink = pNetwork->CreateLink( pNode->m_iID, pDestNode->m_iID);
- if ( pLink )
- {
- for (int hull=0;hull<NUM_HULLS;hull++)
- {
- pLink->m_iAcceptedMoveTypes[hull] = acceptedMotions[hull];
- }
- DebugConnectMsg( pNode->m_iID, i, " Added link\n" );
- }
- }
- else
- {
- m_NeighborsTable[pNode->m_iID].Clear(pDestNode->m_iID);
- DebugConnectMsg(pNode->m_iID, i, " NO LINK\n" );
- }
- }
- else
- DebugConnectMsg( pNode->m_iID, i, " NO LINK (not neighbors)\n" );
- }
-}
-
-//-----------------------------------------------------------------------------
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "fmtstr.h"
+#include "filesystem.h"
+#include "filesystem/IQueuedLoader.h"
+#include "utlbuffer.h"
+#include "utlrbtree.h"
+#include "editor_sendcommand.h"
+
+#include "ai_networkmanager.h"
+#include "ai_network.h"
+#include "ai_node.h"
+#include "ai_navigator.h"
+#include "ai_link.h"
+#include "ai_dynamiclink.h"
+#include "ai_initutils.h"
+#include "ai_moveprobe.h"
+#include "ai_hull.h"
+#include "ndebugoverlay.h"
+#include "ai_hint.h"
+#include "tier0/icommandline.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// Increment this to force rebuilding of all networks
+#define AINET_VERSION_NUMBER 37
+
+//-----------------------------------------------------------------------------
+
+int g_DebugConnectNode1 = -1;
+int g_DebugConnectNode2 = -1;
+#define DebuggingConnect( node1, node2 ) ( ( node1 == g_DebugConnectNode1 && node2 == g_DebugConnectNode2 ) || ( node1 == g_DebugConnectNode2 && node2 == g_DebugConnectNode1 ) )
+
+inline void DebugConnectMsg( int node1, int node2, const char *pszFormat, ... )
+{
+ if ( DebuggingConnect( node1, node2 ) )
+ {
+ char string[ 2048 ];
+ va_list argptr;
+ va_start( argptr, pszFormat );
+ Q_vsnprintf( string, sizeof(string), pszFormat, argptr );
+ va_end( argptr );
+
+ DevMsg( "%s", string );
+ }
+}
+
+CON_COMMAND( ai_debug_node_connect, "Debug the attempted connection between two nodes" )
+{
+ g_DebugConnectNode1 = atoi( args[1] );
+ g_DebugConnectNode2 = atoi( args[2] );
+
+ DevMsg( "ai_debug_node_connect: debugging enbabled for %d <--> %d\n", g_DebugConnectNode1, g_DebugConnectNode2 );
+}
+
+//-----------------------------------------------------------------------------
+// This CVAR allows level designers to override the building
+// of node graphs due to date conflicts with the BSP and AIN
+// files. That way they don't have to wait for the node graph
+// to rebuild following small only-ents changes. This CVAR
+// always defaults to 0 and must be set at the command
+// line to properly override the node graph building.
+
+ConVar g_ai_norebuildgraph( "ai_norebuildgraph", "0" );
+
+
+//-----------------------------------------------------------------------------
+// CAI_NetworkManager
+//
+//-----------------------------------------------------------------------------
+
+CAI_NetworkManager *g_pAINetworkManager;
+
+//-----------------------------------------------------------------------------
+
+bool CAI_NetworkManager::gm_fNetworksLoaded;
+
+LINK_ENTITY_TO_CLASS(ai_network,CAI_NetworkManager);
+
+BEGIN_DATADESC( CAI_NetworkManager )
+
+ DEFINE_FIELD( m_bNeedGraphRebuild, FIELD_BOOLEAN ),
+ // m_pEditOps
+ // m_pNetwork
+ // DEFINE_FIELD( m_bDontSaveGraph, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fInitalized, FIELD_BOOLEAN ),
+
+ // Function pointers
+ DEFINE_FUNCTION( DelayedInit ),
+ DEFINE_FUNCTION( RebuildThink ),
+
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------
+
+CAI_NetworkManager::CAI_NetworkManager(void)
+{
+ m_pNetwork = new CAI_Network;
+ m_pEditOps = new CAI_NetworkEditTools(this);
+ m_bNeedGraphRebuild = false;
+ m_fInitalized = false;
+ CAI_DynamicLink::gm_bInitialized = false;
+
+ // ---------------------------------
+ // Add to linked list of networks
+ // ---------------------------------
+};
+
+//-----------------------------------------------------------------------------
+
+CAI_NetworkManager::~CAI_NetworkManager(void)
+{
+ // ---------------------------------------
+ // Remove from linked list of AINetworks
+ // ---------------------------------------
+ delete m_pEditOps;
+ delete m_pNetwork;
+ if ( g_pAINetworkManager == this )
+ {
+ g_pAINetworkManager = NULL;
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose : Think function so we can put message on screen saying we are
+// going to rebuild the network, before we hang during the rebuild
+//------------------------------------------------------------------------------
+
+void CAI_NetworkManager::RebuildThink( void )
+{
+ SetThink(NULL);
+
+ GetEditOps()->m_debugNetOverlays &= ~bits_debugNeedRebuild;
+ StartRebuild( );
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Delay function so we can put message on screen saying we are
+// going to rebuild the network, before we hang during the rebuild
+//------------------------------------------------------------------------------
+
+void CAI_NetworkManager::RebuildNetworkGraph( void )
+{
+ if (m_pfnThink != (void (CBaseEntity::*)())&CAI_NetworkManager::RebuildThink)
+ {
+ UTIL_CenterPrintAll( "Doing partial rebuild of Node Graph...\n" );
+ SetThink(&CAI_NetworkManager::RebuildThink);
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Used for WC edit move to rebuild the network around the given
+// location. Rebuilding the entire network takes too long
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkManager::StartRebuild( void )
+{
+ CAI_DynamicLink::gm_bInitialized = false;
+
+ g_AINetworkBuilder.Rebuild( m_pNetwork );
+
+ // ------------------------------------------------------------
+ // Purge any dynamic links for links that don't exist any more
+ // ------------------------------------------------------------
+ CAI_DynamicLink::PurgeDynamicLinks();
+
+
+ // ------------------------
+ // Reset all dynamic links
+ // ------------------------
+ CAI_DynamicLink::ResetDynamicLinks();
+
+ // --------------------------------------------------
+ // Update display of usable nodes for displayed hull
+ // --------------------------------------------------
+ GetEditOps()->RecalcUsableNodesForHull();
+
+ GetEditOps()->ClearRebuildFlags();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called by save restore code if no valid load graph was loaded at restore time.
+// Prevents writing out of a "bogus" node graph...
+// Input : -
+//-----------------------------------------------------------------------------
+void CAI_NetworkManager::MarkDontSaveGraph()
+{
+ m_bDontSaveGraph = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Only called if network has changed since last time level
+// was loaded
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkManager::SaveNetworkGraph( void )
+{
+ if ( m_bDontSaveGraph )
+ return;
+
+ if ( !m_bNeedGraphRebuild )
+ return;
+
+ //if ( g_AI_Manager.NumAIs() && m_pNetwork->m_iNumNodes == 0 )
+ //{
+ // return;
+ //}
+
+ if ( !g_pGameRules->FAllowNPCs() )
+ {
+ return;
+ }
+
+ // -----------------------------
+ // Make sure directories have been made
+ // -----------------------------
+ char szNrpFilename [MAX_PATH];// text node report filename
+ Q_strncpy( szNrpFilename, "maps/graphs" ,sizeof(szNrpFilename));
+
+ // Usually adding on the map filename and stripping it does nothing, but if the map is under a subdir,
+ // this makes it create the correct subdir under maps/graphs.
+ char tempFilename[MAX_PATH];
+ Q_snprintf( tempFilename, sizeof( tempFilename ), "%s/%s", szNrpFilename, STRING( gpGlobals->mapname ) );
+
+ // Remove the filename.
+ int len = strlen( tempFilename );
+ for ( int i=0; i < len; i++ )
+ {
+ if ( tempFilename[len-i-1] == '/' || tempFilename[len-i-1] == '\\' )
+ {
+ tempFilename[len-i-1] = 0;
+ break;
+ }
+ }
+
+ // Make sure the directories we need exist.
+ filesystem->CreateDirHierarchy( tempFilename, "DEFAULT_WRITE_PATH" );
+
+ // Now add the real map filename.
+ Q_strncat( szNrpFilename, "/", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
+ Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
+ Q_strncat( szNrpFilename, IsX360() ? ".360.ain" : ".ain", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
+
+ CUtlBuffer buf;
+
+ // ---------------------------
+ // Save the version number
+ // ---------------------------
+ buf.PutInt(AINET_VERSION_NUMBER);
+ buf.PutInt(gpGlobals->mapversion);
+
+ // -------------------------------
+ // Dump all the nodes to the file
+ // -------------------------------
+ buf.PutInt( m_pNetwork->m_iNumNodes);
+
+ int node;
+ int totalNumLinks = 0;
+ for ( node = 0; node < m_pNetwork->m_iNumNodes; node++)
+ {
+ CAI_Node *pNode = m_pNetwork->GetNode(node);
+ Assert( pNode->GetZone() != AI_NODE_ZONE_UNKNOWN );
+
+ buf.PutFloat( pNode->GetOrigin().x );
+ buf.PutFloat( pNode->GetOrigin().y );
+ buf.PutFloat( pNode->GetOrigin().z );
+ buf.PutFloat( pNode->GetYaw() );
+ buf.Put( pNode->m_flVOffset, sizeof( pNode->m_flVOffset ) );
+ buf.PutChar( pNode->GetType() );
+ if ( IsX360() )
+ {
+ buf.SeekPut( CUtlBuffer::SEEK_CURRENT, 3 );
+ }
+ buf.PutUnsignedShort( pNode->m_eNodeInfo );
+ buf.PutShort( pNode->GetZone() );
+
+ for (int link = 0; link < pNode->NumLinks(); link++)
+ {
+ // Only dump if link source
+ if (node == pNode->GetLinkByIndex(link)->m_iSrcID)
+ {
+ totalNumLinks++;
+ }
+ }
+ }
+
+ // -------------------------------
+ // Dump all the links to the file
+ // -------------------------------
+ buf.PutInt( totalNumLinks );
+
+ for (node = 0; node < m_pNetwork->m_iNumNodes; node++)
+ {
+ CAI_Node *pNode = m_pNetwork->GetNode(node);
+
+ for (int link = 0; link < pNode->NumLinks(); link++)
+ {
+ // Only dump if link source
+ CAI_Link *pLink = pNode->GetLinkByIndex(link);
+ if (node == pLink->m_iSrcID)
+ {
+ buf.PutShort( pLink->m_iSrcID );
+ buf.PutShort( pLink->m_iDestID );
+ buf.Put( pLink->m_iAcceptedMoveTypes, sizeof( pLink->m_iAcceptedMoveTypes) );
+ }
+ }
+ }
+
+ // -------------------------------
+ // Dump WC lookup table
+ // -------------------------------
+ CUtlMap<int, int> wcIDs;
+ SetDefLessFunc(wcIDs);
+ bool bCheckForProblems = false;
+ for (node = 0; node < m_pNetwork->m_iNumNodes; node++)
+ {
+ int iPreviousNodeBinding = wcIDs.Find( GetEditOps()->m_pNodeIndexTable[node] );
+ if ( iPreviousNodeBinding != wcIDs.InvalidIndex() )
+ {
+ if ( !bCheckForProblems )
+ {
+ DevWarning( "******* MAP CONTAINS DUPLICATE HAMMER NODE IDS! CHECK FOR PROBLEMS IN HAMMER TO CORRECT *******\n" );
+ bCheckForProblems = true;
+ }
+ DevWarning( " AI node %d is associated with Hammer node %d, but %d is already bound to node %d\n", node, GetEditOps()->m_pNodeIndexTable[node], GetEditOps()->m_pNodeIndexTable[node], wcIDs[iPreviousNodeBinding] );
+ }
+ else
+ {
+ wcIDs.Insert( GetEditOps()->m_pNodeIndexTable[node], node );
+ }
+ buf.PutInt( GetEditOps()->m_pNodeIndexTable[node] );
+ }
+
+ // -------------------------------
+ // Write the file out
+ // -------------------------------
+
+ FileHandle_t fh = filesystem->Open( szNrpFilename, "wb" );
+ if ( !fh )
+ {
+ DevWarning( 2, "Couldn't create %s!\n", szNrpFilename );
+ return;
+ }
+
+ filesystem->Write( buf.Base(), buf.TellPut(), fh );
+ filesystem->Close(fh);
+}
+
+/* Keep this around for debugging
+//-----------------------------------------------------------------------------
+// Purpose: Only called if network has changed since last time level
+// was loaded
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CAI_NetworkManager::SaveNetworkGraph( void )
+{
+ // -----------------------------
+ // Make sure directories have been made
+ // -----------------------------
+ char szNrpFilename [MAX_PATH];// text node report filename
+ Q_strncpy( szNrpFilename, "maps" ,sizeof(szNrpFilename));
+ filesystem->CreateDirHierarchy( szNrpFilename );
+ Q_strncat( szNrpFilename, "/graphs", COPY_ALL_CHARACTERS );
+ filesystem->CreateDirHierarchy( szNrpFilename );
+
+ Q_strncat( szNrpFilename, "/", COPY_ALL_CHARACTERS );
+ Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), COPY_ALL_CHARACTERS );
+ Q_strncat( szNrpFilename, ".ain", COPY_ALL_CHARACTERS );
+
+ FileHandle_t file = filesystem->Open ( szNrpFilename, "w+" );
+
+ // -----------------------------
+ // Make sure the file opened ok
+ // -----------------------------
+ if ( !file )
+ {
+ DevWarning( 2, "Couldn't create %s!\n", szNrpFilename );
+ return;
+ }
+
+ // ---------------------------
+ // Save the version number
+ // ---------------------------
+ filesystem->FPrintf(file,"Version %4d\n",AINET_VERSION_NUMBER);
+
+ // -------------------------------
+ // Dump all the nodes to the file
+ // -------------------------------
+ filesystem->FPrintf ( file, "NumNodes: %d\n", m_iNumNodes);
+ int totalNumLinks = 0;
+ for (int node = 0; node < m_iNumNodes; node++)
+ {
+ filesystem->FPrintf ( file, "Location %4f,%4f,%4f\n",m_pAInode[node]->GetOrigin().x, m_pAInode[node]->GetOrigin().y, m_pAInode[node]->GetOrigin().z );
+ for (int hull =0;hull<NUM_HULLS;hull++)
+ {
+ filesystem->FPrintf ( file, "Voffset %4f\n", m_pAInode[node]->m_flVOffset[hull]);
+ }
+ filesystem->FPrintf ( file, "HintType: %4d\n", m_pAInode[node]->m_eHintType );
+ filesystem->FPrintf ( file, "HintYaw: %4f\n", m_pAInode[node]->GetYaw() );
+ filesystem->FPrintf ( file, "NodeType %4d\n",m_pAInode[node]->GetType());
+ filesystem->FPrintf ( file, "NodeInfo %4d\n",m_pAInode[node]->m_eNodeInfo);
+ filesystem->FPrintf ( file, "Neighbors ");
+ m_pAInode[node]->m_pNeighborBS->SaveBitString(file);
+ filesystem->FPrintf ( file, "Visible ");
+ m_pAInode[node]->m_pVisibleBS->SaveBitString(file);
+ filesystem->FPrintf ( file, "Connected ");
+ m_pAInode[node]->m_pConnectedBS->SaveBitString(file);
+
+ filesystem->FPrintf ( file, "NumLinks %4d\n",m_pAInode[node]->NumLinks());
+
+ for (int link = 0; link < m_pAInode[node]->NumLinks(); link++)
+ {
+ // Only dump if link source
+ if (node == m_pAInode[node]->GetLinkByIndex(link)->m_iSrcID)
+ {
+ totalNumLinks++;
+ }
+ }
+ }
+
+ // -------------------------------
+ // Dump all the links to the file
+ // -------------------------------
+ filesystem->FPrintf ( file, "TotalNumLinks %4d\n",totalNumLinks);
+
+ for (node = 0; node < m_iNumNodes; node++)
+ {
+ for (int link = 0; link < m_pAInode[node]->NumLinks(); link++)
+ {
+ // Only dump if link source
+ if (node == m_pAInode[node]->GetLinkByIndex(link)->m_iSrcID)
+ {
+ filesystem->FPrintf ( file, "LinkSrcID %4d\n", m_pAInode[node]->GetLinkByIndex(link)->m_iSrcID);
+ filesystem->FPrintf ( file, "LinkDestID %4d\n", m_pAInode[node]->GetLinkByIndex(link)->m_iDestID);
+
+ for (int hull =0;hull<NUM_HULLS;hull++)
+ {
+ filesystem->FPrintf ( file, "Hulls %4d\n", m_pAInode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[hull]);
+ }
+ }
+ }
+ }
+
+ // -------------------------------
+ // Dump WC lookup table
+ // -------------------------------
+ for (node = 0; node < m_iNumNodes; node++)
+ {
+ filesystem->FPrintf( file, "%4d\n",m_pNodeIndexTable[node]);
+ }
+
+ filesystem->Close(file);
+}
+*/
+
+//-----------------------------------------------------------------------------
+// Purpose: Only called if network has changed since last time level
+// was loaded
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkManager::LoadNetworkGraph( void )
+{
+ // ---------------------------------------------------
+ // If I'm in edit mode don't load, always recalculate
+ // ---------------------------------------------------
+ DevMsg( "Loading AI graph\n" );
+ if (engine->IsInEditMode())
+ {
+ DevMsg( "Not loading AI due to edit mode\n" );
+ return;
+ }
+
+ if ( !g_pGameRules->FAllowNPCs() )
+ {
+ DevMsg( "Not loading AI due to games rules\n" );
+ return;
+ }
+
+ DevMsg( "Step 1 loading\n" );
+
+ // -----------------------------
+ // Make sure directories have been made
+ // -----------------------------
+ char szNrpFilename[MAX_PATH];// text node report filename
+ Q_strncpy( szNrpFilename, "maps" ,sizeof(szNrpFilename));
+ filesystem->CreateDirHierarchy( szNrpFilename, "DEFAULT_WRITE_PATH" );
+ Q_strncat( szNrpFilename, "/graphs", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
+ filesystem->CreateDirHierarchy( szNrpFilename, "DEFAULT_WRITE_PATH" );
+
+ Q_strncat( szNrpFilename, "/", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
+ Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
+ Q_strncat( szNrpFilename, IsX360() ? ".360.ain" : ".ain", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
+
+ MEM_ALLOC_CREDIT();
+
+ // Read the file in one gulp
+ CUtlBuffer buf;
+ bool bHaveAIN = false;
+ if ( IsX360() && g_pQueuedLoader->IsMapLoading() )
+ {
+ // .ain was loaded anonymously by bsp, should be ready
+ void *pData;
+ int nDataSize;
+ if ( g_pQueuedLoader->ClaimAnonymousJob( szNrpFilename, &pData, &nDataSize ) )
+ {
+ if ( nDataSize != 0 )
+ {
+ buf.Put( pData, nDataSize );
+ bHaveAIN = true;
+ }
+ filesystem->FreeOptimalReadBuffer( pData );
+ }
+ }
+
+
+
+ if ( !bHaveAIN && !filesystem->ReadFile( szNrpFilename, "game", buf ) )
+ {
+ DevWarning( 2, "Couldn't read %s!\n", szNrpFilename );
+ return;
+ }
+
+ DevMsg( "Checking version\n" );
+
+ // ---------------------------
+ // Check the version number
+ // ---------------------------
+ if ( buf.GetChar() == 'V' && buf.GetChar() == 'e' && buf.GetChar() == 'r' )
+ {
+ DevMsg( "AI node graph %s is out of date\n", szNrpFilename );
+ return;
+ }
+
+ DevMsg( "Passed first ver check\n" );
+
+
+ buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
+
+ int version = buf.GetInt();
+ DevMsg( "Got version %d\n", version );
+
+ if ( version != AINET_VERSION_NUMBER)
+ {
+ DevMsg( "AI node graph %s is out of date\n", szNrpFilename );
+ return;
+ }
+
+ int mapversion = buf.GetInt();
+ DevMsg( "Map version %d\n", mapversion );
+
+ if ( mapversion != gpGlobals->mapversion && !g_ai_norebuildgraph.GetBool() )
+ {
+ bool bOK = false;
+
+ const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" );
+ char szLoweredGameDir[256];
+ Q_strncpy( szLoweredGameDir, pGameDir, sizeof( szLoweredGameDir ) );
+ Q_strlower( szLoweredGameDir );
+
+ // hack for shipped ep1 and hl2 maps
+ // they were rebuilt a week after they were actually shipped so allow the slightly
+ // older node graphs to load for these maps
+ if ( !V_stricmp( szLoweredGameDir, "hl2" ) || !V_stricmp( szLoweredGameDir, "episodic" ) )
+ {
+ bOK = true;
+ }
+
+ if ( !bOK )
+ {
+ DevMsg( "AI node graph %s is out of date (map version changed)\n", szNrpFilename );
+ return;
+ }
+ }
+
+ DevMsg( "Done version checks\n" );
+
+ // ----------------------------------------
+ // Get the network size and allocate space
+ // ----------------------------------------
+ int numNodes = buf.GetInt();
+
+ if ( numNodes > MAX_NODES || numNodes < 0 )
+ {
+ Error( "AI node graph %s is corrupt\n", szNrpFilename );
+ DevMsg( "%s", (const char *)buf.Base() );
+ DevMsg( "\n" );
+ Assert( 0 );
+ return;
+ }
+
+ DevMsg( "Finishing load\n" );
+
+
+ // ------------------------------------------------------------------------
+ // If in wc_edit mode allocate extra space for nodes that might be created
+ // ------------------------------------------------------------------------
+ if ( engine->IsInEditMode() )
+ {
+ numNodes = MAX( numNodes, 1024 );
+ }
+
+ m_pNetwork->m_pAInode = new CAI_Node*[MAX( numNodes, 1 )];
+ memset( m_pNetwork->m_pAInode, 0, sizeof( CAI_Node* ) * MAX( numNodes, 1 ) );
+
+ // -------------------------------
+ // Load all the nodes to the file
+ // -------------------------------
+ int node;
+ for ( node = 0; node < numNodes; node++)
+ {
+ Vector origin;
+ float yaw;
+ origin.x = buf.GetFloat();
+ origin.y = buf.GetFloat();
+ origin.z = buf.GetFloat();
+ yaw = buf.GetFloat();
+
+ CAI_Node *new_node = m_pNetwork->AddNode( origin, yaw );
+
+ buf.Get( new_node->m_flVOffset, sizeof(new_node->m_flVOffset) );
+ new_node->m_eNodeType = (NodeType_e)buf.GetChar();
+ if ( IsX360() )
+ {
+ buf.SeekGet( CUtlBuffer::SEEK_CURRENT, 3 );
+ }
+
+ new_node->m_eNodeInfo = buf.GetUnsignedShort();
+ new_node->m_zone = buf.GetShort();
+ }
+
+ // -------------------------------
+ // Load all the links to the fild
+ // -------------------------------
+ int totalNumLinks = buf.GetInt();
+
+ for (int link = 0; link < totalNumLinks; link++)
+ {
+ int srcID, destID;
+
+ srcID = buf.GetShort();
+ destID = buf.GetShort();
+
+ CAI_Link *pLink = m_pNetwork->CreateLink( srcID, destID );;
+
+ byte ignored[NUM_HULLS];
+ byte *pDest = ( pLink ) ? &pLink->m_iAcceptedMoveTypes[0] : &ignored[0];
+ buf.Get( pDest, sizeof(ignored) );
+ }
+
+ // -------------------------------
+ // Load WC lookup table
+ // -------------------------------
+ delete [] GetEditOps()->m_pNodeIndexTable;
+ GetEditOps()->m_pNodeIndexTable = new int[MAX( m_pNetwork->m_iNumNodes, 1 )];
+ memset( GetEditOps()->m_pNodeIndexTable, 0, sizeof( int ) *MAX( m_pNetwork->m_iNumNodes, 1 ) );
+
+ for (node = 0; node < m_pNetwork->m_iNumNodes; node++)
+ {
+ GetEditOps()->m_pNodeIndexTable[node] = buf.GetInt();
+ }
+
+
+#if 1
+ CUtlRBTree<int> usedIds;
+ CUtlRBTree<int> reportedIds;
+ SetDefLessFunc( usedIds );
+ SetDefLessFunc( reportedIds );
+
+ bool printedHeader = false;
+
+ for (node = 0; node < m_pNetwork->m_iNumNodes; node++)
+ {
+ int editorId = GetEditOps()->m_pNodeIndexTable[node];
+ if ( editorId != NO_NODE )
+ {
+ if ( usedIds.Find( editorId ) != usedIds.InvalidIndex() )
+ {
+ if ( !printedHeader )
+ {
+ Warning( "** Duplicate Hammer Node IDs: " );
+ printedHeader = true;
+ }
+
+ if ( reportedIds.Find( editorId ) == reportedIds.InvalidIndex() )
+ {
+ DevMsg( "%d, ", editorId );
+ reportedIds.Insert( editorId );
+ }
+ }
+ else
+ usedIds.Insert( editorId );
+ }
+ }
+
+ if ( printedHeader )
+ DevMsg( "\n** Should run \"Check For Problems\" on the VMF then verify dynamic links\n" );
+#endif
+
+ gm_fNetworksLoaded = true;
+ CAI_DynamicLink::gm_bInitialized = false;
+}
+
+/* Keep this around for debugging
+//-----------------------------------------------------------------------------
+// Purpose: Only called if network has changed since last time level
+// was loaded
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CAI_NetworkManager::LoadNetworkGraph( void )
+{
+ // ---------------------------------------------------
+ // If I'm in edit mode don't load, always recalculate
+ // ---------------------------------------------------
+ if (engine->IsInEditMode())
+ {
+ return;
+ }
+
+ // -----------------------------
+ // Make sure directories have been made
+ // -----------------------------
+ char szNrpFilename [MAX_PATH];// text node report filename
+ Q_strncpy( szNrpFilename, "maps" ,sizeof(szNrpFilename));
+ filesystem->CreateDirHierarchy( szNrpFilename );
+ Q_strncat( szNrpFilename, "/graphs", COPY_ALL_CHARACTERS );
+ filesystem->CreateDirHierarchy( szNrpFilename );
+
+ Q_strncat( szNrpFilename, "/", COPY_ALL_CHARACTERS );
+ Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), COPY_ALL_CHARACTERS );
+ Q_strncat( szNrpFilename, ".ain", COPY_ALL_CHARACTERS );
+
+ FileHandle_t file = filesystem->Open ( szNrpFilename, "r" );
+
+ // -----------------------------
+ // Make sure the file opened ok
+ // -----------------------------
+ if ( !file )
+ {
+ DevWarning( 2, "Couldn't create %s!\n", szNrpFilename );
+ return;
+ }
+
+ // ---------------------------
+ // Check the version number
+ // ---------------------------
+ char temps[256];
+ int version;
+ fscanf(file,"%255s",&temps);
+ fscanf(file, "%i\n",&version);
+ if (version!=AINET_VERSION_NUMBER)
+ {
+ return;
+ }
+
+ // ----------------------------------------
+ // Get the network size and allocate space
+ // ----------------------------------------
+ int numNodes;
+ fscanf(file,"%255s",&temps);
+ fscanf ( file, "%d\n", &numNodes);
+
+ // ------------------------------------------------------------------------
+ // If in wc_edit mode allocate extra space for nodes that might be created
+ // ------------------------------------------------------------------------
+ if ( engine->IsInEditMode() )
+ {
+ numNodes = MAX( numNodes, 1024 );
+ }
+
+ m_pAInode = new CAI_Node*[numNodes];
+ if ( !m_pAInode )
+ {
+ Warning( "LoadNetworkGraph: Not enough memory to create %i nodes\n", numNodes );
+ Assert(0);
+ return;
+ }
+
+ // -------------------------------
+ // Load all the nodes to the file
+ // -------------------------------
+ for (int node = 0; node < numNodes; node++)
+ {
+ CAI_Node *new_node = AddNode();
+
+ Vector origin;
+ fscanf(file,"%255s",&temps);
+ fscanf(file, "%f,%f,%f\n", &new_node->GetOrigin().x, &new_node->GetOrigin().y, &new_node->GetOrigin().z );
+ for (int hull =0;hull<NUM_HULLS;hull++)
+ {
+ fscanf(file,"%255s",&temps);
+ fscanf(file, "%f\n", &new_node->m_flVOffset[hull]);
+ }
+ fscanf(file,"%255s",&temps);
+ fscanf(file, "%d\n", &new_node->m_eHintType );
+ fscanf(file,"%255s",&temps);
+ fscanf(file, "%f\n", &new_node->GetYaw() );
+ fscanf(file,"%255s",&temps);
+ fscanf(file, "%d\n",&new_node->GetType());
+ fscanf(file,"%255s",&temps);
+ fscanf(file, "%d\n",&new_node->m_eNodeInfo);
+
+ fscanf(file,"%255s",&temps);
+ new_node->m_pNeighborBS = new CVarBitVec(numNodes);
+ new_node->m_pNeighborBS->LoadBitString(file);
+
+ fscanf(file,"%255s",&temps);
+ new_node->m_pVisibleBS = new CVarBitVec(numNodes);
+ new_node->m_pVisibleBS->LoadBitString(file);
+
+ fscanf(file,"%255s",&temps);
+ new_node->m_pConnectedBS = new CVarBitVec(numNodes);
+ new_node->m_pConnectedBS->LoadBitString(file);
+
+ fscanf(file,"%255s",&temps);
+ int numLinks;
+ fscanf (file, "%4d",&numLinks);
+
+ // ------------------------------------------------------------------------
+ // If in wc_edit mode allocate extra space for nodes that might be created
+ // ------------------------------------------------------------------------
+ if ( engine->IsInEditMode() )
+ {
+ numLinks = AI_MAX_NODE_LINKS;
+ }
+
+ //Assert ( numLinks >= 1 );
+ new_node->AllocateLinkSpace( numLinks );
+ }
+
+ // -------------------------------
+ // Load all the links to the fild
+ // -------------------------------
+ int totalNumLinks;
+ fscanf(file,"%255s",&temps);
+ fscanf ( file, "%d\n",&totalNumLinks);
+
+ for (int link = 0; link < totalNumLinks; link++)
+ {
+ CAI_Link *new_link = new CAI_Link;
+
+ fscanf(file,"%255s",&temps);
+ fscanf ( file, "%4d\n", &new_link->m_iSrcID);
+
+ fscanf(file,"%255s",&temps);
+ fscanf ( file, "%4d\n", &new_link->m_iDestID);
+
+ for (int hull =0;hull<NUM_HULLS;hull++)
+ {
+ fscanf(file,"%255s",&temps);
+ fscanf ( file, "%d\n", &new_link->m_iAcceptedMoveTypes[hull]);
+ }
+ // Now add link to source and destination nodes
+ m_pAInode[new_link->m_iSrcID]->AddLink(new_link);
+ m_pAInode[new_link->m_iDestID]->AddLink(new_link);
+ }
+
+ // -------------------------------
+ // Load WC lookup table
+ // -------------------------------
+ m_pNodeIndexTable = new int[m_iNumNodes];
+
+ for (node = 0; node < m_iNumNodes; node++)
+ {
+ fscanf( file, "%d\n",&m_pNodeIndexTable[node]);
+ }
+
+ CAI_NetworkManager::NetworksLoaded() = true;
+ fclose(file);
+}
+*/
+
+//-----------------------------------------------------------------------------
+// Purpose: Deletes all AINetworks from memory
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkManager::DeleteAllAINetworks(void)
+{
+ CAI_DynamicLink::gm_bInitialized = false;
+ gm_fNetworksLoaded = false;
+ g_pBigAINet = NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Only called if network has changed since last time level
+// was loaded
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkManager::BuildNetworkGraph( void )
+{
+ if ( m_bDontSaveGraph )
+ return;
+
+ CAI_DynamicLink::gm_bInitialized = false;
+ g_AINetworkBuilder.Build( m_pNetwork );
+
+ // If I'm loading for the first time save. Otherwise I'm
+ // doing a wc edit and I don't want to save
+ if (!CAI_NetworkManager::NetworksLoaded())
+ {
+ SaveNetworkGraph();
+
+ gm_fNetworksLoaded = true;
+ }
+}
+
+//------------------------------------------------------------------------------
+bool g_bAIDisabledByUser = false;
+
+void CAI_NetworkManager::InitializeAINetworks()
+{
+ // For not just create a single AI Network called "BigNet"
+ // At some later point we may have mulitple AI networks
+ CAI_NetworkManager *pNetwork;
+ g_pAINetworkManager = pNetwork = CREATE_ENTITY( CAI_NetworkManager, "ai_network" );
+ pNetwork->AddEFlags( EFL_KEEP_ON_RECREATE_ENTITIES );
+ g_pBigAINet = pNetwork->GetNetwork();
+ pNetwork->SetName( AllocPooledString("BigNet") );
+ pNetwork->Spawn();
+ if ( engine->IsInEditMode() )
+ {
+ g_ai_norebuildgraph.SetValue( 0 );
+ }
+ if ( CAI_NetworkManager::IsAIFileCurrent( STRING( gpGlobals->mapname ) ) )
+ {
+ pNetwork->LoadNetworkGraph();
+ if ( !g_bAIDisabledByUser )
+ {
+ CAI_BaseNPC::m_nDebugBits &= ~bits_debugDisableAI;
+ }
+ }
+
+ // Reset node counter used during load
+ CNodeEnt::m_nNodeCount = 0;
+
+ pNetwork->SetThink( &CAI_NetworkManager::DelayedInit );
+ pNetwork->SetNextThink( gpGlobals->curtime );
+}
+
+// UNDONE: Where should this be defined?
+#ifndef MAX_PATH
+#define MAX_PATH 256
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if the AINetwork data files are up to date
+//-----------------------------------------------------------------------------
+
+bool CAI_NetworkManager::IsAIFileCurrent ( const char *szMapName )
+{
+ char szBspFilename[MAX_PATH];
+ char szGraphFilename[MAX_PATH];
+
+ if ( !g_pGameRules->FAllowNPCs() )
+ {
+ return false;
+ }
+
+ if ( IsX360() && ( filesystem->GetDVDMode() == DVDMODE_STRICT ) )
+ {
+ // dvd build process validates and guarantees correctness, timestamps are allowed to be wrong
+ return true;
+ }
+
+ {
+ const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" );
+ char szLoweredGameDir[256];
+ Q_strncpy( szLoweredGameDir, pGameDir, sizeof( szLoweredGameDir ) );
+ Q_strlower( szLoweredGameDir );
+
+ if ( !V_stricmp( szLoweredGameDir, "hl2" ) || !V_stricmp( szLoweredGameDir, "episodic" ) || !V_stricmp( szLoweredGameDir, "ep2" ) || !V_stricmp( szLoweredGameDir, "portal" ) || !V_stricmp( szLoweredGameDir, "lostcoast" ) )
+ {
+ // we shipped good node graphs for our games
+ return true;
+ }
+ }
+
+ Q_snprintf( szBspFilename, sizeof( szBspFilename ), "maps/%s%s.bsp" ,szMapName, GetPlatformExt() );
+ Q_snprintf( szGraphFilename, sizeof( szGraphFilename ), "maps/graphs/%s%s.ain", szMapName, GetPlatformExt() );
+
+ int iCompare;
+ if ( engine->CompareFileTime( szBspFilename, szGraphFilename, &iCompare ) )
+ {
+ if ( iCompare > 0 )
+ {
+ // BSP file is newer.
+ if ( g_ai_norebuildgraph.GetInt() )
+ {
+ // The user has specified that they wish to override the
+ // rebuilding of outdated nodegraphs (see top of this file)
+ if ( filesystem->FileExists( szGraphFilename ) )
+ {
+ // Display these messages only if the graph exists, and the
+ // user is asking to override the rebuilding. If the graph does
+ // not exist, we're going to build it whether the user wants to or
+ // not.
+ DevMsg( 2, ".AIN File will *NOT* be updated. User Override.\n\n" );
+ DevMsg( "\n*****Node Graph Rebuild OVERRIDDEN by user*****\n\n" );
+ }
+ return true;
+ }
+ else
+ {
+ // Graph is out of date. Rebuild at usual.
+ DevMsg( 2, ".AIN File will be updated\n\n" );
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+//------------------------------------------------------------------------------
+
+void CAI_NetworkManager::Spawn ( void )
+{
+ SetSolid( SOLID_NONE );
+ SetMoveType( MOVETYPE_NONE );
+}
+
+//------------------------------------------------------------------------------
+
+void CAI_NetworkManager::DelayedInit( void )
+{
+ if ( !g_pGameRules->FAllowNPCs() )
+ {
+ SetThink ( NULL );
+ return;
+ }
+
+ if ( !g_ai_norebuildgraph.GetInt() )
+ {
+ // ----------------------------------------------------------
+ // Actually enter DelayedInit twice when rebuilding the
+ // node graph. The first time through we just print the
+ // warning message. We only actually do the rebuild on
+ // the second pass to make sure the message hits the screen
+ // ----------------------------------------------------------
+ if (m_bNeedGraphRebuild)
+ {
+ Assert( !m_bDontSaveGraph );
+
+ BuildNetworkGraph(); // For now only one AI Network
+
+ if (engine->IsInEditMode())
+ {
+ engine->ServerCommand("exec map_edit.cfg\n");
+ }
+
+ SetThink ( NULL );
+ if ( !g_bAIDisabledByUser )
+ {
+ CAI_BaseNPC::m_nDebugBits &= ~bits_debugDisableAI;
+ }
+ }
+
+
+ // --------------------------------------------
+ // If I haven't loaded a network, or I'm in
+ // WorldCraft edit mode rebuild the network
+ // --------------------------------------------
+ else if ( !m_bDontSaveGraph && ( !CAI_NetworkManager::NetworksLoaded() || engine->IsInEditMode() ) )
+ {
+#ifdef _WIN32
+ // --------------------------------------------------------
+ // If in edit mode start WC session and make sure we are
+ // running the same map in WC and the engine
+ // --------------------------------------------------------
+ if (engine->IsInEditMode())
+ {
+ int status = Editor_BeginSession(STRING(gpGlobals->mapname), gpGlobals->mapversion, false);
+ if (status == Editor_NotRunning)
+ {
+ DevMsg("\nAborting map_edit\nWorldcraft not running...\n\n");
+ UTIL_CenterPrintAll( "Worldcraft not running...\n" );
+ engine->ServerCommand("disconnect\n");
+ SetThink(NULL);
+ return;
+ }
+ else if (status == Editor_BadCommand)
+ {
+ DevMsg("\nAborting map_edit\nWC/Engine map versions different...\n\n");
+ UTIL_CenterPrintAll( "WC/Engine map versions different...\n" );
+ engine->ServerCommand("disconnect\n");
+ SetThink(NULL);
+ return;
+ }
+ else
+ {
+ // Increment version number when session begins
+ gpGlobals->mapversion++;
+ }
+ }
+#endif
+
+ DevMsg( "Node Graph out of Date. Rebuilding... (%d, %d, %d)\n", (int)m_bDontSaveGraph, (int)!CAI_NetworkManager::NetworksLoaded(), (int) engine->IsInEditMode() );
+ UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding...\n" );
+ m_bNeedGraphRebuild = true;
+ g_pAINetworkManager->SetNextThink( gpGlobals->curtime + 1 );
+ return;
+ }
+
+ }
+
+ // --------------------------------------------
+ // Initialize any dynamic links
+ // --------------------------------------------
+ CAI_DynamicLink::InitDynamicLinks();
+ FixupHints();
+
+ GetEditOps()->OnInit();
+
+ m_fInitalized = true;
+
+ if ( g_AI_Manager.NumAIs() != 0 && g_pBigAINet->NumNodes() == 0 )
+ DevMsg( "WARNING: Level contains NPCs but has no path nodes\n" );
+}
+
+//------------------------------------------------------------------------------
+
+void CAI_NetworkManager::FixupHints()
+{
+ AIHintIter_t iter;
+ CAI_Hint *pHint = CAI_HintManager::GetFirstHint( &iter );
+ while ( pHint )
+ {
+ pHint->FixupTargetNode();
+ pHint = CAI_HintManager::GetNextHint( &iter );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// CAI_NetworkEditTools
+//-----------------------------------------------------------------------------
+
+CAI_Node* CAI_NetworkEditTools::m_pLastDeletedNode = NULL; // For undo in wc edit mode
+int CAI_NetworkEditTools::m_iHullDrawNum = HULL_HUMAN; // Which hulls to draw
+int CAI_NetworkEditTools::m_iVisibilityNode = NO_NODE;
+int CAI_NetworkEditTools::m_iGConnectivityNode = NO_NODE;
+bool CAI_NetworkEditTools::m_bAirEditMode = false;
+bool CAI_NetworkEditTools::m_bLinkEditMode = false;
+float CAI_NetworkEditTools::m_flAirEditDistance = 300;
+
+#ifdef AI_PERF_MON
+ // Performance stats (only for development)
+ int CAI_NetworkEditTools::m_nPerfStatNN = 0;
+ int CAI_NetworkEditTools::m_nPerfStatPB = 0;
+ float CAI_NetworkEditTools::m_fNextPerfStatTime = -1;
+#endif
+
+//------------------------------------------------------------------------------
+
+void CAI_NetworkEditTools::OnInit()
+{
+ // --------------------------------------------
+ // If I'm not in edit mode delete WC ID table
+ // --------------------------------------------
+ if ( !engine->IsInEditMode() )
+ {
+// delete[] m_pNodeIndexTable; // For now only one AI Network called "BigNet"
+// m_pNodeIndexTable = NULL;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Given a WorldCraft node ID, return the associated engine ID
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+int CAI_NetworkEditTools::GetNodeIdFromWCId( int nWCId )
+{
+ if ( nWCId == -1 )
+ return -1;
+
+ if (!m_pNodeIndexTable)
+ {
+ DevMsg("ERROR: Trying to get WC ID with no table!\n");
+ return -1;
+ }
+
+ if (!m_pNetwork->NumNodes())
+ {
+ DevMsg("ERROR: Trying to get WC ID with no network!\n");
+ return -1;
+ }
+
+ for (int i=0;i<m_pNetwork->NumNodes();i++)
+ {
+ if (m_pNodeIndexTable[i] == nWCId)
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+
+int CAI_NetworkEditTools::GetWCIdFromNodeId( int nNodeId )
+{
+ if ( nNodeId == -1 || nNodeId >= m_pNetwork->NumNodes() )
+ return -1;
+ return m_pNodeIndexTable[nNodeId];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the nearest ainode that is faced from the given location
+// and within the angular threshold (ignores worldspawn).
+// DO NOT USE FOR ANY RUN TIME RELEASE CODE
+// Used for selection of nodes in debugging display only!
+//
+//-----------------------------------------------------------------------------
+
+CAI_Node *CAI_NetworkEditTools::FindAINodeNearestFacing( const Vector &origin, const Vector &facing, float threshold, int nNodeType)
+{
+ float bestDot = threshold;
+ CAI_Node *best = NULL;
+
+ CAI_Network* aiNet = g_pBigAINet;
+
+ for (int node =0; node < aiNet->NumNodes();node++)
+ {
+ if (aiNet->GetNode(node)->GetType() != NODE_DELETED)
+ {
+ // Pick nodes that are in the current editing type
+ if ( nNodeType == NODE_ANY ||
+ nNodeType == aiNet->GetNode(node)->GetType() )
+ {
+ // Make vector to node
+ Vector to_node = (aiNet->GetNode(node)->GetPosition(m_iHullDrawNum) - origin);
+
+ VectorNormalize( to_node );
+ float dot = DotProduct (facing , to_node );
+ if (dot > bestDot)
+ {
+ // Make sure I have a line of sight to it
+ trace_t tr;
+ AI_TraceLine ( origin, aiNet->GetNode(node)->GetPosition(m_iHullDrawNum),
+ MASK_BLOCKLOS, NULL, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction == 1.0 )
+ {
+ bestDot = dot;
+ best = aiNet->GetNode(node);
+ }
+ }
+ }
+ }
+ }
+ return best;
+}
+
+
+Vector PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint)
+{
+ Vector vEndToStart = (vEndPos - vStartPos);
+ Vector vOrgToStart = (vPoint - vStartPos);
+ float fNumerator = DotProduct(vEndToStart,vOrgToStart);
+ float fDenominator = vEndToStart.Length() * vOrgToStart.Length();
+ float fIntersectDist = vOrgToStart.Length()*(fNumerator/fDenominator);
+ VectorNormalize( vEndToStart );
+ Vector vIntersectPos = vStartPos + vEndToStart * fIntersectDist;
+
+ return vIntersectPos;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the nearest ainode that is faced from the given location
+// and within the angular threshold (ignores worldspawn).
+// DO NOT USE FOR ANY RUN TIME RELEASE CODE
+// Used for selection of nodes in debugging display only!
+//-----------------------------------------------------------------------------
+
+CAI_Link *CAI_NetworkEditTools::FindAILinkNearestFacing( const Vector &vOrigin, const Vector &vFacing, float threshold)
+{
+ float bestDot = threshold;
+ CAI_Link *best = NULL;
+
+ CAI_Network* aiNet = g_pBigAINet;
+
+ for (int node =0; node < aiNet->NumNodes();node++)
+ {
+ if (aiNet->GetNode(node)->GetType() != NODE_DELETED)
+ {
+ // Pick nodes that are in the current editing type
+ if (( m_bAirEditMode && aiNet->GetNode(node)->GetType() == NODE_AIR) ||
+ (!m_bAirEditMode && aiNet->GetNode(node)->GetType() == NODE_GROUND))
+ {
+ // Go through each link
+ for (int link=0; link < aiNet->GetNode(node)->NumLinks();link++)
+ {
+ CAI_Link *nodeLink = aiNet->GetNode(node)->GetLinkByIndex(link);
+
+ // Find position on link that I am looking
+ int endID = nodeLink->DestNodeID(node);
+ Vector startPos = aiNet->GetNode(node)->GetPosition(m_iHullDrawNum);
+ Vector endPos = aiNet->GetNode(endID)->GetPosition(m_iHullDrawNum);
+ Vector vNearest = PointOnLineNearestPoint(startPos, endPos, vOrigin);
+
+ // Get angle between viewing dir. and nearest point on line
+ Vector vOriginToNearest = (vNearest - vOrigin);
+ float fNumerator = DotProduct(vOriginToNearest,vFacing);
+ float fDenominator = vOriginToNearest.Length();
+ float fAngleToNearest = acos(fNumerator/fDenominator);
+
+ // If not facing the line reject
+ if (fAngleToNearest > 1.57)
+ {
+ continue;
+ }
+
+ // Calculate intersection of facing direction to nearest point
+ float fIntersectDist = vOriginToNearest.Length() * tan(fAngleToNearest);
+ Vector dir = endPos-startPos;
+ float fLineLen = VectorNormalize( dir );
+ Vector vIntersection = vNearest + (fIntersectDist * dir);
+
+ // Reject of beyond end of line
+ if (((vIntersection - startPos).Length() > fLineLen) ||
+ ((vIntersection - endPos ).Length() > fLineLen) )
+ {
+ continue;
+ }
+
+ // Now test dot to the look position
+ Vector toLink = vIntersection - vOrigin;
+ VectorNormalize(toLink);
+ float lookDot = DotProduct (vFacing , toLink);
+ if (lookDot > bestDot)
+ {
+ // Make sure I have a line of sight to it
+ trace_t tr;
+ AI_TraceLine ( vOrigin, vIntersection, MASK_BLOCKLOS, NULL, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction == 1.0 )
+ {
+ bestDot = lookDot;
+ best = nodeLink;
+ }
+ }
+ }
+ }
+ }
+ }
+ return best;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Used for WC edit more. Marks that the network should be
+// rebuild and turns of any displays that have been invalidated
+// as the network is now out of date
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CAI_NetworkEditTools::SetRebuildFlags( void )
+{
+ m_debugNetOverlays |= bits_debugNeedRebuild;
+ m_debugNetOverlays &= ~bits_debugOverlayConnections;
+ m_debugNetOverlays &= ~bits_debugOverlayGraphConnect;
+ m_debugNetOverlays &= ~bits_debugOverlayVisibility;
+ m_debugNetOverlays &= ~bits_debugOverlayHulls;
+
+ // Not allowed to edit links when graph outdated
+ m_bLinkEditMode = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Used for WC edit more. After node graph has been rebuild
+// marks it as so and turns connection display back on
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CAI_NetworkEditTools::ClearRebuildFlags( void )
+{
+ m_debugNetOverlays |= bits_debugOverlayConnections;
+
+ // ------------------------------------------
+ // Clear all rebuild flags in nodes
+ // ------------------------------------------
+ for (int i = 0; i < m_pNetwork->NumNodes(); i++)
+ {
+ m_pNetwork->GetNode(i)->m_eNodeInfo &= ~bits_NODE_WC_CHANGED;
+ m_pNetwork->GetNode(i)->ClearNeedsRebuild();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the next hull to draw, or none if at end of hulls
+//-----------------------------------------------------------------------------
+void CAI_NetworkEditTools::DrawNextHull(const char *ainet_name)
+{
+ m_iHullDrawNum++;
+ if (m_iHullDrawNum == NUM_HULLS)
+ {
+ m_iHullDrawNum = 0;
+ }
+
+ // Recalculate usable nodes for current hull
+ g_pAINetworkManager->GetEditOps()->RecalcUsableNodesForHull();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_NetworkEditTools::DrawHull(Hull_t eHull)
+{
+ m_iHullDrawNum = eHull;
+ if (m_iHullDrawNum >= NUM_HULLS)
+ {
+ m_iHullDrawNum = 0;
+ }
+
+ // Recalculate usable nodes for current hull
+ g_pAINetworkManager->GetEditOps()->RecalcUsableNodesForHull();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Used just for debug display, to color nodes grey that the
+// currently selected hull size can't use.
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkEditTools::RecalcUsableNodesForHull(void)
+{
+ // -----------------------------------------------------
+ // Use test hull to check hull sizes
+ // -----------------------------------------------------
+ CAI_TestHull *m_pTestHull = CAI_TestHull::GetTestHull();
+ m_pTestHull->GetNavigator()->SetNetwork( g_pBigAINet );
+ m_pTestHull->SetHullType((Hull_t)m_iHullDrawNum);
+ m_pTestHull->SetHullSizeNormal();
+
+ for (int node=0;node<m_pNetwork->NumNodes();node++)
+ {
+ if ( ( m_pNetwork->GetNode(node)->m_eNodeInfo & ( HullToBit( (Hull_t)m_iHullDrawNum ) << NODE_ENT_FLAGS_SHIFT ) ) ||
+ m_pTestHull->GetNavigator()->CanFitAtNode(node))
+ {
+ m_pNetwork->GetNode(node)->m_eNodeInfo &= ~bits_NODE_WONT_FIT_HULL;
+ }
+ else
+ {
+ m_pNetwork->GetNode(node)->m_eNodeInfo |= bits_NODE_WONT_FIT_HULL;
+ }
+ }
+ CAI_TestHull::ReturnTestHull();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets debug bits
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkEditTools::SetDebugBits(const char *ainet_name,int debug_bit)
+{
+ CAI_NetworkEditTools *pEditOps = g_pAINetworkManager->GetEditOps();
+ if ( !pEditOps )
+ return;
+
+ if (debug_bit & bits_debugOverlayNodes)
+ {
+ if (pEditOps->m_debugNetOverlays & bits_debugOverlayNodesLev2)
+ {
+ pEditOps->m_debugNetOverlays &= ~bits_debugOverlayNodes;
+ pEditOps->m_debugNetOverlays &= ~bits_debugOverlayNodesLev2;
+ }
+ else if (pEditOps->m_debugNetOverlays & bits_debugOverlayNodes)
+ {
+ pEditOps->m_debugNetOverlays |= bits_debugOverlayNodesLev2;
+ }
+ else
+ {
+ pEditOps->m_debugNetOverlays |= bits_debugOverlayNodes;
+
+ // Recalculate usable nodes for current hull
+ pEditOps->RecalcUsableNodesForHull();
+ }
+ }
+ else if (pEditOps->m_debugNetOverlays & debug_bit)
+ {
+ pEditOps->m_debugNetOverlays &= ~debug_bit;
+ }
+ else
+ {
+ pEditOps->m_debugNetOverlays |= debug_bit;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Draws edit display info on screen
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkEditTools::DrawEditInfoOverlay(void)
+{
+ hudtextparms_s tTextParam;
+ tTextParam.x = 0.8;
+ tTextParam.y = 0.8;
+ tTextParam.effect = 0;
+ tTextParam.r1 = 255;
+ tTextParam.g1 = 255;
+ tTextParam.b1 = 255;
+ tTextParam.a1 = 255;
+ tTextParam.r2 = 255;
+ tTextParam.g2 = 255;
+ tTextParam.b2 = 255;
+ tTextParam.a2 = 255;
+ tTextParam.fadeinTime = 0;
+ tTextParam.fadeoutTime = 0;
+ tTextParam.holdTime = 1;
+ tTextParam.fxTime = 0;
+ tTextParam.channel = 0;
+
+ char hullTypeTxt[50];
+ char nodeTypeTxt[50];
+ char editTypeTxt[50];
+ char outTxt[255];
+
+ Q_snprintf(hullTypeTxt,sizeof(hullTypeTxt)," %s",NAI_Hull::Name(m_iHullDrawNum));
+ Q_snprintf(outTxt,sizeof(outTxt),"Displaying:\n%s\n\n", hullTypeTxt);
+
+ if (engine->IsInEditMode())
+ {
+ char outTxt2[255];
+ Q_snprintf(nodeTypeTxt,sizeof(nodeTypeTxt)," %s (l)", m_bLinkEditMode ? "Links":"Nodes");
+ Q_snprintf(editTypeTxt,sizeof(editTypeTxt)," %s (m)", m_bAirEditMode ? "Air":"Ground");
+ Q_snprintf(outTxt2,sizeof(outTxt2),"Editing:\n%s\n%s", editTypeTxt,nodeTypeTxt);
+ Q_strncat(outTxt,outTxt2,sizeof(outTxt), COPY_ALL_CHARACTERS);
+
+ // Print in red if network needs rebuilding
+ if (m_debugNetOverlays & bits_debugNeedRebuild)
+ {
+ tTextParam.g1 = 0;
+ tTextParam.b1 = 0;
+ }
+ }
+
+ UTIL_HudMessageAll( tTextParam, outTxt );
+
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Draws AINetwork on the screen
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkEditTools::DrawAINetworkOverlay(void)
+{
+ // ------------------------------------
+ // If network isn't loaded yet return
+ // ------------------------------------
+ if (!CAI_NetworkManager::NetworksLoaded())
+ {
+ return;
+ }
+
+ // ----------------------------------------------
+ // So we don't fill up the client message queue
+ // with node drawing messages, only send them
+ // in chuncks
+ // ----------------------------------------------
+ static int startDrawNode = 0;
+ static int endDrawNode = 0;
+ static float flDrawDuration;
+ endDrawNode = startDrawNode + 20;
+ flDrawDuration = 0.1 * (m_pNetwork->NumNodes()-1)/20;
+ if ( flDrawDuration < .1 )
+ flDrawDuration = .1;
+ if (endDrawNode > m_pNetwork->NumNodes())
+ {
+ endDrawNode = m_pNetwork->NumNodes();
+ }
+
+ // ---------------------
+ // Draw grid
+ // ---------------------
+ if (m_debugNetOverlays & bits_debugOverlayGrid)
+ {
+ // Trace a line to where player is looking
+ CBasePlayer* pPlayer = UTIL_PlayerByIndex(CBaseEntity::m_nDebugPlayer);
+
+ if (pPlayer)
+ {
+ Vector vForward;
+ Vector vSource = pPlayer->EyePosition();
+ pPlayer->EyeVectors( &vForward );
+
+ trace_t tr;
+ AI_TraceLine ( vSource, vSource + vForward * 2048, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr);
+
+ float dotPr = DotProduct(Vector(0,0,1),tr.plane.normal);
+ if (tr.fraction != 1.0 && dotPr > 0.5)
+ {
+ NDebugOverlay::Grid( tr.endpos + Vector(0,0,1) );
+ }
+ }
+ }
+
+ // --------------------
+ CAI_Node **pAINode = m_pNetwork->AccessNodes();
+
+ // --------------------
+ // Draw the graph connectivity
+ // ---------------------
+ if (m_debugNetOverlays & bits_debugOverlayGraphConnect)
+ {
+ // ---------------------------------------------------
+ // If network needs rebuilding do so before display
+ // --------------------------------------------------
+ if (m_debugNetOverlays & bits_debugNeedRebuild)
+ {
+ m_pManager->RebuildNetworkGraph();
+ }
+ else if (m_iGConnectivityNode != NO_NODE)
+ {
+ for (int node=0;node<m_pNetwork->NumNodes();node++)
+ {
+ if ( m_pNetwork->IsConnected( m_iGConnectivityNode, node) )
+ {
+ Vector srcPos = pAINode[m_iGConnectivityNode]->GetPosition(m_iHullDrawNum);
+ Vector desPos = pAINode[node]->GetPosition(m_iHullDrawNum);
+ NDebugOverlay::Line(srcPos, desPos, 255,0,255, false,0);
+ }
+ }
+ }
+ }
+
+ // --------------------
+ // Draw the hulls
+ // ---------------------
+ if (m_debugNetOverlays & bits_debugOverlayHulls)
+ {
+ // ---------------------------------------------------
+ // If network needs rebuilding do so before display
+ // --------------------------------------------------
+ if (m_debugNetOverlays & bits_debugNeedRebuild)
+ {
+ m_pManager->RebuildNetworkGraph();
+ }
+ else
+ {
+ for (int node=startDrawNode;node<endDrawNode;node++)
+ {
+ for (int link=0;link<pAINode[node]->NumLinks();link++)
+ {
+ // Only draw link once
+ if (pAINode[node]->GetLinkByIndex(link)->DestNodeID(node) < node)
+ {
+
+ Vector srcPos = pAINode[pAINode[node]->GetLinkByIndex(link)->m_iSrcID]->GetPosition(m_iHullDrawNum);
+ Vector desPos = pAINode[pAINode[node]->GetLinkByIndex(link)->m_iDestID]->GetPosition(m_iHullDrawNum);
+ Vector direction = desPos - srcPos;
+ float length = VectorNormalize(direction);
+ Vector hullMins = NAI_Hull::Mins(m_iHullDrawNum);
+ Vector hullMaxs = NAI_Hull::Maxs(m_iHullDrawNum);
+ hullMaxs.x = length + hullMaxs.x;
+
+ if (pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum] & bits_CAP_MOVE_FLY)
+ {
+ NDebugOverlay::BoxDirection(srcPos, hullMins, hullMaxs, direction, 100,255,255,20,flDrawDuration);
+ }
+
+ if (pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum] & bits_CAP_MOVE_CLIMB)
+ {
+ // Display as a vertical slice up the climbing surface unless dismount node
+ if (pAINode[pAINode[node]->GetLinkByIndex(link)->m_iSrcID]->GetOrigin() != pAINode[pAINode[node]->GetLinkByIndex(link)->m_iDestID]->GetOrigin())
+ {
+ hullMaxs.x = hullMaxs.x - length;
+ if (srcPos.z < desPos.z)
+ {
+ hullMaxs.z = length + hullMaxs.z;
+ }
+ else
+ {
+ hullMins.z = hullMins.z - length;
+ }
+ direction = Vector(0,1,0);
+
+ }
+ NDebugOverlay::BoxDirection(srcPos, hullMins, hullMaxs, direction, 255,0,255,20,flDrawDuration);
+ }
+
+ if (pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum] & bits_CAP_MOVE_GROUND)
+ {
+ NDebugOverlay::BoxDirection(srcPos, hullMins, hullMaxs, direction, 0,255,50,20,flDrawDuration);
+ }
+
+ else if (pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum] & bits_CAP_MOVE_JUMP)
+ {
+ NDebugOverlay::BoxDirection(srcPos, hullMins, hullMaxs, direction, 0,0,255,20,flDrawDuration);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // --------------------
+ // Draw the hints
+ // ---------------------
+ if (m_debugNetOverlays & bits_debugOverlayHints)
+ {
+ CAI_HintManager::DrawHintOverlays(flDrawDuration);
+ }
+
+ // -------------------------------
+ // Draw the nodes and connections
+ // -------------------------------
+ if (m_debugNetOverlays & (bits_debugOverlayNodes | bits_debugOverlayConnections))
+ {
+ for (int node=startDrawNode;node<endDrawNode;node++) {
+
+ // This gets expensive, so see if the node is visible to the client
+ if (pAINode[node]->GetType() != NODE_DELETED)
+ {
+ // --------------------
+ // Draw the connections
+ // ---------------------
+ if (m_debugNetOverlays & bits_debugOverlayConnections)
+ {
+ // ---------------------------------------------------
+ // If network needs rebuilding do so before display
+ // --------------------------------------------------
+ if (m_debugNetOverlays & bits_debugNeedRebuild)
+ {
+ m_pManager->RebuildNetworkGraph();
+ }
+ else
+ {
+ for (int link=0;link<pAINode[node]->NumLinks();link++) {
+
+ // Only draw link once
+ if (pAINode[node]->GetLinkByIndex(link)->DestNodeID(node) < node)
+ {
+ int srcID = pAINode[node]->GetLinkByIndex(link)->m_iSrcID;
+ int desID = pAINode[node]->GetLinkByIndex(link)->m_iDestID;
+
+ Vector srcPos = pAINode[srcID]->GetPosition(m_iHullDrawNum);
+ Vector desPos = pAINode[desID]->GetPosition(m_iHullDrawNum);
+
+ int srcType = pAINode[pAINode[node]->GetLinkByIndex(link)->m_iSrcID]->GetType();
+ int desType = pAINode[pAINode[node]->GetLinkByIndex(link)->m_iDestID]->GetType();
+
+ int linkInfo = pAINode[node]->GetLinkByIndex(link)->m_LinkInfo;
+ int moveTypes = pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum];
+
+ // when rendering, raise NODE_GROUND off the floor slighty as they seem to clip too much
+ if ( srcType == NODE_GROUND)
+ {
+ srcPos.z += 1.0;
+ }
+
+ if ( desType == NODE_GROUND)
+ {
+ desPos.z += 1.0;
+ }
+
+ // Draw in red if stale link
+ if (linkInfo & bits_LINK_STALE_SUGGESTED)
+ {
+ NDebugOverlay::Line(srcPos, desPos, 255,0,0, false, flDrawDuration);
+ }
+ // Draw in grey if link turned off
+ else if (linkInfo & bits_LINK_OFF)
+ {
+ NDebugOverlay::Line(srcPos, desPos, 100,100,100, false, flDrawDuration);
+ }
+ else if ((m_debugNetOverlays & bits_debugOverlayFlyConnections) && (moveTypes & bits_CAP_MOVE_FLY))
+ {
+ NDebugOverlay::Line(srcPos, desPos, 100,255,255, false, flDrawDuration);
+ }
+ else if (moveTypes & bits_CAP_MOVE_CLIMB)
+ {
+ NDebugOverlay::Line(srcPos, desPos, 255,0,255, false, flDrawDuration);
+ }
+ else if (moveTypes & bits_CAP_MOVE_GROUND)
+ {
+ NDebugOverlay::Line(srcPos, desPos, 0,255,50, false, flDrawDuration);
+ }
+ else if ((m_debugNetOverlays & bits_debugOverlayJumpConnections) && (moveTypes & bits_CAP_MOVE_JUMP) )
+ {
+ NDebugOverlay::Line(srcPos, desPos, 0,0,255, false, flDrawDuration);
+ }
+ else
+ { // Dark red if this hull can't use
+ bool isFly = ( srcType == NODE_AIR || desType == NODE_AIR );
+ bool isJump = true;
+ for ( int i = HULL_HUMAN; i < NUM_HULLS; i++ )
+ {
+ if ( pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[i] & ~bits_CAP_MOVE_JUMP )
+ {
+ isJump = false;
+ break;
+ }
+ }
+ if ( ( isFly && (m_debugNetOverlays & bits_debugOverlayFlyConnections) ) ||
+ ( isJump && (m_debugNetOverlays & bits_debugOverlayJumpConnections) ) ||
+ ( !isFly && !isJump ) )
+ {
+ NDebugOverlay::Line(srcPos, desPos, 100,25,25, false, flDrawDuration);
+ }
+ }
+ }
+ }
+ }
+ }
+ if (m_debugNetOverlays & bits_debugOverlayNodes)
+ {
+ int r = 255;
+ int g = 0;
+ int b = 0;
+
+ // If checking visibility base color off of visibility info
+ if (m_debugNetOverlays & bits_debugOverlayVisibility &&
+ m_iVisibilityNode != NO_NODE)
+ {
+ // ---------------------------------------------------
+ // If network needs rebuilding do so before display
+ // --------------------------------------------------
+ if (m_debugNetOverlays & bits_debugNeedRebuild)
+ {
+ m_pManager->RebuildNetworkGraph();
+ }
+ }
+
+ // If checking graph connectivity base color off of connectivity info
+ if (m_debugNetOverlays & bits_debugOverlayGraphConnect &&
+ m_iGConnectivityNode != NO_NODE)
+ {
+ // ---------------------------------------------------
+ // If network needs rebuilding do so before display
+ // --------------------------------------------------
+ if (m_debugNetOverlays & bits_debugNeedRebuild)
+ {
+ m_pManager->RebuildNetworkGraph();
+ }
+ else if (m_pNetwork->IsConnected( m_iGConnectivityNode, node) )
+ {
+ r = 0;
+ g = 0;
+ b = 255;
+ }
+ }
+ // Otherwise base color off of node type
+ else
+ {
+ // If node is new and hasn't been rebuild yet
+ if (pAINode[node]->m_eNodeInfo & bits_NODE_WC_CHANGED)
+ {
+ r = 200;
+ g = 200;
+ b = 200;
+ }
+
+ // If node doesn't fit the current hull size
+ else if (pAINode[node]->m_eNodeInfo & bits_NODE_WONT_FIT_HULL)
+ {
+ r = 255;
+ g = 25;
+ b = 25;
+ }
+
+ else if (pAINode[node]->GetType() == NODE_CLIMB)
+ {
+ r = 255;
+ g = 0;
+ b = 255;
+ }
+ else if (pAINode[node]->GetType() == NODE_AIR)
+ {
+ r = 0;
+ g = 255;
+ b = 255;
+ }
+ else if (pAINode[node]->GetType() == NODE_GROUND)
+ {
+ r = 0;
+ g = 255;
+ b = 100;
+ }
+ }
+
+
+ Vector nodePos;
+
+ nodePos = pAINode[node]->GetPosition(m_iHullDrawNum);
+
+ NDebugOverlay::Box(nodePos, Vector(-5,-5,-5), Vector(5,5,5), r,g,b,0,flDrawDuration);
+
+ // If climb node draw line in facing direction
+ if (pAINode[node]->GetType() == NODE_CLIMB)
+ {
+ Vector offsetDir = 12.0 * Vector(cos(DEG2RAD(pAINode[node]->GetYaw())),sin(DEG2RAD(pAINode[node]->GetYaw())),flDrawDuration);
+ NDebugOverlay::Line(nodePos, nodePos+offsetDir, r,g,b,false,flDrawDuration);
+ }
+
+ if ( pAINode[node]->GetHint() )
+ {
+ NDebugOverlay::Box( nodePos, Vector(-7,-7,-7), Vector(7,7,7), 255,255,0,0,flDrawDuration);
+ }
+
+ if (m_debugNetOverlays & bits_debugOverlayNodesLev2)
+ {
+ CFmtStr msg;
+
+ if ( m_pNodeIndexTable )
+ msg.sprintf("%i (wc:%i; z:%i)",node,m_pNodeIndexTable[pAINode[node]->GetId()], pAINode[node]->GetZone());
+ else
+ msg.sprintf("%i (z:%i)",node,pAINode[node]->GetZone());
+
+ Vector loc = nodePos;
+ loc.x+=6;
+ loc.y+=6;
+ loc.z+=6;
+ NDebugOverlay::Text( loc, msg, true, flDrawDuration);
+
+ // Print the hintgroup if we have one
+ if ( pAINode[node]->GetHint() )
+ {
+ msg.sprintf("%s", STRING( pAINode[node]->GetHint()->GetGroup() ));
+ loc.z-=3;
+ NDebugOverlay::Text( loc, msg, true, flDrawDuration);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // -------------------------------
+ // Identify hull being displayed
+ // -------------------------------
+ if (m_debugNetOverlays & (bits_debugOverlayNodes | bits_debugOverlayConnections | bits_debugOverlayHulls))
+ {
+ DrawEditInfoOverlay();
+ }
+
+ // ----------------------------
+ // Increment node draw chunk
+ // ----------------------------
+ startDrawNode = endDrawNode;
+ if (startDrawNode >= m_pNetwork->NumNodes())
+ {
+ startDrawNode = 0;
+ }
+
+ // ----------------------------
+ // Output performance stats
+ // ----------------------------
+#ifdef AI_PERF_MON
+ if (m_fNextPerfStatTime < gpGlobals->curtime)
+ {
+ char temp[512];
+ Q_snprintf(temp,sizeof(temp),"%3.2f NN/m\n%3.2f P/m\n",(m_nPerfStatNN/1.0),(m_nPerfStatPB/1.0));
+ UTIL_CenterPrintAll(temp);
+
+ m_fNextPerfStatTime = gpGlobals->curtime + 1;
+ m_nPerfStatNN = 0;
+ m_nPerfStatPB = 0;
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+
+CAI_NetworkEditTools::CAI_NetworkEditTools(CAI_NetworkManager *pNetworkManager)
+{
+ // ----------------------------------------------------------------------------
+ // If in wc_edit mode
+ // ----------------------------------------------------------------------------
+ if (engine->IsInEditMode())
+ {
+ // ----------------------------------------------------------------------------
+ // Allocate extra space for storing undropped node positions
+ // ----------------------------------------------------------------------------
+ m_pWCPosition = new Vector[MAX_NODES];
+ }
+ else
+ {
+ m_pWCPosition = NULL;
+ }
+
+ m_pNodeIndexTable = NULL;
+ m_debugNetOverlays = 0;
+
+ // ----------------------------------------------------------------------------
+ // Allocate table of WC Id's. If not in edit mode Deleted after initialization
+ // ----------------------------------------------------------------------------
+ m_pNodeIndexTable = new int[MAX_NODES];
+ for ( int i = 0; i < MAX_NODES; i++ )
+ m_pNodeIndexTable[i] = NO_NODE;
+ m_nNextWCIndex = 0;
+
+ m_pNetwork = pNetworkManager->GetNetwork(); // @tbd
+ m_pManager = pNetworkManager;
+
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+
+CAI_NetworkEditTools::~CAI_NetworkEditTools()
+{
+ // --------------------------------------------------------
+ // If in edit mode tell WC I'm ending my session
+ // --------------------------------------------------------
+#ifdef _WIN32
+ Editor_EndSession(false);
+#endif
+ delete[] m_pNodeIndexTable;
+}
+
+//-----------------------------------------------------------------------------
+// CAI_NetworkBuilder
+//
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkBuilder::FloodFillZone( CAI_Node **ppNodes, CAI_Node *pNode, int zone )
+{
+ pNode->SetZone( zone );
+
+ for (int link = 0; link < pNode->NumLinks(); link++)
+ {
+ CAI_Link *pLink = pNode->GetLinkByIndex(link);
+ CAI_Node *pLinkedNode = ( pLink->m_iDestID == pNode->GetId()) ? ppNodes[pLink->m_iSrcID] : ppNodes[pLink->m_iDestID];
+ if ( pLinkedNode->GetZone() == AI_NODE_ZONE_UNKNOWN )
+ FloodFillZone( ppNodes, pLinkedNode, zone );
+
+ Assert( pLinkedNode->GetZone() == pNode->GetZone() && pNode->GetZone() == zone );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkBuilder::InitZones( CAI_Network *pNetwork )
+{
+ int nNodes = pNetwork->NumNodes();
+ CAI_Node **ppNodes = pNetwork->AccessNodes();
+
+ if ( !nNodes )
+ return;
+
+ int i;
+
+ for (i = 0; i < nNodes; i++)
+ {
+ ppNodes[i]->SetZone( AI_NODE_ZONE_UNKNOWN );
+ }
+
+ // Mark solo nodes
+ for (i = 0; i < nNodes; i++)
+ {
+ if ( ppNodes[i]->NumLinks() == 0 )
+ ppNodes[i]->SetZone( AI_NODE_ZONE_SOLO );
+ }
+
+ int curZone = AI_NODE_FIRST_ZONE;
+
+ for (i = 0; i < nNodes; i++)
+ {
+ if ( ppNodes[i]->GetZone() == AI_NODE_ZONE_UNKNOWN )
+ {
+ FloodFillZone( (CAI_Node **)ppNodes, ppNodes[i], curZone );
+ curZone++;
+ }
+ }
+
+#ifdef DEBUG
+ for (i = 0; i < nNodes; i++)
+ {
+ Assert( ppNodes[i]->GetZone() != AI_NODE_ZONE_UNKNOWN );
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Used for WC edit move to rebuild the network around the given
+// location. Rebuilding the entire network takes too long
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkBuilder::Rebuild( CAI_Network *pNetwork )
+{
+ int nNodes = pNetwork->NumNodes();
+ CAI_Node **ppNodes = pNetwork->AccessNodes();
+
+ if ( !nNodes )
+ return;
+
+ BeginBuild();
+
+ // ------------------------------------------------------------
+ // First mark all nodes around vecPos as having to be rebuilt
+ // ------------------------------------------------------------
+ int i;
+ for (i = 0; i < nNodes; i++)
+ {
+ // --------------------------------------------------------------------
+ // If changed, mark all nodes that are within the max link distance to
+ // the changed node as having to be rebuild
+ // --------------------------------------------------------------------
+ if (ppNodes[i]->m_eNodeInfo & bits_NODE_WC_CHANGED)
+ {
+ Vector vRebuildPos = ppNodes[i]->GetOrigin();
+ ppNodes[i]->SetNeedsRebuild();
+ ppNodes[i]->SetZone( AI_NODE_ZONE_UNIVERSAL );
+ for (int node = 0; node < nNodes; node++)
+ {
+ if ( ppNodes[node]->GetType() == NODE_AIR )
+ {
+ if ((ppNodes[node]->GetOrigin() - vRebuildPos).LengthSqr() < MAX_AIR_NODE_LINK_DIST_SQ)
+ {
+ ppNodes[node]->SetNeedsRebuild();
+ ppNodes[node]->SetZone( AI_NODE_ZONE_UNIVERSAL );
+ }
+ }
+ else
+ {
+ if ((ppNodes[node]->GetOrigin() - vRebuildPos).LengthSqr() < MAX_NODE_LINK_DIST_SQ)
+ {
+ ppNodes[node]->SetNeedsRebuild();
+ ppNodes[node]->SetZone( AI_NODE_ZONE_UNIVERSAL );
+ }
+ }
+ }
+ }
+ }
+
+ // ---------------------------
+ // Initialize node positions
+ // ---------------------------
+ for (i = 0; i < nNodes; i++)
+ {
+ if (ppNodes[i]->NeedsRebuild())
+ {
+ InitNodePosition( pNetwork, ppNodes[i] );
+ }
+ }
+ nNodes = pNetwork->NumNodes(); // InitNodePosition can create nodes
+
+ // ---------------------------
+ // Initialize node neighbors
+ // ---------------------------
+ m_DidSetNeighborsTable.Resize( nNodes );
+ m_DidSetNeighborsTable.ClearAll();
+ m_NeighborsTable.SetSize( nNodes );
+ for (i = 0; i < nNodes; i++)
+ {
+ m_NeighborsTable[i].Resize( nNodes );
+ }
+ for (i = 0; i < nNodes; i++)
+ {
+ // If near point of change recalculate
+ if (ppNodes[i]->NeedsRebuild())
+ {
+ InitNeighbors( pNetwork, ppNodes[i] );
+ }
+ }
+
+ // ---------------------------
+ // Force node neighbors for dynamic links
+ // ---------------------------
+ ForceDynamicLinkNeighbors();
+
+ // ---------------------------
+ // Initialize accepted hulls
+ // ---------------------------
+ for (i = 0; i < nNodes; i++)
+ {
+ if (ppNodes[i]->NeedsRebuild())
+ {
+ ppNodes[i]->ClearLinks();
+ }
+ }
+ for (i = 0; i < nNodes; i++)
+ {
+ if (ppNodes[i]->NeedsRebuild())
+ {
+ InitLinks( pNetwork, ppNodes[i] );
+ }
+ }
+
+ g_pAINetworkManager->FixupHints();
+
+ EndBuild();
+}
+
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkBuilder::BeginBuild()
+{
+ m_pTestHull = CAI_TestHull::GetTestHull();
+}
+
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkBuilder::EndBuild()
+{
+ m_NeighborsTable.SetSize(0);
+ m_DidSetNeighborsTable.Resize(0);
+ CAI_TestHull::ReturnTestHull();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Only called if network has changed since last time level
+// was loaded
+//-----------------------------------------------------------------------------
+
+
+void CAI_NetworkBuilder::Build( CAI_Network *pNetwork )
+{
+ int nNodes = pNetwork->NumNodes();
+ CAI_Node **ppNodes = pNetwork->AccessNodes();
+
+ if ( !nNodes )
+ return;
+
+ CAI_NetworkBuildHelper *pHelper = (CAI_NetworkBuildHelper *)CreateEntityByName( "ai_network_build_helper" );
+
+ VPROF( "AINet" );
+
+ BeginBuild();
+
+ CFastTimer masterTimer;
+ CFastTimer timer;
+
+ DevMsg( "Building AI node graph...\n");
+ masterTimer.Start();
+
+ // ---------------------------
+ // Initialize node positions
+ // ---------------------------
+ DevMsg( "Initializing node positions...\n" );
+ timer.Start();
+ int i;
+ for ( i = 0; i < nNodes; i++)
+ {
+ InitNodePosition( pNetwork, ppNodes[i] );
+ if ( pHelper )
+ pHelper->PostInitNodePosition( pNetwork, ppNodes[i] );
+ }
+ nNodes = pNetwork->NumNodes(); // InitNodePosition can create nodes
+ timer.End();
+ DevMsg( "...done initializing node positions. %f seconds\n", timer.GetDuration().GetSeconds() );
+
+ // ---------------------------
+ // Initialize node neighbors
+ // ---------------------------
+ DevMsg( "Initializing node neighbors...\n" );
+ timer.Start();
+ m_DidSetNeighborsTable.Resize( nNodes );
+ m_DidSetNeighborsTable.ClearAll();
+ m_NeighborsTable.SetSize( nNodes );
+ for (i = 0; i < nNodes; i++)
+ {
+ m_NeighborsTable[i].Resize( nNodes );
+ m_NeighborsTable[i].ClearAll();
+ }
+ for (i = 0; i < nNodes; i++)
+ {
+ InitNeighbors( pNetwork, ppNodes[i] );
+ }
+ timer.End();
+ DevMsg( "...done initializing node neighbors. %f seconds\n", timer.GetDuration().GetSeconds() );
+
+ // ---------------------------
+ // Force node neighbors for dynamic links
+ // ---------------------------
+ DevMsg( "Forcing dynamic link neighbors...\n" );
+ timer.Start();
+ ForceDynamicLinkNeighbors();
+ timer.End();
+ DevMsg( "...done forcing dynamic link neighbors. %f seconds\n", timer.GetDuration().GetSeconds() );
+
+ // ---------------------------
+ // Initialize accepted hulls
+ // ---------------------------
+ DevMsg( "Determining links...\n" );
+ timer.Start();
+ for (i = 0; i < nNodes; i++)
+ {
+ // Make sure all the links are clear
+ ppNodes[i]->ClearLinks();
+ }
+ for (i = 0; i < nNodes; i++)
+ {
+ InitLinks( pNetwork, ppNodes[i] );
+ }
+ timer.End();
+ DevMsg( "...done determining links. %f seconds\n", timer.GetDuration().GetSeconds() );
+
+ // ------------------------------
+ // Initialize disconnected nodes
+ // ------------------------------
+ DevMsg( "Determining zones...\n" );
+ timer.Start();
+ InitZones( pNetwork);
+ timer.End();
+ masterTimer.End();
+ DevMsg( "...done determining zones. %f seconds\n", timer.GetDuration().GetSeconds() );
+ DevMsg( "...done building AI node graph, %f seconds\n", masterTimer.GetDuration().GetSeconds() );
+
+ g_pAINetworkManager->FixupHints();
+
+ EndBuild();
+
+ if ( pHelper )
+ UTIL_Remove( pHelper );
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Forces testing of a connection between src and dest IDs for all dynamic links
+//
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CAI_NetworkBuilder::ForceDynamicLinkNeighbors(void)
+{
+ if (!g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable)
+ {
+ DevMsg("ERROR: Trying initialize links with no WC ID table!\n");
+ return;
+ }
+
+ CAI_DynamicLink* pDynamicLink = CAI_DynamicLink::m_pAllDynamicLinks;
+
+ while (pDynamicLink)
+ {
+ // -------------------------------------------------------------
+ // First convert this links WC IDs to engine IDs
+ // -------------------------------------------------------------
+ int nSrcID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( pDynamicLink->m_nSrcEditID );
+ if (nSrcID == -1)
+ {
+ DevMsg("ERROR: Dynamic link source WC node %d not found\n", pDynamicLink->m_nSrcEditID );
+ }
+
+ int nDestID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( pDynamicLink->m_nDestEditID );
+ if (nDestID == -1)
+ {
+ DevMsg("ERROR: Dynamic link dest WC node %d not found\n", pDynamicLink->m_nDestEditID );
+ }
+
+ if ( nSrcID != -1 && nDestID != -1 )
+ {
+ if ( nSrcID < g_pBigAINet->NumNodes() && nDestID < g_pBigAINet->NumNodes() )
+ {
+ CAI_Node *pSrcNode = g_pBigAINet->GetNode( nSrcID );
+ CAI_Node *pDestNode = g_pBigAINet->GetNode( nDestID );
+
+ // -------------------------------------------------------------
+ // Force visibility and neighbor-ness between the nodes
+ // -------------------------------------------------------------
+ Assert( pSrcNode );
+ Assert( pDestNode );
+
+ m_NeighborsTable[pSrcNode->GetId()].Set(pDestNode->GetId());
+ m_NeighborsTable[pDestNode->GetId()].Set(pSrcNode->GetId());
+ }
+ }
+
+ // Go on to the next dynamic link
+ pDynamicLink = pDynamicLink->m_pNextDynamicLink;
+ }
+}
+
+CAI_NetworkBuilder g_AINetworkBuilder;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes position of climb node in the world. Climb nodes are
+// set to be just above the floor or at the same level at the
+// dismount point for the node
+//
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CAI_NetworkBuilder::InitClimbNodePosition(CAI_Network *pNetwork, CAI_Node *pNode)
+{
+ AI_PROFILE_SCOPE( CAI_Node_InitClimbNodePosition );
+
+ // If this is a node for mounting/dismounting the climb skip it
+ if ( pNode->m_eNodeInfo & (bits_NODE_CLIMB_OFF_FORWARD | bits_NODE_CLIMB_OFF_LEFT | bits_NODE_CLIMB_OFF_RIGHT) )
+ {
+ return;
+ }
+
+ // Figure out which directions I can dismount from the climb node
+
+ //float hullLength = NAI_Hull::Length(HULL_SMALL);
+ //Vector offsetDir = Vector(cos(DEG2RAD(m_flYaw)),sin(DEG2RAD(m_flYaw)),0);
+
+ // ----------------
+ // Check position
+ // ----------------
+ trace_t trace;
+ Vector posOnLadder = pNode->GetPosition(HULL_SMALL_CENTERED);
+ AI_TraceHull( posOnLadder, posOnLadder + Vector( 0, 0, -37 ),
+ NAI_Hull::Mins(HULL_SMALL_CENTERED), NAI_Hull::Maxs(HULL_SMALL_CENTERED),
+ MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace );
+
+ // --------------------------------------------------------------------
+ // If climb node is right above the floor, we don't need any dismount
+ // nodes. Accept this dropped position and note that this climb node
+ // is at the bottom
+ // --------------------------------------------------------------------
+ if (!trace.startsolid && trace.fraction != 1)
+ {
+ pNode->m_eNodeInfo = bits_NODE_CLIMB_BOTTOM;
+ InitGroundNodePosition( pNetwork, pNode );
+ return;
+ }
+
+ // ---------------------------------------------------------------------
+ // If network was already loaded this means we are in wc edit mode
+ // so we shouldn't recreate the added climb nodes
+ // ---------------------------------------------------------------------
+ if (g_pAINetworkManager->NetworksLoaded())
+ {
+ return;
+ }
+
+ // ---------------------------------------------------------------------
+ // Otherwise we need to create climb nodes for dismounting the climb
+ // and place the height of the climb node at the dismount position
+ // ---------------------------------------------------------------------
+ int checkNodeTypes[3] = { bits_NODE_CLIMB_OFF_FORWARD, bits_NODE_CLIMB_OFF_LEFT, bits_NODE_CLIMB_OFF_RIGHT };
+
+ int numExits = 0;
+
+ // DevMsg( "testing %f %f %f\n", GetOrigin().x, GetOrigin().y, GetOrigin().z );
+
+ for (int i = 0; i < 3; i++)
+ {
+ pNode->m_eNodeInfo = checkNodeTypes[i];
+
+ Vector origin = pNode->GetPosition(HULL_SMALL_CENTERED);
+
+ // DevMsg( "testing %f %f %f\n", origin.x, origin.y, origin.z );
+ // ----------------
+ // Check outward
+ // ----------------
+ AI_TraceLine ( posOnLadder,
+ origin,
+ MASK_NPCSOLID_BRUSHONLY,
+ NULL,
+ COLLISION_GROUP_NONE,
+ &trace );
+
+ // DevMsg( "to %f %f %f : %d %f", origin.x, origin.y, origin.z, trace.startsolid, trace.fraction );
+
+ if (!trace.startsolid && trace.fraction == 1.0)
+ {
+ float floorZ = GetFloorZ(origin); // FIXME: don't use this
+
+ if (abs(pNode->GetOrigin().z - floorZ) < 36)
+ {
+ CAI_Node *new_node = pNetwork->AddNode( pNode->GetOrigin(), pNode->m_flYaw );
+ new_node->m_pHint = NULL;
+ new_node->m_eNodeType = NODE_CLIMB;
+ new_node->m_eNodeInfo = pNode->m_eNodeInfo;
+ InitGroundNodePosition( pNetwork, new_node );
+
+ // copy over the offsets for the first CLIMB_OFF node
+ // FIXME: this method is broken for when the CLIMB_OFF nodes are at different heights
+ if (numExits == 0)
+ {
+ for (int hull = 0; hull < NUM_HULLS; hull++)
+ {
+ pNode->m_flVOffset[hull] = new_node->m_flVOffset[hull];
+ }
+ }
+ else
+ {
+ for (int hull = 0; hull < NUM_HULLS; hull++)
+ {
+ if (fabs(pNode->m_flVOffset[hull] - new_node->m_flVOffset[hull]) > 1)
+ {
+ DevMsg(2, "Warning: Climb Node %i has different exit heights for hull %s\n", pNode->m_iID, NAI_Hull::Name(hull));
+ }
+ }
+ }
+
+ numExits++;
+ }
+ }
+ // DevMsg( "\n");
+ }
+
+ if (numExits == 0)
+ {
+ DevMsg("ERROR: Climb Node %i has no way off\n",pNode->m_iID);
+ }
+
+ // this is a node that can't get gotten to directly
+ pNode->m_eNodeInfo = bits_NODE_CLIMB_ON;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes position of the node sitting on the ground.
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CAI_NetworkBuilder::InitGroundNodePosition(CAI_Network *pNetwork, CAI_Node *pNode)
+{
+ AI_PROFILE_SCOPE( CAI_Node_InitGroundNodePosition );
+
+ if ( pNode->m_eNodeInfo & bits_DONT_DROP )
+ return;
+
+ // find actual floor for each hull type
+ for (int hull = 0; hull < NUM_HULLS; hull++)
+ {
+ trace_t tr;
+ Vector origin = pNode->GetOrigin();
+ Vector mins, maxs;
+
+ // turn hull into pancake to avoid problems with ceiling
+ mins = NAI_Hull::Mins(hull);
+ maxs = NAI_Hull::Maxs(hull);
+ maxs.z = mins.z;
+
+ // Add an epsilon for cast
+ origin.z += 0.1;
+
+ // shift up so bottom of box is at center of node
+ origin.z -= mins.z;
+
+ AI_TraceHull( origin, origin + Vector( 0, 0, -384 ), mins, maxs, MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
+
+ if ( !tr.startsolid )
+ pNode->m_flVOffset[hull] = tr.endpos.z - pNode->GetOrigin().z + 0.1;
+ else
+ pNode->m_flVOffset[hull] = -mins.z + 0.1;
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes position of the node in the world. Only called if
+// the network was never initialized
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CAI_NetworkBuilder::InitNodePosition(CAI_Network *pNetwork, CAI_Node *pNode)
+{
+ AI_PROFILE_SCOPE( CAI_Node_InitNodePosition );
+
+ if (pNode->m_eNodeType == NODE_AIR)
+ {
+ return;
+ }
+ else if (pNode->m_eNodeType == NODE_CLIMB)
+ {
+ InitClimbNodePosition(pNetwork, pNode);
+ return;
+ }
+
+ // Otherwise mark as a land node and drop to the floor
+
+ else if (pNode->m_eNodeType == NODE_GROUND)
+ {
+ InitGroundNodePosition( pNetwork, pNode );
+
+ if (pNode->m_flVOffset[HULL_SMALL_CENTERED] < -100)
+ {
+ Assert( pNetwork == g_pBigAINet );
+ DevWarning("ERROR: Node %.0f %.0f %.0f, WC ID# %i, is either too low (fell through floor) or too high (>100 units above floor)\n",
+ pNode->GetOrigin().x, pNode->GetOrigin().y, pNode->GetOrigin().z,
+ g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pNode->m_iID]);
+
+ pNode->m_eNodeInfo |= bits_NODE_FALLEN;
+ }
+ return;
+ }
+ /* // If under water, not that the node is in water <<TODO>> when we get water
+ else if ( UTIL_PointContents(GetOrigin()) & MASK_WATER )
+ {
+ m_eNodeType |= NODE_WATER;
+ }
+ */
+ else if (pNode->m_eNodeType != NODE_DELETED)
+ {
+ DevMsg( "Bad node type!\n" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the visibility for this node. (What nodes it can see with a
+// line trace)
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CAI_NetworkBuilder::InitVisibility(CAI_Network *pNetwork, CAI_Node *pNode)
+{
+ AI_PROFILE_SCOPE( CAI_Node_InitVisibility );
+
+ // If a deleted node bail
+ if (pNode->m_eNodeType == NODE_DELETED)
+ {
+ return;
+ }
+ // The actual position of some nodes may be inside geometry as they have
+ // hull specific position offsets (e.g. climb nodes). Get the hull specific
+ // position using the smallest hull to make sure were not in geometry
+ Vector srcPos = pNode->GetPosition(HULL_SMALL_CENTERED);
+
+ // Check the visibility on every other node in the network
+ for (int testnode = 0; testnode < pNetwork->NumNodes(); testnode++ )
+ {
+ CAI_Node *testNode = pNetwork->GetNode( testnode );
+
+ if ( DebuggingConnect( pNode->m_iID, testnode ) )
+ {
+ DevMsg( " " ); // break here..
+ }
+
+ // We know we can view ourself
+ if (pNode->m_iID == testnode)
+ {
+ m_NeighborsTable[pNode->m_iID].Set(testNode->m_iID);
+ continue;
+ }
+
+ // Remove duplicate nodes unless a climb node as they move
+ if (testNode->GetOrigin() == pNode->GetOrigin() && testNode->GetType() != NODE_CLIMB)
+ {
+ testNode->SetType( NODE_DELETED );
+ DevMsg( 2, "Probable duplicate node placed at %s\n", VecToString(testNode->GetOrigin()) );
+ continue;
+ }
+
+ // If a deleted node we don't care about it
+ if (testNode->GetType() == NODE_DELETED)
+ {
+ continue;
+ }
+
+ if ( m_DidSetNeighborsTable.IsBitSet( testNode->m_iID ) )
+ {
+ if ( m_NeighborsTable[testNode->m_iID].IsBitSet(pNode->m_iID))
+ m_NeighborsTable[pNode->m_iID].Set(testNode->m_iID);
+
+ continue;
+ }
+
+ float flDistToCheckNode = ( testNode->GetOrigin() - pNode->GetOrigin() ).LengthSqr();
+
+ if ( testNode->GetType() == NODE_AIR )
+ {
+ if (flDistToCheckNode > MAX_AIR_NODE_LINK_DIST_SQ)
+ continue;
+ }
+ else
+ {
+ if (flDistToCheckNode > MAX_NODE_LINK_DIST_SQ)
+ continue;
+ }
+
+ // The actual position of some nodes may be inside geometry as they have
+ // hull specific position offsets (e.g. climb nodes). Get the hull specific
+ // position using the smallest hull to make sure were not in geometry
+ Vector destPos = pNetwork->GetNode( testnode )->GetPosition(HULL_SMALL_CENTERED);
+
+ trace_t tr;
+ tr.m_pEnt = NULL;
+
+ // Try several line of sight checks
+
+ bool isVisible = false;
+
+ // ------------------
+ // Bottom to bottom
+ // ------------------
+ AI_TraceLine ( srcPos, destPos,MASK_NPCWORLDSTATIC,NULL,COLLISION_GROUP_NONE, &tr );
+ if (!tr.startsolid && tr.fraction == 1.0)
+ {
+ isVisible = true;
+ }
+
+ // ------------------
+ // Top to top
+ // ------------------
+ if (!isVisible)
+ {
+ AI_TraceLine ( srcPos + Vector( 0, 0, 70 ),destPos + Vector( 0, 0, 70 ),MASK_NPCWORLDSTATIC,NULL,COLLISION_GROUP_NONE, &tr );
+ if (!tr.startsolid && tr.fraction == 1.0)
+ {
+ isVisible = true;
+ }
+ }
+
+ // ------------------
+ // Top to Bottom
+ // ------------------
+ if (!isVisible)
+ {
+ AI_TraceLine ( srcPos + Vector( 0, 0, 70 ),destPos,MASK_NPCWORLDSTATIC,NULL,COLLISION_GROUP_NONE, &tr );
+ if (!tr.startsolid && tr.fraction == 1.0)
+ {
+ isVisible = true;
+ }
+ }
+
+ // ------------------
+ // Bottom to Top
+ // ------------------
+ if (!isVisible)
+ {
+ AI_TraceLine ( srcPos,destPos + Vector( 0, 0, 70 ),MASK_NPCWORLDSTATIC,NULL,COLLISION_GROUP_NONE, &tr );
+ if (!tr.startsolid && tr.fraction == 1.0)
+ {
+ isVisible = true;
+ }
+ }
+
+ // ------------------
+ // Failure
+ // ------------------
+ if (!isVisible)
+ {
+ continue;
+ }
+
+ /* <<TODO>> may not apply with editable connections.......
+
+ // trace hit a brush ent, trace backwards to make sure that this ent is the only thing in the way.
+ if ( tr.fraction != 1.0 )
+ {
+ pTraceEnt = tr.u.ent;// store the ent that the trace hit, for comparison
+
+ AI_TraceLine ( srcPos,
+ destPos,
+ MASK_NPCSOLID_BRUSHONLY,
+ NULL,
+ &tr );
+
+
+ // there is a solid_bsp ent in the way of these two nodes, so we must record several things about in order to keep
+ // track of it in the pathfinding code, as well as through save and restore of the node graph. ANY data that is manipulated
+ // as part of the process of adding a LINKENT to a connection here must also be done in CGraph::SetGraphPointers, where reloaded
+ // graphs are prepared for use.
+ if ( tr.u.ent == pTraceEnt && !FClassnameIs( tr.u.ent, "worldspawn" ) )
+ {
+ // get a pointer
+ pLinkPool [ cTotalLinks ].m_pLinkEnt = tr.u.ent;
+
+ // record the modelname, so that we can save/load node trees
+ memcpy( pLinkPool [ cTotalLinks ].m_szLinkEntModelname, STRING( tr.u.ent->model ), 4 );
+
+ // set the flag for this ent that indicates that it is attached to the world graph
+ // if this ent is removed from the world, it must also be removed from the connections
+ // that it formerly blocked.
+ CBaseEntity *e = CBaseEntity::Instance( tr.u.ent );
+ if ( e )
+ {
+ if ( !(e->GetFlags() & FL_GRAPHED ) )
+ {
+ e->AddFlag( FL_GRAPHED );
+ }
+ }
+ }
+ // even if the ent wasn't there, these nodes couldn't be connected. Skip.
+ else
+ {
+ continue;
+ }
+ }
+*/
+ m_NeighborsTable[pNode->m_iID].Set(testNode->m_iID);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes the neighbors list
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+void CAI_NetworkBuilder::InitNeighbors(CAI_Network *pNetwork, CAI_Node *pNode)
+{
+ m_NeighborsTable[pNode->m_iID].ClearAll();
+
+ // Begin by establishing viewability to limit the number of nodes tested
+ InitVisibility( pNetwork, pNode );
+
+ AI_PROFILE_SCOPE_BEGIN( CAI_Node_InitNeighbors );
+
+ // Now check each neighbor against all other neighbors to see if one of
+ // them is a redundant connection
+ for (int checknode = 0; checknode < pNetwork->NumNodes(); checknode++ )
+ {
+ if ( DebuggingConnect( pNode->m_iID, checknode ) )
+ {
+ DevMsg( " " ); // break here..
+ }
+
+ // I'm not a neighbor of myself
+ if ( pNode->m_iID == checknode )
+ {
+ m_NeighborsTable[pNode->m_iID].Clear(checknode);
+ continue;
+ }
+
+ // Only check if already on the neightbor list
+ if (!m_NeighborsTable[pNode->m_iID].IsBitSet(checknode))
+ {
+ continue;
+ }
+
+ CAI_Node *pCheckNode = pNetwork->GetNode(checknode);
+
+ for (int testnode = 0; testnode < pNetwork->NumNodes(); testnode++ )
+ {
+ // don't check against itself
+ if (( testnode == checknode ) || (testnode == pNode->m_iID))
+ {
+ continue;
+ }
+
+ // Only check if already on the neightbor list
+ if (!m_NeighborsTable[pNode->m_iID].IsBitSet(testnode))
+ {
+ continue;
+ }
+
+ CAI_Node *pTestNode = pNetwork->GetNode(testnode);
+
+ // ----------------------------------------------------------
+ // Don't check air nodes against nodes of a different types
+ // ----------------------------------------------------------
+ if ((pCheckNode->GetType() == NODE_AIR && pTestNode->GetType() != NODE_AIR)||
+ (pCheckNode->GetType() != NODE_AIR && pTestNode->GetType() == NODE_AIR))
+ {
+ continue;
+ }
+
+ // ----------------------------------------------------------
+ // If climb node pairs, don't consider redundancy
+ // ----------------------------------------------------------
+ if (pNode->GetType() == NODE_CLIMB &&
+ (pCheckNode->GetType() == NODE_CLIMB || pTestNode->GetType() == NODE_CLIMB))
+ {
+ continue;
+ }
+
+ // ----------------------------------------------------------
+ // If a climb node mounting point is involved, don't consider redundancy
+ // ----------------------------------------------------------
+ if ( ( pCheckNode->GetOrigin() == pNode->GetOrigin() && pNode->GetType() == NODE_CLIMB && pCheckNode->GetType() == NODE_CLIMB ) ||
+ ( pTestNode->GetOrigin() == pNode->GetOrigin() && pNode->GetType() == NODE_CLIMB && pTestNode->GetType() == NODE_CLIMB ) ||
+ ( pTestNode->GetOrigin() == pCheckNode->GetOrigin() && pCheckNode->GetType() == NODE_CLIMB && pTestNode->GetType() == NODE_CLIMB ) )
+ {
+ continue;
+ }
+
+ // @HACKHACK (toml 02-25-04): Ignore redundancy if both nodes are air nodes with
+ // hint type "strider node". Really, really should do this in a clean manner
+ bool nodeIsStrider = ( pNode->GetHint() && pNode->GetHint()->HintType() == HINT_STRIDER_NODE );
+ bool other1IsStrider = ( pCheckNode->GetHint() && pCheckNode->GetHint()->HintType() == HINT_STRIDER_NODE );
+ bool other2IsStrider = ( pTestNode->GetHint() && pTestNode->GetHint()->HintType() == HINT_STRIDER_NODE );
+ if ( nodeIsStrider && other1IsStrider != other2IsStrider )
+ {
+ continue;
+ }
+
+ Vector vec2DirToCheckNode = pCheckNode->GetOrigin() - pNode->GetOrigin();
+ float flDistToCheckNode = VectorNormalize( vec2DirToCheckNode );
+
+ Vector vec2DirToTestNode = ( pTestNode->GetOrigin() - pNode->GetOrigin() );
+ float flDistToTestNode = VectorNormalize( vec2DirToTestNode );
+
+ float tolerance = 0.92388; // 45 degrees
+
+ if ( DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) >= tolerance )
+ {
+ if ( flDistToTestNode < flDistToCheckNode )
+ {
+ DebugConnectMsg( pNode->m_iID, checknode, " Revoking neighbor status to to closer redundant link %d\n", testnode );
+ m_NeighborsTable[pNode->m_iID].Clear(checknode);
+ }
+ else
+ {
+ DebugConnectMsg( pNode->m_iID, testnode, " Revoking neighbor status to to closer redundant link %d\n", checknode );
+ m_NeighborsTable[pNode->m_iID].Clear(testnode);
+ }
+ }
+ }
+ }
+
+ AI_PROFILE_SCOPE_END();
+
+ m_DidSetNeighborsTable.Set(pNode->m_iID);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: For the current node, check its connection to all other nodes
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+static bool IsInLineForClimb( const Vector &srcPos, const Vector &srcFacing, const Vector &destPos, const Vector &destFacing )
+{
+#ifdef DEBUG
+ Vector normSrcFacing( srcFacing ), normDestFacing( destFacing );
+
+ VectorNormalize( normSrcFacing );
+ VectorNormalize( normDestFacing );
+
+ Assert( VectorsAreEqual( srcFacing, normSrcFacing, 0.01 ) && VectorsAreEqual( destFacing, normDestFacing, 0.01 ) );
+#endif
+
+ // If they are not facing the same way...
+ if ( 1 - srcFacing.Dot( destFacing ) > 0.01 )
+ return false;
+
+ // If they aren't in line along the facing...
+ if ( CalcDistanceToLine2D( destPos.AsVector2D(), srcPos.AsVector2D(), srcPos.AsVector2D() + srcFacing.AsVector2D() ) > 0.01 )
+ return false;
+
+ // Check that the angle between them is either staight up, or on at angle of ladder-stairs
+ Vector vecDelta = srcPos - destPos;
+
+ VectorNormalize( vecDelta );
+
+ float fabsCos = fabs( srcFacing.Dot( vecDelta ) );
+
+ const float CosAngLadderStairs = 0.4472; // rise 2 & run 1
+
+ if ( fabsCos > 0.05 && fabs( fabsCos - CosAngLadderStairs ) > 0.05 )
+ return false;
+
+ // *************************** --------------------------------
+ return true;
+}
+
+//-------------------------------------
+
+int CAI_NetworkBuilder::ComputeConnection( CAI_Node *pSrcNode, CAI_Node *pDestNode, Hull_t hull )
+{
+ int srcId = pSrcNode->m_iID;
+ int destId = pDestNode->m_iID;
+ int result = 0;
+ trace_t tr;
+
+ // Set the size of the test hull
+ if ( m_pTestHull->GetHullType() != hull )
+ {
+ m_pTestHull->SetHullType( hull );
+ m_pTestHull->SetHullSizeNormal( true );
+ }
+
+ if ( !( m_pTestHull->GetFlags() & FL_ONGROUND ) )
+ {
+ DevWarning( 2, "OFFGROUND!\n" );
+ }
+ m_pTestHull->AddFlag( FL_ONGROUND );
+
+ // ==============================================================
+ // FIRST CHECK IF HULL CAN EVEN FIT AT THESE NODES
+ // ==============================================================
+ // @Note (toml 02-10-03): this should be optimized, caching the results of CanFitAtNode()
+ if ( !( pSrcNode->m_eNodeInfo & ( HullToBit( hull ) << NODE_ENT_FLAGS_SHIFT ) ) &&
+ !m_pTestHull->GetNavigator()->CanFitAtNode(srcId,MASK_NPCWORLDSTATIC) )
+ {
+ DebugConnectMsg( srcId, destId, " Cannot fit at node %d\n", srcId );
+ return 0;
+ }
+
+ if ( !( pDestNode->m_eNodeInfo & ( HullToBit( hull ) << NODE_ENT_FLAGS_SHIFT ) ) &&
+ !m_pTestHull->GetNavigator()->CanFitAtNode(destId,MASK_NPCWORLDSTATIC) )
+ {
+ DebugConnectMsg( srcId, destId, " Cannot fit at node %d\n", destId );
+ return 0;
+ }
+
+ // ==============================================================
+ // AIR NODES (FLYING)
+ // ==============================================================
+ if (pSrcNode->m_eNodeType == NODE_AIR || pDestNode->GetType() == NODE_AIR)
+ {
+ AI_PROFILE_SCOPE( CAI_Node_InitLinks_Air );
+
+ // Air nodes only connect to other air nodes and nothing else
+ if (pSrcNode->m_eNodeType == NODE_AIR && pDestNode->GetType() == NODE_AIR)
+ {
+ AI_TraceHull( pSrcNode->GetOrigin(), pDestNode->GetOrigin(), NAI_Hull::Mins(hull),NAI_Hull::Maxs(hull), MASK_NPCWORLDSTATIC, m_pTestHull, COLLISION_GROUP_NONE, &tr );
+ if (!tr.startsolid && tr.fraction == 1.0)
+ {
+ result |= bits_CAP_MOVE_FLY;
+ DebugConnectMsg( srcId, destId, " Connect by flying\n" );
+ }
+ }
+ }
+ // =============================================================================
+ // > CLIMBING
+ // =============================================================================
+ // If both are climb nodes just make sure they are above each other
+ // and there is room for the hull to pass between them
+ else if ((pSrcNode->m_eNodeType == NODE_CLIMB) && (pDestNode->GetType() == NODE_CLIMB))
+ {
+ AI_PROFILE_SCOPE( CAI_Node_InitLinks_Climb );
+
+ Vector srcPos = pSrcNode->GetPosition(hull);
+ Vector destPos = pDestNode->GetPosition(hull);
+
+ // If a code genereted climb dismount node the two origins will be the same
+ if (pSrcNode->GetOrigin() == pDestNode->GetOrigin())
+ {
+ AI_TraceHull( srcPos, destPos,
+ NAI_Hull::Mins(hull),NAI_Hull::Maxs(hull),
+ MASK_NPCWORLDSTATIC, m_pTestHull, COLLISION_GROUP_NONE, &tr );
+ if (!tr.startsolid && tr.fraction == 1.0)
+ {
+ result |= bits_CAP_MOVE_CLIMB;
+ DebugConnectMsg( srcId, destId, " Connect by climbing\n" );
+ }
+ }
+ else
+ {
+ if ( !IsInLineForClimb(srcPos, UTIL_YawToVector( pSrcNode->m_flYaw ), destPos, UTIL_YawToVector( pDestNode->m_flYaw ) ) )
+ {
+ Assert( !IsInLineForClimb(destPos, UTIL_YawToVector( pDestNode->m_flYaw ), srcPos, UTIL_YawToVector( pSrcNode->m_flYaw ) ) );
+ DebugConnectMsg( srcId, destId, " Not lined up for proper climbing\n" );
+ return 0;
+ }
+
+ AI_TraceHull( srcPos, destPos, NAI_Hull::Mins(hull),NAI_Hull::Maxs(hull), MASK_NPCWORLDSTATIC, m_pTestHull, COLLISION_GROUP_NONE, &tr );
+ if (!tr.startsolid && tr.fraction == 1.0)
+ {
+ result |= bits_CAP_MOVE_CLIMB;
+ DebugConnectMsg( srcId, destId, " Connect by climbing\n" );
+ }
+ }
+ }
+ // ====================================================
+ // > TWO LAND NODES
+ // =====================================================
+ else if ((pSrcNode->m_eNodeType == NODE_GROUND) || (pDestNode->GetType() == NODE_GROUND))
+ {
+ // BUG: this could use GroundMoveLimit, except there's no version of world but not brushes (doors open, etc).
+
+ // ====================================================
+ // > WALKING : walk the space between the nodes
+ // =====================================================
+
+ // in this loop we take tiny steps from the current node to the nodes that it links to, one at a time.
+ bool fStandFailed = false;
+ bool fWalkFailed = true;
+
+ AI_PROFILE_SCOPE_BEGIN( CAI_Node_InitLinks_Ground );
+
+ Vector srcPos = pSrcNode->GetPosition(hull);
+ Vector destPos = pDestNode->GetPosition(hull);
+
+ if (!m_pTestHull->GetMoveProbe()->CheckStandPosition( srcPos, MASK_NPCWORLDSTATIC))
+ {
+ DebugConnectMsg( srcId, destId, " Failed to stand at %d\n", srcId );
+ fStandFailed = true;
+ }
+
+ if (!m_pTestHull->GetMoveProbe()->CheckStandPosition( destPos, MASK_NPCWORLDSTATIC))
+ {
+ DebugConnectMsg( srcId, destId, " Failed to stand at %d\n", destId );
+ fStandFailed = true;
+ }
+
+ //if (hull == 0)
+ // DevMsg("from %.1f %.1f %.1f to %.1f %.1f %.1f\n", srcPos.x, srcPos.y, srcPos.z, destPos.x, destPos.y, destPos.z );
+
+ if ( !fStandFailed )
+ {
+ fWalkFailed = !m_pTestHull->GetMoveProbe()->TestGroundMove( srcPos, destPos, MASK_NPCWORLDSTATIC, AITGM_IGNORE_INITIAL_STAND_POS, NULL );
+ if ( fWalkFailed )
+ DebugConnectMsg( srcId, destId, " Failed to walk between nodes\n" );
+ }
+
+ // Add to our list of accepable hulls
+ if (!fWalkFailed && !fStandFailed)
+ {
+ result |= bits_CAP_MOVE_GROUND;
+ DebugConnectMsg( srcId, destId, " Nodes connect for ground movement\n" );
+ }
+
+ AI_PROFILE_SCOPE_END();
+
+ // =============================================================================
+ // > JUMPING : jump the space between the nodes, but only if walk failed
+ // =============================================================================
+ if (!fStandFailed && fWalkFailed && (pSrcNode->m_eNodeType == NODE_GROUND) && (pDestNode->GetType() == NODE_GROUND))
+ {
+ AI_PROFILE_SCOPE( CAI_Node_InitLinks_Jump );
+
+ Vector srcPos = pSrcNode->GetPosition(hull);
+ Vector destPos = pDestNode->GetPosition(hull);
+
+ // Jumps aren't bi-directional. We can jump down further than we can jump up so
+ // we have to test for either one
+ bool canDestJump = m_pTestHull->IsJumpLegal(srcPos, destPos, destPos);
+ bool canSrcJump = m_pTestHull->IsJumpLegal(destPos, srcPos, srcPos);
+
+ if (canDestJump || canSrcJump)
+ {
+ CAI_MoveProbe *pMoveProbe = m_pTestHull->GetMoveProbe();
+
+ bool fJumpLegal = false;
+ m_pTestHull->SetGravity(1.0);
+
+ AIMoveTrace_t moveTrace;
+ pMoveProbe->MoveLimit( NAV_JUMP, srcPos,destPos, MASK_NPCWORLDSTATIC, NULL, &moveTrace);
+ if (!IsMoveBlocked(moveTrace))
+ {
+ fJumpLegal = true;
+ }
+ pMoveProbe->MoveLimit( NAV_JUMP, destPos,srcPos, MASK_NPCWORLDSTATIC, NULL, &moveTrace);
+ if (!IsMoveBlocked(moveTrace))
+ {
+ fJumpLegal = true;
+ }
+
+ // Add to our list of accepable hulls
+ if (fJumpLegal)
+ {
+ result |= bits_CAP_MOVE_JUMP;
+ DebugConnectMsg( srcId, destId, " Nodes connect for jumping\n" );
+ }
+ }
+ }
+ }
+ return result;
+}
+
+
+
+//-------------------------------------
+
+void CAI_NetworkBuilder::InitLinks(CAI_Network *pNetwork, CAI_Node *pNode)
+{
+ AI_PROFILE_SCOPE( CAI_Node_InitLinks );
+
+ // -----------------------------------------------------
+ // Get test hull
+ // -----------------------------------------------------
+ m_pTestHull->GetNavigator()->SetNetwork( pNetwork );
+
+ // -----------------------------------------------------
+ // Initialize links to every node
+ // -----------------------------------------------------
+ for (int i = 0; i < pNetwork->NumNodes(); i++ )
+ {
+ // -------------------------------------------------
+ // Check for redundant link building
+ // -------------------------------------------------
+ DebugConnectMsg( pNode->m_iID, i, "Testing connection between %d and %d:\n", pNode->m_iID, i );
+
+ if (pNode->HasLink(i))
+ {
+ // A link has been already created when the other node was processed...
+ DebugConnectMsg( pNode->m_iID, i, " Nodes already connected\n" );
+ continue;
+ }
+
+ // ---------------------------------------------------------------------
+ // If link has been already created in other node just share it
+ // ---------------------------------------------------------------------
+ CAI_Node *pDestNode = pNetwork->GetNode( i );
+
+ CAI_Link *pOldLink = pDestNode->HasLink(pNode->m_iID);
+ if (pOldLink)
+ {
+ DebugConnectMsg( pNode->m_iID, i, " Sharing previously establish connection\n" );
+ ((CAI_Node *)pNode)->AddLink(pOldLink);
+ continue;
+ }
+
+ // Only check if the node is a neighbor
+ if ( m_NeighborsTable[pNode->m_iID].IsBitSet(pDestNode->m_iID) )
+ {
+ int acceptedMotions[NUM_HULLS];
+
+ bool bAllFailed = true;
+
+ if ( DebuggingConnect( pNode->m_iID, i ) )
+ {
+ DevMsg( " " ); // break here..
+ }
+
+ if ( !(pNode->m_eNodeInfo & bits_NODE_FALLEN) && !(pDestNode->m_eNodeInfo & bits_NODE_FALLEN) )
+ {
+ for (int hull = 0 ; hull < NUM_HULLS; hull++ )
+ {
+ DebugConnectMsg( pNode->m_iID, i, " Testing for hull %s\n", NAI_Hull::Name( (Hull_t)hull ) );
+
+ acceptedMotions[hull] = ComputeConnection( pNode, pDestNode, (Hull_t)hull );
+ if ( acceptedMotions[hull] != 0 )
+ bAllFailed = false;
+ }
+ }
+ else
+ DebugConnectMsg( pNode->m_iID, i, " No connection: one or both are fallen nodes\n" );
+
+ // If there were any passible hulls create link
+ if (!bAllFailed)
+ {
+ CAI_Link *pLink = pNetwork->CreateLink( pNode->m_iID, pDestNode->m_iID);
+ if ( pLink )
+ {
+ for (int hull=0;hull<NUM_HULLS;hull++)
+ {
+ pLink->m_iAcceptedMoveTypes[hull] = acceptedMotions[hull];
+ }
+ DebugConnectMsg( pNode->m_iID, i, " Added link\n" );
+ }
+ }
+ else
+ {
+ m_NeighborsTable[pNode->m_iID].Clear(pDestNode->m_iID);
+ DebugConnectMsg(pNode->m_iID, i, " NO LINK\n" );
+ }
+ }
+ else
+ DebugConnectMsg( pNode->m_iID, i, " NO LINK (not neighbors)\n" );
+ }
+}
+
+//-----------------------------------------------------------------------------