diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /sp/src/game/server/scripted.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'sp/src/game/server/scripted.cpp')
| -rw-r--r-- | sp/src/game/server/scripted.cpp | 4490 |
1 files changed, 2245 insertions, 2245 deletions
diff --git a/sp/src/game/server/scripted.cpp b/sp/src/game/server/scripted.cpp index af11e83f..6a492708 100644 --- a/sp/src/game/server/scripted.cpp +++ b/sp/src/game/server/scripted.cpp @@ -1,2245 +1,2245 @@ -//========= Copyright 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);
-
-#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;
-
- 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->IncrementInterpolationFrame();
- 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->IncrementInterpolationFrame();
- }
- //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.f, 0.15f );
-
- 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\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 )
- {
- // 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;
-}
-
+//========= Copyright 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); + +#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; + + 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->IncrementInterpolationFrame(); + 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->IncrementInterpolationFrame(); + } + //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.f, 0.15f ); + + 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\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 ) + { + // 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; +} + |