//========= Copyright <EFBFBD> 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 ( CFmtStr ( & pszFormat ) ) ;
}
}
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 ( m_bDontSaveGraph )
return ;
if ( ! m_bNeedGraphRebuild )
return ;
//if ( g_AI_Manager.NumAIs() && m_pNetwork->m_iNumNodes == 0 )
//{
// return;
//}
if ( ! g_pGameRules - > FAllowNPCs ( ) )
{
return ;
}
// -----------------------------
// Make sure directories have been made
// -----------------------------
char szNrpFilename [ MAX_PATH ] ; // text node report filename
Q_strncpy ( szNrpFilename , " maps/graphs " , sizeof ( szNrpFilename ) ) ;
// Usually adding on the map filename and stripping it does nothing, but if the map is under a subdir,
// this makes it create the correct subdir under maps/graphs.
char tempFilename [ MAX_PATH ] ;
Q_snprintf ( tempFilename , sizeof ( tempFilename ) , " %s/%s " , szNrpFilename , STRING ( gpGlobals - > mapname ) ) ;
// Remove the filename.
int len = strlen ( tempFilename ) ;
for ( int i = 0 ; i < len ; i + + )
{
if ( tempFilename [ len - i - 1 ] = = ' / ' | | tempFilename [ len - i - 1 ] = = ' \\ ' )
{
tempFilename [ len - i - 1 ] = 0 ;
break ;
}
}
// Make sure the directories we need exist.
filesystem - > CreateDirHierarchy ( tempFilename , " DEFAULT_WRITE_PATH " ) ;
// Now add the real map filename.
Q_strncat ( szNrpFilename , " / " , sizeof ( szNrpFilename ) , COPY_ALL_CHARACTERS ) ;
Q_strncat ( szNrpFilename , STRING ( gpGlobals - > mapname ) , sizeof ( szNrpFilename ) , COPY_ALL_CHARACTERS ) ;
Q_strncat ( szNrpFilename , IsX360 ( ) ? " .360.ain " : " .ain " , sizeof ( szNrpFilename ) , COPY_ALL_CHARACTERS ) ;
CUtlBuffer buf ;
// ---------------------------
// Save the version number
// ---------------------------
buf . PutInt ( AINET_VERSION_NUMBER ) ;
buf . PutInt ( gpGlobals - > mapversion ) ;
// -------------------------------
// Dump all the nodes to the file
// -------------------------------
buf . PutInt ( m_pNetwork - > m_iNumNodes ) ;
int node ;
int totalNumLinks = 0 ;
for ( node = 0 ; node < m_pNetwork - > m_iNumNodes ; node + + )
{
CAI_Node * pNode = m_pNetwork - > GetNode ( node ) ;
Assert ( pNode - > GetZone ( ) ! = AI_NODE_ZONE_UNKNOWN ) ;
buf . PutFloat ( pNode - > GetOrigin ( ) . x ) ;
buf . PutFloat ( pNode - > GetOrigin ( ) . y ) ;
buf . PutFloat ( pNode - > GetOrigin ( ) . z ) ;
buf . PutFloat ( pNode - > GetYaw ( ) ) ;
buf . Put ( pNode - > m_flVOffset , sizeof ( pNode - > m_flVOffset ) ) ;
buf . PutChar ( pNode - > GetType ( ) ) ;
if ( IsX360 ( ) )
{
buf . SeekPut ( CUtlBuffer : : SEEK_CURRENT , 3 ) ;
}
buf . 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 < int , int > wcIDs ;
SetDefLessFunc ( wcIDs ) ;
bool bCheckForProblems = false ;
for ( node = 0 ; node < m_pNetwork - > m_iNumNodes ; node + + )
{
int iPreviousNodeBinding = wcIDs . Find ( GetEditOps ( ) - > m_pNodeIndexTable [ node ] ) ;
if ( iPreviousNodeBinding ! = wcIDs . InvalidIndex ( ) )
{
if ( ! bCheckForProblems )
{
DevWarning ( " ******* MAP CONTAINS DUPLICATE HAMMER NODE IDS! CHECK FOR PROBLEMS IN HAMMER TO CORRECT ******* \n " ) ;
bCheckForProblems = true ;
}
DevWarning ( " AI node %d is associated with Hammer node %d, but %d is already bound to node %d \n " , node , GetEditOps ( ) - > m_pNodeIndexTable [ node ] , GetEditOps ( ) - > m_pNodeIndexTable [ node ] , wcIDs [ iPreviousNodeBinding ] ) ;
}
else
{
wcIDs . Insert ( GetEditOps ( ) - > m_pNodeIndexTable [ node ] , node ) ;
}
buf . PutInt ( GetEditOps ( ) - > m_pNodeIndexTable [ node ] ) ;
}
// -------------------------------
// Write the file out
// -------------------------------
FileHandle_t fh = filesystem - > Open ( szNrpFilename , " wb " ) ;
if ( ! fh )
{
DevWarning ( 2 , " Couldn't create %s! \n " , szNrpFilename ) ;
return ;
}
filesystem - > Write ( buf . Base ( ) , buf . TellPut ( ) , fh ) ;
filesystem - > Close ( fh ) ;
}
/* Keep this around for debugging
//-----------------------------------------------------------------------------
// Purpose: Only called if network has changed since last time level
// was loaded
// Input :
// Output :
//-----------------------------------------------------------------------------
void CAI_NetworkManager : : SaveNetworkGraph ( void )
{
// -----------------------------
// Make sure directories have been made
// -----------------------------
char szNrpFilename [ MAX_PATH ] ; // text node report filename
Q_strncpy ( szNrpFilename , " maps " , sizeof ( szNrpFilename ) ) ;
filesystem - > CreateDirHierarchy ( szNrpFilename ) ;
Q_strncat ( szNrpFilename , " /graphs " , COPY_ALL_CHARACTERS ) ;
filesystem - > CreateDirHierarchy ( szNrpFilename ) ;
Q_strncat ( szNrpFilename , " / " , COPY_ALL_CHARACTERS ) ;
Q_strncat ( szNrpFilename , STRING ( gpGlobals - > mapname ) , COPY_ALL_CHARACTERS ) ;
Q_strncat ( szNrpFilename , " .ain " , COPY_ALL_CHARACTERS ) ;
FileHandle_t file = filesystem - > Open ( szNrpFilename , " w+ " ) ;
// -----------------------------
// Make sure the file opened ok
// -----------------------------
if ( ! file )
{
DevWarning ( 2 , " Couldn't create %s! \n " , szNrpFilename ) ;
return ;
}
// ---------------------------
// Save the version number
// ---------------------------
filesystem - > FPrintf ( file , " Version %4d \n " , AINET_VERSION_NUMBER ) ;
// -------------------------------
// Dump all the nodes to the file
// -------------------------------
filesystem - > FPrintf ( file , " NumNodes: %d \n " , m_iNumNodes ) ;
int totalNumLinks = 0 ;
for ( int node = 0 ; node < m_iNumNodes ; node + + )
{
filesystem - > FPrintf ( file , " Location %4f,%4f,%4f \n " , m_pAInode [ node ] - > GetOrigin ( ) . x , m_pAInode [ node ] - > GetOrigin ( ) . y , m_pAInode [ node ] - > GetOrigin ( ) . z ) ;
for ( int hull = 0 ; hull < NUM_HULLS ; hull + + )
{
filesystem - > FPrintf ( file , " Voffset %4f \n " , m_pAInode [ node ] - > m_flVOffset [ hull ] ) ;
}
filesystem - > FPrintf ( file , " HintType: %4d \n " , m_pAInode [ node ] - > m_eHintType ) ;
filesystem - > FPrintf ( file , " HintYaw: %4f \n " , m_pAInode [ node ] - > GetYaw ( ) ) ;
filesystem - > FPrintf ( file , " NodeType %4d \n " , m_pAInode [ node ] - > GetType ( ) ) ;
filesystem - > FPrintf ( file , " NodeInfo %4d \n " , m_pAInode [ node ] - > m_eNodeInfo ) ;
filesystem - > FPrintf ( file , " Neighbors " ) ;
m_pAInode [ node ] - > m_pNeighborBS - > SaveBitString ( file ) ;
filesystem - > FPrintf ( file , " Visible " ) ;
m_pAInode [ node ] - > m_pVisibleBS - > SaveBitString ( file ) ;
filesystem - > FPrintf ( file , " Connected " ) ;
m_pAInode [ node ] - > m_pConnectedBS - > SaveBitString ( file ) ;
filesystem - > FPrintf ( file , " NumLinks %4d \n " , m_pAInode [ node ] - > NumLinks ( ) ) ;
for ( int link = 0 ; link < m_pAInode [ node ] - > NumLinks ( ) ; link + + )
{
// Only dump if link source
if ( node = = m_pAInode [ node ] - > GetLinkByIndex ( link ) - > m_iSrcID )
{
totalNumLinks + + ;
}
}
}
// -------------------------------
// Dump all the links to the file
// -------------------------------
filesystem - > FPrintf ( file , " TotalNumLinks %4d \n " , totalNumLinks ) ;
for ( node = 0 ; node < m_iNumNodes ; node + + )
{
for ( int link = 0 ; link < m_pAInode [ node ] - > NumLinks ( ) ; link + + )
{
// Only dump if link source
if ( node = = m_pAInode [ node ] - > GetLinkByIndex ( link ) - > m_iSrcID )
{
filesystem - > FPrintf ( file , " LinkSrcID %4d \n " , m_pAInode [ node ] - > GetLinkByIndex ( link ) - > m_iSrcID ) ;
filesystem - > FPrintf ( file , " LinkDestID %4d \n " , m_pAInode [ node ] - > GetLinkByIndex ( link ) - > m_iDestID ) ;
for ( int hull = 0 ; hull < NUM_HULLS ; hull + + )
{
filesystem - > FPrintf ( file , " Hulls %4d \n " , m_pAInode [ node ] - > GetLinkByIndex ( link ) - > m_iAcceptedMoveTypes [ hull ] ) ;
}
}
}
}
// -------------------------------
// Dump WC lookup table
// -------------------------------
for ( node = 0 ; node < m_iNumNodes ; node + + )
{
filesystem - > FPrintf ( file , " %4d \n " , m_pNodeIndexTable [ node ] ) ;
}
filesystem - > Close ( file ) ;
}
*/
//-----------------------------------------------------------------------------
// Purpose: Only called if network has changed since last time level
// was loaded
//-----------------------------------------------------------------------------
void CAI_NetworkManager : : LoadNetworkGraph ( void )
{
// ---------------------------------------------------
// If I'm in edit mode don't load, always recalculate
// ---------------------------------------------------
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 , IsX360 ( ) ? " .360.ain " : " .ain " , sizeof ( szNrpFilename ) , COPY_ALL_CHARACTERS ) ;
MEM_ALLOC_CREDIT ( ) ;
// Read the file in one gulp
CUtlBuffer buf ;
bool bHaveAIN = false ;
if ( IsX360 ( ) & & g_pQueuedLoader - > IsMapLoading ( ) )
{
// .ain was loaded anonymously by bsp, should be ready
void * pData ;
int nDataSize ;
if ( g_pQueuedLoader - > ClaimAnonymousJob ( szNrpFilename , & pData , & nDataSize ) )
{
if ( nDataSize ! = 0 )
{
buf . Put ( pData , nDataSize ) ;
bHaveAIN = true ;
}
filesystem - > FreeOptimalReadBuffer ( pData ) ;
}
}
if ( ! bHaveAIN & & ! filesystem - > ReadFile ( szNrpFilename , " game " , buf ) )
{
DevWarning ( 2 , " Couldn't read %s! \n " , szNrpFilename ) ;
return ;
}
// ---------------------------
// 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 ( ( 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 ( IsX360 ( ) )
{
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 < int > usedIds ;
CUtlRBTree < int > reportedIds ;
SetDefLessFunc ( usedIds ) ;
SetDefLessFunc ( reportedIds ) ;
bool printedHeader = false ;
for ( node = 0 ; node < m_pNetwork - > m_iNumNodes ; node + + )
{
int editorId = GetEditOps ( ) - > m_pNodeIndexTable [ node ] ;
if ( editorId ! = NO_NODE )
{
if ( usedIds . Find ( editorId ) ! = usedIds . InvalidIndex ( ) )
{
if ( ! printedHeader )
{
Warning ( " ** Duplicate Hammer Node IDs: " ) ;
printedHeader = true ;
}
if ( reportedIds . Find ( editorId ) = = reportedIds . InvalidIndex ( ) )
{
DevMsg ( " %d, " , editorId ) ;
reportedIds . Insert ( editorId ) ;
}
}
else
usedIds . Insert ( editorId ) ;
}
}
if ( printedHeader )
DevMsg ( " \n ** Should run \" Check For Problems \" on the VMF then verify dynamic links \n " ) ;
# endif
gm_fNetworksLoaded = true ;
CAI_DynamicLink : : gm_bInitialized = false ;
}
/* Keep this around for debugging
//-----------------------------------------------------------------------------
// Purpose: Only called if network has changed since last time level
// was loaded
// Input :
// Output :
//-----------------------------------------------------------------------------
void CAI_NetworkManager : : LoadNetworkGraph ( void )
{
// ---------------------------------------------------
// If I'm in edit mode don't load, always recalculate
// ---------------------------------------------------
if ( engine - > IsInEditMode ( ) )
{
return ;
}
// -----------------------------
// Make sure directories have been made
// -----------------------------
char szNrpFilename [ MAX_PATH ] ; // text node report filename
Q_strncpy ( szNrpFilename , " maps " , sizeof ( szNrpFilename ) ) ;
filesystem - > CreateDirHierarchy ( szNrpFilename ) ;
Q_strncat ( szNrpFilename , " /graphs " , COPY_ALL_CHARACTERS ) ;
filesystem - > CreateDirHierarchy ( szNrpFilename ) ;
Q_strncat ( szNrpFilename , " / " , COPY_ALL_CHARACTERS ) ;
Q_strncat ( szNrpFilename , STRING ( gpGlobals - > mapname ) , COPY_ALL_CHARACTERS ) ;
Q_strncat ( szNrpFilename , " .ain " , COPY_ALL_CHARACTERS ) ;
FileHandle_t file = filesystem - > Open ( szNrpFilename , " r " ) ;
// -----------------------------
// Make sure the file opened ok
// -----------------------------
if ( ! file )
{
DevWarning ( 2 , " Couldn't create %s! \n " , szNrpFilename ) ;
return ;
}
// ---------------------------
// Check the version number
// ---------------------------
char temps [ 256 ] ;
int version ;
fscanf ( file , " %255s " , & temps ) ;
fscanf ( file , " %i \n " , & version ) ;
if ( version ! = AINET_VERSION_NUMBER )
{
return ;
}
// ----------------------------------------
// Get the network size and allocate space
// ----------------------------------------
int numNodes ;
fscanf ( file , " %255s " , & temps ) ;
fscanf ( file , " %d \n " , & numNodes ) ;
// ------------------------------------------------------------------------
// If in wc_edit mode allocate extra space for nodes that might be created
// ------------------------------------------------------------------------
if ( engine - > IsInEditMode ( ) )
{
numNodes = MAX ( numNodes , 1024 ) ;
}
m_pAInode = new CAI_Node * [ numNodes ] ;
if ( ! m_pAInode )
{
Warning ( " LoadNetworkGraph: Not enough memory to create %i nodes \n " , numNodes ) ;
Assert ( 0 ) ;
return ;
}
// -------------------------------
// Load all the nodes to the file
// -------------------------------
for ( int node = 0 ; node < numNodes ; node + + )
{
CAI_Node * new_node = AddNode ( ) ;
Vector origin ;
fscanf ( file , " %255s " , & temps ) ;
fscanf ( file , " %f,%f,%f \n " , & new_node - > GetOrigin ( ) . x , & new_node - > GetOrigin ( ) . y , & new_node - > GetOrigin ( ) . z ) ;
for ( int hull = 0 ; hull < NUM_HULLS ; hull + + )
{
fscanf ( file , " %255s " , & temps ) ;
fscanf ( file , " %f \n " , & new_node - > m_flVOffset [ hull ] ) ;
}
fscanf ( file , " %255s " , & temps ) ;
fscanf ( file , " %d \n " , & new_node - > m_eHintType ) ;
fscanf ( file , " %255s " , & temps ) ;
fscanf ( file , " %f \n " , & new_node - > GetYaw ( ) ) ;
fscanf ( file , " %255s " , & temps ) ;
fscanf ( file , " %d \n " , & new_node - > GetType ( ) ) ;
fscanf ( file , " %255s " , & temps ) ;
fscanf ( file , " %d \n " , & new_node - > m_eNodeInfo ) ;
fscanf ( file , " %255s " , & temps ) ;
new_node - > m_pNeighborBS = new CVarBitVec ( numNodes ) ;
new_node - > m_pNeighborBS - > LoadBitString ( file ) ;
fscanf ( file , " %255s " , & temps ) ;
new_node - > m_pVisibleBS = new CVarBitVec ( numNodes ) ;
new_node - > m_pVisibleBS - > LoadBitString ( file ) ;
fscanf ( file , " %255s " , & temps ) ;
new_node - > m_pConnectedBS = new CVarBitVec ( numNodes ) ;
new_node - > m_pConnectedBS - > LoadBitString ( file ) ;
fscanf ( file , " %255s " , & temps ) ;
int numLinks ;
fscanf ( file , " %4d " , & numLinks ) ;
// ------------------------------------------------------------------------
// If in wc_edit mode allocate extra space for nodes that might be created
// ------------------------------------------------------------------------
if ( engine - > IsInEditMode ( ) )
{
numLinks = AI_MAX_NODE_LINKS ;
}
//Assert ( numLinks >= 1 );
new_node - > AllocateLinkSpace ( numLinks ) ;
}
// -------------------------------
// Load all the links to the fild
// -------------------------------
int totalNumLinks ;
fscanf ( file , " %255s " , & temps ) ;
fscanf ( file , " %d \n " , & totalNumLinks ) ;
for ( int link = 0 ; link < totalNumLinks ; link + + )
{
CAI_Link * new_link = new CAI_Link ;
fscanf ( file , " %255s " , & temps ) ;
fscanf ( file , " %4d \n " , & new_link - > m_iSrcID ) ;
fscanf ( file , " %255s " , & temps ) ;
fscanf ( file , " %4d \n " , & new_link - > m_iDestID ) ;
for ( int hull = 0 ; hull < NUM_HULLS ; hull + + )
{
fscanf ( file , " %255s " , & temps ) ;
fscanf ( file , " %d \n " , & new_link - > m_iAcceptedMoveTypes [ hull ] ) ;
}
// Now add link to source and destination nodes
m_pAInode [ new_link - > m_iSrcID ] - > AddLink ( new_link ) ;
m_pAInode [ new_link - > m_iDestID ] - > AddLink ( new_link ) ;
}
// -------------------------------
// Load WC lookup table
// -------------------------------
m_pNodeIndexTable = new int [ m_iNumNodes ] ;
for ( node = 0 ; node < m_iNumNodes ; node + + )
{
fscanf ( file , " %d \n " , & m_pNodeIndexTable [ node ] ) ;
}
CAI_NetworkManager : : NetworksLoaded ( ) = true ;
fclose ( file ) ;
}
*/
//-----------------------------------------------------------------------------
// Purpose: Deletes all AINetworks from memory
//-----------------------------------------------------------------------------
void CAI_NetworkManager : : DeleteAllAINetworks ( void )
{
CAI_DynamicLink : : gm_bInitialized = false ;
gm_fNetworksLoaded = false ;
g_pBigAINet = NULL ;
}
//-----------------------------------------------------------------------------
// Purpose: Only called if network has changed since last time level
// was loaded
//-----------------------------------------------------------------------------
void CAI_NetworkManager : : BuildNetworkGraph ( void )
{
if ( m_bDontSaveGraph )
return ;
CAI_DynamicLink : : gm_bInitialized = false ;
g_AINetworkBuilder . Build ( m_pNetwork ) ;
// If I'm loading for the first time save. Otherwise I'm
// doing a wc edit and I don't want to save
if ( ! CAI_NetworkManager : : NetworksLoaded ( ) )
{
SaveNetworkGraph ( ) ;
gm_fNetworksLoaded = true ;
}
}
//------------------------------------------------------------------------------
bool g_bAIDisabledByUser = false ;
void CAI_NetworkManager : : InitializeAINetworks ( )
{
// For not just create a single AI Network called "BigNet"
// At some later point we may have mulitple AI networks
CAI_NetworkManager * pNetwork ;
g_pAINetworkManager = pNetwork = CREATE_ENTITY ( CAI_NetworkManager , " ai_network " ) ;
pNetwork - > AddEFlags ( EFL_KEEP_ON_RECREATE_ENTITIES ) ;
g_pBigAINet = pNetwork - > GetNetwork ( ) ;
pNetwork - > SetName ( AllocPooledString ( " BigNet " ) ) ;
pNetwork - > Spawn ( ) ;
if ( engine - > IsInEditMode ( ) )
{
g_ai_norebuildgraph . SetValue ( 0 ) ;
}
if ( CAI_NetworkManager : : IsAIFileCurrent ( STRING ( gpGlobals - > mapname ) ) )
{
pNetwork - > LoadNetworkGraph ( ) ;
if ( ! g_bAIDisabledByUser )
{
CAI_BaseNPC : : m_nDebugBits & = ~ bits_debugDisableAI ;
}
}
// Reset node counter used during load
CNodeEnt : : m_nNodeCount = 0 ;
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 ( IsX360 ( ) & & ( 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 ) ;
}
//------------------------------------------------------------------------------
void CAI_NetworkManager : : DelayedInit ( void )
{
if ( ! g_pGameRules - > FAllowNPCs ( ) )
{
SetThink ( NULL ) ;
return ;
}
if ( ! g_ai_norebuildgraph . GetInt ( ) )
{
// ----------------------------------------------------------
// Actually enter DelayedInit twice when rebuilding the
// node graph. The first time through we just print the
// warning message. We only actually do the rebuild on
// the second pass to make sure the message hits the screen
// ----------------------------------------------------------
if ( m_bNeedGraphRebuild )
{
Assert ( ! m_bDontSaveGraph ) ;
BuildNetworkGraph ( ) ; // For now only one AI Network
if ( engine - > IsInEditMode ( ) )
{
engine - > ServerCommand ( " exec map_edit.cfg \n " ) ;
}
SetThink ( NULL ) ;
if ( ! g_bAIDisabledByUser )
{
CAI_BaseNPC : : m_nDebugBits & = ~ bits_debugDisableAI ;
}
}
// --------------------------------------------
// If I haven't loaded a network, or I'm in
// WorldCraft edit mode rebuild the network
// --------------------------------------------
else if ( ! m_bDontSaveGraph & & ( ! CAI_NetworkManager : : NetworksLoaded ( ) | | engine - > IsInEditMode ( ) ) )
{
# ifdef _WIN32
// --------------------------------------------------------
// If in edit mode start WC session and make sure we are
// running the same map in WC and the engine
// --------------------------------------------------------
if ( engine - > IsInEditMode ( ) )
{
int status = Editor_BeginSession ( STRING ( gpGlobals - > mapname ) , gpGlobals - > mapversion , false ) ;
if ( status = = Editor_NotRunning )
{
DevMsg ( " \n Aborting map_edit \n Worldcraft not running... \n \n " ) ;
UTIL_CenterPrintAll ( " Worldcraft not running... \n " ) ;
engine - > ServerCommand ( " disconnect \n " ) ;
SetThink ( NULL ) ;
return ;
}
else if ( status = = Editor_BadCommand )
{
DevMsg ( " \n Aborting map_edit \n WC/Engine map versions different... \n \n " ) ;
UTIL_CenterPrintAll ( " WC/Engine map versions different... \n " ) ;
engine - > ServerCommand ( " disconnect \n " ) ;
SetThink ( NULL ) ;
return ;
}
else
{
// Increment version number when session begins
gpGlobals - > mapversion + + ;
}
}
# endif
DevMsg ( " Node Graph out of Date. Rebuilding... \n " ) ;
UTIL_CenterPrintAll ( " Node Graph out of Date. Rebuilding... \n " ) ;
m_bNeedGraphRebuild = true ;
g_pAINetworkManager - > SetNextThink ( gpGlobals - > curtime + 1 ) ;
return ;
}
// --------------------------------------------
// Initialize any dynamic links
// --------------------------------------------
CAI_DynamicLink : : InitDynamicLinks ( ) ;
FixupHints ( ) ;
}
GetEditOps ( ) - > OnInit ( ) ;
m_fInitalized = true ;
if ( g_AI_Manager . NumAIs ( ) ! = 0 & & g_pBigAINet - > NumNodes ( ) = = 0 )
DevMsg ( " WARNING: Level contains NPCs but has no path nodes \n " ) ;
}
//------------------------------------------------------------------------------
void CAI_NetworkManager : : 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 " , 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 ) ;
}
}
unsigned CAI_NetworkManager : : ThreadedBuildJob ( /* (ThreadedGraphBuildData *) */ void * pBuildData )
{
ThreadedGraphBuildData * RESTRICT pBuild = reinterpret_cast < ThreadedGraphBuildData * > ( 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 ; i < m_pNetwork - > NumNodes ( ) ; i + + )
{
if ( m_pNodeIndexTable [ i ] = = nWCId )
{
return i ;
}
}
return - 1 ;
}
//-----------------------------------------------------------------------------
int CAI_NetworkEditTools : : GetWCIdFromNodeId ( int nNodeId )
{
if ( nNodeId = = - 1 | | nNodeId > = m_pNetwork - > NumNodes ( ) )
return - 1 ;
return m_pNodeIndexTable [ nNodeId ] ;
}
//-----------------------------------------------------------------------------
// Purpose: Find the nearest ainode that is faced from the given location
// and within the angular threshold (ignores worldspawn).
// DO NOT USE FOR ANY RUN TIME RELEASE CODE
// Used for selection of nodes in debugging display only!
//
//-----------------------------------------------------------------------------
CAI_Node * CAI_NetworkEditTools : : FindAINodeNearestFacing ( const Vector & origin , const Vector & facing , float threshold , int nNodeType )
{
float bestDot = threshold ;
CAI_Node * best = NULL ;
CAI_Network * aiNet = g_pBigAINet ;
for ( int node = 0 ; node < aiNet - > NumNodes ( ) ; node + + )
{
if ( aiNet - > GetNode ( node ) - > GetType ( ) ! = NODE_DELETED )
{
// Pick nodes that are in the current editing type
if ( nNodeType = = NODE_ANY | |
nNodeType = = aiNet - > GetNode ( node ) - > GetType ( ) )
{
// Make vector to node
Vector to_node = ( aiNet - > GetNode ( node ) - > GetPosition ( m_iHullDrawNum ) - origin ) ;
VectorNormalize ( to_node ) ;
float dot = DotProduct ( facing , to_node ) ;
if ( dot > bestDot )
{
// Make sure I have a line of sight to it
trace_t tr ;
AI_TraceLine ( origin , aiNet - > GetNode ( node ) - > GetPosition ( m_iHullDrawNum ) ,
MASK_BLOCKLOS , NULL , COLLISION_GROUP_NONE , & tr ) ;
if ( tr . fraction = = 1.0 )
{
bestDot = dot ;
best = aiNet - > GetNode ( node ) ;
}
}
}
}
}
return best ;
}
Vector PointOnLineNearestPoint ( const Vector & vStartPos , const Vector & vEndPos , const Vector & vPoint )
{
Vector vEndToStart = ( vEndPos - vStartPos ) ;
Vector vOrgToStart = ( vPoint - vStartPos ) ;
float fNumerator = DotProduct ( vEndToStart , vOrgToStart ) ;
float fDenominator = vEndToStart . Length ( ) * vOrgToStart . Length ( ) ;
float fIntersectDist = vOrgToStart . Length ( ) * ( fNumerator / fDenominator ) ;
VectorNormalize ( vEndToStart ) ;
Vector vIntersectPos = vStartPos + vEndToStart * fIntersectDist ;
return vIntersectPos ;
}
//-----------------------------------------------------------------------------
// Purpose: Find the nearest ainode that is faced from the given location
// and within the angular threshold (ignores worldspawn).
// DO NOT USE FOR ANY RUN TIME RELEASE CODE
// Used for selection of nodes in debugging display only!
//-----------------------------------------------------------------------------
CAI_Link * CAI_NetworkEditTools : : FindAILinkNearestFacing ( const Vector & vOrigin , const Vector & vFacing , float threshold )
{
float bestDot = threshold ;
CAI_Link * best = NULL ;
CAI_Network * aiNet = g_pBigAINet ;
for ( int node = 0 ; node < aiNet - > NumNodes ( ) ; node + + )
{
if ( aiNet - > GetNode ( node ) - > GetType ( ) ! = NODE_DELETED )
{
// Pick nodes that are in the current editing type
if ( ( m_bAirEditMode & & aiNet - > GetNode ( node ) - > GetType ( ) = = NODE_AIR ) | |
( ! m_bAirEditMode & & aiNet - > GetNode ( node ) - > GetType ( ) = = NODE_GROUND ) )
{
// Go through each link
for ( int link = 0 ; link < aiNet - > GetNode ( node ) - > NumLinks ( ) ; link + + )
{
CAI_Link * nodeLink = aiNet - > GetNode ( node ) - > GetLinkByIndex ( link ) ;
// Find position on link that I am looking
int endID = nodeLink - > DestNodeID ( node ) ;
Vector startPos = aiNet - > GetNode ( node ) - > GetPosition ( m_iHullDrawNum ) ;
Vector endPos = aiNet - > GetNode ( endID ) - > GetPosition ( m_iHullDrawNum ) ;
Vector vNearest = PointOnLineNearestPoint ( startPos , endPos , vOrigin ) ;
// Get angle between viewing dir. and nearest point on line
Vector vOriginToNearest = ( vNearest - vOrigin ) ;
float fNumerator = DotProduct ( vOriginToNearest , vFacing ) ;
float fDenominator = vOriginToNearest . Length ( ) ;
float fAngleToNearest = acos ( fNumerator / fDenominator ) ;
// If not facing the line reject
if ( fAngleToNearest > 1.57 )
{
continue ;
}
// Calculate intersection of facing direction to nearest point
float fIntersectDist = vOriginToNearest . Length ( ) * tan ( fAngleToNearest ) ;
Vector dir = endPos - startPos ;
float fLineLen = VectorNormalize ( dir ) ;
Vector vIntersection = vNearest + ( fIntersectDist * dir ) ;
// Reject of beyond end of line
if ( ( ( vIntersection - startPos ) . Length ( ) > fLineLen ) | |
( ( vIntersection - endPos ) . Length ( ) > fLineLen ) )
{
continue ;
}
// Now test dot to the look position
Vector toLink = vIntersection - vOrigin ;
VectorNormalize ( toLink ) ;
float lookDot = DotProduct ( vFacing , toLink ) ;
if ( lookDot > bestDot )
{
// Make sure I have a line of sight to it
trace_t tr ;
AI_TraceLine ( vOrigin , vIntersection , MASK_BLOCKLOS , NULL , COLLISION_GROUP_NONE , & tr ) ;
if ( tr . fraction = = 1.0 )
{
bestDot = lookDot ;
best = nodeLink ;
}
}
}
}
}
}
return best ;
}
//-----------------------------------------------------------------------------
// Purpose: Used for WC edit more. Marks that the network should be
// rebuild and turns of any displays that have been invalidated
// as the network is now out of date
// Input :
// Output :
//-----------------------------------------------------------------------------
void CAI_NetworkEditTools : : SetRebuildFlags ( void )
{
m_debugNetOverlays | = bits_debugNeedRebuild ;
m_debugNetOverlays & = ~ bits_debugOverlayConnections ;
m_debugNetOverlays & = ~ bits_debugOverlayGraphConnect ;
m_debugNetOverlays & = ~ bits_debugOverlayVisibility ;
m_debugNetOverlays & = ~ bits_debugOverlayHulls ;
// Not allowed to edit links when graph outdated
m_bLinkEditMode = false ;
}
//-----------------------------------------------------------------------------
// Purpose: Used for WC edit more. After node graph has been rebuild
// marks it as so and turns connection display back on
// Input :
// Output :
//-----------------------------------------------------------------------------
void CAI_NetworkEditTools : : ClearRebuildFlags ( void )
{
m_debugNetOverlays | = bits_debugOverlayConnections ;
// ------------------------------------------
// Clear all rebuild flags in nodes
// ------------------------------------------
for ( int i = 0 ; i < m_pNetwork - > NumNodes ( ) ; i + + )
{
m_pNetwork - > GetNode ( i ) - > m_eNodeInfo & = ~ bits_NODE_WC_CHANGED ;
m_pNetwork - > GetNode ( i ) - > ClearNeedsRebuild ( ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Sets the next hull to draw, or none if at end of hulls
//-----------------------------------------------------------------------------
void CAI_NetworkEditTools : : DrawNextHull ( const char * ainet_name )
{
m_iHullDrawNum + + ;
if ( m_iHullDrawNum = = NUM_HULLS )
{
m_iHullDrawNum = 0 ;
}
// Recalculate usable nodes for current hull
g_pAINetworkManager - > GetEditOps ( ) - > RecalcUsableNodesForHull ( ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_NetworkEditTools : : DrawHull ( Hull_t eHull )
{
m_iHullDrawNum = eHull ;
if ( m_iHullDrawNum > = NUM_HULLS )
{
m_iHullDrawNum = 0 ;
}
// Recalculate usable nodes for current hull
g_pAINetworkManager - > GetEditOps ( ) - > RecalcUsableNodesForHull ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Used just for debug display, to color nodes grey that the
// currently selected hull size can't use.
//-----------------------------------------------------------------------------
void CAI_NetworkEditTools : : RecalcUsableNodesForHull ( void )
{
// -----------------------------------------------------
// Use test hull to check hull sizes
// -----------------------------------------------------
CAI_TestHull * m_pTestHull = CAI_TestHull : : GetTestHull ( ) ;
m_pTestHull - > GetNavigator ( ) - > SetNetwork ( g_pBigAINet ) ;
m_pTestHull - > SetHullType ( ( Hull_t ) m_iHullDrawNum ) ;
m_pTestHull - > SetHullSizeNormal ( ) ;
for ( int node = 0 ; node < m_pNetwork - > NumNodes ( ) ; node + + )
{
if ( ( m_pNetwork - > GetNode ( node ) - > m_eNodeInfo & ( HullToBit ( ( Hull_t ) m_iHullDrawNum ) < < NODE_ENT_FLAGS_SHIFT ) ) | |
m_pTestHull - > GetNavigator ( ) - > CanFitAtNode ( node ) )
{
m_pNetwork - > GetNode ( node ) - > m_eNodeInfo & = ~ bits_NODE_WONT_FIT_HULL ;
}
else
{
m_pNetwork - > GetNode ( node ) - > m_eNodeInfo | = bits_NODE_WONT_FIT_HULL ;
}
}
CAI_TestHull : : ReturnTestHull ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Sets debug bits
//-----------------------------------------------------------------------------
void CAI_NetworkEditTools : : SetDebugBits ( const char * ainet_name , int debug_bit )
{
CAI_NetworkEditTools * pEditOps = g_pAINetworkManager - > GetEditOps ( ) ;
if ( ! pEditOps )
return ;
if ( debug_bit & bits_debugOverlayNodes )
{
if ( pEditOps - > m_debugNetOverlays & bits_debugOverlayNodesLev2 )
{
pEditOps - > m_debugNetOverlays & = ~ bits_debugOverlayNodes ;
pEditOps - > m_debugNetOverlays & = ~ bits_debugOverlayNodesLev2 ;
}
else if ( pEditOps - > m_debugNetOverlays & bits_debugOverlayNodes )
{
pEditOps - > m_debugNetOverlays | = bits_debugOverlayNodesLev2 ;
}
else
{
pEditOps - > m_debugNetOverlays | = bits_debugOverlayNodes ;
// Recalculate usable nodes for current hull
pEditOps - > RecalcUsableNodesForHull ( ) ;
}
}
else if ( pEditOps - > m_debugNetOverlays & debug_bit )
{
pEditOps - > m_debugNetOverlays & = ~ debug_bit ;
}
else
{
pEditOps - > m_debugNetOverlays | = debug_bit ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Draws edit display info on screen
//-----------------------------------------------------------------------------
void CAI_NetworkEditTools : : DrawEditInfoOverlay ( void )
{
hudtextparms_s tTextParam ;
tTextParam . x = 0.8 ;
tTextParam . y = 0.8 ;
tTextParam . effect = 0 ;
tTextParam . r1 = 255 ;
tTextParam . g1 = 255 ;
tTextParam . b1 = 255 ;
tTextParam . a1 = 255 ;
tTextParam . r2 = 255 ;
tTextParam . g2 = 255 ;
tTextParam . b2 = 255 ;
tTextParam . a2 = 255 ;
tTextParam . fadeinTime = 0 ;
tTextParam . fadeoutTime = 0 ;
tTextParam . holdTime = 1 ;
tTextParam . fxTime = 0 ;
tTextParam . channel = 0 ;
char hullTypeTxt [ 50 ] ;
char nodeTypeTxt [ 50 ] ;
char editTypeTxt [ 50 ] ;
char outTxt [ 255 ] ;
Q_snprintf ( hullTypeTxt , sizeof ( hullTypeTxt ) , " %s " , NAI_Hull : : Name ( m_iHullDrawNum ) ) ;
Q_snprintf ( outTxt , sizeof ( outTxt ) , " Displaying: \n %s \n \n " , hullTypeTxt ) ;
if ( engine - > IsInEditMode ( ) )
{
char outTxt2 [ 255 ] ;
Q_snprintf ( nodeTypeTxt , sizeof ( nodeTypeTxt ) , " %s (l) " , m_bLinkEditMode ? " Links " : " Nodes " ) ;
Q_snprintf ( editTypeTxt , sizeof ( editTypeTxt ) , " %s (m) " , m_bAirEditMode ? " Air " : " Ground " ) ;
Q_snprintf ( outTxt2 , sizeof ( outTxt2 ) , " Editing: \n %s \n %s " , editTypeTxt , nodeTypeTxt ) ;
Q_strncat ( outTxt , outTxt2 , sizeof ( outTxt ) , COPY_ALL_CHARACTERS ) ;
// Print in red if network needs rebuilding
if ( m_debugNetOverlays & bits_debugNeedRebuild )
{
tTextParam . g1 = 0 ;
tTextParam . b1 = 0 ;
}
}
UTIL_HudMessageAll ( tTextParam , outTxt ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Draws AINetwork on the screen
//-----------------------------------------------------------------------------
void CAI_NetworkEditTools : : DrawAINetworkOverlay ( void )
{
// ------------------------------------
// If network isn't loaded yet return
// ------------------------------------
if ( ! CAI_NetworkManager : : NetworksLoaded ( ) )
{
return ;
}
// ----------------------------------------------
// So we don't fill up the client message queue
// with node drawing messages, only send them
// in chuncks
// ----------------------------------------------
static int startDrawNode = 0 ;
static int endDrawNode = 0 ;
static float flDrawDuration ;
endDrawNode = startDrawNode + 20 ;
flDrawDuration = 0.1 * ( m_pNetwork - > NumNodes ( ) - 1 ) / 20 ;
if ( flDrawDuration < .1 )
flDrawDuration = .1 ;
if ( endDrawNode > m_pNetwork - > NumNodes ( ) )
{
endDrawNode = m_pNetwork - > NumNodes ( ) ;
}
// ---------------------
// Draw grid
// ---------------------
if ( m_debugNetOverlays & bits_debugOverlayGrid )
{
// Trace a line to where player is looking
CBasePlayer * pPlayer = UTIL_PlayerByIndex ( CBaseEntity : : m_nDebugPlayer ) ;
if ( pPlayer )
{
Vector vForward ;
Vector vSource = pPlayer - > EyePosition ( ) ;
pPlayer - > EyeVectors ( & vForward ) ;
trace_t tr ;
AI_TraceLine ( vSource , vSource + vForward * 2048 , MASK_SOLID , pPlayer , COLLISION_GROUP_NONE , & tr ) ;
float dotPr = DotProduct ( Vector ( 0 , 0 , 1 ) , tr . plane . normal ) ;
if ( tr . fraction ! = 1.0 & & dotPr > 0.5 )
{
NDebugOverlay : : Grid ( tr . endpos + Vector ( 0 , 0 , 1 ) ) ;
}
}
}
// --------------------
CAI_Node * * pAINode = m_pNetwork - > AccessNodes ( ) ;
// --------------------
// Draw the graph connectivity
// ---------------------
if ( m_debugNetOverlays & bits_debugOverlayGraphConnect )
{
// ---------------------------------------------------
// If network needs rebuilding do so before display
// --------------------------------------------------
if ( m_debugNetOverlays & bits_debugNeedRebuild )
{
m_pManager - > RebuildNetworkGraph ( ) ;
}
else if ( m_iGConnectivityNode ! = NO_NODE )
{
for ( int node = 0 ; node < m_pNetwork - > NumNodes ( ) ; node + + )
{
if ( m_pNetwork - > IsConnected ( m_iGConnectivityNode , node ) )
{
Vector srcPos = pAINode [ m_iGConnectivityNode ] - > GetPosition ( m_iHullDrawNum ) ;
Vector desPos = pAINode [ node ] - > GetPosition ( m_iHullDrawNum ) ;
NDebugOverlay : : Line ( srcPos , desPos , 255 , 0 , 255 , false , 0 ) ;
}
}
}
}
// --------------------
// Draw the hulls
// ---------------------
if ( m_debugNetOverlays & bits_debugOverlayHulls )
{
// ---------------------------------------------------
// If network needs rebuilding do so before display
// --------------------------------------------------
if ( m_debugNetOverlays & bits_debugNeedRebuild )
{
m_pManager - > RebuildNetworkGraph ( ) ;
}
else
{
for ( int node = startDrawNode ; node < endDrawNode ; node + + )
{
for ( int link = 0 ; link < pAINode [ node ] - > NumLinks ( ) ; link + + )
{
// Only draw link once
if ( pAINode [ node ] - > GetLinkByIndex ( link ) - > DestNodeID ( node ) < node )
{
Vector srcPos = pAINode [ pAINode [ node ] - > GetLinkByIndex ( link ) - > m_iSrcID ] - > GetPosition ( m_iHullDrawNum ) ;
Vector desPos = pAINode [ pAINode [ node ] - > GetLinkByIndex ( link ) - > m_iDestID ] - > GetPosition ( m_iHullDrawNum ) ;
Vector direction = desPos - srcPos ;
float length = VectorNormalize ( direction ) ;
Vector hullMins = NAI_Hull : : Mins ( m_iHullDrawNum ) ;
Vector hullMaxs = NAI_Hull : : Maxs ( m_iHullDrawNum ) ;
hullMaxs . x = length + hullMaxs . x ;
if ( pAINode [ node ] - > GetLinkByIndex ( link ) - > m_iAcceptedMoveTypes [ m_iHullDrawNum ] & bits_CAP_MOVE_FLY )
{
NDebugOverlay : : BoxDirection ( srcPos , hullMins , hullMaxs , direction , 100 , 255 , 255 , 20 , flDrawDuration ) ;
}
if ( pAINode [ node ] - > GetLinkByIndex ( link ) - > m_iAcceptedMoveTypes [ m_iHullDrawNum ] & bits_CAP_MOVE_CLIMB )
{
// Display as a vertical slice up the climbing surface unless dismount node
if ( pAINode [ pAINode [ node ] - > GetLinkByIndex ( link ) - > m_iSrcID ] - > GetOrigin ( ) ! = pAINode [ pAINode [ node ] - > GetLinkByIndex ( link ) - > m_iDestID ] - > GetOrigin ( ) )
{
hullMaxs . x = hullMaxs . x - length ;
if ( srcPos . z < desPos . z )
{
hullMaxs . z = length + hullMaxs . z ;
}
else
{
hullMins . z = hullMins . z - length ;
}
direction = Vector ( 0 , 1 , 0 ) ;
}
NDebugOverlay : : BoxDirection ( srcPos , hullMins , hullMaxs , direction , 255 , 0 , 255 , 20 , flDrawDuration ) ;
}
if ( pAINode [ node ] - > GetLinkByIndex ( link ) - > m_iAcceptedMoveTypes [ m_iHullDrawNum ] & bits_CAP_MOVE_GROUND )
{
NDebugOverlay : : BoxDirection ( srcPos , hullMins , hullMaxs , direction , 0 , 255 , 50 , 20 , flDrawDuration ) ;
}
else if ( pAINode [ node ] - > GetLinkByIndex ( link ) - > m_iAcceptedMoveTypes [ m_iHullDrawNum ] & bits_CAP_MOVE_JUMP )
{
NDebugOverlay : : BoxDirection ( srcPos , hullMins , hullMaxs , direction , 0 , 0 , 255 , 20 , flDrawDuration ) ;
}
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 ; node < endDrawNode ; node + + )
{
// This gets expensive, so see if the node is visible to the client
if ( pAINode [ node ] - > GetType ( ) = = 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 ; link < pAINode [ node ] - > NumLinks ( ) ; 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 <<TODO>> when we get water
else if ( UTIL_PointContents ( GetOrigin ( ) ) & MASK_WATER )
{
m_eNodeType | = NODE_WATER ;
}
*/
else if ( pNode - > m_eNodeType ! = NODE_DELETED )
{
DevMsg ( " Bad node type! \n " ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Set the visibility for this node. (What nodes it can see with a
// line trace)
// Input :
// Output :
//-----------------------------------------------------------------------------
void CAI_NetworkBuilder : : InitVisibility ( CAI_Network * pNetwork , CAI_Node * pNode )
{
AI_PROFILE_SCOPE ( CAI_Node_InitVisibility ) ;
// If a deleted node bail
if ( pNode - > m_eNodeType = = NODE_DELETED )
{
return ;
}
// The actual position of some nodes may be inside geometry as they have
// hull specific position offsets (e.g. climb nodes). Get the hull specific
// position using the smallest hull to make sure were not in geometry
Vector srcPos = pNode - > GetPosition ( HULL_SMALL_CENTERED ) ;
// Check the visibility on every other node in the network
for ( int testnode = 0 ; testnode < pNetwork - > NumNodes ( ) ; testnode + + )
{
CAI_Node * testNode = pNetwork - > GetNode ( testnode ) ;
if ( DebuggingConnect ( pNode - > m_iID , testnode ) )
{
DevMsg ( " " ) ; // break here..
}
// We know we can view ourself
if ( pNode - > m_iID = = testnode )
{
m_NeighborsTable [ pNode - > m_iID ] . Set ( testNode - > m_iID ) ;
continue ;
}
// Remove duplicate nodes unless a climb node as they move
if ( testNode - > GetOrigin ( ) = = pNode - > GetOrigin ( ) & & testNode - > GetType ( ) ! = NODE_CLIMB )
{
testNode - > SetType ( NODE_DELETED ) ;
DevMsg ( 2 , " Probable duplicate node placed at %s \n " , VecToString ( testNode - > GetOrigin ( ) ) ) ;
continue ;
}
// If a deleted node we don't care about it
if ( testNode - > GetType ( ) = = NODE_DELETED )
{
continue ;
}
if ( m_DidSetNeighborsTable . IsBitSet ( testNode - > m_iID ) )
{
if ( m_NeighborsTable [ testNode - > m_iID ] . IsBitSet ( pNode - > m_iID ) )
m_NeighborsTable [ pNode - > m_iID ] . Set ( testNode - > m_iID ) ;
continue ;
}
float flDistToCheckNode = ( testNode - > GetOrigin ( ) - pNode - > GetOrigin ( ) ) . LengthSqr ( ) ;
if ( testNode - > GetType ( ) = = NODE_AIR )
{
if ( flDistToCheckNode > MAX_AIR_NODE_LINK_DIST_SQ )
continue ;
}
else
{
if ( flDistToCheckNode > MAX_NODE_LINK_DIST_SQ )
continue ;
}
// The actual position of some nodes may be inside geometry as they have
// hull specific position offsets (e.g. climb nodes). Get the hull specific
// position using the smallest hull to make sure were not in geometry
Vector destPos = pNetwork - > GetNode ( testnode ) - > GetPosition ( HULL_SMALL_CENTERED ) ;
trace_t tr ;
tr . m_pEnt = NULL ;
// Try several line of sight checks
bool isVisible = false ;
// ------------------
// Bottom to bottom
// ------------------
AI_TraceLine ( srcPos , destPos , MASK_NPCWORLDSTATIC_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 ;
}
/* <<TODO>> may not apply with editable connections.......
// trace hit a brush ent, trace backwards to make sure that this ent is the only thing in the way.
if ( tr . fraction ! = 1.0 )
{
pTraceEnt = tr . u . ent ; // store the ent that the trace hit, for comparison
AI_TraceLine ( srcPos ,
destPos ,
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 ; hull < NUM_HULLS ; hull + + )
{
pLink - > m_iAcceptedMoveTypes [ hull ] = acceptedMotions [ hull ] ;
}
DebugConnectMsg ( pNode - > m_iID , i , " Added link \n " ) ;
}
}
else
{
m_NeighborsTable [ pNode - > m_iID ] . Clear ( pDestNode - > m_iID ) ;
DebugConnectMsg ( pNode - > m_iID , i , " NO LINK \n " ) ;
}
}
else
DebugConnectMsg ( pNode - > m_iID , i , " NO LINK (not neighbors) \n " ) ;
}
}
//-----------------------------------------------------------------------------