You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3374 lines
102 KiB
3374 lines
102 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
|
|
#include "cbase.h" |
|
#include "fmtstr.h" |
|
#include "filesystem.h" |
|
#include "filesystem/IQueuedLoader.h" |
|
#include "utlbuffer.h" |
|
#include "utlrbtree.h" |
|
#include "editor_sendcommand.h" |
|
|
|
#include "ai_networkmanager.h" |
|
#include "ai_network.h" |
|
#include "ai_node.h" |
|
#include "ai_navigator.h" |
|
#include "ai_link.h" |
|
#include "ai_dynamiclink.h" |
|
#include "ai_initutils.h" |
|
#include "ai_moveprobe.h" |
|
#include "ai_hull.h" |
|
#include "ndebugoverlay.h" |
|
#include "ai_hint.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
// Increment this to force rebuilding of all networks |
|
#define AINET_VERSION_NUMBER 41 |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
int g_DebugConnectNode1 = -1; |
|
int g_DebugConnectNode2 = -1; |
|
#define DebuggingConnect( node1, node2 ) ( ( node1 == g_DebugConnectNode1 && node2 == g_DebugConnectNode2 ) || ( node1 == g_DebugConnectNode2 && node2 == g_DebugConnectNode1 ) ) |
|
|
|
inline void DebugConnectMsg( int node1, int node2, const char *pszFormat, ... ) |
|
{ |
|
if ( DebuggingConnect( node1, node2 ) ) |
|
{ |
|
DevMsg( 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("\nAborting map_edit\nWorldcraft not running...\n\n"); |
|
UTIL_CenterPrintAll( "Worldcraft not running...\n" ); |
|
engine->ServerCommand("disconnect\n"); |
|
SetThink(NULL); |
|
return; |
|
} |
|
else if (status == Editor_BadCommand) |
|
{ |
|
DevMsg("\nAborting map_edit\nWC/Engine map versions different...\n\n"); |
|
UTIL_CenterPrintAll( "WC/Engine map versions different...\n" ); |
|
engine->ServerCommand("disconnect\n"); |
|
SetThink(NULL); |
|
return; |
|
} |
|
else |
|
{ |
|
// Increment version number when session begins |
|
gpGlobals->mapversion++; |
|
} |
|
} |
|
#endif |
|
DevMsg( "Node Graph out of Date. Rebuilding...\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" ); |
|
} |
|
} |
|
|
|
//-----------------------------------------------------------------------------
|
|
|