Team Fortress 2 Source Code as on 22/4/2020
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

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