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.
1411 lines
33 KiB
1411 lines
33 KiB
![]()
4 years ago
|
//========= 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()
|