aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/npc_talker.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/npc_talker.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/npc_talker.cpp')
-rw-r--r--mp/src/game/server/npc_talker.cpp1353
1 files changed, 1353 insertions, 0 deletions
diff --git a/mp/src/game/server/npc_talker.cpp b/mp/src/game/server/npc_talker.cpp
new file mode 100644
index 00000000..7b980f77
--- /dev/null
+++ b/mp/src/game/server/npc_talker.cpp
@@ -0,0 +1,1353 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+
+#include "npc_talker.h"
+#include "npcevent.h"
+#include "scriptevent.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+
+BEGIN_SIMPLE_DATADESC( CNPCSimpleTalkerExpresser )
+ // m_pSink (reconnected on load)
+ DEFINE_AUTO_ARRAY( m_szMonologSentence, FIELD_CHARACTER ),
+ DEFINE_FIELD( m_iMonologIndex, FIELD_INTEGER ),
+ DEFINE_FIELD( m_fMonologSuspended, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_hMonologTalkTarget, FIELD_EHANDLE ),
+END_DATADESC()
+
+BEGIN_DATADESC( CNPCSimpleTalker )
+ DEFINE_FIELD( m_useTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextIdleSpeechTime, FIELD_TIME ),
+ DEFINE_FIELD( m_nSpeak, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iszUse, FIELD_STRING ),
+ DEFINE_FIELD( m_iszUnUse, FIELD_STRING ),
+ // m_FollowBehavior (auto saved by AI)
+ // Function Pointers
+ DEFINE_USEFUNC( FollowerUse ),
+
+END_DATADESC()
+
+// array of friend names
+char *CNPCSimpleTalker::m_szFriends[TLK_CFRIENDS] =
+{
+ "NPC_barney",
+ "NPC_scientist",
+ "NPC_sitting_scientist",
+ NULL,
+};
+
+bool CNPCSimpleTalker::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if (FStrEq(szKeyName, "UseSentence"))
+ {
+ m_iszUse = AllocPooledString(szValue);
+ }
+ else if (FStrEq(szKeyName, "UnUseSentence"))
+ {
+ m_iszUnUse = AllocPooledString(szValue);
+ }
+ else
+ return BaseClass::KeyValue( szKeyName, szValue );
+
+ return true;
+}
+
+void CNPCSimpleTalker::Precache( void )
+{
+ /*
+ // FIXME: Need to figure out how to hook these...
+ if ( m_iszUse != NULL_STRING )
+ GetExpresser()->ModifyConcept( TLK_STARTFOLLOW, STRING( m_iszUse ) );
+ if ( m_iszUnUse != NULL_STRING )
+ GetExpresser()->ModifyConcept( TLK_STOPFOLLOW, STRING( m_iszUnUse ) );
+
+ */
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows for modification of the interrupt mask for the current schedule.
+// In the most cases the base implementation should be called first.
+//-----------------------------------------------------------------------------
+void CNPCSimpleTalker::BuildScheduleTestBits( void )
+{
+ BaseClass::BuildScheduleTestBits();
+
+ // Assume that if I move from the player, I can respond to a question
+ if ( ConditionInterruptsCurSchedule( COND_PLAYER_PUSHING ) || ConditionInterruptsCurSchedule( COND_PROVOKED ) )
+ {
+ SetCustomInterruptCondition( COND_TALKER_RESPOND_TO_QUESTION );
+ }
+}
+
+void CNPCSimpleTalker::PrescheduleThink( void )
+{
+ BaseClass::PrescheduleThink();
+
+ (assert_cast<CNPCSimpleTalkerExpresser *>(GetExpresser()))->SpeakMonolog();
+}
+
+bool CNPCSimpleTalker::ShouldSuspendMonolog( void )
+{
+ float flDist;
+
+ flDist = ((assert_cast<CNPCSimpleTalkerExpresser *>(GetExpresser()))->GetMonologueTarget()->GetAbsOrigin() - GetAbsOrigin()).Length();
+
+ if( flDist >= 384 )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+bool CNPCSimpleTalker::ShouldResumeMonolog( void )
+{
+ float flDist;
+
+ if( HasCondition( COND_SEE_PLAYER ) )
+ {
+ flDist = ((assert_cast<CNPCSimpleTalkerExpresser *>(GetExpresser()))->GetMonologueTarget()->GetAbsOrigin() - GetAbsOrigin()).Length();
+
+ if( flDist <= 256 )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int CNPCSimpleTalker::SelectSchedule( void )
+{
+ if ( !HasCondition(COND_RECEIVED_ORDERS) )
+ {
+ if ( GetState() == NPC_STATE_IDLE )
+ {
+ // if never seen player, try to greet him
+ // Filter might be preventing us from ever greeting the player
+ if ( HasCondition( COND_SEE_PLAYER ) && CanSayHello())
+ {
+ return SCHED_TALKER_IDLE_HELLO;
+ }
+ }
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+void CNPCSimpleTalker::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_TALKER_WAIT_FOR_SEMAPHORE:
+ if ( GetExpresser()->SemaphoreIsAvailable( this ) )
+ TaskComplete();
+ break;
+
+ case TASK_TALKER_SPEAK:
+ // ask question or make statement
+ FIdleSpeak();
+ TaskComplete();
+ break;
+
+ case TASK_TALKER_RESPOND:
+ // respond to question
+ IdleRespond();
+ TaskComplete();
+ break;
+
+ case TASK_TALKER_HELLO:
+ // greet player
+ FIdleHello();
+ TaskComplete();
+ break;
+
+ case TASK_TALKER_STARE:
+ // let the player know I know he's staring at me.
+ FIdleStare();
+ TaskComplete();
+ break;
+
+ case TASK_TALKER_LOOK_AT_CLIENT:
+ case TASK_TALKER_CLIENT_STARE:
+ // track head to the client for a while.
+ SetWait( pTask->flTaskData );
+ break;
+
+ case TASK_TALKER_EYECONTACT:
+ break;
+
+ case TASK_TALKER_IDEALYAW:
+ if (GetSpeechTarget() != NULL)
+ {
+ GetMotor()->SetIdealYawToTarget( GetSpeechTarget()->GetAbsOrigin() );
+ }
+ TaskComplete();
+ break;
+
+ case TASK_TALKER_HEADRESET:
+ // reset head position after looking at something
+ SetSpeechTarget( NULL );
+ TaskComplete();
+ break;
+
+ case TASK_TALKER_BETRAYED:
+ Speak( TLK_BETRAYED );
+ TaskComplete();
+ break;
+
+ case TASK_TALKER_STOPSHOOTING:
+ // tell player to stop shooting
+ Speak( TLK_NOSHOOT );
+ TaskComplete();
+ break;
+ default:
+ BaseClass::StartTask( pTask );
+ }
+}
+
+void CNPCSimpleTalker::RunTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_TALKER_WAIT_FOR_SEMAPHORE:
+ if ( GetExpresser()->SemaphoreIsAvailable( this ) )
+ TaskComplete();
+ break;
+
+ case TASK_TALKER_CLIENT_STARE:
+ case TASK_TALKER_LOOK_AT_CLIENT:
+
+ if ( pTask->iTask == TASK_TALKER_CLIENT_STARE && AI_IsSinglePlayer() )
+ {
+ // Get edict for one player
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+ Assert( pPlayer );
+
+ // fail out if the player looks away or moves away.
+ if ( ( pPlayer->GetAbsOrigin() - GetAbsOrigin() ).Length2D() > TALKER_STARE_DIST )
+ {
+ // player moved away.
+ TaskFail("Player moved away");
+ }
+
+ Vector forward;
+ AngleVectors( pPlayer->GetLocalAngles(), &forward );
+ if ( UTIL_DotPoints( pPlayer->GetAbsOrigin(), GetAbsOrigin(), forward ) < m_flFieldOfView )
+ {
+ // player looked away
+ TaskFail("Player looked away");
+ }
+ }
+
+ if ( IsWaitFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+
+ case TASK_TALKER_EYECONTACT:
+ if (IsMoving() || !GetExpresser()->IsSpeaking() || GetSpeechTarget() == NULL)
+ {
+ TaskComplete();
+ }
+ break;
+
+ case TASK_WAIT_FOR_MOVEMENT:
+ FIdleSpeakWhileMoving();
+ BaseClass::RunTask( pTask );
+ break;
+
+ default:
+ BaseClass::RunTask( pTask );
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+
+Activity CNPCSimpleTalker::NPC_TranslateActivity( Activity eNewActivity )
+{
+ if ((eNewActivity == ACT_IDLE) &&
+ (GetExpresser()->IsSpeaking()) &&
+ (SelectWeightedSequence ( ACT_SIGNAL3 ) != ACTIVITY_NOT_AVAILABLE) )
+ {
+ return ACT_SIGNAL3;
+ }
+ else if ((eNewActivity == ACT_SIGNAL3) &&
+ (SelectWeightedSequence ( ACT_SIGNAL3 ) == ACTIVITY_NOT_AVAILABLE) )
+ {
+ return ACT_IDLE;
+ }
+ return BaseClass::NPC_TranslateActivity( eNewActivity );
+}
+
+
+void CNPCSimpleTalker::Event_Killed( const CTakeDamageInfo &info )
+{
+ AlertFriends( info.GetAttacker() );
+ if ( info.GetAttacker()->GetFlags() & FL_CLIENT )
+ {
+ LimitFollowers( info.GetAttacker(), 0 );
+ }
+ BaseClass::Event_Killed( info );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseEntity *CNPCSimpleTalker::EnumFriends( CBaseEntity *pPrevious, int listNumber, bool bTrace )
+{
+ CBaseEntity *pFriend = pPrevious;
+ char *pszFriend;
+ trace_t tr;
+ Vector vecCheck;
+
+ pszFriend = m_szFriends[ FriendNumber(listNumber) ];
+ while ( pszFriend != NULL && ((pFriend = gEntList.FindEntityByClassname( pFriend, pszFriend )) != NULL) )
+ {
+ if (pFriend == this || !pFriend->IsAlive())
+ // don't talk to self or dead people
+ continue;
+
+ if ( bTrace )
+ {
+ Vector vecCheck;
+ pFriend->CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 1.0f ), &vecCheck );
+ UTIL_TraceLine( GetAbsOrigin(), vecCheck, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+ }
+ else
+ {
+ tr.fraction = 1.0;
+ }
+
+ if (tr.fraction == 1.0)
+ {
+ return pFriend;
+ }
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pKiller -
+//-----------------------------------------------------------------------------
+void CNPCSimpleTalker::AlertFriends( CBaseEntity *pKiller )
+{
+ CBaseEntity *pFriend = NULL;
+ int i;
+
+ // for each friend in this bsp...
+ for ( i = 0; i < TLK_CFRIENDS; i++ )
+ {
+ while ((pFriend = EnumFriends( pFriend, i, true )) != NULL )
+ {
+ CAI_BaseNPC *pNPC = pFriend->MyNPCPointer();
+ if ( pNPC->IsAlive() )
+ {
+ // If a client killed me, make everyone else mad/afraid of him
+ if ( pKiller->GetFlags() & FL_CLIENT )
+ {
+ CNPCSimpleTalker*pTalkNPC = (CNPCSimpleTalker *)pFriend;
+
+ if (pTalkNPC && pTalkNPC->IsOkToCombatSpeak())
+ {
+ // FIXME: need to check CanSpeakConcept?
+ pTalkNPC->Speak( TLK_BETRAYED );
+ }
+ }
+ else
+ {
+ if( IRelationType(pKiller) == D_HT)
+ {
+ // Killed by an enemy!!!
+ CNPCSimpleTalker *pAlly = (CNPCSimpleTalker *)pNPC;
+
+ if( pAlly && pAlly->GetExpresser()->CanSpeakConcept( TLK_ALLY_KILLED ) )
+ {
+ pAlly->Speak( TLK_ALLY_KILLED );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPCSimpleTalker::ShutUpFriends( void )
+{
+ CBaseEntity *pFriend = NULL;
+ int i;
+
+ // for each friend in this bsp...
+ for ( i = 0; i < TLK_CFRIENDS; i++ )
+ {
+ while ((pFriend = EnumFriends( pFriend, i, true )) != NULL)
+ {
+ CAI_BaseNPC *pNPC = pFriend->MyNPCPointer();
+ if ( pNPC )
+ {
+ pNPC->SentenceStop();
+ }
+ }
+ }
+}
+
+
+// UNDONE: Keep a follow time in each follower, make a list of followers in this function and do LRU
+// UNDONE: Check this in Restore to keep restored NPCs from joining a full list of followers
+void CNPCSimpleTalker::LimitFollowers( CBaseEntity *pPlayer, int maxFollowers )
+{
+ CBaseEntity *pFriend = NULL;
+ int i, count;
+
+ count = 0;
+ // for each friend in this bsp...
+ for ( i = 0; i < TLK_CFRIENDS; i++ )
+ {
+ while ((pFriend = EnumFriends( pFriend, i, false )) != NULL)
+ {
+ CAI_BaseNPC *pNPC = pFriend->MyNPCPointer();
+ CNPCSimpleTalker *pTalker;
+ if ( pNPC )
+ {
+ if ( pNPC->GetTarget() == pPlayer )
+ {
+ count++;
+ if ( count > maxFollowers && (pTalker = dynamic_cast<CNPCSimpleTalker *>( pNPC ) ) != NULL )
+ pTalker->StopFollowing();
+ }
+ }
+ }
+ }
+}
+
+//=========================================================
+// HandleAnimEvent - catches the NPC-specific messages
+// that occur when tagged animation frames are played.
+//=========================================================
+void CNPCSimpleTalker::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 25% of the time
+ if (random->RandomInt(0,99) < 75)
+ break;
+ // fall through...
+ case SCRIPT_EVENT_SENTENCE: // Play a named sentence group
+ ShutUpFriends();
+ PlaySentence( pEvent->options, random->RandomFloat(2.8, 3.4) );
+ //Msg( "script event speak\n");
+ break;
+
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Scan for nearest, visible friend. If fPlayer is true, look for nearest player
+//-----------------------------------------------------------------------------
+bool CNPCSimpleTalker::IsValidSpeechTarget( int flags, CBaseEntity *pEntity )
+{
+ return BaseClass::IsValidSpeechTarget( flags, pEntity );
+}
+
+CBaseEntity *CNPCSimpleTalker::FindNearestFriend(bool fPlayer)
+{
+ return FindSpeechTarget( (fPlayer) ? AIST_PLAYERS : AIST_NPCS );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+// Purpose: Respond to a previous question
+//-----------------------------------------------------------------------------
+void CNPCSimpleTalker::IdleRespond( void )
+{
+ if (!IsOkToSpeak())
+ return;
+
+ // play response
+ SpeakAnswerFriend( GetSpeechTarget() );
+
+ DeferAllIdleSpeech( random->RandomFloat( TALKER_DEFER_IDLE_SPEAK_MIN, TALKER_DEFER_IDLE_SPEAK_MAX ) );
+}
+
+bool CNPCSimpleTalker::IsOkToSpeak( void )
+{
+ if ( m_flNextIdleSpeechTime > gpGlobals->curtime )
+ return false;
+
+ return BaseClass::IsOkToSpeak();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Find a nearby friend to stare at
+//-----------------------------------------------------------------------------
+int CNPCSimpleTalker::FIdleStare( void )
+{
+ // Don't idly speak if our speech filter is preventing us
+ if ( GetSpeechFilter() && GetSpeechFilter()->GetIdleModifier() == 0 )
+ return true;
+
+ SpeakIfAllowed( TLK_STARE );
+
+ SetSpeechTarget( FindNearestFriend( true ) );
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Try to greet player first time he's seen
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPCSimpleTalker::FIdleHello( void )
+{
+ // Filter might be preventing us from ever greeting the player
+ if ( !CanSayHello() )
+ return false;
+
+ // get a player
+ CBaseEntity *pPlayer = FindNearestFriend(true);
+
+ if (pPlayer)
+ {
+ if (FInViewCone(pPlayer) && FVisible(pPlayer))
+ {
+ SayHelloToPlayer( pPlayer );
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Say hello to the specified player
+//-----------------------------------------------------------------------------
+void CNPCSimpleTalker::SayHelloToPlayer( CBaseEntity *pPlayer )
+{
+ Assert( !GetExpresser()->SpokeConcept(TLK_HELLO) );
+
+ SetSpeechTarget( pPlayer );
+
+ Speak( TLK_HELLO );
+ DeferAllIdleSpeech( random->RandomFloat( 5, 10 ) );
+
+ CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
+ CAI_PlayerAlly *pTalker;
+ for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
+ {
+ pTalker = dynamic_cast<CAI_PlayerAlly *>(ppAIs[i]);
+
+ if( pTalker && FVisible( pTalker ) )
+ {
+ // Tell this guy he's already said hello to the player, too.
+ pTalker->GetExpresser()->SetSpokeConcept( TLK_HELLO, NULL );
+ }
+ }
+}
+
+
+//---------------------------------------------------------
+// Stop all allies from idle speech for a fixed amount
+// of time. Mostly filthy hack to hold us over until
+// acting comes online.
+//---------------------------------------------------------
+void CNPCSimpleTalker::DeferAllIdleSpeech( float flDelay, CAI_BaseNPC *pIgnore )
+{
+ // Brute force. Just plow through NPC list looking for talkers.
+ CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
+ CNPCSimpleTalker *pTalker;
+
+ float flTime = gpGlobals->curtime + flDelay;
+
+ for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
+ {
+ if( ppAIs[i] != pIgnore )
+ {
+ pTalker = dynamic_cast<CNPCSimpleTalker *>(ppAIs[i]);
+
+ if( pTalker )
+ {
+ pTalker->m_flNextIdleSpeechTime = flTime;
+ }
+ }
+ }
+
+ BaseClass::DeferAllIdleSpeech( flDelay, pIgnore );
+}
+
+//=========================================================
+// FIdleSpeak
+// ask question of nearby friend, or make statement
+//=========================================================
+int CNPCSimpleTalker::FIdleSpeak( void )
+{
+ // try to start a conversation, or make statement
+ int pitch;
+
+ if (!IsOkToSpeak())
+ return false;
+
+ Assert( GetExpresser()->SemaphoreIsAvailable( this ) );
+
+ pitch = GetExpresser()->GetVoicePitch();
+
+ // player using this entity is alive and wounded?
+ CBaseEntity *pTarget = GetTarget();
+
+ if ( pTarget != NULL )
+ {
+ if ( pTarget->IsPlayer() )
+ {
+ if ( pTarget->IsAlive() )
+ {
+ SetSpeechTarget( GetTarget() );
+ if (GetExpresser()->CanSpeakConcept( TLK_PLHURT3) &&
+ (GetTarget()->m_iHealth <= GetTarget()->m_iMaxHealth / 8))
+ {
+ Speak( TLK_PLHURT3 );
+ return true;
+ }
+ else if (GetExpresser()->CanSpeakConcept( TLK_PLHURT2) &&
+ (GetTarget()->m_iHealth <= GetTarget()->m_iMaxHealth / 4))
+ {
+ Speak( TLK_PLHURT2 );
+ return true;
+ }
+ else if (GetExpresser()->CanSpeakConcept( TLK_PLHURT1) &&
+ (GetTarget()->m_iHealth <= GetTarget()->m_iMaxHealth / 2))
+ {
+ Speak( TLK_PLHURT1 );
+ return true;
+ }
+ }
+ else
+ {
+ //!!!KELLY - here's a cool spot to have the talkNPC talk about the dead player if we want.
+ // "Oh dear, Gordon Freeman is dead!" -Scientist
+ // "Damn, I can't do this without you." -Barney
+ }
+ }
+ }
+
+ // ROBIN: Disabled idle question & answer for now
+ /*
+ // if there is a friend nearby to speak to, play sentence, set friend's response time, return
+ CBaseEntity *pFriend = FindNearestFriend(false);
+
+ // 75% chance of talking to another citizen if one is available.
+ if (pFriend && !(pFriend->IsMoving()) && random->RandomInt( 0, 3 ) != 0 )
+ {
+ if ( SpeakQuestionFriend( pFriend ) )
+ {
+ // force friend to answer
+ CAI_PlayerAlly *pTalkNPC = dynamic_cast<CAI_PlayerAlly *>(pFriend);
+ if (pTalkNPC && !pTalkNPC->HasSpawnFlags(SF_NPC_GAG) && !pTalkNPC->IsInAScript() )
+ {
+ SetSpeechTarget( pFriend );
+ pTalkNPC->SetAnswerQuestion( this );
+ pTalkNPC->GetExpresser()->BlockSpeechUntil( GetExpresser()->GetTimeSpeechComplete() );
+
+ m_nSpeak++;
+ }
+
+ // Don't let anyone else butt in.
+ DeferAllIdleSpeech( random->RandomFloat( TALKER_DEFER_IDLE_SPEAK_MIN, TALKER_DEFER_IDLE_SPEAK_MAX ), pTalkNPC );
+ return true;
+ }
+ }
+ */
+
+ // Otherwise, play an idle statement, try to face client when making a statement.
+ CBaseEntity *pFriend = FindNearestFriend(true);
+ if ( pFriend )
+ {
+ SetSpeechTarget( pFriend );
+
+ // If we're about to talk to the player, and we've never said hello, say hello first
+ if ( !GetSpeechFilter() || !GetSpeechFilter()->NeverSayHello() )
+ {
+ if ( GetExpresser()->CanSpeakConcept( TLK_HELLO ) && !GetExpresser()->SpokeConcept( TLK_HELLO ) )
+ {
+ SayHelloToPlayer( pFriend );
+ return true;
+ }
+ }
+
+ if ( Speak( TLK_IDLE ) )
+ {
+ DeferAllIdleSpeech( random->RandomFloat( TALKER_DEFER_IDLE_SPEAK_MIN, TALKER_DEFER_IDLE_SPEAK_MAX ) );
+ m_nSpeak++;
+ }
+ else
+ {
+ // We failed to speak. Don't try again for a bit.
+ m_flNextIdleSpeechTime = gpGlobals->curtime + 3;
+ }
+
+ return true;
+ }
+
+ // didn't speak
+ m_flNextIdleSpeechTime = gpGlobals->curtime + 3;
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Speak the right question based upon who we're asking
+//-----------------------------------------------------------------------------
+bool CNPCSimpleTalker::SpeakQuestionFriend( CBaseEntity *pFriend )
+{
+ return Speak( TLK_QUESTION );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Speak the right answer based upon who we're answering
+//-----------------------------------------------------------------------------
+bool CNPCSimpleTalker::SpeakAnswerFriend( CBaseEntity *pFriend )
+{
+ return Speak( TLK_ANSWER );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPCSimpleTalker::FIdleSpeakWhileMoving( void )
+{
+ if ( GetExpresser()->CanSpeak() )
+ {
+ if (!GetExpresser()->IsSpeaking() || GetSpeechTarget() == NULL)
+ {
+ // override so that during walk, a scientist may talk and greet player
+ FIdleHello();
+
+ if ( ShouldSpeakRandom( m_nSpeak * 20, GetSpeechFilter() ? GetSpeechFilter()->GetIdleModifier() : 1.0 ) )
+ {
+ FIdleSpeak();
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPCSimpleTalker::PlayScriptedSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener )
+{
+ if ( !bConcurrent )
+ ShutUpFriends();
+
+ int sentenceIndex = BaseClass::PlayScriptedSentence( pszSentence, delay, volume, soundlevel, bConcurrent, pListener );
+ delay += engine->SentenceLength( sentenceIndex );
+ if ( delay < 0 )
+ delay = 0;
+ m_useTime = gpGlobals->curtime + delay;
+
+ // Stop all idle speech until after the sentence has completed
+ DeferAllIdleSpeech( delay + random->RandomInt( 3.0f, 5.0f ) );
+
+ return sentenceIndex;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell this NPC to answer a question from another NPC
+//-----------------------------------------------------------------------------
+void CNPCSimpleTalker::SetAnswerQuestion( CNPCSimpleTalker *pSpeaker )
+{
+ if ( !m_hCine )
+ {
+ SetCondition( COND_TALKER_RESPOND_TO_QUESTION );
+ }
+
+ SetSpeechTarget( (CAI_BaseNPC *)pSpeaker );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPCSimpleTalker::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ CTakeDamageInfo subInfo = info;
+
+ // if player damaged this entity, have other friends talk about it.
+ if (subInfo.GetAttacker() && (subInfo.GetAttacker()->GetFlags() & FL_CLIENT) && subInfo.GetDamage() < GetHealth() )
+ {
+ CBaseEntity *pFriend = FindNearestFriend(false);
+
+ if (pFriend && pFriend->IsAlive())
+ {
+ // only if not dead or dying!
+ CNPCSimpleTalker *pTalkNPC = (CNPCSimpleTalker *)pFriend;
+
+ if (pTalkNPC && pTalkNPC->IsOkToCombatSpeak())
+ {
+ pTalkNPC->Speak( TLK_NOSHOOT );
+ }
+ }
+ }
+ return BaseClass::OnTakeDamage_Alive( subInfo );
+}
+
+int CNPCSimpleTalker::SelectNonCombatSpeechSchedule()
+{
+ if ( !IsOkToSpeak() )
+ return SCHED_NONE;
+
+ // talk about world
+ if ( ShouldSpeakRandom( m_nSpeak * 2, GetSpeechFilter() ? GetSpeechFilter()->GetIdleModifier() : 1.0 ) )
+ {
+ //Msg("standing idle speak\n" );
+ return SCHED_TALKER_IDLE_SPEAK;
+ }
+
+ // failed to speak, so look at the player if he's around
+ if ( AI_IsSinglePlayer() && GetExpresser()->CanSpeak() && HasCondition ( COND_SEE_PLAYER ) && random->RandomInt( 0, 6 ) == 0 )
+ {
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+ Assert( pPlayer );
+
+ if ( pPlayer )
+ {
+ // watch the client.
+ Vector forward;
+ AngleVectors( pPlayer->GetLocalAngles(), &forward );
+ if ( ( pPlayer->GetAbsOrigin() - GetAbsOrigin() ).Length2D() < TALKER_STARE_DIST &&
+ UTIL_DotPoints( pPlayer->GetAbsOrigin(), GetAbsOrigin(), forward ) >= m_flFieldOfView )
+ {
+ // go into the special STARE schedule if the player is close, and looking at me too.
+ return SCHED_TALKER_IDLE_WATCH_CLIENT_STARE;
+ }
+
+ return SCHED_TALKER_IDLE_WATCH_CLIENT;
+ }
+ }
+ else
+ {
+ // look at who we're talking to
+ if ( GetSpeechTarget() && GetExpresser()->IsSpeaking() )
+ return SCHED_TALKER_IDLE_EYE_CONTACT;
+ }
+ return SCHED_NONE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPCSimpleTalker::CanSayHello( void )
+{
+#ifndef HL1_DLL
+ if ( Classify() == CLASS_PLAYER_ALLY_VITAL )
+ return false;
+#endif
+
+ if ( GetSpeechFilter() && GetSpeechFilter()->NeverSayHello() )
+ return false;
+
+ if ( !GetExpresser()->CanSpeakConcept(TLK_HELLO) || GetExpresser()->SpokeConcept(TLK_HELLO) )
+ return false;
+
+ if ( !IsOkToSpeak() )
+ return false;
+
+ return true;
+}
+
+void CNPCSimpleTalker::OnStartingFollow( CBaseEntity *pTarget )
+{
+ GetExpresser()->SetSpokeConcept( TLK_HELLO, NULL ); // Don't say hi after you've started following
+ if ( IsOkToSpeak() ) // don't speak if idle talk is blocked. player commanded/use follow will always speak
+ Speak( TLK_STARTFOLLOW );
+ SetSpeechTarget( GetTarget() );
+ ClearCondition( COND_PLAYER_PUSHING );
+}
+
+void CNPCSimpleTalker::OnStoppingFollow( CBaseEntity *pTarget )
+{
+ if ( !(m_afMemory & bits_MEMORY_PROVOKED) )
+ {
+ if ( IsOkToCombatSpeak() )
+ {
+ if ( pTarget == NULL )
+ Speak( TLK_STOPFOLLOW );
+ else
+ Speak( TLK_STOP );
+ }
+ SetSpeechTarget( FindNearestFriend(true) );
+ }
+}
+
+void CNPCSimpleTalker::FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ // Don't allow use during a scripted_sentence
+ if ( m_useTime > gpGlobals->curtime )
+ return;
+
+ if ( pCaller != NULL && pCaller->IsPlayer() )
+ {
+ if ( !m_FollowBehavior.GetFollowTarget() && IsInterruptable() )
+ {
+#if TOML_TODO
+ LimitFollowers( pCaller , 1 );
+#endif
+
+ if ( m_afMemory & bits_MEMORY_PROVOKED )
+ Msg( "I'm not following you, you evil person!\n" );
+ else
+ {
+ StartFollowing( pCaller );
+ }
+ }
+ else
+ {
+ StopFollowing();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CNPCSimpleTalker::InputIdleRespond( inputdata_t &inputdata )
+{
+ // We've been told to respond. Check combat speak, not isoktospeak, because
+ // we don't want to check the idle speech time.
+ if (!IsOkToCombatSpeak())
+ return;
+
+ IdleRespond();
+}
+
+int CNPCSimpleTalkerExpresser::SpeakRawSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, CBaseEntity *pListener )
+{
+ char szSpecificSentence[1024];
+ int sentenceIndex = -1;
+
+ if ( !pszSentence )
+ return sentenceIndex;
+
+ if ( pszSentence[0] == AI_SP_START_MONOLOG )
+ {
+ // this sentence command will start this NPC speaking
+ // lengthy monolog from smaller sentences.
+ BeginMonolog( (char *)pszSentence, pListener );
+ return -1;
+ }
+ else if ( pszSentence[0] == AI_SP_MONOLOG_LINE )
+ {
+ Q_strncpy(szSpecificSentence, pszSentence, sizeof(szSpecificSentence) );
+ szSpecificSentence[0] = AI_SP_SPECIFIC_SENTENCE;
+ pszSentence = szSpecificSentence;
+ }
+ else
+ {
+ // this bit of speech is interrupting my monolog!
+ SuspendMonolog( 0 );
+ }
+
+ return CAI_Expresser::SpeakRawSentence( pszSentence, delay, volume, soundlevel, pListener );
+}
+
+//-------------------------------------
+
+void CNPCSimpleTalkerExpresser::BeginMonolog( char *pszSentenceName, CBaseEntity *pListener )
+{
+ if( pListener )
+ {
+ m_hMonologTalkTarget = pListener;
+ }
+ else
+ {
+ Warning( "NULL Listener in BeginMonolog()!\n" );
+ Assert(0);
+ EndMonolog();
+ return;
+ }
+
+ Q_strncpy( m_szMonologSentence, pszSentenceName ,sizeof(m_szMonologSentence));
+
+ // change the "AI_SP_START_MONOLOG" to an "AI_SP_MONOLOG_LINE". m_sMonologSentence is now the
+ // string we'll tack numbers onto to play sentences from this group in
+ // sequential order.
+ m_szMonologSentence[0] = AI_SP_MONOLOG_LINE;
+
+ m_fMonologSuspended = false;
+
+ m_iMonologIndex = 0;
+}
+
+//-------------------------------------
+
+void CNPCSimpleTalkerExpresser::EndMonolog( void )
+{
+ m_szMonologSentence[0] = 0;
+ m_iMonologIndex = -1;
+ m_fMonologSuspended = false;
+ m_hMonologTalkTarget = NULL;
+}
+
+//-------------------------------------
+
+void CNPCSimpleTalkerExpresser::SpeakMonolog( void )
+{
+ int i;
+ char szSentence[ MONOLOGNAME_LEN ];
+
+ if( !HasMonolog() )
+ {
+ return;
+ }
+
+ if( CanSpeak() )
+ {
+ if( m_fMonologSuspended )
+ {
+ if ( GetOuter()->ShouldResumeMonolog() )
+ {
+ ResumeMonolog();
+ }
+
+ return;
+ }
+
+ Q_snprintf( szSentence,sizeof(szSentence), "%s%d", m_szMonologSentence, m_iMonologIndex );
+ m_iMonologIndex++;
+
+ i = SpeakRawSentence( szSentence, 0, VOL_NORM );
+
+ if ( i == -1 )
+ {
+ EndMonolog();
+ }
+ }
+ else
+ {
+ if( GetOuter()->ShouldSuspendMonolog() )
+ {
+ SuspendMonolog( 0 );
+ }
+ }
+}
+
+//-------------------------------------
+
+void CNPCSimpleTalkerExpresser::SuspendMonolog( float flInterval )
+{
+ if( HasMonolog() )
+ {
+ m_fMonologSuspended = true;
+ }
+
+ // free up other characters to speak.
+ if ( GetSink()->UseSemaphore() )
+ {
+ GetSpeechSemaphore( GetOuter() )->Release();
+ }
+}
+
+//-------------------------------------
+
+void CNPCSimpleTalkerExpresser::ResumeMonolog( void )
+{
+ if( m_iMonologIndex > 0 )
+ {
+ // back up and repeat what I was saying
+ // when interrupted.
+ m_iMonologIndex--;
+ }
+
+ GetOuter()->OnResumeMonolog();
+ m_fMonologSuspended = false;
+}
+
+// try to smell something
+void CNPCSimpleTalker::TrySmellTalk( void )
+{
+ if ( !IsOkToSpeak() )
+ return;
+
+ if ( HasCondition( COND_SMELL ) && GetExpresser()->CanSpeakConcept( TLK_SMELL ) )
+ Speak( TLK_SMELL );
+}
+
+void CNPCSimpleTalker::OnChangeRunningBehavior( CAI_BehaviorBase *pOldBehavior, CAI_BehaviorBase *pNewBehavior )
+{
+ BaseClass::OnChangeRunningBehavior( pOldBehavior, pNewBehavior );
+
+ CAI_FollowBehavior *pFollowBehavior;
+ if ( ( pFollowBehavior = dynamic_cast<CAI_FollowBehavior *>(pNewBehavior) ) != NULL )
+ {
+ OnStartingFollow( pFollowBehavior->GetFollowTarget() );
+ }
+ else if ( ( pFollowBehavior = dynamic_cast<CAI_FollowBehavior *>(pOldBehavior) ) != NULL )
+ {
+ OnStoppingFollow( pFollowBehavior->GetFollowTarget() );
+ }
+}
+
+
+bool CNPCSimpleTalker::OnBehaviorChangeStatus( CAI_BehaviorBase *pBehavior, bool fCanFinishSchedule )
+{
+ bool interrupt = BaseClass::OnBehaviorChangeStatus( pBehavior, fCanFinishSchedule );
+ if ( !interrupt )
+ {
+ interrupt = ( dynamic_cast<CAI_FollowBehavior *>(pBehavior) != NULL && ( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT ) );
+ }
+ return interrupt;
+
+}
+//-----------------------------------------------------------------------------
+// Purpose: Return true if I should speak based on the chance & the speech filter's modifier
+//-----------------------------------------------------------------------------
+bool CNPCSimpleTalker::ShouldSpeakRandom( int iChance, float flModifier )
+{
+ if ( flModifier != 1.0 )
+ {
+ // Avoid divide by zero
+ if ( !flModifier )
+ return false;
+
+ iChance = floor( (float)iChance / flModifier );
+ }
+
+ return (random->RandomInt(0,iChance) == 0);
+}
+
+
+AI_BEGIN_CUSTOM_NPC(talk_monster,CNPCSimpleTalker)
+ DECLARE_USES_SCHEDULE_PROVIDER( CAI_FollowBehavior )
+
+ DECLARE_TASK(TASK_TALKER_RESPOND)
+ DECLARE_TASK(TASK_TALKER_SPEAK)
+ DECLARE_TASK(TASK_TALKER_HELLO)
+ DECLARE_TASK(TASK_TALKER_BETRAYED)
+ DECLARE_TASK(TASK_TALKER_HEADRESET)
+ DECLARE_TASK(TASK_TALKER_STOPSHOOTING)
+ DECLARE_TASK(TASK_TALKER_STARE)
+ DECLARE_TASK(TASK_TALKER_LOOK_AT_CLIENT)
+ DECLARE_TASK(TASK_TALKER_CLIENT_STARE)
+ DECLARE_TASK(TASK_TALKER_EYECONTACT)
+ DECLARE_TASK(TASK_TALKER_IDEALYAW)
+ DECLARE_TASK(TASK_TALKER_WAIT_FOR_SEMAPHORE)
+
+ //=========================================================
+ // > SCHED_TALKER_IDLE_RESPONSE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_TALKER_IDLE_RESPONSE,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Stop and listen
+ " TASK_WAIT 0.5" // Wait until sure it's me they are talking to
+ " TASK_TALKER_IDEALYAW 0" // look at who I'm talking to
+ " TASK_FACE_IDEAL 0"
+ " TASK_TALKER_EYECONTACT 0" // Wait until speaker is done
+ " TASK_TALKER_WAIT_FOR_SEMAPHORE 0"
+ " TASK_TALKER_EYECONTACT 0" // Wait until speaker is done
+ " TASK_TALKER_RESPOND 0" // Wait and then say my response
+ " TASK_TALKER_IDEALYAW 0" // look at who I'm talking to
+ " TASK_FACE_IDEAL 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_SIGNAL3"
+ " TASK_TALKER_EYECONTACT 0" // Wait until speaker is done
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ " COND_PLAYER_PUSHING"
+ " COND_GIVE_WAY"
+ )
+
+ //=========================================================
+ // > SCHED_TALKER_IDLE_SPEAK
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_TALKER_IDLE_SPEAK,
+
+ " Tasks"
+ " TASK_TALKER_SPEAK 0" // question or remark
+ " TASK_TALKER_IDEALYAW 0" // look at who I'm talking to
+ " TASK_FACE_IDEAL 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_SIGNAL3"
+ " TASK_TALKER_EYECONTACT 0"
+ " TASK_WAIT_RANDOM 0.5"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ " COND_PLAYER_PUSHING"
+ " COND_GIVE_WAY"
+ )
+
+ //=========================================================
+ // > SCHED_TALKER_IDLE_HELLO
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_TALKER_IDLE_HELLO,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_SIGNAL3" // Stop and talk
+ " TASK_TALKER_HELLO 0" // Try to say hello to player
+ " TASK_TALKER_EYECONTACT 0"
+ " TASK_WAIT 0.5" // wait a bit
+ " TASK_TALKER_HELLO 0" // Try to say hello to player
+ " TASK_TALKER_EYECONTACT 0"
+ " TASK_WAIT 0.5" // wait a bit
+ " TASK_TALKER_HELLO 0" // Try to say hello to player
+ " TASK_TALKER_EYECONTACT 0"
+ " TASK_WAIT 0.5" // wait a bit
+ " TASK_TALKER_HELLO 0" // Try to say hello to player
+ " TASK_TALKER_EYECONTACT 0"
+ " TASK_WAIT 0.5 " // wait a bit
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_PROVOKED"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_DANGER"
+ " COND_PLAYER_PUSHING"
+ " COND_GIVE_WAY"
+ )
+
+ //=========================================================
+ // > SCHED_TALKER_IDLE_STOP_SHOOTING
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_TALKER_IDLE_STOP_SHOOTING,
+
+ " Tasks"
+ " TASK_TALKER_STOPSHOOTING 0" // tell player to stop shooting friend
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // Scold the player before attacking.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_TALKER_BETRAYED,
+
+ " Tasks"
+ " TASK_TALKER_BETRAYED 0"
+ " TASK_WAIT 0.5"
+ ""
+ " Interrupts"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_TALKER_IDLE_WATCH_CLIENT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_TALKER_IDLE_WATCH_CLIENT,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_TALKER_LOOK_AT_CLIENT 6"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_PROVOKED"
+ " COND_HEAR_COMBAT" // sound flags - change these and you'll break the talking code.
+ " COND_HEAR_DANGER"
+ " COND_SMELL"
+ " COND_PLAYER_PUSHING"
+ " COND_TALKER_CLIENTUNSEEN"
+ " COND_GIVE_WAY"
+ " COND_IDLE_INTERRUPT"
+ )
+
+ //=========================================================
+ // > SCHED_TALKER_IDLE_WATCH_CLIENT_STARE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_TALKER_IDLE_WATCH_CLIENT_STARE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_TALKER_CLIENT_STARE 6"
+ " TASK_TALKER_STARE 0"
+ " TASK_TALKER_IDEALYAW 0" // look at who I'm talking to
+ " TASK_FACE_IDEAL 0 "
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_SIGNAL3"
+ " TASK_TALKER_EYECONTACT 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_PROVOKED"
+ " COND_HEAR_COMBAT" // sound flags - change these and you'll break the talking code.
+ " COND_HEAR_DANGER"
+ " COND_SMELL"
+ " COND_PLAYER_PUSHING"
+ " COND_TALKER_CLIENTUNSEEN"
+ " COND_GIVE_WAY"
+ " COND_IDLE_INTERRUPT"
+ )
+
+ //=========================================================
+ // > SCHED_TALKER_IDLE_EYE_CONTACT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_TALKER_IDLE_EYE_CONTACT,
+
+ " Tasks"
+ " TASK_TALKER_IDEALYAW 0" // look at who I'm talking to
+ " TASK_FACE_IDEAL 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_SIGNAL3"
+ " TASK_TALKER_EYECONTACT 0" // Wait until speaker is done
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ " COND_PLAYER_PUSHING"
+ " COND_GIVE_WAY"
+ " COND_IDLE_INTERRUPT"
+ )
+
+AI_END_CUSTOM_NPC()