diff --git a/dlls/CMakeLists.txt b/dlls/CMakeLists.txt index a034b3fb..ab23f608 100644 --- a/dlls/CMakeLists.txt +++ b/dlls/CMakeLists.txt @@ -129,10 +129,13 @@ set (SVDLL_SOURCES zombie.cpp rp/blkop_apache.cpp rp/blkop_osprey.cpp + rp/fgrunt.cpp rp/gonome.cpp rp/massn.cpp + rp/ngrunt.cpp rp/otis.cpp rp/pitdrone.cpp + rp/zgrunt.cpp ../pm_shared/pm_debug.c ../pm_shared/pm_math.c ../pm_shared/pm_shared.c diff --git a/dlls/hgrunt.cpp b/dlls/hgrunt.cpp index 8b67847f..0cbf64b4 100644 --- a/dlls/hgrunt.cpp +++ b/dlls/hgrunt.cpp @@ -46,80 +46,6 @@ int g_fGruntQuestion; // true if an idle grunt asked a question. Cleared when extern DLL_GLOBAL int g_iSkillLevel; -//========================================================= -// 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_ATTN ATTN_NORM // attenutation 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. - -//========================================================= -// monster-specific schedule types -//========================================================= -enum -{ - SCHED_GRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, - SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,// move to a location to set up an attack against the enemy. (usually when a friendly is in the way). - SCHED_GRUNT_COVER_AND_RELOAD, - SCHED_GRUNT_SWEEP, - SCHED_GRUNT_FOUND_ENEMY, - SCHED_GRUNT_REPEL, - SCHED_GRUNT_REPEL_ATTACK, - SCHED_GRUNT_REPEL_LAND, - SCHED_GRUNT_WAIT_FACE_ENEMY, - SCHED_GRUNT_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. - SCHED_GRUNT_ELOF_FAIL -}; - -//========================================================= -// monster-specific tasks -//========================================================= -enum -{ - TASK_GRUNT_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, - TASK_GRUNT_SPEAK_SENTENCE, - TASK_GRUNT_CHECK_FIRE -}; - -//========================================================= -// monster-specific conditions -//========================================================= -#define bits_COND_GRUNT_NOFIRE ( bits_COND_SPECIAL1 ) - LINK_ENTITY_TO_CLASS( monster_human_grunt, CHGrunt ) TYPEDESCRIPTION CHGrunt::m_SaveData[] = @@ -151,18 +77,6 @@ const char *CHGrunt::pGruntSentences[] = "HG_TAUNT", // say rude things }; -typedef enum -{ - 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 -} HGRUNT_SENTENCE_TYPES; - //========================================================= // Speak Sentence - say your cued up sentence. // @@ -647,7 +561,7 @@ void CHGrunt::IdleSound( void ) SENTENCEG_PlayRndSz( ENT( pev ), "HG_CLEAR", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); break; case 2: - // question + // question SENTENCEG_PlayRndSz( ENT( pev ), "HG_ANSWER", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); break; } @@ -859,7 +773,6 @@ void CHGrunt::HandleAnimEvent( MonsterEvent_t *pEvent ) else { Shotgun(); - EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM ); } @@ -907,6 +820,7 @@ void CHGrunt::Spawn() Precache(); SET_MODEL( ENT( pev ), "models/hgrunt.mdl" ); + UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); pev->solid = SOLID_SLIDEBOX; @@ -975,7 +889,7 @@ void CHGrunt::Precache() PRECACHE_SOUND( "hgrunt/gr_mgun1.wav" ); PRECACHE_SOUND( "hgrunt/gr_mgun2.wav" ); - + PRECACHE_SOUND( "hgrunt/gr_die1.wav" ); PRECACHE_SOUND( "hgrunt/gr_die2.wav" ); PRECACHE_SOUND( "hgrunt/gr_die3.wav" ); @@ -986,12 +900,10 @@ void CHGrunt::Precache() PRECACHE_SOUND( "hgrunt/gr_pain4.wav" ); PRECACHE_SOUND( "hgrunt/gr_pain5.wav" ); + PRECACHE_SOUND( "weapons/sbarrel1.wav" ); PRECACHE_SOUND( "hgrunt/gr_reload1.wav" ); PRECACHE_SOUND( "weapons/glauncher.wav" ); - - PRECACHE_SOUND( "weapons/sbarrel1.wav" ); - PRECACHE_SOUND( "zombie/claw_miss2.wav" );// because we use the basemonster SWIPE animation event // get voice pitch @@ -1097,23 +1009,22 @@ void CHGrunt::PainSound( void ) #endif switch( RANDOM_LONG( 0, 6 ) ) { - case 0: - EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_pain3.wav", 1, ATTN_NORM ); + case 0: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_pain3.wav", 1, ATTN_NORM ); break; case 1: - EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_pain4.wav", 1, ATTN_NORM ); + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_pain4.wav", 1, ATTN_NORM ); break; case 2: - EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_pain5.wav", 1, ATTN_NORM ); + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_pain5.wav", 1, ATTN_NORM ); break; case 3: - EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_pain1.wav", 1, ATTN_NORM ); + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_pain1.wav", 1, ATTN_NORM ); break; case 4: - EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_pain2.wav", 1, ATTN_NORM ); + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_pain2.wav", 1, ATTN_NORM ); break; } - m_flNextPainTime = gpGlobals->time + 1; } } @@ -1126,13 +1037,13 @@ void CHGrunt::DeathSound( void ) switch( RANDOM_LONG( 0, 2 ) ) { case 0: - EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_die1.wav", 1, ATTN_IDLE ); + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_die1.wav", 1, ATTN_IDLE ); break; case 1: - EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_die2.wav", 1, ATTN_IDLE ); + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_die2.wav", 1, ATTN_IDLE ); break; case 2: - EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_die3.wav", 1, ATTN_IDLE ); + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hgrunt/gr_die3.wav", 1, ATTN_IDLE ); break; } } @@ -2290,6 +2201,7 @@ void CHGruntRepel::Spawn( void ) void CHGruntRepel::Precache( void ) { UTIL_PrecacheOther( "monster_human_grunt" ); + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); } @@ -2349,23 +2261,14 @@ void CDeadHGrunt::KeyValue( KeyValueData *pkvd ) } LINK_ENTITY_TO_CLASS( monster_hgrunt_dead, CDeadHGrunt ) -LINK_ENTITY_TO_CLASS( monster_human_grunt_ally_dead, CDeadHGrunt ) //========================================================= // ********** DeadHGrunt SPAWN ********** //========================================================= void CDeadHGrunt::Spawn( void ) { - if( FClassnameIs( pev, "monster_human_grunt_ally_dead" ) ) - { - PRECACHE_MODEL( "models/hgrunt_opfor.mdl" ); - SET_MODEL( ENT( pev ), "models/hgrunt_opfor.mdl" ); - } - else - { - PRECACHE_MODEL( "models/hgrunt.mdl" ); - SET_MODEL( ENT( pev ), "models/hgrunt.mdl" ); - } + PRECACHE_MODEL( "models/hgrunt.mdl" ); + SET_MODEL( ENT( pev ), "models/hgrunt.mdl" ); pev->effects = 0; pev->yaw_speed = 8; diff --git a/dlls/hgrunt.h b/dlls/hgrunt.h index 6d1a14e8..153b2bbb 100644 --- a/dlls/hgrunt.h +++ b/dlls/hgrunt.h @@ -15,6 +15,80 @@ #ifndef HGRUNT_H #define HGRUNT_H +//========================================================= +// 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_ATTN ATTN_NORM // attenutation 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. + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_GRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, + SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,// move to a location to set up an attack against the enemy. (usually when a friendly is in the way). + SCHED_GRUNT_COVER_AND_RELOAD, + SCHED_GRUNT_SWEEP, + SCHED_GRUNT_FOUND_ENEMY, + SCHED_GRUNT_REPEL, + SCHED_GRUNT_REPEL_ATTACK, + SCHED_GRUNT_REPEL_LAND, + SCHED_GRUNT_WAIT_FACE_ENEMY, + SCHED_GRUNT_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. + SCHED_GRUNT_ELOF_FAIL +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_GRUNT_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, + TASK_GRUNT_SPEAK_SENTENCE, + TASK_GRUNT_CHECK_FIRE +}; + +//========================================================= +// monster-specific conditions +//========================================================= +#define bits_COND_GRUNT_NOFIRE ( bits_COND_SPECIAL1 ) + //========================================================= // hgrunt //========================================================= @@ -40,7 +114,7 @@ public: virtual void IdleSound(void); Vector GetGunPosition(void); void Shoot(void); - void Shotgun(void); + virtual void Shotgun(void); void PrescheduleThink(void); virtual void GibMonster(void); virtual void SpeakSentence(void); @@ -54,7 +128,7 @@ public: void TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); int TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); - int IRelationship(CBaseEntity *pTarget); + virtual int IRelationship(CBaseEntity *pTarget); virtual BOOL FOkToSpeak(void); void JustSpoke(void); @@ -85,6 +159,17 @@ public: static const char *pGruntSentences[]; }; +typedef enum +{ + 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 +} HGRUNT_SENTENCE_TYPES; //========================================================= // CHGruntRepel - when triggered, spawns a monster_human_grunt diff --git a/dlls/rp/fgrunt.cpp b/dlls/rp/fgrunt.cpp new file mode 100644 index 00000000..dce169e3 --- /dev/null +++ b/dlls/rp/fgrunt.cpp @@ -0,0 +1,1687 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// fgrunt +//========================================================= + +//========================================================= +// Hit groups! +//========================================================= +/* + + 1 - Head + 2 - Stomach + 3 - Gun + +*/ + +#include "extdll.h" +#include "plane.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "squadmonster.h" +#include "weapons.h" +#include "talkmonster.h" +#include "soundent.h" +#include "effects.h" +#include "customentity.h" +#include "hgrunt.h" + +extern int g_fGruntQuestion; // true if an idle grunt asked a question. Cleared when someone answers. + +extern DLL_GLOBAL int g_iSkillLevel; + +extern Schedule_t slIdleStand[]; +extern Schedule_t slGruntFail[]; +extern Schedule_t slGruntCombatFail[]; +extern Schedule_t slGruntVictoryDance[]; +extern Schedule_t slGruntEstablishLineOfFire[]; +extern Schedule_t slGruntFoundEnemy[]; +extern Schedule_t slGruntCombatFace[]; +extern Schedule_t slGruntSignalSuppress[]; +extern Schedule_t slGruntSuppress[]; +extern Schedule_t slGruntWaitInCover[]; +extern Schedule_t slGruntTakeCover[]; +extern Schedule_t slGruntGrenadeCover[]; +extern Schedule_t slGruntTossGrenadeCover[]; +extern Schedule_t slGruntTakeCoverFromBestSound[]; +extern Schedule_t slGruntHideReload[]; +extern Schedule_t slGruntSweep[]; +extern Schedule_t slGruntRangeAttack1A[]; +extern Schedule_t slGruntRangeAttack1B[]; +extern Schedule_t slGruntRangeAttack2[]; +extern Schedule_t slGruntRepel[]; +extern Schedule_t slGruntRepelAttack[]; +extern Schedule_t slGruntRepelLand[]; + +class CFGrunt : public CHGrunt +{ +public: + void Spawn(void); + void Precache(void); + int Classify(void); + void HandleAnimEvent(MonsterEvent_t *pEvent); + void DeathSound(void); + void PainSound(void); + void IdleSound(void); + void SpeakSentence(void); + + int Save(CSave &save); + int Restore(CRestore &restore); + + //CBaseEntity *Kick(void); + Schedule_t *GetSchedule(void); + Schedule_t *GetScheduleOfType(int Type); + + int IRelationship(CBaseEntity *pTarget); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pGruntSentences[]; +}; + +LINK_ENTITY_TO_CLASS( monster_human_grunt_ally, CFGrunt ) + +TYPEDESCRIPTION CFGrunt::m_SaveData[] = +{ + DEFINE_FIELD( CFGrunt, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CFGrunt, m_flNextPainTime, FIELD_TIME ), + //DEFINE_FIELD( CFGrunt, m_flLastEnemySightTime, FIELD_TIME ), // don't save, go to zero + DEFINE_FIELD( CFGrunt, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CFGrunt, m_fThrowGrenade, FIELD_BOOLEAN ), + DEFINE_FIELD( CFGrunt, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CFGrunt, m_fFirstEncounter, FIELD_BOOLEAN ), + DEFINE_FIELD( CFGrunt, m_cClipSize, FIELD_INTEGER ), + DEFINE_FIELD( CFGrunt, m_voicePitch, FIELD_INTEGER ), + //DEFINE_FIELD( CShotgun, m_iBrassShell, FIELD_INTEGER ), + //DEFINE_FIELD( CShotgun, m_iShotgunShell, FIELD_INTEGER ), + DEFINE_FIELD( CFGrunt, m_iSentence, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFGrunt, CSquadMonster ) + +const char *CFGrunt::pGruntSentences[] = +{ + "FG_GREN", // grenade scared grunt + "FG_ALERT", // sees player + "FG_MONSTER", // sees monster + "FG_COVER", // running to cover + "FG_THROW", // about to throw grenade + "FG_CHARGE", // running out to get the enemy + "FG_TAUNT", // say rude things +}; + +//========================================================= +// 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 CFGrunt::SpeakSentence( void ) +{ + if( m_iSentence == HGRUNT_SENT_NONE ) + { + // no sentence cued up. + return; + } + + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), pGruntSentences[m_iSentence], HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } +} + +//========================================================= +// IRelationship - overridden because Alien Grunts are +// Human Grunt's nemesis. +//========================================================= +int CFGrunt::IRelationship( CBaseEntity *pTarget ) +{ + if( FClassnameIs( pTarget->pev, "monster_barnacle" ) || ( FClassnameIs( pTarget->pev, "monster_xarnacle" ) ) ) + { + return R_NM; + } + + return CSquadMonster::IRelationship( pTarget ); +} + +void CFGrunt::IdleSound( void ) +{ + if( FOkToSpeak() && ( g_fGruntQuestion || RANDOM_LONG( 0, 1 ) ) ) + { + if( !g_fGruntQuestion ) + { + // ask question or make statement + switch( RANDOM_LONG( 0, 2 ) ) + { + case 0: + // check in + SENTENCEG_PlayRndSz( ENT( pev ), "FG_CHECK", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + g_fGruntQuestion = 1; + break; + case 1: + // question + SENTENCEG_PlayRndSz( ENT( pev ), "FG_QUEST", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + g_fGruntQuestion = 2; + break; + case 2: + // statement + SENTENCEG_PlayRndSz( ENT( pev ), "FG_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + break; + } + } + else + { + switch( g_fGruntQuestion ) + { + case 1: + // check in + SENTENCEG_PlayRndSz( ENT( pev ), "FG_CLEAR", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + break; + case 2: + // question + SENTENCEG_PlayRndSz( ENT( pev ), "FG_ANSWER", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + break; + } + g_fGruntQuestion = 0; + } + JustSpoke(); + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CFGrunt::Classify( void ) +{ + return CLASS_PLAYER_ALLY; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CFGrunt::HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case HGRUNT_AE_DROP_GUN: + { + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( GUN_GROUP, GUN_NONE ); + + // now spawn a gun. + if( FBitSet( pev->weapons, HGRUNT_SHOTGUN ) ) + { + DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else + { + DropItem( "weapon_9mmAR", vecGunPos, vecGunAngles ); + } + + if( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER ) ) + { + DropItem( "ammo_ARgrenades", BodyTarget( pev->origin ), vecGunAngles ); + } + } + break; + case HGRUNT_AE_RELOAD: + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "hgrunt/gr_reload1.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions( bits_COND_NO_AMMO_LOADED ); + break; + case HGRUNT_AE_GREN_TOSS: + { + UTIL_MakeVectors( pev->angles ); + // CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector( 0, 0, 32 ), m_vecTossVelocity, 3.5 ); + CGrenade::ShootTimed( pev, GetGunPosition(), m_vecTossVelocity, 3.5 ); + + m_fThrowGrenade = FALSE; + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + // !!!LATER - when in a group, only try to throw grenade if ordered. + } + break; + case HGRUNT_AE_GREN_LAUNCH: + { + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM ); + CGrenade::ShootContact( pev, GetGunPosition(), m_vecTossVelocity ); + m_fThrowGrenade = FALSE; + if( g_iSkillLevel == SKILL_HARD ) + m_flNextGrenadeCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 );// wait a random amount of time before shooting again + else + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + } + break; + case HGRUNT_AE_GREN_DROP: + { + UTIL_MakeVectors( pev->angles ); + CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 17 - gpGlobals->v_right * 27 + gpGlobals->v_up * 6, g_vecZero, 3 ); + } + break; + case HGRUNT_AE_BURST1: + { + if( FBitSet( pev->weapons, HGRUNT_9MMAR ) ) + { + Shoot(); + + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + if( RANDOM_LONG( 0, 1 ) ) + { + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "hgrunt/gr_mgun1.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "hgrunt/gr_mgun2.wav", 1, ATTN_NORM ); + } + } + else + { + Shotgun(); + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM ); + } + + CSoundEnt::InsertSound( bits_SOUND_COMBAT, pev->origin, 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! + UTIL_MakeVectors( pev->angles ); + pHurt->pev->punchangle.x = 15; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; + pHurt->TakeDamage( pev, pev, gSkillData.hgruntDmgKick, DMG_CLUB ); + } + } + break; + case HGRUNT_AE_CAUGHT_ENEMY: + { + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "FG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + + } + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CFGrunt::Spawn() +{ + Precache(); + + SET_MODEL( ENT( pev ), "models/hgrunt_opfor.mdl" ); + + UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + pev->health = gSkillData.hgruntHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = HGRUNT_SENT_NONE; + + m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + m_fEnemyEluded = FALSE; + 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( pev->weapons == 0 ) + { + // initialize to original values + pev->weapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE; + // pev->weapons = HGRUNT_SHOTGUN; + // pev->weapons = HGRUNT_9MMAR | HGRUNT_GRENADELAUNCHER; + } + + if( FBitSet( pev->weapons, HGRUNT_SHOTGUN ) ) + { + SetBodygroup( HEAD_M203, GUN_SHOTGUN ); + m_cClipSize = 8; + } + else + { + m_cClipSize = GRUNT_CLIP_SIZE; + } + m_cAmmoLoaded = m_cClipSize; +/* + if( RANDOM_LONG( 0, 99 ) < 80 ) + pev->skin = 0; // light skin + else + pev->skin = 1; // dark skin + + if( FBitSet( pev->weapons, HGRUNT_SHOTGUN ) ) + { + SetBodygroup( HEAD_GROUP, HEAD_SHOTGUN ); + } + else if( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER ) ) + { + SetBodygroup( HEAD_GROUP, HEAD_M203 ); + pev->skin = 1; // alway dark skin + } +*/ + CTalkMonster::g_talkWaitTime = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CFGrunt::Precache() +{ + PRECACHE_MODEL( "models/hgrunt_opfor.mdl" ); + + PRECACHE_SOUND( "hgrunt/gr_mgun1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_mgun2.wav" ); + + PRECACHE_SOUND( "fgrunt/death1.wav" ); + PRECACHE_SOUND( "fgrunt/death2.wav" ); + PRECACHE_SOUND( "fgrunt/death3.wav" ); + PRECACHE_SOUND( "fgrunt/death4.wav" ); + PRECACHE_SOUND( "fgrunt/death5.wav" ); + PRECACHE_SOUND( "fgrunt/death6.wav" ); + + PRECACHE_SOUND( "fgrunt/gr_pain1.wav" ); + PRECACHE_SOUND( "fgrunt/gr_pain2.wav" ); + PRECACHE_SOUND( "fgrunt/gr_pain3.wav" ); + PRECACHE_SOUND( "fgrunt/gr_pain4.wav" ); + PRECACHE_SOUND( "fgrunt/gr_pain5.wav" ); + PRECACHE_SOUND( "fgrunt/gr_pain6.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_reload1.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND( "weapons/sbarrel1.wav" ); + PRECACHE_SOUND( "zombie/claw_miss2.wav" );// because we use the basemonster SWIPE animation event + + // get voice pitch + if( RANDOM_LONG( 0, 1 ) ) + m_voicePitch = 109 + RANDOM_LONG( 0, 7 ); + else + m_voicePitch = 100; + + m_iBrassShell = PRECACHE_MODEL( "models/shell.mdl" );// brass shell + m_iShotgunShell = PRECACHE_MODEL( "models/shotgunshell.mdl" ); +} + +//========================================================= +// PainSound +//========================================================= +void CFGrunt::PainSound( void ) +{ + if( gpGlobals->time > m_flNextPainTime ) + { +#if 0 + if( RANDOM_LONG( 0, 99 ) < 5 ) + { + // pain sentences are rare + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "FG_PAIN", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, PITCH_NORM ); + JustSpoke(); + return; + } + } +#endif + switch( RANDOM_LONG( 0, 6 ) ) + { + case 0: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/gr_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/gr_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/gr_pain5.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/gr_pain6.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/gr_pain1.wav", 1, ATTN_NORM ); + break; + case 5: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/gr_pain2.wav", 1, ATTN_NORM ); + break; + } + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CFGrunt::DeathSound( void ) +{ + switch( RANDOM_LONG( 0, 5 ) ) + { + case 0: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/death1.wav", 1, ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/death2.wav", 1, ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/death3.wav", 1, ATTN_IDLE ); + break; + case 3: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/death4.wav", 1, ATTN_IDLE ); + break; + case 4: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/death5.wav", 1, ATTN_IDLE ); + break; + case 5: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/death6.wav", 1, ATTN_IDLE ); + break; + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +/* +//========================================================= +// GruntFail +//========================================================= +Task_t tlGruntFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntFail[] = +{ + { + tlGruntFail, + ARRAYSIZE( tlGruntFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + 0, + "Grunt Fail" + }, +}; + +//========================================================= +// Grunt Combat Fail +//========================================================= +Task_t tlGruntCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntCombatFail[] = +{ + { + tlGruntCombatFail, + ARRAYSIZE( tlGruntCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Grunt Combat Fail" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlGruntVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, +}; + +Schedule_t slGruntVictoryDance[] = +{ + { + tlGruntVictoryDance, + ARRAYSIZE( tlGruntVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "GruntVictoryDance" + }, +}; + +//========================================================= +// Establish line of fire - move to a position that allows +// the grunt to attack. +//========================================================= +Task_t tlGruntEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slGruntEstablishLineOfFire[] = +{ + { + tlGruntEstablishLineOfFire, + ARRAYSIZE( tlGruntEstablishLineOfFire ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntEstablishLineOfFire" + }, +}; + +//========================================================= +// GruntFoundEnemy - grunt established sight with an enemy +// that was hiding from the squad. +//========================================================= +Task_t tlGruntFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL1 }, +}; + +Schedule_t slGruntFoundEnemy[] = +{ + { + tlGruntFoundEnemy, + ARRAYSIZE( tlGruntFoundEnemy ), + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntFoundEnemy" + }, +}; + +//========================================================= +// GruntCombatFace Schedule +//========================================================= +Task_t tlGruntCombatFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_SWEEP }, +}; + +Schedule_t slGruntCombatFace[] = +{ + { + tlGruntCombatFace1, + ARRAYSIZE( tlGruntCombatFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Suppressing fire - don't stop shooting until the clip is +// empty or grunt gets hurt. +//========================================================= +Task_t tlGruntSignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0}, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSignalSuppress[] = +{ + { + tlGruntSignalSuppress, + ARRAYSIZE( tlGruntSignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; + +Task_t tlGruntSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSuppress[] = +{ + { + tlGruntSuppress, + ARRAYSIZE( tlGruntSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + bits_SOUND_DANGER, + "Suppress" + }, +}; + +//========================================================= +// 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. +//========================================================= +Task_t tlGruntWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slGruntWaitInCover[] = +{ + { + tlGruntWaitInCover, + ARRAYSIZE( tlGruntWaitInCover ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + bits_SOUND_DANGER, + "GruntWaitInCover" + }, +}; + +//========================================================= +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. +//========================================================= +Task_t tlGruntTakeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntTakeCover[] = +{ + { + tlGruntTakeCover1, + ARRAYSIZE ( tlGruntTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntGrenadeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)99 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_PLAY_SEQUENCE, (float)ACT_SPECIAL_ATTACK1 }, + { TASK_CLEAR_MOVE_WAIT, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntGrenadeCover[] = +{ + { + tlGruntGrenadeCover1, + ARRAYSIZE( tlGruntGrenadeCover1 ), + 0, + 0, + "GrenadeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntTossGrenadeCover1[] = +{ + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slGruntTossGrenadeCover[] = +{ + { + tlGruntTossGrenadeCover1, + ARRAYSIZE( tlGruntTossGrenadeCover1 ), + 0, + 0, + "TossGrenadeCover" + }, +}; + +//========================================================= +// hide from the loudest sound source (to run from grenade) +//========================================================= +Task_t tlGruntTakeCoverFromBestSound[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_COWER },// duck and cover if cannot move from explosion + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slGruntTakeCoverFromBestSound[] = +{ + { + tlGruntTakeCoverFromBestSound, + ARRAYSIZE( tlGruntTakeCoverFromBestSound ), + 0, + 0, + "GruntTakeCoverFromBestSound" + }, +}; + +//========================================================= +// Grunt reload schedule +//========================================================= +Task_t tlGruntHideReload[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RELOAD }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RELOAD }, +}; + +Schedule_t slGruntHideReload[] = +{ + { + tlGruntHideReload, + ARRAYSIZE( tlGruntHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntHideReload" + } +}; + +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlGruntSweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slGruntSweep[] = +{ + { + tlGruntSweep, + ARRAYSIZE( tlGruntSweep ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_HEAR_SOUND, + bits_SOUND_WORLD |// sound flags + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + "Grunt Sweep" + }, +}; + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack1A[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1A[] = +{ + { + tlGruntRangeAttack1A, + ARRAYSIZE( tlGruntRangeAttack1A ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + bits_SOUND_DANGER, + "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. +//========================================================= +Task_t tlGruntRangeAttack1B[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_IDLE_ANGRY }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1B[] = +{ + { + tlGruntRangeAttack1B, + ARRAYSIZE( tlGruntRangeAttack1B ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_GRUNT_NOFIRE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "Range Attack1B" + }, +}; + +//========================================================= +// secondary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GRUNT_FACE_TOSS_DIR, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RANGE_ATTACK2 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY },// don't run immediately after throwing grenade. +}; + +Schedule_t slGruntRangeAttack2[] = +{ + { + tlGruntRangeAttack2, + ARRAYSIZE( tlGruntRangeAttack2 ), + 0, + 0, + "RangeAttack2" + }, +}; + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepel[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_GLIDE }, +}; + +Schedule_t slGruntRepel[] = +{ + { + tlGruntRepel, + ARRAYSIZE( tlGruntRepel ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel" + }, +}; + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepelAttack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_FLY }, +}; + +Schedule_t slGruntRepelAttack[] = +{ + { + tlGruntRepelAttack, + ARRAYSIZE( tlGruntRepelAttack ), + bits_COND_ENEMY_OCCLUDED, + 0, + "Repel Attack" + }, +}; + +//========================================================= +// repel land +//========================================================= +Task_t tlGruntRepelLand[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_LAND }, + { TASK_GET_PATH_TO_LASTPOSITION, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slGruntRepelLand[] = +{ + { + tlGruntRepelLand, + ARRAYSIZE( tlGruntRepelLand ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel Land" + }, +}; +*/ + +DEFINE_CUSTOM_SCHEDULES( CFGrunt ) +{ + slGruntFail, + slGruntCombatFail, + slGruntVictoryDance, + slGruntEstablishLineOfFire, + slGruntFoundEnemy, + slGruntCombatFace, + slGruntSignalSuppress, + slGruntSuppress, + slGruntWaitInCover, + slGruntTakeCover, + slGruntGrenadeCover, + slGruntTossGrenadeCover, + slGruntTakeCoverFromBestSound, + slGruntHideReload, + slGruntSweep, + slGruntRangeAttack1A, + slGruntRangeAttack1B, + slGruntRangeAttack2, + slGruntRepel, + slGruntRepelAttack, + slGruntRepelLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CFGrunt, CSquadMonster ) + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CFGrunt::GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = HGRUNT_SENT_NONE; + + // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling. + if( pev->movetype == MOVETYPE_FLY && m_MonsterState != MONSTERSTATE_PRONE ) + { + if( pev->flags & FL_ONGROUND ) + { + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType( SCHED_GRUNT_REPEL_LAND ); + } + else + { + // repel down a rope, + if( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType( SCHED_GRUNT_REPEL_ATTACK ); + else + return GetScheduleOfType( SCHED_GRUNT_REPEL ); + } + } + + // grunts place HIGH priority on running away from danger sounds. + if( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if( pSound ) + { + if( pSound->m_iType & bits_SOUND_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( ENT( pev ), "FG_GREN", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + /* + if( !HasConditions( bits_COND_SEE_ENEMY ) && ( pSound->m_iType & ( bits_SOUND_PLAYER | bits_SOUND_COMBAT ) ) ) + { + MakeIdealYaw( pSound->m_vecOrigin ); + } + */ + } + } + switch( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { + // dead enemy + if( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster::GetSchedule(); + } + + // new enemy + if( HasConditions( bits_COND_NEW_ENEMY ) ) + { + if( InSquad() ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + + if( !IsLeader() ) + { + return GetScheduleOfType( 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( ( m_hEnemy != 0 ) && m_hEnemy->IsPlayer() ) + // player + SENTENCEG_PlayRndSz( ENT( pev ), "FG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + else if( ( m_hEnemy != 0 ) && + ( m_hEnemy->Classify() != CLASS_PLAYER_ALLY ) && + ( m_hEnemy->Classify() != CLASS_HUMAN_PASSIVE ) && + ( m_hEnemy->Classify() != CLASS_MACHINE ) ) + // monster + SENTENCEG_PlayRndSz( ENT( pev ), "FG_MONST", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + + JustSpoke(); + } + + if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_GRUNT_SUPPRESS ); + } + else + { + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + } + // no ammo + else if( HasConditions( bits_COND_NO_AMMO_LOADED ) ) + { + //!!!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 GetScheduleOfType( SCHED_GRUNT_COVER_AND_RELOAD ); + } + // damaged just a little + else if( HasConditions( bits_COND_LIGHT_DAMAGE ) ) + { + // if hurt: + // 90% chance of taking cover + // 10% chance of flinch. + int iPercent = RANDOM_LONG( 0, 99 ); + + if( iPercent <= 90 && m_hEnemy != 0 ) + { + // 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 ), "FG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + m_iSentence = HGRUNT_SENT_COVER; + //JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + } + // can kick + else if( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + // can grenade launch + else if( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER) && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // shoot a grenade if you can + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + // can shoot + else if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if( InSquad() ) + { + // 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( MySquadLeader()->m_fEnemyEluded && !HasConditions( bits_COND_ENEMY_FACING_ME ) ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + return GetScheduleOfType( SCHED_GRUNT_FOUND_ENEMY ); + } + } + + if( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + // try to take an available ENGAGE slot + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + else if( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // throw a grenade if can and no engage slots are available + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else + { + // hide! + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } + // can't see enemy + else if( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + //!!!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( ENT( pev ), "FG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else if( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + //!!!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 ), "FG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + m_iSentence = HGRUNT_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( 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_LONG( 0, 1 ) ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "FG_TAUNT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + break; + default: + break; + } + + // no special cases here, call the base class + return CSquadMonster::GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t *CFGrunt::GetScheduleOfType( int Type ) +{ + switch( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + { + if( InSquad() ) + { + if( g_iSkillLevel == SKILL_HARD && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "FG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return slGruntTossGrenadeCover; + } + else + { + return &slGruntTakeCover[0]; + } + } + else + { + if( RANDOM_LONG( 0, 1 ) ) + { + return &slGruntTakeCover[0]; + } + else + { + return &slGruntGrenadeCover[0]; + } + } + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slGruntTakeCoverFromBestSound[0]; + } + case SCHED_GRUNT_TAKECOVER_FAILED: + { + if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType( SCHED_FAIL ); + } + break; + case SCHED_GRUNT_ELOF_FAIL: + { + // human grunt is unable to move to a position that allows him to attack the enemy. + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + case SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE: + { + return &slGruntEstablishLineOfFire[0]; + } + break; + case SCHED_RANGE_ATTACK1: + { + // randomly stand or crouch + if( RANDOM_LONG( 0, 9 ) == 0 ) + m_fStanding = RANDOM_LONG( 0, 1 ); + + if( m_fStanding ) + return &slGruntRangeAttack1B[0]; + else + return &slGruntRangeAttack1A[0]; + } + case SCHED_RANGE_ATTACK2: + { + return &slGruntRangeAttack2[0]; + } + case SCHED_COMBAT_FACE: + { + return &slGruntCombatFace[0]; + } + case SCHED_GRUNT_WAIT_FACE_ENEMY: + { + return &slGruntWaitInCover[0]; + } + case SCHED_GRUNT_SWEEP: + { + return &slGruntSweep[0]; + } + case SCHED_GRUNT_COVER_AND_RELOAD: + { + return &slGruntHideReload[0]; + } + case SCHED_GRUNT_FOUND_ENEMY: + { + return &slGruntFoundEnemy[0]; + } + case SCHED_VICTORY_DANCE: + { + if( InSquad() ) + { + if( !IsLeader() ) + { + return &slGruntFail[0]; + } + } + + return &slGruntVictoryDance[0]; + } + case SCHED_GRUNT_SUPPRESS: + { + if( m_hEnemy->IsPlayer() && m_fFirstEncounter ) + { + m_fFirstEncounter = FALSE;// after first encounter, leader won't issue handsigns anymore when he has a new enemy + return &slGruntSignalSuppress[0]; + } + else + { + return &slGruntSuppress[0]; + } + } + case SCHED_FAIL: + { + if( m_hEnemy != 0 ) + { + // grunt has an enemy, so pick a different default fail schedule most likely to help recover. + return &slGruntCombatFail[0]; + } + + return &slGruntFail[0]; + } + case SCHED_GRUNT_REPEL: + { + if( pev->velocity.z > -128 ) + pev->velocity.z -= 32; + return &slGruntRepel[0]; + } + case SCHED_GRUNT_REPEL_ATTACK: + { + if( pev->velocity.z > -128 ) + pev->velocity.z -= 32; + return &slGruntRepelAttack[0]; + } + case SCHED_GRUNT_REPEL_LAND: + { + return &slGruntRepelLand[0]; + } + default: + { + return CSquadMonster::GetScheduleOfType( Type ); + } + } +} + +//========================================================= +// CNGruntRepel - when triggered, spawns a monster_nari_grunt +// repelling down a line. +//========================================================= +class CFGruntRepel : public CHGruntRepel +{ +public: + void Spawn(void); + void Precache(void); + void EXPORT RepelUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); +}; + +LINK_ENTITY_TO_CLASS( monster_grunt_repel_ally, CFGruntRepel ) + +void CFGruntRepel::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + + SetUse( &CFGruntRepel::RepelUse ); +} + +void CFGruntRepel::Precache( void ) +{ + UTIL_PrecacheOther( "monster_human_grunt_ally" ); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); +} + +void CFGruntRepel::RepelUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0 ), dont_ignore_monsters, ENT( pev ), &tr ); + /* + if( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP ) + return NULL; + */ + + CBaseEntity *pEntity = Create( "monster_human_grunt_ally", pev->origin, pev->angles ); + CBaseMonster *pGrunt = pEntity->MyMonsterPointer(); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pGrunt->SetActivity( ACT_GLIDE ); + // UNDONE: position? + pGrunt->m_vecLastPosition = tr.vecEndPos; + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( pev->origin + Vector( 0, 0, 112 ), pGrunt->entindex() ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink( &CBaseEntity::SUB_Remove ); + pBeam->pev->nextthink = gpGlobals->time + -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5; + + UTIL_Remove( this ); +} + +//========================================================= +// DEAD FGRUNT PROP +//========================================================= +class CDeadFGrunt : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static const char *m_szPoses[3]; +}; + +const char *CDeadFGrunt::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; + +void CDeadFGrunt::KeyValue( KeyValueData *pkvd ) +{ + if( FStrEq( pkvd->szKeyName, "pose" ) ) + { + m_iPose = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_human_grunt_ally_dead, CDeadFGrunt ) + +//========================================================= +// ********** DeadFGrunt SPAWN ********** +//========================================================= +void CDeadFGrunt::Spawn( void ) +{ + PRECACHE_MODEL( "models/hgrunt_opfor.mdl" ); + SET_MODEL( ENT( pev ), "models/hgrunt_opfor.mdl" ); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if( pev->sequence == -1 ) + { + ALERT( at_console, "Dead fgrunt with bad pose\n" ); + } + + // Corpses have less health + pev->health = 8; + + // map old bodies onto new bodies + switch( pev->body ) + { + case 0: + case 1: + // Grunt with Gun + pev->body = 0; + SetBodygroup( HEAD_M203, HEAD_GRUNT ); + break; + case 2: + case 3: + // Grunt no Gun + pev->body = 0; + SetBodygroup( HEAD_M203, HEAD_M203 ); + break; + } + + MonsterInitDead(); +} diff --git a/dlls/rp/ngrunt.cpp b/dlls/rp/ngrunt.cpp new file mode 100644 index 00000000..93c642e2 --- /dev/null +++ b/dlls/rp/ngrunt.cpp @@ -0,0 +1,1581 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// ngrunt +//========================================================= + +//========================================================= +// Hit groups! +//========================================================= +/* + + 1 - Head + 2 - Stomach + 3 - Gun + +*/ + +#include "extdll.h" +#include "plane.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "squadmonster.h" +#include "weapons.h" +#include "talkmonster.h" +#include "soundent.h" +#include "effects.h" +#include "customentity.h" +#include "hgrunt.h" + +extern int g_fGruntQuestion; // true if an idle grunt asked a question. Cleared when someone answers. + +extern DLL_GLOBAL int g_iSkillLevel; + +extern Schedule_t slIdleStand[]; +extern Schedule_t slGruntFail[]; +extern Schedule_t slGruntCombatFail[]; +extern Schedule_t slGruntVictoryDance[]; +extern Schedule_t slGruntEstablishLineOfFire[]; +extern Schedule_t slGruntFoundEnemy[]; +extern Schedule_t slGruntCombatFace[]; +extern Schedule_t slGruntSignalSuppress[]; +extern Schedule_t slGruntSuppress[]; +extern Schedule_t slGruntWaitInCover[]; +extern Schedule_t slGruntTakeCover[]; +extern Schedule_t slGruntGrenadeCover[]; +extern Schedule_t slGruntTossGrenadeCover[]; +extern Schedule_t slGruntTakeCoverFromBestSound[]; +extern Schedule_t slGruntHideReload[]; +extern Schedule_t slGruntSweep[]; +extern Schedule_t slGruntRangeAttack1A[]; +extern Schedule_t slGruntRangeAttack1B[]; +extern Schedule_t slGruntRangeAttack2[]; +extern Schedule_t slGruntRepel[]; +extern Schedule_t slGruntRepelAttack[]; +extern Schedule_t slGruntRepelLand[]; + +class CNGrunt : public CHGrunt +{ +public: + void Spawn(void); + void Precache(void); + void HandleAnimEvent(MonsterEvent_t *pEvent); + void DeathSound(void); + void PainSound(void); + void IdleSound(void); + void SpeakSentence(void); + + int Save(CSave &save); + int Restore(CRestore &restore); + + //CBaseEntity *Kick(void); + Schedule_t *GetSchedule(void); + Schedule_t *GetScheduleOfType(int Type); + + int IRelationship(CBaseEntity *pTarget); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pGruntSentences[]; +}; + +LINK_ENTITY_TO_CLASS( monster_nari_grunt, CNGrunt ) + +TYPEDESCRIPTION CNGrunt::m_SaveData[] = +{ + DEFINE_FIELD( CNGrunt, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CNGrunt, m_flNextPainTime, FIELD_TIME ), + //DEFINE_FIELD( CNGrunt, m_flLastEnemySightTime, FIELD_TIME ), // don't save, go to zero + DEFINE_FIELD( CNGrunt, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CNGrunt, m_fThrowGrenade, FIELD_BOOLEAN ), + DEFINE_FIELD( CNGrunt, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CNGrunt, m_fFirstEncounter, FIELD_BOOLEAN ), + DEFINE_FIELD( CNGrunt, m_cClipSize, FIELD_INTEGER ), + DEFINE_FIELD( CNGrunt, m_voicePitch, FIELD_INTEGER ), + //DEFINE_FIELD( CShotgun, m_iBrassShell, FIELD_INTEGER ), + //DEFINE_FIELD( CShotgun, m_iShotgunShell, FIELD_INTEGER ), + DEFINE_FIELD( CNGrunt, m_iSentence, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CNGrunt, CSquadMonster ) + +const char *CNGrunt::pGruntSentences[] = +{ + "FG_GREN", // grenade scared grunt + "NG_ALERT", // sees player + "FG_MONSTER", // sees monster + "FG_COVER", // running to cover + "FG_THROW", // about to throw grenade + "FG_CHARGE", // running out to get the enemy + "FG_TAUNT", // say rude things +}; + +//========================================================= +// 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 CNGrunt::SpeakSentence( void ) +{ + if( m_iSentence == HGRUNT_SENT_NONE ) + { + // no sentence cued up. + return; + } + + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), pGruntSentences[m_iSentence], HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } +} + +//========================================================= +// IRelationship - overridden because Alien Grunts are +// Human Grunt's nemesis. +//========================================================= +int CNGrunt::IRelationship( CBaseEntity *pTarget ) +{ + return CSquadMonster::IRelationship( pTarget ); +} + +void CNGrunt::IdleSound( void ) +{ + if( FOkToSpeak() && ( g_fGruntQuestion || RANDOM_LONG( 0, 1 ) ) ) + { + if( !g_fGruntQuestion ) + { + // ask question or make statement + switch( RANDOM_LONG( 0, 2 ) ) + { + case 0: + // check in + //SENTENCEG_PlayRndSz( ENT( pev ), "NG_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + g_fGruntQuestion = 1; + break; + case 1: + // question + //SENTENCEG_PlayRndSz( ENT( pev ), "NG_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + g_fGruntQuestion = 2; + break; + case 2: + // statement + //SENTENCEG_PlayRndSz( ENT( pev ), "NG_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + break; + } + } + else + { + /*switch( g_fGruntQuestion ) + { + case 1: + case 2: + // statement + SENTENCEG_PlayRndSz( ENT( pev ), "NG_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + break; + }*/ + g_fGruntQuestion = 0; + } + SENTENCEG_PlayRndSz( ENT( pev ), "NG_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + JustSpoke(); + } +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CNGrunt::HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case HGRUNT_AE_DROP_GUN: + { + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( GUN_GROUP, GUN_NONE ); + + // now spawn a gun. + if( FBitSet( pev->weapons, HGRUNT_SHOTGUN ) ) + { + // DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else + { + DropItem( "weapon_9mmAR", vecGunPos, vecGunAngles ); + } + + if( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER ) ) + { + DropItem( "ammo_ARgrenades", BodyTarget( pev->origin ), vecGunAngles ); + } + } + break; + case HGRUNT_AE_RELOAD: + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "hgrunt/gr_reload1.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions( bits_COND_NO_AMMO_LOADED ); + break; + case HGRUNT_AE_GREN_TOSS: + { + UTIL_MakeVectors( pev->angles ); + // CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector( 0, 0, 32 ), m_vecTossVelocity, 3.5 ); + CGrenade::ShootTimed( pev, GetGunPosition(), m_vecTossVelocity, 3.5 ); + + m_fThrowGrenade = FALSE; + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + // !!!LATER - when in a group, only try to throw grenade if ordered. + } + break; + case HGRUNT_AE_GREN_LAUNCH: + { + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM ); + CGrenade::ShootContact( pev, GetGunPosition(), m_vecTossVelocity ); + m_fThrowGrenade = FALSE; + if( g_iSkillLevel == SKILL_HARD ) + m_flNextGrenadeCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 );// wait a random amount of time before shooting again + else + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + } + break; + case HGRUNT_AE_GREN_DROP: + { + UTIL_MakeVectors( pev->angles ); + CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 17 - gpGlobals->v_right * 27 + gpGlobals->v_up * 6, g_vecZero, 3 ); + } + break; + case HGRUNT_AE_BURST1: + { + if( FBitSet( pev->weapons, HGRUNT_9MMAR ) ) + { + Shoot(); + + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + if( RANDOM_LONG( 0, 1 ) ) + { + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "hgrunt/gr_mgun1.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "hgrunt/gr_mgun2.wav", 1, ATTN_NORM ); + } + } + else + { + Shotgun(); + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/sniper_fire.wav", 1, ATTN_NORM ); + } + + CSoundEnt::InsertSound( bits_SOUND_COMBAT, pev->origin, 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! + UTIL_MakeVectors( pev->angles ); + pHurt->pev->punchangle.x = 15; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; + pHurt->TakeDamage( pev, pev, gSkillData.hgruntDmgKick, DMG_CLUB ); + } + } + break; + case HGRUNT_AE_CAUGHT_ENEMY: + { + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "NG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + + } + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CNGrunt::Spawn() +{ + Precache(); + + SET_MODEL( ENT( pev ), "models/ngrunt.mdl" ); + + UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + pev->health = gSkillData.hgruntHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = HGRUNT_SENT_NONE; + + m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + m_fEnemyEluded = FALSE; + 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( pev->weapons == 0 ) + { + // initialize to original values + pev->weapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE; + // pev->weapons = HGRUNT_SHOTGUN; + // pev->weapons = HGRUNT_9MMAR | HGRUNT_GRENADELAUNCHER; + } + + if( FBitSet( pev->weapons, HGRUNT_SHOTGUN ) ) + { + SetBodygroup( HEAD_COMMANDER, GUN_SHOTGUN ); + m_cClipSize = 8; + } + else + { + m_cClipSize = GRUNT_CLIP_SIZE; + } + m_cAmmoLoaded = m_cClipSize; + + if( RANDOM_LONG( 0, 99 ) < 80 ) + pev->skin = 0; // light skin + else + pev->skin = 1; // dark skin + + if( FBitSet( pev->weapons, HGRUNT_SHOTGUN ) ) + { + SetBodygroup( HEAD_GROUP, HEAD_SHOTGUN ); + } + else if( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER ) ) + { + SetBodygroup( HEAD_GROUP, HEAD_M203 ); + pev->skin = 1; // alway dark skin + } + + CTalkMonster::g_talkWaitTime = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CNGrunt::Precache() +{ + PRECACHE_MODEL( "models/ngrunt.mdl" ); + + PRECACHE_SOUND( "hgrunt/gr_mgun1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_mgun2.wav" ); + + PRECACHE_SOUND( "ngrunt/ngr_death1.wav" ); + PRECACHE_SOUND( "ngrunt/ngr_death2.wav" ); + PRECACHE_SOUND( "ngrunt/ngr_death3.wav" ); + + PRECACHE_SOUND( "fgrunt/gr_pain1.wav" ); + PRECACHE_SOUND( "fgrunt/gr_pain2.wav" ); + PRECACHE_SOUND( "fgrunt/gr_pain3.wav" ); + PRECACHE_SOUND( "fgrunt/gr_pain4.wav" ); + PRECACHE_SOUND( "fgrunt/gr_pain5.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_reload1.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND( "weapons/sniper_fire.wav" ); + PRECACHE_SOUND( "zombie/claw_miss2.wav" );// because we use the basemonster SWIPE animation event + + // get voice pitch + if( RANDOM_LONG( 0, 1 ) ) + m_voicePitch = 109 + RANDOM_LONG( 0, 7 ); + else + m_voicePitch = 100; + + m_iBrassShell = PRECACHE_MODEL( "models/shell.mdl" );// brass shell + m_iShotgunShell = PRECACHE_MODEL( "models/shotgunshell.mdl" ); +} + +//========================================================= +// PainSound +//========================================================= +void CNGrunt::PainSound( void ) +{ + if( gpGlobals->time > m_flNextPainTime ) + { +#if 0 + if( RANDOM_LONG( 0, 99 ) < 5 ) + { + // pain sentences are rare + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "FG_PAIN", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, PITCH_NORM ); + JustSpoke(); + return; + } + } +#endif + switch( RANDOM_LONG( 0, 6 ) ) + { + case 0: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/gr_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/gr_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/gr_pain5.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/gr_pain1.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "fgrunt/gr_pain2.wav", 1, ATTN_NORM ); + break; + } + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CNGrunt::DeathSound( void ) +{ + switch( RANDOM_LONG( 0, 2 ) ) + { + case 0: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "ngrunt/ngr_death1.wav", 1, ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "ngrunt/ngr_death2.wav", 1, ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "ngrunt/ngr_death3.wav", 1, ATTN_IDLE ); + break; + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +/* +//========================================================= +// GruntFail +//========================================================= +Task_t tlGruntFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntFail[] = +{ + { + tlGruntFail, + ARRAYSIZE( tlGruntFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + 0, + "Grunt Fail" + }, +}; + +//========================================================= +// Grunt Combat Fail +//========================================================= +Task_t tlGruntCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntCombatFail[] = +{ + { + tlGruntCombatFail, + ARRAYSIZE( tlGruntCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Grunt Combat Fail" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlGruntVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, +}; + +Schedule_t slGruntVictoryDance[] = +{ + { + tlGruntVictoryDance, + ARRAYSIZE( tlGruntVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "GruntVictoryDance" + }, +}; + +//========================================================= +// Establish line of fire - move to a position that allows +// the grunt to attack. +//========================================================= +Task_t tlGruntEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slGruntEstablishLineOfFire[] = +{ + { + tlGruntEstablishLineOfFire, + ARRAYSIZE( tlGruntEstablishLineOfFire ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntEstablishLineOfFire" + }, +}; + +//========================================================= +// GruntFoundEnemy - grunt established sight with an enemy +// that was hiding from the squad. +//========================================================= +Task_t tlGruntFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL1 }, +}; + +Schedule_t slGruntFoundEnemy[] = +{ + { + tlGruntFoundEnemy, + ARRAYSIZE( tlGruntFoundEnemy ), + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntFoundEnemy" + }, +}; + +//========================================================= +// GruntCombatFace Schedule +//========================================================= +Task_t tlGruntCombatFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_SWEEP }, +}; + +Schedule_t slGruntCombatFace[] = +{ + { + tlGruntCombatFace1, + ARRAYSIZE( tlGruntCombatFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Suppressing fire - don't stop shooting until the clip is +// empty or grunt gets hurt. +//========================================================= +Task_t tlGruntSignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0}, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSignalSuppress[] = +{ + { + tlGruntSignalSuppress, + ARRAYSIZE( tlGruntSignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; + +Task_t tlGruntSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSuppress[] = +{ + { + tlGruntSuppress, + ARRAYSIZE( tlGruntSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + bits_SOUND_DANGER, + "Suppress" + }, +}; + +//========================================================= +// 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. +//========================================================= +Task_t tlGruntWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slGruntWaitInCover[] = +{ + { + tlGruntWaitInCover, + ARRAYSIZE( tlGruntWaitInCover ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + bits_SOUND_DANGER, + "GruntWaitInCover" + }, +}; + +//========================================================= +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. +//========================================================= +Task_t tlGruntTakeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntTakeCover[] = +{ + { + tlGruntTakeCover1, + ARRAYSIZE ( tlGruntTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntGrenadeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)99 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_PLAY_SEQUENCE, (float)ACT_SPECIAL_ATTACK1 }, + { TASK_CLEAR_MOVE_WAIT, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntGrenadeCover[] = +{ + { + tlGruntGrenadeCover1, + ARRAYSIZE( tlGruntGrenadeCover1 ), + 0, + 0, + "GrenadeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntTossGrenadeCover1[] = +{ + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slGruntTossGrenadeCover[] = +{ + { + tlGruntTossGrenadeCover1, + ARRAYSIZE( tlGruntTossGrenadeCover1 ), + 0, + 0, + "TossGrenadeCover" + }, +}; + +//========================================================= +// hide from the loudest sound source (to run from grenade) +//========================================================= +Task_t tlGruntTakeCoverFromBestSound[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_COWER },// duck and cover if cannot move from explosion + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slGruntTakeCoverFromBestSound[] = +{ + { + tlGruntTakeCoverFromBestSound, + ARRAYSIZE( tlGruntTakeCoverFromBestSound ), + 0, + 0, + "GruntTakeCoverFromBestSound" + }, +}; + +//========================================================= +// Grunt reload schedule +//========================================================= +Task_t tlGruntHideReload[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RELOAD }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RELOAD }, +}; + +Schedule_t slGruntHideReload[] = +{ + { + tlGruntHideReload, + ARRAYSIZE( tlGruntHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntHideReload" + } +}; + +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlGruntSweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slGruntSweep[] = +{ + { + tlGruntSweep, + ARRAYSIZE( tlGruntSweep ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_HEAR_SOUND, + bits_SOUND_WORLD |// sound flags + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + "Grunt Sweep" + }, +}; + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack1A[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1A[] = +{ + { + tlGruntRangeAttack1A, + ARRAYSIZE( tlGruntRangeAttack1A ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + bits_SOUND_DANGER, + "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. +//========================================================= +Task_t tlGruntRangeAttack1B[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_IDLE_ANGRY }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1B[] = +{ + { + tlGruntRangeAttack1B, + ARRAYSIZE( tlGruntRangeAttack1B ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_GRUNT_NOFIRE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "Range Attack1B" + }, +}; + +//========================================================= +// secondary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GRUNT_FACE_TOSS_DIR, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RANGE_ATTACK2 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY },// don't run immediately after throwing grenade. +}; + +Schedule_t slGruntRangeAttack2[] = +{ + { + tlGruntRangeAttack2, + ARRAYSIZE( tlGruntRangeAttack2 ), + 0, + 0, + "RangeAttack2" + }, +}; + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepel[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_GLIDE }, +}; + +Schedule_t slGruntRepel[] = +{ + { + tlGruntRepel, + ARRAYSIZE( tlGruntRepel ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel" + }, +}; + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepelAttack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_FLY }, +}; + +Schedule_t slGruntRepelAttack[] = +{ + { + tlGruntRepelAttack, + ARRAYSIZE( tlGruntRepelAttack ), + bits_COND_ENEMY_OCCLUDED, + 0, + "Repel Attack" + }, +}; + +//========================================================= +// repel land +//========================================================= +Task_t tlGruntRepelLand[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_LAND }, + { TASK_GET_PATH_TO_LASTPOSITION, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slGruntRepelLand[] = +{ + { + tlGruntRepelLand, + ARRAYSIZE( tlGruntRepelLand ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel Land" + }, +}; +*/ + +DEFINE_CUSTOM_SCHEDULES( CNGrunt ) +{ + slGruntFail, + slGruntCombatFail, + slGruntVictoryDance, + slGruntEstablishLineOfFire, + slGruntFoundEnemy, + slGruntCombatFace, + slGruntSignalSuppress, + slGruntSuppress, + slGruntWaitInCover, + slGruntTakeCover, + slGruntGrenadeCover, + slGruntTossGrenadeCover, + slGruntTakeCoverFromBestSound, + slGruntHideReload, + slGruntSweep, + slGruntRangeAttack1A, + slGruntRangeAttack1B, + slGruntRangeAttack2, + slGruntRepel, + slGruntRepelAttack, + slGruntRepelLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CNGrunt, CSquadMonster ) + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CNGrunt::GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = HGRUNT_SENT_NONE; + + // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling. + if( pev->movetype == MOVETYPE_FLY && m_MonsterState != MONSTERSTATE_PRONE ) + { + if( pev->flags & FL_ONGROUND ) + { + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType( SCHED_GRUNT_REPEL_LAND ); + } + else + { + // repel down a rope, + if( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType( SCHED_GRUNT_REPEL_ATTACK ); + else + return GetScheduleOfType( SCHED_GRUNT_REPEL ); + } + } + + // grunts place HIGH priority on running away from danger sounds. + if( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if( pSound ) + { + if( pSound->m_iType & bits_SOUND_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( ENT( pev ), "FG_GREN", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + /* + if( !HasConditions( bits_COND_SEE_ENEMY ) && ( pSound->m_iType & ( bits_SOUND_PLAYER | bits_SOUND_COMBAT ) ) ) + { + MakeIdealYaw( pSound->m_vecOrigin ); + } + */ + } + } + switch( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { + // dead enemy + if( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster::GetSchedule(); + } + + // new enemy + if( HasConditions( bits_COND_NEW_ENEMY ) ) + { + if( InSquad() ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + + if( !IsLeader() ) + { + return GetScheduleOfType( 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( ( m_hEnemy != 0 ) && m_hEnemy->IsPlayer() ) + // player + SENTENCEG_PlayRndSz( ENT( pev ), "NG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + else if( ( m_hEnemy != 0 ) && + ( m_hEnemy->Classify() != CLASS_PLAYER_ALLY ) && + ( m_hEnemy->Classify() != CLASS_HUMAN_PASSIVE ) && + ( m_hEnemy->Classify() != CLASS_MACHINE ) ) + // monster + SENTENCEG_PlayRndSz( ENT( pev ), "FG_MONST", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + + JustSpoke(); + } + + if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_GRUNT_SUPPRESS ); + } + else + { + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + } + // no ammo + else if( HasConditions( bits_COND_NO_AMMO_LOADED ) ) + { + //!!!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 GetScheduleOfType( SCHED_GRUNT_COVER_AND_RELOAD ); + } + // damaged just a little + else if( HasConditions( bits_COND_LIGHT_DAMAGE ) ) + { + // if hurt: + // 90% chance of taking cover + // 10% chance of flinch. + int iPercent = RANDOM_LONG( 0, 99 ); + + if( iPercent <= 90 && m_hEnemy != 0 ) + { + // 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 ), "FG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + m_iSentence = HGRUNT_SENT_COVER; + //JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + } + // can kick + else if( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + // can grenade launch + else if( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER) && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // shoot a grenade if you can + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + // can shoot + else if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if( InSquad() ) + { + // 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( MySquadLeader()->m_fEnemyEluded && !HasConditions( bits_COND_ENEMY_FACING_ME ) ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + return GetScheduleOfType( SCHED_GRUNT_FOUND_ENEMY ); + } + } + + if( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + // try to take an available ENGAGE slot + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + else if( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // throw a grenade if can and no engage slots are available + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else + { + // hide! + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } + // can't see enemy + else if( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + //!!!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( ENT( pev ), "FG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else if( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + //!!!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 ), "FG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + m_iSentence = HGRUNT_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( 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_LONG( 0, 1 ) ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "FG_TAUNT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + break; + default: + break; + } + + // no special cases here, call the base class + return CSquadMonster::GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t *CNGrunt::GetScheduleOfType( int Type ) +{ + switch( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + { + if( InSquad() ) + { + if( g_iSkillLevel == SKILL_HARD && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "FG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return slGruntTossGrenadeCover; + } + else + { + return &slGruntTakeCover[0]; + } + } + else + { + if( RANDOM_LONG( 0, 1 ) ) + { + return &slGruntTakeCover[0]; + } + else + { + return &slGruntGrenadeCover[0]; + } + } + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slGruntTakeCoverFromBestSound[0]; + } + case SCHED_GRUNT_TAKECOVER_FAILED: + { + if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType( SCHED_FAIL ); + } + break; + case SCHED_GRUNT_ELOF_FAIL: + { + // human grunt is unable to move to a position that allows him to attack the enemy. + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + case SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE: + { + return &slGruntEstablishLineOfFire[0]; + } + break; + case SCHED_RANGE_ATTACK1: + { + // randomly stand or crouch + if( RANDOM_LONG( 0, 9 ) == 0 ) + m_fStanding = RANDOM_LONG( 0, 1 ); + + if( m_fStanding ) + return &slGruntRangeAttack1B[0]; + else + return &slGruntRangeAttack1A[0]; + } + case SCHED_RANGE_ATTACK2: + { + return &slGruntRangeAttack2[0]; + } + case SCHED_COMBAT_FACE: + { + return &slGruntCombatFace[0]; + } + case SCHED_GRUNT_WAIT_FACE_ENEMY: + { + return &slGruntWaitInCover[0]; + } + case SCHED_GRUNT_SWEEP: + { + return &slGruntSweep[0]; + } + case SCHED_GRUNT_COVER_AND_RELOAD: + { + return &slGruntHideReload[0]; + } + case SCHED_GRUNT_FOUND_ENEMY: + { + return &slGruntFoundEnemy[0]; + } + case SCHED_VICTORY_DANCE: + { + if( InSquad() ) + { + if( !IsLeader() ) + { + return &slGruntFail[0]; + } + } + + return &slGruntVictoryDance[0]; + } + case SCHED_GRUNT_SUPPRESS: + { + if( m_hEnemy->IsPlayer() && m_fFirstEncounter ) + { + m_fFirstEncounter = FALSE;// after first encounter, leader won't issue handsigns anymore when he has a new enemy + return &slGruntSignalSuppress[0]; + } + else + { + return &slGruntSuppress[0]; + } + } + case SCHED_FAIL: + { + if( m_hEnemy != 0 ) + { + // grunt has an enemy, so pick a different default fail schedule most likely to help recover. + return &slGruntCombatFail[0]; + } + + return &slGruntFail[0]; + } + case SCHED_GRUNT_REPEL: + { + if( pev->velocity.z > -128 ) + pev->velocity.z -= 32; + return &slGruntRepel[0]; + } + case SCHED_GRUNT_REPEL_ATTACK: + { + if( pev->velocity.z > -128 ) + pev->velocity.z -= 32; + return &slGruntRepelAttack[0]; + } + case SCHED_GRUNT_REPEL_LAND: + { + return &slGruntRepelLand[0]; + } + default: + { + return CSquadMonster::GetScheduleOfType( Type ); + } + } +} + +//========================================================= +// CNGruntRepel - when triggered, spawns a monster_nari_grunt +// repelling down a line. +//========================================================= +class CNGruntRepel : public CHGruntRepel +{ +public: + void Spawn(void); + void Precache(void); + void EXPORT RepelUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); +}; + +LINK_ENTITY_TO_CLASS( monster_ngrunt_repel, CNGruntRepel ) + +void CNGruntRepel::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + + SetUse( &CNGruntRepel::RepelUse ); +} + +void CNGruntRepel::Precache( void ) +{ + UTIL_PrecacheOther( "monster_nari_grunt" ); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); +} + +void CNGruntRepel::RepelUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0 ), dont_ignore_monsters, ENT( pev ), &tr ); + /* + if( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP ) + return NULL; + */ + + CBaseEntity *pEntity = Create( "monster_nari_grunt", pev->origin, pev->angles ); + CBaseMonster *pGrunt = pEntity->MyMonsterPointer(); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pGrunt->SetActivity( ACT_GLIDE ); + // UNDONE: position? + pGrunt->m_vecLastPosition = tr.vecEndPos; + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( pev->origin + Vector( 0, 0, 112 ), pGrunt->entindex() ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink( &CBaseEntity::SUB_Remove ); + pBeam->pev->nextthink = gpGlobals->time + -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5; + + UTIL_Remove( this ); +} diff --git a/dlls/rp/zgrunt.cpp b/dlls/rp/zgrunt.cpp new file mode 100644 index 00000000..a586565b --- /dev/null +++ b/dlls/rp/zgrunt.cpp @@ -0,0 +1,1720 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// zgrunt +//========================================================= + +//========================================================= +// Hit groups! +//========================================================= +/* + + 1 - Head + 2 - Stomach + 3 - Gun + +*/ + +#include "extdll.h" +#include "plane.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "squadmonster.h" +#include "weapons.h" +#include "talkmonster.h" +#include "soundent.h" +#include "effects.h" +#include "customentity.h" +#include "hgrunt.h" + +extern int g_fGruntQuestion; // true if an idle grunt asked a question. Cleared when someone answers. + +extern DLL_GLOBAL int g_iSkillLevel; + +extern Schedule_t slIdleStand[]; +extern Schedule_t slGruntFail[]; +extern Schedule_t slGruntCombatFail[]; +extern Schedule_t slGruntVictoryDance[]; +extern Schedule_t slGruntEstablishLineOfFire[]; +extern Schedule_t slGruntFoundEnemy[]; +extern Schedule_t slGruntCombatFace[]; +extern Schedule_t slGruntSignalSuppress[]; +extern Schedule_t slGruntSuppress[]; +extern Schedule_t slGruntWaitInCover[]; +extern Schedule_t slGruntTakeCover[]; +extern Schedule_t slGruntGrenadeCover[]; +extern Schedule_t slGruntTossGrenadeCover[]; +extern Schedule_t slGruntTakeCoverFromBestSound[]; +extern Schedule_t slGruntHideReload[]; +extern Schedule_t slGruntSweep[]; +extern Schedule_t slGruntRangeAttack1A[]; +extern Schedule_t slGruntRangeAttack1B[]; +extern Schedule_t slGruntRangeAttack2[]; +extern Schedule_t slGruntRepel[]; +extern Schedule_t slGruntRepelAttack[]; +extern Schedule_t slGruntRepelLand[]; + +class CZGrunt : public CHGrunt +{ +public: + void Spawn(void); + void Precache(void); + int Classify(void); + void Shotgun(); + void HandleAnimEvent(MonsterEvent_t *pEvent); + void DeathSound(void); + void PainSound(void); + void IdleSound(void); + void SpeakSentence(void); + + int Save(CSave &save); + int Restore(CRestore &restore); + + Schedule_t *GetSchedule(void); + Schedule_t *GetScheduleOfType(int Type); + + int IRelationship(CBaseEntity *pTarget); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pGruntSentences[]; +}; + +LINK_ENTITY_TO_CLASS( monster_zombie_grunt, CZGrunt ) + +TYPEDESCRIPTION CZGrunt::m_SaveData[] = +{ + DEFINE_FIELD( CZGrunt, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CZGrunt, m_flNextPainTime, FIELD_TIME ), + //DEFINE_FIELD( CZGrunt, m_flLastEnemySightTime, FIELD_TIME ), // don't save, go to zero + DEFINE_FIELD( CZGrunt, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CZGrunt, m_fThrowGrenade, FIELD_BOOLEAN ), + DEFINE_FIELD( CZGrunt, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CZGrunt, m_fFirstEncounter, FIELD_BOOLEAN ), + DEFINE_FIELD( CZGrunt, m_cClipSize, FIELD_INTEGER ), + DEFINE_FIELD( CZGrunt, m_voicePitch, FIELD_INTEGER ), + //DEFINE_FIELD( CShotgun, m_iBrassShell, FIELD_INTEGER ), + //DEFINE_FIELD( CShotgun, m_iShotgunShell, FIELD_INTEGER ), + DEFINE_FIELD( CZGrunt, m_iSentence, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CZGrunt, CSquadMonster ) + +const char *CZGrunt::pGruntSentences[] = +{ + "ZG_GREN", // grenade scared grunt + "ZG_ALERT", // sees player + "ZG_MONSTER", // sees monster + "ZG_COVER", // running to cover + "ZG_THROW", // about to throw grenade + "ZG_CHARGE", // running out to get the enemy + "ZG_TAUNT", // say rude things +}; + +//========================================================= +// 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 CZGrunt::SpeakSentence( void ) +{ + if( m_iSentence == HGRUNT_SENT_NONE ) + { + // no sentence cued up. + return; + } + + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), pGruntSentences[m_iSentence], HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } +} + +//========================================================= +// IRelationship - overridden because Alien Grunts are +// Human Grunt's nemesis. +//========================================================= +int CZGrunt::IRelationship( CBaseEntity *pTarget ) +{ + if( FClassnameIs( pTarget->pev, "monster_human_grunt" ) || ( FClassnameIs( pTarget->pev, "monster_male_assassin" ) ) ) + { + return R_NM; + } + + return CSquadMonster::IRelationship( pTarget ); +} + +void CZGrunt::IdleSound( void ) +{ + if( FOkToSpeak() && ( g_fGruntQuestion || RANDOM_LONG( 0, 1 ) ) ) + { + if( !g_fGruntQuestion ) + { + // ask question or make statement + switch( RANDOM_LONG( 0, 2 ) ) + { + case 0: + // check in + SENTENCEG_PlayRndSz( ENT( pev ), "ZG_CHECK", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + g_fGruntQuestion = 1; + break; + case 1: + // question + SENTENCEG_PlayRndSz( ENT( pev ), "ZG_QUEST", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + g_fGruntQuestion = 2; + break; + case 2: + // statement + SENTENCEG_PlayRndSz( ENT( pev ), "ZG_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + break; + } + } + else + { + switch( g_fGruntQuestion ) + { + case 1: + // check in + SENTENCEG_PlayRndSz( ENT( pev ), "ZG_CLEAR", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + break; + case 2: + // question + SENTENCEG_PlayRndSz( ENT( pev ), "ZG_ANSWER", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + break; + } + g_fGruntQuestion = 0; + } + JustSpoke(); + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CZGrunt::Classify( void ) +{ + return CLASS_ALIEN_MILITARY; +} + +//========================================================= +// Shoot +//========================================================= +void CZGrunt::Shotgun( void ) +{ + if( m_hEnemy == 0 ) + { + return; + } + + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + UTIL_MakeVectors( pev->angles ); + + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT( 40, 90 ) + gpGlobals->v_up * RANDOM_FLOAT( 75, 200 ) + gpGlobals->v_forward * RANDOM_FLOAT( -40, 40 ); + EjectBrass( vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iShotgunShell, TE_BOUNCE_SHOTSHELL ); + FireBullets( gSkillData.hgruntShotgunPellets, vecShootOrigin, vecShootDir, VECTOR_CONE_15DEGREES, 2048, BULLET_PLAYER_BUCKSHOT, 0 ); // shoot +-7.5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CZGrunt::HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case HGRUNT_AE_DROP_GUN: + { + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( GUN_GROUP, GUN_NONE ); + + // now spawn a gun. + if( FBitSet( pev->weapons, HGRUNT_SHOTGUN ) ) + { + DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else + { + DropItem( "weapon_9mmAR", vecGunPos, vecGunAngles ); + } + + if( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER ) ) + { + DropItem( "ammo_ARgrenades", BodyTarget( pev->origin ), vecGunAngles ); + } + } + break; + case HGRUNT_AE_RELOAD: + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "hgrunt/gr_reload1.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions( bits_COND_NO_AMMO_LOADED ); + break; + case HGRUNT_AE_GREN_TOSS: + { + UTIL_MakeVectors( pev->angles ); + // CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector( 0, 0, 32 ), m_vecTossVelocity, 3.5 ); + CGrenade::ShootTimed( pev, GetGunPosition(), m_vecTossVelocity, 3.5 ); + + m_fThrowGrenade = FALSE; + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + // !!!LATER - when in a group, only try to throw grenade if ordered. + } + break; + case HGRUNT_AE_GREN_LAUNCH: + { + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM ); + CGrenade::ShootContact( pev, GetGunPosition(), m_vecTossVelocity ); + m_fThrowGrenade = FALSE; + if( g_iSkillLevel == SKILL_HARD ) + m_flNextGrenadeCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 );// wait a random amount of time before shooting again + else + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + } + break; + case HGRUNT_AE_GREN_DROP: + { + UTIL_MakeVectors( pev->angles ); + CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 17 - gpGlobals->v_right * 27 + gpGlobals->v_up * 6, g_vecZero, 3 ); + } + break; + case HGRUNT_AE_BURST1: + { + if( FBitSet( pev->weapons, HGRUNT_9MMAR ) ) + { + Shoot(); + + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + if( RANDOM_LONG( 0, 1 ) ) + { + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "hgrunt/gr_mgun1.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "hgrunt/gr_mgun2.wav", 1, ATTN_NORM ); + } + } + else + { + Shotgun(); + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM ); + } + + CSoundEnt::InsertSound( bits_SOUND_COMBAT, pev->origin, 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! + UTIL_MakeVectors( pev->angles ); + pHurt->pev->punchangle.x = 15; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; + pHurt->TakeDamage( pev, pev, gSkillData.zgruntDmgKick, DMG_CLUB ); + } + } + break; + case HGRUNT_AE_CAUGHT_ENEMY: + { + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "ZG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + + } + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CZGrunt::Spawn() +{ + Precache(); + + SET_MODEL( ENT( pev ), "models/zgrunt.mdl" ); + + UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.zgruntHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = HGRUNT_SENT_NONE; + + m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + m_fEnemyEluded = FALSE; + 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( pev->weapons == 0 ) + { + // initialize to original values + pev->weapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE; + // pev->weapons = HGRUNT_SHOTGUN; + // pev->weapons = HGRUNT_9MMAR | HGRUNT_GRENADELAUNCHER; + } + + if( FBitSet( pev->weapons, HGRUNT_SHOTGUN ) ) + { + SetBodygroup( GUN_GROUP, GUN_SHOTGUN ); + m_cClipSize = 8; + } + else + { + m_cClipSize = GRUNT_CLIP_SIZE; + } + m_cAmmoLoaded = m_cClipSize; + + if( RANDOM_LONG( 0, 99 ) < 80 ) + pev->skin = 0; // light skin + else + pev->skin = 1; // dark skin + + if( FBitSet( pev->weapons, HGRUNT_SHOTGUN ) ) + { + SetBodygroup( HEAD_GROUP, HEAD_SHOTGUN ); + } + else if( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER ) ) + { + SetBodygroup( HEAD_GROUP, HEAD_M203 ); + pev->skin = 1; // alway dark skin + } + + CTalkMonster::g_talkWaitTime = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CZGrunt::Precache() +{ + PRECACHE_MODEL( "models/zgrunt.mdl" ); + + PRECACHE_SOUND( "hgrunt/gr_mgun1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_mgun2.wav" ); + + PRECACHE_SOUND( "agrunt/ag_die1.wav" ); + PRECACHE_SOUND( "agrunt/ag_die2.wav" ); + PRECACHE_SOUND( "agrunt/ag_die3.wav" ); + PRECACHE_SOUND( "agrunt/ag_die4.wav" ); + PRECACHE_SOUND( "agrunt/ag_die5.wav" ); + + PRECACHE_SOUND( "agrunt/ag_pain1.wav" ); + PRECACHE_SOUND( "agrunt/ag_pain2.wav" ); + PRECACHE_SOUND( "agrunt/ag_pain3.wav" ); + PRECACHE_SOUND( "agrunt/ag_pain4.wav" ); + PRECACHE_SOUND( "agrunt/ag_pain5.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND( "weapons/sbarrel1.wav" ); + PRECACHE_SOUND( "zombie/claw_miss2.wav" );// because we use the basemonster SWIPE animation event + + // get voice pitch + if( RANDOM_LONG( 0, 1 ) ) + m_voicePitch = 109 + RANDOM_LONG( 0, 7 ); + else + m_voicePitch = 100; + + m_iBrassShell = PRECACHE_MODEL( "models/shell.mdl" );// brass shell + m_iShotgunShell = PRECACHE_MODEL( "models/shotgunshell.mdl" ); +} + +//========================================================= +// PainSound +//========================================================= +void CZGrunt::PainSound( void ) +{ + if( gpGlobals->time > m_flNextPainTime ) + { +#if 0 + if( RANDOM_LONG( 0, 99 ) < 5 ) + { + // pain sentences are rare + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "ZG_PAIN", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, PITCH_NORM ); + JustSpoke(); + return; + } + } +#endif + switch( RANDOM_LONG( 0, 6 ) ) + { + case 0: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "agrunt/ag_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "agrunt/ag_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "agrunt/ag_pain5.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "agrunt/ag_pain1.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "agrunt/ag_pain2.wav", 1, ATTN_NORM ); + break; + } + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CZGrunt::DeathSound( void ) +{ + switch( RANDOM_LONG( 0, 2 ) ) + { + case 0: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "agrunt/ag_die1.wav", 1, ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "agrunt/ag_die2.wav", 1, ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "agrunt/ag_die3.wav", 1, ATTN_IDLE ); + break; + case 3: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "agrunt/ag_die4.wav", 1, ATTN_IDLE ); + break; + case 4: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "agrunt/ag_die5.wav", 1, ATTN_IDLE ); + break; + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +/* +//========================================================= +// GruntFail +//========================================================= +Task_t tlGruntFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntFail[] = +{ + { + tlGruntFail, + ARRAYSIZE( tlGruntFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + 0, + "Grunt Fail" + }, +}; + +//========================================================= +// Grunt Combat Fail +//========================================================= +Task_t tlGruntCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntCombatFail[] = +{ + { + tlGruntCombatFail, + ARRAYSIZE( tlGruntCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Grunt Combat Fail" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlGruntVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, +}; + +Schedule_t slGruntVictoryDance[] = +{ + { + tlGruntVictoryDance, + ARRAYSIZE( tlGruntVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "GruntVictoryDance" + }, +}; + +//========================================================= +// Establish line of fire - move to a position that allows +// the grunt to attack. +//========================================================= +Task_t tlGruntEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slGruntEstablishLineOfFire[] = +{ + { + tlGruntEstablishLineOfFire, + ARRAYSIZE( tlGruntEstablishLineOfFire ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntEstablishLineOfFire" + }, +}; + +//========================================================= +// GruntFoundEnemy - grunt established sight with an enemy +// that was hiding from the squad. +//========================================================= +Task_t tlGruntFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL1 }, +}; + +Schedule_t slGruntFoundEnemy[] = +{ + { + tlGruntFoundEnemy, + ARRAYSIZE( tlGruntFoundEnemy ), + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntFoundEnemy" + }, +}; + +//========================================================= +// GruntCombatFace Schedule +//========================================================= +Task_t tlGruntCombatFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_SWEEP }, +}; + +Schedule_t slGruntCombatFace[] = +{ + { + tlGruntCombatFace1, + ARRAYSIZE( tlGruntCombatFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Suppressing fire - don't stop shooting until the clip is +// empty or grunt gets hurt. +//========================================================= +Task_t tlGruntSignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0}, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSignalSuppress[] = +{ + { + tlGruntSignalSuppress, + ARRAYSIZE( tlGruntSignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; + +Task_t tlGruntSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSuppress[] = +{ + { + tlGruntSuppress, + ARRAYSIZE( tlGruntSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + bits_SOUND_DANGER, + "Suppress" + }, +}; + +//========================================================= +// 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. +//========================================================= +Task_t tlGruntWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slGruntWaitInCover[] = +{ + { + tlGruntWaitInCover, + ARRAYSIZE( tlGruntWaitInCover ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + bits_SOUND_DANGER, + "GruntWaitInCover" + }, +}; + +//========================================================= +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. +//========================================================= +Task_t tlGruntTakeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntTakeCover[] = +{ + { + tlGruntTakeCover1, + ARRAYSIZE ( tlGruntTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntGrenadeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)99 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_PLAY_SEQUENCE, (float)ACT_SPECIAL_ATTACK1 }, + { TASK_CLEAR_MOVE_WAIT, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntGrenadeCover[] = +{ + { + tlGruntGrenadeCover1, + ARRAYSIZE( tlGruntGrenadeCover1 ), + 0, + 0, + "GrenadeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntTossGrenadeCover1[] = +{ + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slGruntTossGrenadeCover[] = +{ + { + tlGruntTossGrenadeCover1, + ARRAYSIZE( tlGruntTossGrenadeCover1 ), + 0, + 0, + "TossGrenadeCover" + }, +}; + +//========================================================= +// hide from the loudest sound source (to run from grenade) +//========================================================= +Task_t tlGruntTakeCoverFromBestSound[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_COWER },// duck and cover if cannot move from explosion + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slGruntTakeCoverFromBestSound[] = +{ + { + tlGruntTakeCoverFromBestSound, + ARRAYSIZE( tlGruntTakeCoverFromBestSound ), + 0, + 0, + "GruntTakeCoverFromBestSound" + }, +}; + +//========================================================= +// Grunt reload schedule +//========================================================= +Task_t tlGruntHideReload[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RELOAD }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RELOAD }, +}; + +Schedule_t slGruntHideReload[] = +{ + { + tlGruntHideReload, + ARRAYSIZE( tlGruntHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntHideReload" + } +}; + +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlGruntSweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slGruntSweep[] = +{ + { + tlGruntSweep, + ARRAYSIZE( tlGruntSweep ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_HEAR_SOUND, + bits_SOUND_WORLD |// sound flags + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + "Grunt Sweep" + }, +}; + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack1A[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1A[] = +{ + { + tlGruntRangeAttack1A, + ARRAYSIZE( tlGruntRangeAttack1A ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + bits_SOUND_DANGER, + "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. +//========================================================= +Task_t tlGruntRangeAttack1B[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_IDLE_ANGRY }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1B[] = +{ + { + tlGruntRangeAttack1B, + ARRAYSIZE( tlGruntRangeAttack1B ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_GRUNT_NOFIRE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "Range Attack1B" + }, +}; + +//========================================================= +// secondary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GRUNT_FACE_TOSS_DIR, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RANGE_ATTACK2 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY },// don't run immediately after throwing grenade. +}; + +Schedule_t slGruntRangeAttack2[] = +{ + { + tlGruntRangeAttack2, + ARRAYSIZE( tlGruntRangeAttack2 ), + 0, + 0, + "RangeAttack2" + }, +}; + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepel[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_GLIDE }, +}; + +Schedule_t slGruntRepel[] = +{ + { + tlGruntRepel, + ARRAYSIZE( tlGruntRepel ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel" + }, +}; + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepelAttack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_FLY }, +}; + +Schedule_t slGruntRepelAttack[] = +{ + { + tlGruntRepelAttack, + ARRAYSIZE( tlGruntRepelAttack ), + bits_COND_ENEMY_OCCLUDED, + 0, + "Repel Attack" + }, +}; + +//========================================================= +// repel land +//========================================================= +Task_t tlGruntRepelLand[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_LAND }, + { TASK_GET_PATH_TO_LASTPOSITION, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slGruntRepelLand[] = +{ + { + tlGruntRepelLand, + ARRAYSIZE( tlGruntRepelLand ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel Land" + }, +}; +*/ + +DEFINE_CUSTOM_SCHEDULES( CZGrunt ) +{ + slGruntFail, + slGruntCombatFail, + slGruntVictoryDance, + slGruntEstablishLineOfFire, + slGruntFoundEnemy, + slGruntCombatFace, + slGruntSignalSuppress, + slGruntSuppress, + slGruntWaitInCover, + slGruntTakeCover, + slGruntGrenadeCover, + slGruntTossGrenadeCover, + slGruntTakeCoverFromBestSound, + slGruntHideReload, + slGruntSweep, + slGruntRangeAttack1A, + slGruntRangeAttack1B, + slGruntRangeAttack2, + slGruntRepel, + slGruntRepelAttack, + slGruntRepelLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CZGrunt, CSquadMonster ) + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CZGrunt::GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = HGRUNT_SENT_NONE; + + // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling. + if( pev->movetype == MOVETYPE_FLY && m_MonsterState != MONSTERSTATE_PRONE ) + { + if( pev->flags & FL_ONGROUND ) + { + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType( SCHED_GRUNT_REPEL_LAND ); + } + else + { + // repel down a rope, + if( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType( SCHED_GRUNT_REPEL_ATTACK ); + else + return GetScheduleOfType( SCHED_GRUNT_REPEL ); + } + } + + // grunts place HIGH priority on running away from danger sounds. + if( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if( pSound ) + { + if( pSound->m_iType & bits_SOUND_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( ENT( pev ), "ZG_GREN", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + /* + if( !HasConditions( bits_COND_SEE_ENEMY ) && ( pSound->m_iType & ( bits_SOUND_PLAYER | bits_SOUND_COMBAT ) ) ) + { + MakeIdealYaw( pSound->m_vecOrigin ); + } + */ + } + } + switch( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { + // dead enemy + if( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster::GetSchedule(); + } + + // new enemy + if( HasConditions( bits_COND_NEW_ENEMY ) ) + { + if( InSquad() ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + + if( !IsLeader() ) + { + return GetScheduleOfType( 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( ( m_hEnemy != 0 ) && m_hEnemy->IsPlayer() ) + // player + SENTENCEG_PlayRndSz( ENT( pev ), "ZG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + else if( ( m_hEnemy != 0 ) && + ( m_hEnemy->Classify() != CLASS_PLAYER_ALLY ) && + ( m_hEnemy->Classify() != CLASS_HUMAN_PASSIVE ) && + ( m_hEnemy->Classify() != CLASS_MACHINE ) ) + // monster + SENTENCEG_PlayRndSz( ENT( pev ), "ZG_MONST", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + + JustSpoke(); + } + + if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_GRUNT_SUPPRESS ); + } + else + { + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + } + // no ammo + else if( HasConditions( bits_COND_NO_AMMO_LOADED ) ) + { + //!!!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 GetScheduleOfType( SCHED_GRUNT_COVER_AND_RELOAD ); + } + // damaged just a little + else if( HasConditions( bits_COND_LIGHT_DAMAGE ) ) + { + // if hurt: + // 90% chance of taking cover + // 10% chance of flinch. + int iPercent = RANDOM_LONG( 0, 99 ); + + if( iPercent <= 90 && m_hEnemy != 0 ) + { + // 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 ), "ZG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + m_iSentence = HGRUNT_SENT_COVER; + //JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + } + // can kick + else if( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + // can grenade launch + else if( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER) && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // shoot a grenade if you can + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + // can shoot + else if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if( InSquad() ) + { + // 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( MySquadLeader()->m_fEnemyEluded && !HasConditions( bits_COND_ENEMY_FACING_ME ) ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + return GetScheduleOfType( SCHED_GRUNT_FOUND_ENEMY ); + } + } + + if( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + // try to take an available ENGAGE slot + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + else if( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // throw a grenade if can and no engage slots are available + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else + { + // hide! + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } + // can't see enemy + else if( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + //!!!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( ENT( pev ), "ZG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else if( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + //!!!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 ), "ZG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + m_iSentence = HGRUNT_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( 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_LONG( 0, 1 ) ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "ZG_TAUNT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + break; + default: + break; + } + + // no special cases here, call the base class + return CSquadMonster::GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t *CZGrunt::GetScheduleOfType( int Type ) +{ + switch( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + { + if( InSquad() ) + { + if( g_iSkillLevel == SKILL_HARD && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "ZG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return slGruntTossGrenadeCover; + } + else + { + return &slGruntTakeCover[0]; + } + } + else + { + if( RANDOM_LONG( 0, 1 ) ) + { + return &slGruntTakeCover[0]; + } + else + { + return &slGruntGrenadeCover[0]; + } + } + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slGruntTakeCoverFromBestSound[0]; + } + case SCHED_GRUNT_TAKECOVER_FAILED: + { + if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType( SCHED_FAIL ); + } + break; + case SCHED_GRUNT_ELOF_FAIL: + { + // human grunt is unable to move to a position that allows him to attack the enemy. + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + case SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE: + { + return &slGruntEstablishLineOfFire[0]; + } + break; + case SCHED_RANGE_ATTACK1: + { + // randomly stand or crouch + if( RANDOM_LONG( 0, 9 ) == 0 ) + m_fStanding = RANDOM_LONG( 0, 1 ); + + if( m_fStanding ) + return &slGruntRangeAttack1B[0]; + else + return &slGruntRangeAttack1A[0]; + } + case SCHED_RANGE_ATTACK2: + { + return &slGruntRangeAttack2[0]; + } + case SCHED_COMBAT_FACE: + { + return &slGruntCombatFace[0]; + } + case SCHED_GRUNT_WAIT_FACE_ENEMY: + { + return &slGruntWaitInCover[0]; + } + case SCHED_GRUNT_SWEEP: + { + return &slGruntSweep[0]; + } + case SCHED_GRUNT_COVER_AND_RELOAD: + { + return &slGruntHideReload[0]; + } + case SCHED_GRUNT_FOUND_ENEMY: + { + return &slGruntFoundEnemy[0]; + } + case SCHED_VICTORY_DANCE: + { + if( InSquad() ) + { + if( !IsLeader() ) + { + return &slGruntFail[0]; + } + } + + return &slGruntVictoryDance[0]; + } + case SCHED_GRUNT_SUPPRESS: + { + if( m_hEnemy->IsPlayer() && m_fFirstEncounter ) + { + m_fFirstEncounter = FALSE;// after first encounter, leader won't issue handsigns anymore when he has a new enemy + return &slGruntSignalSuppress[0]; + } + else + { + return &slGruntSuppress[0]; + } + } + case SCHED_FAIL: + { + if( m_hEnemy != 0 ) + { + // grunt has an enemy, so pick a different default fail schedule most likely to help recover. + return &slGruntCombatFail[0]; + } + + return &slGruntFail[0]; + } + case SCHED_GRUNT_REPEL: + { + if( pev->velocity.z > -128 ) + pev->velocity.z -= 32; + return &slGruntRepel[0]; + } + case SCHED_GRUNT_REPEL_ATTACK: + { + if( pev->velocity.z > -128 ) + pev->velocity.z -= 32; + return &slGruntRepelAttack[0]; + } + case SCHED_GRUNT_REPEL_LAND: + { + return &slGruntRepelLand[0]; + } + default: + { + return CSquadMonster::GetScheduleOfType( Type ); + } + } +} + +//========================================================= +// CZGruntRepel - when triggered, spawns a monster_nari_grunt +// repelling down a line. +//========================================================= +class CZGruntRepel : public CHGruntRepel +{ +public: + void Spawn(void); + void Precache(void); + void EXPORT RepelUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); +}; + +LINK_ENTITY_TO_CLASS( monster_zgrunt_repel, CZGruntRepel ) + +void CZGruntRepel::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + + SetUse( &CZGruntRepel::RepelUse ); +} + +void CZGruntRepel::Precache( void ) +{ + UTIL_PrecacheOther( "monster_zombie_grunt" ); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); +} + +void CZGruntRepel::RepelUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0 ), dont_ignore_monsters, ENT( pev ), &tr ); + /* + if( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP ) + return NULL; + */ + + CBaseEntity *pEntity = Create( "monster_zombie_grunt", pev->origin, pev->angles ); + CBaseMonster *pGrunt = pEntity->MyMonsterPointer(); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pGrunt->SetActivity( ACT_GLIDE ); + // UNDONE: position? + pGrunt->m_vecLastPosition = tr.vecEndPos; + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( pev->origin + Vector( 0, 0, 112 ), pGrunt->entindex() ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink( &CBaseEntity::SUB_Remove ); + pBeam->pev->nextthink = gpGlobals->time + -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5; + + UTIL_Remove( this ); +} + +//========================================================= +// DEAD ZGRUNT PROP +//========================================================= +class CDeadZGrunt : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static const char *m_szPoses[3]; +}; + +const char *CDeadZGrunt::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; + +void CDeadZGrunt::KeyValue( KeyValueData *pkvd ) +{ + if( FStrEq( pkvd->szKeyName, "pose" ) ) + { + m_iPose = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_zgrunt_dead, CDeadZGrunt ) + +//========================================================= +// ********** DeadZGrunt SPAWN ********** +//========================================================= +void CDeadZGrunt::Spawn( void ) +{ + PRECACHE_MODEL( "models/zgrunt.mdl" ); + SET_MODEL( ENT( pev ), "models/zgrunt.mdl" ); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + m_bloodColor = BLOOD_COLOR_GREEN; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if( pev->sequence == -1 ) + { + ALERT( at_console, "Dead zgrunt with bad pose\n" ); + } + + // Corpses have less health + pev->health = 8; + + // map old bodies onto new bodies + switch( pev->body ) + { + case 0: + // Grunt with Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_GRUNT ); + SetBodygroup( GUN_GROUP, GUN_MP5 ); + break; + case 1: + // Commander with Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_COMMANDER ); + SetBodygroup( GUN_GROUP, GUN_MP5 ); + break; + case 2: + // Grunt no Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_GRUNT ); + SetBodygroup( GUN_GROUP, GUN_NONE ); + break; + case 3: + // Commander no Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_COMMANDER ); + SetBodygroup( GUN_GROUP, GUN_NONE ); + break; + } + + MonsterInitDead(); +} diff --git a/dlls/zombie.cpp b/dlls/zombie.cpp index 6aa58f64..a4c67033 100644 --- a/dlls/zombie.cpp +++ b/dlls/zombie.cpp @@ -37,7 +37,6 @@ LINK_ENTITY_TO_CLASS( monster_zombie, CZombie ) LINK_ENTITY_TO_CLASS( monster_zombie_barney, CZombie ) LINK_ENTITY_TO_CLASS( monster_zombie_soldier, CZombie ) -LINK_ENTITY_TO_CLASS( monster_zombie_grunt, CZombie ) const char *CZombie::pAttackHitSounds[] = { @@ -244,8 +243,6 @@ void CZombie::Spawn() SET_MODEL( ENT( pev ), "models/zombie_barney.mdl" ); else if( FClassnameIs( pev, "monster_zombie_soldier" ) ) SET_MODEL( ENT( pev ), "models/zombie_soldier.mdl" ); - else if( FClassnameIs( pev, "monster_zombie_grunt" ) ) - SET_MODEL( ENT( pev ), "models/zgrunt.mdl" ); else SET_MODEL( ENT( pev ), "models/zombie.mdl" ); UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); @@ -273,8 +270,6 @@ void CZombie::Precache() PRECACHE_MODEL( "models/zombie_barney.mdl" ); else if( FClassnameIs( pev, "monster_zombie_soldier" ) ) PRECACHE_MODEL( "models/zombie_soldier.mdl" ); - else if( FClassnameIs( pev, "monster_zombie_grunt" ) ) - PRECACHE_MODEL( "models/zgrunt.mdl" ); else PRECACHE_MODEL( "models/zombie.mdl" ); @@ -326,9 +321,9 @@ int CZombie::IgnoreConditions( void ) } //========================================================= -// DEAD ZGRUNT PROP +// DEAD ZSOLDIER PROP //========================================================= -class CDeadZGrunt : public CBaseMonster +class CDeadZSoldier : public CBaseMonster { public: void Spawn(); @@ -340,13 +335,13 @@ public: static char *m_szPoses[2]; }; -char *CDeadZGrunt::m_szPoses[] = +char *CDeadZSoldier::m_szPoses[] = { "dead_on_back", "dead_on_stomach" }; -void CDeadZGrunt::KeyValue( KeyValueData *pkvd ) +void CDeadZSoldier::KeyValue( KeyValueData *pkvd ) { if( FStrEq( pkvd->szKeyName, "pose" ) ) { @@ -357,24 +352,15 @@ void CDeadZGrunt::KeyValue( KeyValueData *pkvd ) CBaseMonster::KeyValue( pkvd ); } -LINK_ENTITY_TO_CLASS( monster_zombie_soldier_dead, CDeadZGrunt ) -LINK_ENTITY_TO_CLASS( monster_zgrunt_dead, CDeadZGrunt ) +LINK_ENTITY_TO_CLASS( monster_zombie_soldier_dead, CDeadZSoldier ) //========================================================= // ********** DeadZGrunt SPAWN ********** //========================================================= -void CDeadZGrunt::Spawn() +void CDeadZSoldier::Spawn() { - if( FClassnameIs( pev, "monster_zgrunt_dead" ) ) - { - PRECACHE_MODEL( "models/zgrunt.mdl" ); - SET_MODEL( ENT( pev ), "models/zgrunt.mdl" ); - } - else - { - PRECACHE_MODEL( "models/zombie_soldier.mdl" ); - SET_MODEL( ENT( pev ), "models/zombie_soldier.mdl" ); - } + PRECACHE_MODEL( "models/zombie_soldier.mdl" ); + SET_MODEL( ENT( pev ), "models/zombie_soldier.mdl" ); pev->effects = 0; pev->yaw_speed = 8;