diff options
Diffstat (limited to 'game/server/hl1/hl1_npc_barney.cpp')
| -rw-r--r-- | game/server/hl1/hl1_npc_barney.cpp | 938 |
1 files changed, 938 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_npc_barney.cpp b/game/server/hl1/hl1_npc_barney.cpp new file mode 100644 index 0000000..1ff032c --- /dev/null +++ b/game/server/hl1/hl1_npc_barney.cpp @@ -0,0 +1,938 @@ +//========= 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(); +} |