2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
# include "cbase.h"
# include "ai_speech.h"
# include "game.h"
# include "engine/IEngineSound.h"
# include "KeyValues.h"
# include "ai_basenpc.h"
# include "AI_Criteria.h"
# include "isaverestore.h"
# include "sceneentity.h"
// 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 ( )
{
2022-03-01 20:00:42 +00:00
if ( response )
{
delete response ;
}
2020-04-22 16:56:21 +00:00
response = NULL ;
}
ConceptHistory_t : : ConceptHistory_t ( const ConceptHistory_t & src )
{
timeSpoken = src . timeSpoken ;
response = NULL ;
if ( src . response )
{
response = new AI_Response ( * src . response ) ;
}
}
ConceptHistory_t & ConceptHistory_t : : operator = ( const ConceptHistory_t & src )
{
2022-03-01 20:00:42 +00:00
if ( this = = & src )
return * this ;
2020-04-22 16:56:21 +00:00
2022-03-01 20:00:42 +00:00
timeSpoken = src . timeSpoken ;
response = NULL ;
if ( src . response )
{
response = new AI_Response ( * src . response ) ;
2020-04-22 16:56:21 +00: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 20:00:42 +00:00
2020-04-22 16:56:21 +00:00
// Write element name
pSave - > WriteString ( ch - > GetElementName ( i ) ) ;
// Write data
pSave - > WriteAll ( pHistory ) ;
// Write response blob
2022-03-01 20:00:42 +00:00
bool hasresponse = pHistory - > response ! = NULL ? true : false ;
2020-04-22 16:56:21 +00:00
pSave - > WriteBool ( & hasresponse ) ;
if ( hasresponse )
{
pSave - > WriteAll ( pHistory - > response ) ;
}
// 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 20:00:42 +00:00
2020-04-22 16:56:21 +00:00
pRestore - > ReadBool ( & hasresponse ) ;
if ( hasresponse )
{
history . response = new AI_Response ( ) ;
pRestore - > ReadAll ( history . response ) ;
}
}
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 20:00:42 +00:00
2020-04-22 16:56:21 +00: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 ;
//-----------------------------------------------------------------------------
//
// 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
CAI_Expresser : : CAI_Expresser ( CBaseFlex * pOuter )
: m_pOuter ( pOuter ) ,
m_pSink ( NULL ) ,
m_flStopTalkTime ( 0 ) ,
m_flLastTimeAcceptedSpeak ( 0 ) ,
m_flBlockedTalkTime ( 0 ) ,
m_flStopTalkTimeWithoutDelay ( 0 ) ,
m_voicePitch ( 100 )
{
# ifdef DEBUG
g_nExpressers + + ;
# endif
}
CAI_Expresser : : ~ CAI_Expresser ( )
{
m_ConceptHistories . Purge ( ) ;
CAI_TimedSemaphore * pSemaphore = GetMySpeechSemaphore ( GetOuter ( ) ) ;
if ( pSemaphore )
{
if ( pSemaphore - > GetOwner ( ) = = GetOuter ( ) )
pSemaphore - > Release ( ) ;
# ifdef DEBUG
g_nExpressers - - ;
if ( g_nExpressers = = 0 & & pSemaphore - > GetOwner ( ) )
DevMsg ( 2 , " Speech semaphore being held by non-talker entity \n " ) ;
# endif
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_Expresser : : TestAllResponses ( )
{
IResponseSystem * pResponseSystem = GetOuter ( ) - > GetResponseSystem ( ) ;
if ( pResponseSystem )
{
CUtlVector < AI_Response * > responses ;
pResponseSystem - > GetAllResponses ( & responses ) ;
for ( int i = 0 ; i < responses . Count ( ) ; i + + )
{
2022-03-01 20:00:42 +00:00
char response [ 256 ] ;
responses [ i ] - > GetResponse ( response , sizeof ( response ) ) ;
2020-04-22 16:56:21 +00:00
2022-03-01 20:00:42 +00:00
Msg ( " Response: %s \n " , response ) ;
SpeakDispatchResponse ( " " , responses [ i ] ) ;
2020-04-22 16:56:21 +00:00
}
}
}
//-----------------------------------------------------------------------------
static const int LEN_SPECIFIC_SCENE_MODIFIER = strlen ( AI_SPECIFIC_SCENE_MODIFIER ) ;
//-----------------------------------------------------------------------------
// Purpose: Searches for a possible response
// Input : concept -
// NULL -
// Output : AI_Response
//-----------------------------------------------------------------------------
2022-03-01 20:00:42 +00:00
AI_Response * CAI_Expresser : : SpeakFindResponse ( AIConcept_t concept , const char * modifiers /*= NULL*/ )
2020-04-22 16:56:21 +00:00
{
IResponseSystem * rs = GetOuter ( ) - > GetResponseSystem ( ) ;
if ( ! rs )
{
Assert ( ! " No response system installed for CAI_Expresser::GetOuter()!!! " ) ;
2022-03-01 20:00:42 +00:00
return NULL ;
2020-04-22 16:56:21 +00:00
}
AI_CriteriaSet set ;
// Always include the concept name
set . AppendCriteria ( " concept " , concept , CONCEPT_WEIGHT ) ;
// Always include any optional modifiers
2022-03-01 20:00:42 +00:00
if ( modifiers ! = NULL )
2020-04-22 16:56:21 +00: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 )
{
pCopy = SplitContext ( pCopy , key , sizeof ( key ) , value , sizeof ( value ) , NULL ) ;
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 ) ;
}
// Now that we have a criteria set, ask for a suitable response
2022-03-01 20:00:42 +00:00
AI_Response * result = new AI_Response ;
Assert ( result & & " new AI_Response: Returned a NULL AI_Response! " ) ;
bool found = rs - > FindBestResponse ( set , * result , this ) ;
2020-04-22 16:56:21 +00:00
if ( rr_debugresponses . GetInt ( ) = = 3 )
{
if ( ( GetOuter ( ) - > MyNPCPointer ( ) & & GetOuter ( ) - > m_debugOverlays & OVERLAY_NPC_SELECTED_BIT ) | | GetOuter ( ) - > IsPlayer ( ) )
{
2022-03-01 20:00:42 +00:00
const char * pszName ;
if ( GetOuter ( ) - > IsPlayer ( ) )
{
pszName = ( ( CBasePlayer * ) GetOuter ( ) ) - > GetPlayerName ( ) ;
}
else
{
pszName = GetOuter ( ) - > GetDebugName ( ) ;
}
2020-04-22 16:56:21 +00:00
if ( found )
{
2022-03-01 20:00:42 +00:00
char response [ 256 ] ;
result - > GetResponse ( response , sizeof ( response ) ) ;
Warning ( " RESPONSERULES: %s spoke '%s'. Found response '%s'. \n " , pszName , concept , response ) ;
2020-04-22 16:56:21 +00:00
}
else
{
Warning ( " RESPONSERULES: %s spoke '%s'. Found no matching response. \n " , pszName , concept ) ;
}
}
}
if ( ! found )
2022-03-01 20:00:42 +00:00
{
//Assert( !"rs->FindBestResponse: Returned a NULL AI_Response!" );
delete result ;
return NULL ;
}
2020-04-22 16:56:21 +00:00
2022-03-01 20:00:42 +00:00
char response [ 256 ] ;
result - > GetResponse ( response , sizeof ( response ) ) ;
2020-04-22 16:56:21 +00:00
2022-03-01 20:00:42 +00:00
if ( ! response [ 0 ] )
{
delete result ;
return NULL ;
}
2020-04-22 16:56:21 +00:00
2022-03-01 20:00:42 +00:00
if ( result - > GetOdds ( ) < 100 & & random - > RandomInt ( 1 , 100 ) < = result - > GetOdds ( ) )
{
delete result ;
return NULL ;
}
return result ;
2020-04-22 16:56:21 +00:00
}
//-----------------------------------------------------------------------------
// Purpose: Dispatches the result
// Input : *response -
//-----------------------------------------------------------------------------
2022-03-01 20:00:42 +00:00
bool CAI_Expresser : : SpeakDispatchResponse ( AIConcept_t concept , AI_Response * result , IRecipientFilter * filter /* = NULL */ )
2020-04-22 16:56:21 +00:00
{
2022-03-01 20:00:42 +00:00
char response [ 256 ] ;
result - > GetResponse ( response , sizeof ( response ) ) ;
float delay = result - > GetDelay ( ) ;
2020-04-22 16:56:21 +00:00
bool spoke = false ;
2022-03-01 20:00:42 +00:00
soundlevel_t soundlevel = result - > GetSoundLevel ( ) ;
2020-04-22 16:56:21 +00:00
if ( IsSpeaking ( ) & & concept [ 0 ] ! = 0 )
{
DevMsg ( " SpeakDispatchResponse: Entity ( %i/%s ) already speaking, forcing '%s' \n " , GetOuter ( ) - > entindex ( ) , STRING ( GetOuter ( ) - > GetEntityName ( ) ) , concept ) ;
// 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 ( ) ) )
{
DevMsg ( " SpeakDispatchResponse: Entity ( %i/%s ) refusing to speak due to scene entity, tossing '%s' \n " , GetOuter ( ) - > entindex ( ) , STRING ( GetOuter ( ) - > GetEntityName ( ) ) , concept ) ;
2022-03-01 20:00:42 +00:00
delete result ;
2020-04-22 16:56:21 +00:00
return false ;
}
}
2022-03-01 20:00:42 +00:00
switch ( result - > GetType ( ) )
2020-04-22 16:56:21 +00:00
{
default :
case RESPONSE_NONE :
break ;
case RESPONSE_SPEAK :
{
2022-03-01 20:00:42 +00: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 16:56:21 +00:00
2022-03-01 20:00:42 +00:00
DevMsg ( " SpeakDispatchResponse: Entity ( %i/%s ) playing sound '%s' \n " , GetOuter ( ) - > entindex ( ) , STRING ( GetOuter ( ) - > GetEntityName ( ) ) , response ) ;
NoteSpeaking ( speakTime , delay ) ;
spoke = true ;
}
2020-04-22 16:56:21 +00:00
}
break ;
case RESPONSE_SENTENCE :
2022-03-01 20:00:42 +00:00
{
spoke = ( - 1 ! = SpeakRawSentence ( response , delay , VOL_NORM , soundlevel ) ) ? true : false ;
}
2020-04-22 16:56:21 +00:00
break ;
case RESPONSE_SCENE :
2022-03-01 20:00:42 +00:00
{
spoke = SpeakRawScene ( response , delay , result , filter ) ;
}
2020-04-22 16:56:21 +00:00
break ;
case RESPONSE_RESPONSE :
2022-03-01 20:00:42 +00:00
{
// This should have been recursively resolved already
Assert ( 0 ) ;
}
2020-04-22 16:56:21 +00:00
break ;
case RESPONSE_PRINT :
{
2022-03-01 20:00:42 +00: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 ) ;
spoke = true ;
}
2020-04-22 16:56:21 +00:00
}
break ;
}
if ( spoke )
{
m_flLastTimeAcceptedSpeak = gpGlobals - > curtime ;
2022-03-01 20:00:42 +00:00
if ( DebuggingSpeech ( ) & & g_pDeveloper - > GetInt ( ) > 0 & & response & & result - > GetType ( ) ! = RESPONSE_PRINT )
2020-04-22 16:56:21 +00:00
{
Vector vPrintPos ;
GetOuter ( ) - > CollisionProp ( ) - > NormalizedToWorldSpace ( Vector ( 0.5 , 0.5 , 1.0f ) , & vPrintPos ) ;
2022-03-01 20:00:42 +00:00
NDebugOverlay : : Text ( vPrintPos , CFmtStr ( " %s: %s " , concept , response ) , true , 1.5 ) ;
2020-04-22 16:56:21 +00:00
}
2022-03-01 20:00:42 +00:00
if ( result - > IsApplyContextToWorld ( ) )
2020-04-22 16:56:21 +00:00
{
CBaseEntity * pEntity = CBaseEntity : : Instance ( engine - > PEntityOfEntIndex ( 0 ) ) ;
if ( pEntity )
{
2022-03-01 20:00:42 +00:00
pEntity - > AddContext ( result - > GetContext ( ) ) ;
2020-04-22 16:56:21 +00:00
}
}
else
{
2022-03-01 20:00:42 +00:00
GetOuter ( ) - > AddContext ( result - > GetContext ( ) ) ;
2020-04-22 16:56:21 +00:00
}
2022-03-01 20:00:42 +00:00
SetSpokeConcept ( concept , result ) ;
}
else
{
delete result ;
2020-04-22 16:56:21 +00:00
}
return spoke ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *response -
// Output : float
//-----------------------------------------------------------------------------
2022-03-01 20:00:42 +00:00
float CAI_Expresser : : GetResponseDuration ( AI_Response * result )
2020-04-22 16:56:21 +00:00
{
2022-03-01 20:00:42 +00:00
Assert ( result ) ;
char response [ 256 ] ;
result - > GetResponse ( response , sizeof ( response ) ) ;
2020-04-22 16:56:21 +00:00
2022-03-01 20:00:42 +00:00
switch ( result - > GetType ( ) )
2020-04-22 16:56:21 +00:00
{
default :
case RESPONSE_NONE :
break ;
case RESPONSE_SPEAK :
2022-03-01 20:00:42 +00:00
{
return GetOuter ( ) - > GetSoundDuration ( response , STRING ( GetOuter ( ) - > GetModelName ( ) ) ) ;
}
break ;
2020-04-22 16:56:21 +00:00
case RESPONSE_SENTENCE :
2022-03-01 20:00:42 +00:00
{
Assert ( 0 ) ;
return 999.0f ;
}
break ;
2020-04-22 16:56:21 +00:00
case RESPONSE_SCENE :
2022-03-01 20:00:42 +00:00
{
return GetSceneDuration ( response ) ;
}
break ;
2020-04-22 16:56:21 +00:00
case RESPONSE_RESPONSE :
2022-03-01 20:00:42 +00:00
{
// This should have been recursively resolved already
Assert ( 0 ) ;
}
2020-04-22 16:56:21 +00:00
break ;
case RESPONSE_PRINT :
2022-03-01 20:00:42 +00:00
{
return 1.0 ;
}
break ;
2020-04-22 16:56:21 +00:00
}
return 0.0f ;
}
//-----------------------------------------------------------------------------
// Purpose: Placeholder for rules based response system
// Input : concept -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_Expresser : : Speak ( AIConcept_t concept , const char * modifiers /*= NULL*/ , char * pszOutResponseChosen /* = NULL*/ , size_t bufsize /* = 0 */ , IRecipientFilter * filter /* = NULL */ )
{
2022-03-01 20:00:42 +00:00
AI_Response * result = SpeakFindResponse ( concept , modifiers ) ;
2020-04-22 16:56:21 +00:00
if ( ! result )
2022-03-01 20:00:42 +00:00
{
2020-04-22 16:56:21 +00:00
return false ;
2022-03-01 20:00:42 +00:00
}
2020-04-22 16:56:21 +00:00
SpeechMsg ( GetOuter ( ) , " %s (%p) spoke %s (%f) \n " , STRING ( GetOuter ( ) - > GetEntityName ( ) ) , GetOuter ( ) , concept , gpGlobals - > curtime ) ;
2022-03-01 20:00:42 +00:00
bool spoke = SpeakDispatchResponse ( concept , result , filter ) ;
2020-04-22 16:56:21 +00:00
if ( pszOutResponseChosen )
{
2022-03-01 20:00:42 +00:00
result - > GetResponse ( pszOutResponseChosen , bufsize ) ;
2020-04-22 16:56:21 +00: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 ) ;
# if defined( HL2_EPISODIC ) || defined( TF_DLL )
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 ) ;
AI_Response * response = history - > response ;
if ( ! response )
return true ;
if ( response - > GetSpeakOnce ( ) )
return false ;
float respeakDelay = response - > GetRespeakDelay ( ) ;
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 )
{
2022-03-01 20:00:42 +00:00
AI_Response * r = slot - > response ;
if ( r )
{
delete r ;
}
// FIXME: Are we leaking AI_Responses?
slot - > response = response ;
2020-04-22 16:56:21 +00: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 )
{
if ( type = = RESPONSE_SCENE )
{
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 ( ) )
return NULL ;
return ( pNpc - > MyNPCPointer ( ) - > IsPlayerAlly ( ) ? & g_AIFriendliesTalkSemaphore : & g_AIFoesTalkSemaphore ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_Expresser : : SpeechMsg ( CBaseEntity * pFlex , const char * pszFormat , . . . )
{
if ( ! DebuggingSpeech ( ) )
return ;
char string [ 2048 ] ;
va_list argptr ;
va_start ( argptr , pszFormat ) ;
Q_vsnprintf ( string , sizeof ( string ) , pszFormat , argptr ) ;
va_end ( argptr ) ;
if ( pFlex - > MyNPCPointer ( ) )
{
2022-03-01 20:00:42 +00:00
DevMsg ( pFlex - > MyNPCPointer ( ) , string ) ;
2020-04-22 16:56:21 +00:00
}
else
{
DevMsg ( " %s " , string ) ;
}
2022-03-01 20:00:42 +00:00
UTIL_LogPrintf ( string ) ;
2020-04-22 16:56:21 +00: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 " ) ;
}
}
//-----------------------------------------------------------------------------
//=============================================================================
// HPE_BEGIN:
// [Forrest] Remove npc_speakall from Counter-Strike.
//=============================================================================
# ifndef CSTRIKE_DLL
extern CBaseEntity * FindPickerEntity ( CBasePlayer * pPlayer ) ;
CON_COMMAND ( npc_speakall , " Force the npc to try and speak all their responses " )
{
if ( ! UTIL_IsCommandIssuedByServerAdmin ( ) )
return ;
CBaseEntity * pEntity ;
if ( args [ 1 ] & & * args [ 1 ] )
{
pEntity = gEntList . FindEntityByName ( NULL , args [ 1 ] , NULL ) ;
if ( ! pEntity )
{
pEntity = gEntList . FindEntityByClassname ( NULL , args [ 1 ] ) ;
}
}
else
{
pEntity = FindPickerEntity ( UTIL_GetCommandClient ( ) ) ;
}
if ( pEntity )
{
CAI_BaseNPC * pNPC = pEntity - > MyNPCPointer ( ) ;
if ( pNPC )
{
if ( pNPC - > GetExpresser ( ) )
{
bool save = engine - > LockNetworkStringTables ( false ) ;
pNPC - > GetExpresser ( ) - > TestAllResponses ( ) ;
engine - > LockNetworkStringTables ( save ) ;
}
}
}
}
# endif
//=============================================================================
// HPE_END
//=============================================================================
//-----------------------------------------------------------------------------
CMultiplayer_Expresser : : CMultiplayer_Expresser ( CBaseFlex * pOuter ) : CAI_Expresser ( pOuter )
{
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 ;
}