diff --git a/dlls/CAd/massn.cpp b/dlls/CAd/massn.cpp new file mode 100644 index 00000000..3b7eeb5d --- /dev/null +++ b/dlls/CAd/massn.cpp @@ -0,0 +1,1672 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// male assassin +//========================================================= + +//========================================================= +// Hit groups! +//========================================================= +/* + + 1 - Head + 2 - Stomach + 3 - Gun + +*/ + +#include "extdll.h" +#include "plane.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "squadmonster.h" +#include "weapons.h" +#include "talkmonster.h" +#include "soundent.h" +#include "effects.h" +#include "customentity.h" +#include "hgrunt.h" + +extern int g_fGruntQuestion; // true if an idle grunt asked a question. Cleared when someone answers. + +extern DLL_GLOBAL int g_iSkillLevel; + +extern Schedule_t slIdleStand[]; +extern Schedule_t slGruntFail[]; +extern Schedule_t slGruntCombatFail[]; +extern Schedule_t slGruntVictoryDance[]; +extern Schedule_t slGruntEstablishLineOfFire[]; +extern Schedule_t slGruntFoundEnemy[]; +extern Schedule_t slGruntCombatFace[]; +extern Schedule_t slGruntSignalSuppress[]; +extern Schedule_t slGruntSuppress[]; +extern Schedule_t slGruntWaitInCover[]; +extern Schedule_t slGruntTakeCover[]; +extern Schedule_t slGruntGrenadeCover[]; +extern Schedule_t slGruntTossGrenadeCover[]; +extern Schedule_t slGruntTakeCoverFromBestSound[]; +extern Schedule_t slGruntHideReload[]; +extern Schedule_t slGruntSweep[]; +extern Schedule_t slGruntRangeAttack1A[]; +extern Schedule_t slGruntRangeAttack1B[]; +extern Schedule_t slGruntRangeAttack2[]; +extern Schedule_t slGruntRepel[]; +extern Schedule_t slGruntRepelAttack[]; +extern Schedule_t slGruntRepelLand[]; + +class CMassn : public CHGrunt +{ +public: + void Spawn(void); + void Precache(void); + void HandleAnimEvent(MonsterEvent_t *pEvent); + void DeathSound(void); + void PainSound(void); + void IdleSound(void); + void SpeakSentence(void); + + int Save(CSave &save); + int Restore(CRestore &restore); + + //CBaseEntity *Kick(void); + Schedule_t *GetSchedule(void); + Schedule_t *GetScheduleOfType(int Type); + + int IRelationship(CBaseEntity *pTarget); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pGruntSentences[]; +}; + +LINK_ENTITY_TO_CLASS( monster_massn, CMassn ) + +TYPEDESCRIPTION CMassn::m_SaveData[] = +{ + DEFINE_FIELD( CMassn, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CMassn, m_flNextPainTime, FIELD_TIME ), + //DEFINE_FIELD( CMassn, m_flLastEnemySightTime, FIELD_TIME ), // don't save, go to zero + DEFINE_FIELD( CMassn, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CMassn, m_fThrowGrenade, FIELD_BOOLEAN ), + DEFINE_FIELD( CMassn, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CMassn, m_fFirstEncounter, FIELD_BOOLEAN ), + DEFINE_FIELD( CMassn, m_cClipSize, FIELD_INTEGER ), + DEFINE_FIELD( CMassn, m_voicePitch, FIELD_INTEGER ), + //DEFINE_FIELD( CShotgun, m_iBrassShell, FIELD_INTEGER ), + //DEFINE_FIELD( CShotgun, m_iShotgunShell, FIELD_INTEGER ), + DEFINE_FIELD( CMassn, m_iSentence, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CMassn, CSquadMonster ) + +const char *CMassn::pGruntSentences[] = +{ + "MN_GREN", // grenade scared grunt + "MN_ALERT", // sees player + "MN_MONSTER", // sees monster + "MN_COVER", // running to cover + "MN_THROW", // about to throw grenade + "MN_CHARGE", // running out to get the enemy + "MN_TAUNT", // say rude things +}; + +//========================================================= +// Speak Sentence - say your cued up sentence. +// +// Some grunt sentences (take cover and charge) rely on actually +// being able to execute the intended action. It's really lame +// when a grunt says 'COVER ME' and then doesn't move. The problem +// is that the sentences were played when the decision to TRY +// to move to cover was made. Now the sentence is played after +// we know for sure that there is a valid path. The schedule +// may still fail but in most cases, well after the grunt has +// started moving. +//========================================================= +void CMassn::SpeakSentence( void ) +{ + if( m_iSentence == HGRUNT_SENT_NONE ) + { + // no sentence cued up. + return; + } + + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), pGruntSentences[m_iSentence], HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } +} + +//========================================================= +// IRelationship - overridden because Alien Grunts are +// Human Grunt's nemesis. +//========================================================= +int CMassn::IRelationship( CBaseEntity *pTarget ) +{ + if( FClassnameIs( pTarget->pev, "monster_alien_grunt" ) || FClassnameIs( pTarget->pev, "monster_gargantua" ) || FClassnameIs( pTarget->pev, "monster_human_grunt" ) ) + { + return R_NM; + } + return CSquadMonster::IRelationship( pTarget ); +} + +void CMassn::IdleSound( void ) +{ + if( FOkToSpeak() && ( g_fGruntQuestion || RANDOM_LONG( 0, 1 ) ) ) + { + if( !g_fGruntQuestion ) + { + // ask question or make statement + switch( RANDOM_LONG( 0, 2 ) ) + { + case 0: + // check in + SENTENCEG_PlayRndSz( ENT( pev ), "MN_CHECK", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + g_fGruntQuestion = 1; + break; + case 1: + // question + SENTENCEG_PlayRndSz( ENT( pev ), "MN_QUEST", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + g_fGruntQuestion = 2; + break; + case 2: + // statement + SENTENCEG_PlayRndSz( ENT( pev ), "MN_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + break; + } + } + else + { + switch( g_fGruntQuestion ) + { + case 1: + // check in + SENTENCEG_PlayRndSz( ENT( pev ), "MN_CLEAR", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + break; + case 2: + // question + SENTENCEG_PlayRndSz( ENT( pev ), "MN_ANSWER", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch ); + break; + } + g_fGruntQuestion = 0; + } + JustSpoke(); + } +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CMassn::HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case HGRUNT_AE_DROP_GUN: + { + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( GUN_GROUP, GUN_NONE ); + + // now spawn a gun. + if( FBitSet( pev->weapons, HGRUNT_SHOTGUN ) ) + { + //DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else + { + DropItem( "weapon_9mmAR", vecGunPos, vecGunAngles ); + } + + if( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER ) ) + { + DropItem( "ammo_ARgrenades", BodyTarget( pev->origin ), vecGunAngles ); + } + } + break; + case HGRUNT_AE_RELOAD: + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "massn/gr_reload1.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions( bits_COND_NO_AMMO_LOADED ); + break; + case HGRUNT_AE_GREN_TOSS: + { + UTIL_MakeVectors( pev->angles ); + // CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector( 0, 0, 32 ), m_vecTossVelocity, 3.5 ); + CGrenade::ShootTimed( pev, GetGunPosition(), m_vecTossVelocity, 3.5 ); + + m_fThrowGrenade = FALSE; + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + // !!!LATER - when in a group, only try to throw grenade if ordered. + } + break; + case HGRUNT_AE_GREN_LAUNCH: + { + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM ); + CGrenade::ShootContact( pev, GetGunPosition(), m_vecTossVelocity ); + m_fThrowGrenade = FALSE; + if( g_iSkillLevel == SKILL_HARD ) + m_flNextGrenadeCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 );// wait a random amount of time before shooting again + else + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + } + break; + case HGRUNT_AE_GREN_DROP: + { + UTIL_MakeVectors( pev->angles ); + CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 17 - gpGlobals->v_right * 27 + gpGlobals->v_up * 6, g_vecZero, 3 ); + } + break; + case HGRUNT_AE_BURST1: + { + if( FBitSet( pev->weapons, HGRUNT_9MMAR ) ) + { + Shoot(); + + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + if( RANDOM_LONG( 0, 1 ) ) + { + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "massn/gr_mgun1.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "massn/gr_mgun2.wav", 1, ATTN_NORM ); + } + } + else + { + Shotgun(); + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "ambience/rifle1.wav", 1, ATTN_NORM ); + } + + CSoundEnt::InsertSound( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + } + break; + case HGRUNT_AE_BURST2: + case HGRUNT_AE_BURST3: + Shoot(); + break; + case HGRUNT_AE_KICK: + { + CBaseEntity *pHurt = Kick(); + + if( pHurt ) + { + // SOUND HERE! + UTIL_MakeVectors( pev->angles ); + pHurt->pev->punchangle.x = 15; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; + pHurt->TakeDamage( pev, pev, gSkillData.hgruntDmgKick, DMG_CLUB ); + } + } + break; + case HGRUNT_AE_CAUGHT_ENEMY: + { + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "MN_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + + } + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CMassn::Spawn() +{ + Precache(); + + SET_MODEL( ENT( pev ), "models/massn.mdl" ); + + UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + pev->health = gSkillData.hgruntHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = HGRUNT_SENT_NONE; + + m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + m_fEnemyEluded = FALSE; + m_fFirstEncounter = TRUE;// this is true when the grunt spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector( 0, 0, 55 ); + + if( pev->weapons == 0 ) + { + // initialize to original values + pev->weapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE; + // pev->weapons = HGRUNT_SHOTGUN; + // pev->weapons = HGRUNT_9MMAR | HGRUNT_GRENADELAUNCHER; + } + + if( FBitSet( pev->weapons, HGRUNT_SHOTGUN ) ) + { + SetBodygroup( HEAD_SHOTGUN, GUN_SHOTGUN ); + m_cClipSize = 5; + } + else + { + m_cClipSize = GRUNT_CLIP_SIZE; + } + m_cAmmoLoaded = m_cClipSize; +/* + if( RANDOM_LONG( 0, 99 ) < 80 ) + pev->skin = 0; // light skin + else + pev->skin = 1; // dark skin + + if( FBitSet( pev->weapons, HGRUNT_SHOTGUN ) ) + { + SetBodygroup( HEAD_GROUP, HEAD_SHOTGUN ); + } + else if( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER ) ) + { + SetBodygroup( HEAD_GROUP, HEAD_M203 ); + pev->skin = 1; // alway dark skin + } +*/ + CTalkMonster::g_talkWaitTime = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMassn::Precache() +{ + PRECACHE_MODEL( "models/massn.mdl" ); + + PRECACHE_SOUND( "massn/gr_mgun1.wav" ); + PRECACHE_SOUND( "massn/gr_mgun2.wav" ); + + PRECACHE_SOUND( "massn/gr_die1.wav" ); + PRECACHE_SOUND( "massn/gr_die2.wav" ); + PRECACHE_SOUND( "massn/gr_die3.wav" ); + + PRECACHE_SOUND( "massn/gr_pain1.wav" ); + PRECACHE_SOUND( "massn/gr_pain2.wav" ); + PRECACHE_SOUND( "massn/gr_pain3.wav" ); + PRECACHE_SOUND( "massn/gr_pain4.wav" ); + PRECACHE_SOUND( "massn/gr_pain5.wav" ); + + PRECACHE_SOUND( "massn/gr_reload1.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND( "ambience/rifle1.wav" ); + PRECACHE_SOUND( "zombie/claw_miss2.wav" );// because we use the basemonster SWIPE animation event + + // get voice pitch + if( RANDOM_LONG( 0, 1 ) ) + m_voicePitch = 109 + RANDOM_LONG( 0, 7 ); + else + m_voicePitch = 100; + + m_iBrassShell = PRECACHE_MODEL( "models/shell.mdl" );// brass shell + m_iShotgunShell = PRECACHE_MODEL( "models/shotgunshell.mdl" ); +} + +//========================================================= +// PainSound +//========================================================= +void CMassn::PainSound( void ) +{ + if( gpGlobals->time > m_flNextPainTime ) + { +#if 0 + if( RANDOM_LONG( 0, 99 ) < 5 ) + { + // pain sentences are rare + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "MN_PAIN", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, PITCH_NORM ); + JustSpoke(); + return; + } + } +#endif + switch( RANDOM_LONG( 0, 6 ) ) + { + case 0: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "massn/gr_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "massn/gr_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "massn/gr_pain5.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "massn/gr_pain1.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "massn/gr_pain2.wav", 1, ATTN_NORM ); + break; + } + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CMassn::DeathSound( void ) +{ + switch( RANDOM_LONG( 0, 2 ) ) + { + case 0: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "massn/gr_die1.wav", 1, ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "massn/gr_die2.wav", 1, ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "massn/gr_die3.wav", 1, ATTN_IDLE ); + break; + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +/* +//========================================================= +// GruntFail +//========================================================= +Task_t tlGruntFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntFail[] = +{ + { + tlGruntFail, + ARRAYSIZE( tlGruntFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + 0, + "Grunt Fail" + }, +}; + +//========================================================= +// Grunt Combat Fail +//========================================================= +Task_t tlGruntCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntCombatFail[] = +{ + { + tlGruntCombatFail, + ARRAYSIZE( tlGruntCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Grunt Combat Fail" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlGruntVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, +}; + +Schedule_t slGruntVictoryDance[] = +{ + { + tlGruntVictoryDance, + ARRAYSIZE( tlGruntVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "GruntVictoryDance" + }, +}; + +//========================================================= +// Establish line of fire - move to a position that allows +// the grunt to attack. +//========================================================= +Task_t tlGruntEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slGruntEstablishLineOfFire[] = +{ + { + tlGruntEstablishLineOfFire, + ARRAYSIZE( tlGruntEstablishLineOfFire ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntEstablishLineOfFire" + }, +}; + +//========================================================= +// GruntFoundEnemy - grunt established sight with an enemy +// that was hiding from the squad. +//========================================================= +Task_t tlGruntFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL1 }, +}; + +Schedule_t slGruntFoundEnemy[] = +{ + { + tlGruntFoundEnemy, + ARRAYSIZE( tlGruntFoundEnemy ), + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntFoundEnemy" + }, +}; + +//========================================================= +// GruntCombatFace Schedule +//========================================================= +Task_t tlGruntCombatFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_SWEEP }, +}; + +Schedule_t slGruntCombatFace[] = +{ + { + tlGruntCombatFace1, + ARRAYSIZE( tlGruntCombatFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Suppressing fire - don't stop shooting until the clip is +// empty or grunt gets hurt. +//========================================================= +Task_t tlGruntSignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0}, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSignalSuppress[] = +{ + { + tlGruntSignalSuppress, + ARRAYSIZE( tlGruntSignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; + +Task_t tlGruntSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSuppress[] = +{ + { + tlGruntSuppress, + ARRAYSIZE( tlGruntSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + bits_SOUND_DANGER, + "Suppress" + }, +}; + +//========================================================= +// grunt wait in cover - we don't allow danger or the ability +// to attack to break a grunt's run to cover schedule, but +// when a grunt is in cover, we do want them to attack if they can. +//========================================================= +Task_t tlGruntWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slGruntWaitInCover[] = +{ + { + tlGruntWaitInCover, + ARRAYSIZE( tlGruntWaitInCover ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + bits_SOUND_DANGER, + "GruntWaitInCover" + }, +}; + +//========================================================= +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. +//========================================================= +Task_t tlGruntTakeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntTakeCover[] = +{ + { + tlGruntTakeCover1, + ARRAYSIZE ( tlGruntTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntGrenadeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)99 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_PLAY_SEQUENCE, (float)ACT_SPECIAL_ATTACK1 }, + { TASK_CLEAR_MOVE_WAIT, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntGrenadeCover[] = +{ + { + tlGruntGrenadeCover1, + ARRAYSIZE( tlGruntGrenadeCover1 ), + 0, + 0, + "GrenadeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntTossGrenadeCover1[] = +{ + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slGruntTossGrenadeCover[] = +{ + { + tlGruntTossGrenadeCover1, + ARRAYSIZE( tlGruntTossGrenadeCover1 ), + 0, + 0, + "TossGrenadeCover" + }, +}; + +//========================================================= +// hide from the loudest sound source (to run from grenade) +//========================================================= +Task_t tlGruntTakeCoverFromBestSound[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_COWER },// duck and cover if cannot move from explosion + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slGruntTakeCoverFromBestSound[] = +{ + { + tlGruntTakeCoverFromBestSound, + ARRAYSIZE( tlGruntTakeCoverFromBestSound ), + 0, + 0, + "GruntTakeCoverFromBestSound" + }, +}; + +//========================================================= +// Grunt reload schedule +//========================================================= +Task_t tlGruntHideReload[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RELOAD }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RELOAD }, +}; + +Schedule_t slGruntHideReload[] = +{ + { + tlGruntHideReload, + ARRAYSIZE( tlGruntHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntHideReload" + } +}; + +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlGruntSweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slGruntSweep[] = +{ + { + tlGruntSweep, + ARRAYSIZE( tlGruntSweep ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_HEAR_SOUND, + bits_SOUND_WORLD |// sound flags + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + "Grunt Sweep" + }, +}; + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack1A[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1A[] = +{ + { + tlGruntRangeAttack1A, + ARRAYSIZE( tlGruntRangeAttack1A ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + bits_SOUND_DANGER, + "Range Attack1A" + }, +}; + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack1B[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_IDLE_ANGRY }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1B[] = +{ + { + tlGruntRangeAttack1B, + ARRAYSIZE( tlGruntRangeAttack1B ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_GRUNT_NOFIRE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "Range Attack1B" + }, +}; + +//========================================================= +// secondary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GRUNT_FACE_TOSS_DIR, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RANGE_ATTACK2 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY },// don't run immediately after throwing grenade. +}; + +Schedule_t slGruntRangeAttack2[] = +{ + { + tlGruntRangeAttack2, + ARRAYSIZE( tlGruntRangeAttack2 ), + 0, + 0, + "RangeAttack2" + }, +}; + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepel[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_GLIDE }, +}; + +Schedule_t slGruntRepel[] = +{ + { + tlGruntRepel, + ARRAYSIZE( tlGruntRepel ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel" + }, +}; + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepelAttack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_FLY }, +}; + +Schedule_t slGruntRepelAttack[] = +{ + { + tlGruntRepelAttack, + ARRAYSIZE( tlGruntRepelAttack ), + bits_COND_ENEMY_OCCLUDED, + 0, + "Repel Attack" + }, +}; + +//========================================================= +// repel land +//========================================================= +Task_t tlGruntRepelLand[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_LAND }, + { TASK_GET_PATH_TO_LASTPOSITION, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slGruntRepelLand[] = +{ + { + tlGruntRepelLand, + ARRAYSIZE( tlGruntRepelLand ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel Land" + }, +}; +*/ + +DEFINE_CUSTOM_SCHEDULES( CMassn ) +{ + slGruntFail, + slGruntCombatFail, + slGruntVictoryDance, + slGruntEstablishLineOfFire, + slGruntFoundEnemy, + slGruntCombatFace, + slGruntSignalSuppress, + slGruntSuppress, + slGruntWaitInCover, + slGruntTakeCover, + slGruntGrenadeCover, + slGruntTossGrenadeCover, + slGruntTakeCoverFromBestSound, + slGruntHideReload, + slGruntSweep, + slGruntRangeAttack1A, + slGruntRangeAttack1B, + slGruntRangeAttack2, + slGruntRepel, + slGruntRepelAttack, + slGruntRepelLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMassn, CSquadMonster ) + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CMassn::GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = HGRUNT_SENT_NONE; + + // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling. + if( pev->movetype == MOVETYPE_FLY && m_MonsterState != MONSTERSTATE_PRONE ) + { + if( pev->flags & FL_ONGROUND ) + { + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType( SCHED_GRUNT_REPEL_LAND ); + } + else + { + // repel down a rope, + if( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType( SCHED_GRUNT_REPEL_ATTACK ); + else + return GetScheduleOfType( SCHED_GRUNT_REPEL ); + } + } + + // grunts place HIGH priority on running away from danger sounds. + if( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if( pSound ) + { + if( pSound->m_iType & bits_SOUND_DANGER ) + { + // dangerous sound nearby! + + //!!!KELLY - currently, this is the grunt's signal that a grenade has landed nearby, + // and the grunt should find cover from the blast + // good place for "SHIT!" or some other colorful verbal indicator of dismay. + // It's not safe to play a verbal order here "Scatter", etc cause + // this may only affect a single individual in a squad. + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "MN_GREN", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + /* + if( !HasConditions( bits_COND_SEE_ENEMY ) && ( pSound->m_iType & ( bits_SOUND_PLAYER | bits_SOUND_COMBAT ) ) ) + { + MakeIdealYaw( pSound->m_vecOrigin ); + } + */ + } + } + switch( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { + // dead enemy + if( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster::GetSchedule(); + } + + // new enemy + if( HasConditions( bits_COND_NEW_ENEMY ) ) + { + if( InSquad() ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + + if( !IsLeader() ) + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + //!!!KELLY - the leader of a squad of grunts has just seen the player or a + // monster and has made it the squad's enemy. You + // can check pev->flags for FL_CLIENT to determine whether this is the player + // or a monster. He's going to immediately start + // firing, though. If you'd like, we can make an alternate "first sight" + // schedule where the leader plays a handsign anim + // that gives us enough time to hear a short sentence or spoken command + // before he starts pluggin away. + if( FOkToSpeak() )// && RANDOM_LONG( 0, 1 ) ) + { + if( ( m_hEnemy != 0 ) && m_hEnemy->IsPlayer() ) + // player + SENTENCEG_PlayRndSz( ENT( pev ), "MN_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + else if( ( m_hEnemy != 0 ) && + ( m_hEnemy->Classify() != CLASS_PLAYER_ALLY ) && + ( m_hEnemy->Classify() != CLASS_HUMAN_PASSIVE ) && + ( m_hEnemy->Classify() != CLASS_MACHINE ) ) + // monster + SENTENCEG_PlayRndSz( ENT( pev ), "MN_MONST", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + + JustSpoke(); + } + + if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_GRUNT_SUPPRESS ); + } + else + { + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + } + // no ammo + else if( HasConditions( bits_COND_NO_AMMO_LOADED ) ) + { + //!!!KELLY - this individual just realized he's out of bullet ammo. + // He's going to try to find cover to run to and reload, but rarely, if + // none is available, he'll drop and reload in the open here. + return GetScheduleOfType( SCHED_GRUNT_COVER_AND_RELOAD ); + } + // damaged just a little + else if( HasConditions( bits_COND_LIGHT_DAMAGE ) ) + { + // if hurt: + // 90% chance of taking cover + // 10% chance of flinch. + int iPercent = RANDOM_LONG( 0, 99 ); + + if( iPercent <= 90 && m_hEnemy != 0 ) + { + // only try to take cover if we actually have an enemy! + + //!!!KELLY - this grunt was hit and is going to run to cover. + if( FOkToSpeak() ) // && RANDOM_LONG( 0, 1 ) ) + { + //SENTENCEG_PlayRndSz( ENT( pev ), "MN_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + m_iSentence = HGRUNT_SENT_COVER; + //JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + } + // can kick + else if( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + // can grenade launch + else if( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER) && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // shoot a grenade if you can + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + // can shoot + else if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if( InSquad() ) + { + // if the enemy has eluded the squad and a squad member has just located the enemy + // and the enemy does not see the squad member, issue a call to the squad to waste a + // little time and give the player a chance to turn. + if( MySquadLeader()->m_fEnemyEluded && !HasConditions( bits_COND_ENEMY_FACING_ME ) ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + return GetScheduleOfType( SCHED_GRUNT_FOUND_ENEMY ); + } + } + + if( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + // try to take an available ENGAGE slot + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + else if( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // throw a grenade if can and no engage slots are available + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else + { + // hide! + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } + // can't see enemy + else if( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + //!!!KELLY - this grunt is about to throw or fire a grenade at the player. Great place for "fire in the hole" "frag out" etc + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "MN_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else if( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + //!!!KELLY - grunt cannot see the enemy and has just decided to + // charge the enemy's position. + if( FOkToSpeak() )// && RANDOM_LONG( 0, 1 ) ) + { + //SENTENCEG_PlayRndSz( ENT( pev ), "MN_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + m_iSentence = HGRUNT_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + else + { + //!!!KELLY - grunt is going to stay put for a couple seconds to see if + // the enemy wanders back out into the open, or approaches the + // grunt's covered position. Good place for a taunt, I guess? + if( FOkToSpeak() && RANDOM_LONG( 0, 1 ) ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "MN_TAUNT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + break; + default: + break; + } + + // no special cases here, call the base class + return CSquadMonster::GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t *CMassn::GetScheduleOfType( int Type ) +{ + switch( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + { + if( InSquad() ) + { + if( g_iSkillLevel == SKILL_HARD && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "MN_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return slGruntTossGrenadeCover; + } + else + { + return &slGruntTakeCover[0]; + } + } + else + { + if( RANDOM_LONG( 0, 1 ) ) + { + return &slGruntTakeCover[0]; + } + else + { + return &slGruntGrenadeCover[0]; + } + } + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slGruntTakeCoverFromBestSound[0]; + } + case SCHED_GRUNT_TAKECOVER_FAILED: + { + if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType( SCHED_FAIL ); + } + break; + case SCHED_GRUNT_ELOF_FAIL: + { + // human grunt is unable to move to a position that allows him to attack the enemy. + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + case SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE: + { + return &slGruntEstablishLineOfFire[0]; + } + break; + case SCHED_RANGE_ATTACK1: + { + // randomly stand or crouch + if( RANDOM_LONG( 0, 9 ) == 0 ) + m_fStanding = RANDOM_LONG( 0, 1 ); + + if( m_fStanding ) + return &slGruntRangeAttack1B[0]; + else + return &slGruntRangeAttack1A[0]; + } + case SCHED_RANGE_ATTACK2: + { + return &slGruntRangeAttack2[0]; + } + case SCHED_COMBAT_FACE: + { + return &slGruntCombatFace[0]; + } + case SCHED_GRUNT_WAIT_FACE_ENEMY: + { + return &slGruntWaitInCover[0]; + } + case SCHED_GRUNT_SWEEP: + { + return &slGruntSweep[0]; + } + case SCHED_GRUNT_COVER_AND_RELOAD: + { + return &slGruntHideReload[0]; + } + case SCHED_GRUNT_FOUND_ENEMY: + { + return &slGruntFoundEnemy[0]; + } + case SCHED_VICTORY_DANCE: + { + if( InSquad() ) + { + if( !IsLeader() ) + { + return &slGruntFail[0]; + } + } + + return &slGruntVictoryDance[0]; + } + case SCHED_GRUNT_SUPPRESS: + { + if( m_hEnemy->IsPlayer() && m_fFirstEncounter ) + { + m_fFirstEncounter = FALSE;// after first encounter, leader won't issue handsigns anymore when he has a new enemy + return &slGruntSignalSuppress[0]; + } + else + { + return &slGruntSuppress[0]; + } + } + case SCHED_FAIL: + { + if( m_hEnemy != 0 ) + { + // grunt has an enemy, so pick a different default fail schedule most likely to help recover. + return &slGruntCombatFail[0]; + } + + return &slGruntFail[0]; + } + case SCHED_GRUNT_REPEL: + { + if( pev->velocity.z > -128 ) + pev->velocity.z -= 32; + return &slGruntRepel[0]; + } + case SCHED_GRUNT_REPEL_ATTACK: + { + if( pev->velocity.z > -128 ) + pev->velocity.z -= 32; + return &slGruntRepelAttack[0]; + } + case SCHED_GRUNT_REPEL_LAND: + { + return &slGruntRepelLand[0]; + } + default: + { + return CSquadMonster::GetScheduleOfType( Type ); + } + } +} + +//========================================================= +// CMassnRepel - when triggered, spawns a monster_nari_grunt +// repelling down a line. +//========================================================= +class CMassnRepel : public CHGruntRepel +{ +public: + void Spawn(void); + void Precache(void); + void EXPORT RepelUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); +}; + +LINK_ENTITY_TO_CLASS( monster_massn_repel, CMassnRepel ) + +void CMassnRepel::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + + SetUse( &CMassnRepel::RepelUse ); +} + +void CMassnRepel::Precache( void ) +{ + UTIL_PrecacheOther( "monster_massn" ); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); +} + +void CMassnRepel::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_massn", pev->origin, pev->angles ); + CBaseMonster *pGrunt = pEntity->MyMonsterPointer(); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pGrunt->SetActivity( ACT_GLIDE ); + // UNDONE: position? + pGrunt->m_vecLastPosition = tr.vecEndPos; + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( pev->origin + Vector( 0, 0, 112 ), pGrunt->entindex() ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink( &CBaseEntity::SUB_Remove ); + pBeam->pev->nextthink = gpGlobals->time + -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5; + + UTIL_Remove( this ); +} + +//========================================================= +// DEAD Male Assasin PROP +//========================================================= +class CDeadMassn : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static const char *m_szPoses[3]; +}; + +const char *CDeadMassn::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; + +void CDeadMassn::KeyValue( KeyValueData *pkvd ) +{ + if( FStrEq( pkvd->szKeyName, "pose" ) ) + { + m_iPose = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_massn_dead, CDeadMassn ) + +//========================================================= +// ********** DeadMassn SPAWN ********** +//========================================================= +void CDeadMassn::Spawn( void ) +{ + PRECACHE_MODEL( "models/massn.mdl" ); + SET_MODEL( ENT( pev ), "models/massn.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 male assassin with bad pose\n" ); + } + + // Corpses have less health + pev->health = 8; + + // map old bodies onto new bodies + switch( pev->body ) + { + case 0: + // Grunt with Gun + pev->body = 0; + SetBodygroup( HEAD_GROUP, HEAD_GRUNT ); + SetBodygroup( GUN_GROUP, GUN_MP5 ); + break; + case 1: + // Commander with Gun + pev->body = 0; + SetBodygroup( HEAD_GROUP, HEAD_COMMANDER ); + SetBodygroup( GUN_GROUP, GUN_MP5 ); + break; + case 2: + // Grunt no Gun + pev->body = 0; + SetBodygroup( HEAD_GROUP, HEAD_GRUNT ); + SetBodygroup( GUN_GROUP, GUN_NONE ); + break; + case 3: + // Commander no Gun + pev->body = 0; + SetBodygroup( HEAD_GROUP, HEAD_COMMANDER ); + SetBodygroup( GUN_GROUP, GUN_NONE ); + break; + } + + MonsterInitDead(); +} diff --git a/dlls/CMakeLists.txt b/dlls/CMakeLists.txt index 3ad74c29..ca15d5a9 100644 --- a/dlls/CMakeLists.txt +++ b/dlls/CMakeLists.txt @@ -131,6 +131,7 @@ set (SVDLL_SOURCES CAd/eagle.cpp CAd/helmet.cpp CAd/gonome.cpp + CAd/massn.cpp CAd/otis.cpp ../pm_shared/pm_debug.c ../pm_shared/pm_math.c diff --git a/dlls/hgrunt.cpp b/dlls/hgrunt.cpp index 4cb85c2c..4a03150e 100644 --- a/dlls/hgrunt.cpp +++ b/dlls/hgrunt.cpp @@ -40,152 +40,12 @@ #include "soundent.h" #include "effects.h" #include "customentity.h" +#include "hgrunt.h" int g_fGruntQuestion; // true if an idle grunt asked a question. Cleared when someone answers. extern DLL_GLOBAL int g_iSkillLevel; -//========================================================= -// monster-specific DEFINE's -//========================================================= -#define GRUNT_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! -#define GRUNT_VOL 0.35 // volume of grunt sounds -#define GRUNT_ATTN ATTN_NORM // attenutation of grunt sentences -#define HGRUNT_LIMP_HEALTH 20 -#define HGRUNT_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot. -#define HGRUNT_NUM_HEADS 2 // how many grunt heads are there? -#define HGRUNT_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill -#define HGRUNT_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences - -#define HGRUNT_9MMAR ( 1 << 0) -#define HGRUNT_HANDGRENADE ( 1 << 1) -#define HGRUNT_GRENADELAUNCHER ( 1 << 2) -#define HGRUNT_SHOTGUN ( 1 << 3) - -#define HEAD_GROUP 1 -#define HEAD_GRUNT 0 -#define HEAD_COMMANDER 1 -#define HEAD_SHOTGUN 2 -#define HEAD_M203 3 -#define GUN_GROUP 2 -#define GUN_MP5 0 -#define GUN_SHOTGUN 1 -#define GUN_NONE 2 - -//========================================================= -// Monster's Anim Events Go Here -//========================================================= -#define HGRUNT_AE_RELOAD ( 2 ) -#define HGRUNT_AE_KICK ( 3 ) -#define HGRUNT_AE_BURST1 ( 4 ) -#define HGRUNT_AE_BURST2 ( 5 ) -#define HGRUNT_AE_BURST3 ( 6 ) -#define HGRUNT_AE_GREN_TOSS ( 7 ) -#define HGRUNT_AE_GREN_LAUNCH ( 8 ) -#define HGRUNT_AE_GREN_DROP ( 9 ) -#define HGRUNT_AE_CAUGHT_ENEMY ( 10 ) // grunt established sight with an enemy (player only) that had previously eluded the squad. -#define HGRUNT_AE_DROP_GUN ( 11 ) // grunt (probably dead) is dropping his mp5. - -//========================================================= -// monster-specific schedule types -//========================================================= -enum -{ - SCHED_GRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, - SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,// move to a location to set up an attack against the enemy. (usually when a friendly is in the way). - SCHED_GRUNT_COVER_AND_RELOAD, - SCHED_GRUNT_SWEEP, - SCHED_GRUNT_FOUND_ENEMY, - SCHED_GRUNT_REPEL, - SCHED_GRUNT_REPEL_ATTACK, - SCHED_GRUNT_REPEL_LAND, - SCHED_GRUNT_WAIT_FACE_ENEMY, - SCHED_GRUNT_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. - SCHED_GRUNT_ELOF_FAIL -}; - -//========================================================= -// monster-specific tasks -//========================================================= -enum -{ - TASK_GRUNT_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, - TASK_GRUNT_SPEAK_SENTENCE, - TASK_GRUNT_CHECK_FIRE -}; - -//========================================================= -// monster-specific conditions -//========================================================= -#define bits_COND_GRUNT_NOFIRE ( bits_COND_SPECIAL1 ) - -class CHGrunt : public CSquadMonster -{ -public: - void Spawn( void ); - void Precache( void ); - void SetYawSpeed( void ); - int Classify( void ); - int ISoundMask( void ); - void HandleAnimEvent( MonsterEvent_t *pEvent ); - BOOL FCanCheckAttacks( void ); - BOOL CheckMeleeAttack1( float flDot, float flDist ); - BOOL CheckRangeAttack1( float flDot, float flDist ); - BOOL CheckRangeAttack2( float flDot, float flDist ); - void CheckAmmo( void ); - void SetActivity( Activity NewActivity ); - void StartTask( Task_t *pTask ); - void RunTask( Task_t *pTask ); - void DeathSound( void ); - void PainSound( void ); - void IdleSound( void ); - Vector GetGunPosition( void ); - void Shoot( void ); - void Shotgun( void ); - void PrescheduleThink( void ); - void GibMonster( void ); - void SpeakSentence( void ); - - int Save( CSave &save ); - int Restore( CRestore &restore ); - - CBaseEntity *Kick( void ); - Schedule_t *GetSchedule( void ); - Schedule_t *GetScheduleOfType( int Type ); - 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 ); - - BOOL FOkToSpeak( void ); - void JustSpoke( void ); - - CUSTOM_SCHEDULES - static TYPEDESCRIPTION m_SaveData[]; - - // 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_iSentence; - - static const char *pGruntSentences[]; -}; - LINK_ENTITY_TO_CLASS( monster_human_grunt, CHGrunt ) TYPEDESCRIPTION CHGrunt::m_SaveData[] = @@ -217,18 +77,6 @@ const char *CHGrunt::pGruntSentences[] = "HG_TAUNT", // say rude things }; -typedef enum -{ - HGRUNT_SENT_NONE = -1, - HGRUNT_SENT_GREN = 0, - HGRUNT_SENT_ALERT, - HGRUNT_SENT_MONSTER, - HGRUNT_SENT_COVER, - HGRUNT_SENT_THROW, - HGRUNT_SENT_CHARGE, - HGRUNT_SENT_TAUNT -} HGRUNT_SENTENCE_TYPES; - //========================================================= // Speak Sentence - say your cued up sentence. // @@ -262,7 +110,7 @@ void CHGrunt::SpeakSentence( void ) //========================================================= int CHGrunt::IRelationship( CBaseEntity *pTarget ) { - if( FClassnameIs( pTarget->pev, "monster_alien_grunt" ) || ( FClassnameIs( pTarget->pev, "monster_gargantua" ) ) ) + if( FClassnameIs( pTarget->pev, "monster_alien_grunt" ) || FClassnameIs( pTarget->pev, "monster_gargantua" ) || FClassnameIs( pTarget->pev, "monster_massn" ) ) { return R_NM; } @@ -2344,15 +2192,6 @@ Schedule_t *CHGrunt::GetScheduleOfType( int Type ) // repelling down a line. //========================================================= -class CHGruntRepel : public CBaseMonster -{ -public: - 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 -}; - LINK_ENTITY_TO_CLASS( monster_grunt_repel, CHGruntRepel ) void CHGruntRepel::Spawn( void ) diff --git a/dlls/hgrunt.h b/dlls/hgrunt.h new file mode 100644 index 00000000..153b2bbb --- /dev/null +++ b/dlls/hgrunt.h @@ -0,0 +1,188 @@ +/*** +* +* 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. +* +****/ +#ifndef HGRUNT_H +#define HGRUNT_H + +//========================================================= +// monster-specific DEFINE's +//========================================================= +#define GRUNT_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! +#define GRUNT_VOL 0.35 // volume of grunt sounds +#define GRUNT_ATTN ATTN_NORM // attenutation of grunt sentences +#define HGRUNT_LIMP_HEALTH 20 +#define HGRUNT_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot. +#define HGRUNT_NUM_HEADS 2 // how many grunt heads are there? +#define HGRUNT_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill +#define HGRUNT_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences + +#define HGRUNT_9MMAR ( 1 << 0) +#define HGRUNT_HANDGRENADE ( 1 << 1) +#define HGRUNT_GRENADELAUNCHER ( 1 << 2) +#define HGRUNT_SHOTGUN ( 1 << 3) + +#define HEAD_GROUP 1 +#define HEAD_GRUNT 0 +#define HEAD_COMMANDER 1 +#define HEAD_SHOTGUN 2 +#define HEAD_M203 3 +#define GUN_GROUP 2 +#define GUN_MP5 0 +#define GUN_SHOTGUN 1 +#define GUN_NONE 2 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HGRUNT_AE_RELOAD ( 2 ) +#define HGRUNT_AE_KICK ( 3 ) +#define HGRUNT_AE_BURST1 ( 4 ) +#define HGRUNT_AE_BURST2 ( 5 ) +#define HGRUNT_AE_BURST3 ( 6 ) +#define HGRUNT_AE_GREN_TOSS ( 7 ) +#define HGRUNT_AE_GREN_LAUNCH ( 8 ) +#define HGRUNT_AE_GREN_DROP ( 9 ) +#define HGRUNT_AE_CAUGHT_ENEMY ( 10 ) // grunt established sight with an enemy (player only) that had previously eluded the squad. +#define HGRUNT_AE_DROP_GUN ( 11 ) // grunt (probably dead) is dropping his mp5. + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_GRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, + SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,// move to a location to set up an attack against the enemy. (usually when a friendly is in the way). + SCHED_GRUNT_COVER_AND_RELOAD, + SCHED_GRUNT_SWEEP, + SCHED_GRUNT_FOUND_ENEMY, + SCHED_GRUNT_REPEL, + SCHED_GRUNT_REPEL_ATTACK, + SCHED_GRUNT_REPEL_LAND, + SCHED_GRUNT_WAIT_FACE_ENEMY, + SCHED_GRUNT_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. + SCHED_GRUNT_ELOF_FAIL +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_GRUNT_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, + TASK_GRUNT_SPEAK_SENTENCE, + TASK_GRUNT_CHECK_FIRE +}; + +//========================================================= +// monster-specific conditions +//========================================================= +#define bits_COND_GRUNT_NOFIRE ( bits_COND_SPECIAL1 ) + +//========================================================= +// hgrunt +//========================================================= +class CHGrunt : public CSquadMonster +{ +public: + virtual void Spawn(void); + virtual void Precache(void); + void SetYawSpeed(void); + virtual int Classify(void); + int ISoundMask(void); + virtual void HandleAnimEvent(MonsterEvent_t *pEvent); + BOOL FCanCheckAttacks(void); + BOOL CheckMeleeAttack1(float flDot, float flDist); + BOOL CheckRangeAttack1(float flDot, float flDist); + BOOL CheckRangeAttack2(float flDot, float flDist); + void CheckAmmo(void); + void SetActivity(Activity NewActivity); + void StartTask(Task_t *pTask); + void RunTask(Task_t *pTask); + virtual void DeathSound(void); + virtual void PainSound(void); + virtual void IdleSound(void); + Vector GetGunPosition(void); + void Shoot(void); + virtual void Shotgun(void); + void PrescheduleThink(void); + virtual void GibMonster(void); + virtual void SpeakSentence(void); + + int Save(CSave &save); + int Restore(CRestore &restore); + + CBaseEntity *Kick(void); + Schedule_t *GetSchedule(void); + Schedule_t *GetScheduleOfType(int Type); + 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); + + virtual int IRelationship(CBaseEntity *pTarget); + + virtual BOOL FOkToSpeak(void); + void JustSpoke(void); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + // 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_iSentence; + + static const char *pGruntSentences[]; +}; + +typedef enum +{ + HGRUNT_SENT_NONE = -1, + HGRUNT_SENT_GREN = 0, + HGRUNT_SENT_ALERT, + HGRUNT_SENT_MONSTER, + HGRUNT_SENT_COVER, + HGRUNT_SENT_THROW, + HGRUNT_SENT_CHARGE, + HGRUNT_SENT_TAUNT +} HGRUNT_SENTENCE_TYPES; + +//========================================================= +// CHGruntRepel - when triggered, spawns a monster_human_grunt +// repelling down a line. +//========================================================= + +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); + int m_iSpriteTexture; // Don't save, precache +}; + +#endif // HGRUNT_H