//========= Copyright © 1996-2005, 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" // 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 41 //----------------------------------------------------------------------------- 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 ) ) { DevMsg( "%s", CFmtStr( &pszFormat ).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" ); ConVar g_ai_threadedgraphbuild( "g_ai_threadedgraphbuild", "0", FCVAR_NONE, "If true, use experimental threaded node graph building." ); //----------------------------------------------------------------------------- // 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( ThreadedInit ), 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 defined( PORTAL2 ) // not used return; #endif 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, PLATFORM_EXT ".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 ( IsGameConsole() ) { buf.SeekPut( CUtlBuffer::SEEK_CURRENT, 3 ); } buf.PutInt( ( pNode->m_eNodeInfo & bits_NODE_SAVE_MASK ) ); 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 defined( PORTAL2 ) // not used return; #endif // --------------------------------------------------- // If I'm in edit mode don't load, always recalculate // --------------------------------------------------- if (engine->IsInEditMode()) { return; } if ( !g_pGameRules->FAllowNPCs() ) { return; } // ----------------------------- // 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, PLATFORM_EXT ".ain", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS ); MEM_ALLOC_CREDIT(); // Read the file in one gulp CUtlBuffer buf; bool bHaveAIN = false; if ( IsGameConsole() && 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; } // --------------------------- // 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; } buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); int version = buf.GetInt(); if ( version != AINET_VERSION_NUMBER) { DevMsg( "AI node graph %s is out of date\n", szNrpFilename ); return; } int mapversion = buf.GetInt(); if ( mapversion != gpGlobals->mapversion && !g_ai_norebuildgraph.GetBool() ) { DevMsg( "AI node graph %s is out of date (map version changed)\n", szNrpFilename ); return; } // ---------------------------------------- // 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; } // ------------------------------------------------------------------------ // 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 ( IsGameConsole() ) { buf.SeekGet( CUtlBuffer::SEEK_CURRENT, 3 ); } new_node->m_eNodeInfo = buf.GetInt(); 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; if ( g_ai_threadedgraphbuild.GetBool() && !engine->IsInEditMode() ) { pNetwork->SetThink( &CAI_NetworkManager::ThreadedInit ); } else { 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 ( IsGameConsole() && ( filesystem->GetDVDMode() == DVDMODE_STRICT ) ) { // dvd build process validates and guarantees correctness, timestamps are allowed to be wrong 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 ); } //------------------------------------------------------------------------------ static bool SetupEditMode() { // CSGO disables regular NPC AI but we still want to support hammer_update_safe_entities so init this in edit mode 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"); } 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"); } else { // Increment version number when session begins gpGlobals->mapversion++; return true; } } return false; } void CAI_NetworkManager::DelayedInit( void ) { #if defined( PORTAL2 ) SetThink ( NULL ); m_fInitalized = true; #else if ( !g_pGameRules->FAllowNPCs() ) { SetThink ( NULL ); SetupEditMode(); 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()) { if ( !SetupEditMode() ) { SetThink( NULL ); return; } } #endif DevMsg( "Node Graph out of Date. Rebuilding...\n" ); if ( developer.GetInt() ) { 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" ); #endif } //------------------------------------------------------------------------------ void CAI_NetworkManager::ThreadedInit( void ) { Assert( !engine->IsInEditMode() ); if ( !g_pGameRules->FAllowNPCs() ) { SetThink ( NULL ); return; } if ( m_bDontSaveGraph ) { Assert( !m_bDontSaveGraph ); Warning( "m_bDontSaveGraph set, using synchronous map rebuild\n" ); SetThink( &CAI_NetworkManager::DelayedInit ); SetNextThink(gpGlobals->curtime); return DelayedInit(); } // We have three stages: // * graph not built yet // * graph building underway // * graph built if ( !CAI_NetworkManager::NetworksLoaded() ) { // maps not loaded from disk. switch ( m_ThreadedBuild.nBuildStage ) { case ThreadedGraphBuildData::BUILD_NOT_STARTED: { // seize the mutex while I set up the struct if ( !m_ThreadedBuild.mutex.TryLock() ) { AssertMsg( false, "FAILED to initiate threaded node graph build due to already locked mutex!\n" ); Warning( "FAILED to initiate threaded node graph build due to already locked mutex!" ); return; } // kick off a threaded map build. first, temporarily reset the global ai network // pointer as a hack so all ais think it doesn't exist yet m_ThreadedBuild.pBuildingNetwork = GetNetwork(); //#pragma message("Warning: find some way to prevent AI from using network before it's ready, but allowing the TestHull to still instantiate.") // g_pBigAINet = m_pNetwork = NULL; // // fire off a thread to build the map m_ThreadedBuild.nBuildStage = ThreadedGraphBuildData::BUILD_UNDERWAY; m_ThreadedBuild.job = CreateSimpleThread( ThreadedBuildJob, &m_ThreadedBuild ); // could SetThreadAffinity... CAI_DynamicLink::gm_bInitialized = false; // and off you go! m_ThreadedBuild.mutex.Unlock(); // print a "building" message UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding in background.\n" ); SetNextThink( gpGlobals->curtime + 1 ); return; break; } case ThreadedGraphBuildData::BUILD_UNDERWAY: { // print a "building" message UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding in background.\n" ); SetNextThink( gpGlobals->curtime + 1 ); return; break; } case ThreadedGraphBuildData::BUILD_DONE: // grab the lock again to make sure we're done if ( !m_ThreadedBuild.mutex.TryLock() ) { AssertMsg( false, "Had to wait for threaded node graph build lock even though it was supposedly done." ); m_ThreadedBuild.mutex.Lock(); } // set the global pointers again g_pBigAINet = m_pNetwork = m_ThreadedBuild.pBuildingNetwork; m_ThreadedBuild.pBuildingNetwork = NULL; ReleaseThreadHandle( m_ThreadedBuild.job ); m_ThreadedBuild.job = NULL; // 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; } // -------------------------------------------- // Initialize any dynamic links // -------------------------------------------- CAI_DynamicLink::InitDynamicLinks(); FixupHints(); break; default: AssertMsg1( false, "Invalid threaded node graph build stage %d!\n", (int) m_ThreadedBuild.nBuildStage ); } } // job's done 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" ); SetThink ( NULL ); } //------------------------------------------------------------------------------ void CAI_NetworkManager::FixupHints() { AIHintIter_t iter; CAI_Hint *pHint = CAI_HintManager::GetFirstHint( &iter ); while ( pHint ) { pHint->FixupTargetNode(); pHint = CAI_HintManager::GetNextHint( &iter ); } } uintp CAI_NetworkManager::ThreadedBuildJob( /* (ThreadedGraphBuildData *) */ void *pBuildData ) { ThreadedGraphBuildData * RESTRICT pBuild = reinterpret_cast(pBuildData); pBuild->mutex.Lock(); g_AINetworkBuilder.Build( pBuild->pBuildingNetwork ); pBuild->nBuildStage = ThreadedGraphBuildData::BUILD_DONE; pBuild->mutex.Unlock(); return 0; } //----------------------------------------------------------------------------- // 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); } else if (pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum] & bits_CAP_MOVE_CRAWL) { NDebugOverlay::Line( srcPos, desPos, 255, 255, 0, false, 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) continue; // -------------------- // 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 CAI_Link *pAILink = pAINode[node]->GetLinkByIndex(link); if ( pAILink->DestNodeID(node) >= node ) continue; int srcID = pAILink->m_iSrcID; int desID = pAILink->m_iDestID; Vector srcPos = pAINode[srcID]->GetPosition(m_iHullDrawNum); Vector desPos = pAINode[desID]->GetPosition(m_iHullDrawNum); int srcType = pAINode[srcID]->GetType(); int desType = pAINode[desID]->GetType(); int linkInfo = pAILink->m_LinkInfo; int moveTypes = pAILink->m_iAcceptedMoveTypes[m_iHullDrawNum]; bool IsDangerous = ( pAILink->m_nDangerCount > 0 ); bool bIsLastResort = ( pAILink->m_LinkInfo & bits_PREFER_AVOID ) != 0; // 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; } unsigned char r, g, b; // Draw in red if stale link if (linkInfo & bits_LINK_STALE_SUGGESTED) { r = 255; g = 0; b = 0; bIsLastResort = false; } // Draw in grey if link turned off else if (linkInfo & bits_LINK_OFF) { r = 100; g = 100; b = 100; bIsLastResort = false; } // Draw in yellow if link is dangerous else if ( IsDangerous ) { r = 192; g = 192; b = 0; } else if ((m_debugNetOverlays & bits_debugOverlayFlyConnections) && (moveTypes & bits_CAP_MOVE_FLY)) { r = 100; g = 255; b = 255; } else if (moveTypes & bits_CAP_MOVE_CLIMB) { r = 255; g = 0; b = 255; } else if (moveTypes & bits_CAP_MOVE_GROUND) { r = 0; g = 255; b = 50; } else if ((m_debugNetOverlays & bits_debugOverlayCrawlConnections) && (moveTypes & bits_CAP_MOVE_CRAWL)) { r = 255; g = 255; b = 255; } else if ((m_debugNetOverlays & bits_debugOverlayJumpConnections) && (moveTypes & bits_CAP_MOVE_JUMP) ) { r = 0; g = 0; b = 255; } 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; } } bool isCrawl = true; for ( int i = HULL_HUMAN; i < NUM_HULLS; i++ ) { if ( pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[i] & ~bits_CAP_MOVE_CRAWL ) { isCrawl = false; break; } } if ( ( isFly && (m_debugNetOverlays & bits_debugOverlayFlyConnections) ) || ( isJump && (m_debugNetOverlays & bits_debugOverlayJumpConnections) ) || ( isCrawl && (m_debugNetOverlays & bits_debugOverlayCrawlConnections) ) || ( !isFly && !isJump && !isCrawl ) ) { r = 100; g = 25; b = 25; } else { continue; } } if ( bIsLastResort ) { r /= 2; g /= 2; b /= 2; } NDebugOverlay::Line( srcPos, desPos, r, g, b, 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_FLUID,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_FLUID,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_FLUID,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_FLUID,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, GetAITraceMask_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 closer redundant link %d\n", testnode ); m_NeighborsTable[pNode->m_iID].Clear(checknode); } else { DebugConnectMsg( pNode->m_iID, testnode, " Revoking neighbor status 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,NAI_Hull::TraceMask( hull )) ) { 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,NAI_Hull::TraceMask( hull )) ) { 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), NAI_Hull::TraceMask(hull), 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), NAI_Hull::TraceMask(hull), 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), NAI_Hull::TraceMask(hull), 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; bool fCrawlFailed = 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, NAI_Hull::TraceMask(hull))) { DebugConnectMsg( srcId, destId, " Failed to stand at %d\n", srcId ); fStandFailed = true; } if (!m_pTestHull->GetMoveProbe()->CheckStandPosition( destPos, NAI_Hull::TraceMask(hull))) { 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, NAI_Hull::TraceMask(hull), AITGM_IGNORE_INITIAL_STAND_POS, NULL ); if ( fWalkFailed ) DebugConnectMsg( srcId, destId, " Failed to walk between nodes\n" ); } // Add to our list of acceptable hulls if ( !fStandFailed ) { if ( !fWalkFailed ) { result |= bits_CAP_MOVE_GROUND; DebugConnectMsg( srcId, destId, " Nodes connect for ground movement\n" ); } else { // Try it with a very large step height fCrawlFailed = !m_pTestHull->GetMoveProbe()->TestGroundMove( srcPos, destPos, NAI_Hull::TraceMask(hull), AITGM_IGNORE_INITIAL_STAND_POS | AITGM_CRAWL_LARGE_STEPS, NULL ); if ( !fCrawlFailed ) { DebugConnectMsg( srcId, destId, " Nodes connect for crawl movement\n" ); result |= bits_CAP_MOVE_CRAWL; } } } 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, NAI_Hull::TraceMask(hull), NULL, &moveTrace); if (!IsMoveBlocked(moveTrace)) { fJumpLegal = true; } pMoveProbe->MoveLimit( NAV_JUMP, destPos,srcPos, NAI_Hull::TraceMask(hull), 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" ); } } //-----------------------------------------------------------------------------