You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1410 lines
33 KiB
1410 lines
33 KiB
//========= 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()
|
|
|