2023-10-03 17:23:56 +03:00
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
2020-04-22 12:56:21 -04:00
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
# include "cbase.h"
# include "ai_speech.h"
# include "game.h"
2023-10-03 17:23:56 +03:00
# include "engine/ienginesound.h"
# include "keyvalues.h"
2020-04-22 12:56:21 -04:00
# include "ai_basenpc.h"
2023-10-03 17:23:56 +03:00
# include "ai_criteria.h"
2020-04-22 12:56:21 -04:00
# include "isaverestore.h"
# include "sceneentity.h"
2023-10-03 17:23:56 +03:00
# include "ai_speechqueue.h"
2020-04-22 12:56:21 -04:00
// memdbgon must be the last include file in a .cpp file!!!
# include <tier0/memdbgon.h>
# define DEBUG_AISPEECH 1
# ifdef DEBUG_AISPEECH
ConVar ai_debug_speech ( " ai_debug_speech " , " 0 " ) ;
# define DebuggingSpeech() ai_debug_speech.GetBool()
# else
inline void SpeechMsg ( . . . ) { }
# define DebuggingSpeech() (false)
# endif
extern ConVar rr_debugresponses ;
//-----------------------------------------------------------------------------
CAI_TimedSemaphore g_AIFriendliesTalkSemaphore ;
CAI_TimedSemaphore g_AIFoesTalkSemaphore ;
ConceptHistory_t : : ~ ConceptHistory_t ( )
{
}
ConceptHistory_t : : ConceptHistory_t ( const ConceptHistory_t & src )
{
timeSpoken = src . timeSpoken ;
2023-10-03 17:23:56 +03:00
m_response = src . m_response ;
2020-04-22 12:56:21 -04:00
}
ConceptHistory_t & ConceptHistory_t : : operator = ( const ConceptHistory_t & src )
{
2022-03-01 23:00:42 +03:00
if ( this = = & src )
return * this ;
2020-04-22 12:56:21 -04:00
2022-03-01 23:00:42 +03:00
timeSpoken = src . timeSpoken ;
2023-10-03 17:23:56 +03:00
m_response = src . m_response ;
2020-04-22 12:56:21 -04:00
return * this ;
}
BEGIN_SIMPLE_DATADESC ( ConceptHistory_t )
DEFINE_FIELD ( timeSpoken , FIELD_TIME ) , // Relative to server time
// DEFINE_EMBEDDED( response, FIELD_??? ), // This is manually saved/restored by the ConceptHistory saverestore ops below
END_DATADESC ( )
class CConceptHistoriesDataOps : public CDefSaveRestoreOps
{
public :
virtual void Save ( const SaveRestoreFieldInfo_t & fieldInfo , ISave * pSave )
{
CUtlDict < ConceptHistory_t , int > * ch = ( ( CUtlDict < ConceptHistory_t , int > * ) fieldInfo . pField ) ;
int count = ch - > Count ( ) ;
pSave - > WriteInt ( & count ) ;
for ( int i = 0 ; i < count ; i + + )
{
ConceptHistory_t * pHistory = & ( * ch ) [ i ] ;
pSave - > StartBlock ( ) ;
{
2022-03-01 23:00:42 +03:00
2020-04-22 12:56:21 -04:00
// Write element name
pSave - > WriteString ( ch - > GetElementName ( i ) ) ;
// Write data
pSave - > WriteAll ( pHistory ) ;
// Write response blob
2023-10-03 17:23:56 +03:00
bool hasresponse = ! pHistory - > m_response . IsEmpty ( ) ;
2020-04-22 12:56:21 -04:00
pSave - > WriteBool ( & hasresponse ) ;
if ( hasresponse )
{
2023-10-03 17:23:56 +03:00
pSave - > WriteAll ( & pHistory - > m_response ) ;
2020-04-22 12:56:21 -04:00
}
// TODO: Could blat out pHistory->criteria pointer here, if it's needed
}
pSave - > EndBlock ( ) ;
}
}
virtual void Restore ( const SaveRestoreFieldInfo_t & fieldInfo , IRestore * pRestore )
{
CUtlDict < ConceptHistory_t , int > * ch = ( ( CUtlDict < ConceptHistory_t , int > * ) fieldInfo . pField ) ;
int count = pRestore - > ReadInt ( ) ;
Assert ( count > = 0 ) ;
for ( int i = 0 ; i < count ; i + + )
{
char conceptname [ 512 ] ;
conceptname [ 0 ] = 0 ;
ConceptHistory_t history ;
pRestore - > StartBlock ( ) ;
{
pRestore - > ReadString ( conceptname , sizeof ( conceptname ) , 0 ) ;
pRestore - > ReadAll ( & history ) ;
bool hasresponse = false ;
2022-03-01 23:00:42 +03:00
2020-04-22 12:56:21 -04:00
pRestore - > ReadBool ( & hasresponse ) ;
if ( hasresponse )
{
2023-10-03 17:23:56 +03:00
history . m_response ;
pRestore - > ReadAll ( & history . m_response ) ;
}
else
{
history . m_response . Invalidate ( ) ;
2020-04-22 12:56:21 -04:00
}
}
pRestore - > EndBlock ( ) ;
// TODO: Could restore pHistory->criteria pointer here, if it's needed
// Add to utldict
if ( conceptname [ 0 ] ! = 0 )
{
ch - > Insert ( conceptname , history ) ;
}
else
{
Assert ( ! " Error restoring ConceptHistory_t, discarding! " ) ;
}
}
}
2022-03-01 23:00:42 +03:00
2020-04-22 12:56:21 -04:00
virtual void MakeEmpty ( const SaveRestoreFieldInfo_t & fieldInfo )
{
}
virtual bool IsEmpty ( const SaveRestoreFieldInfo_t & fieldInfo )
{
CUtlDict < ConceptHistory_t , int > * ch = ( ( CUtlDict < ConceptHistory_t , int > * ) fieldInfo . pField ) ;
return ch - > Count ( ) = = 0 ? true : false ;
}
} ;
CConceptHistoriesDataOps g_ConceptHistoriesSaveDataOps ;
2023-10-03 17:23:56 +03:00
/////////////////////////////////////////////////
// context operators
RR : : CApplyContextOperator RR : : sm_OpCopy ( 0 ) ; // "
RR : : CIncrementOperator RR : : sm_OpIncrement ( 2 ) ; // "++"
RR : : CDecrementOperator RR : : sm_OpDecrement ( 2 ) ; // "--"
RR : : CToggleOperator RR : : sm_OpToggle ( 1 ) ; // "!"
RR : : CApplyContextOperator * RR : : CApplyContextOperator : : FindOperator ( const char * pContextString )
{
if ( ! pContextString | | pContextString [ 0 ] = = 0 )
{
return & sm_OpCopy ;
}
if ( pContextString [ 0 ] = = ' + ' & & pContextString [ 1 ] = = ' + ' & & pContextString [ 2 ] ! = ' \0 ' )
{
return & sm_OpIncrement ;
}
else if ( pContextString [ 0 ] = = ' - ' & & pContextString [ 1 ] = = ' - ' & & pContextString [ 2 ] ! = ' \0 ' )
{
return & sm_OpDecrement ;
}
else if ( pContextString [ 0 ] = = ' ! ' )
{
return & sm_OpToggle ;
}
else
{
return & sm_OpCopy ;
}
}
// default is just copy
bool RR : : CApplyContextOperator : : Apply ( const char * pOldValue , const char * pOperator , char * pNewValue , int pNewValBufSize )
{
Assert ( pOperator & & pNewValue & & pNewValBufSize > 0 ) ;
Assert ( m_nSkipChars = = 0 ) ;
if ( pOperator )
{
V_strncpy ( pNewValue , pOperator , pNewValBufSize ) ;
}
else
{
* pNewValue = 0 ;
}
return true ;
}
bool RR : : CIncrementOperator : : Apply ( const char * pOldValue , const char * pOperator , char * pNewValue , int pNewValBufSize )
{
Assert ( pOperator [ 0 ] = = ' + ' & & pOperator [ 1 ] = = ' + ' ) ;
// parse out the old value as a numeric
int nOld = pOldValue ? V_atoi ( pOldValue ) : 0 ;
int nInc = V_atoi ( pOperator + m_nSkipChars ) ;
V_snprintf ( pNewValue , pNewValBufSize , " %d " , nOld + nInc ) ;
return true ;
}
bool RR : : CDecrementOperator : : Apply ( const char * pOldValue , const char * pOperator , char * pNewValue , int pNewValBufSize )
{
Assert ( pOperator [ 0 ] = = ' - ' & & pOperator [ 1 ] = = ' - ' ) ;
// parse out the old value as a numeric
int nOld = pOldValue ? V_atoi ( pOldValue ) : 0 ;
int nInc = V_atoi ( pOperator + m_nSkipChars ) ;
V_snprintf ( pNewValue , pNewValBufSize , " %d " , nOld - nInc ) ;
return true ;
}
bool RR : : CToggleOperator : : Apply ( const char * pOldValue , const char * pOperator , char * pNewValue , int pNewValBufSize )
{
Assert ( pOperator [ 0 ] = = ' ! ' ) ;
// parse out the old value as a numeric
int nOld = pOldValue ? V_atoi ( pOldValue ) : 0 ;
V_snprintf ( pNewValue , pNewValBufSize , " %d " , nOld ? 0 : 1 ) ;
return true ;
}
2020-04-22 12:56:21 -04:00
//-----------------------------------------------------------------------------
//
// CLASS: CAI_Expresser
//
BEGIN_SIMPLE_DATADESC ( CAI_Expresser )
// m_pSink (reconnected on load)
// DEFINE_FIELD( m_pOuter, CHandle < CBaseFlex > ),
DEFINE_CUSTOM_FIELD ( m_ConceptHistories , & g_ConceptHistoriesSaveDataOps ) ,
DEFINE_FIELD ( m_flStopTalkTime , FIELD_TIME ) ,
DEFINE_FIELD ( m_flStopTalkTimeWithoutDelay , FIELD_TIME ) ,
DEFINE_FIELD ( m_flBlockedTalkTime , FIELD_TIME ) ,
DEFINE_FIELD ( m_voicePitch , FIELD_INTEGER ) ,
DEFINE_FIELD ( m_flLastTimeAcceptedSpeak , FIELD_TIME ) ,
END_DATADESC ( )
//-------------------------------------
bool CAI_Expresser : : SemaphoreIsAvailable ( CBaseEntity * pTalker )
{
if ( ! GetSink ( ) - > UseSemaphore ( ) )
return true ;
CAI_TimedSemaphore * pSemaphore = GetMySpeechSemaphore ( pTalker - > MyNPCPointer ( ) ) ;
return ( pSemaphore ? pSemaphore - > IsAvailable ( pTalker ) : true ) ;
}
//-------------------------------------
float CAI_Expresser : : GetSemaphoreAvailableTime ( CBaseEntity * pTalker )
{
CAI_TimedSemaphore * pSemaphore = GetMySpeechSemaphore ( pTalker - > MyNPCPointer ( ) ) ;
return ( pSemaphore ? pSemaphore - > GetReleaseTime ( ) : 0 ) ;
}
//-------------------------------------
int CAI_Expresser : : GetVoicePitch ( ) const
{
return m_voicePitch + random - > RandomInt ( 0 , 3 ) ;
}
# ifdef DEBUG
static int g_nExpressers ;
# endif
2023-10-03 17:23:56 +03:00
/*
inline bool ShouldBeInExpresserQueue ( CBaseFlex * pOuter )
{
return true ; // return IsTerrorPlayer( pOuter, TEAM_SURVIVOR );
}
*/
2020-04-22 12:56:21 -04:00
CAI_Expresser : : CAI_Expresser ( CBaseFlex * pOuter )
: m_pOuter ( pOuter ) ,
m_pSink ( NULL ) ,
m_flStopTalkTime ( 0 ) ,
m_flBlockedTalkTime ( 0 ) ,
m_flStopTalkTimeWithoutDelay ( 0 ) ,
2023-10-03 17:23:56 +03:00
m_voicePitch ( 100 ) ,
m_flLastTimeAcceptedSpeak ( 0 )
2020-04-22 12:56:21 -04:00
{
# ifdef DEBUG
g_nExpressers + + ;
# endif
2023-10-03 17:23:56 +03:00
if ( m_pOuter )
{
// register me with the global expresser queue.
// L4D: something a little ass backwards is happening here. We only want
// survivors to be in the queue. However, the team number isn't
// specified yet. So, we actually need to do this in the player's ChangeTeam.
g_ResponseQueueManager . GetQueue ( ) - > AddExpresserHost ( m_pOuter ) ;
}
2020-04-22 12:56:21 -04:00
}
CAI_Expresser : : ~ CAI_Expresser ( )
{
m_ConceptHistories . Purge ( ) ;
2023-10-03 17:23:56 +03:00
CBaseFlex * RESTRICT outer = GetOuter ( ) ;
if ( outer )
2020-04-22 12:56:21 -04:00
{
2023-10-03 17:23:56 +03:00
CAI_TimedSemaphore * pSemaphore = GetMySpeechSemaphore ( outer ) ;
if ( pSemaphore )
{
if ( pSemaphore - > GetOwner ( ) = = outer )
pSemaphore - > Release ( ) ;
2020-04-22 12:56:21 -04:00
# ifdef DEBUG
2023-10-03 17:23:56 +03:00
g_nExpressers - - ;
if ( g_nExpressers = = 0 & & pSemaphore - > GetOwner ( ) )
DevMsg ( 2 , " Speech semaphore being held by non-talker entity \n " ) ;
2020-04-22 12:56:21 -04:00
# endif
2023-10-03 17:23:56 +03:00
}
g_ResponseQueueManager . GetQueue ( ) - > RemoveExpresserHost ( outer ) ;
2020-04-22 12:56:21 -04:00
}
}
//-----------------------------------------------------------------------------
void CAI_Expresser : : TestAllResponses ( )
{
IResponseSystem * pResponseSystem = GetOuter ( ) - > GetResponseSystem ( ) ;
if ( pResponseSystem )
{
2023-10-03 17:23:56 +03:00
CUtlVector < AI_Response > responses ;
2020-04-22 12:56:21 -04:00
pResponseSystem - > GetAllResponses ( & responses ) ;
for ( int i = 0 ; i < responses . Count ( ) ; i + + )
{
2022-03-01 23:00:42 +03:00
char response [ 256 ] ;
2023-10-03 17:23:56 +03:00
responses [ i ] . GetResponse ( response , sizeof ( response ) ) ;
2020-04-22 12:56:21 -04:00
2022-03-01 23:00:42 +03:00
Msg ( " Response: %s \n " , response ) ;
2023-10-03 17:23:56 +03:00
AIConcept_t concept ;
SpeakDispatchResponse ( concept , & responses [ i ] , NULL ) ;
2020-04-22 12:56:21 -04:00
}
}
}
2023-10-03 17:23:56 +03:00
//-----------------------------------------------------------------------------
void CAI_Expresser : : SetOuter ( CBaseFlex * pOuter )
{
// if we're changing outers (which is a strange thing to do)
// unregister the old one from the queue.
if ( m_pOuter & & ( m_pOuter ! = pOuter ) )
{
AssertMsg2 ( false , " Expresser is switching its Outer from %s to %s. Why? " , m_pOuter - > GetDebugName ( ) , pOuter - > GetDebugName ( ) ) ;
// unregister me with the global expresser queue
g_ResponseQueueManager . GetQueue ( ) - > RemoveExpresserHost ( m_pOuter ) ;
}
m_pOuter = pOuter ;
}
2020-04-22 12:56:21 -04:00
//-----------------------------------------------------------------------------
static const int LEN_SPECIFIC_SCENE_MODIFIER = strlen ( AI_SPECIFIC_SCENE_MODIFIER ) ;
2023-10-03 17:23:56 +03:00
// This function appends "Global world" criteria that are always added to
// any character doing any match. This represents global concepts like weather, who's
// alive, etc.
static void ModifyOrAppendGlobalCriteria ( AI_CriteriaSet * RESTRICT outputSet )
{
return ;
}
void CAI_Expresser : : GatherCriteria ( AI_CriteriaSet * RESTRICT outputSet , const AIConcept_t & concept , const char * RESTRICT modifiers )
{
// Always include the concept name
outputSet - > AppendCriteria ( " concept " , concept , CONCEPT_WEIGHT ) ;
# if 1
outputSet - > Merge ( modifiers ) ;
# else
// Always include any optional modifiers
if ( modifiers ! = NULL )
{
char copy_modifiers [ 255 ] ;
const char * pCopy ;
char key [ 128 ] = { 0 } ;
char value [ 128 ] = { 0 } ;
Q_strncpy ( copy_modifiers , modifiers , sizeof ( copy_modifiers ) ) ;
pCopy = copy_modifiers ;
while ( pCopy )
{
pCopy = SplitContext ( pCopy , key , sizeof ( key ) , value , sizeof ( value ) , NULL , modifiers ) ;
if ( * key & & * value )
{
outputSet - > AppendCriteria ( key , value , CONCEPT_WEIGHT ) ;
}
}
}
# endif
// include any global criteria
ModifyOrAppendGlobalCriteria ( outputSet ) ;
// Let our outer fill in most match criteria
GetOuter ( ) - > ModifyOrAppendCriteria ( * outputSet ) ;
// Append local player criteria to set, but not if this is a player doing the talking
if ( ! GetOuter ( ) - > IsPlayer ( ) )
{
CBasePlayer * pPlayer = UTIL_PlayerByIndex ( 1 ) ;
if ( pPlayer )
pPlayer - > ModifyOrAppendPlayerCriteria ( * outputSet ) ;
}
}
2020-04-22 12:56:21 -04:00
//-----------------------------------------------------------------------------
// Purpose: Searches for a possible response
// Input : concept -
// NULL -
// Output : AI_Response
//-----------------------------------------------------------------------------
2023-10-03 17:23:56 +03:00
// AI_Response *CAI_Expresser::SpeakFindResponse( AIConcept_t concept, const char *modifiers /*= NULL*/ )
bool CAI_Expresser : : FindResponse ( AI_Response & outResponse , AIConcept_t & concept , AI_CriteriaSet * criteria )
{
VPROF ( " CAI_Expresser::FindResponse " ) ;
IResponseSystem * rs = GetOuter ( ) - > GetResponseSystem ( ) ;
if ( ! rs )
{
Assert ( ! " No response system installed for CAI_Expresser::GetOuter()!!! " ) ;
return NULL ;
}
// if I'm dead, I can't possibly match dialog.
if ( ! GetOuter ( ) - > IsAlive ( ) )
{
return false ;
}
#if 0 // this is the old technique, where we always gathered criteria in this function
AI_CriteriaSet set ;
// Always include the concept name
set . AppendCriteria ( " concept " , concept , CONCEPT_WEIGHT ) ;
// Always include any optional modifiers
if ( modifiers ! = NULL )
{
char copy_modifiers [ 255 ] ;
const char * pCopy ;
char key [ 128 ] = { 0 } ;
char value [ 128 ] = { 0 } ;
Q_strncpy ( copy_modifiers , modifiers , sizeof ( copy_modifiers ) ) ;
pCopy = copy_modifiers ;
while ( pCopy )
{
pCopy = SplitContext ( pCopy , key , sizeof ( key ) , value , sizeof ( value ) , NULL , modifiers ) ;
if ( * key & & * value )
{
set . AppendCriteria ( key , value , CONCEPT_WEIGHT ) ;
}
}
}
// Let our outer fill in most match criteria
GetOuter ( ) - > ModifyOrAppendCriteria ( set ) ;
// Append local player criteria to set, but not if this is a player doing the talking
if ( ! GetOuter ( ) - > IsPlayer ( ) )
{
CBasePlayer * pPlayer = UTIL_PlayerByIndex ( 1 ) ;
if ( pPlayer )
pPlayer - > ModifyOrAppendPlayerCriteria ( set ) ;
}
# else
AI_CriteriaSet localCriteriaSet ; // put it on the stack so we don't deal with new/delete
if ( criteria = = NULL )
{
GatherCriteria ( & localCriteriaSet , concept , NULL ) ;
criteria = & localCriteriaSet ;
}
# endif
/// intercept any deferred criteria that are being sent to world
AI_CriteriaSet worldWritebackCriteria ;
AI_CriteriaSet : : InterceptWorldSetContexts ( criteria , & worldWritebackCriteria ) ;
// Now that we have a criteria set, ask for a suitable response
bool found = rs - > FindBestResponse ( * criteria , outResponse , this ) ;
if ( rr_debugresponses . GetInt ( ) = = 4 )
{
if ( ( GetOuter ( ) - > MyNPCPointer ( ) & & GetOuter ( ) - > m_debugOverlays & OVERLAY_NPC_SELECTED_BIT ) | | GetOuter ( ) - > IsPlayer ( ) )
{
const char * pszName ;
if ( GetOuter ( ) - > IsPlayer ( ) )
{
pszName = ( ( CBasePlayer * ) GetOuter ( ) ) - > GetPlayerName ( ) ;
}
else
{
pszName = GetOuter ( ) - > GetDebugName ( ) ;
}
if ( found )
{
char response [ 256 ] ;
outResponse . GetResponse ( response , sizeof ( response ) ) ;
Warning ( " RESPONSERULES: %s spoke '%s'. Found response '%s'. \n " , pszName , ( const char * ) concept , response ) ;
}
else
{
Warning ( " RESPONSERULES: %s spoke '%s'. Found no matching response. \n " , pszName , ( const char * ) concept ) ;
}
}
}
if ( ! found )
{
return false ;
}
else if ( worldWritebackCriteria . GetCount ( ) > 0 )
{
Assert ( CBaseEntity : : Instance ( INDEXENT ( 0 ) ) - > IsWorld ( ) ) ;
worldWritebackCriteria . WriteToEntity ( CBaseEntity : : Instance ( INDEXENT ( 0 ) ) ) ;
}
if ( outResponse . IsEmpty ( ) )
{
// AssertMsg2( false, "RR: %s got empty but valid response for %s", GetOuter()->GetDebugName(), concept.GetStringConcept() );
return false ;
}
else
{
return true ;
}
}
#if 0
//-----------------------------------------------------------------------------
// Purpose: Searches for a possible response; writes it into a response passed as
// parameter rather than new'ing one up.
// Input : concept -
// NULL -
// Output : bool : true on success, false on fail
//-----------------------------------------------------------------------------
AI_Response * CAI_Expresser : : SpeakFindResponse ( AI_Response * result , AIConcept_t & concept , AI_CriteriaSet * criteria )
2020-04-22 12:56:21 -04:00
{
2023-10-03 17:23:56 +03:00
Assert ( response ) ;
2020-04-22 12:56:21 -04:00
IResponseSystem * rs = GetOuter ( ) - > GetResponseSystem ( ) ;
if ( ! rs )
{
Assert ( ! " No response system installed for CAI_Expresser::GetOuter()!!! " ) ;
2022-03-01 23:00:42 +03:00
return NULL ;
2020-04-22 12:56:21 -04:00
}
2023-10-03 17:23:56 +03:00
#if 0
2020-04-22 12:56:21 -04:00
AI_CriteriaSet set ;
// Always include the concept name
set . AppendCriteria ( " concept " , concept , CONCEPT_WEIGHT ) ;
// Always include any optional modifiers
2022-03-01 23:00:42 +03:00
if ( modifiers ! = NULL )
2020-04-22 12:56:21 -04:00
{
char copy_modifiers [ 255 ] ;
const char * pCopy ;
char key [ 128 ] = { 0 } ;
char value [ 128 ] = { 0 } ;
Q_strncpy ( copy_modifiers , modifiers , sizeof ( copy_modifiers ) ) ;
pCopy = copy_modifiers ;
while ( pCopy )
{
2023-10-03 17:23:56 +03:00
pCopy = SplitContext ( pCopy , key , sizeof ( key ) , value , sizeof ( value ) , NULL , modifiers ) ;
2020-04-22 12:56:21 -04:00
if ( * key & & * value )
{
set . AppendCriteria ( key , value , CONCEPT_WEIGHT ) ;
}
}
}
// Let our outer fill in most match criteria
GetOuter ( ) - > ModifyOrAppendCriteria ( set ) ;
// Append local player criteria to set, but not if this is a player doing the talking
if ( ! GetOuter ( ) - > IsPlayer ( ) )
{
CBasePlayer * pPlayer = UTIL_PlayerByIndex ( 1 ) ;
if ( pPlayer )
pPlayer - > ModifyOrAppendPlayerCriteria ( set ) ;
}
2023-10-03 17:23:56 +03:00
# else
AI_CriteriaSet & set = * criteria ;
# endif
2020-04-22 12:56:21 -04:00
// Now that we have a criteria set, ask for a suitable response
2022-03-01 23:00:42 +03:00
bool found = rs - > FindBestResponse ( set , * result , this ) ;
2020-04-22 12:56:21 -04:00
2023-10-03 17:23:56 +03:00
if ( rr_debugresponses . GetInt ( ) = = 4 )
2020-04-22 12:56:21 -04:00
{
if ( ( GetOuter ( ) - > MyNPCPointer ( ) & & GetOuter ( ) - > m_debugOverlays & OVERLAY_NPC_SELECTED_BIT ) | | GetOuter ( ) - > IsPlayer ( ) )
{
2022-03-01 23:00:42 +03:00
const char * pszName ;
if ( GetOuter ( ) - > IsPlayer ( ) )
{
pszName = ( ( CBasePlayer * ) GetOuter ( ) ) - > GetPlayerName ( ) ;
}
else
{
pszName = GetOuter ( ) - > GetDebugName ( ) ;
}
2020-04-22 12:56:21 -04:00
if ( found )
{
2022-03-01 23:00:42 +03:00
char response [ 256 ] ;
result - > GetResponse ( response , sizeof ( response ) ) ;
Warning ( " RESPONSERULES: %s spoke '%s'. Found response '%s'. \n " , pszName , concept , response ) ;
2020-04-22 12:56:21 -04:00
}
else
{
Warning ( " RESPONSERULES: %s spoke '%s'. Found no matching response. \n " , pszName , concept ) ;
}
}
}
if ( ! found )
2022-03-01 23:00:42 +03:00
{
//Assert( !"rs->FindBestResponse: Returned a NULL AI_Response!" );
2023-10-03 17:23:56 +03:00
return false ;
2022-03-01 23:00:42 +03:00
}
2020-04-22 12:56:21 -04:00
2022-03-01 23:00:42 +03:00
char response [ 256 ] ;
result - > GetResponse ( response , sizeof ( response ) ) ;
2020-04-22 12:56:21 -04:00
2022-03-01 23:00:42 +03:00
if ( ! response [ 0 ] )
{
2023-10-03 17:23:56 +03:00
return false ;
2022-03-01 23:00:42 +03:00
}
2023-10-03 17:23:56 +03:00
return true ;
2020-04-22 12:56:21 -04:00
}
2023-10-03 17:23:56 +03:00
# endif
2020-04-22 12:56:21 -04:00
//-----------------------------------------------------------------------------
// Purpose: Dispatches the result
// Input : *response -
//-----------------------------------------------------------------------------
2023-10-03 17:23:56 +03:00
bool CAI_Expresser : : SpeakDispatchResponse ( AIConcept_t & concept , AI_Response * result , AI_CriteriaSet * criteria , IRecipientFilter * filter /* = NULL */ )
2020-04-22 12:56:21 -04:00
{
2022-03-01 23:00:42 +03:00
char response [ 256 ] ;
result - > GetResponse ( response , sizeof ( response ) ) ;
float delay = result - > GetDelay ( ) ;
2020-04-22 12:56:21 -04:00
bool spoke = false ;
2022-03-01 23:00:42 +03:00
soundlevel_t soundlevel = result - > GetSoundLevel ( ) ;
2020-04-22 12:56:21 -04:00
2023-10-03 17:23:56 +03:00
if ( IsSpeaking ( ) & & concept [ 0 ] ! = 0 & & result - > GetType ( ) ! = ResponseRules : : RESPONSE_PRINT )
2020-04-22 12:56:21 -04:00
{
2023-10-03 17:23:56 +03:00
const char * entityName = STRING ( GetOuter ( ) - > GetEntityName ( ) ) ;
if ( GetOuter ( ) - > IsPlayer ( ) )
{
entityName = ToBasePlayer ( GetOuter ( ) ) - > GetPlayerName ( ) ;
}
DevMsg ( 2 , " SpeakDispatchResponse: Entity ( %i/%s ) already speaking, forcing '%s' \n " , GetOuter ( ) - > entindex ( ) , entityName ? entityName : " UNKNOWN " , ( const char * ) concept ) ;
2020-04-22 12:56:21 -04:00
// Tracker 15911: Can break the game if we stop an imported map placed lcs here, so only
// cancel actor out of instanced scripted scenes. ywb
RemoveActorFromScriptedScenes ( GetOuter ( ) , true /*instanced scenes only*/ ) ;
GetOuter ( ) - > SentenceStop ( ) ;
if ( IsRunningScriptedScene ( GetOuter ( ) ) )
{
2023-10-03 17:23:56 +03:00
DevMsg ( " SpeakDispatchResponse: Entity ( %i/%s ) refusing to speak due to scene entity, tossing '%s' \n " , GetOuter ( ) - > entindex ( ) , entityName ? entityName : " UNKNOWN " , ( const char * ) concept ) ;
2020-04-22 12:56:21 -04:00
return false ;
}
}
2022-03-01 23:00:42 +03:00
switch ( result - > GetType ( ) )
2020-04-22 12:56:21 -04:00
{
default :
2023-10-03 17:23:56 +03:00
case ResponseRules : : RESPONSE_NONE :
2020-04-22 12:56:21 -04:00
break ;
2023-10-03 17:23:56 +03:00
case ResponseRules : : RESPONSE_SPEAK :
2020-04-22 12:56:21 -04:00
{
2022-03-01 23:00:42 +03:00
if ( ! result - > ShouldntUseScene ( ) )
{
// This generates a fake CChoreoScene wrapping the sound.txt name
spoke = SpeakAutoGeneratedScene ( response , delay ) ;
}
else
{
float speakTime = GetResponseDuration ( result ) ;
GetOuter ( ) - > EmitSound ( response ) ;
2020-04-22 12:56:21 -04:00
2023-10-03 17:23:56 +03:00
DevMsg ( 2 , " SpeakDispatchResponse: Entity ( %i/%s ) playing sound '%s' \n " , GetOuter ( ) - > entindex ( ) , STRING ( GetOuter ( ) - > GetEntityName ( ) ) , response ) ;
2022-03-01 23:00:42 +03:00
NoteSpeaking ( speakTime , delay ) ;
spoke = true ;
}
2020-04-22 12:56:21 -04:00
}
break ;
2023-10-03 17:23:56 +03:00
case ResponseRules : : RESPONSE_SENTENCE :
2022-03-01 23:00:42 +03:00
{
spoke = ( - 1 ! = SpeakRawSentence ( response , delay , VOL_NORM , soundlevel ) ) ? true : false ;
}
2020-04-22 12:56:21 -04:00
break ;
2023-10-03 17:23:56 +03:00
case ResponseRules : : RESPONSE_SCENE :
2022-03-01 23:00:42 +03:00
{
spoke = SpeakRawScene ( response , delay , result , filter ) ;
}
2020-04-22 12:56:21 -04:00
break ;
2023-10-03 17:23:56 +03:00
case ResponseRules : : RESPONSE_RESPONSE :
2022-03-01 23:00:42 +03:00
{
// This should have been recursively resolved already
Assert ( 0 ) ;
}
2020-04-22 12:56:21 -04:00
break ;
2023-10-03 17:23:56 +03:00
case ResponseRules : : RESPONSE_PRINT :
2020-04-22 12:56:21 -04:00
{
2022-03-01 23:00:42 +03:00
if ( g_pDeveloper - > GetInt ( ) > 0 )
{
Vector vPrintPos ;
GetOuter ( ) - > CollisionProp ( ) - > NormalizedToWorldSpace ( Vector ( 0.5 , 0.5 , 1.0f ) , & vPrintPos ) ;
NDebugOverlay : : Text ( vPrintPos , response , true , 1.5 ) ;
2023-10-03 17:23:56 +03:00
}
2022-03-01 23:00:42 +03:00
spoke = true ;
}
2023-10-03 17:23:56 +03:00
break ;
case ResponseRules : : RESPONSE_ENTITYIO :
{
return FireEntIOFromResponse ( response , GetOuter ( ) ) ;
2020-04-22 12:56:21 -04:00
}
break ;
}
if ( spoke )
{
m_flLastTimeAcceptedSpeak = gpGlobals - > curtime ;
2023-10-03 17:23:56 +03:00
if ( DebuggingSpeech ( ) & & g_pDeveloper - > GetInt ( ) > 0 & & response & & result - > GetType ( ) ! = ResponseRules : : RESPONSE_PRINT )
2020-04-22 12:56:21 -04:00
{
Vector vPrintPos ;
GetOuter ( ) - > CollisionProp ( ) - > NormalizedToWorldSpace ( Vector ( 0.5 , 0.5 , 1.0f ) , & vPrintPos ) ;
2023-10-03 17:23:56 +03:00
NDebugOverlay : : Text ( vPrintPos , CFmtStr ( " %s: %s " , ( const char * ) concept , response ) , true , 1.5 ) ;
2020-04-22 12:56:21 -04:00
}
2022-03-01 23:00:42 +03:00
if ( result - > IsApplyContextToWorld ( ) )
2020-04-22 12:56:21 -04:00
{
2023-10-03 17:23:56 +03:00
CBaseEntity * pEntity = CBaseEntity : : Instance ( INDEXENT ( 0 ) ) ;
2020-04-22 12:56:21 -04:00
if ( pEntity )
{
2022-03-01 23:00:42 +03:00
pEntity - > AddContext ( result - > GetContext ( ) ) ;
2020-04-22 12:56:21 -04:00
}
}
else
{
2022-03-01 23:00:42 +03:00
GetOuter ( ) - > AddContext ( result - > GetContext ( ) ) ;
2020-04-22 12:56:21 -04:00
}
2022-03-01 23:00:42 +03:00
SetSpokeConcept ( concept , result ) ;
}
else
{
2020-04-22 12:56:21 -04:00
}
return spoke ;
}
2023-10-03 17:23:56 +03:00
bool CAI_Expresser : : FireEntIOFromResponse ( char * response , CBaseEntity * pInitiator )
{
// find the space-separator in the response name, then split into entityname, input, and parameter
// may barf in linux; there, should make some StringTokenizer() class that wraps the strtok_s behavior, etc.
char * pszEntname ;
char * pszInput ;
char * pszParam ;
char * strtokContext ;
pszEntname = strtok_s ( response , " " , & strtokContext ) ;
if ( ! pszEntname )
{
Warning ( " Response was entityio but had bad value %s \n " , response ) ;
return false ;
}
pszInput = strtok_s ( NULL , " " , & strtokContext ) ;
if ( ! pszInput )
{
Warning ( " Response was entityio but had bad value %s \n " , response ) ;
return false ;
}
pszParam = strtok_s ( NULL , " " , & strtokContext ) ;
// poke entity io
CBaseEntity * pTarget = gEntList . FindEntityByName ( NULL , pszEntname , pInitiator ) ;
if ( ! pTarget )
{
Msg ( " Response rule targeted %s with entityio, but that doesn't exist. \n " , pszEntname ) ;
// but this is actually a legit use case, so return true (below).
}
else
{
// pump the action into the target
variant_t variant ;
if ( pszParam )
{
variant . SetString ( MAKE_STRING ( pszParam ) ) ;
}
pTarget - > AcceptInput ( pszInput , pInitiator , pInitiator , variant , 0 ) ;
}
return true ;
}
2020-04-22 12:56:21 -04:00
//-----------------------------------------------------------------------------
// Purpose:
// Input : *response -
// Output : float
//-----------------------------------------------------------------------------
2022-03-01 23:00:42 +03:00
float CAI_Expresser : : GetResponseDuration ( AI_Response * result )
2020-04-22 12:56:21 -04:00
{
2022-03-01 23:00:42 +03:00
Assert ( result ) ;
char response [ 256 ] ;
result - > GetResponse ( response , sizeof ( response ) ) ;
2020-04-22 12:56:21 -04:00
2022-03-01 23:00:42 +03:00
switch ( result - > GetType ( ) )
2020-04-22 12:56:21 -04:00
{
2023-10-03 17:23:56 +03:00
case ResponseRules : : RESPONSE_SPEAK :
2022-03-01 23:00:42 +03:00
{
return GetOuter ( ) - > GetSoundDuration ( response , STRING ( GetOuter ( ) - > GetModelName ( ) ) ) ;
}
break ;
2023-10-03 17:23:56 +03:00
case ResponseRules : : RESPONSE_SENTENCE :
2022-03-01 23:00:42 +03:00
{
Assert ( 0 ) ;
return 999.0f ;
}
break ;
2023-10-03 17:23:56 +03:00
case ResponseRules : : RESPONSE_SCENE :
2022-03-01 23:00:42 +03:00
{
return GetSceneDuration ( response ) ;
}
break ;
2023-10-03 17:23:56 +03:00
case ResponseRules : : RESPONSE_RESPONSE :
2022-03-01 23:00:42 +03:00
{
// This should have been recursively resolved already
Assert ( 0 ) ;
}
2020-04-22 12:56:21 -04:00
break ;
2023-10-03 17:23:56 +03:00
case ResponseRules : : RESPONSE_PRINT :
2022-03-01 23:00:42 +03:00
{
return 1.0 ;
}
break ;
2023-10-03 17:23:56 +03:00
default :
case ResponseRules : : RESPONSE_NONE :
case ResponseRules : : RESPONSE_ENTITYIO :
return 0.0f ;
2020-04-22 12:56:21 -04:00
}
return 0.0f ;
}
//-----------------------------------------------------------------------------
// Purpose: Placeholder for rules based response system
// Input : concept -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
2023-10-03 17:23:56 +03:00
bool CAI_Expresser : : Speak ( AIConcept_t & concept , const char * modifiers /*= NULL*/ , char * pszOutResponseChosen /* = NULL*/ , size_t bufsize /* = 0 */ , IRecipientFilter * filter /* = NULL */ )
2020-04-22 12:56:21 -04:00
{
2023-10-03 17:23:56 +03:00
concept . SetSpeaker ( GetOuter ( ) ) ;
AI_CriteriaSet criteria ;
GatherCriteria ( & criteria , concept , modifiers ) ;
return Speak ( concept , & criteria , pszOutResponseChosen , bufsize , filter ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CAI_Expresser : : Speak ( AIConcept_t & concept , AI_CriteriaSet * RESTRICT criteria , char * pszOutResponseChosen , size_t bufsize , IRecipientFilter * filter )
{
VPROF ( " CAI_Expresser::Speak " ) ;
if ( IsSpeechGloballySuppressed ( ) )
{
return false ;
}
GetOuter ( ) - > ModifyOrAppendDerivedCriteria ( * criteria ) ;
AI_Response result ;
if ( ! FindResponse ( result , concept , criteria ) )
2022-03-01 23:00:42 +03:00
{
2020-04-22 12:56:21 -04:00
return false ;
2022-03-01 23:00:42 +03:00
}
2020-04-22 12:56:21 -04:00
2023-10-03 17:23:56 +03:00
SpeechMsg ( GetOuter ( ) , " %s (%x) spoke %s (%f) " , STRING ( GetOuter ( ) - > GetEntityName ( ) ) , GetOuter ( ) , ( const char * ) concept , gpGlobals - > curtime ) ;
// Msg( "%s:%s to %s:%s\n", GetOuter()->GetDebugName(), concept.GetStringConcept(), criteria.GetValue(criteria.FindCriterionIndex("Subject")), pTarget ? pTarget->GetDebugName() : "none" );
2020-04-22 12:56:21 -04:00
2023-10-03 17:23:56 +03:00
bool spoke = SpeakDispatchResponse ( concept , & result , criteria , filter ) ;
2020-04-22 12:56:21 -04:00
if ( pszOutResponseChosen )
{
2023-10-03 17:23:56 +03:00
result . GetResponse ( pszOutResponseChosen , bufsize ) ;
2020-04-22 12:56:21 -04:00
}
2023-10-03 17:23:56 +03:00
2020-04-22 12:56:21 -04:00
return spoke ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CAI_Expresser : : SpeakRawScene ( const char * pszScene , float delay , AI_Response * response , IRecipientFilter * filter /* = NULL */ )
{
float sceneLength = GetOuter ( ) - > PlayScene ( pszScene , delay , response , filter ) ;
if ( sceneLength > 0 )
{
SpeechMsg ( GetOuter ( ) , " SpeakRawScene( %s, %f) %f \n " , pszScene , delay , sceneLength ) ;
2023-10-03 17:23:56 +03:00
# if defined( HL2_EPISODIC )
2020-04-22 12:56:21 -04:00
char szInstanceFilename [ 256 ] ;
GetOuter ( ) - > GenderExpandString ( pszScene , szInstanceFilename , sizeof ( szInstanceFilename ) ) ;
// Only mark ourselves as speaking if the scene has speech
if ( GetSceneSpeechCount ( szInstanceFilename ) > 0 )
{
NoteSpeaking ( sceneLength , delay ) ;
}
# else
NoteSpeaking ( sceneLength , delay ) ;
# endif
return true ;
}
return false ;
}
// This will create a fake .vcd/CChoreoScene to wrap the sound to be played
bool CAI_Expresser : : SpeakAutoGeneratedScene ( char const * soundname , float delay )
{
float speakTime = GetOuter ( ) - > PlayAutoGeneratedSoundScene ( soundname ) ;
if ( speakTime > 0 )
{
SpeechMsg ( GetOuter ( ) , " SpeakAutoGeneratedScene( %s, %f) %f \n " , soundname , delay , speakTime ) ;
NoteSpeaking ( speakTime , delay ) ;
return true ;
}
return false ;
}
//-------------------------------------
int CAI_Expresser : : SpeakRawSentence ( const char * pszSentence , float delay , float volume , soundlevel_t soundlevel , CBaseEntity * pListener )
{
int sentenceIndex = - 1 ;
if ( ! pszSentence )
return sentenceIndex ;
if ( pszSentence [ 0 ] = = AI_SP_SPECIFIC_SENTENCE )
{
sentenceIndex = SENTENCEG_Lookup ( pszSentence ) ;
if ( sentenceIndex = = - 1 )
{
// sentence not found
return - 1 ;
}
CPASAttenuationFilter filter ( GetOuter ( ) , soundlevel ) ;
CBaseEntity : : EmitSentenceByIndex ( filter , GetOuter ( ) - > entindex ( ) , CHAN_VOICE , sentenceIndex , volume , soundlevel , 0 , GetVoicePitch ( ) ) ;
}
else
{
sentenceIndex = SENTENCEG_PlayRndSz ( GetOuter ( ) - > NetworkProp ( ) - > edict ( ) , pszSentence , volume , soundlevel , 0 , GetVoicePitch ( ) ) ;
}
SpeechMsg ( GetOuter ( ) , " SpeakRawSentence( %s, %f) %f \n " , pszSentence , delay , engine - > SentenceLength ( sentenceIndex ) ) ;
NoteSpeaking ( engine - > SentenceLength ( sentenceIndex ) , delay ) ;
return sentenceIndex ;
}
//-------------------------------------
void CAI_Expresser : : BlockSpeechUntil ( float time )
{
SpeechMsg ( GetOuter ( ) , " BlockSpeechUntil(%f) %f \n " , time , time - gpGlobals - > curtime ) ;
m_flBlockedTalkTime = time ;
}
//-------------------------------------
void CAI_Expresser : : NoteSpeaking ( float duration , float delay )
{
duration + = delay ;
GetSink ( ) - > OnStartSpeaking ( ) ;
if ( duration < = 0 )
{
// no duration :(
m_flStopTalkTime = gpGlobals - > curtime + 3 ;
duration = 0 ;
}
else
{
m_flStopTalkTime = gpGlobals - > curtime + duration ;
}
m_flStopTalkTimeWithoutDelay = m_flStopTalkTime - delay ;
SpeechMsg ( GetOuter ( ) , " NoteSpeaking( %f, %f ) (stop at %f) \n " , duration , delay , m_flStopTalkTime ) ;
if ( GetSink ( ) - > UseSemaphore ( ) )
{
CAI_TimedSemaphore * pSemaphore = GetMySpeechSemaphore ( GetOuter ( ) ) ;
if ( pSemaphore )
{
pSemaphore - > Acquire ( duration , GetOuter ( ) ) ;
}
}
}
//-------------------------------------
void CAI_Expresser : : ForceNotSpeaking ( void )
{
if ( IsSpeaking ( ) )
{
m_flStopTalkTime = gpGlobals - > curtime ;
m_flStopTalkTimeWithoutDelay = gpGlobals - > curtime ;
CAI_TimedSemaphore * pSemaphore = GetMySpeechSemaphore ( GetOuter ( ) ) ;
if ( pSemaphore )
{
if ( pSemaphore - > GetOwner ( ) = = GetOuter ( ) )
{
pSemaphore - > Release ( ) ;
}
}
}
}
//-------------------------------------
bool CAI_Expresser : : IsSpeaking ( void )
{
if ( m_flStopTalkTime > gpGlobals - > curtime )
SpeechMsg ( GetOuter ( ) , " IsSpeaking() %f \n " , m_flStopTalkTime - gpGlobals - > curtime ) ;
if ( m_flLastTimeAcceptedSpeak = = gpGlobals - > curtime ) // only one speak accepted per think
return true ;
return ( m_flStopTalkTime > gpGlobals - > curtime ) ;
}
//-------------------------------------
bool CAI_Expresser : : CanSpeak ( )
{
if ( m_flLastTimeAcceptedSpeak = = gpGlobals - > curtime ) // only one speak accepted per think
return false ;
float timeOk = MAX ( m_flStopTalkTime , m_flBlockedTalkTime ) ;
return ( timeOk < = gpGlobals - > curtime ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if it's ok for this entity to speak after himself.
// The base CanSpeak() includes the default speech delay, and won't
// return true until that delay time has passed after finishing the
// speech. This returns true as soon as the speech finishes.
//-----------------------------------------------------------------------------
bool CAI_Expresser : : CanSpeakAfterMyself ( )
{
if ( m_flLastTimeAcceptedSpeak = = gpGlobals - > curtime ) // only one speak accepted per think
return false ;
float timeOk = MAX ( m_flStopTalkTimeWithoutDelay , m_flBlockedTalkTime ) ;
return ( timeOk < = gpGlobals - > curtime ) ;
}
//-------------------------------------
bool CAI_Expresser : : CanSpeakConcept ( AIConcept_t concept )
{
// Not in history?
int iter = m_ConceptHistories . Find ( concept ) ;
if ( iter = = m_ConceptHistories . InvalidIndex ( ) )
{
return true ;
}
ConceptHistory_t * history = & m_ConceptHistories [ iter ] ;
Assert ( history ) ;
2023-10-03 17:23:56 +03:00
const AI_Response & response = history - > m_response ;
if ( response . IsEmpty ( ) )
2020-04-22 12:56:21 -04:00
return true ;
2023-10-03 17:23:56 +03:00
if ( response . GetSpeakOnce ( ) )
2020-04-22 12:56:21 -04:00
return false ;
2023-10-03 17:23:56 +03:00
float respeakDelay = response . GetRespeakDelay ( ) ;
2020-04-22 12:56:21 -04:00
if ( respeakDelay ! = 0.0f )
{
if ( history - > timeSpoken ! = - 1 & & ( gpGlobals - > curtime < history - > timeSpoken + respeakDelay ) )
return false ;
}
return true ;
}
//-------------------------------------
bool CAI_Expresser : : SpokeConcept ( AIConcept_t concept )
{
return GetTimeSpokeConcept ( concept ) ! = - 1.f ;
}
//-------------------------------------
float CAI_Expresser : : GetTimeSpokeConcept ( AIConcept_t concept )
{
int iter = m_ConceptHistories . Find ( concept ) ;
if ( iter = = m_ConceptHistories . InvalidIndex ( ) )
return - 1 ;
ConceptHistory_t * h = & m_ConceptHistories [ iter ] ;
return h - > timeSpoken ;
}
//-------------------------------------
void CAI_Expresser : : SetSpokeConcept ( AIConcept_t concept , AI_Response * response , bool bCallback )
{
int idx = m_ConceptHistories . Find ( concept ) ;
if ( idx = = m_ConceptHistories . InvalidIndex ( ) )
{
ConceptHistory_t h ;
h . timeSpoken = gpGlobals - > curtime ;
idx = m_ConceptHistories . Insert ( concept , h ) ;
}
ConceptHistory_t * slot = & m_ConceptHistories [ idx ] ;
slot - > timeSpoken = gpGlobals - > curtime ;
// Update response info
if ( response )
{
2023-10-03 17:23:56 +03:00
slot - > m_response = * response ;
2020-04-22 12:56:21 -04:00
}
if ( bCallback )
GetSink ( ) - > OnSpokeConcept ( concept , response ) ;
}
//-------------------------------------
void CAI_Expresser : : ClearSpokeConcept ( AIConcept_t concept )
{
m_ConceptHistories . Remove ( concept ) ;
}
//-------------------------------------
void CAI_Expresser : : DumpHistories ( )
{
int c = 1 ;
for ( int i = m_ConceptHistories . First ( ) ; i ! = m_ConceptHistories . InvalidIndex ( ) ; i = m_ConceptHistories . Next ( i ) )
{
ConceptHistory_t * h = & m_ConceptHistories [ i ] ;
DevMsg ( " %i: %s at %f \n " , c + + , m_ConceptHistories . GetElementName ( i ) , h - > timeSpoken ) ;
}
}
//-------------------------------------
bool CAI_Expresser : : IsValidResponse ( ResponseType_t type , const char * pszValue )
{
2023-10-03 17:23:56 +03:00
if ( type = = ResponseRules : : RESPONSE_SCENE )
2020-04-22 12:56:21 -04:00
{
char szInstanceFilename [ 256 ] ;
GetOuter ( ) - > GenderExpandString ( pszValue , szInstanceFilename , sizeof ( szInstanceFilename ) ) ;
return ( GetSceneDuration ( szInstanceFilename ) > 0 ) ;
}
return true ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CAI_TimedSemaphore * CAI_Expresser : : GetMySpeechSemaphore ( CBaseEntity * pNpc )
{
if ( ! pNpc - > MyNPCPointer ( ) )
2023-10-03 17:23:56 +03:00
return false ;
2020-04-22 12:56:21 -04:00
return ( pNpc - > MyNPCPointer ( ) - > IsPlayerAlly ( ) ? & g_AIFriendliesTalkSemaphore : & g_AIFoesTalkSemaphore ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_Expresser : : SpeechMsg ( CBaseEntity * pFlex , const char * pszFormat , . . . )
{
if ( ! DebuggingSpeech ( ) )
return ;
if ( pFlex - > MyNPCPointer ( ) )
{
2023-10-03 17:23:56 +03:00
DevMsg ( pFlex - > MyNPCPointer ( ) , CFmtStr ( & pszFormat ) ) ;
2020-04-22 12:56:21 -04:00
}
else
{
2023-10-03 17:23:56 +03:00
DevMsg ( CFmtStr ( & pszFormat ) ) ;
2020-04-22 12:56:21 -04:00
}
2023-10-03 17:23:56 +03:00
UTIL_LogPrintf ( ( char * ) ( ( const char * ) CFmtStr ( & pszFormat ) ) ) ;
2020-04-22 12:56:21 -04:00
}
2023-10-03 17:23:56 +03:00
//-----------------------------------------------------------------------------
// Purpose: returns true when l4d is in credits screen or some other
// speech-forbidden state
//-----------------------------------------------------------------------------
bool CAI_Expresser : : IsSpeechGloballySuppressed ( )
{
return false ;
}
2020-04-22 12:56:21 -04:00
//-----------------------------------------------------------------------------
void CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria ( CAI_BaseNPC * pSpeaker , AI_CriteriaSet & set )
{
// Append current activity name
const char * pActivityName = pSpeaker - > GetActivityName ( pSpeaker - > GetActivity ( ) ) ;
if ( pActivityName )
{
set . AppendCriteria ( " activity " , pActivityName ) ;
}
static const char * pStateNames [ ] = { " None " , " Idle " , " Alert " , " Combat " , " Scripted " , " PlayDead " , " Dead " } ;
if ( ( int ) pSpeaker - > m_NPCState < ARRAYSIZE ( pStateNames ) )
{
set . AppendCriteria ( " npcstate " , UTIL_VarArgs ( " [NPCState::%s] " , pStateNames [ pSpeaker - > m_NPCState ] ) ) ;
}
if ( pSpeaker - > GetEnemy ( ) )
{
set . AppendCriteria ( " enemy " , pSpeaker - > GetEnemy ( ) - > GetClassname ( ) ) ;
set . AppendCriteria ( " timesincecombat " , " -1 " ) ;
}
else
{
if ( pSpeaker - > GetLastEnemyTime ( ) = = 0.0 )
set . AppendCriteria ( " timesincecombat " , " 999999.0 " ) ;
else
set . AppendCriteria ( " timesincecombat " , UTIL_VarArgs ( " %f " , gpGlobals - > curtime - pSpeaker - > GetLastEnemyTime ( ) ) ) ;
}
set . AppendCriteria ( " speed " , UTIL_VarArgs ( " %.3f " , pSpeaker - > GetSmoothedVelocity ( ) . Length ( ) ) ) ;
CBaseCombatWeapon * weapon = pSpeaker - > GetActiveWeapon ( ) ;
if ( weapon )
{
set . AppendCriteria ( " weapon " , weapon - > GetClassname ( ) ) ;
}
else
{
set . AppendCriteria ( " weapon " , " none " ) ;
}
CBasePlayer * pPlayer = AI_GetSinglePlayer ( ) ;
if ( pPlayer )
{
Vector distance = pPlayer - > GetAbsOrigin ( ) - pSpeaker - > GetAbsOrigin ( ) ;
set . AppendCriteria ( " distancetoplayer " , UTIL_VarArgs ( " %f " , distance . Length ( ) ) ) ;
}
else
{
set . AppendCriteria ( " distancetoplayer " , UTIL_VarArgs ( " %i " , MAX_COORD_RANGE ) ) ;
}
if ( pSpeaker - > HasCondition ( COND_SEE_PLAYER ) )
{
set . AppendCriteria ( " seeplayer " , " 1 " ) ;
}
else
{
set . AppendCriteria ( " seeplayer " , " 0 " ) ;
}
if ( pPlayer & & pPlayer - > FInViewCone ( pSpeaker ) & & pPlayer - > FVisible ( pSpeaker ) )
{
set . AppendCriteria ( " seenbyplayer " , " 1 " ) ;
}
else
{
set . AppendCriteria ( " seenbyplayer " , " 0 " ) ;
}
}
//-----------------------------------------------------------------------------
CON_COMMAND ( npc_speakall , " Force the npc to try and speak all their responses " )
{
CBaseEntity * pEntity ;
if ( args [ 1 ] & & * args [ 1 ] )
{
pEntity = gEntList . FindEntityByName ( NULL , args [ 1 ] , NULL ) ;
if ( ! pEntity )
{
pEntity = gEntList . FindEntityByClassname ( NULL , args [ 1 ] ) ;
}
}
else
{
2023-10-03 17:23:56 +03:00
pEntity = UTIL_GetCommandClient ( ) ? UTIL_GetCommandClient ( ) - > FindPickerEntity ( ) : NULL ;
2020-04-22 12:56:21 -04:00
}
if ( pEntity )
{
CAI_BaseNPC * pNPC = pEntity - > MyNPCPointer ( ) ;
if ( pNPC )
{
if ( pNPC - > GetExpresser ( ) )
{
bool save = engine - > LockNetworkStringTables ( false ) ;
pNPC - > GetExpresser ( ) - > TestAllResponses ( ) ;
engine - > LockNetworkStringTables ( save ) ;
}
}
}
}
//-----------------------------------------------------------------------------
2023-10-03 17:23:56 +03:00
CMultiplayer_Expresser : : CMultiplayer_Expresser ( CBaseFlex * pOuter ) : CAI_ExpresserWithFollowup ( pOuter )
2020-04-22 12:56:21 -04:00
{
m_bAllowMultipleScenes = false ;
}
bool CMultiplayer_Expresser : : IsSpeaking ( void )
{
if ( m_bAllowMultipleScenes )
{
return false ;
}
return CAI_Expresser : : IsSpeaking ( ) ;
}
void CMultiplayer_Expresser : : AllowMultipleScenes ( )
{
m_bAllowMultipleScenes = true ;
}
void CMultiplayer_Expresser : : DisallowMultipleScenes ( )
{
m_bAllowMultipleScenes = false ;
2022-11-04 01:03:52 +00:00
}