diff --git a/sp/src/game/server/ai_hint.cpp b/sp/src/game/server/ai_hint.cpp index 1f8ec4d1..2e8e90af 100644 --- a/sp/src/game/server/ai_hint.cpp +++ b/sp/src/game/server/ai_hint.cpp @@ -35,6 +35,10 @@ CHintCriteria::CHintCriteria( void ) m_strGroup = NULL_STRING; m_iFlags = 0; m_HintTypes.Purge(); +#ifdef MAPBASE // From Alien Swarm SDK + m_pfnFilter = NULL; + m_pFilterContext = NULL; +#endif } //----------------------------------------------------------------------------- @@ -1117,10 +1121,10 @@ void CAI_Hint::NPCHandleStartNav( CAI_BaseNPC *pNPC, bool bDefaultFacing ) HintIgnoreFacing_t facing = GetIgnoreFacing(); if (facing == HIF_DEFAULT) - facing = bDefaultFacing ? HIF_YES : HIF_NO; + facing = bDefaultFacing ? HIF_NO : HIF_YES; - if (facing == HIF_YES) - pNPC->GetNavigator()->SetArrivalDirection(GetDirection()); + if (facing == HIF_NO) + pNPC->GetNavigator()->SetArrivalDirection( GetDirection() ); if (HintActivityName() != NULL_STRING) { @@ -1139,6 +1143,51 @@ void CAI_Hint::NPCHandleStartNav( CAI_BaseNPC *pNPC, bool bDefaultFacing ) } } } + +//----------------------------------------------------------------------------- +// Purpose: Returns true if this hint should override a NPC's yaw even during regular AI. +//----------------------------------------------------------------------------- +bool CAI_Hint::OverridesNPCYaw( CAI_BaseNPC *pNPC ) +{ + switch (HintType()) + { + case HINT_TACTICAL_COVER_CUSTOM: + case HINT_TACTICAL_COVER_MED: + case HINT_TACTICAL_COVER_LOW: + { + if (pNPC->HasMemory( bits_MEMORY_INCOVER )) + { + // By default, don't override yaw on cover nodes unless they use custom activities. + HintIgnoreFacing_t facing = GetIgnoreFacing(); + if (facing == HIF_DEFAULT) + return ( HintActivityName() != NULL_STRING ); + + return facing == HIF_NO; + } + + break; + } + + case HINT_PLAYER_ALLY_MOVE_AWAY_DEST: + { + Vector vHintPos; + GetPosition( pNPC, &vHintPos ); + if (VectorsAreEqual( vHintPos, pNPC->GetAbsOrigin(), 0.1f )) + { + // By default, don't override yaw on move away destinations unless they use custom activities. + HintIgnoreFacing_t facing = GetIgnoreFacing(); + if (facing == HIF_DEFAULT) + return ( HintActivityName() != NULL_STRING ); + + return facing == HIF_NO; + } + + break; + } + } + + return false; +} #endif //----------------------------------------------------------------------------- @@ -1258,6 +1307,30 @@ bool CAI_Hint::HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hint return false; } +#ifdef MAPBASE + // Test against generic filter + // (From Alien Swarm SDK) + if ( !hintCriteria.PassesFilter( this ) ) + { + REPORTFAILURE( "Failed filter test" ); + return false; + } + + // (From Alien Swarm SDK) + int nRadius = GetRadius(); + if ( nRadius != 0 ) + { + // Calculate our distance + float distance = (GetAbsOrigin() - position).LengthSqr(); + + if ( distance > nRadius * nRadius ) + { + REPORTFAILURE( "NPC is not within the node's radius." ); + return false; + } + } +#endif + if ( hintCriteria.HasFlag(bits_HINT_NPC_IN_NODE_FOV) ) { if ( pNPC == NULL ) @@ -1377,10 +1450,18 @@ bool CAI_Hint::HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hint { trace_t tr; // Can my bounding box fit there? +#ifdef MAPBASE // From Alien Swarm SDK + Vector vStep( 0, 0, pNPC->StepHeight() ); + AI_TraceHull ( GetAbsOrigin() + vStep, GetAbsOrigin(), pNPC->WorldAlignMins(), pNPC->WorldAlignMaxs() - vStep, + MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction < 0.95 ) +#else AI_TraceHull ( GetAbsOrigin(), GetAbsOrigin(), pNPC->WorldAlignMins(), pNPC->WorldAlignMaxs(), MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction != 1.0 ) +#endif { REPORTFAILURE( "Node isn't clear." ); return false; @@ -1396,6 +1477,15 @@ bool CAI_Hint::HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hint // Calculate our distance float distance = (GetAbsOrigin() - position).Length(); +#ifdef MAPBASE + // Divide by hint weight + float flWeight = GetHintWeight(); + if ( flWeight != 1.0f ) + { + distance *= GetHintWeightInverse(); + } +#endif + // Must be closer than the current best if ( distance > *flNearestDistance ) { @@ -1576,6 +1666,14 @@ void CAI_Hint::OnRestore() m_NodeData.nNodeID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( m_NodeData.nWCNodeID ); FixupTargetNode(); +#ifdef MAPBASE + if (m_NodeData.flWeight != 0.0f && m_NodeData.flWeight != 1.0f) + { + // Re-invert the weight + m_NodeData.flWeightInverse = 1.0f / m_NodeData.flWeight; + } +#endif + CAI_Node *pNode = GetNode(); if ( !pNode ) @@ -1751,6 +1849,11 @@ void CC_ai_drop_hint( const CCommand &args ) nodeData.fIgnoreFacing = HIF_DEFAULT; nodeData.minState = NPC_STATE_IDLE; nodeData.maxState = NPC_STATE_COMBAT; +#ifdef MAPBASE + nodeData.nRadius = 0; // From Alien Swarm SDK + nodeData.flWeight = 1.0f; + nodeData.flWeightInverse = 1.0f; +#endif CAI_Hint *pHint = CAI_HintManager::CreateHint( &nodeData, NULL ); if ( pHint ) { diff --git a/sp/src/game/server/ai_hint.h b/sp/src/game/server/ai_hint.h index 60052ded..570f85ef 100644 --- a/sp/src/game/server/ai_hint.h +++ b/sp/src/game/server/ai_hint.h @@ -112,6 +112,13 @@ enum Hint_e // CS port hints HINT_CSTRIKE_HOSTAGE_ESCAPE = 1100, + +#ifdef MAPBASE + // Mapbase hints + // (these start at a high number to avoid potential conflicts with mod hints) + + HINT_TACTICAL_COVER_CUSTOM = 10000, // Cover node with a custom hint activity (NPCs can take cover and reload here while playing said activity) +#endif }; const char *GetHintTypeDescription( Hint_e iHintType ); const char *GetHintTypeDescription( CAI_Hint *pHint ); @@ -120,6 +127,10 @@ const char *GetHintTypeDescription( CAI_Hint *pHint ); // CHintCriteria //----------------------------------------------------------------------------- +#ifdef MAPBASE // From Alien Swarm SDK +typedef bool (*HintSearchFilterFunc_t)( void *pContext, CAI_Hint *pCandidate ); +#endif + class CHintCriteria { public: @@ -134,6 +145,11 @@ public: void SetGroup( string_t group ); string_t GetGroup( void ) const { return m_strGroup; } +#ifdef MAPBASE // From Alien Swarm SDK + void SetFilterFunc( HintSearchFilterFunc_t pfnFilter, void *pContext = NULL ) { m_pfnFilter = pfnFilter; m_pFilterContext = pContext; } + bool PassesFilter( CAI_Hint *pCandidate ) const { return (m_pfnFilter) ? (*m_pfnFilter)(m_pFilterContext, pCandidate) : true; } +#endif + int GetFirstHintType( void ) const { return m_iFirstHintType; } int GetLastHintType( void ) const { return m_iLastHintType; } bool MatchesHintType( int hintType ) const; @@ -176,6 +192,11 @@ private: zoneList_t m_zoneInclude; zoneList_t m_zoneExclude; + +#ifdef MAPBASE + HintSearchFilterFunc_t m_pfnFilter; + void * m_pFilterContext; +#endif }; class CAI_Node; @@ -310,11 +331,21 @@ public: int GetNodeId() { return m_NodeData.nNodeID; } int GetWCId() { return m_NodeData.nWCNodeID; } +#ifdef MAPBASE + int GetRadius() const { return m_NodeData.nRadius; } // From Alien Swarm SDK + + float GetHintWeight() const { return m_NodeData.flWeight; } + float GetHintWeightInverse() const { return m_NodeData.flWeightInverse; } // Used to multiply distances +#endif + bool HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hintCriteria, const Vector &position, float *flNearestDistance, bool bIgnoreLock = false, bool bIgnoreHintType = false ); bool IsInNodeFOV( CBaseEntity *pOther ); #ifdef MAPBASE void NPCHandleStartNav( CAI_BaseNPC *pNPC, bool bDefaultFacing ); + + // Returns true if this hint should override a NPC's yaw even during regular AI. + bool OverridesNPCYaw( CAI_BaseNPC *pNPC ); #endif #ifdef MAPBASE_VSCRIPT diff --git a/sp/src/game/server/ai_initutils.cpp b/sp/src/game/server/ai_initutils.cpp index 62d5c6ec..cd792798 100644 --- a/sp/src/game/server/ai_initutils.cpp +++ b/sp/src/game/server/ai_initutils.cpp @@ -173,6 +173,10 @@ BEGIN_SIMPLE_DATADESC( HintNodeData ) DEFINE_KEYFIELD( fIgnoreFacing, FIELD_INTEGER, "IgnoreFacing" ), DEFINE_KEYFIELD( minState, FIELD_INTEGER, "MinimumState" ), DEFINE_KEYFIELD( maxState, FIELD_INTEGER, "MaximumState" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( nRadius, FIELD_INTEGER, "radius" ), // From Alien Swarm SDK + DEFINE_KEYFIELD( flWeight, FIELD_FLOAT, "hintweight" ), +#endif END_DATADESC() @@ -205,6 +209,17 @@ int CNodeEnt::Spawn( const char *pMapData ) m_NodeData.minState = NPC_STATE_IDLE; if ( m_NodeData.maxState == NPC_STATE_NONE ) m_NodeData.maxState = NPC_STATE_COMBAT; +#ifdef MAPBASE + if (m_NodeData.flWeight == 0.0f) + { + m_NodeData.flWeight = 1.0f; + } + else if (m_NodeData.flWeight != 1.0f) + { + // Invert the weight so that it could be used as a direct multiplier for distances, etc. + m_NodeData.flWeightInverse = 1.0f / m_NodeData.flWeight; + } +#endif // --------------------------------------------------------------------------------- // If just a hint node (not used for navigation) just create a hint and bail // --------------------------------------------------------------------------------- diff --git a/sp/src/game/server/ai_initutils.h b/sp/src/game/server/ai_initutils.h index 22aaf5f3..70bd019c 100644 --- a/sp/src/game/server/ai_initutils.h +++ b/sp/src/game/server/ai_initutils.h @@ -42,6 +42,11 @@ struct HintNodeData HintIgnoreFacing_t fIgnoreFacing; NPC_STATE minState; NPC_STATE maxState; +#ifdef MAPBASE + int nRadius; // From Alien Swarm SDK + float flWeight; + float flWeightInverse; // Not saved +#endif int nWCNodeID; // Node ID assigned by worldcraft (not same as engine!) diff --git a/sp/src/game/server/ai_network.cpp b/sp/src/game/server/ai_network.cpp index 16d8a7fa..818324f0 100644 --- a/sp/src/game/server/ai_network.cpp +++ b/sp/src/game/server/ai_network.cpp @@ -144,6 +144,31 @@ public: int m_capabilities; // cache this }; +#ifdef MAPBASE +//------------------------------------- +// Purpose: A version of CNodeFilter which allows hints to influence the result. +//------------------------------------- +class CNodeHintFilter : public CNodeFilter +{ +public: + CNodeHintFilter( CAI_BaseNPC *pNPC, const Vector &pos ) : CNodeFilter( pNPC, pos ) {} + CNodeHintFilter( const Vector &pos ) : CNodeFilter( pos ) {} + + virtual float NodeDistanceSqr( CAI_Node &node ) + { + // Heavier hints are considered closer + if (node.GetHint() && node.GetHint()->GetHintWeight() != 1.0f) + { + return CNodeFilter::NodeDistanceSqr( node ) * node.GetHint()->GetHintWeightInverse(); + } + else + { + return CNodeFilter::NodeDistanceSqr( node ); + } + } +}; +#endif + //----------------------------------------------------------------------------- // CAI_Network //----------------------------------------------------------------------------- @@ -351,7 +376,12 @@ int CAI_Network::NearestNodeToPoint( CAI_BaseNPC *pNPC, const Vector &vecOrigin, // First get nodes distances and eliminate those that are beyond // the maximum allowed distance for local movements // --------------------------------------------------------------- +#ifdef MAPBASE + // Allow hint weight to influence supposed distance + CNodeHintFilter filter( pNPC, vecOrigin ); +#else CNodeFilter filter( pNPC, vecOrigin ); +#endif #ifdef AI_PERF_MON m_nPerfStatNN++; @@ -605,8 +635,13 @@ CAI_Node *CAI_Network::AddNode( const Vector &origin, float yaw ) CAI_Link *CAI_Network::CreateLink( int srcID, int destID, CAI_DynamicLink *pDynamicLink ) { +#ifdef MAPBASE // From Alien Swarm SDK + CAI_Node *pSrcNode = GetNode( srcID ); + CAI_Node *pDestNode = GetNode( destID ); +#else CAI_Node *pSrcNode = g_pBigAINet->GetNode( srcID ); CAI_Node *pDestNode = g_pBigAINet->GetNode( destID ); +#endif Assert( pSrcNode && pDestNode && pSrcNode != pDestNode );