//========= Copyright <EFBFBD> 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"
ConVar ai_task_pre_script ( " ai_task_pre_script " , " 0 " , FCVAR_NONE ) ;
//
// 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_iszEntry , FIELD_STRING , " m_iszEntry " ) ,
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_bIsPlayingEntry , FIELD_BOOLEAN ) ,
DEFINE_KEYFIELD ( m_bLoopActionSequence , FIELD_BOOLEAN , " m_bLoopActionSequence " ) ,
DEFINE_KEYFIELD ( m_bSynchPostIdles , FIELD_BOOLEAN , " m_bSynchPostIdles " ) ,
DEFINE_KEYFIELD ( m_bIgnoreGravity , FIELD_BOOLEAN , " m_bIgnoreGravity " ) ,
DEFINE_KEYFIELD ( m_bDisableNPCCollisions , FIELD_BOOLEAN , " m_bDisableNPCCollisions " ) ,
DEFINE_FIELD ( m_iDelay , FIELD_INTEGER ) ,
DEFINE_FIELD ( m_bDelayed , FIELD_BOOLEAN ) ,
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_savedCollisionGroup , 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_hForcedTarget , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_bDontCancelOtherSequences , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_bForceSynch , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_bThinking , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_bInitiatedSelfDelete , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_bIsTeleportingDueToMoveTo , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_matInteractionPosition , FIELD_VMATRIX ) ,
DEFINE_FIELD ( m_hInteractionRelativeEntity , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_bTargetWasAsleep , 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 ) ,
DEFINE_KEYFIELD ( m_iPlayerDeathBehavior , FIELD_INTEGER , " onplayerdeath " ) ,
DEFINE_INPUTFUNC ( FIELD_VOID , " ScriptPlayerDeath " , InputScriptPlayerDeath ) ,
// Outputs
DEFINE_OUTPUT ( m_OnBeginSequence , " OnBeginSequence " ) ,
DEFINE_OUTPUT ( m_OnEndSequence , " OnEndSequence " ) ,
DEFINE_OUTPUT ( m_OnPostIdleEndSequence , " OnPostIdleEndSequence " ) ,
DEFINE_OUTPUT ( m_OnCancelSequence , " OnCancelSequence " ) ,
DEFINE_OUTPUT ( m_OnCancelFailedSequence , " OnCancelFailedSequence " ) ,
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 , bool bPretendSuccess )
{
// 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 ) ;
// 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 ;
if ( bPretendSuccess )
{
// We need to pretend that this sequence actually finished fully
pCineTarget - > m_OnEndSequence . FireOutput ( NULL , pCineTarget ) ;
pCineTarget - > m_OnPostIdleEndSequence . FireOutput ( NULL , pCineTarget ) ;
}
else
{
// Fire the cancel
pCineTarget - > m_OnCancelSequence . FireOutput ( NULL , pCineTarget ) ;
if ( pCineTarget - > m_startTime = = 0 )
{
// If start time is 0, this sequence never actually ran. Fire the failed output.
pCineTarget - > m_OnCancelFailedSequence . FireOutput ( NULL , pCineTarget ) ;
}
}
}
}
//-----------------------------------------------------------------------------
// 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 & & m_hForcedTarget )
{
if ( FindEntity ( ) )
{
pEntity = GetTarget ( ) ;
}
}
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 ( ) ;
// Because we are directly calling the new "think" function ScriptThink, assume we're done
// This fixes the following bug (along with the WokeThisTick() code herein:
// A zombie is created in the asleep state and then, the mapper fires both Wake and BeginSequence
// messages to have it jump up out of the slime, e.g. What happens before this change is that
// the Wake code removed EF_NODRAW, but so the zombie is transmitted to the client, but the script
// hasn't started and won't start until the next Think time (2 ticks on xbox) at which time the
// actual sequence starts causing the zombie to quickly lie down.
// The changes here are to track what tick we "awoke" on and get rid of the lag between Wake and
// ScriptThink by actually calling ScriptThink directly on the same frame and checking for the
// zombie having woken up and been instructed to play a sequence in the same frame.
SetNextThink ( TICK_NEVER_THINK ) ;
ScriptThink ( ) ;
}
}
//-----------------------------------------------------------------------------
// 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 ) ;
}
void CAI_ScriptedSequence : : InputScriptPlayerDeath ( inputdata_t & inputdata )
{
if ( m_iPlayerDeathBehavior = = SCRIPT_CANCEL )
{
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 ;
CBaseEntity * pEntity ;
int interrupt ;
if ( m_hForcedTarget )
{
interrupt = SS_INTERRUPT_BY_NAME ;
pEntity = m_hForcedTarget ;
}
else
{
interrupt = SS_INTERRUPT_BY_NAME ;
pEntity = gEntList . FindEntityByNameWithin ( m_hLastFoundEntity , STRING ( m_iszEntity ) , GetAbsOrigin ( ) , m_flRadius ) ;
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 ( m_hForcedTarget )
{
Warning ( " Code forced %s(%s), to be the target of scripted sequence %s, but it can't play it. \n " ,
pEntity - > GetClassname ( ) , pEntity - > GetDebugName ( ) , GetDebugName ( ) ) ;
pEntity = NULL ;
UTIL_Remove ( this ) ;
return NULL ;
}
else
{
if ( interrupt = = SS_INTERRUPT_BY_NAME )
pEntity = gEntList . FindEntityByNameWithin ( pEntity , STRING ( m_iszEntity ) , GetAbsOrigin ( ) , m_flRadius ) ;
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 )
{
pTarget - > RemoveSpawnFlags ( SF_NPC_WAIT_FOR_SCRIPT ) ;
//
// 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 ) ;
// Notify the NPC tat we're stomping them into a scene!
pTarget - > OnStartScene ( ) ;
{
m_bTargetWasAsleep = ( pTarget - > GetSleepState ( ) ! = AISS_AWAKE ) ? true : false ;
bool justAwoke = pTarget - > WokeThisTick ( ) ;
if ( m_bTargetWasAsleep | | justAwoke )
{
// Note, Wake() will remove the EF_NODRAW flag, but if we are starting a seq on a hidden entity
// we don't want it to draw on the client until the sequence actually starts to play
// Make sure it stays hidden!!!
if ( m_bTargetWasAsleep )
{
pTarget - > Wake ( ) ;
}
m_bTargetWasAsleep = true ;
// Even if awakened this frame, temporarily keep the entity hidden for now
pTarget - > AddEffects ( EF_NODRAW ) ;
}
}
// If the entity was asleep at the start, make sure we don't make it invisible
// AFTER the script finishes (can't think of a case where you'd want that to happen)
m_saved_effects = pTarget - > GetEffects ( ) & ~ EF_NODRAW ;
pTarget - > AddEffects ( GetEffects ( ) ) ;
m_savedFlags = pTarget - > GetFlags ( ) ;
m_savedCollisionGroup = pTarget - > GetCollisionGroup ( ) ;
if ( m_bDisableNPCCollisions )
{
pTarget - > SetCollisionGroup ( COLLISION_GROUP_NPC_SCRIPTED ) ;
}
switch ( m_fMoveTo )
{
case CINE_MOVETO_WAIT :
case CINE_MOVETO_WAIT_FACING :
pTarget - > m_scriptState = CAI_BaseNPC : : SCRIPT_WAIT ;
if ( m_bIgnoreGravity )
{
pTarget - > AddFlag ( FL_FLY ) ;
pTarget - > SetGroundEntity ( NULL ) ;
}
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 :
m_bIsTeleportingDueToMoveTo = true ;
pTarget - > Teleport ( & GetAbsOrigin ( ) , NULL , & vec3_origin ) ;
m_bIsTeleportingDueToMoveTo = false ;
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 ;
if ( m_bIgnoreGravity )
{
pTarget - > AddFlag ( FL_FLY ) ;
pTarget - > SetGroundEntity ( NULL ) ;
}
// 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.
m_bDelayed = false ;
DelayStart ( true ) ;
pTarget - > SetIdealState ( NPC_STATE_SCRIPT ) ;
// FIXME: not sure why this is happening, or what to do about truely dormant NPCs
if ( pTarget - > IsEFlagSet ( EFL_NO_THINK_FUNCTION ) & & pTarget - > GetNextThink ( ) ! = TICK_NEVER_THINK )
{
DevWarning ( " scripted_sequence %d:%s - restarting dormant entity %d:%s : %.1f:%.1f \n " , entindex ( ) , GetDebugName ( ) , pTarget - > entindex ( ) , pTarget - > GetDebugName ( ) , gpGlobals - > curtime , pTarget - > GetNextThink ( ) ) ;
pTarget - > SetNextThink ( gpGlobals - > curtime ) ;
}
}
}
//-----------------------------------------------------------------------------
// 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 + 0.1f ) ;
}
else if ( FindEntity ( ) )
{
StartScript ( ) ;
DevMsg ( 2 , " scripted_sequence %d: \" %s \" using NPC %d: \" %s \" (%s) \n " , entindex ( ) , GetDebugName ( ) , GetTarget ( ) - > entindex ( ) , GetTarget ( ) - > GetEntityName ( ) . ToCStr ( ) , STRING ( m_iszEntity ) ) ;
}
else
{
CancelScript ( ) ;
DevMsg ( 2 , " scripted_sequence %d: \" %s \" can't find NPC \" %s \" \n " , entindex ( ) , 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 )
{
Assert ( pTarget ) ;
m_sequenceStarted = true ;
m_bIsPlayingEntry = ( iszSeq = = m_iszEntry ) ;
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 ) ;
// If the target was hidden even though we woke it up, only make it drawable if we're not still on the preidle seq...
if ( m_bTargetWasAsleep & &
iszSeq ! = m_iszPreIdle )
{
m_bTargetWasAsleep = false ;
// Show it
pTarget - > RemoveEffects ( EF_NODRAW ) ;
// Don't blend...
pTarget - > AddEffects ( EF_NOINTERP ) ;
}
//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 )
{
//Msg("%s (for %s) called SynchronizeSequence() at %0.2f\n", GetTarget()->GetDebugName(), GetDebugName(), gpGlobals->curtime);
Assert ( m_iDelay = = 0 ) ;
Assert ( m_bWaitForBeginSequence = = false ) ;
m_bForceSynch = 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().ToCStr(), 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 ;
// do the movement for the missed portion of the sequence
pNPC - > SetCycle ( 0.0f ) ;
pNPC - > AutoMovement ( flInterval ) ;
// reset the cycle to a common basis
pNPC - > SetCycle ( flInterval * flCycleRate ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Moves to the next action sequence if the scripted_sequence wants to,
// or returns true if it wants to leave the action sequence
//-----------------------------------------------------------------------------
bool CAI_ScriptedSequence : : FinishedActionSequence ( CAI_BaseNPC * pNPC )
{
// Restart the action sequence when the entry finishes, or when the action
// finishes and we're set to loop it.
if ( IsPlayingEntry ( ) )
{
if ( GetEntityName ( ) ! = NULL_STRING )
{
// Force all matching ss's to synchronize their action sequences
SynchNewSequence ( CAI_BaseNPC : : SCRIPT_PLAYING , m_iszPlay , true ) ;
}
else
{
StartSequence ( pNPC , m_iszPlay , true ) ;
}
return false ;
}
// Let the core action sequence continue to loop
if ( ShouldLoopActionSequence ( ) )
{
// If the NPC has reached post idle state, we need to stop looping the action sequence
if ( pNPC - > m_scriptState = = CAI_BaseNPC : : SCRIPT_POST_IDLE )
return true ;
return false ;
}
return true ;
}
//-----------------------------------------------------------------------------
// 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 ) );
//Msg("%s SequenceDone() at %0.2f\n", pNPC->GetDebugName(), gpGlobals->curtime );
// If we're part of a synchronised post-idle sequence, we need to do things differently
if ( m_bSynchPostIdles & & GetEntityName ( ) ! = NULL_STRING )
{
// If we're already in POST_IDLE state, then one of the other scripted
// sequences we're synching with has already kicked us into running
// the post idle sequence, so we do nothing.
if ( pNPC - > m_scriptState ! = CAI_BaseNPC : : SCRIPT_POST_IDLE )
{
if ( ( m_iszPostIdle ! = NULL_STRING ) & & ( m_hNextCine = = NULL ) )
{
SynchNewSequence ( CAI_BaseNPC : : SCRIPT_POST_IDLE , m_iszPostIdle , true ) ;
}
else
{
PostIdleDone ( pNPC ) ;
}
}
}
else
{
//
// 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:
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence : : SynchNewSequence ( CAI_BaseNPC : : SCRIPTSTATE newState , string_t iszSequence , bool bSynchOtherScenes )
{
// Do we need to synchronize all our matching scripted scenes?
if ( bSynchOtherScenes )
{
//Msg("%s (for %s) forcing synch of %s at %0.2f\n", GetTarget()->GetDebugName(), GetDebugName(), iszSequence, gpGlobals->curtime);
CBaseEntity * pentCine = gEntList . FindEntityByName ( NULL , GetEntityName ( ) , NULL ) ;
while ( pentCine )
{
CAI_ScriptedSequence * pScene = dynamic_cast < CAI_ScriptedSequence * > ( pentCine ) ;
if ( pScene & & pScene ! = this )
{
pScene - > SynchNewSequence ( newState , iszSequence , false ) ;
}
pentCine = gEntList . FindEntityByName ( pentCine , GetEntityName ( ) , NULL ) ;
}
}
// Now force this one to start the post idle?
if ( ! GetTarget ( ) )
return ;
CAI_BaseNPC * pNPC = GetTarget ( ) - > MyNPCPointer ( ) ;
if ( ! pNPC )
return ;
//Msg("%s (for %s) starting %s seq at %0.2f\n", pNPC->GetDebugName(), GetDebugName(), iszSequence, gpGlobals->curtime);
m_startTime = gpGlobals - > curtime ;
pNPC - > m_scriptState = newState ;
StartSequence ( pNPC , iszSequence , false ) ;
// Force the NPC to synchronize animations on their next think
m_bForceSynch = true ;
}
//-----------------------------------------------------------------------------
// 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 ( ) ;
}
}
}
//Msg("%s finished post idle at %0.2f\n", pNPC->GetDebugName(), gpGlobals->curtime );
m_OnPostIdleEndSequence . FireOutput ( NULL , this ) ;
}
//-----------------------------------------------------------------------------
// 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 ( " Finished scripted sequence " ) ;
}
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 | | pNPC - > GetTask ( ) - > iTask = = TASK_PLAY_SCRIPT_POST_IDLE ) )
{
if ( ! ( iSavedCineFlags & FL_FLY ) )
{
if ( pNPC - > GetFlags ( ) & FL_FLY )
{
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:
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence : : StopActionLoop ( bool bStopSynchronizedScenes )
{
// Stop looping our action sequence. Next time the loop finishes,
// we'll move to the post idle sequence instead.
m_bLoopActionSequence = false ;
// If we have synchronized scenes, and we're supposed to stop them, do so
if ( ! bStopSynchronizedScenes | | GetEntityName ( ) = = NULL_STRING )
return ;
CBaseEntity * pentCine = gEntList . FindEntityByName ( NULL , GetEntityName ( ) , NULL ) ;
while ( pentCine )
{
CAI_ScriptedSequence * pScene = dynamic_cast < CAI_ScriptedSequence * > ( pentCine ) ;
if ( pScene & & pScene ! = this )
{
pScene - > StopActionLoop ( false ) ;
}
pentCine = gEntList . FindEntityByName ( pentCine , GetEntityName ( ) , NULL ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Code method of forcing a scripted sequence entity to use a particular NPC.
// Useful when you don't know if the NPC has a unique targetname.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence : : ForceSetTargetEntity ( CAI_BaseNPC * pTarget , bool bDontCancelOtherSequences )
{
m_hForcedTarget = pTarget ;
m_iszEntity = m_hForcedTarget - > GetEntityName ( ) ; // Not guaranteed to be unique
m_bDontCancelOtherSequences = bDontCancelOtherSequences ;
}
//-----------------------------------------------------------------------------
// Purpose: Setup this scripted sequence to maintain the desired position offset
// to the other NPC in the scripted interaction.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence : : SetupInteractionPosition ( CBaseEntity * pRelativeEntity , VMatrix & matDesiredLocalToWorld )
{
m_matInteractionPosition = matDesiredLocalToWorld ;
m_hInteractionRelativeEntity = pRelativeEntity ;
}
extern ConVar ai_debug_dyninteractions ;
//-----------------------------------------------------------------------------
// Purpose: Modify the target AutoMovement() position before the NPC moves.
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence : : ModifyScriptedAutoMovement ( Vector * vecNewPos )
{
if ( m_hInteractionRelativeEntity )
{
// If we have an entry animation, only do it on the entry
if ( m_iszEntry ! = NULL_STRING & & ! m_bIsPlayingEntry )
return ;
Vector vecRelativeOrigin = m_hInteractionRelativeEntity - > GetAbsOrigin ( ) ;
QAngle angRelativeAngles = m_hInteractionRelativeEntity - > GetAbsAngles ( ) ;
CAI_BaseNPC * pNPC = m_hInteractionRelativeEntity - > MyNPCPointer ( ) ;
if ( pNPC )
{
angRelativeAngles [ YAW ] = pNPC - > GetInteractionYaw ( ) ;
}
bool bDebug = ai_debug_dyninteractions . GetInt ( ) = = 2 ;
if ( bDebug )
{
Msg ( " -- \n %s current org: %f %f \n " , m_hTargetEnt - > GetDebugName ( ) , m_hTargetEnt - > GetAbsOrigin ( ) . x , m_hTargetEnt - > GetAbsOrigin ( ) . y ) ;
Msg ( " %s current org: %f %f " , m_hInteractionRelativeEntity - > GetDebugName ( ) , vecRelativeOrigin . x , vecRelativeOrigin . y ) ;
}
CBaseAnimating * pAnimating = dynamic_cast < CBaseAnimating * > ( m_hInteractionRelativeEntity . Get ( ) ) ;
if ( pAnimating )
{
Vector vecDeltaPos ;
QAngle vecDeltaAngles ;
pAnimating - > GetSequenceMovement ( pAnimating - > GetSequence ( ) , 0.0f , pAnimating - > GetCycle ( ) , vecDeltaPos , vecDeltaAngles ) ;
VectorYawRotate ( vecDeltaPos , pAnimating - > GetLocalAngles ( ) . y , vecDeltaPos ) ;
if ( bDebug )
{
NDebugOverlay : : Box ( vecRelativeOrigin , - Vector ( 2 , 2 , 2 ) , Vector ( 2 , 2 , 2 ) , 0 , 255 , 0 , 8 , 0.1 ) ;
}
vecRelativeOrigin - = vecDeltaPos ;
if ( bDebug )
{
Msg ( " , relative to sequence start: %f %f \n " , vecRelativeOrigin . x , vecRelativeOrigin . y ) ;
NDebugOverlay : : Box ( vecRelativeOrigin , - Vector ( 3 , 3 , 3 ) , Vector ( 3 , 3 , 3 ) , 255 , 0 , 0 , 8 , 0.1 ) ;
}
}
// We've been asked to maintain a specific position relative to the other NPC
// we're interacting with. Lerp towards the relative position.
VMatrix matMeToWorld , matLocalToWorld ;
matMeToWorld . SetupMatrixOrgAngles ( vecRelativeOrigin , angRelativeAngles ) ;
MatrixMultiply ( matMeToWorld , m_matInteractionPosition , matLocalToWorld ) ;
// Get the desired NPC position in worldspace
Vector vecOrigin ;
QAngle angAngles ;
vecOrigin = matLocalToWorld . GetTranslation ( ) ;
MatrixToAngles ( matLocalToWorld , angAngles ) ;
if ( bDebug )
{
Msg ( " Desired Origin for %s: %f %f \n " , m_hTargetEnt - > GetDebugName ( ) , vecOrigin . x , vecOrigin . y ) ;
NDebugOverlay : : Axis ( vecOrigin , angAngles , 5 , true , 0.1 ) ;
}
// Lerp to it over time
Vector vecToTarget = ( vecOrigin - * vecNewPos ) ;
if ( bDebug )
{
Msg ( " Automovement's output origin: %f %f \n " , ( * vecNewPos ) . x , ( * vecNewPos ) . y ) ;
Msg ( " Vector from automovement to desired: %f %f \n " , vecToTarget . x , vecToTarget . y ) ;
}
* vecNewPos + = ( vecToTarget * pAnimating - > GetCycle ( ) ) ;
}
}
//-----------------------------------------------------------------------------
// 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 ) ) ;
// Don't cancel matching sequences if we're asked not to, unless we didn't actually
// succeed in starting, in which case we should always cancel. This fixes
// dynamic interactions where an NPC was killed the same frame another NPC
// started a dynamic interaction with him.
bool bDontCancelOther = ( ( m_bDontCancelOtherSequences | | HasSpawnFlags ( SF_SCRIPT_ALLOW_DEATH ) ) & & ( m_startTime ! = 0 ) ) ;
if ( bDontCancelOther | | ! GetEntityName ( ) )
{
ScriptEntityCancel ( this ) ;
return ;
}
CBaseEntity * pentCineTarget = gEntList . FindEntityByName ( NULL , GetEntityName ( ) ) ;
while ( pentCineTarget )
{
ScriptEntityCancel ( pentCineTarget ) ;
pentCineTarget = gEntList . FindEntityByName ( pentCineTarget , GetEntityName ( ) ) ;
}
}
//-----------------------------------------------------------------------------
// 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("SSEQ: %.2f \"%s\" (%d) DelayStart( %d ). Current m_iDelay is: %d\n", gpGlobals->curtime, GetDebugName(), entindex(), bDelay, m_iDelay );
if ( ai_task_pre_script . GetBool ( ) )
{
if ( bDelay = = m_bDelayed )
return ;
m_bDelayed = 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 ( ) ) ;
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 + + ;
//Msg("SSEQ: (%d) Found matching SS (%d). Incrementing MY m_iDelay to %d.\n", entindex(), pTarget->entindex(), m_iDelay );
}
else
{
// if ready, decrement each of other scripts in the group
// members not yet delayed will decrement below zero.
pTarget - > m_iDelay - - ;
//Msg("SSEQ: (%d) Found matching SS (%d). Decrementing THEIR m_iDelay to %d.\n", entindex(), pTarget->entindex(), pTarget->m_iDelay );
// once everything is balanced, everyone will start.
if ( pTarget - > m_iDelay = = 0 )
{
pTarget - > m_startTime = gpGlobals - > curtime ;
//Msg("SSEQ: STARTING SEQUENCE for \"%s\" (%d) (m_iDelay reached 0).\n", pTarget->GetDebugName(), pTarget->entindex() );
}
}
}
pentCine = gEntList . FindEntityByName ( pentCine , GetEntityName ( ) ) ;
}
//Msg("SSEQ: Exited DelayStart() with m_iDelay of: %d.\n", m_iDelay );
}
//-----------------------------------------------------------------------------
// 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 ( ) ;
//
// See if there is another script specified to run immediately after this one.
//
m_hNextCine = gEntList . FindEntityByName ( NULL , m_iszNextScript ) ;
if ( m_hNextCine = = NULL )
{
m_iszNextScript = NULL_STRING ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_ScriptedSequence : : DrawDebugGeometryOverlays ( void )
{
BaseClass : : DrawDebugGeometryOverlays ( ) ;
if ( m_debugOverlays & OVERLAY_TEXT_BIT )
{
if ( GetTarget ( ) )
{
NDebugOverlay : : HorzArrow ( GetAbsOrigin ( ) , GetTarget ( ) - > GetAbsOrigin ( ) , 16 , 0 , 255 , 0 , 64 , true , 0.0f ) ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Input :
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CAI_ScriptedSequence : : DrawDebugTextOverlays ( void )
{
int text_offset = BaseClass : : DrawDebugTextOverlays ( ) ;
if ( m_debugOverlays & OVERLAY_TEXT_BIT )
{
char tempstr [ 512 ] ;
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Target: %s " , GetTarget ( ) ? GetTarget ( ) - > GetDebugName ( ) : " None " ) ;
EntityText ( text_offset , tempstr , 0 ) ;
text_offset + + ;
switch ( m_fMoveTo )
{
case CINE_MOVETO_WAIT :
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Moveto: Wait " ) ;
break ;
case CINE_MOVETO_WAIT_FACING :
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Moveto: Wait Facing " ) ;
break ;
case CINE_MOVETO_WALK :
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Moveto: Walk to Mark " ) ;
break ;
case CINE_MOVETO_RUN :
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Moveto: Run to Mark " ) ;
break ;
case CINE_MOVETO_CUSTOM :
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Moveto: Custom move to Mark " ) ;
break ;
case CINE_MOVETO_TELEPORT :
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Moveto: Teleport to Mark " ) ;
break ;
}
EntityText ( text_offset , tempstr , 0 ) ;
text_offset + + ;
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Thinking: %s " , m_bThinking ? " Yes " : " No " ) ;
EntityText ( text_offset , tempstr , 0 ) ;
text_offset + + ;
if ( GetEntityName ( ) ! = NULL_STRING )
{
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Delay: %d " , m_iDelay ) ;
EntityText ( text_offset , tempstr , 0 ) ;
text_offset + + ;
}
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Start Time: %f " , m_startTime ) ;
EntityText ( text_offset , tempstr , 0 ) ;
text_offset + + ;
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Sequence has started: %s " , m_sequenceStarted ? " Yes " : " No " ) ;
EntityText ( text_offset , tempstr , 0 ) ;
text_offset + + ;
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Cancel Other Sequences: %s " , m_bDontCancelOtherSequences ? " No " : " Yes " ) ;
EntityText ( text_offset , tempstr , 0 ) ;
text_offset + + ;
if ( m_bWaitForBeginSequence )
{
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Is waiting for BeingSequence " ) ;
EntityText ( text_offset , tempstr , 0 ) ;
text_offset + + ;
}
if ( m_bIsPlayingEntry )
{
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Is playing entry " ) ;
EntityText ( text_offset , tempstr , 0 ) ;
text_offset + + ;
}
if ( m_bLoopActionSequence )
{
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Will loop action sequence " ) ;
EntityText ( text_offset , tempstr , 0 ) ;
text_offset + + ;
}
if ( m_bSynchPostIdles )
{
Q_snprintf ( tempstr , sizeof ( tempstr ) , " Will synch post idles " ) ;
EntityText ( text_offset , tempstr , 0 ) ;
text_offset + + ;
}
}
return text_offset ;
}
//-----------------------------------------------------------------------------
// Purpose: Modifies an NPC's AI state without taking it out of its AI.
//-----------------------------------------------------------------------------
class CAI_ScriptedSchedule : public CBaseEntity
{
DECLARE_CLASS ( CAI_ScriptedSchedule , CBaseEntity ) ;
public :
CAI_ScriptedSchedule ( void ) ;
private :
void StartSchedule ( CAI_BaseNPC * pTarget ) ;
void StopSchedule ( CAI_BaseNPC * pTarget ) ;
void ScriptThink ( void ) ;
// Input handlers
void InputStartSchedule ( inputdata_t & inputdata ) ;
void InputStopSchedule ( 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 ,
} ;
//---------------------------------
EHANDLE m_hLastFoundEntity ;
EHANDLE m_hActivator ; // Held from the input to allow procedural calls
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_FIELD ( m_hActivator , FIELD_EHANDLE ) ,
DEFINE_THINKFUNC ( ScriptThink ) ,
DEFINE_INPUTFUNC ( FIELD_VOID , " StartSchedule " , InputStartSchedule ) ,
DEFINE_INPUTFUNC ( FIELD_VOID , " StopSchedule " , InputStopSchedule ) ,
END_DATADESC ( )
LINK_ENTITY_TO_CLASS ( aiscripted_schedule , CAI_ScriptedSchedule ) ;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CAI_ScriptedSchedule : : CAI_ScriptedSchedule ( void ) : m_hActivator ( NULL )
{
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
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 ( ) . ToCStr ( ) ) ;
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 ( ) . ToCStr ( ) , 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 , m_hActivator ) ;
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 \n Can'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 \n Can'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 \n Can't execute script %s \n " , STRING ( m_sGoalEnt ) , GetDebugName ( ) ) ;
}
return ;
}
bDidSetSchedule = true ;
break ;
}
}
if ( bDidSetSchedule )
{
// Chain this to the target so that it can add the base and any custom interrupts to this
pTarget - > SetScriptedScheduleIgnoreConditions ( m_Interruptability ) ;
}
}
//-----------------------------------------------------------------------------
// 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?
m_hActivator = inputdata . pActivator ;
SetThink ( & CAI_ScriptedSchedule : : ScriptThink ) ;
SetNextThink ( gpGlobals - > curtime ) ;
}
else
{
DevMsg ( 2 , " aiscripted_schedule - not playing schedule again: not flagged to repeat \n " ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler to stop a previously activated scripted schedule.
//-----------------------------------------------------------------------------
void CAI_ScriptedSchedule : : InputStopSchedule ( inputdata_t & inputdata )
{
if ( ! m_bDidFireOnce )
{
DevMsg ( 2 , " aiscripted_schedule - StopSchedule called, but schedule's never started. \n " ) ;
return ;
}
CAI_BaseNPC * pTarget ;
if ( ! m_bGrabAll )
{
pTarget = FindScriptEntity ( ( m_spawnflags & SF_SCRIPT_SEARCH_CYCLICALLY ) ! = 0 ) ;
if ( pTarget )
{
StopSchedule ( pTarget ) ;
}
}
else
{
m_hLastFoundEntity = NULL ;
while ( ( pTarget = FindScriptEntity ( true ) ) ! = NULL )
{
StopSchedule ( pTarget ) ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: If the target entity appears to be running this scripted schedule break it
//-----------------------------------------------------------------------------
void CAI_ScriptedSchedule : : StopSchedule ( CAI_BaseNPC * pTarget )
{
if ( pTarget - > IsCurSchedule ( SCHED_IDLE_WALK ) )
{
DevMsg ( 2 , " %s (%s): StopSchedule called on NPC %s. \n " , GetClassname ( ) , GetDebugName ( ) , pTarget - > GetDebugName ( ) ) ;
pTarget - > ClearSchedule ( " Stopping scripted schedule " ) ;
}
}
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 ) ;
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 ) ;
}
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 ;
}