|
|
//========= 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() { delete response; 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 ) { if ( this != &src ) { timeSpoken = src.timeSpoken;
delete response; response = NULL; if ( src.response ) { response = new AI_Response( *src.response ); } }
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(); { // Write element name
pSave->WriteString( ch->GetElementName( i ) );
// Write data
pSave->WriteAll( pHistory );
// Write response blob
bool hasresponse = !!pHistory->response; 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; 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!" ); } } }
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++ ) { const char *szResponse = responses[i]->GetResponsePtr();
Msg( "Response: %s\n", szResponse ); SpeakDispatchResponse( "", *responses[i] ); } } }
//-----------------------------------------------------------------------------
static const int LEN_SPECIFIC_SCENE_MODIFIER = strlen( AI_SPECIFIC_SCENE_MODIFIER );
//-----------------------------------------------------------------------------
// Purpose: Searches for a possible response
// Input : concept -
// NULL -
// Output : AI_Response
//-----------------------------------------------------------------------------
bool CAI_Expresser::SpeakFindResponse( AI_Response &outResponse, AIConcept_t concept, const char *modifiers /*= NULL*/ ) { IResponseSystem *rs = GetOuter()->GetResponseSystem(); if ( !rs ) { Assert( !"No response system installed for CAI_Expresser::GetOuter()!!!" ); return false; }
AI_CriteriaSet set; // Always include the concept name
set.AppendCriteria( "concept", concept, CONCEPT_WEIGHT );
// Always include any optional modifiers
if ( modifiers ) { 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
bool found = rs->FindBestResponse( set, outResponse, this );
if ( rr_debugresponses.GetInt() == 3 ) { if ( ( GetOuter()->MyNPCPointer() && GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT ) || GetOuter()->IsPlayer() ) { const char *pszName = GetOuter()->IsPlayer() ? ((CBasePlayer*)GetOuter())->GetPlayerName() : GetOuter()->GetDebugName();
if ( found ) { const char *szReponse = outResponse.GetResponsePtr(); Warning( "RESPONSERULES: %s spoke '%s'. Found response '%s'.\n", pszName, concept, szReponse ); } else { Warning( "RESPONSERULES: %s spoke '%s'. Found no matching response.\n", pszName, concept ); } } }
if ( !found ) return false;
const char *szReponse = outResponse.GetResponsePtr(); if ( !szReponse[0] ) return false;
if ( ( outResponse.GetOdds() < 100 ) && ( random->RandomInt( 1, 100 ) <= outResponse.GetOdds() ) ) return false;
return true; }
//-----------------------------------------------------------------------------
// Purpose: Dispatches the result
// Input : *response -
//-----------------------------------------------------------------------------
bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response& response, IRecipientFilter *filter /* = NULL */ ) { bool spoke = false; float delay = response.GetDelay(); const char *szResponse = response.GetResponsePtr(); soundlevel_t soundlevel = response.GetSoundLevel();
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 ); return false; } }
switch ( response.GetType() ) { default: case RESPONSE_NONE: break;
case RESPONSE_SPEAK: if ( !response.ShouldntUseScene() ) { // This generates a fake CChoreoScene wrapping the sound.txt name
spoke = SpeakAutoGeneratedScene( szResponse, delay ); } else { float speakTime = GetResponseDuration( response ); GetOuter()->EmitSound( szResponse );
DevMsg( "SpeakDispatchResponse: Entity ( %i/%s ) playing sound '%s'\n", GetOuter()->entindex(), STRING( GetOuter()->GetEntityName() ), szResponse ); NoteSpeaking( speakTime, delay ); spoke = true; } break;
case RESPONSE_SENTENCE: spoke = ( -1 != SpeakRawSentence( szResponse, delay, VOL_NORM, soundlevel ) ) ? true : false; break;
case RESPONSE_SCENE: spoke = SpeakRawScene( szResponse, delay, &response, filter ); break;
case RESPONSE_RESPONSE: // This should have been recursively resolved already
Assert( 0 ); break; case RESPONSE_PRINT: if ( g_pDeveloper->GetInt() > 0 ) { Vector vPrintPos; GetOuter()->CollisionProp()->NormalizedToWorldSpace( Vector(0.5,0.5,1.0f), &vPrintPos ); NDebugOverlay::Text( vPrintPos, szResponse, true, 1.5 ); spoke = true; } break; }
if ( spoke ) { m_flLastTimeAcceptedSpeak = gpGlobals->curtime; if ( DebuggingSpeech() && g_pDeveloper->GetInt() > 0 && response.GetType() != RESPONSE_PRINT ) { Vector vPrintPos; GetOuter()->CollisionProp()->NormalizedToWorldSpace( Vector(0.5,0.5,1.0f), &vPrintPos ); NDebugOverlay::Text( vPrintPos, CFmtStr( "%s: %s", concept, szResponse ), true, 1.5 ); }
if ( response.IsApplyContextToWorld() ) { CBaseEntity *pEntity = CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) ); if ( pEntity ) { pEntity->AddContext( response.GetContext() ); } } else { GetOuter()->AddContext( response.GetContext() ); }
SetSpokeConcept( concept, &response ); }
return spoke; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : *response -
// Output : float
//-----------------------------------------------------------------------------
float CAI_Expresser::GetResponseDuration( AI_Response& response ) { const char *szResponse = response.GetResponsePtr();
switch ( response.GetType() ) { default: case RESPONSE_NONE: break;
case RESPONSE_SPEAK: return GetOuter()->GetSoundDuration( szResponse, STRING( GetOuter()->GetModelName() ) );
case RESPONSE_SENTENCE: Assert( 0 ); return 999.0f;
case RESPONSE_SCENE: return GetSceneDuration( szResponse );
case RESPONSE_RESPONSE: // This should have been recursively resolved already
Assert( 0 ); break;
case RESPONSE_PRINT: return 1.0; }
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 */ ) { AI_Response response; bool result = SpeakFindResponse( response, concept, modifiers ); if ( !result ) return false;
SpeechMsg( GetOuter(), "%s (%p) spoke %s (%f)\n", STRING(GetOuter()->GetEntityName()), GetOuter(), concept, gpGlobals->curtime );
bool spoke = SpeakDispatchResponse( concept, response, filter ); if ( pszOutResponseChosen ) { const char *szResponse = response.GetResponsePtr(); Q_strncpy( pszOutResponseChosen, szResponse, bufsize ); } 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 ) { delete slot->response; slot->response = new AI_Response( *response ); }
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() ) { DevMsg( pFlex->MyNPCPointer(), "%s", string ); } else { DevMsg( "%s", string ); } UTIL_LogPrintf( "%s", string ); }
//-----------------------------------------------------------------------------
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; }
|