//========= Copyright © 1996-2005, 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(GetExpresser()))->SpeakMonolog(); } bool CNPCSimpleTalker::ShouldSuspendMonolog( void ) { float flDist; flDist = ((assert_cast(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(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( 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(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(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(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 ) { if ( Classify() == CLASS_PLAYER_ALLY_VITAL ) return false; 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(pNewBehavior) ) != NULL ) { OnStartingFollow( pFollowBehavior->GetFollowTarget() ); } else if ( ( pFollowBehavior = dynamic_cast(pOldBehavior) ) != NULL ) { OnStoppingFollow( pFollowBehavior->GetFollowTarget() ); } } bool CNPCSimpleTalker::OnBehaviorChangeStatus( CAI_BehaviorBase *pBehavior, bool fCanFinishSchedule ) { bool interrupt = BaseClass::OnBehaviorChangeStatus( pBehavior, fCanFinishSchedule ); if ( !interrupt ) { interrupt = ( dynamic_cast(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()