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.
938 lines
22 KiB
938 lines
22 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_barney.h" |
|
#include "soundent.h" |
|
#include "game.h" |
|
#include "npcevent.h" |
|
#include "entitylist.h" |
|
#include "activitylist.h" |
|
#include "animation.h" |
|
#include "basecombatweapon.h" |
|
#include "IEffects.h" |
|
#include "vstdlib/random.h" |
|
#include "engine/IEngineSound.h" |
|
#include "ammodef.h" |
|
#include "ai_behavior_follow.h" |
|
#include "AI_Criteria.h" |
|
#include "SoundEmitterSystem/isoundemittersystembase.h" |
|
|
|
#define BA_ATTACK "BA_ATTACK" |
|
#define BA_MAD "BA_MAD" |
|
#define BA_SHOT "BA_SHOT" |
|
#define BA_KILL "BA_KILL" |
|
#define BA_POK "BA_POK" |
|
|
|
ConVar sk_barney_health( "sk_barney_health","35"); |
|
|
|
//========================================================= |
|
// Monster's Anim Events Go Here |
|
//========================================================= |
|
// first flag is barney dying for scripted sequences? |
|
#define BARNEY_AE_DRAW ( 2 ) |
|
#define BARNEY_AE_SHOOT ( 3 ) |
|
#define BARNEY_AE_HOLSTER ( 4 ) |
|
|
|
#define BARNEY_BODY_GUNHOLSTERED 0 |
|
#define BARNEY_BODY_GUNDRAWN 1 |
|
#define BARNEY_BODY_GUNGONE 2 |
|
|
|
|
|
//--------------------------------------------------------- |
|
// Save/Restore |
|
//--------------------------------------------------------- |
|
BEGIN_DATADESC( CNPC_Barney ) |
|
DEFINE_FIELD( m_fGunDrawn, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_flPainTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flCheckAttackTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_fLastAttackCheck, FIELD_BOOLEAN ), |
|
|
|
DEFINE_THINKFUNC( SUB_LVFadeOut ), |
|
|
|
//DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ), |
|
END_DATADESC() |
|
|
|
|
|
LINK_ENTITY_TO_CLASS( monster_barney, CNPC_Barney ); |
|
|
|
|
|
static BOOL IsFacing( CBaseEntity *pevTest, const Vector &reference ) |
|
{ |
|
Vector vecDir = (reference - pevTest->GetAbsOrigin()); |
|
vecDir.z = 0; |
|
VectorNormalize( vecDir ); |
|
Vector forward; |
|
QAngle angle; |
|
angle = pevTest->GetAbsAngles(); |
|
angle.x = 0; |
|
AngleVectors( angle, &forward ); |
|
// He's facing me, he meant it |
|
if ( DotProduct( forward, vecDir ) > 0.96 ) // +/- 15 degrees or so |
|
{ |
|
return TRUE; |
|
} |
|
return FALSE; |
|
} |
|
|
|
//========================================================= |
|
// Spawn |
|
//========================================================= |
|
void CNPC_Barney::Spawn() |
|
{ |
|
Precache( ); |
|
|
|
SetModel( "models/barney.mdl"); |
|
|
|
SetRenderColor( 255, 255, 255, 255 ); |
|
|
|
SetHullType(HULL_HUMAN); |
|
SetHullSizeNormal(); |
|
|
|
SetSolid( SOLID_BBOX ); |
|
AddSolidFlags( FSOLID_NOT_STANDABLE ); |
|
SetMoveType( MOVETYPE_STEP ); |
|
m_bloodColor = BLOOD_COLOR_RED; |
|
m_iHealth = sk_barney_health.GetFloat(); |
|
SetViewOffset( Vector ( 0, 0, 100 ) );// position of the eyes relative to monster's origin. |
|
m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello |
|
m_NPCState = NPC_STATE_NONE; |
|
|
|
SetBodygroup( 1, 0 ); |
|
|
|
m_fGunDrawn = false; |
|
|
|
CapabilitiesClear(); |
|
CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE | bits_CAP_DOORS_GROUP); |
|
CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_TURN_HEAD | bits_CAP_ANIMATEDFACE ); |
|
|
|
NPCInit(); |
|
|
|
SetUse( &CNPC_Barney::FollowerUse ); |
|
} |
|
|
|
//========================================================= |
|
// Precache - precaches all resources this monster needs |
|
//========================================================= |
|
void CNPC_Barney::Precache() |
|
{ |
|
m_iAmmoType = GetAmmoDef()->Index("9mmRound"); |
|
|
|
PrecacheModel("models/barney.mdl"); |
|
|
|
PrecacheScriptSound( "Barney.FirePistol" ); |
|
PrecacheScriptSound( "Barney.Pain" ); |
|
PrecacheScriptSound( "Barney.Die" ); |
|
|
|
// every new barney must call this, otherwise |
|
// when a level is loaded, nobody will talk (time is reset to 0) |
|
TalkInit(); |
|
BaseClass::Precache(); |
|
} |
|
|
|
void CNPC_Barney::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_Barney::TalkInit() |
|
{ |
|
BaseClass::TalkInit(); |
|
|
|
// get voice for head - just one barney voice for now |
|
GetExpresser()->SetVoicePitch( 100 ); |
|
} |
|
|
|
|
|
//========================================================= |
|
// GetSoundInterests - returns a bit mask indicating which types |
|
// of sounds this monster regards. |
|
//========================================================= |
|
int CNPC_Barney::GetSoundInterests ( void) |
|
{ |
|
return SOUND_WORLD | |
|
SOUND_COMBAT | |
|
SOUND_CARCASS | |
|
SOUND_MEAT | |
|
SOUND_GARBAGE | |
|
SOUND_DANGER | |
|
SOUND_PLAYER; |
|
} |
|
|
|
//========================================================= |
|
// Classify - indicates this monster's place in the |
|
// relationship table. |
|
//========================================================= |
|
Class_T CNPC_Barney::Classify ( void ) |
|
{ |
|
return CLASS_PLAYER_ALLY; |
|
} |
|
|
|
//========================================================= |
|
// ALertSound - barney says "Freeze!" |
|
//========================================================= |
|
void CNPC_Barney::AlertSound( void ) |
|
{ |
|
if ( GetEnemy() != NULL ) |
|
{ |
|
if ( IsOkToSpeak() ) |
|
{ |
|
Speak( BA_ATTACK ); |
|
} |
|
} |
|
|
|
} |
|
//========================================================= |
|
// SetYawSpeed - allows each sequence to have a different |
|
// turn rate associated with it. |
|
//========================================================= |
|
void CNPC_Barney::SetYawSpeed ( void ) |
|
{ |
|
int ys; |
|
|
|
ys = 0; |
|
|
|
switch ( GetActivity() ) |
|
{ |
|
case ACT_IDLE: |
|
ys = 70; |
|
break; |
|
case ACT_WALK: |
|
ys = 70; |
|
break; |
|
case ACT_RUN: |
|
ys = 90; |
|
break; |
|
default: |
|
ys = 70; |
|
break; |
|
} |
|
|
|
GetMotor()->SetYawSpeed( ys ); |
|
} |
|
|
|
//========================================================= |
|
// CheckRangeAttack1 |
|
//========================================================= |
|
bool CNPC_Barney::CheckRangeAttack1 ( float flDot, float flDist ) |
|
{ |
|
if ( gpGlobals->curtime > m_flCheckAttackTime ) |
|
{ |
|
trace_t tr; |
|
|
|
Vector shootOrigin = GetAbsOrigin() + Vector( 0, 0, 55 ); |
|
CBaseEntity *pEnemy = GetEnemy(); |
|
Vector shootTarget = ( (pEnemy->BodyTarget( shootOrigin ) - pEnemy->GetAbsOrigin()) + GetEnemyLKP() ); |
|
|
|
UTIL_TraceLine ( shootOrigin, shootTarget, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr); |
|
m_flCheckAttackTime = gpGlobals->curtime + 1; |
|
if ( tr.fraction == 1.0 || ( tr.m_pEnt != NULL && tr.m_pEnt == pEnemy) ) |
|
m_fLastAttackCheck = TRUE; |
|
else |
|
m_fLastAttackCheck = FALSE; |
|
|
|
m_flCheckAttackTime = gpGlobals->curtime + 1.5; |
|
} |
|
|
|
return m_fLastAttackCheck; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : For innate range attack |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
int CNPC_Barney::RangeAttack1Conditions( float flDot, float flDist ) |
|
{ |
|
if (GetEnemy() == NULL) |
|
{ |
|
return( COND_NONE ); |
|
} |
|
|
|
else if ( flDist > 1024 ) |
|
{ |
|
return( COND_TOO_FAR_TO_ATTACK ); |
|
} |
|
else if ( flDot < 0.5 ) |
|
{ |
|
return( COND_NOT_FACING_ATTACK ); |
|
} |
|
|
|
if ( CheckRangeAttack1 ( flDot, flDist ) ) |
|
return( COND_CAN_RANGE_ATTACK1 ); |
|
|
|
return COND_NONE; |
|
} |
|
|
|
|
|
//========================================================= |
|
// BarneyFirePistol - shoots one round from the pistol at |
|
// the enemy barney is facing. |
|
//========================================================= |
|
void CNPC_Barney::BarneyFirePistol ( void ) |
|
{ |
|
Vector vecShootOrigin; |
|
|
|
vecShootOrigin = GetAbsOrigin() + Vector( 0, 0, 55 ); |
|
Vector vecShootDir = GetShootEnemyDir( vecShootOrigin ); |
|
|
|
QAngle angDir; |
|
|
|
VectorAngles( vecShootDir, angDir ); |
|
// SetBlending( 0, angDir.x ); |
|
DoMuzzleFlash(); |
|
|
|
FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_2DEGREES, 1024, m_iAmmoType ); |
|
|
|
int pitchShift = random->RandomInt( 0, 20 ); |
|
|
|
// Only shift about half the time |
|
if ( pitchShift > 10 ) |
|
pitchShift = 0; |
|
else |
|
pitchShift -= 5; |
|
|
|
CPASAttenuationFilter filter( this ); |
|
|
|
EmitSound_t params; |
|
params.m_pSoundName = "Barney.FirePistol"; |
|
params.m_flVolume = 1; |
|
params.m_nChannel= CHAN_WEAPON; |
|
params.m_SoundLevel = SNDLVL_NORM; |
|
params.m_nPitch = 100 + pitchShift; |
|
EmitSound( filter, entindex(), params ); |
|
|
|
CSoundEnt::InsertSound ( SOUND_COMBAT, GetAbsOrigin(), 384, 0.3 ); |
|
|
|
// UNDONE: Reload? |
|
m_cAmmoLoaded--;// take away a bullet! |
|
} |
|
|
|
int CNPC_Barney::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ) |
|
{ |
|
// make sure friends talk about it if player hurts talkmonsters... |
|
int ret = BaseClass::OnTakeDamage_Alive( inputInfo ); |
|
|
|
if ( !IsAlive() || m_lifeState == LIFE_DYING ) |
|
return ret; |
|
|
|
if ( m_NPCState != NPC_STATE_PRONE && ( inputInfo.GetAttacker()->GetFlags() & FL_CLIENT ) ) |
|
{ |
|
// This is a heurstic to determine if the player intended to harm me |
|
// If I have an enemy, we can't establish intent (may just be crossfire) |
|
if ( GetEnemy() == NULL ) |
|
{ |
|
// If the player was facing directly at me, or I'm already suspicious, get mad |
|
if ( HasMemory( bits_MEMORY_SUSPICIOUS ) || IsFacing( inputInfo.GetAttacker(), GetAbsOrigin() ) ) |
|
{ |
|
// Alright, now I'm pissed! |
|
Speak( BA_MAD ); |
|
|
|
Remember( bits_MEMORY_PROVOKED ); |
|
StopFollowing(); |
|
} |
|
else |
|
{ |
|
// Hey, be careful with that |
|
Speak( BA_SHOT ); |
|
Remember( bits_MEMORY_SUSPICIOUS ); |
|
} |
|
} |
|
else if ( !(GetEnemy()->IsPlayer()) && m_lifeState == LIFE_ALIVE ) |
|
{ |
|
Speak( BA_SHOT ); |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
//========================================================= |
|
// PainSound |
|
//========================================================= |
|
void CNPC_Barney::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( "Barney.Pain", params, NULL ) ) |
|
{ |
|
params.pitch = GetExpresser()->GetVoicePitch(); |
|
|
|
EmitSound_t ep( params ); |
|
|
|
EmitSound( filter, entindex(), ep ); |
|
} |
|
} |
|
|
|
//========================================================= |
|
// DeathSound |
|
//========================================================= |
|
void CNPC_Barney::DeathSound( const CTakeDamageInfo &info ) |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
|
|
CSoundParameters params; |
|
if ( GetParametersForSound( "Barney.Die", params, NULL ) ) |
|
{ |
|
params.pitch = GetExpresser()->GetVoicePitch(); |
|
|
|
EmitSound_t ep( params ); |
|
|
|
EmitSound( filter, entindex(), ep ); |
|
} |
|
} |
|
|
|
void CNPC_Barney::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) |
|
{ |
|
CTakeDamageInfo info = inputInfo; |
|
|
|
switch( ptr->hitgroup ) |
|
{ |
|
case HITGROUP_CHEST: |
|
case HITGROUP_STOMACH: |
|
if ( info.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_BLAST) ) |
|
{ |
|
info.ScaleDamage( 0.5f ); |
|
} |
|
break; |
|
case 10: |
|
if ( info.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_CLUB) ) |
|
{ |
|
info.SetDamage( info.GetDamage() - 20 ); |
|
if ( info.GetDamage() <= 0 ) |
|
{ |
|
g_pEffects->Ricochet( ptr->endpos, ptr->plane.normal ); |
|
info.SetDamage( 0.01 ); |
|
} |
|
} |
|
// always a head shot |
|
ptr->hitgroup = HITGROUP_HEAD; |
|
break; |
|
} |
|
|
|
BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator ); |
|
} |
|
|
|
void CNPC_Barney::Event_Killed( const CTakeDamageInfo &info ) |
|
{ |
|
if ( m_nBody < BARNEY_BODY_GUNGONE ) |
|
{ |
|
// drop the gun! |
|
Vector vecGunPos; |
|
QAngle angGunAngles; |
|
CBaseEntity *pGun = NULL; |
|
|
|
SetBodygroup( 1, BARNEY_BODY_GUNGONE); |
|
|
|
GetAttachment( "0", vecGunPos, angGunAngles ); |
|
|
|
angGunAngles.y += 180; |
|
pGun = DropItem( "weapon_glock", vecGunPos, angGunAngles ); |
|
} |
|
|
|
SetUse( NULL ); |
|
BaseClass::Event_Killed( info ); |
|
|
|
if ( UTIL_IsLowViolence() ) |
|
{ |
|
SUB_StartLVFadeOut( 0.0f ); |
|
} |
|
} |
|
|
|
void CNPC_Barney::SUB_StartLVFadeOut( float delay, bool notSolid ) |
|
{ |
|
SetThink( &CNPC_Barney::SUB_LVFadeOut ); |
|
SetNextThink( gpGlobals->curtime + delay ); |
|
SetRenderColorA( 255 ); |
|
m_nRenderMode = kRenderNormal; |
|
|
|
if ( notSolid ) |
|
{ |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
SetLocalAngularVelocity( vec3_angle ); |
|
} |
|
} |
|
|
|
void CNPC_Barney::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_Barney::StartTask( const Task_t *pTask ) |
|
{ |
|
BaseClass::StartTask( pTask ); |
|
} |
|
|
|
void CNPC_Barney::RunTask( const Task_t *pTask ) |
|
{ |
|
switch ( pTask->iTask ) |
|
{ |
|
case TASK_RANGE_ATTACK1: |
|
if (GetEnemy() != NULL && (GetEnemy()->IsPlayer())) |
|
{ |
|
m_flPlaybackRate = 1.5; |
|
} |
|
BaseClass::RunTask( pTask ); |
|
break; |
|
default: |
|
BaseClass::RunTask( pTask ); |
|
break; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// HandleAnimEvent - catches the monster-specific messages |
|
// that occur when tagged animation frames are played. |
|
// |
|
// Returns number of events handled, 0 if none. |
|
//========================================================= |
|
void CNPC_Barney::HandleAnimEvent( animevent_t *pEvent ) |
|
{ |
|
switch( pEvent->event ) |
|
{ |
|
case BARNEY_AE_SHOOT: |
|
BarneyFirePistol(); |
|
break; |
|
|
|
case BARNEY_AE_DRAW: |
|
// barney's bodygroup switches here so he can pull gun from holster |
|
SetBodygroup( 1, BARNEY_BODY_GUNDRAWN); |
|
m_fGunDrawn = true; |
|
break; |
|
|
|
case BARNEY_AE_HOLSTER: |
|
// change bodygroup to replace gun in holster |
|
SetBodygroup( 1, BARNEY_BODY_GUNHOLSTERED); |
|
m_fGunDrawn = false; |
|
break; |
|
|
|
default: |
|
BaseClass::HandleAnimEvent( pEvent ); |
|
} |
|
} |
|
|
|
|
|
//========================================================= |
|
// AI Schedules Specific to this monster |
|
//========================================================= |
|
|
|
int CNPC_Barney::TranslateSchedule( int scheduleType ) |
|
{ |
|
switch( scheduleType ) |
|
{ |
|
case SCHED_ARM_WEAPON: |
|
if ( GetEnemy() != NULL ) |
|
{ |
|
// face enemy, then draw. |
|
return SCHED_BARNEY_ENEMY_DRAW; |
|
} |
|
break; |
|
|
|
// 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_BARNEY_FACE_TARGET; |
|
else |
|
return baseType; |
|
} |
|
break; |
|
|
|
case SCHED_TARGET_CHASE: |
|
{ |
|
return SCHED_BARNEY_FOLLOW; |
|
break; |
|
} |
|
|
|
case SCHED_IDLE_STAND: |
|
{ |
|
int baseType; |
|
|
|
// call base class default so that scientist will talk |
|
// when 'used' |
|
baseType = BaseClass::TranslateSchedule( scheduleType ); |
|
|
|
if ( baseType == SCHED_IDLE_STAND ) |
|
return SCHED_BARNEY_IDLE_STAND; |
|
else |
|
return baseType; |
|
} |
|
break; |
|
|
|
case SCHED_TAKE_COVER_FROM_ENEMY: |
|
case SCHED_CHASE_ENEMY: |
|
{ |
|
if ( HasCondition( COND_HEAVY_DAMAGE ) ) |
|
return SCHED_TAKE_COVER_FROM_ENEMY; |
|
|
|
// No need to take cover since I can see him |
|
// SHOOT! |
|
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && m_fGunDrawn ) |
|
return SCHED_RANGE_ATTACK1; |
|
} |
|
break; |
|
} |
|
|
|
return BaseClass::TranslateSchedule( scheduleType ); |
|
} |
|
|
|
//========================================================= |
|
// SelectSchedule - Decides which type of schedule best suits |
|
// the monster's current state and conditions. Then calls |
|
// monster's member function to get a pointer to a schedule |
|
// of the proper type. |
|
//========================================================= |
|
int CNPC_Barney::SelectSchedule( void ) |
|
{ |
|
if ( m_NPCState == NPC_STATE_COMBAT || GetEnemy() != NULL ) |
|
{ |
|
// Priority action! |
|
if (!m_fGunDrawn ) |
|
return SCHED_ARM_WEAPON; |
|
} |
|
|
|
if ( GetFollowTarget() == NULL ) |
|
{ |
|
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 ) ) |
|
{ |
|
CSound *pSound; |
|
pSound = GetBestSound(); |
|
|
|
ASSERT( pSound != NULL ); |
|
|
|
if ( pSound && pSound->IsSoundType( SOUND_DANGER ) ) |
|
return SCHED_TAKE_COVER_FROM_BEST_SOUND; |
|
} |
|
if ( HasCondition( COND_ENEMY_DEAD ) && IsOkToSpeak() ) |
|
{ |
|
Speak( BA_KILL ); |
|
} |
|
|
|
switch( m_NPCState ) |
|
{ |
|
case NPC_STATE_COMBAT: |
|
{ |
|
// dead enemy |
|
if ( HasCondition( COND_ENEMY_DEAD ) ) |
|
return BaseClass::SelectSchedule(); // call base class, all code to handle dead enemies is centralized there. |
|
|
|
// always act surprized with a new enemy |
|
if ( HasCondition( COND_NEW_ENEMY ) && HasCondition( COND_LIGHT_DAMAGE) ) |
|
return SCHED_SMALL_FLINCH; |
|
|
|
if ( HasCondition( COND_HEAVY_DAMAGE ) ) |
|
return SCHED_TAKE_COVER_FROM_ENEMY; |
|
|
|
if ( !HasCondition(COND_SEE_ENEMY) ) |
|
{ |
|
// we can't see the enemy |
|
if ( !HasCondition(COND_ENEMY_OCCLUDED) ) |
|
{ |
|
// enemy is unseen, but not occluded! |
|
// turn to face enemy |
|
return SCHED_COMBAT_FACE; |
|
} |
|
else |
|
{ |
|
return SCHED_CHASE_ENEMY; |
|
} |
|
} |
|
} |
|
break; |
|
|
|
case NPC_STATE_ALERT: |
|
case NPC_STATE_IDLE: |
|
if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) ) |
|
{ |
|
// flinch if hurt |
|
return SCHED_SMALL_FLINCH; |
|
} |
|
|
|
if ( GetEnemy() == NULL && GetFollowTarget() ) |
|
{ |
|
if ( !GetFollowTarget()->IsAlive() ) |
|
{ |
|
// UNDONE: Comment about the recently dead player here? |
|
StopFollowing(); |
|
break; |
|
} |
|
else |
|
{ |
|
|
|
return SCHED_TARGET_FACE; |
|
} |
|
} |
|
|
|
// try to say something about smells |
|
TrySmellTalk(); |
|
break; |
|
} |
|
|
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
NPC_STATE CNPC_Barney::SelectIdealState ( void ) |
|
{ |
|
return BaseClass::SelectIdealState(); |
|
} |
|
|
|
void CNPC_Barney::DeclineFollowing( void ) |
|
{ |
|
if ( CanSpeakAfterMyself() ) |
|
{ |
|
Speak( BA_POK ); |
|
} |
|
} |
|
|
|
bool CNPC_Barney::CanBecomeRagdoll( void ) |
|
{ |
|
if ( UTIL_IsLowViolence() ) |
|
{ |
|
return false; |
|
} |
|
|
|
return BaseClass::CanBecomeRagdoll(); |
|
} |
|
|
|
bool CNPC_Barney::ShouldGib( const CTakeDamageInfo &info ) |
|
{ |
|
if ( UTIL_IsLowViolence() ) |
|
{ |
|
return false; |
|
} |
|
|
|
return BaseClass::ShouldGib( info ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// |
|
// Schedules |
|
// |
|
//------------------------------------------------------------------------------ |
|
|
|
AI_BEGIN_CUSTOM_NPC( monster_barney, CNPC_Barney ) |
|
|
|
//========================================================= |
|
// > SCHED_BARNEY_FOLLOW |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_BARNEY_FOLLOW, |
|
|
|
" Tasks" |
|
// " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_BARNEY_STOP_FOLLOWING" |
|
" TASK_GET_PATH_TO_TARGET 0" |
|
" TASK_MOVE_TO_TARGET_RANGE 180" |
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_TARGET_FACE" |
|
" " |
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
" COND_LIGHT_DAMAGE" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_HEAR_DANGER" |
|
" COND_PROVOKED" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_BARNEY_ENEMY_DRAW |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_BARNEY_ENEMY_DRAW, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_FACE_ENEMY 0" |
|
" TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_ARM" |
|
" " |
|
" Interrupts" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_BARNEY_FACE_TARGET |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_BARNEY_FACE_TARGET, |
|
|
|
" Tasks" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" |
|
" TASK_FACE_TARGET 0" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" |
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_BARNEY_FOLLOW" |
|
" " |
|
" Interrupts" |
|
" COND_GIVE_WAY" |
|
" COND_NEW_ENEMY" |
|
" COND_LIGHT_DAMAGE" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_PROVOKED" |
|
" COND_HEAR_DANGER" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_BARNEY_IDLE_STAND |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_BARNEY_IDLE_STAND, |
|
|
|
" 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_PROVOKED" |
|
" COND_HEAR_COMBAT" |
|
" COND_SMELL" |
|
) |
|
|
|
AI_END_CUSTOM_NPC() |
|
|
|
|
|
//========================================================= |
|
// DEAD BARNEY PROP |
|
// |
|
// Designer selects a pose in worldcraft, 0 through num_poses-1 |
|
// this value is added to what is selected as the 'first dead pose' |
|
// among the monster's normal animations. All dead poses must |
|
// appear sequentially in the model file. Be sure and set |
|
// the m_iFirstPose properly! |
|
// |
|
//========================================================= |
|
class CNPC_DeadBarney : public CAI_BaseNPC |
|
{ |
|
DECLARE_CLASS( CNPC_DeadBarney, 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[3]; |
|
|
|
DECLARE_DATADESC(); |
|
}; |
|
|
|
char *CNPC_DeadBarney::m_szPoses[] = { "lying_on_back", "lying_on_side", "lying_on_stomach" }; |
|
|
|
bool CNPC_DeadBarney::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_barney_dead, CNPC_DeadBarney ); |
|
|
|
BEGIN_DATADESC( CNPC_DeadBarney ) |
|
END_DATADESC() |
|
|
|
//========================================================= |
|
// ********** DeadBarney SPAWN ********** |
|
//========================================================= |
|
void CNPC_DeadBarney::Spawn( void ) |
|
{ |
|
PrecacheModel("models/barney.mdl"); |
|
SetModel( "models/barney.mdl"); |
|
|
|
ClearEffects(); |
|
SetSequence( 0 ); |
|
m_bloodColor = BLOOD_COLOR_RED; |
|
|
|
SetRenderColor( 255, 255, 255, 255 ); |
|
|
|
SetSequence( m_iDesiredSequence = LookupSequence( m_szPoses[m_iPose] ) ); |
|
if ( GetSequence() == -1 ) |
|
{ |
|
Msg ( "Dead barney with bad pose\n" ); |
|
} |
|
// Corpses have less health |
|
m_iHealth = 0.0;//gSkillData.barneyHealth; |
|
|
|
NPCInitDead(); |
|
}
|
|
|