Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

664 lines
16 KiB

/***
*
* 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();
}