summaryrefslogtreecommitdiff
path: root/game/server/hl1/hl1_npc_scientist.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/hl1/hl1_npc_scientist.cpp')
-rw-r--r--game/server/hl1/hl1_npc_scientist.cpp1410
1 files changed, 1410 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_npc_scientist.cpp b/game/server/hl1/hl1_npc_scientist.cpp
new file mode 100644
index 0000000..d4f0bd9
--- /dev/null
+++ b/game/server/hl1/hl1_npc_scientist.cpp
@@ -0,0 +1,1410 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Bullseyes act as targets for other NPC's to attack and to trigger
+// events
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "ai_default.h"
+#include "ai_task.h"
+#include "ai_schedule.h"
+#include "ai_node.h"
+#include "ai_hull.h"
+#include "ai_hint.h"
+#include "ai_memory.h"
+#include "ai_route.h"
+#include "ai_motor.h"
+#include "hl1_npc_scientist.h"
+#include "soundent.h"
+#include "game.h"
+#include "npcevent.h"
+#include "entitylist.h"
+#include "activitylist.h"
+#include "animation.h"
+#include "engine/IEngineSound.h"
+#include "ai_navigator.h"
+#include "ai_behavior_follow.h"
+#include "AI_Criteria.h"
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+
+#define SC_PLFEAR "SC_PLFEAR"
+#define SC_FEAR "SC_FEAR"
+#define SC_HEAL "SC_HEAL"
+#define SC_SCREAM "SC_SCREAM"
+#define SC_POK "SC_POK"
+
+ConVar sk_scientist_health( "sk_scientist_health","20");
+ConVar sk_scientist_heal( "sk_scientist_heal","25");
+
+#define NUM_SCIENTIST_HEADS 4 // four heads available for scientist model
+enum { HEAD_GLASSES = 0, HEAD_EINSTEIN = 1, HEAD_LUTHER = 2, HEAD_SLICK = 3 };
+
+
+int ACT_EXCITED;
+
+//=========================================================
+// Makes it fast to check barnacle classnames in
+// IsValidEnemy()
+//=========================================================
+string_t s_iszBarnacleClassname;
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define SCIENTIST_AE_HEAL ( 1 )
+#define SCIENTIST_AE_NEEDLEON ( 2 )
+#define SCIENTIST_AE_NEEDLEOFF ( 3 )
+
+//=======================================================
+// Scientist
+//=======================================================
+
+LINK_ENTITY_TO_CLASS( monster_scientist, CNPC_Scientist );
+
+//IMPLEMENT_SERVERCLASS_ST( CNPC_Scientist, DT_NPC_Scientist )
+//END_SEND_TABLE()
+
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CNPC_Scientist )
+ DEFINE_FIELD( m_flFearTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flHealTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flPainTime, FIELD_TIME ),
+
+ DEFINE_THINKFUNC( SUB_LVFadeOut ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+void CNPC_Scientist::Precache( void )
+{
+ PrecacheModel( "models/scientist.mdl" );
+
+ PrecacheScriptSound( "Scientist.Pain" );
+
+ TalkInit();
+
+ BaseClass::Precache();
+}
+
+void CNPC_Scientist::ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet )
+{
+ BaseClass::ModifyOrAppendCriteria( criteriaSet );
+
+ bool predisaster = FBitSet( m_spawnflags, SF_NPC_PREDISASTER ) ? true : false;
+
+ criteriaSet.AppendCriteria( "disaster", predisaster ? "[disaster::pre]" : "[disaster::post]" );
+}
+
+// Init talk data
+void CNPC_Scientist::TalkInit()
+{
+
+ BaseClass::TalkInit();
+
+ // scientist will try to talk to friends in this order:
+
+ m_szFriends[0] = "monster_scientist";
+ m_szFriends[1] = "monster_sitting_scientist";
+ m_szFriends[2] = "monster_barney";
+
+ // get voice for head
+ switch (m_nBody % 3)
+ {
+ default:
+ case HEAD_GLASSES: GetExpresser()->SetVoicePitch( 105 ); break; //glasses
+ case HEAD_EINSTEIN: GetExpresser()->SetVoicePitch( 100 ); break; //einstein
+ case HEAD_LUTHER: GetExpresser()->SetVoicePitch( 95 ); break; //luther
+ case HEAD_SLICK: GetExpresser()->SetVoicePitch( 100 ); break;//slick
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+void CNPC_Scientist::Spawn( void )
+{
+
+ //Select the body first if it's going to be random cause we set his voice pitch in Precache.
+ if ( m_nBody == -1 )
+ m_nBody = random->RandomInt( 0, NUM_SCIENTIST_HEADS-1 );// pick a head, any head
+
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ Precache();
+
+ SetModel( "models/scientist.mdl" );
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_bloodColor = BLOOD_COLOR_RED;
+ ClearEffects();
+ m_iHealth = sk_scientist_health.GetFloat();
+ m_flFieldOfView = VIEW_FIELD_WIDE;
+ m_NPCState = NPC_STATE_NONE;
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE );
+ CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_ANIMATEDFACE );
+
+ // White hands
+ m_nSkin = 0;
+
+
+ // Luther is black, make his hands black
+ if ( m_nBody == HEAD_LUTHER )
+ m_nSkin = 1;
+
+ NPCInit();
+
+ SetUse( &CNPC_Scientist::FollowerUse );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Scientist::Activate()
+{
+ s_iszBarnacleClassname = FindPooledString( "monster_barnacle" );
+ BaseClass::Activate();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+// Output :
+//-----------------------------------------------------------------------------
+Class_T CNPC_Scientist::Classify( void )
+{
+ return CLASS_HUMAN_PASSIVE;
+}
+
+int CNPC_Scientist::GetSoundInterests ( void )
+{
+ return SOUND_WORLD |
+ SOUND_COMBAT |
+ SOUND_DANGER |
+ SOUND_PLAYER;
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//=========================================================
+void CNPC_Scientist::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case SCIENTIST_AE_HEAL: // Heal my target (if within range)
+ Heal();
+ break;
+ case SCIENTIST_AE_NEEDLEON:
+ {
+ int oldBody = m_nBody;
+ m_nBody = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 1;
+ }
+ break;
+ case SCIENTIST_AE_NEEDLEOFF:
+ {
+ int oldBody = m_nBody;
+ m_nBody = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 0;
+ }
+ break;
+
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+void CNPC_Scientist::DeclineFollowing( void )
+{
+ if ( CanSpeakAfterMyself() )
+ {
+ Speak( SC_POK );
+ }
+}
+
+bool CNPC_Scientist::CanBecomeRagdoll( void )
+{
+ if ( UTIL_IsLowViolence() )
+ {
+ return false;
+ }
+
+ return BaseClass::CanBecomeRagdoll();
+}
+
+bool CNPC_Scientist::ShouldGib( const CTakeDamageInfo &info )
+{
+ if ( UTIL_IsLowViolence() )
+ {
+ return false;
+ }
+
+ return BaseClass::ShouldGib( info );
+}
+
+void CNPC_Scientist::SUB_StartLVFadeOut( float delay, bool notSolid )
+{
+ SetThink( &CNPC_Scientist::SUB_LVFadeOut );
+ SetNextThink( gpGlobals->curtime + delay );
+ SetRenderColorA( 255 );
+ m_nRenderMode = kRenderNormal;
+
+ if ( notSolid )
+ {
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ SetLocalAngularVelocity( vec3_angle );
+ }
+}
+
+void CNPC_Scientist::SUB_LVFadeOut( void )
+{
+ if( VPhysicsGetObject() )
+ {
+ if( VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD || GetEFlags() & EFL_IS_BEING_LIFTED_BY_BARNACLE )
+ {
+ // Try again in a few seconds.
+ SetNextThink( gpGlobals->curtime + 5 );
+ SetRenderColorA( 255 );
+ return;
+ }
+ }
+
+ float dt = gpGlobals->frametime;
+ if ( dt > 0.1f )
+ {
+ dt = 0.1f;
+ }
+ m_nRenderMode = kRenderTransTexture;
+ int speed = MAX(3,256*dt); // fade out over 3 seconds
+ SetRenderColorA( UTIL_Approach( 0, m_clrRender->a, speed ) );
+ NetworkStateChanged();
+
+ if ( m_clrRender->a == 0 )
+ {
+ UTIL_Remove(this);
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime );
+ }
+}
+
+void CNPC_Scientist::Scream( void )
+{
+ if ( IsOkToSpeak() )
+ {
+ GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 10 );
+ SetSpeechTarget( GetEnemy() );
+ Speak( SC_SCREAM );
+ }
+}
+
+Activity CNPC_Scientist::GetStoppedActivity( void )
+{
+ if ( GetEnemy() != NULL )
+ return (Activity)ACT_EXCITED;
+
+ return BaseClass::GetStoppedActivity();
+}
+
+float CNPC_Scientist::MaxYawSpeed( void )
+{
+ switch( GetActivity() )
+ {
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ return 160;
+ break;
+ case ACT_RUN:
+ return 160;
+ break;
+ default:
+ return 60;
+ break;
+ }
+}
+
+void CNPC_Scientist::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_SAY_HEAL:
+
+ GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 2 );
+ SetSpeechTarget( GetTarget() );
+ Speak( SC_HEAL );
+
+ TaskComplete();
+ break;
+
+ case TASK_SCREAM:
+ Scream();
+ TaskComplete();
+ break;
+
+ case TASK_RANDOM_SCREAM:
+ if ( random->RandomFloat( 0, 1 ) < pTask->flTaskData )
+ Scream();
+ TaskComplete();
+ break;
+
+ case TASK_SAY_FEAR:
+ if ( IsOkToSpeak() )
+ {
+ GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 2 );
+ SetSpeechTarget( GetEnemy() );
+ if ( GetEnemy() && GetEnemy()->IsPlayer() )
+ Speak( SC_PLFEAR );
+ else
+ Speak( SC_FEAR );
+ }
+ TaskComplete();
+ break;
+
+ case TASK_HEAL:
+ SetIdealActivity( ACT_MELEE_ATTACK1 );
+ break;
+
+ case TASK_RUN_PATH_SCARED:
+ GetNavigator()->SetMovementActivity( ACT_RUN_SCARED );
+ break;
+
+ case TASK_MOVE_TO_TARGET_RANGE_SCARED:
+ {
+ if ( GetTarget() == NULL)
+ {
+ TaskFail(FAIL_NO_TARGET);
+ }
+ else if ( (GetTarget()->GetAbsOrigin() - GetAbsOrigin()).Length() < 1 )
+ {
+ TaskComplete();
+ }
+ }
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+void CNPC_Scientist::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_RUN_PATH_SCARED:
+ if ( !IsMoving() )
+ TaskComplete();
+ if ( random->RandomInt(0,31) < 8 )
+ Scream();
+ break;
+
+ case TASK_MOVE_TO_TARGET_RANGE_SCARED:
+ {
+ float distance;
+
+ if ( GetTarget() == NULL )
+ {
+ TaskFail(FAIL_NO_TARGET);
+ }
+ else
+ {
+ distance = ( GetNavigator()->GetPath()->ActualGoalPosition() - GetAbsOrigin() ).Length2D();
+ // Re-evaluate when you think your finished, or the target has moved too far
+ if ( (distance < pTask->flTaskData) || (GetNavigator()->GetPath()->ActualGoalPosition() - GetTarget()->GetAbsOrigin()).Length() > pTask->flTaskData * 0.5 )
+ {
+ GetNavigator()->GetPath()->ResetGoalPosition(GetTarget()->GetAbsOrigin());
+ distance = ( GetNavigator()->GetPath()->ActualGoalPosition() - GetAbsOrigin() ).Length2D();
+// GetNavigator()->GetPath()->Find();
+ GetNavigator()->SetGoal( GOALTYPE_TARGETENT );
+ }
+
+ // Set the appropriate activity based on an overlapping range
+ // overlap the range to prevent oscillation
+ // BUGBUG: this is checking linear distance (ie. through walls) and not path distance or even visibility
+ if ( distance < pTask->flTaskData )
+ {
+ TaskComplete();
+ GetNavigator()->GetPath()->Clear(); // Stop moving
+ }
+ else
+ {
+ if ( distance < 190 && GetNavigator()->GetMovementActivity() != ACT_WALK_SCARED )
+ GetNavigator()->SetMovementActivity( ACT_WALK_SCARED );
+ else if ( distance >= 270 && GetNavigator()->GetMovementActivity() != ACT_RUN_SCARED )
+ GetNavigator()->SetMovementActivity( ACT_RUN_SCARED );
+ }
+ }
+ }
+ break;
+
+ case TASK_HEAL:
+ if ( IsSequenceFinished() )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ if ( TargetDistance() > 90 )
+ TaskComplete();
+
+ if ( GetTarget() )
+ GetMotor()->SetIdealYaw( UTIL_VecToYaw( GetTarget()->GetAbsOrigin() - GetAbsOrigin() ) );
+
+ //GetMotor()->SetYawSpeed( m_YawSpeed );
+ }
+ break;
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+int CNPC_Scientist::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+
+ if ( inputInfo.GetInflictor() && inputInfo.GetInflictor()->GetFlags() & FL_CLIENT )
+ {
+ Remember( bits_MEMORY_PROVOKED );
+ StopFollowing();
+ }
+
+ // make sure friends talk about it if player hurts scientist...
+ return BaseClass::OnTakeDamage_Alive( inputInfo );
+}
+
+void CNPC_Scientist::Event_Killed( const CTakeDamageInfo &info )
+{
+ SetUse( NULL );
+ BaseClass::Event_Killed( info );
+
+ if ( UTIL_IsLowViolence() )
+ {
+ SUB_StartLVFadeOut( 0.0f );
+ }
+}
+
+bool CNPC_Scientist::CanHeal( void )
+{
+ CBaseEntity *pTarget = GetFollowTarget();
+
+ if ( pTarget == NULL )
+ return false;
+
+ if ( pTarget->IsPlayer() == false )
+ return false;
+
+ if ( (m_flHealTime > gpGlobals->curtime) || (pTarget->m_iHealth > (pTarget->m_iMaxHealth * 0.5)) )
+ return false;
+
+ return true;
+}
+
+//=========================================================
+// PainSound
+//=========================================================
+void CNPC_Scientist::PainSound ( const CTakeDamageInfo &info )
+{
+ if (gpGlobals->curtime < m_flPainTime )
+ return;
+
+ m_flPainTime = gpGlobals->curtime + random->RandomFloat( 0.5, 0.75 );
+
+ CPASAttenuationFilter filter( this );
+
+ CSoundParameters params;
+ if ( GetParametersForSound( "Scientist.Pain", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ params.pitch = GetExpresser()->GetVoicePitch();
+
+ EmitSound( filter, entindex(), ep );
+ }
+}
+
+//=========================================================
+// DeathSound
+//=========================================================
+void CNPC_Scientist::DeathSound( const CTakeDamageInfo &info )
+{
+ PainSound( info );
+}
+
+
+void CNPC_Scientist::Heal( void )
+{
+ if ( !CanHeal() )
+ return;
+
+ Vector target = GetFollowTarget()->GetAbsOrigin() - GetAbsOrigin();
+ if ( target.Length() > 100 )
+ return;
+
+ GetTarget()->TakeHealth( sk_scientist_heal.GetFloat(), DMG_GENERIC );
+ // Don't heal again for 1 minute
+ m_flHealTime = gpGlobals->curtime + 60;
+}
+
+int CNPC_Scientist::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ // Hook these to make a looping schedule
+ case SCHED_TARGET_FACE:
+ {
+ int baseType;
+
+ // call base class default so that scientist will talk
+ // when 'used'
+ baseType = BaseClass::TranslateSchedule( scheduleType );
+
+ if (baseType == SCHED_IDLE_STAND)
+ return SCHED_TARGET_FACE; // override this for different target face behavior
+ else
+ return baseType;
+ }
+ break;
+
+ case SCHED_TARGET_CHASE:
+ return SCHED_SCI_FOLLOWTARGET;
+ break;
+
+ case SCHED_IDLE_STAND:
+ {
+ int baseType;
+
+ baseType = BaseClass::TranslateSchedule( scheduleType );
+
+ if (baseType == SCHED_IDLE_STAND)
+ return SCHED_SCI_IDLESTAND; // override this for different target face behavior
+ else
+ return baseType;
+ }
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+Activity CNPC_Scientist::NPC_TranslateActivity( Activity newActivity )
+{
+ if ( GetFollowTarget() && GetEnemy() )
+ {
+ CBaseEntity *pEnemy = GetEnemy();
+
+ int relationship = D_NU;
+
+ // Nothing scary, just me and the player
+ if ( pEnemy != NULL )
+ relationship = IRelationType( pEnemy );
+
+ if ( relationship == D_HT || relationship == D_FR )
+ {
+ if ( newActivity == ACT_WALK )
+ return ACT_WALK_SCARED;
+ else if ( newActivity == ACT_RUN )
+ return ACT_RUN_SCARED;
+ }
+ }
+
+ return BaseClass::NPC_TranslateActivity( newActivity );
+}
+
+int CNPC_Scientist::SelectSchedule( void )
+{
+ if( m_NPCState == NPC_STATE_PRONE )
+ {
+ // Immediately call up to the talker code. Barnacle death is priority schedule.
+ return BaseClass::SelectSchedule();
+ }
+
+ // so we don't keep calling through the EHANDLE stuff
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if ( GetFollowTarget() )
+ {
+ // so we don't keep calling through the EHANDLE stuff
+ CBaseEntity *pEnemy = GetEnemy();
+
+ int relationship = D_NU;
+
+ // Nothing scary, just me and the player
+ if ( pEnemy != NULL )
+ relationship = IRelationType( pEnemy );
+
+ if ( relationship != D_HT && relationship != D_FR )
+ {
+ // If I'm already close enough to my target
+ if ( TargetDistance() <= 128 )
+ {
+ if ( CanHeal() ) // Heal opportunistically
+ {
+ SetTarget( GetFollowTarget() );
+ return SCHED_SCI_HEAL;
+ }
+ }
+ }
+ }
+ else if ( HasCondition( COND_PLAYER_PUSHING ) && !(GetSpawnFlags() & SF_NPC_PREDISASTER ) )
+ { // Player wants me to move
+ return SCHED_HL1TALKER_FOLLOW_MOVE_AWAY;
+ }
+
+ if ( BehaviorSelectSchedule() )
+ {
+ return BaseClass::SelectSchedule();
+ }
+
+
+
+ if ( HasCondition( COND_HEAR_DANGER ) && m_NPCState != NPC_STATE_PRONE )
+ {
+ CSound *pSound;
+ pSound = GetBestSound();
+
+ if ( pSound && pSound->IsSoundType(SOUND_DANGER) )
+ return SCHED_TAKE_COVER_FROM_BEST_SOUND;
+ }
+
+ switch( m_NPCState )
+ {
+
+ case NPC_STATE_ALERT:
+ case NPC_STATE_IDLE:
+
+ if ( pEnemy )
+ {
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ m_flFearTime = gpGlobals->curtime;
+ else if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert
+ {
+ SetEnemy( NULL );
+ pEnemy = NULL;
+ }
+ }
+
+ if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ // flinch if hurt
+ return SCHED_SMALL_FLINCH;
+ }
+
+ // Cower when you hear something scary
+ if ( HasCondition( COND_HEAR_DANGER ) || HasCondition( COND_HEAR_COMBAT ) )
+ {
+ CSound *pSound;
+ pSound = GetBestSound();
+
+ if ( pSound )
+ {
+ if ( pSound->IsSoundType(SOUND_DANGER | SOUND_COMBAT) )
+ {
+ if ( gpGlobals->curtime - m_flFearTime > 3 ) // Only cower every 3 seconds or so
+ {
+ m_flFearTime = gpGlobals->curtime; // Update last fear
+ return SCHED_SCI_STARTLE; // This will just duck for a second
+ }
+ }
+ }
+ }
+
+ if ( GetFollowTarget() )
+ {
+ if ( !GetFollowTarget()->IsAlive() )
+ {
+ // UNDONE: Comment about the recently dead player here?
+ StopFollowing();
+ break;
+ }
+
+ int relationship = D_NU;
+
+ // Nothing scary, just me and the player
+ if ( pEnemy != NULL )
+ relationship = IRelationType( pEnemy );
+
+ if ( relationship != D_HT )
+ {
+ return SCHED_TARGET_FACE; // Just face and follow.
+ }
+ else // UNDONE: When afraid, scientist won't move out of your way. Keep This? If not, write move away scared
+ {
+ if ( HasCondition( COND_NEW_ENEMY ) ) // I just saw something new and scary, react
+ return SCHED_SCI_FEAR; // React to something scary
+ return SCHED_SCI_FACETARGETSCARED; // face and follow, but I'm scared!
+ }
+ }
+
+ // try to say something about smells
+ TrySmellTalk();
+ break;
+
+
+ case NPC_STATE_COMBAT:
+
+ if ( HasCondition( COND_NEW_ENEMY ) )
+ return SCHED_SCI_FEAR; // Point and scream!
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ return SCHED_SCI_COVER; // Take Cover
+
+ if ( HasCondition( COND_HEAR_COMBAT ) || HasCondition( COND_HEAR_DANGER ) )
+ return SCHED_TAKE_COVER_FROM_BEST_SOUND; // Cower and panic from the scary sound!
+
+ return SCHED_SCI_COVER; // Run & Cower
+ break;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+NPC_STATE CNPC_Scientist::SelectIdealState ( void )
+{
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_ALERT:
+ case NPC_STATE_IDLE:
+ if ( HasCondition( COND_NEW_ENEMY ) )
+ {
+ if ( GetFollowTarget() && GetEnemy() )
+ {
+ int relationship = IRelationType( GetEnemy() );
+ if ( relationship != D_FR || relationship != D_HT && ( !HasCondition( COND_LIGHT_DAMAGE ) || !HasCondition( COND_HEAVY_DAMAGE ) ) )
+ {
+ // Don't go to combat if you're following the player
+ return NPC_STATE_ALERT;
+ }
+ StopFollowing();
+ }
+ }
+ else if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ // Stop following if you take damage
+ if ( GetFollowTarget() )
+ StopFollowing();
+ }
+ break;
+
+ case NPC_STATE_COMBAT:
+ {
+ CBaseEntity *pEnemy = GetEnemy();
+ if ( pEnemy != NULL )
+ {
+ if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert
+ {
+ // Strip enemy when going to alert
+ SetEnemy( NULL );
+ return NPC_STATE_ALERT;
+ }
+ // Follow if only scared a little
+ if ( GetFollowTarget() )
+ {
+ return NPC_STATE_ALERT;
+ }
+
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ m_flFearTime = gpGlobals->curtime;
+ return NPC_STATE_COMBAT;
+ }
+
+ }
+ }
+ break;
+ }
+
+ return BaseClass::SelectIdealState();
+}
+
+int CNPC_Scientist::FriendNumber( int arrayNumber )
+{
+ static int array[3] = { 1, 2, 0 };
+ if ( arrayNumber < 3 )
+ return array[ arrayNumber ];
+ return arrayNumber;
+}
+
+float CNPC_Scientist::TargetDistance( void )
+{
+ CBaseEntity *pFollowTarget = GetFollowTarget();
+
+ // If we lose the player, or he dies, return a really large distance
+ if ( pFollowTarget == NULL || !pFollowTarget->IsAlive() )
+ return 1e6;
+
+ return (pFollowTarget->WorldSpaceCenter() - WorldSpaceCenter()).Length();
+}
+
+bool CNPC_Scientist::IsValidEnemy( CBaseEntity *pEnemy )
+{
+ if( pEnemy->m_iClassname == s_iszBarnacleClassname )
+ {
+ // Scientists ignore barnacles rather than freak out.(sjb)
+ return false;
+ }
+
+ return BaseClass::IsValidEnemy(pEnemy);
+}
+
+
+//=========================================================
+// Dead Scientist PROP
+//=========================================================
+class CNPC_DeadScientist : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_DeadScientist, CAI_BaseNPC );
+public:
+
+ void Spawn( void );
+ Class_T Classify ( void ) { return CLASS_NONE; }
+
+ bool KeyValue( const char *szKeyName, const char *szValue );
+ float MaxYawSpeed ( void ) { return 8.0f; }
+
+ int m_iPose;// which sequence to display -- temporary, don't need to save
+ int m_iDesiredSequence;
+ static char *m_szPoses[7];
+};
+
+
+char *CNPC_DeadScientist::m_szPoses[] = { "lying_on_back", "lying_on_stomach", "dead_sitting", "dead_hang", "dead_table1", "dead_table2", "dead_table3" };
+
+bool CNPC_DeadScientist::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if ( FStrEq( szKeyName, "pose" ) )
+ m_iPose = atoi( szValue );
+ else
+ BaseClass::KeyValue( szKeyName, szValue );
+
+ return true;
+}
+
+LINK_ENTITY_TO_CLASS( monster_scientist_dead, CNPC_DeadScientist );
+
+//
+// ********** DeadScientist SPAWN **********
+//
+void CNPC_DeadScientist::Spawn( void )
+{
+ PrecacheModel("models/scientist.mdl");
+ SetModel( "models/scientist.mdl" );
+
+ ClearEffects();
+ SetSequence( 0 );
+ m_bloodColor = BLOOD_COLOR_RED;
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ if ( m_nBody == -1 )
+ {// -1 chooses a random head
+ m_nBody = random->RandomInt( 0, NUM_SCIENTIST_HEADS-1);// pick a head, any head
+ }
+ // Luther is black, make his hands black
+ if ( m_nBody == HEAD_LUTHER )
+ m_nSkin = 1;
+ else
+ m_nSkin = 0;
+
+ SetSequence( LookupSequence( m_szPoses[m_iPose] ) );
+
+ if ( GetSequence() == -1)
+ {
+ Msg ( "Dead scientist with bad pose\n" );
+ }
+
+ m_iHealth = 0.0;//gSkillData.barneyHealth;
+
+ NPCInitDead();
+
+}
+
+
+//=========================================================
+// Sitting Scientist PROP
+//=========================================================
+
+
+LINK_ENTITY_TO_CLASS( monster_sitting_scientist, CNPC_SittingScientist );
+
+//IMPLEMENT_CUSTOM_AI( monster_sitting_scientist, CNPC_SittingScientist );
+
+//IMPLEMENT_SERVERCLASS_ST( CNPC_SittingScientist, DT_NPC_SittingScientist )
+//END_SEND_TABLE()
+
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CNPC_SittingScientist )
+ DEFINE_FIELD( m_iHeadTurn, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flResponseDelay, FIELD_FLOAT ),
+ //DEFINE_FIELD( m_baseSequence, FIELD_INTEGER ),
+
+ DEFINE_THINKFUNC( SittingThink ),
+END_DATADESC()
+
+
+// animation sequence aliases
+typedef enum
+{
+SITTING_ANIM_sitlookleft,
+SITTING_ANIM_sitlookright,
+SITTING_ANIM_sitscared,
+SITTING_ANIM_sitting2,
+SITTING_ANIM_sitting3
+} SITTING_ANIM;
+
+
+//
+// ********** Scientist SPAWN **********
+//
+void CNPC_SittingScientist::Spawn( )
+{
+ PrecacheModel("models/scientist.mdl");
+ SetModel("models/scientist.mdl");
+ Precache();
+
+ InitBoneControllers();
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_iHealth = 50;
+
+ m_bloodColor = BLOOD_COLOR_RED;
+ m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result )
+
+ m_NPCState = NPC_STATE_NONE;
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_TURN_HEAD );
+
+ m_spawnflags |= SF_NPC_PREDISASTER; // predisaster only!
+
+ if ( m_nBody == -1 )
+ {// -1 chooses a random head
+ m_nBody = random->RandomInt( 0, NUM_SCIENTIST_HEADS-1 );// pick a head, any head
+ }
+ // Luther is black, make his hands black
+ if ( m_nBody == HEAD_LUTHER )
+ m_nBody = 1;
+
+ UTIL_DropToFloor( this,MASK_SOLID );
+
+ NPCInit();
+
+ SetThink (&CNPC_SittingScientist::SittingThink);
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ m_baseSequence = LookupSequence( "sitlookleft" );
+ SetSequence( m_baseSequence + random->RandomInt(0,4) );
+ ResetSequenceInfo( );
+}
+
+void CNPC_SittingScientist::Precache( void )
+{
+ m_baseSequence = LookupSequence( "sitlookleft" );
+ TalkInit();
+}
+
+int CNPC_SittingScientist::FriendNumber( int arrayNumber )
+{
+ static int array[3] = { 2, 1, 0 };
+ if ( arrayNumber < 3 )
+ return array[ arrayNumber ];
+ return arrayNumber;
+}
+
+//=========================================================
+// sit, do stuff
+//=========================================================
+void CNPC_SittingScientist::SittingThink( void )
+{
+ CBaseEntity *pent;
+
+ StudioFrameAdvance( );
+
+ // try to greet player
+ //FIXMEFIXME
+
+ //MB - don't greet, done by base talker
+ if ( 0 && GetExpresser()->CanSpeakConcept( TLK_HELLO ) )
+ {
+ pent = FindNearestFriend(true);
+ if (pent)
+ {
+ float yaw = VecToYaw(pent->GetAbsOrigin() - GetAbsOrigin()) - GetAbsAngles().y;
+
+ if (yaw > 180) yaw -= 360;
+ if (yaw < -180) yaw += 360;
+
+ if (yaw > 0)
+ SetSequence( m_baseSequence + SITTING_ANIM_sitlookleft );
+ else
+ SetSequence ( m_baseSequence + SITTING_ANIM_sitlookright );
+
+ ResetSequenceInfo( );
+ SetCycle( 0 );
+ SetBoneController( 0, 0 );
+
+ GetExpresser()->Speak( TLK_HELLO );
+ }
+ }
+ else if ( IsSequenceFinished() )
+ {
+ int i = random->RandomInt(0,99);
+ m_iHeadTurn = 0;
+
+ if (m_flResponseDelay && gpGlobals->curtime > m_flResponseDelay)
+ {
+ // respond to question
+ GetExpresser()->Speak( TLK_QUESTION );
+ SetSequence( m_baseSequence + SITTING_ANIM_sitscared );
+ m_flResponseDelay = 0;
+ }
+ else if (i < 30)
+ {
+ SetSequence( m_baseSequence + SITTING_ANIM_sitting3 );
+
+ // turn towards player or nearest friend and speak
+
+ //FIXME
+ /*/ if (!FBitSet(m_nSpeak, bit_saidHelloPlayer))
+ pent = FindNearestFriend(TRUE);
+ else*/
+ pent = FindNamedEntity( "!nearestfriend" );
+
+ if (!FIdleSpeak() || !pent)
+ {
+ m_iHeadTurn = random->RandomInt(0,8) * 10 - 40;
+ SetSequence( m_baseSequence + SITTING_ANIM_sitting3 );
+ }
+ else
+ {
+ // only turn head if we spoke
+ float yaw = VecToYaw(pent->GetAbsOrigin() - GetAbsOrigin()) - GetAbsAngles().y;
+
+ if (yaw > 180) yaw -= 360;
+ if (yaw < -180) yaw += 360;
+
+ if (yaw > 0)
+ SetSequence( m_baseSequence + SITTING_ANIM_sitlookleft );
+ else
+ SetSequence( m_baseSequence + SITTING_ANIM_sitlookright );
+
+ //ALERT(at_console, "sitting speak\n");
+ }
+ }
+ else if (i < 60)
+ {
+ SetSequence( m_baseSequence + SITTING_ANIM_sitting3 );
+ m_iHeadTurn = random->RandomInt(0,8) * 10 - 40;
+ if ( random->RandomInt(0,99) < 5)
+ {
+ //ALERT(at_console, "sitting speak2\n");
+ FIdleSpeak();
+ }
+ }
+ else if (i < 80)
+ {
+ SetSequence( m_baseSequence + SITTING_ANIM_sitting2 );
+ }
+ else if (i < 100)
+ {
+ SetSequence( m_baseSequence + SITTING_ANIM_sitscared );
+ }
+
+ ResetSequenceInfo( );
+ SetCycle( 0 );
+ SetBoneController( 0, m_iHeadTurn );
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+// prepare sitting scientist to answer a question
+void CNPC_SittingScientist::SetAnswerQuestion( CNPCSimpleTalker *pSpeaker )
+{
+ m_flResponseDelay = gpGlobals->curtime + random->RandomFloat(3, 4);
+ SetSpeechTarget( (CNPCSimpleTalker *)pSpeaker );
+}
+
+//------------------------------------------------------------------------------
+//
+// Schedules
+//
+//------------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( monster_scientist, CNPC_Scientist )
+
+ DECLARE_TASK( TASK_SAY_HEAL )
+ DECLARE_TASK( TASK_HEAL )
+ DECLARE_TASK( TASK_SAY_FEAR )
+ DECLARE_TASK( TASK_RUN_PATH_SCARED )
+ DECLARE_TASK( TASK_SCREAM )
+ DECLARE_TASK( TASK_RANDOM_SCREAM )
+ DECLARE_TASK( TASK_MOVE_TO_TARGET_RANGE_SCARED )
+
+ DECLARE_ACTIVITY( ACT_EXCITED )
+
+ //=========================================================
+ // > SCHED_SCI_HEAL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_HEAL,
+
+ " Tasks"
+ " TASK_GET_PATH_TO_TARGET 0"
+ " TASK_MOVE_TO_TARGET_RANGE 50"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCI_FOLLOWTARGET"
+ " TASK_FACE_IDEAL 0"
+ " TASK_SAY_HEAL 0"
+ " TASK_PLAY_SEQUENCE_FACE_TARGET ACTIVITY:ACT_ARM"
+ " TASK_HEAL 0"
+ " TASK_PLAY_SEQUENCE_FACE_TARGET ACTIVITY:ACT_DISARM"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_FOLLOWTARGET
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_FOLLOWTARGET,
+
+ " Tasks"
+// " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCI_STOPFOLLOWING"
+ " TASK_GET_PATH_TO_TARGET 0"
+ " TASK_MOVE_TO_TARGET_RANGE 128"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_TARGET_FACE"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_STOPFOLLOWING
+ //=========================================================
+// DEFINE_SCHEDULE
+// (
+// SCHED_SCI_STOPFOLLOWING,
+//
+// " Tasks"
+// " TASK_TALKER_CANT_FOLLOW 0"
+// " "
+// " Interrupts"
+// )
+
+ //=========================================================
+ // > SCHED_SCI_FACETARGET
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_FACETARGET,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_TARGET 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_SCI_FOLLOWTARGET"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ " COND_GIVE_WAY"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_COVER
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_COVER,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCI_PANIC"
+ " TASK_STOP_MOVING 0"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_RUN_PATH_SCARED 0"
+ " TASK_TURN_LEFT 179"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_SCI_HIDE"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_HIDE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_HIDE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCI_PANIC"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_CROUCHIDLE"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_CROUCHIDLE"
+ " TASK_WAIT_RANDOM 10"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_SEE_ENEMY"
+ " COND_SEE_HATE"
+ " COND_SEE_FEAR"
+ " COND_SEE_DISLIKE"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_IDLESTAND
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_IDLESTAND,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 2"
+ " TASK_TALKER_HEADRESET 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_SMELL"
+ " COND_PROVOKED"
+ " COND_HEAR_COMBAT"
+ " COND_GIVE_WAY"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_PANIC
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_PANIC,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_SCREAM 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_EXCITED"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_FOLLOWSCARED
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_FOLLOWSCARED,
+
+ " Tasks"
+ " TASK_GET_PATH_TO_TARGET 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCI_FOLLOWTARGET"
+ " TASK_MOVE_TO_TARGET_RANGE_SCARED 128"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_FACETARGETSCARED
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_FACETARGETSCARED,
+
+ " Tasks"
+ " TASK_FACE_TARGET 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_CROUCHIDLE"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_SCI_FOLLOWSCARED"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_FEAR
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_FEAR,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_SAY_FEAR 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_STARTLE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_STARTLE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCI_PANIC"
+ " TASK_RANDOM_SCREAM 0.3"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_CROUCH"
+ " TASK_RANDOM_SCREAM 0.1"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_CROUCHIDLE"
+ " TASK_WAIT_RANDOM 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_SEE_ENEMY"
+ " COND_SEE_HATE"
+ " COND_SEE_FEAR"
+ " COND_SEE_DISLIKE"
+ )
+
+AI_END_CUSTOM_NPC()