summaryrefslogtreecommitdiff
path: root/game/server/hl1/hl1_npc_hgrunt.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/hl1/hl1_npc_hgrunt.cpp')
-rw-r--r--game/server/hl1/hl1_npc_hgrunt.cpp2645
1 files changed, 2645 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_npc_hgrunt.cpp b/game/server/hl1/hl1_npc_hgrunt.cpp
new file mode 100644
index 0000000..375ddf7
--- /dev/null
+++ b/game/server/hl1/hl1_npc_hgrunt.cpp
@@ -0,0 +1,2645 @@
+//========= 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 "beam_shared.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_hgrunt.h"
+#include "soundent.h"
+#include "game.h"
+#include "npcevent.h"
+#include "entitylist.h"
+#include "activitylist.h"
+#include "animation.h"
+#include "engine/IEngineSound.h"
+#include "ammodef.h"
+#include "basecombatweapon.h"
+#include "hl1_basegrenade.h"
+#include "ai_interactions.h"
+#include "scripted.h"
+#include "hl1_basegrenade.h"
+#include "hl1_grenade_mp5.h"
+
+ConVar sk_hgrunt_health( "sk_hgrunt_health","0");
+ConVar sk_hgrunt_kick ( "sk_hgrunt_kick", "0" );
+ConVar sk_hgrunt_pellets ( "sk_hgrunt_pellets", "0" );
+ConVar sk_hgrunt_gspeed ( "sk_hgrunt_gspeed", "0" );
+
+extern ConVar sk_plr_dmg_grenade;
+extern ConVar sk_plr_dmg_mp5_grenade;
+
+#define SF_GRUNT_LEADER ( 1 << 5 )
+
+int g_fGruntQuestion; // true if an idle grunt asked a question. Cleared when someone answers.
+int g_iSquadIndex = 0;
+
+#define HGRUNT_GUN_SPREAD 0.08716f
+
+//=========================================================
+// monster-specific DEFINE's
+//=========================================================
+#define GRUNT_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x!
+#define GRUNT_VOL 0.35 // volume of grunt sounds
+#define GRUNT_SNDLVL SNDLVL_NORM // soundlevel of grunt sentences
+#define HGRUNT_LIMP_HEALTH 20
+#define HGRUNT_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot.
+#define HGRUNT_NUM_HEADS 2 // how many grunt heads are there?
+#define HGRUNT_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill
+#define HGRUNT_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences
+
+#define HGRUNT_9MMAR ( 1 << 0)
+#define HGRUNT_HANDGRENADE ( 1 << 1)
+#define HGRUNT_GRENADELAUNCHER ( 1 << 2)
+#define HGRUNT_SHOTGUN ( 1 << 3)
+
+#define HEAD_GROUP 1
+#define HEAD_GRUNT 0
+#define HEAD_COMMANDER 1
+#define HEAD_SHOTGUN 2
+#define HEAD_M203 3
+#define GUN_GROUP 2
+#define GUN_MP5 0
+#define GUN_SHOTGUN 1
+#define GUN_NONE 2
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define HGRUNT_AE_RELOAD ( 2 )
+#define HGRUNT_AE_KICK ( 3 )
+#define HGRUNT_AE_BURST1 ( 4 )
+#define HGRUNT_AE_BURST2 ( 5 )
+#define HGRUNT_AE_BURST3 ( 6 )
+#define HGRUNT_AE_GREN_TOSS ( 7 )
+#define HGRUNT_AE_GREN_LAUNCH ( 8 )
+#define HGRUNT_AE_GREN_DROP ( 9 )
+#define HGRUNT_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad.
+#define HGRUNT_AE_DROP_GUN ( 11) // grunt (probably dead) is dropping his mp5.
+
+
+const char *CNPC_HGrunt::pGruntSentences[] =
+{
+ "HG_GREN", // grenade scared grunt
+ "HG_ALERT", // sees player
+ "HG_MONSTER", // sees monster
+ "HG_COVER", // running to cover
+ "HG_THROW", // about to throw grenade
+ "HG_CHARGE", // running out to get the enemy
+ "HG_TAUNT", // say rude things
+};
+
+enum HGRUNT_SENTENCE_TYPES
+{
+ HGRUNT_SENT_NONE = -1,
+ HGRUNT_SENT_GREN = 0,
+ HGRUNT_SENT_ALERT,
+ HGRUNT_SENT_MONSTER,
+ HGRUNT_SENT_COVER,
+ HGRUNT_SENT_THROW,
+ HGRUNT_SENT_CHARGE,
+ HGRUNT_SENT_TAUNT,
+} ;
+
+LINK_ENTITY_TO_CLASS( monster_human_grunt, CNPC_HGrunt );
+
+//=========================================================
+// monster-specific schedule types
+//=========================================================
+enum
+{
+ SCHED_GRUNT_FAIL = LAST_SHARED_SCHEDULE,
+ SCHED_GRUNT_COMBAT_FAIL,
+ SCHED_GRUNT_VICTORY_DANCE,
+ SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,
+ SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE_RETRY,
+ SCHED_GRUNT_FOUND_ENEMY,
+ SCHED_GRUNT_COMBAT_FACE,
+ SCHED_GRUNT_SIGNAL_SUPPRESS,
+ SCHED_GRUNT_SUPPRESS,
+ SCHED_GRUNT_WAIT_IN_COVER,
+ SCHED_GRUNT_TAKE_COVER,
+ SCHED_GRUNT_GRENADE_COVER,
+ SCHED_GRUNT_TOSS_GRENADE_COVER,
+ SCHED_GRUNT_HIDE_RELOAD,
+ SCHED_GRUNT_SWEEP,
+ SCHED_GRUNT_RANGE_ATTACK1A,
+ SCHED_GRUNT_RANGE_ATTACK1B,
+ SCHED_GRUNT_RANGE_ATTACK2,
+ SCHED_GRUNT_REPEL,
+ SCHED_GRUNT_REPEL_ATTACK,
+ SCHED_GRUNT_REPEL_LAND,
+ SCHED_GRUNT_TAKE_COVER_FAILED,
+ SCHED_GRUNT_RELOAD,
+ SCHED_GRUNT_TAKE_COVER_FROM_ENEMY,
+ SCHED_GRUNT_BARNACLE_HIT,
+ SCHED_GRUNT_BARNACLE_PULL,
+ SCHED_GRUNT_BARNACLE_CHOMP,
+ SCHED_GRUNT_BARNACLE_CHEW,
+};
+
+//=========================================================
+// monster-specific tasks
+//=========================================================
+enum
+{
+ TASK_GRUNT_FACE_TOSS_DIR = LAST_SHARED_TASK + 1,
+ TASK_GRUNT_SPEAK_SENTENCE,
+ TASK_GRUNT_CHECK_FIRE,
+};
+
+
+//=========================================================
+// monster-specific conditions
+//=========================================================
+enum
+{
+ COND_GRUNT_NOFIRE = LAST_SHARED_CONDITION + 1,
+};
+
+// -----------------------------------------------
+// > Squad slots
+// -----------------------------------------------
+enum SquadSlot_T
+{
+ SQUAD_SLOT_GRENADE1 = LAST_SHARED_SQUADSLOT,
+ SQUAD_SLOT_GRENADE2,
+ SQUAD_SLOT_ENGAGE1,
+ SQUAD_SLOT_ENGAGE2,
+};
+
+
+int ACT_GRUNT_LAUNCH_GRENADE;
+int ACT_GRUNT_TOSS_GRENADE;
+int ACT_GRUNT_MP5_STANDING;
+int ACT_GRUNT_MP5_CROUCHING;
+int ACT_GRUNT_SHOTGUN_STANDING;
+int ACT_GRUNT_SHOTGUN_CROUCHING;
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CNPC_HGrunt )
+ DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextPainTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flCheckAttackTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_iLastGrenadeCondition, FIELD_INTEGER ),
+ DEFINE_FIELD( m_fStanding, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fFirstEncounter, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_iClipSize, FIELD_INTEGER ),
+ DEFINE_FIELD( m_voicePitch, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iSentence, FIELD_INTEGER ),
+ DEFINE_KEYFIELD( m_iWeapons, FIELD_INTEGER, "weapons" ),
+ DEFINE_KEYFIELD( m_SquadName, FIELD_STRING, "netname" ),
+
+ DEFINE_FIELD( m_bInBarnacleMouth, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flLastEnemySightTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flTalkWaitTime, FIELD_TIME ),
+ //DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
+
+END_DATADESC()
+
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_HGrunt::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/hgrunt.mdl" );
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_bloodColor = BLOOD_COLOR_RED;
+ ClearEffects();
+ m_iHealth = sk_hgrunt_health.GetFloat();
+ m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+ m_flNextGrenadeCheck = gpGlobals->curtime + 1;
+ m_flNextPainTime = gpGlobals->curtime;
+ m_iSentence = HGRUNT_SENT_NONE;
+
+ CapabilitiesClear();
+ CapabilitiesAdd ( bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP | bits_CAP_MOVE_GROUND );
+
+ CapabilitiesAdd(bits_CAP_INNATE_RANGE_ATTACK1 );
+
+ // Innate range attack for grenade
+ CapabilitiesAdd(bits_CAP_INNATE_RANGE_ATTACK2 );
+ // Innate range attack for kicking
+ CapabilitiesAdd(bits_CAP_INNATE_MELEE_ATTACK1 );
+
+ m_fFirstEncounter = true;// this is true when the grunt spawns, because he hasn't encountered an enemy yet.
+
+ m_HackedGunPos = Vector ( 0, 0, 55 );
+
+ if ( m_iWeapons == 0)
+ {
+ // initialize to original values
+ m_iWeapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE;
+ // pev->weapons = HGRUNT_SHOTGUN;
+ // pev->weapons = HGRUNT_9MMAR | HGRUNT_GRENADELAUNCHER;
+ }
+
+ if (FBitSet( m_iWeapons, HGRUNT_SHOTGUN ))
+ {
+ SetBodygroup( GUN_GROUP, GUN_SHOTGUN );
+ m_iClipSize = 8;
+ }
+ else
+ {
+ m_iClipSize = GRUNT_CLIP_SIZE;
+ }
+ m_cAmmoLoaded = m_iClipSize;
+
+ if ( random->RandomInt( 0, 99 ) < 80)
+ m_nSkin = 0; // light skin
+ else
+ m_nSkin = 1; // dark skin
+
+ if (FBitSet( m_iWeapons, HGRUNT_SHOTGUN ))
+ {
+ SetBodygroup( HEAD_GROUP, HEAD_SHOTGUN);
+ }
+ else if (FBitSet( m_iWeapons, HGRUNT_GRENADELAUNCHER ))
+ {
+ SetBodygroup( HEAD_GROUP, HEAD_M203 );
+ m_nSkin = 1; // alway dark skin
+ }
+
+ m_flTalkWaitTime = 0;
+
+
+ //HACK
+ g_iSquadIndex = 0;
+
+ BaseClass::Spawn();
+
+ NPCInit();
+}
+
+int CNPC_HGrunt::IRelationPriority( CBaseEntity *pTarget )
+{
+ //I hate alien grunts more than anything.
+ if ( pTarget->Classify() == CLASS_ALIEN_MILITARY )
+ {
+ if ( FClassnameIs( pTarget, "monster_alien_grunt" ) )
+ {
+ return ( BaseClass::IRelationPriority ( pTarget ) + 1 );
+ }
+ }
+
+ return BaseClass::IRelationPriority( pTarget );
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_HGrunt::Precache()
+{
+ m_iAmmoType = GetAmmoDef()->Index("9mmRound");
+
+ PrecacheModel("models/hgrunt.mdl");
+
+ // get voice pitch
+ if ( random->RandomInt(0,1))
+ m_voicePitch = 109 + random->RandomInt(0,7);
+ else
+ m_voicePitch = 100;
+
+ PrecacheScriptSound( "HGrunt.Reload" );
+ PrecacheScriptSound( "HGrunt.GrenadeLaunch" );
+ PrecacheScriptSound( "HGrunt.9MM" );
+ PrecacheScriptSound( "HGrunt.Shotgun" );
+ PrecacheScriptSound( "HGrunt.Pain" );
+ PrecacheScriptSound( "HGrunt.Die" );
+
+ BaseClass::Precache();
+
+ UTIL_PrecacheOther( "grenade_hand" );
+ UTIL_PrecacheOther( "grenade_mp5" );
+}
+
+//=========================================================
+// someone else is talking - don't speak
+//=========================================================
+bool CNPC_HGrunt::FOkToSpeak( void )
+{
+// if someone else is talking, don't speak
+ if ( gpGlobals->curtime <= m_flTalkWaitTime )
+ return FALSE;
+
+ if ( m_spawnflags & SF_NPC_GAG )
+ {
+ if ( m_NPCState != NPC_STATE_COMBAT )
+ {
+ // no talking outside of combat if gagged.
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+
+//=========================================================
+// Speak Sentence - say your cued up sentence.
+//
+// Some grunt sentences (take cover and charge) rely on actually
+// being able to execute the intended action. It's really lame
+// when a grunt says 'COVER ME' and then doesn't move. The problem
+// is that the sentences were played when the decision to TRY
+// to move to cover was made. Now the sentence is played after
+// we know for sure that there is a valid path. The schedule
+// may still fail but in most cases, well after the grunt has
+// started moving.
+//=========================================================
+void CNPC_HGrunt::SpeakSentence( void )
+{
+ if ( m_iSentence == HGRUNT_SENT_NONE )
+ {
+ // no sentence cued up.
+ return;
+ }
+
+ if ( FOkToSpeak() )
+ {
+ SENTENCEG_PlayRndSz( edict(), pGruntSentences[ m_iSentence ], HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ JustSpoke();
+ }
+}
+
+//=========================================================
+//=========================================================
+void CNPC_HGrunt::JustSpoke( void )
+{
+ m_flTalkWaitTime = gpGlobals->curtime + random->RandomFloat( 1.5f, 2.0f );
+ m_iSentence = HGRUNT_SENT_NONE;
+}
+
+//=========================================================
+// PrescheduleThink - this function runs after conditions
+// are collected and before scheduling code is run.
+//=========================================================
+void CNPC_HGrunt::PrescheduleThink ( void )
+{
+ BaseClass::PrescheduleThink();
+
+ if ( m_pSquad && GetEnemy() != NULL )
+ {
+ if ( m_pSquad->GetLeader() == NULL )
+ return;
+
+ CNPC_HGrunt *pSquadLeader = (CNPC_HGrunt*)m_pSquad->GetLeader()->MyNPCPointer();
+
+ if ( pSquadLeader == NULL )
+ return; //Paranoid, so making sure it's ok.
+
+ if ( HasCondition ( COND_SEE_ENEMY ) )
+ {
+ // update the squad's last enemy sighting time.
+ pSquadLeader->m_flLastEnemySightTime = gpGlobals->curtime;
+ }
+ else
+ {
+ if ( gpGlobals->curtime - pSquadLeader->m_flLastEnemySightTime > 5 )
+ {
+ // been a while since we've seen the enemy
+ pSquadLeader->GetEnemies()->MarkAsEluded( GetEnemy() );
+ }
+ }
+ }
+}
+
+Class_T CNPC_HGrunt::Classify ( void )
+{
+ return CLASS_HUMAN_MILITARY;
+}
+
+//=========================================================
+//
+// SquadRecruit(), get some monsters of my classification and
+// link them as a group. returns the group size
+//
+//=========================================================
+int CNPC_HGrunt::SquadRecruit( int searchRadius, int maxMembers )
+{
+ int squadCount;
+ int iMyClass = Classify();// cache this monster's class
+
+ if ( maxMembers < 2 )
+ return 0;
+
+ // I am my own leader
+ squadCount = 1;
+
+ CBaseEntity *pEntity = NULL;
+
+ if ( m_SquadName != NULL_STRING )
+ {
+ // I have a netname, so unconditionally recruit everyone else with that name.
+ pEntity = gEntList.FindEntityByClassname( pEntity, "monster_human_grunt" );
+
+ while ( pEntity )
+ {
+ CNPC_HGrunt *pRecruit = (CNPC_HGrunt*)pEntity->MyNPCPointer();
+
+ if ( pRecruit )
+ {
+ if ( !pRecruit->m_pSquad && pRecruit->Classify() == iMyClass && pRecruit != this )
+ {
+ // minimum protection here against user error.in worldcraft.
+ if ( pRecruit->m_SquadName != NULL_STRING && FStrEq( STRING( m_SquadName ), STRING( pRecruit->m_SquadName ) ) )
+ {
+ pRecruit->InitSquad();
+ squadCount++;
+ }
+ }
+ }
+
+ pEntity = gEntList.FindEntityByClassname( pEntity, "monster_human_grunt" );
+ }
+
+ return squadCount;
+ }
+ else
+ {
+ char szSquadName[64];
+ Q_snprintf( szSquadName, sizeof( szSquadName ), "squad%d\n", g_iSquadIndex );
+
+ m_SquadName = MAKE_STRING( szSquadName );
+
+ while ( ( pEntity = gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), searchRadius ) ) != NULL )
+ {
+ if ( !FClassnameIs ( pEntity, "monster_human_grunt" ) )
+ continue;
+
+ CNPC_HGrunt *pRecruit = (CNPC_HGrunt*)pEntity->MyNPCPointer();
+
+ if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_hCine )
+ {
+ // Can we recruit this guy?
+ if ( !pRecruit->m_pSquad && pRecruit->Classify() == iMyClass &&
+ ( (iMyClass != CLASS_ALIEN_MONSTER) || FClassnameIs( this, pRecruit->GetClassname() ) ) &&
+ !pRecruit->m_SquadName )
+ {
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin() + GetViewOffset(), pRecruit->GetAbsOrigin() + GetViewOffset(), MASK_NPCSOLID_BRUSHONLY, pRecruit, COLLISION_GROUP_NONE, &tr );// try to hit recruit with a traceline.
+
+ if ( tr.fraction == 1.0 )
+ {
+ //We're ready to recruit people, so start a squad if I don't have one.
+ if ( !m_pSquad )
+ {
+ InitSquad();
+ }
+
+ pRecruit->m_SquadName = m_SquadName;
+
+ pRecruit->CapabilitiesAdd ( bits_CAP_SQUAD );
+ pRecruit->InitSquad();
+
+ squadCount++;
+ }
+ }
+ }
+ }
+
+ if ( squadCount > 1 )
+ {
+ g_iSquadIndex++;
+ }
+ }
+
+ return squadCount;
+}
+
+void CNPC_HGrunt::StartNPC ( void )
+{
+ if ( !m_pSquad )
+ {
+ if ( m_SquadName != NULL_STRING )
+ {
+ // if I have a groupname, I can only recruit if I'm flagged as leader
+ if ( GetSpawnFlags() & SF_GRUNT_LEADER )
+ {
+ InitSquad();
+
+ // try to form squads now.
+ int iSquadSize = SquadRecruit( 1024, 4 );
+
+ if ( iSquadSize )
+ {
+ Msg ( "Squad of %d %s formed\n", iSquadSize, GetClassname() );
+ }
+ }
+ else
+ {
+
+ //Hacky.
+ //Revisit me later.
+ const char *pSquadName = STRING( m_SquadName );
+
+ m_SquadName = NULL_STRING;
+
+ BaseClass::StartNPC();
+
+ m_SquadName = MAKE_STRING( pSquadName );
+
+ return;
+ }
+ }
+ else
+ {
+ int iSquadSize = SquadRecruit( 1024, 4 );
+
+ if ( iSquadSize )
+ {
+ Msg ( "Squad of %d %s formed\n", iSquadSize, GetClassname() );
+ }
+ }
+ }
+
+ BaseClass::StartNPC();
+
+ if ( m_pSquad && m_pSquad->IsLeader( this ) )
+ {
+ SetBodygroup( 1, 1 ); // UNDONE: truly ugly hack
+ m_nSkin = 0;
+ }
+}
+
+//=========================================================
+// CheckMeleeAttack1
+//=========================================================
+int CNPC_HGrunt::MeleeAttack1Conditions ( float flDot, float flDist )
+{
+ if (flDist > 64)
+ return COND_TOO_FAR_TO_ATTACK;
+ else if (flDot < 0.7)
+ return COND_NOT_FACING_ATTACK;
+
+ return COND_CAN_MELEE_ATTACK1;
+}
+
+//=========================================================
+// CheckRangeAttack1 - overridden for HGrunt, cause
+// FCanCheckAttacks() doesn't disqualify all attacks based
+// on whether or not the enemy is occluded because unlike
+// the base class, the HGrunt can attack when the enemy is
+// occluded (throw grenade over wall, etc). We must
+// disqualify the machine gun attack if the enemy is occluded.
+//=========================================================
+int CNPC_HGrunt::RangeAttack1Conditions ( float flDot, float flDist )
+{
+ if ( !HasCondition( COND_ENEMY_OCCLUDED ) && flDist <= 2048 && flDot >= 0.5 && NoFriendlyFire() )
+ {
+ trace_t tr;
+
+ if ( !GetEnemy()->IsPlayer() && flDist <= 64 )
+ {
+ // kick nonclients, but don't shoot at them.
+ return COND_NONE;
+ }
+
+ Vector vecSrc;
+ QAngle angAngles;
+
+ GetAttachment( "0", vecSrc, angAngles );
+
+ //NDebugOverlay::Line( GetAbsOrigin() + GetViewOffset(), GetEnemy()->BodyTarget(GetAbsOrigin() + GetViewOffset()), 255, 0, 0, false, 0.1 );
+ // verify that a bullet fired from the gun will hit the enemy before the world.
+ UTIL_TraceLine( GetAbsOrigin() + GetViewOffset(), GetEnemy()->BodyTarget(GetAbsOrigin() + GetViewOffset()), MASK_SHOT, this/*pentIgnore*/, COLLISION_GROUP_NONE, &tr);
+
+ if ( tr.fraction == 1.0 || tr.m_pEnt == GetEnemy() )
+ {
+ //NDebugOverlay::Line( tr.startpos, tr.endpos, 0, 255, 0, false, 1.0 );
+ return COND_CAN_RANGE_ATTACK1;
+ }
+
+ //NDebugOverlay::Line( tr.startpos, tr.endpos, 255, 0, 0, false, 1.0 );
+ }
+
+
+ if ( !NoFriendlyFire() )
+ return COND_WEAPON_BLOCKED_BY_FRIEND; //err =|
+
+ return COND_NONE;
+}
+
+int CNPC_HGrunt::RangeAttack2Conditions( float flDot, float flDist )
+{
+ m_iLastGrenadeCondition = GetGrenadeConditions( flDot, flDist );
+ return m_iLastGrenadeCondition;
+}
+
+int CNPC_HGrunt::GetGrenadeConditions( float flDot, float flDist )
+{
+ if ( !FBitSet( m_iWeapons, ( HGRUNT_HANDGRENADE | HGRUNT_GRENADELAUNCHER ) ) )
+ return COND_NONE;
+
+ // assume things haven't changed too much since last time
+ if (gpGlobals->curtime < m_flNextGrenadeCheck )
+ return m_iLastGrenadeCondition;
+
+ if ( m_flGroundSpeed != 0 )
+ return COND_NONE;
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if (!pEnemy)
+ return COND_NONE;
+
+ Vector flEnemyLKP = GetEnemyLKP();
+ if ( !(pEnemy->GetFlags() & FL_ONGROUND) && pEnemy->GetWaterLevel() == 0 && flEnemyLKP.z > (GetAbsOrigin().z + WorldAlignMaxs().z) )
+ {
+ //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to
+ // be grenaded.
+ // don't throw grenades at anything that isn't on the ground!
+ return COND_NONE;
+ }
+
+ Vector vecTarget;
+
+ if (FBitSet( m_iWeapons, HGRUNT_HANDGRENADE))
+ {
+ // find feet
+ if ( random->RandomInt( 0,1 ) )
+ {
+ // magically know where they are
+ pEnemy->CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.0f ), &vecTarget );
+ }
+ else
+ {
+ // toss it to where you last saw them
+ vecTarget = flEnemyLKP;
+ }
+ }
+ else
+ {
+ // find target
+ // vecTarget = GetEnemy()->BodyTarget( GetAbsOrigin() );
+ vecTarget = GetEnemy()->GetAbsOrigin() + (GetEnemy()->BodyTarget( GetAbsOrigin() ) - GetEnemy()->GetAbsOrigin());
+ // estimate position
+ if ( HasCondition( COND_SEE_ENEMY))
+ {
+ vecTarget = vecTarget + ((vecTarget - GetAbsOrigin()).Length() / sk_hgrunt_gspeed.GetFloat()) * GetEnemy()->GetAbsVelocity();
+ }
+ }
+
+ // are any of my squad members near the intended grenade impact area?
+ if ( m_pSquad )
+ {
+ if ( m_pSquad->SquadMemberInRange( vecTarget, 256 ) )
+ {
+ // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while.
+ m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
+ return COND_NONE;
+ }
+ }
+
+ if ( ( vecTarget - GetAbsOrigin() ).Length2D() <= 256 )
+ {
+ // crap, I don't want to blow myself up
+ m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
+ return COND_NONE;
+ }
+
+
+ if (FBitSet( m_iWeapons, HGRUNT_HANDGRENADE))
+ {
+ Vector vGunPos;
+ QAngle angGunAngles;
+ GetAttachment( "0", vGunPos, angGunAngles );
+
+
+ Vector vecToss = VecCheckToss( this, vGunPos, vecTarget, -1, 0.5, false );
+
+ if ( vecToss != vec3_origin )
+ {
+ m_vecTossVelocity = vecToss;
+
+ // don't check again for a while.
+ m_flNextGrenadeCheck = gpGlobals->curtime + 0.3; // 1/3 second.
+
+ return COND_CAN_RANGE_ATTACK2;
+ }
+ else
+ {
+ // don't check again for a while.
+ m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
+
+ return COND_NONE;
+ }
+ }
+ else
+ {
+ Vector vGunPos;
+ QAngle angGunAngles;
+ GetAttachment( "0", vGunPos, angGunAngles );
+
+ Vector vecToss = VecCheckThrow( this, vGunPos, vecTarget, sk_hgrunt_gspeed.GetFloat(), 0.5 );
+
+ if ( vecToss != vec3_origin )
+ {
+ m_vecTossVelocity = vecToss;
+
+ // don't check again for a while.
+ m_flNextGrenadeCheck = gpGlobals->curtime + 0.3; // 1/3 second.
+
+ return COND_CAN_RANGE_ATTACK2;
+ }
+ else
+ {
+ // don't check again for a while.
+ m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
+
+ return COND_NONE;
+ }
+ }
+}
+
+
+//=========================================================
+// FCanCheckAttacks - this is overridden for human grunts
+// because they can throw/shoot grenades when they can't see their
+// target and the base class doesn't check attacks if the monster
+// cannot see its enemy.
+//
+// !!!BUGBUG - this gets called before a 3-round burst is fired
+// which means that a friendly can still be hit with up to 2 rounds.
+// ALSO, grenades will not be tossed if there is a friendly in front,
+// this is a bad bug. Friendly machine gun fire avoidance
+// will unecessarily prevent the throwing of a grenade as well.
+//=========================================================
+bool CNPC_HGrunt::FCanCheckAttacks( void )
+{
+ // This condition set when too close to a grenade to blow it up
+ if ( !HasCondition( COND_TOO_CLOSE_TO_ATTACK ) )
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+int CNPC_HGrunt::GetSoundInterests( void )
+{
+ return SOUND_WORLD |
+ SOUND_COMBAT |
+ SOUND_PLAYER |
+ SOUND_BULLET_IMPACT |
+ SOUND_DANGER;
+}
+
+
+//=========================================================
+// TraceAttack - make sure we're not taking it in the helmet
+//=========================================================
+void CNPC_HGrunt::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo info = inputInfo;
+
+ // check for helmet shot
+ if (ptr->hitgroup == 11)
+ {
+ // make sure we're wearing one
+ if ( GetBodygroup( 1 ) == HEAD_GRUNT && (info.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB)))
+ {
+ // absorb damage
+ info.SetDamage( info.GetDamage() - 20 );
+ if ( info.GetDamage() <= 0 )
+ info.SetDamage( 0.01 );
+ }
+ // it's head shot anyways
+ ptr->hitgroup = HITGROUP_HEAD;
+ }
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+
+//=========================================================
+// TakeDamage - overridden for the grunt because the grunt
+// needs to forget that he is in cover if he's hurt. (Obviously
+// not in a safe place anymore).
+//=========================================================
+int CNPC_HGrunt::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ Forget( bits_MEMORY_INCOVER );
+
+ return BaseClass::OnTakeDamage_Alive ( inputInfo );
+}
+
+
+//=========================================================
+// SetYawSpeed - allows each sequence to have a different
+// turn rate associated with it.
+//=========================================================
+float CNPC_HGrunt::MaxYawSpeed( void )
+{
+ float flYS;
+
+ switch ( GetActivity() )
+ {
+ case ACT_IDLE:
+ flYS = 150;
+ break;
+ case ACT_RUN:
+ flYS = 150;
+ break;
+ case ACT_WALK:
+ flYS = 180;
+ break;
+ case ACT_RANGE_ATTACK1:
+ flYS = 120;
+ break;
+ case ACT_RANGE_ATTACK2:
+ flYS = 120;
+ break;
+ case ACT_MELEE_ATTACK1:
+ flYS = 120;
+ break;
+ case ACT_MELEE_ATTACK2:
+ flYS = 120;
+ break;
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ flYS = 180;
+ break;
+ case ACT_GLIDE:
+ case ACT_FLY:
+ flYS = 30;
+ break;
+ default:
+ flYS = 90;
+ break;
+ }
+
+ // Yaw speed is handled differently now!
+ return flYS * 0.5f;
+}
+
+void CNPC_HGrunt::IdleSound( void )
+{
+ if (FOkToSpeak() && ( g_fGruntQuestion || random->RandomInt( 0,1 ) ) )
+ {
+ if (!g_fGruntQuestion)
+ {
+ // ask question or make statement
+ switch ( random->RandomInt( 0,2 ) )
+ {
+ case 0: // check in
+ SENTENCEG_PlayRndSz( edict(), "HG_CHECK", HGRUNT_SENTENCE_VOLUME, SNDLVL_NORM, 0, m_voicePitch);
+ g_fGruntQuestion = 1;
+ break;
+ case 1: // question
+ SENTENCEG_PlayRndSz( edict(), "HG_QUEST", HGRUNT_SENTENCE_VOLUME, SNDLVL_NORM, 0, m_voicePitch);
+ g_fGruntQuestion = 2;
+ break;
+ case 2: // statement
+ SENTENCEG_PlayRndSz( edict(), "HG_IDLE", HGRUNT_SENTENCE_VOLUME, SNDLVL_NORM, 0, m_voicePitch);
+ break;
+ }
+ }
+ else
+ {
+ switch (g_fGruntQuestion)
+ {
+ case 1: // check in
+ SENTENCEG_PlayRndSz( edict(), "HG_CLEAR", HGRUNT_SENTENCE_VOLUME, SNDLVL_NORM, 0, m_voicePitch);
+ break;
+ case 2: // question
+ SENTENCEG_PlayRndSz( edict(), "HG_ANSWER", HGRUNT_SENTENCE_VOLUME, SNDLVL_NORM, 0, m_voicePitch);
+ break;
+ }
+ g_fGruntQuestion = 0;
+ }
+ JustSpoke();
+ }
+}
+
+bool CNPC_HGrunt::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
+{
+ if (interactionType == g_interactionBarnacleVictimDangle)
+ {
+ // Force choosing of a new schedule
+ ClearSchedule( "Soldier being eaten by a barnacle" );
+ m_bInBarnacleMouth = true;
+ return true;
+ }
+ else if ( interactionType == g_interactionBarnacleVictimReleased )
+ {
+ SetState ( NPC_STATE_IDLE );
+ m_bInBarnacleMouth = false;
+ SetAbsVelocity( vec3_origin );
+ SetMoveType( MOVETYPE_STEP );
+ return true;
+ }
+ else if ( interactionType == g_interactionBarnacleVictimGrab )
+ {
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ SetGroundEntity( NULL );
+ }
+
+ //Maybe this will break something else.
+ if ( GetState() == NPC_STATE_SCRIPT )
+ {
+ m_hCine->CancelScript();
+ ClearSchedule( "Soldier grabbed by a barnacle" );
+ }
+
+ SetState( NPC_STATE_PRONE );
+
+ CTakeDamageInfo info;
+ PainSound( info );
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Combine needs to check ammo
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CNPC_HGrunt::CheckAmmo ( void )
+{
+ if ( m_cAmmoLoaded <= 0 )
+ SetCondition( COND_NO_PRIMARY_AMMO );
+
+}
+
+//=========================================================
+//=========================================================
+CBaseEntity *CNPC_HGrunt::Kick( void )
+{
+ trace_t tr;
+
+ Vector forward;
+ AngleVectors( GetAbsAngles(), &forward );
+ Vector vecStart = GetAbsOrigin();
+ vecStart.z += WorldAlignSize().z * 0.5;
+ Vector vecEnd = vecStart + (forward * 70);
+
+ UTIL_TraceHull( vecStart, vecEnd, Vector(-16,-16,-18), Vector(16,16,18), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.m_pEnt )
+ {
+ CBaseEntity *pEntity = tr.m_pEnt;
+ return pEntity;
+ }
+
+ return NULL;
+}
+
+Vector CNPC_HGrunt::Weapon_ShootPosition( void )
+{
+ if ( m_fStanding )
+ return GetAbsOrigin() + Vector( 0, 0, 60 );
+ else
+ return GetAbsOrigin() + Vector( 0, 0, 48 );
+}
+
+void CNPC_HGrunt::Event_Killed( const CTakeDamageInfo &info )
+{
+ Vector vecGunPos;
+ QAngle vecGunAngles;
+
+ GetAttachment( "0", vecGunPos, vecGunAngles );
+
+ // switch to body group with no gun.
+ SetBodygroup( GUN_GROUP, GUN_NONE );
+
+ // If the gun would drop into a wall, spawn it at our origin
+ if( UTIL_PointContents( vecGunPos ) & CONTENTS_SOLID )
+ {
+ vecGunPos = GetAbsOrigin();
+ }
+
+ // now spawn a gun.
+ if (FBitSet( m_iWeapons, HGRUNT_SHOTGUN ))
+ {
+ DropItem( "weapon_shotgun", vecGunPos, vecGunAngles );
+ }
+ else
+ {
+ DropItem( "weapon_mp5", vecGunPos, vecGunAngles );
+ }
+
+ if (FBitSet( m_iWeapons, HGRUNT_GRENADELAUNCHER ))
+ {
+ DropItem( "ammo_ARgrenades", BodyTarget( GetAbsOrigin() ), vecGunAngles );
+ }
+
+ BaseClass::Event_Killed( info );
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//=========================================================
+void CNPC_HGrunt::HandleAnimEvent( animevent_t *pEvent )
+{
+ Vector vecShootDir;
+ Vector vecShootOrigin;
+
+ switch( pEvent->event )
+ {
+ case HGRUNT_AE_RELOAD:
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "HGrunt.Reload" );
+
+ m_cAmmoLoaded = m_iClipSize;
+ ClearCondition( COND_NO_PRIMARY_AMMO);
+ }
+ break;
+
+ case HGRUNT_AE_GREN_TOSS:
+ {
+ CHandGrenade *pGrenade = (CHandGrenade*)Create( "grenade_hand", GetAbsOrigin() + Vector(0,0,60), vec3_angle );
+ if ( pGrenade )
+ {
+ pGrenade->ShootTimed( this, m_vecTossVelocity, 3.5 );
+ }
+
+ m_iLastGrenadeCondition = COND_NONE;
+ m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown.
+
+ Msg( "Tossing a grenade to flush you out!\n" );
+ }
+ break;
+
+ case HGRUNT_AE_GREN_LAUNCH:
+ {
+ CPASAttenuationFilter filter2( this );
+ EmitSound( filter2, entindex(), "HGrunt.GrenadeLaunch" );
+
+ Vector vecSrc;
+ QAngle angAngles;
+
+ GetAttachment( "0", vecSrc, angAngles );
+
+ CGrenadeMP5 * m_pMyGrenade = (CGrenadeMP5*)Create( "grenade_mp5", vecSrc, angAngles, this );
+ m_pMyGrenade->SetAbsVelocity( m_vecTossVelocity );
+ m_pMyGrenade->SetLocalAngularVelocity( QAngle( random->RandomFloat( -100, -500 ), 0, 0 ) );
+ m_pMyGrenade->SetMoveType( MOVETYPE_FLYGRAVITY );
+ m_pMyGrenade->SetThrower( this );
+ m_pMyGrenade->SetDamage( sk_plr_dmg_mp5_grenade.GetFloat() );
+
+ if (g_iSkillLevel == SKILL_HARD)
+ m_flNextGrenadeCheck = gpGlobals->curtime + random->RandomFloat( 2, 5 );// wait a random amount of time before shooting again
+ else
+ m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown.
+
+ m_iLastGrenadeCondition = COND_NONE;
+
+ Msg( "Using grenade launcer to flush you out!\n" );
+ }
+ break;
+
+ case HGRUNT_AE_GREN_DROP:
+ {
+ CHandGrenade *pGrenade = (CHandGrenade*)Create( "grenade_hand", Weapon_ShootPosition(), vec3_angle );
+ if ( pGrenade )
+ {
+ pGrenade->ShootTimed( this, m_vecTossVelocity, 3.5 );
+ }
+
+ m_iLastGrenadeCondition = COND_NONE;
+ Msg( "Dropping a grenade!\n" );
+ }
+ break;
+
+ case HGRUNT_AE_BURST1:
+ {
+ if ( FBitSet( m_iWeapons, HGRUNT_9MMAR ) )
+ {
+ Shoot();
+
+ CPASAttenuationFilter filter3( this );
+ // the first round of the three round burst plays the sound and puts a sound in the world sound list.
+ EmitSound( filter3, entindex(), "HGrunt.9MM" );
+ }
+ else
+ {
+ Shotgun( );
+
+ CPASAttenuationFilter filter4( this );
+ EmitSound( filter4, entindex(), "HGrunt.Shotgun" );
+ }
+
+ CSoundEnt::InsertSound ( SOUND_COMBAT, GetAbsOrigin(), 384, 0.3 );
+ }
+ break;
+
+ case HGRUNT_AE_BURST2:
+ case HGRUNT_AE_BURST3:
+ Shoot();
+ break;
+
+ case HGRUNT_AE_KICK:
+ {
+ CBaseEntity *pHurt = Kick();
+
+ if ( pHurt )
+ {
+ // SOUND HERE!
+ Vector forward, up;
+ AngleVectors( GetAbsAngles(), &forward, NULL, &up );
+
+ if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
+ pHurt->ViewPunch( QAngle( 15, 0, 0) );
+
+ // Don't give velocity or damage to the world
+ if( pHurt->entindex() > 0 )
+ {
+ pHurt->ApplyAbsVelocityImpulse( forward * 100 + up * 50 );
+
+ CTakeDamageInfo info( this, this, sk_hgrunt_kick.GetFloat(), DMG_CLUB );
+ CalculateMeleeDamageForce( &info, forward, pHurt->GetAbsOrigin() );
+ pHurt->TakeDamage( info );
+ }
+ }
+ }
+ break;
+
+ case HGRUNT_AE_CAUGHT_ENEMY:
+ {
+ if ( FOkToSpeak() )
+ {
+ SENTENCEG_PlayRndSz( edict(), "HG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ JustSpoke();
+ }
+
+ }
+
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+void CNPC_HGrunt::SetAim( const Vector &aimDir )
+{
+ QAngle angDir;
+ VectorAngles( aimDir, angDir );
+
+ float curPitch = GetPoseParameter( "XR" );
+ float newPitch = curPitch + UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 60 ), curPitch );
+
+ SetPoseParameter( "XR", -newPitch );
+}
+
+//=========================================================
+// Shoot
+//=========================================================
+void CNPC_HGrunt::Shoot ( void )
+{
+ if ( GetEnemy() == NULL )
+ return;
+
+ Vector vecShootOrigin = Weapon_ShootPosition();
+ Vector vecShootDir = GetShootEnemyDir( vecShootOrigin );
+
+ Vector forward, right, up;
+ AngleVectors( GetAbsAngles(), &forward, &right, &up );
+
+ Vector vecShellVelocity = right * random->RandomFloat(40,90) + up * random->RandomFloat( 75,200 ) + forward * random->RandomFloat( -40, 40 );
+ EjectShell( vecShootOrigin - vecShootDir * 24, vecShellVelocity, GetAbsAngles().y, 0 );
+ FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_10DEGREES, 2048, m_iAmmoType ); // shoot +-5 degrees
+
+ DoMuzzleFlash();
+
+ m_cAmmoLoaded--;// take away a bullet!
+
+ SetAim( vecShootDir );
+}
+
+//=========================================================
+// Shoot
+//=========================================================
+void CNPC_HGrunt::Shotgun ( void )
+{
+ if ( GetEnemy() == NULL )
+ return;
+
+ Vector vecShootOrigin = Weapon_ShootPosition();
+ Vector vecShootDir = GetShootEnemyDir( vecShootOrigin );
+
+ Vector forward, right, up;
+ AngleVectors( GetAbsAngles(), &forward, &right, &up );
+
+ Vector vecShellVelocity = right * random->RandomFloat(40,90) + up * random->RandomFloat( 75,200 ) + forward * random->RandomFloat( -40, 40 );
+ EjectShell( vecShootOrigin - vecShootDir * 24, vecShellVelocity, GetAbsAngles().y, 1 );
+ FireBullets( sk_hgrunt_pellets.GetFloat(), vecShootOrigin, vecShootDir, VECTOR_CONE_15DEGREES, 2048, m_iAmmoType, 0 ); // shoot +-7.5 degrees
+
+ DoMuzzleFlash();
+
+ m_cAmmoLoaded--;// take away a bullet!
+
+ SetAim( vecShootDir );
+}
+
+//=========================================================
+// start task
+//=========================================================
+void CNPC_HGrunt::StartTask ( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_GRUNT_CHECK_FIRE:
+ if ( !NoFriendlyFire() )
+ {
+ SetCondition( COND_WEAPON_BLOCKED_BY_FRIEND );
+ }
+ TaskComplete();
+ break;
+
+ case TASK_GRUNT_SPEAK_SENTENCE:
+ SpeakSentence();
+ TaskComplete();
+ break;
+
+ case TASK_WALK_PATH:
+ case TASK_RUN_PATH:
+ // grunt no longer assumes he is covered if he moves
+ Forget( bits_MEMORY_INCOVER );
+ BaseClass ::StartTask( pTask );
+ break;
+
+ case TASK_RELOAD:
+ SetIdealActivity( ACT_RELOAD );
+ break;
+
+ case TASK_GRUNT_FACE_TOSS_DIR:
+ break;
+
+ case TASK_FACE_IDEAL:
+ case TASK_FACE_ENEMY:
+ BaseClass::StartTask( pTask );
+ if (GetMoveType() == MOVETYPE_FLYGRAVITY)
+ {
+ SetIdealActivity( ACT_GLIDE );
+ }
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//=========================================================
+// RunTask
+//=========================================================
+void CNPC_HGrunt::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_GRUNT_FACE_TOSS_DIR:
+ {
+ // project a point along the toss vector and turn to face that point.
+ GetMotor()->SetIdealYawToTargetAndUpdate( GetAbsOrigin() + m_vecTossVelocity * 64, AI_KEEP_YAW_SPEED );
+
+ if ( FacingIdeal() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+ default:
+ {
+ BaseClass::RunTask( pTask );
+ break;
+ }
+ }
+}
+
+//=========================================================
+// PainSound
+//=========================================================
+void CNPC_HGrunt::PainSound( const CTakeDamageInfo &info )
+{
+ if ( gpGlobals->curtime > m_flNextPainTime )
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "HGrunt.Pain" );
+
+ m_flNextPainTime = gpGlobals->curtime + 1;
+ }
+}
+
+//=========================================================
+// DeathSound
+//=========================================================
+void CNPC_HGrunt::DeathSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this, ATTN_IDLE );
+ EmitSound( filter, entindex(), "HGrunt.Die" );
+}
+
+//=========================================================
+// SetActivity
+//=========================================================
+Activity CNPC_HGrunt::NPC_TranslateActivity( Activity eNewActivity )
+{
+ switch ( eNewActivity)
+ {
+ case ACT_RANGE_ATTACK1:
+ // grunt is either shooting standing or shooting crouched
+ if (FBitSet( m_iWeapons, HGRUNT_9MMAR))
+ {
+ if ( m_fStanding )
+ {
+ // get aimable sequence
+ return (Activity)ACT_GRUNT_MP5_STANDING;
+ }
+ else
+ {
+ // get crouching shoot
+ return (Activity)ACT_GRUNT_MP5_CROUCHING;
+ }
+ }
+ else
+ {
+ if ( m_fStanding )
+ {
+ // get aimable sequence
+ return (Activity)ACT_GRUNT_SHOTGUN_STANDING;
+ }
+ else
+ {
+ // get crouching shoot
+ return (Activity)ACT_GRUNT_SHOTGUN_CROUCHING;
+ }
+ }
+ break;
+ case ACT_RANGE_ATTACK2:
+ // grunt is going to a secondary long range attack. This may be a thrown
+ // grenade or fired grenade, we must determine which and pick proper sequence
+ if ( m_iWeapons & HGRUNT_HANDGRENADE )
+ {
+ // get toss anim
+ return (Activity)ACT_GRUNT_TOSS_GRENADE;
+ }
+ else
+ {
+ // get launch anim
+ return (Activity)ACT_GRUNT_LAUNCH_GRENADE;
+ }
+ break;
+ case ACT_RUN:
+ if ( m_iHealth <= HGRUNT_LIMP_HEALTH )
+ {
+ // limp!
+ return ACT_RUN_HURT;
+ }
+ else
+ {
+ return eNewActivity;
+ }
+ break;
+ case ACT_WALK:
+ if ( m_iHealth <= HGRUNT_LIMP_HEALTH )
+ {
+ // limp!
+ return ACT_WALK_HURT;
+ }
+ else
+ {
+ return eNewActivity;
+ }
+ break;
+ case ACT_IDLE:
+ if ( m_NPCState == NPC_STATE_COMBAT )
+ {
+ eNewActivity = ACT_IDLE_ANGRY;
+ }
+
+ break;
+ }
+
+ return BaseClass::NPC_TranslateActivity( eNewActivity );
+}
+
+void CNPC_HGrunt::ClearAttackConditions( void )
+{
+ bool fCanRangeAttack2 = HasCondition( COND_CAN_RANGE_ATTACK2 );
+
+ // Call the base class.
+ BaseClass::ClearAttackConditions();
+
+ if( fCanRangeAttack2 )
+ {
+ // We don't allow the base class to clear this condition because we
+ // don't sense for it every frame.
+ SetCondition( COND_CAN_RANGE_ATTACK2 );
+ }
+}
+
+int CNPC_HGrunt::SelectSchedule( void )
+{
+ // clear old sentence
+ m_iSentence = HGRUNT_SENT_NONE;
+
+ // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling.
+ if ( GetMoveType() == MOVETYPE_FLYGRAVITY && m_NPCState != NPC_STATE_PRONE )
+ {
+ if (GetFlags() & FL_ONGROUND)
+ {
+ // just landed
+ SetMoveType( MOVETYPE_STEP );
+ SetGravity( 1.0 );
+ return SCHED_GRUNT_REPEL_LAND;
+ }
+ else
+ {
+ // repel down a rope,
+ if ( m_NPCState == NPC_STATE_COMBAT )
+ return SCHED_GRUNT_REPEL_ATTACK;
+ else
+ return SCHED_GRUNT_REPEL;
+ }
+ }
+
+ // grunts place HIGH priority on running away from danger sounds.
+ if ( HasCondition ( COND_HEAR_DANGER ) )
+ {
+ // dangerous sound nearby!
+
+ //!!!KELLY - currently, this is the grunt's signal that a grenade has landed nearby,
+ // and the grunt should find cover from the blast
+ // good place for "SHIT!" or some other colorful verbal indicator of dismay.
+ // It's not safe to play a verbal order here "Scatter", etc cause
+ // this may only affect a single individual in a squad.
+
+ if (FOkToSpeak())
+ {
+ SENTENCEG_PlayRndSz( edict(), "HG_GREN", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ JustSpoke();
+ }
+
+ return SCHED_TAKE_COVER_FROM_BEST_SOUND;
+ }
+
+ switch ( m_NPCState )
+ {
+
+ case NPC_STATE_PRONE:
+ {
+ if (m_bInBarnacleMouth)
+ {
+ return SCHED_GRUNT_BARNACLE_CHOMP;
+ }
+ else
+ {
+ return SCHED_GRUNT_BARNACLE_HIT;
+ }
+ }
+
+ case NPC_STATE_COMBAT:
+ {
+// dead enemy
+ if ( HasCondition( COND_ENEMY_DEAD ) )
+ {
+ // call base class, all code to handle dead enemies is centralized there.
+ return BaseClass::SelectSchedule();
+ }
+
+// new enemy
+ if ( HasCondition( COND_NEW_ENEMY) )
+ {
+ if ( m_pSquad )
+ {
+ if ( !m_pSquad->IsLeader( this ) )
+ {
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+ }
+ else
+ {
+ //!!!KELLY - the leader of a squad of grunts has just seen the player or a
+ // monster and has made it the squad's enemy. You
+ // can check pev->flags for FL_CLIENT to determine whether this is the player
+ // or a monster. He's going to immediately start
+ // firing, though. If you'd like, we can make an alternate "first sight"
+ // schedule where the leader plays a handsign anim
+ // that gives us enough time to hear a short sentence or spoken command
+ // before he starts pluggin away.
+ if (FOkToSpeak())// && RANDOM_LONG(0,1))
+ {
+ if ((GetEnemy() != NULL) && GetEnemy()->IsPlayer())
+ // player
+ SENTENCEG_PlayRndSz( edict(), "HG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ else if ((GetEnemy() != NULL) &&
+ (GetEnemy()->Classify() != CLASS_PLAYER_ALLY) &&
+ (GetEnemy()->Classify() != CLASS_HUMAN_PASSIVE) &&
+ (GetEnemy()->Classify() != CLASS_MACHINE) )
+ // monster
+ SENTENCEG_PlayRndSz( edict(), "HG_MONST", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+
+ JustSpoke();
+ }
+
+ if ( HasCondition ( COND_CAN_RANGE_ATTACK1 ) )
+ {
+ return SCHED_GRUNT_SUPPRESS;
+ }
+ else
+ {
+ return SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE;
+ }
+ }
+ }
+ }
+// no ammo
+ else if ( HasCondition ( COND_NO_PRIMARY_AMMO ) )
+ {
+ //!!!KELLY - this individual just realized he's out of bullet ammo.
+ // He's going to try to find cover to run to and reload, but rarely, if
+ // none is available, he'll drop and reload in the open here.
+ return SCHED_GRUNT_HIDE_RELOAD;
+ }
+
+// damaged just a little
+ else if ( HasCondition( COND_LIGHT_DAMAGE ) )
+ {
+ // if hurt:
+ // 90% chance of taking cover
+ // 10% chance of flinch.
+ int iPercent = random->RandomInt(0,99);
+
+ if ( iPercent <= 90 && GetEnemy() != NULL )
+ {
+ // only try to take cover if we actually have an enemy!
+
+ //!!!KELLY - this grunt was hit and is going to run to cover.
+ if (FOkToSpeak()) // && RANDOM_LONG(0,1))
+ {
+ //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ m_iSentence = HGRUNT_SENT_COVER;
+ //JustSpoke();
+ }
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+ }
+ else
+ {
+ return SCHED_SMALL_FLINCH;
+ }
+ }
+// can kick
+ else if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ {
+ return SCHED_MELEE_ATTACK1;
+ }
+// can grenade launch
+
+ else if ( FBitSet( m_iWeapons, HGRUNT_GRENADELAUNCHER) && HasCondition ( COND_CAN_RANGE_ATTACK2 ) && OccupyStrategySlotRange( SQUAD_SLOT_GRENADE1, SQUAD_SLOT_GRENADE2 ) )
+ {
+ // shoot a grenade if you can
+ return SCHED_RANGE_ATTACK2;
+ }
+// can shoot
+ else if ( HasCondition ( COND_CAN_RANGE_ATTACK1 ) )
+ {
+ if ( m_pSquad )
+ {
+ if ( m_pSquad->GetLeader() != NULL )
+ {
+
+ CAI_BaseNPC *pSquadLeader = m_pSquad->GetLeader()->MyNPCPointer();
+
+ // if the enemy has eluded the squad and a squad member has just located the enemy
+ // and the enemy does not see the squad member, issue a call to the squad to waste a
+ // little time and give the player a chance to turn.
+ if ( pSquadLeader && pSquadLeader->EnemyHasEludedMe() && !HasCondition ( COND_ENEMY_FACING_ME ) )
+ {
+ return SCHED_GRUNT_FOUND_ENEMY;
+ }
+ }
+ }
+
+ if ( OccupyStrategySlotRange ( SQUAD_SLOT_ENGAGE1, SQUAD_SLOT_ENGAGE2 ) )
+ {
+ // try to take an available ENGAGE slot
+ return SCHED_RANGE_ATTACK1;
+ }
+ else if ( HasCondition ( COND_CAN_RANGE_ATTACK2 ) && OccupyStrategySlotRange( SQUAD_SLOT_GRENADE1, SQUAD_SLOT_GRENADE2 ) )
+ {
+ // throw a grenade if can and no engage slots are available
+ return SCHED_RANGE_ATTACK2;
+ }
+ else
+ {
+ // hide!
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+ }
+ }
+// can't see enemy
+ else if ( HasCondition( COND_ENEMY_OCCLUDED ) )
+ {
+ if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) && OccupyStrategySlotRange( SQUAD_SLOT_GRENADE1, SQUAD_SLOT_GRENADE2 ) )
+ {
+ //!!!KELLY - this grunt is about to throw or fire a grenade at the player. Great place for "fire in the hole" "frag out" etc
+ if (FOkToSpeak())
+ {
+ SENTENCEG_PlayRndSz( edict(), "HG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ JustSpoke();
+ }
+ return SCHED_RANGE_ATTACK2;
+ }
+ else if ( OccupyStrategySlotRange ( SQUAD_SLOT_ENGAGE1, SQUAD_SLOT_ENGAGE2 ) )
+ {
+ //!!!KELLY - grunt cannot see the enemy and has just decided to
+ // charge the enemy's position.
+ if (FOkToSpeak())// && RANDOM_LONG(0,1))
+ {
+ //SENTENCEG_PlayRndSz( ENT(pev), "HG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ m_iSentence = HGRUNT_SENT_CHARGE;
+ //JustSpoke();
+ }
+
+ return SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE;
+ }
+ else
+ {
+ //!!!KELLY - grunt is going to stay put for a couple seconds to see if
+ // the enemy wanders back out into the open, or approaches the
+ // grunt's covered position. Good place for a taunt, I guess?
+ if (FOkToSpeak() && random->RandomInt(0,1))
+ {
+ SENTENCEG_PlayRndSz( edict(), "HG_TAUNT", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ JustSpoke();
+ }
+ return SCHED_STANDOFF;
+ }
+ }
+
+ if ( HasCondition( COND_SEE_ENEMY ) && !HasCondition ( COND_CAN_RANGE_ATTACK1 ) )
+ {
+ return SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE;
+ }
+ }
+ case NPC_STATE_ALERT:
+ if ( HasCondition( COND_ENEMY_DEAD ) && SelectWeightedSequence( ACT_VICTORY_DANCE ) != ACTIVITY_NOT_AVAILABLE )
+ {
+ // Scan around for new enemies
+ return SCHED_VICTORY_DANCE;
+ }
+ break;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+int CNPC_HGrunt::TranslateSchedule( int scheduleType )
+{
+
+ if ( scheduleType == SCHED_CHASE_ENEMY_FAILED )
+ {
+ return SCHED_ESTABLISH_LINE_OF_FIRE;
+ }
+ switch ( scheduleType )
+ {
+ case SCHED_TAKE_COVER_FROM_ENEMY:
+ {
+ if ( m_pSquad )
+ {
+ if ( g_iSkillLevel == SKILL_HARD && HasCondition( COND_CAN_RANGE_ATTACK2 ) && OccupyStrategySlotRange( SQUAD_SLOT_GRENADE1, SQUAD_SLOT_GRENADE2 ) )
+ {
+ if (FOkToSpeak())
+ {
+ SENTENCEG_PlayRndSz( edict(), "HG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ JustSpoke();
+ }
+ return SCHED_GRUNT_TOSS_GRENADE_COVER;
+ }
+ else
+ {
+ return SCHED_GRUNT_TAKE_COVER;
+ }
+ }
+ else
+ {
+ if ( random->RandomInt(0,1) )
+ {
+ return SCHED_GRUNT_TAKE_COVER;
+ }
+ else
+ {
+ return SCHED_GRUNT_GRENADE_COVER;
+ }
+ }
+ }
+ case SCHED_GRUNT_TAKE_COVER_FAILED:
+ {
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
+ {
+ return SCHED_RANGE_ATTACK1;
+ }
+
+ return SCHED_FAIL;
+ }
+ break;
+
+ case SCHED_RANGE_ATTACK1:
+ {
+ // randomly stand or crouch
+ if ( random->RandomInt( 0,9 ) == 0)
+ {
+ m_fStanding = random->RandomInt( 0, 1 ) != 0;
+ }
+
+ if ( m_fStanding )
+ return SCHED_GRUNT_RANGE_ATTACK1B;
+ else
+ return SCHED_GRUNT_RANGE_ATTACK1A;
+ }
+
+ case SCHED_RANGE_ATTACK2:
+ {
+ return SCHED_GRUNT_RANGE_ATTACK2;
+ }
+ case SCHED_VICTORY_DANCE:
+ {
+ if ( m_pSquad )
+ {
+ if ( !m_pSquad->IsLeader( this ) )
+ {
+ return SCHED_GRUNT_FAIL;
+ }
+ }
+
+ return SCHED_GRUNT_VICTORY_DANCE;
+ }
+ case SCHED_GRUNT_SUPPRESS:
+ {
+ if ( GetEnemy()->IsPlayer() && m_fFirstEncounter )
+ {
+ m_fFirstEncounter = FALSE;// after first encounter, leader won't issue handsigns anymore when he has a new enemy
+ return SCHED_GRUNT_SIGNAL_SUPPRESS;
+ }
+ else
+ {
+ return SCHED_GRUNT_SUPPRESS;
+ }
+ }
+ case SCHED_FAIL:
+ {
+ if ( GetEnemy() != NULL )
+ {
+ // grunt has an enemy, so pick a different default fail schedule most likely to help recover.
+ return SCHED_GRUNT_COMBAT_FAIL;
+ }
+
+ return SCHED_GRUNT_FAIL;
+ }
+ case SCHED_GRUNT_REPEL:
+ {
+ Vector vecVel = GetAbsVelocity();
+ if ( vecVel.z > -128 )
+ {
+ vecVel.z -= 32;
+ SetAbsVelocity( vecVel );
+ }
+
+ return SCHED_GRUNT_REPEL;
+ }
+ case SCHED_GRUNT_REPEL_ATTACK:
+ {
+ Vector vecVel = GetAbsVelocity();
+ if ( vecVel.z > -128 )
+ {
+ vecVel.z -= 32;
+ SetAbsVelocity( vecVel );
+ }
+
+ return SCHED_GRUNT_REPEL_ATTACK;
+ }
+ default:
+ {
+ return BaseClass::TranslateSchedule( scheduleType );
+ }
+ }
+}
+
+//=========================================================
+// CHGruntRepel - when triggered, spawns a monster_human_grunt
+// repelling down a line.
+//=========================================================
+
+class CNPC_HGruntRepel:public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_HGruntRepel, CAI_BaseNPC );
+public:
+ void Spawn( void );
+ void Precache( void );
+ void RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ int m_iSpriteTexture; // Don't save, precache
+
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( monster_grunt_repel, CNPC_HGruntRepel );
+
+
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CNPC_HGruntRepel )
+ DEFINE_USEFUNC( RepelUse ),
+ //DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ),
+END_DATADESC()
+
+void CNPC_HGruntRepel::Spawn( void )
+{
+ Precache( );
+ SetSolid( SOLID_NONE );
+
+ SetUse( &CNPC_HGruntRepel::RepelUse );
+}
+
+void CNPC_HGruntRepel::Precache( void )
+{
+ UTIL_PrecacheOther( "monster_human_grunt" );
+ m_iSpriteTexture = PrecacheModel( "sprites/rope.vmt" );
+}
+
+void CNPC_HGruntRepel::RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, -4096.0), MASK_NPCSOLID, this,COLLISION_GROUP_NONE, &tr);
+
+ CBaseEntity *pEntity = Create( "monster_human_grunt", GetAbsOrigin(), GetAbsAngles() );
+ CAI_BaseNPC *pGrunt = pEntity->MyNPCPointer( );
+ pGrunt->SetMoveType( MOVETYPE_FLYGRAVITY );
+ pGrunt->SetGravity( 0.001 );
+ pGrunt->SetAbsVelocity( Vector( 0, 0, random->RandomFloat( -196, -128 ) ) );
+ pGrunt->SetActivity( ACT_GLIDE );
+ // UNDONE: position?
+ pGrunt->m_vecLastPosition = tr.endpos;
+
+ CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.vmt", 10 );
+ pBeam->PointEntInit( GetAbsOrigin() + Vector(0,0,112), pGrunt );
+ pBeam->SetBeamFlags( FBEAM_SOLID );
+ pBeam->SetColor( 255, 255, 255 );
+ pBeam->SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + -4096.0 * tr.fraction / pGrunt->GetAbsVelocity().z + 0.5 );
+
+ UTIL_Remove( this );
+}
+
+
+//------------------------------------------------------------------------------
+//
+// Schedules
+//
+//------------------------------------------------------------------------------
+AI_BEGIN_CUSTOM_NPC( monster_human_grunt, CNPC_HGrunt )
+
+ DECLARE_ACTIVITY( ACT_GRUNT_LAUNCH_GRENADE )
+ DECLARE_ACTIVITY( ACT_GRUNT_TOSS_GRENADE )
+ DECLARE_ACTIVITY( ACT_GRUNT_MP5_STANDING );
+ DECLARE_ACTIVITY( ACT_GRUNT_MP5_CROUCHING );
+ DECLARE_ACTIVITY( ACT_GRUNT_SHOTGUN_STANDING );
+ DECLARE_ACTIVITY( ACT_GRUNT_SHOTGUN_CROUCHING );
+
+ DECLARE_CONDITION( COND_GRUNT_NOFIRE )
+
+ DECLARE_TASK( TASK_GRUNT_FACE_TOSS_DIR )
+ DECLARE_TASK( TASK_GRUNT_SPEAK_SENTENCE )
+ DECLARE_TASK( TASK_GRUNT_CHECK_FIRE )
+
+ DECLARE_SQUADSLOT( SQUAD_SLOT_GRENADE1 )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_GRENADE2 )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE1 )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE2 )
+
+ //=========================================================
+ // > SCHED_GRUNT_FAIL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_FAIL,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 0"
+ " TASK_WAIT_PVS 0"
+ " "
+ " Interrupts"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_COMBAT_FAIL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_COMBAT_FAIL,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_FACE_ENEMY 2"
+ " TASK_WAIT_PVS 0"
+ " "
+ " Interrupts"
+ " COND_CAN_RANGE_ATTACK1"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_VICTORY_DANCE
+ // Victory dance!
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_VICTORY_DANCE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_WAIT 1.5"
+ " TASK_GET_PATH_TO_ENEMY_CORPSE 0"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE
+ // Establish line of fire - move to a position that allows
+ // the grunt to attack.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE_RETRY"
+ " TASK_GET_PATH_TO_ENEMY 0"
+ " TASK_GRUNT_SPEAK_SENTENCE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // This is a schedule I added that borrows some HL2 technology
+ // to be smarter in cases where HL1 was pretty dumb. I've wedged
+ // this between ESTABLISH_LINE_OF_FIRE and TAKE_COVER_FROM_ENEMY (sjb)
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE_RETRY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_GRUNT_TAKE_COVER_FROM_ENEMY"
+ " TASK_GET_PATH_TO_ENEMY_LKP_LOS 0"
+ " TASK_GRUNT_SPEAK_SENTENCE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_FOUND_ENEMY
+ // Grunt established sight with an enemy
+ // that was hiding from the squad.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_FOUND_ENEMY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_SIGNAL1"
+ " "
+ " Interrupts"
+ " COND_HEAR_DANGER"
+ )
+
+
+ //=========================================================
+ // > SCHED_GRUNT_COMBAT_FACE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_COMBAT_FACE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_ENEMY 0"
+ " TASK_WAIT 1.5"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_SWEEP"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ )
+
+
+ //=========================================================
+ // > SCHED_GRUNT_SIGNAL_SUPPRESS
+ // Suppressing fire - don't stop shooting until the clip is
+ // empty or grunt gets hurt.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_SIGNAL_SUPPRESS,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_SIGNAL2"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_ENEMY_DEAD"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_GRUNT_NOFIRE"
+ " COND_NO_PRIMARY_AMMO"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_SUPPRESS
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_SUPPRESS,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_ENEMY_DEAD"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_GRUNT_NOFIRE"
+ " COND_NO_PRIMARY_AMMO"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_WAIT_IN_COVER
+ // grunt wait in cover - we don't allow danger or the ability
+ // to attack to break a grunt's run to cover schedule, but
+ // when a grunt is in cover, we do want them to attack if they can.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_WAIT_IN_COVER,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_FACE_ENEMY 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ )
+
+
+ //=========================================================
+ // > SCHED_GRUNT_TAKE_COVER
+ // !!!BUGBUG - set a decent fail schedule here.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_TAKE_COVER,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_GRUNT_TAKE_COVER_FAILED"
+ " TASK_WAIT 0.2"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_GRUNT_SPEAK_SENTENCE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_REMEMBER MEMORY:INCOVER"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_WAIT_IN_COVER"
+ " "
+ " Interrupts"
+ )
+
+
+ //=========================================================
+ // > SCHED_GRUNT_GRENADE_COVER
+ // drop grenade then run to cover.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_GRENADE_COVER,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FIND_COVER_FROM_ENEMY 99"
+ " TASK_FIND_FAR_NODE_COVER_FROM_ENEMY 384"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SPECIAL_ATTACK1"
+ " TASK_CLEAR_MOVE_WAIT 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_WAIT_IN_COVER"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_TOSS_GRENADE_COVER
+ // drop grenade then run to cover.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_TOSS_GRENADE_COVER,
+
+ " Tasks"
+ " TASK_FACE_ENEMY 0"
+ " TASK_RANGE_ATTACK2 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_TAKE_COVER_FROM_ENEMY"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_HIDE_RELOAD
+ // Grunt reload schedule
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_HIDE_RELOAD,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_GRUNT_RELOAD"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_REMEMBER MEMORY:INCOVER"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RELOAD"
+ " "
+ " Interrupts"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_SWEEP
+ // Do a turning sweep of the area
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_SWEEP,
+
+ " Tasks"
+ " TASK_TURN_LEFT 179"
+ " TASK_WAIT 1"
+ " TASK_TURN_LEFT 179"
+ " TASK_WAIT 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_HEAR_WORLD"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_PLAYER"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_RANGE_ATTACK1A
+ // primary range attack. Overriden because base class stops attacking when the enemy is occluded.
+ // grunt's grenade toss requires the enemy be occluded.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_RANGE_ATTACK1A,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_CROUCH"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_HEAVY_DAMAGE"
+ " COND_ENEMY_OCCLUDED"
+ " COND_HEAR_DANGER"
+ " COND_GRUNT_NOFIRE"
+ " COND_NO_PRIMARY_AMMO"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_RANGE_ATTACK1B
+ // primary range attack. Overriden because base class stops attacking when the enemy is occluded.
+ // grunt's grenade toss requires the enemy be occluded.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_RANGE_ATTACK1B,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_IDLE_ANGRY"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_HEAVY_DAMAGE"
+ " COND_ENEMY_OCCLUDED"
+ " COND_HEAR_DANGER"
+ " COND_GRUNT_NOFIRE"
+ " COND_NO_PRIMARY_AMMO"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_RANGE_ATTACK2
+ // secondary range attack. Overriden because base class stops attacking when the enemy is occluded.
+ // grunt's grenade toss requires the enemy be occluded.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_RANGE_ATTACK2,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_GRUNT_FACE_TOSS_DIR 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK2"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_WAIT_IN_COVER" // don't run immediately after throwing grenade.
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_REPEL
+ // secondary range attack. Overriden because base class stops attacking when the enemy is occluded.
+ // grunt's grenade toss requires the enemy be occluded.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_REPEL,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_GLIDE"
+ " "
+ " Interrupts"
+ " COND_SEE_ENEMY"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_PLAYER"
+ " COND_HEAR_COMBAT"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_REPEL_ATTACK
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_REPEL_ATTACK,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_FLY"
+ " "
+ " Interrupts"
+ " COND_ENEMY_OCCLUDED"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_REPEL_LAND
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_REPEL_LAND,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_LAND"
+ " TASK_GET_PATH_TO_LASTPOSITION 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_CLEAR_LASTPOSITION 0"
+ " "
+ " Interrupts"
+ " COND_SEE_ENEMY"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_PLAYER"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_RELOAD
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_RELOAD,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RELOAD"
+ " "
+ " Interrupts"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_TAKE_COVER_FROM_ENEMY
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_TAKE_COVER_FROM_ENEMY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WAIT 0.2"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_REMEMBER MEMORY:INCOVER"
+ " TASK_FACE_ENEMY 0"
+ " TASK_WAIT 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_TAKE_COVER_FAILED
+ // special schedule type that forces analysis of conditions and picks
+ // the best possible schedule to recover from this type of failure.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_TAKE_COVER_FAILED,
+ " Tasks"
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_BARNACLE_HIT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_BARNACLE_HIT,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_HIT"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_BARNACLE_PULL"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_BARNACLE_PULL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_BARNACLE_PULL,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_PULL"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_BARNACLE_CHOMP
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_BARNACLE_CHOMP,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_CHOMP"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_BARNACLE_CHEW"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_BARNACLE_CHEW
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_BARNACLE_CHEW,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_CHEW"
+ )
+
+AI_END_CUSTOM_NPC()
+
+
+//=========================================================
+// DEAD HGRUNT PROP
+//=========================================================
+class CNPC_DeadHGrunt : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_DeadHGrunt, CAI_BaseNPC );
+public:
+ void Spawn( void );
+ Class_T Classify ( void ) { return CLASS_HUMAN_MILITARY; }
+ float MaxYawSpeed( void ) { return 8.0f; }
+
+ bool KeyValue( const char *szKeyName, const char *szValue );
+
+ int m_iPose;// which sequence to display -- temporary, don't need to save
+ static char *m_szPoses[3];
+};
+
+char *CNPC_DeadHGrunt::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" };
+
+bool CNPC_DeadHGrunt::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if ( FStrEq( szKeyName, "pose" ) )
+ m_iPose = atoi( szValue );
+ else
+ CAI_BaseNPC::KeyValue( szKeyName, szValue );
+
+ return true;
+}
+
+LINK_ENTITY_TO_CLASS( monster_hgrunt_dead, CNPC_DeadHGrunt );
+
+//=========================================================
+// ********** DeadHGrunt SPAWN **********
+//=========================================================
+void CNPC_DeadHGrunt::Spawn( void )
+{
+ PrecacheModel("models/hgrunt.mdl");
+ SetModel( "models/hgrunt.mdl" );
+
+ ClearEffects();
+ SetSequence( 0 );
+ m_bloodColor = BLOOD_COLOR_RED;
+
+ SetSequence( LookupSequence( m_szPoses[m_iPose] ) );
+
+ if ( GetSequence() == -1 )
+ {
+ Msg ( "Dead hgrunt with bad pose\n" );
+ }
+
+ // Corpses have less health
+ m_iHealth = 8;
+
+ // map old bodies onto new bodies
+ switch( m_nBody )
+ {
+ case 0: // Grunt with Gun
+ m_nBody = 0;
+ m_nSkin = 0;
+ SetBodygroup( HEAD_GROUP, HEAD_GRUNT );
+ SetBodygroup( GUN_GROUP, GUN_MP5 );
+ break;
+ case 1: // Commander with Gun
+ m_nBody = 0;
+ m_nSkin = 0;
+ SetBodygroup( HEAD_GROUP, HEAD_COMMANDER );
+ SetBodygroup( GUN_GROUP, GUN_MP5 );
+ break;
+ case 2: // Grunt no Gun
+ m_nBody = 0;
+ m_nSkin = 0;
+ SetBodygroup( HEAD_GROUP, HEAD_GRUNT );
+ SetBodygroup( GUN_GROUP, GUN_NONE );
+ break;
+ case 3: // Commander no Gun
+ m_nBody = 0;
+ m_nSkin = 0;
+ SetBodygroup( HEAD_GROUP, HEAD_COMMANDER );
+ SetBodygroup( GUN_GROUP, GUN_NONE );
+ break;
+ }
+
+ NPCInitDead();
+}