diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/ai_playerally.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-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.cpp | 3592 |
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() + +//----------------------------------------------------------------------------- |