2013-12-02 19:31:46 -08:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: AI system that makes NPCs verbally respond to game events
//
//=============================================================================
# include "cbase.h"
# include "ai_eventresponse.h"
# include "ai_basenpc.h"
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. " ) ;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CNPCEventResponseSystem g_NPCEventResponseSystem ( " CNPCEventResponseSystem " ) ;
CNPCEventResponseSystem * NPCEventResponse ( )
{
return & g_NPCEventResponseSystem ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPCEventResponseSystem : : LevelInitPreEntity ( void )
{
m_ActiveEvents . Purge ( ) ;
m_flNextEventPoll = 0 ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPCEventResponseSystem : : TriggerEvent ( const char * pResponse , bool bForce , bool bCancelScript )
{
m_flNextEventPoll = gpGlobals - > curtime ;
// Find the event by name
int iIndex = m_ActiveEvents . Find ( pResponse ) ;
if ( iIndex = = m_ActiveEvents . InvalidIndex ( ) )
{
storedevent_t newEvent ;
newEvent . flEventTime = gpGlobals - > curtime ;
newEvent . flNextResponseTime = 0 ;
newEvent . bForce = bForce ;
newEvent . bCancelScript = bCancelScript ;
newEvent . bPreventExpiration = false ;
m_ActiveEvents . Insert ( pResponse , newEvent ) ;
if ( ai_debug_eventresponses . GetBool ( ) )
{
Msg ( " NPCEVENTRESPONSE: (%.2f) Trigger fired for event named: %s \n " , gpGlobals - > curtime , pResponse ) ;
}
}
else
{
// Update the trigger time
m_ActiveEvents [ iIndex ] . flEventTime = gpGlobals - > curtime ;
m_ActiveEvents [ iIndex ] . bForce = bForce ;
m_ActiveEvents [ iIndex ] . bCancelScript = bCancelScript ;
if ( ai_debug_eventresponses . GetBool ( ) )
{
Msg ( " NPCEVENTRESPONSE: (%.2f) Trigger resetting already-active event firing named: %s \n " , gpGlobals - > curtime , pResponse ) ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPCEventResponseSystem : : FrameUpdatePreEntityThink ( )
{
if ( ! m_ActiveEvents . Count ( ) | | ! AI_IsSinglePlayer ( ) | | ! UTIL_GetLocalPlayer ( ) )
return ;
if ( m_flNextEventPoll > gpGlobals - > curtime )
return ;
m_flNextEventPoll = gpGlobals - > curtime + 0.2 ;
// Move through all events, removing expired ones and finding NPCs for active ones.
for ( int i = m_ActiveEvents . First ( ) ; i ! = m_ActiveEvents . InvalidIndex ( ) ; )
{
float flTime = m_ActiveEvents [ i ] . flEventTime ;
const char * pResponse = m_ActiveEvents . GetElementName ( i ) ;
// Save off the next index so we can safely remove this one
int iNext = m_ActiveEvents . Next ( i ) ;
// Should it have expired by now?
if ( ! m_ActiveEvents [ i ] . bPreventExpiration & & ( flTime + NPCEVENTRESPONSE_GIVEUP_TIME ) < gpGlobals - > curtime )
{
if ( ai_debug_eventresponses . GetBool ( ) )
{
Msg ( " NPCEVENTRESPONSE: (%.2f) Removing expired event named: %s \n " , gpGlobals - > curtime , pResponse ) ;
}
m_ActiveEvents . RemoveAt ( i ) ;
}
else if ( m_ActiveEvents [ i ] . flNextResponseTime < gpGlobals - > curtime )
{
// If we've fired once, and our current event should expire now, then expire.
if ( m_ActiveEvents [ i ] . bPreventExpiration & & ( flTime + NPCEVENTRESPONSE_GIVEUP_TIME ) < gpGlobals - > curtime )
{
if ( ai_debug_eventresponses . GetBool ( ) )
{
Msg ( " NPCEVENTRESPONSE: (%.2f) Removing expired fired event named: %s \n " , gpGlobals - > curtime , pResponse ) ;
}
m_ActiveEvents . RemoveAt ( i ) ;
}
else
{
float flNearestDist = NPCEVENTRESPONSE_DISTANCE_SQR ;
CAI_BaseNPC * pNearestNPC = NULL ;
Vector vecPlayerCenter = UTIL_GetLocalPlayer ( ) - > WorldSpaceCenter ( ) ;
// Try and find the nearest NPC to the player
CAI_BaseNPC * * ppAIs = g_AI_Manager . AccessAIs ( ) ;
for ( int j = 0 ; j < g_AI_Manager . NumAIs ( ) ; j + + )
{
if ( ppAIs [ j ] - > CanRespondToEvent ( pResponse ) )
{
float flDistToPlayer = ( vecPlayerCenter - ppAIs [ j ] - > WorldSpaceCenter ( ) ) . LengthSqr ( ) ;
if ( flDistToPlayer < flNearestDist )
{
flNearestDist = flDistToPlayer ;
pNearestNPC = ppAIs [ j ] ;
}
}
}
// Found one?
if ( pNearestNPC )
{
if ( pNearestNPC - > RespondedTo ( pResponse , m_ActiveEvents [ i ] . bForce , m_ActiveEvents [ i ] . bCancelScript ) )
{
// Don't remove the response yet. Leave it around until the refire time has expired.
// This stops repeated firings of the same concept from spamming the NPCs.
m_ActiveEvents [ i ] . bPreventExpiration = true ;
m_ActiveEvents [ i ] . flNextResponseTime = gpGlobals - > curtime + NPCEVENTRESPONSE_REFIRE_TIME ;
if ( ai_debug_eventresponses . GetBool ( ) )
{
Msg ( " NPCEVENTRESPONSE: (%.2f) Event '%s' responded to by NPC '%s'. Refire available at: %.2f \n " , gpGlobals - > curtime , pResponse , pNearestNPC - > GetDebugName ( ) , m_ActiveEvents [ i ] . flNextResponseTime ) ;
}
// Don't issue multiple responses at once
return ;
}
}
}
}
i = iNext ;
}
}
//---------------------------------------------------------------------------------------------
// Entity version for mapmaker to hook into the system
//---------------------------------------------------------------------------------------------
class CNPCEventResponseSystemEntity : public CBaseEntity
{
DECLARE_CLASS ( CNPCEventResponseSystemEntity , CBaseEntity ) ;
public :
DECLARE_DATADESC ( ) ;
void Spawn ( ) ;
void InputTriggerResponseEvent ( inputdata_t & inputdata ) ;
void InputForceTriggerResponseEvent ( inputdata_t & inputdata ) ;
void InputForceTriggerResponseEventNoCancel ( inputdata_t & inputdata ) ;
} ;
LINK_ENTITY_TO_CLASS ( ai_npc_eventresponsesystem , CNPCEventResponseSystemEntity ) ;
BEGIN_DATADESC ( CNPCEventResponseSystemEntity )
DEFINE_INPUTFUNC ( FIELD_STRING , " TriggerResponseEvent " , InputTriggerResponseEvent ) ,
DEFINE_INPUTFUNC ( FIELD_STRING , " ForceTriggerResponseEvent " , InputForceTriggerResponseEvent ) ,
DEFINE_INPUTFUNC ( FIELD_STRING , " ForceTriggerResponseEventNoCancel " , InputForceTriggerResponseEventNoCancel ) ,
END_DATADESC ( )
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPCEventResponseSystemEntity : : Spawn ( void )
{
// Invisible, non solid.
AddSolidFlags ( FSOLID_NOT_SOLID ) ;
AddEffects ( EF_NODRAW ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPCEventResponseSystemEntity : : InputTriggerResponseEvent ( inputdata_t & inputdata )
{
NPCEventResponse ( ) - > TriggerEvent ( inputdata . value . String ( ) , false , false ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPCEventResponseSystemEntity : : InputForceTriggerResponseEvent ( inputdata_t & inputdata )
{
NPCEventResponse ( ) - > TriggerEvent ( inputdata . value . String ( ) , true , true ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPCEventResponseSystemEntity : : InputForceTriggerResponseEventNoCancel ( inputdata_t & inputdata )
{
NPCEventResponse ( ) - > TriggerEvent ( inputdata . value . String ( ) , true , false ) ;
}