aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/ai_playerally.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/ai_playerally.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/ai_playerally.cpp')
-rw-r--r--mp/src/game/server/ai_playerally.cpp3592
1 files changed, 1796 insertions, 1796 deletions
diff --git a/mp/src/game/server/ai_playerally.cpp b/mp/src/game/server/ai_playerally.cpp
index ac644805..1f371fc9 100644
--- a/mp/src/game/server/ai_playerally.cpp
+++ b/mp/src/game/server/ai_playerally.cpp
@@ -1,1796 +1,1796 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-#include "cbase.h"
-
-#include "sceneentity.h"
-#include "ai_playerally.h"
-#include "saverestore_utlmap.h"
-#include "eventqueue.h"
-#include "ai_behavior_lead.h"
-#include "gameinterface.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-extern CServerGameDLL g_ServerGameDLL;
-
-extern ConVar rr_debugresponses;
-
-//-----------------------------------------------------------------------------
-
-ConVar sk_ally_regen_time( "sk_ally_regen_time", "0.3003", FCVAR_NONE, "Time taken for an ally to regenerate a point of health." );
-ConVar sv_npc_talker_maxdist( "sv_npc_talker_maxdist", "1024", 0, "NPCs over this distance from the player won't attempt to speak." );
-ConVar ai_no_talk_delay( "ai_no_talk_delay", "0" );
-
-ConVar rr_debug_qa( "rr_debug_qa", "0", FCVAR_NONE, "Set to 1 to see debug related to the Question & Answer system used to create conversations between allied NPCs.");
-
-//-----------------------------------------------------------------------------
-
-ConceptCategoryInfo_t g_ConceptCategoryInfos[] =
-{
- { 10, 20, 0, 0 },
- { 0, 0, 0, 0 },
- { 0, 0, 0, 0 },
-};
-
-ConceptInfo_t g_ConceptInfos[] =
-{
- { TLK_ANSWER, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_ANSWER, },
- { TLK_ANSWER_HELLO, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_ANSWER, },
- { TLK_QUESTION, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_QUESTION, },
- { TLK_IDLE, SPEECH_IDLE, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_TARGET_PLAYER, },
- { TLK_STARE, SPEECH_IDLE, -1, -1, -1, -1, 180, 0, AICF_DEFAULT | AICF_TARGET_PLAYER, },
- { TLK_LOOK, SPEECH_PRIORITY, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_TARGET_PLAYER, },
- { TLK_HELLO, SPEECH_IDLE, 5, 10, -1, -1, -1, -1, AICF_DEFAULT | AICF_SPEAK_ONCE | AICF_PROPAGATE_SPOKEN | AICF_TARGET_PLAYER, },
- { TLK_PHELLO, SPEECH_IDLE, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_SPEAK_ONCE | AICF_PROPAGATE_SPOKEN | AICF_TARGET_PLAYER, },
- { TLK_HELLO_NPC, SPEECH_IDLE, 5, 10, -1, -1, -1, -1, AICF_DEFAULT | AICF_QUESTION | AICF_SPEAK_ONCE, },
- { TLK_PIDLE, SPEECH_IDLE, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_PQUESTION, SPEECH_IDLE, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_SMELL, SPEECH_IDLE, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_USE, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_STARTFOLLOW, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_STOPFOLLOW, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_JOINPLAYER, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_STOP, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_NOSHOOT, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_PLHURT1, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_PLHURT2, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_PLHURT3, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_PLHURT, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_PLPUSH, SPEECH_IMPORTANT, -1, -1, -1, -1, 15, 30, AICF_DEFAULT, },
- { TLK_PLRELOAD, SPEECH_IMPORTANT, -1, -1, -1, -1, 60, 0, AICF_DEFAULT, },
- { TLK_SHOT, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_WOUND, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_MORTAL, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_SPEAK_ONCE, },
- { TLK_SEE_COMBINE, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_ENEMY_DEAD, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_ALYX_ENEMY_DEAD,SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_SELECTED, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_COMMANDED, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_COMMAND_FAILED, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_DENY_COMMAND, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_BETRAYED, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_ALLY_KILLED, SPEECH_IMPORTANT, -1, -1, -1, -1, 15, 30, AICF_DEFAULT, },
- { TLK_ATTACKING, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_HEAL, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_GIVEAMMO, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_HELP_ME, SPEECH_IMPORTANT, -1, -1, -1, -1, 7, 10, AICF_DEFAULT, },
- { TLK_PLYR_PHYSATK, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_NEWWEAPON, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_STARTCOMBAT, SPEECH_IMPORTANT, -1, -1, -1, -1, 30, 0, AICF_DEFAULT, },
- { TLK_WATCHOUT, SPEECH_IMPORTANT, -1, -1, -1, -1, 30, 45, AICF_DEFAULT, },
- { TLK_MOBBED, SPEECH_IMPORTANT, -1, -1, -1, -1, 10, 12, AICF_DEFAULT, },
- { TLK_MANY_ENEMIES, SPEECH_IMPORTANT, -1, -1, -1, -1, 45, 60, AICF_DEFAULT, },
- { TLK_DANGER, SPEECH_PRIORITY, -1, -1, -1, -1, 5, 7, AICF_DEFAULT, },
- { TLK_PLDEAD, SPEECH_PRIORITY, -1, -1, -1, -1, 100, 0, AICF_DEFAULT, },
- { TLK_HIDEANDRELOAD, SPEECH_PRIORITY, -1, -1, -1, -1, 45, 60, AICF_DEFAULT, },
- { TLK_FLASHLIGHT_ILLUM, SPEECH_PRIORITY,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_FLASHLIGHT_ON, SPEECH_PRIORITY, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_FLASHLIGHT_OFF, SPEECH_PRIORITY, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_FOUNDPLAYER, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_TARGET_PLAYER, },
- { TLK_PLAYER_KILLED_NPC, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_ENEMY_BURNING, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_SPOTTED_ZOMBIE_WAKEUP, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_SPOTTED_HEADCRAB_LEAVING_ZOMBIE,SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_DANGER_ZOMBINE_GRENADE, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_BALLSOCKETED, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
-
- // Darkness mode
- { TLK_DARKNESS_LOSTPLAYER, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_DARKNESS_FOUNDPLAYER, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_DARKNESS_UNKNOWN_WOUND, SPEECH_IMPORTANT,-1,-1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_DARKNESS_HEARDSOUND, SPEECH_IMPORTANT,-1, -1, -1, -1, 20, 30, AICF_DEFAULT, },
- { TLK_DARKNESS_LOSTENEMY_BY_FLASHLIGHT, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_DARKNESS_LOSTENEMY_BY_FLASHLIGHT_EXPIRED, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_DARKNESS_FOUNDENEMY_BY_FLASHLIGHT, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_DARKNESS_FLASHLIGHT_EXPIRED, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_SPOTTED_INCOMING_HEADCRAB, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
-
- // Lead behaviour
- { TLK_LEAD_START, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_LEAD_ARRIVAL, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_LEAD_SUCCESS, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_LEAD_FAILURE, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_LEAD_COMINGBACK,SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_LEAD_CATCHUP, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_LEAD_RETRIEVE, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_LEAD_ATTRACTPLAYER, SPEECH_IMPORTANT,-1,-1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_LEAD_WAITOVER, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_LEAD_MISSINGWEAPON, SPEECH_IMPORTANT,-1,-1, -1, -1, -1, -1, AICF_DEFAULT, },
- { TLK_LEAD_IDLE, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
-
- // Passenger behaviour
- { TLK_PASSENGER_NEW_RADAR_CONTACT, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
-};
-
-//-----------------------------------------------------------------------------
-
-bool ConceptStringLessFunc( const string_t &lhs, const string_t &rhs )
-{
- return CaselessStringLessThan( STRING(lhs), STRING(rhs) );
-}
-
-//-----------------------------------------------------------------------------
-
-class CConceptInfoMap : public CUtlMap<AIConcept_t, ConceptInfo_t *> {
-public:
- CConceptInfoMap() :
- CUtlMap<AIConcept_t, ConceptInfo_t *>( CaselessStringLessThan )
- {
- for ( int i = 0; i < ARRAYSIZE(g_ConceptInfos); i++ )
- {
- Insert( g_ConceptInfos[i].concept, &g_ConceptInfos[i] );
- }
- }
-};
-
-static CConceptInfoMap g_ConceptInfoMap;
-
-CAI_AllySpeechManager::CAI_AllySpeechManager()
-{
- m_ConceptTimers.SetLessFunc( ConceptStringLessFunc );
- Assert( !gm_pSpeechManager );
- gm_pSpeechManager = this;
-}
-
-CAI_AllySpeechManager::~CAI_AllySpeechManager()
-{
- gm_pSpeechManager = NULL;
-}
-
-void CAI_AllySpeechManager::Spawn()
-{
- Assert( g_ConceptInfoMap.Count() != 0 );
- for ( int i = 0; i < ARRAYSIZE(g_ConceptInfos); i++ )
- m_ConceptTimers.Insert( AllocPooledString( g_ConceptInfos[i].concept ), CSimpleSimTimer() );
-}
-
-void CAI_AllySpeechManager::AddCustomConcept( const ConceptInfo_t &conceptInfo )
-{
- Assert( g_ConceptInfoMap.Count() != 0 );
- Assert( m_ConceptTimers.Count() != 0 );
-
- if ( g_ConceptInfoMap.Find( conceptInfo.concept ) == g_ConceptInfoMap.InvalidIndex() )
- {
- g_ConceptInfoMap.Insert( conceptInfo.concept, new ConceptInfo_t( conceptInfo ) );
- }
-
- if ( m_ConceptTimers.Find( AllocPooledString(conceptInfo.concept) ) == m_ConceptTimers.InvalidIndex() )
- {
- m_ConceptTimers.Insert( AllocPooledString( conceptInfo.concept ), CSimpleSimTimer() );
- }
-}
-
-ConceptCategoryInfo_t *CAI_AllySpeechManager::GetConceptCategoryInfo( ConceptCategory_t category )
-{
- return &g_ConceptCategoryInfos[category];
-}
-
-ConceptInfo_t *CAI_AllySpeechManager::GetConceptInfo( AIConcept_t concept )
-{
- int iResult = g_ConceptInfoMap.Find( concept );
-
- return ( iResult != g_ConceptInfoMap.InvalidIndex() ) ? g_ConceptInfoMap[iResult] : NULL;
-}
-
-void CAI_AllySpeechManager::OnSpokeConcept( CAI_PlayerAlly *pPlayerAlly, AIConcept_t concept, AI_Response *response )
-{
- ConceptInfo_t * pConceptInfo = GetConceptInfo( concept );
- ConceptCategory_t category = ( pConceptInfo ) ? pConceptInfo->category : SPEECH_IDLE;
- ConceptCategoryInfo_t * pCategoryInfo = GetConceptCategoryInfo( category );
-
- if ( pConceptInfo )
- {
- if ( pConceptInfo->flags & AICF_PROPAGATE_SPOKEN )
- {
- CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
- CAI_PlayerAlly *pTalker;
- for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
- {
- pTalker = dynamic_cast<CAI_PlayerAlly *>(ppAIs[i]);
-
- if ( pTalker && pTalker != pPlayerAlly &&
- (pTalker->GetAbsOrigin() - pPlayerAlly->GetAbsOrigin()).LengthSqr() < Square(TALKRANGE_MIN * 2) &&
- pPlayerAlly->FVisible( pTalker ) )
- {
- // Tell this guy he's already said the concept to the player, too.
- pTalker->GetExpresser()->SetSpokeConcept( concept, NULL, false );
- }
- }
- }
- }
-
- if ( !ai_no_talk_delay.GetBool() )
- {
- if ( pConceptInfo && pConceptInfo->minGlobalCategoryDelay != -1 )
- {
- Assert( pConceptInfo->maxGlobalCategoryDelay != -1 );
- SetCategoryDelay( pConceptInfo->category, pConceptInfo->minGlobalCategoryDelay, pConceptInfo->maxGlobalCategoryDelay );
- }
- else if ( pCategoryInfo->maxGlobalDelay > 0 )
- {
- SetCategoryDelay( category, pCategoryInfo->minGlobalDelay, pCategoryInfo->maxGlobalDelay );
- }
-
- if ( pConceptInfo && pConceptInfo->minPersonalCategoryDelay != -1 )
- {
- Assert( pConceptInfo->maxPersonalCategoryDelay != -1 );
- pPlayerAlly->SetCategoryDelay( pConceptInfo->category, pConceptInfo->minPersonalCategoryDelay, pConceptInfo->maxPersonalCategoryDelay );
- }
- else if ( pCategoryInfo->maxPersonalDelay > 0 )
- {
- pPlayerAlly->SetCategoryDelay( category, pCategoryInfo->minPersonalDelay, pCategoryInfo->maxPersonalDelay );
- }
-
- if ( pConceptInfo && pConceptInfo->minConceptDelay != -1 )
- {
- Assert( pConceptInfo->maxConceptDelay != -1 );
- char iConceptTimer = m_ConceptTimers.Find( MAKE_STRING(concept) );
- if ( iConceptTimer != m_ConceptTimers.InvalidIndex() )
- m_ConceptTimers[iConceptTimer].Set( pConceptInfo->minConceptDelay, pConceptInfo->minConceptDelay );
- }
- }
-}
-
-void CAI_AllySpeechManager::SetCategoryDelay( ConceptCategory_t category, float minDelay, float maxDelay )
-{
- if ( category != SPEECH_PRIORITY )
- m_ConceptCategoryTimers[category].Set( minDelay, maxDelay );
-}
-
-bool CAI_AllySpeechManager::CategoryDelayExpired( ConceptCategory_t category )
-{
- if ( category == SPEECH_PRIORITY )
- return true;
- return m_ConceptCategoryTimers[category].Expired();
-}
-
-bool CAI_AllySpeechManager::ConceptDelayExpired( AIConcept_t concept )
-{
- char iConceptTimer = m_ConceptTimers.Find( MAKE_STRING(concept) );
- if ( iConceptTimer != m_ConceptTimers.InvalidIndex() )
- return m_ConceptTimers[iConceptTimer].Expired();
- return true;
-}
-
-//-----------------------------------------------------------------------------
-
-LINK_ENTITY_TO_CLASS( ai_ally_speech_manager, CAI_AllySpeechManager );
-
-BEGIN_DATADESC( CAI_AllySpeechManager )
-
- DEFINE_EMBEDDED_AUTO_ARRAY(m_ConceptCategoryTimers),
- DEFINE_UTLMAP( m_ConceptTimers, FIELD_STRING, FIELD_EMBEDDED ),
-
-END_DATADESC()
-
-//-----------------------------------------------------------------------------
-
-CAI_AllySpeechManager *CAI_AllySpeechManager::gm_pSpeechManager;
-
-//-----------------------------------------------------------------------------
-
-CAI_AllySpeechManager *GetAllySpeechManager()
-{
- if ( !CAI_AllySpeechManager::gm_pSpeechManager )
- {
- CreateEntityByName( "ai_ally_speech_manager" );
- Assert( CAI_AllySpeechManager::gm_pSpeechManager );
- if ( CAI_AllySpeechManager::gm_pSpeechManager )
- DispatchSpawn( CAI_AllySpeechManager::gm_pSpeechManager );
- }
-
- return CAI_AllySpeechManager::gm_pSpeechManager;
-}
-
-//-----------------------------------------------------------------------------
-//
-// CLASS: CAI_PlayerAlly
-//
-//-----------------------------------------------------------------------------
-
-BEGIN_DATADESC( CAI_PlayerAlly )
-
- DEFINE_EMBEDDED( m_PendingResponse ),
- DEFINE_STDSTRING( m_PendingConcept ),
- DEFINE_FIELD( m_TimePendingSet, FIELD_TIME ),
- DEFINE_FIELD( m_hTalkTarget, FIELD_EHANDLE ),
- DEFINE_FIELD( m_flNextRegenTime, FIELD_TIME ),
- DEFINE_FIELD( m_flTimePlayerStartStare, FIELD_TIME ),
- DEFINE_FIELD( m_hPotentialSpeechTarget, FIELD_EHANDLE ),
- DEFINE_FIELD( m_flNextIdleSpeechTime, FIELD_TIME ),
- DEFINE_FIELD( m_iQARandomNumber, FIELD_INTEGER ),
- DEFINE_FIELD( m_hSpeechFilter, FIELD_EHANDLE ),
- DEFINE_EMBEDDED_AUTO_ARRAY(m_ConceptCategoryTimers),
-
- DEFINE_KEYFIELD( m_bGameEndAlly, FIELD_BOOLEAN, "GameEndAlly" ),
- DEFINE_FIELD( m_bCanSpeakWhileScripting, FIELD_BOOLEAN ),
-
- DEFINE_FIELD( m_flHealthAccumulator, FIELD_FLOAT ),
- DEFINE_FIELD( m_flTimeLastRegen, FIELD_TIME ),
-
- // Inputs
- DEFINE_INPUTFUNC( FIELD_VOID, "IdleRespond", InputIdleRespond ),
- DEFINE_INPUTFUNC( FIELD_STRING, "SpeakResponseConcept", InputSpeakResponseConcept ),
- DEFINE_INPUTFUNC( FIELD_VOID, "MakeGameEndAlly", InputMakeGameEndAlly ),
- DEFINE_INPUTFUNC( FIELD_VOID, "MakeRegularAlly", InputMakeRegularAlly ),
- DEFINE_INPUTFUNC( FIELD_INTEGER, "AnswerQuestion", InputAnswerQuestion ),
- DEFINE_INPUTFUNC( FIELD_INTEGER, "AnswerQuestionHello", InputAnswerQuestionHello ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableSpeakWhileScripting", InputEnableSpeakWhileScripting ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableSpeakWhileScripting", InputDisableSpeakWhileScripting ),
-
-END_DATADESC()
-
-CBaseEntity *CreatePlayerLoadSave( Vector vOrigin, float flDuration, float flHoldTime, float flLoadTime );
-ConVar npc_ally_deathmessage( "npc_ally_deathmessage", "1", FCVAR_CHEAT );
-
-void CAI_PlayerAlly::InputMakeGameEndAlly( inputdata_t &inputdata )
-{
- m_bGameEndAlly = true;
-}
-
-void CAI_PlayerAlly::InputMakeRegularAlly( inputdata_t &inputdata )
-{
- m_bGameEndAlly = false;
-}
-
-void CAI_PlayerAlly::DisplayDeathMessage( void )
-{
- if ( m_bGameEndAlly == false )
- return;
-
- if ( npc_ally_deathmessage.GetBool() == 0 )
- return;
-
- CBaseEntity *pPlayer = AI_GetSinglePlayer();
-
- if ( pPlayer )
- {
- UTIL_ShowMessage( GetDeathMessageText(), ToBasePlayer( pPlayer ) );
- ToBasePlayer(pPlayer)->NotifySinglePlayerGameEnding();
- }
-
- CBaseEntity *pReload = CreatePlayerLoadSave( GetAbsOrigin(), 1.5f, 8.0f, 4.5f );
-
- if ( pReload )
- {
- pReload->SetRenderColor( 0, 0, 0, 255 );
-
- g_EventQueue.AddEvent( pReload, "Reload", 1.5f, pReload, pReload );
- }
-
- // clear any pending autosavedangerous
- g_ServerGameDLL.m_fAutoSaveDangerousTime = 0.0f;
- g_ServerGameDLL.m_fAutoSaveDangerousMinHealthToCommit = 0.0f;
-}
-
-//-----------------------------------------------------------------------------
-// NPCs derived from CAI_PlayerAlly should call this in precache()
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::TalkInit( void )
-{
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::GatherConditions( void )
-{
- BaseClass::GatherConditions();
-
- if ( !HasCondition( COND_SEE_PLAYER ) )
- {
- SetCondition( COND_TALKER_CLIENTUNSEEN );
- }
-
- CBasePlayer *pLocalPlayer = AI_GetSinglePlayer();
-
- if ( !pLocalPlayer )
- {
- if ( AI_IsSinglePlayer() )
- SetCondition( COND_TALKER_PLAYER_DEAD );
- return;
- }
-
- if ( !pLocalPlayer->IsAlive() )
- {
- SetCondition( COND_TALKER_PLAYER_DEAD );
- }
-
- if ( HasCondition( COND_SEE_PLAYER ) )
- {
-
- bool bPlayerIsLooking = false;
- if ( ( pLocalPlayer->GetAbsOrigin() - GetAbsOrigin() ).Length2DSqr() < Square(TALKER_STARE_DIST) )
- {
- if ( pLocalPlayer->FInViewCone( EyePosition() ) )
- {
- if ( pLocalPlayer->GetSmoothedVelocity().LengthSqr() < Square( 100 ) )
- bPlayerIsLooking = true;
- }
- }
-
- if ( bPlayerIsLooking )
- {
- SetCondition( COND_TALKER_PLAYER_STARING );
- if ( m_flTimePlayerStartStare == 0 )
- m_flTimePlayerStartStare = gpGlobals->curtime;
- }
- else
- {
- m_flTimePlayerStartStare = 0;
- ClearCondition( COND_TALKER_PLAYER_STARING );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::GatherEnemyConditions( CBaseEntity *pEnemy )
-{
- BaseClass::GatherEnemyConditions( pEnemy );
- if ( GetLastEnemyTime() == 0 || gpGlobals->curtime - GetLastEnemyTime() > 30 )
- {
-#ifdef HL2_DLL
- if ( HasCondition( COND_SEE_ENEMY ) && ( pEnemy->Classify() != CLASS_BULLSEYE ) )
- {
- if( Classify() == CLASS_PLAYER_ALLY_VITAL && hl2_episodic.GetBool() )
- {
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
-
- if( pPlayer )
- {
-
- // If I can see the player, and the player would see this enemy if he turned around...
- if( !pPlayer->FInViewCone(pEnemy) && FVisible( pPlayer ) && pPlayer->FVisible(pEnemy) )
- {
- Vector2D vecPlayerView = pPlayer->EyeDirection2D().AsVector2D();
- Vector2D vecToEnemy = ( pEnemy->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).AsVector2D();
- Vector2DNormalize( vecToEnemy );
-
- if( DotProduct2D(vecPlayerView, vecToEnemy) <= -0.75 )
- {
- SpeakIfAllowed( TLK_WATCHOUT, "dangerloc:behind" );
- return;
- }
- }
- }
- }
- SpeakIfAllowed( TLK_STARTCOMBAT );
- }
-#else
- if ( HasCondition( COND_SEE_ENEMY ) )
- {
- SpeakIfAllowed( TLK_STARTCOMBAT );
- }
-#endif //HL2_DLL
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::OnStateChange( NPC_STATE OldState, NPC_STATE NewState )
-{
- BaseClass::OnStateChange( OldState, NewState );
-
- if ( OldState == NPC_STATE_COMBAT )
- {
- DeferAllIdleSpeech();
- }
-
- if ( GetState() == NPC_STATE_IDLE || GetState() == NPC_STATE_ALERT )
- {
- m_flNextIdleSpeechTime = gpGlobals->curtime + RandomFloat(5,10);
- }
- else
- {
- m_flNextIdleSpeechTime = 0;
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::PrescheduleThink( void )
-{
- BaseClass::PrescheduleThink();
-
-#ifdef HL2_DLL
- // Vital allies regenerate
- if( GetHealth() >= GetMaxHealth() )
- {
- // Avoid huge deltas on first regeneration of health after long period of time at full health.
- m_flTimeLastRegen = gpGlobals->curtime;
- }
- else if ( ShouldRegenerateHealth() )
- {
- float flDelta = gpGlobals->curtime - m_flTimeLastRegen;
- float flHealthPerSecond = 1.0f / sk_ally_regen_time.GetFloat();
-
- float flHealthRegen = flHealthPerSecond * flDelta;
-
- if ( g_pGameRules->IsSkillLevel(SKILL_HARD) )
- flHealthRegen *= 0.5f;
- else if ( g_pGameRules->IsSkillLevel(SKILL_EASY) )
- flHealthRegen *= 1.5f;
-
- m_flTimeLastRegen = gpGlobals->curtime;
-
- TakeHealth( flHealthRegen, DMG_GENERIC );
- }
-
-#ifdef HL2_EPISODIC
- if ( (GetState() == NPC_STATE_IDLE || GetState() == NPC_STATE_ALERT)
- && !HasCondition(COND_RECEIVED_ORDERS) && !IsInAScript() )
- {
- if ( m_flNextIdleSpeechTime && m_flNextIdleSpeechTime < gpGlobals->curtime )
- {
- AISpeechSelection_t selection;
- if ( SelectNonCombatSpeech( &selection ) )
- {
- SetSpeechTarget( selection.hSpeechTarget );
- SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
- m_flNextIdleSpeechTime = gpGlobals->curtime + RandomFloat( 20,30 );
- }
- else
- {
- m_flNextIdleSpeechTime = gpGlobals->curtime + RandomFloat( 10,20 );
- }
- }
- }
-#endif // HL2_EPISODIC
-
-#endif // HL2_DLL
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CAI_PlayerAlly::SelectSchedule( void )
-{
- if ( !HasCondition(COND_RECEIVED_ORDERS) )
- {
- // sustained light wounds?
- if ( m_iHealth <= m_iMaxHealth * 0.75 && IsAllowedToSpeak( TLK_WOUND ) && !GetExpresser()->SpokeConcept(TLK_WOUND) )
- {
- CTakeDamageInfo info;
- PainSound( info );
- }
- // sustained heavy wounds?
- else if ( m_iHealth <= m_iMaxHealth * 0.5 && IsAllowedToSpeak( TLK_MORTAL) )
- {
- Speak( TLK_MORTAL );
- }
- }
-
- return BaseClass::SelectSchedule();
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CAI_PlayerAlly::SelectSpeechResponse( AIConcept_t concept, const char *pszModifiers, CBaseEntity *pTarget, AISpeechSelection_t *pSelection )
-{
- if ( IsAllowedToSpeak( concept ) )
- {
- AI_Response *pResponse = SpeakFindResponse( concept, pszModifiers );
- if ( pResponse )
- {
- pSelection->Set( concept, pResponse, pTarget );
- return true;
- }
- }
- return false;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::SetPendingSpeech( AIConcept_t concept, AI_Response *pResponse )
-{
- m_PendingResponse = *pResponse;
- pResponse->Release();
- m_PendingConcept = concept;
- m_TimePendingSet = gpGlobals->curtime;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::ClearPendingSpeech()
-{
- m_PendingConcept.erase();
- m_TimePendingSet = 0;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-
-bool CAI_PlayerAlly::SelectIdleSpeech( AISpeechSelection_t *pSelection )
-{
- if ( !IsOkToSpeak( SPEECH_IDLE ) )
- return false;
-
- CBasePlayer *pTarget = assert_cast<CBasePlayer *>(FindSpeechTarget( AIST_PLAYERS | AIST_FACING_TARGET ));
- if ( pTarget )
- {
- if ( SelectSpeechResponse( TLK_HELLO, NULL, pTarget, pSelection ) )
- return true;
-
- if ( GetTimePlayerStaring() > 6 && !IsMoving() )
- {
- if ( SelectSpeechResponse( TLK_STARE, NULL, pTarget, pSelection ) )
- return true;
- }
-
- int chance = ( IsMoving() ) ? 20 : 2;
- if ( ShouldSpeakRandom( TLK_IDLE, chance ) && SelectSpeechResponse( TLK_IDLE, NULL, pTarget, pSelection ) )
- return true;
- }
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CAI_PlayerAlly::SelectAlertSpeech( AISpeechSelection_t *pSelection )
-{
-#ifdef HL2_EPISODIC
- CBasePlayer *pTarget = assert_cast<CBasePlayer *>(FindSpeechTarget( AIST_PLAYERS | AIST_FACING_TARGET ));
- if ( pTarget )
- {
- if ( pTarget->IsAlive() )
- {
- float flHealthPerc = ((float)pTarget->m_iHealth / (float)pTarget->m_iMaxHealth);
- if ( flHealthPerc < 1.0 )
- {
- if ( SelectSpeechResponse( TLK_PLHURT, NULL, pTarget, pSelection ) )
- return true;
- }
- }
- }
-#endif
-
- return SelectIdleSpeech( pSelection );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-
-bool CAI_PlayerAlly::SelectInterjection()
-{
- if ( HasPendingSpeech() )
- return false;
-
- if ( HasCondition(COND_RECEIVED_ORDERS) )
- return false;
-
- if ( GetState() == NPC_STATE_IDLE || GetState() == NPC_STATE_ALERT )
- {
- AISpeechSelection_t selection;
-
- if ( SelectIdleSpeech( &selection ) )
- {
- SetSpeechTarget( selection.hSpeechTarget );
- SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
- return true;
- }
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-
-bool CAI_PlayerAlly::SelectPlayerUseSpeech()
-{
- if( IsOkToSpeakInResponseToPlayer() )
- {
- if ( Speak( TLK_USE ) )
- DeferAllIdleSpeech();
- else
- return Speak( ( !GetExpresser()->SpokeConcept( TLK_HELLO ) ) ? TLK_HELLO : TLK_IDLE );
- }
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CAI_PlayerAlly::SelectQuestionAndAnswerSpeech( AISpeechSelection_t *pSelection )
-{
- if ( !IsOkToSpeak( SPEECH_IDLE ) )
- return false;
-
- if ( IsMoving() )
- return false;
-
- // if there is a friend nearby to speak to, play sentence, set friend's response time, return
- CAI_PlayerAlly *pFriend = dynamic_cast<CAI_PlayerAlly *>(FindSpeechTarget( AIST_NPCS ));
- if ( pFriend && !pFriend->IsMoving() && !pFriend->HasSpawnFlags(SF_NPC_GAG) )
- return SelectQuestionFriend( pFriend, pSelection );
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::PostSpeakDispatchResponse( AIConcept_t concept, AI_Response *response )
-{
-#ifdef HL2_EPISODIC
- CAI_AllySpeechManager *pSpeechManager = GetAllySpeechManager();
- ConceptInfo_t *pConceptInfo = pSpeechManager->GetConceptInfo( concept );
- if ( pConceptInfo && (pConceptInfo->flags & AICF_QUESTION) && GetSpeechTarget() )
- {
- bool bSaidHelloToNPC = !Q_strcmp(concept, "TLK_HELLO_NPC");
-
- float duration = GetExpresser()->GetSemaphoreAvailableTime(this) - gpGlobals->curtime;
-
- if ( rr_debug_qa.GetBool() )
- {
- if ( bSaidHelloToNPC )
- {
- Warning("Q&A: '%s' said Hello to '%s' (concept %s)\n", GetDebugName(), GetSpeechTarget()->GetDebugName(), concept );
- }
- else
- {
- Warning("Q&A: '%s' questioned '%s' (concept %s)\n", GetDebugName(), GetSpeechTarget()->GetDebugName(), concept );
- }
- NDebugOverlay::HorzArrow( GetAbsOrigin(), GetSpeechTarget()->GetAbsOrigin(), 8, 0, 255, 0, 64, true, duration );
- }
-
- // If we spoke a Question, tell our friend to answer
- const char *pszInput;
- if ( bSaidHelloToNPC )
- {
- pszInput = "AnswerQuestionHello";
- }
- else
- {
- pszInput = "AnswerQuestion";
- }
-
- // Set the input parameter to the random number we used to find the Question
- variant_t value;
- value.SetInt( m_iQARandomNumber );
- g_EventQueue.AddEvent( GetSpeechTarget(), pszInput, value, duration + .2, this, this );
-
- if ( GetSpeechTarget()->MyNPCPointer() )
- {
- AddLookTarget( GetSpeechTarget()->MyNPCPointer(), 1.0, duration + random->RandomFloat( 0.4, 1.2 ), 0.5 );
- GetSpeechTarget()->MyNPCPointer()->AddLookTarget( this, 1.0, duration + random->RandomFloat( 0.4, 1 ), 0.7 );
- }
-
- // Don't let anyone else butt in.
- DeferAllIdleSpeech( random->RandomFloat( TALKER_DEFER_IDLE_SPEAK_MIN, TALKER_DEFER_IDLE_SPEAK_MAX ), GetSpeechTarget()->MyNPCPointer() );
- }
- else if ( pConceptInfo && (pConceptInfo->flags & AICF_ANSWER) && GetSpeechTarget() )
- {
- float duration = GetExpresser()->GetSemaphoreAvailableTime(this) - gpGlobals->curtime;
- if ( rr_debug_qa.GetBool() )
- {
- NDebugOverlay::HorzArrow( GetAbsOrigin(), GetSpeechTarget()->GetAbsOrigin(), 8, 0, 255, 0, 64, true, duration );
- }
- if ( GetSpeechTarget()->MyNPCPointer() )
- {
- AddLookTarget( GetSpeechTarget()->MyNPCPointer(), 1.0, duration + random->RandomFloat( 0, 0.3 ), 0.5 );
- GetSpeechTarget()->MyNPCPointer()->AddLookTarget( this, 1.0, duration + random->RandomFloat( 0.2, 0.5 ), 0.7 );
- }
- }
-
- m_hPotentialSpeechTarget = NULL;
-#endif // HL2_EPISODIC
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Find a concept to question a nearby friend
-//-----------------------------------------------------------------------------
-bool CAI_PlayerAlly::SelectQuestionFriend( CBaseEntity *pFriend, AISpeechSelection_t *pSelection )
-{
- if ( !ShouldSpeakRandom( TLK_QUESTION, 3 ) )
- return false;
-
- // Tell the response rules who we're trying to question
- m_hPotentialSpeechTarget = pFriend;
- m_iQARandomNumber = RandomInt(0,100);
-
- // If we haven't said hello, say hello first.
- // Only ever say hello to NPCs other than my type.
- if ( !GetExpresser()->SpokeConcept( TLK_HELLO_NPC ) && !FClassnameIs( this, pFriend->GetClassname()) )
- {
- if ( SelectSpeechResponse( TLK_HELLO_NPC, NULL, pFriend, pSelection ) )
- return true;
-
- GetExpresser()->SetSpokeConcept( TLK_HELLO_NPC, NULL );
- }
-
- return SelectSpeechResponse( TLK_QUESTION, NULL, pFriend, pSelection );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Find a concept to answer our friend's question
-//-----------------------------------------------------------------------------
-bool CAI_PlayerAlly::SelectAnswerFriend( CBaseEntity *pFriend, AISpeechSelection_t *pSelection, bool bRespondingToHello )
-{
- // Tell the response rules who we're trying to answer
- m_hPotentialSpeechTarget = pFriend;
-
- if ( bRespondingToHello )
- {
- if ( SelectSpeechResponse( TLK_ANSWER_HELLO, NULL, pFriend, pSelection ) )
- return true;
- }
-
- return SelectSpeechResponse( TLK_ANSWER, NULL, pFriend, pSelection );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::InputAnswerQuestion( inputdata_t &inputdata )
-{
- AnswerQuestion( dynamic_cast<CAI_PlayerAlly *>(inputdata.pActivator), inputdata.value.Int(), false );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::InputAnswerQuestionHello( inputdata_t &inputdata )
-{
- AnswerQuestion( dynamic_cast<CAI_PlayerAlly *>(inputdata.pActivator), inputdata.value.Int(), true );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::AnswerQuestion( CAI_PlayerAlly *pQuestioner, int iQARandomNum, bool bAnsweringHello )
-{
- // Original questioner may have died
- if ( !pQuestioner )
- return;
-
- AISpeechSelection_t selection;
-
- // Use the random number that the questioner used to determine his Question (so we can match answers via response rules)
- m_iQARandomNumber = iQARandomNum;
-
- // The activator is the person we're responding to
- if ( SelectAnswerFriend( pQuestioner, &selection, bAnsweringHello ) )
- {
- if ( rr_debug_qa.GetBool() )
- {
- if ( bAnsweringHello )
- {
- Warning("Q&A: '%s' answered the Hello from '%s'\n", GetDebugName(), pQuestioner->GetDebugName() );
- }
- else
- {
- Warning("Q&A: '%s' answered the Question from '%s'\n", GetDebugName(), pQuestioner->GetDebugName() );
- }
- }
-
- Assert( selection.pResponse );
- SetSpeechTarget( selection.hSpeechTarget );
- SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
-
- // Prevent idle speech for a while
- DeferAllIdleSpeech( random->RandomFloat( TALKER_DEFER_IDLE_SPEAK_MIN, TALKER_DEFER_IDLE_SPEAK_MAX ), GetSpeechTarget()->MyNPCPointer() );
- }
- else if ( rr_debug_qa.GetBool() )
- {
- Warning("Q&A: '%s' couldn't answer '%s'\n", GetDebugName(), pQuestioner->GetDebugName() );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Output : int
-//-----------------------------------------------------------------------------
-int CAI_PlayerAlly::SelectNonCombatSpeech( AISpeechSelection_t *pSelection )
-{
- bool bResult = false;
-
- #ifdef HL2_EPISODIC
- // See if we can Q&A first
- if ( GetState() == NPC_STATE_IDLE || GetState() == NPC_STATE_ALERT )
- {
- bResult = SelectQuestionAndAnswerSpeech( pSelection );
- }
- #endif // HL2_EPISODIC
-
- if ( !bResult )
- {
- if ( GetState() == NPC_STATE_ALERT )
- {
- bResult = SelectAlertSpeech( pSelection );
- }
- else
- {
- bResult = SelectIdleSpeech( pSelection );
- }
- }
-
- return bResult;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CAI_PlayerAlly::SelectNonCombatSpeechSchedule()
-{
- if ( !HasPendingSpeech() )
- {
- AISpeechSelection_t selection;
- if ( SelectNonCombatSpeech( &selection ) )
- {
- Assert( selection.pResponse );
- SetSpeechTarget( selection.hSpeechTarget );
- SetPendingSpeech( selection.concept.c_str(), selection.pResponse );
- }
- }
-
- if ( HasPendingSpeech() )
- {
- if ( m_TimePendingSet == gpGlobals->curtime || IsAllowedToSpeak( m_PendingConcept.c_str() ) )
- return SCHED_TALKER_SPEAK_PENDING_IDLE;
- }
-
- return SCHED_NONE;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CAI_PlayerAlly::TranslateSchedule( int schedule )
-{
- if ( ( GetState() == NPC_STATE_IDLE || GetState() == NPC_STATE_ALERT ) &&
- ConditionInterruptsSchedule( schedule, COND_IDLE_INTERRUPT ) &&
- !HasCondition(COND_RECEIVED_ORDERS) )
- {
- int speechSchedule = SelectNonCombatSpeechSchedule();
- if ( speechSchedule != SCHED_NONE )
- return speechSchedule;
- }
-
- switch( schedule )
- {
- case SCHED_CHASE_ENEMY_FAILED:
- {
- int baseType = BaseClass::TranslateSchedule(schedule);
- if ( baseType != SCHED_CHASE_ENEMY_FAILED )
- return baseType;
-
- return SCHED_TAKE_COVER_FROM_ENEMY;
- }
- break;
- }
- return BaseClass::TranslateSchedule( schedule );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::OnStartSchedule( int schedule )
-{
- if ( schedule == SCHED_HIDE_AND_RELOAD )
- SpeakIfAllowed( TLK_HIDEANDRELOAD );
- BaseClass::OnStartSchedule( schedule );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::StartTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_MOVE_AWAY_PATH:
- {
- if ( HasCondition( COND_PLAYER_PUSHING ) && AI_IsSinglePlayer() )
- {
- // @TODO (toml 10-22-04): cope with multiplayer push
- GetMotor()->SetIdealYawToTarget( UTIL_GetLocalPlayer()->WorldSpaceCenter() );
- }
- BaseClass::StartTask( pTask );
- break;
- }
-
- case TASK_PLAY_SCRIPT:
- SetSpeechTarget( NULL );
- BaseClass::StartTask( pTask );
- break;
-
- case TASK_TALKER_SPEAK_PENDING:
- if ( !m_PendingConcept.empty() )
- {
- AI_Response *pResponse = new AI_Response;
- *pResponse = m_PendingResponse;
- SpeakDispatchResponse( m_PendingConcept.c_str(), pResponse );
- m_PendingConcept.erase();
- TaskComplete();
- }
- else
- TaskFail( FAIL_NO_SOUND );
- break;
-
- default:
- BaseClass::StartTask( pTask );
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::RunTask( const Task_t *pTask )
-{
- BaseClass::RunTask( pTask );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::TaskFail( AI_TaskFailureCode_t code )
-{
- if ( IsCurSchedule( SCHED_TALKER_SPEAK_PENDING_IDLE, false ) )
- ClearPendingSpeech();
- BaseClass::TaskFail( code );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::ClearTransientConditions()
-{
- CAI_BaseNPC::ClearTransientConditions();
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::Touch( CBaseEntity *pOther )
-{
- BaseClass::Touch( pOther );
-
- // Did the player touch me?
- if ( pOther->IsPlayer() )
- {
- // Ignore if pissed at player
- if ( m_afMemory & bits_MEMORY_PROVOKED )
- return;
-
- // Stay put during speech
- if ( GetExpresser()->IsSpeaking() )
- return;
-
- TestPlayerPushing( pOther );
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::OnKilledNPC( CBaseCombatCharacter *pKilled )
-{
- if ( pKilled )
- {
- if ( !pKilled->IsNPC() ||
- ( pKilled->MyNPCPointer()->GetLastPlayerDamageTime() == 0 ||
- gpGlobals->curtime - pKilled->MyNPCPointer()->GetLastPlayerDamageTime() > 5 ) )
- {
- SpeakIfAllowed( TLK_ENEMY_DEAD );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
-{
- const char *pszHitLocCriterion = NULL;
-
- if ( ptr->hitgroup == HITGROUP_LEFTLEG || ptr->hitgroup == HITGROUP_RIGHTLEG )
- {
- pszHitLocCriterion = "shotloc:leg";
- }
- else if ( ptr->hitgroup == HITGROUP_LEFTARM || ptr->hitgroup == HITGROUP_RIGHTARM )
- {
- pszHitLocCriterion = "shotloc:arm";
- }
- else if ( ptr->hitgroup == HITGROUP_STOMACH )
- {
- pszHitLocCriterion = "shotloc:gut";
- }
-
- // set up the speech modifiers
- CFmtStrN<128> modifiers( "%s,damageammo:%s", pszHitLocCriterion, info.GetAmmoName() );
-
- SpeakIfAllowed( TLK_SHOT, modifiers );
-
- BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CAI_PlayerAlly::OnTakeDamage_Alive( const CTakeDamageInfo &info )
-{
- CTakeDamageInfo subInfo = info;
- // Vital allies never take more than 25% of their health in a single hit (except for physics damage)
-#ifdef HL2_DLL
- // Don't do damage reduction for DMG_GENERIC. This allows SetHealth inputs to still do full damage.
- if ( subInfo.GetDamageType() != DMG_GENERIC )
- {
- if ( Classify() == CLASS_PLAYER_ALLY_VITAL && !(subInfo.GetDamageType() & DMG_CRUSH) )
- {
- float flDamage = subInfo.GetDamage();
- if ( flDamage > ( GetMaxHealth() * 0.25 ) )
- {
- flDamage = ( GetMaxHealth() * 0.25 );
- subInfo.SetDamage( flDamage );
- }
- }
- }
-#endif
-
- return BaseClass::OnTakeDamage_Alive( subInfo );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CAI_PlayerAlly::TakeHealth( float flHealth, int bitsDamageType )
-{
- int intPortion;
- float floatPortion;
-
- intPortion = ((int)flHealth);
- floatPortion = flHealth - intPortion;
-
- m_flHealthAccumulator += floatPortion;
-
- while ( m_flHealthAccumulator > 1.0f )
- {
- m_flHealthAccumulator -= 1.0f;
- intPortion += 1;
- }
-
- return BaseClass::TakeHealth( ((float)intPortion), bitsDamageType );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::Event_Killed( const CTakeDamageInfo &info )
-{
- // notify the player
- if ( IsInPlayerSquad() )
- {
- CBasePlayer *player = AI_GetSinglePlayer();
- if ( player )
- {
- variant_t emptyVariant;
- player->AcceptInput( "OnSquadMemberKilled", this, this, emptyVariant, 0 );
- }
- }
-
- if ( GetSpeechSemaphore( this )->GetOwner() == this )
- GetSpeechSemaphore( this )->Release();
-
- CAI_PlayerAlly *pMourner = dynamic_cast<CAI_PlayerAlly *>(FindSpeechTarget( AIST_NPCS ));
- if ( pMourner )
- {
- pMourner->SpeakIfAllowed( TLK_ALLY_KILLED );
- }
-
- SetTarget( NULL );
- // Don't finish that sentence
- SentenceStop();
- SetUse( NULL );
- BaseClass::Event_Killed( info );
-
- DisplayDeathMessage();
-}
-
-// Player allies should use simple shadows to save CPU. This means they can't
-// be killed by crush damage.
-bool CAI_PlayerAlly::CreateVPhysics()
-{
- bool bRet = BaseClass::CreateVPhysics();
- return bRet;
-}
-
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::PainSound( const CTakeDamageInfo &info )
-{
- SpeakIfAllowed( TLK_WOUND );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Implemented to look at talk target
-//-----------------------------------------------------------------------------
-CBaseEntity *CAI_PlayerAlly::EyeLookTarget( void )
-{
- // FIXME: this should be in the VCD
- // FIXME: this is dead code
- if (GetExpresser()->IsSpeaking() && GetSpeechTarget() != NULL)
- {
- return GetSpeechTarget();
- }
- return NULL;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: returns who we're talking to for vcd's
-//-----------------------------------------------------------------------------
-CBaseEntity *CAI_PlayerAlly::FindNamedEntity( const char *pszName, IEntityFindFilter *pFilter )
-{
- if ( !stricmp( pszName, "!speechtarget" ))
- {
- return GetSpeechTarget();
- }
-
- if ( !stricmp( pszName, "!friend" ))
- {
- return FindSpeechTarget( AIST_NPCS );
- }
-
-
- return BaseClass::FindNamedEntity( pszName, pFilter );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CAI_PlayerAlly::IsValidSpeechTarget( int flags, CBaseEntity *pEntity )
-{
- if ( pEntity == this )
- return false;
-
- if ( !(flags & AIST_IGNORE_RELATIONSHIP) )
- {
- if ( pEntity->IsPlayer() )
- {
- if ( !IsPlayerAlly( (CBasePlayer *)pEntity ) )
- return false;
- }
- else
- {
- if ( IRelationType( pEntity ) != D_LI )
- return false;
- }
- }
-
- if ( !pEntity->IsAlive() )
- // don't dead people
- return false;
-
- // Ignore no-target entities
- if ( pEntity->GetFlags() & FL_NOTARGET )
- return false;
-
- CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
- if ( pNPC )
- {
- // If not a NPC for some reason, or in a script.
- if ( (pNPC->m_NPCState == NPC_STATE_SCRIPT || pNPC->m_NPCState == NPC_STATE_PRONE))
- return false;
-
- if ( pNPC->IsInAScript() )
- return false;
-
- // Don't bother people who don't want to be bothered
- if ( !pNPC->CanBeUsedAsAFriend() )
- return false;
- }
-
- if ( flags & AIST_FACING_TARGET )
- {
- if ( pEntity->IsPlayer() )
- return HasCondition( COND_SEE_PLAYER );
- else if ( !FInViewCone( pEntity ) )
- return false;
- }
-
- return FVisible( pEntity );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-CBaseEntity *CAI_PlayerAlly::FindSpeechTarget( int flags )
-{
- const Vector & vAbsOrigin = GetAbsOrigin();
- float closestDistSq = FLT_MAX;
- CBaseEntity * pNearest = NULL;
- float distSq;
- int i;
-
- if ( flags & AIST_PLAYERS )
- {
- for ( i = 1; i <= gpGlobals->maxClients; i++ )
- {
- CBaseEntity *pPlayer = UTIL_PlayerByIndex( i );
- if ( pPlayer )
- {
- distSq = ( vAbsOrigin - pPlayer->GetAbsOrigin() ).LengthSqr();
-
- if ( distSq > Square(TALKRANGE_MIN) )
- continue;
-
- if ( !(flags & AIST_ANY_QUALIFIED) && distSq > closestDistSq )
- continue;
-
- if ( IsValidSpeechTarget( flags, pPlayer ) )
- {
- if ( flags & AIST_ANY_QUALIFIED )
- return pPlayer;
-
- closestDistSq = distSq;
- pNearest = pPlayer;
- }
- }
- }
- }
-
- if ( flags & AIST_NPCS )
- {
- for ( i = 0; i < g_AI_Manager.NumAIs(); i++ )
- {
- CAI_BaseNPC *pNPC = (g_AI_Manager.AccessAIs())[i];
-
- distSq = ( vAbsOrigin - pNPC->GetAbsOrigin() ).LengthSqr();
-
- if ( distSq > Square(TALKRANGE_MIN) )
- continue;
-
- if ( !(flags & AIST_ANY_QUALIFIED) && distSq > closestDistSq )
- continue;
-
- if ( IsValidSpeechTarget( flags, pNPC ) )
- {
- if ( flags & AIST_ANY_QUALIFIED )
- return pNPC;
-
- closestDistSq = distSq;
- pNearest = pNPC;
- }
- }
- }
-
- return pNearest;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CAI_PlayerAlly::CanPlaySentence( bool fDisregardState )
-{
- if ( fDisregardState )
- return BaseClass::CanPlaySentence( fDisregardState );
- return IsOkToSpeak();
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CAI_PlayerAlly::PlayScriptedSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener )
-{
- ClearCondition( COND_PLAYER_PUSHING ); // Forget about moving! I've got something to say!
- int sentenceIndex = BaseClass::PlayScriptedSentence( pszSentence, delay, volume, soundlevel, bConcurrent, pListener );
- SetSpeechTarget( pListener );
-
- return sentenceIndex;
-}
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-void CAI_PlayerAlly::DeferAllIdleSpeech( float flDelay, CAI_BaseNPC *pIgnore )
-{
- CAI_AllySpeechManager *pSpeechManager = GetAllySpeechManager();
- if ( flDelay == -1 )
- {
- ConceptCategoryInfo_t *pCategoryInfo = pSpeechManager->GetConceptCategoryInfo( SPEECH_IDLE );
- pSpeechManager->SetCategoryDelay( SPEECH_IDLE, pCategoryInfo->minGlobalDelay, pCategoryInfo->maxGlobalDelay );
- }
- else
- pSpeechManager->SetCategoryDelay( SPEECH_IDLE, flDelay );
-}
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-bool CAI_PlayerAlly::IsOkToSpeak( ConceptCategory_t category, bool fRespondingToPlayer )
-{
- CAI_AllySpeechManager *pSpeechManager = GetAllySpeechManager();
-
- // if not alive, certainly don't speak
- if ( !IsAlive() )
- return false;
-
- if ( m_spawnflags & SF_NPC_GAG )
- return false;
-
- // Don't speak if playing a script.
- if ( ( m_NPCState == NPC_STATE_SCRIPT ) && !m_bCanSpeakWhileScripting )
- return false;
-
- // Don't speak if being eaten by a barnacle
- if ( IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
- return false;
-
- if ( IsInAScript() && !m_bCanSpeakWhileScripting )
- return false;
-
- if ( !fRespondingToPlayer )
- {
- if ( !pSpeechManager->CategoryDelayExpired( category ) || !CategoryDelayExpired( category ) )
- return false;
- }
-
- if ( category == SPEECH_IDLE )
- {
- if ( GetState() != NPC_STATE_IDLE && GetState() != NPC_STATE_ALERT )
- return false;
- if ( GetSpeechFilter() && GetSpeechFilter()->GetIdleModifier() < 0.001 )
- return false;
- }
-
- // if player is not in pvs, don't speak
- if ( !UTIL_FindClientInPVS(edict()) )
- return false;
-
- if ( category != SPEECH_PRIORITY )
- {
- // if someone else is talking, don't speak
- if ( !GetExpresser()->SemaphoreIsAvailable( this ) )
- return false;
-
- if ( fRespondingToPlayer )
- {
- if ( !GetExpresser()->CanSpeakAfterMyself() )
- return false;
- }
- else
- {
- if ( !GetExpresser()->CanSpeak() )
- return false;
- }
-
- // Don't talk if we're too far from the player
- CBaseEntity *pPlayer = AI_GetSinglePlayer();
- if ( pPlayer )
- {
- float flDist = sv_npc_talker_maxdist.GetFloat();
- flDist *= flDist;
- if ( (pPlayer->WorldSpaceCenter() - WorldSpaceCenter()).LengthSqr() > flDist )
- return false;
- }
- }
-
- if ( fRespondingToPlayer )
- {
- // If we're responding to the player, don't respond if the scene has speech in it
- if ( IsRunningScriptedSceneWithSpeechAndNotPaused( this ) )
- {
- if( rr_debugresponses.GetInt() > 0 )
- {
- DevMsg("%s not allowed to speak because they are in a scripted scene\n", GetDebugName() );
- }
-
- return false;
- }
- }
- else
- {
- // If we're not responding to the player, don't talk if running a logic_choreo
- if ( IsRunningScriptedSceneAndNotPaused( this ) )
- {
- if( rr_debugresponses.GetInt() > 0 )
- {
- DevMsg("%s not allowed to speak because they are in a scripted scene\n", GetDebugName() );
- }
-
- return false;
- }
- }
-
- return true;
-}
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-bool CAI_PlayerAlly::IsOkToSpeak( void )
-{
- return IsOkToSpeak( SPEECH_IDLE );
-}
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-bool CAI_PlayerAlly::IsOkToCombatSpeak( void )
-{
- return IsOkToSpeak( SPEECH_IMPORTANT );
-}
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-bool CAI_PlayerAlly::IsOkToSpeakInResponseToPlayer( void )
-{
- return IsOkToSpeak( SPEECH_IMPORTANT, true );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Return true if I should speak based on the chance & the speech filter's modifier
-//-----------------------------------------------------------------------------
-bool CAI_PlayerAlly::ShouldSpeakRandom( AIConcept_t concept, int iChance )
-{
- CAI_AllySpeechManager * pSpeechManager = GetAllySpeechManager();
- ConceptInfo_t * pInfo = pSpeechManager->GetConceptInfo( concept );
- ConceptCategory_t category = ( pInfo ) ? pInfo->category : SPEECH_IDLE;
-
- if ( GetSpeechFilter() )
- {
- if ( category == SPEECH_IDLE )
- {
- float flModifier = GetSpeechFilter()->GetIdleModifier();
- if ( flModifier < 0.001 )
- return false;
-
- iChance = floor( (float)iChance / flModifier );
- }
- }
-
- if ( iChance < 1 )
- return false;
-
- if ( iChance == 1 )
- return true;
-
- return (random->RandomInt(1,iChance) == 1);
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CAI_PlayerAlly::IsAllowedToSpeak( AIConcept_t concept, bool bRespondingToPlayer )
-{
- CAI_AllySpeechManager * pSpeechManager = GetAllySpeechManager();
- ConceptInfo_t * pInfo = pSpeechManager->GetConceptInfo( concept );
- ConceptCategory_t category = ( pInfo ) ? pInfo->category : SPEECH_IDLE;
-
- if ( !IsOkToSpeak( category, bRespondingToPlayer ) )
- return false;
-
- if ( GetSpeechFilter() && GetSpeechFilter()->NeverSayHello() )
- {
- if ( CompareConcepts( concept, TLK_HELLO ) )
- return false;
- if ( CompareConcepts( concept, TLK_HELLO_NPC ) )
- return false;
- }
-
- if ( !pSpeechManager->ConceptDelayExpired( concept ) )
- return false;
-
- if ( ( pInfo && pInfo->flags & AICF_SPEAK_ONCE ) && GetExpresser()->SpokeConcept( concept ) )
- return false;
-
- if ( !GetExpresser()->CanSpeakConcept( concept ) )
- return false;
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CAI_PlayerAlly::SpeakIfAllowed( AIConcept_t concept, const char *modifiers, bool bRespondingToPlayer, char *pszOutResponseChosen, size_t bufsize )
-{
- if ( IsAllowedToSpeak( concept, bRespondingToPlayer ) )
- {
- return Speak( concept, modifiers, pszOutResponseChosen, bufsize );
- }
- return false;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::ModifyOrAppendCriteria( AI_CriteriaSet& set )
-{
- BaseClass::ModifyOrAppendCriteria( set );
-
- if ( m_hPotentialSpeechTarget )
- {
- set.AppendCriteria( "speechtarget", m_hPotentialSpeechTarget->GetClassname() );
- set.AppendCriteria( "speechtargetname", STRING(m_hPotentialSpeechTarget->GetEntityName()) );
- set.AppendCriteria( "randomnum", UTIL_VarArgs("%d", m_iQARandomNumber) );
- }
-
- // Do we have a speech filter? If so, append it's criteria too
- if ( GetSpeechFilter() )
- {
- GetSpeechFilter()->AppendContextToCriteria( set );
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::OnSpokeConcept( AIConcept_t concept, AI_Response *response )
-{
- CAI_AllySpeechManager *pSpeechManager = GetAllySpeechManager();
- pSpeechManager->OnSpokeConcept( this, concept, response );
-
- if( response != NULL && (response->GetParams()->flags & AI_ResponseParams::RG_WEAPONDELAY) )
- {
- // Stop shooting, as instructed, so that my speech can be heard.
- GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + response->GetWeaponDelay() );
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::OnStartSpeaking()
-{
- // If you say anything, don't greet the player - you may have already spoken to them
- if ( !GetExpresser()->SpokeConcept( TLK_HELLO ) )
- {
- GetExpresser()->SetSpokeConcept( TLK_HELLO, NULL );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Mapmaker input to force this NPC to speak a response rules concept
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::InputSpeakResponseConcept( inputdata_t &inputdata )
-{
- SpeakMapmakerInterruptConcept( inputdata.value.StringID() );
-}
-
-
-//-----------------------------------------------------------------------------
-// Allows mapmakers to override NPC_STATE_SCRIPT or IsScripting() for responses.
-//-----------------------------------------------------------------------------
-void CAI_PlayerAlly::InputEnableSpeakWhileScripting( inputdata_t &inputdata )
-{
- m_bCanSpeakWhileScripting = true;
-}
-
-void CAI_PlayerAlly::InputDisableSpeakWhileScripting( inputdata_t &inputdata )
-{
- m_bCanSpeakWhileScripting = false;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CAI_PlayerAlly::SpeakMapmakerInterruptConcept( string_t iszConcept )
-{
- // Let behaviors override
- if ( BaseClass::SpeakMapmakerInterruptConcept(iszConcept) )
- return false;
-
- if (!IsOkToSpeakInResponseToPlayer())
- return false;
-
- Speak( STRING(iszConcept) );
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CAI_PlayerAlly::CanRespondToEvent( const char *ResponseConcept )
-{
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CAI_PlayerAlly::RespondedTo( const char *ResponseConcept, bool bForce, bool bCancelScene )
-{
- if ( bForce )
- {
- // We're being forced to respond to the event, probably because it's the
- // player dying or something equally important.
- AI_Response *result = SpeakFindResponse( ResponseConcept, NULL );
- if ( result )
- {
- // We've got something to say. Stop any scenes we're in, and speak the response.
- if ( bCancelScene )
- RemoveActorFromScriptedScenes( this, false );
-
- bool spoke = SpeakDispatchResponse( ResponseConcept, result );
- return spoke;
- }
-
- return false;
- }
-
- if ( SpeakIfAllowed( ResponseConcept, NULL, true ) )
- return true;
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-//
-// Schedules
-//
-//-----------------------------------------------------------------------------
-
-AI_BEGIN_CUSTOM_NPC(talk_monster_base,CAI_PlayerAlly)
-
- DECLARE_TASK(TASK_TALKER_SPEAK_PENDING)
-
- DECLARE_CONDITION(COND_TALKER_CLIENTUNSEEN)
- DECLARE_CONDITION(COND_TALKER_PLAYER_DEAD)
- DECLARE_CONDITION(COND_TALKER_PLAYER_STARING)
-
- //=========================================================
- // > SCHED_TALKER_SPEAK_PENDING_IDLE
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_TALKER_SPEAK_PENDING_IDLE,
-
- " Tasks"
- " TASK_TALKER_SPEAK_PENDING 0"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT_FOR_SPEAK_FINISH 0"
- " TASK_WAIT_RANDOM 0.5"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_HEAR_DANGER"
- " COND_HEAR_COMBAT"
- " COND_PLAYER_PUSHING"
- " COND_GIVE_WAY"
- )
-
- //=========================================================
- // > SCHED_TALKER_SPEAK_PENDING_ALERT
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_TALKER_SPEAK_PENDING_ALERT,
-
- " Tasks"
- " TASK_TALKER_SPEAK_PENDING 0"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT_FOR_SPEAK_FINISH 0"
- " TASK_WAIT_RANDOM 0.5"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_HEAR_DANGER"
- " COND_PLAYER_PUSHING"
- " COND_GIVE_WAY"
- )
-
- //=========================================================
- // > SCHED_TALKER_SPEAK_PENDING_COMBAT
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_TALKER_SPEAK_PENDING_COMBAT,
-
- " Tasks"
- " TASK_TALKER_SPEAK_PENDING 0"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT_FOR_SPEAK_FINISH 0"
- ""
- " Interrupts"
- " COND_HEAVY_DAMAGE"
- " COND_HEAR_DANGER"
- )
-
-AI_END_CUSTOM_NPC()
-
-//-----------------------------------------------------------------------------
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "sceneentity.h"
+#include "ai_playerally.h"
+#include "saverestore_utlmap.h"
+#include "eventqueue.h"
+#include "ai_behavior_lead.h"
+#include "gameinterface.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+extern CServerGameDLL g_ServerGameDLL;
+
+extern ConVar rr_debugresponses;
+
+//-----------------------------------------------------------------------------
+
+ConVar sk_ally_regen_time( "sk_ally_regen_time", "0.3003", FCVAR_NONE, "Time taken for an ally to regenerate a point of health." );
+ConVar sv_npc_talker_maxdist( "sv_npc_talker_maxdist", "1024", 0, "NPCs over this distance from the player won't attempt to speak." );
+ConVar ai_no_talk_delay( "ai_no_talk_delay", "0" );
+
+ConVar rr_debug_qa( "rr_debug_qa", "0", FCVAR_NONE, "Set to 1 to see debug related to the Question & Answer system used to create conversations between allied NPCs.");
+
+//-----------------------------------------------------------------------------
+
+ConceptCategoryInfo_t g_ConceptCategoryInfos[] =
+{
+ { 10, 20, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+};
+
+ConceptInfo_t g_ConceptInfos[] =
+{
+ { TLK_ANSWER, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_ANSWER, },
+ { TLK_ANSWER_HELLO, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_ANSWER, },
+ { TLK_QUESTION, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_QUESTION, },
+ { TLK_IDLE, SPEECH_IDLE, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_TARGET_PLAYER, },
+ { TLK_STARE, SPEECH_IDLE, -1, -1, -1, -1, 180, 0, AICF_DEFAULT | AICF_TARGET_PLAYER, },
+ { TLK_LOOK, SPEECH_PRIORITY, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_TARGET_PLAYER, },
+ { TLK_HELLO, SPEECH_IDLE, 5, 10, -1, -1, -1, -1, AICF_DEFAULT | AICF_SPEAK_ONCE | AICF_PROPAGATE_SPOKEN | AICF_TARGET_PLAYER, },
+ { TLK_PHELLO, SPEECH_IDLE, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_SPEAK_ONCE | AICF_PROPAGATE_SPOKEN | AICF_TARGET_PLAYER, },
+ { TLK_HELLO_NPC, SPEECH_IDLE, 5, 10, -1, -1, -1, -1, AICF_DEFAULT | AICF_QUESTION | AICF_SPEAK_ONCE, },
+ { TLK_PIDLE, SPEECH_IDLE, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_PQUESTION, SPEECH_IDLE, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_SMELL, SPEECH_IDLE, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_USE, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_STARTFOLLOW, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_STOPFOLLOW, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_JOINPLAYER, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_STOP, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_NOSHOOT, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_PLHURT1, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_PLHURT2, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_PLHURT3, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_PLHURT, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_PLPUSH, SPEECH_IMPORTANT, -1, -1, -1, -1, 15, 30, AICF_DEFAULT, },
+ { TLK_PLRELOAD, SPEECH_IMPORTANT, -1, -1, -1, -1, 60, 0, AICF_DEFAULT, },
+ { TLK_SHOT, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_WOUND, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_MORTAL, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT | AICF_SPEAK_ONCE, },
+ { TLK_SEE_COMBINE, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_ENEMY_DEAD, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_ALYX_ENEMY_DEAD,SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_SELECTED, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_COMMANDED, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_COMMAND_FAILED, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_DENY_COMMAND, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_BETRAYED, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_ALLY_KILLED, SPEECH_IMPORTANT, -1, -1, -1, -1, 15, 30, AICF_DEFAULT, },
+ { TLK_ATTACKING, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_HEAL, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_GIVEAMMO, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_HELP_ME, SPEECH_IMPORTANT, -1, -1, -1, -1, 7, 10, AICF_DEFAULT, },
+ { TLK_PLYR_PHYSATK, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_NEWWEAPON, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_STARTCOMBAT, SPEECH_IMPORTANT, -1, -1, -1, -1, 30, 0, AICF_DEFAULT, },
+ { TLK_WATCHOUT, SPEECH_IMPORTANT, -1, -1, -1, -1, 30, 45, AICF_DEFAULT, },
+ { TLK_MOBBED, SPEECH_IMPORTANT, -1, -1, -1, -1, 10, 12, AICF_DEFAULT, },
+ { TLK_MANY_ENEMIES, SPEECH_IMPORTANT, -1, -1, -1, -1, 45, 60, AICF_DEFAULT, },
+ { TLK_DANGER, SPEECH_PRIORITY, -1, -1, -1, -1, 5, 7, AICF_DEFAULT, },
+ { TLK_PLDEAD, SPEECH_PRIORITY, -1, -1, -1, -1, 100, 0, AICF_DEFAULT, },
+ { TLK_HIDEANDRELOAD, SPEECH_PRIORITY, -1, -1, -1, -1, 45, 60, AICF_DEFAULT, },
+ { TLK_FLASHLIGHT_ILLUM, SPEECH_PRIORITY,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_FLASHLIGHT_ON, SPEECH_PRIORITY, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_FLASHLIGHT_OFF, SPEECH_PRIORITY, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_FOUNDPLAYER, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_TARGET_PLAYER, },
+ { TLK_PLAYER_KILLED_NPC, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_ENEMY_BURNING, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_SPOTTED_ZOMBIE_WAKEUP, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_SPOTTED_HEADCRAB_LEAVING_ZOMBIE,SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_DANGER_ZOMBINE_GRENADE, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_BALLSOCKETED, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+
+ // Darkness mode
+ { TLK_DARKNESS_LOSTPLAYER, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_DARKNESS_FOUNDPLAYER, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_DARKNESS_UNKNOWN_WOUND, SPEECH_IMPORTANT,-1,-1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_DARKNESS_HEARDSOUND, SPEECH_IMPORTANT,-1, -1, -1, -1, 20, 30, AICF_DEFAULT, },
+ { TLK_DARKNESS_LOSTENEMY_BY_FLASHLIGHT, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_DARKNESS_LOSTENEMY_BY_FLASHLIGHT_EXPIRED, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_DARKNESS_FOUNDENEMY_BY_FLASHLIGHT, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_DARKNESS_FLASHLIGHT_EXPIRED, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_SPOTTED_INCOMING_HEADCRAB, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+
+ // Lead behaviour
+ { TLK_LEAD_START, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_LEAD_ARRIVAL, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_LEAD_SUCCESS, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_LEAD_FAILURE, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_LEAD_COMINGBACK,SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_LEAD_CATCHUP, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_LEAD_RETRIEVE, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_LEAD_ATTRACTPLAYER, SPEECH_IMPORTANT,-1,-1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_LEAD_WAITOVER, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_LEAD_MISSINGWEAPON, SPEECH_IMPORTANT,-1,-1, -1, -1, -1, -1, AICF_DEFAULT, },
+ { TLK_LEAD_IDLE, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+
+ // Passenger behaviour
+ { TLK_PASSENGER_NEW_RADAR_CONTACT, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
+};
+
+//-----------------------------------------------------------------------------
+
+bool ConceptStringLessFunc( const string_t &lhs, const string_t &rhs )
+{
+ return CaselessStringLessThan( STRING(lhs), STRING(rhs) );
+}
+
+//-----------------------------------------------------------------------------
+
+class CConceptInfoMap : public CUtlMap<AIConcept_t, ConceptInfo_t *> {
+public:
+ CConceptInfoMap() :
+ CUtlMap<AIConcept_t, ConceptInfo_t *>( CaselessStringLessThan )
+ {
+ for ( int i = 0; i < ARRAYSIZE(g_ConceptInfos); i++ )
+ {
+ Insert( g_ConceptInfos[i].concept, &g_ConceptInfos[i] );
+ }
+ }
+};
+
+static CConceptInfoMap g_ConceptInfoMap;
+
+CAI_AllySpeechManager::CAI_AllySpeechManager()
+{
+ m_ConceptTimers.SetLessFunc( ConceptStringLessFunc );
+ Assert( !gm_pSpeechManager );
+ gm_pSpeechManager = this;
+}
+
+CAI_AllySpeechManager::~CAI_AllySpeechManager()
+{
+ gm_pSpeechManager = NULL;
+}
+
+void CAI_AllySpeechManager::Spawn()
+{
+ Assert( g_ConceptInfoMap.Count() != 0 );
+ for ( int i = 0; i < ARRAYSIZE(g_ConceptInfos); i++ )
+ m_ConceptTimers.Insert( AllocPooledString( g_ConceptInfos[i].concept ), CSimpleSimTimer() );
+}
+
+void CAI_AllySpeechManager::AddCustomConcept( const ConceptInfo_t &conceptInfo )
+{
+ Assert( g_ConceptInfoMap.Count() != 0 );
+ Assert( m_ConceptTimers.Count() != 0 );
+
+ if ( g_ConceptInfoMap.Find( conceptInfo.concept ) == g_ConceptInfoMap.InvalidIndex() )
+ {
+ g_ConceptInfoMap.Insert( conceptInfo.concept, new ConceptInfo_t( conceptInfo ) );
+ }
+
+ if ( m_ConceptTimers.Find( AllocPooledString(conceptInfo.concept) ) == m_ConceptTimers.InvalidIndex() )
+ {
+ m_ConceptTimers.Insert( AllocPooledString( conceptInfo.concept ), CSimpleSimTimer() );
+ }
+}
+
+ConceptCategoryInfo_t *CAI_AllySpeechManager::GetConceptCategoryInfo( ConceptCategory_t category )
+{
+ return &g_ConceptCategoryInfos[category];
+}
+
+ConceptInfo_t *CAI_AllySpeechManager::GetConceptInfo( AIConcept_t concept )
+{
+ int iResult = g_ConceptInfoMap.Find( concept );
+
+ return ( iResult != g_ConceptInfoMap.InvalidIndex() ) ? g_ConceptInfoMap[iResult] : NULL;
+}
+
+void CAI_AllySpeechManager::OnSpokeConcept( CAI_PlayerAlly *pPlayerAlly, AIConcept_t concept, AI_Response *response )
+{
+ ConceptInfo_t * pConceptInfo = GetConceptInfo( concept );
+ ConceptCategory_t category = ( pConceptInfo ) ? pConceptInfo->category : SPEECH_IDLE;
+ ConceptCategoryInfo_t * pCategoryInfo = GetConceptCategoryInfo( category );
+
+ if ( pConceptInfo )
+ {
+ if ( pConceptInfo->flags & AICF_PROPAGATE_SPOKEN )
+ {
+ CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
+ CAI_PlayerAlly *pTalker;
+ for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
+ {
+ pTalker = dynamic_cast<CAI_PlayerAlly *>(ppAIs[i]);
+
+ if ( pTalker && pTalker != pPlayerAlly &&
+ (pTalker->GetAbsOrigin() - pPlayerAlly->GetAbsOrigin()).LengthSqr() < Square(TALKRANGE_MIN * 2) &&
+ pPlayerAlly->FVisible( pTalker ) )
+ {
+ // Tell this guy he's already said the concept to the player, too.
+ pTalker->GetExpresser()->SetSpokeConcept( concept, NULL, false );
+ }
+ }
+ }
+ }
+
+ if ( !ai_no_talk_delay.GetBool() )
+ {
+ if ( pConceptInfo && pConceptInfo->minGlobalCategoryDelay != -1 )
+ {
+ Assert( pConceptInfo->maxGlobalCategoryDelay != -1 );
+ SetCategoryDelay( pConceptInfo->category, pConceptInfo->minGlobalCategoryDelay, pConceptInfo->maxGlobalCategoryDelay );
+ }
+ else if ( pCategoryInfo->maxGlobalDelay > 0 )
+ {
+ SetCategoryDelay( category, pCategoryInfo->minGlobalDelay, pCategoryInfo->maxGlobalDelay );
+ }
+
+ if ( pConceptInfo && pConceptInfo->minPersonalCategoryDelay != -1 )
+ {
+ Assert( pConceptInfo->maxPersonalCategoryDelay != -1 );
+ pPlayerAlly->SetCategoryDelay( pConceptInfo->category, pConceptInfo->minPersonalCategoryDelay, pConceptInfo->maxPersonalCategoryDelay );
+ }
+ else if ( pCategoryInfo->maxPersonalDelay > 0 )
+ {
+ pPlayerAlly->SetCategoryDelay( category, pCategoryInfo->minPersonalDelay, pCategoryInfo->maxPersonalDelay );
+ }
+
+ if ( pConceptInfo && pConceptInfo->minConceptDelay != -1 )
+ {
+ Assert( pConceptInfo->maxConceptDelay != -1 );
+ char iConceptTimer = m_ConceptTimers.Find( MAKE_STRING(concept) );
+ if ( iConceptTimer != m_ConceptTimers.InvalidIndex() )
+ m_ConceptTimers[iConceptTimer].Set( pConceptInfo->minConceptDelay, pConceptInfo->minConceptDelay );
+ }
+ }
+}
+
+void CAI_AllySpeechManager::SetCategoryDelay( ConceptCategory_t category, float minDelay, float maxDelay )
+{
+ if ( category != SPEECH_PRIORITY )
+ m_ConceptCategoryTimers[category].Set( minDelay, maxDelay );
+}
+
+bool CAI_AllySpeechManager::CategoryDelayExpired( ConceptCategory_t category )
+{
+ if ( category == SPEECH_PRIORITY )
+ return true;
+ return m_ConceptCategoryTimers[category].Expired();
+}
+
+bool CAI_AllySpeechManager::ConceptDelayExpired( AIConcept_t concept )
+{
+ char iConceptTimer = m_ConceptTimers.Find( MAKE_STRING(concept) );
+ if ( iConceptTimer != m_ConceptTimers.InvalidIndex() )
+ return m_ConceptTimers[iConceptTimer].Expired();
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+
+LINK_ENTITY_TO_CLASS( ai_ally_speech_manager, CAI_AllySpeechManager );
+
+BEGIN_DATADESC( CAI_AllySpeechManager )
+
+ DEFINE_EMBEDDED_AUTO_ARRAY(m_ConceptCategoryTimers),
+ DEFINE_UTLMAP( m_ConceptTimers, FIELD_STRING, FIELD_EMBEDDED ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+
+CAI_AllySpeechManager *CAI_AllySpeechManager::gm_pSpeechManager;
+
+//-----------------------------------------------------------------------------
+
+CAI_AllySpeechManager *GetAllySpeechManager()
+{
+ if ( !CAI_AllySpeechManager::gm_pSpeechManager )
+ {
+ CreateEntityByName( "ai_ally_speech_manager" );
+ Assert( CAI_AllySpeechManager::gm_pSpeechManager );
+ if ( CAI_AllySpeechManager::gm_pSpeechManager )
+ DispatchSpawn( CAI_AllySpeechManager::gm_pSpeechManager );
+ }
+
+ return CAI_AllySpeechManager::gm_pSpeechManager;
+}
+
+//-----------------------------------------------------------------------------
+//
+// CLASS: CAI_PlayerAlly
+//
+//-----------------------------------------------------------------------------
+
+BEGIN_DATADESC( CAI_PlayerAlly )
+
+ DEFINE_EMBEDDED( m_PendingResponse ),
+ DEFINE_STDSTRING( m_PendingConcept ),
+ DEFINE_FIELD( m_TimePendingSet, FIELD_TIME ),
+ DEFINE_FIELD( m_hTalkTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flNextRegenTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flTimePlayerStartStare, FIELD_TIME ),
+ DEFINE_FIELD( m_hPotentialSpeechTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flNextIdleSpeechTime, FIELD_TIME ),
+ DEFINE_FIELD( m_iQARandomNumber, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hSpeechFilter, FIELD_EHANDLE ),
+ DEFINE_EMBEDDED_AUTO_ARRAY(m_ConceptCategoryTimers),
+
+ DEFINE_KEYFIELD( m_bGameEndAlly, FIELD_BOOLEAN, "GameEndAlly" ),
+ DEFINE_FIELD( m_bCanSpeakWhileScripting, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_flHealthAccumulator, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flTimeLastRegen, FIELD_TIME ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "IdleRespond", InputIdleRespond ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "SpeakResponseConcept", InputSpeakResponseConcept ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "MakeGameEndAlly", InputMakeGameEndAlly ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "MakeRegularAlly", InputMakeRegularAlly ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "AnswerQuestion", InputAnswerQuestion ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "AnswerQuestionHello", InputAnswerQuestionHello ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableSpeakWhileScripting", InputEnableSpeakWhileScripting ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableSpeakWhileScripting", InputDisableSpeakWhileScripting ),
+
+END_DATADESC()
+
+CBaseEntity *CreatePlayerLoadSave( Vector vOrigin, float flDuration, float flHoldTime, float flLoadTime );
+ConVar npc_ally_deathmessage( "npc_ally_deathmessage", "1", FCVAR_CHEAT );
+
+void CAI_PlayerAlly::InputMakeGameEndAlly( inputdata_t &inputdata )
+{
+ m_bGameEndAlly = true;
+}
+
+void CAI_PlayerAlly::InputMakeRegularAlly( inputdata_t &inputdata )
+{
+ m_bGameEndAlly = false;
+}
+
+void CAI_PlayerAlly::DisplayDeathMessage( void )
+{
+ if ( m_bGameEndAlly == false )
+ return;
+
+ if ( npc_ally_deathmessage.GetBool() == 0 )
+ return;
+
+ CBaseEntity *pPlayer = AI_GetSinglePlayer();
+
+ if ( pPlayer )
+ {
+ UTIL_ShowMessage( GetDeathMessageText(), ToBasePlayer( pPlayer ) );
+ ToBasePlayer(pPlayer)->NotifySinglePlayerGameEnding();
+ }
+
+ CBaseEntity *pReload = CreatePlayerLoadSave( GetAbsOrigin(), 1.5f, 8.0f, 4.5f );
+
+ if ( pReload )
+ {
+ pReload->SetRenderColor( 0, 0, 0, 255 );
+
+ g_EventQueue.AddEvent( pReload, "Reload", 1.5f, pReload, pReload );
+ }
+
+ // clear any pending autosavedangerous
+ g_ServerGameDLL.m_fAutoSaveDangerousTime = 0.0f;
+ g_ServerGameDLL.m_fAutoSaveDangerousMinHealthToCommit = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// NPCs derived from CAI_PlayerAlly should call this in precache()
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::TalkInit( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::GatherConditions( void )
+{
+ BaseClass::GatherConditions();
+
+ if ( !HasCondition( COND_SEE_PLAYER ) )
+ {
+ SetCondition( COND_TALKER_CLIENTUNSEEN );
+ }
+
+ CBasePlayer *pLocalPlayer = AI_GetSinglePlayer();
+
+ if ( !pLocalPlayer )
+ {
+ if ( AI_IsSinglePlayer() )
+ SetCondition( COND_TALKER_PLAYER_DEAD );
+ return;
+ }
+
+ if ( !pLocalPlayer->IsAlive() )
+ {
+ SetCondition( COND_TALKER_PLAYER_DEAD );
+ }
+
+ if ( HasCondition( COND_SEE_PLAYER ) )
+ {
+
+ bool bPlayerIsLooking = false;
+ if ( ( pLocalPlayer->GetAbsOrigin() - GetAbsOrigin() ).Length2DSqr() < Square(TALKER_STARE_DIST) )
+ {
+ if ( pLocalPlayer->FInViewCone( EyePosition() ) )
+ {
+ if ( pLocalPlayer->GetSmoothedVelocity().LengthSqr() < Square( 100 ) )
+ bPlayerIsLooking = true;
+ }
+ }
+
+ if ( bPlayerIsLooking )
+ {
+ SetCondition( COND_TALKER_PLAYER_STARING );
+ if ( m_flTimePlayerStartStare == 0 )
+ m_flTimePlayerStartStare = gpGlobals->curtime;
+ }
+ else
+ {
+ m_flTimePlayerStartStare = 0;
+ ClearCondition( COND_TALKER_PLAYER_STARING );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::GatherEnemyConditions( CBaseEntity *pEnemy )
+{
+ BaseClass::GatherEnemyConditions( pEnemy );
+ if ( GetLastEnemyTime() == 0 || gpGlobals->curtime - GetLastEnemyTime() > 30 )
+ {
+#ifdef HL2_DLL
+ if ( HasCondition( COND_SEE_ENEMY ) && ( pEnemy->Classify() != CLASS_BULLSEYE ) )
+ {
+ if( Classify() == CLASS_PLAYER_ALLY_VITAL && hl2_episodic.GetBool() )
+ {
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+
+ if( pPlayer )
+ {
+
+ // If I can see the player, and the player would see this enemy if he turned around...
+ if( !pPlayer->FInViewCone(pEnemy) && FVisible( pPlayer ) && pPlayer->FVisible(pEnemy) )
+ {
+ Vector2D vecPlayerView = pPlayer->EyeDirection2D().AsVector2D();
+ Vector2D vecToEnemy = ( pEnemy->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).AsVector2D();
+ Vector2DNormalize( vecToEnemy );
+
+ if( DotProduct2D(vecPlayerView, vecToEnemy) <= -0.75 )
+ {
+ SpeakIfAllowed( TLK_WATCHOUT, "dangerloc:behind" );
+ return;
+ }
+ }
+ }
+ }
+ SpeakIfAllowed( TLK_STARTCOMBAT );
+ }
+#else
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ SpeakIfAllowed( TLK_STARTCOMBAT );
+ }
+#endif //HL2_DLL
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::OnStateChange( NPC_STATE OldState, NPC_STATE NewState )
+{
+ BaseClass::OnStateChange( OldState, NewState );
+
+ if ( OldState == NPC_STATE_COMBAT )
+ {
+ DeferAllIdleSpeech();
+ }
+
+ if ( GetState() == NPC_STATE_IDLE || GetState() == NPC_STATE_ALERT )
+ {
+ m_flNextIdleSpeechTime = gpGlobals->curtime + RandomFloat(5,10);
+ }
+ else
+ {
+ m_flNextIdleSpeechTime = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::PrescheduleThink( void )
+{
+ BaseClass::PrescheduleThink();
+
+#ifdef HL2_DLL
+ // Vital allies regenerate
+ if( GetHealth() >= GetMaxHealth() )
+ {
+ // Avoid huge deltas on first regeneration of health after long period of time at full health.
+ m_flTimeLastRegen = gpGlobals->curtime;
+ }
+ else if ( ShouldRegenerateHealth() )
+ {
+ float flDelta = gpGlobals->curtime - m_flTimeLastRegen;
+ float flHealthPerSecond = 1.0f / sk_ally_regen_time.GetFloat();
+
+ float flHealthRegen = flHealthPerSecond * flDelta;
+
+ if ( g_pGameRules->IsSkillLevel(SKILL_HARD) )
+ flHealthRegen *= 0.5f;
+ else if ( g_pGameRules->IsSkillLevel(SKILL_EASY) )
+ flHealthRegen *= 1.5f;
+
+ m_flTimeLastRegen = gpGlobals->curtime;
+
+ TakeHealth( flHealthRegen, DMG_GENERIC );
+ }
+
+#ifdef HL2_EPISODIC
+ if ( (GetState() == NPC_STATE_IDLE || GetState() == NPC_STATE_ALERT)
+ && !HasCondition(COND_RECEIVED_ORDERS) && !IsInAScript() )
+ {
+ if ( m_flNextIdleSpeechTime && m_flNextIdleSpeechTime < gpGlobals->curtime )
+ {
+ AISpeechSelection_t selection;
+ if ( SelectNonCombatSpeech( &selection ) )
+ {
+ SetSpeechTarget( selection.hSpeechTarget );
+ SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
+ m_flNextIdleSpeechTime = gpGlobals->curtime + RandomFloat( 20,30 );
+ }
+ else
+ {
+ m_flNextIdleSpeechTime = gpGlobals->curtime + RandomFloat( 10,20 );
+ }
+ }
+ }
+#endif // HL2_EPISODIC
+
+#endif // HL2_DLL
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAI_PlayerAlly::SelectSchedule( void )
+{
+ if ( !HasCondition(COND_RECEIVED_ORDERS) )
+ {
+ // sustained light wounds?
+ if ( m_iHealth <= m_iMaxHealth * 0.75 && IsAllowedToSpeak( TLK_WOUND ) && !GetExpresser()->SpokeConcept(TLK_WOUND) )
+ {
+ CTakeDamageInfo info;
+ PainSound( info );
+ }
+ // sustained heavy wounds?
+ else if ( m_iHealth <= m_iMaxHealth * 0.5 && IsAllowedToSpeak( TLK_MORTAL) )
+ {
+ Speak( TLK_MORTAL );
+ }
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CAI_PlayerAlly::SelectSpeechResponse( AIConcept_t concept, const char *pszModifiers, CBaseEntity *pTarget, AISpeechSelection_t *pSelection )
+{
+ if ( IsAllowedToSpeak( concept ) )
+ {
+ AI_Response *pResponse = SpeakFindResponse( concept, pszModifiers );
+ if ( pResponse )
+ {
+ pSelection->Set( concept, pResponse, pTarget );
+ return true;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::SetPendingSpeech( AIConcept_t concept, AI_Response *pResponse )
+{
+ m_PendingResponse = *pResponse;
+ pResponse->Release();
+ m_PendingConcept = concept;
+ m_TimePendingSet = gpGlobals->curtime;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::ClearPendingSpeech()
+{
+ m_PendingConcept.erase();
+ m_TimePendingSet = 0;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+bool CAI_PlayerAlly::SelectIdleSpeech( AISpeechSelection_t *pSelection )
+{
+ if ( !IsOkToSpeak( SPEECH_IDLE ) )
+ return false;
+
+ CBasePlayer *pTarget = assert_cast<CBasePlayer *>(FindSpeechTarget( AIST_PLAYERS | AIST_FACING_TARGET ));
+ if ( pTarget )
+ {
+ if ( SelectSpeechResponse( TLK_HELLO, NULL, pTarget, pSelection ) )
+ return true;
+
+ if ( GetTimePlayerStaring() > 6 && !IsMoving() )
+ {
+ if ( SelectSpeechResponse( TLK_STARE, NULL, pTarget, pSelection ) )
+ return true;
+ }
+
+ int chance = ( IsMoving() ) ? 20 : 2;
+ if ( ShouldSpeakRandom( TLK_IDLE, chance ) && SelectSpeechResponse( TLK_IDLE, NULL, pTarget, pSelection ) )
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CAI_PlayerAlly::SelectAlertSpeech( AISpeechSelection_t *pSelection )
+{
+#ifdef HL2_EPISODIC
+ CBasePlayer *pTarget = assert_cast<CBasePlayer *>(FindSpeechTarget( AIST_PLAYERS | AIST_FACING_TARGET ));
+ if ( pTarget )
+ {
+ if ( pTarget->IsAlive() )
+ {
+ float flHealthPerc = ((float)pTarget->m_iHealth / (float)pTarget->m_iMaxHealth);
+ if ( flHealthPerc < 1.0 )
+ {
+ if ( SelectSpeechResponse( TLK_PLHURT, NULL, pTarget, pSelection ) )
+ return true;
+ }
+ }
+ }
+#endif
+
+ return SelectIdleSpeech( pSelection );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+bool CAI_PlayerAlly::SelectInterjection()
+{
+ if ( HasPendingSpeech() )
+ return false;
+
+ if ( HasCondition(COND_RECEIVED_ORDERS) )
+ return false;
+
+ if ( GetState() == NPC_STATE_IDLE || GetState() == NPC_STATE_ALERT )
+ {
+ AISpeechSelection_t selection;
+
+ if ( SelectIdleSpeech( &selection ) )
+ {
+ SetSpeechTarget( selection.hSpeechTarget );
+ SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+bool CAI_PlayerAlly::SelectPlayerUseSpeech()
+{
+ if( IsOkToSpeakInResponseToPlayer() )
+ {
+ if ( Speak( TLK_USE ) )
+ DeferAllIdleSpeech();
+ else
+ return Speak( ( !GetExpresser()->SpokeConcept( TLK_HELLO ) ) ? TLK_HELLO : TLK_IDLE );
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CAI_PlayerAlly::SelectQuestionAndAnswerSpeech( AISpeechSelection_t *pSelection )
+{
+ if ( !IsOkToSpeak( SPEECH_IDLE ) )
+ return false;
+
+ if ( IsMoving() )
+ return false;
+
+ // if there is a friend nearby to speak to, play sentence, set friend's response time, return
+ CAI_PlayerAlly *pFriend = dynamic_cast<CAI_PlayerAlly *>(FindSpeechTarget( AIST_NPCS ));
+ if ( pFriend && !pFriend->IsMoving() && !pFriend->HasSpawnFlags(SF_NPC_GAG) )
+ return SelectQuestionFriend( pFriend, pSelection );
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::PostSpeakDispatchResponse( AIConcept_t concept, AI_Response *response )
+{
+#ifdef HL2_EPISODIC
+ CAI_AllySpeechManager *pSpeechManager = GetAllySpeechManager();
+ ConceptInfo_t *pConceptInfo = pSpeechManager->GetConceptInfo( concept );
+ if ( pConceptInfo && (pConceptInfo->flags & AICF_QUESTION) && GetSpeechTarget() )
+ {
+ bool bSaidHelloToNPC = !Q_strcmp(concept, "TLK_HELLO_NPC");
+
+ float duration = GetExpresser()->GetSemaphoreAvailableTime(this) - gpGlobals->curtime;
+
+ if ( rr_debug_qa.GetBool() )
+ {
+ if ( bSaidHelloToNPC )
+ {
+ Warning("Q&A: '%s' said Hello to '%s' (concept %s)\n", GetDebugName(), GetSpeechTarget()->GetDebugName(), concept );
+ }
+ else
+ {
+ Warning("Q&A: '%s' questioned '%s' (concept %s)\n", GetDebugName(), GetSpeechTarget()->GetDebugName(), concept );
+ }
+ NDebugOverlay::HorzArrow( GetAbsOrigin(), GetSpeechTarget()->GetAbsOrigin(), 8, 0, 255, 0, 64, true, duration );
+ }
+
+ // If we spoke a Question, tell our friend to answer
+ const char *pszInput;
+ if ( bSaidHelloToNPC )
+ {
+ pszInput = "AnswerQuestionHello";
+ }
+ else
+ {
+ pszInput = "AnswerQuestion";
+ }
+
+ // Set the input parameter to the random number we used to find the Question
+ variant_t value;
+ value.SetInt( m_iQARandomNumber );
+ g_EventQueue.AddEvent( GetSpeechTarget(), pszInput, value, duration + .2, this, this );
+
+ if ( GetSpeechTarget()->MyNPCPointer() )
+ {
+ AddLookTarget( GetSpeechTarget()->MyNPCPointer(), 1.0, duration + random->RandomFloat( 0.4, 1.2 ), 0.5 );
+ GetSpeechTarget()->MyNPCPointer()->AddLookTarget( this, 1.0, duration + random->RandomFloat( 0.4, 1 ), 0.7 );
+ }
+
+ // Don't let anyone else butt in.
+ DeferAllIdleSpeech( random->RandomFloat( TALKER_DEFER_IDLE_SPEAK_MIN, TALKER_DEFER_IDLE_SPEAK_MAX ), GetSpeechTarget()->MyNPCPointer() );
+ }
+ else if ( pConceptInfo && (pConceptInfo->flags & AICF_ANSWER) && GetSpeechTarget() )
+ {
+ float duration = GetExpresser()->GetSemaphoreAvailableTime(this) - gpGlobals->curtime;
+ if ( rr_debug_qa.GetBool() )
+ {
+ NDebugOverlay::HorzArrow( GetAbsOrigin(), GetSpeechTarget()->GetAbsOrigin(), 8, 0, 255, 0, 64, true, duration );
+ }
+ if ( GetSpeechTarget()->MyNPCPointer() )
+ {
+ AddLookTarget( GetSpeechTarget()->MyNPCPointer(), 1.0, duration + random->RandomFloat( 0, 0.3 ), 0.5 );
+ GetSpeechTarget()->MyNPCPointer()->AddLookTarget( this, 1.0, duration + random->RandomFloat( 0.2, 0.5 ), 0.7 );
+ }
+ }
+
+ m_hPotentialSpeechTarget = NULL;
+#endif // HL2_EPISODIC
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find a concept to question a nearby friend
+//-----------------------------------------------------------------------------
+bool CAI_PlayerAlly::SelectQuestionFriend( CBaseEntity *pFriend, AISpeechSelection_t *pSelection )
+{
+ if ( !ShouldSpeakRandom( TLK_QUESTION, 3 ) )
+ return false;
+
+ // Tell the response rules who we're trying to question
+ m_hPotentialSpeechTarget = pFriend;
+ m_iQARandomNumber = RandomInt(0,100);
+
+ // If we haven't said hello, say hello first.
+ // Only ever say hello to NPCs other than my type.
+ if ( !GetExpresser()->SpokeConcept( TLK_HELLO_NPC ) && !FClassnameIs( this, pFriend->GetClassname()) )
+ {
+ if ( SelectSpeechResponse( TLK_HELLO_NPC, NULL, pFriend, pSelection ) )
+ return true;
+
+ GetExpresser()->SetSpokeConcept( TLK_HELLO_NPC, NULL );
+ }
+
+ return SelectSpeechResponse( TLK_QUESTION, NULL, pFriend, pSelection );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find a concept to answer our friend's question
+//-----------------------------------------------------------------------------
+bool CAI_PlayerAlly::SelectAnswerFriend( CBaseEntity *pFriend, AISpeechSelection_t *pSelection, bool bRespondingToHello )
+{
+ // Tell the response rules who we're trying to answer
+ m_hPotentialSpeechTarget = pFriend;
+
+ if ( bRespondingToHello )
+ {
+ if ( SelectSpeechResponse( TLK_ANSWER_HELLO, NULL, pFriend, pSelection ) )
+ return true;
+ }
+
+ return SelectSpeechResponse( TLK_ANSWER, NULL, pFriend, pSelection );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::InputAnswerQuestion( inputdata_t &inputdata )
+{
+ AnswerQuestion( dynamic_cast<CAI_PlayerAlly *>(inputdata.pActivator), inputdata.value.Int(), false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::InputAnswerQuestionHello( inputdata_t &inputdata )
+{
+ AnswerQuestion( dynamic_cast<CAI_PlayerAlly *>(inputdata.pActivator), inputdata.value.Int(), true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::AnswerQuestion( CAI_PlayerAlly *pQuestioner, int iQARandomNum, bool bAnsweringHello )
+{
+ // Original questioner may have died
+ if ( !pQuestioner )
+ return;
+
+ AISpeechSelection_t selection;
+
+ // Use the random number that the questioner used to determine his Question (so we can match answers via response rules)
+ m_iQARandomNumber = iQARandomNum;
+
+ // The activator is the person we're responding to
+ if ( SelectAnswerFriend( pQuestioner, &selection, bAnsweringHello ) )
+ {
+ if ( rr_debug_qa.GetBool() )
+ {
+ if ( bAnsweringHello )
+ {
+ Warning("Q&A: '%s' answered the Hello from '%s'\n", GetDebugName(), pQuestioner->GetDebugName() );
+ }
+ else
+ {
+ Warning("Q&A: '%s' answered the Question from '%s'\n", GetDebugName(), pQuestioner->GetDebugName() );
+ }
+ }
+
+ Assert( selection.pResponse );
+ SetSpeechTarget( selection.hSpeechTarget );
+ SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
+
+ // Prevent idle speech for a while
+ DeferAllIdleSpeech( random->RandomFloat( TALKER_DEFER_IDLE_SPEAK_MIN, TALKER_DEFER_IDLE_SPEAK_MAX ), GetSpeechTarget()->MyNPCPointer() );
+ }
+ else if ( rr_debug_qa.GetBool() )
+ {
+ Warning("Q&A: '%s' couldn't answer '%s'\n", GetDebugName(), pQuestioner->GetDebugName() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Output : int
+//-----------------------------------------------------------------------------
+int CAI_PlayerAlly::SelectNonCombatSpeech( AISpeechSelection_t *pSelection )
+{
+ bool bResult = false;
+
+ #ifdef HL2_EPISODIC
+ // See if we can Q&A first
+ if ( GetState() == NPC_STATE_IDLE || GetState() == NPC_STATE_ALERT )
+ {
+ bResult = SelectQuestionAndAnswerSpeech( pSelection );
+ }
+ #endif // HL2_EPISODIC
+
+ if ( !bResult )
+ {
+ if ( GetState() == NPC_STATE_ALERT )
+ {
+ bResult = SelectAlertSpeech( pSelection );
+ }
+ else
+ {
+ bResult = SelectIdleSpeech( pSelection );
+ }
+ }
+
+ return bResult;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAI_PlayerAlly::SelectNonCombatSpeechSchedule()
+{
+ if ( !HasPendingSpeech() )
+ {
+ AISpeechSelection_t selection;
+ if ( SelectNonCombatSpeech( &selection ) )
+ {
+ Assert( selection.pResponse );
+ SetSpeechTarget( selection.hSpeechTarget );
+ SetPendingSpeech( selection.concept.c_str(), selection.pResponse );
+ }
+ }
+
+ if ( HasPendingSpeech() )
+ {
+ if ( m_TimePendingSet == gpGlobals->curtime || IsAllowedToSpeak( m_PendingConcept.c_str() ) )
+ return SCHED_TALKER_SPEAK_PENDING_IDLE;
+ }
+
+ return SCHED_NONE;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAI_PlayerAlly::TranslateSchedule( int schedule )
+{
+ if ( ( GetState() == NPC_STATE_IDLE || GetState() == NPC_STATE_ALERT ) &&
+ ConditionInterruptsSchedule( schedule, COND_IDLE_INTERRUPT ) &&
+ !HasCondition(COND_RECEIVED_ORDERS) )
+ {
+ int speechSchedule = SelectNonCombatSpeechSchedule();
+ if ( speechSchedule != SCHED_NONE )
+ return speechSchedule;
+ }
+
+ switch( schedule )
+ {
+ case SCHED_CHASE_ENEMY_FAILED:
+ {
+ int baseType = BaseClass::TranslateSchedule(schedule);
+ if ( baseType != SCHED_CHASE_ENEMY_FAILED )
+ return baseType;
+
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+ }
+ break;
+ }
+ return BaseClass::TranslateSchedule( schedule );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::OnStartSchedule( int schedule )
+{
+ if ( schedule == SCHED_HIDE_AND_RELOAD )
+ SpeakIfAllowed( TLK_HIDEANDRELOAD );
+ BaseClass::OnStartSchedule( schedule );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_MOVE_AWAY_PATH:
+ {
+ if ( HasCondition( COND_PLAYER_PUSHING ) && AI_IsSinglePlayer() )
+ {
+ // @TODO (toml 10-22-04): cope with multiplayer push
+ GetMotor()->SetIdealYawToTarget( UTIL_GetLocalPlayer()->WorldSpaceCenter() );
+ }
+ BaseClass::StartTask( pTask );
+ break;
+ }
+
+ case TASK_PLAY_SCRIPT:
+ SetSpeechTarget( NULL );
+ BaseClass::StartTask( pTask );
+ break;
+
+ case TASK_TALKER_SPEAK_PENDING:
+ if ( !m_PendingConcept.empty() )
+ {
+ AI_Response *pResponse = new AI_Response;
+ *pResponse = m_PendingResponse;
+ SpeakDispatchResponse( m_PendingConcept.c_str(), pResponse );
+ m_PendingConcept.erase();
+ TaskComplete();
+ }
+ else
+ TaskFail( FAIL_NO_SOUND );
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::RunTask( const Task_t *pTask )
+{
+ BaseClass::RunTask( pTask );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::TaskFail( AI_TaskFailureCode_t code )
+{
+ if ( IsCurSchedule( SCHED_TALKER_SPEAK_PENDING_IDLE, false ) )
+ ClearPendingSpeech();
+ BaseClass::TaskFail( code );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::ClearTransientConditions()
+{
+ CAI_BaseNPC::ClearTransientConditions();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::Touch( CBaseEntity *pOther )
+{
+ BaseClass::Touch( pOther );
+
+ // Did the player touch me?
+ if ( pOther->IsPlayer() )
+ {
+ // Ignore if pissed at player
+ if ( m_afMemory & bits_MEMORY_PROVOKED )
+ return;
+
+ // Stay put during speech
+ if ( GetExpresser()->IsSpeaking() )
+ return;
+
+ TestPlayerPushing( pOther );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::OnKilledNPC( CBaseCombatCharacter *pKilled )
+{
+ if ( pKilled )
+ {
+ if ( !pKilled->IsNPC() ||
+ ( pKilled->MyNPCPointer()->GetLastPlayerDamageTime() == 0 ||
+ gpGlobals->curtime - pKilled->MyNPCPointer()->GetLastPlayerDamageTime() > 5 ) )
+ {
+ SpeakIfAllowed( TLK_ENEMY_DEAD );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ const char *pszHitLocCriterion = NULL;
+
+ if ( ptr->hitgroup == HITGROUP_LEFTLEG || ptr->hitgroup == HITGROUP_RIGHTLEG )
+ {
+ pszHitLocCriterion = "shotloc:leg";
+ }
+ else if ( ptr->hitgroup == HITGROUP_LEFTARM || ptr->hitgroup == HITGROUP_RIGHTARM )
+ {
+ pszHitLocCriterion = "shotloc:arm";
+ }
+ else if ( ptr->hitgroup == HITGROUP_STOMACH )
+ {
+ pszHitLocCriterion = "shotloc:gut";
+ }
+
+ // set up the speech modifiers
+ CFmtStrN<128> modifiers( "%s,damageammo:%s", pszHitLocCriterion, info.GetAmmoName() );
+
+ SpeakIfAllowed( TLK_SHOT, modifiers );
+
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAI_PlayerAlly::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ CTakeDamageInfo subInfo = info;
+ // Vital allies never take more than 25% of their health in a single hit (except for physics damage)
+#ifdef HL2_DLL
+ // Don't do damage reduction for DMG_GENERIC. This allows SetHealth inputs to still do full damage.
+ if ( subInfo.GetDamageType() != DMG_GENERIC )
+ {
+ if ( Classify() == CLASS_PLAYER_ALLY_VITAL && !(subInfo.GetDamageType() & DMG_CRUSH) )
+ {
+ float flDamage = subInfo.GetDamage();
+ if ( flDamage > ( GetMaxHealth() * 0.25 ) )
+ {
+ flDamage = ( GetMaxHealth() * 0.25 );
+ subInfo.SetDamage( flDamage );
+ }
+ }
+ }
+#endif
+
+ return BaseClass::OnTakeDamage_Alive( subInfo );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAI_PlayerAlly::TakeHealth( float flHealth, int bitsDamageType )
+{
+ int intPortion;
+ float floatPortion;
+
+ intPortion = ((int)flHealth);
+ floatPortion = flHealth - intPortion;
+
+ m_flHealthAccumulator += floatPortion;
+
+ while ( m_flHealthAccumulator > 1.0f )
+ {
+ m_flHealthAccumulator -= 1.0f;
+ intPortion += 1;
+ }
+
+ return BaseClass::TakeHealth( ((float)intPortion), bitsDamageType );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::Event_Killed( const CTakeDamageInfo &info )
+{
+ // notify the player
+ if ( IsInPlayerSquad() )
+ {
+ CBasePlayer *player = AI_GetSinglePlayer();
+ if ( player )
+ {
+ variant_t emptyVariant;
+ player->AcceptInput( "OnSquadMemberKilled", this, this, emptyVariant, 0 );
+ }
+ }
+
+ if ( GetSpeechSemaphore( this )->GetOwner() == this )
+ GetSpeechSemaphore( this )->Release();
+
+ CAI_PlayerAlly *pMourner = dynamic_cast<CAI_PlayerAlly *>(FindSpeechTarget( AIST_NPCS ));
+ if ( pMourner )
+ {
+ pMourner->SpeakIfAllowed( TLK_ALLY_KILLED );
+ }
+
+ SetTarget( NULL );
+ // Don't finish that sentence
+ SentenceStop();
+ SetUse( NULL );
+ BaseClass::Event_Killed( info );
+
+ DisplayDeathMessage();
+}
+
+// Player allies should use simple shadows to save CPU. This means they can't
+// be killed by crush damage.
+bool CAI_PlayerAlly::CreateVPhysics()
+{
+ bool bRet = BaseClass::CreateVPhysics();
+ return bRet;
+}
+
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::PainSound( const CTakeDamageInfo &info )
+{
+ SpeakIfAllowed( TLK_WOUND );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Implemented to look at talk target
+//-----------------------------------------------------------------------------
+CBaseEntity *CAI_PlayerAlly::EyeLookTarget( void )
+{
+ // FIXME: this should be in the VCD
+ // FIXME: this is dead code
+ if (GetExpresser()->IsSpeaking() && GetSpeechTarget() != NULL)
+ {
+ return GetSpeechTarget();
+ }
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: returns who we're talking to for vcd's
+//-----------------------------------------------------------------------------
+CBaseEntity *CAI_PlayerAlly::FindNamedEntity( const char *pszName, IEntityFindFilter *pFilter )
+{
+ if ( !stricmp( pszName, "!speechtarget" ))
+ {
+ return GetSpeechTarget();
+ }
+
+ if ( !stricmp( pszName, "!friend" ))
+ {
+ return FindSpeechTarget( AIST_NPCS );
+ }
+
+
+ return BaseClass::FindNamedEntity( pszName, pFilter );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CAI_PlayerAlly::IsValidSpeechTarget( int flags, CBaseEntity *pEntity )
+{
+ if ( pEntity == this )
+ return false;
+
+ if ( !(flags & AIST_IGNORE_RELATIONSHIP) )
+ {
+ if ( pEntity->IsPlayer() )
+ {
+ if ( !IsPlayerAlly( (CBasePlayer *)pEntity ) )
+ return false;
+ }
+ else
+ {
+ if ( IRelationType( pEntity ) != D_LI )
+ return false;
+ }
+ }
+
+ if ( !pEntity->IsAlive() )
+ // don't dead people
+ return false;
+
+ // Ignore no-target entities
+ if ( pEntity->GetFlags() & FL_NOTARGET )
+ return false;
+
+ CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
+ if ( pNPC )
+ {
+ // If not a NPC for some reason, or in a script.
+ if ( (pNPC->m_NPCState == NPC_STATE_SCRIPT || pNPC->m_NPCState == NPC_STATE_PRONE))
+ return false;
+
+ if ( pNPC->IsInAScript() )
+ return false;
+
+ // Don't bother people who don't want to be bothered
+ if ( !pNPC->CanBeUsedAsAFriend() )
+ return false;
+ }
+
+ if ( flags & AIST_FACING_TARGET )
+ {
+ if ( pEntity->IsPlayer() )
+ return HasCondition( COND_SEE_PLAYER );
+ else if ( !FInViewCone( pEntity ) )
+ return false;
+ }
+
+ return FVisible( pEntity );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CBaseEntity *CAI_PlayerAlly::FindSpeechTarget( int flags )
+{
+ const Vector & vAbsOrigin = GetAbsOrigin();
+ float closestDistSq = FLT_MAX;
+ CBaseEntity * pNearest = NULL;
+ float distSq;
+ int i;
+
+ if ( flags & AIST_PLAYERS )
+ {
+ for ( i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseEntity *pPlayer = UTIL_PlayerByIndex( i );
+ if ( pPlayer )
+ {
+ distSq = ( vAbsOrigin - pPlayer->GetAbsOrigin() ).LengthSqr();
+
+ if ( distSq > Square(TALKRANGE_MIN) )
+ continue;
+
+ if ( !(flags & AIST_ANY_QUALIFIED) && distSq > closestDistSq )
+ continue;
+
+ if ( IsValidSpeechTarget( flags, pPlayer ) )
+ {
+ if ( flags & AIST_ANY_QUALIFIED )
+ return pPlayer;
+
+ closestDistSq = distSq;
+ pNearest = pPlayer;
+ }
+ }
+ }
+ }
+
+ if ( flags & AIST_NPCS )
+ {
+ for ( i = 0; i < g_AI_Manager.NumAIs(); i++ )
+ {
+ CAI_BaseNPC *pNPC = (g_AI_Manager.AccessAIs())[i];
+
+ distSq = ( vAbsOrigin - pNPC->GetAbsOrigin() ).LengthSqr();
+
+ if ( distSq > Square(TALKRANGE_MIN) )
+ continue;
+
+ if ( !(flags & AIST_ANY_QUALIFIED) && distSq > closestDistSq )
+ continue;
+
+ if ( IsValidSpeechTarget( flags, pNPC ) )
+ {
+ if ( flags & AIST_ANY_QUALIFIED )
+ return pNPC;
+
+ closestDistSq = distSq;
+ pNearest = pNPC;
+ }
+ }
+ }
+
+ return pNearest;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CAI_PlayerAlly::CanPlaySentence( bool fDisregardState )
+{
+ if ( fDisregardState )
+ return BaseClass::CanPlaySentence( fDisregardState );
+ return IsOkToSpeak();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAI_PlayerAlly::PlayScriptedSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener )
+{
+ ClearCondition( COND_PLAYER_PUSHING ); // Forget about moving! I've got something to say!
+ int sentenceIndex = BaseClass::PlayScriptedSentence( pszSentence, delay, volume, soundlevel, bConcurrent, pListener );
+ SetSpeechTarget( pListener );
+
+ return sentenceIndex;
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+void CAI_PlayerAlly::DeferAllIdleSpeech( float flDelay, CAI_BaseNPC *pIgnore )
+{
+ CAI_AllySpeechManager *pSpeechManager = GetAllySpeechManager();
+ if ( flDelay == -1 )
+ {
+ ConceptCategoryInfo_t *pCategoryInfo = pSpeechManager->GetConceptCategoryInfo( SPEECH_IDLE );
+ pSpeechManager->SetCategoryDelay( SPEECH_IDLE, pCategoryInfo->minGlobalDelay, pCategoryInfo->maxGlobalDelay );
+ }
+ else
+ pSpeechManager->SetCategoryDelay( SPEECH_IDLE, flDelay );
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+bool CAI_PlayerAlly::IsOkToSpeak( ConceptCategory_t category, bool fRespondingToPlayer )
+{
+ CAI_AllySpeechManager *pSpeechManager = GetAllySpeechManager();
+
+ // if not alive, certainly don't speak
+ if ( !IsAlive() )
+ return false;
+
+ if ( m_spawnflags & SF_NPC_GAG )
+ return false;
+
+ // Don't speak if playing a script.
+ if ( ( m_NPCState == NPC_STATE_SCRIPT ) && !m_bCanSpeakWhileScripting )
+ return false;
+
+ // Don't speak if being eaten by a barnacle
+ if ( IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
+ return false;
+
+ if ( IsInAScript() && !m_bCanSpeakWhileScripting )
+ return false;
+
+ if ( !fRespondingToPlayer )
+ {
+ if ( !pSpeechManager->CategoryDelayExpired( category ) || !CategoryDelayExpired( category ) )
+ return false;
+ }
+
+ if ( category == SPEECH_IDLE )
+ {
+ if ( GetState() != NPC_STATE_IDLE && GetState() != NPC_STATE_ALERT )
+ return false;
+ if ( GetSpeechFilter() && GetSpeechFilter()->GetIdleModifier() < 0.001 )
+ return false;
+ }
+
+ // if player is not in pvs, don't speak
+ if ( !UTIL_FindClientInPVS(edict()) )
+ return false;
+
+ if ( category != SPEECH_PRIORITY )
+ {
+ // if someone else is talking, don't speak
+ if ( !GetExpresser()->SemaphoreIsAvailable( this ) )
+ return false;
+
+ if ( fRespondingToPlayer )
+ {
+ if ( !GetExpresser()->CanSpeakAfterMyself() )
+ return false;
+ }
+ else
+ {
+ if ( !GetExpresser()->CanSpeak() )
+ return false;
+ }
+
+ // Don't talk if we're too far from the player
+ CBaseEntity *pPlayer = AI_GetSinglePlayer();
+ if ( pPlayer )
+ {
+ float flDist = sv_npc_talker_maxdist.GetFloat();
+ flDist *= flDist;
+ if ( (pPlayer->WorldSpaceCenter() - WorldSpaceCenter()).LengthSqr() > flDist )
+ return false;
+ }
+ }
+
+ if ( fRespondingToPlayer )
+ {
+ // If we're responding to the player, don't respond if the scene has speech in it
+ if ( IsRunningScriptedSceneWithSpeechAndNotPaused( this ) )
+ {
+ if( rr_debugresponses.GetInt() > 0 )
+ {
+ DevMsg("%s not allowed to speak because they are in a scripted scene\n", GetDebugName() );
+ }
+
+ return false;
+ }
+ }
+ else
+ {
+ // If we're not responding to the player, don't talk if running a logic_choreo
+ if ( IsRunningScriptedSceneAndNotPaused( this ) )
+ {
+ if( rr_debugresponses.GetInt() > 0 )
+ {
+ DevMsg("%s not allowed to speak because they are in a scripted scene\n", GetDebugName() );
+ }
+
+ return false;
+ }
+ }
+
+ return true;
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+bool CAI_PlayerAlly::IsOkToSpeak( void )
+{
+ return IsOkToSpeak( SPEECH_IDLE );
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+bool CAI_PlayerAlly::IsOkToCombatSpeak( void )
+{
+ return IsOkToSpeak( SPEECH_IMPORTANT );
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+bool CAI_PlayerAlly::IsOkToSpeakInResponseToPlayer( void )
+{
+ return IsOkToSpeak( SPEECH_IMPORTANT, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if I should speak based on the chance & the speech filter's modifier
+//-----------------------------------------------------------------------------
+bool CAI_PlayerAlly::ShouldSpeakRandom( AIConcept_t concept, int iChance )
+{
+ CAI_AllySpeechManager * pSpeechManager = GetAllySpeechManager();
+ ConceptInfo_t * pInfo = pSpeechManager->GetConceptInfo( concept );
+ ConceptCategory_t category = ( pInfo ) ? pInfo->category : SPEECH_IDLE;
+
+ if ( GetSpeechFilter() )
+ {
+ if ( category == SPEECH_IDLE )
+ {
+ float flModifier = GetSpeechFilter()->GetIdleModifier();
+ if ( flModifier < 0.001 )
+ return false;
+
+ iChance = floor( (float)iChance / flModifier );
+ }
+ }
+
+ if ( iChance < 1 )
+ return false;
+
+ if ( iChance == 1 )
+ return true;
+
+ return (random->RandomInt(1,iChance) == 1);
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CAI_PlayerAlly::IsAllowedToSpeak( AIConcept_t concept, bool bRespondingToPlayer )
+{
+ CAI_AllySpeechManager * pSpeechManager = GetAllySpeechManager();
+ ConceptInfo_t * pInfo = pSpeechManager->GetConceptInfo( concept );
+ ConceptCategory_t category = ( pInfo ) ? pInfo->category : SPEECH_IDLE;
+
+ if ( !IsOkToSpeak( category, bRespondingToPlayer ) )
+ return false;
+
+ if ( GetSpeechFilter() && GetSpeechFilter()->NeverSayHello() )
+ {
+ if ( CompareConcepts( concept, TLK_HELLO ) )
+ return false;
+ if ( CompareConcepts( concept, TLK_HELLO_NPC ) )
+ return false;
+ }
+
+ if ( !pSpeechManager->ConceptDelayExpired( concept ) )
+ return false;
+
+ if ( ( pInfo && pInfo->flags & AICF_SPEAK_ONCE ) && GetExpresser()->SpokeConcept( concept ) )
+ return false;
+
+ if ( !GetExpresser()->CanSpeakConcept( concept ) )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CAI_PlayerAlly::SpeakIfAllowed( AIConcept_t concept, const char *modifiers, bool bRespondingToPlayer, char *pszOutResponseChosen, size_t bufsize )
+{
+ if ( IsAllowedToSpeak( concept, bRespondingToPlayer ) )
+ {
+ return Speak( concept, modifiers, pszOutResponseChosen, bufsize );
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::ModifyOrAppendCriteria( AI_CriteriaSet& set )
+{
+ BaseClass::ModifyOrAppendCriteria( set );
+
+ if ( m_hPotentialSpeechTarget )
+ {
+ set.AppendCriteria( "speechtarget", m_hPotentialSpeechTarget->GetClassname() );
+ set.AppendCriteria( "speechtargetname", STRING(m_hPotentialSpeechTarget->GetEntityName()) );
+ set.AppendCriteria( "randomnum", UTIL_VarArgs("%d", m_iQARandomNumber) );
+ }
+
+ // Do we have a speech filter? If so, append it's criteria too
+ if ( GetSpeechFilter() )
+ {
+ GetSpeechFilter()->AppendContextToCriteria( set );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::OnSpokeConcept( AIConcept_t concept, AI_Response *response )
+{
+ CAI_AllySpeechManager *pSpeechManager = GetAllySpeechManager();
+ pSpeechManager->OnSpokeConcept( this, concept, response );
+
+ if( response != NULL && (response->GetParams()->flags & AI_ResponseParams::RG_WEAPONDELAY) )
+ {
+ // Stop shooting, as instructed, so that my speech can be heard.
+ GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + response->GetWeaponDelay() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::OnStartSpeaking()
+{
+ // If you say anything, don't greet the player - you may have already spoken to them
+ if ( !GetExpresser()->SpokeConcept( TLK_HELLO ) )
+ {
+ GetExpresser()->SetSpokeConcept( TLK_HELLO, NULL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Mapmaker input to force this NPC to speak a response rules concept
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::InputSpeakResponseConcept( inputdata_t &inputdata )
+{
+ SpeakMapmakerInterruptConcept( inputdata.value.StringID() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Allows mapmakers to override NPC_STATE_SCRIPT or IsScripting() for responses.
+//-----------------------------------------------------------------------------
+void CAI_PlayerAlly::InputEnableSpeakWhileScripting( inputdata_t &inputdata )
+{
+ m_bCanSpeakWhileScripting = true;
+}
+
+void CAI_PlayerAlly::InputDisableSpeakWhileScripting( inputdata_t &inputdata )
+{
+ m_bCanSpeakWhileScripting = false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CAI_PlayerAlly::SpeakMapmakerInterruptConcept( string_t iszConcept )
+{
+ // Let behaviors override
+ if ( BaseClass::SpeakMapmakerInterruptConcept(iszConcept) )
+ return false;
+
+ if (!IsOkToSpeakInResponseToPlayer())
+ return false;
+
+ Speak( STRING(iszConcept) );
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CAI_PlayerAlly::CanRespondToEvent( const char *ResponseConcept )
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CAI_PlayerAlly::RespondedTo( const char *ResponseConcept, bool bForce, bool bCancelScene )
+{
+ if ( bForce )
+ {
+ // We're being forced to respond to the event, probably because it's the
+ // player dying or something equally important.
+ AI_Response *result = SpeakFindResponse( ResponseConcept, NULL );
+ if ( result )
+ {
+ // We've got something to say. Stop any scenes we're in, and speak the response.
+ if ( bCancelScene )
+ RemoveActorFromScriptedScenes( this, false );
+
+ bool spoke = SpeakDispatchResponse( ResponseConcept, result );
+ return spoke;
+ }
+
+ return false;
+ }
+
+ if ( SpeakIfAllowed( ResponseConcept, NULL, true ) )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Schedules
+//
+//-----------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC(talk_monster_base,CAI_PlayerAlly)
+
+ DECLARE_TASK(TASK_TALKER_SPEAK_PENDING)
+
+ DECLARE_CONDITION(COND_TALKER_CLIENTUNSEEN)
+ DECLARE_CONDITION(COND_TALKER_PLAYER_DEAD)
+ DECLARE_CONDITION(COND_TALKER_PLAYER_STARING)
+
+ //=========================================================
+ // > SCHED_TALKER_SPEAK_PENDING_IDLE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_TALKER_SPEAK_PENDING_IDLE,
+
+ " Tasks"
+ " TASK_TALKER_SPEAK_PENDING 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_FOR_SPEAK_FINISH 0"
+ " TASK_WAIT_RANDOM 0.5"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ " COND_PLAYER_PUSHING"
+ " COND_GIVE_WAY"
+ )
+
+ //=========================================================
+ // > SCHED_TALKER_SPEAK_PENDING_ALERT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_TALKER_SPEAK_PENDING_ALERT,
+
+ " Tasks"
+ " TASK_TALKER_SPEAK_PENDING 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_FOR_SPEAK_FINISH 0"
+ " TASK_WAIT_RANDOM 0.5"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_PLAYER_PUSHING"
+ " COND_GIVE_WAY"
+ )
+
+ //=========================================================
+ // > SCHED_TALKER_SPEAK_PENDING_COMBAT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_TALKER_SPEAK_PENDING_COMBAT,
+
+ " Tasks"
+ " TASK_TALKER_SPEAK_PENDING 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_FOR_SPEAK_FINISH 0"
+ ""
+ " Interrupts"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ )
+
+AI_END_CUSTOM_NPC()
+
+//-----------------------------------------------------------------------------