You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1353 lines
35 KiB
1353 lines
35 KiB
//========= 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()
|
|
|