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