text: //========= 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" );
  #ifdef MAPBASE
  ConVar g_ai_nographrebuildmessage( "ai_nographrebuildmessage", "0", FCVAR_ARCHIVE, "Stops the \"Node graph out of date\" message from appearing when rbeuilding node graph" );
  #endif
  
  
  //-----------------------------------------------------------------------------
  // 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" )  || !V_stricmp( szLoweredGameDir, "hl1" ) )
  		{
  			// 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() );
  #ifdef MAPBASE
  			if (!g_ai_nographrebuildmessage.GetBool())
  				UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding...\n" );
  
  			// Do it much sooner after map load
  			g_pAINetworkManager->SetNextThink( gpGlobals->curtime + 0.5 );
  			m_bNeedGraphRebuild = true;
  #else
  			UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding...\n" );
  			m_bNeedGraphRebuild = true;
  			g_pAINetworkManager->SetNextThink( gpGlobals->curtime + 1 );
  #endif
  			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" );
  	}
  }
  
  //-----------------------------------------------------------------------------
  