Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

215 lines
7.7 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: AI system that makes NPCs verbally respond to game events
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "ai_eventresponse.h"
  8. #include "ai_basenpc.h"
  9. ConVar ai_debug_eventresponses( "ai_debug_eventresponses", "0", FCVAR_NONE, "Set to 1 to see all NPC response events trigger, and which NPCs choose to respond to them." );
  10. //-----------------------------------------------------------------------------
  11. // Purpose:
  12. //-----------------------------------------------------------------------------
  13. CNPCEventResponseSystem g_NPCEventResponseSystem( "CNPCEventResponseSystem" );
  14. CNPCEventResponseSystem *NPCEventResponse()
  15. {
  16. return &g_NPCEventResponseSystem;
  17. }
  18. //-----------------------------------------------------------------------------
  19. // Purpose:
  20. //-----------------------------------------------------------------------------
  21. void CNPCEventResponseSystem::LevelInitPreEntity( void )
  22. {
  23. m_ActiveEvents.Purge();
  24. m_flNextEventPoll = 0;
  25. }
  26. //-----------------------------------------------------------------------------
  27. // Purpose:
  28. //-----------------------------------------------------------------------------
  29. void CNPCEventResponseSystem::TriggerEvent( const char *pResponse, bool bForce, bool bCancelScript )
  30. {
  31. m_flNextEventPoll = gpGlobals->curtime;
  32. // Find the event by name
  33. int iIndex = m_ActiveEvents.Find( pResponse );
  34. if ( iIndex == m_ActiveEvents.InvalidIndex() )
  35. {
  36. storedevent_t newEvent;
  37. newEvent.flEventTime = gpGlobals->curtime;
  38. newEvent.flNextResponseTime = 0;
  39. newEvent.bForce = bForce;
  40. newEvent.bCancelScript = bCancelScript;
  41. newEvent.bPreventExpiration = false;
  42. m_ActiveEvents.Insert( pResponse, newEvent );
  43. if ( ai_debug_eventresponses.GetBool() )
  44. {
  45. Msg( "NPCEVENTRESPONSE: (%.2f) Trigger fired for event named: %s\n", gpGlobals->curtime, pResponse );
  46. }
  47. }
  48. else
  49. {
  50. // Update the trigger time
  51. m_ActiveEvents[iIndex].flEventTime = gpGlobals->curtime;
  52. m_ActiveEvents[iIndex].bForce = bForce;
  53. m_ActiveEvents[iIndex].bCancelScript = bCancelScript;
  54. if ( ai_debug_eventresponses.GetBool() )
  55. {
  56. Msg( "NPCEVENTRESPONSE: (%.2f) Trigger resetting already-active event firing named: %s\n", gpGlobals->curtime, pResponse );
  57. }
  58. }
  59. }
  60. //-----------------------------------------------------------------------------
  61. // Purpose:
  62. //-----------------------------------------------------------------------------
  63. void CNPCEventResponseSystem::FrameUpdatePreEntityThink()
  64. {
  65. if ( !m_ActiveEvents.Count() || !AI_IsSinglePlayer() || !UTIL_GetLocalPlayer() )
  66. return;
  67. if ( m_flNextEventPoll > gpGlobals->curtime )
  68. return;
  69. m_flNextEventPoll = gpGlobals->curtime + 0.2;
  70. // Move through all events, removing expired ones and finding NPCs for active ones.
  71. for ( int i = m_ActiveEvents.First(); i != m_ActiveEvents.InvalidIndex(); )
  72. {
  73. float flTime = m_ActiveEvents[i].flEventTime;
  74. const char *pResponse = m_ActiveEvents.GetElementName(i);
  75. // Save off the next index so we can safely remove this one
  76. int iNext = m_ActiveEvents.Next(i);
  77. // Should it have expired by now?
  78. if ( !m_ActiveEvents[i].bPreventExpiration && (flTime + NPCEVENTRESPONSE_GIVEUP_TIME) < gpGlobals->curtime )
  79. {
  80. if ( ai_debug_eventresponses.GetBool() )
  81. {
  82. Msg( "NPCEVENTRESPONSE: (%.2f) Removing expired event named: %s\n", gpGlobals->curtime, pResponse );
  83. }
  84. m_ActiveEvents.RemoveAt(i);
  85. }
  86. else if ( m_ActiveEvents[i].flNextResponseTime < gpGlobals->curtime )
  87. {
  88. // If we've fired once, and our current event should expire now, then expire.
  89. if ( m_ActiveEvents[i].bPreventExpiration && (flTime + NPCEVENTRESPONSE_GIVEUP_TIME) < gpGlobals->curtime )
  90. {
  91. if ( ai_debug_eventresponses.GetBool() )
  92. {
  93. Msg( "NPCEVENTRESPONSE: (%.2f) Removing expired fired event named: %s\n", gpGlobals->curtime, pResponse );
  94. }
  95. m_ActiveEvents.RemoveAt(i);
  96. }
  97. else
  98. {
  99. float flNearestDist = NPCEVENTRESPONSE_DISTANCE_SQR;
  100. CAI_BaseNPC *pNearestNPC = NULL;
  101. Vector vecPlayerCenter = UTIL_GetLocalPlayer()->WorldSpaceCenter();
  102. // Try and find the nearest NPC to the player
  103. CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
  104. for ( int j = 0; j < g_AI_Manager.NumAIs(); j++ )
  105. {
  106. if ( ppAIs[j]->CanRespondToEvent( pResponse ))
  107. {
  108. float flDistToPlayer = ( vecPlayerCenter - ppAIs[j]->WorldSpaceCenter()).LengthSqr();
  109. if ( flDistToPlayer < flNearestDist )
  110. {
  111. flNearestDist = flDistToPlayer;
  112. pNearestNPC = ppAIs[j];
  113. }
  114. }
  115. }
  116. // Found one?
  117. if ( pNearestNPC )
  118. {
  119. if ( pNearestNPC->RespondedTo( pResponse, m_ActiveEvents[i].bForce, m_ActiveEvents[i].bCancelScript ) )
  120. {
  121. // Don't remove the response yet. Leave it around until the refire time has expired.
  122. // This stops repeated firings of the same concept from spamming the NPCs.
  123. m_ActiveEvents[i].bPreventExpiration = true;
  124. m_ActiveEvents[i].flNextResponseTime = gpGlobals->curtime + NPCEVENTRESPONSE_REFIRE_TIME;
  125. if ( ai_debug_eventresponses.GetBool() )
  126. {
  127. Msg( "NPCEVENTRESPONSE: (%.2f) Event '%s' responded to by NPC '%s'. Refire available at: %.2f\n", gpGlobals->curtime, pResponse, pNearestNPC->GetDebugName(), m_ActiveEvents[i].flNextResponseTime );
  128. }
  129. // Don't issue multiple responses at once
  130. return;
  131. }
  132. }
  133. }
  134. }
  135. i = iNext;
  136. }
  137. }
  138. //---------------------------------------------------------------------------------------------
  139. // Entity version for mapmaker to hook into the system
  140. //---------------------------------------------------------------------------------------------
  141. class CNPCEventResponseSystemEntity : public CBaseEntity
  142. {
  143. DECLARE_CLASS( CNPCEventResponseSystemEntity, CBaseEntity );
  144. public:
  145. DECLARE_DATADESC();
  146. void Spawn();
  147. void InputTriggerResponseEvent( inputdata_t &inputdata );
  148. void InputForceTriggerResponseEvent( inputdata_t &inputdata );
  149. void InputForceTriggerResponseEventNoCancel( inputdata_t &inputdata );
  150. };
  151. LINK_ENTITY_TO_CLASS( ai_npc_eventresponsesystem, CNPCEventResponseSystemEntity );
  152. BEGIN_DATADESC( CNPCEventResponseSystemEntity )
  153. DEFINE_INPUTFUNC( FIELD_STRING, "TriggerResponseEvent", InputTriggerResponseEvent ),
  154. DEFINE_INPUTFUNC( FIELD_STRING, "ForceTriggerResponseEvent", InputForceTriggerResponseEvent ),
  155. DEFINE_INPUTFUNC( FIELD_STRING, "ForceTriggerResponseEventNoCancel", InputForceTriggerResponseEventNoCancel ),
  156. END_DATADESC()
  157. //-----------------------------------------------------------------------------
  158. // Purpose:
  159. //-----------------------------------------------------------------------------
  160. void CNPCEventResponseSystemEntity::Spawn( void )
  161. {
  162. // Invisible, non solid.
  163. AddSolidFlags( FSOLID_NOT_SOLID );
  164. AddEffects( EF_NODRAW );
  165. }
  166. //-----------------------------------------------------------------------------
  167. // Purpose:
  168. //-----------------------------------------------------------------------------
  169. void CNPCEventResponseSystemEntity::InputTriggerResponseEvent( inputdata_t &inputdata )
  170. {
  171. NPCEventResponse()->TriggerEvent( inputdata.value.String(), false, false );
  172. }
  173. //-----------------------------------------------------------------------------
  174. // Purpose:
  175. //-----------------------------------------------------------------------------
  176. void CNPCEventResponseSystemEntity::InputForceTriggerResponseEvent( inputdata_t &inputdata )
  177. {
  178. NPCEventResponse()->TriggerEvent( inputdata.value.String(), true, true );
  179. }
  180. //-----------------------------------------------------------------------------
  181. // Purpose:
  182. //-----------------------------------------------------------------------------
  183. void CNPCEventResponseSystemEntity::InputForceTriggerResponseEventNoCancel( inputdata_t &inputdata )
  184. {
  185. NPCEventResponse()->TriggerEvent( inputdata.value.String(), true, false );
  186. }