diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/ai_hint.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/ai_hint.cpp')
| -rw-r--r-- | mp/src/game/server/ai_hint.cpp | 3374 |
1 files changed, 1687 insertions, 1687 deletions
diff --git a/mp/src/game/server/ai_hint.cpp b/mp/src/game/server/ai_hint.cpp index 1883f6ff..c0014220 100644 --- a/mp/src/game/server/ai_hint.cpp +++ b/mp/src/game/server/ai_hint.cpp @@ -1,1687 +1,1687 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Hint node utilities and functions
-//
-// $NoKeywords: $
-//=============================================================================//
-
-// @TODO (toml 03-04-03): there is far too much duplicate code in here
-
-#include "cbase.h"
-#include "ai_hint.h"
-#include "ai_network.h"
-#include "ai_node.h"
-#include "ai_basenpc.h"
-#include "ai_networkmanager.h"
-#include "ndebugoverlay.h"
-#include "animation.h"
-#include "tier1/strtools.h"
-#include "mapentities_shared.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-#define REPORTFAILURE(text) if ( hintCriteria.HasFlag( bits_HINT_NODE_REPORT_FAILURES ) ) \
- NDebugOverlay::Text( GetAbsOrigin(), text, false, 60 )
-
-//==================================================
-// CHintCriteria
-//==================================================
-
-CHintCriteria::CHintCriteria( void )
-{
- m_iFirstHintType = HINT_NONE;
- m_iLastHintType = HINT_NONE;
- m_strGroup = NULL_STRING;
- m_iFlags = 0;
- m_HintTypes.Purge();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Destructor
-//-----------------------------------------------------------------------------
-CHintCriteria::~CHintCriteria( void )
-{
- m_zoneInclude.Purge();
- m_zoneExclude.Purge();
- m_HintTypes.Purge();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sets the hint type for this search criteria
-// Input : nHintType - the hint type for this search criteria
-//-----------------------------------------------------------------------------
-void CHintCriteria::SetHintType( int nHintType )
-{
- m_iFirstHintType = nHintType;
- m_iLastHintType = HINT_NONE;
- m_HintTypes.Purge();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add another type of hint that matches the search criteria
-//-----------------------------------------------------------------------------
-void CHintCriteria::AddHintType( int hintType )
-{
- m_HintTypes.AddToTail( hintType );
-}
-
-int CHintCriteria::NumHintTypes() const
-{
- return m_HintTypes.Count();
-}
-
-int CHintCriteria::GetHintType( int idx ) const
-{
- return m_HintTypes[ idx ];
-}
-
-bool CHintCriteria::MatchesSingleHintType() const
-{
- if ( m_HintTypes.Count() != 0 )
- {
- return false;
- }
-
- if ( m_iFirstHintType != HINT_ANY &&
- m_iLastHintType == HINT_NONE )
- {
- return true;
- }
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CHintCriteria::MatchesHintType( int hintType ) const
-{
- int c = m_HintTypes.Count();
- for ( int i = 0; i < c; ++i )
- {
- if ( m_HintTypes[i] == hintType )
- return true;
- }
-
- // See if we're trying to filter the nodes
- if ( GetFirstHintType() != HINT_ANY )
- {
- if( GetLastHintType() == HINT_NONE )
- {
- // Searching for a single type of hint.
- if( GetFirstHintType() != hintType )
- return false;
- }
- else
- {
- // This search is for a range of hint types.
- if( hintType < GetFirstHintType() || hintType > GetLastHintType() )
- return false;
- }
-
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Allows us to search for nodes within a range of consecutive types.
-//-----------------------------------------------------------------------------
-void CHintCriteria::SetHintTypeRange( int firstType, int lastType )
-{
- if( lastType < firstType )
- {
- DevMsg( 2, "Hint Type Range is backwards - Fixing up.\n" );
-
- int temp;
-
- temp = firstType;
- firstType = lastType;
- lastType = temp;
- }
-
- m_iFirstHintType = firstType;
- m_iLastHintType = lastType;
- m_HintTypes.Purge();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : bitmask -
-//-----------------------------------------------------------------------------
-void CHintCriteria::SetFlag( int bitmask )
-{
- m_iFlags |= bitmask;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : bitmask -
-//-----------------------------------------------------------------------------
-void CHintCriteria::ClearFlag( int bitmask )
-{
- m_iFlags &= ~bitmask;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : group -
-//-----------------------------------------------------------------------------
-void CHintCriteria::SetGroup( string_t group )
-{
- m_strGroup = group;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Adds a zone to a zone list
-// Input : list - the list of zones to add the new zone to
-// &position - the origin point of the zone
-// radius - the radius of the zone
-//-----------------------------------------------------------------------------
-void CHintCriteria::AddZone( zoneList_t &list, const Vector &position, float radius )
-{
- int id = list.AddToTail();
- list[id].position = position;
- list[id].radiussqr = radius*radius;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Adds an include zone to the search criteria
-// Input : &position - the origin point of the zone
-// radius - the radius of the zone
-//-----------------------------------------------------------------------------
-void CHintCriteria::AddIncludePosition( const Vector &position, float radius )
-{
- AddZone( m_zoneInclude, position, radius );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Adds an exclude zone to the search criteria
-// Input : &position - the origin point of the zone
-// radius - the radius of the zone
-//-----------------------------------------------------------------------------
-void CHintCriteria::AddExcludePosition( const Vector &position, float radius )
-{
- AddZone( m_zoneExclude, position, radius );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Test to see if this position falls within any of the zones in the list
-// Input : *zone - list of zones to test against
-// &testPosition - position to test with
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-inline bool CHintCriteria::InZone( const zoneList_t &zone, const Vector &testPosition ) const
-{
- int numZones = zone.Count();
-
- //Iterate through all zones in the list
- for ( int i = 0; i < numZones; i++ )
- {
- if ( ((zone[i].position) - testPosition).LengthSqr() < (zone[i].radiussqr) )
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Determine if a point within our include list
-// Input : &testPosition - position to test with
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CHintCriteria::InIncludedZone( const Vector &testPosition ) const
-{
- return InZone( m_zoneInclude, testPosition );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Determine if a point within our exclude list
-// Input : &testPosition - position to test with
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CHintCriteria::InExcludedZone( const Vector &testPosition ) const
-{
- return InZone( m_zoneExclude, testPosition );
-}
-
-//-----------------------------------------------------------------------------
-// Init static variables
-//-----------------------------------------------------------------------------
-CAIHintVector CAI_HintManager::gm_AllHints;
-CUtlMap< int, CAIHintVector > CAI_HintManager::gm_TypedHints( 0, 0, DefLessFunc( int ) );
-CAI_Hint* CAI_HintManager::gm_pLastFoundHints[ CAI_HintManager::HINT_HISTORY ];
-int CAI_HintManager::gm_nFoundHintIndex = 0;
-
-CAI_Hint *CAI_HintManager::AddFoundHint( CAI_Hint *hint )
-{
- if ( hint )
- {
- CAI_HintManager::gm_nFoundHintIndex = ( CAI_HintManager::gm_nFoundHintIndex + 1 ) & CAI_HintManager::HINT_HISTORY_MASK;
- gm_pLastFoundHints[ CAI_HintManager::gm_nFoundHintIndex ] = hint;
- }
- return hint;
-
-}
-
-int CAI_HintManager::GetFoundHintCount()
-{
- return CAI_HintManager::HINT_HISTORY;
-}
-
-CAI_Hint *CAI_HintManager::GetFoundHint( int index )
-{
- return gm_pLastFoundHints[ ( CAI_HintManager::gm_nFoundHintIndex + index ) & CAI_HintManager::HINT_HISTORY_MASK ];
-}
-
-CAI_Hint *CAI_HintManager::GetLastFoundHint()
-{
- for ( int i = 0; i < CAI_HintManager::HINT_HISTORY; ++i )
- {
- // Walk backward
- int slot = ( ( CAI_HintManager::gm_nFoundHintIndex - i ) & CAI_HintManager::HINT_HISTORY_MASK );
- if ( gm_pLastFoundHints[ slot ] )
- return gm_pLastFoundHints[ slot ];
- }
- return NULL;
-}
-
-void CAI_HintManager::ResetFoundHints()
-{
- Q_memset( gm_pLastFoundHints, 0, sizeof( gm_pLastFoundHints ) );
- CAI_HintManager::gm_nFoundHintIndex = 0;
-}
-
-bool CAI_HintManager::IsInFoundHintList( CAI_Hint *hint )
-{
- for ( int i = 0; i < CAI_HintManager::HINT_HISTORY; ++i )
- {
- if ( gm_pLastFoundHints[ i ] == hint )
- return true;
- }
- return false;
-}
-
-//-----------------------------------------------------------------------------
-int CAI_HintManager::FindAllHints( CAI_BaseNPC *pNPC, const Vector &position, const CHintCriteria &hintCriteria, CUtlVector<CAI_Hint *> *pResult )
-{
- // If we have no hints, bail
- int c = CAI_HintManager::gm_AllHints.Count();
- if ( !c )
- return NULL;
-
- // Remove the nearest flag. It makes now sense with random.
- bool hadNearest = hintCriteria.HasFlag( bits_HINT_NODE_NEAREST );
- (const_cast<CHintCriteria &>(hintCriteria)).ClearFlag( bits_HINT_NODE_NEAREST );
-
- // Now loop till we find a valid hint or return to the start
- CAI_Hint *pTestHint;
- for ( int i = 0; i < c; ++i )
- {
- pTestHint = CAI_HintManager::gm_AllHints[ i ];
- Assert( pTestHint );
- if ( pTestHint->HintMatchesCriteria( pNPC, hintCriteria, position, NULL ) )
- pResult->AddToTail( pTestHint );
- }
-
- if ( hadNearest )
- (const_cast<CHintCriteria &>(hintCriteria)).SetFlag( bits_HINT_NODE_NEAREST );
-
- return pResult->Count();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Finds a random hint within the requested radious of the npc
-// Builds a list of all suitable hints and chooses randomly from amongst them.
-// Input : *pNPC -
-// nHintType -
-// nFlags -
-// flMaxDist -
-// Output : CAI_Hint
-//-----------------------------------------------------------------------------
-CAI_Hint *CAI_HintManager::FindHintRandom( CAI_BaseNPC *pNPC, const Vector &position, const CHintCriteria &hintCriteria )
-{
- CUtlVector<CAI_Hint *> hintList;
-
- if ( FindAllHints( pNPC, position, hintCriteria, &hintList ) > 0 )
- {
- // Pick one randomly
- return ( CAI_HintManager::AddFoundHint( hintList[ random->RandomInt( 0, hintList.Size() - 1 ) ] ) );
- }
-
- // start at the top of the list for the next search
- CAI_HintManager::ResetFoundHints();
- return NULL;
-}
-
-// #define HINT_PROFILING 1
-#if defined( HINT_PROFILING )
-static void AppendTimer( int idx, char *buf, size_t bufsize, CFastTimer& timer )
-{
- char s[ 32 ];
- Q_snprintf( s, sizeof( s ), "%d %6.3f ms", idx, timer.GetDuration().GetMillisecondsF() );
-
- Q_strncat( buf, s, bufsize );
-}
-#endif
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *hintCriteria -
-// Output : CAI_Hint
-//-----------------------------------------------------------------------------
-CAI_Hint *CAI_HintManager::FindHint( CAI_BaseNPC *pNPC, const Vector &position, const CHintCriteria &hintCriteria )
-{
-#if defined( HINT_PROFILING )
- CFastTimer timer;
- timer.Start();
-#endif
- bool singleType = hintCriteria.MatchesSingleHintType();
- bool lookingForNearest = hintCriteria.HasFlag( bits_HINT_NODE_NEAREST );
- bool bIgnoreHintType = true;
-
- CUtlVector< CAIHintVector * > lists;
- if ( singleType )
- {
- int slot = CAI_HintManager::gm_TypedHints.Find( hintCriteria.GetFirstHintType() );
- if ( slot != CAI_HintManager::gm_TypedHints.InvalidIndex() )
- {
- lists.AddToTail( &CAI_HintManager::gm_TypedHints[ slot ] );
- }
- }
- else
- {
- int typeCount = hintCriteria.NumHintTypes();
- if ( typeCount > 0 )
- {
- for ( int listType = 0; listType < typeCount; ++listType )
- {
- int slot = CAI_HintManager::gm_TypedHints.Find( hintCriteria.GetHintType( listType ) );
- if ( slot != CAI_HintManager::gm_TypedHints.InvalidIndex() )
- {
- lists.AddToTail( &CAI_HintManager::gm_TypedHints[ slot ] );
- }
- }
- }
- else
- {
- // Still need to check hint type in this case
- lists.AddToTail( &CAI_HintManager::gm_AllHints );
- bIgnoreHintType = false;
- }
- }
-
- CAI_Hint *pBestHint = NULL;
-
- int visited = 0;
-
- int listCount = lists.Count();
-
- if ( listCount == 0 )
- return NULL;
-
- // Try the fast match path
- int i, count;
- // Start with hint after the last one used
- CAI_Hint *pTestHint = NULL;
-
- float flBestDistance = MAX_TRACE_LENGTH;
-
- if ( !lookingForNearest )
- {
- // Fast check of previous results
- count = CAI_HintManager::GetFoundHintCount();
- for ( i = 0; i < count; ++i )
- {
- pTestHint = CAI_HintManager::GetFoundHint( i );
- if ( pTestHint )
- {
- Assert( dynamic_cast<CAI_Hint *>(pTestHint) != NULL );
- ++visited;
- if ( pTestHint->HintMatchesCriteria( pNPC, hintCriteria, position, &flBestDistance ) )
- {
-#if defined( HINT_PROFILING )
- Msg( "fast result visited %d\n", visited );
-#endif
- return pTestHint;
- }
- }
- }
- }
-
- // Longer search, reset best distance
- flBestDistance = MAX_TRACE_LENGTH;
-
- for ( int listNum = 0; listNum < listCount; ++listNum )
- {
- CAIHintVector *list = lists[ listNum ];
- count = list->Count();
- // -------------------------------------------
- // If we have no hints, bail
- // -------------------------------------------
- if ( !count )
- continue;
-
- // Now loop till we find a valid hint or return to the start
- for ( i = 0 ; i < count; ++i )
- {
- pTestHint = list->Element( i );
- Assert( pTestHint );
-
- ++visited;
-
- Assert( dynamic_cast<CAI_Hint *>(pTestHint) != NULL );
- if ( pTestHint->HintMatchesCriteria( pNPC, hintCriteria, position, &flBestDistance, false, bIgnoreHintType ) )
- {
- // If we were searching for the nearest, just note that this is now the nearest node
- if ( lookingForNearest )
- {
- pBestHint = pTestHint;
- }
- else
- {
- // If we're not looking for the nearest, we're done
- CAI_HintManager::AddFoundHint( pTestHint );
-#if defined( HINT_PROFILING )
- Msg( "visited %d\n", visited );
-#endif
- return pTestHint;
- }
- }
- }
- }
- // Return the nearest node that we found
- if ( pBestHint )
- {
- CAI_HintManager::AddFoundHint( pBestHint );
- }
-
-#if defined( HINT_PROFILING )
- timer.End();
-
- Msg( "visited %d\n", visited );
- if ( !pBestHint )
- {
- Msg( "%i search failed for [%d] at pos %.3f %.3f %.3f [%.4f msec ~ %.4f msec per node]\n",
- gpGlobals->tickcount,
- pNPC ? pNPC->entindex() : -1,
- position.x, position.y, position.z,
- timer.GetDuration().GetMillisecondsF(),
- timer.GetDuration().GetMillisecondsF()/MAX( (float)visited, 1.0f ) );
- }
-#endif
- return pBestHint;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Searches for a hint node that this NPC cares about. If one is
-// claims that hint node for this NPC so that no other NPCs
-// try to use it.
-//
-// Input : nFlags - Search criterea. Can currently be one or more of the following:
-// bits_HINT_NODE_VISIBLE - searches for visible hint nodes.
-// bits_HINT_NODE_RANDOM - calls through the FindHintRandom and builds list of all matching
-// nodes and picks randomly from among them. Note: Depending on number of hint nodes, this
-// could be slower, so use with care.
-//
-// Output : Returns pointer to hint node if available hint node was found that matches the
-// given criterea that this NPC also cares about. Otherwise, returns NULL
-//-----------------------------------------------------------------------------
-CAI_Hint* CAI_HintManager::FindHint( CAI_BaseNPC *pNPC, Hint_e nHintType, int nFlags, float flMaxDist, const Vector *pMaxDistFrom )
-{
- assert( pNPC != NULL );
- if ( pNPC == NULL )
- return NULL;
-
- CHintCriteria hintCriteria;
- hintCriteria.SetHintType( nHintType );
- hintCriteria.SetFlag( nFlags );
-
- // Using the NPC's hint group?
- if ( nFlags & bits_HINT_NODE_USE_GROUP )
- {
- hintCriteria.SetGroup( pNPC->GetHintGroup() );
- }
-
- // Add the search position
- Vector vecPosition = ( pMaxDistFrom != NULL ) ? (*pMaxDistFrom) : pNPC->GetAbsOrigin();
- hintCriteria.AddIncludePosition( vecPosition, flMaxDist );
-
- // If asking for a random node, use random logic instead
- if ( nFlags & bits_HINT_NODE_RANDOM )
- return FindHintRandom( pNPC, vecPosition, hintCriteria );
-
- return FindHint( pNPC, vecPosition, hintCriteria );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Position only search
-// Output : CAI_Hint
-//-----------------------------------------------------------------------------
-CAI_Hint *CAI_HintManager::FindHint( const Vector &position, const CHintCriteria &hintCriteria )
-{
- return FindHint( NULL, position, hintCriteria );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: NPC only search
-// Output : CAI_Hint
-//-----------------------------------------------------------------------------
-CAI_Hint *CAI_HintManager::FindHint( CAI_BaseNPC *pNPC, const CHintCriteria &hintCriteria )
-{
- assert( pNPC != NULL );
- if ( pNPC == NULL )
- return NULL;
-
- return FindHint( pNPC, pNPC->GetAbsOrigin(), hintCriteria );
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-CAI_Hint* CAI_HintManager::CreateHint( HintNodeData *pNodeData, const char *pMapData )
-{
- // Reset last found hint if new node is added
- CAI_HintManager::ResetFoundHints();
-
- CAI_Hint *pHint = (CAI_Hint*)CreateEntityByName("ai_hint");
- if ( pHint )
- {
- // First, parse the mapdata chunk we were passed
- if ( pMapData )
- {
- CEntityMapData entData( (char*)pMapData );
- pHint->ParseMapData( &entData );
-
- // Restore the desired classname (parsing the mapdata stomps it)
- pHint->SetClassname( "ai_hint" );
- }
-
- pHint->SetName( pNodeData->strEntityName );
- pHint->SetAbsOrigin( pNodeData->vecPosition );
- memcpy( &(pHint->m_NodeData), pNodeData, sizeof(HintNodeData) );
- DispatchSpawn( pHint );
-
- return pHint;
- }
-
- return NULL;
-}
-
-//------------------------------------------------------------------------------
-void CAI_HintManager::AddHint( CAI_Hint *pHint )
-{
- // ---------------------------------
- // Add to linked list of hints
- // ---------------------------------
- CAI_HintManager::gm_AllHints.AddToTail( pHint );
- CAI_HintManager::AddHintByType( pHint );
-}
-
-void CAI_Hint::SetHintType( int hintType, bool force /*= false*/ )
-{
- if ( !force && hintType == m_NodeData.nHintType )
- return;
-
- CAI_HintManager::RemoveHintByType( this );
- m_NodeData.nHintType = hintType;
- CAI_HintManager::AddHintByType( this );
-}
-
-void CAI_HintManager::AddHintByType( CAI_Hint *pHint )
-{
- Hint_e type = pHint->HintType();
-
- int slot = CAI_HintManager::gm_TypedHints.Find( type );
- if ( slot == CAI_HintManager::gm_TypedHints.InvalidIndex() )
- {
- slot = CAI_HintManager::gm_TypedHints.Insert( type);
- }
- CAI_HintManager::gm_TypedHints[ slot ].AddToTail( pHint );
-}
-
-void CAI_HintManager::RemoveHintByType( CAI_Hint *pHintToRemove )
-{
- int slot = CAI_HintManager::gm_TypedHints.Find( pHintToRemove->HintType() );
- if ( slot != CAI_HintManager::gm_TypedHints.InvalidIndex() )
- {
- CAI_HintManager::gm_TypedHints[ slot ].FindAndRemove( pHintToRemove );
- }
-}
-
-//------------------------------------------------------------------------------
-void CAI_HintManager::RemoveHint( CAI_Hint *pHintToRemove )
-{
- // --------------------------------------
- // Remove from linked list of hints
- // --------------------------------------
- gm_AllHints.FindAndRemove( pHintToRemove );
- RemoveHintByType( pHintToRemove );
-
- if ( CAI_HintManager::IsInFoundHintList( pHintToRemove ) )
- {
- CAI_HintManager::ResetFoundHints();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *token -
-// Output : int
-//-----------------------------------------------------------------------------
-int CAI_HintManager::GetFlags( const char *token )
-{
- int len = strlen( token );
- if ( len <= 0 )
- {
- return bits_HINT_NODE_NONE;
- }
-
- char *lowercase = (char *)_alloca( len + 1 );
- Q_strncpy( lowercase, token, len+1 );
- strlwr( lowercase );
-
- if ( strstr( "none", lowercase ) )
- {
- return bits_HINT_NODE_NONE;
- }
-
- int bits = 0;
-
- if ( strstr( "visible", lowercase ) )
- {
- bits |= bits_HINT_NODE_VISIBLE;
- }
-
- if ( strstr( "nearest", lowercase ) )
- {
- bits |= bits_HINT_NODE_NEAREST;
- }
-
- if ( strstr( "random", lowercase ) )
- {
- bits |= bits_HINT_NODE_RANDOM;
- }
-
- // Can't be nearest and random, defer to nearest
- if ( ( bits & bits_HINT_NODE_NEAREST ) &&
- ( bits & bits_HINT_NODE_RANDOM ) )
- {
- // Remove random
- bits &= ~bits_HINT_NODE_RANDOM;
-
- DevMsg( "HINTFLAGS:%s, inconsistent, the nearest node is never a random hint node, treating as nearest request!\n",
- token );
- }
-
- return bits;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-CAI_Hint *CAI_HintManager::GetFirstHint( AIHintIter_t *pIter )
-{
- if ( !gm_AllHints.Count() )
- {
- *pIter = (AIHintIter_t)gm_AllHints.InvalidIndex();
- return NULL;
- }
- *pIter = (AIHintIter_t)0;
- return gm_AllHints[0];
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-CAI_Hint *CAI_HintManager::GetNextHint( AIHintIter_t *pIter )
-{
- if ( (int)*pIter != gm_AllHints.InvalidIndex() )
- {
- int i = ( (int)*pIter ) + 1;
- if ( gm_AllHints.Count() <= i )
- {
- *pIter = (AIHintIter_t)gm_AllHints.InvalidIndex();
- return NULL;
- }
- *pIter = (AIHintIter_t)i;
- return gm_AllHints[i];
- }
- return NULL;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_HintManager::DumpHints()
-{
- AIHintIter_t iter;
- CAI_Hint *pCurHint = GetFirstHint( &iter );
- while (pCurHint)
- {
- const Vector &v = pCurHint->GetAbsOrigin();
- Msg( "(%.1f, %.1f, %.1f) -- Node ID: %d; WC id %d; type %d\n",
- v.x, v.y, v.z,
- pCurHint->GetNodeId(),
- pCurHint->GetWCId(),
- pCurHint->HintType() );
- pCurHint = GetNextHint( &iter );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_HintManager::ValidateHints()
-{
-#ifdef _DEBUG
- int nTyped = 0;
- FOR_EACH_VEC( gm_AllHints, i )
- {
- Assert( dynamic_cast<CAI_Hint *>(gm_AllHints[i]) != NULL );
- }
-
- for ( int i = gm_TypedHints.FirstInorder(); i != gm_TypedHints.InvalidIndex(); i = gm_TypedHints.NextInorder( i ) )
- {
- FOR_EACH_VEC( gm_TypedHints[i], j )
- {
- nTyped++;
- Assert( dynamic_cast<CAI_Hint *>(gm_TypedHints[i][j]) != NULL );
- }
- }
-
- Assert( gm_AllHints.Count() == nTyped );
-#endif
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CAI_HintManager::DrawHintOverlays(float flDrawDuration)
-{
- int c = gm_AllHints.Count();
- for ( int i = 0; i < c; ++i )
- {
- CAI_Hint *pHint = gm_AllHints[ i ];
- int r = 0;
- int g = 0;
- int b = 255;
- Vector vHintPos;
-
- if (pHint->m_NodeData.nNodeID != NO_NODE)
- {
- vHintPos = g_pBigAINet->GetNode(pHint->m_NodeData.nNodeID)->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum);
- }
- else
- {
- vHintPos = pHint->GetAbsOrigin();
- }
-
- if ( pHint->GetNodeId() != NO_NODE )
- NDebugOverlay::Text( vHintPos + Vector(0,6,8), CFmtStr("(%d), (%d)", pHint->HintType(), pHint->GetNodeId()), true, flDrawDuration );
- else
- NDebugOverlay::Text( vHintPos + Vector(0,6,8), CFmtStr("(%d)", pHint->HintType()), true, flDrawDuration );
-
- // If node is currently locked
- if (pHint->m_NodeData.iDisabled)
- {
- r = 100;
- g = 100;
- b = 100;
- }
- else if (pHint->m_hHintOwner != NULL)
- {
- r = 255;
- g = 0;
- b = 0;
-
- CBaseEntity* pOwner = pHint->User();
- if (pOwner)
- {
- char owner[255];
- Q_strncpy(owner,pOwner->GetDebugName(),sizeof(owner));
- Vector loc = vHintPos;
- loc.x+=6;
- loc.y+=6;
- loc.z+=6;
- NDebugOverlay::Text( loc, owner, true, flDrawDuration );
- NDebugOverlay::Line( vHintPos, pOwner->WorldSpaceCenter(), 128, 128, 128, false, 0);
- }
- }
- else if (pHint->IsLocked())
- {
- r = 200;
- g = 150;
- b = 10;
- }
-
- NDebugOverlay::Box(vHintPos, Vector(-3,-3,-3), Vector(3,3,3), r,g,b,0,flDrawDuration);
-
- // Draw line in facing direction
- Vector offsetDir = 12.0 * Vector(cos(DEG2RAD(pHint->Yaw())),sin(DEG2RAD(pHint->Yaw())),0);
- NDebugOverlay::Line(vHintPos, vHintPos+offsetDir, r,g,b,false,flDrawDuration);
- }
-}
-
-//##################################################################
-// > CAI_Hint
-//##################################################################
-LINK_ENTITY_TO_CLASS( ai_hint, CAI_Hint );
-
-BEGIN_DATADESC( CAI_Hint )
-
- DEFINE_EMBEDDED( m_NodeData ),
- // m_nTargetNodeID (reset on load)
-
- DEFINE_FIELD( m_hHintOwner, FIELD_EHANDLE),
- DEFINE_FIELD( m_flNextUseTime, FIELD_TIME),
- DEFINE_FIELD( m_vecForward, FIELD_VECTOR),
- DEFINE_KEYFIELD( m_nodeFOV, FIELD_FLOAT, "nodeFOV" ),
-
- DEFINE_THINKFUNC( EnableThink ),
-
- // Inputs
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableHint", InputEnableHint ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableHint", InputDisableHint ),
-
- // Outputs
- DEFINE_OUTPUT( m_OnNPCStartedUsing, "OnNPCStartedUsing" ),
- DEFINE_OUTPUT( m_OnNPCStoppedUsing, "OnNPCStoppedUsing" ),
-
-END_DATADESC( );
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-void CAI_Hint::InputEnableHint( inputdata_t &inputdata )
-{
- m_NodeData.iDisabled = false;
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-void CAI_Hint::InputDisableHint( inputdata_t &inputdata )
-{
- m_NodeData.iDisabled = true;
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose :
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CAI_Hint::Spawn( void )
-{
- // Cache off the forward vector
- GetVectors( &m_vecForward, NULL, NULL );
-
- if( m_nodeFOV != 360 )
- {
- // As a micro-optimization, leave the FOV at 360 to save us
- // a dot product later when checking node FOV.
- m_nodeFOV = cos( DEG2RAD(m_nodeFOV/2) );
- }
-
- SetSolid( SOLID_NONE );
-}
-
-void CAI_Hint::Activate()
-{
- BaseClass::Activate();
- CAI_HintManager::AddHint( this );
-}
-
-void CAI_Hint::UpdateOnRemove( void )
-{
- CAI_HintManager::RemoveHint( this );
- BaseClass::UpdateOnRemove();
-}
-
-//------------------------------------------------------------------------------
-// Purpose : If connected to a node returns node position, otherwise
-// returns local hint position
-//
-// NOTE: Assumes not using multiple AI networks
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-void CAI_Hint::GetPosition(CBaseCombatCharacter *pBCC, Vector *vPosition)
-{
- if ( m_NodeData.nNodeID != NO_NODE )
- {
- *vPosition = g_pBigAINet->GetNodePosition( pBCC, m_NodeData.nNodeID );
- }
- else
- {
- *vPosition = GetAbsOrigin();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : hull -
-// *vPosition -
-//-----------------------------------------------------------------------------
-void CAI_Hint::GetPosition( Hull_t hull, Vector *vPosition )
-{
- if ( m_NodeData.nNodeID != NO_NODE )
- {
- *vPosition = g_pBigAINet->GetNodePosition( hull, m_NodeData.nNodeID );
- }
- else
- {
- *vPosition = GetAbsOrigin();
- }
-}
-
-//------------------------------------------------------------------------------
-// Purpose : If connected to a node returns node direction, otherwise
-// returns local hint direction
-//
-// NOTE: Assumes not using multiple AI networks
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-Vector CAI_Hint::GetDirection( )
-{
- return UTIL_YawToVector( Yaw() );
-}
-
-//------------------------------------------------------------------------------
-// Purpose : If connected to a node returns node yaw, otherwise
-// returns local hint yaw
-//
-// NOTE: Assumes not using multiple AI networks
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-float CAI_Hint::Yaw(void)
-{
- if (m_NodeData.nNodeID != NO_NODE)
- {
- return g_pBigAINet->GetNodeYaw(m_NodeData.nNodeID );
- }
- else
- {
- return GetLocalAngles().y;
- }
-}
-
-
-
-//------------------------------------------------------------------------------
-// Purpose : Returns if this is something that's interesting to look at
-//
-// NOTE: Assumes not using multiple AI networks
-// Input :
-// Output :
-//------------------------------------------------------------------------------
-bool CAI_Hint::IsViewable(void)
-{
- if (m_NodeData.iDisabled)
- {
- return false;
- }
-
- switch( HintType() )
- {
- case HINT_WORLD_VISUALLY_INTERESTING:
- case HINT_WORLD_VISUALLY_INTERESTING_DONT_AIM:
- case HINT_WORLD_VISUALLY_INTERESTING_STEALTH:
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CAI_Hint::IsInNodeFOV( CBaseEntity *pOther )
-{
- if( m_nodeFOV == 360 )
- {
- return true;
- }
-
-#if 0
- NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + m_vecForward * 16, 255, 255, 0, false, 1 );
-#endif
-
- Vector vecToNPC = pOther->GetAbsOrigin() - GetAbsOrigin();
- VectorNormalize( vecToNPC );
- float flDot = DotProduct( vecToNPC, m_vecForward );
-
- if( flDot > m_nodeFOV )
- {
-#if 0
- NDebugOverlay::Line( GetAbsOrigin(), pOther->GetAbsOrigin(), 0, 255, 0, false, 1 );
-#endif
- return true;
- }
-
-#if 0
- NDebugOverlay::Line( GetAbsOrigin(), pOther->GetAbsOrigin(), 255, 0, 0, false, 1 );
-#endif
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Locks the node for use by an AI for hints
-// Output : Returns true if the node was available for locking, false on failure.
-//-----------------------------------------------------------------------------
-bool CAI_Hint::Lock( CBaseEntity* pNPC )
-{
- if ( m_hHintOwner != pNPC && m_hHintOwner != NULL )
- return false;
- m_hHintOwner = pNPC;
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Unlocks the node, making it available for hint use by other AIs.
-// after the given delay time
-//-----------------------------------------------------------------------------
-void CAI_Hint::Unlock( float delay )
-{
- m_hHintOwner = NULL;
- m_flNextUseTime = gpGlobals->curtime + delay;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns true is hint node is open for use
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-bool CAI_Hint::IsLockedBy( CBaseEntity *pNPC )
-{
- return (m_hHintOwner == pNPC);
-};
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns true is hint node is open for use
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-bool CAI_Hint::IsLocked( void )
-{
- if (m_NodeData.iDisabled)
- {
- return true;
- }
-
- if (gpGlobals->curtime < m_flNextUseTime)
- {
- return true;
- }
-
- if (m_hHintOwner != NULL)
- {
- return true;
- }
- return false;
-};
-
-//-----------------------------------------------------------------------------
-// Purpose: Return true if pTestHint passes the criteria specified in hintCriteria
-//-----------------------------------------------------------------------------
-bool CAI_Hint::HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hintCriteria, const Vector &position, float *flNearestDistance, bool bIgnoreLock, bool bIgnoreHintType )
-{
- // Cannot be locked
- if ( !bIgnoreLock && IsLocked() )
- {
- REPORTFAILURE( "Node is locked." );
- return false;
- }
-
- if ( !bIgnoreHintType && !hintCriteria.MatchesHintType( HintType() ) )
- {
- return false;
- }
-
- if ( GetMinState() > NPC_STATE_IDLE || GetMaxState() < NPC_STATE_COMBAT )
- {
- if ( pNPC && ( pNPC->GetState() < GetMinState() || pNPC->GetState() > GetMaxState() ) )
- {
- REPORTFAILURE( "NPC not in correct state." );
- return false;
- }
- }
-
- // See if we're filtering by group name
- if ( hintCriteria.GetGroup() != NULL_STRING )
- {
- AssertIsValidString( GetGroup() );
- AssertIsValidString( hintCriteria.GetGroup() );
- if ( GetGroup() == NULL_STRING || GetGroup() != hintCriteria.GetGroup() )
- {
- Assert(GetGroup() == NULL_STRING || strcmp( STRING(GetGroup()), STRING(hintCriteria.GetGroup())) != 0 );
- REPORTFAILURE( "Doesn't match NPC hint group." );
- return false;
- }
- }
-
- // If we're watching for include zones, test it
- if ( ( hintCriteria.HasIncludeZones() ) && ( hintCriteria.InIncludedZone( GetAbsOrigin() ) == false ) )
- {
- REPORTFAILURE( "Not inside include zones." );
- return false;
- }
-
- // If we're watching for exclude zones, test it
- if ( ( hintCriteria.HasExcludeZones() ) && ( hintCriteria.InExcludedZone( GetAbsOrigin() ) ) )
- {
- REPORTFAILURE( "Inside exclude zones." );
- return false;
- }
-
- // See if the class handles this hint type
- if ( ( pNPC != NULL ) && ( pNPC->FValidateHintType( this ) == false ) )
- {
- REPORTFAILURE( "NPC doesn't know how to handle that type." );
- return false;
- }
-
- if ( hintCriteria.HasFlag(bits_HINT_NPC_IN_NODE_FOV) )
- {
- if ( pNPC == NULL )
- {
- AssertMsg(0,"Hint node attempted to verify NPC in node FOV without NPC!\n");
- }
- else
- {
- if( !IsInNodeFOV(pNPC) )
- {
- REPORTFAILURE( "NPC Not in hint's FOV" );
- return false;
- }
- }
- }
-
- if ( hintCriteria.HasFlag( bits_HINT_NODE_IN_AIMCONE ) )
- {
- if ( pNPC == NULL )
- {
- AssertMsg( 0, "Hint node attempted to find node in aimcone without specifying NPC!\n" );
- }
- else
- {
- if( !pNPC->FInAimCone( GetAbsOrigin() ) )
- {
- REPORTFAILURE( "Hint isn't in NPC's aimcone" );
- return false;
- }
- }
- }
-
- if ( hintCriteria.HasFlag( bits_HINT_NODE_IN_VIEWCONE ) )
- {
- if ( pNPC == NULL )
- {
- AssertMsg( 0, "Hint node attempted to find node in viewcone without specifying NPC!\n" );
- }
- else
- {
- if( !pNPC->FInViewCone( this ) )
- {
- REPORTFAILURE( "Hint isn't in NPC's viewcone" );
- return false;
- }
- }
- }
-
- if ( hintCriteria.HasFlag( bits_HINT_NOT_CLOSE_TO_ENEMY ) )
- {
- if ( pNPC == NULL )
- {
- AssertMsg( 0, "Hint node attempted to find node not close to enemy without specifying NPC!\n" );
- }
- else
- {
- if( pNPC->GetEnemy() )
- {
- float flDistHintToEnemySqr = GetAbsOrigin().DistToSqr( pNPC->GetEnemy()->GetAbsOrigin() ) ;
-
- if( flDistHintToEnemySqr < Square( 30.0f * 12.0f ) )
- {
- REPORTFAILURE( "Hint takes NPC close to Enemy" );
- return false;
- }
- }
- }
- }
-
- {
- AI_PROFILE_SCOPE( HINT_FVisible );
- // See if we're requesting a visible node
- if ( hintCriteria.HasFlag( bits_HINT_NODE_VISIBLE ) )
- {
- if ( pNPC == NULL )
- {
- //NOTENOTE: If you're hitting this, you've asked for a visible node without specifing an NPC!
- AssertMsg( 0, "Hint node attempted to find visible node without specifying NPC!\n" );
- }
- else
- {
- if( m_NodeData.nNodeID == NO_NODE )
- {
- // This is just an info_hint, not a node.
- if( !pNPC->FVisible( this ) )
- {
- REPORTFAILURE( "Hint isn't visible to NPC." );
- return false;
- }
- }
- else
- {
- // This hint associated with a node.
- trace_t tr;
- Vector vHintPos;
- GetPosition(pNPC,&vHintPos);
- AI_TraceLine ( pNPC->EyePosition(), vHintPos + pNPC->GetViewOffset(), MASK_NPCSOLID_BRUSHONLY, pNPC, COLLISION_GROUP_NONE, &tr );
- if ( tr.fraction != 1.0f )
- {
- REPORTFAILURE( "Node isn't visible to NPC." );
- return false;
- }
- }
- }
- }
- }
-
- // Check for clear if requested
- if ( hintCriteria.HasFlag( bits_HINT_NODE_CLEAR ) )
- {
- if ( pNPC == NULL )
- {
- //NOTENOTE: If you're hitting this, you've asked for a clear node without specifing an NPC!
- AssertMsg( 0, "Hint node attempted to find clear node without specifying NPC!\n" );
- }
- else
- {
- trace_t tr;
- // Can my bounding box fit there?
- AI_TraceHull ( GetAbsOrigin(), GetAbsOrigin(), pNPC->WorldAlignMins(), pNPC->WorldAlignMaxs(),
- MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.fraction != 1.0 )
- {
- REPORTFAILURE( "Node isn't clear." );
- return false;
- }
- }
- }
-
- // See if this is our next, closest node
- if ( hintCriteria.HasFlag( bits_HINT_NODE_NEAREST ) )
- {
- Assert( flNearestDistance );
-
- // Calculate our distance
- float distance = (GetAbsOrigin() - position).Length();
-
- // Must be closer than the current best
- if ( distance > *flNearestDistance )
- {
- REPORTFAILURE( "Not the nearest node." );
- return false;
- }
-
- // Remember the distance
- *flNearestDistance = distance;
- }
-
- if ( hintCriteria.HasFlag(bits_HINT_HAS_LOS_TO_PLAYER|bits_HAS_EYEPOSITION_LOS_TO_PLAYER) )
- {
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
-
- if( pPlayer != NULL )
- {
- Vector vecDest = GetAbsOrigin();
-
- if( hintCriteria.HasFlag(bits_HAS_EYEPOSITION_LOS_TO_PLAYER) )
- {
- vecDest += pNPC->GetDefaultEyeOffset();
- }
-
- if( !pPlayer->FVisible(vecDest) )
- {
- REPORTFAILURE( "Do not have LOS to player" );
- return false;
- }
- }
- }
-
- // Must either be visible or not if requested
- if ( hintCriteria.HasFlag( bits_HINT_NODE_NOT_VISIBLE_TO_PLAYER|bits_HINT_NODE_VISIBLE_TO_PLAYER ) )
- {
- bool bWasSeen = false;
- // Test all potential seers
- for ( int i = 1; i <= gpGlobals->maxClients; i++ )
- {
- CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
-
- if ( pPlayer )
- {
- // Only spawn if the player's looking away from me
- Vector vLookDir = pPlayer->EyeDirection3D();
- Vector vTargetDir = GetAbsOrigin() - pPlayer->EyePosition();
- VectorNormalize(vTargetDir);
-
- float fDotPr = DotProduct(vLookDir,vTargetDir);
- if ( fDotPr > 0 )
- {
- trace_t tr;
- UTIL_TraceLine( pPlayer->EyePosition(), GetAbsOrigin(), MASK_SOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, &tr);
-
- if ( tr.fraction == 1.0 )
- {
- if ( hintCriteria.HasFlag( bits_HINT_NODE_NOT_VISIBLE_TO_PLAYER ) )
- {
- REPORTFAILURE( "Node is visible to player." );
- return false;
- }
- bWasSeen = true;
- }
- }
- }
- }
-
- if ( !bWasSeen && hintCriteria.HasFlag( bits_HINT_NODE_VISIBLE_TO_PLAYER ) )
- {
- REPORTFAILURE( "Node isn't visible to player." );
- return false;
- }
- }
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Draw any debug text overlays
-// Input :
-// Output : Current text offset from the top
-//-----------------------------------------------------------------------------
-int CAI_Hint::DrawDebugTextOverlays(void)
-{
- int text_offset = BaseClass::DrawDebugTextOverlays();
-
- if (m_debugOverlays & OVERLAY_TEXT_BIT)
- {
- char tempstr[512];
- Q_snprintf(tempstr,sizeof(tempstr),"%s (%i)", GetHintTypeDescription( HintType() ), HintType());
- EntityText(text_offset,tempstr,0);
- text_offset++;
- Q_snprintf(tempstr,sizeof(tempstr),"delay %f", MAX( 0.0f, m_flNextUseTime - gpGlobals->curtime ) ) ;
- EntityText(text_offset,tempstr,0);
- text_offset++;
-
- if ( m_NodeData.iDisabled )
- {
- Q_snprintf(tempstr,sizeof(tempstr),"DISABLED" );
- EntityText(text_offset,tempstr,0);
- text_offset++;
- }
-
- }
- return text_offset;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Constructor
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-CAI_Hint::CAI_Hint(void)
-{
- m_flNextUseTime = 0;
- m_nTargetNodeID = NO_NODE;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Destructor
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-CAI_Hint::~CAI_Hint(void)
-{
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sometimes FValidateHint, etc. will want to examine the underlying node to
-// see if it's truly suitable ( e.g., in the same air/ground network of nodes? )
-// Output : C_AINode *
-//-----------------------------------------------------------------------------
-CAI_Node *CAI_Hint::GetNode( void )
-{
- if ( m_NodeData.nNodeID != NO_NODE )
- {
- return g_pBigAINet->GetNode( m_NodeData.nNodeID, false );
- }
- return NULL;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_Hint::DisableForSeconds( float flSeconds )
-{
- Unlock( flSeconds );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_Hint::EnableThink()
-{
- SetDisabled( false );
- SetThink( NULL );
-}
-
-void CAI_Hint::FixupTargetNode()
-{
- if ( m_NodeData.nTargetWCNodeID != -1 )
- m_nTargetNodeID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( m_NodeData.nTargetWCNodeID );
- else
- m_nTargetNodeID = NO_NODE;
-}
-
-void CAI_Hint::OnRestore()
-{
- BaseClass::OnRestore();
-
- m_NodeData.nNodeID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( m_NodeData.nWCNodeID );
- FixupTargetNode();
-
- CAI_Node *pNode = GetNode();
-
- if ( !pNode )
- {
- if ( m_NodeData.nWCNodeID > 0 )
- DevMsg("Warning: AI hint has incorrect or no AI node\n");
- }
- else
- {
- m_NodeData.vecPosition = pNode->GetOrigin();
- Teleport( &m_NodeData.vecPosition, NULL, NULL );
- pNode->SetHint( this );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_Hint::NPCStartedUsing( CAI_BaseNPC *pNPC )
-{
- m_OnNPCStartedUsing.Set( pNPC, pNPC, this );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_Hint::NPCStoppedUsing( CAI_BaseNPC *pNPC )
-{
- m_OnNPCStoppedUsing.Set( pNPC, pNPC, this );
-}
-
-
-CON_COMMAND(ai_dump_hints, "")
-{
- if ( !UTIL_IsCommandIssuedByServerAdmin() )
- return;
-
- CAI_HintManager::ValidateHints();
- CAI_HintManager::DumpHints();
-}
-
-
-//-----------------------------------------------------------------------------
-//
-// hints - these MUST coincide with the HINTS listed under Hint_e
-//
-//-----------------------------------------------------------------------------
-struct hinttypedescs_t
-{
- Hint_e iType;
- const char *pszDesc;
-};
-hinttypedescs_t g_pszHintDescriptions[] =
-{
- { HINT_NONE, "None" },
- //{ HINT_NOT_USED_WORLD_DOOR, "Obsolete / Unused" },
- { HINT_WORLD_WINDOW, "World: Window" },
- //{ HINT_NOT_USED_WORLD_BUTTON, "Obsolete / Unused" },
- //{ HINT_NOT_USED_WORLD_MACHINERY, "Obsolete / Unused" },
- //{ HINT_NOT_USED_WORLD_LEDGE, "Obsolete / Unused" },
- //{ HINT_NOT_USED_WORLD_LIGHT_SOURCE, "Obsolete / Unused" },
- //{ HINT_NOT_USED_WORLD_HEAT_SOURCE, "Obsolete / Unused" },
- //{ HINT_NOT_USED_WORLD_BLINKING_LIGHT, "Obsolete / Unused" },
- //{ HINT_NOT_USED_WORLD_BRIGHT_COLORS, "Obsolete / Unused" },
- //{ HINT_NOT_USED_WORLD_HUMAN_BLOOD, "Obsolete / Unused" },
- //{ HINT_NOT_USED_WORLD_ALIEN_BLOOD, "Obsolete / Unused" },
-
- { HINT_WORLD_WORK_POSITION, "Act Busy" },
- { HINT_WORLD_VISUALLY_INTERESTING, "World: Visually Interesting" },
- { HINT_WORLD_VISUALLY_INTERESTING_DONT_AIM, "World: Visually Interesting (Don't Aim)" },
- { HINT_WORLD_INHIBIT_COMBINE_MINES, "World: Inhibit Combine Mines" },
- { HINT_WORLD_VISUALLY_INTERESTING_STEALTH, "World: Visually Interesting (Stealth)" },
-
- { HINT_TACTICAL_COVER_MED, "Tactical: Cover Medium" },
- { HINT_TACTICAL_COVER_LOW, "Tactical: Cover Low" },
- { HINT_TACTICAL_SPAWN, "Tactical: Spawn" },
- { HINT_TACTICAL_PINCH, "Tactical: Pinch" },
- //{ HINT_NOT_USED_TACTICAL_GUARD, "Obsolete / Unused" },
- { HINT_TACTICAL_ENEMY_DISADVANTAGED, "Tactical: Enemy Disadvantage" },
- //{ HINT_NOT_USED_HEALTH_KIT, "Obsolete / Unused" },
-
- //{ HINT_NOT_USED_URBAN_STREETCORNER, "Obsolete / Unused" },
- //{ HINT_NOT_USED_URBAN_STREETLAMP, "Obsolete / Unused" },
- //{ HINT_NOT_USED_URBAN_DARK_SPOT, "Obsolete / Unused" },
- //{ HINT_NOT_USED_URBAN_POSTER, "Obsolete / Unused" },
- //{ HINT_NOT_USED_URBAN_SHELTER, "Obsolete / Unused" },
-
- //{ HINT_NOT_USED_ASSASSIN_SECLUDED, "Obsolete / Unused" },
- //{ HINT_NOT_USED_ASSASSIN_RAFTERS, "Obsolete / Unused" },
- //{ HINT_NOT_USED_ASSASSIN_GROUND, "Obsolete / Unused" },
- //{ HINT_NOT_USED_ASSASSIN_MONKEYBARS, "Obsolete / Unused" },
-
- { HINT_ANTLION_BURROW_POINT, "Antlion: Burrow Point" },
- { HINT_ANTLION_THUMPER_FLEE_POINT, "Antlion: Thumper Flee Point" },
-
- //{ HINT_HEADCRAB_BURROW_POINT, "Obsolete / Unused" },
-
- //{ HINT_NOT_USED_ROLLER_PATROL_POINT, "Obsolete / Unused" },
- //{ HINT_NOT_USED_ROLLER_CLEANUP_POINT, "Obsolete / Unused" },
-
- //{ HINT_NOT_USED_PSTORM_ROCK_SPAWN, "Obsolete / Unused" },
-
- { HINT_CROW_FLYTO_POINT, "Crow: Flyto Point" },
-
- //{ HINT_BUG_PATROL_POINT, "Obsolete / Unused" },
-
- { HINT_FOLLOW_WAIT_POINT, "Follow: Wait Point" },
- { HINT_JUMP_OVERRIDE, "Jump Override" },
- { HINT_PLAYER_SQUAD_TRANSITON_POINT, "Squad Transition Point" },
- { HINT_NPC_EXIT_POINT, "Act Busy: Exit Point" },
- { HINT_STRIDER_NODE, "Strider" },
-
- { HINT_PLAYER_ALLY_MOVE_AWAY_DEST, "Ally MoveAway Point" },
-
- { HINT_HL1_WORLD_MACHINERY, "HL1: World: Machinery" },
- { HINT_HL1_WORLD_BLINKING_LIGHT, "HL1: World: Blinking Light" },
- { HINT_HL1_WORLD_HUMAN_BLOOD, "HL1: World: Human Blood" },
- { HINT_HL1_WORLD_ALIEN_BLOOD, "HL1: World: Alien Blood" },
-
- { HINT_CSTRIKE_HOSTAGE_ESCAPE, "CS Port: Hostage Escape" },
-};
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-const char *GetHintTypeDescription( Hint_e iHintType )
-{
- for ( int i = 0; i < ARRAYSIZE(g_pszHintDescriptions); i++ )
- {
- if ( g_pszHintDescriptions[i].iType == iHintType )
- return g_pszHintDescriptions[i].pszDesc;
- }
-
- return "Obsolete / Unused";
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-const char *GetHintTypeDescription( CAI_Hint *pHint )
-{
- return GetHintTypeDescription( pHint->HintType() );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Debug command to drop hints into the world
-//-----------------------------------------------------------------------------
-void CC_ai_drop_hint( const CCommand &args )
-{
- CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
- if ( !pPlayer )
- return;
-
- if ( args.ArgC() < 2 )
- {
- Msg("Invalid hint type specified. Format: ai_drop_hint <hint type>\nValid hint types:\n");
-
- for ( int i = 0; i < ARRAYSIZE(g_pszHintDescriptions); i++ )
- {
- Msg("%d : %s\n", g_pszHintDescriptions[i].iType, g_pszHintDescriptions[i].pszDesc );
- }
- return;
- }
-
- HintNodeData nodeData;
- nodeData.strEntityName = MAKE_STRING("ai_drop_hint");
- nodeData.vecPosition = pPlayer->EyePosition();
- nodeData.nHintType = atoi( args[1] );
- nodeData.nNodeID = NO_NODE;
- nodeData.strGroup = NULL_STRING;
- nodeData.iDisabled = false;
- nodeData.iszActivityName = NULL_STRING;
- nodeData.fIgnoreFacing = HIF_DEFAULT;
- nodeData.minState = NPC_STATE_IDLE;
- nodeData.maxState = NPC_STATE_COMBAT;
- CAI_Hint *pHint = CAI_HintManager::CreateHint( &nodeData, NULL );
- if ( pHint )
- {
- ((CBaseEntity *)pHint)->Activate();
- pHint->KeyValue( "nodeFOV", "360" );
- pHint->m_debugOverlays |= (OVERLAY_TEXT_BIT | OVERLAY_BBOX_BIT);
- }
-}
-ConCommand ai_drop_hint( "ai_drop_hint", CC_ai_drop_hint, "Drop an ai_hint at the player's current eye position.", FCVAR_CHEAT );
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Hint node utilities and functions +// +// $NoKeywords: $ +//=============================================================================// + +// @TODO (toml 03-04-03): there is far too much duplicate code in here + +#include "cbase.h" +#include "ai_hint.h" +#include "ai_network.h" +#include "ai_node.h" +#include "ai_basenpc.h" +#include "ai_networkmanager.h" +#include "ndebugoverlay.h" +#include "animation.h" +#include "tier1/strtools.h" +#include "mapentities_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define REPORTFAILURE(text) if ( hintCriteria.HasFlag( bits_HINT_NODE_REPORT_FAILURES ) ) \ + NDebugOverlay::Text( GetAbsOrigin(), text, false, 60 ) + +//================================================== +// CHintCriteria +//================================================== + +CHintCriteria::CHintCriteria( void ) +{ + m_iFirstHintType = HINT_NONE; + m_iLastHintType = HINT_NONE; + m_strGroup = NULL_STRING; + m_iFlags = 0; + m_HintTypes.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CHintCriteria::~CHintCriteria( void ) +{ + m_zoneInclude.Purge(); + m_zoneExclude.Purge(); + m_HintTypes.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the hint type for this search criteria +// Input : nHintType - the hint type for this search criteria +//----------------------------------------------------------------------------- +void CHintCriteria::SetHintType( int nHintType ) +{ + m_iFirstHintType = nHintType; + m_iLastHintType = HINT_NONE; + m_HintTypes.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Add another type of hint that matches the search criteria +//----------------------------------------------------------------------------- +void CHintCriteria::AddHintType( int hintType ) +{ + m_HintTypes.AddToTail( hintType ); +} + +int CHintCriteria::NumHintTypes() const +{ + return m_HintTypes.Count(); +} + +int CHintCriteria::GetHintType( int idx ) const +{ + return m_HintTypes[ idx ]; +} + +bool CHintCriteria::MatchesSingleHintType() const +{ + if ( m_HintTypes.Count() != 0 ) + { + return false; + } + + if ( m_iFirstHintType != HINT_ANY && + m_iLastHintType == HINT_NONE ) + { + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CHintCriteria::MatchesHintType( int hintType ) const +{ + int c = m_HintTypes.Count(); + for ( int i = 0; i < c; ++i ) + { + if ( m_HintTypes[i] == hintType ) + return true; + } + + // See if we're trying to filter the nodes + if ( GetFirstHintType() != HINT_ANY ) + { + if( GetLastHintType() == HINT_NONE ) + { + // Searching for a single type of hint. + if( GetFirstHintType() != hintType ) + return false; + } + else + { + // This search is for a range of hint types. + if( hintType < GetFirstHintType() || hintType > GetLastHintType() ) + return false; + } + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Allows us to search for nodes within a range of consecutive types. +//----------------------------------------------------------------------------- +void CHintCriteria::SetHintTypeRange( int firstType, int lastType ) +{ + if( lastType < firstType ) + { + DevMsg( 2, "Hint Type Range is backwards - Fixing up.\n" ); + + int temp; + + temp = firstType; + firstType = lastType; + lastType = temp; + } + + m_iFirstHintType = firstType; + m_iLastHintType = lastType; + m_HintTypes.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bitmask - +//----------------------------------------------------------------------------- +void CHintCriteria::SetFlag( int bitmask ) +{ + m_iFlags |= bitmask; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bitmask - +//----------------------------------------------------------------------------- +void CHintCriteria::ClearFlag( int bitmask ) +{ + m_iFlags &= ~bitmask; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : group - +//----------------------------------------------------------------------------- +void CHintCriteria::SetGroup( string_t group ) +{ + m_strGroup = group; +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a zone to a zone list +// Input : list - the list of zones to add the new zone to +// &position - the origin point of the zone +// radius - the radius of the zone +//----------------------------------------------------------------------------- +void CHintCriteria::AddZone( zoneList_t &list, const Vector &position, float radius ) +{ + int id = list.AddToTail(); + list[id].position = position; + list[id].radiussqr = radius*radius; +} + +//----------------------------------------------------------------------------- +// Purpose: Adds an include zone to the search criteria +// Input : &position - the origin point of the zone +// radius - the radius of the zone +//----------------------------------------------------------------------------- +void CHintCriteria::AddIncludePosition( const Vector &position, float radius ) +{ + AddZone( m_zoneInclude, position, radius ); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds an exclude zone to the search criteria +// Input : &position - the origin point of the zone +// radius - the radius of the zone +//----------------------------------------------------------------------------- +void CHintCriteria::AddExcludePosition( const Vector &position, float radius ) +{ + AddZone( m_zoneExclude, position, radius ); +} + +//----------------------------------------------------------------------------- +// Purpose: Test to see if this position falls within any of the zones in the list +// Input : *zone - list of zones to test against +// &testPosition - position to test with +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +inline bool CHintCriteria::InZone( const zoneList_t &zone, const Vector &testPosition ) const +{ + int numZones = zone.Count(); + + //Iterate through all zones in the list + for ( int i = 0; i < numZones; i++ ) + { + if ( ((zone[i].position) - testPosition).LengthSqr() < (zone[i].radiussqr) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Determine if a point within our include list +// Input : &testPosition - position to test with +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CHintCriteria::InIncludedZone( const Vector &testPosition ) const +{ + return InZone( m_zoneInclude, testPosition ); +} + +//----------------------------------------------------------------------------- +// Purpose: Determine if a point within our exclude list +// Input : &testPosition - position to test with +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CHintCriteria::InExcludedZone( const Vector &testPosition ) const +{ + return InZone( m_zoneExclude, testPosition ); +} + +//----------------------------------------------------------------------------- +// Init static variables +//----------------------------------------------------------------------------- +CAIHintVector CAI_HintManager::gm_AllHints; +CUtlMap< int, CAIHintVector > CAI_HintManager::gm_TypedHints( 0, 0, DefLessFunc( int ) ); +CAI_Hint* CAI_HintManager::gm_pLastFoundHints[ CAI_HintManager::HINT_HISTORY ]; +int CAI_HintManager::gm_nFoundHintIndex = 0; + +CAI_Hint *CAI_HintManager::AddFoundHint( CAI_Hint *hint ) +{ + if ( hint ) + { + CAI_HintManager::gm_nFoundHintIndex = ( CAI_HintManager::gm_nFoundHintIndex + 1 ) & CAI_HintManager::HINT_HISTORY_MASK; + gm_pLastFoundHints[ CAI_HintManager::gm_nFoundHintIndex ] = hint; + } + return hint; + +} + +int CAI_HintManager::GetFoundHintCount() +{ + return CAI_HintManager::HINT_HISTORY; +} + +CAI_Hint *CAI_HintManager::GetFoundHint( int index ) +{ + return gm_pLastFoundHints[ ( CAI_HintManager::gm_nFoundHintIndex + index ) & CAI_HintManager::HINT_HISTORY_MASK ]; +} + +CAI_Hint *CAI_HintManager::GetLastFoundHint() +{ + for ( int i = 0; i < CAI_HintManager::HINT_HISTORY; ++i ) + { + // Walk backward + int slot = ( ( CAI_HintManager::gm_nFoundHintIndex - i ) & CAI_HintManager::HINT_HISTORY_MASK ); + if ( gm_pLastFoundHints[ slot ] ) + return gm_pLastFoundHints[ slot ]; + } + return NULL; +} + +void CAI_HintManager::ResetFoundHints() +{ + Q_memset( gm_pLastFoundHints, 0, sizeof( gm_pLastFoundHints ) ); + CAI_HintManager::gm_nFoundHintIndex = 0; +} + +bool CAI_HintManager::IsInFoundHintList( CAI_Hint *hint ) +{ + for ( int i = 0; i < CAI_HintManager::HINT_HISTORY; ++i ) + { + if ( gm_pLastFoundHints[ i ] == hint ) + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +int CAI_HintManager::FindAllHints( CAI_BaseNPC *pNPC, const Vector &position, const CHintCriteria &hintCriteria, CUtlVector<CAI_Hint *> *pResult ) +{ + // If we have no hints, bail + int c = CAI_HintManager::gm_AllHints.Count(); + if ( !c ) + return NULL; + + // Remove the nearest flag. It makes now sense with random. + bool hadNearest = hintCriteria.HasFlag( bits_HINT_NODE_NEAREST ); + (const_cast<CHintCriteria &>(hintCriteria)).ClearFlag( bits_HINT_NODE_NEAREST ); + + // Now loop till we find a valid hint or return to the start + CAI_Hint *pTestHint; + for ( int i = 0; i < c; ++i ) + { + pTestHint = CAI_HintManager::gm_AllHints[ i ]; + Assert( pTestHint ); + if ( pTestHint->HintMatchesCriteria( pNPC, hintCriteria, position, NULL ) ) + pResult->AddToTail( pTestHint ); + } + + if ( hadNearest ) + (const_cast<CHintCriteria &>(hintCriteria)).SetFlag( bits_HINT_NODE_NEAREST ); + + return pResult->Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: Finds a random hint within the requested radious of the npc +// Builds a list of all suitable hints and chooses randomly from amongst them. +// Input : *pNPC - +// nHintType - +// nFlags - +// flMaxDist - +// Output : CAI_Hint +//----------------------------------------------------------------------------- +CAI_Hint *CAI_HintManager::FindHintRandom( CAI_BaseNPC *pNPC, const Vector &position, const CHintCriteria &hintCriteria ) +{ + CUtlVector<CAI_Hint *> hintList; + + if ( FindAllHints( pNPC, position, hintCriteria, &hintList ) > 0 ) + { + // Pick one randomly + return ( CAI_HintManager::AddFoundHint( hintList[ random->RandomInt( 0, hintList.Size() - 1 ) ] ) ); + } + + // start at the top of the list for the next search + CAI_HintManager::ResetFoundHints(); + return NULL; +} + +// #define HINT_PROFILING 1 +#if defined( HINT_PROFILING ) +static void AppendTimer( int idx, char *buf, size_t bufsize, CFastTimer& timer ) +{ + char s[ 32 ]; + Q_snprintf( s, sizeof( s ), "%d %6.3f ms", idx, timer.GetDuration().GetMillisecondsF() ); + + Q_strncat( buf, s, bufsize ); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *hintCriteria - +// Output : CAI_Hint +//----------------------------------------------------------------------------- +CAI_Hint *CAI_HintManager::FindHint( CAI_BaseNPC *pNPC, const Vector &position, const CHintCriteria &hintCriteria ) +{ +#if defined( HINT_PROFILING ) + CFastTimer timer; + timer.Start(); +#endif + bool singleType = hintCriteria.MatchesSingleHintType(); + bool lookingForNearest = hintCriteria.HasFlag( bits_HINT_NODE_NEAREST ); + bool bIgnoreHintType = true; + + CUtlVector< CAIHintVector * > lists; + if ( singleType ) + { + int slot = CAI_HintManager::gm_TypedHints.Find( hintCriteria.GetFirstHintType() ); + if ( slot != CAI_HintManager::gm_TypedHints.InvalidIndex() ) + { + lists.AddToTail( &CAI_HintManager::gm_TypedHints[ slot ] ); + } + } + else + { + int typeCount = hintCriteria.NumHintTypes(); + if ( typeCount > 0 ) + { + for ( int listType = 0; listType < typeCount; ++listType ) + { + int slot = CAI_HintManager::gm_TypedHints.Find( hintCriteria.GetHintType( listType ) ); + if ( slot != CAI_HintManager::gm_TypedHints.InvalidIndex() ) + { + lists.AddToTail( &CAI_HintManager::gm_TypedHints[ slot ] ); + } + } + } + else + { + // Still need to check hint type in this case + lists.AddToTail( &CAI_HintManager::gm_AllHints ); + bIgnoreHintType = false; + } + } + + CAI_Hint *pBestHint = NULL; + + int visited = 0; + + int listCount = lists.Count(); + + if ( listCount == 0 ) + return NULL; + + // Try the fast match path + int i, count; + // Start with hint after the last one used + CAI_Hint *pTestHint = NULL; + + float flBestDistance = MAX_TRACE_LENGTH; + + if ( !lookingForNearest ) + { + // Fast check of previous results + count = CAI_HintManager::GetFoundHintCount(); + for ( i = 0; i < count; ++i ) + { + pTestHint = CAI_HintManager::GetFoundHint( i ); + if ( pTestHint ) + { + Assert( dynamic_cast<CAI_Hint *>(pTestHint) != NULL ); + ++visited; + if ( pTestHint->HintMatchesCriteria( pNPC, hintCriteria, position, &flBestDistance ) ) + { +#if defined( HINT_PROFILING ) + Msg( "fast result visited %d\n", visited ); +#endif + return pTestHint; + } + } + } + } + + // Longer search, reset best distance + flBestDistance = MAX_TRACE_LENGTH; + + for ( int listNum = 0; listNum < listCount; ++listNum ) + { + CAIHintVector *list = lists[ listNum ]; + count = list->Count(); + // ------------------------------------------- + // If we have no hints, bail + // ------------------------------------------- + if ( !count ) + continue; + + // Now loop till we find a valid hint or return to the start + for ( i = 0 ; i < count; ++i ) + { + pTestHint = list->Element( i ); + Assert( pTestHint ); + + ++visited; + + Assert( dynamic_cast<CAI_Hint *>(pTestHint) != NULL ); + if ( pTestHint->HintMatchesCriteria( pNPC, hintCriteria, position, &flBestDistance, false, bIgnoreHintType ) ) + { + // If we were searching for the nearest, just note that this is now the nearest node + if ( lookingForNearest ) + { + pBestHint = pTestHint; + } + else + { + // If we're not looking for the nearest, we're done + CAI_HintManager::AddFoundHint( pTestHint ); +#if defined( HINT_PROFILING ) + Msg( "visited %d\n", visited ); +#endif + return pTestHint; + } + } + } + } + // Return the nearest node that we found + if ( pBestHint ) + { + CAI_HintManager::AddFoundHint( pBestHint ); + } + +#if defined( HINT_PROFILING ) + timer.End(); + + Msg( "visited %d\n", visited ); + if ( !pBestHint ) + { + Msg( "%i search failed for [%d] at pos %.3f %.3f %.3f [%.4f msec ~ %.4f msec per node]\n", + gpGlobals->tickcount, + pNPC ? pNPC->entindex() : -1, + position.x, position.y, position.z, + timer.GetDuration().GetMillisecondsF(), + timer.GetDuration().GetMillisecondsF()/MAX( (float)visited, 1.0f ) ); + } +#endif + return pBestHint; +} + +//----------------------------------------------------------------------------- +// Purpose: Searches for a hint node that this NPC cares about. If one is +// claims that hint node for this NPC so that no other NPCs +// try to use it. +// +// Input : nFlags - Search criterea. Can currently be one or more of the following: +// bits_HINT_NODE_VISIBLE - searches for visible hint nodes. +// bits_HINT_NODE_RANDOM - calls through the FindHintRandom and builds list of all matching +// nodes and picks randomly from among them. Note: Depending on number of hint nodes, this +// could be slower, so use with care. +// +// Output : Returns pointer to hint node if available hint node was found that matches the +// given criterea that this NPC also cares about. Otherwise, returns NULL +//----------------------------------------------------------------------------- +CAI_Hint* CAI_HintManager::FindHint( CAI_BaseNPC *pNPC, Hint_e nHintType, int nFlags, float flMaxDist, const Vector *pMaxDistFrom ) +{ + assert( pNPC != NULL ); + if ( pNPC == NULL ) + return NULL; + + CHintCriteria hintCriteria; + hintCriteria.SetHintType( nHintType ); + hintCriteria.SetFlag( nFlags ); + + // Using the NPC's hint group? + if ( nFlags & bits_HINT_NODE_USE_GROUP ) + { + hintCriteria.SetGroup( pNPC->GetHintGroup() ); + } + + // Add the search position + Vector vecPosition = ( pMaxDistFrom != NULL ) ? (*pMaxDistFrom) : pNPC->GetAbsOrigin(); + hintCriteria.AddIncludePosition( vecPosition, flMaxDist ); + + // If asking for a random node, use random logic instead + if ( nFlags & bits_HINT_NODE_RANDOM ) + return FindHintRandom( pNPC, vecPosition, hintCriteria ); + + return FindHint( pNPC, vecPosition, hintCriteria ); +} + +//----------------------------------------------------------------------------- +// Purpose: Position only search +// Output : CAI_Hint +//----------------------------------------------------------------------------- +CAI_Hint *CAI_HintManager::FindHint( const Vector &position, const CHintCriteria &hintCriteria ) +{ + return FindHint( NULL, position, hintCriteria ); +} + +//----------------------------------------------------------------------------- +// Purpose: NPC only search +// Output : CAI_Hint +//----------------------------------------------------------------------------- +CAI_Hint *CAI_HintManager::FindHint( CAI_BaseNPC *pNPC, const CHintCriteria &hintCriteria ) +{ + assert( pNPC != NULL ); + if ( pNPC == NULL ) + return NULL; + + return FindHint( pNPC, pNPC->GetAbsOrigin(), hintCriteria ); +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +CAI_Hint* CAI_HintManager::CreateHint( HintNodeData *pNodeData, const char *pMapData ) +{ + // Reset last found hint if new node is added + CAI_HintManager::ResetFoundHints(); + + CAI_Hint *pHint = (CAI_Hint*)CreateEntityByName("ai_hint"); + if ( pHint ) + { + // First, parse the mapdata chunk we were passed + if ( pMapData ) + { + CEntityMapData entData( (char*)pMapData ); + pHint->ParseMapData( &entData ); + + // Restore the desired classname (parsing the mapdata stomps it) + pHint->SetClassname( "ai_hint" ); + } + + pHint->SetName( pNodeData->strEntityName ); + pHint->SetAbsOrigin( pNodeData->vecPosition ); + memcpy( &(pHint->m_NodeData), pNodeData, sizeof(HintNodeData) ); + DispatchSpawn( pHint ); + + return pHint; + } + + return NULL; +} + +//------------------------------------------------------------------------------ +void CAI_HintManager::AddHint( CAI_Hint *pHint ) +{ + // --------------------------------- + // Add to linked list of hints + // --------------------------------- + CAI_HintManager::gm_AllHints.AddToTail( pHint ); + CAI_HintManager::AddHintByType( pHint ); +} + +void CAI_Hint::SetHintType( int hintType, bool force /*= false*/ ) +{ + if ( !force && hintType == m_NodeData.nHintType ) + return; + + CAI_HintManager::RemoveHintByType( this ); + m_NodeData.nHintType = hintType; + CAI_HintManager::AddHintByType( this ); +} + +void CAI_HintManager::AddHintByType( CAI_Hint *pHint ) +{ + Hint_e type = pHint->HintType(); + + int slot = CAI_HintManager::gm_TypedHints.Find( type ); + if ( slot == CAI_HintManager::gm_TypedHints.InvalidIndex() ) + { + slot = CAI_HintManager::gm_TypedHints.Insert( type); + } + CAI_HintManager::gm_TypedHints[ slot ].AddToTail( pHint ); +} + +void CAI_HintManager::RemoveHintByType( CAI_Hint *pHintToRemove ) +{ + int slot = CAI_HintManager::gm_TypedHints.Find( pHintToRemove->HintType() ); + if ( slot != CAI_HintManager::gm_TypedHints.InvalidIndex() ) + { + CAI_HintManager::gm_TypedHints[ slot ].FindAndRemove( pHintToRemove ); + } +} + +//------------------------------------------------------------------------------ +void CAI_HintManager::RemoveHint( CAI_Hint *pHintToRemove ) +{ + // -------------------------------------- + // Remove from linked list of hints + // -------------------------------------- + gm_AllHints.FindAndRemove( pHintToRemove ); + RemoveHintByType( pHintToRemove ); + + if ( CAI_HintManager::IsInFoundHintList( pHintToRemove ) ) + { + CAI_HintManager::ResetFoundHints(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *token - +// Output : int +//----------------------------------------------------------------------------- +int CAI_HintManager::GetFlags( const char *token ) +{ + int len = strlen( token ); + if ( len <= 0 ) + { + return bits_HINT_NODE_NONE; + } + + char *lowercase = (char *)_alloca( len + 1 ); + Q_strncpy( lowercase, token, len+1 ); + strlwr( lowercase ); + + if ( strstr( "none", lowercase ) ) + { + return bits_HINT_NODE_NONE; + } + + int bits = 0; + + if ( strstr( "visible", lowercase ) ) + { + bits |= bits_HINT_NODE_VISIBLE; + } + + if ( strstr( "nearest", lowercase ) ) + { + bits |= bits_HINT_NODE_NEAREST; + } + + if ( strstr( "random", lowercase ) ) + { + bits |= bits_HINT_NODE_RANDOM; + } + + // Can't be nearest and random, defer to nearest + if ( ( bits & bits_HINT_NODE_NEAREST ) && + ( bits & bits_HINT_NODE_RANDOM ) ) + { + // Remove random + bits &= ~bits_HINT_NODE_RANDOM; + + DevMsg( "HINTFLAGS:%s, inconsistent, the nearest node is never a random hint node, treating as nearest request!\n", + token ); + } + + return bits; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CAI_Hint *CAI_HintManager::GetFirstHint( AIHintIter_t *pIter ) +{ + if ( !gm_AllHints.Count() ) + { + *pIter = (AIHintIter_t)gm_AllHints.InvalidIndex(); + return NULL; + } + *pIter = (AIHintIter_t)0; + return gm_AllHints[0]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CAI_Hint *CAI_HintManager::GetNextHint( AIHintIter_t *pIter ) +{ + if ( (int)*pIter != gm_AllHints.InvalidIndex() ) + { + int i = ( (int)*pIter ) + 1; + if ( gm_AllHints.Count() <= i ) + { + *pIter = (AIHintIter_t)gm_AllHints.InvalidIndex(); + return NULL; + } + *pIter = (AIHintIter_t)i; + return gm_AllHints[i]; + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_HintManager::DumpHints() +{ + AIHintIter_t iter; + CAI_Hint *pCurHint = GetFirstHint( &iter ); + while (pCurHint) + { + const Vector &v = pCurHint->GetAbsOrigin(); + Msg( "(%.1f, %.1f, %.1f) -- Node ID: %d; WC id %d; type %d\n", + v.x, v.y, v.z, + pCurHint->GetNodeId(), + pCurHint->GetWCId(), + pCurHint->HintType() ); + pCurHint = GetNextHint( &iter ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_HintManager::ValidateHints() +{ +#ifdef _DEBUG + int nTyped = 0; + FOR_EACH_VEC( gm_AllHints, i ) + { + Assert( dynamic_cast<CAI_Hint *>(gm_AllHints[i]) != NULL ); + } + + for ( int i = gm_TypedHints.FirstInorder(); i != gm_TypedHints.InvalidIndex(); i = gm_TypedHints.NextInorder( i ) ) + { + FOR_EACH_VEC( gm_TypedHints[i], j ) + { + nTyped++; + Assert( dynamic_cast<CAI_Hint *>(gm_TypedHints[i][j]) != NULL ); + } + } + + Assert( gm_AllHints.Count() == nTyped ); +#endif +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CAI_HintManager::DrawHintOverlays(float flDrawDuration) +{ + int c = gm_AllHints.Count(); + for ( int i = 0; i < c; ++i ) + { + CAI_Hint *pHint = gm_AllHints[ i ]; + int r = 0; + int g = 0; + int b = 255; + Vector vHintPos; + + if (pHint->m_NodeData.nNodeID != NO_NODE) + { + vHintPos = g_pBigAINet->GetNode(pHint->m_NodeData.nNodeID)->GetPosition(g_pAINetworkManager->GetEditOps()->m_iHullDrawNum); + } + else + { + vHintPos = pHint->GetAbsOrigin(); + } + + if ( pHint->GetNodeId() != NO_NODE ) + NDebugOverlay::Text( vHintPos + Vector(0,6,8), CFmtStr("(%d), (%d)", pHint->HintType(), pHint->GetNodeId()), true, flDrawDuration ); + else + NDebugOverlay::Text( vHintPos + Vector(0,6,8), CFmtStr("(%d)", pHint->HintType()), true, flDrawDuration ); + + // If node is currently locked + if (pHint->m_NodeData.iDisabled) + { + r = 100; + g = 100; + b = 100; + } + else if (pHint->m_hHintOwner != NULL) + { + r = 255; + g = 0; + b = 0; + + CBaseEntity* pOwner = pHint->User(); + if (pOwner) + { + char owner[255]; + Q_strncpy(owner,pOwner->GetDebugName(),sizeof(owner)); + Vector loc = vHintPos; + loc.x+=6; + loc.y+=6; + loc.z+=6; + NDebugOverlay::Text( loc, owner, true, flDrawDuration ); + NDebugOverlay::Line( vHintPos, pOwner->WorldSpaceCenter(), 128, 128, 128, false, 0); + } + } + else if (pHint->IsLocked()) + { + r = 200; + g = 150; + b = 10; + } + + NDebugOverlay::Box(vHintPos, Vector(-3,-3,-3), Vector(3,3,3), r,g,b,0,flDrawDuration); + + // Draw line in facing direction + Vector offsetDir = 12.0 * Vector(cos(DEG2RAD(pHint->Yaw())),sin(DEG2RAD(pHint->Yaw())),0); + NDebugOverlay::Line(vHintPos, vHintPos+offsetDir, r,g,b,false,flDrawDuration); + } +} + +//################################################################## +// > CAI_Hint +//################################################################## +LINK_ENTITY_TO_CLASS( ai_hint, CAI_Hint ); + +BEGIN_DATADESC( CAI_Hint ) + + DEFINE_EMBEDDED( m_NodeData ), + // m_nTargetNodeID (reset on load) + + DEFINE_FIELD( m_hHintOwner, FIELD_EHANDLE), + DEFINE_FIELD( m_flNextUseTime, FIELD_TIME), + DEFINE_FIELD( m_vecForward, FIELD_VECTOR), + DEFINE_KEYFIELD( m_nodeFOV, FIELD_FLOAT, "nodeFOV" ), + + DEFINE_THINKFUNC( EnableThink ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "EnableHint", InputEnableHint ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableHint", InputDisableHint ), + + // Outputs + DEFINE_OUTPUT( m_OnNPCStartedUsing, "OnNPCStartedUsing" ), + DEFINE_OUTPUT( m_OnNPCStoppedUsing, "OnNPCStoppedUsing" ), + +END_DATADESC( ); + +//------------------------------------------------------------------------------ +// Purpose : +//------------------------------------------------------------------------------ +void CAI_Hint::InputEnableHint( inputdata_t &inputdata ) +{ + m_NodeData.iDisabled = false; +} + +//------------------------------------------------------------------------------ +// Purpose : +//------------------------------------------------------------------------------ +void CAI_Hint::InputDisableHint( inputdata_t &inputdata ) +{ + m_NodeData.iDisabled = true; +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CAI_Hint::Spawn( void ) +{ + // Cache off the forward vector + GetVectors( &m_vecForward, NULL, NULL ); + + if( m_nodeFOV != 360 ) + { + // As a micro-optimization, leave the FOV at 360 to save us + // a dot product later when checking node FOV. + m_nodeFOV = cos( DEG2RAD(m_nodeFOV/2) ); + } + + SetSolid( SOLID_NONE ); +} + +void CAI_Hint::Activate() +{ + BaseClass::Activate(); + CAI_HintManager::AddHint( this ); +} + +void CAI_Hint::UpdateOnRemove( void ) +{ + CAI_HintManager::RemoveHint( this ); + BaseClass::UpdateOnRemove(); +} + +//------------------------------------------------------------------------------ +// Purpose : If connected to a node returns node position, otherwise +// returns local hint position +// +// NOTE: Assumes not using multiple AI networks +// Input : +// Output : +//------------------------------------------------------------------------------ +void CAI_Hint::GetPosition(CBaseCombatCharacter *pBCC, Vector *vPosition) +{ + if ( m_NodeData.nNodeID != NO_NODE ) + { + *vPosition = g_pBigAINet->GetNodePosition( pBCC, m_NodeData.nNodeID ); + } + else + { + *vPosition = GetAbsOrigin(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hull - +// *vPosition - +//----------------------------------------------------------------------------- +void CAI_Hint::GetPosition( Hull_t hull, Vector *vPosition ) +{ + if ( m_NodeData.nNodeID != NO_NODE ) + { + *vPosition = g_pBigAINet->GetNodePosition( hull, m_NodeData.nNodeID ); + } + else + { + *vPosition = GetAbsOrigin(); + } +} + +//------------------------------------------------------------------------------ +// Purpose : If connected to a node returns node direction, otherwise +// returns local hint direction +// +// NOTE: Assumes not using multiple AI networks +// Input : +// Output : +//------------------------------------------------------------------------------ +Vector CAI_Hint::GetDirection( ) +{ + return UTIL_YawToVector( Yaw() ); +} + +//------------------------------------------------------------------------------ +// Purpose : If connected to a node returns node yaw, otherwise +// returns local hint yaw +// +// NOTE: Assumes not using multiple AI networks +// Input : +// Output : +//------------------------------------------------------------------------------ +float CAI_Hint::Yaw(void) +{ + if (m_NodeData.nNodeID != NO_NODE) + { + return g_pBigAINet->GetNodeYaw(m_NodeData.nNodeID ); + } + else + { + return GetLocalAngles().y; + } +} + + + +//------------------------------------------------------------------------------ +// Purpose : Returns if this is something that's interesting to look at +// +// NOTE: Assumes not using multiple AI networks +// Input : +// Output : +//------------------------------------------------------------------------------ +bool CAI_Hint::IsViewable(void) +{ + if (m_NodeData.iDisabled) + { + return false; + } + + switch( HintType() ) + { + case HINT_WORLD_VISUALLY_INTERESTING: + case HINT_WORLD_VISUALLY_INTERESTING_DONT_AIM: + case HINT_WORLD_VISUALLY_INTERESTING_STEALTH: + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CAI_Hint::IsInNodeFOV( CBaseEntity *pOther ) +{ + if( m_nodeFOV == 360 ) + { + return true; + } + +#if 0 + NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + m_vecForward * 16, 255, 255, 0, false, 1 ); +#endif + + Vector vecToNPC = pOther->GetAbsOrigin() - GetAbsOrigin(); + VectorNormalize( vecToNPC ); + float flDot = DotProduct( vecToNPC, m_vecForward ); + + if( flDot > m_nodeFOV ) + { +#if 0 + NDebugOverlay::Line( GetAbsOrigin(), pOther->GetAbsOrigin(), 0, 255, 0, false, 1 ); +#endif + return true; + } + +#if 0 + NDebugOverlay::Line( GetAbsOrigin(), pOther->GetAbsOrigin(), 255, 0, 0, false, 1 ); +#endif + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Locks the node for use by an AI for hints +// Output : Returns true if the node was available for locking, false on failure. +//----------------------------------------------------------------------------- +bool CAI_Hint::Lock( CBaseEntity* pNPC ) +{ + if ( m_hHintOwner != pNPC && m_hHintOwner != NULL ) + return false; + m_hHintOwner = pNPC; + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Unlocks the node, making it available for hint use by other AIs. +// after the given delay time +//----------------------------------------------------------------------------- +void CAI_Hint::Unlock( float delay ) +{ + m_hHintOwner = NULL; + m_flNextUseTime = gpGlobals->curtime + delay; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true is hint node is open for use +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CAI_Hint::IsLockedBy( CBaseEntity *pNPC ) +{ + return (m_hHintOwner == pNPC); +}; + +//----------------------------------------------------------------------------- +// Purpose: Returns true is hint node is open for use +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CAI_Hint::IsLocked( void ) +{ + if (m_NodeData.iDisabled) + { + return true; + } + + if (gpGlobals->curtime < m_flNextUseTime) + { + return true; + } + + if (m_hHintOwner != NULL) + { + return true; + } + return false; +}; + +//----------------------------------------------------------------------------- +// Purpose: Return true if pTestHint passes the criteria specified in hintCriteria +//----------------------------------------------------------------------------- +bool CAI_Hint::HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hintCriteria, const Vector &position, float *flNearestDistance, bool bIgnoreLock, bool bIgnoreHintType ) +{ + // Cannot be locked + if ( !bIgnoreLock && IsLocked() ) + { + REPORTFAILURE( "Node is locked." ); + return false; + } + + if ( !bIgnoreHintType && !hintCriteria.MatchesHintType( HintType() ) ) + { + return false; + } + + if ( GetMinState() > NPC_STATE_IDLE || GetMaxState() < NPC_STATE_COMBAT ) + { + if ( pNPC && ( pNPC->GetState() < GetMinState() || pNPC->GetState() > GetMaxState() ) ) + { + REPORTFAILURE( "NPC not in correct state." ); + return false; + } + } + + // See if we're filtering by group name + if ( hintCriteria.GetGroup() != NULL_STRING ) + { + AssertIsValidString( GetGroup() ); + AssertIsValidString( hintCriteria.GetGroup() ); + if ( GetGroup() == NULL_STRING || GetGroup() != hintCriteria.GetGroup() ) + { + Assert(GetGroup() == NULL_STRING || strcmp( STRING(GetGroup()), STRING(hintCriteria.GetGroup())) != 0 ); + REPORTFAILURE( "Doesn't match NPC hint group." ); + return false; + } + } + + // If we're watching for include zones, test it + if ( ( hintCriteria.HasIncludeZones() ) && ( hintCriteria.InIncludedZone( GetAbsOrigin() ) == false ) ) + { + REPORTFAILURE( "Not inside include zones." ); + return false; + } + + // If we're watching for exclude zones, test it + if ( ( hintCriteria.HasExcludeZones() ) && ( hintCriteria.InExcludedZone( GetAbsOrigin() ) ) ) + { + REPORTFAILURE( "Inside exclude zones." ); + return false; + } + + // See if the class handles this hint type + if ( ( pNPC != NULL ) && ( pNPC->FValidateHintType( this ) == false ) ) + { + REPORTFAILURE( "NPC doesn't know how to handle that type." ); + return false; + } + + if ( hintCriteria.HasFlag(bits_HINT_NPC_IN_NODE_FOV) ) + { + if ( pNPC == NULL ) + { + AssertMsg(0,"Hint node attempted to verify NPC in node FOV without NPC!\n"); + } + else + { + if( !IsInNodeFOV(pNPC) ) + { + REPORTFAILURE( "NPC Not in hint's FOV" ); + return false; + } + } + } + + if ( hintCriteria.HasFlag( bits_HINT_NODE_IN_AIMCONE ) ) + { + if ( pNPC == NULL ) + { + AssertMsg( 0, "Hint node attempted to find node in aimcone without specifying NPC!\n" ); + } + else + { + if( !pNPC->FInAimCone( GetAbsOrigin() ) ) + { + REPORTFAILURE( "Hint isn't in NPC's aimcone" ); + return false; + } + } + } + + if ( hintCriteria.HasFlag( bits_HINT_NODE_IN_VIEWCONE ) ) + { + if ( pNPC == NULL ) + { + AssertMsg( 0, "Hint node attempted to find node in viewcone without specifying NPC!\n" ); + } + else + { + if( !pNPC->FInViewCone( this ) ) + { + REPORTFAILURE( "Hint isn't in NPC's viewcone" ); + return false; + } + } + } + + if ( hintCriteria.HasFlag( bits_HINT_NOT_CLOSE_TO_ENEMY ) ) + { + if ( pNPC == NULL ) + { + AssertMsg( 0, "Hint node attempted to find node not close to enemy without specifying NPC!\n" ); + } + else + { + if( pNPC->GetEnemy() ) + { + float flDistHintToEnemySqr = GetAbsOrigin().DistToSqr( pNPC->GetEnemy()->GetAbsOrigin() ) ; + + if( flDistHintToEnemySqr < Square( 30.0f * 12.0f ) ) + { + REPORTFAILURE( "Hint takes NPC close to Enemy" ); + return false; + } + } + } + } + + { + AI_PROFILE_SCOPE( HINT_FVisible ); + // See if we're requesting a visible node + if ( hintCriteria.HasFlag( bits_HINT_NODE_VISIBLE ) ) + { + if ( pNPC == NULL ) + { + //NOTENOTE: If you're hitting this, you've asked for a visible node without specifing an NPC! + AssertMsg( 0, "Hint node attempted to find visible node without specifying NPC!\n" ); + } + else + { + if( m_NodeData.nNodeID == NO_NODE ) + { + // This is just an info_hint, not a node. + if( !pNPC->FVisible( this ) ) + { + REPORTFAILURE( "Hint isn't visible to NPC." ); + return false; + } + } + else + { + // This hint associated with a node. + trace_t tr; + Vector vHintPos; + GetPosition(pNPC,&vHintPos); + AI_TraceLine ( pNPC->EyePosition(), vHintPos + pNPC->GetViewOffset(), MASK_NPCSOLID_BRUSHONLY, pNPC, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction != 1.0f ) + { + REPORTFAILURE( "Node isn't visible to NPC." ); + return false; + } + } + } + } + } + + // Check for clear if requested + if ( hintCriteria.HasFlag( bits_HINT_NODE_CLEAR ) ) + { + if ( pNPC == NULL ) + { + //NOTENOTE: If you're hitting this, you've asked for a clear node without specifing an NPC! + AssertMsg( 0, "Hint node attempted to find clear node without specifying NPC!\n" ); + } + else + { + trace_t tr; + // Can my bounding box fit there? + AI_TraceHull ( GetAbsOrigin(), GetAbsOrigin(), pNPC->WorldAlignMins(), pNPC->WorldAlignMaxs(), + MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction != 1.0 ) + { + REPORTFAILURE( "Node isn't clear." ); + return false; + } + } + } + + // See if this is our next, closest node + if ( hintCriteria.HasFlag( bits_HINT_NODE_NEAREST ) ) + { + Assert( flNearestDistance ); + + // Calculate our distance + float distance = (GetAbsOrigin() - position).Length(); + + // Must be closer than the current best + if ( distance > *flNearestDistance ) + { + REPORTFAILURE( "Not the nearest node." ); + return false; + } + + // Remember the distance + *flNearestDistance = distance; + } + + if ( hintCriteria.HasFlag(bits_HINT_HAS_LOS_TO_PLAYER|bits_HAS_EYEPOSITION_LOS_TO_PLAYER) ) + { + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + + if( pPlayer != NULL ) + { + Vector vecDest = GetAbsOrigin(); + + if( hintCriteria.HasFlag(bits_HAS_EYEPOSITION_LOS_TO_PLAYER) ) + { + vecDest += pNPC->GetDefaultEyeOffset(); + } + + if( !pPlayer->FVisible(vecDest) ) + { + REPORTFAILURE( "Do not have LOS to player" ); + return false; + } + } + } + + // Must either be visible or not if requested + if ( hintCriteria.HasFlag( bits_HINT_NODE_NOT_VISIBLE_TO_PLAYER|bits_HINT_NODE_VISIBLE_TO_PLAYER ) ) + { + bool bWasSeen = false; + // Test all potential seers + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); + + if ( pPlayer ) + { + // Only spawn if the player's looking away from me + Vector vLookDir = pPlayer->EyeDirection3D(); + Vector vTargetDir = GetAbsOrigin() - pPlayer->EyePosition(); + VectorNormalize(vTargetDir); + + float fDotPr = DotProduct(vLookDir,vTargetDir); + if ( fDotPr > 0 ) + { + trace_t tr; + UTIL_TraceLine( pPlayer->EyePosition(), GetAbsOrigin(), MASK_SOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, &tr); + + if ( tr.fraction == 1.0 ) + { + if ( hintCriteria.HasFlag( bits_HINT_NODE_NOT_VISIBLE_TO_PLAYER ) ) + { + REPORTFAILURE( "Node is visible to player." ); + return false; + } + bWasSeen = true; + } + } + } + } + + if ( !bWasSeen && hintCriteria.HasFlag( bits_HINT_NODE_VISIBLE_TO_PLAYER ) ) + { + REPORTFAILURE( "Node isn't visible to player." ); + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Input : +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CAI_Hint::DrawDebugTextOverlays(void) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + Q_snprintf(tempstr,sizeof(tempstr),"%s (%i)", GetHintTypeDescription( HintType() ), HintType()); + EntityText(text_offset,tempstr,0); + text_offset++; + Q_snprintf(tempstr,sizeof(tempstr),"delay %f", MAX( 0.0f, m_flNextUseTime - gpGlobals->curtime ) ) ; + EntityText(text_offset,tempstr,0); + text_offset++; + + if ( m_NodeData.iDisabled ) + { + Q_snprintf(tempstr,sizeof(tempstr),"DISABLED" ); + EntityText(text_offset,tempstr,0); + text_offset++; + } + + } + return text_offset; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input : +// Output : +//----------------------------------------------------------------------------- +CAI_Hint::CAI_Hint(void) +{ + m_flNextUseTime = 0; + m_nTargetNodeID = NO_NODE; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +// Input : +// Output : +//----------------------------------------------------------------------------- +CAI_Hint::~CAI_Hint(void) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Sometimes FValidateHint, etc. will want to examine the underlying node to +// see if it's truly suitable ( e.g., in the same air/ground network of nodes? ) +// Output : C_AINode * +//----------------------------------------------------------------------------- +CAI_Node *CAI_Hint::GetNode( void ) +{ + if ( m_NodeData.nNodeID != NO_NODE ) + { + return g_pBigAINet->GetNode( m_NodeData.nNodeID, false ); + } + return NULL; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_Hint::DisableForSeconds( float flSeconds ) +{ + Unlock( flSeconds ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_Hint::EnableThink() +{ + SetDisabled( false ); + SetThink( NULL ); +} + +void CAI_Hint::FixupTargetNode() +{ + if ( m_NodeData.nTargetWCNodeID != -1 ) + m_nTargetNodeID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( m_NodeData.nTargetWCNodeID ); + else + m_nTargetNodeID = NO_NODE; +} + +void CAI_Hint::OnRestore() +{ + BaseClass::OnRestore(); + + m_NodeData.nNodeID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( m_NodeData.nWCNodeID ); + FixupTargetNode(); + + CAI_Node *pNode = GetNode(); + + if ( !pNode ) + { + if ( m_NodeData.nWCNodeID > 0 ) + DevMsg("Warning: AI hint has incorrect or no AI node\n"); + } + else + { + m_NodeData.vecPosition = pNode->GetOrigin(); + Teleport( &m_NodeData.vecPosition, NULL, NULL ); + pNode->SetHint( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_Hint::NPCStartedUsing( CAI_BaseNPC *pNPC ) +{ + m_OnNPCStartedUsing.Set( pNPC, pNPC, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_Hint::NPCStoppedUsing( CAI_BaseNPC *pNPC ) +{ + m_OnNPCStoppedUsing.Set( pNPC, pNPC, this ); +} + + +CON_COMMAND(ai_dump_hints, "") +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + CAI_HintManager::ValidateHints(); + CAI_HintManager::DumpHints(); +} + + +//----------------------------------------------------------------------------- +// +// hints - these MUST coincide with the HINTS listed under Hint_e +// +//----------------------------------------------------------------------------- +struct hinttypedescs_t +{ + Hint_e iType; + const char *pszDesc; +}; +hinttypedescs_t g_pszHintDescriptions[] = +{ + { HINT_NONE, "None" }, + //{ HINT_NOT_USED_WORLD_DOOR, "Obsolete / Unused" }, + { HINT_WORLD_WINDOW, "World: Window" }, + //{ HINT_NOT_USED_WORLD_BUTTON, "Obsolete / Unused" }, + //{ HINT_NOT_USED_WORLD_MACHINERY, "Obsolete / Unused" }, + //{ HINT_NOT_USED_WORLD_LEDGE, "Obsolete / Unused" }, + //{ HINT_NOT_USED_WORLD_LIGHT_SOURCE, "Obsolete / Unused" }, + //{ HINT_NOT_USED_WORLD_HEAT_SOURCE, "Obsolete / Unused" }, + //{ HINT_NOT_USED_WORLD_BLINKING_LIGHT, "Obsolete / Unused" }, + //{ HINT_NOT_USED_WORLD_BRIGHT_COLORS, "Obsolete / Unused" }, + //{ HINT_NOT_USED_WORLD_HUMAN_BLOOD, "Obsolete / Unused" }, + //{ HINT_NOT_USED_WORLD_ALIEN_BLOOD, "Obsolete / Unused" }, + + { HINT_WORLD_WORK_POSITION, "Act Busy" }, + { HINT_WORLD_VISUALLY_INTERESTING, "World: Visually Interesting" }, + { HINT_WORLD_VISUALLY_INTERESTING_DONT_AIM, "World: Visually Interesting (Don't Aim)" }, + { HINT_WORLD_INHIBIT_COMBINE_MINES, "World: Inhibit Combine Mines" }, + { HINT_WORLD_VISUALLY_INTERESTING_STEALTH, "World: Visually Interesting (Stealth)" }, + + { HINT_TACTICAL_COVER_MED, "Tactical: Cover Medium" }, + { HINT_TACTICAL_COVER_LOW, "Tactical: Cover Low" }, + { HINT_TACTICAL_SPAWN, "Tactical: Spawn" }, + { HINT_TACTICAL_PINCH, "Tactical: Pinch" }, + //{ HINT_NOT_USED_TACTICAL_GUARD, "Obsolete / Unused" }, + { HINT_TACTICAL_ENEMY_DISADVANTAGED, "Tactical: Enemy Disadvantage" }, + //{ HINT_NOT_USED_HEALTH_KIT, "Obsolete / Unused" }, + + //{ HINT_NOT_USED_URBAN_STREETCORNER, "Obsolete / Unused" }, + //{ HINT_NOT_USED_URBAN_STREETLAMP, "Obsolete / Unused" }, + //{ HINT_NOT_USED_URBAN_DARK_SPOT, "Obsolete / Unused" }, + //{ HINT_NOT_USED_URBAN_POSTER, "Obsolete / Unused" }, + //{ HINT_NOT_USED_URBAN_SHELTER, "Obsolete / Unused" }, + + //{ HINT_NOT_USED_ASSASSIN_SECLUDED, "Obsolete / Unused" }, + //{ HINT_NOT_USED_ASSASSIN_RAFTERS, "Obsolete / Unused" }, + //{ HINT_NOT_USED_ASSASSIN_GROUND, "Obsolete / Unused" }, + //{ HINT_NOT_USED_ASSASSIN_MONKEYBARS, "Obsolete / Unused" }, + + { HINT_ANTLION_BURROW_POINT, "Antlion: Burrow Point" }, + { HINT_ANTLION_THUMPER_FLEE_POINT, "Antlion: Thumper Flee Point" }, + + //{ HINT_HEADCRAB_BURROW_POINT, "Obsolete / Unused" }, + + //{ HINT_NOT_USED_ROLLER_PATROL_POINT, "Obsolete / Unused" }, + //{ HINT_NOT_USED_ROLLER_CLEANUP_POINT, "Obsolete / Unused" }, + + //{ HINT_NOT_USED_PSTORM_ROCK_SPAWN, "Obsolete / Unused" }, + + { HINT_CROW_FLYTO_POINT, "Crow: Flyto Point" }, + + //{ HINT_BUG_PATROL_POINT, "Obsolete / Unused" }, + + { HINT_FOLLOW_WAIT_POINT, "Follow: Wait Point" }, + { HINT_JUMP_OVERRIDE, "Jump Override" }, + { HINT_PLAYER_SQUAD_TRANSITON_POINT, "Squad Transition Point" }, + { HINT_NPC_EXIT_POINT, "Act Busy: Exit Point" }, + { HINT_STRIDER_NODE, "Strider" }, + + { HINT_PLAYER_ALLY_MOVE_AWAY_DEST, "Ally MoveAway Point" }, + + { HINT_HL1_WORLD_MACHINERY, "HL1: World: Machinery" }, + { HINT_HL1_WORLD_BLINKING_LIGHT, "HL1: World: Blinking Light" }, + { HINT_HL1_WORLD_HUMAN_BLOOD, "HL1: World: Human Blood" }, + { HINT_HL1_WORLD_ALIEN_BLOOD, "HL1: World: Alien Blood" }, + + { HINT_CSTRIKE_HOSTAGE_ESCAPE, "CS Port: Hostage Escape" }, +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *GetHintTypeDescription( Hint_e iHintType ) +{ + for ( int i = 0; i < ARRAYSIZE(g_pszHintDescriptions); i++ ) + { + if ( g_pszHintDescriptions[i].iType == iHintType ) + return g_pszHintDescriptions[i].pszDesc; + } + + return "Obsolete / Unused"; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *GetHintTypeDescription( CAI_Hint *pHint ) +{ + return GetHintTypeDescription( pHint->HintType() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Debug command to drop hints into the world +//----------------------------------------------------------------------------- +void CC_ai_drop_hint( const CCommand &args ) +{ + CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() ); + if ( !pPlayer ) + return; + + if ( args.ArgC() < 2 ) + { + Msg("Invalid hint type specified. Format: ai_drop_hint <hint type>\nValid hint types:\n"); + + for ( int i = 0; i < ARRAYSIZE(g_pszHintDescriptions); i++ ) + { + Msg("%d : %s\n", g_pszHintDescriptions[i].iType, g_pszHintDescriptions[i].pszDesc ); + } + return; + } + + HintNodeData nodeData; + nodeData.strEntityName = MAKE_STRING("ai_drop_hint"); + nodeData.vecPosition = pPlayer->EyePosition(); + nodeData.nHintType = atoi( args[1] ); + nodeData.nNodeID = NO_NODE; + nodeData.strGroup = NULL_STRING; + nodeData.iDisabled = false; + nodeData.iszActivityName = NULL_STRING; + nodeData.fIgnoreFacing = HIF_DEFAULT; + nodeData.minState = NPC_STATE_IDLE; + nodeData.maxState = NPC_STATE_COMBAT; + CAI_Hint *pHint = CAI_HintManager::CreateHint( &nodeData, NULL ); + if ( pHint ) + { + ((CBaseEntity *)pHint)->Activate(); + pHint->KeyValue( "nodeFOV", "360" ); + pHint->m_debugOverlays |= (OVERLAY_TEXT_BIT | OVERLAY_BBOX_BIT); + } +} +ConCommand ai_drop_hint( "ai_drop_hint", CC_ai_drop_hint, "Drop an ai_hint at the player's current eye position.", FCVAR_CHEAT ); |