diff options
Diffstat (limited to 'game/server/hl1/hl1_npc_scientist.cpp')
| -rw-r--r-- | game/server/hl1/hl1_npc_scientist.cpp | 1410 |
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() |