//========= Copyright © 1996-2005, 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 ( size_t 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 ( size_t 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 )
{
	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 );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
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 = (int)floor( 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()

//-----------------------------------------------------------------------------
