//========= 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()