mirror of
https://github.com/mapbase-source/source-sdk-2013.git
synced 2025-05-20 10:38:08 +03:00
* Adds support for Visual Studio 2012 and 2013 * VR Mode: . Switches from headtrack.dll to sourcevr.dll . Improved readability of the UI in VR . Removed the IPD calibration tool. TF2 will now obey the Oculus configuration file. Use the Oculus calibration tool in your SDK or install and run "OpenVR" under Tools in Steam to calibrate your IPD. . Added dropdown to enable VR mode in the Video options. Removed the -vr command line option. . Added the ability to switch in and out of VR mode without quitting the game . By default VR mode will run full screen. To switch back to a borderless window set the vr_force_windowed convar. . Added support for VR mode on Linux * Many assorted bug fixes and other changes from Team Fortress in various shared files
3249 lines
99 KiB
C++
3249 lines
99 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
//=============================================================================//
|
|
|
|
|
|
#include "cbase.h"
|
|
#include "fmtstr.h"
|
|
#include "filesystem.h"
|
|
#include "filesystem/IQueuedLoader.h"
|
|
#include "utlbuffer.h"
|
|
#include "utlrbtree.h"
|
|
#include "editor_sendcommand.h"
|
|
|
|
#include "ai_networkmanager.h"
|
|
#include "ai_network.h"
|
|
#include "ai_node.h"
|
|
#include "ai_navigator.h"
|
|
#include "ai_link.h"
|
|
#include "ai_dynamiclink.h"
|
|
#include "ai_initutils.h"
|
|
#include "ai_moveprobe.h"
|
|
#include "ai_hull.h"
|
|
#include "ndebugoverlay.h"
|
|
#include "ai_hint.h"
|
|
#include "tier0/icommandline.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
// Increment this to force rebuilding of all networks
|
|
#define AINET_VERSION_NUMBER 37
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int g_DebugConnectNode1 = -1;
|
|
int g_DebugConnectNode2 = -1;
|
|
#define DebuggingConnect( node1, node2 ) ( ( node1 == g_DebugConnectNode1 && node2 == g_DebugConnectNode2 ) || ( node1 == g_DebugConnectNode2 && node2 == g_DebugConnectNode1 ) )
|
|
|
|
inline void DebugConnectMsg( int node1, int node2, const char *pszFormat, ... )
|
|
{
|
|
if ( DebuggingConnect( node1, node2 ) )
|
|
{
|
|
char string[ 2048 ];
|
|
va_list argptr;
|
|
va_start( argptr, pszFormat );
|
|
Q_vsnprintf( string, sizeof(string), pszFormat, argptr );
|
|
va_end( argptr );
|
|
|
|
DevMsg( "%s", string );
|
|
}
|
|
}
|
|
|
|
CON_COMMAND( ai_debug_node_connect, "Debug the attempted connection between two nodes" )
|
|
{
|
|
g_DebugConnectNode1 = atoi( args[1] );
|
|
g_DebugConnectNode2 = atoi( args[2] );
|
|
|
|
DevMsg( "ai_debug_node_connect: debugging enbabled for %d <--> %d\n", g_DebugConnectNode1, g_DebugConnectNode2 );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This CVAR allows level designers to override the building
|
|
// of node graphs due to date conflicts with the BSP and AIN
|
|
// files. That way they don't have to wait for the node graph
|
|
// to rebuild following small only-ents changes. This CVAR
|
|
// always defaults to 0 and must be set at the command
|
|
// line to properly override the node graph building.
|
|
|
|
ConVar g_ai_norebuildgraph( "ai_norebuildgraph", "0" );
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CAI_NetworkManager
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CAI_NetworkManager *g_pAINetworkManager;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CAI_NetworkManager::gm_fNetworksLoaded;
|
|
|
|
LINK_ENTITY_TO_CLASS(ai_network,CAI_NetworkManager);
|
|
|
|
BEGIN_DATADESC( CAI_NetworkManager )
|
|
|
|
DEFINE_FIELD( m_bNeedGraphRebuild, FIELD_BOOLEAN ),
|
|
// m_pEditOps
|
|
// m_pNetwork
|
|
// DEFINE_FIELD( m_bDontSaveGraph, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_fInitalized, FIELD_BOOLEAN ),
|
|
|
|
// Function pointers
|
|
DEFINE_FUNCTION( DelayedInit ),
|
|
DEFINE_FUNCTION( RebuildThink ),
|
|
|
|
END_DATADESC()
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CAI_NetworkManager::CAI_NetworkManager(void)
|
|
{
|
|
m_pNetwork = new CAI_Network;
|
|
m_pEditOps = new CAI_NetworkEditTools(this);
|
|
m_bNeedGraphRebuild = false;
|
|
m_fInitalized = false;
|
|
CAI_DynamicLink::gm_bInitialized = false;
|
|
|
|
// ---------------------------------
|
|
// Add to linked list of networks
|
|
// ---------------------------------
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CAI_NetworkManager::~CAI_NetworkManager(void)
|
|
{
|
|
// ---------------------------------------
|
|
// Remove from linked list of AINetworks
|
|
// ---------------------------------------
|
|
delete m_pEditOps;
|
|
delete m_pNetwork;
|
|
if ( g_pAINetworkManager == this )
|
|
{
|
|
g_pAINetworkManager = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Think function so we can put message on screen saying we are
|
|
// going to rebuild the network, before we hang during the rebuild
|
|
//------------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkManager::RebuildThink( void )
|
|
{
|
|
SetThink(NULL);
|
|
|
|
GetEditOps()->m_debugNetOverlays &= ~bits_debugNeedRebuild;
|
|
StartRebuild( );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Delay function so we can put message on screen saying we are
|
|
// going to rebuild the network, before we hang during the rebuild
|
|
//------------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkManager::RebuildNetworkGraph( void )
|
|
{
|
|
if (m_pfnThink != (void (CBaseEntity::*)())&CAI_NetworkManager::RebuildThink)
|
|
{
|
|
UTIL_CenterPrintAll( "Doing partial rebuild of Node Graph...\n" );
|
|
SetThink(&CAI_NetworkManager::RebuildThink);
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Used for WC edit move to rebuild the network around the given
|
|
// location. Rebuilding the entire network takes too long
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkManager::StartRebuild( void )
|
|
{
|
|
CAI_DynamicLink::gm_bInitialized = false;
|
|
|
|
g_AINetworkBuilder.Rebuild( m_pNetwork );
|
|
|
|
// ------------------------------------------------------------
|
|
// Purge any dynamic links for links that don't exist any more
|
|
// ------------------------------------------------------------
|
|
CAI_DynamicLink::PurgeDynamicLinks();
|
|
|
|
|
|
// ------------------------
|
|
// Reset all dynamic links
|
|
// ------------------------
|
|
CAI_DynamicLink::ResetDynamicLinks();
|
|
|
|
// --------------------------------------------------
|
|
// Update display of usable nodes for displayed hull
|
|
// --------------------------------------------------
|
|
GetEditOps()->RecalcUsableNodesForHull();
|
|
|
|
GetEditOps()->ClearRebuildFlags();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called by save restore code if no valid load graph was loaded at restore time.
|
|
// Prevents writing out of a "bogus" node graph...
|
|
// Input : -
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_NetworkManager::MarkDontSaveGraph()
|
|
{
|
|
m_bDontSaveGraph = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Only called if network has changed since last time level
|
|
// was loaded
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkManager::SaveNetworkGraph( void )
|
|
{
|
|
if ( m_bDontSaveGraph )
|
|
return;
|
|
|
|
if ( !m_bNeedGraphRebuild )
|
|
return;
|
|
|
|
//if ( g_AI_Manager.NumAIs() && m_pNetwork->m_iNumNodes == 0 )
|
|
//{
|
|
// return;
|
|
//}
|
|
|
|
if ( !g_pGameRules->FAllowNPCs() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// -----------------------------
|
|
// Make sure directories have been made
|
|
// -----------------------------
|
|
char szNrpFilename [MAX_PATH];// text node report filename
|
|
Q_strncpy( szNrpFilename, "maps/graphs" ,sizeof(szNrpFilename));
|
|
|
|
// Usually adding on the map filename and stripping it does nothing, but if the map is under a subdir,
|
|
// this makes it create the correct subdir under maps/graphs.
|
|
char tempFilename[MAX_PATH];
|
|
Q_snprintf( tempFilename, sizeof( tempFilename ), "%s/%s", szNrpFilename, STRING( gpGlobals->mapname ) );
|
|
|
|
// Remove the filename.
|
|
int len = strlen( tempFilename );
|
|
for ( int i=0; i < len; i++ )
|
|
{
|
|
if ( tempFilename[len-i-1] == '/' || tempFilename[len-i-1] == '\\' )
|
|
{
|
|
tempFilename[len-i-1] = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Make sure the directories we need exist.
|
|
filesystem->CreateDirHierarchy( tempFilename, "DEFAULT_WRITE_PATH" );
|
|
|
|
// Now add the real map filename.
|
|
Q_strncat( szNrpFilename, "/", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
|
|
Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
|
|
Q_strncat( szNrpFilename, IsX360() ? ".360.ain" : ".ain", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
|
|
|
|
CUtlBuffer buf;
|
|
|
|
// ---------------------------
|
|
// Save the version number
|
|
// ---------------------------
|
|
buf.PutInt(AINET_VERSION_NUMBER);
|
|
buf.PutInt(gpGlobals->mapversion);
|
|
|
|
// -------------------------------
|
|
// Dump all the nodes to the file
|
|
// -------------------------------
|
|
buf.PutInt( m_pNetwork->m_iNumNodes);
|
|
|
|
int node;
|
|
int totalNumLinks = 0;
|
|
for ( node = 0; node < m_pNetwork->m_iNumNodes; node++)
|
|
{
|
|
CAI_Node *pNode = m_pNetwork->GetNode(node);
|
|
Assert( pNode->GetZone() != AI_NODE_ZONE_UNKNOWN );
|
|
|
|
buf.PutFloat( pNode->GetOrigin().x );
|
|
buf.PutFloat( pNode->GetOrigin().y );
|
|
buf.PutFloat( pNode->GetOrigin().z );
|
|
buf.PutFloat( pNode->GetYaw() );
|
|
buf.Put( pNode->m_flVOffset, sizeof( pNode->m_flVOffset ) );
|
|
buf.PutChar( pNode->GetType() );
|
|
if ( IsX360() )
|
|
{
|
|
buf.SeekPut( CUtlBuffer::SEEK_CURRENT, 3 );
|
|
}
|
|
buf.PutUnsignedShort( pNode->m_eNodeInfo );
|
|
buf.PutShort( pNode->GetZone() );
|
|
|
|
for (int link = 0; link < pNode->NumLinks(); link++)
|
|
{
|
|
// Only dump if link source
|
|
if (node == pNode->GetLinkByIndex(link)->m_iSrcID)
|
|
{
|
|
totalNumLinks++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------
|
|
// Dump all the links to the file
|
|
// -------------------------------
|
|
buf.PutInt( totalNumLinks );
|
|
|
|
for (node = 0; node < m_pNetwork->m_iNumNodes; node++)
|
|
{
|
|
CAI_Node *pNode = m_pNetwork->GetNode(node);
|
|
|
|
for (int link = 0; link < pNode->NumLinks(); link++)
|
|
{
|
|
// Only dump if link source
|
|
CAI_Link *pLink = pNode->GetLinkByIndex(link);
|
|
if (node == pLink->m_iSrcID)
|
|
{
|
|
buf.PutShort( pLink->m_iSrcID );
|
|
buf.PutShort( pLink->m_iDestID );
|
|
buf.Put( pLink->m_iAcceptedMoveTypes, sizeof( pLink->m_iAcceptedMoveTypes) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------
|
|
// Dump WC lookup table
|
|
// -------------------------------
|
|
CUtlMap<int, int> wcIDs;
|
|
SetDefLessFunc(wcIDs);
|
|
bool bCheckForProblems = false;
|
|
for (node = 0; node < m_pNetwork->m_iNumNodes; node++)
|
|
{
|
|
int iPreviousNodeBinding = wcIDs.Find( GetEditOps()->m_pNodeIndexTable[node] );
|
|
if ( iPreviousNodeBinding != wcIDs.InvalidIndex() )
|
|
{
|
|
if ( !bCheckForProblems )
|
|
{
|
|
DevWarning( "******* MAP CONTAINS DUPLICATE HAMMER NODE IDS! CHECK FOR PROBLEMS IN HAMMER TO CORRECT *******\n" );
|
|
bCheckForProblems = true;
|
|
}
|
|
DevWarning( " AI node %d is associated with Hammer node %d, but %d is already bound to node %d\n", node, GetEditOps()->m_pNodeIndexTable[node], GetEditOps()->m_pNodeIndexTable[node], wcIDs[iPreviousNodeBinding] );
|
|
}
|
|
else
|
|
{
|
|
wcIDs.Insert( GetEditOps()->m_pNodeIndexTable[node], node );
|
|
}
|
|
buf.PutInt( GetEditOps()->m_pNodeIndexTable[node] );
|
|
}
|
|
|
|
// -------------------------------
|
|
// Write the file out
|
|
// -------------------------------
|
|
|
|
FileHandle_t fh = filesystem->Open( szNrpFilename, "wb" );
|
|
if ( !fh )
|
|
{
|
|
DevWarning( 2, "Couldn't create %s!\n", szNrpFilename );
|
|
return;
|
|
}
|
|
|
|
filesystem->Write( buf.Base(), buf.TellPut(), fh );
|
|
filesystem->Close(fh);
|
|
}
|
|
|
|
/* Keep this around for debugging
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Only called if network has changed since last time level
|
|
// was loaded
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_NetworkManager::SaveNetworkGraph( void )
|
|
{
|
|
// -----------------------------
|
|
// Make sure directories have been made
|
|
// -----------------------------
|
|
char szNrpFilename [MAX_PATH];// text node report filename
|
|
Q_strncpy( szNrpFilename, "maps" ,sizeof(szNrpFilename));
|
|
filesystem->CreateDirHierarchy( szNrpFilename );
|
|
Q_strncat( szNrpFilename, "/graphs", COPY_ALL_CHARACTERS );
|
|
filesystem->CreateDirHierarchy( szNrpFilename );
|
|
|
|
Q_strncat( szNrpFilename, "/", COPY_ALL_CHARACTERS );
|
|
Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), COPY_ALL_CHARACTERS );
|
|
Q_strncat( szNrpFilename, ".ain", COPY_ALL_CHARACTERS );
|
|
|
|
FileHandle_t file = filesystem->Open ( szNrpFilename, "w+" );
|
|
|
|
// -----------------------------
|
|
// Make sure the file opened ok
|
|
// -----------------------------
|
|
if ( !file )
|
|
{
|
|
DevWarning( 2, "Couldn't create %s!\n", szNrpFilename );
|
|
return;
|
|
}
|
|
|
|
// ---------------------------
|
|
// Save the version number
|
|
// ---------------------------
|
|
filesystem->FPrintf(file,"Version %4d\n",AINET_VERSION_NUMBER);
|
|
|
|
// -------------------------------
|
|
// Dump all the nodes to the file
|
|
// -------------------------------
|
|
filesystem->FPrintf ( file, "NumNodes: %d\n", m_iNumNodes);
|
|
int totalNumLinks = 0;
|
|
for (int node = 0; node < m_iNumNodes; node++)
|
|
{
|
|
filesystem->FPrintf ( file, "Location %4f,%4f,%4f\n",m_pAInode[node]->GetOrigin().x, m_pAInode[node]->GetOrigin().y, m_pAInode[node]->GetOrigin().z );
|
|
for (int hull =0;hull<NUM_HULLS;hull++)
|
|
{
|
|
filesystem->FPrintf ( file, "Voffset %4f\n", m_pAInode[node]->m_flVOffset[hull]);
|
|
}
|
|
filesystem->FPrintf ( file, "HintType: %4d\n", m_pAInode[node]->m_eHintType );
|
|
filesystem->FPrintf ( file, "HintYaw: %4f\n", m_pAInode[node]->GetYaw() );
|
|
filesystem->FPrintf ( file, "NodeType %4d\n",m_pAInode[node]->GetType());
|
|
filesystem->FPrintf ( file, "NodeInfo %4d\n",m_pAInode[node]->m_eNodeInfo);
|
|
filesystem->FPrintf ( file, "Neighbors ");
|
|
m_pAInode[node]->m_pNeighborBS->SaveBitString(file);
|
|
filesystem->FPrintf ( file, "Visible ");
|
|
m_pAInode[node]->m_pVisibleBS->SaveBitString(file);
|
|
filesystem->FPrintf ( file, "Connected ");
|
|
m_pAInode[node]->m_pConnectedBS->SaveBitString(file);
|
|
|
|
filesystem->FPrintf ( file, "NumLinks %4d\n",m_pAInode[node]->NumLinks());
|
|
|
|
for (int link = 0; link < m_pAInode[node]->NumLinks(); link++)
|
|
{
|
|
// Only dump if link source
|
|
if (node == m_pAInode[node]->GetLinkByIndex(link)->m_iSrcID)
|
|
{
|
|
totalNumLinks++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------
|
|
// Dump all the links to the file
|
|
// -------------------------------
|
|
filesystem->FPrintf ( file, "TotalNumLinks %4d\n",totalNumLinks);
|
|
|
|
for (node = 0; node < m_iNumNodes; node++)
|
|
{
|
|
for (int link = 0; link < m_pAInode[node]->NumLinks(); link++)
|
|
{
|
|
// Only dump if link source
|
|
if (node == m_pAInode[node]->GetLinkByIndex(link)->m_iSrcID)
|
|
{
|
|
filesystem->FPrintf ( file, "LinkSrcID %4d\n", m_pAInode[node]->GetLinkByIndex(link)->m_iSrcID);
|
|
filesystem->FPrintf ( file, "LinkDestID %4d\n", m_pAInode[node]->GetLinkByIndex(link)->m_iDestID);
|
|
|
|
for (int hull =0;hull<NUM_HULLS;hull++)
|
|
{
|
|
filesystem->FPrintf ( file, "Hulls %4d\n", m_pAInode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[hull]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------
|
|
// Dump WC lookup table
|
|
// -------------------------------
|
|
for (node = 0; node < m_iNumNodes; node++)
|
|
{
|
|
filesystem->FPrintf( file, "%4d\n",m_pNodeIndexTable[node]);
|
|
}
|
|
|
|
filesystem->Close(file);
|
|
}
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Only called if network has changed since last time level
|
|
// was loaded
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkManager::LoadNetworkGraph( void )
|
|
{
|
|
// ---------------------------------------------------
|
|
// If I'm in edit mode don't load, always recalculate
|
|
// ---------------------------------------------------
|
|
DevMsg( "Loading AI graph\n" );
|
|
if (engine->IsInEditMode())
|
|
{
|
|
DevMsg( "Not loading AI due to edit mode\n" );
|
|
return;
|
|
}
|
|
|
|
if ( !g_pGameRules->FAllowNPCs() )
|
|
{
|
|
DevMsg( "Not loading AI due to games rules\n" );
|
|
return;
|
|
}
|
|
|
|
DevMsg( "Step 1 loading\n" );
|
|
|
|
// -----------------------------
|
|
// Make sure directories have been made
|
|
// -----------------------------
|
|
char szNrpFilename[MAX_PATH];// text node report filename
|
|
Q_strncpy( szNrpFilename, "maps" ,sizeof(szNrpFilename));
|
|
filesystem->CreateDirHierarchy( szNrpFilename, "DEFAULT_WRITE_PATH" );
|
|
Q_strncat( szNrpFilename, "/graphs", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
|
|
filesystem->CreateDirHierarchy( szNrpFilename, "DEFAULT_WRITE_PATH" );
|
|
|
|
Q_strncat( szNrpFilename, "/", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
|
|
Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
|
|
Q_strncat( szNrpFilename, IsX360() ? ".360.ain" : ".ain", sizeof( szNrpFilename ), COPY_ALL_CHARACTERS );
|
|
|
|
MEM_ALLOC_CREDIT();
|
|
|
|
// Read the file in one gulp
|
|
CUtlBuffer buf;
|
|
bool bHaveAIN = false;
|
|
if ( IsX360() && g_pQueuedLoader->IsMapLoading() )
|
|
{
|
|
// .ain was loaded anonymously by bsp, should be ready
|
|
void *pData;
|
|
int nDataSize;
|
|
if ( g_pQueuedLoader->ClaimAnonymousJob( szNrpFilename, &pData, &nDataSize ) )
|
|
{
|
|
if ( nDataSize != 0 )
|
|
{
|
|
buf.Put( pData, nDataSize );
|
|
bHaveAIN = true;
|
|
}
|
|
filesystem->FreeOptimalReadBuffer( pData );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if ( !bHaveAIN && !filesystem->ReadFile( szNrpFilename, "game", buf ) )
|
|
{
|
|
DevWarning( 2, "Couldn't read %s!\n", szNrpFilename );
|
|
return;
|
|
}
|
|
|
|
DevMsg( "Checking version\n" );
|
|
|
|
// ---------------------------
|
|
// Check the version number
|
|
// ---------------------------
|
|
if ( buf.GetChar() == 'V' && buf.GetChar() == 'e' && buf.GetChar() == 'r' )
|
|
{
|
|
DevMsg( "AI node graph %s is out of date\n", szNrpFilename );
|
|
return;
|
|
}
|
|
|
|
DevMsg( "Passed first ver check\n" );
|
|
|
|
|
|
buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
|
|
|
|
int version = buf.GetInt();
|
|
DevMsg( "Got version %d\n", version );
|
|
|
|
if ( version != AINET_VERSION_NUMBER)
|
|
{
|
|
DevMsg( "AI node graph %s is out of date\n", szNrpFilename );
|
|
return;
|
|
}
|
|
|
|
int mapversion = buf.GetInt();
|
|
DevMsg( "Map version %d\n", mapversion );
|
|
|
|
if ( mapversion != gpGlobals->mapversion && !g_ai_norebuildgraph.GetBool() )
|
|
{
|
|
bool bOK = false;
|
|
|
|
const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" );
|
|
char szLoweredGameDir[256];
|
|
Q_strncpy( szLoweredGameDir, pGameDir, sizeof( szLoweredGameDir ) );
|
|
Q_strlower( szLoweredGameDir );
|
|
|
|
// hack for shipped ep1 and hl2 maps
|
|
// they were rebuilt a week after they were actually shipped so allow the slightly
|
|
// older node graphs to load for these maps
|
|
if ( !V_stricmp( szLoweredGameDir, "hl2" ) || !V_stricmp( szLoweredGameDir, "episodic" ) )
|
|
{
|
|
bOK = true;
|
|
}
|
|
|
|
if ( !bOK )
|
|
{
|
|
DevMsg( "AI node graph %s is out of date (map version changed)\n", szNrpFilename );
|
|
return;
|
|
}
|
|
}
|
|
|
|
DevMsg( "Done version checks\n" );
|
|
|
|
// ----------------------------------------
|
|
// Get the network size and allocate space
|
|
// ----------------------------------------
|
|
int numNodes = buf.GetInt();
|
|
|
|
if ( numNodes > MAX_NODES || numNodes < 0 )
|
|
{
|
|
Error( "AI node graph %s is corrupt\n", szNrpFilename );
|
|
DevMsg( "%s", (const char *)buf.Base() );
|
|
DevMsg( "\n" );
|
|
Assert( 0 );
|
|
return;
|
|
}
|
|
|
|
DevMsg( "Finishing load\n" );
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
// If in wc_edit mode allocate extra space for nodes that might be created
|
|
// ------------------------------------------------------------------------
|
|
if ( engine->IsInEditMode() )
|
|
{
|
|
numNodes = MAX( numNodes, 1024 );
|
|
}
|
|
|
|
m_pNetwork->m_pAInode = new CAI_Node*[MAX( numNodes, 1 )];
|
|
memset( m_pNetwork->m_pAInode, 0, sizeof( CAI_Node* ) * MAX( numNodes, 1 ) );
|
|
|
|
// -------------------------------
|
|
// Load all the nodes to the file
|
|
// -------------------------------
|
|
int node;
|
|
for ( node = 0; node < numNodes; node++)
|
|
{
|
|
Vector origin;
|
|
float yaw;
|
|
origin.x = buf.GetFloat();
|
|
origin.y = buf.GetFloat();
|
|
origin.z = buf.GetFloat();
|
|
yaw = buf.GetFloat();
|
|
|
|
CAI_Node *new_node = m_pNetwork->AddNode( origin, yaw );
|
|
|
|
buf.Get( new_node->m_flVOffset, sizeof(new_node->m_flVOffset) );
|
|
new_node->m_eNodeType = (NodeType_e)buf.GetChar();
|
|
if ( IsX360() )
|
|
{
|
|
buf.SeekGet( CUtlBuffer::SEEK_CURRENT, 3 );
|
|
}
|
|
|
|
new_node->m_eNodeInfo = buf.GetUnsignedShort();
|
|
new_node->m_zone = buf.GetShort();
|
|
}
|
|
|
|
// -------------------------------
|
|
// Load all the links to the fild
|
|
// -------------------------------
|
|
int totalNumLinks = buf.GetInt();
|
|
|
|
for (int link = 0; link < totalNumLinks; link++)
|
|
{
|
|
int srcID, destID;
|
|
|
|
srcID = buf.GetShort();
|
|
destID = buf.GetShort();
|
|
|
|
CAI_Link *pLink = m_pNetwork->CreateLink( srcID, destID );;
|
|
|
|
byte ignored[NUM_HULLS];
|
|
byte *pDest = ( pLink ) ? &pLink->m_iAcceptedMoveTypes[0] : &ignored[0];
|
|
buf.Get( pDest, sizeof(ignored) );
|
|
}
|
|
|
|
// -------------------------------
|
|
// Load WC lookup table
|
|
// -------------------------------
|
|
delete [] GetEditOps()->m_pNodeIndexTable;
|
|
GetEditOps()->m_pNodeIndexTable = new int[MAX( m_pNetwork->m_iNumNodes, 1 )];
|
|
memset( GetEditOps()->m_pNodeIndexTable, 0, sizeof( int ) *MAX( m_pNetwork->m_iNumNodes, 1 ) );
|
|
|
|
for (node = 0; node < m_pNetwork->m_iNumNodes; node++)
|
|
{
|
|
GetEditOps()->m_pNodeIndexTable[node] = buf.GetInt();
|
|
}
|
|
|
|
|
|
#if 1
|
|
CUtlRBTree<int> usedIds;
|
|
CUtlRBTree<int> reportedIds;
|
|
SetDefLessFunc( usedIds );
|
|
SetDefLessFunc( reportedIds );
|
|
|
|
bool printedHeader = false;
|
|
|
|
for (node = 0; node < m_pNetwork->m_iNumNodes; node++)
|
|
{
|
|
int editorId = GetEditOps()->m_pNodeIndexTable[node];
|
|
if ( editorId != NO_NODE )
|
|
{
|
|
if ( usedIds.Find( editorId ) != usedIds.InvalidIndex() )
|
|
{
|
|
if ( !printedHeader )
|
|
{
|
|
Warning( "** Duplicate Hammer Node IDs: " );
|
|
printedHeader = true;
|
|
}
|
|
|
|
if ( reportedIds.Find( editorId ) == reportedIds.InvalidIndex() )
|
|
{
|
|
DevMsg( "%d, ", editorId );
|
|
reportedIds.Insert( editorId );
|
|
}
|
|
}
|
|
else
|
|
usedIds.Insert( editorId );
|
|
}
|
|
}
|
|
|
|
if ( printedHeader )
|
|
DevMsg( "\n** Should run \"Check For Problems\" on the VMF then verify dynamic links\n" );
|
|
#endif
|
|
|
|
gm_fNetworksLoaded = true;
|
|
CAI_DynamicLink::gm_bInitialized = false;
|
|
}
|
|
|
|
/* Keep this around for debugging
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Only called if network has changed since last time level
|
|
// was loaded
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_NetworkManager::LoadNetworkGraph( void )
|
|
{
|
|
// ---------------------------------------------------
|
|
// If I'm in edit mode don't load, always recalculate
|
|
// ---------------------------------------------------
|
|
if (engine->IsInEditMode())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// -----------------------------
|
|
// Make sure directories have been made
|
|
// -----------------------------
|
|
char szNrpFilename [MAX_PATH];// text node report filename
|
|
Q_strncpy( szNrpFilename, "maps" ,sizeof(szNrpFilename));
|
|
filesystem->CreateDirHierarchy( szNrpFilename );
|
|
Q_strncat( szNrpFilename, "/graphs", COPY_ALL_CHARACTERS );
|
|
filesystem->CreateDirHierarchy( szNrpFilename );
|
|
|
|
Q_strncat( szNrpFilename, "/", COPY_ALL_CHARACTERS );
|
|
Q_strncat( szNrpFilename, STRING( gpGlobals->mapname ), COPY_ALL_CHARACTERS );
|
|
Q_strncat( szNrpFilename, ".ain", COPY_ALL_CHARACTERS );
|
|
|
|
FileHandle_t file = filesystem->Open ( szNrpFilename, "r" );
|
|
|
|
// -----------------------------
|
|
// Make sure the file opened ok
|
|
// -----------------------------
|
|
if ( !file )
|
|
{
|
|
DevWarning( 2, "Couldn't create %s!\n", szNrpFilename );
|
|
return;
|
|
}
|
|
|
|
// ---------------------------
|
|
// Check the version number
|
|
// ---------------------------
|
|
char temps[256];
|
|
int version;
|
|
fscanf(file,"%255s",&temps);
|
|
fscanf(file, "%i\n",&version);
|
|
if (version!=AINET_VERSION_NUMBER)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// ----------------------------------------
|
|
// Get the network size and allocate space
|
|
// ----------------------------------------
|
|
int numNodes;
|
|
fscanf(file,"%255s",&temps);
|
|
fscanf ( file, "%d\n", &numNodes);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// If in wc_edit mode allocate extra space for nodes that might be created
|
|
// ------------------------------------------------------------------------
|
|
if ( engine->IsInEditMode() )
|
|
{
|
|
numNodes = MAX( numNodes, 1024 );
|
|
}
|
|
|
|
m_pAInode = new CAI_Node*[numNodes];
|
|
if ( !m_pAInode )
|
|
{
|
|
Warning( "LoadNetworkGraph: Not enough memory to create %i nodes\n", numNodes );
|
|
Assert(0);
|
|
return;
|
|
}
|
|
|
|
// -------------------------------
|
|
// Load all the nodes to the file
|
|
// -------------------------------
|
|
for (int node = 0; node < numNodes; node++)
|
|
{
|
|
CAI_Node *new_node = AddNode();
|
|
|
|
Vector origin;
|
|
fscanf(file,"%255s",&temps);
|
|
fscanf(file, "%f,%f,%f\n", &new_node->GetOrigin().x, &new_node->GetOrigin().y, &new_node->GetOrigin().z );
|
|
for (int hull =0;hull<NUM_HULLS;hull++)
|
|
{
|
|
fscanf(file,"%255s",&temps);
|
|
fscanf(file, "%f\n", &new_node->m_flVOffset[hull]);
|
|
}
|
|
fscanf(file,"%255s",&temps);
|
|
fscanf(file, "%d\n", &new_node->m_eHintType );
|
|
fscanf(file,"%255s",&temps);
|
|
fscanf(file, "%f\n", &new_node->GetYaw() );
|
|
fscanf(file,"%255s",&temps);
|
|
fscanf(file, "%d\n",&new_node->GetType());
|
|
fscanf(file,"%255s",&temps);
|
|
fscanf(file, "%d\n",&new_node->m_eNodeInfo);
|
|
|
|
fscanf(file,"%255s",&temps);
|
|
new_node->m_pNeighborBS = new CVarBitVec(numNodes);
|
|
new_node->m_pNeighborBS->LoadBitString(file);
|
|
|
|
fscanf(file,"%255s",&temps);
|
|
new_node->m_pVisibleBS = new CVarBitVec(numNodes);
|
|
new_node->m_pVisibleBS->LoadBitString(file);
|
|
|
|
fscanf(file,"%255s",&temps);
|
|
new_node->m_pConnectedBS = new CVarBitVec(numNodes);
|
|
new_node->m_pConnectedBS->LoadBitString(file);
|
|
|
|
fscanf(file,"%255s",&temps);
|
|
int numLinks;
|
|
fscanf (file, "%4d",&numLinks);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// If in wc_edit mode allocate extra space for nodes that might be created
|
|
// ------------------------------------------------------------------------
|
|
if ( engine->IsInEditMode() )
|
|
{
|
|
numLinks = AI_MAX_NODE_LINKS;
|
|
}
|
|
|
|
//Assert ( numLinks >= 1 );
|
|
new_node->AllocateLinkSpace( numLinks );
|
|
}
|
|
|
|
// -------------------------------
|
|
// Load all the links to the fild
|
|
// -------------------------------
|
|
int totalNumLinks;
|
|
fscanf(file,"%255s",&temps);
|
|
fscanf ( file, "%d\n",&totalNumLinks);
|
|
|
|
for (int link = 0; link < totalNumLinks; link++)
|
|
{
|
|
CAI_Link *new_link = new CAI_Link;
|
|
|
|
fscanf(file,"%255s",&temps);
|
|
fscanf ( file, "%4d\n", &new_link->m_iSrcID);
|
|
|
|
fscanf(file,"%255s",&temps);
|
|
fscanf ( file, "%4d\n", &new_link->m_iDestID);
|
|
|
|
for (int hull =0;hull<NUM_HULLS;hull++)
|
|
{
|
|
fscanf(file,"%255s",&temps);
|
|
fscanf ( file, "%d\n", &new_link->m_iAcceptedMoveTypes[hull]);
|
|
}
|
|
// Now add link to source and destination nodes
|
|
m_pAInode[new_link->m_iSrcID]->AddLink(new_link);
|
|
m_pAInode[new_link->m_iDestID]->AddLink(new_link);
|
|
}
|
|
|
|
// -------------------------------
|
|
// Load WC lookup table
|
|
// -------------------------------
|
|
m_pNodeIndexTable = new int[m_iNumNodes];
|
|
|
|
for (node = 0; node < m_iNumNodes; node++)
|
|
{
|
|
fscanf( file, "%d\n",&m_pNodeIndexTable[node]);
|
|
}
|
|
|
|
CAI_NetworkManager::NetworksLoaded() = true;
|
|
fclose(file);
|
|
}
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Deletes all AINetworks from memory
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkManager::DeleteAllAINetworks(void)
|
|
{
|
|
CAI_DynamicLink::gm_bInitialized = false;
|
|
gm_fNetworksLoaded = false;
|
|
g_pBigAINet = NULL;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Only called if network has changed since last time level
|
|
// was loaded
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkManager::BuildNetworkGraph( void )
|
|
{
|
|
if ( m_bDontSaveGraph )
|
|
return;
|
|
|
|
CAI_DynamicLink::gm_bInitialized = false;
|
|
g_AINetworkBuilder.Build( m_pNetwork );
|
|
|
|
// If I'm loading for the first time save. Otherwise I'm
|
|
// doing a wc edit and I don't want to save
|
|
if (!CAI_NetworkManager::NetworksLoaded())
|
|
{
|
|
SaveNetworkGraph();
|
|
|
|
gm_fNetworksLoaded = true;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool g_bAIDisabledByUser = false;
|
|
|
|
void CAI_NetworkManager::InitializeAINetworks()
|
|
{
|
|
// For not just create a single AI Network called "BigNet"
|
|
// At some later point we may have mulitple AI networks
|
|
CAI_NetworkManager *pNetwork;
|
|
g_pAINetworkManager = pNetwork = CREATE_ENTITY( CAI_NetworkManager, "ai_network" );
|
|
pNetwork->AddEFlags( EFL_KEEP_ON_RECREATE_ENTITIES );
|
|
g_pBigAINet = pNetwork->GetNetwork();
|
|
pNetwork->SetName( AllocPooledString("BigNet") );
|
|
pNetwork->Spawn();
|
|
if ( engine->IsInEditMode() )
|
|
{
|
|
g_ai_norebuildgraph.SetValue( 0 );
|
|
}
|
|
if ( CAI_NetworkManager::IsAIFileCurrent( STRING( gpGlobals->mapname ) ) )
|
|
{
|
|
pNetwork->LoadNetworkGraph();
|
|
if ( !g_bAIDisabledByUser )
|
|
{
|
|
CAI_BaseNPC::m_nDebugBits &= ~bits_debugDisableAI;
|
|
}
|
|
}
|
|
|
|
// Reset node counter used during load
|
|
CNodeEnt::m_nNodeCount = 0;
|
|
|
|
pNetwork->SetThink( &CAI_NetworkManager::DelayedInit );
|
|
pNetwork->SetNextThink( gpGlobals->curtime );
|
|
}
|
|
|
|
// UNDONE: Where should this be defined?
|
|
#ifndef MAX_PATH
|
|
#define MAX_PATH 256
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns true if the AINetwork data files are up to date
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CAI_NetworkManager::IsAIFileCurrent ( const char *szMapName )
|
|
{
|
|
char szBspFilename[MAX_PATH];
|
|
char szGraphFilename[MAX_PATH];
|
|
|
|
if ( !g_pGameRules->FAllowNPCs() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( IsX360() && ( filesystem->GetDVDMode() == DVDMODE_STRICT ) )
|
|
{
|
|
// dvd build process validates and guarantees correctness, timestamps are allowed to be wrong
|
|
return true;
|
|
}
|
|
|
|
{
|
|
const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" );
|
|
char szLoweredGameDir[256];
|
|
Q_strncpy( szLoweredGameDir, pGameDir, sizeof( szLoweredGameDir ) );
|
|
Q_strlower( szLoweredGameDir );
|
|
|
|
if ( !V_stricmp( szLoweredGameDir, "hl2" ) || !V_stricmp( szLoweredGameDir, "episodic" ) || !V_stricmp( szLoweredGameDir, "ep2" ) || !V_stricmp( szLoweredGameDir, "portal" ) || !V_stricmp( szLoweredGameDir, "lostcoast" ) || !V_stricmp( szLoweredGameDir, "hl1" ) )
|
|
{
|
|
// we shipped good node graphs for our games
|
|
return true;
|
|
}
|
|
}
|
|
|
|
Q_snprintf( szBspFilename, sizeof( szBspFilename ), "maps/%s%s.bsp" ,szMapName, GetPlatformExt() );
|
|
Q_snprintf( szGraphFilename, sizeof( szGraphFilename ), "maps/graphs/%s%s.ain", szMapName, GetPlatformExt() );
|
|
|
|
int iCompare;
|
|
if ( engine->CompareFileTime( szBspFilename, szGraphFilename, &iCompare ) )
|
|
{
|
|
if ( iCompare > 0 )
|
|
{
|
|
// BSP file is newer.
|
|
if ( g_ai_norebuildgraph.GetInt() )
|
|
{
|
|
// The user has specified that they wish to override the
|
|
// rebuilding of outdated nodegraphs (see top of this file)
|
|
if ( filesystem->FileExists( szGraphFilename ) )
|
|
{
|
|
// Display these messages only if the graph exists, and the
|
|
// user is asking to override the rebuilding. If the graph does
|
|
// not exist, we're going to build it whether the user wants to or
|
|
// not.
|
|
DevMsg( 2, ".AIN File will *NOT* be updated. User Override.\n\n" );
|
|
DevMsg( "\n*****Node Graph Rebuild OVERRIDDEN by user*****\n\n" );
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Graph is out of date. Rebuild at usual.
|
|
DevMsg( 2, ".AIN File will be updated\n\n" );
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkManager::Spawn ( void )
|
|
{
|
|
SetSolid( SOLID_NONE );
|
|
SetMoveType( MOVETYPE_NONE );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkManager::DelayedInit( void )
|
|
{
|
|
if ( !g_pGameRules->FAllowNPCs() )
|
|
{
|
|
SetThink ( NULL );
|
|
return;
|
|
}
|
|
|
|
if ( !g_ai_norebuildgraph.GetInt() )
|
|
{
|
|
// ----------------------------------------------------------
|
|
// Actually enter DelayedInit twice when rebuilding the
|
|
// node graph. The first time through we just print the
|
|
// warning message. We only actually do the rebuild on
|
|
// the second pass to make sure the message hits the screen
|
|
// ----------------------------------------------------------
|
|
if (m_bNeedGraphRebuild)
|
|
{
|
|
Assert( !m_bDontSaveGraph );
|
|
|
|
BuildNetworkGraph(); // For now only one AI Network
|
|
|
|
if (engine->IsInEditMode())
|
|
{
|
|
engine->ServerCommand("exec map_edit.cfg\n");
|
|
}
|
|
|
|
SetThink ( NULL );
|
|
if ( !g_bAIDisabledByUser )
|
|
{
|
|
CAI_BaseNPC::m_nDebugBits &= ~bits_debugDisableAI;
|
|
}
|
|
}
|
|
|
|
|
|
// --------------------------------------------
|
|
// If I haven't loaded a network, or I'm in
|
|
// WorldCraft edit mode rebuild the network
|
|
// --------------------------------------------
|
|
else if ( !m_bDontSaveGraph && ( !CAI_NetworkManager::NetworksLoaded() || engine->IsInEditMode() ) )
|
|
{
|
|
#ifdef _WIN32
|
|
// --------------------------------------------------------
|
|
// If in edit mode start WC session and make sure we are
|
|
// running the same map in WC and the engine
|
|
// --------------------------------------------------------
|
|
if (engine->IsInEditMode())
|
|
{
|
|
int status = Editor_BeginSession(STRING(gpGlobals->mapname), gpGlobals->mapversion, false);
|
|
if (status == Editor_NotRunning)
|
|
{
|
|
DevMsg("\nAborting map_edit\nWorldcraft not running...\n\n");
|
|
UTIL_CenterPrintAll( "Worldcraft not running...\n" );
|
|
engine->ServerCommand("disconnect\n");
|
|
SetThink(NULL);
|
|
return;
|
|
}
|
|
else if (status == Editor_BadCommand)
|
|
{
|
|
DevMsg("\nAborting map_edit\nWC/Engine map versions different...\n\n");
|
|
UTIL_CenterPrintAll( "WC/Engine map versions different...\n" );
|
|
engine->ServerCommand("disconnect\n");
|
|
SetThink(NULL);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Increment version number when session begins
|
|
gpGlobals->mapversion++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
DevMsg( "Node Graph out of Date. Rebuilding... (%d, %d, %d)\n", (int)m_bDontSaveGraph, (int)!CAI_NetworkManager::NetworksLoaded(), (int) engine->IsInEditMode() );
|
|
UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding...\n" );
|
|
m_bNeedGraphRebuild = true;
|
|
g_pAINetworkManager->SetNextThink( gpGlobals->curtime + 1 );
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
// --------------------------------------------
|
|
// Initialize any dynamic links
|
|
// --------------------------------------------
|
|
CAI_DynamicLink::InitDynamicLinks();
|
|
FixupHints();
|
|
|
|
GetEditOps()->OnInit();
|
|
|
|
m_fInitalized = true;
|
|
|
|
if ( g_AI_Manager.NumAIs() != 0 && g_pBigAINet->NumNodes() == 0 )
|
|
DevMsg( "WARNING: Level contains NPCs but has no path nodes\n" );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkManager::FixupHints()
|
|
{
|
|
AIHintIter_t iter;
|
|
CAI_Hint *pHint = CAI_HintManager::GetFirstHint( &iter );
|
|
while ( pHint )
|
|
{
|
|
pHint->FixupTargetNode();
|
|
pHint = CAI_HintManager::GetNextHint( &iter );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CAI_NetworkEditTools
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CAI_Node* CAI_NetworkEditTools::m_pLastDeletedNode = NULL; // For undo in wc edit mode
|
|
int CAI_NetworkEditTools::m_iHullDrawNum = HULL_HUMAN; // Which hulls to draw
|
|
int CAI_NetworkEditTools::m_iVisibilityNode = NO_NODE;
|
|
int CAI_NetworkEditTools::m_iGConnectivityNode = NO_NODE;
|
|
bool CAI_NetworkEditTools::m_bAirEditMode = false;
|
|
bool CAI_NetworkEditTools::m_bLinkEditMode = false;
|
|
float CAI_NetworkEditTools::m_flAirEditDistance = 300;
|
|
|
|
#ifdef AI_PERF_MON
|
|
// Performance stats (only for development)
|
|
int CAI_NetworkEditTools::m_nPerfStatNN = 0;
|
|
int CAI_NetworkEditTools::m_nPerfStatPB = 0;
|
|
float CAI_NetworkEditTools::m_fNextPerfStatTime = -1;
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkEditTools::OnInit()
|
|
{
|
|
// --------------------------------------------
|
|
// If I'm not in edit mode delete WC ID table
|
|
// --------------------------------------------
|
|
if ( !engine->IsInEditMode() )
|
|
{
|
|
// delete[] m_pNodeIndexTable; // For now only one AI Network called "BigNet"
|
|
// m_pNodeIndexTable = NULL;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Given a WorldCraft node ID, return the associated engine ID
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
int CAI_NetworkEditTools::GetNodeIdFromWCId( int nWCId )
|
|
{
|
|
if ( nWCId == -1 )
|
|
return -1;
|
|
|
|
if (!m_pNodeIndexTable)
|
|
{
|
|
DevMsg("ERROR: Trying to get WC ID with no table!\n");
|
|
return -1;
|
|
}
|
|
|
|
if (!m_pNetwork->NumNodes())
|
|
{
|
|
DevMsg("ERROR: Trying to get WC ID with no network!\n");
|
|
return -1;
|
|
}
|
|
|
|
for (int i=0;i<m_pNetwork->NumNodes();i++)
|
|
{
|
|
if (m_pNodeIndexTable[i] == nWCId)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int CAI_NetworkEditTools::GetWCIdFromNodeId( int nNodeId )
|
|
{
|
|
if ( nNodeId == -1 || nNodeId >= m_pNetwork->NumNodes() )
|
|
return -1;
|
|
return m_pNodeIndexTable[nNodeId];
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Find the nearest ainode that is faced from the given location
|
|
// and within the angular threshold (ignores worldspawn).
|
|
// DO NOT USE FOR ANY RUN TIME RELEASE CODE
|
|
// Used for selection of nodes in debugging display only!
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CAI_Node *CAI_NetworkEditTools::FindAINodeNearestFacing( const Vector &origin, const Vector &facing, float threshold, int nNodeType)
|
|
{
|
|
float bestDot = threshold;
|
|
CAI_Node *best = NULL;
|
|
|
|
CAI_Network* aiNet = g_pBigAINet;
|
|
|
|
for (int node =0; node < aiNet->NumNodes();node++)
|
|
{
|
|
if (aiNet->GetNode(node)->GetType() != NODE_DELETED)
|
|
{
|
|
// Pick nodes that are in the current editing type
|
|
if ( nNodeType == NODE_ANY ||
|
|
nNodeType == aiNet->GetNode(node)->GetType() )
|
|
{
|
|
// Make vector to node
|
|
Vector to_node = (aiNet->GetNode(node)->GetPosition(m_iHullDrawNum) - origin);
|
|
|
|
VectorNormalize( to_node );
|
|
float dot = DotProduct (facing , to_node );
|
|
if (dot > bestDot)
|
|
{
|
|
// Make sure I have a line of sight to it
|
|
trace_t tr;
|
|
AI_TraceLine ( origin, aiNet->GetNode(node)->GetPosition(m_iHullDrawNum),
|
|
MASK_BLOCKLOS, NULL, COLLISION_GROUP_NONE, &tr );
|
|
if ( tr.fraction == 1.0 )
|
|
{
|
|
bestDot = dot;
|
|
best = aiNet->GetNode(node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
|
|
Vector PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint)
|
|
{
|
|
Vector vEndToStart = (vEndPos - vStartPos);
|
|
Vector vOrgToStart = (vPoint - vStartPos);
|
|
float fNumerator = DotProduct(vEndToStart,vOrgToStart);
|
|
float fDenominator = vEndToStart.Length() * vOrgToStart.Length();
|
|
float fIntersectDist = vOrgToStart.Length()*(fNumerator/fDenominator);
|
|
VectorNormalize( vEndToStart );
|
|
Vector vIntersectPos = vStartPos + vEndToStart * fIntersectDist;
|
|
|
|
return vIntersectPos;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Find the nearest ainode that is faced from the given location
|
|
// and within the angular threshold (ignores worldspawn).
|
|
// DO NOT USE FOR ANY RUN TIME RELEASE CODE
|
|
// Used for selection of nodes in debugging display only!
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CAI_Link *CAI_NetworkEditTools::FindAILinkNearestFacing( const Vector &vOrigin, const Vector &vFacing, float threshold)
|
|
{
|
|
float bestDot = threshold;
|
|
CAI_Link *best = NULL;
|
|
|
|
CAI_Network* aiNet = g_pBigAINet;
|
|
|
|
for (int node =0; node < aiNet->NumNodes();node++)
|
|
{
|
|
if (aiNet->GetNode(node)->GetType() != NODE_DELETED)
|
|
{
|
|
// Pick nodes that are in the current editing type
|
|
if (( m_bAirEditMode && aiNet->GetNode(node)->GetType() == NODE_AIR) ||
|
|
(!m_bAirEditMode && aiNet->GetNode(node)->GetType() == NODE_GROUND))
|
|
{
|
|
// Go through each link
|
|
for (int link=0; link < aiNet->GetNode(node)->NumLinks();link++)
|
|
{
|
|
CAI_Link *nodeLink = aiNet->GetNode(node)->GetLinkByIndex(link);
|
|
|
|
// Find position on link that I am looking
|
|
int endID = nodeLink->DestNodeID(node);
|
|
Vector startPos = aiNet->GetNode(node)->GetPosition(m_iHullDrawNum);
|
|
Vector endPos = aiNet->GetNode(endID)->GetPosition(m_iHullDrawNum);
|
|
Vector vNearest = PointOnLineNearestPoint(startPos, endPos, vOrigin);
|
|
|
|
// Get angle between viewing dir. and nearest point on line
|
|
Vector vOriginToNearest = (vNearest - vOrigin);
|
|
float fNumerator = DotProduct(vOriginToNearest,vFacing);
|
|
float fDenominator = vOriginToNearest.Length();
|
|
float fAngleToNearest = acos(fNumerator/fDenominator);
|
|
|
|
// If not facing the line reject
|
|
if (fAngleToNearest > 1.57)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Calculate intersection of facing direction to nearest point
|
|
float fIntersectDist = vOriginToNearest.Length() * tan(fAngleToNearest);
|
|
Vector dir = endPos-startPos;
|
|
float fLineLen = VectorNormalize( dir );
|
|
Vector vIntersection = vNearest + (fIntersectDist * dir);
|
|
|
|
// Reject of beyond end of line
|
|
if (((vIntersection - startPos).Length() > fLineLen) ||
|
|
((vIntersection - endPos ).Length() > fLineLen) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Now test dot to the look position
|
|
Vector toLink = vIntersection - vOrigin;
|
|
VectorNormalize(toLink);
|
|
float lookDot = DotProduct (vFacing , toLink);
|
|
if (lookDot > bestDot)
|
|
{
|
|
// Make sure I have a line of sight to it
|
|
trace_t tr;
|
|
AI_TraceLine ( vOrigin, vIntersection, MASK_BLOCKLOS, NULL, COLLISION_GROUP_NONE, &tr );
|
|
if ( tr.fraction == 1.0 )
|
|
{
|
|
bestDot = lookDot;
|
|
best = nodeLink;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Used for WC edit more. Marks that the network should be
|
|
// rebuild and turns of any displays that have been invalidated
|
|
// as the network is now out of date
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_NetworkEditTools::SetRebuildFlags( void )
|
|
{
|
|
m_debugNetOverlays |= bits_debugNeedRebuild;
|
|
m_debugNetOverlays &= ~bits_debugOverlayConnections;
|
|
m_debugNetOverlays &= ~bits_debugOverlayGraphConnect;
|
|
m_debugNetOverlays &= ~bits_debugOverlayVisibility;
|
|
m_debugNetOverlays &= ~bits_debugOverlayHulls;
|
|
|
|
// Not allowed to edit links when graph outdated
|
|
m_bLinkEditMode = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Used for WC edit more. After node graph has been rebuild
|
|
// marks it as so and turns connection display back on
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_NetworkEditTools::ClearRebuildFlags( void )
|
|
{
|
|
m_debugNetOverlays |= bits_debugOverlayConnections;
|
|
|
|
// ------------------------------------------
|
|
// Clear all rebuild flags in nodes
|
|
// ------------------------------------------
|
|
for (int i = 0; i < m_pNetwork->NumNodes(); i++)
|
|
{
|
|
m_pNetwork->GetNode(i)->m_eNodeInfo &= ~bits_NODE_WC_CHANGED;
|
|
m_pNetwork->GetNode(i)->ClearNeedsRebuild();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the next hull to draw, or none if at end of hulls
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_NetworkEditTools::DrawNextHull(const char *ainet_name)
|
|
{
|
|
m_iHullDrawNum++;
|
|
if (m_iHullDrawNum == NUM_HULLS)
|
|
{
|
|
m_iHullDrawNum = 0;
|
|
}
|
|
|
|
// Recalculate usable nodes for current hull
|
|
g_pAINetworkManager->GetEditOps()->RecalcUsableNodesForHull();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_NetworkEditTools::DrawHull(Hull_t eHull)
|
|
{
|
|
m_iHullDrawNum = eHull;
|
|
if (m_iHullDrawNum >= NUM_HULLS)
|
|
{
|
|
m_iHullDrawNum = 0;
|
|
}
|
|
|
|
// Recalculate usable nodes for current hull
|
|
g_pAINetworkManager->GetEditOps()->RecalcUsableNodesForHull();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Used just for debug display, to color nodes grey that the
|
|
// currently selected hull size can't use.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkEditTools::RecalcUsableNodesForHull(void)
|
|
{
|
|
// -----------------------------------------------------
|
|
// Use test hull to check hull sizes
|
|
// -----------------------------------------------------
|
|
CAI_TestHull *m_pTestHull = CAI_TestHull::GetTestHull();
|
|
m_pTestHull->GetNavigator()->SetNetwork( g_pBigAINet );
|
|
m_pTestHull->SetHullType((Hull_t)m_iHullDrawNum);
|
|
m_pTestHull->SetHullSizeNormal();
|
|
|
|
for (int node=0;node<m_pNetwork->NumNodes();node++)
|
|
{
|
|
if ( ( m_pNetwork->GetNode(node)->m_eNodeInfo & ( HullToBit( (Hull_t)m_iHullDrawNum ) << NODE_ENT_FLAGS_SHIFT ) ) ||
|
|
m_pTestHull->GetNavigator()->CanFitAtNode(node))
|
|
{
|
|
m_pNetwork->GetNode(node)->m_eNodeInfo &= ~bits_NODE_WONT_FIT_HULL;
|
|
}
|
|
else
|
|
{
|
|
m_pNetwork->GetNode(node)->m_eNodeInfo |= bits_NODE_WONT_FIT_HULL;
|
|
}
|
|
}
|
|
CAI_TestHull::ReturnTestHull();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets debug bits
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkEditTools::SetDebugBits(const char *ainet_name,int debug_bit)
|
|
{
|
|
CAI_NetworkEditTools *pEditOps = g_pAINetworkManager->GetEditOps();
|
|
if ( !pEditOps )
|
|
return;
|
|
|
|
if (debug_bit & bits_debugOverlayNodes)
|
|
{
|
|
if (pEditOps->m_debugNetOverlays & bits_debugOverlayNodesLev2)
|
|
{
|
|
pEditOps->m_debugNetOverlays &= ~bits_debugOverlayNodes;
|
|
pEditOps->m_debugNetOverlays &= ~bits_debugOverlayNodesLev2;
|
|
}
|
|
else if (pEditOps->m_debugNetOverlays & bits_debugOverlayNodes)
|
|
{
|
|
pEditOps->m_debugNetOverlays |= bits_debugOverlayNodesLev2;
|
|
}
|
|
else
|
|
{
|
|
pEditOps->m_debugNetOverlays |= bits_debugOverlayNodes;
|
|
|
|
// Recalculate usable nodes for current hull
|
|
pEditOps->RecalcUsableNodesForHull();
|
|
}
|
|
}
|
|
else if (pEditOps->m_debugNetOverlays & debug_bit)
|
|
{
|
|
pEditOps->m_debugNetOverlays &= ~debug_bit;
|
|
}
|
|
else
|
|
{
|
|
pEditOps->m_debugNetOverlays |= debug_bit;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draws edit display info on screen
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkEditTools::DrawEditInfoOverlay(void)
|
|
{
|
|
hudtextparms_s tTextParam;
|
|
tTextParam.x = 0.8;
|
|
tTextParam.y = 0.8;
|
|
tTextParam.effect = 0;
|
|
tTextParam.r1 = 255;
|
|
tTextParam.g1 = 255;
|
|
tTextParam.b1 = 255;
|
|
tTextParam.a1 = 255;
|
|
tTextParam.r2 = 255;
|
|
tTextParam.g2 = 255;
|
|
tTextParam.b2 = 255;
|
|
tTextParam.a2 = 255;
|
|
tTextParam.fadeinTime = 0;
|
|
tTextParam.fadeoutTime = 0;
|
|
tTextParam.holdTime = 1;
|
|
tTextParam.fxTime = 0;
|
|
tTextParam.channel = 0;
|
|
|
|
char hullTypeTxt[50];
|
|
char nodeTypeTxt[50];
|
|
char editTypeTxt[50];
|
|
char outTxt[255];
|
|
|
|
Q_snprintf(hullTypeTxt,sizeof(hullTypeTxt)," %s",NAI_Hull::Name(m_iHullDrawNum));
|
|
Q_snprintf(outTxt,sizeof(outTxt),"Displaying:\n%s\n\n", hullTypeTxt);
|
|
|
|
if (engine->IsInEditMode())
|
|
{
|
|
char outTxt2[255];
|
|
Q_snprintf(nodeTypeTxt,sizeof(nodeTypeTxt)," %s (l)", m_bLinkEditMode ? "Links":"Nodes");
|
|
Q_snprintf(editTypeTxt,sizeof(editTypeTxt)," %s (m)", m_bAirEditMode ? "Air":"Ground");
|
|
Q_snprintf(outTxt2,sizeof(outTxt2),"Editing:\n%s\n%s", editTypeTxt,nodeTypeTxt);
|
|
Q_strncat(outTxt,outTxt2,sizeof(outTxt), COPY_ALL_CHARACTERS);
|
|
|
|
// Print in red if network needs rebuilding
|
|
if (m_debugNetOverlays & bits_debugNeedRebuild)
|
|
{
|
|
tTextParam.g1 = 0;
|
|
tTextParam.b1 = 0;
|
|
}
|
|
}
|
|
|
|
UTIL_HudMessageAll( tTextParam, outTxt );
|
|
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draws AINetwork on the screen
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkEditTools::DrawAINetworkOverlay(void)
|
|
{
|
|
// ------------------------------------
|
|
// If network isn't loaded yet return
|
|
// ------------------------------------
|
|
if (!CAI_NetworkManager::NetworksLoaded())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
// So we don't fill up the client message queue
|
|
// with node drawing messages, only send them
|
|
// in chuncks
|
|
// ----------------------------------------------
|
|
static int startDrawNode = 0;
|
|
static int endDrawNode = 0;
|
|
static float flDrawDuration;
|
|
endDrawNode = startDrawNode + 20;
|
|
flDrawDuration = 0.1 * (m_pNetwork->NumNodes()-1)/20;
|
|
if ( flDrawDuration < .1 )
|
|
flDrawDuration = .1;
|
|
if (endDrawNode > m_pNetwork->NumNodes())
|
|
{
|
|
endDrawNode = m_pNetwork->NumNodes();
|
|
}
|
|
|
|
// ---------------------
|
|
// Draw grid
|
|
// ---------------------
|
|
if (m_debugNetOverlays & bits_debugOverlayGrid)
|
|
{
|
|
// Trace a line to where player is looking
|
|
CBasePlayer* pPlayer = UTIL_PlayerByIndex(CBaseEntity::m_nDebugPlayer);
|
|
|
|
if (pPlayer)
|
|
{
|
|
Vector vForward;
|
|
Vector vSource = pPlayer->EyePosition();
|
|
pPlayer->EyeVectors( &vForward );
|
|
|
|
trace_t tr;
|
|
AI_TraceLine ( vSource, vSource + vForward * 2048, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr);
|
|
|
|
float dotPr = DotProduct(Vector(0,0,1),tr.plane.normal);
|
|
if (tr.fraction != 1.0 && dotPr > 0.5)
|
|
{
|
|
NDebugOverlay::Grid( tr.endpos + Vector(0,0,1) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------------------
|
|
CAI_Node **pAINode = m_pNetwork->AccessNodes();
|
|
|
|
// --------------------
|
|
// Draw the graph connectivity
|
|
// ---------------------
|
|
if (m_debugNetOverlays & bits_debugOverlayGraphConnect)
|
|
{
|
|
// ---------------------------------------------------
|
|
// If network needs rebuilding do so before display
|
|
// --------------------------------------------------
|
|
if (m_debugNetOverlays & bits_debugNeedRebuild)
|
|
{
|
|
m_pManager->RebuildNetworkGraph();
|
|
}
|
|
else if (m_iGConnectivityNode != NO_NODE)
|
|
{
|
|
for (int node=0;node<m_pNetwork->NumNodes();node++)
|
|
{
|
|
if ( m_pNetwork->IsConnected( m_iGConnectivityNode, node) )
|
|
{
|
|
Vector srcPos = pAINode[m_iGConnectivityNode]->GetPosition(m_iHullDrawNum);
|
|
Vector desPos = pAINode[node]->GetPosition(m_iHullDrawNum);
|
|
NDebugOverlay::Line(srcPos, desPos, 255,0,255, false,0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------------------
|
|
// Draw the hulls
|
|
// ---------------------
|
|
if (m_debugNetOverlays & bits_debugOverlayHulls)
|
|
{
|
|
// ---------------------------------------------------
|
|
// If network needs rebuilding do so before display
|
|
// --------------------------------------------------
|
|
if (m_debugNetOverlays & bits_debugNeedRebuild)
|
|
{
|
|
m_pManager->RebuildNetworkGraph();
|
|
}
|
|
else
|
|
{
|
|
for (int node=startDrawNode;node<endDrawNode;node++)
|
|
{
|
|
for (int link=0;link<pAINode[node]->NumLinks();link++)
|
|
{
|
|
// Only draw link once
|
|
if (pAINode[node]->GetLinkByIndex(link)->DestNodeID(node) < node)
|
|
{
|
|
|
|
Vector srcPos = pAINode[pAINode[node]->GetLinkByIndex(link)->m_iSrcID]->GetPosition(m_iHullDrawNum);
|
|
Vector desPos = pAINode[pAINode[node]->GetLinkByIndex(link)->m_iDestID]->GetPosition(m_iHullDrawNum);
|
|
Vector direction = desPos - srcPos;
|
|
float length = VectorNormalize(direction);
|
|
Vector hullMins = NAI_Hull::Mins(m_iHullDrawNum);
|
|
Vector hullMaxs = NAI_Hull::Maxs(m_iHullDrawNum);
|
|
hullMaxs.x = length + hullMaxs.x;
|
|
|
|
if (pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum] & bits_CAP_MOVE_FLY)
|
|
{
|
|
NDebugOverlay::BoxDirection(srcPos, hullMins, hullMaxs, direction, 100,255,255,20,flDrawDuration);
|
|
}
|
|
|
|
if (pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum] & bits_CAP_MOVE_CLIMB)
|
|
{
|
|
// Display as a vertical slice up the climbing surface unless dismount node
|
|
if (pAINode[pAINode[node]->GetLinkByIndex(link)->m_iSrcID]->GetOrigin() != pAINode[pAINode[node]->GetLinkByIndex(link)->m_iDestID]->GetOrigin())
|
|
{
|
|
hullMaxs.x = hullMaxs.x - length;
|
|
if (srcPos.z < desPos.z)
|
|
{
|
|
hullMaxs.z = length + hullMaxs.z;
|
|
}
|
|
else
|
|
{
|
|
hullMins.z = hullMins.z - length;
|
|
}
|
|
direction = Vector(0,1,0);
|
|
|
|
}
|
|
NDebugOverlay::BoxDirection(srcPos, hullMins, hullMaxs, direction, 255,0,255,20,flDrawDuration);
|
|
}
|
|
|
|
if (pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum] & bits_CAP_MOVE_GROUND)
|
|
{
|
|
NDebugOverlay::BoxDirection(srcPos, hullMins, hullMaxs, direction, 0,255,50,20,flDrawDuration);
|
|
}
|
|
|
|
else if (pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum] & bits_CAP_MOVE_JUMP)
|
|
{
|
|
NDebugOverlay::BoxDirection(srcPos, hullMins, hullMaxs, direction, 0,0,255,20,flDrawDuration);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------------------
|
|
// Draw the hints
|
|
// ---------------------
|
|
if (m_debugNetOverlays & bits_debugOverlayHints)
|
|
{
|
|
CAI_HintManager::DrawHintOverlays(flDrawDuration);
|
|
}
|
|
|
|
// -------------------------------
|
|
// Draw the nodes and connections
|
|
// -------------------------------
|
|
if (m_debugNetOverlays & (bits_debugOverlayNodes | bits_debugOverlayConnections))
|
|
{
|
|
for (int node=startDrawNode;node<endDrawNode;node++) {
|
|
|
|
// This gets expensive, so see if the node is visible to the client
|
|
if (pAINode[node]->GetType() != NODE_DELETED)
|
|
{
|
|
// --------------------
|
|
// Draw the connections
|
|
// ---------------------
|
|
if (m_debugNetOverlays & bits_debugOverlayConnections)
|
|
{
|
|
// ---------------------------------------------------
|
|
// If network needs rebuilding do so before display
|
|
// --------------------------------------------------
|
|
if (m_debugNetOverlays & bits_debugNeedRebuild)
|
|
{
|
|
m_pManager->RebuildNetworkGraph();
|
|
}
|
|
else
|
|
{
|
|
for (int link=0;link<pAINode[node]->NumLinks();link++) {
|
|
|
|
// Only draw link once
|
|
if (pAINode[node]->GetLinkByIndex(link)->DestNodeID(node) < node)
|
|
{
|
|
int srcID = pAINode[node]->GetLinkByIndex(link)->m_iSrcID;
|
|
int desID = pAINode[node]->GetLinkByIndex(link)->m_iDestID;
|
|
|
|
Vector srcPos = pAINode[srcID]->GetPosition(m_iHullDrawNum);
|
|
Vector desPos = pAINode[desID]->GetPosition(m_iHullDrawNum);
|
|
|
|
int srcType = pAINode[pAINode[node]->GetLinkByIndex(link)->m_iSrcID]->GetType();
|
|
int desType = pAINode[pAINode[node]->GetLinkByIndex(link)->m_iDestID]->GetType();
|
|
|
|
int linkInfo = pAINode[node]->GetLinkByIndex(link)->m_LinkInfo;
|
|
int moveTypes = pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[m_iHullDrawNum];
|
|
|
|
// when rendering, raise NODE_GROUND off the floor slighty as they seem to clip too much
|
|
if ( srcType == NODE_GROUND)
|
|
{
|
|
srcPos.z += 1.0;
|
|
}
|
|
|
|
if ( desType == NODE_GROUND)
|
|
{
|
|
desPos.z += 1.0;
|
|
}
|
|
|
|
// Draw in red if stale link
|
|
if (linkInfo & bits_LINK_STALE_SUGGESTED)
|
|
{
|
|
NDebugOverlay::Line(srcPos, desPos, 255,0,0, false, flDrawDuration);
|
|
}
|
|
// Draw in grey if link turned off
|
|
else if (linkInfo & bits_LINK_OFF)
|
|
{
|
|
NDebugOverlay::Line(srcPos, desPos, 100,100,100, false, flDrawDuration);
|
|
}
|
|
else if ((m_debugNetOverlays & bits_debugOverlayFlyConnections) && (moveTypes & bits_CAP_MOVE_FLY))
|
|
{
|
|
NDebugOverlay::Line(srcPos, desPos, 100,255,255, false, flDrawDuration);
|
|
}
|
|
else if (moveTypes & bits_CAP_MOVE_CLIMB)
|
|
{
|
|
NDebugOverlay::Line(srcPos, desPos, 255,0,255, false, flDrawDuration);
|
|
}
|
|
else if (moveTypes & bits_CAP_MOVE_GROUND)
|
|
{
|
|
NDebugOverlay::Line(srcPos, desPos, 0,255,50, false, flDrawDuration);
|
|
}
|
|
else if ((m_debugNetOverlays & bits_debugOverlayJumpConnections) && (moveTypes & bits_CAP_MOVE_JUMP) )
|
|
{
|
|
NDebugOverlay::Line(srcPos, desPos, 0,0,255, false, flDrawDuration);
|
|
}
|
|
else
|
|
{ // Dark red if this hull can't use
|
|
bool isFly = ( srcType == NODE_AIR || desType == NODE_AIR );
|
|
bool isJump = true;
|
|
for ( int i = HULL_HUMAN; i < NUM_HULLS; i++ )
|
|
{
|
|
if ( pAINode[node]->GetLinkByIndex(link)->m_iAcceptedMoveTypes[i] & ~bits_CAP_MOVE_JUMP )
|
|
{
|
|
isJump = false;
|
|
break;
|
|
}
|
|
}
|
|
if ( ( isFly && (m_debugNetOverlays & bits_debugOverlayFlyConnections) ) ||
|
|
( isJump && (m_debugNetOverlays & bits_debugOverlayJumpConnections) ) ||
|
|
( !isFly && !isJump ) )
|
|
{
|
|
NDebugOverlay::Line(srcPos, desPos, 100,25,25, false, flDrawDuration);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (m_debugNetOverlays & bits_debugOverlayNodes)
|
|
{
|
|
int r = 255;
|
|
int g = 0;
|
|
int b = 0;
|
|
|
|
// If checking visibility base color off of visibility info
|
|
if (m_debugNetOverlays & bits_debugOverlayVisibility &&
|
|
m_iVisibilityNode != NO_NODE)
|
|
{
|
|
// ---------------------------------------------------
|
|
// If network needs rebuilding do so before display
|
|
// --------------------------------------------------
|
|
if (m_debugNetOverlays & bits_debugNeedRebuild)
|
|
{
|
|
m_pManager->RebuildNetworkGraph();
|
|
}
|
|
}
|
|
|
|
// If checking graph connectivity base color off of connectivity info
|
|
if (m_debugNetOverlays & bits_debugOverlayGraphConnect &&
|
|
m_iGConnectivityNode != NO_NODE)
|
|
{
|
|
// ---------------------------------------------------
|
|
// If network needs rebuilding do so before display
|
|
// --------------------------------------------------
|
|
if (m_debugNetOverlays & bits_debugNeedRebuild)
|
|
{
|
|
m_pManager->RebuildNetworkGraph();
|
|
}
|
|
else if (m_pNetwork->IsConnected( m_iGConnectivityNode, node) )
|
|
{
|
|
r = 0;
|
|
g = 0;
|
|
b = 255;
|
|
}
|
|
}
|
|
// Otherwise base color off of node type
|
|
else
|
|
{
|
|
// If node is new and hasn't been rebuild yet
|
|
if (pAINode[node]->m_eNodeInfo & bits_NODE_WC_CHANGED)
|
|
{
|
|
r = 200;
|
|
g = 200;
|
|
b = 200;
|
|
}
|
|
|
|
// If node doesn't fit the current hull size
|
|
else if (pAINode[node]->m_eNodeInfo & bits_NODE_WONT_FIT_HULL)
|
|
{
|
|
r = 255;
|
|
g = 25;
|
|
b = 25;
|
|
}
|
|
|
|
else if (pAINode[node]->GetType() == NODE_CLIMB)
|
|
{
|
|
r = 255;
|
|
g = 0;
|
|
b = 255;
|
|
}
|
|
else if (pAINode[node]->GetType() == NODE_AIR)
|
|
{
|
|
r = 0;
|
|
g = 255;
|
|
b = 255;
|
|
}
|
|
else if (pAINode[node]->GetType() == NODE_GROUND)
|
|
{
|
|
r = 0;
|
|
g = 255;
|
|
b = 100;
|
|
}
|
|
}
|
|
|
|
|
|
Vector nodePos;
|
|
|
|
nodePos = pAINode[node]->GetPosition(m_iHullDrawNum);
|
|
|
|
NDebugOverlay::Box(nodePos, Vector(-5,-5,-5), Vector(5,5,5), r,g,b,0,flDrawDuration);
|
|
|
|
// If climb node draw line in facing direction
|
|
if (pAINode[node]->GetType() == NODE_CLIMB)
|
|
{
|
|
Vector offsetDir = 12.0 * Vector(cos(DEG2RAD(pAINode[node]->GetYaw())),sin(DEG2RAD(pAINode[node]->GetYaw())),flDrawDuration);
|
|
NDebugOverlay::Line(nodePos, nodePos+offsetDir, r,g,b,false,flDrawDuration);
|
|
}
|
|
|
|
if ( pAINode[node]->GetHint() )
|
|
{
|
|
NDebugOverlay::Box( nodePos, Vector(-7,-7,-7), Vector(7,7,7), 255,255,0,0,flDrawDuration);
|
|
}
|
|
|
|
if (m_debugNetOverlays & bits_debugOverlayNodesLev2)
|
|
{
|
|
CFmtStr msg;
|
|
|
|
if ( m_pNodeIndexTable )
|
|
msg.sprintf("%i (wc:%i; z:%i)",node,m_pNodeIndexTable[pAINode[node]->GetId()], pAINode[node]->GetZone());
|
|
else
|
|
msg.sprintf("%i (z:%i)",node,pAINode[node]->GetZone());
|
|
|
|
Vector loc = nodePos;
|
|
loc.x+=6;
|
|
loc.y+=6;
|
|
loc.z+=6;
|
|
NDebugOverlay::Text( loc, msg, true, flDrawDuration);
|
|
|
|
// Print the hintgroup if we have one
|
|
if ( pAINode[node]->GetHint() )
|
|
{
|
|
msg.sprintf("%s", STRING( pAINode[node]->GetHint()->GetGroup() ));
|
|
loc.z-=3;
|
|
NDebugOverlay::Text( loc, msg, true, flDrawDuration);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------
|
|
// Identify hull being displayed
|
|
// -------------------------------
|
|
if (m_debugNetOverlays & (bits_debugOverlayNodes | bits_debugOverlayConnections | bits_debugOverlayHulls))
|
|
{
|
|
DrawEditInfoOverlay();
|
|
}
|
|
|
|
// ----------------------------
|
|
// Increment node draw chunk
|
|
// ----------------------------
|
|
startDrawNode = endDrawNode;
|
|
if (startDrawNode >= m_pNetwork->NumNodes())
|
|
{
|
|
startDrawNode = 0;
|
|
}
|
|
|
|
// ----------------------------
|
|
// Output performance stats
|
|
// ----------------------------
|
|
#ifdef AI_PERF_MON
|
|
if (m_fNextPerfStatTime < gpGlobals->curtime)
|
|
{
|
|
char temp[512];
|
|
Q_snprintf(temp,sizeof(temp),"%3.2f NN/m\n%3.2f P/m\n",(m_nPerfStatNN/1.0),(m_nPerfStatPB/1.0));
|
|
UTIL_CenterPrintAll(temp);
|
|
|
|
m_fNextPerfStatTime = gpGlobals->curtime + 1;
|
|
m_nPerfStatNN = 0;
|
|
m_nPerfStatPB = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Constructor
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CAI_NetworkEditTools::CAI_NetworkEditTools(CAI_NetworkManager *pNetworkManager)
|
|
{
|
|
// ----------------------------------------------------------------------------
|
|
// If in wc_edit mode
|
|
// ----------------------------------------------------------------------------
|
|
if (engine->IsInEditMode())
|
|
{
|
|
// ----------------------------------------------------------------------------
|
|
// Allocate extra space for storing undropped node positions
|
|
// ----------------------------------------------------------------------------
|
|
m_pWCPosition = new Vector[MAX_NODES];
|
|
}
|
|
else
|
|
{
|
|
m_pWCPosition = NULL;
|
|
}
|
|
|
|
m_pNodeIndexTable = NULL;
|
|
m_debugNetOverlays = 0;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Allocate table of WC Id's. If not in edit mode Deleted after initialization
|
|
// ----------------------------------------------------------------------------
|
|
m_pNodeIndexTable = new int[MAX_NODES];
|
|
for ( int i = 0; i < MAX_NODES; i++ )
|
|
m_pNodeIndexTable[i] = NO_NODE;
|
|
m_nNextWCIndex = 0;
|
|
|
|
m_pNetwork = pNetworkManager->GetNetwork(); // @tbd
|
|
m_pManager = pNetworkManager;
|
|
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Destructor
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CAI_NetworkEditTools::~CAI_NetworkEditTools()
|
|
{
|
|
// --------------------------------------------------------
|
|
// If in edit mode tell WC I'm ending my session
|
|
// --------------------------------------------------------
|
|
#ifdef _WIN32
|
|
Editor_EndSession(false);
|
|
#endif
|
|
delete[] m_pNodeIndexTable;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CAI_NetworkBuilder
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkBuilder::FloodFillZone( CAI_Node **ppNodes, CAI_Node *pNode, int zone )
|
|
{
|
|
pNode->SetZone( zone );
|
|
|
|
for (int link = 0; link < pNode->NumLinks(); link++)
|
|
{
|
|
CAI_Link *pLink = pNode->GetLinkByIndex(link);
|
|
CAI_Node *pLinkedNode = ( pLink->m_iDestID == pNode->GetId()) ? ppNodes[pLink->m_iSrcID] : ppNodes[pLink->m_iDestID];
|
|
if ( pLinkedNode->GetZone() == AI_NODE_ZONE_UNKNOWN )
|
|
FloodFillZone( ppNodes, pLinkedNode, zone );
|
|
|
|
Assert( pLinkedNode->GetZone() == pNode->GetZone() && pNode->GetZone() == zone );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkBuilder::InitZones( CAI_Network *pNetwork )
|
|
{
|
|
int nNodes = pNetwork->NumNodes();
|
|
CAI_Node **ppNodes = pNetwork->AccessNodes();
|
|
|
|
if ( !nNodes )
|
|
return;
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
ppNodes[i]->SetZone( AI_NODE_ZONE_UNKNOWN );
|
|
}
|
|
|
|
// Mark solo nodes
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
if ( ppNodes[i]->NumLinks() == 0 )
|
|
ppNodes[i]->SetZone( AI_NODE_ZONE_SOLO );
|
|
}
|
|
|
|
int curZone = AI_NODE_FIRST_ZONE;
|
|
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
if ( ppNodes[i]->GetZone() == AI_NODE_ZONE_UNKNOWN )
|
|
{
|
|
FloodFillZone( (CAI_Node **)ppNodes, ppNodes[i], curZone );
|
|
curZone++;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
Assert( ppNodes[i]->GetZone() != AI_NODE_ZONE_UNKNOWN );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Used for WC edit move to rebuild the network around the given
|
|
// location. Rebuilding the entire network takes too long
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkBuilder::Rebuild( CAI_Network *pNetwork )
|
|
{
|
|
int nNodes = pNetwork->NumNodes();
|
|
CAI_Node **ppNodes = pNetwork->AccessNodes();
|
|
|
|
if ( !nNodes )
|
|
return;
|
|
|
|
BeginBuild();
|
|
|
|
// ------------------------------------------------------------
|
|
// First mark all nodes around vecPos as having to be rebuilt
|
|
// ------------------------------------------------------------
|
|
int i;
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
// --------------------------------------------------------------------
|
|
// If changed, mark all nodes that are within the max link distance to
|
|
// the changed node as having to be rebuild
|
|
// --------------------------------------------------------------------
|
|
if (ppNodes[i]->m_eNodeInfo & bits_NODE_WC_CHANGED)
|
|
{
|
|
Vector vRebuildPos = ppNodes[i]->GetOrigin();
|
|
ppNodes[i]->SetNeedsRebuild();
|
|
ppNodes[i]->SetZone( AI_NODE_ZONE_UNIVERSAL );
|
|
for (int node = 0; node < nNodes; node++)
|
|
{
|
|
if ( ppNodes[node]->GetType() == NODE_AIR )
|
|
{
|
|
if ((ppNodes[node]->GetOrigin() - vRebuildPos).LengthSqr() < MAX_AIR_NODE_LINK_DIST_SQ)
|
|
{
|
|
ppNodes[node]->SetNeedsRebuild();
|
|
ppNodes[node]->SetZone( AI_NODE_ZONE_UNIVERSAL );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((ppNodes[node]->GetOrigin() - vRebuildPos).LengthSqr() < MAX_NODE_LINK_DIST_SQ)
|
|
{
|
|
ppNodes[node]->SetNeedsRebuild();
|
|
ppNodes[node]->SetZone( AI_NODE_ZONE_UNIVERSAL );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------
|
|
// Initialize node positions
|
|
// ---------------------------
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
if (ppNodes[i]->NeedsRebuild())
|
|
{
|
|
InitNodePosition( pNetwork, ppNodes[i] );
|
|
}
|
|
}
|
|
nNodes = pNetwork->NumNodes(); // InitNodePosition can create nodes
|
|
|
|
// ---------------------------
|
|
// Initialize node neighbors
|
|
// ---------------------------
|
|
m_DidSetNeighborsTable.Resize( nNodes );
|
|
m_DidSetNeighborsTable.ClearAll();
|
|
m_NeighborsTable.SetSize( nNodes );
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
m_NeighborsTable[i].Resize( nNodes );
|
|
}
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
// If near point of change recalculate
|
|
if (ppNodes[i]->NeedsRebuild())
|
|
{
|
|
InitNeighbors( pNetwork, ppNodes[i] );
|
|
}
|
|
}
|
|
|
|
// ---------------------------
|
|
// Force node neighbors for dynamic links
|
|
// ---------------------------
|
|
ForceDynamicLinkNeighbors();
|
|
|
|
// ---------------------------
|
|
// Initialize accepted hulls
|
|
// ---------------------------
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
if (ppNodes[i]->NeedsRebuild())
|
|
{
|
|
ppNodes[i]->ClearLinks();
|
|
}
|
|
}
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
if (ppNodes[i]->NeedsRebuild())
|
|
{
|
|
InitLinks( pNetwork, ppNodes[i] );
|
|
}
|
|
}
|
|
|
|
g_pAINetworkManager->FixupHints();
|
|
|
|
EndBuild();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkBuilder::BeginBuild()
|
|
{
|
|
m_pTestHull = CAI_TestHull::GetTestHull();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkBuilder::EndBuild()
|
|
{
|
|
m_NeighborsTable.SetSize(0);
|
|
m_DidSetNeighborsTable.Resize(0);
|
|
CAI_TestHull::ReturnTestHull();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Only called if network has changed since last time level
|
|
// was loaded
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
void CAI_NetworkBuilder::Build( CAI_Network *pNetwork )
|
|
{
|
|
int nNodes = pNetwork->NumNodes();
|
|
CAI_Node **ppNodes = pNetwork->AccessNodes();
|
|
|
|
if ( !nNodes )
|
|
return;
|
|
|
|
CAI_NetworkBuildHelper *pHelper = (CAI_NetworkBuildHelper *)CreateEntityByName( "ai_network_build_helper" );
|
|
|
|
VPROF( "AINet" );
|
|
|
|
BeginBuild();
|
|
|
|
CFastTimer masterTimer;
|
|
CFastTimer timer;
|
|
|
|
DevMsg( "Building AI node graph...\n");
|
|
masterTimer.Start();
|
|
|
|
// ---------------------------
|
|
// Initialize node positions
|
|
// ---------------------------
|
|
DevMsg( "Initializing node positions...\n" );
|
|
timer.Start();
|
|
int i;
|
|
for ( i = 0; i < nNodes; i++)
|
|
{
|
|
InitNodePosition( pNetwork, ppNodes[i] );
|
|
if ( pHelper )
|
|
pHelper->PostInitNodePosition( pNetwork, ppNodes[i] );
|
|
}
|
|
nNodes = pNetwork->NumNodes(); // InitNodePosition can create nodes
|
|
timer.End();
|
|
DevMsg( "...done initializing node positions. %f seconds\n", timer.GetDuration().GetSeconds() );
|
|
|
|
// ---------------------------
|
|
// Initialize node neighbors
|
|
// ---------------------------
|
|
DevMsg( "Initializing node neighbors...\n" );
|
|
timer.Start();
|
|
m_DidSetNeighborsTable.Resize( nNodes );
|
|
m_DidSetNeighborsTable.ClearAll();
|
|
m_NeighborsTable.SetSize( nNodes );
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
m_NeighborsTable[i].Resize( nNodes );
|
|
m_NeighborsTable[i].ClearAll();
|
|
}
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
InitNeighbors( pNetwork, ppNodes[i] );
|
|
}
|
|
timer.End();
|
|
DevMsg( "...done initializing node neighbors. %f seconds\n", timer.GetDuration().GetSeconds() );
|
|
|
|
// ---------------------------
|
|
// Force node neighbors for dynamic links
|
|
// ---------------------------
|
|
DevMsg( "Forcing dynamic link neighbors...\n" );
|
|
timer.Start();
|
|
ForceDynamicLinkNeighbors();
|
|
timer.End();
|
|
DevMsg( "...done forcing dynamic link neighbors. %f seconds\n", timer.GetDuration().GetSeconds() );
|
|
|
|
// ---------------------------
|
|
// Initialize accepted hulls
|
|
// ---------------------------
|
|
DevMsg( "Determining links...\n" );
|
|
timer.Start();
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
// Make sure all the links are clear
|
|
ppNodes[i]->ClearLinks();
|
|
}
|
|
for (i = 0; i < nNodes; i++)
|
|
{
|
|
InitLinks( pNetwork, ppNodes[i] );
|
|
}
|
|
timer.End();
|
|
DevMsg( "...done determining links. %f seconds\n", timer.GetDuration().GetSeconds() );
|
|
|
|
// ------------------------------
|
|
// Initialize disconnected nodes
|
|
// ------------------------------
|
|
DevMsg( "Determining zones...\n" );
|
|
timer.Start();
|
|
InitZones( pNetwork);
|
|
timer.End();
|
|
masterTimer.End();
|
|
DevMsg( "...done determining zones. %f seconds\n", timer.GetDuration().GetSeconds() );
|
|
DevMsg( "...done building AI node graph, %f seconds\n", masterTimer.GetDuration().GetSeconds() );
|
|
|
|
g_pAINetworkManager->FixupHints();
|
|
|
|
EndBuild();
|
|
|
|
if ( pHelper )
|
|
UTIL_Remove( pHelper );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Forces testing of a connection between src and dest IDs for all dynamic links
|
|
//
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void CAI_NetworkBuilder::ForceDynamicLinkNeighbors(void)
|
|
{
|
|
if (!g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable)
|
|
{
|
|
DevMsg("ERROR: Trying initialize links with no WC ID table!\n");
|
|
return;
|
|
}
|
|
|
|
CAI_DynamicLink* pDynamicLink = CAI_DynamicLink::m_pAllDynamicLinks;
|
|
|
|
while (pDynamicLink)
|
|
{
|
|
// -------------------------------------------------------------
|
|
// First convert this links WC IDs to engine IDs
|
|
// -------------------------------------------------------------
|
|
int nSrcID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( pDynamicLink->m_nSrcEditID );
|
|
if (nSrcID == -1)
|
|
{
|
|
DevMsg("ERROR: Dynamic link source WC node %d not found\n", pDynamicLink->m_nSrcEditID );
|
|
}
|
|
|
|
int nDestID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( pDynamicLink->m_nDestEditID );
|
|
if (nDestID == -1)
|
|
{
|
|
DevMsg("ERROR: Dynamic link dest WC node %d not found\n", pDynamicLink->m_nDestEditID );
|
|
}
|
|
|
|
if ( nSrcID != -1 && nDestID != -1 )
|
|
{
|
|
if ( nSrcID < g_pBigAINet->NumNodes() && nDestID < g_pBigAINet->NumNodes() )
|
|
{
|
|
CAI_Node *pSrcNode = g_pBigAINet->GetNode( nSrcID );
|
|
CAI_Node *pDestNode = g_pBigAINet->GetNode( nDestID );
|
|
|
|
// -------------------------------------------------------------
|
|
// Force visibility and neighbor-ness between the nodes
|
|
// -------------------------------------------------------------
|
|
Assert( pSrcNode );
|
|
Assert( pDestNode );
|
|
|
|
m_NeighborsTable[pSrcNode->GetId()].Set(pDestNode->GetId());
|
|
m_NeighborsTable[pDestNode->GetId()].Set(pSrcNode->GetId());
|
|
}
|
|
}
|
|
|
|
// Go on to the next dynamic link
|
|
pDynamicLink = pDynamicLink->m_pNextDynamicLink;
|
|
}
|
|
}
|
|
|
|
CAI_NetworkBuilder g_AINetworkBuilder;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Initializes position of climb node in the world. Climb nodes are
|
|
// set to be just above the floor or at the same level at the
|
|
// dismount point for the node
|
|
//
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_NetworkBuilder::InitClimbNodePosition(CAI_Network *pNetwork, CAI_Node *pNode)
|
|
{
|
|
AI_PROFILE_SCOPE( CAI_Node_InitClimbNodePosition );
|
|
|
|
// If this is a node for mounting/dismounting the climb skip it
|
|
if ( pNode->m_eNodeInfo & (bits_NODE_CLIMB_OFF_FORWARD | bits_NODE_CLIMB_OFF_LEFT | bits_NODE_CLIMB_OFF_RIGHT) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Figure out which directions I can dismount from the climb node
|
|
|
|
//float hullLength = NAI_Hull::Length(HULL_SMALL);
|
|
//Vector offsetDir = Vector(cos(DEG2RAD(m_flYaw)),sin(DEG2RAD(m_flYaw)),0);
|
|
|
|
// ----------------
|
|
// Check position
|
|
// ----------------
|
|
trace_t trace;
|
|
Vector posOnLadder = pNode->GetPosition(HULL_SMALL_CENTERED);
|
|
AI_TraceHull( posOnLadder, posOnLadder + Vector( 0, 0, -37 ),
|
|
NAI_Hull::Mins(HULL_SMALL_CENTERED), NAI_Hull::Maxs(HULL_SMALL_CENTERED),
|
|
MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace );
|
|
|
|
// --------------------------------------------------------------------
|
|
// If climb node is right above the floor, we don't need any dismount
|
|
// nodes. Accept this dropped position and note that this climb node
|
|
// is at the bottom
|
|
// --------------------------------------------------------------------
|
|
if (!trace.startsolid && trace.fraction != 1)
|
|
{
|
|
pNode->m_eNodeInfo = bits_NODE_CLIMB_BOTTOM;
|
|
InitGroundNodePosition( pNetwork, pNode );
|
|
return;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// If network was already loaded this means we are in wc edit mode
|
|
// so we shouldn't recreate the added climb nodes
|
|
// ---------------------------------------------------------------------
|
|
if (g_pAINetworkManager->NetworksLoaded())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Otherwise we need to create climb nodes for dismounting the climb
|
|
// and place the height of the climb node at the dismount position
|
|
// ---------------------------------------------------------------------
|
|
int checkNodeTypes[3] = { bits_NODE_CLIMB_OFF_FORWARD, bits_NODE_CLIMB_OFF_LEFT, bits_NODE_CLIMB_OFF_RIGHT };
|
|
|
|
int numExits = 0;
|
|
|
|
// DevMsg( "testing %f %f %f\n", GetOrigin().x, GetOrigin().y, GetOrigin().z );
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
pNode->m_eNodeInfo = checkNodeTypes[i];
|
|
|
|
Vector origin = pNode->GetPosition(HULL_SMALL_CENTERED);
|
|
|
|
// DevMsg( "testing %f %f %f\n", origin.x, origin.y, origin.z );
|
|
// ----------------
|
|
// Check outward
|
|
// ----------------
|
|
AI_TraceLine ( posOnLadder,
|
|
origin,
|
|
MASK_NPCSOLID_BRUSHONLY,
|
|
NULL,
|
|
COLLISION_GROUP_NONE,
|
|
&trace );
|
|
|
|
// DevMsg( "to %f %f %f : %d %f", origin.x, origin.y, origin.z, trace.startsolid, trace.fraction );
|
|
|
|
if (!trace.startsolid && trace.fraction == 1.0)
|
|
{
|
|
float floorZ = GetFloorZ(origin); // FIXME: don't use this
|
|
|
|
if (abs(pNode->GetOrigin().z - floorZ) < 36)
|
|
{
|
|
CAI_Node *new_node = pNetwork->AddNode( pNode->GetOrigin(), pNode->m_flYaw );
|
|
new_node->m_pHint = NULL;
|
|
new_node->m_eNodeType = NODE_CLIMB;
|
|
new_node->m_eNodeInfo = pNode->m_eNodeInfo;
|
|
InitGroundNodePosition( pNetwork, new_node );
|
|
|
|
// copy over the offsets for the first CLIMB_OFF node
|
|
// FIXME: this method is broken for when the CLIMB_OFF nodes are at different heights
|
|
if (numExits == 0)
|
|
{
|
|
for (int hull = 0; hull < NUM_HULLS; hull++)
|
|
{
|
|
pNode->m_flVOffset[hull] = new_node->m_flVOffset[hull];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int hull = 0; hull < NUM_HULLS; hull++)
|
|
{
|
|
if (fabs(pNode->m_flVOffset[hull] - new_node->m_flVOffset[hull]) > 1)
|
|
{
|
|
DevMsg(2, "Warning: Climb Node %i has different exit heights for hull %s\n", pNode->m_iID, NAI_Hull::Name(hull));
|
|
}
|
|
}
|
|
}
|
|
|
|
numExits++;
|
|
}
|
|
}
|
|
// DevMsg( "\n");
|
|
}
|
|
|
|
if (numExits == 0)
|
|
{
|
|
DevMsg("ERROR: Climb Node %i has no way off\n",pNode->m_iID);
|
|
}
|
|
|
|
// this is a node that can't get gotten to directly
|
|
pNode->m_eNodeInfo = bits_NODE_CLIMB_ON;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Initializes position of the node sitting on the ground.
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_NetworkBuilder::InitGroundNodePosition(CAI_Network *pNetwork, CAI_Node *pNode)
|
|
{
|
|
AI_PROFILE_SCOPE( CAI_Node_InitGroundNodePosition );
|
|
|
|
if ( pNode->m_eNodeInfo & bits_DONT_DROP )
|
|
return;
|
|
|
|
// find actual floor for each hull type
|
|
for (int hull = 0; hull < NUM_HULLS; hull++)
|
|
{
|
|
trace_t tr;
|
|
Vector origin = pNode->GetOrigin();
|
|
Vector mins, maxs;
|
|
|
|
// turn hull into pancake to avoid problems with ceiling
|
|
mins = NAI_Hull::Mins(hull);
|
|
maxs = NAI_Hull::Maxs(hull);
|
|
maxs.z = mins.z;
|
|
|
|
// Add an epsilon for cast
|
|
origin.z += 0.1;
|
|
|
|
// shift up so bottom of box is at center of node
|
|
origin.z -= mins.z;
|
|
|
|
AI_TraceHull( origin, origin + Vector( 0, 0, -384 ), mins, maxs, MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
|
|
|
|
if ( !tr.startsolid )
|
|
pNode->m_flVOffset[hull] = tr.endpos.z - pNode->GetOrigin().z + 0.1;
|
|
else
|
|
pNode->m_flVOffset[hull] = -mins.z + 0.1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Initializes position of the node in the world. Only called if
|
|
// the network was never initialized
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_NetworkBuilder::InitNodePosition(CAI_Network *pNetwork, CAI_Node *pNode)
|
|
{
|
|
AI_PROFILE_SCOPE( CAI_Node_InitNodePosition );
|
|
|
|
if (pNode->m_eNodeType == NODE_AIR)
|
|
{
|
|
return;
|
|
}
|
|
else if (pNode->m_eNodeType == NODE_CLIMB)
|
|
{
|
|
InitClimbNodePosition(pNetwork, pNode);
|
|
return;
|
|
}
|
|
|
|
// Otherwise mark as a land node and drop to the floor
|
|
|
|
else if (pNode->m_eNodeType == NODE_GROUND)
|
|
{
|
|
InitGroundNodePosition( pNetwork, pNode );
|
|
|
|
if (pNode->m_flVOffset[HULL_SMALL_CENTERED] < -100)
|
|
{
|
|
Assert( pNetwork == g_pBigAINet );
|
|
DevWarning("ERROR: Node %.0f %.0f %.0f, WC ID# %i, is either too low (fell through floor) or too high (>100 units above floor)\n",
|
|
pNode->GetOrigin().x, pNode->GetOrigin().y, pNode->GetOrigin().z,
|
|
g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pNode->m_iID]);
|
|
|
|
pNode->m_eNodeInfo |= bits_NODE_FALLEN;
|
|
}
|
|
return;
|
|
}
|
|
/* // If under water, not that the node is in water <<TODO>> when we get water
|
|
else if ( UTIL_PointContents(GetOrigin()) & MASK_WATER )
|
|
{
|
|
m_eNodeType |= NODE_WATER;
|
|
}
|
|
*/
|
|
else if (pNode->m_eNodeType != NODE_DELETED)
|
|
{
|
|
DevMsg( "Bad node type!\n" );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Set the visibility for this node. (What nodes it can see with a
|
|
// line trace)
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
void CAI_NetworkBuilder::InitVisibility(CAI_Network *pNetwork, CAI_Node *pNode)
|
|
{
|
|
AI_PROFILE_SCOPE( CAI_Node_InitVisibility );
|
|
|
|
// If a deleted node bail
|
|
if (pNode->m_eNodeType == NODE_DELETED)
|
|
{
|
|
return;
|
|
}
|
|
// The actual position of some nodes may be inside geometry as they have
|
|
// hull specific position offsets (e.g. climb nodes). Get the hull specific
|
|
// position using the smallest hull to make sure were not in geometry
|
|
Vector srcPos = pNode->GetPosition(HULL_SMALL_CENTERED);
|
|
|
|
// Check the visibility on every other node in the network
|
|
for (int testnode = 0; testnode < pNetwork->NumNodes(); testnode++ )
|
|
{
|
|
CAI_Node *testNode = pNetwork->GetNode( testnode );
|
|
|
|
if ( DebuggingConnect( pNode->m_iID, testnode ) )
|
|
{
|
|
DevMsg( " " ); // break here..
|
|
}
|
|
|
|
// We know we can view ourself
|
|
if (pNode->m_iID == testnode)
|
|
{
|
|
m_NeighborsTable[pNode->m_iID].Set(testNode->m_iID);
|
|
continue;
|
|
}
|
|
|
|
// Remove duplicate nodes unless a climb node as they move
|
|
if (testNode->GetOrigin() == pNode->GetOrigin() && testNode->GetType() != NODE_CLIMB)
|
|
{
|
|
testNode->SetType( NODE_DELETED );
|
|
DevMsg( 2, "Probable duplicate node placed at %s\n", VecToString(testNode->GetOrigin()) );
|
|
continue;
|
|
}
|
|
|
|
// If a deleted node we don't care about it
|
|
if (testNode->GetType() == NODE_DELETED)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( m_DidSetNeighborsTable.IsBitSet( testNode->m_iID ) )
|
|
{
|
|
if ( m_NeighborsTable[testNode->m_iID].IsBitSet(pNode->m_iID))
|
|
m_NeighborsTable[pNode->m_iID].Set(testNode->m_iID);
|
|
|
|
continue;
|
|
}
|
|
|
|
float flDistToCheckNode = ( testNode->GetOrigin() - pNode->GetOrigin() ).LengthSqr();
|
|
|
|
if ( testNode->GetType() == NODE_AIR )
|
|
{
|
|
if (flDistToCheckNode > MAX_AIR_NODE_LINK_DIST_SQ)
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (flDistToCheckNode > MAX_NODE_LINK_DIST_SQ)
|
|
continue;
|
|
}
|
|
|
|
// The actual position of some nodes may be inside geometry as they have
|
|
// hull specific position offsets (e.g. climb nodes). Get the hull specific
|
|
// position using the smallest hull to make sure were not in geometry
|
|
Vector destPos = pNetwork->GetNode( testnode )->GetPosition(HULL_SMALL_CENTERED);
|
|
|
|
trace_t tr;
|
|
tr.m_pEnt = NULL;
|
|
|
|
// Try several line of sight checks
|
|
|
|
bool isVisible = false;
|
|
|
|
// ------------------
|
|
// Bottom to bottom
|
|
// ------------------
|
|
AI_TraceLine ( srcPos, destPos,MASK_NPCWORLDSTATIC,NULL,COLLISION_GROUP_NONE, &tr );
|
|
if (!tr.startsolid && tr.fraction == 1.0)
|
|
{
|
|
isVisible = true;
|
|
}
|
|
|
|
// ------------------
|
|
// Top to top
|
|
// ------------------
|
|
if (!isVisible)
|
|
{
|
|
AI_TraceLine ( srcPos + Vector( 0, 0, 70 ),destPos + Vector( 0, 0, 70 ),MASK_NPCWORLDSTATIC,NULL,COLLISION_GROUP_NONE, &tr );
|
|
if (!tr.startsolid && tr.fraction == 1.0)
|
|
{
|
|
isVisible = true;
|
|
}
|
|
}
|
|
|
|
// ------------------
|
|
// Top to Bottom
|
|
// ------------------
|
|
if (!isVisible)
|
|
{
|
|
AI_TraceLine ( srcPos + Vector( 0, 0, 70 ),destPos,MASK_NPCWORLDSTATIC,NULL,COLLISION_GROUP_NONE, &tr );
|
|
if (!tr.startsolid && tr.fraction == 1.0)
|
|
{
|
|
isVisible = true;
|
|
}
|
|
}
|
|
|
|
// ------------------
|
|
// Bottom to Top
|
|
// ------------------
|
|
if (!isVisible)
|
|
{
|
|
AI_TraceLine ( srcPos,destPos + Vector( 0, 0, 70 ),MASK_NPCWORLDSTATIC,NULL,COLLISION_GROUP_NONE, &tr );
|
|
if (!tr.startsolid && tr.fraction == 1.0)
|
|
{
|
|
isVisible = true;
|
|
}
|
|
}
|
|
|
|
// ------------------
|
|
// Failure
|
|
// ------------------
|
|
if (!isVisible)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* <<TODO>> may not apply with editable connections.......
|
|
|
|
// trace hit a brush ent, trace backwards to make sure that this ent is the only thing in the way.
|
|
if ( tr.fraction != 1.0 )
|
|
{
|
|
pTraceEnt = tr.u.ent;// store the ent that the trace hit, for comparison
|
|
|
|
AI_TraceLine ( srcPos,
|
|
destPos,
|
|
MASK_NPCSOLID_BRUSHONLY,
|
|
NULL,
|
|
&tr );
|
|
|
|
|
|
// there is a solid_bsp ent in the way of these two nodes, so we must record several things about in order to keep
|
|
// track of it in the pathfinding code, as well as through save and restore of the node graph. ANY data that is manipulated
|
|
// as part of the process of adding a LINKENT to a connection here must also be done in CGraph::SetGraphPointers, where reloaded
|
|
// graphs are prepared for use.
|
|
if ( tr.u.ent == pTraceEnt && !FClassnameIs( tr.u.ent, "worldspawn" ) )
|
|
{
|
|
// get a pointer
|
|
pLinkPool [ cTotalLinks ].m_pLinkEnt = tr.u.ent;
|
|
|
|
// record the modelname, so that we can save/load node trees
|
|
memcpy( pLinkPool [ cTotalLinks ].m_szLinkEntModelname, STRING( tr.u.ent->model ), 4 );
|
|
|
|
// set the flag for this ent that indicates that it is attached to the world graph
|
|
// if this ent is removed from the world, it must also be removed from the connections
|
|
// that it formerly blocked.
|
|
CBaseEntity *e = CBaseEntity::Instance( tr.u.ent );
|
|
if ( e )
|
|
{
|
|
if ( !(e->GetFlags() & FL_GRAPHED ) )
|
|
{
|
|
e->AddFlag( FL_GRAPHED );
|
|
}
|
|
}
|
|
}
|
|
// even if the ent wasn't there, these nodes couldn't be connected. Skip.
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
*/
|
|
m_NeighborsTable[pNode->m_iID].Set(testNode->m_iID);
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Initializes the neighbors list
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_NetworkBuilder::InitNeighbors(CAI_Network *pNetwork, CAI_Node *pNode)
|
|
{
|
|
m_NeighborsTable[pNode->m_iID].ClearAll();
|
|
|
|
// Begin by establishing viewability to limit the number of nodes tested
|
|
InitVisibility( pNetwork, pNode );
|
|
|
|
AI_PROFILE_SCOPE_BEGIN( CAI_Node_InitNeighbors );
|
|
|
|
// Now check each neighbor against all other neighbors to see if one of
|
|
// them is a redundant connection
|
|
for (int checknode = 0; checknode < pNetwork->NumNodes(); checknode++ )
|
|
{
|
|
if ( DebuggingConnect( pNode->m_iID, checknode ) )
|
|
{
|
|
DevMsg( " " ); // break here..
|
|
}
|
|
|
|
// I'm not a neighbor of myself
|
|
if ( pNode->m_iID == checknode )
|
|
{
|
|
m_NeighborsTable[pNode->m_iID].Clear(checknode);
|
|
continue;
|
|
}
|
|
|
|
// Only check if already on the neightbor list
|
|
if (!m_NeighborsTable[pNode->m_iID].IsBitSet(checknode))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CAI_Node *pCheckNode = pNetwork->GetNode(checknode);
|
|
|
|
for (int testnode = 0; testnode < pNetwork->NumNodes(); testnode++ )
|
|
{
|
|
// don't check against itself
|
|
if (( testnode == checknode ) || (testnode == pNode->m_iID))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Only check if already on the neightbor list
|
|
if (!m_NeighborsTable[pNode->m_iID].IsBitSet(testnode))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CAI_Node *pTestNode = pNetwork->GetNode(testnode);
|
|
|
|
// ----------------------------------------------------------
|
|
// Don't check air nodes against nodes of a different types
|
|
// ----------------------------------------------------------
|
|
if ((pCheckNode->GetType() == NODE_AIR && pTestNode->GetType() != NODE_AIR)||
|
|
(pCheckNode->GetType() != NODE_AIR && pTestNode->GetType() == NODE_AIR))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
// If climb node pairs, don't consider redundancy
|
|
// ----------------------------------------------------------
|
|
if (pNode->GetType() == NODE_CLIMB &&
|
|
(pCheckNode->GetType() == NODE_CLIMB || pTestNode->GetType() == NODE_CLIMB))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// ----------------------------------------------------------
|
|
// If a climb node mounting point is involved, don't consider redundancy
|
|
// ----------------------------------------------------------
|
|
if ( ( pCheckNode->GetOrigin() == pNode->GetOrigin() && pNode->GetType() == NODE_CLIMB && pCheckNode->GetType() == NODE_CLIMB ) ||
|
|
( pTestNode->GetOrigin() == pNode->GetOrigin() && pNode->GetType() == NODE_CLIMB && pTestNode->GetType() == NODE_CLIMB ) ||
|
|
( pTestNode->GetOrigin() == pCheckNode->GetOrigin() && pCheckNode->GetType() == NODE_CLIMB && pTestNode->GetType() == NODE_CLIMB ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// @HACKHACK (toml 02-25-04): Ignore redundancy if both nodes are air nodes with
|
|
// hint type "strider node". Really, really should do this in a clean manner
|
|
bool nodeIsStrider = ( pNode->GetHint() && pNode->GetHint()->HintType() == HINT_STRIDER_NODE );
|
|
bool other1IsStrider = ( pCheckNode->GetHint() && pCheckNode->GetHint()->HintType() == HINT_STRIDER_NODE );
|
|
bool other2IsStrider = ( pTestNode->GetHint() && pTestNode->GetHint()->HintType() == HINT_STRIDER_NODE );
|
|
if ( nodeIsStrider && other1IsStrider != other2IsStrider )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Vector vec2DirToCheckNode = pCheckNode->GetOrigin() - pNode->GetOrigin();
|
|
float flDistToCheckNode = VectorNormalize( vec2DirToCheckNode );
|
|
|
|
Vector vec2DirToTestNode = ( pTestNode->GetOrigin() - pNode->GetOrigin() );
|
|
float flDistToTestNode = VectorNormalize( vec2DirToTestNode );
|
|
|
|
float tolerance = 0.92388; // 45 degrees
|
|
|
|
if ( DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) >= tolerance )
|
|
{
|
|
if ( flDistToTestNode < flDistToCheckNode )
|
|
{
|
|
DebugConnectMsg( pNode->m_iID, checknode, " Revoking neighbor status to to closer redundant link %d\n", testnode );
|
|
m_NeighborsTable[pNode->m_iID].Clear(checknode);
|
|
}
|
|
else
|
|
{
|
|
DebugConnectMsg( pNode->m_iID, testnode, " Revoking neighbor status to to closer redundant link %d\n", checknode );
|
|
m_NeighborsTable[pNode->m_iID].Clear(testnode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AI_PROFILE_SCOPE_END();
|
|
|
|
m_DidSetNeighborsTable.Set(pNode->m_iID);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: For the current node, check its connection to all other nodes
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static bool IsInLineForClimb( const Vector &srcPos, const Vector &srcFacing, const Vector &destPos, const Vector &destFacing )
|
|
{
|
|
#ifdef DEBUG
|
|
Vector normSrcFacing( srcFacing ), normDestFacing( destFacing );
|
|
|
|
VectorNormalize( normSrcFacing );
|
|
VectorNormalize( normDestFacing );
|
|
|
|
Assert( VectorsAreEqual( srcFacing, normSrcFacing, 0.01 ) && VectorsAreEqual( destFacing, normDestFacing, 0.01 ) );
|
|
#endif
|
|
|
|
// If they are not facing the same way...
|
|
if ( 1 - srcFacing.Dot( destFacing ) > 0.01 )
|
|
return false;
|
|
|
|
// If they aren't in line along the facing...
|
|
if ( CalcDistanceToLine2D( destPos.AsVector2D(), srcPos.AsVector2D(), srcPos.AsVector2D() + srcFacing.AsVector2D() ) > 0.01 )
|
|
return false;
|
|
|
|
// Check that the angle between them is either staight up, or on at angle of ladder-stairs
|
|
Vector vecDelta = srcPos - destPos;
|
|
|
|
VectorNormalize( vecDelta );
|
|
|
|
float fabsCos = fabs( srcFacing.Dot( vecDelta ) );
|
|
|
|
const float CosAngLadderStairs = 0.4472; // rise 2 & run 1
|
|
|
|
if ( fabsCos > 0.05 && fabs( fabsCos - CosAngLadderStairs ) > 0.05 )
|
|
return false;
|
|
|
|
// *************************** --------------------------------
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
int CAI_NetworkBuilder::ComputeConnection( CAI_Node *pSrcNode, CAI_Node *pDestNode, Hull_t hull )
|
|
{
|
|
int srcId = pSrcNode->m_iID;
|
|
int destId = pDestNode->m_iID;
|
|
int result = 0;
|
|
trace_t tr;
|
|
|
|
// Set the size of the test hull
|
|
if ( m_pTestHull->GetHullType() != hull )
|
|
{
|
|
m_pTestHull->SetHullType( hull );
|
|
m_pTestHull->SetHullSizeNormal( true );
|
|
}
|
|
|
|
if ( !( m_pTestHull->GetFlags() & FL_ONGROUND ) )
|
|
{
|
|
DevWarning( 2, "OFFGROUND!\n" );
|
|
}
|
|
m_pTestHull->AddFlag( FL_ONGROUND );
|
|
|
|
// ==============================================================
|
|
// FIRST CHECK IF HULL CAN EVEN FIT AT THESE NODES
|
|
// ==============================================================
|
|
// @Note (toml 02-10-03): this should be optimized, caching the results of CanFitAtNode()
|
|
if ( !( pSrcNode->m_eNodeInfo & ( HullToBit( hull ) << NODE_ENT_FLAGS_SHIFT ) ) &&
|
|
!m_pTestHull->GetNavigator()->CanFitAtNode(srcId,MASK_NPCWORLDSTATIC) )
|
|
{
|
|
DebugConnectMsg( srcId, destId, " Cannot fit at node %d\n", srcId );
|
|
return 0;
|
|
}
|
|
|
|
if ( !( pDestNode->m_eNodeInfo & ( HullToBit( hull ) << NODE_ENT_FLAGS_SHIFT ) ) &&
|
|
!m_pTestHull->GetNavigator()->CanFitAtNode(destId,MASK_NPCWORLDSTATIC) )
|
|
{
|
|
DebugConnectMsg( srcId, destId, " Cannot fit at node %d\n", destId );
|
|
return 0;
|
|
}
|
|
|
|
// ==============================================================
|
|
// AIR NODES (FLYING)
|
|
// ==============================================================
|
|
if (pSrcNode->m_eNodeType == NODE_AIR || pDestNode->GetType() == NODE_AIR)
|
|
{
|
|
AI_PROFILE_SCOPE( CAI_Node_InitLinks_Air );
|
|
|
|
// Air nodes only connect to other air nodes and nothing else
|
|
if (pSrcNode->m_eNodeType == NODE_AIR && pDestNode->GetType() == NODE_AIR)
|
|
{
|
|
AI_TraceHull( pSrcNode->GetOrigin(), pDestNode->GetOrigin(), NAI_Hull::Mins(hull),NAI_Hull::Maxs(hull), MASK_NPCWORLDSTATIC, m_pTestHull, COLLISION_GROUP_NONE, &tr );
|
|
if (!tr.startsolid && tr.fraction == 1.0)
|
|
{
|
|
result |= bits_CAP_MOVE_FLY;
|
|
DebugConnectMsg( srcId, destId, " Connect by flying\n" );
|
|
}
|
|
}
|
|
}
|
|
// =============================================================================
|
|
// > CLIMBING
|
|
// =============================================================================
|
|
// If both are climb nodes just make sure they are above each other
|
|
// and there is room for the hull to pass between them
|
|
else if ((pSrcNode->m_eNodeType == NODE_CLIMB) && (pDestNode->GetType() == NODE_CLIMB))
|
|
{
|
|
AI_PROFILE_SCOPE( CAI_Node_InitLinks_Climb );
|
|
|
|
Vector srcPos = pSrcNode->GetPosition(hull);
|
|
Vector destPos = pDestNode->GetPosition(hull);
|
|
|
|
// If a code genereted climb dismount node the two origins will be the same
|
|
if (pSrcNode->GetOrigin() == pDestNode->GetOrigin())
|
|
{
|
|
AI_TraceHull( srcPos, destPos,
|
|
NAI_Hull::Mins(hull),NAI_Hull::Maxs(hull),
|
|
MASK_NPCWORLDSTATIC, m_pTestHull, COLLISION_GROUP_NONE, &tr );
|
|
if (!tr.startsolid && tr.fraction == 1.0)
|
|
{
|
|
result |= bits_CAP_MOVE_CLIMB;
|
|
DebugConnectMsg( srcId, destId, " Connect by climbing\n" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !IsInLineForClimb(srcPos, UTIL_YawToVector( pSrcNode->m_flYaw ), destPos, UTIL_YawToVector( pDestNode->m_flYaw ) ) )
|
|
{
|
|
Assert( !IsInLineForClimb(destPos, UTIL_YawToVector( pDestNode->m_flYaw ), srcPos, UTIL_YawToVector( pSrcNode->m_flYaw ) ) );
|
|
DebugConnectMsg( srcId, destId, " Not lined up for proper climbing\n" );
|
|
return 0;
|
|
}
|
|
|
|
AI_TraceHull( srcPos, destPos, NAI_Hull::Mins(hull),NAI_Hull::Maxs(hull), MASK_NPCWORLDSTATIC, m_pTestHull, COLLISION_GROUP_NONE, &tr );
|
|
if (!tr.startsolid && tr.fraction == 1.0)
|
|
{
|
|
result |= bits_CAP_MOVE_CLIMB;
|
|
DebugConnectMsg( srcId, destId, " Connect by climbing\n" );
|
|
}
|
|
}
|
|
}
|
|
// ====================================================
|
|
// > TWO LAND NODES
|
|
// =====================================================
|
|
else if ((pSrcNode->m_eNodeType == NODE_GROUND) || (pDestNode->GetType() == NODE_GROUND))
|
|
{
|
|
// BUG: this could use GroundMoveLimit, except there's no version of world but not brushes (doors open, etc).
|
|
|
|
// ====================================================
|
|
// > WALKING : walk the space between the nodes
|
|
// =====================================================
|
|
|
|
// in this loop we take tiny steps from the current node to the nodes that it links to, one at a time.
|
|
bool fStandFailed = false;
|
|
bool fWalkFailed = true;
|
|
|
|
AI_PROFILE_SCOPE_BEGIN( CAI_Node_InitLinks_Ground );
|
|
|
|
Vector srcPos = pSrcNode->GetPosition(hull);
|
|
Vector destPos = pDestNode->GetPosition(hull);
|
|
|
|
if (!m_pTestHull->GetMoveProbe()->CheckStandPosition( srcPos, MASK_NPCWORLDSTATIC))
|
|
{
|
|
DebugConnectMsg( srcId, destId, " Failed to stand at %d\n", srcId );
|
|
fStandFailed = true;
|
|
}
|
|
|
|
if (!m_pTestHull->GetMoveProbe()->CheckStandPosition( destPos, MASK_NPCWORLDSTATIC))
|
|
{
|
|
DebugConnectMsg( srcId, destId, " Failed to stand at %d\n", destId );
|
|
fStandFailed = true;
|
|
}
|
|
|
|
//if (hull == 0)
|
|
// DevMsg("from %.1f %.1f %.1f to %.1f %.1f %.1f\n", srcPos.x, srcPos.y, srcPos.z, destPos.x, destPos.y, destPos.z );
|
|
|
|
if ( !fStandFailed )
|
|
{
|
|
fWalkFailed = !m_pTestHull->GetMoveProbe()->TestGroundMove( srcPos, destPos, MASK_NPCWORLDSTATIC, AITGM_IGNORE_INITIAL_STAND_POS, NULL );
|
|
if ( fWalkFailed )
|
|
DebugConnectMsg( srcId, destId, " Failed to walk between nodes\n" );
|
|
}
|
|
|
|
// Add to our list of accepable hulls
|
|
if (!fWalkFailed && !fStandFailed)
|
|
{
|
|
result |= bits_CAP_MOVE_GROUND;
|
|
DebugConnectMsg( srcId, destId, " Nodes connect for ground movement\n" );
|
|
}
|
|
|
|
AI_PROFILE_SCOPE_END();
|
|
|
|
// =============================================================================
|
|
// > JUMPING : jump the space between the nodes, but only if walk failed
|
|
// =============================================================================
|
|
if (!fStandFailed && fWalkFailed && (pSrcNode->m_eNodeType == NODE_GROUND) && (pDestNode->GetType() == NODE_GROUND))
|
|
{
|
|
AI_PROFILE_SCOPE( CAI_Node_InitLinks_Jump );
|
|
|
|
Vector srcPos = pSrcNode->GetPosition(hull);
|
|
Vector destPos = pDestNode->GetPosition(hull);
|
|
|
|
// Jumps aren't bi-directional. We can jump down further than we can jump up so
|
|
// we have to test for either one
|
|
bool canDestJump = m_pTestHull->IsJumpLegal(srcPos, destPos, destPos);
|
|
bool canSrcJump = m_pTestHull->IsJumpLegal(destPos, srcPos, srcPos);
|
|
|
|
if (canDestJump || canSrcJump)
|
|
{
|
|
CAI_MoveProbe *pMoveProbe = m_pTestHull->GetMoveProbe();
|
|
|
|
bool fJumpLegal = false;
|
|
m_pTestHull->SetGravity(1.0);
|
|
|
|
AIMoveTrace_t moveTrace;
|
|
pMoveProbe->MoveLimit( NAV_JUMP, srcPos,destPos, MASK_NPCWORLDSTATIC, NULL, &moveTrace);
|
|
if (!IsMoveBlocked(moveTrace))
|
|
{
|
|
fJumpLegal = true;
|
|
}
|
|
pMoveProbe->MoveLimit( NAV_JUMP, destPos,srcPos, MASK_NPCWORLDSTATIC, NULL, &moveTrace);
|
|
if (!IsMoveBlocked(moveTrace))
|
|
{
|
|
fJumpLegal = true;
|
|
}
|
|
|
|
// Add to our list of accepable hulls
|
|
if (fJumpLegal)
|
|
{
|
|
result |= bits_CAP_MOVE_JUMP;
|
|
DebugConnectMsg( srcId, destId, " Nodes connect for jumping\n" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
void CAI_NetworkBuilder::InitLinks(CAI_Network *pNetwork, CAI_Node *pNode)
|
|
{
|
|
AI_PROFILE_SCOPE( CAI_Node_InitLinks );
|
|
|
|
// -----------------------------------------------------
|
|
// Get test hull
|
|
// -----------------------------------------------------
|
|
m_pTestHull->GetNavigator()->SetNetwork( pNetwork );
|
|
|
|
// -----------------------------------------------------
|
|
// Initialize links to every node
|
|
// -----------------------------------------------------
|
|
for (int i = 0; i < pNetwork->NumNodes(); i++ )
|
|
{
|
|
// -------------------------------------------------
|
|
// Check for redundant link building
|
|
// -------------------------------------------------
|
|
DebugConnectMsg( pNode->m_iID, i, "Testing connection between %d and %d:\n", pNode->m_iID, i );
|
|
|
|
if (pNode->HasLink(i))
|
|
{
|
|
// A link has been already created when the other node was processed...
|
|
DebugConnectMsg( pNode->m_iID, i, " Nodes already connected\n" );
|
|
continue;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// If link has been already created in other node just share it
|
|
// ---------------------------------------------------------------------
|
|
CAI_Node *pDestNode = pNetwork->GetNode( i );
|
|
|
|
CAI_Link *pOldLink = pDestNode->HasLink(pNode->m_iID);
|
|
if (pOldLink)
|
|
{
|
|
DebugConnectMsg( pNode->m_iID, i, " Sharing previously establish connection\n" );
|
|
((CAI_Node *)pNode)->AddLink(pOldLink);
|
|
continue;
|
|
}
|
|
|
|
// Only check if the node is a neighbor
|
|
if ( m_NeighborsTable[pNode->m_iID].IsBitSet(pDestNode->m_iID) )
|
|
{
|
|
int acceptedMotions[NUM_HULLS];
|
|
|
|
bool bAllFailed = true;
|
|
|
|
if ( DebuggingConnect( pNode->m_iID, i ) )
|
|
{
|
|
DevMsg( " " ); // break here..
|
|
}
|
|
|
|
if ( !(pNode->m_eNodeInfo & bits_NODE_FALLEN) && !(pDestNode->m_eNodeInfo & bits_NODE_FALLEN) )
|
|
{
|
|
for (int hull = 0 ; hull < NUM_HULLS; hull++ )
|
|
{
|
|
DebugConnectMsg( pNode->m_iID, i, " Testing for hull %s\n", NAI_Hull::Name( (Hull_t)hull ) );
|
|
|
|
acceptedMotions[hull] = ComputeConnection( pNode, pDestNode, (Hull_t)hull );
|
|
if ( acceptedMotions[hull] != 0 )
|
|
bAllFailed = false;
|
|
}
|
|
}
|
|
else
|
|
DebugConnectMsg( pNode->m_iID, i, " No connection: one or both are fallen nodes\n" );
|
|
|
|
// If there were any passible hulls create link
|
|
if (!bAllFailed)
|
|
{
|
|
CAI_Link *pLink = pNetwork->CreateLink( pNode->m_iID, pDestNode->m_iID);
|
|
if ( pLink )
|
|
{
|
|
for (int hull=0;hull<NUM_HULLS;hull++)
|
|
{
|
|
pLink->m_iAcceptedMoveTypes[hull] = acceptedMotions[hull];
|
|
}
|
|
DebugConnectMsg( pNode->m_iID, i, " Added link\n" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_NeighborsTable[pNode->m_iID].Clear(pDestNode->m_iID);
|
|
DebugConnectMsg(pNode->m_iID, i, " NO LINK\n" );
|
|
}
|
|
}
|
|
else
|
|
DebugConnectMsg( pNode->m_iID, i, " NO LINK (not neighbors)\n" );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|