From a0c29e7dd67abb15c74c85f07741784877edfdcd Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Mon, 2 Sep 2013 11:39:10 -0700 Subject: General: * Fixed a variety of server browser issues with mods based on this SDK * Fixed many warnings on various platforms * Added source code for fgdlib and raytrace * Updated many source files with the latest shared source from TF2. OSX: * Added support for Xcode 4.6 * Switched OSX builds to use Xcode instead of makefiles * Moved libs from src/lib/osx32 to src/lib/public/osx32 or src/lib/common/osx32 to match windows better. Linux: * Moved libs from src/lib/linux32 to src/lib/public/linux32 or src/lib/common/linux32 to match windows better. --- mp/src/game/server/ai_networkmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mp/src/game/server/ai_networkmanager.cpp') diff --git a/mp/src/game/server/ai_networkmanager.cpp b/mp/src/game/server/ai_networkmanager.cpp index 00826e02..69d6319e 100644 --- a/mp/src/game/server/ai_networkmanager.cpp +++ b/mp/src/game/server/ai_networkmanager.cpp @@ -48,7 +48,7 @@ inline void DebugConnectMsg( int node1, int node2, const char *pszFormat, ... ) Q_vsnprintf( string, sizeof(string), pszFormat, argptr ); va_end( argptr ); - DevMsg( string ); + DevMsg( "%s", string ); } } -- cgit v1.2.3 From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/ai_networkmanager.cpp | 6496 +++++++++++++++--------------- 1 file changed, 3248 insertions(+), 3248 deletions(-) (limited to 'mp/src/game/server/ai_networkmanager.cpp') diff --git a/mp/src/game/server/ai_networkmanager.cpp b/mp/src/game/server/ai_networkmanager.cpp index 69d6319e..7f6bea61 100644 --- a/mp/src/game/server/ai_networkmanager.cpp +++ b/mp/src/game/server/ai_networkmanager.cpp @@ -1,3248 +1,3248 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - - -#include "cbase.h" -#include "fmtstr.h" -#include "filesystem.h" -#include "filesystem/IQueuedLoader.h" -#include "utlbuffer.h" -#include "utlrbtree.h" -#include "editor_sendcommand.h" - -#include "ai_networkmanager.h" -#include "ai_network.h" -#include "ai_node.h" -#include "ai_navigator.h" -#include "ai_link.h" -#include "ai_dynamiclink.h" -#include "ai_initutils.h" -#include "ai_moveprobe.h" -#include "ai_hull.h" -#include "ndebugoverlay.h" -#include "ai_hint.h" -#include "tier0/icommandline.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -// Increment this to force rebuilding of all networks -#define AINET_VERSION_NUMBER 37 - -//----------------------------------------------------------------------------- - -int g_DebugConnectNode1 = -1; -int g_DebugConnectNode2 = -1; -#define DebuggingConnect( node1, node2 ) ( ( node1 == g_DebugConnectNode1 && node2 == g_DebugConnectNode2 ) || ( node1 == g_DebugConnectNode2 && node2 == g_DebugConnectNode1 ) ) - -inline void DebugConnectMsg( int node1, int node2, const char *pszFormat, ... ) -{ - if ( DebuggingConnect( node1, node2 ) ) - { - char string[ 2048 ]; - va_list argptr; - va_start( argptr, pszFormat ); - Q_vsnprintf( string, sizeof(string), pszFormat, argptr ); - va_end( argptr ); - - DevMsg( "%s", string ); - } -} - -CON_COMMAND( ai_debug_node_connect, "Debug the attempted connection between two nodes" ) -{ - g_DebugConnectNode1 = atoi( args[1] ); - g_DebugConnectNode2 = atoi( args[2] ); - - DevMsg( "ai_debug_node_connect: debugging enbabled for %d <--> %d\n", g_DebugConnectNode1, g_DebugConnectNode2 ); -} - -//----------------------------------------------------------------------------- -// This CVAR allows level designers to override the building -// of node graphs due to date conflicts with the BSP and AIN -// files. That way they don't have to wait for the node graph -// to rebuild following small only-ents changes. This CVAR -// always defaults to 0 and must be set at the command -// line to properly override the node graph building. - -ConVar g_ai_norebuildgraph( "ai_norebuildgraph", "0" ); - - -//----------------------------------------------------------------------------- -// CAI_NetworkManager -// -//----------------------------------------------------------------------------- - -CAI_NetworkManager *g_pAINetworkManager; - -//----------------------------------------------------------------------------- - -bool CAI_NetworkManager::gm_fNetworksLoaded; - -LINK_ENTITY_TO_CLASS(ai_network,CAI_NetworkManager); - -BEGIN_DATADESC( CAI_NetworkManager ) - - DEFINE_FIELD( m_bNeedGraphRebuild, FIELD_BOOLEAN ), - // m_pEditOps - // m_pNetwork - // DEFINE_FIELD( m_bDontSaveGraph, FIELD_BOOLEAN ), - DEFINE_FIELD( m_fInitalized, FIELD_BOOLEAN ), - - // Function pointers - DEFINE_FUNCTION( DelayedInit ), - DEFINE_FUNCTION( RebuildThink ), - -END_DATADESC() - - -//----------------------------------------------------------------------------- - -CAI_NetworkManager::CAI_NetworkManager(void) -{ - m_pNetwork = new CAI_Network; - m_pEditOps = new CAI_NetworkEditTools(this); - m_bNeedGraphRebuild = false; - m_fInitalized = false; - CAI_DynamicLink::gm_bInitialized = false; - - // --------------------------------- - // Add to linked list of networks - // --------------------------------- -}; - -//----------------------------------------------------------------------------- - -CAI_NetworkManager::~CAI_NetworkManager(void) -{ - // --------------------------------------- - // Remove from linked list of AINetworks - // --------------------------------------- - delete m_pEditOps; - delete m_pNetwork; - if ( g_pAINetworkManager == this ) - { - g_pAINetworkManager = NULL; - } -} - - -//------------------------------------------------------------------------------ -// Purpose : Think function so we can put message on screen saying we are -// going to rebuild the network, before we hang during the rebuild -//------------------------------------------------------------------------------ - -void CAI_NetworkManager::RebuildThink( void ) -{ - SetThink(NULL); - - GetEditOps()->m_debugNetOverlays &= ~bits_debugNeedRebuild; - StartRebuild( ); -} - -//------------------------------------------------------------------------------ -// Purpose : Delay function so we can put message on screen saying we are -// going to rebuild the network, before we hang during the rebuild -//------------------------------------------------------------------------------ - -void CAI_NetworkManager::RebuildNetworkGraph( void ) -{ - if (m_pfnThink != (void (CBaseEntity::*)())&CAI_NetworkManager::RebuildThink) - { - UTIL_CenterPrintAll( "Doing partial rebuild of Node Graph...\n" ); - SetThink(&CAI_NetworkManager::RebuildThink); - SetNextThink( gpGlobals->curtime + 0.1f ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Used for WC edit move to rebuild the network around the given -// location. Rebuilding the entire network takes too long -//----------------------------------------------------------------------------- - -void CAI_NetworkManager::StartRebuild( void ) -{ - CAI_DynamicLink::gm_bInitialized = false; - - g_AINetworkBuilder.Rebuild( m_pNetwork ); - - // ------------------------------------------------------------ - // Purge any dynamic links for links that don't exist any more - // ------------------------------------------------------------ - CAI_DynamicLink::PurgeDynamicLinks(); - - - // ------------------------ - // Reset all dynamic links - // ------------------------ - CAI_DynamicLink::ResetDynamicLinks(); - - // -------------------------------------------------- - // Update display of usable nodes for displayed hull - // -------------------------------------------------- - GetEditOps()->RecalcUsableNodesForHull(); - - GetEditOps()->ClearRebuildFlags(); -} - - -//----------------------------------------------------------------------------- -// Purpose: Called by save restore code if no valid load graph was loaded at restore time. -// Prevents writing out of a "bogus" node graph... -// Input : - -//----------------------------------------------------------------------------- -void CAI_NetworkManager::MarkDontSaveGraph() -{ - m_bDontSaveGraph = true; -} - -//----------------------------------------------------------------------------- -// Purpose: Only called if network has changed since last time level -// was loaded -// Input : -// Output : -//----------------------------------------------------------------------------- - -void CAI_NetworkManager::SaveNetworkGraph( void ) -{ - if ( m_bDontSaveGraph ) - return; - - if ( !m_bNeedGraphRebuild ) - return; - - //if ( g_AI_Manager.NumAIs() && m_pNetwork->m_iNumNodes == 0 ) - //{ - // return; - //} - - if ( !g_pGameRules->FAllowNPCs() ) - { - return; - } - - // ----------------------------- - // Make sure directories have been made - // ----------------------------- - char szNrpFilename [MAX_PATH];// text node report filename - Q_strncpy( szNrpFilename, "maps/graphs" ,sizeof(szNrpFilename)); - - // Usually adding on the map filename and stripping it does nothing, but if the map is under a subdir, - // this makes it create the correct subdir under maps/graphs. - char tempFilename[MAX_PATH]; - Q_snprintf( tempFilename, sizeof( tempFilename ), "%s/%s", szNrpFilename, STRING( gpGlobals->mapname ) ); - - // Remove the filename. - int len = strlen( tempFilename ); - for ( int i=0; i < len; i++ ) - { - if ( tempFilename[len-i-1] == '/' || tempFilename[len-i-1] == '\\' ) - { - tempFilename[len-i-1] = 0; - break; - } - } - - // Make sure the directories we need exist. - filesystem->CreateDirHierarchy( tempFilename, "DEFAULT_WRITE_PATH" ); - - // Now add the real map filename. - Q_strncat( szNrpFilename, "/", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS ); - Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), sizeof( szNrpFilename ), COPY_ALL_CHARACTERS ); - Q_strncat( szNrpFilename, IsX360() ? ".360.ain" : ".ain", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS ); - - CUtlBuffer buf; - - // --------------------------- - // Save the version number - // --------------------------- - buf.PutInt(AINET_VERSION_NUMBER); - buf.PutInt(gpGlobals->mapversion); - - // ------------------------------- - // Dump all the nodes to the file - // ------------------------------- - buf.PutInt( m_pNetwork->m_iNumNodes); - - int node; - int totalNumLinks = 0; - for ( node = 0; node < m_pNetwork->m_iNumNodes; node++) - { - CAI_Node *pNode = m_pNetwork->GetNode(node); - Assert( pNode->GetZone() != AI_NODE_ZONE_UNKNOWN ); - - buf.PutFloat( pNode->GetOrigin().x ); - buf.PutFloat( pNode->GetOrigin().y ); - buf.PutFloat( pNode->GetOrigin().z ); - buf.PutFloat( pNode->GetYaw() ); - buf.Put( pNode->m_flVOffset, sizeof( pNode->m_flVOffset ) ); - buf.PutChar( pNode->GetType() ); - if ( IsX360() ) - { - buf.SeekPut( CUtlBuffer::SEEK_CURRENT, 3 ); - } - buf.PutUnsignedShort( pNode->m_eNodeInfo ); - buf.PutShort( pNode->GetZone() ); - - for (int link = 0; link < pNode->NumLinks(); link++) - { - // Only dump if link source - if (node == pNode->GetLinkByIndex(link)->m_iSrcID) - { - totalNumLinks++; - } - } - } - - // ------------------------------- - // Dump all the links to the file - // ------------------------------- - buf.PutInt( totalNumLinks ); - - for (node = 0; node < m_pNetwork->m_iNumNodes; node++) - { - CAI_Node *pNode = m_pNetwork->GetNode(node); - - for (int link = 0; link < pNode->NumLinks(); link++) - { - // Only dump if link source - CAI_Link *pLink = pNode->GetLinkByIndex(link); - if (node == pLink->m_iSrcID) - { - buf.PutShort( pLink->m_iSrcID ); - buf.PutShort( pLink->m_iDestID ); - buf.Put( pLink->m_iAcceptedMoveTypes, sizeof( pLink->m_iAcceptedMoveTypes) ); - } - } - } - - // ------------------------------- - // Dump WC lookup table - // ------------------------------- - CUtlMap 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;hullFPrintf ( 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;hullFPrintf ( 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 usedIds; - CUtlRBTree 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;hullm_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;hullm_iAcceptedMoveTypes[hull]); - } - // Now add link to source and destination nodes - m_pAInode[new_link->m_iSrcID]->AddLink(new_link); - m_pAInode[new_link->m_iDestID]->AddLink(new_link); - } - - // ------------------------------- - // Load WC lookup table - // ------------------------------- - m_pNodeIndexTable = new int[m_iNumNodes]; - - for (node = 0; node < m_iNumNodes; node++) - { - fscanf( file, "%d\n",&m_pNodeIndexTable[node]); - } - - CAI_NetworkManager::NetworksLoaded() = true; - fclose(file); -} -*/ - -//----------------------------------------------------------------------------- -// Purpose: Deletes all AINetworks from memory -//----------------------------------------------------------------------------- - -void CAI_NetworkManager::DeleteAllAINetworks(void) -{ - CAI_DynamicLink::gm_bInitialized = false; - gm_fNetworksLoaded = false; - g_pBigAINet = NULL; -} - - -//----------------------------------------------------------------------------- -// Purpose: Only called if network has changed since last time level -// was loaded -//----------------------------------------------------------------------------- - -void CAI_NetworkManager::BuildNetworkGraph( void ) -{ - if ( m_bDontSaveGraph ) - return; - - CAI_DynamicLink::gm_bInitialized = false; - g_AINetworkBuilder.Build( m_pNetwork ); - - // If I'm loading for the first time save. Otherwise I'm - // doing a wc edit and I don't want to save - if (!CAI_NetworkManager::NetworksLoaded()) - { - SaveNetworkGraph(); - - gm_fNetworksLoaded = true; - } -} - -//------------------------------------------------------------------------------ -bool g_bAIDisabledByUser = false; - -void CAI_NetworkManager::InitializeAINetworks() -{ - // For not just create a single AI Network called "BigNet" - // At some later point we may have mulitple AI networks - CAI_NetworkManager *pNetwork; - g_pAINetworkManager = pNetwork = CREATE_ENTITY( CAI_NetworkManager, "ai_network" ); - pNetwork->AddEFlags( EFL_KEEP_ON_RECREATE_ENTITIES ); - g_pBigAINet = pNetwork->GetNetwork(); - pNetwork->SetName( AllocPooledString("BigNet") ); - pNetwork->Spawn(); - if ( engine->IsInEditMode() ) - { - g_ai_norebuildgraph.SetValue( 0 ); - } - if ( CAI_NetworkManager::IsAIFileCurrent( STRING( gpGlobals->mapname ) ) ) - { - pNetwork->LoadNetworkGraph(); - if ( !g_bAIDisabledByUser ) - { - CAI_BaseNPC::m_nDebugBits &= ~bits_debugDisableAI; - } - } - - // Reset node counter used during load - CNodeEnt::m_nNodeCount = 0; - - pNetwork->SetThink( &CAI_NetworkManager::DelayedInit ); - pNetwork->SetNextThink( gpGlobals->curtime ); -} - -// UNDONE: Where should this be defined? -#ifndef MAX_PATH -#define MAX_PATH 256 -#endif - -//----------------------------------------------------------------------------- -// Purpose: Returns true if the AINetwork data files are up to date -//----------------------------------------------------------------------------- - -bool CAI_NetworkManager::IsAIFileCurrent ( const char *szMapName ) -{ - char szBspFilename[MAX_PATH]; - char szGraphFilename[MAX_PATH]; - - if ( !g_pGameRules->FAllowNPCs() ) - { - return false; - } - - if ( IsX360() && ( filesystem->GetDVDMode() == DVDMODE_STRICT ) ) - { - // dvd build process validates and guarantees correctness, timestamps are allowed to be wrong - return true; - } - - { - const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" ); - char szLoweredGameDir[256]; - Q_strncpy( szLoweredGameDir, pGameDir, sizeof( szLoweredGameDir ) ); - Q_strlower( szLoweredGameDir ); - - if ( !V_stricmp( szLoweredGameDir, "hl2" ) || !V_stricmp( szLoweredGameDir, "episodic" ) || !V_stricmp( szLoweredGameDir, "ep2" ) || !V_stricmp( szLoweredGameDir, "portal" ) || !V_stricmp( szLoweredGameDir, "lostcoast" ) ) - { - // we shipped good node graphs for our games - return true; - } - } - - Q_snprintf( szBspFilename, sizeof( szBspFilename ), "maps/%s%s.bsp" ,szMapName, GetPlatformExt() ); - Q_snprintf( szGraphFilename, sizeof( szGraphFilename ), "maps/graphs/%s%s.ain", szMapName, GetPlatformExt() ); - - int iCompare; - if ( engine->CompareFileTime( szBspFilename, szGraphFilename, &iCompare ) ) - { - if ( iCompare > 0 ) - { - // BSP file is newer. - if ( g_ai_norebuildgraph.GetInt() ) - { - // The user has specified that they wish to override the - // rebuilding of outdated nodegraphs (see top of this file) - if ( filesystem->FileExists( szGraphFilename ) ) - { - // Display these messages only if the graph exists, and the - // user is asking to override the rebuilding. If the graph does - // not exist, we're going to build it whether the user wants to or - // not. - DevMsg( 2, ".AIN File will *NOT* be updated. User Override.\n\n" ); - DevMsg( "\n*****Node Graph Rebuild OVERRIDDEN by user*****\n\n" ); - } - return true; - } - else - { - // Graph is out of date. Rebuild at usual. - DevMsg( 2, ".AIN File will be updated\n\n" ); - return false; - } - } - return true; - } - return false; -} - -//------------------------------------------------------------------------------ - -void CAI_NetworkManager::Spawn ( void ) -{ - SetSolid( SOLID_NONE ); - SetMoveType( MOVETYPE_NONE ); -} - -//------------------------------------------------------------------------------ - -void CAI_NetworkManager::DelayedInit( void ) -{ - if ( !g_pGameRules->FAllowNPCs() ) - { - SetThink ( NULL ); - return; - } - - if ( !g_ai_norebuildgraph.GetInt() ) - { - // ---------------------------------------------------------- - // Actually enter DelayedInit twice when rebuilding the - // node graph. The first time through we just print the - // warning message. We only actually do the rebuild on - // the second pass to make sure the message hits the screen - // ---------------------------------------------------------- - if (m_bNeedGraphRebuild) - { - Assert( !m_bDontSaveGraph ); - - BuildNetworkGraph(); // For now only one AI Network - - if (engine->IsInEditMode()) - { - engine->ServerCommand("exec map_edit.cfg\n"); - } - - SetThink ( NULL ); - if ( !g_bAIDisabledByUser ) - { - CAI_BaseNPC::m_nDebugBits &= ~bits_debugDisableAI; - } - } - - - // -------------------------------------------- - // If I haven't loaded a network, or I'm in - // WorldCraft edit mode rebuild the network - // -------------------------------------------- - else if ( !m_bDontSaveGraph && ( !CAI_NetworkManager::NetworksLoaded() || engine->IsInEditMode() ) ) - { -#ifdef _WIN32 - // -------------------------------------------------------- - // If in edit mode start WC session and make sure we are - // running the same map in WC and the engine - // -------------------------------------------------------- - if (engine->IsInEditMode()) - { - int status = Editor_BeginSession(STRING(gpGlobals->mapname), gpGlobals->mapversion, false); - if (status == Editor_NotRunning) - { - DevMsg("\nAborting map_edit\nWorldcraft not running...\n\n"); - UTIL_CenterPrintAll( "Worldcraft not running...\n" ); - engine->ServerCommand("disconnect\n"); - SetThink(NULL); - return; - } - else if (status == Editor_BadCommand) - { - DevMsg("\nAborting map_edit\nWC/Engine map versions different...\n\n"); - UTIL_CenterPrintAll( "WC/Engine map versions different...\n" ); - engine->ServerCommand("disconnect\n"); - SetThink(NULL); - return; - } - else - { - // Increment version number when session begins - gpGlobals->mapversion++; - } - } -#endif - - DevMsg( "Node Graph out of Date. Rebuilding... (%d, %d, %d)\n", (int)m_bDontSaveGraph, (int)!CAI_NetworkManager::NetworksLoaded(), (int) engine->IsInEditMode() ); - UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding...\n" ); - m_bNeedGraphRebuild = true; - g_pAINetworkManager->SetNextThink( gpGlobals->curtime + 1 ); - return; - } - - } - - // -------------------------------------------- - // Initialize any dynamic links - // -------------------------------------------- - CAI_DynamicLink::InitDynamicLinks(); - FixupHints(); - - GetEditOps()->OnInit(); - - m_fInitalized = true; - - if ( g_AI_Manager.NumAIs() != 0 && g_pBigAINet->NumNodes() == 0 ) - DevMsg( "WARNING: Level contains NPCs but has no path nodes\n" ); -} - -//------------------------------------------------------------------------------ - -void CAI_NetworkManager::FixupHints() -{ - AIHintIter_t iter; - CAI_Hint *pHint = CAI_HintManager::GetFirstHint( &iter ); - while ( pHint ) - { - pHint->FixupTargetNode(); - pHint = CAI_HintManager::GetNextHint( &iter ); - } -} - -//----------------------------------------------------------------------------- -// CAI_NetworkEditTools -//----------------------------------------------------------------------------- - -CAI_Node* CAI_NetworkEditTools::m_pLastDeletedNode = NULL; // For undo in wc edit mode -int CAI_NetworkEditTools::m_iHullDrawNum = HULL_HUMAN; // Which hulls to draw -int CAI_NetworkEditTools::m_iVisibilityNode = NO_NODE; -int CAI_NetworkEditTools::m_iGConnectivityNode = NO_NODE; -bool CAI_NetworkEditTools::m_bAirEditMode = false; -bool CAI_NetworkEditTools::m_bLinkEditMode = false; -float CAI_NetworkEditTools::m_flAirEditDistance = 300; - -#ifdef AI_PERF_MON - // Performance stats (only for development) - int CAI_NetworkEditTools::m_nPerfStatNN = 0; - int CAI_NetworkEditTools::m_nPerfStatPB = 0; - float CAI_NetworkEditTools::m_fNextPerfStatTime = -1; -#endif - -//------------------------------------------------------------------------------ - -void CAI_NetworkEditTools::OnInit() -{ - // -------------------------------------------- - // If I'm not in edit mode delete WC ID table - // -------------------------------------------- - if ( !engine->IsInEditMode() ) - { -// delete[] m_pNodeIndexTable; // For now only one AI Network called "BigNet" -// m_pNodeIndexTable = NULL; - } -} - -//------------------------------------------------------------------------------ -// Purpose : Given a WorldCraft node ID, return the associated engine ID -// Input : -// Output : -//------------------------------------------------------------------------------ -int CAI_NetworkEditTools::GetNodeIdFromWCId( int nWCId ) -{ - if ( nWCId == -1 ) - return -1; - - if (!m_pNodeIndexTable) - { - DevMsg("ERROR: Trying to get WC ID with no table!\n"); - return -1; - } - - if (!m_pNetwork->NumNodes()) - { - DevMsg("ERROR: Trying to get WC ID with no network!\n"); - return -1; - } - - for (int i=0;iNumNodes();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;nodeNumNodes();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;nodeNumNodes();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;nodeNumLinks();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;nodeGetType() != 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;linkNumLinks();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 <> 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; - } - - /* <> 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;hullm_iAcceptedMoveTypes[hull] = acceptedMotions[hull]; - } - DebugConnectMsg( pNode->m_iID, i, " Added link\n" ); - } - } - else - { - m_NeighborsTable[pNode->m_iID].Clear(pDestNode->m_iID); - DebugConnectMsg(pNode->m_iID, i, " NO LINK\n" ); - } - } - else - DebugConnectMsg( pNode->m_iID, i, " NO LINK (not neighbors)\n" ); - } -} - -//----------------------------------------------------------------------------- +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "fmtstr.h" +#include "filesystem.h" +#include "filesystem/IQueuedLoader.h" +#include "utlbuffer.h" +#include "utlrbtree.h" +#include "editor_sendcommand.h" + +#include "ai_networkmanager.h" +#include "ai_network.h" +#include "ai_node.h" +#include "ai_navigator.h" +#include "ai_link.h" +#include "ai_dynamiclink.h" +#include "ai_initutils.h" +#include "ai_moveprobe.h" +#include "ai_hull.h" +#include "ndebugoverlay.h" +#include "ai_hint.h" +#include "tier0/icommandline.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Increment this to force rebuilding of all networks +#define AINET_VERSION_NUMBER 37 + +//----------------------------------------------------------------------------- + +int g_DebugConnectNode1 = -1; +int g_DebugConnectNode2 = -1; +#define DebuggingConnect( node1, node2 ) ( ( node1 == g_DebugConnectNode1 && node2 == g_DebugConnectNode2 ) || ( node1 == g_DebugConnectNode2 && node2 == g_DebugConnectNode1 ) ) + +inline void DebugConnectMsg( int node1, int node2, const char *pszFormat, ... ) +{ + if ( DebuggingConnect( node1, node2 ) ) + { + char string[ 2048 ]; + va_list argptr; + va_start( argptr, pszFormat ); + Q_vsnprintf( string, sizeof(string), pszFormat, argptr ); + va_end( argptr ); + + DevMsg( "%s", string ); + } +} + +CON_COMMAND( ai_debug_node_connect, "Debug the attempted connection between two nodes" ) +{ + g_DebugConnectNode1 = atoi( args[1] ); + g_DebugConnectNode2 = atoi( args[2] ); + + DevMsg( "ai_debug_node_connect: debugging enbabled for %d <--> %d\n", g_DebugConnectNode1, g_DebugConnectNode2 ); +} + +//----------------------------------------------------------------------------- +// This CVAR allows level designers to override the building +// of node graphs due to date conflicts with the BSP and AIN +// files. That way they don't have to wait for the node graph +// to rebuild following small only-ents changes. This CVAR +// always defaults to 0 and must be set at the command +// line to properly override the node graph building. + +ConVar g_ai_norebuildgraph( "ai_norebuildgraph", "0" ); + + +//----------------------------------------------------------------------------- +// CAI_NetworkManager +// +//----------------------------------------------------------------------------- + +CAI_NetworkManager *g_pAINetworkManager; + +//----------------------------------------------------------------------------- + +bool CAI_NetworkManager::gm_fNetworksLoaded; + +LINK_ENTITY_TO_CLASS(ai_network,CAI_NetworkManager); + +BEGIN_DATADESC( CAI_NetworkManager ) + + DEFINE_FIELD( m_bNeedGraphRebuild, FIELD_BOOLEAN ), + // m_pEditOps + // m_pNetwork + // DEFINE_FIELD( m_bDontSaveGraph, FIELD_BOOLEAN ), + DEFINE_FIELD( m_fInitalized, FIELD_BOOLEAN ), + + // Function pointers + DEFINE_FUNCTION( DelayedInit ), + DEFINE_FUNCTION( RebuildThink ), + +END_DATADESC() + + +//----------------------------------------------------------------------------- + +CAI_NetworkManager::CAI_NetworkManager(void) +{ + m_pNetwork = new CAI_Network; + m_pEditOps = new CAI_NetworkEditTools(this); + m_bNeedGraphRebuild = false; + m_fInitalized = false; + CAI_DynamicLink::gm_bInitialized = false; + + // --------------------------------- + // Add to linked list of networks + // --------------------------------- +}; + +//----------------------------------------------------------------------------- + +CAI_NetworkManager::~CAI_NetworkManager(void) +{ + // --------------------------------------- + // Remove from linked list of AINetworks + // --------------------------------------- + delete m_pEditOps; + delete m_pNetwork; + if ( g_pAINetworkManager == this ) + { + g_pAINetworkManager = NULL; + } +} + + +//------------------------------------------------------------------------------ +// Purpose : Think function so we can put message on screen saying we are +// going to rebuild the network, before we hang during the rebuild +//------------------------------------------------------------------------------ + +void CAI_NetworkManager::RebuildThink( void ) +{ + SetThink(NULL); + + GetEditOps()->m_debugNetOverlays &= ~bits_debugNeedRebuild; + StartRebuild( ); +} + +//------------------------------------------------------------------------------ +// Purpose : Delay function so we can put message on screen saying we are +// going to rebuild the network, before we hang during the rebuild +//------------------------------------------------------------------------------ + +void CAI_NetworkManager::RebuildNetworkGraph( void ) +{ + if (m_pfnThink != (void (CBaseEntity::*)())&CAI_NetworkManager::RebuildThink) + { + UTIL_CenterPrintAll( "Doing partial rebuild of Node Graph...\n" ); + SetThink(&CAI_NetworkManager::RebuildThink); + SetNextThink( gpGlobals->curtime + 0.1f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Used for WC edit move to rebuild the network around the given +// location. Rebuilding the entire network takes too long +//----------------------------------------------------------------------------- + +void CAI_NetworkManager::StartRebuild( void ) +{ + CAI_DynamicLink::gm_bInitialized = false; + + g_AINetworkBuilder.Rebuild( m_pNetwork ); + + // ------------------------------------------------------------ + // Purge any dynamic links for links that don't exist any more + // ------------------------------------------------------------ + CAI_DynamicLink::PurgeDynamicLinks(); + + + // ------------------------ + // Reset all dynamic links + // ------------------------ + CAI_DynamicLink::ResetDynamicLinks(); + + // -------------------------------------------------- + // Update display of usable nodes for displayed hull + // -------------------------------------------------- + GetEditOps()->RecalcUsableNodesForHull(); + + GetEditOps()->ClearRebuildFlags(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called by save restore code if no valid load graph was loaded at restore time. +// Prevents writing out of a "bogus" node graph... +// Input : - +//----------------------------------------------------------------------------- +void CAI_NetworkManager::MarkDontSaveGraph() +{ + m_bDontSaveGraph = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Only called if network has changed since last time level +// was loaded +// Input : +// Output : +//----------------------------------------------------------------------------- + +void CAI_NetworkManager::SaveNetworkGraph( void ) +{ + if ( m_bDontSaveGraph ) + return; + + if ( !m_bNeedGraphRebuild ) + return; + + //if ( g_AI_Manager.NumAIs() && m_pNetwork->m_iNumNodes == 0 ) + //{ + // return; + //} + + if ( !g_pGameRules->FAllowNPCs() ) + { + return; + } + + // ----------------------------- + // Make sure directories have been made + // ----------------------------- + char szNrpFilename [MAX_PATH];// text node report filename + Q_strncpy( szNrpFilename, "maps/graphs" ,sizeof(szNrpFilename)); + + // Usually adding on the map filename and stripping it does nothing, but if the map is under a subdir, + // this makes it create the correct subdir under maps/graphs. + char tempFilename[MAX_PATH]; + Q_snprintf( tempFilename, sizeof( tempFilename ), "%s/%s", szNrpFilename, STRING( gpGlobals->mapname ) ); + + // Remove the filename. + int len = strlen( tempFilename ); + for ( int i=0; i < len; i++ ) + { + if ( tempFilename[len-i-1] == '/' || tempFilename[len-i-1] == '\\' ) + { + tempFilename[len-i-1] = 0; + break; + } + } + + // Make sure the directories we need exist. + filesystem->CreateDirHierarchy( tempFilename, "DEFAULT_WRITE_PATH" ); + + // Now add the real map filename. + Q_strncat( szNrpFilename, "/", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS ); + Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), sizeof( szNrpFilename ), COPY_ALL_CHARACTERS ); + Q_strncat( szNrpFilename, IsX360() ? ".360.ain" : ".ain", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS ); + + CUtlBuffer buf; + + // --------------------------- + // Save the version number + // --------------------------- + buf.PutInt(AINET_VERSION_NUMBER); + buf.PutInt(gpGlobals->mapversion); + + // ------------------------------- + // Dump all the nodes to the file + // ------------------------------- + buf.PutInt( m_pNetwork->m_iNumNodes); + + int node; + int totalNumLinks = 0; + for ( node = 0; node < m_pNetwork->m_iNumNodes; node++) + { + CAI_Node *pNode = m_pNetwork->GetNode(node); + Assert( pNode->GetZone() != AI_NODE_ZONE_UNKNOWN ); + + buf.PutFloat( pNode->GetOrigin().x ); + buf.PutFloat( pNode->GetOrigin().y ); + buf.PutFloat( pNode->GetOrigin().z ); + buf.PutFloat( pNode->GetYaw() ); + buf.Put( pNode->m_flVOffset, sizeof( pNode->m_flVOffset ) ); + buf.PutChar( pNode->GetType() ); + if ( IsX360() ) + { + buf.SeekPut( CUtlBuffer::SEEK_CURRENT, 3 ); + } + buf.PutUnsignedShort( pNode->m_eNodeInfo ); + buf.PutShort( pNode->GetZone() ); + + for (int link = 0; link < pNode->NumLinks(); link++) + { + // Only dump if link source + if (node == pNode->GetLinkByIndex(link)->m_iSrcID) + { + totalNumLinks++; + } + } + } + + // ------------------------------- + // Dump all the links to the file + // ------------------------------- + buf.PutInt( totalNumLinks ); + + for (node = 0; node < m_pNetwork->m_iNumNodes; node++) + { + CAI_Node *pNode = m_pNetwork->GetNode(node); + + for (int link = 0; link < pNode->NumLinks(); link++) + { + // Only dump if link source + CAI_Link *pLink = pNode->GetLinkByIndex(link); + if (node == pLink->m_iSrcID) + { + buf.PutShort( pLink->m_iSrcID ); + buf.PutShort( pLink->m_iDestID ); + buf.Put( pLink->m_iAcceptedMoveTypes, sizeof( pLink->m_iAcceptedMoveTypes) ); + } + } + } + + // ------------------------------- + // Dump WC lookup table + // ------------------------------- + CUtlMap 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;hullFPrintf ( 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;hullFPrintf ( 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 usedIds; + CUtlRBTree 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;hullm_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;hullm_iAcceptedMoveTypes[hull]); + } + // Now add link to source and destination nodes + m_pAInode[new_link->m_iSrcID]->AddLink(new_link); + m_pAInode[new_link->m_iDestID]->AddLink(new_link); + } + + // ------------------------------- + // Load WC lookup table + // ------------------------------- + m_pNodeIndexTable = new int[m_iNumNodes]; + + for (node = 0; node < m_iNumNodes; node++) + { + fscanf( file, "%d\n",&m_pNodeIndexTable[node]); + } + + CAI_NetworkManager::NetworksLoaded() = true; + fclose(file); +} +*/ + +//----------------------------------------------------------------------------- +// Purpose: Deletes all AINetworks from memory +//----------------------------------------------------------------------------- + +void CAI_NetworkManager::DeleteAllAINetworks(void) +{ + CAI_DynamicLink::gm_bInitialized = false; + gm_fNetworksLoaded = false; + g_pBigAINet = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Only called if network has changed since last time level +// was loaded +//----------------------------------------------------------------------------- + +void CAI_NetworkManager::BuildNetworkGraph( void ) +{ + if ( m_bDontSaveGraph ) + return; + + CAI_DynamicLink::gm_bInitialized = false; + g_AINetworkBuilder.Build( m_pNetwork ); + + // If I'm loading for the first time save. Otherwise I'm + // doing a wc edit and I don't want to save + if (!CAI_NetworkManager::NetworksLoaded()) + { + SaveNetworkGraph(); + + gm_fNetworksLoaded = true; + } +} + +//------------------------------------------------------------------------------ +bool g_bAIDisabledByUser = false; + +void CAI_NetworkManager::InitializeAINetworks() +{ + // For not just create a single AI Network called "BigNet" + // At some later point we may have mulitple AI networks + CAI_NetworkManager *pNetwork; + g_pAINetworkManager = pNetwork = CREATE_ENTITY( CAI_NetworkManager, "ai_network" ); + pNetwork->AddEFlags( EFL_KEEP_ON_RECREATE_ENTITIES ); + g_pBigAINet = pNetwork->GetNetwork(); + pNetwork->SetName( AllocPooledString("BigNet") ); + pNetwork->Spawn(); + if ( engine->IsInEditMode() ) + { + g_ai_norebuildgraph.SetValue( 0 ); + } + if ( CAI_NetworkManager::IsAIFileCurrent( STRING( gpGlobals->mapname ) ) ) + { + pNetwork->LoadNetworkGraph(); + if ( !g_bAIDisabledByUser ) + { + CAI_BaseNPC::m_nDebugBits &= ~bits_debugDisableAI; + } + } + + // Reset node counter used during load + CNodeEnt::m_nNodeCount = 0; + + pNetwork->SetThink( &CAI_NetworkManager::DelayedInit ); + pNetwork->SetNextThink( gpGlobals->curtime ); +} + +// UNDONE: Where should this be defined? +#ifndef MAX_PATH +#define MAX_PATH 256 +#endif + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the AINetwork data files are up to date +//----------------------------------------------------------------------------- + +bool CAI_NetworkManager::IsAIFileCurrent ( const char *szMapName ) +{ + char szBspFilename[MAX_PATH]; + char szGraphFilename[MAX_PATH]; + + if ( !g_pGameRules->FAllowNPCs() ) + { + return false; + } + + if ( IsX360() && ( filesystem->GetDVDMode() == DVDMODE_STRICT ) ) + { + // dvd build process validates and guarantees correctness, timestamps are allowed to be wrong + return true; + } + + { + const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" ); + char szLoweredGameDir[256]; + Q_strncpy( szLoweredGameDir, pGameDir, sizeof( szLoweredGameDir ) ); + Q_strlower( szLoweredGameDir ); + + if ( !V_stricmp( szLoweredGameDir, "hl2" ) || !V_stricmp( szLoweredGameDir, "episodic" ) || !V_stricmp( szLoweredGameDir, "ep2" ) || !V_stricmp( szLoweredGameDir, "portal" ) || !V_stricmp( szLoweredGameDir, "lostcoast" ) ) + { + // we shipped good node graphs for our games + return true; + } + } + + Q_snprintf( szBspFilename, sizeof( szBspFilename ), "maps/%s%s.bsp" ,szMapName, GetPlatformExt() ); + Q_snprintf( szGraphFilename, sizeof( szGraphFilename ), "maps/graphs/%s%s.ain", szMapName, GetPlatformExt() ); + + int iCompare; + if ( engine->CompareFileTime( szBspFilename, szGraphFilename, &iCompare ) ) + { + if ( iCompare > 0 ) + { + // BSP file is newer. + if ( g_ai_norebuildgraph.GetInt() ) + { + // The user has specified that they wish to override the + // rebuilding of outdated nodegraphs (see top of this file) + if ( filesystem->FileExists( szGraphFilename ) ) + { + // Display these messages only if the graph exists, and the + // user is asking to override the rebuilding. If the graph does + // not exist, we're going to build it whether the user wants to or + // not. + DevMsg( 2, ".AIN File will *NOT* be updated. User Override.\n\n" ); + DevMsg( "\n*****Node Graph Rebuild OVERRIDDEN by user*****\n\n" ); + } + return true; + } + else + { + // Graph is out of date. Rebuild at usual. + DevMsg( 2, ".AIN File will be updated\n\n" ); + return false; + } + } + return true; + } + return false; +} + +//------------------------------------------------------------------------------ + +void CAI_NetworkManager::Spawn ( void ) +{ + SetSolid( SOLID_NONE ); + SetMoveType( MOVETYPE_NONE ); +} + +//------------------------------------------------------------------------------ + +void CAI_NetworkManager::DelayedInit( void ) +{ + if ( !g_pGameRules->FAllowNPCs() ) + { + SetThink ( NULL ); + return; + } + + if ( !g_ai_norebuildgraph.GetInt() ) + { + // ---------------------------------------------------------- + // Actually enter DelayedInit twice when rebuilding the + // node graph. The first time through we just print the + // warning message. We only actually do the rebuild on + // the second pass to make sure the message hits the screen + // ---------------------------------------------------------- + if (m_bNeedGraphRebuild) + { + Assert( !m_bDontSaveGraph ); + + BuildNetworkGraph(); // For now only one AI Network + + if (engine->IsInEditMode()) + { + engine->ServerCommand("exec map_edit.cfg\n"); + } + + SetThink ( NULL ); + if ( !g_bAIDisabledByUser ) + { + CAI_BaseNPC::m_nDebugBits &= ~bits_debugDisableAI; + } + } + + + // -------------------------------------------- + // If I haven't loaded a network, or I'm in + // WorldCraft edit mode rebuild the network + // -------------------------------------------- + else if ( !m_bDontSaveGraph && ( !CAI_NetworkManager::NetworksLoaded() || engine->IsInEditMode() ) ) + { +#ifdef _WIN32 + // -------------------------------------------------------- + // If in edit mode start WC session and make sure we are + // running the same map in WC and the engine + // -------------------------------------------------------- + if (engine->IsInEditMode()) + { + int status = Editor_BeginSession(STRING(gpGlobals->mapname), gpGlobals->mapversion, false); + if (status == Editor_NotRunning) + { + DevMsg("\nAborting map_edit\nWorldcraft not running...\n\n"); + UTIL_CenterPrintAll( "Worldcraft not running...\n" ); + engine->ServerCommand("disconnect\n"); + SetThink(NULL); + return; + } + else if (status == Editor_BadCommand) + { + DevMsg("\nAborting map_edit\nWC/Engine map versions different...\n\n"); + UTIL_CenterPrintAll( "WC/Engine map versions different...\n" ); + engine->ServerCommand("disconnect\n"); + SetThink(NULL); + return; + } + else + { + // Increment version number when session begins + gpGlobals->mapversion++; + } + } +#endif + + DevMsg( "Node Graph out of Date. Rebuilding... (%d, %d, %d)\n", (int)m_bDontSaveGraph, (int)!CAI_NetworkManager::NetworksLoaded(), (int) engine->IsInEditMode() ); + UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding...\n" ); + m_bNeedGraphRebuild = true; + g_pAINetworkManager->SetNextThink( gpGlobals->curtime + 1 ); + return; + } + + } + + // -------------------------------------------- + // Initialize any dynamic links + // -------------------------------------------- + CAI_DynamicLink::InitDynamicLinks(); + FixupHints(); + + GetEditOps()->OnInit(); + + m_fInitalized = true; + + if ( g_AI_Manager.NumAIs() != 0 && g_pBigAINet->NumNodes() == 0 ) + DevMsg( "WARNING: Level contains NPCs but has no path nodes\n" ); +} + +//------------------------------------------------------------------------------ + +void CAI_NetworkManager::FixupHints() +{ + AIHintIter_t iter; + CAI_Hint *pHint = CAI_HintManager::GetFirstHint( &iter ); + while ( pHint ) + { + pHint->FixupTargetNode(); + pHint = CAI_HintManager::GetNextHint( &iter ); + } +} + +//----------------------------------------------------------------------------- +// CAI_NetworkEditTools +//----------------------------------------------------------------------------- + +CAI_Node* CAI_NetworkEditTools::m_pLastDeletedNode = NULL; // For undo in wc edit mode +int CAI_NetworkEditTools::m_iHullDrawNum = HULL_HUMAN; // Which hulls to draw +int CAI_NetworkEditTools::m_iVisibilityNode = NO_NODE; +int CAI_NetworkEditTools::m_iGConnectivityNode = NO_NODE; +bool CAI_NetworkEditTools::m_bAirEditMode = false; +bool CAI_NetworkEditTools::m_bLinkEditMode = false; +float CAI_NetworkEditTools::m_flAirEditDistance = 300; + +#ifdef AI_PERF_MON + // Performance stats (only for development) + int CAI_NetworkEditTools::m_nPerfStatNN = 0; + int CAI_NetworkEditTools::m_nPerfStatPB = 0; + float CAI_NetworkEditTools::m_fNextPerfStatTime = -1; +#endif + +//------------------------------------------------------------------------------ + +void CAI_NetworkEditTools::OnInit() +{ + // -------------------------------------------- + // If I'm not in edit mode delete WC ID table + // -------------------------------------------- + if ( !engine->IsInEditMode() ) + { +// delete[] m_pNodeIndexTable; // For now only one AI Network called "BigNet" +// m_pNodeIndexTable = NULL; + } +} + +//------------------------------------------------------------------------------ +// Purpose : Given a WorldCraft node ID, return the associated engine ID +// Input : +// Output : +//------------------------------------------------------------------------------ +int CAI_NetworkEditTools::GetNodeIdFromWCId( int nWCId ) +{ + if ( nWCId == -1 ) + return -1; + + if (!m_pNodeIndexTable) + { + DevMsg("ERROR: Trying to get WC ID with no table!\n"); + return -1; + } + + if (!m_pNetwork->NumNodes()) + { + DevMsg("ERROR: Trying to get WC ID with no network!\n"); + return -1; + } + + for (int i=0;iNumNodes();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;nodeNumNodes();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;nodeNumNodes();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;nodeNumLinks();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;nodeGetType() != 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;linkNumLinks();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 <> 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; + } + + /* <> 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;hullm_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" ); + } +} + +//----------------------------------------------------------------------------- -- cgit v1.2.3