//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Implementation of entities that cause NPCs to participate in
//			scripted events. These entities find and temporarily possess NPCs
//			within a given search radius.
//
//			Multiple scripts with the same targetname will start frame-synchronized.
//
//			Scripts will find available NPCs by name or class name and grab them
//			to play the script. If the NPC is already playing a script, the
//			new script may enqueue itself unless there is already a non critical
//			script in the queue.
//
//=============================================================================//

#include "cbase.h"
#include "ai_schedule.h"
#include "ai_default.h"
#include "ai_motor.h"
#include "ai_hint.h"
#include "ai_networkmanager.h"
#include "ai_network.h"
#include "engine/IEngineSound.h"
#include "animation.h"
#include "scripted.h"
#include "entitylist.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//
// targetname "me" - there can be more than one with the same name, and they act in concert
// target "the_entity_I_want_to_start_playing" or "class entity_classname" will pick the closest inactive scientist
// play "name_of_sequence"
// idle "name of idle sequence to play before starting"
// moveto - if set the NPC first moves to this nodes position
// range # - only search this far to find the target
// spawnflags - (stop if blocked, stop if player seen)
//

BEGIN_DATADESC( CAI_ScriptedSequence )

	DEFINE_KEYFIELD( m_iszPreIdle, FIELD_STRING, "m_iszIdle" ),
	DEFINE_KEYFIELD( m_iszPlay, FIELD_STRING, "m_iszPlay" ),
	DEFINE_KEYFIELD( m_iszPostIdle, FIELD_STRING, "m_iszPostIdle" ),
	DEFINE_KEYFIELD( m_iszCustomMove, FIELD_STRING, "m_iszCustomMove" ),
	DEFINE_KEYFIELD( m_iszNextScript, FIELD_STRING, "m_iszNextScript" ),
	DEFINE_KEYFIELD( m_iszEntity, FIELD_STRING, "m_iszEntity" ),
	DEFINE_KEYFIELD( m_fMoveTo, FIELD_INTEGER, "m_fMoveTo" ),
	DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "m_flRadius" ),
	DEFINE_KEYFIELD( m_flRepeat, FIELD_FLOAT, "m_flRepeat" ),

	DEFINE_FIELD( m_iDelay, FIELD_INTEGER ),
	DEFINE_FIELD( m_startTime, FIELD_TIME ),
	DEFINE_FIELD( m_bWaitForBeginSequence, FIELD_BOOLEAN ),

	DEFINE_FIELD( m_saved_effects, FIELD_INTEGER ),
	DEFINE_FIELD( m_savedFlags, FIELD_INTEGER ),
	
	DEFINE_FIELD( m_interruptable, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_sequenceStarted, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_hTargetEnt, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hNextCine, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hLastFoundEntity, FIELD_EHANDLE ),
	
	DEFINE_FIELD( m_bThinking, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bInitiatedSelfDelete, FIELD_BOOLEAN ),

	// Function Pointers
	DEFINE_THINKFUNC( ScriptThink ),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "MoveToPosition", InputMoveToPosition ),
	DEFINE_INPUTFUNC( FIELD_VOID, "BeginSequence", InputBeginSequence ),
	DEFINE_INPUTFUNC( FIELD_VOID, "CancelSequence", InputCancelSequence ),

	// Outputs
	DEFINE_OUTPUT(m_OnBeginSequence, "OnBeginSequence"),
	DEFINE_OUTPUT(m_OnEndSequence, "OnEndSequence"),
	DEFINE_OUTPUT(m_OnScriptEvent[0], "OnScriptEvent01"),
	DEFINE_OUTPUT(m_OnScriptEvent[1], "OnScriptEvent02"),
	DEFINE_OUTPUT(m_OnScriptEvent[2], "OnScriptEvent03"),
	DEFINE_OUTPUT(m_OnScriptEvent[3], "OnScriptEvent04"),
	DEFINE_OUTPUT(m_OnScriptEvent[4], "OnScriptEvent05"),
	DEFINE_OUTPUT(m_OnScriptEvent[5], "OnScriptEvent06"),
	DEFINE_OUTPUT(m_OnScriptEvent[6], "OnScriptEvent07"),
	DEFINE_OUTPUT(m_OnScriptEvent[7], "OnScriptEvent08"),

END_DATADESC()


LINK_ENTITY_TO_CLASS( scripted_sequence, CAI_ScriptedSequence );
#define CLASSNAME "scripted_sequence"


//-----------------------------------------------------------------------------
// Purpose: Cancels the given scripted sequence.
// Input  : pentCine - 
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::ScriptEntityCancel( CBaseEntity *pentCine )
{
	// make sure they are a scripted_sequence
	if ( FClassnameIs( pentCine, CLASSNAME ) )
	{
		CAI_ScriptedSequence *pCineTarget = (CAI_ScriptedSequence *)pentCine;

		// make sure they have a NPC in mind for the script
		CBaseEntity *pEntity = pCineTarget->GetTarget();
		CAI_BaseNPC	*pTarget = NULL;
		if ( pEntity )
			pTarget = pEntity->MyNPCPointer();

		if (pTarget)
		{
			// make sure their NPC is actually playing a script
			if ( pTarget->m_NPCState == NPC_STATE_SCRIPT )
			{
				// tell them do die
				pTarget->m_scriptState = CAI_BaseNPC::SCRIPT_CLEANUP;

				// We have to save off the flags here, because the NPC's m_hCine is cleared in CineCleanup()
				int iSavedFlags = (pTarget->m_hCine ? pTarget->m_hCine->m_savedFlags : 0);

#ifdef HL1_DLL
				//if we didn't have FL_FLY before the script, remove it
				// for some reason hl2 doesn't have to do this *before* 
				// restoring the position ( which checks FL_FLY ) in CineCleanup
				// Let's not risk breaking anything at this stage and just remove it.
				pCineTarget->FixFlyFlag( pTarget, iSavedFlags );
#endif
				// do it now				
				pTarget->CineCleanup( );
				pCineTarget->FixScriptNPCSchedule( pTarget, iSavedFlags );
			}
			else
			{
				// Robin HACK: If a script is started and then cancelled before an NPC gets to
				//		 think, we have to manually clear it out of scripted state, or it'll never recover.
				pCineTarget->SetTarget( NULL );
				pTarget->SetEffects( pCineTarget->m_saved_effects );
				pTarget->m_hCine = NULL;
				pTarget->SetTarget( NULL );
				pTarget->SetGoalEnt( NULL );
				pTarget->SetIdealState( NPC_STATE_IDLE );
			}
		}

		// FIXME: this needs to be done in a cine cleanup function
		pCineTarget->m_iDelay = 0;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Called before spawning, after keyvalues have been parsed.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::Spawn( void )
{
	SetSolid( SOLID_NONE );

	//
	// If we have no name or we are set to start immediately, find the NPC and
	// have them move to their script position now.
	//
	if ( !GetEntityName() || ( m_spawnflags & SF_SCRIPT_START_ON_SPAWN ) )
	{
		StartThink();
		SetNextThink( gpGlobals->curtime + 1.0f );

		//
		// If we have a name, wait for a BeginSequence input to play the
		// action animation. Otherwise, we'll play the action animation
		// as soon as the NPC reaches the script position.
		//
		if ( GetEntityName() != NULL_STRING )
		{
			m_bWaitForBeginSequence = true;
		}
	}

	if ( m_spawnflags & SF_SCRIPT_NOINTERRUPT )
	{
		m_interruptable = false;
	}
	else
	{
		m_interruptable = true;
	}

	m_sequenceStarted = false;
	m_startTime = 0;
	m_hNextCine = NULL;

	m_hLastFoundEntity = NULL;
}

//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::UpdateOnRemove(void)
{
	ScriptEntityCancel( this );
	BaseClass::UpdateOnRemove();
}

//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::StartThink()
{
	m_sequenceStarted = false;
	m_bThinking = true;
	SetThink( &CAI_ScriptedSequence::ScriptThink );
}

//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::StopThink()
{
	if ( m_bThinking )
	{
		Assert( !m_bInitiatedSelfDelete );
		SetThink( NULL);
		m_bThinking = false;
	}
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if this scripted sequence can possess entities
//			regardless of state.
//-----------------------------------------------------------------------------
bool CAI_ScriptedSequence::FCanOverrideState( void )
{
	if ( m_spawnflags & SF_SCRIPT_OVERRIDESTATE )
		return true;
	return false;
}


//-----------------------------------------------------------------------------
// Purpose: Fires a script event by number.
// Input  : nEvent - One based index of the script event from the , from 1 to 8.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::FireScriptEvent( int nEvent )
{
	if ( ( nEvent >= 1 ) && ( nEvent <= MAX_SCRIPT_EVENTS ) )
	{
		m_OnScriptEvent[nEvent - 1].FireOutput( this, this );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Input handler that causes the NPC to move to the script position.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::InputMoveToPosition( inputdata_t &inputdata )
{
	if ( m_bInitiatedSelfDelete )
		return;
		
	// Have I already grabbed an NPC?
	CBaseEntity *pEntity = GetTarget();
	CAI_BaseNPC	*pTarget = NULL;

	if ( pEntity )
	{
		pTarget = pEntity->MyNPCPointer();
	}

	if ( pTarget != NULL ) 
	{
		// Yes, are they already playing this script?
		if ( pTarget->m_scriptState == CAI_BaseNPC::SCRIPT_PLAYING || pTarget->m_scriptState == CAI_BaseNPC::SCRIPT_POST_IDLE )
		{
			// Yes, see if we can enqueue ourselves.
			if ( pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_BY_NAME ) )
			{
				StartScript();
				m_bWaitForBeginSequence = true;
			}
		}

		// No, presumably they are moving to position or waiting for the BeginSequence input.
	}
	else
	{
		// No, grab the NPC but make them wait until BeginSequence is fired. They'll play
		// their pre-action idle animation until BeginSequence is fired.
		StartThink();
		SetNextThink( gpGlobals->curtime );
		m_bWaitForBeginSequence = true;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Input handler that activates the scripted sequence.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::InputBeginSequence( inputdata_t &inputdata )
{
	if ( m_bInitiatedSelfDelete )
		return;

	// Start the script as soon as possible.
	m_bWaitForBeginSequence = false;
		
	// do I already know who I should use?
	CBaseEntity *pEntity = GetTarget();
	CAI_BaseNPC	*pTarget = NULL;

	if ( pEntity )
	{
		pTarget = pEntity->MyNPCPointer();
	}

	if ( pTarget ) 
	{
		// Are they already playing a script?
		if ( pTarget->m_scriptState == CAI_BaseNPC::SCRIPT_PLAYING || pTarget->m_scriptState == CAI_BaseNPC::SCRIPT_POST_IDLE )
		{
			// See if we can enqueue ourselves after the current script.
			if ( pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_BY_NAME ) )
			{
				StartScript();
			}
		}
	}
	else
	{
		// if not, try finding them
		StartThink();
		SetNextThink( gpGlobals->curtime );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Input handler that activates the scripted sequence.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::InputCancelSequence( inputdata_t &inputdata )
{
	if ( m_bInitiatedSelfDelete )
		return;

	//
	// We don't call CancelScript because entity I/O will handle dispatching
	// this input to all other scripts with our same name.
	//
	DevMsg( 2,  "InputCancelScript: Cancelling script '%s'\n", STRING( m_iszPlay ));
	StopThink();
	ScriptEntityCancel( this );
}


//-----------------------------------------------------------------------------
// Purpose: Returns true if it is time for this script to start, false if the
//			NPC should continue waiting.
//
//			Scripts wait for two reasons:
//
//			1. To frame-syncronize with other scripts of the same name.
//			2. To wait indefinitely for the BeginSequence input after the NPC
//				moves to the script position.
//-----------------------------------------------------------------------------
bool CAI_ScriptedSequence::IsTimeToStart( void )
{
	Assert( !m_bWaitForBeginSequence );
	return ( m_iDelay == 0 );
}


//-----------------------------------------------------------------------------
// Purpose: Returns true if the script is still waiting to call StartScript()
//-----------------------------------------------------------------------------
bool CAI_ScriptedSequence::IsWaitingForBegin( void )
{
	return m_bWaitForBeginSequence;
}

//-----------------------------------------------------------------------------
// Purpose: This doesn't really make sense since only MOVETYPE_PUSH get 'Blocked' events
// Input  : pOther - The entity blocking us.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::Blocked( CBaseEntity *pOther )
{
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pOther - The entity touching us.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::Touch( CBaseEntity *pOther )
{
/*
	DevMsg( 2,  "Cine Touch\n" );
	if (m_pentTarget && OFFSET(pOther->pev) == OFFSET(m_pentTarget))
	{
		CAI_BaseNPC *pTarget = GetClassPtr((CAI_BaseNPC *)VARS(m_pentTarget));
		pTarget->m_NPCState == NPC_STATE_SCRIPT;
	}
*/
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::Die( void )
{
	SetThink( &CAI_ScriptedSequence::SUB_Remove );
	m_bThinking = false;
	m_bInitiatedSelfDelete = true;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::Pain( void )
{
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : eMode - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
CAI_BaseNPC *CAI_ScriptedSequence::FindScriptEntity( )
{
	CAI_BaseNPC *pEnqueueNPC = NULL;

	int interrupt = SS_INTERRUPT_BY_NAME;
	CBaseEntity *pEntity = gEntList.FindEntityByNameWithin( m_hLastFoundEntity, STRING( m_iszEntity ), GetAbsOrigin(), m_flRadius, this );
	if (!pEntity)
	{
		pEntity = gEntList.FindEntityByClassnameWithin( m_hLastFoundEntity, STRING( m_iszEntity ), GetAbsOrigin(), m_flRadius );
		interrupt = SS_INTERRUPT_BY_CLASS;
	}

	while ( pEntity != NULL )
	{
		CAI_BaseNPC *pNPC = pEntity->MyNPCPointer( );
		if ( pNPC )
		{
			//
			// If they can play the sequence...
			//
			CanPlaySequence_t eCanPlay = pNPC->CanPlaySequence( FCanOverrideState(), interrupt );
			if ( eCanPlay == CAN_PLAY_NOW )
			{
				// If they can play it now, we're done!
				return pNPC;
			}
			else if ( eCanPlay == CAN_PLAY_ENQUEUED )
			{
				// They can play it, but only enqueued. We'll use them as a last resort.
				pEnqueueNPC = pNPC;
			}
			else if (!(m_spawnflags & SF_SCRIPT_NO_COMPLAINTS))
			{
				// They cannot play the script.
				DevMsg( "Found %s, but can't play!\n", STRING( m_iszEntity ));
			}
		}

		if ( interrupt == SS_INTERRUPT_BY_NAME )
			pEntity = gEntList.FindEntityByNameWithin( pEntity, STRING( m_iszEntity ), GetAbsOrigin(), m_flRadius, this );
		else
			pEntity = gEntList.FindEntityByClassnameWithin( pEntity, STRING( m_iszEntity ), GetAbsOrigin(), m_flRadius );
	}

	//
	// If we found an NPC that will enqueue the script, use them.
	//
	if ( pEnqueueNPC != NULL )
	{
		return pEnqueueNPC;
	}

	return NULL;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_ScriptedSequence::FindEntity( void )
{
	CAI_BaseNPC *pTarget = FindScriptEntity( );

	if ( (m_spawnflags & SF_SCRIPT_SEARCH_CYCLICALLY))
	{
		// next time this is called, start searching from the one found last time
		m_hLastFoundEntity = pTarget;
	}

	SetTarget( pTarget );

	return pTarget != NULL;
}


//-----------------------------------------------------------------------------
// Purpose: Make the entity enter a scripted sequence.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::StartScript( void )
{
	CBaseEntity		*pEntity = GetTarget();
	CAI_BaseNPC	*pTarget = NULL;
	if ( pEntity )
		pTarget = pEntity->MyNPCPointer();

	if ( pTarget )
	{
		//
		// If the NPC is in another script, just enqueue ourselves and bail out.
		// We'll possess the NPC when the current script finishes with the NPC.
		// Note that we only enqueue one deep currently, so if there is someone
		// else in line we'll stomp them.
		//
		if ( pTarget->m_hCine != NULL )
		{
			if ( pTarget->m_hCine->m_hNextCine != NULL )
			{
				//
				// Kicking another script out of the queue.
				//
				CAI_ScriptedSequence *pCine = ( CAI_ScriptedSequence * )pTarget->m_hCine->m_hNextCine.Get();

				if (pTarget->m_hCine->m_hNextCine != pTarget->m_hCine)
				{
					// Don't clear the currently playing script's target!
					pCine->SetTarget( NULL );
				}
				DevMsg( 2, "script \"%s\" kicking script \"%s\" out of the queue\n", GetDebugName(), pCine->GetDebugName() );
			}

			pTarget->m_hCine->m_hNextCine = this;
			return;
		}

		//
		// If no next script is specified, clear it out so other scripts can enqueue themselves
		// after us.
		//
		if ( !m_iszNextScript )
		{
			m_hNextCine = NULL;
		}

		// UNDONE: Do this to sync up multi-entity scripts?
		//pTarget->SetNextThink( gpGlobals->curtime );

		pTarget->SetGoalEnt( this );
		pTarget->ForceDecisionThink();
		pTarget->m_hCine = this;
		pTarget->SetTarget( this );

		m_saved_effects = pTarget->GetEffects();
		pTarget->AddEffects( GetEffects() );
		m_savedFlags = pTarget->GetFlags();

		switch (m_fMoveTo)
		{
		case CINE_MOVETO_WAIT: 
		case CINE_MOVETO_WAIT_FACING:
			pTarget->m_scriptState = CAI_BaseNPC::SCRIPT_WAIT; 
			break;

		case CINE_MOVETO_WALK:
			pTarget->m_scriptState = CAI_BaseNPC::SCRIPT_WALK_TO_MARK;
			break;

		case CINE_MOVETO_RUN: 
			pTarget->m_scriptState = CAI_BaseNPC::SCRIPT_RUN_TO_MARK; 
			break;

		case CINE_MOVETO_CUSTOM:
			pTarget->m_scriptState = CAI_BaseNPC::SCRIPT_CUSTOM_MOVE_TO_MARK; 
			break;

		case CINE_MOVETO_TELEPORT: 
			pTarget->Teleport( &GetLocalOrigin(), NULL, &vec3_origin );
			pTarget->GetMotor()->SetIdealYaw( GetLocalAngles().y );
			pTarget->SetLocalAngularVelocity( vec3_angle );
			pTarget->AddEffects( EF_NOINTERP );
			QAngle angles = pTarget->GetLocalAngles();
			angles.y = GetLocalAngles().y;
			pTarget->SetLocalAngles( angles );
			pTarget->m_scriptState = CAI_BaseNPC::SCRIPT_WAIT;
			// UNDONE: Add a flag to do this so people can fixup physics after teleporting NPCs
			//pTarget->SetGroundEntity( NULL );
			break;
		}
		//DevMsg( 2,  "\"%s\" found and used (INT: %s)\n", STRING( pTarget->m_iName ), FBitSet(m_spawnflags, SF_SCRIPT_NOINTERRUPT)?"No":"Yes" );

		// Wait until all scripts of the same name are ready to play.
		DelayStart( true ); 

		pTarget->SetIdealState(NPC_STATE_SCRIPT);
	}
}


//-----------------------------------------------------------------------------
// Purpose: First think after activation. Grabs an NPC and makes it do things.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::ScriptThink( void )
{
	if ( g_pAINetworkManager && !g_pAINetworkManager->IsInitialized() )
	{
		SetNextThink( gpGlobals->curtime + 1.0f );
	}
	else if (FindEntity())
	{
		StartScript( );
		DevMsg( 2,  "scripted_sequence \"%s\" using NPC \"%s\"(%s)\n", GetDebugName(), STRING( m_iszEntity ), GetTarget()->GetEntityName() );
	}
	else
	{
		CancelScript( );
		DevMsg( 2,  "scripted_sequence \"%s\" can't find NPC \"%s\"\n", GetDebugName(), STRING( m_iszEntity ) );
		// FIXME: just trying again is bad.  This should fire an output instead.
		// FIXME: Think about puting output triggers in both StartScript() and CancelScript().
		SetNextThink( gpGlobals->curtime + 1.0f );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Callback for firing the begin sequence output. Called by the NPC that
//			is running the script as it starts the action seqeunce.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::OnBeginSequence( void )
{
	m_OnBeginSequence.FireOutput( this, this );
}


//-----------------------------------------------------------------------------
// Purpose: Look up a sequence name and setup the target NPC to play it.
// Input  : pTarget - 
//			iszSeq - 
//			completeOnEmpty - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_ScriptedSequence::StartSequence( CAI_BaseNPC *pTarget, string_t iszSeq, bool completeOnEmpty )
{
	m_sequenceStarted = true;

	if ( !iszSeq && completeOnEmpty )
	{
		SequenceDone( pTarget );
		return false;
	}

	int nSequence = pTarget->LookupSequence( STRING( iszSeq ) );
	if (nSequence == -1)
	{
		Warning( "%s: unknown scripted sequence \"%s\"\n", pTarget->GetDebugName(), STRING( iszSeq ));
		nSequence = 0;
	}

	// look for the activity that this represents
	Activity act = pTarget->GetSequenceActivity( nSequence );
	if (act == ACT_INVALID)
		act = ACT_IDLE;

	pTarget->SetActivityAndSequence( act, nSequence, act, act );

	//DevMsg( 2, "%s (%s): started \"%s\":INT:%s\n", STRING( pTarget->m_iName ), pTarget->GetClassname(), STRING( iszSeq), (m_spawnflags & SF_SCRIPT_NOINTERRUPT) ? "No" : "Yes" );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Called when a scripted sequence is ready to start playing the sequence
// Input  : pNPC - Pointer to the NPC that the sequence possesses.
//-----------------------------------------------------------------------------

void CAI_ScriptedSequence::SynchronizeSequence( CAI_BaseNPC *pNPC )
{
	Assert( m_iDelay == 0 );
	Assert( m_bWaitForBeginSequence == false );

	// Reset cycle position
	float flCycleRate = pNPC->GetSequenceCycleRate( pNPC->GetSequence() );
	float flInterval = gpGlobals->curtime - m_startTime;

	// Msg("%.2f \"%s\"  %s : %f (%f): interval %f\n", gpGlobals->curtime, GetEntityName(), pNPC->GetClassname(), pNPC->m_flAnimTime.Get(), m_startTime, flInterval );
	//Assert( flInterval >= 0.0 && flInterval <= 0.15 );
	flInterval = clamp( flInterval, 0, 0.15 );

	if (flInterval == 0)
		return;

	pNPC->SetCycle( flInterval * flCycleRate );
	pNPC->AutoMovement( flInterval );
}


//-----------------------------------------------------------------------------
// Purpose: Called when a scripted sequence animation sequence is done playing
//			(or when an AI Scripted Sequence doesn't supply an animation sequence
//			to play).
// Input  : pNPC - Pointer to the NPC that the sequence possesses.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::SequenceDone( CAI_BaseNPC *pNPC )
{
	//DevMsg( 2, "Sequence %s finished\n", STRING( pNPC->m_hCine->m_iszPlay ) );

	//
	// If we have a post idle set, and no other script is in the queue for this
	// NPC, start playing the idle now.
	//
	if ( ( m_iszPostIdle != NULL_STRING ) && ( m_hNextCine == NULL ) )
	{
		//
		// First time we've gotten here for this script. Start playing the post idle
		// if one is specified.
		//
		pNPC->m_scriptState = CAI_BaseNPC::SCRIPT_POST_IDLE; 
		StartSequence( pNPC, m_iszPostIdle, false ); // false to prevent recursion here!
	}
	else
	{
		PostIdleDone( pNPC );
	}

	m_OnEndSequence.FireOutput(NULL, this);
}


//-----------------------------------------------------------------------------
// Purpose: Called when a scripted sequence animation sequence is done playing
//			(or when an AI Scripted Sequence doesn't supply an animation sequence
//			to play).
// Input  : pNPC - Pointer to the NPC that the sequence possesses.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::PostIdleDone( CAI_BaseNPC *pNPC )
{
	//
	// If we're set to keep the NPC in a scripted idle, hack them back into the script,
	// but allow another scripted sequence to take control of the NPC if it wants to,
	// unless another script has stolen them from us.
	//
	if ( ( m_iszPostIdle != NULL_STRING ) && ( m_spawnflags & SF_SCRIPT_LOOP_IN_POST_IDLE ) && ( m_hNextCine == NULL ) )
	{
		//
		// First time we've gotten here for this script. Start playing the post idle
		// if one is specified.
		// Only do so if we're selected, to prevent spam
		if ( pNPC->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
		{
			DevMsg( 2, "Post Idle %s finished for %s\n", STRING( pNPC->m_hCine->m_iszPostIdle ), pNPC->GetDebugName() );
		}

		pNPC->m_scriptState = CAI_BaseNPC::SCRIPT_POST_IDLE; 
		StartSequence( pNPC, m_iszPostIdle, false );
	}
	else
	{
		if ( !( m_spawnflags & SF_SCRIPT_REPEATABLE ) )
		{
			SetThink( &CAI_ScriptedSequence::SUB_Remove );
			SetNextThink( gpGlobals->curtime + 0.1f );
			m_bThinking = false;
			m_bInitiatedSelfDelete = true;
		}

		//
		// This is done so that another sequence can take over the NPC when triggered
		// by the first.
		//
		pNPC->CineCleanup();

		// We have to pass in the flags here, because the NPC's m_hCine was cleared in CineCleanup()
		FixScriptNPCSchedule( pNPC, m_savedFlags );

		//
		// If another script is waiting to grab this NPC, start the next script
		// immediately.
		//
		if ( m_hNextCine != NULL )
		{
			CAI_ScriptedSequence *pNextCine = ( CAI_ScriptedSequence * )m_hNextCine.Get();

			//
			// Don't link ourselves in if we are going to be deleted.
			// TODO: use EHANDLEs instead of pointers to scripts!
			//
			if ( ( pNextCine != this ) || ( m_spawnflags & SF_SCRIPT_REPEATABLE ) )
			{
				pNextCine->SetTarget( pNPC );
				pNextCine->StartScript();
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: When a NPC finishes a scripted sequence, we have to fix up its state
//			and schedule for it to return to a normal AI NPC.
//			Scripted sequences just dirty the Schedule and drop the NPC in Idle State.
// Input  : *pNPC - 
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::FixScriptNPCSchedule( CAI_BaseNPC *pNPC, int iSavedCineFlags )
{
	if ( pNPC->GetIdealState() != NPC_STATE_DEAD )
	{
		pNPC->SetIdealState( NPC_STATE_IDLE );
	}

	if ( pNPC == NULL )
		 return;

	FixFlyFlag( pNPC, iSavedCineFlags );

	pNPC->ClearSchedule();
}

void CAI_ScriptedSequence::FixFlyFlag( CAI_BaseNPC *pNPC, int iSavedCineFlags )
{
	//Adrian: We NEED to clear this or the NPC's FL_FLY flag will never be removed cause of ClearSchedule!
	if ( pNPC->GetTask() && pNPC->GetTask()->iTask == TASK_PLAY_SCRIPT )
	{
		if ( !(iSavedCineFlags & FL_FLY) )
		{
			if ( pNPC->HasMovement( pNPC->GetSequence() ))
			{
				 pNPC->RemoveFlag( FL_FLY );
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : fAllow - 
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::AllowInterrupt( bool fAllow )
{
	if ( m_spawnflags & SF_SCRIPT_NOINTERRUPT )
		return;
	m_interruptable = fAllow;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_ScriptedSequence::CanInterrupt( void )
{
	if ( !m_interruptable )
		return false;

	CBaseEntity *pTarget = GetTarget();

	if ( pTarget != NULL && pTarget->IsAlive() )
		return true;

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::RemoveIgnoredConditions( void )
{
	if ( CanInterrupt() ) 
	{
		return;
	}

	CBaseEntity *pEntity = GetTarget();
	if ( pEntity == NULL )
	{
		return;
	}

	CAI_BaseNPC	*pTarget = pEntity->MyNPCPointer();
	if ( pTarget == NULL )
	{
		return;
	}


	if ( pTarget )
	{
		pTarget->ClearCondition(COND_LIGHT_DAMAGE);
		pTarget->ClearCondition(COND_HEAVY_DAMAGE);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Returns true if another script is allowed to enqueue itself after
//			this script. The enqueued script will begin immediately after the
//			current script without dropping the NPC into AI.
//-----------------------------------------------------------------------------
bool CAI_ScriptedSequence::CanEnqueueAfter( void )
{
	if ( m_hNextCine == NULL )
	{
		return true;
	}

	if ( m_iszNextScript != NULL_STRING )
	{
		DevMsg( 2, "%s is specified as the 'Next Script' and cannot be kicked out of the queue\n",  m_hNextCine->GetDebugName() );
		return false;
	}

	if ( !m_hNextCine->HasSpawnFlags( SF_SCRIPT_HIGH_PRIORITY ) )
	{
		return true;
	}

	DevMsg( 2, "%s is a priority script and cannot be kicked out of the queue\n",  m_hNextCine->GetDebugName() );

	return false;	
}


//-----------------------------------------------------------------------------
// Purpose: Find all the cinematic entities with my targetname and stop them
//			from playing.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::CancelScript( void )
{
	DevMsg( 2,  "Cancelling script: %s\n", STRING( m_iszPlay ));
	
	if ( !GetEntityName() )
	{
		ScriptEntityCancel( this );
		return;
	}

	CBaseEntity *pentCineTarget = gEntList.FindEntityByName( NULL, GetEntityName(), NULL );

	while ( pentCineTarget )
	{
		ScriptEntityCancel( pentCineTarget );
		pentCineTarget = gEntList.FindEntityByName( pentCineTarget, GetEntityName(), NULL );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Find all the cinematic entities with my targetname and tell them to
//			wait before starting. This ensures that all scripted sequences with
//			the same name are frame-synchronized.
//
//			When triggered, scripts call this first with a state of 1 to indicate that
//			they are not ready to play (while NPCs move to their cue positions, etc).
//			Once they are ready to play, they call it with a state of 0. When all
//			the scripts are ready, they all are told to start.
//
// Input  : bDelay - true means this script is not ready, false means it is ready.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::DelayStart( bool bDelay )
{
	// Msg("%.2f \"%s\" DelayStart( %d )\n", gpGlobals->curtime, GetEntityName(), bDelay );

	// Without a name, we cannot synchronize with anything else
	if ( GetEntityName() == NULL_STRING )
	{
		m_iDelay = bDelay;
		m_startTime = gpGlobals->curtime;
		return;
	}

	CBaseEntity *pentCine = gEntList.FindEntityByName( NULL, GetEntityName(), NULL );

	while ( pentCine )
	{
		if ( FClassnameIs( pentCine, "scripted_sequence" ) )
		{
			CAI_ScriptedSequence *pTarget = (CAI_ScriptedSequence *)pentCine;
			if (bDelay)
			{
				// if delaying, add up the number of other scripts in the group
				m_iDelay++;
			}
			else
			{
				// if ready, decrement each of other scripts in the group
				// members not yet delayed will decrement below zero.
				pTarget->m_iDelay--;
				// once everything is balanced, everyone will start.
				if (pTarget->m_iDelay == 0)
				{
					pTarget->m_startTime = gpGlobals->curtime;
				}
			}
		}
		pentCine = gEntList.FindEntityByName( pentCine, GetEntityName(), NULL );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Find an entity that I'm interested in and precache the sounds he'll
//			need in the sequence.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence::Activate( void )
{
	BaseClass::Activate();

	CBaseEntity *pTarget = NULL;
	CAI_BaseNPC	*pNPC = NULL;

	while ((pTarget = gEntList.FindEntityGeneric( pTarget, STRING( m_iszEntity ), NULL )) != NULL)
	{
		pNPC = pTarget->MyNPCPointer();
		if (pNPC)
		{
			break;
		}
	}
	
	//
	// See if there is another script specified to run immediately after this one.
	//
	m_hNextCine = gEntList.FindEntityByName( NULL, m_iszNextScript, NULL );
	if ( m_hNextCine == NULL )
	{
		m_iszNextScript = NULL_STRING;
	}
}
		

//-----------------------------------------------------------------------------
// Purpose: Modifies an NPC's AI state without taking it out of its AI.
//-----------------------------------------------------------------------------

class CAI_ScriptedSchedule : public CBaseEntity
{
	DECLARE_CLASS( CAI_ScriptedSchedule, CBaseEntity );
private:

	void StartSchedule( CAI_BaseNPC *pTarget );
	void ScriptThink( void );

	// Input handlers
	void InputStartSchedule( inputdata_t &inputdata );

	CAI_BaseNPC *FindScriptEntity(  bool bCyclic );

	//---------------------------------
	
	enum Schedule_t
	{
		SCHED_SCRIPT_NONE = 0,
		SCHED_SCRIPT_WALK_TO_GOAL,
		SCHED_SCRIPT_RUN_TO_GOAL,
		SCHED_SCRIPT_ENEMY_IS_GOAL,
		SCHED_SCRIPT_WALK_PATH_GOAL,
		SCHED_SCRIPT_RUN_PATH_GOAL,
		SCHED_SCRIPT_ENEMY_IS_GOAL_AND_RUN_TO_GOAL,
	};

	enum Interruptability_t
	{
		GENERAL_INTERRUPTABILITY,
		DAMAGEORDEATH_INTERRUPTABILITY,
		DEATH_INTERRUPTABILITY
	};
	
	//---------------------------------

	EHANDLE 	m_hLastFoundEntity;
	string_t 	m_iszEntity;		// Entity that is wanted for this script
	float 		m_flRadius;			// Range to search for an NPC to possess.

	string_t 	m_sGoalEnt;
	Schedule_t	m_nSchedule;
	int 		m_nForceState;
	
	bool		m_bGrabAll;

	Interruptability_t m_Interruptability;

	bool		m_bDidFireOnce;
	
	//---------------------------------

	DECLARE_DATADESC();

};

BEGIN_DATADESC( CAI_ScriptedSchedule )

	DEFINE_FIELD( m_hLastFoundEntity, FIELD_EHANDLE ),
	DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "m_flRadius" ),
	
	DEFINE_KEYFIELD( m_iszEntity, FIELD_STRING, "m_iszEntity" ),
	DEFINE_KEYFIELD( m_nSchedule, FIELD_INTEGER, "schedule" ),
	DEFINE_KEYFIELD( m_nForceState, FIELD_INTEGER, "forcestate" ),
	DEFINE_KEYFIELD( m_sGoalEnt, FIELD_STRING, "goalent" ),
	DEFINE_KEYFIELD( m_bGrabAll, FIELD_BOOLEAN, "graball" ),
	DEFINE_KEYFIELD( m_Interruptability, FIELD_INTEGER, "interruptability"),

	DEFINE_FIELD( m_bDidFireOnce, FIELD_BOOLEAN ),

	DEFINE_THINKFUNC( ScriptThink ),

	DEFINE_INPUTFUNC( FIELD_VOID, "StartSchedule", InputStartSchedule ),

END_DATADESC()


LINK_ENTITY_TO_CLASS( aiscripted_schedule, CAI_ScriptedSchedule );

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_ScriptedSchedule::ScriptThink( void )
{
	bool success = false;
	CAI_BaseNPC *pTarget;
	
	if ( !m_bGrabAll )
	{
		pTarget = FindScriptEntity( (m_spawnflags & SF_SCRIPT_SEARCH_CYCLICALLY) != 0 );
		if ( pTarget )
		{
			DevMsg( 2,  "scripted_schedule \"%s\" using NPC \"%s\"(%s)\n", GetDebugName(), STRING( m_iszEntity ), pTarget->GetEntityName() );
			StartSchedule( pTarget );
			success = true;
		}
	}
	else
	{
		m_hLastFoundEntity = NULL;
		while ( ( pTarget = FindScriptEntity( true ) ) != NULL )
		{
			DevMsg( 2,  "scripted_schedule \"%s\" using NPC \"%s\"(%s)\n", GetDebugName(), pTarget->GetEntityName(), STRING( m_iszEntity ) );
			StartSchedule( pTarget );
			success = true;
		}
	}
	
	if ( !success )
	{
		DevMsg( 2,  "scripted_schedule \"%s\" can't find NPC \"%s\"\n", GetDebugName(), STRING( m_iszEntity ) );
		// FIXME: just trying again is bad.  This should fire an output instead.
		// FIXME: Think about puting output triggers on success true and sucess false
		// FIXME: also needs to check the result of StartSchedule(), which can fail and not complain
		SetNextThink( gpGlobals->curtime + 1.0f );
	}
	else
	{
		m_bDidFireOnce = true;
	}
}
	
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CAI_BaseNPC *CAI_ScriptedSchedule::FindScriptEntity( bool bCyclic )
{
	CBaseEntity *pEntity = gEntList.FindEntityGenericWithin( m_hLastFoundEntity, STRING( m_iszEntity ), GetAbsOrigin(), m_flRadius, this, NULL );

	while ( pEntity != NULL )
	{
		CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
		if ( pNPC && pNPC->IsAlive() && pNPC->IsInterruptable())
		{
			if ( bCyclic )
			{
				// next time this is called, start searching from the one found last time
				m_hLastFoundEntity = pNPC;
			}

			return pNPC;
		}

		pEntity = gEntList.FindEntityGenericWithin( pEntity, STRING( m_iszEntity ), GetAbsOrigin(), m_flRadius, this, NULL );
	}

	m_hLastFoundEntity = NULL;
	return NULL;
}


//-----------------------------------------------------------------------------
// Purpose: Make the entity carry out the scripted instructions, but without
//			destroying the NPC's state.
//-----------------------------------------------------------------------------
void CAI_ScriptedSchedule::StartSchedule( CAI_BaseNPC *pTarget )
{
	if ( pTarget == NULL )
		return;

	CBaseEntity *pGoalEnt = gEntList.FindEntityGeneric( NULL, STRING( m_sGoalEnt ), this, NULL );

	// NOTE: !!! all possible choices require a goal ent currently
	if ( !pGoalEnt ) 
	{
		CHintCriteria hintCriteria;
		hintCriteria.SetGroup( m_sGoalEnt );
		hintCriteria.SetHintType( HINT_ANY );
		hintCriteria.AddIncludePosition( pTarget->GetAbsOrigin(), FLT_MAX );
		CAI_Hint *pHint = CAI_HintManager::FindHint( pTarget->GetAbsOrigin(), hintCriteria );
		if ( !pHint )
		{
			DevMsg( 1, "Can't find goal entity %s\nCan't execute script %s\n", STRING(m_sGoalEnt), GetDebugName() );
			return;
		}
		pGoalEnt = pHint;
	}
	
	static NPC_STATE forcedStatesMap[] = 
	{
		NPC_STATE_NONE,
		NPC_STATE_IDLE,
		NPC_STATE_ALERT,
		NPC_STATE_COMBAT
	};

	if ( pTarget->GetSleepState() > AISS_AWAKE )
		pTarget->Wake();
	
	pTarget->ForceDecisionThink();

	Assert( m_nForceState >= 0 && m_nForceState < ARRAYSIZE(forcedStatesMap) );
	
	NPC_STATE forcedState = forcedStatesMap[m_nForceState];

	// trap if this isn't a legal thing to do
	Assert( pTarget->IsInterruptable() );

	if ( forcedState != NPC_STATE_NONE )
		pTarget->SetState( forcedState );

	//
	// Set enemy and make the NPC aware of the enemy's current position.
	//
	if ( m_nSchedule == SCHED_SCRIPT_ENEMY_IS_GOAL || m_nSchedule == SCHED_SCRIPT_ENEMY_IS_GOAL_AND_RUN_TO_GOAL )
	{
		if ( pGoalEnt && pGoalEnt->MyCombatCharacterPointer() )
		{
			pTarget->SetEnemy( pGoalEnt );
			pTarget->UpdateEnemyMemory( pGoalEnt, pGoalEnt->GetAbsOrigin() );
			pTarget->SetCondition( COND_SCHEDULE_DONE );
		}
		else
			DevMsg( "Scripted schedule %s specified an invalid enemy %s\n", STRING( GetEntityName() ), STRING( m_sGoalEnt ) );
	}

	bool bDidSetSchedule = false;

	switch ( m_nSchedule )
	{
		//
		// Walk or run to position.
		//
		case SCHED_SCRIPT_WALK_TO_GOAL:
		case SCHED_SCRIPT_RUN_TO_GOAL:
		case SCHED_SCRIPT_ENEMY_IS_GOAL_AND_RUN_TO_GOAL:
		{
			Activity movementActivity = ( m_nSchedule == SCHED_SCRIPT_WALK_TO_GOAL ) ? ACT_WALK : ACT_RUN;
			bool bIsFlying = (pTarget->GetMoveType() == MOVETYPE_FLY) || (pTarget->GetMoveType() == MOVETYPE_FLYGRAVITY);
			if ( bIsFlying )
			{
				movementActivity = ACT_FLY;
			}

			if (!pTarget->ScheduledMoveToGoalEntity( SCHED_IDLE_WALK, pGoalEnt, movementActivity ))
			{
				if (!(m_spawnflags & SF_SCRIPT_NO_COMPLAINTS))
				{
					DevMsg( 1, "ScheduledMoveToGoalEntity to goal entity %s failed\nCan't execute script %s\n", STRING(m_sGoalEnt), GetDebugName() );
				}
				return;
			}
			bDidSetSchedule = true;

			break;
		}

		case SCHED_SCRIPT_WALK_PATH_GOAL:
		case SCHED_SCRIPT_RUN_PATH_GOAL:
		{
			Activity movementActivity = ( m_nSchedule == SCHED_SCRIPT_WALK_PATH_GOAL ) ? ACT_WALK : ACT_RUN;
			bool bIsFlying = (pTarget->GetMoveType() == MOVETYPE_FLY) || (pTarget->GetMoveType() == MOVETYPE_FLYGRAVITY);
			if ( bIsFlying )
			{
				movementActivity = ACT_FLY;
			}
			if (!pTarget->ScheduledFollowPath( SCHED_IDLE_WALK, pGoalEnt, movementActivity ))
			{
				if (!(m_spawnflags & SF_SCRIPT_NO_COMPLAINTS))
				{
					DevMsg( 1, "ScheduledFollowPath to goal entity %s failed\nCan't execute script %s\n", STRING(m_sGoalEnt), GetDebugName() );
				}
				return;
			}
			bDidSetSchedule = true;
			break;
		}
	}

	if ( bDidSetSchedule )
	{
		static int g_GeneralConditions[] = 
		{
			COND_CAN_MELEE_ATTACK1,
			COND_CAN_MELEE_ATTACK2,
			COND_CAN_RANGE_ATTACK1,
			COND_CAN_RANGE_ATTACK2,
			COND_ENEMY_DEAD,
			COND_HEAR_BULLET_IMPACT,
			COND_HEAR_COMBAT,
			COND_HEAR_DANGER,
			COND_NEW_ENEMY,
			COND_PROVOKED,
			COND_SEE_ENEMY,
			COND_SEE_FEAR,
			COND_SMELL,
		};

		static int g_DamageConditions[] = 
		{
			COND_HEAVY_DAMAGE,
			COND_LIGHT_DAMAGE,
			COND_RECEIVED_ORDERS,
		};

		pTarget->ClearIgnoreConditions( g_GeneralConditions, ARRAYSIZE(g_GeneralConditions) );
		pTarget->ClearIgnoreConditions( g_DamageConditions, ARRAYSIZE(g_DamageConditions) );

		if ( m_Interruptability > GENERAL_INTERRUPTABILITY )
			pTarget->SetIgnoreConditions( g_GeneralConditions, ARRAYSIZE(g_GeneralConditions) );

		if ( m_Interruptability > DAMAGEORDEATH_INTERRUPTABILITY )
			pTarget->SetIgnoreConditions( g_DamageConditions, ARRAYSIZE(g_DamageConditions) );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Input handler to activate the scripted schedule. Finds the NPC to
//			act on and sets a think for the near future to do the real work.
//-----------------------------------------------------------------------------
void CAI_ScriptedSchedule::InputStartSchedule( inputdata_t &inputdata )
{
	if (( m_nForceState == 0 ) && ( m_nSchedule == 0 ))
	{
		DevMsg( 2,  "aiscripted_schedule - no schedule or state has been set!\n" );
	}
	
	if ( !m_bDidFireOnce || ( m_spawnflags & SF_SCRIPT_REPEATABLE ) )
	{
		// DVS TODO: Is the NPC already playing the script?
		SetThink( &CAI_ScriptedSchedule::ScriptThink );
		SetNextThink( gpGlobals->curtime );
	}
	else
	{
		DevMsg( 2, "aiscripted_schedule - not playing schedule again: not flagged to repeat\n" );
	}
}


class CAI_ScriptedSentence : public CPointEntity
{
public:
	DECLARE_CLASS( CAI_ScriptedSentence, CPointEntity );

	void Spawn( void );
	bool KeyValue( const char *szKeyName, const char *szValue );
	void FindThink( void );
	void DelayThink( void );
	int	 ObjectCaps( void ) { return (BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); }

	// Input handlers
	void InputBeginSentence( inputdata_t &inputdata );

	DECLARE_DATADESC();

	CAI_BaseNPC *FindEntity( void );
	bool AcceptableSpeaker( CAI_BaseNPC *pNPC );
	int StartSentence( CAI_BaseNPC *pTarget );

private:
	string_t m_iszSentence;		// string index for sentence name
	string_t m_iszEntity;	// entity that is wanted for this sentence
	float	m_flRadius;		// range to search
	float	m_flDelay;	// How long the sentence lasts
	float	m_flRepeat;	// repeat rate
	soundlevel_t	m_iSoundLevel;
	int		m_TempAttenuation;
	float	m_flVolume;
	bool	m_active;
	string_t m_iszListener;	// name of entity to look at while talking
	CBaseEntity *m_pActivator;

	COutputEvent m_OnBeginSentence;
	COutputEvent m_OnEndSentence;
};


#define SF_SENTENCE_ONCE				0x0001
#define SF_SENTENCE_FOLLOWERS			0x0002	// only say if following player
#define SF_SENTENCE_INTERRUPT			0x0004	// force talking except when dead
#define SF_SENTENCE_CONCURRENT			0x0008	// allow other people to keep talking
#define SF_SENTENCE_SPEAKTOACTIVATOR	0x0010

BEGIN_DATADESC( CAI_ScriptedSentence )

	DEFINE_KEYFIELD( m_iszSentence, FIELD_STRING, "sentence" ),
	DEFINE_KEYFIELD( m_iszEntity, FIELD_STRING, "entity" ),
	DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ),
	DEFINE_KEYFIELD( m_flDelay, FIELD_FLOAT, "delay" ),
	DEFINE_KEYFIELD( m_flRepeat, FIELD_FLOAT, "refire" ),
	DEFINE_KEYFIELD( m_iszListener, FIELD_STRING, "listener" ),

	DEFINE_KEYFIELD( m_TempAttenuation, FIELD_INTEGER, "attenuation" ),

	DEFINE_FIELD( m_iSoundLevel, FIELD_INTEGER ),
	DEFINE_FIELD( m_flVolume, FIELD_FLOAT ),
	DEFINE_FIELD( m_active, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_pActivator, FIELD_EHANDLE ),

	// Function Pointers
	DEFINE_FUNCTION( FindThink ),
	DEFINE_FUNCTION( DelayThink ),

	// Inputs
	DEFINE_INPUTFUNC(FIELD_VOID, "BeginSentence", InputBeginSentence),

	// Outputs
	DEFINE_OUTPUT(m_OnBeginSentence, "OnBeginSentence"),
	DEFINE_OUTPUT(m_OnEndSentence, "OnEndSentence"),

END_DATADESC()



LINK_ENTITY_TO_CLASS( scripted_sentence, CAI_ScriptedSentence );


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : szKeyName - 
//			szValue - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_ScriptedSentence::KeyValue( const char *szKeyName, const char *szValue )
{
	if(FStrEq(szKeyName, "volume"))
	{
		m_flVolume = atof( szValue ) * 0.1;
	}
	else
	{
		return BaseClass::KeyValue( szKeyName, szValue );
	}

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Input handler for starting the scripted sentence.
//-----------------------------------------------------------------------------
void CAI_ScriptedSentence::InputBeginSentence( inputdata_t &inputdata )
{
	if ( !m_active )
		return;

	m_pActivator = inputdata.pActivator;

	//Msg( "Firing sentence: %s\n", STRING( m_iszSentence ));
	SetThink( &CAI_ScriptedSentence::FindThink );
	SetNextThink( gpGlobals->curtime );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_ScriptedSentence::Spawn( void )
{
	SetSolid( SOLID_NONE );
	
	m_active = true;
	// if no targetname, start now
	if ( !GetEntityName() )
	{
		SetThink( &CAI_ScriptedSentence::FindThink );
		SetNextThink( gpGlobals->curtime + 1.0f );
	}

	switch( m_TempAttenuation )
	{
	case 1: // Medium radius
		m_iSoundLevel = SNDLVL_80dB;
		break;
	
	case 2:	// Large radius
		m_iSoundLevel = SNDLVL_85dB;
		break;

	case 3:	//EVERYWHERE
		m_iSoundLevel = SNDLVL_NONE;
		break;
	
	default:
	case 0: // Small radius
		m_iSoundLevel = SNDLVL_70dB;
		break;
	}
	m_TempAttenuation = 0;

	// No volume, use normal
	if ( m_flVolume <= 0 )
		m_flVolume = 1.0;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_ScriptedSentence::FindThink( void )
{
	CAI_BaseNPC *pNPC = FindEntity();
	if ( pNPC )
	{
		int index = StartSentence( pNPC );
		float length = engine->SentenceLength(index);
		
		m_OnEndSentence.FireOutput(NULL, this, length + m_flRepeat);

		if ( m_spawnflags & SF_SENTENCE_ONCE )
			UTIL_Remove( this );
		
		float delay = m_flDelay + length + 0.1;
		if ( delay < 0 )
			delay = 0;

		SetThink( &CAI_ScriptedSentence::DelayThink );
		// calculate delay dynamically because this could play a sentence group
		// rather than a single sentence.
		// add 0.1 because the sound engine mixes ahead -- the sentence will actually start ~0.1 secs from now
		SetNextThink( gpGlobals->curtime + delay + m_flRepeat );
		m_active = false;
		//Msg( "%s: found NPC %s\n", STRING(m_iszSentence), STRING(m_iszEntity) );
	}
	else
	{
		//Msg( "%s: can't find NPC %s\n", STRING(m_iszSentence), STRING(m_iszEntity) );
		SetNextThink( gpGlobals->curtime + m_flRepeat + 0.5 );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_ScriptedSentence::DelayThink( void )
{
	m_active = true;
	if ( !GetEntityName() )
		SetNextThink( gpGlobals->curtime + 0.1f );
	SetThink( &CAI_ScriptedSentence::FindThink );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pNPC - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_ScriptedSentence::AcceptableSpeaker( CAI_BaseNPC *pNPC )
{
	if ( pNPC )
	{
		if ( m_spawnflags & SF_SENTENCE_FOLLOWERS )
		{
			if ( pNPC->GetTarget() == NULL || !pNPC->GetTarget()->IsPlayer() )
				return false;
		}
		bool override;
		if ( m_spawnflags & SF_SENTENCE_INTERRUPT )
			override = true;
		else
			override = false;
		if ( pNPC->CanPlaySentence( override ) )
			return true;
	}
	return false;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : 
//-----------------------------------------------------------------------------
CAI_BaseNPC *CAI_ScriptedSentence::FindEntity( void )
{
	CBaseEntity *pentTarget;
	CAI_BaseNPC *pNPC;

	pentTarget = gEntList.FindEntityByName( NULL, m_iszEntity, NULL );
	pNPC = NULL;

	while (pentTarget)
	{
		pNPC = pentTarget->MyNPCPointer();
		if ( pNPC != NULL )
		{
			if ( AcceptableSpeaker( pNPC ) )
				return pNPC;
			//Msg( "%s (%s), not acceptable\n", pNPC->GetClassname(), pNPC->GetDebugName() );
		}
		pentTarget = gEntList.FindEntityByName( pentTarget, m_iszEntity, NULL );
	}
	
	CBaseEntity *pEntity = NULL;
	for ( CEntitySphereQuery sphere( GetAbsOrigin(), m_flRadius, FL_NPC ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
	{
		if (FClassnameIs( pEntity, STRING(m_iszEntity)))
		{
			pNPC = pEntity->MyNPCPointer( );
			if ( AcceptableSpeaker( pNPC ) )
				return pNPC;
		}
	}
	
	return NULL;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pTarget - 
// Output : 
//-----------------------------------------------------------------------------
int CAI_ScriptedSentence::StartSentence( CAI_BaseNPC *pTarget )
{
	if ( !pTarget )
	{
		DevMsg( 2, "Not Playing sentence %s\n", STRING(m_iszSentence) );
		return -1;
	}

	bool bConcurrent = false;
	if ( !(m_spawnflags & SF_SENTENCE_CONCURRENT) )
		bConcurrent = true;

	CBaseEntity *pListener = NULL;

	if ( m_spawnflags & SF_SENTENCE_SPEAKTOACTIVATOR )
	{
		pListener = m_pActivator;
	}
	else if (m_iszListener != NULL_STRING)
	{
		float radius = m_flRadius;

		if ( FStrEq( STRING(m_iszListener ), "!player" ) )
			radius = MAX_TRACE_LENGTH;	// Always find the player

		pListener = gEntList.FindEntityGenericNearest( STRING( m_iszListener ), pTarget->GetAbsOrigin(), radius, this, NULL );
	}

	int sentenceIndex = pTarget->PlayScriptedSentence( STRING(m_iszSentence), m_flDelay,  m_flVolume, m_iSoundLevel, bConcurrent, pListener );
	DevMsg( 2, "Playing sentence %s\n", STRING(m_iszSentence) );

	m_OnBeginSentence.FireOutput(NULL, this);

	return sentenceIndex;
}



// HACKHACK: This is a little expensive with the dynamic_cast<> and all, but it lets us solve
// the problem of matching scripts back to entities without new state.
const char *CAI_ScriptedSequence::GetSpawnPreIdleSequenceForScript( CBaseEntity *pEntity )
{
	CAI_ScriptedSequence *pScript = gEntList.NextEntByClass( (CAI_ScriptedSequence *)NULL );
	while ( pScript )
	{
		if ( pScript->HasSpawnFlags( SF_SCRIPT_START_ON_SPAWN ) && pScript->m_iszEntity == pEntity->GetEntityName() )
		{
			if ( pScript->m_iszPreIdle != NULL_STRING )
			{
				return STRING(pScript->m_iszPreIdle);
			}
			return NULL;
		}
		pScript = gEntList.NextEntByClass( pScript );
	}
	return NULL;
}
