From 1fe1a7fcec5d6e9fd21b9a61e6e8f35f90b22bd3 Mon Sep 17 00:00:00 2001 From: Roman Chistokhodov Date: Wed, 31 Jul 2019 02:29:48 +0300 Subject: [PATCH] Update fgrunts --- dlls/CMakeLists.txt | 2 - dlls/barney.cpp | 2 +- dlls/gamerules.cpp | 22 +- dlls/gearbox/fgrunt.cpp | 5178 +++++++++++++++++++++------------ dlls/gearbox/fgrunt.h | 140 - dlls/gearbox/fgrunt_medic.cpp | 622 ---- dlls/gearbox/fgrunt_torch.cpp | 59 - dlls/gearbox/massn.cpp | 40 +- dlls/gearbox/otis.cpp | 18 - dlls/hgrunt.cpp | 20 +- dlls/hgrunt.h | 7 +- dlls/schedule.h | 3 + dlls/scientist.cpp | 8 +- dlls/skill.h | 26 +- dlls/talkmonster.cpp | 43 +- dlls/talkmonster.h | 12 +- 16 files changed, 3371 insertions(+), 2831 deletions(-) delete mode 100644 dlls/gearbox/fgrunt.h delete mode 100644 dlls/gearbox/fgrunt_medic.cpp delete mode 100644 dlls/gearbox/fgrunt_torch.cpp diff --git a/dlls/CMakeLists.txt b/dlls/CMakeLists.txt index d96c6dfc..d4990bfa 100644 --- a/dlls/CMakeLists.txt +++ b/dlls/CMakeLists.txt @@ -47,8 +47,6 @@ set (SVDLL_SOURCES gearbox/displacer.cpp gearbox/drillsergeant.cpp gearbox/eagle.cpp - gearbox/fgrunt_medic.cpp - gearbox/fgrunt_torch.cpp gearbox/fgrunt.cpp gearbox/func_tank_of.cpp # gearbox/gearbox_client.cpp diff --git a/dlls/barney.cpp b/dlls/barney.cpp index 2b5950c0..1248417a 100644 --- a/dlls/barney.cpp +++ b/dlls/barney.cpp @@ -426,7 +426,7 @@ void CBarney::TalkInit() m_voicePitch = 100; } -static BOOL IsFacing( entvars_t *pevTest, const Vector &reference ) +BOOL IsFacing( entvars_t *pevTest, const Vector &reference ) { Vector vecDir = reference - pevTest->origin; vecDir.z = 0; diff --git a/dlls/gamerules.cpp b/dlls/gamerules.cpp index 909244e8..604aacc6 100644 --- a/dlls/gamerules.cpp +++ b/dlls/gamerules.cpp @@ -237,21 +237,21 @@ void CGameRules::RefreshSkillData ( void ) gSkillData.pitdroneDmgSpit = GetSkillCvar( "sk_pitdrone_dmg_spit" ); // Hgrunt Ally - gSkillData.hgruntAllyHealth = GetSkillCvar( "sk_hgrunt_ally_health" ); - gSkillData.hgruntAllyDmgKick = GetSkillCvar( "sk_hgrunt_ally_kick" ); - gSkillData.hgruntAllyShotgunPellets = GetSkillCvar( "sk_hgrunt_ally_pellets" ); - gSkillData.hgruntAllyGrenadeSpeed = GetSkillCvar( "sk_hgrunt_ally_gspeed" ); + gSkillData.fgruntHealth = GetSkillCvar( "sk_hgrunt_ally_health" ); + gSkillData.fgruntDmgKick = GetSkillCvar( "sk_hgrunt_ally_kick" ); + gSkillData.fgruntShotgunPellets = GetSkillCvar( "sk_hgrunt_ally_pellets" ); + gSkillData.fgruntGrenadeSpeed = GetSkillCvar( "sk_hgrunt_ally_gspeed" ); // Medic Ally - gSkillData.medicAllyHealth = GetSkillCvar( "sk_medic_ally_health" ); - gSkillData.medicAllyDmgKick = GetSkillCvar( "sk_medic_ally_kick" ); - gSkillData.medicAllyGrenadeSpeed = GetSkillCvar( "sk_medic_ally_gspeed" ); - gSkillData.medicAllyHeal = GetSkillCvar( "sk_medic_ally_heal" ); + gSkillData.medicHealth = GetSkillCvar( "sk_medic_ally_health" ); + gSkillData.medicDmgKick = GetSkillCvar( "sk_medic_ally_kick" ); + gSkillData.medicGrenadeSpeed = GetSkillCvar( "sk_medic_ally_gspeed" ); + gSkillData.medicHeal = GetSkillCvar( "sk_medic_ally_heal" ); // Torch Ally - gSkillData.torchAllyHealth = GetSkillCvar( "sk_torch_ally_health" ); - gSkillData.torchAllyDmgKick = GetSkillCvar( "sk_torch_ally_kick" ); - gSkillData.torchAllyGrenadeSpeed = GetSkillCvar( "sk_torch_ally_gspeed" ); + gSkillData.torchHealth = GetSkillCvar( "sk_torch_ally_health" ); + gSkillData.torchDmgKick = GetSkillCvar( "sk_torch_ally_kick" ); + gSkillData.torchGrenadeSpeed = GetSkillCvar( "sk_torch_ally_gspeed" ); // Male Assassin gSkillData.massnHealth = GetSkillCvar( "sk_massassin_health" ); diff --git a/dlls/gearbox/fgrunt.cpp b/dlls/gearbox/fgrunt.cpp index db38c98f..048dcef7 100644 --- a/dlls/gearbox/fgrunt.cpp +++ b/dlls/gearbox/fgrunt.cpp @@ -6,349 +6,300 @@ * 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. +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. * ****/ -//========================================================= -// friendly grunt -//========================================================= -// UNDONE: Holster weapon? +// Based on implementation by Raven City Team, modified by FreeSlave #include "extdll.h" -#include "plane.h" #include "util.h" #include "cbase.h" #include "monsters.h" -#include "squad.h" -#include "squadmonster.h" +#include "animation.h" #include "talkmonster.h" #include "schedule.h" -#include "animation.h" +#include "defaultai.h" +#include "scripted.h" #include "weapons.h" -#include "talkmonster.h" #include "soundent.h" -#include "effects.h" #include "customentity.h" -#include "fgrunt.h" - -#ifdef DEBUG -#include "gearbox_utils.h" -#endif - -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[]; - -extern Schedule_t slBaFollow[]; -extern Schedule_t slBarneyEnemyDraw[]; -extern Schedule_t slBaFaceTarget[]; -extern Schedule_t slIdleBaStand[]; - - -//========================================================= -// monster-specific DEFINE's -//========================================================= -#define FGRUNT_CLIP_SIZE_MP5 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! -#define FGRUNT_CLIP_SIZE_SAW 50 // Same as above -#define FGRUNT_VOL 0.35 // volume of grunt sounds -#define FGRUNT_ATTN ATTN_NORM // attenutation of grunt sentences -#define FGRUNT_LIMP_HEALTH 20 -#define FGRUNT_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot. -#define FGRUNT_NUM_HEADS 8 // how many grunt heads are there? -#define FGRUNT_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill -#define FGRUNT_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences - -#define FGRUNT_9MMAR ( 1 << 0 ) -#define FGRUNT_HANDGRENADE ( 1 << 1 ) -#define FGRUNT_GRENADELAUNCHER ( 1 << 2 ) -#define FGRUNT_SHOTGUN ( 1 << 3 ) -#define FGRUNT_SAW ( 1 << 4 ) - -#define HEAD_GROUP 1 -#define TORSO_GROUP 2 -#define GUN_GROUP 3 - -#define HEAD_GASMASK 0 -#define HEAD_BERET 1 -#define HEAD_OPS_MASK 2 -#define HEAD_BANDANA_WHITE 3 -#define HEAD_BANDANA_BLACK 4 -#define HEAD_MP 5 -#define HEAD_MAJOR 6 -#define HEAD_BERET_BLACK 7 - -#define TORSO_BACKPACK 0 -#define TORSO_SAW 1 -#define TORSO_NOBACKPACK 2 -#define TORSO_SHELLS 3 - -#define GUN_MP5 0 -#define GUN_SHOTGUN 1 -#define GUN_SAW 2 -#define GUN_NONE 3 +#include "decals.h" +#include "hgrunt.h" //========================================================= -// Monster's Anim Events Go Here +// //========================================================= -#define FGRUNT_AE_RELOAD ( 2 ) -#define FGRUNT_AE_KICK ( 3 ) -#define FGRUNT_AE_BURST1 ( 4 ) -#define FGRUNT_AE_BURST2 ( 5 ) -#define FGRUNT_AE_BURST3 ( 6 ) -#define FGRUNT_AE_GREN_TOSS ( 7 ) -#define FGRUNT_AE_GREN_LAUNCH ( 8 ) -#define FGRUNT_AE_GREN_DROP ( 9 ) -#define FGRUNT_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad. -#define FGRUNT_AE_DROP_GUN ( 11) // grunt (probably dead) is dropping his mp5. +#define FGRUNT_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! +#define FGRUNT_LIMP_HEALTH 20 +#define FGRUNT_SENTENCE_VOLUME 0.35 + +#define FGRUNT_9MMAR ( 1 << 0) +#define FGRUNT_HANDGRENADE ( 1 << 1) +#define FGRUNT_GRENADELAUNCHER ( 1 << 2) +#define FGRUNT_SHOTGUN ( 1 << 3) +#define FGRUNT_M249 ( 1 << 4) + +// Torso group for weapons +#define FG_TORSO_GROUP 2 +#define FG_TORSO_DEFAULT 0 +#define FG_TORSO_M249 1 +#define FG_TORSO_FLAT 2 +#define FG_TORSO_SHOTGUN 3 + +// Weapon group +#define FG_GUN_GROUP 3 +#define FG_GUN_MP5 0 +#define FG_GUN_SHOTGUN 1 +#define FG_GUN_SAW 2 +#define FG_GUN_NONE 3 +#define CALL_MEDIC_DELAY 6 // Wait before calling for medic again. //========================================================= -// monster-specific schedule types +// monster-specific tasks //========================================================= enum { - SCHED_FGRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, - SCHED_FGRUNT_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_FGRUNT_COVER_AND_RELOAD, - SCHED_FGRUNT_SWEEP, - SCHED_FGRUNT_FOUND_ENEMY, - SCHED_FGRUNT_REPEL, - SCHED_FGRUNT_REPEL_ATTACK, - SCHED_FGRUNT_REPEL_LAND, - SCHED_FGRUNT_WAIT_FACE_ENEMY, - SCHED_FGRUNT_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. - SCHED_FGRUNT_ELOF_FAIL, + TASK_HGRUNT_ALLY_FACE_TOSS_DIR = LAST_TALKMONSTER_TASK + 1, + TASK_HGRUNT_ALLY_SPEAK_SENTENCE, + TASK_HGRUNT_ALLY_CHECK_FIRE, + TASK_HGRUNT_ALLY_FIND_MEDIC, + LAST_HGRUNT_ALLY_TASK }; - //========================================================= -// monster-specific tasks +// monster heads //========================================================= + +// Head group +#define FG_HEAD_GROUP 1 enum { - TASK_FGRUNT_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, - TASK_FGRUNT_SPEAK_SENTENCE, - TASK_FGRUNT_CHECK_FIRE, + FG_HEAD_MASK, + FG_HEAD_BERET, + FG_HEAD_SHOTGUN, + FG_HEAD_SAW, + FG_HEAD_SAW_BLACK, + FG_HEAD_MP, + FG_HEAD_MAJOR, + FG_HEAD_BERET_BLACK, + FG_HEAD_COUNT }; -static const char* g_pszDeathSounds[] = +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HGRUNT_ALLY_AE_RELOAD ( 2 ) +#define HGRUNT_ALLY_AE_KICK ( 3 ) +#define HGRUNT_ALLY_AE_BURST1 ( 4 ) +#define HGRUNT_ALLY_AE_BURST2 ( 5 ) +#define HGRUNT_ALLY_AE_BURST3 ( 6 ) +#define HGRUNT_ALLY_AE_GREN_TOSS ( 7 ) +#define HGRUNT_ALLY_AE_GREN_LAUNCH ( 8 ) +#define HGRUNT_ALLY_AE_GREN_DROP ( 9 ) +#define HGRUNT_ALLY_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad. +#define HGRUNT_ALLY_AE_DROP_GUN ( 11) // grunt (probably dead) is dropping his mp5. +//========================================================= +// monster-specific schedule types +//========================================================= +enum { - "fgrunt/death1.wav", - "fgrunt/death2.wav", - "fgrunt/death3.wav", - "fgrunt/death4.wav", - "fgrunt/death5.wav", - "fgrunt/death6.wav", + SCHED_HGRUNT_ALLY_SUPPRESS = LAST_TALKMONSTER_SCHEDULE + 1, + SCHED_HGRUNT_ALLY_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_HGRUNT_ALLY_COVER_AND_RELOAD, + SCHED_HGRUNT_ALLY_SWEEP, + SCHED_HGRUNT_ALLY_FOUND_ENEMY, + SCHED_HGRUNT_ALLY_REPEL, + SCHED_HGRUNT_ALLY_REPEL_ATTACK, + SCHED_HGRUNT_ALLY_REPEL_LAND, + SCHED_HGRUNT_ALLY_WAIT_FACE_ENEMY, + SCHED_HGRUNT_ALLY_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. + SCHED_HGRUNT_ALLY_ELOF_FAIL, + SCHED_HGRUNT_ALLY_FIND_MEDIC, + LAST_HGRUNT_ALLY_SCHEDULE, }; -static const char* g_pszPainSounds[] = +enum { - "fgrunt/pain1.wav", - "fgrunt/pain2.wav", - "fgrunt/pain3.wav", - "fgrunt/pain4.wav", - "fgrunt/pain5.wav", - "fgrunt/pain6.wav", + SCHED_MEDIC_HEAL = LAST_HGRUNT_ALLY_SCHEDULE, }; -#define FGRUNT_NUM_DEATH_SOUNDS ARRAYSIZE( g_pszDeathSounds ) -#define FGRUNT_NUM_PAIN_SOUNDS ARRAYSIZE( g_pszPainSounds ) +class CHFGrunt : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int ISoundMask( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void CheckAmmo ( void ); + void SetActivity ( Activity NewActivity ); + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + virtual int ObjectCaps(void) { return CTalkMonster::ObjectCaps() | FCAP_IMPULSE_USE; } + void KeyValue( KeyValueData *pkvd ); + BOOL FCanCheckAttacks ( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + + void DeclineFollowing(void); + int MaxFollowers() { return 5; } + const char* FriendByNumber( int arrayNumber ) { return m_szFriends[FriendNumber(arrayNumber)]; } + int NumberOfFriends() { return ARRAYSIZE(m_szFriends); } + + bool WantsToCallMedic(); + bool TryCallForMedic(CBaseEntity* pOther); + + void PrescheduleThink ( void ); + Vector GetGunPosition( void ); + void Shoot ( void ); + void Shotgun ( void ); + void M249 ( void ); + CBaseEntity *Kick( void ); + // Override these to set behavior + Schedule_t *GetScheduleOfType ( int Type ); + Schedule_t *GetSchedule ( void ); + MONSTERSTATE GetIdealState ( void ); + + void AlertSound( void ); + void DeathSound( void ); + void PainSound( void ); + void IdleSound( void ); + + void GibMonster( void ); + void SpeakSentence( void ); + void TalkInit( void ); + + BOOL FOkToSpeak( void ); + void JustSpoke( void ); + + void DropMyItems(BOOL isGibbed); + void DropMyItem(const char *entityName, const Vector &vecGunPos, const Vector &vecGunAngles, BOOL isGibbed); + + 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 Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; -//========================================================= -// CFGrunt -//========================================================= + // checking the feasibility of a grenade toss is kind of costly, so we do it every couple of seconds, + // not every server frame. + float m_flNextGrenadeCheck; + float m_flNextPainTime; + float m_flMedicWaitTime; + bool m_flLinkToggle; -LINK_ENTITY_TO_CLASS(monster_human_grunt_ally, CFGrunt); + Vector m_vecTossVelocity; -TYPEDESCRIPTION CFGrunt::m_SaveData[] = -{ - DEFINE_FIELD(CFGrunt, m_painTime, FIELD_TIME), - DEFINE_FIELD(CFGrunt, m_checkAttackTime, FIELD_TIME), - DEFINE_FIELD(CFGrunt, m_lastAttackCheck, FIELD_BOOLEAN), - DEFINE_FIELD(CFGrunt, m_flPlayerDamage, FIELD_FLOAT), + BOOL m_fThrowGrenade; + BOOL m_fStanding; + BOOL m_fFirstEncounter;// only put on the handsign show in the squad's first encounter. + int m_cClipSize; - DEFINE_FIELD(CFGrunt, head, FIELD_INTEGER), - DEFINE_FIELD(CFGrunt, torso, FIELD_INTEGER), -}; + int m_iSentence; + int m_iHead; -IMPLEMENT_SAVERESTORE(CFGrunt, CTalkMonster); + float m_flLastHitByPlayer; + int m_iPlayerHits; + int m_iBrassShell; + int m_iShotgunShell; + int m_iM249Shell; + int m_iM249Link; -#if 0 + static const char *pGruntSentences[]; -//========================================================= -// AI Schedules Specific to this monster -//========================================================= -Task_t tlFgFollow[] = -{ - { TASK_MOVE_TO_TARGET_RANGE, (float)128 }, // Move within 128 of target ent (client) - { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, -}; + static const char *m_szFriends[3]; -Schedule_t slFgFollow[] = -{ - { - tlFgFollow, - ARRAYSIZE(tlFgFollow), - bits_COND_NEW_ENEMY | - bits_COND_LIGHT_DAMAGE | - bits_COND_HEAVY_DAMAGE | - bits_COND_HEAR_SOUND | - bits_COND_PROVOKED, - bits_SOUND_DANGER, - "Follow" - }, -}; + static int g_fGruntAllyQuestion; -//========================================================= -// BarneyDraw- much better looking draw schedule for when -// barney knows who he's gonna attack. -//========================================================= -Task_t tlFgEnemyDraw[] = -{ - { TASK_STOP_MOVING, 0 }, - { TASK_FACE_ENEMY, 0 }, - { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_ARM }, -}; + CUSTOM_SCHEDULES -Schedule_t slFgEnemyDraw[] = -{ - { - tlFgEnemyDraw, - ARRAYSIZE(tlFgEnemyDraw), - 0, - 0, - "FGrunt Enemy Draw" +protected: + void KickImpl(float kickDamage); + void PrecacheHelper(); + void SpawnHelper(const char* model, float health); + const char* SentenceByNumber(int sentence) { + return pGruntSentences[sentence]; } }; -Task_t tlFgFaceTarget[] = -{ - { TASK_SET_ACTIVITY, (float)ACT_IDLE }, - { TASK_FACE_TARGET, (float)0 }, - { TASK_SET_ACTIVITY, (float)ACT_IDLE }, - { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE }, -}; - -Schedule_t slFgFaceTarget[] = -{ - { - tlFgFaceTarget, - ARRAYSIZE(tlFgFaceTarget), - bits_COND_CLIENT_PUSH | - bits_COND_NEW_ENEMY | - bits_COND_LIGHT_DAMAGE | - bits_COND_HEAVY_DAMAGE | - bits_COND_HEAR_SOUND | - bits_COND_PROVOKED, - bits_SOUND_DANGER, - "FaceTarget" - }, -}; - +LINK_ENTITY_TO_CLASS( monster_human_grunt_ally, CHFGrunt ) -Task_t tlIdleFgStand[] = -{ - { TASK_STOP_MOVING, 0 }, - { TASK_SET_ACTIVITY, (float)ACT_IDLE }, - { TASK_WAIT, (float)2 }, // repick IDLESTAND every two seconds. - { TASK_TLK_HEADRESET, (float)0 }, // reset head position -}; +int CHFGrunt::g_fGruntAllyQuestion = 0; -Schedule_t slIdleFgStand[] = +class CMedic : public CHFGrunt { - { - tlIdleFgStand, - ARRAYSIZE(tlIdleFgStand), - bits_COND_NEW_ENEMY | - bits_COND_LIGHT_DAMAGE | - bits_COND_HEAVY_DAMAGE | - bits_COND_HEAR_SOUND | - bits_COND_SMELL | - bits_COND_PROVOKED, - - bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. - //bits_SOUND_PLAYER | - //bits_SOUND_WORLD | - - bits_SOUND_DANGER | - bits_SOUND_MEAT |// scents - bits_SOUND_CARCASS | - bits_SOUND_GARBAGE, - "IdleStand" - }, -}; +public: + void Spawn( void ); + void Precache( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + void GibMonster(); + + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + Schedule_t *GetSchedule ( void ); + Schedule_t *GetScheduleOfType(int Type); + void StopFollowing( BOOL clearSchedule ); + void SetAnswerQuestion(CTalkMonster *pSpeaker); + + void DropMyItems(BOOL isGibbed); + + void FirePistol ( const char* shotSound, Bullet bullet ); + bool Heal(); + void StartFollowingHealTarget(CBaseEntity* pTarget); + bool ReadyToHeal(); + void StopHealing(); + CBaseEntity* HealTarget(); + inline bool HasHealTarget() { return HealTarget() != 0; } + inline bool HasHealCharge() { return m_flHealCharge >= 1; } + bool InHealSchedule(); + bool CheckHealCharge(); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; -DEFINE_CUSTOM_SCHEDULES(CFGrunt) -{ - slFgFollow, - slFgEnemyDraw, - slFgFaceTarget, - slIdleFgStand, + CUSTOM_SCHEDULES + float m_flHealCharge; + BOOL m_fDepleteLine; + BOOL m_fHealing; }; -#else -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, - slBaFollow, - slBarneyEnemyDraw, - slBaFaceTarget, - slIdleBaStand, +TYPEDESCRIPTION CHFGrunt::m_SaveData[] = +{ + DEFINE_FIELD( CHFGrunt, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CHFGrunt, m_flNextPainTime, FIELD_TIME ), + DEFINE_FIELD( CHFGrunt, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CHFGrunt, m_fThrowGrenade, FIELD_BOOLEAN ), + DEFINE_FIELD( CHFGrunt, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CHFGrunt, m_fFirstEncounter, FIELD_BOOLEAN ), + DEFINE_FIELD( CHFGrunt, m_cClipSize, FIELD_INTEGER ), + DEFINE_FIELD( CHFGrunt, m_iHead, FIELD_INTEGER ), + DEFINE_FIELD( CHFGrunt, m_flMedicWaitTime, FIELD_TIME ), + DEFINE_FIELD( CHFGrunt, m_flLastHitByPlayer, FIELD_TIME ), + DEFINE_FIELD( CHFGrunt, m_iPlayerHits, FIELD_INTEGER ), }; -#endif +IMPLEMENT_SAVERESTORE( CHFGrunt, CTalkMonster ) -IMPLEMENT_CUSTOM_SCHEDULES(CFGrunt, CTalkMonster); - +const char *CHFGrunt::m_szFriends[3] = +{ + "monster_human_grunt_ally", + "monster_human_torch_ally", + "monster_human_medic_ally", +}; -const char *CFGrunt::pGruntSentences[] = +const char *CHFGrunt::pGruntSentences[] = { "FG_GREN", // grenade scared grunt "FG_ALERT", // sees player @@ -369,2098 +320,3535 @@ typedef enum FGRUNT_SENT_THROW, FGRUNT_SENT_CHARGE, FGRUNT_SENT_TAUNT, - } FGRUNT_SENTENCE_TYPES; //========================================================= -// Speak Sentence - say your cued up sentence. +// KeyValue // -// 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. +// !!! netname entvar field is used in squadmonster for groupname!!! //========================================================= -void CFGrunt::SpeakSentence(void) +void CHFGrunt :: KeyValue( KeyValueData *pkvd ) { - if (m_iSentence == FGRUNT_SENT_NONE) + if (FStrEq(pkvd->szKeyName, "head")) { - // no sentence cued up. - return; + m_iHead = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; } - - if (FOkToSpeak()) + else { - SENTENCEG_PlayRndSz(ENT(pev), pGruntSentences[m_iSentence], FGRUNT_SENTENCE_VOLUME, FGRUNT_ATTN, 0, m_voicePitch); - JustSpoke(); + CTalkMonster::KeyValue( pkvd ); } } - //========================================================= -// IRelationship - overridden because Alien Grunts are -// Human Grunt's nemesis. +// someone else is talking - don't speak //========================================================= -int CFGrunt::IRelationship(CBaseEntity *pTarget) +BOOL CHFGrunt :: FOkToSpeak( void ) { - if (FClassnameIs(pTarget->pev, "monster_alien_grunt") || (FClassnameIs(pTarget->pev, "monster_gargantua"))) +// if someone else is talking, don't speak + if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) + return FALSE; + + // if in the grip of a barnacle, don't speak + if ( m_MonsterState == MONSTERSTATE_PRONE || m_IdealMonsterState == MONSTERSTATE_PRONE ) { - return R_NM; + return FALSE; } - return CTalkMonster::IRelationship(pTarget); -} - -//========================================================= -// GibMonster - make gun fly through the air. -//========================================================= -void CFGrunt::GibMonster(void) -{ - Vector vecGunPos; - Vector vecGunAngles; - - if (GetBodygroup(2) != 2) - {// throw a gun if the grunt has one - GetAttachment(0, vecGunPos, vecGunAngles); - - CBaseEntity *pGun; - if (FBitSet(pev->weapons, FGRUNT_SHOTGUN)) - { - pGun = DropItem("weapon_shotgun", vecGunPos, vecGunAngles); - } - else if (FBitSet(pev->weapons, FGRUNT_SAW)) - { - pGun = DropItem("weapon_m249", vecGunPos, vecGunAngles); - } - else - { - pGun = DropItem("weapon_9mmAR", vecGunPos, vecGunAngles); - } - if (pGun) - { - pGun->pev->velocity = Vector(RANDOM_FLOAT(-100, 100), RANDOM_FLOAT(-100, 100), RANDOM_FLOAT(200, 300)); - pGun->pev->avelocity = Vector(0, RANDOM_FLOAT(200, 400), 0); - } + // if not alive, certainly don't speak + if ( pev->deadflag != DEAD_NO ) + { + return FALSE; + } - if (FBitSet(pev->weapons, FGRUNT_GRENADELAUNCHER)) + if ( pev->spawnflags & SF_MONSTER_GAG ) + { + if ( m_MonsterState != MONSTERSTATE_COMBAT ) { - pGun = DropItem("ammo_ARgrenades", vecGunPos, vecGunAngles); - if (pGun) - { - pGun->pev->velocity = Vector(RANDOM_FLOAT(-100, 100), RANDOM_FLOAT(-100, 100), RANDOM_FLOAT(200, 300)); - pGun->pev->avelocity = Vector(0, RANDOM_FLOAT(200, 400), 0); - } + // no talking outside of combat if gagged. + return FALSE; } } - CTalkMonster::GibMonster(); + return TRUE; } - //========================================================= -// ISoundMask - returns a bit mask indicating which types -// of sounds this monster regards. //========================================================= -int CFGrunt::ISoundMask(void) +void CHFGrunt :: JustSpoke( void ) { - return bits_SOUND_WORLD | - bits_SOUND_COMBAT | - bits_SOUND_CARCASS | - bits_SOUND_MEAT | - bits_SOUND_GARBAGE | - bits_SOUND_DANGER | - bits_SOUND_PLAYER; + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(1.5, 2.0); + m_iSentence = FGRUNT_SENT_NONE; } - - //========================================================= -// someone else is talking - don't speak +// IRelationship - overridden because Male Assassins are +// Human Grunt's nemesis. //========================================================= -BOOL CFGrunt::FOkToSpeak(void) +int CHFGrunt::IRelationship ( CBaseEntity *pTarget ) { - // if someone else is talking, don't speak - if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) - return FALSE; - - if (pev->spawnflags & SF_MONSTER_GAG) + if ( FClassnameIs( pTarget->pev, "monster_male_assassin" ) ) { - if (m_MonsterState != MONSTERSTATE_COMBAT) - { - // no talking outside of combat if gagged. - return FALSE; - } + return R_NM; } - // if player is not in pvs, don't speak - // if (FNullEnt(FIND_CLIENT_IN_PVS(edict()))) - // return FALSE; - - return TRUE; + return CTalkMonster::IRelationship( pTarget ); } //========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// FGruntFail //========================================================= -void CFGrunt::JustSpoke(void) +Task_t tlFGruntFail[] = { - CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(1.5, 2.0); - m_iSentence = FGRUNT_SENT_NONE; -} + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; +Schedule_t slFGruntFail[] = +{ + { + tlFGruntFail, + ARRAYSIZE ( tlFGruntFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + 0, + "FGrunt Fail" + }, +}; //========================================================= -// Classify - indicates this monster's place in the -// relationship table. +// FGrunt Combat Fail //========================================================= -int CFGrunt::Classify(void) +Task_t tlFGruntCombatFail[] = { - return CLASS_PLAYER_ALLY; -} + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; +Schedule_t slFGruntCombatFail[] = +{ + { + tlFGruntCombatFail, + ARRAYSIZE ( tlFGruntCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "FGrunt Combat Fail" + }, +}; //========================================================= +// Victory dance! //========================================================= -CBaseEntity *CFGrunt::Kick(void) +Task_t tlFGruntVictoryDance[] = { - TraceResult tr; + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_FAIL }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, 1.5f }, + { TASK_GET_PATH_TO_ENEMY_CORPSE, 64.0f }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, +}; - UTIL_MakeVectors(pev->angles); - Vector vecStart = pev->origin; - vecStart.z += pev->size.z * 0.5; - Vector vecEnd = vecStart + (gpGlobals->v_forward * 70); +Schedule_t slFGruntVictoryDance[] = +{ + { + tlFGruntVictoryDance, + ARRAYSIZE ( tlFGruntVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "FGruntVictoryDance" + }, +}; - UTIL_TraceHull(vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr); +//========================================================= +// Establish line of fire - move to a position that allows +// the grunt to attack. +//========================================================= +Task_t tlFGruntEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_HGRUNT_ALLY_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; - if (tr.pHit) +Schedule_t slFGruntEstablishLineOfFire[] = +{ { - CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); - return pEntity; - } + tlFGruntEstablishLineOfFire, + ARRAYSIZE ( tlFGruntEstablishLineOfFire ), + 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, - return NULL; -} + bits_SOUND_DANGER, + "FGruntEstablishLineOfFire" + }, +}; //========================================================= -// GetGunPosition return the end of the barrel +// FGruntFoundEnemy - FGrunt established sight with an enemy +// that was hiding from the squad. //========================================================= +Task_t tlFGruntFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_SIGNAL1 }, +}; -Vector CFGrunt::GetGunPosition() +Schedule_t slFGruntFoundEnemy[] = { - if (m_fStanding) { - return pev->origin + Vector(0, 0, 60); - } - else - { - return pev->origin + Vector(0, 0, 48); - } -} + tlFGruntFoundEnemy, + ARRAYSIZE ( tlFGruntFoundEnemy ), + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "FGruntFoundEnemy" + }, +}; //========================================================= -// ALertSound - barney says "Freeze!" +// GruntCombatFace Schedule //========================================================= -void CFGrunt::AlertSound(void) +Task_t tlFGruntCombatFace1[] = +{ + { 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_HGRUNT_ALLY_SWEEP }, +}; + +Schedule_t slFGruntCombatFace[] = { - if (m_hEnemy != 0) { - if (FOkToSpeak()) - { - PlaySentence("FG_ATTACK", RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE); - } - } + tlFGruntCombatFace1, + ARRAYSIZE ( tlFGruntCombatFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Combat Face" + }, +}; -} -//========================================================= -// SetYawSpeed - allows each sequence to have a different -// turn rate associated with it. //========================================================= -void CFGrunt::SetYawSpeed(void) +// Suppressing fire - don't stop shooting until the clip is +// empty or FGrunt gets hurt. +//========================================================= +Task_t tlFGruntSignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slFGruntSignalSuppress[] = { - int ys; + { + tlFGruntSignalSuppress, + ARRAYSIZE ( tlFGruntSignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; - ys = 0; +Task_t tlFGruntSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; - switch (m_Activity) +Schedule_t slFGruntSuppress[] = +{ { - case ACT_IDLE: - ys = 70; - break; - case ACT_WALK: - ys = 70; - break; - case ACT_RUN: - ys = 90; - break; - default: - ys = 70; - break; - } + tlFGruntSuppress, + ARRAYSIZE ( tlFGruntSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_NOFIRE | + bits_COND_NO_AMMO_LOADED, - pev->yaw_speed = ys; -} + bits_SOUND_DANGER, + "Suppress" + }, +}; //========================================================= -// FCanCheckAttacks - this is overridden for human grunts -// because they can throw/shoot grenades when they can't see their -// target and the base class doesn't check attacks if the monster -// cannot see its enemy. -// -// !!!BUGBUG - this gets called before a 3-round burst is fired -// which means that a friendly can still be hit with up to 2 rounds. -// ALSO, grenades will not be tossed if there is a friendly in front, -// this is a bad bug. Friendly machine gun fire avoidance -// will unecessarily prevent the throwing of a grenade as well. +// FGrunt 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. //========================================================= -BOOL CFGrunt::FCanCheckAttacks(void) +Task_t tlFGruntWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slFGruntWaitInCover[] = { - if (!HasConditions(bits_COND_ENEMY_TOOFAR)) - { - return TRUE; - } - else { - return FALSE; - } -} + tlFGruntWaitInCover, + ARRAYSIZE ( tlFGruntWaitInCover ), + 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, + "FGruntWaitInCover" + }, +}; //========================================================= -// CheckMeleeAttack1 +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. //========================================================= -BOOL CFGrunt::CheckMeleeAttack1(float flDot, float flDist) +Task_t tlFGruntTakeCover1[] = { - CBaseMonster *pEnemy; + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_HGRUNT_ALLY_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_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_HGRUNT_ALLY_WAIT_FACE_ENEMY }, +}; - if (m_hEnemy != 0) +Schedule_t slFGruntTakeCover[] = +{ { - pEnemy = m_hEnemy->MyMonsterPointer(); + tlFGruntTakeCover1, + ARRAYSIZE ( tlFGruntTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; - if (!pEnemy) - { - return FALSE; - } - } +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlFGruntGrenadeCover1[] = +{ + { 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_HGRUNT_ALLY_WAIT_FACE_ENEMY }, +}; - if (flDist <= 64 && flDot >= 0.7 && - pEnemy->Classify() != CLASS_ALIEN_BIOWEAPON && - pEnemy->Classify() != CLASS_PLAYER_BIOWEAPON) +Schedule_t slFGruntGrenadeCover[] = +{ { - return TRUE; - } - return FALSE; -} + tlFGruntGrenadeCover1, + ARRAYSIZE ( tlFGruntGrenadeCover1 ), + 0, + 0, + "GrenadeCover" + }, +}; //========================================================= -// CheckRangeAttack1 +// drop grenade then run to cover. //========================================================= -BOOL CFGrunt::CheckRangeAttack1(float flDot, float flDist) +Task_t tlFGruntTossGrenadeCover1[] = { - if (flDist <= 1024 && flDot >= 0.5) - { - if (gpGlobals->time > m_checkAttackTime) - { - TraceResult tr; - - Vector shootOrigin = pev->origin + Vector(0, 0, 55); - CBaseEntity *pEnemy = m_hEnemy; - Vector shootTarget = ((pEnemy->BodyTarget(shootOrigin) - pEnemy->pev->origin) + m_vecEnemyLKP); - UTIL_TraceLine(shootOrigin, shootTarget, dont_ignore_monsters, ENT(pev), &tr); - m_checkAttackTime = gpGlobals->time + 1; - if (tr.flFraction == 1.0 || (tr.pHit != NULL && CBaseEntity::Instance(tr.pHit) == pEnemy)) - m_lastAttackCheck = TRUE; - else - m_lastAttackCheck = FALSE; - m_checkAttackTime = gpGlobals->time + 1.5; - } - return m_lastAttackCheck; - } - - CSquadMonster* pSquadMonster = MySquadMonsterPointer(); + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; - if (pSquadMonster && pSquadMonster->InSquad()) +Schedule_t slFGruntTossGrenadeCover[] = +{ { - if (!HasConditions(bits_COND_ENEMY_OCCLUDED) && flDist <= 2048 && flDot >= 0.5 && pSquadMonster->NoFriendlyFire()) - { - TraceResult tr; - - if (!m_hEnemy->IsPlayer() && flDist <= 64) - { - // kick nonclients, but don't shoot at them. - return FALSE; - } - - Vector vecSrc = GetGunPosition(); - - // verify that a bullet fired from the gun will hit the enemy before the world. - UTIL_TraceLine(vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); - - if (tr.flFraction == 1.0) - { - return TRUE; - } - } - } - - return FALSE; -} - + tlFGruntTossGrenadeCover1, + ARRAYSIZE ( tlFGruntTossGrenadeCover1 ), + 0, + 0, + "TossGrenadeCover" + }, +}; //========================================================= -// CheckRangeAttack2 - this checks the Grunt's grenade -// attack. +// hide from the loudest sound source (to run from grenade) //========================================================= -BOOL CFGrunt::CheckRangeAttack2(float flDot, float flDist) +Task_t tlFGruntTakeCoverFromBestSound[] = { - if (!FBitSet(pev->weapons, (FGRUNT_HANDGRENADE | FGRUNT_GRENADELAUNCHER))) - { - return FALSE; - } + { 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 }, +}; - // if the grunt isn't moving, it's ok to check. - if (m_flGroundSpeed != 0) +Schedule_t slFGruntTakeCoverFromBestSound[] = +{ { - m_fThrowGrenade = FALSE; - return m_fThrowGrenade; - } + tlFGruntTakeCoverFromBestSound, + ARRAYSIZE ( tlFGruntTakeCoverFromBestSound ), + 0, + 0, + "FGruntTakeCoverFromBestSound" + }, +}; - // assume things haven't changed too much since last time - if (gpGlobals->time < m_flNextGrenadeCheck) - { - return m_fThrowGrenade; - } +//========================================================= +// Grunt reload schedule +//========================================================= +Task_t tlFGruntHideReload[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RELOAD }, + { TASK_STOP_MOVING, (float)0 }, + { 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 }, +}; - if (!FBitSet(m_hEnemy->pev->flags, FL_ONGROUND) && m_hEnemy->pev->waterlevel == 0 && m_vecEnemyLKP.z > pev->absmax.z) +Schedule_t slFGruntHideReload[] = +{ { - //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to - // be grenaded. - // don't throw grenades at anything that isn't on the ground! - m_fThrowGrenade = FALSE; - return m_fThrowGrenade; - } + tlFGruntHideReload, + ARRAYSIZE ( tlFGruntHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_DEAD | // stop running away if enemy is already dead + bits_COND_HEAR_SOUND, - Vector vecTarget; - - if (FBitSet(pev->weapons, FGRUNT_HANDGRENADE)) - { - // find feet - if (RANDOM_LONG(0, 1)) - { - // magically know where they are - vecTarget = Vector(m_hEnemy->pev->origin.x, m_hEnemy->pev->origin.y, m_hEnemy->pev->absmin.z); - } - else - { - // toss it to where you last saw them - vecTarget = m_vecEnemyLKP; - } - // vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); - // estimate position - // vecTarget = vecTarget + m_hEnemy->pev->velocity * 2; - } - else - { - // find target - // vecTarget = m_hEnemy->BodyTarget( pev->origin ); - vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget(pev->origin) - m_hEnemy->pev->origin); - // estimate position - if (HasConditions(bits_COND_SEE_ENEMY)) - vecTarget = vecTarget + ((vecTarget - pev->origin).Length() / gSkillData.hgruntGrenadeSpeed) * m_hEnemy->pev->velocity; + bits_SOUND_DANGER, + "FGruntHideReload" } +}; - // are any of my squad members near the intended grenade impact area? - CSquadMonster* pSquadMonster = MySquadMonsterPointer(); - if (pSquadMonster && pSquadMonster->InSquad()) - { - if (pSquadMonster->SquadMemberInRange(vecTarget, 256)) - { - // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while. - m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. - m_fThrowGrenade = FALSE; - } - } +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlFGruntSweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; - if ((vecTarget - pev->origin).Length2D() <= 256) +Schedule_t slFGruntSweep[] = +{ { - // crap, I don't want to blow myself up - m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. - m_fThrowGrenade = FALSE; - return m_fThrowGrenade; - } + tlFGruntSweep, + ARRAYSIZE ( tlFGruntSweep ), + 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, - if (FBitSet(pev->weapons, FGRUNT_HANDGRENADE)) - { - Vector vecToss = VecCheckToss(pev, GetGunPosition(), vecTarget, 0.5); + bits_SOUND_WORLD |// sound flags + bits_SOUND_DANGER | + bits_SOUND_PLAYER, - if (vecToss != g_vecZero) - { - m_vecTossVelocity = vecToss; + "FGrunt Sweep" + }, +}; - // throw a hand grenade - m_fThrowGrenade = TRUE; - // don't check again for a while. - m_flNextGrenadeCheck = gpGlobals->time; // 1/3 second. - } - else - { - // don't throw - m_fThrowGrenade = FALSE; - // don't check again for a while. - m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. - } - } - else - { - Vector vecToss = VecCheckThrow(pev, GetGunPosition(), vecTarget, gSkillData.hgruntGrenadeSpeed, 0.5); +//========================================================= +// 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 tlFGruntRangeAttack1A[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; - if (vecToss != g_vecZero) - { - m_vecTossVelocity = vecToss; +Schedule_t slFGruntRangeAttack1A[] = +{ + { + tlFGruntRangeAttack1A, + ARRAYSIZE ( tlFGruntRangeAttack1A ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND | + bits_COND_NOFIRE | + bits_COND_NO_AMMO_LOADED, - // throw a hand grenade - m_fThrowGrenade = TRUE; - // don't check again for a while. - m_flNextGrenadeCheck = gpGlobals->time + 0.3; // 1/3 second. - } - else - { - // don't throw - m_fThrowGrenade = FALSE; - // don't check again for a while. - m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. - } - } + bits_SOUND_DANGER, + "Range Attack1A" + }, +}; - return m_fThrowGrenade; -} //========================================================= -// Fire +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. //========================================================= -void CFGrunt::Fire(const Vector& vecShootOrigin, const Vector& vecShootDir, const Vector& vecSpread, int model, int effects, int bulletType, int soundType) +Task_t tlFGruntRangeAttack1B[] = { - 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, model, soundType); - FireBullets(1, vecShootOrigin, vecShootDir, vecSpread, 2048, bulletType); // shoot +-5 degrees + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_IDLE_ANGRY }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_HGRUNT_ALLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; - pev->effects |= effects; +Schedule_t slFGruntRangeAttack1B[] = +{ + { + tlFGruntRangeAttack1B, + ARRAYSIZE ( tlFGruntRangeAttack1B ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_NOFIRE | + bits_COND_HEAR_SOUND, - Vector angDir = UTIL_VecToAngles(vecShootDir); - SetBlending(0, angDir.x); -} + bits_SOUND_DANGER, + "Range Attack1B" + }, +}; //========================================================= -// Shoot +// secondary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. //========================================================= -void CFGrunt::Shoot(void) +Task_t tlFGruntRangeAttack2[] = { - if (m_hEnemy == 0) - { - return; - } + { TASK_STOP_MOVING, (float)0 }, + { TASK_HGRUNT_ALLY_FACE_TOSS_DIR, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RANGE_ATTACK2 }, + { TASK_SET_SCHEDULE, (float)SCHED_HGRUNT_ALLY_WAIT_FACE_ENEMY },// don't run immediately after throwing grenade. +}; - Fire(GetGunPosition(), ShootAtEnemy(GetGunPosition()), VECTOR_CONE_10DEGREES, m_iBrassShell, EF_MUZZLEFLASH, BULLET_MONSTER_MP5, TE_BOUNCE_SHELL); +Schedule_t slFGruntRangeAttack2[] = +{ + { + tlFGruntRangeAttack2, + ARRAYSIZE ( tlFGruntRangeAttack2 ), + 0, + 0, + "RangeAttack2" + }, +}; - m_cAmmoLoaded--;// take away a bullet! -} //========================================================= -// Shotgun +// repel //========================================================= -void CFGrunt::Shotgun(void) +Task_t tlFGruntRepel[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_GLIDE }, +}; + +Schedule_t slFGruntRepel[] = { - if (m_hEnemy == 0) { - return; - } + tlFGruntRepel, + ARRAYSIZE ( tlFGruntRepel ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, - Fire(GetGunPosition(), ShootAtEnemy(GetGunPosition()), VECTOR_CONE_15DEGREES, m_iShotgunShell, EF_MUZZLEFLASH, BULLET_PLAYER_BUCKSHOT, TE_BOUNCE_SHELL); + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel" + }, +}; - m_cAmmoLoaded--;// take away a bullet! -} //========================================================= -// Saw +// repel //========================================================= -void CFGrunt::Saw(void) +Task_t tlFGruntRepelAttack[] = { - if (m_hEnemy == 0) - { - return; - } - - Fire(GetGunPosition(), ShootAtEnemy(GetGunPosition()), VECTOR_CONE_10DEGREES, m_iSawShell, EF_MUZZLEFLASH, BULLET_PLAYER_556, TE_BOUNCE_SHELL); - - m_cAmmoLoaded--;// take away a bullet! -} + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_FLY }, +}; +Schedule_t slFGruntRepelAttack[] = +{ + { + tlFGruntRepelAttack, + ARRAYSIZE ( tlFGruntRepelAttack ), + bits_COND_ENEMY_OCCLUDED, + 0, + "Repel Attack" + }, +}; //========================================================= -// HandleAnimEvent - catches the monster-specific messages -// that occur when tagged animation frames are played. -// -// Returns number of events handled, 0 if none. +// repel land //========================================================= -void CFGrunt::HandleAnimEvent(MonsterEvent_t *pEvent) +Task_t tlFGruntRepelLand[] = { - Vector vecShootDir; - Vector vecShootOrigin; + { 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 }, +}; - switch (pEvent->event) - { - case FGRUNT_AE_DROP_GUN: +Schedule_t slFGruntRepelLand[] = +{ { - Vector vecGunPos; - Vector vecGunAngles; - - GetAttachment(0, vecGunPos, vecGunAngles); + tlFGruntRepelLand, + ARRAYSIZE ( tlFGruntRepelLand ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, - // switch to body group with no gun. - SetBodygroup(GUN_GROUP, GUN_NONE); + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel Land" + }, +}; - // now spawn a gun. - DropGun(vecGunPos, vecGunAngles); +//========================================================= +// Find medic. Grunt stops moving and calls the nearest medic, +// if none is around, we don't do much. I don't think I have much +// to put in here, other than to make the grunt stop moving, and +// run the medic calling task, I guess. +//========================================================= +Task_t tlGruntFindMedic[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_HGRUNT_ALLY_FIND_MEDIC, (float)0 }, + { TASK_WAIT, (float)2 }, +}; - // Drop grenades if supported. - if (FBitSet(pev->weapons, FGRUNT_GRENADELAUNCHER)) - { - DropItem("ammo_ARgrenades", BodyTarget(pev->origin), vecGunAngles); - } +Schedule_t slGruntFindMedic[] = +{ + { + tlGruntFindMedic, + ARRAYSIZE ( tlGruntFindMedic ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, - } - break; + "Find Medic" + }, +}; - case FGRUNT_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; +Task_t tlGruntFaceTarget[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE }, +}; - case FGRUNT_AE_GREN_TOSS: +Schedule_t slGruntFaceTarget[] = +{ { - 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); + tlGruntFaceTarget, + ARRAYSIZE( tlGruntFaceTarget ), + bits_COND_CLIENT_PUSH | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "FaceTarget" + }, +}; - 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; +Task_t tlIdleGruntStand[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, // repick IDLESTAND every two seconds. + { TASK_TLK_HEADRESET, (float)0 }, // reset head position +}; - case FGRUNT_AE_GREN_LAUNCH: +Schedule_t slIdleGruntStand[] = +{ { - 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; + tlIdleGruntStand, + ARRAYSIZE( tlIdleGruntStand ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_PROVOKED, + bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "IdleStand" + }, +}; + +Task_t tlGruntFollow[] = +{ + { TASK_MOVE_TO_TARGET_RANGE, (float)128 }, // Move within 128 of target ent (client) + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; - case FGRUNT_AE_GREN_DROP: +Schedule_t slGruntFollow[] = +{ { - 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; + tlGruntFollow, + ARRAYSIZE( tlGruntFollow ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "Follow" + }, +}; - case FGRUNT_AE_BURST1: +DEFINE_CUSTOM_SCHEDULES( CHFGrunt ) +{ + slFGruntFail, + slFGruntCombatFail, + slFGruntVictoryDance, + slFGruntEstablishLineOfFire, + slFGruntFoundEnemy, + slFGruntCombatFace, + slFGruntSignalSuppress, + slFGruntSuppress, + slFGruntWaitInCover, + slFGruntTakeCover, + slFGruntGrenadeCover, + slFGruntTossGrenadeCover, + slFGruntTakeCoverFromBestSound, + slFGruntHideReload, + slFGruntSweep, + slFGruntRangeAttack1A, + slFGruntRangeAttack1B, + slFGruntRangeAttack2, + slFGruntRepel, + slFGruntRepelAttack, + slFGruntRepelLand, + slGruntFindMedic, + slGruntFaceTarget, + slIdleGruntStand, + slGruntFollow, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CHFGrunt, CTalkMonster ) + +void CHFGrunt :: StartTask( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) { - if (FBitSet(pev->weapons, FGRUNT_9MMAR)) + case TASK_HGRUNT_ALLY_CHECK_FIRE: + if( !NoFriendlyFire() ) { - 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); - } + SetConditions( bits_COND_NOFIRE ); } - else if (FBitSet(pev->weapons, FGRUNT_SAW)) - { - Saw(); + TaskComplete(); + break; + case TASK_HGRUNT_ALLY_SPEAK_SENTENCE: + SpeakSentence(); + TaskComplete(); + break; - switch (RANDOM_LONG(0, 2)) - { - case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/saw_fire1.wav", 1, ATTN_NORM); break; - case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/saw_fire2.wav", 1, ATTN_NORM); break; - case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/saw_fire3.wav", 1, ATTN_NORM); break; - default: - break; - } - } - else - { - Shotgun(); + case TASK_WALK_PATH: + case TASK_RUN_PATH: + // grunt no longer assumes he is covered if he moves + Forget( bits_MEMORY_INCOVER ); + CTalkMonster ::StartTask( pTask ); + break; - EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM); - } + case TASK_RELOAD: + m_IdealActivity = ACT_RELOAD; + break; - CSoundEnt::InsertSound(bits_SOUND_COMBAT, pev->origin, 384, 0.3); - } - break; + case TASK_HGRUNT_ALLY_FACE_TOSS_DIR: + break; - case FGRUNT_AE_BURST2: - case FGRUNT_AE_BURST3: - if (FBitSet(pev->weapons, FGRUNT_9MMAR)) + case TASK_FACE_IDEAL: + case TASK_FACE_ENEMY: + CTalkMonster :: StartTask( pTask ); + if (pev->movetype == MOVETYPE_FLY) { - Shoot(); + m_IdealActivity = ACT_GLIDE; } - else if(FBitSet(pev->weapons, FGRUNT_SAW)) + break; + case TASK_HGRUNT_ALLY_FIND_MEDIC: { - Saw(); + // First try looking for a medic in my squad + if ( InSquad() ) + { + CSquadMonster *pSquadLeader = MySquadLeader( ); + if ( pSquadLeader ) + { + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + if (pMember != 0 && FClassnameIs(pMember->pev, "monster_human_medic_ally") && TryCallForMedic(pMember)) + { + TaskComplete(); + break; + } + } + } + } + // If not, search bsp. + if ( !TaskIsComplete() ) + { + // for each medic in this bsp... + CBaseEntity *pFriend = NULL; + TraceResult tr; + Vector vecCheck; + + while( ( pFriend = UTIL_FindEntityByClassname( pFriend, "monster_human_medic_ally" ) ) ) + { + if( pFriend == this || !pFriend->IsAlive() ) + // don't talk to self or dead people + continue; + vecCheck = pFriend->pev->origin; + vecCheck.z = pFriend->pev->absmax.z; + + UTIL_TraceLine( pev->origin, vecCheck, ignore_monsters, ENT( pev ), &tr ); + + if( tr.flFraction == 1.0 ) + { + if (TryCallForMedic(pFriend)) + { + TaskComplete(); + break; + } + } + } + } + if ( !TaskIsComplete() ) + { + TaskFail(); + } + m_flMedicWaitTime = CALL_MEDIC_DELAY + gpGlobals->time; } break; + default: + CTalkMonster :: StartTask( pTask ); + break; + } +} - case FGRUNT_AE_KICK: +void CHFGrunt :: RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) { - CBaseEntity *pHurt = Kick(); - if (pHurt) + case TASK_HGRUNT_ALLY_FACE_TOSS_DIR: { - // 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.hgruntAllyDmgKick, DMG_CLUB); - } - } - break; + // project a point along the toss vector and turn to face that point. + MakeIdealYaw( pev->origin + m_vecTossVelocity * 64 ); + ChangeYaw( pev->yaw_speed ); - case FGRUNT_AE_CAUGHT_ENEMY: - { - if (FOkToSpeak()) - { - SENTENCEG_PlayRndSz(ENT(pev), "FG_ALERT", FGRUNT_SENTENCE_VOLUME, FGRUNT_ATTN, 0, m_voicePitch); - JustSpoke(); + if ( FacingIdeal() ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; } - - } - default: - CTalkMonster::HandleAnimEvent(pEvent); - break; + { + CTalkMonster :: RunTask( pTask ); + break; + } } } - //========================================================= -// Spawn +// GibMonster - make gun fly through the air. //========================================================= -void CFGrunt::Spawn() +void CHFGrunt :: GibMonster ( void ) { - Precache(); - - SET_MODEL(ENT(pev), "models/hgrunt_opfor.mdl"); - UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + Vector vecGunPos; + Vector vecGunAngles; - pev->solid = SOLID_SLIDEBOX; - pev->movetype = MOVETYPE_STEP; - m_bloodColor = BLOOD_COLOR_RED; - pev->health = gSkillData.hgruntAllyHealth; - pev->view_ofs = Vector(0, 0, 50);// position of the eyes relative to monster's origin. - m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello - m_MonsterState = MONSTERSTATE_NONE; + if ( GetBodygroup( FG_GUN_GROUP ) != FG_GUN_NONE ) + {// throw a gun if the grunt has one + DropMyItems(TRUE); + } - pev->body = 0; // gun in holster + CTalkMonster::GibMonster(); +} - m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; +void CHFGrunt::DropMyItem(const char* entityName, const Vector& vecGunPos, const Vector& vecGunAngles, BOOL isGibbed) +{ + CBaseEntity* pGun = DropItem(entityName, vecGunPos, vecGunAngles); + if (pGun && isGibbed) { + pGun->pev->velocity = Vector( RANDOM_FLOAT( -100, 100 ), RANDOM_FLOAT( -100, 100 ), RANDOM_FLOAT( 200, 300 ) ); + pGun->pev->avelocity = Vector( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } +} +void CHFGrunt::DropMyItems(BOOL isGibbed) +{ + Vector vecGunPos; + Vector vecGunAngles; + GetAttachment( 0, vecGunPos, vecGunAngles ); - // Select a random head. - if (head == -1) + if (!isGibbed) { + SetBodygroup( FG_GUN_GROUP, FG_GUN_NONE ); + } + if (FBitSet( pev->weapons, FGRUNT_SHOTGUN )) { - SetBodygroup(HEAD_GROUP, RANDOM_LONG(0, FGRUNT_NUM_HEADS - 1 )); + DropMyItem( "weapon_shotgun", vecGunPos, vecGunAngles, isGibbed ); } - else + else if (FBitSet( pev->weapons, FGRUNT_9MMAR )) { - SetBodygroup(HEAD_GROUP, head); + DropMyItem( "weapon_9mmAR", vecGunPos, vecGunAngles, isGibbed ); } - - if (pev->weapons == 0) + else if (FBitSet( pev->weapons, FGRUNT_M249 )) { - // initialize to original values - pev->weapons = FGRUNT_9MMAR | FGRUNT_HANDGRENADE; + DropMyItem( "weapon_m249", vecGunPos, vecGunAngles, isGibbed ); } - // Setup bodygroups. - if (FBitSet(pev->weapons, FGRUNT_SHOTGUN)) + if (FBitSet( pev->weapons, FGRUNT_GRENADELAUNCHER )) { - torso = TORSO_SHELLS; - - SetBodygroup(TORSO_GROUP, torso); - SetBodygroup(GUN_GROUP, GUN_SHOTGUN); - - m_cClipSize = 8; + DropMyItem( "ammo_ARgrenades", isGibbed ? vecGunPos : BodyTarget( pev->origin ), vecGunAngles, isGibbed ); } - else if (FBitSet(pev->weapons, FGRUNT_SAW)) - { - torso = TORSO_SAW; - - SetBodygroup(TORSO_GROUP, torso); - SetBodygroup(GUN_GROUP, GUN_SAW); + pev->weapons = 0; +} - m_cClipSize = FGRUNT_CLIP_SIZE_SAW; - } - else +void CHFGrunt::SpeakSentence( void ) +{ + if( m_iSentence == FGRUNT_SENT_NONE ) { - torso = TORSO_BACKPACK; - - SetBodygroup(TORSO_GROUP, torso); - SetBodygroup(GUN_GROUP, GUN_MP5); - - m_cClipSize = FGRUNT_CLIP_SIZE_MP5; + // no sentence cued up. + return; } - - m_cAmmoLoaded = m_cClipSize; - - MonsterInit(); - SetUse(&CFGrunt::FollowerUse); + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), SentenceByNumber(m_iSentence), FGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + JustSpoke(); + } } //========================================================= -// Precache - precaches all resources this monster needs +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. //========================================================= -void CFGrunt::Precache() +int CHFGrunt :: ISoundMask ( void) { - PRECACHE_MODEL("models/hgrunt_opfor.mdl"); - - PRECACHE_SOUND("hgrunt/gr_mgun1.wav"); - PRECACHE_SOUND("hgrunt/gr_mgun2.wav"); - - PRECACHE_SOUND("hgrunt/saw_fire1.wav"); - PRECACHE_SOUND("hgrunt/saw_fire2.wav"); - PRECACHE_SOUND("hgrunt/saw_fire3.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 - - PRECACHE_SOUND_ARRAY( g_pszDeathSounds ); - - PRECACHE_SOUND_ARRAY( g_pszPainSounds ); - - m_iBrassShell = PRECACHE_MODEL("models/shell.mdl");// brass shell - m_iShotgunShell = PRECACHE_MODEL("models/shotgunshell.mdl"); - m_iSawShell = PRECACHE_MODEL("models/saw_shell.mdl"); - - - // every new barney must call this, otherwise - // when a level is loaded, nobody will talk (time is reset to 0) - TalkInit(); - CTalkMonster::Precache(); -} - -// Init talk data -void CFGrunt::TalkInit() -{ - - CTalkMonster::TalkInit(); - - // scientists speach group names (group names are in sentences.txt) - - m_szGrp[TLK_ANSWER] = "FG_ANSWER"; - m_szGrp[TLK_QUESTION] = "FG_QUESTION"; - m_szGrp[TLK_IDLE] = "FG_IDLE"; - m_szGrp[TLK_STARE] = "FG_STARE"; - m_szGrp[TLK_USE] = "FG_OK"; - m_szGrp[TLK_UNUSE] = "FG_WAIT"; - m_szGrp[TLK_STOP] = "FG_STOP"; - - m_szGrp[TLK_NOSHOOT] = "FG_SCARED"; - m_szGrp[TLK_HELLO] = "FG_HELLO"; - - m_szGrp[TLK_PLHURT1] = "!FG_CUREA"; - m_szGrp[TLK_PLHURT2] = "!FG_CUREB"; - m_szGrp[TLK_PLHURT3] = "!FG_CUREC"; - - m_szGrp[TLK_PHELLO] = NULL; - m_szGrp[TLK_PIDLE] = NULL; - m_szGrp[TLK_PQUESTION] = "FG_PQUEST"; - - m_szGrp[TLK_SMELL] = "FG_SMELL"; - - m_szGrp[TLK_WOUND] = "FG_WOUND"; - m_szGrp[TLK_MORTAL] = "FG_MORTAL"; - - // get voice for head - just one barney voice for now - m_voicePitch = 100; + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_CARCASS | + bits_SOUND_MEAT | + bits_SOUND_GARBAGE | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; } - - -static BOOL IsFacing(entvars_t *pevTest, const Vector &reference) +//========================================================= +// CheckAmmo - overridden for the grunt because he actually +// uses ammo! (base class doesn't) +//========================================================= +void CHFGrunt :: CheckAmmo ( void ) { - Vector vecDir = (reference - pevTest->origin); - vecDir.z = 0; - vecDir = vecDir.Normalize(); - Vector forward, angle; - angle = pevTest->v_angle; - angle.x = 0; - UTIL_MakeVectorsPrivate(angle, forward, NULL, NULL); - // He's facing me, he meant it - if (DotProduct(forward, vecDir) > 0.96) // +/- 15 degrees or so + if ( m_cAmmoLoaded <= 0 ) { - return TRUE; + SetConditions(bits_COND_NO_AMMO_LOADED); } - return FALSE; } - - -int CFGrunt::TakeDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHFGrunt :: Classify ( void ) { - // make sure friends talk about it if player hurts talkmonsters... - int ret = CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); - if (!IsAlive() || pev->deadflag == DEAD_DYING) - return ret; + return CLASS_PLAYER_ALLY; +} +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHFGrunt :: SetYawSpeed ( void ) +{ + int ys; - if (m_MonsterState != MONSTERSTATE_PRONE && (pevAttacker->flags & FL_CLIENT)) + switch ( m_Activity ) { - m_flPlayerDamage += flDamage; - - // This is a heurstic to determine if the player intended to harm me - // If I have an enemy, we can't establish intent (may just be crossfire) - if (m_hEnemy == 0) - { - // If the player was facing directly at me, or I'm already suspicious, get mad - if ((m_afMemory & bits_MEMORY_SUSPICIOUS) || IsFacing(pevAttacker, pev->origin)) - { - // Alright, now I'm pissed! - PlaySentence("FG_MAD", 4, VOL_NORM, ATTN_NORM); - - Remember(bits_MEMORY_PROVOKED); - StopFollowing(TRUE); - } - else - { - // Hey, be careful with that - PlaySentence("FG_SHOT", 4, VOL_NORM, ATTN_NORM); - Remember(bits_MEMORY_SUSPICIOUS); - } - } - else if (!(m_hEnemy->IsPlayer()) && pev->deadflag == DEAD_NO) - { - PlaySentence("FG_SHOT", 4, VOL_NORM, ATTN_NORM); - } + case ACT_IDLE: + ys = 150; + break; + case ACT_RUN: + ys = 150; + break; + case ACT_WALK: + ys = 180; + break; + case ACT_RANGE_ATTACK1: + ys = 120; + break; + case ACT_RANGE_ATTACK2: + ys = 120; + break; + case ACT_MELEE_ATTACK1: + ys = 120; + break; + case ACT_MELEE_ATTACK2: + ys = 120; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 180; + break; + case ACT_GLIDE: + case ACT_FLY: + ys = 30; + break; + default: + ys = 90; + break; } - return ret; -} - - -//========================================================= -// PainSound -//========================================================= -void CFGrunt::PainSound(void) -{ - if (gpGlobals->time < m_painTime) - return; - - m_painTime = gpGlobals->time + RANDOM_FLOAT(0.5, 0.75); - - EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, g_pszPainSounds[ RANDOM_LONG(0, FGRUNT_NUM_PAIN_SOUNDS - 1) ], 1, ATTN_NORM, 0, GetVoicePitch()); -} - -//========================================================= -// DeathSound -//========================================================= -void CFGrunt::DeathSound(void) -{ - EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, g_pszDeathSounds[RANDOM_LONG(0, FGRUNT_NUM_DEATH_SOUNDS - 1)], 1, ATTN_NORM, 0, GetVoicePitch()); + pev->yaw_speed = ys; } //========================================================= -// TractAttack +// PrescheduleThink - this function runs after conditions +// are collected and before scheduling code is run. //========================================================= -void CFGrunt::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +void CHFGrunt :: PrescheduleThink ( void ) { - switch (ptr->iHitgroup) + if ( InSquad() && m_hEnemy != 0 ) { - case HITGROUP_CHEST: - case HITGROUP_STOMACH: - if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST)) + if ( HasConditions ( bits_COND_SEE_ENEMY ) ) { - flDamage = flDamage / 2; + // update the squad's last enemy sighting time. + MySquadLeader()->m_flLastEnemySightTime = gpGlobals->time; } - break; - case 10: - if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB)) + else { - flDamage -= 20; - if (flDamage <= 0) + if ( gpGlobals->time - MySquadLeader()->m_flLastEnemySightTime > 5 ) { - UTIL_Ricochet(ptr->vecEndPos, 1.0); - flDamage = 0.01; + // been a while since we've seen the enemy + MySquadLeader()->m_fEnemyEluded = TRUE; } } - // always a head shot - ptr->iHitgroup = HITGROUP_HEAD; - break; } - - CTalkMonster::TraceAttack(pevAttacker, flDamage, vecDir, ptr, bitsDamageType); + CTalkMonster::PrescheduleThink(); } - -void CFGrunt::Killed(entvars_t *pevAttacker, int iGib) +bool CHFGrunt::WantsToCallMedic() { - if (GetBodygroup(2) != 2) - { - Vector vecGunPos; - Vector vecGunAngles; - - // throw a gun if the grunt has one - GetAttachment(0, vecGunPos, vecGunAngles); - - DropGun(vecGunPos, vecGunAngles); - } - - SetUse(NULL); - CTalkMonster::Killed(pevAttacker, iGib); + return pev->health <= pev->max_health * 0.5 && ( m_flMedicWaitTime < gpGlobals->time ); } - -//========================================================= -// DropGun -//========================================================= -CBaseEntity* CFGrunt::DropGun(const Vector& vecGunPos, const Vector& vecGunAngles, char* szClassname) +bool CHFGrunt::TryCallForMedic(CBaseEntity* pOther) { - CBaseEntity* pGun = NULL; - - if (szClassname && *szClassname) + if ( pOther && pOther != this && pOther->pev->deadflag == DEAD_NO ) { - pGun = DropItem(szClassname, vecGunPos, vecGunAngles); + CMedic* medic = (CMedic*)pOther->MySquadMonsterPointer(); - if (pGun) - { - return pGun; - } - else + if ( medic != 0 && medic->ReadyToHeal() ) { - ALERT(at_console, "ERROR: Could not find classname %s. No such class.\n", szClassname); + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "fgrunt/medic.wav", 1, ATTN_NORM, 0, GetVoicePitch()); + ALERT( at_aiconsole, "Injured %s called for %s\n", STRING(pev->classname), STRING(medic->pev->classname) ); + medic->StartFollowingHealTarget(this); + return true; } } + return false; +} - if (FBitSet(pev->weapons, FGRUNT_SHOTGUN)) - { - pGun = DropItem("weapon_shotgun", vecGunPos, vecGunAngles); - } - else if (FBitSet(pev->weapons, FGRUNT_SAW)) +//========================================================= +// FCanCheckAttacks - this is overridden for human grunts +// because they can throw/shoot grenades when they can't see their +// target and the base class doesn't check attacks if the monster +// cannot see its enemy. +// +// !!!BUGBUG - this gets called before a 3-round burst is fired +// which means that a friendly can still be hit with up to 2 rounds. +// ALSO, grenades will not be tossed if there is a friendly in front, +// this is a bad bug. Friendly machine gun fire avoidance +// will unecessarily prevent the throwing of a grenade as well. +//========================================================= +BOOL CHFGrunt :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) { - pGun = DropItem("weapon_m249", vecGunPos, vecGunAngles); + return TRUE; } else { - pGun = DropItem("weapon_9mmAR", vecGunPos, vecGunAngles); + return FALSE; } - - return pGun; } + //========================================================= -// PrescheduleThink - this function runs after conditions -// are collected and before scheduling code is run. +// CheckMeleeAttack1 //========================================================= -void CFGrunt::PrescheduleThink(void) +BOOL CHFGrunt :: CheckMeleeAttack1 ( float flDot, float flDist ) { - CSquadMonster* pSquadMonster = MySquadMonsterPointer(); + CBaseMonster *pEnemy; - if (pSquadMonster && pSquadMonster->InSquad() && m_hEnemy != 0) + if ( m_hEnemy != 0 ) { - if (HasConditions(bits_COND_SEE_ENEMY)) - { - // update the squad's last enemy sighting time. - + pEnemy = m_hEnemy->MyMonsterPointer(); - pSquadMonster->MySquadLeader()->m_flLastEnemySightTime = gpGlobals->time; - } - else + if ( !pEnemy ) { - if (gpGlobals->time - pSquadMonster->MySquadLeader()->m_flLastEnemySightTime > 5) - { - // been a while since we've seen the enemy - pSquadMonster->MySquadLeader()->m_fEnemyEluded = TRUE; - } + return FALSE; } } + + if ( flDist <= 64 && flDot >= 0.7 && + pEnemy->Classify() != CLASS_ALIEN_BIOWEAPON && + pEnemy->Classify() != CLASS_PLAYER_BIOWEAPON ) + { + return TRUE; + } + return FALSE; } + //========================================================= -// AI Schedules Specific to this monster +// CheckRangeAttack1 - overridden for HGrunt, cause +// FCanCheckAttacks() doesn't disqualify all attacks based +// on whether or not the enemy is occluded because unlike +// the base class, the HGrunt can attack when the enemy is +// occluded (throw grenade over wall, etc). We must +// disqualify the machine gun attack if the enemy is occluded. //========================================================= - -Schedule_t* CFGrunt::GetScheduleOfType(int Type) +BOOL CHFGrunt :: CheckRangeAttack1 ( float flDot, float flDist ) { -#if 1 - Schedule_t *psched; - - switch( Type ) + if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 2048 && flDot >= 0.5 && NoFriendlyFire() && ( GetBodygroup( 3 ) != 3 ) ) { - case SCHED_TAKE_COVER_FROM_ENEMY: - { - CSquadMonster* pSquadMonster = MySquadMonsterPointer(); - if (pSquadMonster && pSquadMonster->InSquad()) + TraceResult tr; + + if ( !m_hEnemy->IsPlayer() && flDist <= 64 ) { - if (g_iSkillLevel == SKILL_HARD && HasConditions(bits_COND_CAN_RANGE_ATTACK2) && pSquadMonster->OccupySlot(bits_SLOTS_HGRUNT_GRENADE)) - { - if (FOkToSpeak()) - { - SENTENCEG_PlayRndSz(ENT(pev), "FG_THROW", FGRUNT_SENTENCE_VOLUME, FGRUNT_ATTN, 0, m_voicePitch); - JustSpoke(); - } - return slGruntTossGrenadeCover; - } - else - { - return &slGruntTakeCover[0]; - } + // kick nonclients, but don't shoot at them. + return FALSE; } - else + + Vector vecSrc = GetGunPosition(); + + // verify that a bullet fired from the gun will hit the enemy before the world. + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + + if ( tr.flFraction == 1.0 ) { - if (RANDOM_LONG(0, 1)) - { - return &slGruntTakeCover[0]; - } - else - { - return &slGruntGrenadeCover[0]; - } + return TRUE; } } - case SCHED_TAKE_COVER_FROM_BEST_SOUND: - { - return &slGruntTakeCoverFromBestSound[0]; - } - case SCHED_FGRUNT_TAKECOVER_FAILED: - { - CSquadMonster* pSquadMonster = MySquadMonsterPointer(); - if (pSquadMonster && pSquadMonster->InSquad()) - { - if (HasConditions(bits_COND_CAN_RANGE_ATTACK1) && pSquadMonster->OccupySlot(bits_SLOTS_HGRUNT_ENGAGE)) - { - return GetScheduleOfType(SCHED_RANGE_ATTACK1); - } - } - return GetScheduleOfType(SCHED_FAIL); - } - break; - case SCHED_FGRUNT_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_FGRUNT_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); + return FALSE; +} - 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_FGRUNT_WAIT_FACE_ENEMY: +//========================================================= +// CheckRangeAttack2 - this checks the Grunt's grenade +// attack. +//========================================================= +BOOL CHFGrunt :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if (! FBitSet(pev->weapons, (FGRUNT_HANDGRENADE | FGRUNT_GRENADELAUNCHER)) ) { - return &slGruntWaitInCover[0]; + return FALSE; } - case SCHED_FGRUNT_SWEEP: + + // if the grunt isn't moving, it's ok to check. + if ( m_flGroundSpeed != 0 ) { - return &slGruntSweep[0]; + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; } - case SCHED_FGRUNT_COVER_AND_RELOAD: + + // assume things haven't changed too much since last time + if (gpGlobals->time < m_flNextGrenadeCheck ) { - return &slGruntHideReload[0]; + return m_fThrowGrenade; } - case SCHED_FGRUNT_FOUND_ENEMY: + + if ( !FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) && m_hEnemy->pev->waterlevel == 0 && m_vecEnemyLKP.z > pev->absmax.z ) { - return &slGruntFoundEnemy[0]; + //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to + // be grenaded. + // don't throw grenades at anything that isn't on the ground! + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; } - case SCHED_VICTORY_DANCE: - { - CSquadMonster* pSquadMonster = MySquadMonsterPointer(); - if (pSquadMonster && pSquadMonster->InSquad()) - { - if (!pSquadMonster->IsLeader()) - { - return &slGruntFail[0]; - } - } - return &slGruntVictoryDance[0]; - } - case SCHED_FGRUNT_SUPPRESS: + Vector vecTarget; + + if (FBitSet( pev->weapons, FGRUNT_HANDGRENADE)) { - if (m_hEnemy->IsPlayer() && m_fFirstEncounter) + // find feet + if (RANDOM_LONG(0,1)) { - m_fFirstEncounter = FALSE;// after first encounter, leader won't issue handsigns anymore when he has a new enemy - return &slGruntSignalSuppress[0]; + // magically know where they are + vecTarget = Vector( m_hEnemy->pev->origin.x, m_hEnemy->pev->origin.y, m_hEnemy->pev->absmin.z ); } else { - return &slGruntSuppress[0]; + // toss it to where you last saw them + vecTarget = m_vecEnemyLKP; } + // vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); + // estimate position + // vecTarget = vecTarget + m_hEnemy->pev->velocity * 2; } - case SCHED_FAIL: + else { - 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]; + // find target + // vecTarget = m_hEnemy->BodyTarget( pev->origin ); + vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); + // estimate position + if (HasConditions( bits_COND_SEE_ENEMY)) + vecTarget = vecTarget + ((vecTarget - pev->origin).Length() / gSkillData.fgruntGrenadeSpeed) * m_hEnemy->pev->velocity; } - case SCHED_FGRUNT_REPEL: + + // are any of my allies near the intended grenade impact area? + if (SquadMemberInRange( vecTarget, 256 )) { - if (pev->velocity.z > -128) - pev->velocity.z -= 32; - return &slGruntRepel[0]; + // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; //AJH need this or it is overridden later. } - case SCHED_FGRUNT_REPEL_ATTACK: + + if ( ( vecTarget - pev->origin ).Length2D() <= 256 ) { - if (pev->velocity.z > -128) - pev->velocity.z -= 32; - return &slGruntRepelAttack[0]; + // crap, I don't want to blow myself up + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; } - case SCHED_FGRUNT_REPEL_LAND: + + + if (FBitSet( pev->weapons, FGRUNT_HANDGRENADE)) { - return &slGruntRepelLand[0]; - } + Vector vecToss = VecCheckToss( pev, GetGunPosition(), vecTarget, 0.5 ); - case SCHED_ARM_WEAPON: - if ( m_hEnemy != 0 ) + if ( vecToss != g_vecZero ) { - // face enemy, then draw. - return slBarneyEnemyDraw; - } - break; - - // Hook these to make a looping schedule - case SCHED_TARGET_FACE: - // call base class default so that barney will talk - // when 'used' - psched = CTalkMonster::GetScheduleOfType(Type); + m_vecTossVelocity = vecToss; - if (psched == slIdleStand) - return slBaFaceTarget; // override this gfor different target face behavior + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time; // 1/3 second. + } else - return psched; - - case SCHED_TARGET_CHASE: - return slBaFollow; - - case SCHED_IDLE_STAND: - // call base class default so that scientist will talk - // when standing during idle - psched = CTalkMonster::GetScheduleOfType(Type); - - if (psched == slIdleStand) { - // just look straight ahead. - return slIdleBaStand; + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. } - else - return psched; } + else + { + Vector vecToss = VecCheckThrow( pev, GetGunPosition(), vecTarget, gSkillData.fgruntGrenadeSpeed, 0.5 ); -#else - Schedule_t *psched; + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; - switch (Type) - { - case SCHED_ARM_WEAPON: - if (m_hEnemy != NULL) + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 0.3; // 1/3 second. + } + else { - // face enemy, then draw. - return slFgEnemyDraw; + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. } - break; - - // Hook these to make a looping schedule - case SCHED_TARGET_FACE: - // call base class default so that barney will talk - // when 'used' - psched = CTalkMonster::GetScheduleOfType(Type); + } - if (psched == slIdleStand) - return slFgFaceTarget; // override this for different target face behavior - else - return psched; - case SCHED_TARGET_CHASE: - return slFgFollow; - case SCHED_IDLE_STAND: - // call base class default so that scientist will talk - // when standing during idle - psched = CTalkMonster::GetScheduleOfType(Type); + return m_fThrowGrenade; +} +//========================================================= +//========================================================= +CBaseEntity *CHFGrunt :: Kick( void ) +{ + TraceResult tr; - if (psched == slIdleStand) - { - // just look straight ahead. - return slIdleFgStand; - } - else - return psched; + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin; + vecStart.z += pev->size.z * 0.5; + Vector vecEnd = vecStart + (gpGlobals->v_forward * 70); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + if (pEntity && IRelationship(pEntity) != R_AL) + return pEntity; } -#endif - return CTalkMonster::GetScheduleOfType(Type); + return NULL; } - -//========================================================= -// GetSchedule - Decides which type of schedule best suits -// the monster's current state and conditions. Then calls -// monster's member function to get a pointer to a schedule -// of the proper type. -// -// Only supported when this monster has squad support! -// -//========================================================= -Schedule_t *CFGrunt::GetSquadSchedule(void) +void CHFGrunt::KickImpl(float kickDamage) { - CSquadMonster* pSquadMonster = MySquadMonsterPointer(); + CBaseEntity *pHurt = Kick(); - // This method is reserved for NPCs with squad support!. - ASSERT(pSquadMonster != NULL); + 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, kickDamage, DMG_CLUB ); + } +} - CSquadMonster* pSquadLeader = pSquadMonster->MySquadLeader(); +//========================================================= +// GetGunPosition return the end of the barrel +//========================================================= - switch (m_MonsterState) +Vector CHFGrunt :: GetGunPosition( ) +{ + if (m_fStanding ) { - case MONSTERSTATE_COMBAT: + return pev->origin + Vector( 0, 0, 60 ); + } + else { - // new enemy - if (HasConditions(bits_COND_NEW_ENEMY)) - { - pSquadLeader->m_fEnemyEluded = FALSE; + return pev->origin + Vector( 0, 0, 48 ); + } +} - if (!pSquadMonster->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", FGRUNT_SENTENCE_VOLUME, FGRUNT_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", FGRUNT_SENTENCE_VOLUME, FGRUNT_ATTN, 0, m_voicePitch); +//========================================================= +// Shoot +//========================================================= +void CHFGrunt :: Shoot ( void ) +{ + if (m_hEnemy == 0 ) + { + return; + } - JustSpoke(); - } + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); - if (HasConditions(bits_COND_CAN_RANGE_ATTACK1)) - { - return GetScheduleOfType(SCHED_FGRUNT_SUPPRESS); - } - else - { - return GetScheduleOfType(SCHED_FGRUNT_ESTABLISH_LINE_OF_FIRE); - } - } - } + UTIL_MakeVectors ( pev->angles ); - // can grenade launch - else if (FBitSet(pev->weapons, FGRUNT_GRENADELAUNCHER) && HasConditions(bits_COND_CAN_RANGE_ATTACK2) && pSquadMonster->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 the enemy has eluded the squad and a squad member has just located the enemy - // and the enemy does not see the squad member, issue a call to the squad to waste a - // little time and give the player a chance to turn. - if (pSquadLeader->m_fEnemyEluded && !HasConditions(bits_COND_ENEMY_FACING_ME)) - { - pSquadLeader->m_fEnemyEluded = FALSE; - return GetScheduleOfType(SCHED_FGRUNT_FOUND_ENEMY); - } + 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_iBrassShell, TE_BOUNCE_SHELL); + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_4DEGREES, 2048, BULLET_MONSTER_MP5 ); // shoot +-5 degrees - if (pSquadMonster->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) && pSquadMonster->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) && pSquadMonster->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", FGRUNT_SENTENCE_VOLUME, FGRUNT_ATTN, 0, m_voicePitch); - JustSpoke(); - } - return GetScheduleOfType(SCHED_RANGE_ATTACK2); - } - else if (pSquadMonster->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), "HG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); - m_iSentence = FGRUNT_SENT_CHARGE; - //JustSpoke(); - } + pev->effects |= EF_MUZZLEFLASH; - return GetScheduleOfType(SCHED_FGRUNT_ESTABLISH_LINE_OF_FIRE); - } - } + //WeaponFlash ( vecShootOrigin ); - } - break; - default: - break; - } + m_cAmmoLoaded--;// take away a bullet! - return NULL; + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); } //========================================================= -// GetSchedule - Decides which type of schedule best suits -// the monster's current state and conditions. Then calls -// monster's member function to get a pointer to a schedule -// of the proper type. +// Shoot //========================================================= -Schedule_t *CFGrunt::GetSchedule(void) +void CHFGrunt :: Shotgun ( void ) { - // clear old sentence - m_iSentence = FGRUNT_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 (m_hEnemy == 0) { - if (pev->flags & FL_ONGROUND) - { - // just landed - pev->movetype = MOVETYPE_STEP; - return GetScheduleOfType(SCHED_FGRUNT_REPEL_LAND); - } - else - { - // repel down a rope, - if (m_MonsterState == MONSTERSTATE_COMBAT) - return GetScheduleOfType(SCHED_FGRUNT_REPEL_ATTACK); - else - return GetScheduleOfType(SCHED_FGRUNT_REPEL); - } + return; } - // grunts place HIGH priority on running away from danger sounds. - if (HasConditions(bits_COND_HEAR_SOUND)) - { - CSound *pSound; - pSound = PBestSound(); + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); - ASSERT( pSound != NULL ); - if ( pSound) - { - if (pSound->m_iType & bits_SOUND_DANGER) - { - // dangerous sound nearby! + UTIL_MakeVectors ( pev->angles ); - //!!!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. + 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.fgruntShotgunPellets, vecShootOrigin, vecShootDir, VECTOR_CONE_9DEGREES, 2048, BULLET_PLAYER_BUCKSHOT, 0 ); // shoot +-7.5 degrees - if (FOkToSpeak()) - { - SENTENCEG_PlayRndSz( ENT(pev), "FG_GREN", FGRUNT_SENTENCE_VOLUME, FGRUNT_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 ); - } - */ - } + pev->effects |= EF_MUZZLEFLASH; + + //WeaponFlash ( vecShootOrigin ); + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} +//========================================================= +// Shoot +//========================================================= +void CHFGrunt :: M249 ( void ) +{ + if (m_hEnemy == 0 ) + { + return; } - if (HasConditions(bits_COND_ENEMY_DEAD) && FOkToSpeak()) + + switch ( RANDOM_LONG(0,2) ) { - PlaySentence("FG_KILL", 4, VOL_NORM, ATTN_NORM); + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/saw_fire1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/saw_fire2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/saw_fire3.wav", 1, ATTN_NORM ); break; } -#if 1 - // Check if we have squad support. - CSquadMonster* pSquadMonster = MySquadMonsterPointer(); + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); - if (pSquadMonster) - { - // squad schedule. - Schedule_t* squadSched = GetSquadSchedule(); + UTIL_MakeVectors ( pev->angles ); - if (squadSched) - { - return squadSched; - } - } -#endif + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + m_flLinkToggle = !m_flLinkToggle; - switch (m_MonsterState) - { - case MONSTERSTATE_COMBAT: + if (!m_flLinkToggle) + EjectBrass ( vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iM249Shell, TE_BOUNCE_SHELL); + else + EjectBrass ( vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iM249Link, TE_BOUNCE_SHELL); + + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_6DEGREES, 2048, BULLET_MONSTER_556 ); // shoot +-5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + //WeaponFlash ( vecShootOrigin ); + + 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 CHFGrunt :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) { - // dead enemy - if (HasConditions(bits_COND_ENEMY_DEAD)) + case HGRUNT_ALLY_AE_DROP_GUN: { - // call base class, all code to handle dead enemies is centralized there. - return CBaseMonster::GetSchedule(); + DropMyItems(FALSE); + SetUse( NULL ); } + break; + + case HGRUNT_ALLY_AE_RELOAD: + if (FBitSet( pev->weapons, FGRUNT_9MMAR | FGRUNT_SHOTGUN )) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_reload1.wav", 1, ATTN_NORM ); + } + else if (FBitSet( pev->weapons, FGRUNT_M249 )) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/saw_reload2.wav", 1, ATTN_NORM ); + } + m_cAmmoLoaded = m_cClipSize; + ClearConditions(bits_COND_NO_AMMO_LOADED); + break; - // new enemy - if (HasConditions(bits_COND_NEW_ENEMY)) + case HGRUNT_ALLY_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; - // no ammo - else if (HasConditions(bits_COND_NO_AMMO_LOADED)) + case HGRUNT_ALLY_AE_GREN_LAUNCH: { - //!!!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_FGRUNT_COVER_AND_RELOAD); + 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_EASY) + 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; - // damaged just a little - else if (HasConditions(bits_COND_LIGHT_DAMAGE)) + case HGRUNT_ALLY_AE_GREN_DROP: { - // if hurt: - // 90% chance of taking cover - // 10% chance of flinch. - int iPercent = RANDOM_LONG(0, 99); + 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; - if (iPercent <= 90 && m_hEnemy != 0) + case HGRUNT_ALLY_AE_BURST1: + { + if ( FBitSet( pev->weapons, FGRUNT_9MMAR )) { - // 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)) + 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 { - //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); - m_iSentence = FGRUNT_SENT_COVER; - //JustSpoke(); + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "hgrunt/gr_mgun2.wav", 1, ATTN_NORM ); } - return GetScheduleOfType(SCHED_TAKE_COVER_FROM_ENEMY); + } + else if ( FBitSet( pev->weapons, FGRUNT_SHOTGUN )) + { + Shotgun( ); + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM ); } else { - return GetScheduleOfType(SCHED_SMALL_FLINCH); + M249( ); } + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); } - // can kick - else if (HasConditions(bits_COND_CAN_MELEE_ATTACK1)) - { - return GetScheduleOfType(SCHED_MELEE_ATTACK1); - } - // can shoot - else if (HasConditions(bits_COND_CAN_RANGE_ATTACK1)) + break; + + case HGRUNT_ALLY_AE_BURST2: + case HGRUNT_ALLY_AE_BURST3: + if ( FBitSet( pev->weapons, FGRUNT_9MMAR )) + Shoot(); + else if ( FBitSet( pev->weapons, FGRUNT_M249 )) + M249(); + break; + + case HGRUNT_ALLY_AE_KICK: { - if (FBitSet(pev->weapons, FGRUNT_GRENADELAUNCHER) && HasConditions(bits_COND_CAN_RANGE_ATTACK2)) - { - // shoot a grenade if you can - return GetScheduleOfType(SCHED_RANGE_ATTACK2); - } - // can shoot - else if (HasConditions(bits_COND_CAN_RANGE_ATTACK1)) - { - return GetScheduleOfType(SCHED_RANGE_ATTACK1); - } + KickImpl(gSkillData.fgruntDmgKick); } - // can't see enemy - else if (HasConditions(bits_COND_ENEMY_OCCLUDED)) + break; + + case HGRUNT_ALLY_AE_CAUGHT_ENEMY: { - //!!!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)) + if ( FOkToSpeak() ) { - SENTENCEG_PlayRndSz(ENT(pev), "FG_TAUNT", FGRUNT_SENTENCE_VOLUME, FGRUNT_ATTN, 0, m_voicePitch); + SENTENCEG_PlayRndSz(ENT(pev), SentenceByNumber(FGRUNT_SENT_ALERT), FGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); JustSpoke(); } - return GetScheduleOfType(SCHED_STANDOFF); + } - if (HasConditions(bits_COND_SEE_ENEMY) && !HasConditions(bits_COND_CAN_RANGE_ATTACK1)) - { - return GetScheduleOfType(SCHED_FGRUNT_ESTABLISH_LINE_OF_FIRE); + default: + CTalkMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHFGrunt :: Spawn() +{ + Precache( ); + + SpawnHelper("models/hgrunt_opfor.mdl", gSkillData.fgruntHealth); + + if ( m_iHead <= -2 ) + { + // skip major and MP heads + m_iHead = RANDOM_LONG(0, FG_HEAD_SAW_BLACK+1); + if (m_iHead == FG_HEAD_SAW_BLACK+1) { + m_iHead = FG_HEAD_BERET_BLACK; } } - case MONSTERSTATE_ALERT: - case MONSTERSTATE_IDLE: + else if ( m_iHead == -1 ) { - if (HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + if (FBitSet(pev->spawnflags, SF_SQUADMONSTER_LEADER)) { - // flinch if hurt - return GetScheduleOfType(SCHED_SMALL_FLINCH); + m_iHead = RANDOM_LONG(0,1) ? FG_HEAD_BERET : FG_HEAD_BERET_BLACK; } - - if (m_hEnemy == 0 && IsFollowing()) + else if (FBitSet(pev->weapons, FGRUNT_SHOTGUN)) { - if (!m_hTargetEnt->IsAlive()) - { - // UNDONE: Comment about the recently dead player here? - StopFollowing(FALSE); - break; - } - else - { - if (HasConditions(bits_COND_CLIENT_PUSH)) - { - return GetScheduleOfType(SCHED_MOVE_AWAY_FOLLOW); - } - return GetScheduleOfType(SCHED_TARGET_FACE); - } + m_iHead = FG_HEAD_SHOTGUN; } - - if (HasConditions(bits_COND_CLIENT_PUSH)) + else if (FBitSet(pev->weapons, FGRUNT_9MMAR)) + { + m_iHead = FG_HEAD_MASK; + } + else if (FBitSet(pev->weapons, FGRUNT_M249)) { - return GetScheduleOfType(SCHED_MOVE_AWAY); + m_iHead = RANDOM_LONG(FG_HEAD_SAW, FG_HEAD_SAW_BLACK); } + else + m_iHead = FG_HEAD_MASK; + } + else if ( m_iHead >= FG_HEAD_COUNT ) + m_iHead = FG_HEAD_MASK; - // try to say something about smells - TrySmellTalk(); - break; + if ( pev->weapons <= 0 ) + { + pev->weapons = FGRUNT_9MMAR; + } + if (FBitSet( pev->weapons, FGRUNT_SHOTGUN )) + { + SetBodygroup( FG_GUN_GROUP, FG_GUN_SHOTGUN ); + SetBodygroup( FG_TORSO_GROUP, FG_TORSO_SHOTGUN ); + m_cClipSize = 8; + } + if (FBitSet( pev->weapons, FGRUNT_9MMAR )) + { + SetBodygroup( FG_GUN_GROUP, FG_GUN_MP5 ); + m_cClipSize = FGRUNT_CLIP_SIZE; } + if (FBitSet( pev->weapons, FGRUNT_M249 )) + { + SetBodygroup( FG_GUN_GROUP, FG_GUN_SAW ); + SetBodygroup( FG_TORSO_GROUP, FG_TORSO_M249 ); + m_cClipSize = FGRUNT_CLIP_SIZE; } - return CTalkMonster::GetSchedule(); -} + SetBodygroup( FG_HEAD_GROUP, m_iHead ); -MONSTERSTATE CFGrunt::GetIdealState(void) -{ - return CTalkMonster::GetIdealState(); -} + m_cAmmoLoaded = m_cClipSize; -void CFGrunt::DeclineFollowing(void) -{ - PlaySentence("FG_POK", 2, VOL_NORM, ATTN_NORM); + MonsterInit(); + SetUse( &CTalkMonster::FollowerUse ); } -void CFGrunt::StartTask(Task_t *pTask) +void CHFGrunt::SpawnHelper(const char *model, float health) { -#if 1 - m_iTaskStatus = TASKSTATUS_RUNNING; + SET_MODEL( ENT( pev ), model ); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); - switch (pTask->iTask) - { - case TASK_FGRUNT_CHECK_FIRE: - { - CSquadMonster* pSquadMonster = MySquadMonsterPointer(); + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = health; + pev->view_ofs = Vector ( 0, 0, 50 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; - if (pSquadMonster && !pSquadMonster->NoFriendlyFire()) - { - SetConditions(bits_COND_SPECIAL1); // bits_COND_GRUNT_NOFIRE - } - TaskComplete(); + m_afCapability = bits_CAP_HEAR | 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 ); + m_flLastHitByPlayer = gpGlobals->time; + m_iPlayerHits = 0; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHFGrunt :: Precache() +{ + PRECACHE_MODEL("models/hgrunt_opfor.mdl"); + + PRECACHE_SOUND( "hgrunt/gr_mgun1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_mgun2.wav" ); + + PRECACHE_SOUND("weapons/saw_fire1.wav" ); + PRECACHE_SOUND("weapons/saw_fire2.wav" ); + PRECACHE_SOUND("weapons/saw_fire3.wav" ); + + PrecacheHelper(); + + PRECACHE_SOUND("hgrunt/gr_reload1.wav"); + + PRECACHE_SOUND("weapons/saw_reload2.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 + + m_iShotgunShell = PRECACHE_MODEL ("models/shotgunshell.mdl");// shotgun shell + m_iM249Shell = PRECACHE_MODEL ("models/saw_shell.mdl");// saw shell + m_iM249Link = PRECACHE_MODEL ("models/saw_link.mdl");// saw link + + TalkInit(); + switch ( m_iHead ) { + case FG_HEAD_SHOTGUN: + case FG_HEAD_SAW_BLACK: + case FG_HEAD_BERET_BLACK: + m_voicePitch = 90; + break; + default: + m_voicePitch = 100; + break; + } + + CTalkMonster::Precache(); +} + +void CHFGrunt::PrecacheHelper() +{ + 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("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/medic.wav"); + + m_iBrassShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell +} + +// Init talk data +void CHFGrunt :: TalkInit() +{ + CTalkMonster::TalkInit(); + + m_szGrp[TLK_ANSWER] = "FG_ANSWER"; + m_szGrp[TLK_QUESTION] = "FG_QUESTION"; + m_szGrp[TLK_IDLE] = "FG_IDLE"; + m_szGrp[TLK_STARE] = "FG_STARE"; + m_szGrp[TLK_USE] = "FG_OK"; + m_szGrp[TLK_UNUSE] = "FG_WAIT"; + //m_szGrp[TLK_DECLINE] = "FG_STOP"; + m_szGrp[TLK_STOP] = "FG_STOP"; + + /* FG_SCARED in opfor has sentences more suitable for FG_HEAR. + * Disabling for now. + */ + //m_szGrp[TLK_NOSHOOT] = "FG_SCARED"; + m_szGrp[TLK_HELLO] = "FG_HELLO"; + + m_szGrp[TLK_PLHURT1] = "FG_CURE"; + m_szGrp[TLK_PLHURT2] = "FG_CURE"; + m_szGrp[TLK_PLHURT3] = "FG_CURE"; + + m_szGrp[TLK_SMELL] = "FG_SMELL"; + + m_szGrp[TLK_WOUND] = "FG_WOUND"; + m_szGrp[TLK_MORTAL] = "FG_MORTAL"; +} + +//========================================================= +// PainSound +//========================================================= +void CHFGrunt :: PainSound ( void ) +{ + if ( gpGlobals->time > m_flNextPainTime ) + { + switch ( RANDOM_LONG(0,5) ) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "fgrunt/gr_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "fgrunt/gr_pain2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "fgrunt/gr_pain3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 3: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "fgrunt/gr_pain4.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 4: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "fgrunt/gr_pain5.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 5: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "fgrunt/gr_pain6.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } + m_flNextPainTime = gpGlobals->time + 1; + } +} + +void CHFGrunt::AlertSound() +{ + if (m_hEnemy !=0 && FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "FG_ATTACK", FGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + } +} + +//========================================================= +// DeathSound +//========================================================= +void CHFGrunt :: DeathSound ( void ) +{ + switch (RANDOM_LONG(0,5)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "fgrunt/death1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "fgrunt/death2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "fgrunt/death3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 3: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "fgrunt/death4.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 4: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "fgrunt/death5.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 5: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "fgrunt/death6.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + + +void CHFGrunt::IdleSound() +{ + if (FOkToSpeak() && (g_fGruntAllyQuestion || RANDOM_LONG(0,1))) + { + const float duration = RANDOM_FLOAT(1.5, 2.5); + if (g_fGruntAllyQuestion) + { + switch (g_fGruntAllyQuestion) { + case 1: + PlaySentence( "FG_CLEAR", duration, FGRUNT_SENTENCE_VOLUME, ATTN_IDLE ); + break; + case 2: + PlaySentence( m_szGrp[TLK_ANSWER], duration, FGRUNT_SENTENCE_VOLUME, ATTN_IDLE ); + break; + default: + break; + } + g_fGruntAllyQuestion = 0; + } + else + { + switch (RANDOM_LONG(0,2)) { + case 0: + PlaySentence( "FG_CHECK", duration, FGRUNT_SENTENCE_VOLUME, ATTN_IDLE ); + g_fGruntAllyQuestion = 1; + break; + case 1: + PlaySentence( m_szGrp[TLK_QUESTION], duration, FGRUNT_SENTENCE_VOLUME, ATTN_IDLE ); + g_fGruntAllyQuestion = 2; + break; + case 2: + PlaySentence( m_szGrp[TLK_IDLE], duration, FGRUNT_SENTENCE_VOLUME, ATTN_IDLE ); + break; + default: + break; + } + } + m_iSentence = FGRUNT_SENT_NONE; + } +} + +//========================================================= +// TraceAttack - make sure we're not taking it in the helmet +//========================================================= +void CHFGrunt :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // reduce damage on vest + if (ptr->iHitgroup == HITGROUP_CHEST || ptr->iHitgroup == HITGROUP_STOMACH) + { + if (bitsDamageType & ( DMG_BULLET | DMG_SLASH | DMG_BLAST )) + { + flDamage *= 0.5; + } + } + // check for helmet shot + if (ptr->iHitgroup == 11) + { + // make sure we're wearing one + if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB)) + { + // absorb damage + flDamage -= 20; + if (flDamage <= 0) + { + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + flDamage = 0.01; + } + } + // it's head shot anyways + ptr->iHitgroup = HITGROUP_HEAD; + } + CTalkMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} +//========================================================= +// TakeDamage - overridden for the grunt because the grunt +// needs to forget that he is in cover if he's hurt. (Obviously +// not in a safe place anymore). +//========================================================= +int CHFGrunt :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Forget( bits_MEMORY_INCOVER ); + + int ret = CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); + if( !IsAlive() || pev->deadflag == DEAD_DYING ) + return ret; + + if( m_MonsterState != MONSTERSTATE_PRONE && ( pevAttacker->flags & FL_CLIENT ) ) + { + // This is a heurstic to determine if the player intended to harm me + // If I have an enemy, we can't establish intent (may just be crossfire) + if( m_hEnemy == 0 ) + { + // If the player was facing directly at me, or I'm already suspicious, get mad + if( ( m_afMemory & bits_MEMORY_SUSPICIOUS ) || IsFacing( pevAttacker, pev->origin ) ) + { + if (gpGlobals->time - m_flLastHitByPlayer < 4.0 && m_iPlayerHits >= 3) + { + // Alright, now I'm pissed! + PlaySentence( "FG_MAD", 4, VOL_NORM, ATTN_NORM ); + + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } + } + else + { + if ( gpGlobals->time - m_flLastHitByPlayer >= 4.0 ) + m_iPlayerHits = 0; + m_iPlayerHits++; + m_flLastHitByPlayer = gpGlobals->time; + + // Hey, be careful with that + PlaySentence( "FG_SHOT", 4, VOL_NORM, ATTN_NORM ); + Remember( bits_MEMORY_SUSPICIOUS ); + } + } + else if( !( m_hEnemy->IsPlayer()) && pev->deadflag == DEAD_NO ) + { + PlaySentence( "FG_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +Schedule_t* CHFGrunt :: GetScheduleOfType ( int Type ) +{ + Schedule_t *psched; + + switch( Type ) + { + // Hook these to make a looping schedule + case SCHED_TARGET_FACE: + // call base class default so that barney will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType( Type ); + + if( psched == slIdleStand ) + return slGruntFaceTarget; // override this for different target face behavior + else + return psched; + case SCHED_TARGET_CHASE: + return slGruntFollow; + case SCHED_IDLE_STAND: + // call base class default so that scientist will talk + // when standing during idle + psched = CTalkMonster::GetScheduleOfType( Type ); + + if( psched == slIdleStand ) + { + // just look straight ahead. + return slIdleGruntStand; + } + else + return psched; + case SCHED_TAKE_COVER_FROM_ENEMY: + { + return &slFGruntTakeCover[ 0 ]; + } + break; + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slFGruntTakeCoverFromBestSound[ 0 ]; + } + break; + case SCHED_HGRUNT_ALLY_TAKECOVER_FAILED: + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + else + { + return GetScheduleOfType ( SCHED_FAIL ); + } + } + break; + case SCHED_HGRUNT_ALLY_ELOF_FAIL: + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + break; + case SCHED_HGRUNT_ALLY_ESTABLISH_LINE_OF_FIRE: + { + return &slFGruntEstablishLineOfFire[ 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 &slFGruntRangeAttack1B[ 0 ]; + else + return &slFGruntRangeAttack1A[ 0 ]; + } + break; + case SCHED_RANGE_ATTACK2: + { + return &slFGruntRangeAttack2[ 0 ]; + } + break; + case SCHED_COMBAT_FACE: + { + return &slFGruntCombatFace[ 0 ]; + } + break; + case SCHED_HGRUNT_ALLY_WAIT_FACE_ENEMY: + { + return &slFGruntWaitInCover[ 0 ]; + } + case SCHED_HGRUNT_ALLY_SWEEP: + { + return &slFGruntSweep[ 0 ]; + } + break; + case SCHED_HGRUNT_ALLY_COVER_AND_RELOAD: + { + return &slFGruntHideReload[ 0 ]; + } + break; + case SCHED_HGRUNT_ALLY_FOUND_ENEMY: + { + return &slFGruntFoundEnemy[ 0 ]; + } + break; + case SCHED_VICTORY_DANCE: + { + if ( InSquad() ) + { + if ( !IsLeader() ) + { + return &slFGruntFail[ 0 ]; + } + } + if ( m_hTalkTarget != 0 ) + { + return &slFGruntFail[ 0 ]; + } + + return &slFGruntVictoryDance[ 0 ]; + } + break; + case SCHED_HGRUNT_ALLY_SUPPRESS: + { + if ( m_fFirstEncounter ) + { + m_fFirstEncounter = FALSE;// after first encounter, leader won't issue handsigns anymore when he has a new enemy + return &slFGruntSignalSuppress[ 0 ]; + } + else + { + return &slFGruntSuppress[ 0 ]; + } + } + break; + case SCHED_FAIL: + { + if ( m_hEnemy != 0 ) + { + // grunt has an enemy, so pick a different default fail schedule most likely to help recover. + return &slFGruntCombatFail[ 0 ]; + } + + return &slFGruntFail[ 0 ]; + } + break; + case SCHED_HGRUNT_ALLY_REPEL: + { + if (pev->velocity.z > -128) + pev->velocity.z -= 32; + return &slFGruntRepel[ 0 ]; + } + break; + case SCHED_HGRUNT_ALLY_REPEL_ATTACK: + { + if (pev->velocity.z > -128) + pev->velocity.z -= 32; + return &slFGruntRepelAttack[ 0 ]; + } + break; + case SCHED_HGRUNT_ALLY_REPEL_LAND: + { + return &slFGruntRepelLand[ 0 ]; + } + break; + case SCHED_HGRUNT_ALLY_FIND_MEDIC: + { + return slGruntFindMedic; + } + break; + default: + { + return CTalkMonster :: GetScheduleOfType ( Type ); + } + } +} +//========================================================= +// SetActivity +//========================================================= +void CHFGrunt :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_RANGE_ATTACK1: + // grunt is either shooting standing or shooting crouched + if (FBitSet( pev->weapons, FGRUNT_SHOTGUN)) + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_shotgun" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_shotgun" ); + } + } + else if (FBitSet( pev->weapons, FGRUNT_M249 )) + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_saw" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_saw" ); + } + } + else + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_mp5" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_mp5" ); + } + } + break; + case ACT_RANGE_ATTACK2: + // grunt is going to a secondary long range attack. This may be a thrown + // grenade or fired grenade, we must determine which and pick proper sequence + if ( pev->weapons & FGRUNT_HANDGRENADE ) + { + // get toss anim + iSequence = LookupSequence( "throwgrenade" ); + } + else if ( pev->weapons & FGRUNT_GRENADELAUNCHER ) + { + // get launch anim + iSequence = LookupSequence( "launchgrenade" ); + } + break; + case ACT_RUN: + if ( pev->health <= FGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_RUN_HURT ); + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_WALK: + if ( pev->health <= FGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_WALK_HURT ); + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_IDLE: + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + NewActivity = ACT_IDLE_ANGRY; + } + iSequence = LookupActivity ( NewActivity ); + break; + default: + iSequence = LookupActivity ( NewActivity ); + break; + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + SetYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_console, "%s has no sequence for act:%d\n", STRING(pev->classname), NewActivity ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CHFGrunt :: GetSchedule ( void ) +{ + // 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), SentenceByNumber(FGRUNT_SENT_GREN), FGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + } + } + // 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_HGRUNT_ALLY_REPEL_LAND ); + } + else + { + // repel down a rope, + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType ( SCHED_HGRUNT_ALLY_REPEL_ATTACK ); + else + return GetScheduleOfType ( SCHED_HGRUNT_ALLY_REPEL ); + } + } + if ( HasConditions( bits_COND_ENEMY_DEAD ) && FOkToSpeak() ) + { + PlaySentence( "FG_KILL", 4, VOL_NORM, ATTN_NORM ); + } + + 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 CTalkMonster::GetSchedule(); + } +// new enemy + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + if ( InSquad() ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + + if ( !IsLeader() ) + { + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_HGRUNT_ALLY_SUPPRESS ); + } + else + { + return GetScheduleOfType ( SCHED_HGRUNT_ALLY_ESTABLISH_LINE_OF_FIRE ); + } + } + 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) + { + if ( ( m_hEnemy->Classify() != CLASS_PLAYER_ALLY ) && + ( m_hEnemy->Classify() != CLASS_HUMAN_PASSIVE ) && + ( m_hEnemy->Classify() != CLASS_HUMAN_MILITARY ) && + ( m_hEnemy->Classify() != CLASS_MACHINE ) ) + SENTENCEG_PlayRndSz( ENT(pev), SentenceByNumber(FGRUNT_SENT_MONSTER), FGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + else + SENTENCEG_PlayRndSz( ENT(pev), SentenceByNumber(FGRUNT_SENT_ALERT), FGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + } + + JustSpoke(); + } + + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_HGRUNT_ALLY_SUPPRESS ); + } + else + { + return GetScheduleOfType ( SCHED_HGRUNT_ALLY_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_HGRUNT_ALLY_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 ) + { + if (FOkToSpeak()) + { + m_iSentence = FGRUNT_SENT_COVER; + } + // only try to take cover if we actually have an enemy! + + 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, FGRUNT_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_HGRUNT_ALLY_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), SentenceByNumber(FGRUNT_SENT_THROW), FGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else if ( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + if( FOkToSpeak() ) + { + m_iSentence = FGRUNT_SENT_CHARGE; + } + return GetScheduleOfType( SCHED_HGRUNT_ALLY_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), SentenceByNumber(FGRUNT_SENT_TAUNT), FGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_HGRUNT_ALLY_ESTABLISH_LINE_OF_FIRE ); + } + } + break; + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( HasConditions ( bits_COND_NO_AMMO_LOADED ) ) + { + return GetScheduleOfType ( SCHED_RELOAD ); + } + if ( WantsToCallMedic() ) + { + return GetScheduleOfType( SCHED_HGRUNT_ALLY_FIND_MEDIC ); + } + + if( m_hEnemy == 0 && IsFollowing() ) + { + if( !m_hTargetEnt->IsAlive() ) + { + // UNDONE: Comment about the recently dead player here? + StopFollowing( FALSE ); + break; + } + else + { + if( HasConditions( bits_COND_CLIENT_PUSH ) ) + { + return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); + } + return GetScheduleOfType( SCHED_TARGET_FACE ); + } + } + + if( HasConditions( bits_COND_CLIENT_PUSH ) ) + { + return GetScheduleOfType( SCHED_MOVE_AWAY ); + } + + // try to say something about smells + TrySmellTalk(); + break; } - break; - case TASK_FGRUNT_SPEAK_SENTENCE: - SpeakSentence(); - TaskComplete(); + return CTalkMonster :: GetSchedule(); +} +MONSTERSTATE CHFGrunt :: GetIdealState ( void ) +{ + return CTalkMonster::GetIdealState(); +} + +void CHFGrunt::DeclineFollowing( void ) +{ + PlaySentence( "FG_POK", 2, VOL_NORM, ATTN_NORM ); +} + +//========================================================= +// CHFGruntRepel - when triggered, spawns a +// repelling down a line. +//========================================================= + +class CHFGruntRepel : public CHGruntRepel +{ +public: + void KeyValue(KeyValueData* pkvd); + const char* TrooperName() { + return "monster_human_grunt_ally"; + } + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iGruntHead; +}; + +LINK_ENTITY_TO_CLASS( monster_grunt_ally_repel, CHFGruntRepel ) + +TYPEDESCRIPTION CHFGruntRepel::m_SaveData[] = +{ + DEFINE_FIELD( CHFGruntRepel, m_iGruntHead, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CHFGruntRepel, CHGruntRepel ) + +void CHFGruntRepel::KeyValue(KeyValueData *pkvd) +{ + if( FStrEq(pkvd->szKeyName, "head" ) ) + { + m_iGruntHead = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CHGruntRepel::KeyValue( pkvd ); +} + +class CMedicRepel : public CHFGruntRepel +{ +public: + const char* TrooperName() { + return "monster_human_medic_ally"; + } +}; + +LINK_ENTITY_TO_CLASS( monster_medic_ally_repel, CMedicRepel ) + +class CTorchRepel : public CHGruntRepel +{ +public: + const char* TrooperName() { + return "monster_human_torch_ally"; + } +}; + +LINK_ENTITY_TO_CLASS( monster_torch_ally_repel, CTorchRepel ) + +//========================================================= +// FGrunt Dead PROP +//========================================================= + +class CDeadFGrunt : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_PLAYER_ALLY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose; + int m_iHead; + static const char *m_szPoses[7]; +}; + +const char *CDeadFGrunt::m_szPoses[] = { "deadstomach", "deadside", "deadsitting", "dead_on_back", "hgrunt_dead_stomach", "dead_headcrabed" }; + +void CDeadFGrunt::KeyValue( KeyValueData *pkvd ) +{ + if( FStrEq( pkvd->szKeyName, "pose" ) ) + { + m_iPose = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "head")) + { + m_iHead = 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( ) +{ + 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 barney with bad pose\n" ); + } + // Corpses have less health + pev->health = 8;//gSkillData.barneyHealth; + + if ( pev->weapons <= 0 ) + { + SetBodygroup( FG_GUN_GROUP, FG_GUN_NONE ); + } + if (FBitSet( pev->weapons, FGRUNT_SHOTGUN )) + { + SetBodygroup( FG_GUN_GROUP, FG_GUN_SHOTGUN ); + SetBodygroup( FG_TORSO_GROUP, FG_TORSO_SHOTGUN ); + } + if (FBitSet( pev->weapons, FGRUNT_9MMAR )) + { + SetBodygroup( FG_GUN_GROUP, FG_GUN_MP5 ); + } + if (FBitSet( pev->weapons, FGRUNT_M249 )) + { + SetBodygroup( FG_GUN_GROUP, FG_GUN_SAW ); + SetBodygroup( FG_TORSO_GROUP, FG_TORSO_M249 ); + } + + if ( m_iHead <= -2 ) + { + // skip major and MP heads + m_iHead = RANDOM_LONG(0, FG_HEAD_SAW_BLACK+1); + if (m_iHead == FG_HEAD_SAW_BLACK+1) { + m_iHead = FG_HEAD_BERET_BLACK; + } + } + else if ( m_iHead < 0 || m_iHead >= FG_HEAD_COUNT ) + m_iHead = 0; + + SetBodygroup( FG_HEAD_GROUP, m_iHead ); + + MonsterInitDead(); +} + +#define TORCH_CLIP_SIZE 7 + +#define TORCH_EAGLE ( 1 << 0) +#define TORCH_BLOWTORCH ( 1 << 1) + +// Weapon group +#define TORCH_GUN_GROUP 2 +#define TORCH_GUN_EAGLE 0 +#define TORCH_GUN_TORCH 1 +#define TORCH_GUN_NONE 2 + +// Torch specific animation events +#define TORCH_AE_SHOWGUN ( 17) +#define TORCH_AE_SHOWTORCH ( 18) +#define TORCH_AE_HIDETORCH ( 19) +#define TORCH_AE_ONGAS ( 20) +#define TORCH_AE_OFFGAS ( 21) + +class CTorch : public CHFGrunt +{ +public: + void Spawn( void ); + void Precache( void ); + void HandleAnimEvent( MonsterEvent_t* pEvent ); + BOOL CheckRangeAttack1(float flDot, float flDist); + BOOL CheckRangeAttack2(float flDot, float flDist); + void GibMonster(); + void TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + void PrescheduleThink(); + + void DropMyItems(BOOL isGibbed); + + void MakeGas( void ); + void UpdateGas( void ); + void KillGas( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CBeam *m_pBeam; +}; + +LINK_ENTITY_TO_CLASS( monster_human_torch_ally, CTorch ) + +TYPEDESCRIPTION CTorch::m_SaveData[] = +{ + DEFINE_FIELD( CTorch, m_pBeam, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CTorch, CHFGrunt ) + +void CTorch::Spawn() +{ + Precache( ); + + SpawnHelper("models/hgrunt_torch.mdl", gSkillData.torchHealth); + + if (!pev->weapons) + pev->weapons = TORCH_EAGLE; + + if ( FBitSet( pev->weapons, TORCH_EAGLE ) ) + { + pev->body = TORCH_GUN_EAGLE; + } + if ( FBitSet( pev->weapons, TORCH_BLOWTORCH ) ) + { + pev->body = TORCH_GUN_TORCH; + } + m_cClipSize = TORCH_CLIP_SIZE; + m_cAmmoLoaded = m_cClipSize; + m_pBeam = NULL; + MonsterInit(); + SetUse( &CTalkMonster::FollowerUse ); +} + +void CTorch::Precache() +{ + PRECACHE_MODEL("models/hgrunt_torch.mdl"); + PRECACHE_SOUND("weapons/desert_eagle_fire.wav"); + PRECACHE_SOUND("weapons/desert_eagle_reload.wav"); + PrecacheHelper(); + TalkInit(); + m_voicePitch = 95; + CTalkMonster::Precache(); +} + +void CTorch::HandleAnimEvent(MonsterEvent_t *pEvent) +{ + switch ( pEvent->event ) + { + case TORCH_AE_SHOWTORCH: + pev->body = 1; + break; + + case TORCH_AE_SHOWGUN: + if ( FBitSet( pev->weapons, TORCH_EAGLE ) ) + pev->body = 0; + else + pev->body = 1; + break; + + case TORCH_AE_HIDETORCH: + pev->body = 2; + break; + + case TORCH_AE_ONGAS: + MakeGas (); + UpdateGas (); + break; + + case TORCH_AE_OFFGAS: + KillGas (); + break; + case HGRUNT_ALLY_AE_DROP_GUN: + if ( FBitSet( pev->weapons, TORCH_EAGLE ) ) + { + DropMyItems(FALSE); + } + break; + case HGRUNT_ALLY_AE_RELOAD: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/desert_eagle_reload.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions(bits_COND_NO_AMMO_LOADED); + break; + case HGRUNT_ALLY_AE_BURST1: + { + UTIL_MakeVectors( pev->angles ); + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); + pev->effects = EF_MUZZLEFLASH; + + FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_2DEGREES, 1024, BULLET_MONSTER_357 ); + + int pitchShift = RANDOM_LONG( 0, 20 ); + // Only shift about half the time + if( pitchShift > 10 ) + pitchShift = 0; + else + pitchShift -= 5; + EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, "weapons/desert_eagle_fire.wav", 1, ATTN_NORM, 0, 100 + pitchShift ); + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + m_cAmmoLoaded--;// take away a bullet! + } + break; + case HGRUNT_ALLY_AE_BURST2: + case HGRUNT_ALLY_AE_BURST3: + break; + case HGRUNT_ALLY_AE_KICK: + { + KickImpl(gSkillData.torchDmgKick); + } + break; + default: + CHFGrunt::HandleAnimEvent(pEvent); break; + } +} + +BOOL CTorch::CheckRangeAttack1(float flDot, float flDist) +{ + return FBitSet( pev->weapons, TORCH_EAGLE ) && CHFGrunt::CheckRangeAttack1(flDot, flDist); +} + +BOOL CTorch::CheckRangeAttack2(float flDot, float flDist) +{ + return FALSE; +} + +void CTorch::GibMonster() +{ + if ( FBitSet( pev->weapons, TORCH_EAGLE ) && pev->body != TORCH_GUN_NONE ) + {// throw a gun if the grunt has one + DropMyItems(TRUE); + } + CTalkMonster::GibMonster(); +} + +void CTorch::DropMyItems(BOOL isGibbed) +{ + if (!isGibbed) { + pev->body = TORCH_GUN_NONE; + } + Vector vecGunPos; + Vector vecGunAngles; + GetAttachment( 0, vecGunPos, vecGunAngles ); + DropMyItem("weapon_eagle", vecGunPos, vecGunAngles, isGibbed); +} + +void CTorch::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // check for gas tank + if (ptr->iHitgroup == 8) + { + if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB)) + { + bitsDamageType = (DMG_ALWAYSGIB | DMG_BLAST); + flDamage = pev->health + 1; + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( ptr->vecEndPos.x ); // Send to PAS because of the sound + WRITE_COORD( ptr->vecEndPos.y ); + WRITE_COORD( ptr->vecEndPos.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 15 ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + ::RadiusDamage ( pev->origin, pev, pev, Q_min(pev->max_health, 75), 125, CLASS_NONE, DMG_BLAST ); + Create( "spark_shower", pev->origin, ptr->vecPlaneNormal, NULL ); + } + } + CHFGrunt::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + +void CTorch::PrescheduleThink() +{ + if (m_pBeam) + { + UpdateGas(); + } + CHFGrunt::PrescheduleThink(); +} + +//========================================================= +// AUTOGENE +//========================================================= +void CTorch::UpdateGas( void ) +{ + TraceResult tr; + Vector posGun, angleGun; + + if ( m_pBeam ) + { + UTIL_MakeVectors( pev->angles ); + + GetAttachment( 2, posGun, angleGun ); + + Vector vecEnd = (gpGlobals->v_forward * 5) + posGun; + UTIL_TraceLine( posGun, vecEnd, dont_ignore_monsters, edict(), &tr ); + + if ( tr.flFraction != 1.0 ) + { + m_pBeam->DoSparks( tr.vecEndPos, posGun ); + UTIL_DecalTrace(&tr, DECAL_BIGSHOT1 + RANDOM_LONG(0,4)); + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, tr.vecEndPos ); + WRITE_BYTE( TE_STREAK_SPLASH ); + WRITE_COORD( tr.vecEndPos.x ); // origin + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_COORD( tr.vecPlaneNormal.x ); // direction + WRITE_COORD( tr.vecPlaneNormal.y ); + WRITE_COORD( tr.vecPlaneNormal.z ); + WRITE_BYTE( 10 ); // Streak color 6 + WRITE_SHORT( 60 ); // count + WRITE_SHORT( 25 ); + WRITE_SHORT( 50 ); // Random velocity modifier + MESSAGE_END(); + } + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_DLIGHT ); + WRITE_COORD( posGun.x ); // origin + WRITE_COORD( posGun.y ); + WRITE_COORD( posGun.z ); + WRITE_BYTE( RANDOM_LONG(4, 16) ); // radius + WRITE_BYTE( 251 ); // R + WRITE_BYTE( 68 ); // G + WRITE_BYTE( 36 ); // B + WRITE_BYTE( 1 ); // life * 10 + WRITE_BYTE( 0 ); // decay + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 * 3 ); // entity, attachment + WRITE_COORD( posGun.x ); // origin + WRITE_COORD( posGun.y ); + WRITE_COORD( posGun.z ); + WRITE_COORD( RANDOM_LONG(8, 12) ); // radius + WRITE_BYTE( 251 ); // R + WRITE_BYTE( 68 ); // G + WRITE_BYTE( 36 ); // B + WRITE_BYTE( 1 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + } +} + +void CTorch::MakeGas( void ) +{ + Vector posGun, angleGun; + TraceResult tr; + Vector vecEndPos; + + UTIL_MakeVectors( pev->angles ); + m_pBeam = CBeam::BeamCreate( g_pModelNameLaser, 7 ); + + if ( m_pBeam ) + { + GetAttachment( 4, posGun, angleGun ); + GetAttachment( 3, posGun, angleGun ); + UTIL_Sparks( posGun ); + Vector vecEnd = (gpGlobals->v_forward * 5) + posGun; + UTIL_TraceLine( posGun, vecEnd, dont_ignore_monsters, edict(), &tr ); + + m_pBeam->EntsInit( entindex(), entindex() ); + m_pBeam->SetColor( 24, 121, 239 ); + m_pBeam->SetBrightness( 190 ); + m_pBeam->SetScrollRate( 20 ); + m_pBeam->SetStartAttachment( 4 ); + m_pBeam->SetEndAttachment( 3 ); + m_pBeam->DoSparks( tr.vecEndPos, posGun ); + m_pBeam->SetFlags( BEAM_FSHADEIN ); + m_pBeam->pev->spawnflags = SF_BEAM_SPARKSTART | SF_BEAM_TEMPORARY; + UTIL_Sparks( tr.vecEndPos ); + } + return; +} + +void CTorch :: KillGas( void ) +{ + if ( m_pBeam ) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } + return; +} + +#define MEDIC_CLIP_SIZE 17 +#define MEDIC_CLIP_SIZE_EAGLE 7 + +#define MEDIC_EAGLE 1 << 0 +#define MEDIC_HANDGUN 1 << 1 +#define MEDIC_NEEDLE 1 << 2 + +// Weapon group +#define MEDIC_GUN_GROUP 3 +#define MEDIC_GUN_EAGLE 0 +#define MEDIC_GUN_PISTOL 1 +#define MEDIC_GUN_NEEDLE 2 +#define MEDIC_GUN_NONE 3 + +// Head group +#define MEDIC_HEAD_GROUP 2 +enum { + MEDIC_HEAD_WHITE, + MEDIC_HEAD_BLACK, + MEDIC_HEAD_COUNT +}; + +//========================================================= +// Medic specific animation events +//========================================================= +#define MEDIC_AE_HIDEGUN ( 15) +#define MEDIC_AE_SHOWNEEDLE ( 16) +#define MEDIC_AE_HIDENEEDLE ( 17) +#define MEDIC_AE_SHOWGUN ( 18) + +enum +{ + TASK_MEDIC_SAY_HEAL = LAST_HGRUNT_ALLY_TASK + 1, + TASK_MEDIC_HEAL, +}; + +Task_t tlMedicHeal[] = +{ + { TASK_MOVE_TO_TARGET_RANGE, (float)50 }, // Move within 50 of target ent (client) + { TASK_FACE_IDEAL, (float)0 }, + { TASK_MEDIC_SAY_HEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_ARM }, // Whip out the needle + { TASK_MEDIC_HEAL, (float)0 }, // Put it in the player + { TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_DISARM }, // Put away the needle +}; + +Schedule_t slMedicHeal[] = +{ + { + tlMedicHeal, + ARRAYSIZE ( tlMedicHeal ), + 0, // Don't interrupt or he'll end up running around with a needle all the time + 0, + "Heal" + }, +}; + +Task_t tlMedicDrawGun[] = +{ + { TASK_PLAY_SEQUENCE, (float)ACT_DISARM }, // Put away the needle +}; + +Schedule_t slMedicDrawGun[] = +{ + { + tlMedicDrawGun, + ARRAYSIZE ( tlMedicDrawGun ), + 0, // Don't interrupt or he'll end up running around with a needle all the time + 0, + "DrawGun" + }, +}; + +LINK_ENTITY_TO_CLASS( monster_human_medic_ally, CMedic ) + +TYPEDESCRIPTION CMedic::m_SaveData[] = +{ + DEFINE_FIELD( CMedic, m_flHealCharge, FIELD_FLOAT ), + DEFINE_FIELD( CMedic, m_fDepleteLine, FIELD_BOOLEAN ), + DEFINE_FIELD( CMedic, m_fHealing, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CMedic, CHFGrunt ) + +DEFINE_CUSTOM_SCHEDULES( CMedic ) +{ + slMedicHeal, + slMedicDrawGun, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMedic, CHFGrunt ) + +bool CMedic::Heal( void ) +{ + if ( !HasHealCharge() || !HasHealTarget() ) + return false; - case TASK_WALK_PATH: - case TASK_RUN_PATH: - // grunt no longer assumes he is covered if he moves - Forget(bits_MEMORY_INCOVER); - CTalkMonster::StartTask(pTask); - break; + Vector target = m_hTargetEnt->pev->origin - pev->origin; + if ( target.Length() > 100 ) + return false; - case TASK_RELOAD: - m_IdealActivity = ACT_RELOAD; - break; + m_flHealCharge -= m_hTargetEnt->TakeHealth( Q_min(10, m_flHealCharge), DMG_GENERIC ); + ALERT(at_aiconsole, "Medic grunt heal charge left: %f\n", m_flHealCharge); + m_fHealing = TRUE; + return true; +} - case TASK_FGRUNT_FACE_TOSS_DIR: +void CMedic::StartTask(Task_t *pTask) +{ + switch( pTask->iTask ) + { + case TASK_MEDIC_HEAL: + m_IdealActivity = ACT_MELEE_ATTACK2; + if (Heal()) + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "fgrunt/medic_give_shot.wav", 1, ATTN_NORM ); break; - - case TASK_FACE_IDEAL: - case TASK_FACE_ENEMY: - CTalkMonster::StartTask(pTask); - if (pev->movetype == MOVETYPE_FLY) - { - m_IdealActivity = ACT_GLIDE; - } + case TASK_MEDIC_SAY_HEAL: + m_hTalkTarget = m_hTargetEnt; + PlaySentence( "MG_HEAL", 2, VOL_NORM, ATTN_IDLE ); + TaskComplete(); break; - default: - CTalkMonster::StartTask(pTask); + CHFGrunt::StartTask(pTask); break; } -#endif - CTalkMonster::StartTask(pTask); } -void CFGrunt::RunTask(Task_t *pTask) +void CMedic::RunTask(Task_t *pTask) { -#if 1 switch ( pTask->iTask ) { - case TASK_FGRUNT_FACE_TOSS_DIR: - { - // project a point along the toss vector and turn to face that point. - MakeIdealYaw( pev->origin + m_vecTossVelocity * 64 ); - ChangeYaw( pev->yaw_speed ); - - if ( FacingIdeal() ) + case TASK_MEDIC_HEAL: + if ( m_fSequenceFinished ) { - m_iTaskStatus = TASKSTATUS_COMPLETE; + if (HasHealTarget() && CheckHealCharge()) { + m_IdealActivity = ACT_MELEE_ATTACK2; + ALERT(at_aiconsole, "Medic continuing healing\n"); + Heal(); + } else { + TaskComplete(); + StopHealing(); + } } - } - break; - - case TASK_RANGE_ATTACK1: - { - //if (m_hEnemy != NULL && (m_hEnemy->IsPlayer())) - //{ - // pev->framerate = 1.5; - //} - CTalkMonster::RunTask(pTask); - } - break; - + else + { + if ( TargetDistance() > 90 ) { + TaskComplete(); + StopHealing(); + } + if (m_hTargetEnt != 0) + { + pev->ideal_yaw = UTIL_VecToYaw( m_hTargetEnt->pev->origin - pev->origin ); + ChangeYaw( pev->yaw_speed ); + } + } + break; default: - { - CTalkMonster::RunTask(pTask); + CHFGrunt::RunTask(pTask); break; } } -#else - switch (pTask->iTask) +Schedule_t *CMedic::GetSchedule() +{ + if (m_fHealing) { + StopHealing(); + } + if ( FBitSet( pev->weapons, MEDIC_EAGLE|MEDIC_HANDGUN ) && + (GetBodygroup(MEDIC_GUN_GROUP) == MEDIC_GUN_NEEDLE || GetBodygroup(MEDIC_GUN_GROUP) == MEDIC_GUN_NONE)) { + return slMedicDrawGun; + } + switch( m_MonsterState ) { - case TASK_RANGE_ATTACK1: - if (m_hEnemy != NULL && (m_hEnemy->IsPlayer())) + case MONSTERSTATE_IDLE: + case MONSTERSTATE_ALERT: + if ( m_hEnemy == 0 ) { - pev->framerate = 1.5; + if (m_hTargetEnt != 0 && m_hTargetEnt->IsPlayer()) + { + if ( TargetDistance() <= 128 ) + { + if ( CheckHealCharge() && m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health * 0.75 ) { + ALERT(at_aiconsole, "Medic is going to heal a player\n"); + return GetScheduleOfType(SCHED_MEDIC_HEAL); + } + } + } + // was called by other ally + else if (HasHealCharge() && HasHealTarget()) { + return GetScheduleOfType(SCHED_MEDIC_HEAL); + } } - CTalkMonster::RunTask(pTask); - break; + } + return CHFGrunt::GetSchedule(); +} + +Schedule_t *CMedic::GetScheduleOfType(int Type) +{ + switch (Type) { + case SCHED_MEDIC_HEAL: + return slMedicHeal; default: - CTalkMonster::RunTask(pTask); - break; + return CHFGrunt::GetScheduleOfType(Type); } -#endif } +void CMedic::StopFollowing(BOOL clearSchedule) +{ + if (InHealSchedule() && (m_hTargetEnt != 0 && !m_hTargetEnt->IsPlayer())) + clearSchedule = FALSE; + CHFGrunt::StopFollowing(clearSchedule); +} -void CFGrunt::KeyValue(KeyValueData *pkvd) +void CMedic::SetAnswerQuestion(CTalkMonster *pSpeaker) { - if (FStrEq(pkvd->szKeyName, "head")) - { - head = atoi(pkvd->szValue); - pkvd->fHandled = TRUE; + if (!m_fHealing) { + CTalkMonster::SetAnswerQuestion(pSpeaker); } - else - CTalkMonster::KeyValue(pkvd); } -//========================================================= -// SetActivity -//========================================================= -void CFGrunt::SetActivity(Activity NewActivity) +void CMedic::Spawn() { - int iSequence = ACTIVITY_NOT_AVAILABLE; - void *pmodel = GET_MODEL_PTR(ENT(pev)); + Precache( ); -#ifdef DEBUG - ALERT(at_console, "ALERT: incoming activity: %s\n", UTIL_GetActivityNameBySequence(LookupActivity(NewActivity))); -#endif + SpawnHelper("models/hgrunt_medic.mdl", gSkillData.medicHealth); - switch (NewActivity) + if (!pev->weapons) { - case ACT_RANGE_ATTACK1: - // grunt is either shooting standing or shooting crouched - if (FBitSet(pev->weapons, FGRUNT_9MMAR)) - { - if (m_fStanding) - { - // get aimable sequence - iSequence = LookupSequence("standing_mp5"); - } - else - { - // get crouching shoot - iSequence = LookupSequence("crouching_mp5"); - } - } - else if (FBitSet(pev->weapons, FGRUNT_SAW)) - { - if (m_fStanding) - { - // get aimable sequence - iSequence = LookupSequence("standing_saw"); - } - else - { - // get crouching shoot - iSequence = LookupSequence("crouching_saw"); - } - } - else - { - if (m_fStanding) - { - // get aimable sequence - iSequence = LookupSequence("standing_shotgun"); - } - else - { - // get crouching shoot - iSequence = LookupSequence("crouching_shotgun"); - } - } + pev->weapons |= MEDIC_EAGLE; // some medics on existing maps don't have a weapon selected + } + m_cClipSize = MEDIC_CLIP_SIZE_EAGLE; + if ( FBitSet( pev->weapons, MEDIC_EAGLE ) ) + { + SetBodygroup( MEDIC_GUN_GROUP, MEDIC_GUN_EAGLE ); + } + else if ( FBitSet( pev->weapons, MEDIC_HANDGUN ) ) + { + SetBodygroup( MEDIC_GUN_GROUP, MEDIC_GUN_PISTOL ); + m_cClipSize = MEDIC_CLIP_SIZE; + } + else if ( FBitSet( pev->weapons, MEDIC_NEEDLE ) ) + { + SetBodygroup( MEDIC_GUN_GROUP, MEDIC_GUN_NEEDLE ); + } + m_cAmmoLoaded = m_cClipSize; + + if (m_iHead < 0 || m_iHead >= MEDIC_HEAD_COUNT) { + m_iHead = RANDOM_LONG(MEDIC_HEAD_WHITE, MEDIC_HEAD_BLACK); + } + + SetBodygroup(MEDIC_HEAD_GROUP, m_iHead); + + m_flHealCharge = gSkillData.medicHeal; + MonsterInit(); + SetUse( &CTalkMonster::FollowerUse ); +} + +void CMedic::Precache() +{ + PRECACHE_MODEL("models/hgrunt_medic.mdl"); + PRECACHE_SOUND("weapons/desert_eagle_fire.wav"); + PRECACHE_SOUND("weapons/desert_eagle_reload.wav"); + PRECACHE_SOUND("weapons/pl_gun3.wav"); + PRECACHE_SOUND("fgrunt/medic_give_shot.wav"); + PRECACHE_SOUND("fgrunt/medical.wav"); + PrecacheHelper(); + TalkInit(); + if (m_iHead == MEDIC_HEAD_BLACK) + m_voicePitch = 95; + else + m_voicePitch = 105; + CTalkMonster::Precache(); +} + +void CMedic::HandleAnimEvent(MonsterEvent_t *pEvent) +{ + switch ( pEvent->event ) + { + case MEDIC_AE_SHOWNEEDLE: + SetBodygroup( MEDIC_GUN_GROUP, MEDIC_GUN_NEEDLE ); break; - case ACT_RANGE_ATTACK2: - // grunt is going to a secondary long range attack. This may be a thrown - // grenade or fired grenade, we must determine which and pick proper sequence - if (pev->weapons & FGRUNT_HANDGRENADE) - { - // get toss anim - iSequence = LookupSequence("throwgrenade"); - } + + case MEDIC_AE_SHOWGUN: + if ( FBitSet( pev->weapons, MEDIC_EAGLE ) ) + SetBodygroup( MEDIC_GUN_GROUP, MEDIC_GUN_EAGLE ); + else if ( FBitSet( pev->weapons, MEDIC_HANDGUN ) ) + SetBodygroup( MEDIC_GUN_GROUP, MEDIC_GUN_PISTOL ); else - { - // get launch anim - iSequence = LookupSequence("launchgrenade"); - } + SetBodygroup( MEDIC_GUN_GROUP, MEDIC_GUN_NEEDLE ); break; - case ACT_RUN: - if (pev->health <= FGRUNT_LIMP_HEALTH) - { - // limp! - iSequence = LookupActivity(ACT_RUN_HURT); - } - else - { - iSequence = LookupActivity(NewActivity); - } + + case MEDIC_AE_HIDENEEDLE: + SetBodygroup( MEDIC_GUN_GROUP, MEDIC_GUN_NONE ); break; - case ACT_WALK: - if (pev->health <= FGRUNT_LIMP_HEALTH) - { - // limp! - iSequence = LookupActivity(ACT_WALK_HURT); - } - else - { - iSequence = LookupActivity(NewActivity); - } + case MEDIC_AE_HIDEGUN: + SetBodygroup( MEDIC_GUN_GROUP, MEDIC_GUN_NONE ); break; - case ACT_IDLE: - if (m_MonsterState == MONSTERSTATE_COMBAT) + case HGRUNT_ALLY_AE_DROP_GUN: + if ( FBitSet( pev->weapons, MEDIC_EAGLE | MEDIC_HANDGUN ) ) { - NewActivity = ACT_IDLE_ANGRY; + DropMyItems(FALSE); } - iSequence = LookupActivity(NewActivity); break; -#if 1 - - case ACT_FLINCH_LEFTLEG: - case ACT_FLINCH_RIGHTLEG: - case ACT_FLINCH_LEFTARM: - case ACT_FLINCH_RIGHTARM: - { - NewActivity = ACT_SMALL_FLINCH; - iSequence = LookupActivity(NewActivity); - } - break; -#endif - - default: - iSequence = LookupActivity(NewActivity); + case HGRUNT_ALLY_AE_RELOAD: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/desert_eagle_reload.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions(bits_COND_NO_AMMO_LOADED); break; - } - - m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present - - // Set to the desired anim, or default anim if the desired is not present - if (iSequence > ACTIVITY_NOT_AVAILABLE) + case HGRUNT_ALLY_AE_BURST1: { - if (pev->sequence != iSequence || !m_fSequenceLoops) - { - pev->frame = 0; + if (FBitSet(pev->weapons, MEDIC_EAGLE)) { + FirePistol("weapons/desert_eagle_fire.wav", BULLET_MONSTER_357); + } else if (FBitSet(pev->weapons, MEDIC_HANDGUN)) { + FirePistol("weapons/pl_gun3.wav", BULLET_MONSTER_9MM); } - - pev->sequence = iSequence; // Set to the reset anim (if it's there) - ResetSequenceInfo(); - SetYawSpeed(); } - else + break; + case HGRUNT_ALLY_AE_BURST2: + case HGRUNT_ALLY_AE_BURST3: + break; + case HGRUNT_ALLY_AE_KICK: { - // Not available try to get default anim - ALERT(at_console, "%s has no sequence for act:%d\n", STRING(pev->classname), NewActivity); - pev->sequence = 0; // Set to the reset anim (if it's there) + KickImpl(gSkillData.medicDmgKick); + } + break; + default: + CHFGrunt::HandleAnimEvent(pEvent); + break; } - - -#ifdef DEBUG - UTIL_PrintActivity(this); -#endif } - -//========================================================= -// CHGruntRepel - when triggered, spawns a monster_human_grunt -// repelling down a line. -//========================================================= - -class CFGruntRepel : public CBaseMonster +BOOL CMedic::CheckRangeAttack1(float flDot, float flDist) { -public: - virtual void Spawn(void); - virtual void Precache(void); - virtual void EXPORT RepelUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); - int m_iSpriteTexture; // Don't save, precache -}; - - - -//========================================================= -// CHGruntRepel - when triggered, spawns a monster_human_grunt -// repelling down a line. -//========================================================= - -LINK_ENTITY_TO_CLASS(monster_grunt_ally_repel, CFGruntRepel); + return FBitSet( pev->weapons, MEDIC_EAGLE | MEDIC_HANDGUN ) && CHFGrunt::CheckRangeAttack1(flDot, flDist); +} -void CFGruntRepel::Spawn(void) +BOOL CMedic::CheckRangeAttack2(float flDot, float flDist) { - Precache(); - pev->solid = SOLID_NOT; - - SetUse(&CFGruntRepel::RepelUse); + return FALSE; } -void CFGruntRepel::Precache(void) +void CMedic::GibMonster() { - UTIL_PrecacheOther("monster_human_grunt_ally"); - m_iSpriteTexture = PRECACHE_MODEL("sprites/rope.spr"); + if ( FBitSet( pev->weapons, MEDIC_EAGLE | MEDIC_HANDGUN ) && GetBodygroup(MEDIC_GUN_GROUP) != MEDIC_GUN_NONE ) + {// throw a gun if the grunt has one + DropMyItems(TRUE); + } + CTalkMonster::GibMonster(); } -void CFGruntRepel::RepelUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value) +void CMedic::DropMyItems(BOOL isGibbed) { - 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(&CBeam::SUB_Remove); - pBeam->pev->nextthink = gpGlobals->time + -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5; - - UTIL_Remove(this); + if (!isGibbed) { + SetBodygroup( MEDIC_GUN_GROUP, MEDIC_GUN_NONE ); + } + Vector vecGunPos; + Vector vecGunAngles; + GetAttachment( 0, vecGunPos, vecGunAngles ); + if (FBitSet(pev->weapons, MEDIC_EAGLE)) + DropMyItem("weapon_eagle", vecGunPos, vecGunAngles, isGibbed); + else if (FBitSet(pev->weapons, MEDIC_HANDGUN)) { + DropMyItem("weapon_9mmhandgun", vecGunPos, vecGunAngles, isGibbed); + } } - - - - - -//========================================================= -// DEAD FGRUNT PROP -//========================================================= -class CDeadFGrunt : public CBaseMonster +void CMedic::FirePistol(const char *shotSound , Bullet bullet) { -public: - void Spawn(void); - int Classify(void) { return CLASS_HUMAN_MILITARY; } + UTIL_MakeVectors( pev->angles ); + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); - void KeyValue(KeyValueData *pkvd); + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); + pev->effects = EF_MUZZLEFLASH; - virtual int Save(CSave &save); - virtual int Restore(CRestore &restore); - static TYPEDESCRIPTION m_SaveData[]; + FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_2DEGREES, 1024, bullet ); - int m_iPose;// which sequence to display -- temporary, don't need to save - int m_iHead; - static const char *m_szPoses[6]; -}; + int pitchShift = RANDOM_LONG( 0, 20 ); + // Only shift about half the time + if( pitchShift > 10 ) + pitchShift = 0; + else + pitchShift -= 5; + EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, shotSound, 1, ATTN_NORM, 0, 100 + pitchShift ); + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); -LINK_ENTITY_TO_CLASS(monster_human_grunt_ally_dead, CDeadFGrunt); + m_cAmmoLoaded--;// take away a bullet! +} -TYPEDESCRIPTION CDeadFGrunt::m_SaveData[] = +void CMedic::StartFollowingHealTarget(CBaseEntity *pTarget) { - DEFINE_FIELD(CDeadFGrunt, m_iHead, FIELD_INTEGER), -}; + if( m_pCine ) + m_pCine->CancelScript(); -IMPLEMENT_SAVERESTORE(CDeadFGrunt, CBaseMonster); + if( m_hEnemy != 0 ) + m_IdealMonsterState = MONSTERSTATE_ALERT; -const char *CDeadFGrunt::m_szPoses[] = { "deadstomach", "deadside", "deadsitting", "dead_on_back", "dead_on_stomach", "dead_headcrabed" }; + m_hTargetEnt = pTarget; + ClearConditions( bits_COND_CLIENT_PUSH ); + ClearSchedule(); + ChangeSchedule(GetScheduleOfType(SCHED_MEDIC_HEAL)); + ALERT(at_aiconsole, "Medic started to follow injured %s\n", STRING(pTarget->pev->classname)); +} -void CDeadFGrunt::KeyValue(KeyValueData *pkvd) +void CMedic::StopHealing() { - if (FStrEq(pkvd->szKeyName, "pose")) - { - m_iPose = atoi(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "head")) - { - m_iHead = atoi(pkvd->szValue); - pkvd->fHandled = TRUE; + m_fHealing = FALSE; + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "common/null.wav", 1, ATTN_NORM ); + if (m_hTargetEnt != 0 && !m_hTargetEnt->IsPlayer()) { + if(m_movementGoal & MOVEGOAL_TARGETENT) + RouteClear(); // Stop him from walking toward the player + m_hTargetEnt = 0; + if( m_hEnemy != 0 ) + m_IdealMonsterState = MONSTERSTATE_COMBAT; } - else - CBaseMonster::KeyValue(pkvd); } - -//========================================================= -// ********** DeadHGrunt SPAWN ********** -//========================================================= -void CDeadFGrunt::Spawn(void) +CBaseEntity* CMedic::HealTarget() { - 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"); + if (m_hTargetEnt != 0 && m_hTargetEnt->IsAlive() && (m_hTargetEnt->pev->health < m_hTargetEnt->pev->max_health) && + ((m_hTargetEnt->MyMonsterPointer() && IRelationship(m_hTargetEnt) < R_DL) || m_hTargetEnt->IsPlayer())) { + return m_hTargetEnt; } + return 0; +} - // Corpses have less health - pev->health = 8; - - - // Select a random head. - if (m_iHead == -1) +bool CMedic::CheckHealCharge() +{ + if ( !HasHealCharge() ) { - SetBodygroup(HEAD_GROUP, RANDOM_LONG(0, FGRUNT_NUM_HEADS - 1)); + if ( !m_fDepleteLine ) + { + PlaySentence( "MG_NOTHEAL", 2, VOL_NORM, ATTN_IDLE ); + m_fDepleteLine = TRUE; + } + return false; } + return true; +} - if (pev->weapons == 0) - { - SetBodygroup(HEAD_GROUP, HEAD_GASMASK); - SetBodygroup(TORSO_GROUP, TORSO_BACKPACK); - SetBodygroup(GUN_GROUP, GUN_MP5); - } - else if (FBitSet(pev->weapons, FGRUNT_9MMAR)) +bool CMedic::ReadyToHeal() +{ + if( m_MonsterState == MONSTERSTATE_SCRIPT ) { - SetBodygroup(TORSO_GROUP, TORSO_BACKPACK); - SetBodygroup(GUN_GROUP, GUN_MP5); + if( !m_pCine ) + return false; + if( !m_pCine->CanInterrupt() ) + return false; } -#if 0 - // 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; - } -#else - // Until we get head support, set these to default. - pev->body = 0; - pev->skin = 0; -#endif + if( !IsAlive() ) + return false; - MonsterInitDead(); + return HasHealCharge() && !InHealSchedule(); +} + +bool CMedic::InHealSchedule() +{ + return m_pSchedule == slMedicHeal; } diff --git a/dlls/gearbox/fgrunt.h b/dlls/gearbox/fgrunt.h deleted file mode 100644 index 1697ec9d..00000000 --- a/dlls/gearbox/fgrunt.h +++ /dev/null @@ -1,140 +0,0 @@ -/*** -* -* Copyright (c) 1996-2001, 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. -* -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. -* -****/ -#ifndef FGRUNT_H -#define FGRUNT_H - - -//========================================================= -// CFGrunt -//========================================================= -class CFGrunt : public CTalkMonster -{ -public: -#if 1 - virtual void KeyValue(KeyValueData *pkvd); -#endif - - void Spawn(void); - void Precache(void); - void SetYawSpeed(void); - int ISoundMask(void); - void AlertSound(void); - int Classify(void); - void HandleAnimEvent(MonsterEvent_t *pEvent); - void PrescheduleThink( void ); - - void RunTask(Task_t *pTask); - void StartTask(Task_t *pTask); - virtual int ObjectCaps(void) { return CTalkMonster::ObjectCaps() | FCAP_IMPULSE_USE; } - int TakeDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); - BOOL FCanCheckAttacks(void); - BOOL CheckMeleeAttack1(float flDot, float flDist); - BOOL CheckRangeAttack1(float flDot, float flDist); - BOOL CheckRangeAttack2(float flDot, float flDist); - - void DeclineFollowing(void); - - // AI functions - void SetActivity(Activity newActivity); - - // Override these to set behavior - Schedule_t *GetScheduleOfType(int Type); - Schedule_t *GetSchedule(void); - Schedule_t *GetSquadSchedule(void); - MONSTERSTATE GetIdealState(void); - - void DeathSound(void); - void PainSound(void); - - virtual void TalkInit(void); - - void TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); - void Killed(entvars_t *pevAttacker, int iGib); - -#if 1 - - virtual BOOL IsMedic(void) const { return FALSE; } - - virtual CBaseEntity* DropGun(const Vector& vecSrc, const Vector& vecAngles, char* szClassname = NULL); - - void Fire( - const Vector& vecShootOrigin, - const Vector& vecShoorDir, - const Vector& vecSpread, - int model, - int effects = EF_MUZZLEFLASH, - int bulletType = BULLET_MONSTER_MP5, - int soundType = TE_BOUNCE_SHELL); - - Vector GetGunPosition(void); - void Shoot(void); - void Shotgun(void); - void Saw(void); - - virtual void GibMonster(void); - void SpeakSentence(void); - - int IRelationship(CBaseEntity *pTarget); - - virtual BOOL FOkToSpeak(void); - void JustSpoke(void); - - CBaseEntity *Kick(void); -#endif - - virtual int Save(CSave &save); - virtual int Restore(CRestore &restore); - static TYPEDESCRIPTION m_SaveData[]; - - float m_painTime; - float m_checkAttackTime; - BOOL m_lastAttackCheck; - - // UNDONE: What is this for? It isn't used? - float m_flPlayerDamage;// how much pain has the player inflicted on me? - - - - // checking the feasibility of a grenade toss is kind of costly, so we do it every couple of seconds, - // not every server frame. - float m_flNextGrenadeCheck; - float m_flNextPainTime; - float m_flLastEnemySightTime; - - Vector m_vecTossVelocity; - - BOOL m_fThrowGrenade; - BOOL m_fStanding; - BOOL m_fFirstEncounter;// only put on the handsign show in the squad's first encounter. - int m_cClipSize; - - int m_voicePitch; - - int m_iBrassShell; - int m_iShotgunShell; - int m_iSawShell; - - int m_iSentence; - - static const char *pGruntSentences[]; - - CUSTOM_SCHEDULES; - - int head; - int torso; -}; - - -#endif // FGRUNT_H \ No newline at end of file diff --git a/dlls/gearbox/fgrunt_medic.cpp b/dlls/gearbox/fgrunt_medic.cpp deleted file mode 100644 index 320cf726..00000000 --- a/dlls/gearbox/fgrunt_medic.cpp +++ /dev/null @@ -1,622 +0,0 @@ -/*** -* -* Copyright (c) 1996-2001, 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. -* -****/ -//========================================================= -// friendly grunt - medic -//========================================================= -// UNDONE: Holster weapon? - -#include "extdll.h" -#include "plane.h" -#include "util.h" -#include "cbase.h" -#include "monsters.h" -#include "talkmonster.h" -#include "schedule.h" -#include "animation.h" -#include "weapons.h" -#include "talkmonster.h" -#include "soundent.h" -#include "effects.h" -#include "customentity.h" -#include "fgrunt.h" - - -extern int g_fGruntQuestion; - -extern Schedule_t slIdleFgStand[]; -extern Schedule_t slFgFaceTarget[]; - -//========================================================= -// monster-specific DEFINE's -//========================================================= -#define MGRUNT_CLIP_SIZE_EAGLE 7 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! -#define MGRUNT_CLIP_SIZE_9MMHANDGUN 17 // Same as above -#define MGRUNT_VOL 0.35 // volume of grunt sounds -#define MGRUNT_ATTN ATTN_NORM // attenutation of grunt sentences -#define MGRUNT_LIMP_HEALTH 20 -#define MGRUNT_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot. -#define MGRUNT_NUM_HEADS 2 // how many grunt heads are there? -#define MGRUNT_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill -#define MGRUNT_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences -#define MGRUNT_MAX_HEALTH_RESTORE 10 // Maximum health restore. // 200 - -#define MGRUNT_EAGLE (1 << 0) -#define MGRUNT_9MMHANDGUN (1 << 1) -#define MGRUNT_NEEDLE (1 << 2) - -#define HEAD_GROUP 2 -#define GUN_GROUP 3 - -#define HEAD_WHITE 0 -#define HEAD_BLACK 1 - -#define GUN_EAGLE 0 -#define GUN_9MMHANDGUN 1 -#define GUN_NEEDLE 2 -#define GUN_NONE 3 - - -//========================================================= -// Monster's Anim Events Go Here -//========================================================= -#define MGRUNT_AE_BURST1 ( 4 ) -#define MGRUNT_AE_BURST2 ( 5 ) -#define MGRUNT_AE_BURST3 ( 6 ) - -#define MGRUNT_AE_GUN_HOLSTER ( 15 ) -#define MGRUNT_AE_NEEDLE_DRAW ( 16 ) -#define MGRUNT_AE_NEEDLE_STORE ( 17 ) -#define MGRUNT_AE_GUN_DRAW ( 18 ) - -static const char* g_pszDeathSounds[] = -{ - "fgrunt/death1.wav", - "fgrunt/death2.wav", - "fgrunt/death3.wav", - "fgrunt/death4.wav", - "fgrunt/death5.wav", - "fgrunt/death6.wav", -}; - -static const char* g_pszPainSounds[] = -{ - "fgrunt/pain1.wav", - "fgrunt/pain2.wav", - "fgrunt/pain3.wav", - "fgrunt/pain4.wav", - "fgrunt/pain5.wav", - "fgrunt/pain6.wav", -}; - -#define MGRUNT_NUM_DEATH_SOUNDS ARRAYSIZE( g_pszDeathSounds ) -#define MGRUNT_NUM_PAIN_SOUNDS ARRAYSIZE( g_pszPainSounds ) - - -/* - - MGrunt animations. - -*/ - -/* -pull_needle (23) ACT_SIGNAL3 -store_needle (24) ACT_TWITCH -give_shot (25) ACT_COWER -heal_crouch (46) ACT_INSPECT_WALL -*/ - -class CMGrunt : public CFGrunt -{ -public: - void TalkInit(void); - - void Spawn(void); - void Precache(void); - void HandleAnimEvent(MonsterEvent_t* pEvent); - void RunTask(Task_t *pTask); - void StartTask(Task_t *pTask); - - int Save(CSave &save); - int Restore(CRestore &restore); - static TYPEDESCRIPTION m_SaveData[]; - - BOOL IsMedic(void) const { return TRUE; } - - CBaseEntity* DropGun(const Vector& vecSrc, const Vector& vecAngles, char* szClassname = NULL); - - void FireEagle( void ); - void FirePistol( void ); - - CUSTOM_SCHEDULES; - - static const char *pMGruntSentences[]; - - int m_nHealthRestore; -}; - -LINK_ENTITY_TO_CLASS(monster_human_medic_ally, CMGrunt); - -TYPEDESCRIPTION CMGrunt::m_SaveData[] = -{ - DEFINE_FIELD(CMGrunt, m_nHealthRestore, FIELD_INTEGER), -}; - -IMPLEMENT_SAVERESTORE(CMGrunt, CTalkMonster); - -//========================================================= -// monster-specific schedule types -//========================================================= -enum -{ - SCHED_MGRUNT_FIRST_SCHEDULE = LAST_COMMON_SCHEDULE + 1, - SCHED_MGRUNT_HEAL_ALLY, - SCHED_MGRUNT_HEAL_PLAYER, -}; - -//========================================================= -// monster-specific tasks -//========================================================= -enum -{ - TASK_MGRUNT_NEEDLE_PULL = LAST_COMMON_TASK + 1, - TASK_MGRUNT_NEEDLE_STORE, - TASK_MGRUNT_GIVE_SHOT, -}; - - - - -//========================================================= -// Medic draws the needle -//========================================================= -Task_t tlMgNeedlePull[] = -{ - { TASK_STOP_MOVING, 0 }, - { TASK_FACE_TARGET, (float)0 }, - { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, -}; - -Schedule_t slMgNeedlePull[] = -{ - { - tlMgNeedlePull, // task array pointer. - ARRAYSIZE(tlMgNeedlePull), // task count - 0, // COND interrupts (.i.e Light damage, heavy damage) - 0, // soundmask (.i.e hear danger...) - "MGrunt Needle Pull" // name - } -}; - - -//========================================================= -// Medic stores the needle -//========================================================= -Task_t tlMgNeedleStore[] = -{ - { TASK_STOP_MOVING, 0 }, - { TASK_FACE_TARGET, (float)0 }, - { TASK_SET_ACTIVITY, (float)ACT_TWITCH }, -}; - -Schedule_t slMgNeedleStore[] = -{ - { - tlMgNeedleStore, - ARRAYSIZE(tlMgNeedleStore), - 0, - 0, - "MGrunt Needle Store" - } -}; - - -//========================================================= -// Medic gives a needle shot -//========================================================= -Task_t tlMgGiveShot[] = -{ - { TASK_STOP_MOVING, 0 }, - { TASK_FACE_TARGET, (float)0 }, - { TASK_SET_ACTIVITY, (float)ACT_COWER }, -}; - -Schedule_t slMgGiveShot[] = -{ - { - tlMgGiveShot, - ARRAYSIZE(tlMgGiveShot), - 0, - 0, - "MGrunt Give Shot" - } -}; - -//========================================================= -// Medic heals a target (crouched) -//========================================================= -Task_t tlMgHealCrouch[] = -{ - { TASK_STOP_MOVING, 0 }, - { TASK_FACE_TARGET, (float)0 }, - { TASK_SET_ACTIVITY, (float)ACT_INSPECT_WALL }, -}; - -Schedule_t slMgHealCrouch[] = -{ - { - tlMgHealCrouch, - ARRAYSIZE(tlMgHealCrouch), - 0, - 0, - "MGrunt Heal Crouch" - } -}; - - - -DEFINE_CUSTOM_SCHEDULES(CMGrunt) -{ - slMgNeedlePull, - slMgNeedleStore, - slMgGiveShot, -}; - -IMPLEMENT_CUSTOM_SCHEDULES(CMGrunt, CFGrunt); - -const char *CMGrunt::pMGruntSentences[] = -{ - "MG_HEAL", - "MG_NOTHEAL", -}; - -typedef enum -{ - MGRUNT_SENT_NONE = -1, - MGRUNT_SENT_HEAL = 0, - MGRUNT_SENT_NOTHEAL, -} MGRUNT_SENTENCE_TYPES; - - -//========================================================= -// Spawn -//========================================================= -void CMGrunt::Spawn() -{ - Precache(); - - SET_MODEL(ENT(pev), "models/hgrunt_medic.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->health = gSkillData.medicAllyHealth; - pev->view_ofs = Vector(0, 0, 50);// position of the eyes relative to monster's origin. - m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello - m_MonsterState = MONSTERSTATE_NONE; - - pev->body = 0; // gun in holster - - m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; - - // Medic has no torso support. - torso = 0; - - // Select a random head. - if (head == -1) - { - SetBodygroup(HEAD_GROUP, RANDOM_LONG(0, MGRUNT_NUM_HEADS - 1)); - } - else - { - SetBodygroup(HEAD_GROUP, head); - } - - if (pev->weapons == 0) - { - // initialize to original values - pev->weapons = MGRUNT_9MMHANDGUN; - } - - // Setup bodygroups. - if (FBitSet(pev->weapons, MGRUNT_EAGLE)) - { - SetBodygroup(GUN_GROUP, GUN_EAGLE); - m_cClipSize = MGRUNT_CLIP_SIZE_EAGLE; - } - else if (FBitSet(pev->weapons, MGRUNT_9MMHANDGUN)) - { - SetBodygroup(GUN_GROUP, GUN_9MMHANDGUN); - m_cClipSize = MGRUNT_CLIP_SIZE_9MMHANDGUN; - } - else - { - ALERT(at_console, "ERROR: entity %s uses unsupported weapon flags %d\n", pev->classname, pev->weapons); - m_cClipSize = -1; - } - - m_cAmmoLoaded = m_cClipSize; - - m_nHealthRestore = MGRUNT_MAX_HEALTH_RESTORE; - -#if 0 - - const char* szSeqName = "pull_needle"; - int seq = LookupSequence(szSeqName); - int i; - - for (i = 0; i < ACT_FLINCH_RIGHTLEG; i++) - { - if (i == seq) - { - ALERT(at_console, "%s has sequence: %s with activity value (%d).\n", pev->classname, szSeqName, i); - break; - } - } - - szSeqName = "store_needle"; - seq = LookupSequence(szSeqName); - - for (i = 0; i < ACT_FLINCH_RIGHTLEG; i++) - { - if (i == seq) - { - ALERT(at_console, "%s has sequence: %s with activity value (%d).\n", pev->classname, szSeqName, i); - break; - } - } - - szSeqName = "give_shot"; - seq = LookupSequence(szSeqName); - - for (i = 0; i < ACT_FLINCH_RIGHTLEG; i++) - { - if (i == seq) - { - ALERT(at_console, "%s has sequence: %s with activity value (%d).\n", pev->classname, szSeqName, i); - break; - } - } - - szSeqName = "heal_crouch"; - seq = LookupSequence(szSeqName); - - for (i = 0; i < ACT_FLINCH_RIGHTLEG; i++) - { - if (i == seq) - { - ALERT(at_console, "%s has sequence: %s with activity value (%d).\n", pev->classname, szSeqName, i); - break; - } - } - -#endif - - MonsterInit(); - SetUse(&CMGrunt::FollowerUse); -} - -//========================================================= -// Precache - precaches all resources this monster needs -//========================================================= -void CMGrunt::Precache() -{ - PRECACHE_MODEL("models/hgrunt_medic.mdl"); - - PRECACHE_SOUND("barney/ba_attack1.wav"); - PRECACHE_SOUND("barney/ba_attack2.wav"); - - PRECACHE_SOUND("barney/desert_eagle_fire.wav"); - - PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event - - PRECACHE_SOUND("barney/medic_give_shot.wav"); - - PRECACHE_SOUND_ARRAY( g_pszDeathSounds ); - - PRECACHE_SOUND_ARRAY( g_pszPainSounds ); - - // every new barney must call this, otherwise - // when a level is loaded, nobody will talk (time is reset to 0) - TalkInit(); - CTalkMonster::Precache(); -} - - -//========================================================= -// Purpose: -//========================================================= -void CMGrunt::TalkInit( void ) -{ - CFGrunt::TalkInit(); -} - -//========================================================= -// Purpose: -//========================================================= -void CMGrunt::HandleAnimEvent(MonsterEvent_t* pEvent) -{ - switch (pEvent->event) - { - - case MGRUNT_AE_BURST1: - { - if (FBitSet(pev->weapons, MGRUNT_EAGLE)) - { - FireEagle(); - } - else - { - FirePistol(); - } - } - break; - - case MGRUNT_AE_BURST2: - break; - - case MGRUNT_AE_BURST3: - break; - - - case MGRUNT_AE_GUN_DRAW: - { - if (FBitSet(pev->weapons, MGRUNT_EAGLE)) - { - SetBodygroup(GUN_GROUP, GUN_EAGLE); - } - else if (FBitSet(pev->weapons, MGRUNT_9MMHANDGUN)) - { - SetBodygroup(GUN_GROUP, MGRUNT_9MMHANDGUN); - } - } - break; - - case MGRUNT_AE_GUN_HOLSTER: - case MGRUNT_AE_NEEDLE_STORE: - { - SetBodygroup( GUN_GROUP, GUN_NONE ); - } - break; - - case MGRUNT_AE_NEEDLE_DRAW: - { - SetBodygroup( GUN_GROUP, GUN_NEEDLE ); - } - break; - - default: - CFGrunt::HandleAnimEvent( pEvent ); - break; - } -} - -//========================================================= -// DropGun -//========================================================= -CBaseEntity* CMGrunt::DropGun(const Vector& vecGunPos, const Vector& vecGunAngles, char* szClassname) -{ - CBaseEntity* pGun = NULL; - - if (szClassname && *szClassname) - { - pGun = DropItem(szClassname, vecGunPos, vecGunAngles); - - if (pGun) - { - return pGun; - } - else - { - ALERT(at_console, "ERROR: Could not find classname %s. No such class.\n", szClassname); - } - } - - if (FBitSet(pev->weapons, MGRUNT_EAGLE)) - { - pGun = DropItem("weapon_eagle", vecGunPos, vecGunAngles); - } - else - { - pGun = DropItem("weapon_9mmhandgun", vecGunPos, vecGunAngles); - } - - return pGun; -} - -//========================================================= -// Shoot -//========================================================= -void CMGrunt::FireEagle(void) -{ - Vector vecShootOrigin; - - UTIL_MakeVectors(pev->angles); - vecShootOrigin = pev->origin + Vector(0, 0, 55); - Vector vecShootDir = ShootAtEnemy(vecShootOrigin); - - Vector angDir = UTIL_VecToAngles(vecShootDir); - SetBlending(0, angDir.x); - pev->effects = EF_MUZZLEFLASH; - - FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_1DEGREES, 1024, BULLET_PLAYER_357); - - int pitchShift = RANDOM_LONG(0, 20); - - // Only shift about half the time - if (pitchShift > 10) - pitchShift = 0; - else - pitchShift -= 5; - EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, "weapons/desert_eagle_fire.wav", 1, ATTN_NORM, 0, 100 + pitchShift); - - CSoundEnt::InsertSound(bits_SOUND_COMBAT, pev->origin, 384, 0.3); - - // UNDONE: Reload? - m_cAmmoLoaded--;// take away a bullet! -} - -//========================================================= -// Shoot -//========================================================= -void CMGrunt::FirePistol(void) -{ - Vector vecShootOrigin; - - UTIL_MakeVectors(pev->angles); - vecShootOrigin = pev->origin + Vector(0, 0, 55); - Vector vecShootDir = ShootAtEnemy(vecShootOrigin); - - Vector angDir = UTIL_VecToAngles(vecShootDir); - SetBlending(0, angDir.x); - pev->effects = EF_MUZZLEFLASH; - - FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_2DEGREES, 1024, BULLET_MONSTER_9MM); - - int pitchShift = RANDOM_LONG(0, 20); - - // Only shift about half the time - if (pitchShift > 10) - pitchShift = 0; - else - pitchShift -= 5; - EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, "barney/ba_attack2.wav", 1, ATTN_NORM, 0, 100 + pitchShift); - - CSoundEnt::InsertSound(bits_SOUND_COMBAT, pev->origin, 384, 0.3); - - // UNDONE: Reload? - m_cAmmoLoaded--;// take away a bullet! -} - - -void CMGrunt::StartTask(Task_t *pTask) -{ - switch (pTask->iTask) - { - case 0: - default: - break; - } -} - -void CMGrunt::RunTask(Task_t *pTask) -{ - switch (pTask->iTask) - { - case 0: - default: - break; - } -} diff --git a/dlls/gearbox/fgrunt_torch.cpp b/dlls/gearbox/fgrunt_torch.cpp deleted file mode 100644 index 587993a5..00000000 --- a/dlls/gearbox/fgrunt_torch.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/*** -* -* Copyright (c) 1996-2001, 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. -* -****/ -//========================================================= -// friendly grunt - torch -//========================================================= -// UNDONE: Holster weapon? - -#include "extdll.h" -#include "plane.h" -#include "util.h" -#include "cbase.h" -#include "monsters.h" -#include "talkmonster.h" -#include "schedule.h" -#include "animation.h" -#include "weapons.h" -#include "talkmonster.h" -#include "soundent.h" -#include "effects.h" -#include "customentity.h" -#include "fgrunt.h" - -#if 0 -class CHTorch : public CFGrunt -{ -public: - - void Spawn(void); - void Precache(void); - - - int Save(CSave &save); - int Restore(CRestore &restore); - static TYPEDESCRIPTION m_SaveData[]; - - int m_iTemp; -}; - -LINK_ENTITY_TO_CLASS(monster_human_torch_ally, CHTorch); - -TYPEDESCRIPTION CHTorch::m_SaveData[] = -{ - DEFINE_FIELD(CHTorch, m_iTemp, FIELD_INTEGER), -}; - -IMPLEMENT_SAVERESTORE(CHTorch, CFGrunt); -#endif \ No newline at end of file diff --git a/dlls/gearbox/massn.cpp b/dlls/gearbox/massn.cpp index cc3b5676..1f14fd8c 100644 --- a/dlls/gearbox/massn.cpp +++ b/dlls/gearbox/massn.cpp @@ -336,41 +336,9 @@ void CMassn::DeathSound(void) class CAssassinRepel : public CHGruntRepel { public: - void Precache(void); - void EXPORT RepelUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); + const char* TrooperName() { + return "monster_male_assassin"; + } }; -LINK_ENTITY_TO_CLASS(monster_assassin_repel, CAssassinRepel); - -void CAssassinRepel::Precache(void) -{ - UTIL_PrecacheOther("monster_male_assassin"); - m_iSpriteTexture = PRECACHE_MODEL("sprites/rope.spr"); -} - -void CAssassinRepel::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_male_assassin", 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(&CBeam::SUB_Remove); - pBeam->pev->nextthink = gpGlobals->time + -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5; - - UTIL_Remove(this); -} +LINK_ENTITY_TO_CLASS(monster_assassin_repel, CAssassinRepel) diff --git a/dlls/gearbox/otis.cpp b/dlls/gearbox/otis.cpp index acdf1d27..8e15366a 100644 --- a/dlls/gearbox/otis.cpp +++ b/dlls/gearbox/otis.cpp @@ -269,24 +269,6 @@ void COtis::TalkInit() } -static BOOL IsFacing(entvars_t *pevTest, const Vector &reference) -{ - Vector vecDir = (reference - pevTest->origin); - vecDir.z = 0; - vecDir = vecDir.Normalize(); - Vector forward, angle; - angle = pevTest->v_angle; - angle.x = 0; - UTIL_MakeVectorsPrivate(angle, forward, NULL, NULL); - // He's facing me, he meant it - if (DotProduct(forward, vecDir) > 0.96) // +/- 15 degrees or so - { - return TRUE; - } - return FALSE; -} - - int COtis::TakeDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) { // make sure friends talk about it if player hurts talkmonsters... diff --git a/dlls/hgrunt.cpp b/dlls/hgrunt.cpp index 5c4d7ec5..5d11ca8a 100644 --- a/dlls/hgrunt.cpp +++ b/dlls/hgrunt.cpp @@ -118,7 +118,7 @@ enum //========================================================= // monster-specific conditions //========================================================= -#define bits_COND_GRUNT_NOFIRE ( bits_COND_SPECIAL1 ) +#define bits_COND_GRUNT_NOFIRE ( bits_COND_NOFIRE ) LINK_ENTITY_TO_CLASS( monster_human_grunt, CHGrunt ) @@ -2285,13 +2285,19 @@ void CHGruntRepel::Spawn( void ) { Precache(); pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; SetUse( &CHGruntRepel::RepelUse ); } +const char* CHGruntRepel::TrooperName() +{ + return "monster_human_grunt"; +} + void CHGruntRepel::Precache( void ) { - UTIL_PrecacheOther( "monster_human_grunt" ); + UTIL_PrecacheOther( TrooperName() ); m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); } @@ -2304,8 +2310,16 @@ void CHGruntRepel::RepelUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_ return NULL; */ - CBaseEntity *pEntity = Create( "monster_human_grunt", pev->origin, pev->angles ); + CBaseEntity *pEntity = Create( TrooperName(), pev->origin, pev->angles ); + if (!pEntity) { + UTIL_Remove( this ); + return; + } CBaseMonster *pGrunt = pEntity->MyMonsterPointer(); + if (!pGrunt) { + UTIL_Remove( this ); + return; + } pGrunt->pev->movetype = MOVETYPE_FLY; pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); pGrunt->SetActivity( ACT_GLIDE ); diff --git a/dlls/hgrunt.h b/dlls/hgrunt.h index 818f5591..785b0e49 100644 --- a/dlls/hgrunt.h +++ b/dlls/hgrunt.h @@ -97,10 +97,11 @@ public: class CHGruntRepel : public CBaseMonster { public: - virtual void Spawn(void); - virtual void Precache(void); - virtual void EXPORT RepelUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); + void Spawn( void ); + void Precache( void ); + void EXPORT RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); int m_iSpriteTexture; // Don't save, precache + virtual const char* TrooperName(); }; #endif // HGRUNT_H diff --git a/dlls/schedule.h b/dlls/schedule.h index 6414ff15..cb465e3c 100644 --- a/dlls/schedule.h +++ b/dlls/schedule.h @@ -274,6 +274,9 @@ struct WayPoint_t #define bits_COND_SEE_CLIENT ( 1 << 21) // see a client #define bits_COND_SEE_NEMESIS ( 1 << 22) // see my nemesis +#define bits_COND_CLIENT_PUSH ( 1 << 26 ) // Clients can push ally monsters out of their way +#define bits_COND_NOFIRE ( 1 << 27) // no friendly fire + #define bits_COND_SPECIAL1 ( 1 << 28) // Defined by individual monster #define bits_COND_SPECIAL2 ( 1 << 29) // Defined by individual monster diff --git a/dlls/scientist.cpp b/dlls/scientist.cpp index 0b791fe4..c2402752 100644 --- a/dlls/scientist.cpp +++ b/dlls/scientist.cpp @@ -991,8 +991,8 @@ void CScientist::Heal( void ) int CScientist::FriendNumber( int arrayNumber ) { - static int array[3] = { 1, 2, 0 }; - if( arrayNumber < 3 ) + static int array[6] = { 1, 4, 2, 5, 0, 3 }; + if( arrayNumber < 6 ) return array[arrayNumber]; return arrayNumber; } @@ -1146,8 +1146,8 @@ int CSittingScientist::Classify( void ) int CSittingScientist::FriendNumber( int arrayNumber ) { - static int array[3] = { 2, 1, 0 }; - if( arrayNumber < 3 ) + static int array[6] = { 2, 5, 1, 4, 0, 3 }; + if( arrayNumber < 6 ) return array[arrayNumber]; return arrayNumber; } diff --git a/dlls/skill.h b/dlls/skill.h index d6899acd..bddd45c2 100644 --- a/dlls/skill.h +++ b/dlls/skill.h @@ -145,19 +145,19 @@ struct skilldata_t float pitdroneDmgWhip; float pitdroneDmgSpit; - float hgruntAllyHealth; - float hgruntAllyDmgKick; - float hgruntAllyShotgunPellets; - float hgruntAllyGrenadeSpeed; - - float medicAllyHealth; - float medicAllyDmgKick; - float medicAllyGrenadeSpeed; - float medicAllyHeal; - - float torchAllyHealth; - float torchAllyDmgKick; - float torchAllyGrenadeSpeed; + float fgruntHealth; + float fgruntDmgKick; + float fgruntShotgunPellets; + float fgruntGrenadeSpeed; + + float medicHealth; + float medicDmgKick; + float medicGrenadeSpeed; + float medicHeal; + + float torchHealth; + float torchDmgKick; + float torchGrenadeSpeed; float massnHealth; float massnDmgKick; diff --git a/dlls/talkmonster.cpp b/dlls/talkmonster.cpp index 9180f037..1b998717 100644 --- a/dlls/talkmonster.cpp +++ b/dlls/talkmonster.cpp @@ -48,7 +48,7 @@ TYPEDESCRIPTION CTalkMonster::m_SaveData[] = DEFINE_FIELD( CTalkMonster, m_hTalkTarget, FIELD_EHANDLE ), }; -IMPLEMENT_SAVERESTORE( CTalkMonster, CBaseMonster ) +IMPLEMENT_SAVERESTORE( CTalkMonster, CSquadMonster ) // array of friend names const char *CTalkMonster::m_szFriends[TLK_CFRIENDS] = @@ -56,6 +56,9 @@ const char *CTalkMonster::m_szFriends[TLK_CFRIENDS] = "monster_barney", "monster_scientist", "monster_sitting_scientist", + "monster_otis", + "monster_cleansuit_scientist", + "monster_sitting_cleansuit_scientist", }; //========================================================= @@ -69,7 +72,7 @@ Task_t tlIdleResponse[] = { TASK_TLK_RESPOND, (float)0 }, // Wait and then say my response { TASK_TLK_IDEALYAW, (float)0 }, // look at who I'm talking to { TASK_FACE_IDEAL, (float)0 }, - { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_TLK_EYECONTACT, (float)0 }, // Wait until speaker is done }; @@ -91,7 +94,7 @@ Task_t tlIdleSpeak[] = { TASK_TLK_SPEAK, (float)0 },// question or remark { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to { TASK_FACE_IDEAL, (float)0 }, - { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_TLK_EYECONTACT, (float)0 }, { TASK_WAIT_RANDOM, (float)0.5 }, }; @@ -112,7 +115,7 @@ Schedule_t slIdleSpeak[] = Task_t tlIdleSpeakWait[] = { - { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 },// Stop and talk + { TASK_SET_ACTIVITY, (float)ACT_IDLE },// Stop and talk { TASK_TLK_SPEAK, (float)0 },// question or remark { TASK_TLK_EYECONTACT, (float)0 },// { TASK_WAIT, (float)2 },// wait - used when sci is in 'use' mode to keep head turned @@ -134,7 +137,7 @@ Schedule_t slIdleSpeakWait[] = Task_t tlIdleHello[] = { - { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 },// Stop and talk + { TASK_SET_ACTIVITY, (float)ACT_IDLE },// Stop and talk { TASK_TLK_HELLO, (float)0 },// Try to say hello to player { TASK_TLK_EYECONTACT, (float)0 }, { TASK_WAIT, (float)0.5 },// wait a bit @@ -259,7 +262,7 @@ Task_t tlTlkIdleWatchClientStare[] = { TASK_TLK_STARE, (float)0 }, { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to { TASK_FACE_IDEAL, (float)0 }, - { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_TLK_EYECONTACT, (float)0 }, }; @@ -312,7 +315,7 @@ Task_t tlTlkIdleEyecontact[] = { { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to { TASK_FACE_IDEAL, (float)0 }, - { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done }; @@ -345,15 +348,15 @@ DEFINE_CUSTOM_SCHEDULES( CTalkMonster ) slTlkIdleEyecontact, }; -IMPLEMENT_CUSTOM_SCHEDULES( CTalkMonster, CBaseMonster ) +IMPLEMENT_CUSTOM_SCHEDULES( CTalkMonster, CSquadMonster ) void CTalkMonster::SetActivity( Activity newActivity ) { - if( newActivity == ACT_IDLE && IsTalking() ) - newActivity = ACT_SIGNAL3; +// if( newActivity == ACT_IDLE && IsTalking() ) +// newActivity = ACT_SIGNAL3; - if( newActivity == ACT_SIGNAL3 && ( LookupActivity( ACT_SIGNAL3 ) == ACTIVITY_NOT_AVAILABLE ) ) - newActivity = ACT_IDLE; +// if( newActivity == ACT_SIGNAL3 && ( LookupActivity( ACT_SIGNAL3 ) == ACTIVITY_NOT_AVAILABLE ) ) +// newActivity = ACT_IDLE; CBaseMonster::SetActivity( newActivity ); } @@ -617,7 +620,7 @@ CBaseEntity *CTalkMonster::EnumFriends( CBaseEntity *pPrevious, int listNumber, TraceResult tr; Vector vecCheck; - pszFriend = m_szFriends[FriendNumber( listNumber )]; + pszFriend = FriendByNumber( listNumber ); while( ( pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend ) ) ) { if( pFriend == this || !pFriend->IsAlive() ) @@ -648,7 +651,7 @@ void CTalkMonster::AlertFriends( void ) int i; // for each friend in this bsp... - for( i = 0; i < TLK_CFRIENDS; i++ ) + for( i = 0; i < NumberOfFriends(); i++ ) { while( ( pFriend = EnumFriends( pFriend, i, TRUE ) ) ) { @@ -668,7 +671,7 @@ void CTalkMonster::ShutUpFriends( void ) int i; // for each friend in this bsp... - for( i = 0; i < TLK_CFRIENDS; i++ ) + for( i = 0; i < NumberOfFriends(); i++ ) { while( ( pFriend = EnumFriends( pFriend, i, TRUE ) ) ) { @@ -691,7 +694,7 @@ void CTalkMonster::LimitFollowers( CBaseEntity *pPlayer, int maxFollowers ) count = 0; // for each friend in this bsp... - for( i = 0; i < TLK_CFRIENDS; i++ ) + for( i = 0; i < NumberOfFriends(); i++ ) { while( ( pFriend = EnumFriends( pFriend, i, FALSE ) ) ) { @@ -772,7 +775,7 @@ CBaseEntity *CTalkMonster::FindNearestFriend( BOOL fPlayer ) if( fPlayer ) cfriends = 1; else - cfriends = TLK_CFRIENDS; + cfriends = NumberOfFriends(); // for each type of friend... for( i = cfriends-1; i > -1; i-- ) @@ -780,7 +783,7 @@ CBaseEntity *CTalkMonster::FindNearestFriend( BOOL fPlayer ) if( fPlayer ) pszFriend = "player"; else - pszFriend = m_szFriends[FriendNumber( i )]; + pszFriend = FriendByNumber( i ); if( !pszFriend ) continue; @@ -1246,7 +1249,7 @@ Schedule_t *CTalkMonster::GetScheduleOfType( int Type ) break; } - return CBaseMonster::GetScheduleOfType( Type ); + return CSquadMonster::GetScheduleOfType( Type ); } //========================================================= @@ -1369,7 +1372,7 @@ void CTalkMonster::FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, U } else if( CanFollow() ) { - LimitFollowers( pCaller, 1 ); + LimitFollowers( pCaller, MaxFollowers() ); if( m_afMemory & bits_MEMORY_PROVOKED ) ALERT( at_console, "I'm not following you, you evil person!\n" ); diff --git a/dlls/talkmonster.h b/dlls/talkmonster.h index de438c4b..a6403077 100644 --- a/dlls/talkmonster.h +++ b/dlls/talkmonster.h @@ -19,6 +19,7 @@ #ifndef MONSTERS_H #include "monsters.h" #endif +#include "squadmonster.h" //========================================================= // Talking monster base class @@ -38,7 +39,7 @@ #define bit_saidHeard (1<<6) #define bit_saidSmelled (1<<7) -#define TLK_CFRIENDS 3 +#define TLK_CFRIENDS 6 typedef enum { @@ -95,7 +96,7 @@ enum LAST_TALKMONSTER_TASK // MUST be last }; -class CTalkMonster : public CBaseMonster +class CTalkMonster : public CSquadMonster { public: void TalkInit( void ); @@ -149,6 +150,9 @@ public: virtual void SetAnswerQuestion( CTalkMonster *pSpeaker ); virtual int FriendNumber( int arrayNumber ) { return arrayNumber; } + virtual const char* FriendByNumber( int arrayNumber ) { return m_szFriends[FriendNumber(arrayNumber)]; } + virtual int NumberOfFriends() { return TLK_CFRIENDS; } + virtual int MaxFollowers() { return 3; } virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); @@ -174,8 +178,8 @@ public: CUSTOM_SCHEDULES }; -// Clients can push talkmonsters out of their way -#define bits_COND_CLIENT_PUSH ( bits_COND_SPECIAL1 ) +BOOL IsFacing( entvars_t *pevTest, const Vector &reference ); + // Don't see a client right now. #define bits_COND_CLIENT_UNSEEN ( bits_COND_SPECIAL2 )