/*** * * Copyright (c) 1996-2001, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * This source code contains proprietary and confidential information of * Valve LLC and its suppliers. Access to this code is restricted to * persons who have executed a written SDK license with Valve. Any access, * use or distribution of this code by or to any unlicensed person is illegal. * ****/ //========================================================= // human scientist (passive lab worker) //========================================================= #include "extdll.h" #include "util.h" #include "cmbase.h" #include "cmbasemonster.h" #include "monsters.h" #include "schedule.h" #include "defaultai.h" #include "animation.h" #define NUM_SCIENTIST_HEADS 4 // four heads available for scientist model enum { HEAD_GLASSES = 0, HEAD_EINSTEIN = 1, HEAD_LUTHER = 2, HEAD_SLICK = 3 }; enum { SCHED_HIDE = LAST_TALKMONSTER_SCHEDULE + 1, SCHED_FEAR, SCHED_PANIC, SCHED_STARTLE, SCHED_TARGET_CHASE_SCARED, SCHED_TARGET_FACE_SCARED, }; enum { TASK_SAY_HEAL = LAST_TALKMONSTER_TASK + 1, TASK_HEAL, TASK_SAY_FEAR, TASK_RUN_PATH_SCARED, TASK_SCREAM, TASK_RANDOM_SCREAM, TASK_MOVE_TO_TARGET_RANGE_SCARED, }; //========================================================= // Monster's Anim Events Go Here //========================================================= #define SCIENTIST_AE_HEAL ( 1 ) #define SCIENTIST_AE_NEEDLEON ( 2 ) #define SCIENTIST_AE_NEEDLEOFF ( 3 ) //======================================================= // Scientist //======================================================= //========================================================= // AI Schedules Specific to this monster //========================================================= Task_t tlFollow[] = { { TASK_SET_FAIL_SCHEDULE, (float)SCHED_CANT_FOLLOW }, // If you fail, bail out of follow { TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 128 of target ent (client) // { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, }; Schedule_t slFollow[] = { { tlFollow, ARRAYSIZE ( tlFollow ), bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_HEAR_SOUND, 0, "Follow" }, }; Task_t tlFollowScared[] = { { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_CHASE },// If you fail, follow normally { TASK_MOVE_TO_TARGET_RANGE_SCARED,(float)128 }, // Move within 128 of target ent (client) // { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE_SCARED }, }; Schedule_t slFollowScared[] = { { tlFollowScared, ARRAYSIZE ( tlFollowScared ), bits_COND_NEW_ENEMY | bits_COND_HEAR_SOUND | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE, 0, "FollowScared" }, }; Task_t tlFaceTargetScared[] = { { TASK_FACE_TARGET, (float)0 }, { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE_SCARED }, }; Schedule_t slFaceTargetScared[] = { { tlFaceTargetScared, ARRAYSIZE ( tlFaceTargetScared ), bits_COND_HEAR_SOUND | bits_COND_NEW_ENEMY, 0, "FaceTargetScared" }, }; Task_t tlStopFollowing[] = { { TASK_CANT_FOLLOW, (float)0 }, }; Schedule_t slStopFollowing[] = { { tlStopFollowing, ARRAYSIZE ( tlStopFollowing ), 0, 0, "StopFollowing" }, }; Task_t tlHeal[] = { { TASK_MOVE_TO_TARGET_RANGE,(float)50 }, // Move within 60 of target ent (client) { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_CHASE }, // If you fail, catch up with that guy! (change this to put syringe away and then chase) { TASK_FACE_IDEAL, (float)0 }, { TASK_SAY_HEAL, (float)0 }, { TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_ARM }, // Whip out the needle { TASK_HEAL, (float)0 }, // Put it in the player { TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_DISARM }, // Put away the needle }; Schedule_t slHeal[] = { { tlHeal, ARRAYSIZE ( tlHeal ), 0, // Don't interrupt or he'll end up running around with a needle all the time 0, "Heal" }, }; Task_t tlFaceTarget[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_FACE_TARGET, (float)0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE }, }; Schedule_t slFaceTarget[] = { { tlFaceTarget, ARRAYSIZE ( tlFaceTarget ), bits_COND_CLIENT_PUSH | bits_COND_NEW_ENEMY | bits_COND_HEAR_SOUND, 0, "FaceTarget" }, }; Task_t tlSciPanic[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_SCREAM, (float)0 }, { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_EXCITED }, // This is really fear-stricken excitement { TASK_SET_ACTIVITY, (float)ACT_IDLE }, }; Schedule_t slSciPanic[] = { { tlSciPanic, ARRAYSIZE ( tlSciPanic ), 0, 0, "SciPanic" }, }; Task_t tlIdleSciStand[] = { { TASK_STOP_MOVING, 0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_WAIT, (float)2 }, // repick IDLESTAND every two seconds. { TASK_TLK_HEADRESET, (float)0 }, // reset head position }; Schedule_t slIdleSciStand[] = { { tlIdleSciStand, ARRAYSIZE ( tlIdleSciStand ), bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_HEAR_SOUND | bits_COND_SMELL | bits_COND_CLIENT_PUSH | bits_COND_PROVOKED, 0, "IdleSciStand" }, }; Task_t tlScientistCover[] = { { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! { TASK_STOP_MOVING, (float)0 }, { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, { TASK_RUN_PATH_SCARED, (float)0 }, { TASK_TURN_LEFT, (float)179 }, { TASK_SET_SCHEDULE, (float)SCHED_HIDE }, }; Schedule_t slScientistCover[] = { { tlScientistCover, ARRAYSIZE ( tlScientistCover ), bits_COND_NEW_ENEMY, 0, "ScientistCover" }, }; Task_t tlScientistHide[] = { { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! { TASK_STOP_MOVING, (float)0 }, { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, // FIXME: This looks lame { TASK_WAIT_RANDOM, (float)10.0 }, }; Schedule_t slScientistHide[] = { { tlScientistHide, ARRAYSIZE ( tlScientistHide ), bits_COND_NEW_ENEMY | bits_COND_HEAR_SOUND | bits_COND_SEE_ENEMY | bits_COND_SEE_HATE | bits_COND_SEE_FEAR | bits_COND_SEE_DISLIKE, 0, "ScientistHide" }, }; Task_t tlScientistStartle[] = { { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! { TASK_RANDOM_SCREAM, (float)0.3 }, // Scream 30% of the time { TASK_STOP_MOVING, (float)0 }, { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, { TASK_RANDOM_SCREAM, (float)0.1 }, // Scream again 10% of the time { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCHIDLE }, { TASK_WAIT_RANDOM, (float)1.0 }, }; Schedule_t slScientistStartle[] = { { tlScientistStartle, ARRAYSIZE ( tlScientistStartle ), bits_COND_NEW_ENEMY | bits_COND_SEE_ENEMY | bits_COND_SEE_HATE | bits_COND_SEE_FEAR | bits_COND_SEE_DISLIKE, 0, "ScientistStartle" }, }; Task_t tlFear[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_SAY_FEAR, (float)0 }, // { TASK_PLAY_SEQUENCE, (float)ACT_FEAR_DISPLAY }, }; Schedule_t slFear[] = { { tlFear, ARRAYSIZE ( tlFear ), bits_COND_NEW_ENEMY, 0, "Fear" }, }; DEFINE_CUSTOM_SCHEDULES( CMScientist ) { slFollow, slFaceTarget, slIdleSciStand, slFear, slScientistCover, slScientistHide, slScientistStartle, slHeal, slStopFollowing, slSciPanic, slFollowScared, slFaceTargetScared, }; IMPLEMENT_CUSTOM_SCHEDULES( CMScientist, CMTalkMonster ); void CMScientist::DeclineFollowing( void ) { Talk( 10 ); m_hTalkTarget = m_hEnemy; PlaySentence( "SC_POK", 2, VOL_NORM, ATTN_NORM ); } void CMScientist :: Scream( void ) { if ( FOkToSpeak() ) { Talk( 10 ); m_hTalkTarget = m_hEnemy; PlaySentence( "SC_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM ); } } Activity CMScientist::GetStoppedActivity( void ) { if ( m_hEnemy != NULL ) return ACT_EXCITED; return CMTalkMonster::GetStoppedActivity(); } void CMScientist :: StartTask( Task_t *pTask ) { switch( pTask->iTask ) { case TASK_SAY_HEAL: // if ( FOkToSpeak() ) Talk( 2 ); m_hTalkTarget = m_hTargetEnt; PlaySentence( "SC_HEAL", 2, VOL_NORM, ATTN_IDLE ); TaskComplete(); break; case TASK_SCREAM: Scream(); TaskComplete(); break; case TASK_RANDOM_SCREAM: if ( RANDOM_FLOAT( 0, 1 ) < pTask->flData ) Scream(); TaskComplete(); break; case TASK_SAY_FEAR: if ( FOkToSpeak() ) { Talk( 2 ); m_hTalkTarget = m_hEnemy; if ( UTIL_IsPlayer(m_hEnemy) ) PlaySentence( "SC_PLFEAR", 5, VOL_NORM, ATTN_NORM ); else PlaySentence( "SC_FEAR", 5, VOL_NORM, ATTN_NORM ); } TaskComplete(); break; case TASK_HEAL: m_IdealActivity = ACT_MELEE_ATTACK1; break; case TASK_RUN_PATH_SCARED: m_movementActivity = ACT_RUN_SCARED; break; case TASK_MOVE_TO_TARGET_RANGE_SCARED: { if ( (m_hTargetEnt->v.origin - pev->origin).Length() < 1 ) TaskComplete(); else { m_vecMoveGoal = m_hTargetEnt->v.origin; if ( !MoveToTarget( ACT_WALK_SCARED, 0.5 ) ) TaskFail(); } } break; default: CMTalkMonster::StartTask( pTask ); break; } } void CMScientist :: RunTask( Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_RUN_PATH_SCARED: if ( MovementIsComplete() ) TaskComplete(); if ( RANDOM_LONG(0,31) < 8 ) Scream(); break; case TASK_MOVE_TO_TARGET_RANGE_SCARED: { if ( RANDOM_LONG(0,63)< 8 ) Scream(); if ( m_hEnemy == NULL ) { TaskFail(); } else { float distance; distance = ( m_vecMoveGoal - pev->origin ).Length2D(); // Re-evaluate when you think your finished, or the target has moved too far if ( (distance < pTask->flData) || (m_vecMoveGoal - m_hTargetEnt->v.origin).Length() > pTask->flData * 0.5 ) { m_vecMoveGoal = m_hTargetEnt->v.origin; distance = ( m_vecMoveGoal - pev->origin ).Length2D(); FRefreshRoute(); } // Set the appropriate activity based on an overlapping range // overlap the range to prevent oscillation if ( distance < pTask->flData ) { TaskComplete(); RouteClear(); // Stop moving } else if ( distance < 190 && m_movementActivity != ACT_WALK_SCARED ) m_movementActivity = ACT_WALK_SCARED; else if ( distance >= 270 && m_movementActivity != ACT_RUN_SCARED ) m_movementActivity = ACT_RUN_SCARED; } } break; case TASK_HEAL: if ( m_fSequenceFinished ) { TaskComplete(); } else { if ( TargetDistance() > 90 ) TaskComplete(); pev->ideal_yaw = UTIL_VecToYaw( m_hTargetEnt->v.origin - pev->origin ); ChangeYaw( pev->yaw_speed ); } break; default: CMTalkMonster::RunTask( pTask ); break; } } //========================================================= // Classify - indicates this monster's place in the // relationship table. //========================================================= int CMScientist :: Classify ( void ) { if ( m_iClassifyOverride == -1 ) // helper return CLASS_NONE; else if ( m_iClassifyOverride > 0 ) return m_iClassifyOverride; // override return CLASS_HUMAN_PASSIVE; } //========================================================= // SetYawSpeed - allows each sequence to have a different // turn rate associated with it. //========================================================= void CMScientist :: SetYawSpeed ( void ) { int ys; ys = 90; switch ( m_Activity ) { case ACT_IDLE: ys = 120; break; case ACT_WALK: ys = 180; break; case ACT_RUN: ys = 150; break; case ACT_TURN_LEFT: case ACT_TURN_RIGHT: ys = 120; break; } pev->yaw_speed = ys; } //========================================================= // HandleAnimEvent - catches the monster-specific messages // that occur when tagged animation frames are played. //========================================================= void CMScientist :: HandleAnimEvent( MonsterEvent_t *pEvent ) { switch( pEvent->event ) { case SCIENTIST_AE_HEAL: // Heal my target (if within range) Heal(); break; case SCIENTIST_AE_NEEDLEON: { int oldBody = pev->body; pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 1; } break; case SCIENTIST_AE_NEEDLEOFF: { int oldBody = pev->body; pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 0; } break; default: CMTalkMonster::HandleAnimEvent( pEvent ); } } //========================================================= // Spawn //========================================================= void CMScientist :: Spawn( void ) { Precache( ); // every new scientist must call this, otherwise // when a level is loaded, nobody will talk (time is reset to 0) TalkInit(); SET_MODEL(ENT(pev), (!FStringNull( pev->model ) ? STRING( pev->model ) : "models/scientist.mdl")); UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_STEP; m_bloodColor = !m_bloodColor ? BLOOD_COLOR_RED : m_bloodColor; pev->health = gSkillData.scientistHealth; pev->view_ofs = Vector ( 0, 0, 50 );// position of the eyes relative to monster's origin. m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so scientists will notice player and say hello m_MonsterState = MONSTERSTATE_NONE; // m_flDistTooFar = 256.0; m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; // White hands pev->skin = 0; pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS-1); // pick a head, any head // Luther is black, make his hands black if ( pev->body == HEAD_LUTHER ) pev->skin = 1; MonsterInit(); pev->classname = MAKE_STRING( "monster_scientist" ); if ( strlen( STRING( m_szMonsterName ) ) == 0 ) { // default name m_szMonsterName = MAKE_STRING( "Scientist" ); } } //========================================================= // Precache - precaches all resources this monster needs //========================================================= void CMScientist :: Precache( void ) { PRECACHE_MODEL("models/scientist.mdl"); PRECACHE_SOUND("scientist/sci_pain1.wav"); PRECACHE_SOUND("scientist/sci_pain2.wav"); PRECACHE_SOUND("scientist/sci_pain3.wav"); PRECACHE_SOUND("scientist/sci_pain4.wav"); PRECACHE_SOUND("scientist/sci_pain5.wav"); CMTalkMonster::Precache(); } // Init talk data void CMScientist :: TalkInit() { CMTalkMonster::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"; // scientists speach group names (group names are in sentences.txt) m_szGrp[TLK_ANSWER] = "SC_ANSWER"; m_szGrp[TLK_QUESTION] = "SC_QUESTION"; m_szGrp[TLK_IDLE] = "SC_IDLE"; m_szGrp[TLK_STARE] = "SC_STARE"; m_szGrp[TLK_USE] = "SC_OK"; m_szGrp[TLK_UNUSE] = "SC_WAIT"; m_szGrp[TLK_STOP] = "SC_STOP"; m_szGrp[TLK_NOSHOOT] = "SC_SCARED"; m_szGrp[TLK_HELLO] = "SC_HELLO"; m_szGrp[TLK_PLHURT1] = "!SC_CUREA"; m_szGrp[TLK_PLHURT2] = "!SC_CUREB"; m_szGrp[TLK_PLHURT3] = "!SC_CUREC"; m_szGrp[TLK_PHELLO] = "SC_PHELLO"; m_szGrp[TLK_PIDLE] = "SC_PIDLE"; m_szGrp[TLK_PQUESTION] = "SC_PQUEST"; m_szGrp[TLK_SMELL] = "SC_SMELL"; m_szGrp[TLK_WOUND] = "SC_WOUND"; m_szGrp[TLK_MORTAL] = "SC_MORTAL"; // get voice for head switch (pev->body % NUM_SCIENTIST_HEADS) { default: case HEAD_GLASSES: m_voicePitch = 105; break; //glasses case HEAD_EINSTEIN: m_voicePitch = 100; break; //einstein case HEAD_LUTHER: m_voicePitch = 95; break; //luther case HEAD_SLICK: m_voicePitch = 100; break;//slick } } int CMScientist :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) { if ( pevInflictor && pevInflictor->flags & FL_CLIENT ) { Remember( bits_MEMORY_PROVOKED ); StopFollowing( TRUE ); } // make sure friends talk about it if player hurts scientist... return CMTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); } //========================================================= // ISoundMask - returns a bit mask indicating which types // of sounds this monster regards. In the base class implementation, // monsters care about all sounds, but no scents. //========================================================= int CMScientist :: ISoundMask ( void ) { return 0; } //========================================================= // PainSound //========================================================= void CMScientist :: PainSound ( void ) { if (gpGlobals->time < m_painTime ) return; m_painTime = gpGlobals->time + RANDOM_FLOAT(0.5, 0.75); switch (RANDOM_LONG(0,4)) { case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; case 3: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain4.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; case 4: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain5.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; } } //========================================================= // DeathSound //========================================================= void CMScientist :: DeathSound ( void ) { PainSound(); } void CMScientist::Killed( entvars_t *pevAttacker, int iGib ) { SetUse( NULL ); CMTalkMonster::Killed( pevAttacker, iGib ); } void CMScientist :: SetActivity ( Activity newActivity ) { int iSequence; iSequence = LookupActivity ( newActivity ); // Set to the desired anim, or default anim if the desired is not present if ( iSequence == ACTIVITY_NOT_AVAILABLE ) newActivity = ACT_IDLE; CMTalkMonster::SetActivity( newActivity ); } Schedule_t* CMScientist :: GetScheduleOfType ( int Type ) { Schedule_t *psched; switch( Type ) { // Hook these to make a looping schedule case SCHED_TARGET_FACE: // call base class default so that scientist will talk // when 'used' psched = CMTalkMonster::GetScheduleOfType(Type); if (psched == slIdleStand) return slFaceTarget; // override this for different target face behavior else return psched; case SCHED_TARGET_CHASE: return slFollow; case SCHED_CANT_FOLLOW: return slStopFollowing; case SCHED_PANIC: return slSciPanic; case SCHED_TARGET_CHASE_SCARED: return slFollowScared; case SCHED_TARGET_FACE_SCARED: return slFaceTargetScared; case SCHED_IDLE_STAND: // call base class default so that scientist will talk // when standing during idle psched = CMTalkMonster::GetScheduleOfType(Type); if (psched == slIdleStand) return slIdleSciStand; else return psched; case SCHED_HIDE: return slScientistHide; case SCHED_STARTLE: return slScientistStartle; case SCHED_FEAR: return slFear; } return CMTalkMonster::GetScheduleOfType( Type ); } Schedule_t *CMScientist :: GetSchedule ( void ) { // so we don't keep calling through the EHANDLE stuff edict_t *pEnemy = m_hEnemy; int relationship; switch( m_MonsterState ) { case MONSTERSTATE_ALERT: case MONSTERSTATE_IDLE: if ( ( pEnemy ) && !UTIL_IsPlayer(pEnemy) ) { if ( HasConditions( bits_COND_SEE_ENEMY ) ) m_fearTime = gpGlobals->time; else if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert { m_hEnemy = NULL; pEnemy = NULL; } } if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) { // flinch if hurt return GetScheduleOfType( SCHED_SMALL_FLINCH ); } // Nothing scary, just me and the player if ( pEnemy != NULL ) { relationship = R_NO; if (UTIL_IsPlayer(pEnemy)) relationship = R_AL; // allies else if (pEnemy->v.euser4 != NULL) { CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pEnemy)); relationship = IRelationship( pMonster ); } // UNDONE: Model fear properly, fix R_FR and add multiple levels of fear if ( relationship != R_DL && relationship != R_HT ) { // If I'm already close enough to my target if ( TargetDistance() <= 128 ) // uses m_hTargetEnt { if ( CanHeal() ) // Heal opportunistically return slHeal; if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); } //jlb m_hEnemy = NULL; //jlb return GetScheduleOfType( 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 ( HasConditions( bits_COND_NEW_ENEMY ) ) // I just saw something new and scary, react return GetScheduleOfType( SCHED_FEAR ); // React to something scary return GetScheduleOfType( SCHED_TARGET_FACE_SCARED ); // face and follow, but I'm scared! } } if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move return GetScheduleOfType( SCHED_MOVE_AWAY ); // try to say something about smells TrySmellTalk(); break; case MONSTERSTATE_COMBAT: if ( HasConditions( bits_COND_NEW_ENEMY ) ) return slFear; // Point and scream! if ( HasConditions( bits_COND_SEE_ENEMY ) ) return slScientistCover; // Take Cover if ( HasConditions( bits_COND_HEAR_SOUND ) ) return slTakeCoverFromBestSound; // Cower and panic from the scary sound! return slScientistCover; // Run & Cower break; } return CMTalkMonster::GetSchedule(); } MONSTERSTATE CMScientist :: GetIdealState ( void ) { switch ( m_MonsterState ) { case MONSTERSTATE_ALERT: case MONSTERSTATE_IDLE: if ( HasConditions( bits_COND_NEW_ENEMY ) ) { if ( IsFollowing() ) { int relationship = R_NO; if (UTIL_IsPlayer(m_hEnemy)) relationship = R_AL; // allies else if (m_hEnemy->v.euser4 != NULL) { edict_t *pEdict = m_hEnemy; CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pEdict)); relationship = IRelationship( pMonster ); } if ( relationship != R_FR || relationship != R_HT && !HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) { // Don't go to combat if you're following the player m_IdealMonsterState = MONSTERSTATE_ALERT; return m_IdealMonsterState; } StopFollowing( TRUE ); } } else if ( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) { // Stop following if you take damage if ( IsFollowing() ) StopFollowing( TRUE ); } break; case MONSTERSTATE_COMBAT: { edict_t *pEnemy = m_hEnemy; if ( pEnemy != NULL ) { if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert { // Strip enemy when going to alert m_IdealMonsterState = MONSTERSTATE_ALERT; m_hEnemy = NULL; return m_IdealMonsterState; } // Follow if only scared a little if ( m_hTargetEnt != NULL ) { m_IdealMonsterState = MONSTERSTATE_ALERT; return m_IdealMonsterState; } if ( HasConditions ( bits_COND_SEE_ENEMY ) ) { m_fearTime = gpGlobals->time; m_IdealMonsterState = MONSTERSTATE_COMBAT; return m_IdealMonsterState; } } } break; } return CMTalkMonster::GetIdealState(); } BOOL CMScientist::CanHeal( void ) { if ( (m_healTime > gpGlobals->time) || (m_hTargetEnt == NULL) || (m_hTargetEnt->v.health > (m_hTargetEnt->v.max_health * 0.5)) ) return FALSE; return TRUE; } void CMScientist::Heal( void ) { if ( !CanHeal() ) return; Vector target = m_hTargetEnt->v.origin - pev->origin; if ( target.Length() > 100 ) return; if (UTIL_IsPlayer(m_hTargetEnt)) UTIL_TakeHealth( m_hTargetEnt, gSkillData.scientistHeal, DMG_GENERIC ); else if (m_hTargetEnt->v.euser4 != NULL) { edict_t *pEdict = m_hTargetEnt; CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pEdict)); pMonster->TakeHealth( gSkillData.scientistHeal, DMG_GENERIC ); } // Don't heal again for 1 minute m_healTime = gpGlobals->time + 60; } int CMScientist::FriendNumber( int arrayNumber ) { static int array[3] = { 1, 2, 0 }; if ( arrayNumber < 3 ) return array[ arrayNumber ]; return arrayNumber; } BOOL CMScientist :: DisregardEnemy( edict_t *pEnemy ) { return !UTIL_IsAlive(pEnemy) || (gpGlobals->time - m_fearTime) > 15; }