halflife-photomode/dlls/agrunt.cpp

1166 lines
30 KiB
C++
Raw Normal View History

/***
2013-08-30 13:34:05 -07:00
*
* 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.
*
****/
//=========================================================
// Agrunt - Dominant, warlike alien grunt monster
//=========================================================
#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "schedule.h"
#include "squadmonster.h"
#include "weapons.h"
#include "soundent.h"
#include "hornet.h"
2013-08-30 13:34:05 -07:00
//=========================================================
// monster-specific schedule types
//=========================================================
enum
{
SCHED_AGRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1,
SCHED_AGRUNT_THREAT_DISPLAY,
};
//=========================================================
// monster-specific tasks
//=========================================================
enum
2013-08-30 13:34:05 -07:00
{
TASK_AGRUNT_SETUP_HIDE_ATTACK = LAST_COMMON_TASK + 1,
TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE,
};
int iAgruntMuzzleFlash;
//=========================================================
// Monster's Anim Events Go Here
//=========================================================
#define AGRUNT_AE_HORNET1 (1)
#define AGRUNT_AE_HORNET2 (2)
#define AGRUNT_AE_HORNET3 (3)
#define AGRUNT_AE_HORNET4 (4)
#define AGRUNT_AE_HORNET5 (5)
2013-08-30 13:34:05 -07:00
// some events are set up in the QC file that aren't recognized by the code yet.
#define AGRUNT_AE_PUNCH (6)
#define AGRUNT_AE_BITE (7)
2013-08-30 13:34:05 -07:00
#define AGRUNT_AE_LEFT_FOOT (10)
#define AGRUNT_AE_RIGHT_FOOT (11)
2013-08-30 13:34:05 -07:00
#define AGRUNT_AE_LEFT_PUNCH (12)
#define AGRUNT_AE_RIGHT_PUNCH (13)
2013-08-30 13:34:05 -07:00
#define AGRUNT_MELEE_DIST 100
2013-08-30 13:34:05 -07:00
class CAGrunt : public CSquadMonster
{
public:
void Spawn() override;
void Precache() override;
void SetYawSpeed() override;
int Classify() override;
int ISoundMask() override;
void HandleAnimEvent(MonsterEvent_t* pEvent) override;
void SetObjectCollisionBox() override
2013-08-30 13:34:05 -07:00
{
pev->absmin = pev->origin + Vector(-32, -32, 0);
pev->absmax = pev->origin + Vector(32, 32, 85);
2013-08-30 13:34:05 -07:00
}
Schedule_t* GetSchedule() override;
Schedule_t* GetScheduleOfType(int Type) override;
bool FCanCheckAttacks() override;
bool CheckMeleeAttack1(float flDot, float flDist) override;
bool CheckRangeAttack1(float flDot, float flDist) override;
void StartTask(Task_t* pTask) override;
void AlertSound() override;
void DeathSound() override;
void PainSound() override;
void AttackSound();
void PrescheduleThink() override;
void TraceAttack(entvars_t* pevAttacker, float flDamage, Vector vecDir, TraceResult* ptr, int bitsDamageType) override;
int IRelationship(CBaseEntity* pTarget) override;
void StopTalking();
bool ShouldSpeak();
2013-08-30 13:34:05 -07:00
CUSTOM_SCHEDULES;
bool Save(CSave& save) override;
bool Restore(CRestore& restore) override;
static TYPEDESCRIPTION m_SaveData[];
2013-08-30 13:34:05 -07:00
static const char* pAttackHitSounds[];
static const char* pAttackMissSounds[];
static const char* pAttackSounds[];
static const char* pDieSounds[];
static const char* pPainSounds[];
static const char* pIdleSounds[];
static const char* pAlertSounds[];
2013-08-30 13:34:05 -07:00
bool m_fCanHornetAttack;
float m_flNextHornetAttackCheck;
2013-08-30 13:34:05 -07:00
float m_flNextPainTime;
// three hacky fields for speech stuff. These don't really need to be saved.
float m_flNextSpeakTime;
float m_flNextWordTime;
int m_iLastWord;
2013-08-30 13:34:05 -07:00
};
LINK_ENTITY_TO_CLASS(monster_alien_grunt, CAGrunt);
2013-08-30 13:34:05 -07:00
TYPEDESCRIPTION CAGrunt::m_SaveData[] =
{
DEFINE_FIELD(CAGrunt, m_fCanHornetAttack, FIELD_BOOLEAN),
DEFINE_FIELD(CAGrunt, m_flNextHornetAttackCheck, FIELD_TIME),
DEFINE_FIELD(CAGrunt, m_flNextPainTime, FIELD_TIME),
DEFINE_FIELD(CAGrunt, m_flNextSpeakTime, FIELD_TIME),
DEFINE_FIELD(CAGrunt, m_flNextWordTime, FIELD_TIME),
DEFINE_FIELD(CAGrunt, m_iLastWord, FIELD_INTEGER),
2013-08-30 13:34:05 -07:00
};
IMPLEMENT_SAVERESTORE(CAGrunt, CSquadMonster);
2013-08-30 13:34:05 -07:00
const char* CAGrunt::pAttackHitSounds[] =
{
"zombie/claw_strike1.wav",
"zombie/claw_strike2.wav",
"zombie/claw_strike3.wav",
2013-08-30 13:34:05 -07:00
};
const char* CAGrunt::pAttackMissSounds[] =
{
"zombie/claw_miss1.wav",
"zombie/claw_miss2.wav",
2013-08-30 13:34:05 -07:00
};
const char* CAGrunt::pAttackSounds[] =
{
"agrunt/ag_attack1.wav",
"agrunt/ag_attack2.wav",
"agrunt/ag_attack3.wav",
2013-08-30 13:34:05 -07:00
};
const char* CAGrunt::pDieSounds[] =
{
"agrunt/ag_die1.wav",
"agrunt/ag_die4.wav",
"agrunt/ag_die5.wav",
2013-08-30 13:34:05 -07:00
};
const char* CAGrunt::pPainSounds[] =
{
"agrunt/ag_pain1.wav",
"agrunt/ag_pain2.wav",
"agrunt/ag_pain3.wav",
"agrunt/ag_pain4.wav",
"agrunt/ag_pain5.wav",
2013-08-30 13:34:05 -07:00
};
const char* CAGrunt::pIdleSounds[] =
{
"agrunt/ag_idle1.wav",
"agrunt/ag_idle2.wav",
"agrunt/ag_idle3.wav",
"agrunt/ag_idle4.wav",
2013-08-30 13:34:05 -07:00
};
const char* CAGrunt::pAlertSounds[] =
{
"agrunt/ag_alert1.wav",
"agrunt/ag_alert3.wav",
"agrunt/ag_alert4.wav",
"agrunt/ag_alert5.wav",
2013-08-30 13:34:05 -07:00
};
//=========================================================
// IRelationship - overridden because Human Grunts are
2013-08-30 13:34:05 -07:00
// Alien Grunt's nemesis.
//=========================================================
int CAGrunt::IRelationship(CBaseEntity* pTarget)
2013-08-30 13:34:05 -07:00
{
if (FClassnameIs(pTarget->pev, "monster_human_grunt"))
2013-08-30 13:34:05 -07:00
{
return R_NM;
}
return CSquadMonster::IRelationship(pTarget);
2013-08-30 13:34:05 -07:00
}
//=========================================================
// ISoundMask
2013-08-30 13:34:05 -07:00
//=========================================================
int CAGrunt::ISoundMask()
2013-08-30 13:34:05 -07:00
{
return bits_SOUND_WORLD |
bits_SOUND_COMBAT |
bits_SOUND_PLAYER |
bits_SOUND_DANGER;
2013-08-30 13:34:05 -07:00
}
//=========================================================
// TraceAttack
//=========================================================
void CAGrunt::TraceAttack(entvars_t* pevAttacker, float flDamage, Vector vecDir, TraceResult* ptr, int bitsDamageType)
2013-08-30 13:34:05 -07:00
{
if (ptr->iHitgroup == 10 && (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB)) != 0)
2013-08-30 13:34:05 -07:00
{
// hit armor
if (pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0, 10) < 1))
2013-08-30 13:34:05 -07:00
{
UTIL_Ricochet(ptr->vecEndPos, RANDOM_FLOAT(1, 2));
2013-08-30 13:34:05 -07:00
pev->dmgtime = gpGlobals->time;
}
if (RANDOM_LONG(0, 1) == 0)
2013-08-30 13:34:05 -07:00
{
Vector vecTracerDir = vecDir;
vecTracerDir.x += RANDOM_FLOAT(-0.3, 0.3);
vecTracerDir.y += RANDOM_FLOAT(-0.3, 0.3);
vecTracerDir.z += RANDOM_FLOAT(-0.3, 0.3);
2013-08-30 13:34:05 -07:00
vecTracerDir = vecTracerDir * -512;
MESSAGE_BEGIN(MSG_PVS, SVC_TEMPENTITY, ptr->vecEndPos);
WRITE_BYTE(TE_TRACER);
WRITE_COORD(ptr->vecEndPos.x);
WRITE_COORD(ptr->vecEndPos.y);
WRITE_COORD(ptr->vecEndPos.z);
2013-08-30 13:34:05 -07:00
WRITE_COORD(vecTracerDir.x);
WRITE_COORD(vecTracerDir.y);
WRITE_COORD(vecTracerDir.z);
2013-08-30 13:34:05 -07:00
MESSAGE_END();
}
flDamage -= 20;
if (flDamage <= 0)
flDamage = 0.1; // don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated
2013-08-30 13:34:05 -07:00
}
else
{
SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage); // a little surface blood.
TraceBleed(flDamage, vecDir, ptr, bitsDamageType);
2013-08-30 13:34:05 -07:00
}
AddMultiDamage(pevAttacker, this, flDamage, bitsDamageType);
2013-08-30 13:34:05 -07:00
}
//=========================================================
// StopTalking - won't speak again for 10-20 seconds.
//=========================================================
void CAGrunt::StopTalking()
2013-08-30 13:34:05 -07:00
{
m_flNextWordTime = m_flNextSpeakTime = gpGlobals->time + 10 + RANDOM_LONG(0, 10);
}
//=========================================================
// ShouldSpeak - Should this agrunt be talking?
//=========================================================
bool CAGrunt::ShouldSpeak()
2013-08-30 13:34:05 -07:00
{
if (m_flNextSpeakTime > gpGlobals->time)
2013-08-30 13:34:05 -07:00
{
// my time to talk is still in the future.
2021-11-19 13:43:33 +01:00
return false;
2013-08-30 13:34:05 -07:00
}
if ((pev->spawnflags & SF_MONSTER_GAG) != 0)
2013-08-30 13:34:05 -07:00
{
if (m_MonsterState != MONSTERSTATE_COMBAT)
2013-08-30 13:34:05 -07:00
{
// if gagged, don't talk outside of combat.
// if not going to talk because of this, put the talk time
// into the future a bit, so we don't talk immediately after
2013-08-30 13:34:05 -07:00
// going into combat
m_flNextSpeakTime = gpGlobals->time + 3;
2021-11-19 13:43:33 +01:00
return false;
2013-08-30 13:34:05 -07:00
}
}
2021-11-19 13:45:16 +01:00
return true;
2013-08-30 13:34:05 -07:00
}
//=========================================================
// PrescheduleThink
2013-08-30 13:34:05 -07:00
//=========================================================
void CAGrunt::PrescheduleThink()
2013-08-30 13:34:05 -07:00
{
if (ShouldSpeak())
2013-08-30 13:34:05 -07:00
{
if (m_flNextWordTime < gpGlobals->time)
2013-08-30 13:34:05 -07:00
{
int num = -1;
do
{
num = RANDOM_LONG(0, ARRAYSIZE(pIdleSounds) - 1);
} while (num == m_iLastWord);
2013-08-30 13:34:05 -07:00
m_iLastWord = num;
// play a new sound
EMIT_SOUND(ENT(pev), CHAN_VOICE, pIdleSounds[num], 1.0, ATTN_NORM);
2013-08-30 13:34:05 -07:00
// is this word our last?
if (RANDOM_LONG(1, 10) <= 1)
2013-08-30 13:34:05 -07:00
{
// stop talking.
StopTalking();
}
else
{
m_flNextWordTime = gpGlobals->time + RANDOM_FLOAT(0.5, 1);
2013-08-30 13:34:05 -07:00
}
}
}
}
//=========================================================
// DieSound
//=========================================================
void CAGrunt::DeathSound()
2013-08-30 13:34:05 -07:00
{
StopTalking();
2021-12-02 17:42:33 -05:00
EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pDieSounds), 1.0, ATTN_NORM);
2013-08-30 13:34:05 -07:00
}
//=========================================================
// AlertSound
//=========================================================
void CAGrunt::AlertSound()
2013-08-30 13:34:05 -07:00
{
StopTalking();
2021-12-02 17:42:33 -05:00
EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAlertSounds), 1.0, ATTN_NORM);
2013-08-30 13:34:05 -07:00
}
//=========================================================
// AttackSound
//=========================================================
void CAGrunt::AttackSound()
2013-08-30 13:34:05 -07:00
{
StopTalking();
2021-12-02 17:42:33 -05:00
EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAttackSounds), 1.0, ATTN_NORM);
2013-08-30 13:34:05 -07:00
}
//=========================================================
// PainSound
//=========================================================
void CAGrunt::PainSound()
2013-08-30 13:34:05 -07:00
{
if (m_flNextPainTime > gpGlobals->time)
2013-08-30 13:34:05 -07:00
{
return;
}
m_flNextPainTime = gpGlobals->time + 0.6;
StopTalking();
2021-12-02 17:42:33 -05:00
EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pPainSounds), 1.0, ATTN_NORM);
2013-08-30 13:34:05 -07:00
}
//=========================================================
// Classify - indicates this monster's place in the
2013-08-30 13:34:05 -07:00
// relationship table.
//=========================================================
int CAGrunt::Classify()
2013-08-30 13:34:05 -07:00
{
return CLASS_ALIEN_MILITARY;
2013-08-30 13:34:05 -07:00
}
//=========================================================
// SetYawSpeed - allows each sequence to have a different
// turn rate associated with it.
//=========================================================
void CAGrunt::SetYawSpeed()
2013-08-30 13:34:05 -07:00
{
int ys;
switch (m_Activity)
2013-08-30 13:34:05 -07:00
{
case ACT_TURN_LEFT:
case ACT_TURN_RIGHT:
ys = 110;
break;
default:
ys = 100;
2013-08-30 13:34:05 -07:00
}
pev->yaw_speed = ys;
}
//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//
// Returns number of events handled, 0 if none.
//=========================================================
void CAGrunt::HandleAnimEvent(MonsterEvent_t* pEvent)
2013-08-30 13:34:05 -07:00
{
switch (pEvent->event)
2013-08-30 13:34:05 -07:00
{
case AGRUNT_AE_HORNET1:
case AGRUNT_AE_HORNET2:
case AGRUNT_AE_HORNET3:
case AGRUNT_AE_HORNET4:
2021-11-29 20:55:01 +01:00
case AGRUNT_AE_HORNET5:
{
// m_vecEnemyLKP should be center of enemy body
Vector vecArmPos, vecArmDir;
Vector vecDirToEnemy;
Vector angDir;
if (HasConditions(bits_COND_SEE_ENEMY))
{
vecDirToEnemy = ((m_vecEnemyLKP)-pev->origin);
angDir = UTIL_VecToAngles(vecDirToEnemy);
vecDirToEnemy = vecDirToEnemy.Normalize();
}
else
2013-08-30 13:34:05 -07:00
{
angDir = pev->angles;
UTIL_MakeAimVectors(angDir);
vecDirToEnemy = gpGlobals->v_forward;
}
2013-08-30 13:34:05 -07:00
pev->effects = EF_MUZZLEFLASH;
// make angles +-180
if (angDir.x > 180)
{
angDir.x = angDir.x - 360;
}
2013-08-30 13:34:05 -07:00
SetBlending(0, angDir.x);
GetAttachment(0, vecArmPos, vecArmDir);
2013-08-30 13:34:05 -07:00
vecArmPos = vecArmPos + vecDirToEnemy * 32;
MESSAGE_BEGIN(MSG_PVS, SVC_TEMPENTITY, vecArmPos);
WRITE_BYTE(TE_SPRITE);
WRITE_COORD(vecArmPos.x); // pos
WRITE_COORD(vecArmPos.y);
WRITE_COORD(vecArmPos.z);
WRITE_SHORT(iAgruntMuzzleFlash); // model
WRITE_BYTE(6); // size * 10
WRITE_BYTE(128); // brightness
MESSAGE_END();
2013-08-30 13:34:05 -07:00
CBaseEntity* pHornet = CBaseEntity::Create("hornet", vecArmPos, UTIL_VecToAngles(vecDirToEnemy), edict());
UTIL_MakeVectors(pHornet->pev->angles);
pHornet->pev->velocity = gpGlobals->v_forward * 300;
2013-08-30 13:34:05 -07:00
switch (RANDOM_LONG(0, 2))
{
case 0:
EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, "agrunt/ag_fire1.wav", 1.0, ATTN_NORM, 0, 100);
break;
case 1:
EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, "agrunt/ag_fire2.wav", 1.0, ATTN_NORM, 0, 100);
break;
case 2:
EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, "agrunt/ag_fire3.wav", 1.0, ATTN_NORM, 0, 100);
break;
2013-08-30 13:34:05 -07:00
}
CBaseMonster* pHornetMonster = pHornet->MyMonsterPointer();
if (pHornetMonster)
{
pHornetMonster->m_hEnemy = m_hEnemy;
}
}
break;
2013-08-30 13:34:05 -07:00
case AGRUNT_AE_LEFT_FOOT:
switch (RANDOM_LONG(0, 1))
2013-08-30 13:34:05 -07:00
{
// left foot
case 0:
EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "player/pl_ladder2.wav", 1, ATTN_NORM, 0, 70);
break;
case 1:
EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "player/pl_ladder4.wav", 1, ATTN_NORM, 0, 70);
break;
2013-08-30 13:34:05 -07:00
}
break;
case AGRUNT_AE_RIGHT_FOOT:
// right foot
switch (RANDOM_LONG(0, 1))
2013-08-30 13:34:05 -07:00
{
case 0:
EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "player/pl_ladder1.wav", 1, ATTN_NORM, 0, 70);
break;
case 1:
EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "player/pl_ladder3.wav", 1, ATTN_NORM, 0, 70);
break;
2013-08-30 13:34:05 -07:00
}
break;
2021-11-29 20:55:01 +01:00
case AGRUNT_AE_LEFT_PUNCH:
{
CBaseEntity* pHurt = CheckTraceHullAttack(AGRUNT_MELEE_DIST, gSkillData.agruntDmgPunch, DMG_CLUB);
2013-08-30 13:34:05 -07:00
if (pHurt)
{
pHurt->pev->punchangle.y = -25;
pHurt->pev->punchangle.x = 8;
2013-08-30 13:34:05 -07:00
// OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above.
if (pHurt->IsPlayer())
2013-08-30 13:34:05 -07:00
{
// this is a player. Knock him around.
pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * 250;
2013-08-30 13:34:05 -07:00
}
2021-12-02 17:42:33 -05:00
EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pAttackHitSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5, 5));
2013-08-30 13:34:05 -07:00
Vector vecArmPos, vecArmAng;
GetAttachment(0, vecArmPos, vecArmAng);
SpawnBlood(vecArmPos, pHurt->BloodColor(), 25); // a little surface blood.
}
else
{
// Play a random attack miss sound
2021-12-02 17:42:33 -05:00
EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pAttackMissSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5, 5));
}
}
break;
2013-08-30 13:34:05 -07:00
2021-11-29 20:55:01 +01:00
case AGRUNT_AE_RIGHT_PUNCH:
{
CBaseEntity* pHurt = CheckTraceHullAttack(AGRUNT_MELEE_DIST, gSkillData.agruntDmgPunch, DMG_CLUB);
2013-08-30 13:34:05 -07:00
if (pHurt)
{
pHurt->pev->punchangle.y = 25;
pHurt->pev->punchangle.x = 8;
2013-08-30 13:34:05 -07:00
// OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above.
if (pHurt->IsPlayer())
2013-08-30 13:34:05 -07:00
{
// this is a player. Knock him around.
pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * -250;
2013-08-30 13:34:05 -07:00
}
2021-12-02 17:42:33 -05:00
EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pAttackHitSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5, 5));
Vector vecArmPos, vecArmAng;
GetAttachment(0, vecArmPos, vecArmAng);
SpawnBlood(vecArmPos, pHurt->BloodColor(), 25); // a little surface blood.
2013-08-30 13:34:05 -07:00
}
else
{
// Play a random attack miss sound
2021-12-02 17:42:33 -05:00
EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pAttackMissSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5, 5));
}
}
break;
2013-08-30 13:34:05 -07:00
default:
CSquadMonster::HandleAnimEvent(pEvent);
2013-08-30 13:34:05 -07:00
break;
}
}
//=========================================================
// Spawn
//=========================================================
void CAGrunt::Spawn()
2013-08-30 13:34:05 -07:00
{
Precache();
2013-08-30 13:34:05 -07:00
SET_MODEL(ENT(pev), "models/agrunt.mdl");
UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64));
pev->solid = SOLID_SLIDEBOX;
pev->movetype = MOVETYPE_STEP;
m_bloodColor = BLOOD_COLOR_GREEN;
pev->effects = 0;
pev->health = gSkillData.agruntHealth;
m_flFieldOfView = 0.2; // indicates the width of this monster's forward view cone ( as a dotproduct result )
m_MonsterState = MONSTERSTATE_NONE;
m_afCapability = 0;
m_afCapability |= bits_CAP_SQUAD;
2013-08-30 13:34:05 -07:00
m_HackedGunPos = Vector(24, 64, 48);
2013-08-30 13:34:05 -07:00
m_flNextSpeakTime = m_flNextWordTime = gpGlobals->time + 10 + RANDOM_LONG(0, 10);
2013-08-30 13:34:05 -07:00
MonsterInit();
}
//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CAGrunt::Precache()
2013-08-30 13:34:05 -07:00
{
PRECACHE_MODEL("models/agrunt.mdl");
2021-12-02 17:42:33 -05:00
PRECACHE_SOUND_ARRAY(pAttackHitSounds);
PRECACHE_SOUND_ARRAY(pAttackMissSounds);
PRECACHE_SOUND_ARRAY(pIdleSounds);
PRECACHE_SOUND_ARRAY(pDieSounds);
PRECACHE_SOUND_ARRAY(pPainSounds);
PRECACHE_SOUND_ARRAY(pAttackSounds);
PRECACHE_SOUND_ARRAY(pAlertSounds);
2013-08-30 13:34:05 -07:00
PRECACHE_SOUND("hassault/hw_shoot1.wav");
2013-08-30 13:34:05 -07:00
iAgruntMuzzleFlash = PRECACHE_MODEL("sprites/muz4.spr");
2013-08-30 13:34:05 -07:00
UTIL_PrecacheOther("hornet");
}
2013-08-30 13:34:05 -07:00
//=========================================================
// AI Schedules Specific to this monster
//=========================================================
//=========================================================
// Fail Schedule
//=========================================================
Task_t tlAGruntFail[] =
{
{TASK_STOP_MOVING, 0},
{TASK_SET_ACTIVITY, (float)ACT_IDLE},
{TASK_WAIT, (float)2},
{TASK_WAIT_PVS, (float)0},
2013-08-30 13:34:05 -07:00
};
Schedule_t slAGruntFail[] =
2013-08-30 13:34:05 -07:00
{
{tlAGruntFail,
ARRAYSIZE(tlAGruntFail),
bits_COND_CAN_RANGE_ATTACK1 |
bits_COND_CAN_MELEE_ATTACK1,
0,
"AGrunt Fail"},
2013-08-30 13:34:05 -07:00
};
//=========================================================
// Combat Fail Schedule
//=========================================================
Task_t tlAGruntCombatFail[] =
{
{TASK_STOP_MOVING, 0},
{TASK_SET_ACTIVITY, (float)ACT_IDLE},
{TASK_WAIT_FACE_ENEMY, (float)2},
{TASK_WAIT_PVS, (float)0},
2013-08-30 13:34:05 -07:00
};
Schedule_t slAGruntCombatFail[] =
2013-08-30 13:34:05 -07:00
{
{tlAGruntCombatFail,
ARRAYSIZE(tlAGruntCombatFail),
bits_COND_CAN_RANGE_ATTACK1 |
bits_COND_CAN_MELEE_ATTACK1,
0,
"AGrunt Combat Fail"},
2013-08-30 13:34:05 -07:00
};
//=========================================================
// Standoff schedule. Used in combat when a monster is
// hiding in cover or the enemy has moved out of sight.
2013-08-30 13:34:05 -07:00
// Should we look around in this schedule?
//=========================================================
Task_t tlAGruntStandoff[] =
{
{TASK_STOP_MOVING, (float)0},
{TASK_SET_ACTIVITY, (float)ACT_IDLE},
{TASK_WAIT_FACE_ENEMY, (float)2},
2013-08-30 13:34:05 -07:00
};
Schedule_t slAGruntStandoff[] =
2013-08-30 13:34:05 -07:00
{
{tlAGruntStandoff,
ARRAYSIZE(tlAGruntStandoff),
bits_COND_CAN_RANGE_ATTACK1 |
bits_COND_CAN_MELEE_ATTACK1 |
bits_COND_SEE_ENEMY |
bits_COND_NEW_ENEMY |
bits_COND_HEAR_SOUND,
bits_SOUND_DANGER,
"Agrunt Standoff"}};
2013-08-30 13:34:05 -07:00
//=========================================================
// Suppress
//=========================================================
Task_t tlAGruntSuppressHornet[] =
{
{TASK_STOP_MOVING, (float)0},
{TASK_RANGE_ATTACK1, (float)0},
2013-08-30 13:34:05 -07:00
};
Schedule_t slAGruntSuppress[] =
{
{
tlAGruntSuppressHornet,
ARRAYSIZE(tlAGruntSuppressHornet),
0,
0,
"AGrunt Suppress Hornet",
},
2013-08-30 13:34:05 -07:00
};
//=========================================================
// primary range attacks
//=========================================================
Task_t tlAGruntRangeAttack1[] =
{
{TASK_STOP_MOVING, (float)0},
{TASK_FACE_ENEMY, (float)0},
{TASK_RANGE_ATTACK1, (float)0},
2013-08-30 13:34:05 -07:00
};
Schedule_t slAGruntRangeAttack1[] =
{
{tlAGruntRangeAttack1,
ARRAYSIZE(tlAGruntRangeAttack1),
bits_COND_NEW_ENEMY |
bits_COND_ENEMY_DEAD |
bits_COND_HEAVY_DAMAGE,
0,
"AGrunt Range Attack1"},
2013-08-30 13:34:05 -07:00
};
Task_t tlAGruntHiddenRangeAttack1[] =
{
{TASK_SET_FAIL_SCHEDULE, (float)SCHED_STANDOFF},
{TASK_AGRUNT_SETUP_HIDE_ATTACK, 0},
{TASK_STOP_MOVING, 0},
{TASK_FACE_IDEAL, 0},
{TASK_RANGE_ATTACK1_NOTURN, (float)0},
2013-08-30 13:34:05 -07:00
};
Schedule_t slAGruntHiddenRangeAttack[] =
{
{tlAGruntHiddenRangeAttack1,
ARRAYSIZE(tlAGruntHiddenRangeAttack1),
bits_COND_NEW_ENEMY |
bits_COND_HEAVY_DAMAGE |
bits_COND_HEAR_SOUND,
bits_SOUND_DANGER,
"AGrunt Hidden Range Attack1"},
2013-08-30 13:34:05 -07:00
};
//=========================================================
// Take cover from enemy! Tries lateral cover before node
// cover!
2013-08-30 13:34:05 -07:00
//=========================================================
Task_t tlAGruntTakeCoverFromEnemy[] =
{
{TASK_STOP_MOVING, (float)0},
{TASK_WAIT, (float)0.2},
{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},
2013-08-30 13:34:05 -07:00
};
Schedule_t slAGruntTakeCoverFromEnemy[] =
{
{tlAGruntTakeCoverFromEnemy,
ARRAYSIZE(tlAGruntTakeCoverFromEnemy),
bits_COND_NEW_ENEMY,
0,
"AGruntTakeCoverFromEnemy"},
2013-08-30 13:34:05 -07:00
};
//=========================================================
// Victory dance!
//=========================================================
Task_t tlAGruntVictoryDance[] =
{
{TASK_STOP_MOVING, (float)0},
{TASK_SET_FAIL_SCHEDULE, (float)SCHED_AGRUNT_THREAT_DISPLAY},
{TASK_WAIT, (float)0.2},
{TASK_AGRUNT_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_CROUCH},
{TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE},
{TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE},
{TASK_PLAY_SEQUENCE, (float)ACT_STAND},
{TASK_PLAY_SEQUENCE, (float)ACT_THREAT_DISPLAY},
{TASK_PLAY_SEQUENCE, (float)ACT_CROUCH},
{TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE},
{TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE},
{TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE},
{TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE},
{TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE},
{TASK_PLAY_SEQUENCE, (float)ACT_STAND},
2013-08-30 13:34:05 -07:00
};
Schedule_t slAGruntVictoryDance[] =
{
{tlAGruntVictoryDance,
ARRAYSIZE(tlAGruntVictoryDance),
bits_COND_NEW_ENEMY |
bits_COND_LIGHT_DAMAGE |
bits_COND_HEAVY_DAMAGE,
0,
"AGruntVictoryDance"},
2013-08-30 13:34:05 -07:00
};
//=========================================================
//=========================================================
Task_t tlAGruntThreatDisplay[] =
{
{TASK_STOP_MOVING, (float)0},
{TASK_FACE_ENEMY, (float)0},
{TASK_PLAY_SEQUENCE, (float)ACT_THREAT_DISPLAY},
2013-08-30 13:34:05 -07:00
};
Schedule_t slAGruntThreatDisplay[] =
{
{tlAGruntThreatDisplay,
ARRAYSIZE(tlAGruntThreatDisplay),
bits_COND_NEW_ENEMY |
bits_COND_LIGHT_DAMAGE |
bits_COND_HEAVY_DAMAGE,
bits_SOUND_PLAYER |
bits_SOUND_COMBAT |
bits_SOUND_WORLD,
"AGruntThreatDisplay"},
2013-08-30 13:34:05 -07:00
};
DEFINE_CUSTOM_SCHEDULES(CAGrunt){
2013-08-30 13:34:05 -07:00
slAGruntFail,
slAGruntCombatFail,
slAGruntStandoff,
slAGruntSuppress,
slAGruntRangeAttack1,
slAGruntHiddenRangeAttack,
slAGruntTakeCoverFromEnemy,
slAGruntVictoryDance,
slAGruntThreatDisplay,
};
IMPLEMENT_CUSTOM_SCHEDULES(CAGrunt, CSquadMonster);
2013-08-30 13:34:05 -07:00
//=========================================================
// FCanCheckAttacks - this is overridden for alien grunts
// because they can use their smart weapons against unseen
// enemies. Base class doesn't attack anyone it can't see.
//=========================================================
bool CAGrunt::FCanCheckAttacks()
2013-08-30 13:34:05 -07:00
{
if (!HasConditions(bits_COND_ENEMY_TOOFAR))
2013-08-30 13:34:05 -07:00
{
2021-11-19 13:45:16 +01:00
return true;
2013-08-30 13:34:05 -07:00
}
else
{
2021-11-19 13:43:33 +01:00
return false;
2013-08-30 13:34:05 -07:00
}
}
//=========================================================
// CheckMeleeAttack1 - alien grunts zap the crap out of
// any enemy that gets too close.
2013-08-30 13:34:05 -07:00
//=========================================================
bool CAGrunt::CheckMeleeAttack1(float flDot, float flDist)
2013-08-30 13:34:05 -07:00
{
if (HasConditions(bits_COND_SEE_ENEMY) && flDist <= AGRUNT_MELEE_DIST && flDot >= 0.6 && m_hEnemy != NULL)
2013-08-30 13:34:05 -07:00
{
2021-11-19 13:45:16 +01:00
return true;
2013-08-30 13:34:05 -07:00
}
2021-11-19 13:43:33 +01:00
return false;
2013-08-30 13:34:05 -07:00
}
//=========================================================
// CheckRangeAttack1
2013-08-30 13:34:05 -07:00
//
// !!!LATER - we may want to load balance this. Several
// tracelines are done, so we may not want to do this every
// server frame. Definitely not while firing.
2013-08-30 13:34:05 -07:00
//=========================================================
bool CAGrunt::CheckRangeAttack1(float flDot, float flDist)
2013-08-30 13:34:05 -07:00
{
if (gpGlobals->time < m_flNextHornetAttackCheck)
2013-08-30 13:34:05 -07:00
{
return m_fCanHornetAttack;
}
if (HasConditions(bits_COND_SEE_ENEMY) && flDist >= AGRUNT_MELEE_DIST && flDist <= 1024 && flDot >= 0.5 && NoFriendlyFire())
2013-08-30 13:34:05 -07:00
{
TraceResult tr;
Vector vecArmPos, vecArmDir;
2013-08-30 13:34:05 -07:00
// verify that a shot fired from the gun will hit the enemy before the world.
// !!!LATER - we may wish to do something different for projectile weapons as opposed to instant-hit
UTIL_MakeVectors(pev->angles);
GetAttachment(0, vecArmPos, vecArmDir);
// UTIL_TraceLine( vecArmPos, vecArmPos + gpGlobals->v_forward * 256, ignore_monsters, ENT(pev), &tr);
UTIL_TraceLine(vecArmPos, m_hEnemy->BodyTarget(vecArmPos), dont_ignore_monsters, ENT(pev), &tr);
2013-08-30 13:34:05 -07:00
if (tr.flFraction == 1.0 || tr.pHit == m_hEnemy->edict())
2013-08-30 13:34:05 -07:00
{
m_flNextHornetAttackCheck = gpGlobals->time + RANDOM_FLOAT(2, 5);
2021-11-19 13:45:16 +01:00
m_fCanHornetAttack = true;
2013-08-30 13:34:05 -07:00
return m_fCanHornetAttack;
}
}
m_flNextHornetAttackCheck = gpGlobals->time + 0.2; // don't check for half second if this check wasn't successful
2021-11-19 13:43:33 +01:00
m_fCanHornetAttack = false;
2013-08-30 13:34:05 -07:00
return m_fCanHornetAttack;
}
//=========================================================
// StartTask
//=========================================================
void CAGrunt::StartTask(Task_t* pTask)
2013-08-30 13:34:05 -07:00
{
switch (pTask->iTask)
2013-08-30 13:34:05 -07:00
{
2021-11-29 20:55:01 +01:00
case TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE:
{
UTIL_MakeVectors(pev->angles);
if (BuildRoute(m_vecEnemyLKP - gpGlobals->v_forward * 50, bits_MF_TO_LOCATION, NULL))
2013-08-30 13:34:05 -07:00
{
TaskComplete();
2013-08-30 13:34:05 -07:00
}
else
{
ALERT(at_aiconsole, "AGruntGetPathToEnemyCorpse failed!!\n");
TaskFail();
}
}
break;
2013-08-30 13:34:05 -07:00
case TASK_AGRUNT_SETUP_HIDE_ATTACK:
// alien grunt shoots hornets back out into the open from a concealed location.
2013-08-30 13:34:05 -07:00
// try to find a spot to throw that gives the smart weapon a good chance of finding the enemy.
// ideally, this spot is along a line that is perpendicular to a line drawn from the agrunt to the enemy.
CBaseMonster* pEnemyMonsterPtr;
2013-08-30 13:34:05 -07:00
pEnemyMonsterPtr = m_hEnemy->MyMonsterPointer();
if (pEnemyMonsterPtr)
2013-08-30 13:34:05 -07:00
{
Vector vecCenter;
TraceResult tr;
bool fSkip;
2013-08-30 13:34:05 -07:00
2021-11-19 13:43:33 +01:00
fSkip = false;
2013-08-30 13:34:05 -07:00
vecCenter = Center();
UTIL_VecToAngles(m_vecEnemyLKP - pev->origin);
2013-08-30 13:34:05 -07:00
UTIL_TraceLine(Center() + gpGlobals->v_forward * 128, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr);
if (tr.flFraction == 1.0)
2013-08-30 13:34:05 -07:00
{
MakeIdealYaw(pev->origin + gpGlobals->v_right * 128);
2021-11-19 13:45:16 +01:00
fSkip = true;
2013-08-30 13:34:05 -07:00
TaskComplete();
}
if (!fSkip)
2013-08-30 13:34:05 -07:00
{
UTIL_TraceLine(Center() - gpGlobals->v_forward * 128, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr);
if (tr.flFraction == 1.0)
2013-08-30 13:34:05 -07:00
{
MakeIdealYaw(pev->origin - gpGlobals->v_right * 128);
2021-11-19 13:45:16 +01:00
fSkip = true;
2013-08-30 13:34:05 -07:00
TaskComplete();
}
}
if (!fSkip)
2013-08-30 13:34:05 -07:00
{
UTIL_TraceLine(Center() + gpGlobals->v_forward * 256, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr);
if (tr.flFraction == 1.0)
2013-08-30 13:34:05 -07:00
{
MakeIdealYaw(pev->origin + gpGlobals->v_right * 256);
2021-11-19 13:45:16 +01:00
fSkip = true;
2013-08-30 13:34:05 -07:00
TaskComplete();
}
}
if (!fSkip)
2013-08-30 13:34:05 -07:00
{
UTIL_TraceLine(Center() - gpGlobals->v_forward * 256, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr);
if (tr.flFraction == 1.0)
2013-08-30 13:34:05 -07:00
{
MakeIdealYaw(pev->origin - gpGlobals->v_right * 256);
2021-11-19 13:45:16 +01:00
fSkip = true;
2013-08-30 13:34:05 -07:00
TaskComplete();
}
}
if (!fSkip)
2013-08-30 13:34:05 -07:00
{
TaskFail();
}
}
else
{
ALERT(at_aiconsole, "AGRunt - no enemy monster ptr!!!\n");
2013-08-30 13:34:05 -07:00
TaskFail();
}
break;
default:
CSquadMonster::StartTask(pTask);
2013-08-30 13:34:05 -07:00
break;
}
}
//=========================================================
// 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* CAGrunt::GetSchedule()
2013-08-30 13:34:05 -07:00
{
if (HasConditions(bits_COND_HEAR_SOUND))
2013-08-30 13:34:05 -07:00
{
CSound* pSound;
2013-08-30 13:34:05 -07:00
pSound = PBestSound();
ASSERT(pSound != NULL);
if (pSound && (pSound->m_iType & bits_SOUND_DANGER) != 0)
2013-08-30 13:34:05 -07:00
{
// dangerous sound nearby!
return GetScheduleOfType(SCHED_TAKE_COVER_FROM_BEST_SOUND);
2013-08-30 13:34:05 -07:00
}
}
switch (m_MonsterState)
2013-08-30 13:34:05 -07:00
{
2021-11-29 20:55:01 +01:00
case MONSTERSTATE_COMBAT:
{
// dead enemy
if (HasConditions(bits_COND_ENEMY_DEAD))
2013-08-30 13:34:05 -07:00
{
// call base class, all code to handle dead enemies is centralized there.
return CBaseMonster::GetSchedule();
}
2013-08-30 13:34:05 -07:00
if (HasConditions(bits_COND_NEW_ENEMY))
{
return GetScheduleOfType(SCHED_WAKE_ANGRY);
}
2013-08-30 13:34:05 -07:00
// zap player!
if (HasConditions(bits_COND_CAN_MELEE_ATTACK1))
{
AttackSound(); // this is a total hack. Should be parto f the schedule
return GetScheduleOfType(SCHED_MELEE_ATTACK1);
}
2013-08-30 13:34:05 -07:00
if (HasConditions(bits_COND_HEAVY_DAMAGE))
{
return GetScheduleOfType(SCHED_SMALL_FLINCH);
}
2013-08-30 13:34:05 -07:00
// can attack
if (HasConditions(bits_COND_CAN_RANGE_ATTACK1) && OccupySlot(bits_SLOTS_AGRUNT_HORNET))
{
return GetScheduleOfType(SCHED_RANGE_ATTACK1);
}
2013-08-30 13:34:05 -07:00
if (OccupySlot(bits_SLOT_AGRUNT_CHASE))
{
return GetScheduleOfType(SCHED_CHASE_ENEMY);
2013-08-30 13:34:05 -07:00
}
return GetScheduleOfType(SCHED_STANDOFF);
}
2013-08-30 13:34:05 -07:00
}
return CSquadMonster::GetSchedule();
2013-08-30 13:34:05 -07:00
}
//=========================================================
//=========================================================
Schedule_t* CAGrunt::GetScheduleOfType(int Type)
2013-08-30 13:34:05 -07:00
{
switch (Type)
2013-08-30 13:34:05 -07:00
{
case SCHED_TAKE_COVER_FROM_ENEMY:
return &slAGruntTakeCoverFromEnemy[0];
2013-08-30 13:34:05 -07:00
break;
2013-08-30 13:34:05 -07:00
case SCHED_RANGE_ATTACK1:
if (HasConditions(bits_COND_SEE_ENEMY))
2013-08-30 13:34:05 -07:00
{
//normal attack
return &slAGruntRangeAttack1[0];
2013-08-30 13:34:05 -07:00
}
else
{
// attack an unseen enemy
// return &slAGruntHiddenRangeAttack[ 0 ];
return &slAGruntRangeAttack1[0];
2013-08-30 13:34:05 -07:00
}
break;
case SCHED_AGRUNT_THREAT_DISPLAY:
return &slAGruntThreatDisplay[0];
2013-08-30 13:34:05 -07:00
break;
case SCHED_AGRUNT_SUPPRESS:
return &slAGruntSuppress[0];
2013-08-30 13:34:05 -07:00
break;
case SCHED_STANDOFF:
return &slAGruntStandoff[0];
2013-08-30 13:34:05 -07:00
break;
case SCHED_VICTORY_DANCE:
return &slAGruntVictoryDance[0];
2013-08-30 13:34:05 -07:00
break;
case SCHED_FAIL:
// no fail schedule specified, so pick a good generic one.
{
if (m_hEnemy != NULL)
2013-08-30 13:34:05 -07:00
{
// I have an enemy
// !!!LATER - what if this enemy is really far away and i'm chasing him?
// this schedule will make me stop, face his last known position for 2
2013-08-30 13:34:05 -07:00
// seconds, and then try to move again
return &slAGruntCombatFail[0];
2013-08-30 13:34:05 -07:00
}
return &slAGruntFail[0];
2013-08-30 13:34:05 -07:00
}
break;
}
return CSquadMonster::GetScheduleOfType(Type);
2013-08-30 13:34:05 -07:00
}