/*** * * 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. * ****/ //========================================================= // 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" //========================================================= // monster-specific DEFINE's //========================================================= #define TERRORIST_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! #define TERRORIST_VOL 0.35 // volume of grunt sounds #define TERRORIST_ATTN ATTN_NORM // attenutation of grunt sentences #define TERRORIST_LIMP_HEALTH 20 #define TERRORIST_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot. #define TERRORIST_NUM_HEADS 2 // how many grunt heads are there? #define TERRORIST_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill #define TERRORIST_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences #define TERROR_AK47 ( 1 << 0) #define TERROR_HANDGRENADE ( 1 << 1) #define TERROR_GRENADELAUNCHER ( 1 << 2) #define TERROR_SHOTGUN ( 1 << 3) #define HEAD_GROUP 0 #define HEAD_GRUNT 0 #define HEAD_COMMANDER 1 #define HEAD_SHOTGUN 2 #define HEAD_M203 3 #define GUN_GROUP 1 #define GUN_MP5 0 #define GUN_SHOTGUN 1 #define GUN_NONE 2 enum { TERROR_SENT_NONE = -1, TERROR_SENT_GREN = 0, TERROR_SENT_ALERT, TERROR_SENT_MONSTER, TERROR_SENT_COVER, TERROR_SENT_THROW, TERROR_SENT_CHARGE, TERROR_SENT_TAUNT, }; //========================================================= // Monster's Anim Events Go Here //========================================================= #define TERROR_AE_RELOAD ( 2 ) #define TERROR_AE_KICK ( 3 ) #define TERROR_AE_BURST1 ( 4 ) #define TERROR_AE_BURST2 ( 5 ) #define TERROR_AE_BURST3 ( 6 ) #define TERROR_AE_GREN_TOSS ( 7 ) #define TERROR_AE_GREN_LAUNCH ( 8 ) #define TERROR_AE_GREN_DROP ( 9 ) #define TERROR_AE_CAUGHT_ENEMY ( 10 ) // grunt established sight with an enemy (player only) that had previously eluded the squad. #define TERROR_AE_DROP_GUN ( 11 ) // grunt (probably dead) is dropping his mp5. // // Terrorist special flags. // #define TF_HUMAN_SENTRY 1 extern Schedule_t slGruntCombatFail[]; extern Schedule_t slGruntEstablishLineOfFire[]; extern Schedule_t slGruntTakeCover[]; extern Schedule_t slGruntGrenadeCover[]; extern Schedule_t slGruntTossGrenadeCover[]; extern Schedule_t slGruntTakeCoverFromBestSound[]; extern Schedule_t slGruntHideReload[]; extern Schedule_t slGruntRangeAttack1A[]; extern Schedule_t slGruntRangeAttack1B[]; //========================================================= // Terrorist //========================================================= class CTerrorist : public CHGrunt { public: void Spawn(void); void Precache(void); void HandleAnimEvent(MonsterEvent_t* pEvent); void Killed(entvars_t *pevAttacker, int iGib); void Shoot(Vector vecSpread, int iBulletType); void ShootAK47(Vector vecSpread); void DeathSound(void); void PainSound(void); void GibMonster(void); BOOL IsHumanSentry() const; BOOL HasWeapon(); void DropWeapon(Vector vecVelocity = g_vecZero, Vector angVelocity = g_vecZero); void TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); Schedule_t *GetScheduleOfType(int Type); int Save(CSave &save); int Restore(CRestore &restore); static TYPEDESCRIPTION m_SaveData[]; static const char *pPainSounds[]; static const char *pDeathSounds[]; static const char *pBurstSounds[]; int m_iTerrorFlags; BOOL m_bHasWeapon; }; LINK_ENTITY_TO_CLASS(monster_terrorist, CTerrorist); LINK_ENTITY_TO_CLASS(monster_human_sentry, CTerrorist); TYPEDESCRIPTION CTerrorist::m_SaveData[] = { DEFINE_FIELD(CTerrorist, m_iTerrorFlags, FIELD_INTEGER), DEFINE_FIELD(CTerrorist, m_bHasWeapon, FIELD_BOOLEAN), }; IMPLEMENT_SAVERESTORE(CTerrorist, CHGrunt); const char *CTerrorist::pPainSounds[] = { "hgrunt/gr_pain1.wav", "hgrunt/gr_pain2.wav", "hgrunt/gr_pain3.wav", "hgrunt/gr_pain4.wav", "hgrunt/gr_pain5.wav", }; const char *CTerrorist::pDeathSounds[] = { "hgrunt/gr_die1.wav", "hgrunt/gr_die2.wav", "hgrunt/gr_die3.wav", }; const char *CTerrorist::pBurstSounds[] = { "weapons/hks1.wav", "weapons/hks2.wav", "weapons/hks3.wav", }; //========================================================= // Shoot //========================================================= void CTerrorist::Shoot(Vector vecSpread, int iBulletType) { if (m_hEnemy == NULL) { return; } Vector vecShootOrigin = GetGunPosition(); Vector vecShootDir = ShootAtEnemy(vecShootOrigin); UTIL_MakeVectors(pev->angles); Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40, 90) + gpGlobals->v_up * RANDOM_FLOAT(75, 200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); EjectBrass(vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL); FireBullets(1, vecShootOrigin, vecShootDir, vecSpread, 2048, iBulletType); // shoot +-5 degrees pev->effects |= EF_MUZZLEFLASH; m_cAmmoLoaded--;// take away a bullet! Vector angDir = UTIL_VecToAngles(vecShootDir); SetBlending(0, angDir.x); } //========================================================= // ShootAK47 //========================================================= void CTerrorist::ShootAK47(Vector vecSpread) { Shoot(VECTOR_CONE_10DEGREES, BULLET_MONSTER_MP5); EMIT_SOUND(ENT(pev), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pBurstSounds), 1, ATTN_NORM); } //========================================================= // HandleAnimEvent - catches the monster-specific messages // that occur when tagged animation frames are played. //========================================================= void CTerrorist::HandleAnimEvent(MonsterEvent_t *pEvent) { Vector vecShootDir; Vector vecShootOrigin; switch (pEvent->event) { case TERROR_AE_DROP_GUN: { if (HasWeapon()) { DropWeapon(); } break; } case TERROR_AE_BURST1: { if (FBitSet(pev->weapons, TERROR_AK47)) { ShootAK47( VECTOR_CONE_10DEGREES ); } else { Shotgun(); EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM); } CSoundEnt::InsertSound(bits_SOUND_COMBAT, pev->origin, 384, 0.3); } break; case TERROR_AE_BURST2: case TERROR_AE_BURST3: ShootAK47( VECTOR_CONE_10DEGREES ); break; default: CHGrunt::HandleAnimEvent(pEvent); break; } } void CTerrorist::Killed(entvars_t *pevAttacker, int iGib) { SetBodygroup(GUN_GROUP, GUN_NONE); CHGrunt::Killed(pevAttacker, iGib); } //========================================================= // Spawn //========================================================= void CTerrorist::Spawn() { Precache(); SET_MODEL(ENT(pev), "models/terrorist.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 = TERROR_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); m_bHasWeapon = TRUE; if (pev->weapons == 0) { // initialize to original values pev->weapons = TERROR_AK47 | TERROR_HANDGRENADE; } // Prevent terrorist from using grenade launchers. pev->weapons &= ~TERROR_GRENADELAUNCHER; if (FBitSet(pev->weapons, TERROR_SHOTGUN)) { SetBodygroup(GUN_GROUP, GUN_SHOTGUN); m_cClipSize = SHOTGUN_MAX_CLIP; } else { m_cClipSize = TERRORIST_CLIP_SIZE; } m_cAmmoLoaded = m_cClipSize; m_iTerrorFlags = 0; if (FClassnameIs(pev, "monster_human_sentry")) { m_iTerrorFlags |= TF_HUMAN_SENTRY; } CTalkMonster::g_talkWaitTime = 0; MonsterInit(); } //========================================================= // Precache - precaches all resources this monster needs //========================================================= void CTerrorist::Precache() { PRECACHE_MODEL("models/terrorist.mdl"); PRECACHE_SOUND_ARRAY( pPainSounds ); PRECACHE_SOUND_ARRAY( pDeathSounds ); PRECACHE_SOUND_ARRAY( pBurstSounds ); PRECACHE_SOUND("hgrunt/gr_reload1.wav"); PRECACHE_SOUND("weapons/sbarrel1.wav"); PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event // get voice pitch if (RANDOM_LONG(0, 1)) m_voicePitch = 109 + RANDOM_LONG(0, 7); else m_voicePitch = 100; m_iBrassShell = PRECACHE_MODEL("models/shell.mdl");// brass shell m_iShotgunShell = PRECACHE_MODEL("models/shotgunshell.mdl"); } //========================================================= // GibMonster - make gun fly through the air. //========================================================= void CTerrorist::GibMonster(void) { if (HasWeapon() ) { Vector velocity = Vector(RANDOM_FLOAT(-100, 100), RANDOM_FLOAT(-100, 100), RANDOM_FLOAT(200, 300)); Vector angVelocity = Vector(0, RANDOM_FLOAT(200, 400), 0); DropWeapon(velocity, angVelocity); } CBaseMonster::GibMonster(); } //========================================================= // PainSound //========================================================= void CTerrorist::PainSound(void) { if (gpGlobals->time > m_flNextPainTime) { EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pPainSounds), 1, ATTN_NORM); m_flNextPainTime = gpGlobals->time + 1; } } //========================================================= // DeathSound //========================================================= void CTerrorist::DeathSound(void) { EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pDeathSounds), 1, ATTN_IDLE); } //========================================================= // TraceAttack - make sure we're not taking it in the helmet //========================================================= void CTerrorist::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { if (pev->takedamage) { // Human sentries are not allowed to drop weapons. if (!IsHumanSentry()) { if (ptr->iHitgroup == HITGROUP_RIGHTARM) { // ========================================== // Code changes for- Night at the Office: // ========================================== // // -Droppable guns. If terrorist is shot in the arm, he will drop // his gun and seek cover. Very useful in situation where player // is low on health/ammo or in a stealthy situation. Difficult to // get an arm shot while in combat, due to stance of the terrorist. if (HasWeapon()) { Vector velocity = Vector(RANDOM_FLOAT(-10, 10), RANDOM_FLOAT(-10, 10), RANDOM_FLOAT(30, 40)); Vector angVelocity = Vector(0, RANDOM_FLOAT(10, 20), 0); DropWeapon(velocity, angVelocity); // Remove ability to shoot at enemy, now that we dropped our weapon. m_afCapability &= ~bits_CAP_RANGE_ATTACK1; } } } // check for helmet shot else if (ptr->iHitgroup == 11) { // it's head shot anyways ptr->iHitgroup = HITGROUP_HEAD; } } CSquadMonster::TraceAttack(pevAttacker, flDamage, vecDir, ptr, bitsDamageType); } //========================================================= //========================================================= Schedule_t* CTerrorist :: GetScheduleOfType ( int Type ) { switch ( Type ) { case SCHED_TAKE_COVER_FROM_ENEMY: { // Human sentries are not allowed to take cover. if (IsHumanSentry()) { return CSquadMonster::GetScheduleOfType( SCHED_COMBAT_FACE ); } else { if (InSquad()) { if (g_iSkillLevel == SKILL_HARD && HasConditions(bits_COND_CAN_RANGE_ATTACK2) && OccupySlot(bits_SLOTS_HGRUNT_GRENADE)) { if (FOkToSpeak()) { SENTENCEG_PlayRndSz(ENT(pev), "HG_THROW", TERRORIST_SENTENCE_VOLUME, TERRORIST_ATTN, 0, m_voicePitch); JustSpoke(); } return slGruntTossGrenadeCover; } else { return &slGruntTakeCover[0]; } } else { if (HasWeapon()) { if (RANDOM_LONG(0, 1)) { return &slGruntTakeCover[0]; } else { return &slGruntGrenadeCover[0]; } } else { return &slGruntTakeCover[0]; } } } } case SCHED_TAKE_COVER_FROM_BEST_SOUND: { // Human sentries are not allowed to run away from sounds. if (IsHumanSentry()) { return GetScheduleOfType( SCHED_COWER ); } else { return &slGruntTakeCoverFromBestSound[ 0 ]; } } case SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE: { // Human sentries are not allowed to strafe to get a better shot. if (IsHumanSentry()) { return &slGruntCombatFail[ 0 ]; } else { return &slGruntEstablishLineOfFire[0]; } } break; case SCHED_RANGE_ATTACK1: { if ( HasWeapon() ) { // 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]; } else { return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); } } case SCHED_GRUNT_COVER_AND_RELOAD: { // Human sentries can only reload in situ. if (IsHumanSentry()) { return CSquadMonster::GetScheduleOfType( SCHED_RELOAD ); } else { return &slGruntHideReload[ 0 ]; } } default: { return CHGrunt :: GetScheduleOfType ( Type ); } } } BOOL CTerrorist::IsHumanSentry() const { return (m_iTerrorFlags & TF_HUMAN_SENTRY); } BOOL CTerrorist::HasWeapon() { return m_bHasWeapon; /* (GetBodygroup(GUN_GROUP) != GUN_NONE)*/; } void CTerrorist::DropWeapon(Vector vecVelocity, Vector angVelocity) { Vector vecGunPos; Vector vecGunAngles; CBaseEntity *pGun; GetAttachment(0, vecGunPos, vecGunAngles); // switch to body group with no gun. SetBodygroup(GUN_GROUP, GUN_NONE); // now spawn a gun. if (FBitSet(pev->weapons, TERROR_SHOTGUN)) { pGun = DropItem("weapon_shotgun", vecGunPos, vecGunAngles); } else { pGun = DropItem("weapon_9mmAR", vecGunPos, vecGunAngles); } if (pGun) { pGun->pev->velocity = vecVelocity; pGun->pev->avelocity = angVelocity; } m_bHasWeapon = FALSE; } //========================================================= // DEAD TERRORIST PROP //========================================================= class CDeadTerrorist : public CDeadHGrunt { public: void Spawn(void); }; LINK_ENTITY_TO_CLASS(monster_terrorist_dead, CDeadTerrorist); //========================================================= // ********** DeadTerrorist SPAWN ********** //========================================================= void CDeadTerrorist::Spawn(void) { PRECACHE_MODEL("models/terrorist.mdl"); SET_MODEL(ENT(pev), "models/terrorist.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 terrorist with bad pose\n"); } // Corpses have less health pev->health = 8; // map old bodies onto new bodies switch (pev->body) { case 0: // Grunt with Gun pev->body = 0; pev->skin = 0; SetBodygroup(GUN_GROUP, GUN_MP5); break; case 1: // Commander with Gun pev->body = 0; pev->skin = 0; SetBodygroup(GUN_GROUP, GUN_MP5); break; case 2: // Grunt no Gun pev->body = 0; pev->skin = 0; SetBodygroup(GUN_GROUP, GUN_NONE); break; case 3: // Commander no Gun pev->body = 0; pev->skin = 0; SetBodygroup(GUN_GROUP, GUN_NONE); break; } MonsterInitDead(); }