mirror of
https://github.com/Gigaslav/HL2Overcharged.git
synced 2026-01-05 06:10:21 +03:00
963 lines
23 KiB
C++
963 lines
23 KiB
C++
#include "cbase.h"
|
|
#include "AI_Default.h"
|
|
#include "AI_Task.h"
|
|
#include "AI_Schedule.h"
|
|
#include "AI_Node.h"
|
|
#include "AI_Hull.h"
|
|
#include "AI_Hint.h"
|
|
#include "AI_Route.h"
|
|
#include "soundent.h"
|
|
#include "game.h"
|
|
#include "NPCEvent.h"
|
|
#include "EntityList.h"
|
|
#include "activitylist.h"
|
|
#include "animation.h"
|
|
#include "basecombatweapon.h"
|
|
#include "IEffects.h"
|
|
#include "vstdlib/random.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "ammodef.h"
|
|
#include "util.h"
|
|
#include "ai_basenpc.h"
|
|
#include "grenade_frag.h"
|
|
#include "movevars_shared.h"
|
|
#include "ai_basenpc.h"
|
|
|
|
|
|
ConVar sk_alien_assassin_health("sk_alien_assassin_health", "120");
|
|
|
|
#define FRAG_ATTACHMENT "grenadehand"
|
|
#define KNIFE_ATTACHMENT "guntip"
|
|
//=========================================================
|
|
// monster-specific schedule types
|
|
//=========================================================
|
|
enum
|
|
{
|
|
SCHED_ASSASSIN_EXPOSED = LAST_SHARED_SCHEDULE,// cover was blown.
|
|
SCHED_ASSASSIN_JUMP, // fly through the air
|
|
SCHED_ASSASSIN_JUMP_ATTACK, // fly through the air and shoot
|
|
SCHED_ASSASSIN_JUMP_LAND, // hit and run away
|
|
SCHED_ASSASSIN_FAIL,
|
|
SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY1,
|
|
SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2,
|
|
SCHED_ASSASSIN_TAKE_COVER_FROM_BEST_SOUND,
|
|
SCHED_ASSASSIN_HIDE,
|
|
SCHED_ASSASSIN_HUNT,
|
|
};
|
|
|
|
Activity ACT_ASSASSIN_FLY_UP;
|
|
Activity ACT_ASSASSIN_FLY_ATTACK;
|
|
Activity ACT_ASSASSIN_FLY_DOWN;
|
|
|
|
//=========================================================
|
|
// monster-specific tasks
|
|
//=========================================================
|
|
|
|
enum
|
|
{
|
|
TASK_ASSASSIN_FALL_TO_GROUND = LAST_SHARED_TASK + 1, // falling and waiting to hit ground
|
|
};
|
|
|
|
|
|
//=========================================================
|
|
// Monster's Anim Events Go Here
|
|
//=========================================================
|
|
#define ASSASSIN_AE_SHOOT1 1
|
|
#define ASSASSIN_AE_TOSS1 2
|
|
#define ASSASSIN_AE_JUMP 3
|
|
|
|
|
|
#define MEMORY_BADJUMP bits_MEMORY_CUSTOM1
|
|
|
|
class CNPC_AlienAssassin : public CAI_BaseNPC
|
|
{
|
|
DECLARE_CLASS(CNPC_AlienAssassin, CAI_BaseNPC);
|
|
|
|
public:
|
|
void Spawn(void);
|
|
void Precache(void);
|
|
|
|
int TranslateSchedule(int scheduleType);
|
|
|
|
void HandleAnimEvent(animevent_t *pEvent);
|
|
float MaxYawSpeed() { return 360.0f; }
|
|
|
|
void Shoot(void);
|
|
|
|
int MeleeAttack1Conditions(float flDot, float flDist);
|
|
int RangeAttack1Conditions(float flDot, float flDist);
|
|
int RangeAttack2Conditions(float flDot, float flDist);
|
|
|
|
int SelectSchedule(void);
|
|
|
|
void RunTask(const Task_t *pTask);
|
|
void StartTask(const Task_t *pTask);
|
|
|
|
Class_T Classify(void);
|
|
|
|
int GetSoundInterests(void);
|
|
|
|
void RunAI(void);
|
|
|
|
float m_flLastShot;
|
|
float m_flDiviation;
|
|
|
|
float m_flNextJump;
|
|
Vector m_vecJumpVelocity;
|
|
|
|
float m_flNextGrenadeCheck;
|
|
Vector m_vecTossVelocity;
|
|
bool m_fThrowGrenade;
|
|
|
|
int m_iTargetRanderamt;
|
|
|
|
int m_iFrustration;
|
|
|
|
int m_iShell;
|
|
int m_iAmmoType;
|
|
|
|
public:
|
|
DECLARE_DATADESC();
|
|
DEFINE_CUSTOM_AI;
|
|
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS(npc_alien_assassin, CNPC_AlienAssassin);
|
|
|
|
BEGIN_DATADESC(CNPC_AlienAssassin)
|
|
DEFINE_FIELD(m_flLastShot, FIELD_TIME),
|
|
DEFINE_FIELD(m_flDiviation, FIELD_FLOAT),
|
|
|
|
DEFINE_FIELD(m_flNextJump, FIELD_TIME),
|
|
DEFINE_FIELD(m_vecJumpVelocity, FIELD_VECTOR),
|
|
|
|
DEFINE_FIELD(m_flNextGrenadeCheck, FIELD_TIME),
|
|
DEFINE_FIELD(m_vecTossVelocity, FIELD_VECTOR),
|
|
DEFINE_FIELD(m_fThrowGrenade, FIELD_BOOLEAN),
|
|
|
|
DEFINE_FIELD(m_iTargetRanderamt, FIELD_INTEGER),
|
|
DEFINE_FIELD(m_iFrustration, FIELD_INTEGER),
|
|
END_DATADESC()
|
|
|
|
//=========================================================
|
|
// Spawn
|
|
//=========================================================
|
|
void CNPC_AlienAssassin::Spawn()
|
|
{
|
|
Precache();
|
|
|
|
SetModel("models/hassassin.mdl");
|
|
|
|
SetHullType(HULL_HUMAN);
|
|
SetHullSizeNormal();
|
|
|
|
|
|
SetNavType(NAV_GROUND);
|
|
SetSolid(SOLID_BBOX);
|
|
AddSolidFlags(FSOLID_NOT_STANDABLE);
|
|
SetMoveType(MOVETYPE_STEP);
|
|
m_bloodColor = BLOOD_COLOR_RED;
|
|
//m_fEffects = 0;
|
|
|
|
m_iHealth = sk_alien_assassin_health.GetFloat();
|
|
m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result )
|
|
m_NPCState = NPC_STATE_NONE;
|
|
|
|
m_HackedGunPos = Vector(0, 24, 48);
|
|
|
|
m_iTargetRanderamt = 20;
|
|
SetRenderColor(255, 255, 255, 20);
|
|
m_nRenderMode = kRenderTransTexture;
|
|
|
|
CapabilitiesClear();
|
|
CapabilitiesAdd(bits_CAP_MOVE_GROUND);
|
|
CapabilitiesAdd(bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_INNATE_MELEE_ATTACK1);
|
|
|
|
NPCInit();
|
|
}
|
|
|
|
//=========================================================
|
|
// Precache - precaches all resources this monster needs
|
|
//=========================================================
|
|
void CNPC_AlienAssassin::Precache()
|
|
{
|
|
m_iAmmoType = GetAmmoDef()->Index("9mmRound");
|
|
|
|
engine->PrecacheModel("models/hassassin.mdl");
|
|
|
|
enginesound->PrecacheSound("weapons/pl_gun1.wav");
|
|
enginesound->PrecacheSound("weapons/pl_gun2.wav");
|
|
|
|
enginesound->PrecacheSound("debris/beamstart1.wav");
|
|
|
|
UTIL_PrecacheOther("npc_handgrenade");
|
|
|
|
enginesound->PrecacheSound("player/pl_step1.wav");
|
|
enginesound->PrecacheSound("player/pl_step2.wav");
|
|
enginesound->PrecacheSound("player/pl_step3.wav");
|
|
enginesound->PrecacheSound("player/pl_step4.wav");
|
|
|
|
// m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell
|
|
}
|
|
|
|
int CNPC_AlienAssassin::GetSoundInterests(void)
|
|
{
|
|
return SOUND_WORLD |
|
|
SOUND_COMBAT |
|
|
SOUND_PLAYER |
|
|
SOUND_DANGER;
|
|
}
|
|
|
|
Class_T CNPC_AlienAssassin::Classify(void)
|
|
{
|
|
return CLASS_COMBINE;
|
|
}
|
|
|
|
//=========================================================
|
|
// CheckMeleeAttack1 - jump like crazy if the enemy gets too close.
|
|
//=========================================================
|
|
int CNPC_AlienAssassin::MeleeAttack1Conditions(float flDot, float flDist)
|
|
{
|
|
if (m_flNextJump < gpGlobals->curtime && (flDist <= 128 || HasMemory(MEMORY_BADJUMP)) && GetEnemy() != NULL)
|
|
{
|
|
trace_t tr;
|
|
|
|
Vector vecMin = Vector(random->RandomFloat(0, -64), random->RandomFloat(0, -64), 0);
|
|
Vector vecMax = Vector(random->RandomFloat(0, 64), random->RandomFloat(0, 64), 160);
|
|
|
|
Vector vecDest = GetAbsOrigin() + Vector(random->RandomFloat(-64, 64), random->RandomFloat(-64, 64), 160);
|
|
|
|
UTIL_TraceHull(GetAbsOrigin() + Vector(0, 0, 36), GetAbsOrigin() + Vector(0, 0, 36), vecMin, vecMax, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
|
|
|
|
//NDebugOverlay::Box( GetAbsOrigin() + Vector( 0, 0, 36 ), vecMin, vecMax, 0,0, 255, 0, 2.0 );
|
|
|
|
if (tr.startsolid || tr.fraction < 1.0)
|
|
{
|
|
return COND_TOO_CLOSE_TO_ATTACK;
|
|
}
|
|
|
|
float flGravity = sv_gravity.GetFloat();
|
|
|
|
float time = sqrt(160 / (0.5 * flGravity));
|
|
float speed = flGravity * time / 160;
|
|
m_vecJumpVelocity = (vecDest - GetAbsOrigin()) * speed;
|
|
|
|
return COND_CAN_MELEE_ATTACK1;
|
|
}
|
|
|
|
if (flDist > 128)
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
|
|
return COND_NONE;
|
|
}
|
|
|
|
//=========================================================
|
|
// CheckRangeAttack1 - drop a cap in their ass
|
|
//
|
|
//=========================================================
|
|
int CNPC_AlienAssassin::RangeAttack1Conditions(float flDot, float flDist)
|
|
{
|
|
if (!HasCondition(COND_ENEMY_OCCLUDED) && flDist > 64 && flDist <= 2048)
|
|
{
|
|
trace_t tr;
|
|
|
|
Vector vecSrc = GetAbsOrigin() + m_HackedGunPos;
|
|
|
|
// verify that a bullet fired from the gun will hit the enemy before the world.
|
|
UTIL_TraceLine(vecSrc, GetEnemy()->BodyTarget(vecSrc), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
|
|
|
|
if (tr.fraction == 1.0 || tr.m_pEnt == GetEnemy())
|
|
{
|
|
return COND_CAN_RANGE_ATTACK1;
|
|
}
|
|
}
|
|
|
|
return COND_NONE;
|
|
}
|
|
|
|
//=========================================================
|
|
// CheckRangeAttack2 - toss grenade is enemy gets in the way and is too close.
|
|
//=========================================================
|
|
int CNPC_AlienAssassin::RangeAttack2Conditions(float flDot, float flDist)
|
|
{
|
|
m_fThrowGrenade = false;
|
|
if (!FBitSet(GetEnemy()->GetFlags(), FL_ONGROUND))
|
|
{
|
|
// don't throw grenades at anything that isn't on the ground!
|
|
return COND_NONE;
|
|
}
|
|
|
|
// don't get grenade happy unless the player starts to piss you off
|
|
if (m_iFrustration <= 2)
|
|
return COND_NONE;
|
|
|
|
if (m_flNextGrenadeCheck < gpGlobals->curtime && !HasCondition(COND_ENEMY_OCCLUDED) && flDist <= 512)
|
|
{
|
|
Vector vTossPos;
|
|
QAngle vAngles;
|
|
|
|
GetAttachment("grenadehand", vTossPos, vAngles);
|
|
|
|
Vector vecToss = VecCheckThrow(this, vTossPos, GetEnemy()->WorldSpaceCenter(), flDist, 0.5); // use dist as speed to get there in 1 second
|
|
|
|
if (vecToss != vec3_origin)
|
|
{
|
|
m_vecTossVelocity = vecToss;
|
|
|
|
// throw a hand grenade
|
|
m_fThrowGrenade = TRUE;
|
|
|
|
return COND_CAN_RANGE_ATTACK2;
|
|
}
|
|
}
|
|
|
|
return COND_NONE;
|
|
}
|
|
|
|
//=========================================================
|
|
// StartTask
|
|
//=========================================================
|
|
void CNPC_AlienAssassin::StartTask(const Task_t *pTask)
|
|
{
|
|
switch (pTask->iTask)
|
|
{
|
|
case TASK_RANGE_ATTACK2:
|
|
if (!m_fThrowGrenade)
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
BaseClass::StartTask(pTask);
|
|
}
|
|
break;
|
|
case TASK_ASSASSIN_FALL_TO_GROUND:
|
|
break;
|
|
default:
|
|
BaseClass::StartTask(pTask);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// RunTask
|
|
//=========================================================
|
|
void CNPC_AlienAssassin::RunTask(const Task_t *pTask)
|
|
{
|
|
switch (pTask->iTask)
|
|
{
|
|
case TASK_ASSASSIN_FALL_TO_GROUND:
|
|
GetMotor()->SetIdealYawAndUpdate(GetEnemyLKP());
|
|
|
|
if (IsSequenceFinished())
|
|
{
|
|
if (GetAbsVelocity().z > 0)
|
|
{
|
|
SetActivity(ACT_ASSASSIN_FLY_UP);
|
|
}
|
|
else if (HasCondition(COND_SEE_ENEMY))
|
|
{
|
|
SetActivity(ACT_ASSASSIN_FLY_ATTACK);
|
|
//m_flCycle = 0;
|
|
SetCycle(0);
|
|
}
|
|
else
|
|
{
|
|
SetActivity(ACT_ASSASSIN_FLY_DOWN);
|
|
//m_flCycle = 0;
|
|
SetCycle(0);
|
|
}
|
|
|
|
ResetSequenceInfo();
|
|
}
|
|
if (GetFlags() & FL_ONGROUND)
|
|
{
|
|
// ALERT( at_console, "on ground\n");
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
default:
|
|
BaseClass::RunTask(pTask);
|
|
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.
|
|
//=========================================================
|
|
int CNPC_AlienAssassin::SelectSchedule(void)
|
|
{
|
|
switch (m_NPCState)
|
|
{
|
|
case NPC_STATE_IDLE:
|
|
case NPC_STATE_ALERT:
|
|
{
|
|
if (HasCondition(COND_HEAR_DANGER) || HasCondition(COND_HEAR_COMBAT))
|
|
{
|
|
if (HasCondition(COND_HEAR_DANGER))
|
|
return SCHED_TAKE_COVER_FROM_BEST_SOUND;
|
|
|
|
else
|
|
return SCHED_INVESTIGATE_SOUND;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NPC_STATE_COMBAT:
|
|
{
|
|
// dead enemy
|
|
if (HasCondition(COND_ENEMY_DEAD))
|
|
{
|
|
// call base class, all code to handle dead enemies is centralized there.
|
|
return BaseClass::SelectSchedule();
|
|
}
|
|
|
|
// flying?
|
|
if (GetMoveType() == MOVETYPE_FLYGRAVITY)
|
|
{
|
|
if (GetFlags() & FL_ONGROUND)
|
|
{
|
|
//Msg( "landed\n" );
|
|
// just landed
|
|
SetMoveType(MOVETYPE_STEP);
|
|
return SCHED_ASSASSIN_JUMP_LAND;
|
|
}
|
|
else
|
|
{
|
|
//Msg("jump\n");
|
|
// jump or jump/shoot
|
|
if (m_NPCState == NPC_STATE_COMBAT)
|
|
return SCHED_ASSASSIN_JUMP;
|
|
else
|
|
return SCHED_ASSASSIN_JUMP_ATTACK;
|
|
}
|
|
}
|
|
|
|
if (HasCondition(COND_HEAR_DANGER))
|
|
{
|
|
return SCHED_TAKE_COVER_FROM_BEST_SOUND;
|
|
}
|
|
|
|
if (HasCondition(COND_LIGHT_DAMAGE))
|
|
{
|
|
m_iFrustration++;
|
|
}
|
|
if (HasCondition(COND_HEAVY_DAMAGE))
|
|
{
|
|
m_iFrustration++;
|
|
}
|
|
|
|
// jump player!
|
|
if (HasCondition(COND_CAN_MELEE_ATTACK1))
|
|
{
|
|
//Msg( "melee attack 1\n");
|
|
return SCHED_MELEE_ATTACK1;
|
|
}
|
|
|
|
// throw grenade
|
|
if (HasCondition(COND_CAN_RANGE_ATTACK2))
|
|
{
|
|
//Msg( "range attack 2\n");
|
|
return SCHED_RANGE_ATTACK2;
|
|
}
|
|
|
|
// spotted
|
|
if (HasCondition(COND_SEE_ENEMY) && HasCondition(COND_ENEMY_FACING_ME))
|
|
{
|
|
//Msg("exposed\n");
|
|
m_iFrustration++;
|
|
return SCHED_ASSASSIN_EXPOSED;
|
|
}
|
|
|
|
// can attack
|
|
if (HasCondition(COND_CAN_RANGE_ATTACK1))
|
|
{
|
|
//Msg( "range attack 1\n" );
|
|
m_iFrustration = 0;
|
|
return SCHED_RANGE_ATTACK1;
|
|
}
|
|
|
|
if (HasCondition(COND_SEE_ENEMY))
|
|
{
|
|
//Msg( "face\n");
|
|
return SCHED_COMBAT_FACE;
|
|
}
|
|
|
|
// new enemy
|
|
if (HasCondition(COND_NEW_ENEMY))
|
|
{
|
|
//Msg( "take cover\n");
|
|
return SCHED_TAKE_COVER_FROM_ENEMY;
|
|
}
|
|
|
|
// ALERT( at_console, "stand\n");
|
|
return SCHED_ALERT_STAND;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return BaseClass::SelectSchedule();
|
|
}
|
|
|
|
//=========================================================
|
|
// HandleAnimEvent - catches the monster-specific messages
|
|
// that occur when tagged animation frames are played.
|
|
//
|
|
// Returns number of events handled, 0 if none.
|
|
//=========================================================
|
|
void CNPC_AlienAssassin::HandleAnimEvent(animevent_t *pEvent)
|
|
{
|
|
switch (pEvent->event)
|
|
{
|
|
case ASSASSIN_AE_SHOOT1:
|
|
Shoot();
|
|
break;
|
|
case ASSASSIN_AE_TOSS1:
|
|
{
|
|
Vector vTossPos;
|
|
QAngle vAngles;
|
|
|
|
GetAttachment(FRAG_ATTACHMENT, vTossPos, vAngles);
|
|
Vector vecSpin;
|
|
vecSpin.x = random->RandomFloat(-1000.0, 1000.0);
|
|
vecSpin.y = random->RandomFloat(-1000.0, 1000.0);
|
|
vecSpin.z = random->RandomFloat(-1000.0, 1000.0);
|
|
Fraggrenade_Create(vTossPos, vec3_angle, m_vecTossVelocity, vecSpin, this, 3.5f, true);
|
|
|
|
/*CHL1BaseGrenade *pGrenade = (CHL1BaseGrenade*)CreateNoSpawn("npc_handgrenade", vTossPos, vec3_angle, this);
|
|
pGrenade->SetAbsVelocity(m_vecTossVelocity);
|
|
pGrenade->m_flDetonateTime = gpGlobals->curtime + 2.0;
|
|
pGrenade->Spawn();
|
|
pGrenade->SetOwner(this);
|
|
pGrenade->SetOwnerEntity(this);
|
|
pGrenade->SetGravity(0.5);*/
|
|
|
|
m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown.
|
|
m_fThrowGrenade = FALSE;
|
|
// !!!LATER - when in a group, only try to throw grenade if ordered.
|
|
}
|
|
break;
|
|
case ASSASSIN_AE_JUMP:
|
|
{
|
|
SetMoveType(MOVETYPE_FLYGRAVITY);
|
|
RemoveFlag(FL_ONGROUND);
|
|
SetAbsVelocity(m_vecJumpVelocity);
|
|
m_flNextJump = gpGlobals->curtime + 3.0;
|
|
}
|
|
return;
|
|
default:
|
|
BaseClass::HandleAnimEvent(pEvent);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
// Shoot
|
|
//=========================================================
|
|
void CNPC_AlienAssassin::Shoot(void)
|
|
{
|
|
//TODO Knife throwing
|
|
|
|
Vector vForward, vRight, vUp;
|
|
Vector vecShootOrigin;
|
|
QAngle vAngles;
|
|
|
|
if (GetEnemy() == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetAttachment(KNIFE_ATTACHMENT, vecShootOrigin, vAngles);
|
|
|
|
Vector vecShootDir = GetShootEnemyDir(vecShootOrigin);
|
|
|
|
if (m_flLastShot + 2 < gpGlobals->curtime)
|
|
{
|
|
m_flDiviation = 0.10;
|
|
}
|
|
else
|
|
{
|
|
m_flDiviation -= 0.01;
|
|
if (m_flDiviation < 0.02)
|
|
m_flDiviation = 0.02;
|
|
}
|
|
m_flLastShot = gpGlobals->curtime;
|
|
|
|
AngleVectors(GetAbsAngles(), &vForward, &vRight, &vUp);
|
|
|
|
Vector vecShellVelocity = vRight * random->RandomFloat(40, 90) + vUp * random->RandomFloat(75, 200) + vForward * random->RandomFloat(-40, 40);
|
|
//EjectBrass ( pev->origin + gpGlobals->v_up * 32 + gpGlobals->v_forward * 12, vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHELL);
|
|
FireBullets(1, vecShootOrigin, vecShootDir, Vector(m_flDiviation, m_flDiviation, m_flDiviation), 2048, m_iAmmoType); // shoot +-8 degrees
|
|
|
|
//NDebugOverlay::Line( vecShootOrigin, vecShootOrigin + vecShootDir * 2048, 255, 0, 0, true, 2.0 );
|
|
|
|
CPASAttenuationFilter filter(this);
|
|
|
|
switch (random->RandomInt(0, 1))
|
|
{
|
|
case 0:
|
|
enginesound->EmitSound(filter, entindex(), CHAN_WEAPON, "weapons/pl_gun1.wav", random->RandomFloat(0.6, 0.8), ATTN_NORM);
|
|
break;
|
|
case 1:
|
|
enginesound->EmitSound(filter, entindex(), CHAN_WEAPON, "weapons/pl_gun2.wav", random->RandomFloat(0.6, 0.8), ATTN_NORM);
|
|
break;
|
|
}
|
|
|
|
//SetEffects(EF_MUZZLEFLASH);
|
|
|
|
VectorAngles(vecShootDir, vAngles);
|
|
SetPoseParameter("shoot", vecShootDir.x);
|
|
|
|
m_cAmmoLoaded--;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CNPC_AlienAssassin::TranslateSchedule(int scheduleType)
|
|
{
|
|
// Msg( "%d\n", m_iFrustration );
|
|
switch (scheduleType)
|
|
{
|
|
case SCHED_TAKE_COVER_FROM_ENEMY:
|
|
|
|
if (m_iHealth > 30)
|
|
return SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY1;
|
|
else
|
|
return SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2;
|
|
|
|
case SCHED_TAKE_COVER_FROM_BEST_SOUND:
|
|
return SCHED_ASSASSIN_TAKE_COVER_FROM_BEST_SOUND;
|
|
case SCHED_FAIL:
|
|
|
|
if (m_NPCState == NPC_STATE_COMBAT)
|
|
return SCHED_ASSASSIN_FAIL;
|
|
|
|
break;
|
|
case SCHED_ALERT_STAND:
|
|
|
|
if (m_NPCState == NPC_STATE_COMBAT)
|
|
{
|
|
//m_nSkin ==
|
|
return SCHED_ASSASSIN_HIDE;
|
|
}
|
|
|
|
break;
|
|
//case SCHED_CHASE_ENEMY:
|
|
// return SCHED_ASSASSIN_HUNT;
|
|
|
|
case SCHED_MELEE_ATTACK1:
|
|
|
|
if (GetFlags() & FL_ONGROUND)
|
|
{
|
|
if (m_flNextJump > gpGlobals->curtime)
|
|
{
|
|
// can't jump yet, go ahead and fail
|
|
return SCHED_ASSASSIN_FAIL;
|
|
}
|
|
else
|
|
{
|
|
return SCHED_ASSASSIN_JUMP;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return SCHED_ASSASSIN_JUMP_ATTACK;
|
|
}
|
|
}
|
|
|
|
return BaseClass::TranslateSchedule(scheduleType);
|
|
}
|
|
|
|
//=========================================================
|
|
// RunAI
|
|
//=========================================================
|
|
void CNPC_AlienAssassin::RunAI(void)
|
|
{
|
|
BaseClass::RunAI();
|
|
|
|
// always visible if moving
|
|
// always visible is not on hard
|
|
if (g_iSkillLevel != SKILL_HARD || GetEnemy() == NULL || m_lifeState == LIFE_DEAD || GetActivity() == ACT_RUN || GetActivity() == ACT_WALK || !(GetFlags() & FL_ONGROUND))
|
|
m_iTargetRanderamt = 255;
|
|
else
|
|
m_iTargetRanderamt = 20;
|
|
|
|
CPASAttenuationFilter filter(this);
|
|
|
|
if (GetRenderColor().a > m_iTargetRanderamt)
|
|
{
|
|
if (GetRenderColor().a == 255)
|
|
{
|
|
enginesound->EmitSound(filter, entindex(), CHAN_BODY, "debris/beamstart1.wav", 0.2, ATTN_NORM);
|
|
}
|
|
|
|
SetRenderColorA(max(GetRenderColor().a - 50, m_iTargetRanderamt));
|
|
m_nRenderMode = kRenderTransTexture;
|
|
}
|
|
else if (GetRenderColor().a < m_iTargetRanderamt)
|
|
{
|
|
SetRenderColorA(min(GetRenderColor().a + 50, m_iTargetRanderamt));
|
|
if (GetRenderColor().a == 255)
|
|
m_nRenderMode = kRenderNormal;
|
|
}
|
|
|
|
if (GetActivity() == ACT_RUN || GetActivity() == ACT_WALK)
|
|
{
|
|
static int iStep = 0;
|
|
iStep = !iStep;
|
|
if (iStep)
|
|
{
|
|
switch (random->RandomInt(0, 3))
|
|
{
|
|
case 0: enginesound->EmitSound(filter, entindex(), CHAN_BODY, "player/pl_step1.wav", 0.5, ATTN_NORM); break;
|
|
case 1: enginesound->EmitSound(filter, entindex(), CHAN_BODY, "player/pl_step3.wav", 0.5, ATTN_NORM); break;
|
|
case 2: enginesound->EmitSound(filter, entindex(), CHAN_BODY, "player/pl_step2.wav", 0.5, ATTN_NORM); break;
|
|
case 3: enginesound->EmitSound(filter, entindex(), CHAN_BODY, "player/pl_step4.wav", 0.5, ATTN_NORM); break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AI_BEGIN_CUSTOM_NPC(monster_human_assassin, CNPC_AlienAssassin)
|
|
|
|
DECLARE_TASK(TASK_ASSASSIN_FALL_TO_GROUND)
|
|
|
|
DECLARE_ACTIVITY(ACT_ASSASSIN_FLY_UP)
|
|
DECLARE_ACTIVITY(ACT_ASSASSIN_FLY_ATTACK)
|
|
DECLARE_ACTIVITY(ACT_ASSASSIN_FLY_DOWN)
|
|
|
|
//=========================================================
|
|
// AI Schedules Specific to this monster
|
|
//=========================================================
|
|
|
|
//=========================================================
|
|
// Enemy exposed assasin's cover
|
|
//=========================================================
|
|
|
|
//=========================================================
|
|
// > SCHED_ASSASSIN_EXPOSED
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASSASSIN_EXPOSED,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_RANGE_ATTACK1 0"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_JUMP"
|
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
|
|
" "
|
|
" Interrupts"
|
|
" COND_CAN_MELEE_ATTACK1"
|
|
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_ASSASSIN_JUMP
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASSASSIN_JUMP,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_HOP"
|
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_ASSASSIN_JUMP_ATTACK"
|
|
" "
|
|
" Interrupts"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_ASSASSIN_JUMP_ATTACK
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASSASSIN_JUMP_ATTACK,
|
|
|
|
" Tasks"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_JUMP_LAND"
|
|
" TASK_ASSASSIN_FALL_TO_GROUND 0"
|
|
" "
|
|
" Interrupts"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_ASSASSIN_JUMP_LAND
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASSASSIN_JUMP_LAND,
|
|
|
|
" Tasks"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_EXPOSED"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
|
|
" TASK_REMEMBER MEMORY:CUSTOM1"
|
|
" TASK_FIND_NODE_COVER_FROM_ENEMY 0"
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_FORGET MEMORY:CUSTOM1"
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
" TASK_REMEMBER MEMORY:INCOVER"
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RANGE_ATTACK1"
|
|
|
|
" "
|
|
" Interrupts"
|
|
)
|
|
|
|
//=========================================================
|
|
// Fail Schedule
|
|
//=========================================================
|
|
|
|
//=========================================================
|
|
// > SCHED_ASSASSIN_FAIL
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASSASSIN_FAIL,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
|
|
" TASK_WAIT_FACE_ENEMY 2"
|
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY"
|
|
" "
|
|
" Interrupts"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_CAN_RANGE_ATTACK1"
|
|
" COND_CAN_RANGE_ATTACK2"
|
|
" COND_CAN_MELEE_ATTACK1"
|
|
" COND_HEAR_DANGER"
|
|
" COND_HEAR_PLAYER"
|
|
)
|
|
|
|
|
|
|
|
|
|
//=========================================================
|
|
// > SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY1
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY1,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_WAIT 0.2"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RANGE_ATTACK1"
|
|
" TASK_FIND_COVER_FROM_ENEMY 0"
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
" TASK_REMEMBER MEMORY:INCOVER"
|
|
" TASK_FACE_ENEMY 0"
|
|
" "
|
|
" Interrupts"
|
|
" COND_CAN_MELEE_ATTACK1"
|
|
" COND_NEW_ENEMY"
|
|
" COND_HEAR_DANGER"
|
|
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_WAIT 0.2"
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_RANGE_ATTACK1 0"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RANGE_ATTACK1"
|
|
" TASK_FIND_COVER_FROM_ENEMY 0"
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
" TASK_REMEMBER MEMORY:INCOVER"
|
|
" TASK_FACE_ENEMY 0"
|
|
" "
|
|
" Interrupts"
|
|
" COND_CAN_MELEE_ATTACK1"
|
|
" COND_NEW_ENEMY"
|
|
" COND_HEAR_DANGER"
|
|
|
|
)
|
|
|
|
|
|
|
|
//=========================================================
|
|
// hide from the loudest sound source
|
|
//=========================================================
|
|
|
|
//=========================================================
|
|
// > SCHED_ASSASSIN_TAKE_COVER_FROM_BEST_SOUND
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASSASSIN_TAKE_COVER_FROM_BEST_SOUND,
|
|
|
|
" Tasks"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_MELEE_ATTACK1"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_FIND_COVER_FROM_BEST_SOUND 0"
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
" TASK_REMEMBER MEMORY:INCOVER"
|
|
" TASK_TURN_LEFT 179"
|
|
" "
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_ASSASSIN_HIDE
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASSASSIN_HIDE,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
|
|
" TASK_WAIT 2.0"
|
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY"
|
|
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_SEE_ENEMY"
|
|
" COND_SEE_FEAR"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_PROVOKED"
|
|
" COND_HEAR_DANGER"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_ASSASSIN_HUNT
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASSASSIN_HUNT,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2"
|
|
" TASK_GET_PATH_TO_ENEMY 0"
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_CAN_RANGE_ATTACK1"
|
|
" COND_HEAR_DANGER"
|
|
)
|
|
|
|
AI_END_CUSTOM_NPC() |