aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/episodic/npc_hunter.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/episodic/npc_hunter.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/episodic/npc_hunter.cpp')
-rw-r--r--mp/src/game/server/episodic/npc_hunter.cpp15528
1 files changed, 7764 insertions, 7764 deletions
diff --git a/mp/src/game/server/episodic/npc_hunter.cpp b/mp/src/game/server/episodic/npc_hunter.cpp
index 9d7dfd3e..d55053bc 100644
--- a/mp/src/game/server/episodic/npc_hunter.cpp
+++ b/mp/src/game/server/episodic/npc_hunter.cpp
@@ -1,7764 +1,7764 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Small, fast version of the strider. Goes where striders cannot, such
-// as into buildings. Best killed with physics objects and explosives.
-//
-//=============================================================================
-
-#include "cbase.h"
-#include "npc_strider.h"
-#include "npc_hunter.h"
-#include "ai_behavior_follow.h"
-#include "ai_moveprobe.h"
-#include "ai_senses.h"
-#include "ai_speech.h"
-#include "ai_task.h"
-#include "ai_default.h"
-#include "ai_schedule.h"
-#include "ai_hull.h"
-#include "ai_baseactor.h"
-#include "ai_waypoint.h"
-#include "ai_link.h"
-#include "ai_hint.h"
-#include "ai_squadslot.h"
-#include "ai_squad.h"
-#include "ai_tacticalservices.h"
-#include "beam_shared.h"
-#include "datacache/imdlcache.h"
-#include "eventqueue.h"
-#include "gib.h"
-#include "globalstate.h"
-#include "hierarchy.h"
-#include "movevars_shared.h"
-#include "npcevent.h"
-#include "saverestore_utlvector.h"
-#include "particle_parse.h"
-#include "te_particlesystem.h"
-#include "sceneentity.h"
-#include "shake.h"
-#include "soundenvelope.h"
-#include "soundent.h"
-#include "SpriteTrail.h"
-#include "IEffects.h"
-#include "engine/IEngineSound.h"
-#include "bone_setup.h"
-#include "studio.h"
-#include "ai_route.h"
-#include "ammodef.h"
-#include "npc_bullseye.h"
-#include "physobj.h"
-#include "ai_memory.h"
-#include "collisionutils.h"
-#include "shot_manipulator.h"
-#include "steamjet.h"
-#include "physics_prop_ragdoll.h"
-#include "vehicle_base.h"
-#include "coordsize.h"
-#include "hl2_shareddefs.h"
-#include "te_effect_dispatch.h"
-#include "beam_flags.h"
-#include "prop_combine_ball.h"
-#include "explode.h"
-#include "weapon_physcannon.h"
-#include "weapon_striderbuster.h"
-#include "monstermaker.h"
-#include "weapon_rpg.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-class CNPC_Hunter;
-
-
-static const char *HUNTER_FLECHETTE_MODEL = "models/weapons/hunter_flechette.mdl";
-
-// Think contexts
-static const char *HUNTER_BLEED_THINK = "HunterBleed";
-static const char *HUNTER_ZAP_THINK = "HunterZap";
-static const char *HUNTER_JOSTLE_VEHICLE_THINK = "HunterJostle";
-
-
-ConVar sk_hunter_health( "sk_hunter_health", "210" );
-
-// Melee attacks
-ConVar sk_hunter_dmg_one_slash( "sk_hunter_dmg_one_slash", "20" );
-ConVar sk_hunter_dmg_charge( "sk_hunter_dmg_charge", "20" );
-
-// Flechette volley attack
-ConVar hunter_flechette_max_range( "hunter_flechette_max_range", "1200" );
-ConVar hunter_flechette_min_range( "hunter_flechette_min_range", "100" );
-ConVar hunter_flechette_volley_size( "hunter_flechette_volley_size", "8" );
-ConVar hunter_flechette_speed( "hunter_flechette_speed", "2000" );
-ConVar sk_hunter_dmg_flechette( "sk_hunter_dmg_flechette", "4.0" );
-ConVar sk_hunter_flechette_explode_dmg( "sk_hunter_flechette_explode_dmg", "12.0" );
-ConVar sk_hunter_flechette_explode_radius( "sk_hunter_flechette_explode_radius", "128.0" );
-ConVar hunter_flechette_explode_delay( "hunter_flechette_explode_delay", "2.5" );
-ConVar hunter_flechette_delay( "hunter_flechette_delay", "0.1" );
-ConVar hunter_first_flechette_delay( "hunter_first_flechette_delay", "0.5" );
-ConVar hunter_flechette_max_concurrent_volleys( "hunter_flechette_max_concurrent_volleys", "2" );
-ConVar hunter_flechette_volley_start_min_delay( "hunter_flechette_volley_start_min_delay", ".25" );
-ConVar hunter_flechette_volley_start_max_delay( "hunter_flechette_volley_start_max_delay", ".95" );
-ConVar hunter_flechette_volley_end_min_delay( "hunter_flechette_volley_end_min_delay", "1" );
-ConVar hunter_flechette_volley_end_max_delay( "hunter_flechette_volley_end_max_delay", "2" );
-ConVar hunter_flechette_test( "hunter_flechette_test", "0" );
-ConVar hunter_clamp_shots( "hunter_clamp_shots", "1" );
-ConVar hunter_cheap_explosions( "hunter_cheap_explosions", "1" );
-
-// Damage received
-ConVar sk_hunter_bullet_damage_scale( "sk_hunter_bullet_damage_scale", "0.6" );
-ConVar sk_hunter_charge_damage_scale( "sk_hunter_charge_damage_scale", "2.0" );
-ConVar sk_hunter_buckshot_damage_scale( "sk_hunter_buckshot_damage_scale", "0.5" );
-ConVar sk_hunter_vehicle_damage_scale( "sk_hunter_vehicle_damage_scale", "2.2" );
-ConVar sk_hunter_dmg_from_striderbuster( "sk_hunter_dmg_from_striderbuster", "150" );
-ConVar sk_hunter_citizen_damage_scale( "sk_hunter_citizen_damage_scale", "0.3" );
-
-ConVar hunter_allow_dissolve( "hunter_allow_dissolve", "1" );
-ConVar hunter_random_expressions( "hunter_random_expressions", "0" );
-ConVar hunter_show_weapon_los_z( "hunter_show_weapon_los_z", "0" );
-ConVar hunter_show_weapon_los_condition( "hunter_show_weapon_los_condition", "0" );
-
-ConVar hunter_melee_delay( "hunter_melee_delay", "2.0" );
-
-// Bullrush charge.
-ConVar hunter_charge( "hunter_charge", "1" );
-ConVar hunter_charge_min_delay( "hunter_charge_min_delay", "10.0" );
-ConVar hunter_charge_pct( "hunter_charge_pct", "25" );
-ConVar hunter_charge_test( "hunter_charge_test", "0" );
-
-// Vehicle dodging.
-ConVar hunter_dodge_warning( "hunter_dodge_warning", "1.1" );
-ConVar hunter_dodge_warning_width( "hunter_dodge_warning_width", "180" );
-ConVar hunter_dodge_warning_cone( "hunter_dodge_warning_cone", ".5" );
-ConVar hunter_dodge_debug( "hunter_dodge_debug", "0" );
-
-// Jostle vehicles when hit by them
-ConVar hunter_jostle_car_min_speed( "hunter_jostle_car_min_speed", "100" ); // If hit by a car going at least this fast, jostle the car
-ConVar hunter_jostle_car_max_speed( "hunter_jostle_car_max_speed", "600" ); // Used for determining jostle scale
-
-ConVar hunter_free_knowledge( "hunter_free_knowledge", "10.0" );
-ConVar hunter_plant_adjust_z( "hunter_plant_adjust_z", "12" );
-
-ConVar hunter_disable_patrol( "hunter_disable_patrol", "0" );
-
-// Dealing with striderbusters
-ConVar hunter_hate_held_striderbusters( "hunter_hate_held_striderbusters", "1" );
-ConVar hunter_hate_thrown_striderbusters( "hunter_hate_thrown_striderbusters", "1" );
-ConVar hunter_hate_attached_striderbusters( "hunter_hate_attached_striderbusters", "1" );
-ConVar hunter_hate_held_striderbusters_delay( "hunter_hate_held_striderbusters_delay", "0.5" );
-ConVar hunter_hate_held_striderbusters_tolerance( "hunter_hate_held_striderbusters_tolerance", "2000.0" );
-ConVar hunter_hate_thrown_striderbusters_tolerance( "hunter_hate_thrown_striderbusters_tolerance", "300.0" );
-ConVar hunter_seek_thrown_striderbusters_tolerance( "hunter_seek_thrown_striderbusters_tolerance", "400.0" );
-ConVar hunter_retreat_striderbusters( "hunter_retreat_striderbusters", "1", FCVAR_NONE, "If true, the hunter will retreat when a buster is glued to him." );
-
-ConVar hunter_allow_nav_jump( "hunter_allow_nav_jump", "0" );
-ConVar g_debug_hunter_charge( "g_debug_hunter_charge", "0" );
-
-ConVar hunter_stand_still( "hunter_stand_still", "0" ); // used for debugging, keeps them rooted in place
-
-ConVar hunter_siege_frequency( "hunter_siege_frequency", "12" );
-
-#define HUNTER_FOV_DOT 0.0 // 180 degree field of view
-#define HUNTER_CHARGE_MIN 256
-#define HUNTER_CHARGE_MAX 1024
-#define HUNTER_FACE_ENEMY_DIST 512.0f
-#define HUNTER_MELEE_REACH 80
-#define HUNTER_BLOOD_LEFT_FOOT 0
-#define HUNTER_IGNORE_ENEMY_TIME 5 // How long the hunter will ignore another enemy when distracted by the player.
-
-#define HUNTER_FACING_DOT 0.8 // The angle within which we start shooting
-#define HUNTER_SHOOT_MAX_YAW_DEG 60.0f // Once shooting, clamp to +/- these degrees of yaw deflection as our target moves
-#define HUNTER_SHOOT_MAX_YAW_COS 0.5f // The cosine of the above angle
-
-#define HUNTER_FLECHETTE_WARN_TIME 1.0f
-
-#define HUNTER_SEE_ENEMY_TIME_INVALID -1
-
-#define NUM_FLECHETTE_VOLLEY_ON_FOLLOW 4
-
-#define HUNTER_SIEGE_MAX_DIST_MODIFIER 2.0f
-
-//-----------------------------------------------------------------------------
-// Animation events
-//-----------------------------------------------------------------------------
-int AE_HUNTER_FOOTSTEP_LEFT;
-int AE_HUNTER_FOOTSTEP_RIGHT;
-int AE_HUNTER_FOOTSTEP_BACK;
-int AE_HUNTER_MELEE_ANNOUNCE;
-int AE_HUNTER_MELEE_ATTACK_LEFT;
-int AE_HUNTER_MELEE_ATTACK_RIGHT;
-int AE_HUNTER_DIE;
-int AE_HUNTER_SPRAY_BLOOD;
-int AE_HUNTER_START_EXPRESSION;
-int AE_HUNTER_END_EXPRESSION;
-
-
-//-----------------------------------------------------------------------------
-// Interactions.
-//-----------------------------------------------------------------------------
-int g_interactionHunterFoundEnemy = 0;
-
-
-//-----------------------------------------------------------------------------
-// Local stuff.
-//-----------------------------------------------------------------------------
-static string_t s_iszStriderClassname;
-static string_t s_iszStriderBusterClassname;
-static string_t s_iszMagnadeClassname;
-static string_t s_iszPhysPropClassname;
-static string_t s_iszHuntersToRunOver;
-
-
-//-----------------------------------------------------------------------------
-// Custom Activities
-//-----------------------------------------------------------------------------
-Activity ACT_HUNTER_DEPLOYRA2;
-Activity ACT_HUNTER_DODGER;
-Activity ACT_HUNTER_DODGEL;
-Activity ACT_HUNTER_GESTURE_SHOOT;
-Activity ACT_HUNTER_FLINCH_STICKYBOMB;
-Activity ACT_HUNTER_STAGGER;
-Activity ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER;
-Activity ACT_DI_HUNTER_MELEE;
-Activity ACT_DI_HUNTER_THROW;
-Activity ACT_HUNTER_ANGRY;
-Activity ACT_HUNTER_WALK_ANGRY;
-Activity ACT_HUNTER_FOUND_ENEMY;
-Activity ACT_HUNTER_FOUND_ENEMY_ACK;
-Activity ACT_HUNTER_CHARGE_START;
-Activity ACT_HUNTER_CHARGE_RUN;
-Activity ACT_HUNTER_CHARGE_STOP;
-Activity ACT_HUNTER_CHARGE_CRASH;
-Activity ACT_HUNTER_CHARGE_HIT;
-Activity ACT_HUNTER_RANGE_ATTACK2_UNPLANTED;
-Activity ACT_HUNTER_IDLE_PLANTED;
-Activity ACT_HUNTER_FLINCH_N;
-Activity ACT_HUNTER_FLINCH_S;
-Activity ACT_HUNTER_FLINCH_E;
-Activity ACT_HUNTER_FLINCH_W;
-
-
-//-----------------------------------------------------------------------------
-// Squad slots
-//-----------------------------------------------------------------------------
-enum SquadSlot_t
-{
- SQUAD_SLOT_HUNTER_CHARGE = LAST_SHARED_SQUADSLOT,
- SQUAD_SLOT_HUNTER_FLANK_FIRST,
- SQUAD_SLOT_HUNTER_FLANK_LAST = SQUAD_SLOT_HUNTER_FLANK_FIRST,
- SQUAD_SLOT_RUN_SHOOT,
-};
-
-#define HUNTER_FOLLOW_DISTANCE 2000.0f
-#define HUNTER_FOLLOW_DISTANCE_SQR (HUNTER_FOLLOW_DISTANCE * HUNTER_FOLLOW_DISTANCE)
-
-#define HUNTER_RUNDOWN_SQUADDATA 0
-
-
-//-----------------------------------------------------------------------------
-// We're doing this quite a lot, so this makes the check a lot faster since
-// we don't have to compare strings.
-//-----------------------------------------------------------------------------
-bool IsStriderBuster( CBaseEntity *pEntity )
-{
- if ( !pEntity )
- return false;
-
- if( pEntity->m_iClassname == s_iszStriderBusterClassname ||
- pEntity->m_iClassname == s_iszMagnadeClassname)
- return true;
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool HateThisStriderBuster( CBaseEntity *pTarget )
-{
- if ( StriderBuster_WasKnockedOffStrider(pTarget) )
- return false;
-
- if ( pTarget->VPhysicsGetObject() )
- {
- if ( hunter_hate_held_striderbusters.GetBool() ||
- hunter_hate_thrown_striderbusters.GetBool() ||
- hunter_hate_attached_striderbusters.GetBool() )
- {
- if ( ( pTarget->VPhysicsGetObject()->GetGameFlags() & ( FVPHYSICS_PLAYER_HELD | FVPHYSICS_WAS_THROWN ) ) )
- {
- return true;
- }
-
- if ( StriderBuster_IsAttachedStriderBuster( pTarget ) )
- {
- return true;
- }
- }
- }
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-// The hunter can fire a volley of explosive flechettes.
-//-----------------------------------------------------------------------------
-static const char *s_szHunterFlechetteBubbles = "HunterFlechetteBubbles";
-static const char *s_szHunterFlechetteSeekThink = "HunterFlechetteSeekThink";
-static const char *s_szHunterFlechetteDangerSoundThink = "HunterFlechetteDangerSoundThink";
-static const char *s_szHunterFlechetteSpriteTrail = "sprites/bluelaser1.vmt";
-static int s_nHunterFlechetteImpact = -2;
-static int s_nFlechetteFuseAttach = -1;
-
-#define FLECHETTE_AIR_VELOCITY 2500
-
-class CHunterFlechette : public CPhysicsProp, public IParentPropInteraction
-{
- DECLARE_CLASS( CHunterFlechette, CPhysicsProp );
-
-public:
-
- CHunterFlechette();
- ~CHunterFlechette();
-
- Class_T Classify() { return CLASS_NONE; }
-
- bool WasThrownBack()
- {
- return m_bThrownBack;
- }
-
-public:
-
- void Spawn();
- void Activate();
- void Precache();
- void Shoot( Vector &vecVelocity, bool bBright );
- void SetSeekTarget( CBaseEntity *pTargetEntity );
- void Explode();
-
- bool CreateVPhysics();
-
- unsigned int PhysicsSolidMaskForEntity() const;
- static CHunterFlechette *FlechetteCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner = NULL );
-
- // IParentPropInteraction
- void OnParentCollisionInteraction( parentCollisionInteraction_t eType, int index, gamevcollisionevent_t *pEvent );
- void OnParentPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason );
-
-protected:
-
- void SetupGlobalModelData();
-
- void StickTo( CBaseEntity *pOther, trace_t &tr );
-
- void BubbleThink();
- void DangerSoundThink();
- void ExplodeThink();
- void DopplerThink();
- void SeekThink();
-
- bool CreateSprites( bool bBright );
-
- void FlechetteTouch( CBaseEntity *pOther );
-
- Vector m_vecShootPosition;
- EHANDLE m_hSeekTarget;
- bool m_bThrownBack;
-
- DECLARE_DATADESC();
- //DECLARE_SERVERCLASS();
-};
-
-LINK_ENTITY_TO_CLASS( hunter_flechette, CHunterFlechette );
-
-BEGIN_DATADESC( CHunterFlechette )
-
- DEFINE_THINKFUNC( BubbleThink ),
- DEFINE_THINKFUNC( DangerSoundThink ),
- DEFINE_THINKFUNC( ExplodeThink ),
- DEFINE_THINKFUNC( DopplerThink ),
- DEFINE_THINKFUNC( SeekThink ),
-
- DEFINE_ENTITYFUNC( FlechetteTouch ),
-
- DEFINE_FIELD( m_vecShootPosition, FIELD_POSITION_VECTOR ),
- DEFINE_FIELD( m_hSeekTarget, FIELD_EHANDLE ),
- DEFINE_FIELD( m_bThrownBack, FIELD_BOOLEAN ),
-
-END_DATADESC()
-
-//IMPLEMENT_SERVERCLASS_ST( CHunterFlechette, DT_HunterFlechette )
-//END_SEND_TABLE()
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-CHunterFlechette *CHunterFlechette::FlechetteCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner )
-{
- // Create a new entity with CHunterFlechette private data
- CHunterFlechette *pFlechette = (CHunterFlechette *)CreateEntityByName( "hunter_flechette" );
- UTIL_SetOrigin( pFlechette, vecOrigin );
- pFlechette->SetAbsAngles( angAngles );
- pFlechette->Spawn();
- pFlechette->Activate();
- pFlechette->SetOwnerEntity( pentOwner );
-
- return pFlechette;
-}
-
-
-//------------------------------------------------------------------------------
-//------------------------------------------------------------------------------
-void CC_Hunter_Shoot_Flechette( const CCommand& args )
-{
- MDLCACHE_CRITICAL_SECTION();
-
- bool allowPrecache = CBaseEntity::IsPrecacheAllowed();
- CBaseEntity::SetAllowPrecache( true );
-
- CBasePlayer *pPlayer = UTIL_GetCommandClient();
-
- QAngle angEye = pPlayer->EyeAngles();
- CHunterFlechette *entity = CHunterFlechette::FlechetteCreate( pPlayer->EyePosition(), angEye, pPlayer );
- if ( entity )
- {
- entity->Precache();
- DispatchSpawn( entity );
-
- // Shoot the flechette.
- Vector forward;
- pPlayer->EyeVectors( &forward );
- forward *= 2000.0f;
- entity->Shoot( forward, false );
- }
-
- CBaseEntity::SetAllowPrecache( allowPrecache );
-}
-
-static ConCommand ent_create("hunter_shoot_flechette", CC_Hunter_Shoot_Flechette, "Fires a hunter flechette where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT);
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-CHunterFlechette::CHunterFlechette()
-{
- UseClientSideAnimation();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-CHunterFlechette::~CHunterFlechette()
-{
-}
-
-
-//-----------------------------------------------------------------------------
-// If set, the flechette will seek unerringly toward the target as it flies.
-//-----------------------------------------------------------------------------
-void CHunterFlechette::SetSeekTarget( CBaseEntity *pTargetEntity )
-{
- if ( pTargetEntity )
- {
- m_hSeekTarget = pTargetEntity;
- SetContextThink( &CHunterFlechette::SeekThink, gpGlobals->curtime, s_szHunterFlechetteSeekThink );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CHunterFlechette::CreateVPhysics()
-{
- // Create the object in the physics system
- VPhysicsInitNormal( SOLID_BBOX, FSOLID_NOT_STANDABLE, false );
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-unsigned int CHunterFlechette::PhysicsSolidMaskForEntity() const
-{
- return ( BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX ) & ~CONTENTS_GRATE;
-}
-
-
-//-----------------------------------------------------------------------------
-// Called from CPropPhysics code when we're attached to a physics object.
-//-----------------------------------------------------------------------------
-void CHunterFlechette::OnParentCollisionInteraction( parentCollisionInteraction_t eType, int index, gamevcollisionevent_t *pEvent )
-{
- if ( eType == COLLISIONINTER_PARENT_FIRST_IMPACT )
- {
- m_bThrownBack = true;
- Explode();
- }
-}
-
-void CHunterFlechette::OnParentPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
-{
- m_bThrownBack = true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CHunterFlechette::CreateSprites( bool bBright )
-{
- if ( bBright )
- {
- DispatchParticleEffect( "hunter_flechette_trail_striderbuster", PATTACH_ABSORIGIN_FOLLOW, this );
- }
- else
- {
- DispatchParticleEffect( "hunter_flechette_trail", PATTACH_ABSORIGIN_FOLLOW, this );
- }
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::Spawn()
-{
- Precache( );
-
- SetModel( HUNTER_FLECHETTE_MODEL );
- SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
- UTIL_SetSize( this, -Vector(1,1,1), Vector(1,1,1) );
- SetSolid( SOLID_BBOX );
- SetGravity( 0.05f );
- SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
-
- // Make sure we're updated if we're underwater
- UpdateWaterState();
-
- SetTouch( &CHunterFlechette::FlechetteTouch );
-
- // Make us glow until we've hit the wall
- m_nSkin = 1;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::Activate()
-{
- BaseClass::Activate();
- SetupGlobalModelData();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::SetupGlobalModelData()
-{
- if ( s_nHunterFlechetteImpact == -2 )
- {
- s_nHunterFlechetteImpact = LookupSequence( "impact" );
- s_nFlechetteFuseAttach = LookupAttachment( "attach_fuse" );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::Precache()
-{
- PrecacheModel( HUNTER_FLECHETTE_MODEL );
- PrecacheModel( "sprites/light_glow02_noz.vmt" );
-
- PrecacheScriptSound( "NPC_Hunter.FlechetteNearmiss" );
- PrecacheScriptSound( "NPC_Hunter.FlechetteHitBody" );
- PrecacheScriptSound( "NPC_Hunter.FlechetteHitWorld" );
- PrecacheScriptSound( "NPC_Hunter.FlechettePreExplode" );
- PrecacheScriptSound( "NPC_Hunter.FlechetteExplode" );
-
- PrecacheParticleSystem( "hunter_flechette_trail_striderbuster" );
- PrecacheParticleSystem( "hunter_flechette_trail" );
- PrecacheParticleSystem( "hunter_projectile_explosion_1" );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::StickTo( CBaseEntity *pOther, trace_t &tr )
-{
- EmitSound( "NPC_Hunter.FlechetteHitWorld" );
-
- SetMoveType( MOVETYPE_NONE );
-
- if ( !pOther->IsWorld() )
- {
- SetParent( pOther );
- SetSolid( SOLID_NONE );
- SetSolidFlags( FSOLID_NOT_SOLID );
- }
-
- // Do an impact effect.
- //Vector vecDir = GetAbsVelocity();
- //float speed = VectorNormalize( vecDir );
-
- //Vector vForward;
- //AngleVectors( GetAbsAngles(), &vForward );
- //VectorNormalize ( vForward );
-
- //CEffectData data;
- //data.m_vOrigin = tr.endpos;
- //data.m_vNormal = vForward;
- //data.m_nEntIndex = 0;
- //DispatchEffect( "BoltImpact", data );
-
- Vector vecVelocity = GetAbsVelocity();
- bool bAttachedToBuster = StriderBuster_OnFlechetteAttach( pOther, vecVelocity );
-
- SetTouch( NULL );
-
- // We're no longer flying. Stop checking for water volumes.
- SetContextThink( NULL, 0, s_szHunterFlechetteBubbles );
-
- // Stop seeking.
- m_hSeekTarget = NULL;
- SetContextThink( NULL, 0, s_szHunterFlechetteSeekThink );
-
- // Get ready to explode.
- if ( !bAttachedToBuster )
- {
- SetThink( &CHunterFlechette::DangerSoundThink );
- SetNextThink( gpGlobals->curtime + (hunter_flechette_explode_delay.GetFloat() - HUNTER_FLECHETTE_WARN_TIME) );
- }
- else
- {
- DangerSoundThink();
- }
-
- // Play our impact animation.
- ResetSequence( s_nHunterFlechetteImpact );
-
- static int s_nImpactCount = 0;
- s_nImpactCount++;
- if ( s_nImpactCount & 0x01 )
- {
- UTIL_ImpactTrace( &tr, DMG_BULLET );
-
- // Shoot some sparks
- if ( UTIL_PointContents( GetAbsOrigin() ) != CONTENTS_WATER)
- {
- g_pEffects->Sparks( GetAbsOrigin() );
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::FlechetteTouch( CBaseEntity *pOther )
-{
- if ( pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS | FSOLID_TRIGGER) )
- {
- // Some NPCs are triggers that can take damage (like antlion grubs). We should hit them.
- if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) )
- return;
- }
-
- if ( FClassnameIs( pOther, "hunter_flechette" ) )
- return;
-
- trace_t tr;
- tr = BaseClass::GetTouchTrace();
-
- if ( pOther->m_takedamage != DAMAGE_NO )
- {
- Vector vecNormalizedVel = GetAbsVelocity();
-
- ClearMultiDamage();
- VectorNormalize( vecNormalizedVel );
-
- float flDamage = sk_hunter_dmg_flechette.GetFloat();
- CBreakable *pBreak = dynamic_cast <CBreakable *>(pOther);
- if ( pBreak && ( pBreak->GetMaterialType() == matGlass ) )
- {
- flDamage = MAX( pOther->GetHealth(), flDamage );
- }
-
- CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), flDamage, DMG_DISSOLVE | DMG_NEVERGIB );
- CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f );
- dmgInfo.SetDamagePosition( tr.endpos );
- pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr );
-
- ApplyMultiDamage();
-
- // Keep going through breakable glass.
- if ( pOther->GetCollisionGroup() == COLLISION_GROUP_BREAKABLE_GLASS )
- return;
-
- SetAbsVelocity( Vector( 0, 0, 0 ) );
-
- // play body "thwack" sound
- EmitSound( "NPC_Hunter.FlechetteHitBody" );
-
- StopParticleEffects( this );
-
- Vector vForward;
- AngleVectors( GetAbsAngles(), &vForward );
- VectorNormalize ( vForward );
-
- trace_t tr2;
- UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vForward * 128, MASK_BLOCKLOS, pOther, COLLISION_GROUP_NONE, &tr2 );
-
- if ( tr2.fraction != 1.0f )
- {
- //NDebugOverlay::Box( tr2.endpos, Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 255, 0, 0, 10 );
- //NDebugOverlay::Box( GetAbsOrigin(), Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 0, 255, 0, 10 );
-
- if ( tr2.m_pEnt == NULL || ( tr2.m_pEnt && tr2.m_pEnt->GetMoveType() == MOVETYPE_NONE ) )
- {
- CEffectData data;
-
- data.m_vOrigin = tr2.endpos;
- data.m_vNormal = vForward;
- data.m_nEntIndex = tr2.fraction != 1.0f;
-
- //DispatchEffect( "BoltImpact", data );
- }
- }
-
- if ( ( ( pOther->GetMoveType() == MOVETYPE_VPHYSICS ) || ( pOther->GetMoveType() == MOVETYPE_PUSH ) ) && ( ( pOther->GetHealth() > 0 ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) ) )
- {
- CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>( pOther );
- if ( pProp )
- {
- pProp->SetInteraction( PROPINTER_PHYSGUN_NOTIFY_CHILDREN );
- }
-
- // We hit a physics object that survived the impact. Stick to it.
- StickTo( pOther, tr );
- }
- else
- {
- SetTouch( NULL );
- SetThink( NULL );
- SetContextThink( NULL, 0, s_szHunterFlechetteBubbles );
-
- UTIL_Remove( this );
- }
- }
- else
- {
- // See if we struck the world
- if ( pOther->GetMoveType() == MOVETYPE_NONE && !( tr.surface.flags & SURF_SKY ) )
- {
- // We hit a physics object that survived the impact. Stick to it.
- StickTo( pOther, tr );
- }
- else if( pOther->GetMoveType() == MOVETYPE_PUSH && FClassnameIs(pOther, "func_breakable") )
- {
- // We hit a func_breakable, stick to it.
- // The MOVETYPE_PUSH is a micro-optimization to cut down on the classname checks.
- StickTo( pOther, tr );
- }
- else
- {
- // Put a mark unless we've hit the sky
- if ( ( tr.surface.flags & SURF_SKY ) == false )
- {
- UTIL_ImpactTrace( &tr, DMG_BULLET );
- }
-
- UTIL_Remove( this );
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Fixup flechette position when seeking towards a striderbuster.
-//-----------------------------------------------------------------------------
-void CHunterFlechette::SeekThink()
-{
- if ( m_hSeekTarget )
- {
- Vector vecBodyTarget = m_hSeekTarget->BodyTarget( GetAbsOrigin() );
-
- Vector vecClosest;
- CalcClosestPointOnLineSegment( GetAbsOrigin(), m_vecShootPosition, vecBodyTarget, vecClosest, NULL );
-
- Vector vecDelta = vecBodyTarget - m_vecShootPosition;
- VectorNormalize( vecDelta );
-
- QAngle angShoot;
- VectorAngles( vecDelta, angShoot );
-
- float flSpeed = hunter_flechette_speed.GetFloat();
- if ( !flSpeed )
- {
- flSpeed = 2500.0f;
- }
-
- Vector vecVelocity = vecDelta * flSpeed;
- Teleport( &vecClosest, &angShoot, &vecVelocity );
-
- SetNextThink( gpGlobals->curtime, s_szHunterFlechetteSeekThink );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Play a near miss sound as we travel past the player.
-//-----------------------------------------------------------------------------
-void CHunterFlechette::DopplerThink()
-{
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- if ( !pPlayer )
- return;
-
- Vector vecVelocity = GetAbsVelocity();
- VectorNormalize( vecVelocity );
-
- float flMyDot = DotProduct( vecVelocity, GetAbsOrigin() );
- float flPlayerDot = DotProduct( vecVelocity, pPlayer->GetAbsOrigin() );
-
- if ( flPlayerDot <= flMyDot )
- {
- EmitSound( "NPC_Hunter.FlechetteNearMiss" );
-
- // We've played the near miss sound and we're not seeking. Stop thinking.
- SetThink( NULL );
- }
- else
- {
- SetNextThink( gpGlobals->curtime );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Think every 0.1 seconds to make bubbles if we're flying through water.
-//-----------------------------------------------------------------------------
-void CHunterFlechette::BubbleThink()
-{
- SetNextThink( gpGlobals->curtime + 0.1f, s_szHunterFlechetteBubbles );
-
- if ( GetWaterLevel() == 0 )
- return;
-
- UTIL_BubbleTrail( GetAbsOrigin() - GetAbsVelocity() * 0.1f, GetAbsOrigin(), 5 );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::Shoot( Vector &vecVelocity, bool bBrightFX )
-{
- CreateSprites( bBrightFX );
-
- m_vecShootPosition = GetAbsOrigin();
-
- SetAbsVelocity( vecVelocity );
-
- SetThink( &CHunterFlechette::DopplerThink );
- SetNextThink( gpGlobals->curtime );
-
- SetContextThink( &CHunterFlechette::BubbleThink, gpGlobals->curtime + 0.1, s_szHunterFlechetteBubbles );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::DangerSoundThink()
-{
- EmitSound( "NPC_Hunter.FlechettePreExplode" );
-
- CSoundEnt::InsertSound( SOUND_DANGER|SOUND_CONTEXT_EXCLUDE_COMBINE, GetAbsOrigin(), 150.0f, 0.5, this );
- SetThink( &CHunterFlechette::ExplodeThink );
- SetNextThink( gpGlobals->curtime + HUNTER_FLECHETTE_WARN_TIME );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::ExplodeThink()
-{
- Explode();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CHunterFlechette::Explode()
-{
- SetSolid( SOLID_NONE );
-
- // Don't catch self in own explosion!
- m_takedamage = DAMAGE_NO;
-
- EmitSound( "NPC_Hunter.FlechetteExplode" );
-
- // Move the explosion effect to the tip to reduce intersection with the world.
- Vector vecFuse;
- GetAttachment( s_nFlechetteFuseAttach, vecFuse );
- DispatchParticleEffect( "hunter_projectile_explosion_1", vecFuse, GetAbsAngles(), NULL );
-
- int nDamageType = DMG_DISSOLVE;
-
- // Perf optimization - only every other explosion makes a physics force. This is
- // hardly noticeable since flechettes usually explode in clumps.
- static int s_nExplosionCount = 0;
- s_nExplosionCount++;
- if ( ( s_nExplosionCount & 0x01 ) && hunter_cheap_explosions.GetBool() )
- {
- nDamageType |= DMG_PREVENT_PHYSICS_FORCE;
- }
-
- RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), sk_hunter_flechette_explode_dmg.GetFloat(), nDamageType ), GetAbsOrigin(), sk_hunter_flechette_explode_radius.GetFloat(), CLASS_NONE, NULL );
-
- AddEffects( EF_NODRAW );
-
- SetThink( &CBaseEntity::SUB_Remove );
- SetNextThink( gpGlobals->curtime + 0.1f );
-}
-
-
-//-----------------------------------------------------------------------------
-// Calculate & apply damage & force for a charge to a target.
-// Done outside of the hunter because we need to do this inside a trace filter.
-//-----------------------------------------------------------------------------
-void Hunter_ApplyChargeDamage( CBaseEntity *pHunter, CBaseEntity *pTarget, float flDamage )
-{
- Vector attackDir = ( pTarget->WorldSpaceCenter() - pHunter->WorldSpaceCenter() );
- VectorNormalize( attackDir );
- Vector offset = RandomVector( -32, 32 ) + pTarget->WorldSpaceCenter();
-
- // Generate enough force to make a 75kg guy move away at 700 in/sec
- Vector vecForce = attackDir * ImpulseScale( 75, 700 );
-
- // Deal the damage
- CTakeDamageInfo info( pHunter, pHunter, vecForce, offset, flDamage, DMG_CLUB );
- pTarget->TakeDamage( info );
-}
-
-
-//-----------------------------------------------------------------------------
-// A simple trace filter class to skip small moveable physics objects
-//-----------------------------------------------------------------------------
-class CHunterTraceFilterSkipPhysics : public CTraceFilter
-{
-public:
- // It does have a base, but we'll never network anything below here..
- DECLARE_CLASS_NOBASE( CHunterTraceFilterSkipPhysics );
-
- CHunterTraceFilterSkipPhysics( const IHandleEntity *passentity, int collisionGroup, float minMass )
- : m_pPassEnt(passentity), m_collisionGroup(collisionGroup), m_minMass(minMass)
- {
- }
- virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
- {
- if ( !StandardFilterRules( pHandleEntity, contentsMask ) )
- return false;
-
- if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) )
- return false;
-
- // Don't test if the game code tells us we should ignore this collision...
- CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
- if ( pEntity )
- {
- if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) )
- return false;
-
- if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) )
- return false;
-
- // don't test small moveable physics objects (unless it's an NPC)
- if ( !pEntity->IsNPC() && pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
- {
- float entMass = PhysGetEntityMass( pEntity ) ;
- if ( entMass < m_minMass )
- {
- if ( entMass < m_minMass * 0.666f || pEntity->CollisionProp()->BoundingRadius() < (assert_cast<const CAI_BaseNPC *>(EntityFromEntityHandle( m_pPassEnt )))->GetHullHeight() )
- {
- return false;
- }
- }
- }
-
- // If we hit an antlion, don't stop, but kill it
- if ( pEntity->Classify() == CLASS_ANTLION )
- {
- CBaseEntity *pHunter = (CBaseEntity *)EntityFromEntityHandle( m_pPassEnt );
- Hunter_ApplyChargeDamage( pHunter, pEntity, pEntity->GetHealth() );
- return false;
- }
- }
-
- return true;
- }
-
-private:
- const IHandleEntity *m_pPassEnt;
- int m_collisionGroup;
- float m_minMass;
-};
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-inline void HunterTraceHull_SkipPhysics( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin,
- const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore,
- int collisionGroup, trace_t *ptr, float minMass )
-{
- Ray_t ray;
- ray.Init( vecAbsStart, vecAbsEnd, hullMin, hullMax );
- CHunterTraceFilterSkipPhysics traceFilter( ignore, collisionGroup, minMass );
- enginetrace->TraceRay( ray, mask, &traceFilter, ptr );
-}
-
-
-//-----------------------------------------------------------------------------
-// Hunter follow behavior
-//-----------------------------------------------------------------------------
-class CAI_HunterEscortBehavior : public CAI_FollowBehavior
-{
-public:
- DECLARE_CLASS( CAI_HunterEscortBehavior, CAI_FollowBehavior );
-
- CAI_HunterEscortBehavior() :
- BaseClass( AI_FollowParams_t( AIF_HUNTER, true ) ),
- m_flTimeEscortReturn( 0 ),
- m_bEnabled( false )
- {
- }
-
- CNPC_Hunter *GetOuter() { return (CNPC_Hunter *)( BaseClass::GetOuter() ); }
-
- void SetEscortTarget( CNPC_Strider *pLeader, bool fFinishCurSchedule = false );
- CNPC_Strider * GetEscortTarget() { return (CNPC_Strider *)GetFollowTarget(); }
-
- bool FarFromFollowTarget()
- {
- return ( GetFollowTarget() && (GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin()).LengthSqr() > HUNTER_FOLLOW_DISTANCE_SQR );
- }
-
- void DrawDebugGeometryOverlays();
- bool ShouldFollow();
- void BuildScheduleTestBits();
-
- void BeginScheduleSelection();
-
- void GatherConditions();
- void GatherConditionsNotActive();
- int SelectSchedule();
- int FollowCallBaseSelectSchedule();
- void StartTask( const Task_t *pTask );
- void RunTask( const Task_t *pTask );
-
- void CheckBreakEscort();
-
- void OnDamage( const CTakeDamageInfo &info );
- static void DistributeFreeHunters();
- static void FindFreeHunters( CUtlVector<CNPC_Hunter *> *pFreeHunters );
-
- float m_flTimeEscortReturn;
- CSimpleSimTimer m_FollowAttackTimer;
- bool m_bEnabled;
-
- static float gm_flLastDefendSound; // not saved and loaded, it's okay to yell again after a load
-
- //---------------------------------
-
- DECLARE_DATADESC();
-};
-
-
-BEGIN_DATADESC( CAI_HunterEscortBehavior )
- DEFINE_FIELD( m_flTimeEscortReturn, FIELD_TIME ),
- DEFINE_EMBEDDED( m_FollowAttackTimer ),
- DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
-END_DATADESC();
-
-float CAI_HunterEscortBehavior::gm_flLastDefendSound;
-
-//-----------------------------------------------------------------------------
-// Hunter PHYSICS DAMAGE TABLE
-//-----------------------------------------------------------------------------
-#define HUNTER_MIN_PHYSICS_DAMAGE 10
-
-static impactentry_t s_HunterLinearTable[] =
-{
- { 150*150, 75 },
- { 350*350, 105 },
- { 1000*1000, 300 },
-};
-
-static impactentry_t s_HunterAngularTable[] =
-{
- { 100*100, 75 },
- { 200*200, 105 },
- { 300*300, 300 },
-};
-
-impactdamagetable_t s_HunterImpactDamageTable =
-{
- s_HunterLinearTable,
- s_HunterAngularTable,
-
- ARRAYSIZE(s_HunterLinearTable),
- ARRAYSIZE(s_HunterAngularTable),
-
- 24*24, // minimum linear speed squared
- 360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage)
- 5, // can't take damage from anything under 5kg
-
- 10, // anything less than 10kg is "small"
- HUNTER_MIN_PHYSICS_DAMAGE, // never take more than 10 pts of damage from anything under 10kg
- 36*36, // <10kg objects must go faster than 36 in/s to do damage
-
- VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg
- 4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
- 5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway)
- 0.0f, // min vel
-};
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-class CNPC_Hunter : public CAI_BaseActor
-{
- DECLARE_CLASS( CNPC_Hunter, CAI_BaseActor );
-
-public:
- CNPC_Hunter();
- ~CNPC_Hunter();
-
- //---------------------------------
-
- void Precache();
- void Spawn();
- void PostNPCInit();
- void Activate();
- void UpdateOnRemove();
- void OnRestore();
- bool CreateBehaviors();
- void IdleSound();
- bool ShouldPlayIdleSound();
- bool CanBecomeRagdoll();
- Activity GetDeathActivity();
- void StopLoopingSounds();
-
- const impactdamagetable_t &GetPhysicsImpactDamageTable();
-
- Class_T Classify();
- Vector BodyTarget( const Vector &posSrc, bool bNoisy /*= true*/ );
-
- int DrawDebugTextOverlays();
- void DrawDebugGeometryOverlays();
-
- void UpdateEfficiency( bool bInPVS );
-
- //---------------------------------
-
- virtual Vector GetNodeViewOffset() { return BaseClass::GetDefaultEyeOffset(); }
-
- int GetSoundInterests();
-
- bool IsInLargeOutdoorMap();
-
- //---------------------------------
- // CAI_BaseActor
- //---------------------------------
- const char *SelectRandomExpressionForState( NPC_STATE state );
- void PlayExpressionForState( NPC_STATE state );
-
- //---------------------------------
- // CBaseAnimating
- //---------------------------------
- float GetIdealAccel() const { return GetIdealSpeed(); }
-
- //---------------------------------
- // Behavior
- //---------------------------------
- void NPCThink();
- void PrescheduleThink();
- void GatherConditions();
- void CollectSiegeTargets();
- void ManageSiegeTargets();
- void KillCurrentSiegeTarget();
- bool QueryHearSound( CSound *pSound );
- void OnSeeEntity( CBaseEntity *pEntity );
- void CheckFlinches() {} // Hunter handles on own
- void BuildScheduleTestBits();
- NPC_STATE SelectIdealState();
- int SelectSchedule();
- int SelectCombatSchedule();
- int SelectSiegeSchedule();
- int TranslateSchedule( int scheduleType );
- void StartTask( const Task_t *pTask );
- void RunTask( const Task_t *pTask );
- Activity NPC_TranslateActivity( Activity baseAct );
- void OnChangeActivity( Activity eNewActivity );
-
- void HandleAnimEvent( animevent_t *pEvent );
- bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *pSourceEnt);
-
- void PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot );
-
- void AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority );
- float EnemyDistTolerance() { return 100.0f; }
-
- bool ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity );
-
- void OnChangeHintGroup( string_t oldGroup, string_t newGroup );
-
- bool IsUsingSiegeTargets() { return m_iszSiegeTargetName != NULL_STRING; }
-
- //---------------------------------
- // Inputs
- //---------------------------------
- void InputDodge( inputdata_t &inputdata );
- void InputFlankEnemy( inputdata_t &inputdata );
- void InputDisableShooting( inputdata_t &inputdata );
- void InputEnableShooting( inputdata_t &inputdata );
- void InputFollowStrider( inputdata_t &inputdata );
- void InputUseSiegeTargets( inputdata_t &inputdata );
- void InputEnableSquadShootDelay( inputdata_t &inputdata );
- void InputDisableSquadShootDelay( inputdata_t &inputdata );
- void InputEnableUnplantedShooting( inputdata_t &inputdata );
- void InputDisableUnplantedShooting( inputdata_t &inputdata );
-
- //---------------------------------
- // Combat
- //---------------------------------
- bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
- bool IsValidEnemy( CBaseEntity *pEnemy );
-
- Disposition_t IRelationType( CBaseEntity *pTarget );
- int IRelationPriority( CBaseEntity *pTarget );
-
- void SetSquad( CAI_Squad *pSquad );
-
- bool UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer = NULL );
-
- int RangeAttack1Conditions( float flDot, float flDist );
- int RangeAttack2Conditions( float flDot, float flDist );
-
- int MeleeAttack1Conditions ( float flDot, float flDist );
- int MeleeAttack1ConditionsVsEnemyInVehicle( CBaseCombatCharacter *pEnemy, float flDot );
-
- int MeleeAttack2Conditions( float flDot, float flDist );
-
- bool WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions);
- bool TestShootPosition(const Vector &vecShootPos, const Vector &targetPos );
-
- Vector Weapon_ShootPosition();
-
- CBaseEntity * MeleeAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin );
-
- void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
- void DoMuzzleFlash( int nAttachment );
-
- bool CanShootThrough( const trace_t &tr, const Vector &vecTarget );
-
- int CountRangedAttackers();
- void DelayRangedAttackers( float minDelay, float maxDelay, bool bForced = false );
-
- //---------------------------------
- // Sounds & speech
- //---------------------------------
- void AlertSound();
- void PainSound( const CTakeDamageInfo &info );
- void DeathSound( const CTakeDamageInfo &info );
-
- //---------------------------------
- // Damage handling
- //---------------------------------
- void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
- bool IsHeavyDamage( const CTakeDamageInfo &info );
- int OnTakeDamage( const CTakeDamageInfo &info );
- int OnTakeDamage_Alive( const CTakeDamageInfo &info );
- void Event_Killed( const CTakeDamageInfo &info );
-
- void StartBleeding();
- inline bool IsBleeding() { return m_bIsBleeding; }
- void Explode();
-
- void SetupGlobalModelData();
-
- //---------------------------------
- // Navigation & Movement
- //---------------------------------
- bool OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval );
- float MaxYawSpeed();
- bool IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const;
- float GetJumpGravity() const { return 3.0f; }
- bool ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity );
- void TaskFail( AI_TaskFailureCode_t code );
- void TaskFail( const char *pszGeneralFailText ) { TaskFail( MakeFailCode( pszGeneralFailText ) ); }
-
- CAI_BaseNPC * GetEntity() { return this; }
-
- //---------------------------------
- // Magnade
- //---------------------------------
- void StriderBusterAttached( CBaseEntity *pAttached );
- void StriderBusterDetached( CBaseEntity *pAttached );
-
-private:
-
- void ConsiderFlinching( const CTakeDamageInfo &info );
-
- void TaskFindDodgeActivity();
-
- void GatherChargeConditions();
- void GatherIndoorOutdoorConditions();
-
- // Charge attack.
- bool ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel );
- void ChargeLookAhead();
- float ChargeSteer();
- bool EnemyIsRightInFrontOfMe( CBaseEntity **pEntity );
- void ChargeDamage( CBaseEntity *pTarget );
- bool HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity );
-
- void BeginVolley( int nNum, float flStartTime );
- bool ShootFlechette( CBaseEntity *pTargetEntity, bool bSingleShot );
- bool ShouldSeekTarget( CBaseEntity *pTargetEntity, bool bStriderBuster );
- void GetShootDir( Vector &vecDir, const Vector &vecSrc, CBaseEntity *pTargetEntity, bool bStriderbuster, int nShotNum, bool bSingleShot );
- bool ClampShootDir( Vector &vecDir );
-
- void SetAim( const Vector &aimDir, float flInterval );
- void RelaxAim( float flInterval );
- void UpdateAim();
- void UpdateEyes();
- void LockBothEyes( float flDuration );
- void UnlockBothEyes( float flDuration );
-
- void TeslaThink();
- void BleedThink();
- void JostleVehicleThink();
-
- void FollowStrider( const char *szStrider );
- void FollowStrider( CNPC_Strider * pStrider );
- int NumHuntersInMySquad();
-
- bool CanPlantHere( const Vector &vecPos );
-
- //---------------------------------
- // Foot handling
- //---------------------------------
- Vector LeftFootHit( float eventtime );
- Vector RightFootHit( float eventtime );
- Vector BackFootHit( float eventtime );
-
- void FootFX( const Vector &origin );
-
- CBaseEntity *GetEnemyVehicle();
- bool IsCorporealEnemy( CBaseEntity *pEnemy );
-
- void PhysicsDamageEffect( const Vector &vecPos, const Vector &vecDir );
- bool PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer );
-
- //-----------------------------------------------------
- // Conditions, Schedules, Tasks
- //-----------------------------------------------------
- enum
- {
- SCHED_HUNTER_RANGE_ATTACK1 = BaseClass::NEXT_SCHEDULE,
- SCHED_HUNTER_RANGE_ATTACK2,
- SCHED_HUNTER_MELEE_ATTACK1,
- SCHED_HUNTER_DODGE,
- SCHED_HUNTER_CHASE_ENEMY,
- SCHED_HUNTER_CHASE_ENEMY_MELEE,
- SCHED_HUNTER_COMBAT_FACE,
- SCHED_HUNTER_FLANK_ENEMY,
- SCHED_HUNTER_CHANGE_POSITION,
- SCHED_HUNTER_CHANGE_POSITION_FINISH,
- SCHED_HUNTER_SIDESTEP,
- SCHED_HUNTER_PATROL,
- SCHED_HUNTER_FLINCH_STICKYBOMB,
- SCHED_HUNTER_STAGGER,
- SCHED_HUNTER_PATROL_RUN,
- SCHED_HUNTER_TAKE_COVER_FROM_ENEMY,
- SCHED_HUNTER_HIDE_UNDER_COVER,
- SCHED_HUNTER_FAIL_IMMEDIATE, // instant fail without waiting
- SCHED_HUNTER_CHARGE_ENEMY,
- SCHED_HUNTER_FAIL_CHARGE_ENEMY,
- SCHED_HUNTER_FOUND_ENEMY,
- SCHED_HUNTER_FOUND_ENEMY_ACK,
- SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER,
- SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER_LATENT,
- SCHED_HUNTER_GOTO_HINT,
- SCHED_HUNTER_CLEAR_HINTNODE,
- SCHED_HUNTER_FAIL_DODGE,
- SCHED_HUNTER_SIEGE_STAND,
- SCHED_HUNTER_CHANGE_POSITION_SIEGE,
-
- TASK_HUNTER_AIM = BaseClass::NEXT_TASK,
- TASK_HUNTER_FIND_DODGE_POSITION,
- TASK_HUNTER_DODGE,
- TASK_HUNTER_PRE_RANGE_ATTACK2,
- TASK_HUNTER_SHOOT_COMMIT,
- TASK_HUNTER_BEGIN_FLANK,
- TASK_HUNTER_ANNOUNCE_FLANK,
- TASK_HUNTER_STAGGER,
- TASK_HUNTER_CORNERED_TIMER,
- TASK_HUNTER_FIND_SIDESTEP_POSITION,
- TASK_HUNTER_CHARGE,
- TASK_HUNTER_CHARGE_DELAY,
- TASK_HUNTER_FINISH_RANGE_ATTACK,
- TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY,
-
- COND_HUNTER_SHOULD_PATROL = BaseClass::NEXT_CONDITION,
- COND_HUNTER_FORCED_FLANK_ENEMY,
- COND_HUNTER_FORCED_DODGE,
- COND_HUNTER_CAN_CHARGE_ENEMY,
- COND_HUNTER_HIT_BY_STICKYBOMB,
- COND_HUNTER_STAGGERED,
- COND_HUNTER_IS_INDOORS,
- COND_HUNTER_SEE_STRIDERBUSTER,
- COND_HUNTER_INCOMING_VEHICLE,
- COND_HUNTER_NEW_HINTGROUP,
- COND_HUNTER_CANT_PLANT,
- COND_HUNTER_SQUADMATE_FOUND_ENEMY,
- };
-
- enum HunterEyeStates_t
- {
- HUNTER_EYE_STATE_TOP_LOCKED = 0,
- HUNTER_EYE_STATE_BOTTOM_LOCKED,
- HUNTER_EYE_STATE_BOTH_LOCKED,
- HUNTER_EYE_STATE_BOTH_UNLOCKED,
- };
-
- string_t m_iszFollowTarget; // Name of the strider we should follow.
- CSimpleStopwatch m_BeginFollowDelay;
-
- int m_nKillingDamageType;
- HunterEyeStates_t m_eEyeState;
-
- float m_aimYaw;
- float m_aimPitch;
-
- float m_flShootAllowInterruptTime;
- float m_flNextChargeTime; // Prevents us from doing our threat display too often.
- float m_flNextDamageTime;
- float m_flNextSideStepTime;
-
- CSimpleSimTimer m_HeavyDamageDelay;
- CSimpleSimTimer m_FlinchTimer;
- CSimpleSimTimer m_EyeSwitchTimer; // Controls how often we switch which eye is focusing on our enemy.
-
- bool m_bTopMuzzle; // Used to alternate between top muzzle FX and bottom muzzle FX.
- bool m_bEnableSquadShootDelay;
- bool m_bIsBleeding;
-
- Activity m_eDodgeActivity;
- CSimpleSimTimer m_RundownDelay;
- CSimpleSimTimer m_IgnoreVehicleTimer;
-
- bool m_bDisableShooting; // Range attack disabled via an input. Used for scripting melee attacks.
-
- bool m_bFlashlightInEyes; // The player is shining the flashlight on our eyes.
- float m_flPupilDilateTime; // When to dilate our pupils if the flashlight is no longer on our eyes.
-
- Vector m_vecEnemyLastSeen;
- Vector m_vecLastCanPlantHerePos;
- Vector m_vecStaggerDir;
-
- bool m_bPlanted;
- bool m_bLastCanPlantHere;
- bool m_bMissLeft;
- bool m_bEnableUnplantedShooting;
-
- static float gm_flMinigunDistZ;
- static Vector gm_vecLocalRelativePositionMinigun;
-
- static int gm_nTopGunAttachment;
- static int gm_nBottomGunAttachment;
- static int gm_nAimYawPoseParam;
- static int gm_nAimPitchPoseParam;
- static int gm_nBodyYawPoseParam;
- static int gm_nBodyPitchPoseParam;
- static int gm_nStaggerYawPoseParam;
- static int gm_nHeadCenterAttachment;
- static int gm_nHeadBottomAttachment;
- static float gm_flHeadRadius;
-
- static int gm_nUnplantedNode;
- static int gm_nPlantedNode;
-
- CAI_HunterEscortBehavior m_EscortBehavior;
-
- int m_nFlechettesQueued;
- int m_nClampedShots; // The number of consecutive shots fired at an out-of-max yaw target.
-
- float m_flNextRangeAttack2Time; // Time when we can fire another volley of flechettes.
- float m_flNextFlechetteTime; // Time to fire the next flechette in this volley.
-
- float m_flNextMeleeTime;
- float m_flTeslaStopTime;
-
- string_t m_iszCurrentExpression;
-
- // buster fu
- CUtlVector< EHANDLE > m_hAttachedBusters; // List of busters attached to us
- float m_fCorneredTimer; ///< hunter was cornered when fleeing player; it won't flee again until this time
-
- CSimpleSimTimer m_CheckHintGroupTimer;
-
- DEFINE_CUSTOM_AI;
-
- DECLARE_DATADESC();
-
- friend class CAI_HunterEscortBehavior;
- friend class CHunterMaker;
-
- bool m_bInLargeOutdoorMap;
- float m_flTimeSawEnemyAgain;
-
- // Sounds
- //CSoundPatch *m_pGunFiringSound;
-
- CUtlVector<EHANDLE> m_pSiegeTargets;
- string_t m_iszSiegeTargetName;
- float m_flTimeNextSiegeTargetAttack;
- EHANDLE m_hCurrentSiegeTarget;
-
- EHANDLE m_hHitByVehicle;
-};
-
-
-LINK_ENTITY_TO_CLASS( npc_hunter, CNPC_Hunter );
-
-
-BEGIN_DATADESC( CNPC_Hunter )
-
- DEFINE_KEYFIELD( m_iszFollowTarget, FIELD_STRING, "FollowTarget" ),
-
- DEFINE_FIELD( m_aimYaw, FIELD_FLOAT ),
- DEFINE_FIELD( m_aimPitch, FIELD_FLOAT ),
-
- DEFINE_FIELD( m_flShootAllowInterruptTime, FIELD_TIME ),
- DEFINE_FIELD( m_flNextChargeTime, FIELD_TIME ),
- //DEFINE_FIELD( m_flNextDamageTime, FIELD_TIME ),
- DEFINE_FIELD( m_flNextSideStepTime, FIELD_TIME ),
-
- DEFINE_EMBEDDED( m_HeavyDamageDelay ),
- DEFINE_EMBEDDED( m_FlinchTimer ),
-
- DEFINE_FIELD( m_eEyeState, FIELD_INTEGER ),
-
- DEFINE_FIELD( m_bTopMuzzle, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bEnableSquadShootDelay, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bIsBleeding, FIELD_BOOLEAN ),
-
- DEFINE_FIELD( m_bDisableShooting, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bFlashlightInEyes, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flPupilDilateTime, FIELD_TIME ),
-
- DEFINE_FIELD( m_vecEnemyLastSeen, FIELD_POSITION_VECTOR ),
- DEFINE_FIELD( m_vecLastCanPlantHerePos, FIELD_POSITION_VECTOR ),
- DEFINE_FIELD( m_vecStaggerDir, FIELD_VECTOR ),
-
- DEFINE_FIELD( m_bPlanted, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bLastCanPlantHere, FIELD_BOOLEAN ),
- //DEFINE_FIELD( m_bMissLeft, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bEnableUnplantedShooting, FIELD_BOOLEAN ),
-
- DEFINE_FIELD( m_nKillingDamageType, FIELD_INTEGER ),
- DEFINE_FIELD( m_eDodgeActivity, FIELD_INTEGER ),
- DEFINE_EMBEDDED( m_RundownDelay ),
- DEFINE_EMBEDDED( m_IgnoreVehicleTimer ),
-
- DEFINE_FIELD( m_flNextMeleeTime, FIELD_TIME ),
- DEFINE_FIELD( m_flTeslaStopTime, FIELD_TIME ),
-
- // (auto saved by AI)
- //DEFINE_FIELD( m_EscortBehavior, FIELD_EMBEDDED ),
-
- DEFINE_FIELD( m_iszCurrentExpression, FIELD_STRING ),
-
- DEFINE_FIELD( m_fCorneredTimer, FIELD_TIME),
-
- DEFINE_EMBEDDED( m_CheckHintGroupTimer ),
-
- // (Recomputed in Precache())
- //DEFINE_FIELD( m_bInLargeOutdoorMap, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flTimeSawEnemyAgain, FIELD_TIME ),
-
- //DEFINE_SOUNDPATCH( m_pGunFiringSound ),
-
- //DEFINE_UTLVECTOR( m_pSiegeTarget, FIELD_EHANDLE ),
- DEFINE_FIELD( m_iszSiegeTargetName, FIELD_STRING ),
- DEFINE_FIELD( m_flTimeNextSiegeTargetAttack, FIELD_TIME ),
- DEFINE_FIELD( m_hCurrentSiegeTarget, FIELD_EHANDLE ),
- DEFINE_FIELD( m_hHitByVehicle, FIELD_EHANDLE ),
-
- DEFINE_EMBEDDED( m_BeginFollowDelay ),
- DEFINE_EMBEDDED( m_EyeSwitchTimer ),
-
- DEFINE_FIELD( m_nFlechettesQueued, FIELD_INTEGER ),
- DEFINE_FIELD( m_nClampedShots, FIELD_INTEGER ),
- DEFINE_FIELD( m_flNextRangeAttack2Time, FIELD_TIME ),
- DEFINE_FIELD( m_flNextFlechetteTime, FIELD_TIME ),
- DEFINE_UTLVECTOR( m_hAttachedBusters, FIELD_EHANDLE ),
- DEFINE_UTLVECTOR( m_pSiegeTargets, FIELD_EHANDLE ),
-
- // inputs
- DEFINE_INPUTFUNC( FIELD_VOID, "Dodge", InputDodge ),
- DEFINE_INPUTFUNC( FIELD_VOID, "FlankEnemy", InputFlankEnemy ),
- DEFINE_INPUTFUNC( FIELD_STRING, "DisableShooting", InputDisableShooting ),
- DEFINE_INPUTFUNC( FIELD_STRING, "EnableShooting", InputEnableShooting ),
- DEFINE_INPUTFUNC( FIELD_STRING, "FollowStrider", InputFollowStrider ),
- DEFINE_INPUTFUNC( FIELD_STRING, "UseSiegeTargets", InputUseSiegeTargets ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableSquadShootDelay", InputEnableSquadShootDelay ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableSquadShootDelay", InputDisableSquadShootDelay ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableUnplantedShooting", InputEnableUnplantedShooting ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableUnplantedShooting", InputDisableUnplantedShooting ),
-
- // Function Pointers
- DEFINE_THINKFUNC( TeslaThink ),
- DEFINE_THINKFUNC( BleedThink ),
- DEFINE_THINKFUNC( JostleVehicleThink ),
-
-END_DATADESC()
-
-//-----------------------------------------------------------------------------
-
-int CNPC_Hunter::gm_nUnplantedNode = 0;
-int CNPC_Hunter::gm_nPlantedNode = 0;
-
-int CNPC_Hunter::gm_nAimYawPoseParam = -1;
-int CNPC_Hunter::gm_nAimPitchPoseParam = -1;
-int CNPC_Hunter::gm_nBodyYawPoseParam = -1;
-int CNPC_Hunter::gm_nBodyPitchPoseParam = -1;
-int CNPC_Hunter::gm_nStaggerYawPoseParam = -1;
-int CNPC_Hunter::gm_nHeadCenterAttachment = -1;
-int CNPC_Hunter::gm_nHeadBottomAttachment = -1;
-float CNPC_Hunter::gm_flHeadRadius = 0;
-
-int CNPC_Hunter::gm_nTopGunAttachment = -1;
-int CNPC_Hunter::gm_nBottomGunAttachment = -1;
-
-float CNPC_Hunter::gm_flMinigunDistZ;
-Vector CNPC_Hunter::gm_vecLocalRelativePositionMinigun;
-
-//-----------------------------------------------------------------------------
-
-static CUtlVector<CNPC_Hunter *> g_Hunters;
-float g_TimeLastDistributeFreeHunters = -1;
-const float FREE_HUNTER_DISTRIBUTE_INTERVAL = 2;
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-CNPC_Hunter::CNPC_Hunter()
-{
- g_Hunters.AddToTail( this );
- g_TimeLastDistributeFreeHunters = -1;
- m_flTimeSawEnemyAgain = HUNTER_SEE_ENEMY_TIME_INVALID;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-CNPC_Hunter::~CNPC_Hunter()
-{
- g_Hunters.FindAndRemove( this );
- g_TimeLastDistributeFreeHunters = -1;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::Precache()
-{
- PrecacheModel( "models/hunter.mdl" );
- PropBreakablePrecacheAll( MAKE_STRING("models/hunter.mdl") );
-
- PrecacheScriptSound( "NPC_Hunter.Idle" );
- PrecacheScriptSound( "NPC_Hunter.Scan" );
- PrecacheScriptSound( "NPC_Hunter.Alert" );
- PrecacheScriptSound( "NPC_Hunter.Pain" );
- PrecacheScriptSound( "NPC_Hunter.PreCharge" );
- PrecacheScriptSound( "NPC_Hunter.Angry" );
- PrecacheScriptSound( "NPC_Hunter.Death" );
- PrecacheScriptSound( "NPC_Hunter.FireMinigun" );
- PrecacheScriptSound( "NPC_Hunter.Footstep" );
- PrecacheScriptSound( "NPC_Hunter.BackFootstep" );
- PrecacheScriptSound( "NPC_Hunter.FlechetteVolleyWarn" );
- PrecacheScriptSound( "NPC_Hunter.FlechetteShoot" );
- PrecacheScriptSound( "NPC_Hunter.FlechetteShootLoop" );
- PrecacheScriptSound( "NPC_Hunter.FlankAnnounce" );
- PrecacheScriptSound( "NPC_Hunter.MeleeAnnounce" );
- PrecacheScriptSound( "NPC_Hunter.MeleeHit" );
- PrecacheScriptSound( "NPC_Hunter.TackleAnnounce" );
- PrecacheScriptSound( "NPC_Hunter.TackleHit" );
- PrecacheScriptSound( "NPC_Hunter.ChargeHitEnemy" );
- PrecacheScriptSound( "NPC_Hunter.ChargeHitWorld" );
- PrecacheScriptSound( "NPC_Hunter.FoundEnemy" );
- PrecacheScriptSound( "NPC_Hunter.FoundEnemyAck" );
- PrecacheScriptSound( "NPC_Hunter.DefendStrider" );
- PrecacheScriptSound( "NPC_Hunter.HitByVehicle" );
-
- PrecacheParticleSystem( "hunter_muzzle_flash" );
- PrecacheParticleSystem( "blood_impact_synth_01" );
- PrecacheParticleSystem( "blood_impact_synth_01_arc_parent" );
- PrecacheParticleSystem( "blood_spurt_synth_01" );
- PrecacheParticleSystem( "blood_drip_synth_01" );
-
- PrecacheInstancedScene( "scenes/npc/hunter/hunter_scan.vcd" );
- PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyeclose.vcd" );
- PrecacheInstancedScene( "scenes/npc/hunter/hunter_roar.vcd" );
- PrecacheInstancedScene( "scenes/npc/hunter/hunter_pain.vcd" );
- PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyedarts_top.vcd" );
- PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyedarts_bottom.vcd" );
-
- PrecacheMaterial( "effects/water_highlight" );
-
- UTIL_PrecacheOther( "hunter_flechette" );
- UTIL_PrecacheOther( "sparktrail" );
-
- m_bInLargeOutdoorMap = false;
- if( !Q_strnicmp( STRING(gpGlobals->mapname), "ep2_outland_12", 14) )
- {
- m_bInLargeOutdoorMap = true;
- }
-
- BaseClass::Precache();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::Spawn()
-{
- Precache();
-
- SetModel( "models/hunter.mdl" );
- BaseClass::Spawn();
-
- //m_debugOverlays |= OVERLAY_NPC_ROUTE_BIT | OVERLAY_BBOX_BIT | OVERLAY_PIVOT_BIT;
-
- SetHullType( HULL_MEDIUM_TALL );
- SetHullSizeNormal();
- SetDefaultEyeOffset();
-
- SetNavType( NAV_GROUND );
- m_flGroundSpeed = 500;
- m_NPCState = NPC_STATE_NONE;
-
- SetBloodColor( DONT_BLEED );
-
- m_iHealth = m_iMaxHealth = sk_hunter_health.GetInt();
-
- m_flFieldOfView = HUNTER_FOV_DOT;
-
- SetSolid( SOLID_BBOX );
- AddSolidFlags( FSOLID_NOT_STANDABLE );
- SetMoveType( MOVETYPE_STEP );
-
- SetupGlobalModelData();
-
- CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_SQUAD | bits_CAP_ANIMATEDFACE );
- CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_INNATE_MELEE_ATTACK1 );
- CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK );
-
- if ( !hunter_allow_dissolve.GetBool() )
- {
- AddEFlags( EFL_NO_DISSOLVE );
- }
-
- if( hunter_allow_nav_jump.GetBool() )
- {
- CapabilitiesAdd( bits_CAP_MOVE_JUMP );
- }
-
- NPCInit();
-
- m_bEnableSquadShootDelay = true;
-
- m_flDistTooFar = hunter_flechette_max_range.GetFloat();
-
- // Discard time must be greater than free knowledge duration. Make it double.
- float freeKnowledge = hunter_free_knowledge.GetFloat();
- if ( freeKnowledge < GetEnemies()->GetEnemyDiscardTime() )
- {
- GetEnemies()->SetEnemyDiscardTime( MAX( freeKnowledge + 0.1, AI_DEF_ENEMY_DISCARD_TIME ) );
- }
- GetEnemies()->SetFreeKnowledgeDuration( freeKnowledge );
-
- // Find out what strider we should follow, if any.
- if ( m_iszFollowTarget != NULL_STRING )
- {
- m_BeginFollowDelay.Set( .1 ); // Allow time for strider to spawn
- }
-
- //if ( !m_pGunFiringSound )
- //{
- // CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- // CPASAttenuationFilter filter( this );
- //
- // m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "NPC_Hunter.FlechetteShootLoop" );
- // controller.Play( m_pGunFiringSound, 0.0, 100 );
- //}
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::UpdateEfficiency( bool bInPVS )
-{
- SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL );
- SetMoveEfficiency( AIME_NORMAL );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::CreateBehaviors()
-{
- AddBehavior( &m_EscortBehavior );
-
- return BaseClass::CreateBehaviors();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::SetupGlobalModelData()
-{
- if ( gm_nBodyYawPoseParam != -1 )
- return;
-
- gm_nAimYawPoseParam = LookupPoseParameter( "aim_yaw" );
- gm_nAimPitchPoseParam = LookupPoseParameter( "aim_pitch" );
-
- gm_nBodyYawPoseParam = LookupPoseParameter( "body_yaw" );
- gm_nBodyPitchPoseParam = LookupPoseParameter( "body_pitch" );
-
- gm_nTopGunAttachment = LookupAttachment( "top_eye" );
- gm_nBottomGunAttachment = LookupAttachment( "bottom_eye" );
- gm_nStaggerYawPoseParam = LookupAttachment( "stagger_yaw" );
-
- gm_nHeadCenterAttachment = LookupAttachment( "head_center" );
- gm_nHeadBottomAttachment = LookupAttachment( "head_radius_measure" );
-
- // Measure the radius of the head.
- Vector vecHeadCenter;
- Vector vecHeadBottom;
- GetAttachment( gm_nHeadCenterAttachment, vecHeadCenter );
- GetAttachment( gm_nHeadBottomAttachment, vecHeadBottom );
- gm_flHeadRadius = ( vecHeadCenter - vecHeadBottom ).Length();
-
- int nSequence = SelectWeightedSequence( ACT_HUNTER_RANGE_ATTACK2_UNPLANTED );
- gm_nUnplantedNode = GetEntryNode( nSequence );
-
- nSequence = SelectWeightedSequence( ACT_RANGE_ATTACK2 );
- gm_nPlantedNode = GetEntryNode( nSequence );
-
- CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES );
-}
-
-
-//-----------------------------------------------------------------------------
-// Shuts down looping sounds when we are killed in combat or deleted.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::StopLoopingSounds()
-{
- BaseClass::StopLoopingSounds();
-
- //if ( m_pGunFiringSound )
- //{
- // CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- // controller.SoundDestroy( m_pGunFiringSound );
- // m_pGunFiringSound = NULL;
- //}
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::OnRestore()
-{
- BaseClass::OnRestore();
- SetupGlobalModelData();
- CreateVPhysics();
-
- if ( IsBleeding() )
- {
- StartBleeding();
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::IdleSound()
-{
- if ( HasCondition( COND_LOST_ENEMY ) )
- {
- EmitSound( "NPC_Hunter.Scan" );
- }
- else
- {
- EmitSound( "NPC_Hunter.Idle" );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ShouldPlayIdleSound()
-{
- if ( random->RandomInt(0, 99) == 0 && !HasSpawnFlags( SF_NPC_GAG ) )
- return true;
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-// Stay facing our enemy when close enough.
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
-{
- if ( GetActivity() == ACT_TRANSITION )
- {
- // No turning while in transitions.
- return true;
- }
-
- bool bSideStepping = IsCurSchedule( SCHED_HUNTER_SIDESTEP, false );
-
- // FIXME: this will break scripted sequences that walk when they have an enemy
- if ( GetEnemy() &&
- ( bSideStepping ||
- ( ( ( GetNavigator()->GetMovementActivity() == ACT_RUN ) || ( GetNavigator()->GetMovementActivity() == ACT_WALK ) ) &&
- !IsCurSchedule( SCHED_HUNTER_TAKE_COVER_FROM_ENEMY, false ) ) ) )
- {
- Vector vecEnemyLKP = GetEnemyLKP();
-
- // Face my enemy if we're close enough
- if ( bSideStepping || UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST )
- {
- AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.2 );
- }
- }
-
- return BaseClass::OverrideMoveFacing( move, flInterval );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::PostNPCInit()
-{
- BaseClass::PostNPCInit();
-
- IPhysicsObject *pPhysObject = VPhysicsGetObject();
- Assert( pPhysObject );
- if ( pPhysObject )
- {
- pPhysObject->SetMass( 600.0 );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::Activate()
-{
- BaseClass::Activate();
-
- s_iszStriderBusterClassname = AllocPooledString( "weapon_striderbuster" );
- s_iszStriderClassname = AllocPooledString( "npc_strider" );
- s_iszMagnadeClassname = AllocPooledString( "npc_grenade_magna" );
- s_iszPhysPropClassname = AllocPooledString( "prop_physics" );
- s_iszHuntersToRunOver = AllocPooledString( "hunters_to_run_over" );
-
- // If no one has initialized the hunters to run over counter, just zero it out.
- if ( !GlobalEntity_IsInTable( s_iszHuntersToRunOver ) )
- {
- GlobalEntity_Add( s_iszHuntersToRunOver, gpGlobals->mapname, GLOBAL_ON );
- GlobalEntity_SetCounter( s_iszHuntersToRunOver, 0 );
- }
-
- CMissile::AddCustomDetonator( this, ( GetHullMaxs().AsVector2D() - GetHullMins().AsVector2D() ).Length() * 0.5, GetHullHeight() );
-
- SetupGlobalModelData();
-
- if ( gm_flMinigunDistZ == 0 )
- {
- // Have to create a virgin hunter to ensure proper pose
- CNPC_Hunter *pHunter = (CNPC_Hunter *)CreateEntityByName( "npc_hunter" );
- Assert(pHunter);
- pHunter->Spawn();
-
- pHunter->SetActivity( ACT_WALK );
- pHunter->InvalidateBoneCache();
-
- // Currently just using the gun for the vertical component!
- Vector defEyePos;
- pHunter->GetAttachment( "minigunbase", defEyePos );
- gm_flMinigunDistZ = defEyePos.z - pHunter->GetAbsOrigin().z;
-
- Vector position;
- pHunter->GetAttachment( gm_nTopGunAttachment, position );
- VectorITransform( position, pHunter->EntityToWorldTransform(), gm_vecLocalRelativePositionMinigun );
- UTIL_Remove( pHunter );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::UpdateOnRemove()
-{
- CMissile::RemoveCustomDetonator( this );
- BaseClass::UpdateOnRemove();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Class_T CNPC_Hunter::Classify()
-{
- return CLASS_COMBINE_HUNTER;
-}
-
-//-----------------------------------------------------------------------------
-// Compensate for the hunter's long legs by moving the bodytarget up to his head.
-//-----------------------------------------------------------------------------
-Vector CNPC_Hunter::BodyTarget( const Vector &posSrc, bool bNoisy /*= true*/ )
-{
- Vector vecResult;
- QAngle vecAngle;
- GetAttachment( gm_nHeadCenterAttachment, vecResult, vecAngle );
-
- if ( bNoisy )
- {
- float rand1 = random->RandomFloat( 0, gm_flHeadRadius ) + random->RandomFloat( 0, gm_flHeadRadius );
- return vecResult + RandomVector( -rand1, rand1 );
- }
-
- return vecResult;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::DrawDebugTextOverlays()
-{
- int text_offset = BaseClass::DrawDebugTextOverlays();
-
- if (m_debugOverlays & OVERLAY_TEXT_BIT)
- {
- EntityText( text_offset, CFmtStr("%s", m_bPlanted ? "Planted" : "Unplanted" ), 0 );
- text_offset++;
-
- EntityText( text_offset, CFmtStr("Eye state: %d", m_eEyeState ), 0 );
- text_offset++;
-
- if( IsUsingSiegeTargets() )
- {
- EntityText( text_offset, CFmtStr("Next Siege Attempt:%f", m_flTimeNextSiegeTargetAttack - gpGlobals->curtime ), 0 );
- text_offset++;
- }
- }
-
- return text_offset;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::LockBothEyes( float flDuration )
-{
- m_eEyeState = HUNTER_EYE_STATE_BOTH_LOCKED;
- m_EyeSwitchTimer.Set( flDuration );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::UnlockBothEyes( float flDuration )
-{
- m_eEyeState = HUNTER_EYE_STATE_BOTH_UNLOCKED;
- m_EyeSwitchTimer.Set( flDuration );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::OnChangeActivity( Activity eNewActivity )
-{
- m_EyeSwitchTimer.Force();
-
- BaseClass::OnChangeActivity( eNewActivity );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::UpdateEyes()
-{
- // If the eyes are controlled by a script, do nothing.
- if ( GetState() == NPC_STATE_SCRIPT )
- return;
-
- if ( m_EyeSwitchTimer.Expired() )
- {
- RemoveActorFromScriptedScenes( this, false );
-
- if ( GetActivity() == ACT_IDLE )
- {
- // Idles have eye motion baked in.
- m_eEyeState = HUNTER_EYE_STATE_BOTH_LOCKED;
- }
- else if ( GetEnemy() == NULL )
- {
- m_eEyeState = HUNTER_EYE_STATE_BOTH_UNLOCKED;
- }
- else if ( m_eEyeState == HUNTER_EYE_STATE_BOTH_LOCKED )
- {
- if ( random->RandomInt( 0, 1 ) == 0 )
- {
- m_eEyeState = HUNTER_EYE_STATE_TOP_LOCKED;
- }
- else
- {
- m_eEyeState = HUNTER_EYE_STATE_BOTTOM_LOCKED;
- }
- }
- else if ( m_eEyeState == HUNTER_EYE_STATE_TOP_LOCKED )
- {
- m_eEyeState = HUNTER_EYE_STATE_BOTTOM_LOCKED;
- }
- else if ( m_eEyeState == HUNTER_EYE_STATE_BOTTOM_LOCKED )
- {
- m_eEyeState = HUNTER_EYE_STATE_TOP_LOCKED;
- }
-
- if ( ( m_eEyeState == HUNTER_EYE_STATE_BOTTOM_LOCKED ) || ( m_eEyeState == HUNTER_EYE_STATE_BOTH_UNLOCKED ) )
- {
- SetExpression( "scenes/npc/hunter/hunter_eyedarts_top.vcd" );
- }
-
- if ( ( m_eEyeState == HUNTER_EYE_STATE_TOP_LOCKED ) || ( m_eEyeState == HUNTER_EYE_STATE_BOTH_UNLOCKED ) )
- {
- SetExpression( "scenes/npc/hunter/hunter_eyedarts_bottom.vcd" );
- }
-
- m_EyeSwitchTimer.Set( random->RandomFloat( 1.0f, 3.0f ) );
- }
-
- /*Vector vecEyePos;
- Vector vecEyeDir;
-
- GetAttachment( gm_nTopGunAttachment, vecEyePos, &vecEyeDir );
- NDebugOverlay::Line( vecEyePos, vecEyePos + vecEyeDir * 36, 255, 0, 0, 0, 0.1 );
-
- GetAttachment( gm_nBottomGunAttachment, vecEyePos, &vecEyeDir );
- NDebugOverlay::Line( vecEyePos, vecEyePos + vecEyeDir * 36, 255, 0, 0, 0, 0.1 );*/
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::NPCThink()
-{
- BaseClass::NPCThink();
-
- // Update our planted/unplanted state.
- m_bPlanted = ( GetEntryNode( GetSequence() ) == gm_nPlantedNode );
-
- UpdateAim();
- UpdateEyes();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::PrescheduleThink()
-{
- BaseClass::PrescheduleThink();
-
- if ( m_BeginFollowDelay.Expired() )
- {
- FollowStrider( STRING( m_iszFollowTarget ) );
- m_BeginFollowDelay.Stop();
- }
-
- m_EscortBehavior.CheckBreakEscort();
-
- // If we're being blinded by the flashlight, see if we should stop
- if ( m_bFlashlightInEyes )
- {
- if ( m_flPupilDilateTime < gpGlobals->curtime )
- {
- CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
- if ( ( pPlayer && !pPlayer->IsIlluminatedByFlashlight( this, NULL ) ) || !PlayerFlashlightOnMyEyes( pPlayer ) )
- {
- //Msg( "NOT SHINING FLASHLIGHT ON ME\n" );
-
- // Remove the actor from the flashlight scene
- RemoveActorFromScriptedScenes( this, true, false, "scenes/npc/hunter/hunter_eyeclose.vcd" );
- m_bFlashlightInEyes = false;
- }
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::GatherChargeConditions()
-{
- ClearCondition( COND_HUNTER_CAN_CHARGE_ENEMY );
-
- if ( !hunter_charge.GetBool() )
- return;
-
- if ( !GetEnemy() )
- return;
-
- if ( GetHintGroup() != NULL_STRING )
- return;
-
- if ( !HasCondition( COND_SEE_ENEMY ) )
- return;
-
- if ( !hunter_charge_test.GetBool() && gpGlobals->curtime < m_flNextChargeTime )
- return;
-
- // No charging Alyx or Barney
- if( GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
- return;
-
- if ( m_EscortBehavior.GetEscortTarget() && GetEnemy()->MyCombatCharacterPointer() && !GetEnemy()->MyCombatCharacterPointer()->FInViewCone( this ) )
- return;
-
- if ( ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), true, false ) )
- {
- SetCondition( COND_HUNTER_CAN_CHARGE_ENEMY );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::GatherConditions()
-{
- GatherIndoorOutdoorConditions();
- GatherChargeConditions();
-
- BaseClass::GatherConditions();
-
- // Enemy LKP that doesn't get updated by the free knowledge code.
- // Used for shooting at where our enemy was when we can't see them.
- ClearCondition( COND_HUNTER_INCOMING_VEHICLE );
- if ( m_IgnoreVehicleTimer.Expired() && GetEnemy() && HasCondition( COND_SEE_ENEMY ) )
- {
- CBaseEntity *pVehicle = GetEnemyVehicle();
- if ( ( pVehicle ) && ( GlobalEntity_GetCounter( s_iszHuntersToRunOver ) <= 0 ) )
- {
- static float timeDrawnArrow;
-
- // Extrapolate the position of the vehicle and see if it's heading toward us.
- float predictTime = hunter_dodge_warning.GetFloat();
- Vector2D vecFuturePos = pVehicle->GetAbsOrigin().AsVector2D() + pVehicle->GetSmoothedVelocity().AsVector2D() * predictTime;
- if ( pVehicle->GetSmoothedVelocity().LengthSqr() > Square( 200 ) )
- {
- float t = 0;
- Vector2D vDirMovement = pVehicle->GetSmoothedVelocity().AsVector2D();
- if ( hunter_dodge_debug.GetBool() )
- {
- NDebugOverlay::Line( pVehicle->GetAbsOrigin(), pVehicle->GetAbsOrigin() + pVehicle->GetSmoothedVelocity(), 255, 255, 255, true, .1 );
- }
- vDirMovement.NormalizeInPlace();
- Vector2D vDirToHunter = GetAbsOrigin().AsVector2D() - pVehicle->GetAbsOrigin().AsVector2D();
- vDirToHunter.NormalizeInPlace();
- if ( DotProduct2D( vDirMovement, vDirToHunter ) > hunter_dodge_warning_cone.GetFloat() &&
- CalcDistanceSqrToLine2D( GetAbsOrigin().AsVector2D(), pVehicle->GetAbsOrigin().AsVector2D(), vecFuturePos, &t ) < Square( hunter_dodge_warning_width.GetFloat() * .5 ) &&
- t > 0.0 && t < 1.0 )
- {
- if ( fabs( predictTime - hunter_dodge_warning.GetFloat() ) < .05 || random->RandomInt( 0, 3 ) )
- {
- SetCondition( COND_HUNTER_INCOMING_VEHICLE );
- }
- else
- {
- if ( hunter_dodge_debug. GetBool() )
- {
- Msg( "Hunter %d failing dodge (ignore)\n", entindex() );
- }
- }
-
- if ( hunter_dodge_debug. GetBool() )
- {
- NDebugOverlay::Cross3D( EyePosition(), 100, 255, 255, 255, true, .1 );
- if ( timeDrawnArrow != gpGlobals->curtime )
- {
- timeDrawnArrow = gpGlobals->curtime;
- Vector vEndpoint( vecFuturePos.x, vecFuturePos.y, UTIL_GetLocalPlayer()->WorldSpaceCenter().z - 24 );
- NDebugOverlay::HorzArrow( UTIL_GetLocalPlayer()->WorldSpaceCenter() - Vector(0, 0, 24), vEndpoint, hunter_dodge_warning_width.GetFloat(), 255, 0, 0, 64, true, .1 );
- }
- }
- }
- else if ( hunter_dodge_debug.GetBool() )
- {
- if ( t <= 0 )
- {
- NDebugOverlay::Cross3D( EyePosition(), 100, 0, 0, 255, true, .1 );
- }
- else
- {
- NDebugOverlay::Cross3D( EyePosition(), 100, 0, 255, 255, true, .1 );
- }
- }
- }
- else if ( hunter_dodge_debug.GetBool() )
- {
- NDebugOverlay::Cross3D( EyePosition(), 100, 0, 255, 0, true, .1 );
- }
- if ( hunter_dodge_debug. GetBool() )
- {
- if ( timeDrawnArrow != gpGlobals->curtime )
- {
- timeDrawnArrow = gpGlobals->curtime;
- Vector vEndpoint( vecFuturePos.x, vecFuturePos.y, UTIL_GetLocalPlayer()->WorldSpaceCenter().z - 24 );
- NDebugOverlay::HorzArrow( UTIL_GetLocalPlayer()->WorldSpaceCenter() - Vector(0, 0, 24), vEndpoint, hunter_dodge_warning_width.GetFloat(), 127, 127, 127, 64, true, .1 );
- }
- }
-
- }
-
- m_vecEnemyLastSeen = GetEnemy()->GetAbsOrigin();
- }
-
- if( !HasCondition(COND_ENEMY_OCCLUDED) )
- {
- // m_flTimeSawEnemyAgain always tells us what time I first saw this
- // enemy again after some period of not seeing them. This is used to
- // compute how long the enemy has been visible to me THIS TIME.
- // Every time I lose sight of the enemy this time is set invalid until
- // I see the enemy again and record that time.
- if( m_flTimeSawEnemyAgain == HUNTER_SEE_ENEMY_TIME_INVALID )
- {
- m_flTimeSawEnemyAgain = gpGlobals->curtime;
- }
- }
- else
- {
- m_flTimeSawEnemyAgain = HUNTER_SEE_ENEMY_TIME_INVALID;
- }
-
- ManageSiegeTargets();
-}
-
-//-----------------------------------------------------------------------------
-// Search all entities in the map
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::CollectSiegeTargets()
-{
- CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_iszSiegeTargetName );
-
- while( pTarget != NULL )
- {
- if( pTarget->Classify() == CLASS_BULLSEYE )
- {
- m_pSiegeTargets.AddToTail( pTarget );
- }
-
- pTarget = gEntList.FindEntityByName( pTarget, m_iszSiegeTargetName );
- };
-
- if( m_pSiegeTargets.Count() < 1 )
- {
- m_iszSiegeTargetName = NULL_STRING; // And stop trying!
- }
-}
-
-//-----------------------------------------------------------------------------
-// For use when Hunters are outside and the player is inside a structure
-// Create a temporary bullseye in a location that makes it seem like
-// I am aware of the location of a player I cannot see. (Then fire at
-// at this bullseye, thus laying 'siege' to the part of the building he
-// is in.) The locations are copied from suitable info_target entities.
-// (these should be placed in exterior windows and doorways so that
-// the Hunter fires into the building through these apertures)
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::ManageSiegeTargets()
-{
- if( gpGlobals->curtime < m_flTimeNextSiegeTargetAttack )
- return;
-
- if( m_pSiegeTargets.Count() == 0 )
- {
- // If my list of siege targets is empty, go and cache all of them now
- // so that I don't have to search the world every time.
- CollectSiegeTargets();
-
- if( m_pSiegeTargets.Count() == 0 )
- return;
- }
-
- m_flTimeNextSiegeTargetAttack = gpGlobals->curtime + (hunter_siege_frequency.GetFloat() * RandomFloat( 0.8f, 1.2f) );
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
-
- // Start by assuming we are not going to create a siege target
- bool bCreateSiegeTarget = false;
- if( GetEnemy() == NULL )
- {
- // If I have no enemy at all, give it a try.
- bCreateSiegeTarget = true;
- }
-
- if( bCreateSiegeTarget )
- {
- // We've decided that the situation calls for a siege target. So, we dig through all of my siege targets and
- // take the closest one to the player that the player can see! (Obey they bullseye's FOV)
- float flClosestDistSqr = Square( 1200.0f ); // Only use siege targets within 100 feet of player
- CBaseEntity *pSiegeTargetLocation = NULL;
- int iTraces = 0;
- for( int i = 0 ; i < m_pSiegeTargets.Count() ; i++ )
- {
- CBaseEntity *pCandidate = m_pSiegeTargets[i];
- if ( pCandidate == NULL )
- continue;
-
- float flDistSqr = pCandidate->GetAbsOrigin().DistToSqr(pPlayer->GetAbsOrigin());
-
- if( flDistSqr < flClosestDistSqr )
- {
- // CollectSiegeTargets() guarantees my list is populated only with bullseye entities.
- CNPC_Bullseye *pBullseye = dynamic_cast<CNPC_Bullseye*>(pCandidate);
- if( !pBullseye->FInViewCone(this) )
- continue;
-
- if( pPlayer->FVisible(pCandidate) )
- {
- iTraces++;// Only counting these as a loose perf measurement
- flClosestDistSqr = flDistSqr;
- pSiegeTargetLocation = pCandidate;
- }
- }
- }
-
- if( pSiegeTargetLocation != NULL )
- {
- // Ditch any leftover siege target.
- KillCurrentSiegeTarget();
-
- // Create a bullseye that will live for 20 seconds. If we can't attack it within 20 seconds, it's probably
- // out of reach anyone, so have it clean itself up after that long.
- CBaseEntity *pSiegeTarget = CreateCustomTarget( pSiegeTargetLocation->GetAbsOrigin(), 20.0f );
- pSiegeTarget->SetName( MAKE_STRING("siegetarget") );
-
- m_hCurrentSiegeTarget.Set( pSiegeTarget );
-
- AddEntityRelationship( pSiegeTarget, D_HT, 1 );
- GetEnemies()->UpdateMemory( GetNavigator()->GetNetwork(), pSiegeTarget, pSiegeTarget->GetAbsOrigin(), 0.0f, true );
- AI_EnemyInfo_t *pMemory = GetEnemies()->Find( pSiegeTarget );
-
- if( pMemory )
- {
- // Pretend we've known about this target longer than we really have so that our AI doesn't waste time running ALERT schedules.
- pMemory->timeFirstSeen = gpGlobals->curtime - 5.0f;
- pMemory->timeLastSeen = gpGlobals->curtime - 1.0f;
- }
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Destroy the bullseye that we're using as a temporary target
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::KillCurrentSiegeTarget()
-{
- if ( m_hCurrentSiegeTarget )
- {
- GetEnemies()->ClearMemory( m_hCurrentSiegeTarget );
-
- UTIL_Remove( m_hCurrentSiegeTarget );
- m_hCurrentSiegeTarget.Set( NULL );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Return true if this NPC can hear the specified sound
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::QueryHearSound( CSound *pSound )
-{
- if ( pSound->SoundContext() & SOUND_CONTEXT_EXCLUDE_COMBINE )
- return false;
-
- if ( pSound->SoundContext() & SOUND_CONTEXT_PLAYER_VEHICLE )
- return false;
-
- return BaseClass::QueryHearSound( pSound );
-}
-
-
-//-----------------------------------------------------------------------------
-// This is a fairly bogus heuristic right now, but it works on 06a and 12 (sjb)
-//
-// Better options: Trace infinitely and check the material we hit for sky
-// Put some leaf info in the BSP
-// Use volumes in the levels? (yucky for designers)
-//-----------------------------------------------------------------------------
-// TODO: use this or nuke it!
-void CNPC_Hunter::GatherIndoorOutdoorConditions()
-{
- // Check indoor/outdoor before calling base class, since base class calls our
- // RangeAttackConditions() functions, and we want those functions to know
- // whether we're indoors or out.
- trace_t tr;
-
- UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 40.0f * 12.0f ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
- if( tr.fraction < 1.0f )
- {
- SetCondition( COND_HUNTER_IS_INDOORS );
- }
- else
- {
- ClearCondition( COND_HUNTER_IS_INDOORS );
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::BuildScheduleTestBits()
-{
- BaseClass::BuildScheduleTestBits();
-
- if ( m_lifeState != LIFE_ALIVE )
- {
- return;
- }
-
- // Our range attack is uninterruptable for the first few seconds.
- if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2, false ) && ( gpGlobals->curtime < m_flShootAllowInterruptTime ) )
- {
- ClearCustomInterruptConditions();
- SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
- }
- else if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2, false ) && ( GetActivity() == ACT_TRANSITION ) )
- {
- // Don't stop unplanting just because we can range attack again.
- ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
- ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
- }
- else if ( !IsInLargeOutdoorMap() && IsCurSchedule( SCHED_HUNTER_FLANK_ENEMY, false ) && GetEnemy() != NULL )
- {
- if( HasCondition(COND_CAN_RANGE_ATTACK2) && m_flTimeSawEnemyAgain != HUNTER_SEE_ENEMY_TIME_INVALID )
- {
- if( (gpGlobals->curtime - m_flTimeSawEnemyAgain) >= 2.0f )
- {
- // When we're running flank behavior, wait a moment AFTER being able to see the enemy before
- // breaking my schedule to range attack. This helps assure that the hunter gets well inside
- // the room before stopping to attack. Otherwise the Hunter may stop immediately in the doorway
- // and stop the progress of any hunters behind it.
- SetCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
- }
- }
- }
-
- // If our enemy is anything but a striderbuster, drop everything if we see one.
- if ( !IsStriderBuster( GetEnemy() ) )
- {
- SetCustomInterruptCondition( COND_HUNTER_SEE_STRIDERBUSTER );
- }
-
- // If we're not too busy, allow ourselves to ACK found enemy signals.
- if ( !GetEnemy() )
- {
- SetCustomInterruptCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY );
- }
-
- // Interrupt everything if we need to dodge.
- if ( !IsCurSchedule( SCHED_HUNTER_DODGE, false ) &&
- !IsCurSchedule( SCHED_HUNTER_STAGGER, false ) &&
- !IsCurSchedule( SCHED_ALERT_FACE_BESTSOUND, false ) )
- {
- SetCustomInterruptCondition( COND_HUNTER_INCOMING_VEHICLE );
- SetCustomInterruptCondition( COND_HEAR_PHYSICS_DANGER );
- SetCustomInterruptCondition( COND_HUNTER_FORCED_DODGE );
- }
-
- // Always interrupt on a flank command.
- SetCustomInterruptCondition( COND_HUNTER_FORCED_FLANK_ENEMY );
-
- // Always interrupt if staggered.
- SetCustomInterruptCondition( COND_HUNTER_STAGGERED );
-
- // Always interrupt if hit by a sticky bomb.
- SetCustomInterruptCondition( COND_HUNTER_HIT_BY_STICKYBOMB );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-static bool IsMovablePhysicsObject( CBaseEntity *pEntity )
-{
- return pEntity && pEntity->GetMoveType() == MOVETYPE_VPHYSICS && pEntity->VPhysicsGetObject() && pEntity->VPhysicsGetObject()->IsMoveable();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-NPC_STATE CNPC_Hunter::SelectIdealState()
-{
- switch ( m_NPCState )
- {
- case NPC_STATE_COMBAT:
- {
- if ( GetEnemy() == NULL )
- {
- if ( !HasCondition( COND_ENEMY_DEAD ) && !hunter_disable_patrol.GetBool() )
- {
- // Lost track of my enemy. Patrol.
- SetCondition( COND_HUNTER_SHOULD_PATROL );
- }
-
- return NPC_STATE_ALERT;
- }
- else if ( HasCondition( COND_ENEMY_DEAD ) )
- {
- // dvs: TODO: announce enemy kills?
- //AnnounceEnemyKill(GetEnemy());
- }
- }
-
- default:
- {
- return BaseClass::SelectIdealState();
- }
- }
-
- return GetIdealState();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel )
-{
- // Must have a target
- if ( !GetEnemy() )
- return false;
-
- // Don't check the distance once we start charging
- if ( !bCheckForCancel && !hunter_charge_test.GetBool() )
- {
- float distance = ( startPos.AsVector2D() - endPos.AsVector2D() ).LengthSqr();
-
- // Must be within our tolerance range
- if ( ( distance < Square(HUNTER_CHARGE_MIN) ) || ( distance > Square(HUNTER_CHARGE_MAX) ) )
- return false;
- }
-
- // FIXME: We'd like to exclude small physics objects from this check!
-
- // We only need to hit the endpos with the edge of our bounding box
- Vector vecDir = endPos - startPos;
- VectorNormalize( vecDir );
- float flWidth = WorldAlignSize().x * 0.5;
- Vector vecTargetPos = endPos - (vecDir * flWidth);
-
- // See if we can directly move there
- AIMoveTrace_t moveTrace;
- GetMoveProbe()->MoveLimit( NAV_GROUND, startPos, vecTargetPos, MASK_NPCSOLID_BRUSHONLY, GetEnemy(), &moveTrace );
-
- // Draw the probe
- if ( g_debug_hunter_charge.GetInt() == 1 )
- {
- Vector enemyDir = (vecTargetPos - startPos);
- float enemyDist = VectorNormalize( enemyDir );
-
- NDebugOverlay::BoxDirection( startPos, GetHullMins(), GetHullMaxs() + Vector(enemyDist,0,0), enemyDir, 0, 255, 0, 8, 1.0f );
- }
-
- // If we're not blocked, charge
- if ( IsMoveBlocked( moveTrace ) )
- {
- // Don't allow it if it's too close to us
- if ( UTIL_DistApprox( WorldSpaceCenter(), moveTrace.vEndPosition ) < HUNTER_CHARGE_MIN )
- return false;
-
- // Allow some special cases to not block us
- if ( moveTrace.pObstruction != NULL )
- {
- // If we've hit the world, see if it's a cliff
- if ( moveTrace.pObstruction == GetContainingEntity( INDEXENT(0) ) )
- {
- // Can't be too far above/below the target
- if ( fabs( moveTrace.vEndPosition.z - vecTargetPos.z ) > StepHeight() )
- return false;
-
- // Allow it if we got pretty close
- if ( UTIL_DistApprox( moveTrace.vEndPosition, vecTargetPos ) < 64 )
- return true;
- }
-
- // Hit things that will take damage
- if ( moveTrace.pObstruction->m_takedamage != DAMAGE_NO )
- return true;
-
- // Hit things that will move
- if ( moveTrace.pObstruction->GetMoveType() == MOVETYPE_VPHYSICS )
- return true;
- }
-
- return false;
- }
-
- float zDelta = endPos.z - moveTrace.vEndPosition.z;
- if ( fabsf(zDelta) > GetHullHeight() * 0.7)
- {
- return false;
- }
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *pSourceEnt)
-{
- if ( ( pSourceEnt != this ) && ( interactionType == g_interactionHunterFoundEnemy ) )
- {
- SetCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY );
- return true;
- }
-
- return BaseClass::HandleInteraction( interactionType, data, pSourceEnt );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::SelectCombatSchedule()
-{
- // If we're here with no enemy, patrol and hope we find one.
- CBaseEntity *pEnemy = GetEnemy();
- if ( pEnemy == NULL )
- {
- if ( !hunter_disable_patrol.GetBool() )
- return SCHED_HUNTER_PATROL_RUN;
- else
- return SCHED_ALERT_STAND;
- }
-
- if ( hunter_flechette_test.GetBool() )
- {
- if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
- {
- return SCHED_HUNTER_RANGE_ATTACK2;
- }
- return SCHED_COMBAT_FACE;
- }
-
- bool bStriderBuster = IsStriderBuster( pEnemy );
- if ( bStriderBuster )
- {
- if ( gpGlobals->curtime - CAI_HunterEscortBehavior::gm_flLastDefendSound > 10.0 )
- {
- EmitSound( "NPC_Hunter.DefendStrider" );
- CAI_HunterEscortBehavior::gm_flLastDefendSound = gpGlobals->curtime;
- }
-
- if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) || HasCondition( COND_NOT_FACING_ATTACK ) )
- {
- return SCHED_HUNTER_RANGE_ATTACK2;
- }
- return SCHED_ESTABLISH_LINE_OF_FIRE;
- }
-
- // Certain behaviors, like flanking and melee attacks, only make sense on visible,
- // corporeal enemies (NOT bullseyes).
- bool bIsCorporealEnemy = IsCorporealEnemy( pEnemy );
-
- // Take a quick swipe at our enemy if able to do so.
- if ( bIsCorporealEnemy && HasCondition( COND_CAN_MELEE_ATTACK1 ) )
- {
- return SCHED_HUNTER_MELEE_ATTACK1;
- }
-
- // React to newly acquired enemies.
- if ( bIsCorporealEnemy && HasCondition( COND_NEW_ENEMY ) )
- {
- AI_EnemyInfo_t *pEnemyInfo = GetEnemies()->Find( pEnemy );
-
- if ( GetSquad() && pEnemyInfo && ( pEnemyInfo->timeFirstSeen == pEnemyInfo->timeAtFirstHand ) )
- {
- GetSquad()->BroadcastInteraction( g_interactionHunterFoundEnemy, NULL, this );
-
- // First contact for my squad.
- return SCHED_HUNTER_FOUND_ENEMY;
- }
- }
-
- if ( HasCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY ) )
- {
- // A squadmate found an enemy. Respond to their call.
- return SCHED_HUNTER_FOUND_ENEMY_ACK;
- }
-
- // Fire a flechette volley. Ignore squad slots if we're attacking a striderbuster.
- // See if there is an opportunity to charge.
- if ( !bStriderBuster && bIsCorporealEnemy && HasCondition( COND_HUNTER_CAN_CHARGE_ENEMY ) )
- {
- if ( hunter_charge_test.GetBool() || random->RandomInt( 1, 100 ) < hunter_charge_pct.GetInt() )
- {
- if ( hunter_charge_test.GetBool() || OccupyStrategySlot( SQUAD_SLOT_HUNTER_CHARGE ) )
- {
- return SCHED_HUNTER_CHARGE_ENEMY;
- }
- }
- }
-
- if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
- {
- if ( bStriderBuster || CountRangedAttackers() < hunter_flechette_max_concurrent_volleys.GetInt() )
- {
- DelayRangedAttackers( hunter_flechette_volley_start_min_delay.GetFloat(), hunter_flechette_volley_start_max_delay.GetFloat(), true );
- return SCHED_HUNTER_RANGE_ATTACK2;
- }
- }
-
- if ( pEnemy->GetGroundEntity() == this )
- {
- return SCHED_HUNTER_MELEE_ATTACK1;
- }
-
- if ( HasCondition( COND_TOO_CLOSE_TO_ATTACK ) )
- {
- return SCHED_MOVE_AWAY_FROM_ENEMY;
- }
-
- // Sidestep every so often if my enemy is nearby and facing me.
-/*
- if ( gpGlobals->curtime > m_flNextSideStepTime )
- {
- if ( HasCondition( COND_ENEMY_FACING_ME ) && ( UTIL_DistApprox( GetEnemy()->GetAbsOrigin(), GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST ) )
- {
- m_flNextSideStepTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
- return SCHED_HUNTER_SIDESTEP;
- }
- }
-*/
- if ( HasCondition( COND_HEAVY_DAMAGE ) && ( gpGlobals->curtime > m_flNextSideStepTime ) )
- {
- m_flNextSideStepTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
- return SCHED_HUNTER_SIDESTEP;
- }
-
- if ( !bStriderBuster && bIsCorporealEnemy )
- {
- if ( HasCondition( COND_HUNTER_CAN_CHARGE_ENEMY ) )
- {
- if ( OccupyStrategySlot( SQUAD_SLOT_HUNTER_CHARGE ) )
- {
- return SCHED_HUNTER_CHARGE_ENEMY;
- }
-/*
- else
- {
- return SCHED_HUNTER_SIDESTEP;
- }
-*/
- }
-
- // Try to be a flanker.
- if ( ( NumHuntersInMySquad() > 1 ) && OccupyStrategySlotRange( SQUAD_SLOT_HUNTER_FLANK_FIRST, SQUAD_SLOT_HUNTER_FLANK_LAST ) )
- {
- return SCHED_HUNTER_FLANK_ENEMY;
- }
- }
-
- // Can't see my enemy.
- if ( HasCondition( COND_ENEMY_OCCLUDED ) || HasCondition( COND_ENEMY_TOO_FAR ) || HasCondition( COND_TOO_FAR_TO_ATTACK ) || HasCondition( COND_NOT_FACING_ATTACK ) )
- {
- return SCHED_HUNTER_CHASE_ENEMY;
- }
-
- if ( HasCondition( COND_HUNTER_CANT_PLANT ) )
- {
- return SCHED_ESTABLISH_LINE_OF_FIRE;
- }
-
- //if ( HasCondition( COND_ENEMY_OCCLUDED ) && IsCurSchedule( SCHED_RANGE_ATTACK1, false ) )
- //{
- // return SCHED_HUNTER_COMBAT_FACE;
- //}
-
- return SCHED_HUNTER_CHANGE_POSITION;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::SelectSiegeSchedule()
-{
- bool bHasEnemy = (GetEnemy() != NULL);
-
- if( bHasEnemy )
- {
- // We have an enemy, so we should be making every effort to attack it.
- if( !HasCondition(COND_SEE_ENEMY) || !HasCondition(COND_CAN_RANGE_ATTACK2) )
- return SCHED_ESTABLISH_LINE_OF_FIRE;
-
- if( HasCondition(COND_CAN_RANGE_ATTACK2) )
- return SCHED_HUNTER_RANGE_ATTACK2;
-
- return SCHED_HUNTER_SIEGE_STAND;
- }
- else
- {
- // Otherwise we are loitering in siege mode. Break line of sight with the player
- // if they expose our position.
- if( HasCondition( COND_SEE_PLAYER ) )
- return SCHED_HUNTER_CHANGE_POSITION_SIEGE;
- }
-
- return SCHED_HUNTER_SIEGE_STAND;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::SelectSchedule()
-{
- if ( hunter_stand_still.GetBool() )
- {
- m_bPlanted = false;
- return SCHED_IDLE_STAND;
- }
-
- if ( HasCondition( COND_HUNTER_FORCED_DODGE ) )
- return SCHED_HUNTER_DODGE;
-
- if ( HasCondition( COND_HUNTER_NEW_HINTGROUP ) || ( GetHintGroup() != NULL_STRING && m_CheckHintGroupTimer.Expired() ) )
- {
- CAI_Hint *pHint;
- CHintCriteria criteria;
- criteria.SetGroup( GetHintGroup() );
- criteria.SetFlag( bits_HINT_NODE_NEAREST );
-
- if ( HasCondition( COND_HUNTER_NEW_HINTGROUP ) )
- {
- ClearCondition( COND_HUNTER_NEW_HINTGROUP );
- if ( GetEnemy() )
- {
- pHint = CAI_HintManager::FindHint( NULL, GetEnemy()->GetAbsOrigin(), criteria );
- }
- else
- {
- pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
- }
-
- if ( pHint )
- {
- pHint->Lock( this );
- }
- }
- else
- {
- pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
- if ( pHint )
- {
- if ( (pHint->GetAbsOrigin() - GetAbsOrigin()).Length2DSqr() < Square( 20*12 ) )
- {
- m_CheckHintGroupTimer.Set( 5 );
- pHint = NULL;
- }
- else
- {
- m_CheckHintGroupTimer.Set( 15 );
- }
- }
- }
-
- if ( pHint )
- {
- SetHintNode( pHint );
- return SCHED_HUNTER_GOTO_HINT;
- }
- }
-
- if ( HasCondition( COND_HUNTER_INCOMING_VEHICLE ) )
- {
- if ( m_RundownDelay.Expired() )
- {
- int iRundownCounter = 0;
- if ( GetSquad() )
- {
- GetSquad()->GetSquadData( HUNTER_RUNDOWN_SQUADDATA, &iRundownCounter );
- }
-
- if ( iRundownCounter % 2 == 0 )
- {
- for ( int i = 0; i < g_Hunters.Count(); i++ )
- {
- if ( g_Hunters[i] != this )
- {
- g_Hunters[i]->m_RundownDelay.Set( 3 );
- g_Hunters[i]->m_IgnoreVehicleTimer.Force();
- }
- }
- m_IgnoreVehicleTimer.Set( hunter_dodge_warning.GetFloat() * 4 );
- if ( hunter_dodge_debug.GetBool() )
- {
- Msg( "Hunter %d rundown\n", entindex() );
- }
-
- if ( HasCondition( COND_SEE_ENEMY ) )
- {
- if ( m_bPlanted && HasCondition( COND_CAN_RANGE_ATTACK2 ) )
- {
- return SCHED_HUNTER_RANGE_ATTACK2;
- }
- else if ( random->RandomInt( 0, 1 ) )
- {
- return SCHED_HUNTER_CHARGE_ENEMY;
- }
- else
- {
- return SCHED_MOVE_AWAY;
- }
- }
- else
- {
- SetTarget( UTIL_GetLocalPlayer() );
- return SCHED_TARGET_FACE;
- }
- }
- else
- {
- if ( hunter_dodge_debug.GetBool() )
- {
- Msg( "Hunter %d safe from rundown\n", entindex() );
- }
- for ( int i = 0; i < g_Hunters.Count(); i++ )
- {
- g_Hunters[i]->m_RundownDelay.Set( 4 );
- g_Hunters[i]->m_IgnoreVehicleTimer.Force();
- }
- if ( GetSquad() )
- {
- GetSquad()->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, iRundownCounter + 1 );
- }
- }
- }
-
- if ( HasCondition( COND_SEE_ENEMY ) )
- {
- if ( hunter_dodge_debug.GetBool() )
- {
- Msg( "Hunter %d try dodge\n", entindex() );
- }
- return SCHED_HUNTER_DODGE;
- }
- else
- {
- SetTarget( UTIL_GetLocalPlayer() );
- return SCHED_TARGET_FACE;
- }
-
- CSound *pBestSound = GetBestSound( SOUND_PHYSICS_DANGER );
- if ( pBestSound && ( pBestSound->SoundContext() & SOUND_CONTEXT_PLAYER_VEHICLE ) )
- {
- return SCHED_ALERT_FACE_BESTSOUND;
- }
- }
-
- if ( HasCondition( COND_HUNTER_FORCED_FLANK_ENEMY ) )
- {
- return SCHED_HUNTER_FLANK_ENEMY;
- }
-
- if ( HasCondition( COND_HUNTER_STAGGERED ) /*|| HasCondition( COND_HUNTER_HIT_BY_STICKYBOMB )*/ )
- {
- return SCHED_HUNTER_STAGGER;
- }
-
- // Now that we're past all of the forced reactions to things, if we're running the siege
- // behavior, go pick an appropriate siege schedule UNLESS we have an enemy. If we have
- // an enemy, we should focus on attacking that enemy.
- if( IsUsingSiegeTargets() )
- {
- return SelectSiegeSchedule();
- }
-
- // back away if there's a magnade glued to my head.
- if ( hunter_retreat_striderbusters.GetBool() /*&& GetEnemy() && ( GetEnemy()->IsPlayer() )*/
- && (m_hAttachedBusters.Count() > 0)
- && m_fCorneredTimer < gpGlobals->curtime)
- {
- return SCHED_HUNTER_TAKE_COVER_FROM_ENEMY;
- }
-
- if ( !BehaviorSelectSchedule() )
- {
- switch ( GetState() )
- {
- case NPC_STATE_IDLE:
- {
- return SCHED_HUNTER_PATROL;
- }
-
- case NPC_STATE_ALERT:
- {
- if ( HasCondition( COND_HUNTER_SHOULD_PATROL ) )
- return SCHED_HUNTER_PATROL;
-
- break;
- }
-
- case NPC_STATE_COMBAT:
- {
- return SelectCombatSchedule();
- }
- }
- }
-
- return BaseClass::SelectSchedule();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::TranslateSchedule( int scheduleType )
-{
- switch ( scheduleType )
- {
- case SCHED_RANGE_ATTACK1:
- {
- return SCHED_HUNTER_RANGE_ATTACK1;
- }
-
- case SCHED_RANGE_ATTACK2:
- case SCHED_HUNTER_RANGE_ATTACK2:
- {
- if ( scheduleType == SCHED_RANGE_ATTACK2 )
- {
- Msg( "HUNTER IGNORING SQUAD SLOTS\n" );
- }
-
- if ( IsStriderBuster( GetEnemy() ) )
- {
- // Attack as FAST as possible. The point is to shoot down the buster.
- return SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER;
- }
-
- return SCHED_HUNTER_RANGE_ATTACK2;
- }
-
- case SCHED_MELEE_ATTACK1:
- {
- return SCHED_HUNTER_MELEE_ATTACK1;
- }
-
- case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK:
- {
- return SCHED_HUNTER_CHANGE_POSITION;
- }
-
- case SCHED_ALERT_STAND:
- {
- if ( !hunter_disable_patrol.GetBool() )
- return SCHED_HUNTER_PATROL_RUN;
- break;
- }
-
- case SCHED_COMBAT_FACE:
- {
- return SCHED_HUNTER_COMBAT_FACE;
- }
-
- case SCHED_HUNTER_PATROL:
- {
- if ( hunter_disable_patrol.GetBool() )
- {
- return SCHED_IDLE_STAND;
- }
- break;
- }
- }
-
- return BaseClass::TranslateSchedule( scheduleType );
-}
-
-
-//-----------------------------------------------------------------------------
-// catch blockage while escaping magnade
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::TaskFail( AI_TaskFailureCode_t code )
-{
- if ( IsCurSchedule( SCHED_HUNTER_TAKE_COVER_FROM_ENEMY, false ) && ( code == FAIL_NO_ROUTE_BLOCKED ) )
- {
- // cornered!
- if ( m_fCorneredTimer < gpGlobals->curtime )
- {
- m_fCorneredTimer = gpGlobals->curtime + 6.0f;
- }
- }
-
- BaseClass::TaskFail( code );
-}
-
-
-//-----------------------------------------------------------------------------
-// The player is speeding toward us in a vehicle! Find a good activity for dodging.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::TaskFindDodgeActivity()
-{
- if ( GetEnemy() == NULL )
- {
- TaskFail( "No enemy to dodge" );
- return;
- }
-
- Vector vecUp;
- Vector vecRight;
- GetVectors( NULL, &vecRight, &vecUp );
-
- // TODO: find most perpendicular 8-way dodge when we get the anims
- Vector vecEnemyDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
- //Vector vecDir = CrossProduct( vecEnemyDir, vecUp );
- VectorNormalize( vecEnemyDir );
- if ( fabs( DotProduct( vecEnemyDir, vecRight ) ) > 0.7 )
- {
- TaskFail( "Can't dodge, enemy approaching perpendicularly" );
- return;
- }
-
- // Check left or right randomly first.
- bool bDodgeLeft = false;
- CBaseEntity *pVehicle = GetEnemyVehicle();
- if ( pVehicle )
- {
- Ray_t enemyRay;
- Ray_t perpendicularRay;
- enemyRay.Init( pVehicle->GetAbsOrigin(), pVehicle->GetAbsOrigin() + pVehicle->GetSmoothedVelocity() );
- Vector vPerpendicularPt = vecEnemyDir;
- vPerpendicularPt.y = -vPerpendicularPt.y;
- perpendicularRay.Init( GetAbsOrigin(), GetAbsOrigin() + vPerpendicularPt );
-
- enemyRay.m_Start.z = enemyRay.m_Delta.z = enemyRay.m_StartOffset.z;
- perpendicularRay.m_Start.z = perpendicularRay.m_Delta.z = perpendicularRay.m_StartOffset.z;
-
- float t, s;
-
- IntersectRayWithRay( perpendicularRay, enemyRay, t, s );
-
- if ( t > 0 )
- {
- bDodgeLeft = true;
- }
- }
- else if ( random->RandomInt( 0, 1 ) == 0 )
- {
- bDodgeLeft = true;
- }
-
- bool bFoundDir = false;
- int nTries = 0;
-
- while ( !bFoundDir && ( nTries < 2 ) )
- {
- // Pick a dodge activity to try.
- if ( bDodgeLeft )
- {
- m_eDodgeActivity = ACT_HUNTER_DODGEL;
- }
- else
- {
- m_eDodgeActivity = ACT_HUNTER_DODGER;
- }
-
- // See where the dodge will put us.
- Vector vecLocalDelta;
- int nSeq = SelectWeightedSequence( m_eDodgeActivity );
- GetSequenceLinearMotion( nSeq, &vecLocalDelta );
-
- // Transform the sequence delta into local space.
- matrix3x4_t fRotateMatrix;
- AngleMatrix( GetLocalAngles(), fRotateMatrix );
- Vector vecDelta;
- VectorRotate( vecLocalDelta, fRotateMatrix, vecDelta );
-
- // Trace a bit high so this works better on uneven terrain.
- Vector testHullMins = GetHullMins();
- testHullMins.z += ( StepHeight() * 2 );
-
- // See if all is clear in that direction.
- trace_t tr;
- HunterTraceHull_SkipPhysics( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, GetCollisionGroup(), &tr, VPhysicsGetObject()->GetMass() * 0.5f );
-
- // TODO: dodge anyway if we'll make it a certain percentage of the way through the dodge?
- if ( tr.fraction == 1.0f )
- {
- //NDebugOverlay::SweptBox( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), QAngle( 0, 0, 0 ), 0, 255, 0, 128, 5 );
- bFoundDir = true;
- TaskComplete();
- }
- else
- {
- //NDebugOverlay::SweptBox( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), QAngle( 0, 0, 0 ), 255, 0, 0, 128, 5 );
- nTries++;
- bDodgeLeft = !bDodgeLeft;
- }
- }
-
- if ( nTries < 2 )
- {
- TaskComplete();
- }
- else
- {
- TaskFail( "Couldn't find dodge position\n" );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::StartTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_HUNTER_FINISH_RANGE_ATTACK:
- {
- if( GetEnemy() != NULL && GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
- {
- // Just finished shooting at Alyx! So forget her for a little while and get back on the player
- // !!!LATER - make sure there's someone else in enemy memory to go bother.
- GetEnemies()->SetTimeValidEnemy( GetEnemy(), gpGlobals->curtime + 10.0f );
- }
-
- if( m_hCurrentSiegeTarget )
- {
- // We probably just fired at our siege target, so dump it.
- KillCurrentSiegeTarget();
- }
-
- TaskComplete();
- }
-
- case TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY:
- {
- ChainStartTask( TASK_WAIT_FOR_MOVEMENT, pTask->flTaskData );
- break;
- }
-
- case TASK_HUNTER_BEGIN_FLANK:
- {
- if ( IsInSquad() && GetSquad()->NumMembers() > 1 )
- {
- // Flank relative to the other shooter in our squad.
- // If there's no other shooter, just flank relative to any squad member.
- AISquadIter_t iter;
- CAI_BaseNPC *pNPC = GetSquad()->GetFirstMember( &iter );
- while ( pNPC == this )
- {
- pNPC = GetSquad()->GetNextMember( &iter );
- }
-
- m_vSavePosition = pNPC->GetAbsOrigin();
- }
- else
- {
- // Flank relative to our current position.
- m_vSavePosition = GetAbsOrigin();
- }
-
- TaskComplete();
- break;
- }
-
- case TASK_HUNTER_ANNOUNCE_FLANK:
- {
- EmitSound( "NPC_Hunter.FlankAnnounce" );
- TaskComplete();
- break;
- }
-
- case TASK_HUNTER_DODGE:
- {
- if ( hunter_dodge_debug. GetBool() )
- {
- Msg( "Hunter %d dodging\n", entindex() );
- }
- SetIdealActivity( m_eDodgeActivity );
- break;
- }
-
- // Guarantee a certain delay between volleys. If we aren't already planted,
- // the plant transition animation will take care of that.
- case TASK_HUNTER_PRE_RANGE_ATTACK2:
- {
- if ( !m_bPlanted || ( GetEnemy() && IsStriderBuster( GetEnemy() ) ) )
- {
- TaskComplete();
- }
- else
- {
- SetIdealActivity( ACT_HUNTER_ANGRY );
- }
- break;
- }
-
- case TASK_HUNTER_SHOOT_COMMIT:
- {
- // We're committing to shooting. Don't allow interrupts until after we've shot a bit (see TASK_RANGE_ATTACK1).
- m_flShootAllowInterruptTime = gpGlobals->curtime + 100.0f;
- TaskComplete();
- break;
- }
-
- case TASK_RANGE_ATTACK2:
- {
- if ( GetEnemy() )
- {
- bool bIsBuster = IsStriderBuster( GetEnemy() );
- if ( bIsBuster )
- {
- AddFacingTarget( GetEnemy(), GetEnemy()->GetAbsOrigin() + GetEnemy()->GetSmoothedVelocity() * .5, 1.0, 0.8 );
- }
-
- // Start the firing sound.
- //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- //controller.SoundChangeVolume( m_pGunFiringSound, 1.0, hunter_first_flechette_delay.GetFloat() );
-
- SetIdealActivity( ACT_RANGE_ATTACK2 );
-
- // Decide how many shots to fire.
- int nShots = hunter_flechette_volley_size.GetInt();
- if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
- {
- nShots--;
- }
-
- // Decide when to fire the first shot.
- float initialDelay = hunter_first_flechette_delay.GetFloat();
- if ( bIsBuster )
- {
- initialDelay = 0; //*= 0.5;
- }
-
- BeginVolley( nShots, gpGlobals->curtime + initialDelay );
-
- // In case we need to miss on purpose, pick a direction now.
- m_bMissLeft = false;
- if ( random->RandomInt( 0, 1 ) == 0 )
- {
- m_bMissLeft = true;
- }
-
- LockBothEyes( initialDelay + ( nShots * hunter_flechette_delay.GetFloat() ) );
- }
- else
- {
- TaskFail( FAIL_NO_ENEMY );
- }
-
- break;
- }
-
- case TASK_HUNTER_STAGGER:
- {
- // Stagger in the direction the impact force would push us.
- VMatrix worldToLocalRotation = EntityToWorldTransform();
- Vector vecLocalStaggerDir = worldToLocalRotation.InverseTR().ApplyRotation( m_vecStaggerDir );
-
- float flStaggerYaw = VecToYaw( vecLocalStaggerDir );
- SetPoseParameter( gm_nStaggerYawPoseParam, flStaggerYaw );
-
- // Go straight there!
- SetActivity( ACT_RESET );
- SetActivity( ( Activity )ACT_HUNTER_STAGGER );
- break;
- }
-
- case TASK_MELEE_ATTACK1:
- {
- SetLastAttackTime( gpGlobals->curtime );
-
- if ( GetEnemy() && GetEnemy()->IsPlayer() )
- {
- ResetIdealActivity( ( Activity )ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER );
- }
- else
- {
- ResetIdealActivity( ACT_MELEE_ATTACK1 );
- }
-
- break;
- }
-
- case TASK_HUNTER_CORNERED_TIMER:
- {
- m_fCorneredTimer = gpGlobals->curtime + pTask->flTaskData;
-
- break;
- }
-
- case TASK_HUNTER_FIND_SIDESTEP_POSITION:
- {
- if ( GetEnemy() == NULL )
- {
- TaskFail( "No enemy to sidestep" );
- }
- else
- {
- Vector vecUp;
- GetVectors( NULL, NULL, &vecUp );
-
- Vector vecEnemyDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
- Vector vecDir = CrossProduct( vecEnemyDir, vecUp );
- VectorNormalize( vecDir );
-
- // Sidestep left or right randomly.
- if ( random->RandomInt( 0, 1 ) == 0 )
- {
- vecDir *= -1;
- }
-
- // Start high and then trace down so that it works on uneven terrain.
- Vector vecPos = GetAbsOrigin() + Vector( 0, 0, 64 ) + random->RandomFloat( 120, 200 ) * vecDir;
-
- // Try to find the ground at the sidestep position.
- trace_t tr;
- UTIL_TraceLine( vecPos, vecPos + Vector( 0, 0, -128 ), MASK_NPCSOLID, NULL, COLLISION_GROUP_NONE, &tr );
- if ( tr.fraction < 1.0f )
- {
- //NDebugOverlay::Line( vecPos, tr.endpos, 0, 255, 0, true, 10 );
-
- m_vSavePosition = tr.endpos;
-
- TaskComplete();
- }
- else
- {
- TaskFail( "Couldn't find sidestep position\n" );
- }
- }
-
- break;
- }
-
- case TASK_HUNTER_FIND_DODGE_POSITION:
- {
- TaskFindDodgeActivity();
- break;
- }
-
- case TASK_HUNTER_CHARGE:
- {
- SetIdealActivity( ( Activity )ACT_HUNTER_CHARGE_START );
- break;
- }
-
- case TASK_HUNTER_CHARGE_DELAY:
- {
- m_flNextChargeTime = gpGlobals->curtime + pTask->flTaskData;
- TaskComplete();
- break;
- }
-
- case TASK_DIE:
- {
- GetNavigator()->StopMoving();
- ResetActivity();
- SetIdealActivity( GetDeathActivity() );
- m_lifeState = LIFE_DYING;
-
- break;
- }
-
- //case TASK_HUNTER_END_FLANK:
- //{
- //
- //}
-
- default:
- {
- BaseClass::StartTask( pTask );
- break;
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::RunTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_HUNTER_PRE_RANGE_ATTACK2:
- {
- if ( IsActivityFinished() )
- {
- TaskComplete();
- }
- break;
- }
-
- case TASK_RANGE_ATTACK2:
- {
- if( !hunter_hate_thrown_striderbusters.GetBool() && GetEnemy() != NULL && IsStriderBuster( GetEnemy() ) )
- {
- if( !IsValidEnemy(GetEnemy()) )
- {
- TaskFail("No longer hate this StriderBuster");
- }
- }
-
- bool bIsBuster = IsStriderBuster( GetEnemy() );
- if ( bIsBuster )
- {
- Vector vFuturePosition = GetEnemy()->GetAbsOrigin() + GetEnemy()->GetSmoothedVelocity() * .3;
- AddFacingTarget( GetEnemy(), vFuturePosition, 1.0, 0.8 );
-
- Vector2D vToFuturePositon = ( vFuturePosition.AsVector2D() - GetAbsOrigin().AsVector2D() );
- vToFuturePositon.NormalizeInPlace();
- Vector2D facingDir = BodyDirection2D().AsVector2D();
-
- float flDot = DotProduct2D( vToFuturePositon, facingDir );
-
- if ( flDot < .4 )
- {
- GetMotor()->SetIdealYawToTarget( vFuturePosition );
- GetMotor()->UpdateYaw();
- break;
- }
- }
-
- if ( gpGlobals->curtime >= m_flNextFlechetteTime )
- {
- // Must have an enemy and a shot queued up.
- bool bDone = false;
- if ( GetEnemy() != NULL && m_nFlechettesQueued > 0 )
- {
- if ( ShootFlechette( GetEnemy(), false ) )
- {
- m_nClampedShots++;
- }
- else
- {
- m_nClampedShots = 0;
- }
-
- m_nFlechettesQueued--;
-
- // If we fired three or more clamped shots in a row, call it quits so we don't look dumb.
- if ( ( m_nClampedShots >= 3 ) || ( m_nFlechettesQueued == 0 ) )
- {
- bDone = true;
- }
- else
- {
- // More shooting to do. Schedule our next flechette.
- m_flNextFlechetteTime = gpGlobals->curtime + hunter_flechette_delay.GetFloat();
- }
- }
- else
- {
- bDone = true;
- }
-
- if ( bDone )
- {
- // Stop the firing sound.
- //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- //controller.SoundChangeVolume( m_pGunFiringSound, 0.0f, 0.1f );
-
- DelayRangedAttackers( hunter_flechette_volley_end_min_delay.GetFloat(), hunter_flechette_volley_end_max_delay.GetFloat(), true );
- TaskComplete();
- }
- }
-
- break;
- }
-
- case TASK_GET_PATH_TO_ENEMY_LOS:
- {
- ChainRunTask( TASK_GET_PATH_TO_ENEMY_LKP_LOS, pTask->flTaskData );
- break;
- }
-
- case TASK_HUNTER_DODGE:
- {
- AutoMovement();
-
- if ( IsActivityFinished() )
- {
- TaskComplete();
- }
- break;
- }
-
- case TASK_HUNTER_CORNERED_TIMER:
- {
- TaskComplete();
- break;
- }
-
- case TASK_HUNTER_STAGGER:
- {
- if ( IsActivityFinished() )
- {
- TaskComplete();
- }
- break;
- }
-
- case TASK_HUNTER_CHARGE:
- {
- Activity eActivity = GetActivity();
-
- // See if we're trying to stop after hitting/missing our target
- if ( eActivity == ACT_HUNTER_CHARGE_STOP || eActivity == ACT_HUNTER_CHARGE_CRASH )
- {
- if ( IsActivityFinished() )
- {
- m_flNextChargeTime = gpGlobals->curtime + hunter_charge_min_delay.GetFloat() + random->RandomFloat( 0, 2.5 ) + random->RandomFloat( 0, 2.5 );
- float delayMultiplier = ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) ? 1.5 : 1.0;
- float groupDelay = gpGlobals->curtime + ( 2.0 + random->RandomFloat( 0, 2 ) ) * delayMultiplier;
- for ( int i = 0; i < g_Hunters.Count(); i++ )
- {
- if ( g_Hunters[i] != this && g_Hunters[i]->m_flNextChargeTime < groupDelay )
- {
- g_Hunters[i]->m_flNextChargeTime = groupDelay;
- }
- }
- TaskComplete();
- return;
- }
-
- // Still in the process of slowing down. Run movement until it's done.
- AutoMovement();
- return;
- }
-
- // Check for manual transition
- if ( ( eActivity == ACT_HUNTER_CHARGE_START ) && ( IsActivityFinished() ) )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_RUN );
- }
-
- // See if we're still running
- if ( eActivity == ACT_HUNTER_CHARGE_RUN || eActivity == ACT_HUNTER_CHARGE_START )
- {
- if ( HasCondition( COND_NEW_ENEMY ) || HasCondition( COND_LOST_ENEMY ) || HasCondition( COND_ENEMY_DEAD ) )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
- return;
- }
- else
- {
- if ( GetEnemy() != NULL )
- {
- Vector goalDir = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
- VectorNormalize( goalDir );
-
- if ( DotProduct( BodyDirection2D(), goalDir ) < 0.25f )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
- }
- }
- }
- }
-
- // Steer towards our target
- float idealYaw;
- if ( GetEnemy() == NULL )
- {
- idealYaw = GetMotor()->GetIdealYaw();
- }
- else
- {
- idealYaw = CalcIdealYaw( GetEnemy()->GetAbsOrigin() );
- }
-
- // Add in our steering offset
- idealYaw += ChargeSteer();
-
- // Turn to face
- GetMotor()->SetIdealYawAndUpdate( idealYaw );
-
- // See if we're going to run into anything soon
- ChargeLookAhead();
-
- // Let our animations simply move us forward. Keep the result
- // of the movement so we know whether we've hit our target.
- AIMoveTrace_t moveTrace;
- if ( AutoMovement( GetEnemy(), &moveTrace ) == false )
- {
- // Only stop if we hit the world
- if ( HandleChargeImpact( moveTrace.vEndPosition, moveTrace.pObstruction ) )
- {
- // If we're starting up, this is an error
- if ( eActivity == ACT_HUNTER_CHARGE_START )
- {
- TaskFail( "Unable to make initial movement of charge\n" );
- return;
- }
-
- // Crash unless we're trying to stop already
- if ( eActivity != ACT_HUNTER_CHARGE_STOP )
- {
- if ( moveTrace.fStatus == AIMR_BLOCKED_WORLD && moveTrace.vHitNormal == vec3_origin )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
- }
- else
- {
- // Shake the screen
- if ( moveTrace.fStatus != AIMR_BLOCKED_NPC )
- {
- EmitSound( "NPC_Hunter.ChargeHitWorld" );
- UTIL_ScreenShake( GetAbsOrigin(), 16.0f, 4.0f, 1.0f, 400.0f, SHAKE_START );
- }
- SetIdealActivity( ACT_HUNTER_CHARGE_CRASH );
- }
- }
- }
- else if ( moveTrace.pObstruction )
- {
- // If we hit another hunter, stop
- if ( moveTrace.pObstruction->Classify() == CLASS_COMBINE_HUNTER )
- {
- // Crash unless we're trying to stop already
- if ( eActivity != ACT_HUNTER_CHARGE_STOP )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
- }
- }
- // If we hit an antlion, don't stop, but kill it
- // We never have hunters and antlions together, but you never know.
- else if (moveTrace.pObstruction->Classify() == CLASS_ANTLION )
- {
- if ( FClassnameIs( moveTrace.pObstruction, "npc_antlionguard" ) )
- {
- // Crash unless we're trying to stop already
- if ( eActivity != ACT_HUNTER_CHARGE_STOP )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
- }
- }
- else
- {
- Hunter_ApplyChargeDamage( this, moveTrace.pObstruction, moveTrace.pObstruction->GetHealth() );
- }
- }
- }
- }
-
- break;
- }
-
- case TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY:
- {
- if ( GetEnemy() )
- {
- Vector vecEnemyLKP = GetEnemyLKP();
- AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.8 );
- }
- ChainRunTask( TASK_WAIT_FOR_MOVEMENT, pTask->flTaskData );
- break;
- }
-
- default:
- {
- BaseClass::RunTask( pTask );
- break;
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Return true if our charge target is right in front of the hunter.
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::EnemyIsRightInFrontOfMe( CBaseEntity **pEntity )
-{
- if ( !GetEnemy() )
- return false;
-
- if ( (GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter()).LengthSqr() < (156*156) )
- {
- Vector vecLOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
- vecLOS.z = 0;
- VectorNormalize( vecLOS );
- Vector vBodyDir = BodyDirection2D();
- if ( DotProduct( vecLOS, vBodyDir ) > 0.8 )
- {
- // He's in front of me, and close. Make sure he's not behind a wall.
- trace_t tr;
- UTIL_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), GetHullMins() * 0.5, GetHullMaxs() * 0.5, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
- if ( tr.m_pEnt == GetEnemy() )
- {
- *pEntity = tr.m_pEnt;
- return true;
- }
- }
- }
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-// While charging, look ahead and see if we're going to run into anything.
-// If we are, start the gesture so it looks like we're anticipating the hit.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::ChargeLookAhead( void )
-{
-#if 0
- trace_t tr;
- Vector vecForward;
- GetVectors( &vecForward, NULL, NULL );
- Vector vecTestPos = GetAbsOrigin() + ( vecForward * m_flGroundSpeed * 0.75 );
- Vector testHullMins = GetHullMins();
- testHullMins.z += (StepHeight() * 2);
- HunterTraceHull_SkipPhysics( GetAbsOrigin(), vecTestPos, testHullMins, GetHullMaxs(), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5 );
-
- //NDebugOverlay::Box( tr.startpos, testHullMins, GetHullMaxs(), 0, 255, 0, true, 0.1f );
- //NDebugOverlay::Box( vecTestPos, testHullMins, GetHullMaxs(), 255, 0, 0, true, 0.1f );
-
- if ( tr.fraction != 1.0 )
- {
- // dvs: TODO:
- // Start playing the hit animation
- //AddGesture( ACT_HUNTER_CHARGE_ANTICIPATION );
- }
-#endif
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-float CNPC_Hunter::ChargeSteer()
-{
- trace_t tr;
- Vector testPos, steer, forward, right;
- QAngle angles;
- const float testLength = m_flGroundSpeed * 0.15f;
-
- //Get our facing
- GetVectors( &forward, &right, NULL );
-
- steer = forward;
-
- const float faceYaw = UTIL_VecToYaw( forward );
-
- //Offset right
- VectorAngles( forward, angles );
- angles[YAW] += 45.0f;
- AngleVectors( angles, &forward );
-
- // Probe out
- testPos = GetAbsOrigin() + ( forward * testLength );
-
- // Offset by step height
- Vector testHullMins = GetHullMins();
- testHullMins.z += (StepHeight() * 2);
-
- // Probe
- HunterTraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );
-
- // Debug info
- if ( g_debug_hunter_charge.GetInt() == 1 )
- {
- if ( tr.fraction == 1.0f )
- {
- NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
- }
- else
- {
- NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
- }
- }
-
- // Add in this component
- steer += ( right * 0.5f ) * ( 1.0f - tr.fraction );
-
- // Offset left
- angles[YAW] -= 90.0f;
- AngleVectors( angles, &forward );
-
- // Probe out
- testPos = GetAbsOrigin() + ( forward * testLength );
- HunterTraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );
-
- // Debug
- if ( g_debug_hunter_charge.GetInt() == 1 )
- {
- if ( tr.fraction == 1.0f )
- {
- NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
- }
- else
- {
- NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
- }
- }
-
- // Add in this component
- steer -= ( right * 0.5f ) * ( 1.0f - tr.fraction );
-
- // Debug
- if ( g_debug_hunter_charge.GetInt() == 1 )
- {
- NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( steer * 512.0f ), 255, 255, 0, true, 0.1f );
- NDebugOverlay::Cross3D( GetAbsOrigin() + ( steer * 512.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 255, 0, true, 0.1f );
-
- NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), 255, 0, 255, true, 0.1f );
- NDebugOverlay::Cross3D( GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 0, 255, true, 0.1f );
- }
-
- return UTIL_AngleDiff( UTIL_VecToYaw( steer ), faceYaw );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::ChargeDamage( CBaseEntity *pTarget )
-{
- if ( pTarget == NULL )
- return;
-
- CBasePlayer *pPlayer = ToBasePlayer( pTarget );
-
- if ( pPlayer != NULL )
- {
- //Kick the player angles
- pPlayer->ViewPunch( QAngle( 20, 20, -30 ) );
-
- Vector dir = pPlayer->WorldSpaceCenter() - WorldSpaceCenter();
- VectorNormalize( dir );
- dir.z = 0.0f;
-
- Vector vecNewVelocity = dir * 250.0f;
- vecNewVelocity[2] += 128.0f;
- pPlayer->SetAbsVelocity( vecNewVelocity );
-
- color32 red = {128,0,0,128};
- UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN );
- }
-
- // Player takes less damage
- float flDamage = ( pPlayer == NULL ) ? 250 : sk_hunter_dmg_charge.GetFloat();
-
- // If it's being held by the player, break that bond
- Pickup_ForcePlayerToDropThisObject( pTarget );
-
- // Calculate the physics force
- Hunter_ApplyChargeDamage( this, pTarget, flDamage );
-}
-
-
-//-----------------------------------------------------------------------------
-// Handles the hunter charging into something. Returns true if it hit the world.
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity )
-{
- // Cause a shock wave from this point which will disrupt nearby physics objects
- //ImpactShock( vecImpact, 128, 350 );
-
- // Did we hit anything interesting?
- if ( !pEntity || pEntity->IsWorld() )
- {
- // Robin: Due to some of the finicky details in the motor, the hunter will hit
- // the world when it is blocked by our enemy when trying to step up
- // during a moveprobe. To get around this, we see if the enemy's within
- // a volume in front of the hunter when we hit the world, and if he is,
- // we hit him anyway.
- EnemyIsRightInFrontOfMe( &pEntity );
-
- // Did we manage to find him? If not, increment our charge miss count and abort.
- if ( pEntity->IsWorld() )
- {
- return true;
- }
- }
-
- // Hit anything we don't like
- if ( IRelationType( pEntity ) == D_HT && ( GetNextAttack() < gpGlobals->curtime ) )
- {
- EmitSound( "NPC_Hunter.ChargeHitEnemy" );
-
- // dvs: TODO:
- //if ( !IsPlayingGesture( ACT_HUNTER_CHARGE_HIT ) )
- //{
- // RestartGesture( ACT_HUNTER_CHARGE_HIT );
- //}
-
- ChargeDamage( pEntity );
-
- if ( !pEntity->IsNPC() )
- {
- pEntity->ApplyAbsVelocityImpulse( ( BodyDirection2D() * 400 ) + Vector( 0, 0, 200 ) );
- }
-
- if ( !pEntity->IsAlive() && GetEnemy() == pEntity )
- {
- SetEnemy( NULL );
- }
-
- SetNextAttack( gpGlobals->curtime + 2.0f );
-
- if ( !pEntity->IsAlive() || !pEntity->IsNPC() )
- {
- SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
- return false;
- }
- else
- return true;
-
- }
-
- // Hit something we don't hate. If it's not moveable, crash into it.
- if ( pEntity->GetMoveType() == MOVETYPE_NONE || pEntity->GetMoveType() == MOVETYPE_PUSH )
- {
- CBreakable *pBreakable = dynamic_cast<CBreakable *>(pEntity);
- if ( pBreakable && pBreakable->IsBreakable() && pBreakable->m_takedamage == DAMAGE_YES && pBreakable->GetHealth() > 0 )
- {
- ChargeDamage( pEntity );
- }
- return true;
- }
-
- // If it's a vphysics object that's too heavy, crash into it too.
- if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
- {
- IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
- if ( pPhysics )
- {
- // If the object is being held by the player, knock it out of his hands
- if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
- {
- Pickup_ForcePlayerToDropThisObject( pEntity );
- return false;
- }
-
- if ( !pPhysics->IsMoveable() )
- return true;
-
- float entMass = PhysGetEntityMass( pEntity ) ;
- float minMass = VPhysicsGetObject()->GetMass() * 0.5f;
- if ( entMass < minMass )
- {
- if ( entMass < minMass * 0.666f || pEntity->CollisionProp()->BoundingRadius() < GetHullHeight() )
- {
- if ( pEntity->GetHealth() > 0 )
- {
- CBreakableProp *pBreakable = dynamic_cast<CBreakableProp *>(pEntity);
- if ( pBreakable && pBreakable->m_takedamage == DAMAGE_YES && pBreakable->GetHealth() > 0 && pBreakable->GetHealth() <= 50 )
- {
- ChargeDamage( pEntity );
- }
- }
- pEntity->SetNavIgnore( 2.0 );
- return false;
- }
- }
- return true;
-
- }
- }
-
- return false;
-}
-
-
-//-------------------------------------------------------------------------------------------------
-//-------------------------------------------------------------------------------------------------
-void CNPC_Hunter::Explode()
-{
- Vector velocity = vec3_origin;
- AngularImpulse angVelocity = RandomAngularImpulse( -150, 150 );
-
- PropBreakableCreateAll( GetModelIndex(), NULL, EyePosition(), GetAbsAngles(), velocity, angVelocity, 1.0, 150, COLLISION_GROUP_NPC, this );
-
- ExplosionCreate( EyePosition(), GetAbsAngles(), this, 500, 256, (SF_ENVEXPLOSION_NOPARTICLES|SF_ENVEXPLOSION_NOSPARKS|SF_ENVEXPLOSION_NODLIGHTS|SF_ENVEXPLOSION_NODAMAGE|SF_ENVEXPLOSION_NOSMOKE), false );
-
- // Create liquid fountain gushtacular effect here!
- CEffectData data;
-
- data.m_vOrigin = EyePosition();
- data.m_vNormal = Vector( 0, 0, 1 );
- data.m_flScale = 4.0f;
-
- DispatchEffect( "StriderBlood", data );
-
- // Go away
- m_lifeState = LIFE_DEAD;
-
- SetThink( &CNPC_Hunter::SUB_Remove );
- SetNextThink( gpGlobals->curtime + 0.1f );
-
- AddEffects( EF_NODRAW );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Activity CNPC_Hunter::NPC_TranslateActivity( Activity baseAct )
-{
- if ( ( baseAct == ACT_WALK ) || ( baseAct == ACT_RUN ) )
- {
- if ( GetEnemy() )
- {
- Vector vecEnemyLKP = GetEnemyLKP();
-
- // Only start facing when we're close enough
- if ( UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST )
- {
- return (Activity)ACT_HUNTER_WALK_ANGRY;
- }
- }
- }
- else if ( ( baseAct == ACT_IDLE ) && m_bPlanted )
- {
- return ( Activity )ACT_HUNTER_IDLE_PLANTED;
- }
- else if ( baseAct == ACT_RANGE_ATTACK2 )
- {
- if ( !m_bPlanted && ( m_bEnableUnplantedShooting || IsStriderBuster( GetEnemy() ) ) )
- {
- return (Activity)ACT_HUNTER_RANGE_ATTACK2_UNPLANTED;
- }
- }
-
- return BaseClass::NPC_TranslateActivity( baseAct );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::HandleAnimEvent( animevent_t *pEvent )
-{
- Vector footPosition;
- QAngle angles;
-
- if ( pEvent->event == AE_HUNTER_FOOTSTEP_LEFT )
- {
- LeftFootHit( pEvent->eventtime );
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_FOOTSTEP_RIGHT )
- {
- RightFootHit( pEvent->eventtime );
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_FOOTSTEP_BACK )
- {
- BackFootHit( pEvent->eventtime );
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_START_EXPRESSION )
- {
- if ( pEvent->options && Q_strlen( pEvent->options ) )
- {
- //m_iszCurrentExpression = AllocPooledString( pEvent->options );
- //SetExpression( pEvent->options );
- }
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_END_EXPRESSION )
- {
- if ( pEvent->options && Q_strlen( pEvent->options ) )
- {
- //m_iszCurrentExpression = NULL_STRING;
- //RemoveActorFromScriptedScenes( this, true, false, pEvent->options );
- }
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_MELEE_ANNOUNCE )
- {
- EmitSound( "NPC_Hunter.MeleeAnnounce" );
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_MELEE_ATTACK_LEFT )
- {
- Vector right, forward, dir;
- AngleVectors( GetLocalAngles(), &forward, &right, NULL );
-
- right = right * -100;
- forward = forward * 600;
- dir = right + forward;
- QAngle angle( 25, 30, -20 );
-
- MeleeAttack( HUNTER_MELEE_REACH, sk_hunter_dmg_one_slash.GetFloat(), angle, dir, HUNTER_BLOOD_LEFT_FOOT );
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_MELEE_ATTACK_RIGHT )
- {
- Vector right, forward,dir;
- AngleVectors( GetLocalAngles(), &forward, &right, NULL );
-
- right = right * 100;
- forward = forward * 600;
- dir = right + forward;
-
- QAngle angle( 25, -30, 20 );
-
- MeleeAttack( HUNTER_MELEE_REACH, sk_hunter_dmg_one_slash.GetFloat(), angle, dir, HUNTER_BLOOD_LEFT_FOOT );
- return;
- }
-
- if ( pEvent->event == AE_HUNTER_SPRAY_BLOOD )
- {
- Vector vecOrigin;
- Vector vecDir;
-
- // spray blood from the attachment point
- bool bGotAttachment = false;
- if ( pEvent->options )
- {
- QAngle angDir;
- if ( GetAttachment( pEvent->options, vecOrigin, angDir ) )
- {
- bGotAttachment = true;
- AngleVectors( angDir, &vecDir, NULL, NULL );
- }
- }
-
- // fall back to our center, tracing forward
- if ( !bGotAttachment )
- {
- vecOrigin = WorldSpaceCenter();
- GetVectors( &vecDir, NULL, NULL );
- }
-
- UTIL_BloodSpray( vecOrigin, vecDir, BLOOD_COLOR_RED, 4, FX_BLOODSPRAY_ALL );
-
- for ( int i = 0 ; i < 3 ; i++ )
- {
- Vector vecTraceDir = vecDir;
- vecTraceDir.x += random->RandomFloat( -0.1, 0.1 );
- vecTraceDir.y += random->RandomFloat( -0.1, 0.1 );
- vecTraceDir.z += random->RandomFloat( -0.1, 0.1 );
-
- trace_t tr;
- AI_TraceLine( vecOrigin, vecOrigin + ( vecTraceDir * 192.0f ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
- if ( tr.fraction != 1.0 )
- {
- UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED );
- }
- }
-
- return;
- }
-
- BaseClass::HandleAnimEvent( pEvent );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority )
-{
- if ( nDisposition == D_HT && pEntity->ClassMatches("npc_bullseye") )
- UpdateEnemyMemory( pEntity, pEntity->GetAbsOrigin() );
- BaseClass::AddEntityRelationship( pEntity, nDisposition, nPriority );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity )
-{
- if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK1, false ) )
- {
- SetGoalEnt( pGoalEntity );
- return true;
- }
- return BaseClass::ScheduledMoveToGoalEntity( scheduleType, pGoalEntity, movementActivity );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::OnChangeHintGroup( string_t oldGroup, string_t newGroup )
-{
- SetCondition( COND_HUNTER_NEW_HINTGROUP );
- m_CheckHintGroupTimer.Set( 10 );
-}
-
-
-//-----------------------------------------------------------------------------
-// Tells whether any given hunter is in a squad that contains other hunters.
-// This is useful for preventing timid behavior for Hunters that are not
-// supported by other hunters.
-//
-// NOTE: This counts the self! So a hunter that is alone in his squad
-// receives a result of 1.
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::NumHuntersInMySquad()
-{
- AISquadIter_t iter;
- CAI_BaseNPC *pSquadmate = m_pSquad ? m_pSquad->GetFirstMember( &iter ) : NULL;
-
- if( !pSquadmate )
- {
- // Not in a squad at all, but the caller is not concerned with that. Just
- // tell them that we're in a squad of one (ourself)
- return 1;
- }
-
- int count = 0;
-
- while ( pSquadmate )
- {
- if( pSquadmate->m_iClassname == m_iClassname )
- count++;
-
- pSquadmate = m_pSquad->GetNextMember( &iter );
- }
-
- return count;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::FollowStrider( const char *szStrider )
-{
- if ( !szStrider )
- return;
-
- CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, szStrider, this );
- CNPC_Strider *pStrider = dynamic_cast <CNPC_Strider *>( pEnt );
- FollowStrider(pStrider);
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::FollowStrider( CNPC_Strider * pStrider )
-{
- if ( !IsAlive() )
- {
- return;
- }
-
- if ( pStrider )
- {
- if ( m_EscortBehavior.GetFollowTarget() != pStrider )
- {
- m_iszFollowTarget = pStrider->GetEntityName();
- if ( m_iszFollowTarget == NULL_STRING )
- {
- m_iszFollowTarget = AllocPooledString( "unnamed_strider" );
- }
- m_EscortBehavior.SetEscortTarget( pStrider );
- }
- }
- else
- {
- DevWarning("Hunter set to follow entity %s that is not a strider\n", STRING( m_iszFollowTarget ) );
- m_iszFollowTarget = AllocPooledString( "unknown_strider" );
- }
-}
-
-void CAI_HunterEscortBehavior::SetEscortTarget( CNPC_Strider *pStrider, bool fFinishCurSchedule )
-{
- m_bEnabled = true;
-
- if ( GetOuter()->GetSquad() )
- {
- GetOuter()->GetSquad()->RemoveFromSquad( GetOuter() );
- }
-
- for ( int i = 0; i < g_Hunters.Count(); i++ )
- {
- if ( g_Hunters[i]->m_EscortBehavior.GetFollowTarget() == pStrider )
- {
- Assert( g_Hunters[i]->GetSquad() );
- g_Hunters[i]->GetSquad()->AddToSquad( GetOuter() );
- break;
- }
- }
-
- if ( !GetOuter()->GetSquad() )
- {
- GetOuter()->AddToSquad( AllocPooledString( CFmtStr( "%s_hunter_squad", STRING( pStrider->GetEntityName() ) ) ) );
- }
-
- BaseClass::SetFollowTarget( pStrider );
- m_flTimeEscortReturn = gpGlobals->curtime;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputEnableUnplantedShooting( inputdata_t &inputdata )
-{
- m_bEnableUnplantedShooting = true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputDisableUnplantedShooting( inputdata_t &inputdata )
-{
- m_bEnableUnplantedShooting = false;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputFollowStrider( inputdata_t &inputdata )
-{
- m_iszFollowTarget = inputdata.value.StringID();
- if ( m_iszFollowTarget == s_iszStriderClassname )
- {
- m_EscortBehavior.m_bEnabled = true;
- m_iszFollowTarget = NULL_STRING;
- }
- m_BeginFollowDelay.Start( .1 ); // Allow time for strider to spawn
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputUseSiegeTargets( inputdata_t &inputdata )
-{
- m_iszSiegeTargetName = inputdata.value.StringID();
- m_flTimeNextSiegeTargetAttack = gpGlobals->curtime + random->RandomFloat( 1, hunter_siege_frequency.GetFloat() );
-
- if( m_iszSiegeTargetName == NULL_STRING )
- {
- // Turning the feature off. Restore m_flDistTooFar to default.
- m_flDistTooFar = hunter_flechette_max_range.GetFloat();
- m_pSiegeTargets.RemoveAll();
- }
- else
- {
- // We're going into siege mode. Adjust range accordingly.
- m_flDistTooFar = hunter_flechette_max_range.GetFloat() * HUNTER_SIEGE_MAX_DIST_MODIFIER;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputDodge( inputdata_t &inputdata )
-{
- SetCondition( COND_HUNTER_FORCED_DODGE );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputFlankEnemy( inputdata_t &inputdata )
-{
- SetCondition( COND_HUNTER_FORCED_FLANK_ENEMY );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputDisableShooting( inputdata_t &inputdata )
-{
- m_bDisableShooting = true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputEnableShooting( inputdata_t &inputdata )
-{
- m_bDisableShooting = false;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputEnableSquadShootDelay( inputdata_t &inputdata )
-{
- m_bEnableSquadShootDelay = true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::InputDisableSquadShootDelay( inputdata_t &inputdata )
-{
- m_bEnableSquadShootDelay = false;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
-{
- return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::IsValidEnemy( CBaseEntity *pTarget )
-{
- if ( IsStriderBuster( pTarget) )
- {
- if ( !m_EscortBehavior.m_bEnabled || !m_EscortBehavior.GetEscortTarget() )
- {
- // We only hate striderbusters when we are actively protecting a strider.
- return false;
- }
-
- if ( pTarget->VPhysicsGetObject() )
- {
- if ( ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) &&
- hunter_hate_held_striderbusters.GetBool() )
- {
- if ( gpGlobals->curtime - StriderBuster_GetPickupTime( pTarget ) > hunter_hate_held_striderbusters_delay.GetFloat())
- {
- if ( StriderBuster_NumFlechettesAttached( pTarget ) <= 2 )
- {
- if ( m_EscortBehavior.GetEscortTarget() &&
- ( m_EscortBehavior.GetEscortTarget()->GetAbsOrigin().AsVector2D() - pTarget->GetAbsOrigin().AsVector2D() ).LengthSqr() < Square( hunter_hate_held_striderbusters_tolerance.GetFloat() ) )
- {
- return true;
- }
- }
- }
- return false;
- }
-
- bool bThrown = ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_WAS_THROWN ) != 0;
- bool bAttached = StriderBuster_IsAttachedStriderBuster( pTarget );
-
- if ( ( bThrown && !bAttached ) && hunter_hate_thrown_striderbusters.GetBool() )
- {
- float t;
- float dist = CalcDistanceSqrToLineSegment2D( m_EscortBehavior.GetEscortTarget()->GetAbsOrigin().AsVector2D(),
- pTarget->GetAbsOrigin().AsVector2D(),
- pTarget->GetAbsOrigin().AsVector2D() + pTarget->GetSmoothedVelocity().AsVector2D(), &t );
-
- if ( t > 0 && dist < Square( hunter_hate_thrown_striderbusters_tolerance.GetFloat() ))
- {
- return true;
- }
- return false;
- }
-
- if ( bAttached && StriderBuster_IsAttachedStriderBuster( pTarget, m_EscortBehavior.GetEscortTarget() ) && hunter_hate_attached_striderbusters.GetBool() )
- {
- return true;
- }
- }
- return false;
- }
-
- return BaseClass::IsValidEnemy( pTarget );
-}
-
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Disposition_t CNPC_Hunter::IRelationType( CBaseEntity *pTarget )
-{
- if ( !pTarget )
- return D_NU;
-
- if ( IsStriderBuster( pTarget ) )
- {
- if ( HateThisStriderBuster( pTarget ) )
- return D_HT;
-
- return D_NU;
- }
-
- if ( hunter_retreat_striderbusters.GetBool() )
- {
- if ( pTarget->IsPlayer() && (m_hAttachedBusters.Count() > 0) )
- {
- return D_FR;
- }
- }
-
- return BaseClass::IRelationType( pTarget );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::IRelationPriority( CBaseEntity *pTarget )
-{
- if ( IsStriderBuster( pTarget ) )
- {
- // If we're here, we already know that we hate striderbusters.
- return 1000.0f;
- }
-
- return BaseClass::IRelationPriority( pTarget );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::SetSquad( CAI_Squad *pSquad )
-{
- BaseClass::SetSquad( pSquad );
- if ( pSquad && pSquad->NumMembers() == 1 )
- {
- pSquad->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, 0 );
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::OnSeeEntity( CBaseEntity *pEntity )
-{
- BaseClass::OnSeeEntity(pEntity);
-
- if ( IsStriderBuster( pEntity ) && IsValidEnemy( pEntity ) )
- {
- SetCondition( COND_HUNTER_SEE_STRIDERBUSTER );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer )
-{
- //EmitSound( "NPC_Hunter.Alert" );
- return BaseClass::UpdateEnemyMemory( pEnemy, position, pInformer );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::CanPlantHere( const Vector &vecPos )
-{
- // TODO: cache results?
- //if ( vecPos == m_vecLastCanPlantHerePos )
- //{
- // return m_bLastCanPlantHere;
- //}
-
- Vector vecMins = GetHullMins();
- Vector vecMaxs = GetHullMaxs();
-
- vecMins.x -= 16;
- vecMins.y -= 16;
-
- vecMaxs.x += 16;
- vecMaxs.y += 16;
- vecMaxs.z -= hunter_plant_adjust_z.GetInt();
-
- bool bResult = false;
-
- trace_t tr;
- UTIL_TraceHull( vecPos, vecPos, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
- if ( tr.startsolid )
- {
- // Try again, tracing down from above.
- Vector vecStart = vecPos;
- vecStart.z += hunter_plant_adjust_z.GetInt();
-
- UTIL_TraceHull( vecStart, vecPos, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
- }
-
- if ( tr.startsolid )
- {
- //NDebugOverlay::Box( vecPos, vecMins, vecMaxs, 255, 0, 0, 0, 0 );
- }
- else
- {
- //NDebugOverlay::Box( vecPos, vecMins, vecMaxs, 0, 255, 0, 0, 0 );
- bResult = true;
- }
-
- // Cache the results in case we ask again for the same spot.
- //m_vecLastCanPlantHerePos = vecPos;
- //m_bLastCanPlantHere = bResult;
-
- return bResult;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::MeleeAttack1ConditionsVsEnemyInVehicle( CBaseCombatCharacter *pEnemy, float flDot )
-{
- if( !IsCorporealEnemy( GetEnemy() ) )
- return COND_NONE;
-
- // Try and trace a box to the player, and if I hit the vehicle, attack it
- Vector vecDelta = (pEnemy->WorldSpaceCenter() - WorldSpaceCenter());
- VectorNormalize( vecDelta );
- trace_t tr;
- AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + (vecDelta * 64), -Vector(8,8,8), Vector(8,8,8), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
- if ( tr.fraction != 1.0 && tr.m_pEnt == pEnemy->GetVehicleEntity() )
- {
- // We're near the vehicle. Are we facing it?
- if (flDot < 0.7)
- return COND_NOT_FACING_ATTACK;
-
- return COND_CAN_MELEE_ATTACK1;
- }
-
- return COND_TOO_FAR_TO_ATTACK;
-}
-
-
-//-----------------------------------------------------------------------------
-// For innate melee attack
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::MeleeAttack1Conditions ( float flDot, float flDist )
-{
- if ( !IsCorporealEnemy( GetEnemy() ) )
- return COND_NONE;
-
- if ( ( gpGlobals->curtime < m_flNextMeleeTime ) && // allow berzerk bashing if cornered
- !( m_hAttachedBusters.Count() > 0 && gpGlobals->curtime < m_fCorneredTimer ) )
- {
- return COND_NONE;
- }
-
- if ( GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
- {
- return COND_NONE;
- }
-
- if ( flDist > HUNTER_MELEE_REACH )
- {
- // Translate a hit vehicle into its passenger if found
- if ( GetEnemy() != NULL )
- {
- CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
- if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
- {
- return MeleeAttack1ConditionsVsEnemyInVehicle( pCCEnemy, flDot );
- }
-
-#if defined(HL2_DLL) && !defined(HL2MP)
- // If the player is holding an object, knock it down.
- if ( GetEnemy()->IsPlayer() )
- {
- CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() );
-
- Assert( pPlayer != NULL );
-
- // Is the player carrying something?
- CBaseEntity *pObject = GetPlayerHeldEntity(pPlayer);
-
- if ( !pObject )
- {
- pObject = PhysCannonGetHeldEntity( pPlayer->GetActiveWeapon() );
- }
-
- if ( pObject )
- {
- float flDist = pObject->WorldSpaceCenter().DistTo( WorldSpaceCenter() );
-
- if ( flDist <= HUNTER_MELEE_REACH )
- {
- return COND_CAN_MELEE_ATTACK1;
- }
- }
- }
-#endif
- }
-
- return COND_TOO_FAR_TO_ATTACK;
- }
-
- if (flDot < 0.7)
- {
- return COND_NOT_FACING_ATTACK;
- }
-
- // Build a cube-shaped hull, the same hull that MeleeAttack is going to use.
- Vector vecMins = GetHullMins();
- Vector vecMaxs = GetHullMaxs();
- vecMins.z = vecMins.x;
- vecMaxs.z = vecMaxs.x;
-
- Vector forward;
- GetVectors( &forward, NULL, NULL );
-
- trace_t tr;
- AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + forward * HUNTER_MELEE_REACH, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.fraction == 1.0 || !tr.m_pEnt )
- {
- // This attack would miss completely. Trick the hunter into moving around some more.
- return COND_TOO_FAR_TO_ATTACK;
- }
-
- if ( tr.m_pEnt == GetEnemy() || tr.m_pEnt->IsNPC() || (tr.m_pEnt->m_takedamage == DAMAGE_YES && (dynamic_cast<CBreakableProp*>(tr.m_pEnt))) )
- {
- // Let the hunter swipe at his enemy if he's going to hit them.
- // Also let him swipe at NPC's that happen to be between the hunter and the enemy.
- // This makes mobs of hunters seem more rowdy since it doesn't leave guys in the back row standing around.
- // Also let him swipe at things that takedamage, under the assumptions that they can be broken.
- return COND_CAN_MELEE_ATTACK1;
- }
-
- // dvs TODO: incorporate this
- /*if ( tr.m_pEnt->IsBSPModel() )
- {
- // The trace hit something solid, but it's not the enemy. If this item is closer to the hunter than
- // the enemy is, treat this as an obstruction.
- Vector vecToEnemy = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter();
- Vector vecTrace = tr.endpos - tr.startpos;
-
- if ( vecTrace.Length2DSqr() < vecToEnemy.Length2DSqr() )
- {
- return COND_HUNTER_LOCAL_MELEE_OBSTRUCTION;
- }
- }*/
-
- if ( !tr.m_pEnt->IsWorld() && GetEnemy() && GetEnemy()->GetGroundEntity() == tr.m_pEnt )
- {
- // Try to swat whatever the player is standing on instead of acting like a dill.
- return COND_CAN_MELEE_ATTACK1;
- }
-
- // Move around some more
- return COND_TOO_FAR_TO_ATTACK;
-}
-
-
-//-----------------------------------------------------------------------------
-// For innate melee attack
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::MeleeAttack2Conditions ( float flDot, float flDist )
-{
- return COND_NONE;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::IsCorporealEnemy( CBaseEntity *pEnemy )
-{
- if( !pEnemy )
- return false;
-
- // Generally speaking, don't melee attack anything the player can't see.
- if( pEnemy->IsEffectActive( EF_NODRAW ) )
- return false;
-
- // Don't flank, melee attack striderbusters.
- if ( IsStriderBuster( pEnemy ) )
- return false;
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::RangeAttack1Conditions( float flDot, float flDist )
-{
- return COND_NONE;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::RangeAttack2Conditions( float flDot, float flDist )
-{
- bool bIsBuster = IsStriderBuster( GetEnemy() );
- bool bIsPerfectBullseye = ( GetEnemy() && dynamic_cast<CNPC_Bullseye *>(GetEnemy()) && ((CNPC_Bullseye *)GetEnemy())->UsePerfectAccuracy() );
-
- if ( !bIsPerfectBullseye && !bIsBuster && !hunter_flechette_test.GetBool() && ( gpGlobals->curtime < m_flNextRangeAttack2Time ) )
- {
- return COND_NONE;
- }
-
- if ( m_bDisableShooting )
- {
- return COND_NONE;
- }
-
- if ( !HasCondition( COND_SEE_ENEMY ) )
- {
- return COND_NONE;
- }
-
- float flMaxFlechetteRange = hunter_flechette_max_range.GetFloat();
-
- if ( IsUsingSiegeTargets() )
- {
- flMaxFlechetteRange *= HUNTER_SIEGE_MAX_DIST_MODIFIER;
- }
-
- if ( !bIsBuster && ( flDist > flMaxFlechetteRange ) )
- {
- return COND_TOO_FAR_TO_ATTACK;
- }
- else if ( !bIsBuster && ( !GetEnemy() || !GetEnemy()->ClassMatches( "npc_bullseye" ) ) && flDist < hunter_flechette_min_range.GetFloat() )
- {
- return COND_TOO_CLOSE_TO_ATTACK;
- }
- else if ( flDot < HUNTER_FACING_DOT )
- {
- return COND_NOT_FACING_ATTACK;
- }
-
- if ( !bIsBuster && !m_bEnableUnplantedShooting && !hunter_flechette_test.GetBool() && !CanPlantHere( GetAbsOrigin() ) )
- {
- return COND_HUNTER_CANT_PLANT;
- }
-
- return COND_CAN_RANGE_ATTACK2;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions)
-{
- CBaseEntity *pTargetEnt;
-
- pTargetEnt = GetEnemy();
-
- trace_t tr;
- Vector vFrom = ownerPos + GetViewOffset();
- AI_TraceLine( vFrom, targetPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
-
- if ( ( pTargetEnt && tr.m_pEnt == pTargetEnt) || tr.fraction == 1.0 || CanShootThrough( tr, targetPos ) )
- {
- static Vector vMins( -2.0, -2.0, -2.0 );
- static Vector vMaxs( -vMins);
- // Hit the enemy, or hit nothing (traced all the way to a nonsolid enemy like a bullseye)
- AI_TraceHull( vFrom - Vector( 0, 0, 18 ), targetPos, vMins, vMaxs, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
-
- if ( ( pTargetEnt && tr.m_pEnt == pTargetEnt) || tr.fraction == 1.0 || CanShootThrough( tr, targetPos ) )
- {
- if ( hunter_show_weapon_los_condition.GetBool() )
- {
- NDebugOverlay::Line( vFrom, targetPos, 255, 0, 255, false, 0.1 );
- NDebugOverlay::Line( vFrom - Vector( 0, 0, 18 ), targetPos, 0, 0, 255, false, 0.1 );
- }
- return true;
- }
- }
- else if ( bSetConditions )
- {
- SetCondition( COND_WEAPON_SIGHT_OCCLUDED );
- SetEnemyOccluder( tr.m_pEnt );
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Look in front and see if the claw hit anything.
-//
-// Input : flDist distance to trace
-// iDamage damage to do if attack hits
-// vecViewPunch camera punch (if attack hits player)
-// vecVelocityPunch velocity punch (if attack hits player)
-//
-// Output : The entity hit by claws. NULL if nothing.
-//-----------------------------------------------------------------------------
-CBaseEntity *CNPC_Hunter::MeleeAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin )
-{
- // Added test because claw attack anim sometimes used when for cases other than melee
- if ( GetEnemy() )
- {
- trace_t tr;
- AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.fraction < 1.0f )
- return NULL;
- }
-
- //
- // Trace out a cubic section of our hull and see what we hit.
- //
- Vector vecMins = GetHullMins();
- Vector vecMaxs = GetHullMaxs();
- vecMins.z = vecMins.x;
- vecMaxs.z = vecMaxs.x;
-
- CBaseEntity *pHurt = CheckTraceHullAttack( flDist, vecMins, vecMaxs, iDamage, DMG_SLASH );
-
- if ( pHurt )
- {
- EmitSound( "NPC_Hunter.MeleeHit" );
- EmitSound( "NPC_Hunter.TackleHit" );
-
- CBasePlayer *pPlayer = ToBasePlayer( pHurt );
-
- if ( pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE ) )
- {
- pPlayer->ViewPunch( qaViewPunch );
- pPlayer->VelocityPunch( vecVelocityPunch );
-
- // Shake the screen
- UTIL_ScreenShake( pPlayer->GetAbsOrigin(), 100.0, 1.5, 1.0, 2, SHAKE_START );
-
- // Red damage indicator
- color32 red = { 128, 0, 0, 128 };
- UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN );
-
- /*if ( UTIL_ShouldShowBlood( pPlayer->BloodColor() ) )
- {
- // Spray some of the player's blood on the hunter.
- trace_t tr;
-
- Vector vecHunterEyePos; // = EyePosition();
- QAngle angDiscard;
- GetBonePosition( LookupBone( "MiniStrider.top_eye_bone" ), vecHunterEyePos, angDiscard );
-
- Vector vecPlayerEyePos = pPlayer->EyePosition();
-
- Vector vecDir = vecHunterEyePos - vecPlayerEyePos;
- float flLen = VectorNormalize( vecDir );
-
- Vector vecStart = vecPlayerEyePos - ( vecDir * 64 );
- Vector vecEnd = vecPlayerEyePos + ( vecDir * ( flLen + 64 ) );
-
- NDebugOverlay::HorzArrow( vecStart, vecEnd, 16, 255, 255, 0, 255, false, 10 );
-
- UTIL_TraceLine( vecStart, vecEnd, MASK_SHOT, pPlayer, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.m_pEnt )
- {
- Msg( "Hit %s!!!\n", tr.m_pEnt->GetDebugName() );
- UTIL_DecalTrace( &tr, "Blood" );
- }
- }*/
- }
- else if ( !pPlayer )
- {
- if ( IsMovablePhysicsObject( pHurt ) )
- {
- // If it's a vphysics object that's too heavy, crash into it too.
- IPhysicsObject *pPhysics = pHurt->VPhysicsGetObject();
- if ( pPhysics )
- {
- // If the object is being held by the player, break it or make them drop it.
- if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
- {
- // If it's breakable, break it.
- if ( pHurt->m_takedamage == DAMAGE_YES )
- {
- CBreakableProp *pBreak = dynamic_cast<CBreakableProp*>(pHurt);
- if ( pBreak )
- {
- CTakeDamageInfo info( this, this, 20, DMG_SLASH );
- pBreak->Break( this, info );
- }
- }
- }
- }
- }
-
- if ( UTIL_ShouldShowBlood(pHurt->BloodColor()) )
- {
- // Hit an NPC. Bleed them!
- Vector vecBloodPos;
-
- switch ( BloodOrigin )
- {
- case HUNTER_BLOOD_LEFT_FOOT:
- {
- if ( GetAttachment( "blood_left", vecBloodPos ) )
- {
- SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) );
- }
-
- break;
- }
- }
- }
- }
- }
- else
- {
- // TODO:
- //AttackMissSound();
- }
-
- m_flNextMeleeTime = gpGlobals->curtime + hunter_melee_delay.GetFloat();
-
- return pHurt;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::TestShootPosition(const Vector &vecShootPos, const Vector &targetPos )
-{
- if ( !CanPlantHere(vecShootPos ) )
- {
- return false;
- }
-
- return BaseClass::TestShootPosition( vecShootPos, targetPos );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Vector CNPC_Hunter::Weapon_ShootPosition( )
-{
- matrix3x4_t gunMatrix;
- GetAttachment( gm_nTopGunAttachment, gunMatrix );
-
- Vector vecShootPos;
- MatrixGetColumn( gunMatrix, 3, vecShootPos );
-
- return vecShootPos;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
-{
- float flTracerDist;
- Vector vecDir;
- Vector vecEndPos;
-
- vecDir = tr.endpos - vecTracerSrc;
-
- flTracerDist = VectorNormalize( vecDir );
-
- int nAttachment = LookupAttachment( "MiniGun" );
-
- UTIL_Tracer( vecTracerSrc, tr.endpos, nAttachment, TRACER_FLAG_USEATTACHMENT, 5000, true, "HunterTracer" );
-}
-
-
-//-----------------------------------------------------------------------------
-// Trace didn't hit the intended target, but should the hunter
-// shoot anyway? We use this to get the hunter to destroy
-// breakables that are between him and his target.
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::CanShootThrough( const trace_t &tr, const Vector &vecTarget )
-{
- if ( !tr.m_pEnt )
- {
- return false;
- }
-
- if ( !tr.m_pEnt->GetHealth() )
- {
- return false;
- }
-
- // Don't try to shoot through allies.
- CAI_BaseNPC *pNPC = tr.m_pEnt->MyNPCPointer();
- if ( pNPC && ( IRelationType( pNPC ) == D_LI ) )
- {
- return false;
- }
-
- // Would a trace ignoring this entity continue to the target?
- trace_t continuedTrace;
- AI_TraceLine( tr.endpos, vecTarget, MASK_SHOT, tr.m_pEnt, COLLISION_GROUP_NONE, &continuedTrace );
-
- if ( continuedTrace.fraction != 1.0 )
- {
- if ( continuedTrace.m_pEnt != GetEnemy() )
- {
- return false;
- }
- }
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::GetSoundInterests()
-{
- return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_DANGER | SOUND_PHYSICS_DANGER | SOUND_PLAYER_VEHICLE | SOUND_BULLET_IMPACT | SOUND_MOVE_AWAY;
-}
-
-//-----------------------------------------------------------------------------
-// Tells us whether the Hunter is acting in a large, outdoor map,
-// currently only ep2_outland_12. This allows us to create logic
-// branches here in the AI code so that we can make choices that
-// tailor behavior to larger and smaller maps.
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::IsInLargeOutdoorMap()
-{
- return m_bInLargeOutdoorMap;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::AlertSound()
-{
- EmitSound( "NPC_Hunter.Alert" );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::PainSound( const CTakeDamageInfo &info )
-{
- if ( gpGlobals->curtime > m_flNextDamageTime )
- {
- EmitSound( "NPC_Hunter.Pain" );
- m_flNextDamageTime = gpGlobals->curtime + random->RandomFloat( 0.5, 1.2 );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::DeathSound( const CTakeDamageInfo &info )
-{
- EmitSound( "NPC_Hunter.Death" );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
-{
- CTakeDamageInfo info = inputInfo;
-
- // Even though the damage might not hurt us, we want to react to it
- // if it's from the player.
- if ( info.GetAttacker()->IsPlayer() )
- {
- if ( !HasMemory( bits_MEMORY_PROVOKED ) )
- {
- GetEnemies()->ClearMemory( info.GetAttacker() );
- Remember( bits_MEMORY_PROVOKED );
- SetCondition( COND_LIGHT_DAMAGE );
- }
- }
-
- // HUnters have special resisitance to some types of damage.
- if ( ( info.GetDamageType() & DMG_BULLET ) ||
- ( info.GetDamageType() & DMG_BUCKSHOT ) ||
- ( info.GetDamageType() & DMG_CLUB ) ||
- ( info.GetDamageType() & DMG_NEVERGIB ) )
- {
- float flScale = 1.0;
-
- if ( info.GetDamageType() & DMG_BUCKSHOT )
- {
- flScale = sk_hunter_buckshot_damage_scale.GetFloat();
- }
- else if ( ( info.GetDamageType() & DMG_BULLET ) || ( info.GetDamageType() & DMG_NEVERGIB ) )
- {
- // Hunters resist most bullet damage, but they are actually vulnerable to .357 rounds,
- // since players regard that weapon as one of the game's truly powerful weapons.
- if( info.GetAmmoType() == GetAmmoDef()->Index("357") )
- {
- flScale = 1.16f;
- }
- else
- {
- flScale = sk_hunter_bullet_damage_scale.GetFloat();
- }
- }
-
- if ( GetActivity() == ACT_HUNTER_CHARGE_RUN )
- {
- flScale *= sk_hunter_charge_damage_scale.GetFloat();
- }
-
- if ( flScale != 0 )
- {
- float flDamage = info.GetDamage() * flScale;
- info.SetDamage( flDamage );
- }
-
- QAngle vecAngles;
- VectorAngles( ptr->plane.normal, vecAngles );
- DispatchParticleEffect( "blood_impact_synth_01", ptr->endpos, vecAngles );
- DispatchParticleEffect( "blood_impact_synth_01_arc_parent", PATTACH_POINT_FOLLOW, this, gm_nHeadCenterAttachment );
- }
-
- BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-const impactdamagetable_t &CNPC_Hunter::GetPhysicsImpactDamageTable()
-{
- return s_HunterImpactDamageTable;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::PhysicsDamageEffect( const Vector &vecPos, const Vector &vecDir )
-{
- CEffectData data;
- data.m_vOrigin = vecPos;
- data.m_vNormal = vecDir;
- DispatchEffect( "HunterDamage", data );
-
- if ( random->RandomInt( 0, 1 ) == 0 )
- {
- CBaseEntity *pTrail = CreateEntityByName( "sparktrail" );
- pTrail->SetOwnerEntity( this );
- pTrail->Spawn();
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// We were hit by a strider buster. Do the tesla effect on our hitboxes.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::TeslaThink()
-{
- CEffectData data;
- data.m_nEntIndex = entindex();
- data.m_flMagnitude = 3;
- data.m_flScale = 0.5f;
- DispatchEffect( "TeslaHitboxes", data );
- EmitSound( "RagdollBoogie.Zap" );
-
- if ( gpGlobals->curtime < m_flTeslaStopTime )
- {
- SetContextThink( &CNPC_Hunter::TeslaThink, gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ), HUNTER_ZAP_THINK );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Our health is low. Show damage effects.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::BleedThink()
-{
- // Spurt blood from random points on the hunter's head.
- Vector vecOrigin;
- QAngle angDir;
- GetAttachment( gm_nHeadCenterAttachment, vecOrigin, angDir );
-
- Vector vecDir = RandomVector( -1, 1 );
- VectorNormalize( vecDir );
- VectorAngles( vecDir, Vector( 0, 0, 1 ), angDir );
-
- vecDir *= gm_flHeadRadius;
- DispatchParticleEffect( "blood_spurt_synth_01", vecOrigin + vecDir, angDir );
-
- SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.6, 1.5 ), HUNTER_BLEED_THINK );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::IsHeavyDamage( const CTakeDamageInfo &info )
-{
- if ( info.GetDamage() < 45 )
- {
- return false;
- }
-
- if ( info.GetDamage() < 180 )
- {
- if ( !m_HeavyDamageDelay.Expired() || !BaseClass::IsHeavyDamage( info ) )
- {
- return false;
- }
- }
-
- m_HeavyDamageDelay.Set( 15, 25 );
- return true;
-
-}
-
-
-//-----------------------------------------------------------------------------
-// We've taken some damage. Maybe we should flinch because of it.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::ConsiderFlinching( const CTakeDamageInfo &info )
-{
- if ( !m_FlinchTimer.Expired() )
- {
- // Someone is whaling on us. Push out the timer so we don't keep flinching.
- m_FlinchTimer.Set( random->RandomFloat( 0.3 ) );
- return;
- }
-
- if ( GetState() == NPC_STATE_SCRIPT )
- {
- return;
- }
-
- Activity eGesture = ACT_HUNTER_FLINCH_N;
-
- Vector forward;
- GetVectors( &forward, NULL, NULL );
-
- Vector vecForceDir = info.GetDamageForce();
- VectorNormalize( vecForceDir );
-
- float flDot = DotProduct( forward, vecForceDir );
-
- if ( flDot > 0.707 )
- {
- // flinch forward
- eGesture = ACT_HUNTER_FLINCH_N;
- }
- else if ( flDot < -0.707 )
- {
- // flinch back
- eGesture = ACT_HUNTER_FLINCH_S;
- }
- else
- {
- // flinch left or right
- Vector cross = CrossProduct( forward, vecForceDir );
-
- if ( cross.z > 0 )
- {
- eGesture = ACT_HUNTER_FLINCH_W;
- }
- else
- {
- eGesture = ACT_HUNTER_FLINCH_E;
- }
- }
-
- if ( !IsPlayingGesture( eGesture ) )
- {
- RestartGesture( eGesture );
- m_FlinchTimer.Set( random->RandomFloat( 0.3, 1.0 ) );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// This is done from a think function because when the hunter is killed,
-// the physics code puts the vehicle's pre-collision velocity back so the jostle
-// is basically discared.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::JostleVehicleThink()
-{
- CBaseEntity *pInflictor = m_hHitByVehicle;
- if ( !pInflictor )
- return;
-
- Vector vecVelDir = pInflictor->GetSmoothedVelocity();
- float flSpeed = VectorNormalize( vecVelDir );
- Vector vecForce = CrossProduct( vecVelDir, Vector( 0, 0, 1 ) );
- if ( DotProduct( vecForce, GetAbsOrigin() ) < DotProduct( vecForce, pInflictor->GetAbsOrigin() ) )
- {
- // We're to the left of the vehicle that's hitting us.
- vecForce *= -1;
- }
-
- VectorNormalize( vecForce );
- vecForce.z = 1.0;
-
- float flForceScale = RemapValClamped( flSpeed, hunter_jostle_car_min_speed.GetFloat(), hunter_jostle_car_max_speed.GetFloat(), 50.0f, 150.0f );
-
- vecForce *= ( flForceScale * pInflictor->VPhysicsGetObject()->GetMass() );
-
- pInflictor->VPhysicsGetObject()->ApplyForceOffset( vecForce, WorldSpaceCenter() );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::OnTakeDamage( const CTakeDamageInfo &info )
-{
- CTakeDamageInfo myInfo = info;
-
- if ( ( info.GetDamageType() & DMG_CRUSH ) && !( info.GetDamageType() & DMG_VEHICLE ) )
- {
- // Don't take damage from physics objects that weren't thrown by the player.
- CBaseEntity *pInflictor = info.GetInflictor();
-
- IPhysicsObject *pObj = pInflictor->VPhysicsGetObject();
- //Assert( pObj );
-
- if ( !pObj || !pInflictor->HasPhysicsAttacker( 4.0 ) )
- {
- myInfo.SetDamage( 0 );
- }
- else
- {
- // Physics objects that have flechettes stuck in them spoof
- // a flechette hitting us so we dissolve when killed and award
- // the achievement of killing a hunter with its flechettes.
- CUtlVector<CBaseEntity *> children;
- GetAllChildren( pInflictor, children );
- for (int i = 0; i < children.Count(); i++ )
- {
- CBaseEntity *pent = children.Element( i );
- if ( dynamic_cast<CHunterFlechette *>( pent ) )
- {
- myInfo.SetInflictor( pent );
- myInfo.SetDamageType( myInfo.GetDamageType() | DMG_DISSOLVE );
- }
- }
- }
- }
-
- return BaseClass::OnTakeDamage( myInfo );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
-{
- CTakeDamageInfo myInfo = info;
-
- // don't take damage from my own weapons!!!
- // Exception: I "own" a magnade if it's glued to me.
- CBaseEntity *pInflictor = info.GetInflictor();
- CBaseEntity *pAttacker = info.GetAttacker();
- if ( pInflictor )
- {
- if ( IsStriderBuster( pInflictor ) )
- {
- // Get a tesla effect on our hitboxes for a little while.
- SetContextThink( &CNPC_Hunter::TeslaThink, gpGlobals->curtime, HUNTER_ZAP_THINK );
- m_flTeslaStopTime = gpGlobals->curtime + 2.0f;
-
- myInfo.SetDamage( sk_hunter_dmg_from_striderbuster.GetFloat() ) ;
-
- SetCondition( COND_HUNTER_STAGGERED );
- }
- else if ( pInflictor->ClassMatches( GetClassname() ) && !( info.GetDamageType() == DMG_GENERIC ) )
- {
- return 0;
- }
- else if ( pInflictor->ClassMatches( "hunter_flechette" ) )
- {
- if ( !( ( CHunterFlechette *)pInflictor )->WasThrownBack() )
- {
- // Flechettes only hurt us if they were thrown back at us by the player. This prevents
- // hunters from hurting themselves when they walk into their own flechette clusters.
- return 0;
- }
- }
- }
-
- if ( m_EscortBehavior.GetFollowTarget() && m_EscortBehavior.GetFollowTarget() == pAttacker )
- {
- return 0;
- }
-
- bool bHitByUnoccupiedCar = false;
- if ( ( ( info.GetDamageType() & DMG_CRUSH ) && ( pAttacker && pAttacker->IsPlayer() ) ) ||
- ( info.GetDamageType() & DMG_VEHICLE ) )
- {
- // myInfo, not info! it may have been modified above.
- float flDamage = myInfo.GetDamage();
- if ( flDamage < HUNTER_MIN_PHYSICS_DAMAGE )
- {
- //DevMsg( "hunter: <<<< ZERO PHYSICS DAMAGE: %f\n", flDamage );
- myInfo.SetDamage( 0 );
- }
- else
- {
- CBaseEntity *pInflictor = info.GetInflictor();
- if ( ( info.GetDamageType() & DMG_VEHICLE ) ||
- ( pInflictor && pInflictor->GetServerVehicle() &&
- ( ( bHitByUnoccupiedCar = ( dynamic_cast<CPropVehicleDriveable *>(pInflictor) && static_cast<CPropVehicleDriveable *>(pInflictor)->GetDriver() == NULL ) ) == false ) ) )
- {
- // Adjust the damage from vehicles.
- flDamage *= sk_hunter_vehicle_damage_scale.GetFloat();
- myInfo.SetDamage( flDamage );
-
- // Apply a force to jostle the vehicle that hit us.
- // Pick a force direction based on which side we're on relative to the vehicle's motion.
- Vector vecVelDir = pInflictor->GetSmoothedVelocity();
- if ( vecVelDir.Length() >= hunter_jostle_car_min_speed.GetFloat() )
- {
- EmitSound( "NPC_Hunter.HitByVehicle" );
- m_hHitByVehicle = pInflictor;
- SetContextThink( &CNPC_Hunter::JostleVehicleThink, gpGlobals->curtime, HUNTER_JOSTLE_VEHICLE_THINK );
- }
- }
-
- if ( !bHitByUnoccupiedCar )
- {
- SetCondition( COND_HUNTER_STAGGERED );
- }
- }
-
- //DevMsg( "hunter: >>>> PHYSICS DAMAGE: %f (was %f)\n", flDamage, info.GetDamage() );
- }
-
- // Show damage effects if we actually took damage.
- if ( ( myInfo.GetDamageType() & ( DMG_CRUSH | DMG_BLAST ) ) && ( myInfo.GetDamage() > 0 ) )
- {
- if ( !bHitByUnoccupiedCar )
- SetCondition( COND_HUNTER_STAGGERED );
- }
-
- if ( HasCondition( COND_HUNTER_STAGGERED ) )
- {
- // Throw a bunch of gibs out
- Vector vecForceDir = -myInfo.GetDamageForce();
- VectorNormalize( vecForceDir );
- PhysicsDamageEffect( myInfo.GetDamagePosition(), vecForceDir );
-
- // Stagger away from the direction the damage came from.
- m_vecStaggerDir = myInfo.GetDamageForce();
- VectorNormalize( m_vecStaggerDir );
- }
-
- // Take less damage from citizens and Alyx, otherwise hunters go down too easily.
- float flScale = 1.0;
-
- if ( pAttacker &&
- ( ( pAttacker->Classify() == CLASS_CITIZEN_REBEL ) ||
- ( pAttacker->Classify() == CLASS_PLAYER_ALLY ) ||
- ( pAttacker->Classify() == CLASS_PLAYER_ALLY_VITAL ) ) )
- {
- flScale *= sk_hunter_citizen_damage_scale.GetFloat();
- }
-
- if ( flScale != 0 )
- {
- // We're taking a nonzero amount of damage.
-
- // If we're not staggering, consider flinching!
- if ( !HasCondition( COND_HUNTER_STAGGERED ) )
- {
- ConsiderFlinching( info );
- }
-
- if( pAttacker && pAttacker->IsPlayer() )
- {
- // This block of code will distract the Hunter back to the player if the
- // player does harm to the Hunter but is not the Hunter's current enemy.
- // This is achieved by updating the Hunter's enemy memory of the player and
- // making the Hunter's current enemy invalid for a short time.
- if( !GetEnemy() || !GetEnemy()->IsPlayer() )
- {
- UpdateEnemyMemory( pAttacker, pAttacker->GetAbsOrigin(), this );
-
- if( GetEnemy() )
- {
- // Gotta forget about this person for a little bit.
- GetEnemies()->SetTimeValidEnemy( GetEnemy(), gpGlobals->curtime + HUNTER_IGNORE_ENEMY_TIME );
- }
- }
- }
-
- float flDamage = myInfo.GetDamage() * flScale;
- myInfo.SetDamage( flDamage );
- }
-
- int nRet = BaseClass::OnTakeDamage_Alive( myInfo );
-
- m_EscortBehavior.OnDamage( myInfo );
-
- // Spark at 30% health.
- if ( !IsBleeding() && ( GetHealth() <= sk_hunter_health.GetInt() * 0.3 ) )
- {
- StartBleeding();
- }
-
- if ( IsUsingSiegeTargets() && info.GetAttacker() != NULL && info.GetAttacker()->IsPlayer() )
- {
- // Defend myself. Try to siege attack immediately.
- m_flTimeNextSiegeTargetAttack = gpGlobals->curtime;
- }
-
- return nRet;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::Event_Killed( const CTakeDamageInfo &info )
-{
- // Remember the killing blow to make decisions about ragdolling.
- m_nKillingDamageType = info.GetDamageType();
-
- if ( m_EscortBehavior.GetFollowTarget() )
- {
- if ( AIGetNumFollowers( m_EscortBehavior.GetFollowTarget(), m_iClassname ) == 1 )
- {
- m_EscortBehavior.GetEscortTarget()->AlertSound();
- if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
- {
- m_EscortBehavior.GetEscortTarget()->UpdateEnemyMemory( UTIL_GetLocalPlayer(), UTIL_GetLocalPlayer()->GetAbsOrigin(), this );
- }
- }
- }
-
- if ( info.GetDamageType() & DMG_VEHICLE )
- {
- bool bWasRunDown = false;
- int iRundownCounter = 0;
- if ( GetSquad() )
- {
- if ( !m_IgnoreVehicleTimer.Expired() )
- {
- GetSquad()->GetSquadData( HUNTER_RUNDOWN_SQUADDATA, &iRundownCounter );
- GetSquad()->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, iRundownCounter + 1 );
- bWasRunDown = true;
- }
- }
-
- if ( hunter_dodge_debug.GetBool() )
- Msg( "Hunter %d was%s run down\n", entindex(), ( bWasRunDown ) ? "" : " not" );
-
- // Death by vehicle! Decrement the hunters to run over counter.
- // When the counter reaches zero hunters will start dodging.
- if ( GlobalEntity_GetCounter( s_iszHuntersToRunOver ) > 0 )
- {
- GlobalEntity_AddToCounter( s_iszHuntersToRunOver, -1 );
- }
- }
-
- // Stop all our thinks
- SetContextThink( NULL, 0, HUNTER_BLEED_THINK );
-
- StopParticleEffects( this );
-
- BaseClass::Event_Killed( info );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::StartBleeding()
-{
- // Do this even if we're already bleeding (see OnRestore).
- m_bIsBleeding = true;
-
- // Start gushing blood from our... anus or something.
- DispatchParticleEffect( "blood_drip_synth_01", PATTACH_POINT_FOLLOW, this, gm_nHeadBottomAttachment );
-
- // Emit spurts of our blood
- SetContextThink( &CNPC_Hunter::BleedThink, gpGlobals->curtime + 0.1, HUNTER_BLEED_THINK );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-float CNPC_Hunter::MaxYawSpeed()
-{
- if ( IsStriderBuster( GetEnemy() ) )
- {
- return 60;
- }
-
- if ( GetActivity() == ACT_HUNTER_ANGRY )
- return 0;
-
- if ( GetActivity() == ACT_HUNTER_CHARGE_RUN )
- return 5;
-
- if ( GetActivity() == ACT_HUNTER_IDLE_PLANTED )
- return 0;
-
- if ( GetActivity() == ACT_HUNTER_RANGE_ATTACK2_UNPLANTED )
- return 180;
-
- switch ( GetActivity() )
- {
- case ACT_RANGE_ATTACK2:
- {
- return 0;
- }
-
- case ACT_90_LEFT:
- case ACT_90_RIGHT:
- {
- return 45;
- }
-
- case ACT_TURN_LEFT:
- case ACT_TURN_RIGHT:
- {
- return 45;
- }
-
- case ACT_WALK:
- {
- return 25;
- }
-
- default:
- {
- return 35;
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const
-{
- float MAX_JUMP_RISE = 220.0f;
- float MAX_JUMP_DISTANCE = 512.0f;
- float MAX_JUMP_DROP = 384.0f;
-
- trace_t tr;
- UTIL_TraceHull( startPos, startPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
- if ( tr.startsolid )
- {
- // Trying to start a jump in solid! Consider checking for this in CAI_MoveProbe::JumpMoveLimit.
- Assert( 0 );
- return false;
- }
-
- if ( BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE ) )
- {
- return true;
- }
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-// Let the probe know I can run through small debris
-// Stolen shamelessly from the Antlion Guard
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity )
-{
- if ( s_iszPhysPropClassname != pEntity->m_iClassname )
- return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );
-
- if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
- {
- IPhysicsObject *pPhysObj = pEntity->VPhysicsGetObject();
-
- if( pPhysObj && pPhysObj->GetMass() <= 500.0f )
- {
- return false;
- }
- }
-
- return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::DoMuzzleFlash( int nAttachment )
-{
- BaseClass::DoMuzzleFlash();
-
- DispatchParticleEffect( "hunter_muzzle_flash", PATTACH_POINT_FOLLOW, this, nAttachment );
-
- // Dispatch the elight
- CEffectData data;
- data.m_nAttachmentIndex = nAttachment;
- data.m_nEntIndex = entindex();
- DispatchEffect( "HunterMuzzleFlash", data );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CNPC_Hunter::CountRangedAttackers()
-{
- CBaseEntity *pEnemy = GetEnemy();
- if ( !pEnemy )
- {
- return 0;
- }
-
- int nAttackers = 0;
- for ( int i = 0; i < g_Hunters.Count(); i++ )
- {
- CNPC_Hunter *pOtherHunter = g_Hunters[i];
- if ( pOtherHunter->GetEnemy() == pEnemy )
- {
- if ( pOtherHunter->IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2 ) )
- {
- nAttackers++;
- }
- }
- }
- return nAttackers;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::DelayRangedAttackers( float minDelay, float maxDelay, bool bForced )
-{
- float delayMultiplier = ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) ? 1.25 : 1.0;
- if ( !m_bEnableSquadShootDelay && !bForced )
- {
- m_flNextRangeAttack2Time = gpGlobals->curtime + random->RandomFloat( minDelay, maxDelay ) * delayMultiplier;
- return;
- }
-
- CBaseEntity *pEnemy = GetEnemy();
- for ( int i = 0; i < g_Hunters.Count(); i++ )
- {
- CNPC_Hunter *pOtherHunter = g_Hunters[i];
- if ( pOtherHunter->GetEnemy() == pEnemy )
- {
- float nextTime = gpGlobals->curtime + random->RandomFloat( minDelay, maxDelay ) * delayMultiplier;
- if ( nextTime > pOtherHunter->m_flNextRangeAttack2Time )
- pOtherHunter->m_flNextRangeAttack2Time = nextTime;
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Given a target to shoot at, decide where to aim.
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::GetShootDir( Vector &vecDir, const Vector &vecSrc, CBaseEntity *pTargetEntity, bool bStriderBuster, int nShotNum, bool bSingleShot )
-{
- //RestartGesture( ACT_HUNTER_GESTURE_SHOOT );
-
- EmitSound( "NPC_Hunter.FlechetteShoot" );
-
- Vector vecBodyTarget;
-
- if( pTargetEntity->Classify() == CLASS_PLAYER_ALLY_VITAL )
- {
- // Shooting at Alyx, most likely (in EP2). The attack is designed to displace
- // her, not necessarily actually harm her. So shoot at the area around her feet.
- vecBodyTarget = pTargetEntity->GetAbsOrigin();
- }
- else
- {
- vecBodyTarget = pTargetEntity->BodyTarget( vecSrc );
- }
-
- Vector vecTarget = vecBodyTarget;
-
- Vector vecDelta = pTargetEntity->GetAbsOrigin() - GetAbsOrigin();
- float flDist = vecDelta.Length();
-
- if ( !bStriderBuster )
- {
- // If we're not firing at a strider buster, miss in an entertaining way for the
- // first three shots of each volley.
- if ( ( nShotNum < 3 ) && ( flDist > 200 ) )
- {
- Vector vecTargetForward;
- Vector vecTargetRight;
- pTargetEntity->GetVectors( &vecTargetForward, &vecTargetRight, NULL );
-
- Vector vecForward;
- GetVectors( &vecForward, NULL, NULL );
-
- float flDot = DotProduct( vecTargetForward, vecForward );
-
- if ( flDot < -0.8f )
- {
- // Our target is facing us, shoot the ground between us.
- float flPerc = 0.7 + ( 0.1 * nShotNum );
- vecTarget = GetAbsOrigin() + ( flPerc * ( pTargetEntity->GetAbsOrigin() - GetAbsOrigin() ) );
- }
- else if ( flDot > 0.8f )
- {
- // Our target is facing away from us, shoot to the left or right.
- Vector vecMissDir = vecTargetRight;
- if ( m_bMissLeft )
- {
- vecMissDir *= -1.0f;
- }
-
- vecTarget = pTargetEntity->EyePosition() + ( 36.0f * ( 3 - nShotNum ) ) * vecMissDir;
- }
- else
- {
- // Our target is facing vaguely perpendicular to us, shoot across their view.
- vecTarget = pTargetEntity->EyePosition() + ( 36.0f * ( 3 - nShotNum ) ) * vecTargetForward;
- }
- }
- // If we can't see them, shoot where we last saw them.
- else if ( !HasCondition( COND_SEE_ENEMY ) )
- {
- Vector vecDelta = vecTarget - pTargetEntity->GetAbsOrigin();
- vecTarget = m_vecEnemyLastSeen + vecDelta;
- }
- }
- else
- {
- // If we're firing at a striderbuster, lead it.
- float flSpeed = hunter_flechette_speed.GetFloat();
- if ( !flSpeed )
- {
- flSpeed = 2500.0f;
- }
-
- flSpeed *= 1.5;
-
- float flDeltaTime = flDist / flSpeed;
- vecTarget = vecTarget + flDeltaTime * pTargetEntity->GetSmoothedVelocity();
- }
-
- vecDir = vecTarget - vecSrc;
- VectorNormalize( vecDir );
-}
-
-
-//-----------------------------------------------------------------------------
-// Ensures that we don't exceed our pitch/yaw limits when shooting flechettes.
-// Returns true if we had to clamp, false if not.
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ClampShootDir( Vector &vecDir )
-{
- Vector vecDir2D = vecDir;
- vecDir2D.z = 0;
-
- Vector vecForward;
- GetVectors( &vecForward, NULL, NULL );
-
- Vector vecForward2D = vecForward;
- vecForward2D.z = 0;
-
- float flDot = DotProduct( vecForward2D, vecDir2D );
- if ( flDot >= HUNTER_SHOOT_MAX_YAW_COS )
- {
- // No need to clamp.
- return false;
- }
-
- Vector vecAxis;
- CrossProduct( vecDir, vecForward, vecAxis );
- VectorNormalize( vecAxis );
-
- Quaternion q;
- AxisAngleQuaternion( vecAxis, -HUNTER_SHOOT_MAX_YAW_DEG, q );
-
- matrix3x4_t rot;
- QuaternionMatrix( q, rot );
- VectorRotate( vecForward, rot, vecDir );
- VectorNormalize( vecDir );
-
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ShouldSeekTarget( CBaseEntity *pTargetEntity, bool bStriderBuster )
-{
- bool bSeek = false;
-
- if ( bStriderBuster )
- {
- bool bSeek = false;
-
- if ( pTargetEntity->VPhysicsGetObject() && ( pTargetEntity->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) )
- {
- bSeek = true;
- }
- else if ( StriderBuster_NumFlechettesAttached( pTargetEntity ) == 0 )
- {
- if ( StriderBuster_IsAttachedStriderBuster(pTargetEntity) )
- {
- bSeek = true;
- }
- else if ( hunter_seek_thrown_striderbusters_tolerance.GetFloat() > 0.0 )
- {
- CNPC_Strider *pEscortTarget = m_EscortBehavior.GetEscortTarget();
- if ( pEscortTarget && ( pEscortTarget->GetAbsOrigin() - pTargetEntity->GetAbsOrigin() ).LengthSqr() < Square( hunter_seek_thrown_striderbusters_tolerance.GetFloat() ) )
- {
- bSeek = true;
- }
- }
- }
- }
-
- return bSeek;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::BeginVolley( int nNum, float flStartTime )
-{
- m_nFlechettesQueued = nNum;
- m_nClampedShots = 0;
- m_flNextFlechetteTime = flStartTime;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::ShootFlechette( CBaseEntity *pTargetEntity, bool bSingleShot )
-{
- if ( !pTargetEntity )
- {
- Assert( false );
- return false;
- }
-
- int nShotNum = hunter_flechette_volley_size.GetInt() - m_nFlechettesQueued;
-
- bool bStriderBuster = IsStriderBuster( pTargetEntity );
-
- // Choose the next muzzle to shoot from.
- Vector vecSrc;
- QAngle angMuzzle;
-
- if ( m_bTopMuzzle )
- {
- GetAttachment( gm_nTopGunAttachment, vecSrc, angMuzzle );
- DoMuzzleFlash( gm_nTopGunAttachment );
- }
- else
- {
- GetAttachment( gm_nBottomGunAttachment, vecSrc, angMuzzle );
- DoMuzzleFlash( gm_nBottomGunAttachment );
- }
-
- m_bTopMuzzle = !m_bTopMuzzle;
-
- Vector vecDir;
- GetShootDir( vecDir, vecSrc, pTargetEntity, bStriderBuster, nShotNum, bSingleShot );
-
- bool bClamped = false;
- if ( hunter_clamp_shots.GetBool() )
- {
- bClamped = ClampShootDir( vecDir );
- }
-
- CShotManipulator manipulator( vecDir );
- Vector vecShoot;
-
- if( IsUsingSiegeTargets() && nShotNum >= 2 && (nShotNum % 2) == 0 )
- {
- // Near perfect accuracy for these three shots, so they are likely to fly right into the windows.
- // NOTE! In siege behavior in the map that this behavior was designed for (ep2_outland_10), the
- // Hunters will only ever shoot at siege targets in siege mode. If you allow Hunters in siege mode
- // to attack players or other NPCs, this accuracy bonus will apply unless we apply a bit more logic to it.
- vecShoot = manipulator.ApplySpread( VECTOR_CONE_1DEGREES * 0.5, 1.0f );
- }
- else
- {
- vecShoot = manipulator.ApplySpread( VECTOR_CONE_4DEGREES, 1.0f );
- }
-
- QAngle angShoot;
- VectorAngles( vecShoot, angShoot );
-
- CHunterFlechette *pFlechette = CHunterFlechette::FlechetteCreate( vecSrc, angShoot, this );
-
- pFlechette->AddEffects( EF_NOSHADOW );
-
- vecShoot *= hunter_flechette_speed.GetFloat();
-
- pFlechette->Shoot( vecShoot, bStriderBuster );
-
- if ( ShouldSeekTarget( pTargetEntity, bStriderBuster ) )
- {
- pFlechette->SetSeekTarget( pTargetEntity );
- }
-
- if( nShotNum == 1 && pTargetEntity->Classify() == CLASS_PLAYER_ALLY_VITAL )
- {
- // Make this person afraid and react to ME, not to the flechettes.
- // Otherwise they could be scared into running towards the hunter.
- CSoundEnt::InsertSound( SOUND_DANGER|SOUND_CONTEXT_REACT_TO_SOURCE|SOUND_CONTEXT_EXCLUDE_COMBINE, pTargetEntity->EyePosition(), 180.0f, 2.0f, this );
- }
-
- return bClamped;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Vector CNPC_Hunter::LeftFootHit( float eventtime )
-{
- Vector footPosition;
-
- GetAttachment( "left foot", footPosition );
- CPASAttenuationFilter filter( this );
- EmitSound( filter, entindex(), "NPC_Hunter.Footstep", &footPosition, eventtime );
-
- FootFX( footPosition );
-
- return footPosition;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Vector CNPC_Hunter::RightFootHit( float eventtime )
-{
- Vector footPosition;
-
- GetAttachment( "right foot", footPosition );
- CPASAttenuationFilter filter( this );
- EmitSound( filter, entindex(), "NPC_Hunter.Footstep", &footPosition, eventtime );
- FootFX( footPosition );
-
- return footPosition;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Vector CNPC_Hunter::BackFootHit( float eventtime )
-{
- Vector footPosition;
-
- GetAttachment( "back foot", footPosition );
- CPASAttenuationFilter filter( this );
- EmitSound( filter, entindex(), "NPC_Hunter.BackFootstep", &footPosition, eventtime );
- FootFX( footPosition );
-
- return footPosition;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::FootFX( const Vector &origin )
-{
- return;
-
- // dvs TODO: foot dust? probably too expensive for these guys
- /*trace_t tr;
- AI_TraceLine( origin, origin - Vector(0,0,100), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
- float yaw = random->RandomInt(0,120);
- for ( int i = 0; i < 3; i++ )
- {
- Vector dir = UTIL_YawToVector( yaw + i*120 ) * 10;
- VectorNormalize( dir );
- dir.z = 0.25;
- VectorNormalize( dir );
- g_pEffects->Dust( tr.endpos, dir, 12, 50 );
- }*/
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-CBaseEntity *CNPC_Hunter::GetEnemyVehicle()
-{
- if ( GetEnemy() == NULL )
- return NULL;
-
- CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
- if ( pCCEnemy != NULL )
- return pCCEnemy->GetVehicleEntity();
-
- return NULL;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::DrawDebugGeometryOverlays()
-{
- if (m_debugOverlays & OVERLAY_BBOX_BIT)
- {
- float flViewRange = acos(0.8);
- Vector vEyeDir = EyeDirection2D( );
- Vector vLeftDir, vRightDir;
- float fSin, fCos;
- SinCos( flViewRange, &fSin, &fCos );
-
- vLeftDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
- vLeftDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
- vLeftDir.z = vEyeDir.z;
- fSin = sin(-flViewRange);
- fCos = cos(-flViewRange);
- vRightDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
- vRightDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
- vRightDir.z = vEyeDir.z;
-
- int nSeq = GetSequence();
- if ( ( GetEntryNode( nSeq ) == gm_nPlantedNode ) && ( GetExitNode( nSeq ) == gm_nPlantedNode ) )
- {
- // planted - green
- NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 0, 128, 0 );
- }
- else if ( ( GetEntryNode( nSeq ) == gm_nUnplantedNode ) && ( GetExitNode( nSeq ) == gm_nUnplantedNode ) )
- {
- // unplanted - blue
- NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 0, 255, 128, 0 );
- }
- else if ( ( GetEntryNode( nSeq ) == gm_nUnplantedNode ) && ( GetExitNode( nSeq ) == gm_nPlantedNode ) )
- {
- // planting transition - cyan
- NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 255, 128, 0 );
- }
- else if ( ( GetEntryNode( nSeq ) == gm_nPlantedNode ) && ( GetExitNode( nSeq ) == gm_nUnplantedNode ) )
- {
- // unplanting transition - purple
- NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 255, 128, 0 );
- }
- else
- {
- // unknown / other node - red
- Msg( "UNKNOWN: %s\n", GetSequenceName( GetSequence() ) );
- NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 0, 128, 0 );
- }
-
- NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vLeftDir, 255, 0, 0, 50, 0 );
- NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vRightDir, 255, 0, 0, 50, 0 );
- NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vEyeDir, 0, 255, 0, 50, 0 );
- NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 128, 0 );
- }
-
- m_EscortBehavior.DrawDebugGeometryOverlays();
-
- BaseClass::DrawDebugGeometryOverlays();
-}
-
-
-//-----------------------------------------------------------------------------
-// Player has illuminated this NPC with the flashlight
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
-{
- if ( m_bFlashlightInEyes )
- return;
-
- // Ignore the flashlight if it's not shining at my eyes
- if ( PlayerFlashlightOnMyEyes( pPlayer ) )
- {
- //Msg( ">>>> SHINING FLASHLIGHT ON ME\n" );
- m_bFlashlightInEyes = true;
- SetExpression( "scenes/npc/hunter/hunter_eyeclose.vcd" );
- m_flPupilDilateTime = gpGlobals->curtime + 0.2f;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer )
-{
- Vector vecEyes, vecEyeForward, vecPlayerForward;
- GetAttachment( gm_nTopGunAttachment, vecEyes, &vecEyeForward );
- pPlayer->EyeVectors( &vecPlayerForward );
-
- Vector vecToEyes = (vecEyes - pPlayer->EyePosition());
- //float flDist = VectorNormalize( vecToEyes );
-
- float flDot = DotProduct( vecPlayerForward, vecToEyes );
- if ( flDot < 0.98 )
- return false;
-
- // Check facing to ensure we're in front of her
- Vector los = ( pPlayer->EyePosition() - EyePosition() );
- los.z = 0;
- VectorNormalize( los );
- Vector facingDir = EyeDirection2D();
- flDot = DotProduct( los, facingDir );
- return ( flDot > 0.3 );
-}
-
-
-//-----------------------------------------------------------------------------
-// Return a random expression for the specified state to play over
-// the state's expression loop.
-//-----------------------------------------------------------------------------
-const char *CNPC_Hunter::SelectRandomExpressionForState( NPC_STATE state )
-{
- if ( m_bFlashlightInEyes )
- return NULL;
-
- if ( !hunter_random_expressions.GetBool() )
- return NULL;
-
- char *szExpressions[4] =
- {
- "scenes/npc/hunter/hunter_scan.vcd",
- "scenes/npc/hunter/hunter_eyeclose.vcd",
- "scenes/npc/hunter/hunter_roar.vcd",
- "scenes/npc/hunter/hunter_pain.vcd"
- };
-
- int nIndex = random->RandomInt( 0, 3 );
- //Msg( "RANDOM Expression: %s\n", szExpressions[nIndex] );
- return szExpressions[nIndex];
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::PlayExpressionForState( NPC_STATE state )
-{
- if ( m_bFlashlightInEyes )
- {
- return;
- }
-
- BaseClass::PlayExpressionForState( state );
-}
-
-
-//-----------------------------------------------------------------------------
-// TODO: remove if we're not doing striderbuster stuff
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::StriderBusterAttached( CBaseEntity *pAttached )
-{
- // Add another to the list
- m_hAttachedBusters.AddToTail( pAttached );
-
- SetCondition( COND_HUNTER_HIT_BY_STICKYBOMB );
- if (m_hAttachedBusters.Count() == 1)
- {
- EmitSound( "NPC_Hunter.Alert" );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::StriderBusterDetached( CBaseEntity *pAttached )
-{
- int elem = m_hAttachedBusters.Find(pAttached);
- if (elem >= 0)
- {
- m_hAttachedBusters.FastRemove(elem);
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Set direction that the hunter aims his body and eyes (guns).
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::SetAim( const Vector &aimDir, float flInterval )
-{
- QAngle angDir;
- VectorAngles( aimDir, angDir );
- float curPitch = GetPoseParameter( gm_nBodyPitchPoseParam );
- float curYaw = GetPoseParameter( gm_nBodyYawPoseParam );
-
- float newPitch;
- float newYaw;
-
- if ( GetEnemy() )
- {
- // clamp and dampen movement
- newPitch = curPitch + 0.8 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
-
- float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
- newYaw = curYaw + UTIL_AngleDiff( flRelativeYaw, curYaw );
- }
- else
- {
- // Sweep your weapon more slowly if you're not fighting someone
- newPitch = curPitch + 0.6 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
-
- float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
- newYaw = curYaw + 0.6 * UTIL_AngleDiff( flRelativeYaw, curYaw );
- }
-
- newPitch = AngleNormalize( newPitch );
- newYaw = AngleNormalize( newYaw );
-
- //Msg( "pitch=%f, yaw=%f\n", newPitch, newYaw );
-
- SetPoseParameter( gm_nAimPitchPoseParam, 0 );
- SetPoseParameter( gm_nAimYawPoseParam, 0 );
-
- SetPoseParameter( gm_nBodyPitchPoseParam, clamp( newPitch, -45, 45 ) );
- SetPoseParameter( gm_nBodyYawPoseParam, clamp( newYaw, -45, 45 ) );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::RelaxAim( float flInterval )
-{
- float curPitch = GetPoseParameter( gm_nBodyPitchPoseParam );
- float curYaw = GetPoseParameter( gm_nBodyYawPoseParam );
-
- // dampen existing aim
- float newPitch = AngleNormalize( UTIL_ApproachAngle( 0, curPitch, 3 ) );
- float newYaw = AngleNormalize( UTIL_ApproachAngle( 0, curYaw, 2 ) );
-
- SetPoseParameter( gm_nAimPitchPoseParam, 0 );
- SetPoseParameter( gm_nAimYawPoseParam, 0 );
-
- SetPoseParameter( gm_nBodyPitchPoseParam, clamp( newPitch, -45, 45 ) );
- SetPoseParameter( gm_nBodyYawPoseParam, clamp( newYaw, -45, 45 ) );
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_Hunter::UpdateAim()
-{
- if ( !GetModelPtr() || !GetModelPtr()->SequencesAvailable() )
- return;
-
- float flInterval = GetAnimTimeInterval();
-
- // Some activities look bad if we're giving our enemy the stinkeye.
- int eActivity = GetActivity();
-
- if ( GetEnemy() &&
- GetState() != NPC_STATE_SCRIPT &&
- ( eActivity != ACT_HUNTER_CHARGE_CRASH ) &&
- ( eActivity != ACT_HUNTER_CHARGE_HIT ) )
- {
- Vector vecShootOrigin;
-
- vecShootOrigin = Weapon_ShootPosition();
- Vector vecShootDir = GetShootEnemyDir( vecShootOrigin, false );
-
- SetAim( vecShootDir, flInterval );
- }
- else
- {
- RelaxAim( flInterval );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Don't become a ragdoll until we've finished our death anim
-//-----------------------------------------------------------------------------
-bool CNPC_Hunter::CanBecomeRagdoll()
-{
- return ( m_nKillingDamageType & DMG_CRUSH ) ||
- IsCurSchedule( SCHED_DIE, false ) || // Finished playing death anim, time to ragdoll
- IsCurSchedule( SCHED_HUNTER_CHARGE_ENEMY, false ) || // While moving, it looks better to ragdoll instantly
- IsCurSchedule( SCHED_SCRIPTED_RUN, false ) ||
- ( GetActivity() == ACT_WALK ) || ( GetActivity() == ACT_RUN ) ||
- GetCurSchedule() == NULL; // Failsafe
-}
-
-
-//-----------------------------------------------------------------------------
-// Determines the best type of death anim to play based on how we died.
-//-----------------------------------------------------------------------------
-Activity CNPC_Hunter::GetDeathActivity()
-{
- return ACT_DIESIMPLE;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::OnDamage( const CTakeDamageInfo &info )
-{
- if ( info.GetDamage() > 0 && info.GetAttacker()->IsPlayer() &&
- GetFollowTarget() && ( AIGetNumFollowers( GetFollowTarget() ) > 1 ) &&
- ( GetOuter()->GetSquad()->GetSquadSoundWaitTime() <= gpGlobals->curtime ) ) // && !FarFromFollowTarget()
- {
- // Start the clock ticking. We'll return the the strider when the timer elapses.
- m_flTimeEscortReturn = gpGlobals->curtime + random->RandomFloat( 15.0f, 25.0f );
- GetOuter()->GetSquad()->SetSquadSoundWaitTime( m_flTimeEscortReturn + 1.0 ); // prevent others from breaking escort
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::BuildScheduleTestBits()
-{
- BaseClass::BuildScheduleTestBits();
-
- if ( ( m_flTimeEscortReturn != 0 ) && ( gpGlobals->curtime > m_flTimeEscortReturn ) )
- {
- // We're delinquent! Return to strider!
- GetOuter()->ClearCustomInterruptCondition( COND_NEW_ENEMY );
- GetOuter()->ClearCustomInterruptCondition( COND_SEE_ENEMY );
- GetOuter()->ClearCustomInterruptCondition( COND_SEE_HATE );
- GetOuter()->ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
- GetOuter()->ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::CheckBreakEscort()
-{
- if ( m_flTimeEscortReturn != 0 && ( FarFromFollowTarget() || gpGlobals->curtime >= m_flTimeEscortReturn ) )
- {
- if ( FarFromFollowTarget() )
- {
- m_flTimeEscortReturn = gpGlobals->curtime;
- }
- else
- {
- m_flTimeEscortReturn = 0;
- }
- if ( GetOuter()->GetSquad() )
- {
- GetOuter()->GetSquad()->SetSquadSoundWaitTime( gpGlobals->curtime + random->RandomFloat( 5.0f, 12.0f ) );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::GatherConditionsNotActive( void )
-{
- if ( m_bEnabled )
- {
- DistributeFreeHunters();
- }
-
- BaseClass::GatherConditionsNotActive();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::GatherConditions( void )
-{
- m_bEnabled = true;
-
- DistributeFreeHunters();
-
- BaseClass::GatherConditions();
-
- if ( GetEnemy() && GetEnemy()->IsPlayer() && HasCondition( COND_SEE_ENEMY ) )
- {
- if ( GetOuter()->GetSquad()->GetSquadSoundWaitTime() <= gpGlobals->curtime && ((CBasePlayer *)GetEnemy())->IsInAVehicle() )
- {
- m_flTimeEscortReturn = gpGlobals->curtime + random->RandomFloat( 15.0f, 25.0f );
- GetOuter()->GetSquad()->SetSquadSoundWaitTime( m_flTimeEscortReturn + 1.0 ); // prevent others from breaking escort
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CAI_HunterEscortBehavior::ShouldFollow()
-{
- if ( IsStriderBuster( GetEnemy() ) )
- return false;
-
- if ( HasCondition( COND_HEAR_PHYSICS_DANGER ) )
- return false;
-
- if ( m_flTimeEscortReturn <= gpGlobals->curtime )
- {
- return CAI_FollowBehavior::ShouldFollow();
- }
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::BeginScheduleSelection()
-{
- BaseClass::BeginScheduleSelection();
- Assert( m_SavedDistTooFar == GetOuter()->m_flDistTooFar );
- GetOuter()->m_flDistTooFar *= 2;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CAI_HunterEscortBehavior::SelectSchedule()
-{
- if( m_FollowDelay.IsRunning() && !m_FollowDelay.Expired() )
- {
- return FollowCallBaseSelectSchedule();
- }
- return BaseClass::SelectSchedule();
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CAI_HunterEscortBehavior::FollowCallBaseSelectSchedule()
-{
- if ( GetOuter()->GetState() == NPC_STATE_COMBAT )
- {
- return GetOuter()->SelectCombatSchedule();
- }
-
- return BaseClass::FollowCallBaseSelectSchedule();
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::StartTask( const Task_t *pTask )
-{
- switch( pTask->iTask )
- {
- case TASK_MOVE_TO_FOLLOW_POSITION:
- {
- if ( GetEnemy() )
- {
- if ( GetOuter()->OccupyStrategySlot( SQUAD_SLOT_RUN_SHOOT ) )
- {
- if ( GetOuter()->GetSquad()->GetSquadMemberNearestTo( GetEnemy()->GetAbsOrigin() ) == GetOuter() )
- {
- GetOuter()->BeginVolley( NUM_FLECHETTE_VOLLEY_ON_FOLLOW, gpGlobals->curtime + 1.0 + random->RandomFloat( 0, .25 ) + random->RandomFloat( 0, .25 ) );
- }
- else
- {
- GetOuter()->VacateStrategySlot();
- }
- }
- }
- BaseClass::StartTask( pTask );
- break;
- }
-
- default:
- BaseClass::StartTask( pTask );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::RunTask( const Task_t *pTask )
-{
- switch( pTask->iTask )
- {
- case TASK_MOVE_TO_FOLLOW_POSITION:
- {
- if ( !GetFollowTarget() )
- {
- TaskFail( FAIL_NO_TARGET );
- }
- else
- {
- if ( GetEnemy() )
- {
- CNPC_Hunter *pHunter = GetOuter();
- Vector vecEnemyLKP = pHunter->GetEnemyLKP();
- pHunter->AddFacingTarget( pHunter->GetEnemy(), vecEnemyLKP, 1.0, 0.8 );
- bool bVacate = false;
-
- bool bHasSlot = pHunter->HasStrategySlot( SQUAD_SLOT_RUN_SHOOT );
- if ( HasCondition( COND_SEE_ENEMY ) )
- {
- float maxDist = hunter_flechette_max_range.GetFloat() * 3;
- float distSq = ( pHunter->GetAbsOrigin() - pHunter->GetEnemy()->GetAbsOrigin() ).Length2DSqr();
-
- if ( distSq < Square( maxDist ) )
- {
- if ( gpGlobals->curtime >= pHunter->m_flNextFlechetteTime )
- {
- if ( !bHasSlot )
- {
- if ( GetOuter()->OccupyStrategySlot( SQUAD_SLOT_RUN_SHOOT ) )
- {
- if ( GetOuter()->GetSquad()->GetSquadMemberNearestTo( GetEnemy()->GetAbsOrigin() ) == GetOuter() )
- {
- bHasSlot = true;
- }
- else
- {
- GetOuter()->VacateStrategySlot();
- }
- }
- }
-
- if ( bHasSlot )
- {
- // Start the firing sound.
- //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- //if ( controller.SoundGetVolume( pHunter->m_pGunFiringSound ) == 0.0f )
- //{
- // controller.SoundChangeVolume( pHunter->m_pGunFiringSound, 1.0f, 0.0f );
- //}
-
- pHunter->ShootFlechette( GetEnemy(), true );
-
- if ( --pHunter->m_nFlechettesQueued > 0 )
- {
- pHunter->m_flNextFlechetteTime = gpGlobals->curtime + hunter_flechette_delay.GetFloat();
- }
- else
- {
- // Stop the firing sound.
- //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- //controller.SoundChangeVolume( pHunter->m_pGunFiringSound, 0, 0.01f );
-
- bVacate = true;
- pHunter->BeginVolley( NUM_FLECHETTE_VOLLEY_ON_FOLLOW, gpGlobals->curtime + 1.0 + random->RandomFloat( 0, .25 ) + random->RandomFloat( 0, .25 ) );
- }
- }
- }
- }
- else if ( bHasSlot )
- {
- bVacate = true;
- }
- }
- else if ( bHasSlot )
- {
- bVacate = true;
- }
-
- if ( bVacate )
- {
- pHunter->VacateStrategySlot();
- }
- }
-
- if ( m_FollowAttackTimer.Expired() && IsFollowTargetInRange( .8 ))
- {
- m_FollowAttackTimer.Set( 8, 24 );
- TaskComplete();
- }
- else
- {
- BaseClass::RunTask( pTask );
- }
- }
- break;
- }
-
- default:
- BaseClass::RunTask( pTask );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::FindFreeHunters( CUtlVector<CNPC_Hunter *> *pFreeHunters )
-{
- pFreeHunters->EnsureCapacity( g_Hunters.Count() );
- int i;
-
- for ( i = 0; i < g_Hunters.Count(); i++ )
- {
- CNPC_Hunter *pHunter = g_Hunters[i];
- if ( pHunter->IsAlive() && pHunter->m_EscortBehavior.m_bEnabled )
- {
- if ( pHunter->m_EscortBehavior.GetFollowTarget() == NULL || !pHunter->m_EscortBehavior.GetFollowTarget()->IsAlive() )
- {
- pFreeHunters->AddToTail( pHunter);
- }
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::DistributeFreeHunters()
-{
- if ( g_TimeLastDistributeFreeHunters != -1 && gpGlobals->curtime - g_TimeLastDistributeFreeHunters < FREE_HUNTER_DISTRIBUTE_INTERVAL )
- {
- return;
- }
-
- g_TimeLastDistributeFreeHunters = gpGlobals->curtime;
-
- CUtlVector<CNPC_Hunter *> freeHunters;
- int i;
- FindFreeHunters( &freeHunters );
-
- CAI_BaseNPC **ppNPCs = g_AI_Manager.AccessAIs();
- for ( i = 0; i < g_AI_Manager.NumAIs() && freeHunters.Count(); i++ )
- {
- int nToAdd;
- CNPC_Strider *pStrider = ( ppNPCs[i]->IsAlive() ) ? dynamic_cast<CNPC_Strider *>( ppNPCs[i] ) : NULL;
- if ( pStrider && !pStrider->CarriedByDropship() )
- {
- if ( ( nToAdd = 3 - AIGetNumFollowers( pStrider ) ) > 0 )
- {
- for ( int j = freeHunters.Count() - 1; j >= 0 && nToAdd > 0; --j )
- {
- DevMsg( "npc_hunter %d assigned to npc_strider %d\n", freeHunters[j]->entindex(), pStrider->entindex() );
- freeHunters[j]->FollowStrider( pStrider );
- freeHunters.FastRemove( j );
- nToAdd--;
- }
- }
- }
- }
-
- for ( i = 0; i < freeHunters.Count(); i++ )
- {
- //DevMsg( "npc_hunter %d assigned to free_hunters_squad\n", freeHunters[i]->entindex() );
- freeHunters[i]->m_EscortBehavior.SetFollowTarget( NULL );
- freeHunters[i]->AddToSquad( AllocPooledString( "free_hunters_squad" ) );
- }
-
-#if 0
- CBaseEntity *pHunterMaker = gEntList.FindEntityByClassname( NULL, "npc_hunter_maker" ); // TODO: this picks the same one every time!
- if ( pHunterMaker )
- {
- for ( i = 0; i < freeHunters.Count(); i++ )
- {
- freeHunters[i]->m_EscortBehavior.SetFollowTarget( pHunterMaker );
- }
- }
-#endif
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CAI_HunterEscortBehavior::DrawDebugGeometryOverlays()
-{
- if ( !GetFollowTarget() )
- return;
-
- Vector vecFollowPos = GetGoalPosition();
- if ( FarFromFollowTarget() )
- {
- if ( gpGlobals->curtime >= m_flTimeEscortReturn )
- {
- NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 255, 0, 0, 0, true, 0 );
- }
- else
- {
- NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 255, 255, 0, 0, true, 0 );
- }
- }
- else
- {
- NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 0, 255, 0, 0, true, 0 );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool Hunter_IsHunter(CBaseEntity *pEnt)
-{
- return dynamic_cast<CNPC_Hunter *>(pEnt) != NULL;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void Hunter_StriderBusterLaunched( CBaseEntity *pBuster )
-{
- CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
- int nAIs = g_AI_Manager.NumAIs();
-
- for ( int i = 0; i < nAIs; i++ )
- {
- CAI_BaseNPC *pNPC = ppAIs[ i ];
- if ( pNPC && ( pNPC->Classify() == CLASS_COMBINE_HUNTER ) && pNPC->m_lifeState == LIFE_ALIVE )
- {
- if ( !pNPC->GetEnemy() || !IsStriderBuster( pNPC->GetEnemy() ) )
- {
- Vector vecDelta = pNPC->GetAbsOrigin() - pBuster->GetAbsOrigin();
- if ( vecDelta.Length2DSqr() < 9437184.0f ) // 3072 * 3072
- {
- pNPC->SetEnemy( pBuster );
- pNPC->SetState( NPC_STATE_COMBAT );
- pNPC->UpdateEnemyMemory( pBuster, pBuster->GetAbsOrigin() );
-
- // Stop whatever we're doing.
- pNPC->SetCondition( COND_SCHEDULE_DONE );
- }
- }
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void Hunter_StriderBusterAttached( CBaseEntity *pHunter, CBaseEntity *pAttached )
-{
- Assert(dynamic_cast<CNPC_Hunter *>(pHunter));
-
- static_cast<CNPC_Hunter *>(pHunter)->StriderBusterAttached(pAttached);
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void Hunter_StriderBusterDetached( CBaseEntity *pHunter, CBaseEntity *pAttached )
-{
- Assert(dynamic_cast<CNPC_Hunter *>(pHunter));
-
- static_cast<CNPC_Hunter *>(pHunter)->StriderBusterDetached(pAttached);
-}
-
-//-------------------------------------------------------------------------------------------------
-//
-// ep2_outland_12 custom npc makers
-//
-//-------------------------------------------------------------------------------------------------
-
-class CHunterMaker : public CTemplateNPCMaker
-{
- typedef CTemplateNPCMaker BaseClass;
-public:
- void MakeMultipleNPCS( int nNPCs )
- {
- const float MIN_HEALTH_PCT = 0.2;
-
- CUtlVector<CNPC_Hunter *> candidates;
- CUtlVectorFixed<CNPC_Hunter *, 3> freeHunters;
- CAI_HunterEscortBehavior::FindFreeHunters( &candidates );
-
- freeHunters.EnsureCapacity( 3 );
- int i;
-
- for ( i = 0; i < candidates.Count() && freeHunters.Count() < 3; i++ )
- {
- if ( candidates[i]->GetHealth() > candidates[i]->GetMaxHealth() * MIN_HEALTH_PCT )
- {
- freeHunters.AddToTail( candidates[i] );
- }
- }
-
- int nRequested = nNPCs;
- if ( nNPCs < 3 )
- {
- nNPCs = MIN( 3, nNPCs + freeHunters.Count() );
- }
-
- int nSummoned = 0;
- for ( i = 0; i < freeHunters.Count() && nNPCs; i++ )
- {
- freeHunters[i]->m_EscortBehavior.SetFollowTarget( this ); // this will make them not "free"
- freeHunters[i]->SetName( m_iszTemplateName ); // this will force the hunter to get the FollowStrider input
- nNPCs--;
- nSummoned++;
- }
-
- DevMsg( "Requested %d to spawn, Summoning %d free hunters, spawning %d new hunters\n", nRequested, nSummoned, nNPCs );
- if ( nNPCs )
- {
- BaseClass::MakeMultipleNPCS( nNPCs );
- }
- }
-};
-
-LINK_ENTITY_TO_CLASS( npc_hunter_maker, CHunterMaker );
-
-
-//-------------------------------------------------------------------------------------------------
-//
-// Schedules
-//
-//-------------------------------------------------------------------------------------------------
-AI_BEGIN_CUSTOM_NPC( npc_hunter, CNPC_Hunter )
-
- DECLARE_TASK( TASK_HUNTER_AIM )
- DECLARE_TASK( TASK_HUNTER_FIND_DODGE_POSITION )
- DECLARE_TASK( TASK_HUNTER_DODGE )
- DECLARE_TASK( TASK_HUNTER_PRE_RANGE_ATTACK2 )
- DECLARE_TASK( TASK_HUNTER_SHOOT_COMMIT )
- DECLARE_TASK( TASK_HUNTER_ANNOUNCE_FLANK )
- DECLARE_TASK( TASK_HUNTER_BEGIN_FLANK )
- DECLARE_TASK( TASK_HUNTER_STAGGER )
- DECLARE_TASK( TASK_HUNTER_CORNERED_TIMER )
- DECLARE_TASK( TASK_HUNTER_FIND_SIDESTEP_POSITION )
- DECLARE_TASK( TASK_HUNTER_CHARGE )
- DECLARE_TASK( TASK_HUNTER_FINISH_RANGE_ATTACK )
- DECLARE_TASK( TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY )
- DECLARE_TASK( TASK_HUNTER_CHARGE_DELAY )
-
- DECLARE_ACTIVITY( ACT_HUNTER_DEPLOYRA2 )
- DECLARE_ACTIVITY( ACT_HUNTER_DODGER )
- DECLARE_ACTIVITY( ACT_HUNTER_DODGEL )
- DECLARE_ACTIVITY( ACT_HUNTER_GESTURE_SHOOT )
- DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_STICKYBOMB )
- DECLARE_ACTIVITY( ACT_HUNTER_STAGGER )
- DECLARE_ACTIVITY( ACT_DI_HUNTER_MELEE )
- DECLARE_ACTIVITY( ACT_DI_HUNTER_THROW )
- DECLARE_ACTIVITY( ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER )
- DECLARE_ACTIVITY( ACT_HUNTER_ANGRY )
- DECLARE_ACTIVITY( ACT_HUNTER_WALK_ANGRY )
- DECLARE_ACTIVITY( ACT_HUNTER_FOUND_ENEMY )
- DECLARE_ACTIVITY( ACT_HUNTER_FOUND_ENEMY_ACK )
- DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_START )
- DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_RUN )
- DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_STOP )
- DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_CRASH )
- DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_HIT )
- DECLARE_ACTIVITY( ACT_HUNTER_RANGE_ATTACK2_UNPLANTED )
- DECLARE_ACTIVITY( ACT_HUNTER_IDLE_PLANTED )
- DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_N )
- DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_S )
- DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_E )
- DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_W )
-
- DECLARE_INTERACTION( g_interactionHunterFoundEnemy );
-
- DECLARE_SQUADSLOT( SQUAD_SLOT_HUNTER_CHARGE )
- DECLARE_SQUADSLOT( SQUAD_SLOT_HUNTER_FLANK_FIRST )
- DECLARE_SQUADSLOT( SQUAD_SLOT_RUN_SHOOT )
-
- DECLARE_CONDITION( COND_HUNTER_SHOULD_PATROL )
- DECLARE_CONDITION( COND_HUNTER_FORCED_FLANK_ENEMY )
- DECLARE_CONDITION( COND_HUNTER_CAN_CHARGE_ENEMY )
- DECLARE_CONDITION( COND_HUNTER_STAGGERED )
- DECLARE_CONDITION( COND_HUNTER_IS_INDOORS )
- DECLARE_CONDITION( COND_HUNTER_HIT_BY_STICKYBOMB )
- DECLARE_CONDITION( COND_HUNTER_SEE_STRIDERBUSTER )
- DECLARE_CONDITION( COND_HUNTER_FORCED_DODGE )
- DECLARE_CONDITION( COND_HUNTER_INCOMING_VEHICLE )
- DECLARE_CONDITION( COND_HUNTER_NEW_HINTGROUP )
- DECLARE_CONDITION( COND_HUNTER_CANT_PLANT )
- DECLARE_CONDITION( COND_HUNTER_SQUADMATE_FOUND_ENEMY )
-
- DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_LEFT )
- DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_RIGHT )
- DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_BACK )
- DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ANNOUNCE )
- DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ATTACK_LEFT )
- DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ATTACK_RIGHT )
- DECLARE_ANIMEVENT( AE_HUNTER_DIE )
- DECLARE_ANIMEVENT( AE_HUNTER_SPRAY_BLOOD )
- DECLARE_ANIMEVENT( AE_HUNTER_START_EXPRESSION )
- DECLARE_ANIMEVENT( AE_HUNTER_END_EXPRESSION )
-
- //=========================================================
- // Attack (Deploy/shoot/finish)
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_RANGE_ATTACK1,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_HUNTER_SHOOT_COMMIT 0"
- " TASK_RANGE_ATTACK1 0"
- " "
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_LOST_ENEMY"
- " COND_ENEMY_OCCLUDED"
- " COND_WEAPON_SIGHT_OCCLUDED"
- " COND_TOO_CLOSE_TO_ATTACK"
- " COND_TOO_FAR_TO_ATTACK"
- " COND_NOT_FACING_ATTACK"
- )
-
- //=========================================================
- // Attack (Deploy/shoot/finish)
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_RANGE_ATTACK2,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_HUNTER_PRE_RANGE_ATTACK2 0"
- " TASK_HUNTER_SHOOT_COMMIT 0"
- " TASK_RANGE_ATTACK2 0"
- " TASK_HUNTER_FINISH_RANGE_ATTACK 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT 0.4"
- " TASK_WAIT_RANDOM 0.2"
- " "
- " Interrupts"
- " COND_NEW_ENEMY"
- )
-
- //=========================================================
- // Shoot at striderbuster. Distinct from generic range attack
- // because of BuildScheduleTestBits.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_HUNTER_SHOOT_COMMIT 0"
- " TASK_RANGE_ATTACK2 0"
- " "
- " Interrupts"
- )
-
- //=========================================================
- // Shoot at striderbuster with a little latency beforehand
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER_LATENT,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_HUNTER_SHOOT_COMMIT 0"
- " TASK_WAIT 0.2"
- " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_RANGE_ATTACK2"
- " TASK_RANGE_ATTACK2 0"
- " "
- " Interrupts"
- )
-
- //=========================================================
- // Dodge Incoming vehicle
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_DODGE,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_DODGE"
- " TASK_HUNTER_FIND_DODGE_POSITION 0"
- " TASK_HUNTER_DODGE 0"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // Dodge Incoming vehicle
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_FAIL_DODGE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_FACE_ENEMY 0"
- ""
- " Interrupts"
- )
-
- //==================================================
- // > SCHED_HUNTER_CHARGE_ENEMY
- // Rush at my enemy and head-butt them.
- //==================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CHARGE_ENEMY,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_CHARGE_ENEMY"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_ENEMY 0"
- " TASK_HUNTER_CHARGE 0"
- ""
- " Interrupts"
- " COND_TASK_FAILED"
- " COND_ENEMY_DEAD"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_FAIL_CHARGE_ENEMY,
-
- " Tasks"
- " TASK_HUNTER_CHARGE_DELAY 10"
- )
-
- //=========================================================
- // Chase the enemy with intent to claw
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CHASE_ENEMY_MELEE,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
- " TASK_STOP_MOVING 0"
- " TASK_GET_CHASE_PATH_TO_ENEMY 300"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_FACE_ENEMY 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_ENEMY_UNREACHABLE"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- //" COND_TOO_CLOSE_TO_ATTACK"
- " COND_LOST_ENEMY"
- )
-
- //=========================================================
- // Chase my enemy, shoot or claw when possible to do so.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CHASE_ENEMY,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
- " TASK_STOP_MOVING 0"
- " TASK_GET_CHASE_PATH_TO_ENEMY 300"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_FACE_ENEMY 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_ENEMY_UNREACHABLE"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_TOO_CLOSE_TO_ATTACK"
- " COND_LOST_ENEMY"
- )
-
- //=========================================================
- // Move to a flanking position, then shoot if possible.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_FLANK_ENEMY,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
- " TASK_STOP_MOVING 0"
- " TASK_HUNTER_BEGIN_FLANK 0"
- " TASK_GET_FLANK_ARC_PATH_TO_ENEMY_LOS 30"
- " TASK_HUNTER_ANNOUNCE_FLANK 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_FACE_ENEMY 0"
- //" TASK_HUNTER_END_FLANK 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- //" COND_CAN_RANGE_ATTACK1"
- //" COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_ENEMY_DEAD"
- " COND_ENEMY_UNREACHABLE"
- " COND_TOO_CLOSE_TO_ATTACK"
- " COND_LOST_ENEMY"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_COMBAT_FACE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT_FACE_ENEMY 1"
- ""
- " Interrupts"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- )
-
- //=========================================================
- // Like the base class, only don't stop in the middle of
- // swinging if the enemy is killed, hides, or new enemy.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_MELEE_ATTACK1,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_ENEMY 0"
- " TASK_MELEE_ATTACK1 0"
- //" TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_POST_MELEE_WAIT"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // In a fight with nothing to do. Make busy!
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CHANGE_POSITION,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WANDER 720432" // 6 feet to 36 feet
- " TASK_RUN_PATH 0"
- " TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY 0"
- " TASK_STOP_MOVING 0"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_CHANGE_POSITION_FINISH"
- ""
- " Interrupts"
- " COND_ENEMY_DEAD"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- " COND_NEW_ENEMY"
- )
-
- //=========================================================
- // In a fight with nothing to do. Make busy!
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CHANGE_POSITION_FINISH,
-
- " Tasks"
- " TASK_FACE_ENEMY 0"
- " TASK_WAIT_FACE_ENEMY_RANDOM 5"
- ""
- " Interrupts"
- " COND_ENEMY_DEAD"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- " COND_HEAR_DANGER"
- " COND_HEAR_MOVE_AWAY"
- " COND_NEW_ENEMY"
- )
-
- //=========================================================
- // In a fight with nothing to do. Make busy!
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_SIDESTEP,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE" // used because sched_fail includes a one second pause. ick!
- " TASK_STOP_MOVING 0"
- " TASK_HUNTER_FIND_SIDESTEP_POSITION 0"
- " TASK_GET_PATH_TO_SAVEPOSITION 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_FACE_ENEMY 0"
- ""
- " Interrupts"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_PATROL,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WANDER 720432" // 6 feet to 36 feet
- " TASK_WALK_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_REASONABLE 0"
- " TASK_WAIT_RANDOM 3"
- ""
- " Interrupts"
- " COND_ENEMY_DEAD"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_HEAR_DANGER"
- " COND_HEAR_COMBAT"
- " COND_HEAR_PLAYER"
- " COND_HEAR_BULLET_IMPACT"
- " COND_HEAR_MOVE_AWAY"
- " COND_NEW_ENEMY"
- " COND_SEE_ENEMY"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_MELEE_ATTACK2"
- )
-
- //=========================================================
- // Stagger because I got hit by something heavy
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_STAGGER,
-
- " Tasks"
- " TASK_HUNTER_STAGGER 0"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // Run around randomly until we detect an enemy
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_PATROL_RUN,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE"
- " TASK_SET_ROUTE_SEARCH_TIME 5" // Spend 5 seconds trying to build a path if stuck
- " TASK_GET_PATH_TO_RANDOM_NODE 200"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- ""
- " Interrupts"
- " COND_CAN_RANGE_ATTACK1 "
- " COND_CAN_RANGE_ATTACK2 "
- " COND_CAN_MELEE_ATTACK1 "
- " COND_CAN_MELEE_ATTACK2"
- " COND_GIVE_WAY"
- " COND_NEW_ENEMY"
- " COND_HEAR_COMBAT"
- " COND_HEAR_DANGER"
- " COND_HEAR_PLAYER"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_TAKE_COVER_FROM_ENEMY,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_CHASE_ENEMY_MELEE"
- " TASK_HUNTER_CORNERED_TIMER 10.0"
- " TASK_WAIT 0.0"
- // " TASK_SET_TOLERANCE_DISTANCE 24"
- // " TASK_FIND_COVER_FROM_ENEMY 0"
- " TASK_FIND_FAR_NODE_COVER_FROM_ENEMY 200.0"
- " TASK_RUN_PATH 0"
- " TASK_HUNTER_CORNERED_TIMER 0.0"
- // " TASK_CLEAR_FAIL_SCHEDULE 0" // not used because sched_fail includes a one second pause. ick!
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_REMEMBER MEMORY:INCOVER"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_HIDE_UNDER_COVER"
- /*
- " TASK_FACE_ENEMY 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
- " TASK_WAIT 1"
- */
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_HIDE_UNDER_COVER,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE" // used because sched_fail includes a one second pause. ick!
- " TASK_FACE_ENEMY 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
- " TASK_WAIT 1"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- " COND_HAVE_ENEMY_LOS"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_FOUND_ENEMY,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_ENEMY 0"
- " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_HUNTER_FOUND_ENEMY"
- ""
- " Interrupts"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_FOUND_ENEMY_ACK,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WAIT_RANDOM 0.75"
- " TASK_FACE_ENEMY 0"
- " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_HUNTER_FOUND_ENEMY_ACK"
- ""
- " Interrupts"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- )
-
- //=========================================================
- // An empty schedule that immediately bails out, faster than
- // SCHED_FAIL which stops moving and waits for one second.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_FAIL_IMMEDIATE,
-
- " Tasks"
- " TASK_WAIT 0"
-
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_GOTO_HINT,
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_CLEAR_HINTNODE" // used because sched_fail includes a one second pause. ick!
- " TASK_GET_PATH_TO_HINTNODE 1"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_CLEAR_HINTNODE 0"
- ""
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CLEAR_HINTNODE,
- " Tasks"
- " TASK_CLEAR_HINTNODE 0"
- ""
- ""
- " Interrupts"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_SIEGE_STAND,
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_FACE_PLAYER 0"
- " TASK_WAIT 10"
- " TASK_WAIT_RANDOM 2"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_CHANGE_POSITION_SIEGE"
- ""
- ""
- " Interrupts"
- " COND_SEE_PLAYER"
- " COND_NEW_ENEMY"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_HUNTER_CHANGE_POSITION_SIEGE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WANDER 2400480"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_FACE_PLAYER 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- )
-
- // formula is MIN_DIST * 10000 + MAX_DIST
-
-AI_END_CUSTOM_NPC()
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Small, fast version of the strider. Goes where striders cannot, such
+// as into buildings. Best killed with physics objects and explosives.
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "npc_strider.h"
+#include "npc_hunter.h"
+#include "ai_behavior_follow.h"
+#include "ai_moveprobe.h"
+#include "ai_senses.h"
+#include "ai_speech.h"
+#include "ai_task.h"
+#include "ai_default.h"
+#include "ai_schedule.h"
+#include "ai_hull.h"
+#include "ai_baseactor.h"
+#include "ai_waypoint.h"
+#include "ai_link.h"
+#include "ai_hint.h"
+#include "ai_squadslot.h"
+#include "ai_squad.h"
+#include "ai_tacticalservices.h"
+#include "beam_shared.h"
+#include "datacache/imdlcache.h"
+#include "eventqueue.h"
+#include "gib.h"
+#include "globalstate.h"
+#include "hierarchy.h"
+#include "movevars_shared.h"
+#include "npcevent.h"
+#include "saverestore_utlvector.h"
+#include "particle_parse.h"
+#include "te_particlesystem.h"
+#include "sceneentity.h"
+#include "shake.h"
+#include "soundenvelope.h"
+#include "soundent.h"
+#include "SpriteTrail.h"
+#include "IEffects.h"
+#include "engine/IEngineSound.h"
+#include "bone_setup.h"
+#include "studio.h"
+#include "ai_route.h"
+#include "ammodef.h"
+#include "npc_bullseye.h"
+#include "physobj.h"
+#include "ai_memory.h"
+#include "collisionutils.h"
+#include "shot_manipulator.h"
+#include "steamjet.h"
+#include "physics_prop_ragdoll.h"
+#include "vehicle_base.h"
+#include "coordsize.h"
+#include "hl2_shareddefs.h"
+#include "te_effect_dispatch.h"
+#include "beam_flags.h"
+#include "prop_combine_ball.h"
+#include "explode.h"
+#include "weapon_physcannon.h"
+#include "weapon_striderbuster.h"
+#include "monstermaker.h"
+#include "weapon_rpg.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+class CNPC_Hunter;
+
+
+static const char *HUNTER_FLECHETTE_MODEL = "models/weapons/hunter_flechette.mdl";
+
+// Think contexts
+static const char *HUNTER_BLEED_THINK = "HunterBleed";
+static const char *HUNTER_ZAP_THINK = "HunterZap";
+static const char *HUNTER_JOSTLE_VEHICLE_THINK = "HunterJostle";
+
+
+ConVar sk_hunter_health( "sk_hunter_health", "210" );
+
+// Melee attacks
+ConVar sk_hunter_dmg_one_slash( "sk_hunter_dmg_one_slash", "20" );
+ConVar sk_hunter_dmg_charge( "sk_hunter_dmg_charge", "20" );
+
+// Flechette volley attack
+ConVar hunter_flechette_max_range( "hunter_flechette_max_range", "1200" );
+ConVar hunter_flechette_min_range( "hunter_flechette_min_range", "100" );
+ConVar hunter_flechette_volley_size( "hunter_flechette_volley_size", "8" );
+ConVar hunter_flechette_speed( "hunter_flechette_speed", "2000" );
+ConVar sk_hunter_dmg_flechette( "sk_hunter_dmg_flechette", "4.0" );
+ConVar sk_hunter_flechette_explode_dmg( "sk_hunter_flechette_explode_dmg", "12.0" );
+ConVar sk_hunter_flechette_explode_radius( "sk_hunter_flechette_explode_radius", "128.0" );
+ConVar hunter_flechette_explode_delay( "hunter_flechette_explode_delay", "2.5" );
+ConVar hunter_flechette_delay( "hunter_flechette_delay", "0.1" );
+ConVar hunter_first_flechette_delay( "hunter_first_flechette_delay", "0.5" );
+ConVar hunter_flechette_max_concurrent_volleys( "hunter_flechette_max_concurrent_volleys", "2" );
+ConVar hunter_flechette_volley_start_min_delay( "hunter_flechette_volley_start_min_delay", ".25" );
+ConVar hunter_flechette_volley_start_max_delay( "hunter_flechette_volley_start_max_delay", ".95" );
+ConVar hunter_flechette_volley_end_min_delay( "hunter_flechette_volley_end_min_delay", "1" );
+ConVar hunter_flechette_volley_end_max_delay( "hunter_flechette_volley_end_max_delay", "2" );
+ConVar hunter_flechette_test( "hunter_flechette_test", "0" );
+ConVar hunter_clamp_shots( "hunter_clamp_shots", "1" );
+ConVar hunter_cheap_explosions( "hunter_cheap_explosions", "1" );
+
+// Damage received
+ConVar sk_hunter_bullet_damage_scale( "sk_hunter_bullet_damage_scale", "0.6" );
+ConVar sk_hunter_charge_damage_scale( "sk_hunter_charge_damage_scale", "2.0" );
+ConVar sk_hunter_buckshot_damage_scale( "sk_hunter_buckshot_damage_scale", "0.5" );
+ConVar sk_hunter_vehicle_damage_scale( "sk_hunter_vehicle_damage_scale", "2.2" );
+ConVar sk_hunter_dmg_from_striderbuster( "sk_hunter_dmg_from_striderbuster", "150" );
+ConVar sk_hunter_citizen_damage_scale( "sk_hunter_citizen_damage_scale", "0.3" );
+
+ConVar hunter_allow_dissolve( "hunter_allow_dissolve", "1" );
+ConVar hunter_random_expressions( "hunter_random_expressions", "0" );
+ConVar hunter_show_weapon_los_z( "hunter_show_weapon_los_z", "0" );
+ConVar hunter_show_weapon_los_condition( "hunter_show_weapon_los_condition", "0" );
+
+ConVar hunter_melee_delay( "hunter_melee_delay", "2.0" );
+
+// Bullrush charge.
+ConVar hunter_charge( "hunter_charge", "1" );
+ConVar hunter_charge_min_delay( "hunter_charge_min_delay", "10.0" );
+ConVar hunter_charge_pct( "hunter_charge_pct", "25" );
+ConVar hunter_charge_test( "hunter_charge_test", "0" );
+
+// Vehicle dodging.
+ConVar hunter_dodge_warning( "hunter_dodge_warning", "1.1" );
+ConVar hunter_dodge_warning_width( "hunter_dodge_warning_width", "180" );
+ConVar hunter_dodge_warning_cone( "hunter_dodge_warning_cone", ".5" );
+ConVar hunter_dodge_debug( "hunter_dodge_debug", "0" );
+
+// Jostle vehicles when hit by them
+ConVar hunter_jostle_car_min_speed( "hunter_jostle_car_min_speed", "100" ); // If hit by a car going at least this fast, jostle the car
+ConVar hunter_jostle_car_max_speed( "hunter_jostle_car_max_speed", "600" ); // Used for determining jostle scale
+
+ConVar hunter_free_knowledge( "hunter_free_knowledge", "10.0" );
+ConVar hunter_plant_adjust_z( "hunter_plant_adjust_z", "12" );
+
+ConVar hunter_disable_patrol( "hunter_disable_patrol", "0" );
+
+// Dealing with striderbusters
+ConVar hunter_hate_held_striderbusters( "hunter_hate_held_striderbusters", "1" );
+ConVar hunter_hate_thrown_striderbusters( "hunter_hate_thrown_striderbusters", "1" );
+ConVar hunter_hate_attached_striderbusters( "hunter_hate_attached_striderbusters", "1" );
+ConVar hunter_hate_held_striderbusters_delay( "hunter_hate_held_striderbusters_delay", "0.5" );
+ConVar hunter_hate_held_striderbusters_tolerance( "hunter_hate_held_striderbusters_tolerance", "2000.0" );
+ConVar hunter_hate_thrown_striderbusters_tolerance( "hunter_hate_thrown_striderbusters_tolerance", "300.0" );
+ConVar hunter_seek_thrown_striderbusters_tolerance( "hunter_seek_thrown_striderbusters_tolerance", "400.0" );
+ConVar hunter_retreat_striderbusters( "hunter_retreat_striderbusters", "1", FCVAR_NONE, "If true, the hunter will retreat when a buster is glued to him." );
+
+ConVar hunter_allow_nav_jump( "hunter_allow_nav_jump", "0" );
+ConVar g_debug_hunter_charge( "g_debug_hunter_charge", "0" );
+
+ConVar hunter_stand_still( "hunter_stand_still", "0" ); // used for debugging, keeps them rooted in place
+
+ConVar hunter_siege_frequency( "hunter_siege_frequency", "12" );
+
+#define HUNTER_FOV_DOT 0.0 // 180 degree field of view
+#define HUNTER_CHARGE_MIN 256
+#define HUNTER_CHARGE_MAX 1024
+#define HUNTER_FACE_ENEMY_DIST 512.0f
+#define HUNTER_MELEE_REACH 80
+#define HUNTER_BLOOD_LEFT_FOOT 0
+#define HUNTER_IGNORE_ENEMY_TIME 5 // How long the hunter will ignore another enemy when distracted by the player.
+
+#define HUNTER_FACING_DOT 0.8 // The angle within which we start shooting
+#define HUNTER_SHOOT_MAX_YAW_DEG 60.0f // Once shooting, clamp to +/- these degrees of yaw deflection as our target moves
+#define HUNTER_SHOOT_MAX_YAW_COS 0.5f // The cosine of the above angle
+
+#define HUNTER_FLECHETTE_WARN_TIME 1.0f
+
+#define HUNTER_SEE_ENEMY_TIME_INVALID -1
+
+#define NUM_FLECHETTE_VOLLEY_ON_FOLLOW 4
+
+#define HUNTER_SIEGE_MAX_DIST_MODIFIER 2.0f
+
+//-----------------------------------------------------------------------------
+// Animation events
+//-----------------------------------------------------------------------------
+int AE_HUNTER_FOOTSTEP_LEFT;
+int AE_HUNTER_FOOTSTEP_RIGHT;
+int AE_HUNTER_FOOTSTEP_BACK;
+int AE_HUNTER_MELEE_ANNOUNCE;
+int AE_HUNTER_MELEE_ATTACK_LEFT;
+int AE_HUNTER_MELEE_ATTACK_RIGHT;
+int AE_HUNTER_DIE;
+int AE_HUNTER_SPRAY_BLOOD;
+int AE_HUNTER_START_EXPRESSION;
+int AE_HUNTER_END_EXPRESSION;
+
+
+//-----------------------------------------------------------------------------
+// Interactions.
+//-----------------------------------------------------------------------------
+int g_interactionHunterFoundEnemy = 0;
+
+
+//-----------------------------------------------------------------------------
+// Local stuff.
+//-----------------------------------------------------------------------------
+static string_t s_iszStriderClassname;
+static string_t s_iszStriderBusterClassname;
+static string_t s_iszMagnadeClassname;
+static string_t s_iszPhysPropClassname;
+static string_t s_iszHuntersToRunOver;
+
+
+//-----------------------------------------------------------------------------
+// Custom Activities
+//-----------------------------------------------------------------------------
+Activity ACT_HUNTER_DEPLOYRA2;
+Activity ACT_HUNTER_DODGER;
+Activity ACT_HUNTER_DODGEL;
+Activity ACT_HUNTER_GESTURE_SHOOT;
+Activity ACT_HUNTER_FLINCH_STICKYBOMB;
+Activity ACT_HUNTER_STAGGER;
+Activity ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER;
+Activity ACT_DI_HUNTER_MELEE;
+Activity ACT_DI_HUNTER_THROW;
+Activity ACT_HUNTER_ANGRY;
+Activity ACT_HUNTER_WALK_ANGRY;
+Activity ACT_HUNTER_FOUND_ENEMY;
+Activity ACT_HUNTER_FOUND_ENEMY_ACK;
+Activity ACT_HUNTER_CHARGE_START;
+Activity ACT_HUNTER_CHARGE_RUN;
+Activity ACT_HUNTER_CHARGE_STOP;
+Activity ACT_HUNTER_CHARGE_CRASH;
+Activity ACT_HUNTER_CHARGE_HIT;
+Activity ACT_HUNTER_RANGE_ATTACK2_UNPLANTED;
+Activity ACT_HUNTER_IDLE_PLANTED;
+Activity ACT_HUNTER_FLINCH_N;
+Activity ACT_HUNTER_FLINCH_S;
+Activity ACT_HUNTER_FLINCH_E;
+Activity ACT_HUNTER_FLINCH_W;
+
+
+//-----------------------------------------------------------------------------
+// Squad slots
+//-----------------------------------------------------------------------------
+enum SquadSlot_t
+{
+ SQUAD_SLOT_HUNTER_CHARGE = LAST_SHARED_SQUADSLOT,
+ SQUAD_SLOT_HUNTER_FLANK_FIRST,
+ SQUAD_SLOT_HUNTER_FLANK_LAST = SQUAD_SLOT_HUNTER_FLANK_FIRST,
+ SQUAD_SLOT_RUN_SHOOT,
+};
+
+#define HUNTER_FOLLOW_DISTANCE 2000.0f
+#define HUNTER_FOLLOW_DISTANCE_SQR (HUNTER_FOLLOW_DISTANCE * HUNTER_FOLLOW_DISTANCE)
+
+#define HUNTER_RUNDOWN_SQUADDATA 0
+
+
+//-----------------------------------------------------------------------------
+// We're doing this quite a lot, so this makes the check a lot faster since
+// we don't have to compare strings.
+//-----------------------------------------------------------------------------
+bool IsStriderBuster( CBaseEntity *pEntity )
+{
+ if ( !pEntity )
+ return false;
+
+ if( pEntity->m_iClassname == s_iszStriderBusterClassname ||
+ pEntity->m_iClassname == s_iszMagnadeClassname)
+ return true;
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool HateThisStriderBuster( CBaseEntity *pTarget )
+{
+ if ( StriderBuster_WasKnockedOffStrider(pTarget) )
+ return false;
+
+ if ( pTarget->VPhysicsGetObject() )
+ {
+ if ( hunter_hate_held_striderbusters.GetBool() ||
+ hunter_hate_thrown_striderbusters.GetBool() ||
+ hunter_hate_attached_striderbusters.GetBool() )
+ {
+ if ( ( pTarget->VPhysicsGetObject()->GetGameFlags() & ( FVPHYSICS_PLAYER_HELD | FVPHYSICS_WAS_THROWN ) ) )
+ {
+ return true;
+ }
+
+ if ( StriderBuster_IsAttachedStriderBuster( pTarget ) )
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// The hunter can fire a volley of explosive flechettes.
+//-----------------------------------------------------------------------------
+static const char *s_szHunterFlechetteBubbles = "HunterFlechetteBubbles";
+static const char *s_szHunterFlechetteSeekThink = "HunterFlechetteSeekThink";
+static const char *s_szHunterFlechetteDangerSoundThink = "HunterFlechetteDangerSoundThink";
+static const char *s_szHunterFlechetteSpriteTrail = "sprites/bluelaser1.vmt";
+static int s_nHunterFlechetteImpact = -2;
+static int s_nFlechetteFuseAttach = -1;
+
+#define FLECHETTE_AIR_VELOCITY 2500
+
+class CHunterFlechette : public CPhysicsProp, public IParentPropInteraction
+{
+ DECLARE_CLASS( CHunterFlechette, CPhysicsProp );
+
+public:
+
+ CHunterFlechette();
+ ~CHunterFlechette();
+
+ Class_T Classify() { return CLASS_NONE; }
+
+ bool WasThrownBack()
+ {
+ return m_bThrownBack;
+ }
+
+public:
+
+ void Spawn();
+ void Activate();
+ void Precache();
+ void Shoot( Vector &vecVelocity, bool bBright );
+ void SetSeekTarget( CBaseEntity *pTargetEntity );
+ void Explode();
+
+ bool CreateVPhysics();
+
+ unsigned int PhysicsSolidMaskForEntity() const;
+ static CHunterFlechette *FlechetteCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner = NULL );
+
+ // IParentPropInteraction
+ void OnParentCollisionInteraction( parentCollisionInteraction_t eType, int index, gamevcollisionevent_t *pEvent );
+ void OnParentPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason );
+
+protected:
+
+ void SetupGlobalModelData();
+
+ void StickTo( CBaseEntity *pOther, trace_t &tr );
+
+ void BubbleThink();
+ void DangerSoundThink();
+ void ExplodeThink();
+ void DopplerThink();
+ void SeekThink();
+
+ bool CreateSprites( bool bBright );
+
+ void FlechetteTouch( CBaseEntity *pOther );
+
+ Vector m_vecShootPosition;
+ EHANDLE m_hSeekTarget;
+ bool m_bThrownBack;
+
+ DECLARE_DATADESC();
+ //DECLARE_SERVERCLASS();
+};
+
+LINK_ENTITY_TO_CLASS( hunter_flechette, CHunterFlechette );
+
+BEGIN_DATADESC( CHunterFlechette )
+
+ DEFINE_THINKFUNC( BubbleThink ),
+ DEFINE_THINKFUNC( DangerSoundThink ),
+ DEFINE_THINKFUNC( ExplodeThink ),
+ DEFINE_THINKFUNC( DopplerThink ),
+ DEFINE_THINKFUNC( SeekThink ),
+
+ DEFINE_ENTITYFUNC( FlechetteTouch ),
+
+ DEFINE_FIELD( m_vecShootPosition, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_hSeekTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bThrownBack, FIELD_BOOLEAN ),
+
+END_DATADESC()
+
+//IMPLEMENT_SERVERCLASS_ST( CHunterFlechette, DT_HunterFlechette )
+//END_SEND_TABLE()
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CHunterFlechette *CHunterFlechette::FlechetteCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner )
+{
+ // Create a new entity with CHunterFlechette private data
+ CHunterFlechette *pFlechette = (CHunterFlechette *)CreateEntityByName( "hunter_flechette" );
+ UTIL_SetOrigin( pFlechette, vecOrigin );
+ pFlechette->SetAbsAngles( angAngles );
+ pFlechette->Spawn();
+ pFlechette->Activate();
+ pFlechette->SetOwnerEntity( pentOwner );
+
+ return pFlechette;
+}
+
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+void CC_Hunter_Shoot_Flechette( const CCommand& args )
+{
+ MDLCACHE_CRITICAL_SECTION();
+
+ bool allowPrecache = CBaseEntity::IsPrecacheAllowed();
+ CBaseEntity::SetAllowPrecache( true );
+
+ CBasePlayer *pPlayer = UTIL_GetCommandClient();
+
+ QAngle angEye = pPlayer->EyeAngles();
+ CHunterFlechette *entity = CHunterFlechette::FlechetteCreate( pPlayer->EyePosition(), angEye, pPlayer );
+ if ( entity )
+ {
+ entity->Precache();
+ DispatchSpawn( entity );
+
+ // Shoot the flechette.
+ Vector forward;
+ pPlayer->EyeVectors( &forward );
+ forward *= 2000.0f;
+ entity->Shoot( forward, false );
+ }
+
+ CBaseEntity::SetAllowPrecache( allowPrecache );
+}
+
+static ConCommand ent_create("hunter_shoot_flechette", CC_Hunter_Shoot_Flechette, "Fires a hunter flechette where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT);
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CHunterFlechette::CHunterFlechette()
+{
+ UseClientSideAnimation();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CHunterFlechette::~CHunterFlechette()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// If set, the flechette will seek unerringly toward the target as it flies.
+//-----------------------------------------------------------------------------
+void CHunterFlechette::SetSeekTarget( CBaseEntity *pTargetEntity )
+{
+ if ( pTargetEntity )
+ {
+ m_hSeekTarget = pTargetEntity;
+ SetContextThink( &CHunterFlechette::SeekThink, gpGlobals->curtime, s_szHunterFlechetteSeekThink );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CHunterFlechette::CreateVPhysics()
+{
+ // Create the object in the physics system
+ VPhysicsInitNormal( SOLID_BBOX, FSOLID_NOT_STANDABLE, false );
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+unsigned int CHunterFlechette::PhysicsSolidMaskForEntity() const
+{
+ return ( BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX ) & ~CONTENTS_GRATE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Called from CPropPhysics code when we're attached to a physics object.
+//-----------------------------------------------------------------------------
+void CHunterFlechette::OnParentCollisionInteraction( parentCollisionInteraction_t eType, int index, gamevcollisionevent_t *pEvent )
+{
+ if ( eType == COLLISIONINTER_PARENT_FIRST_IMPACT )
+ {
+ m_bThrownBack = true;
+ Explode();
+ }
+}
+
+void CHunterFlechette::OnParentPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
+{
+ m_bThrownBack = true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CHunterFlechette::CreateSprites( bool bBright )
+{
+ if ( bBright )
+ {
+ DispatchParticleEffect( "hunter_flechette_trail_striderbuster", PATTACH_ABSORIGIN_FOLLOW, this );
+ }
+ else
+ {
+ DispatchParticleEffect( "hunter_flechette_trail", PATTACH_ABSORIGIN_FOLLOW, this );
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::Spawn()
+{
+ Precache( );
+
+ SetModel( HUNTER_FLECHETTE_MODEL );
+ SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
+ UTIL_SetSize( this, -Vector(1,1,1), Vector(1,1,1) );
+ SetSolid( SOLID_BBOX );
+ SetGravity( 0.05f );
+ SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
+
+ // Make sure we're updated if we're underwater
+ UpdateWaterState();
+
+ SetTouch( &CHunterFlechette::FlechetteTouch );
+
+ // Make us glow until we've hit the wall
+ m_nSkin = 1;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::Activate()
+{
+ BaseClass::Activate();
+ SetupGlobalModelData();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::SetupGlobalModelData()
+{
+ if ( s_nHunterFlechetteImpact == -2 )
+ {
+ s_nHunterFlechetteImpact = LookupSequence( "impact" );
+ s_nFlechetteFuseAttach = LookupAttachment( "attach_fuse" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::Precache()
+{
+ PrecacheModel( HUNTER_FLECHETTE_MODEL );
+ PrecacheModel( "sprites/light_glow02_noz.vmt" );
+
+ PrecacheScriptSound( "NPC_Hunter.FlechetteNearmiss" );
+ PrecacheScriptSound( "NPC_Hunter.FlechetteHitBody" );
+ PrecacheScriptSound( "NPC_Hunter.FlechetteHitWorld" );
+ PrecacheScriptSound( "NPC_Hunter.FlechettePreExplode" );
+ PrecacheScriptSound( "NPC_Hunter.FlechetteExplode" );
+
+ PrecacheParticleSystem( "hunter_flechette_trail_striderbuster" );
+ PrecacheParticleSystem( "hunter_flechette_trail" );
+ PrecacheParticleSystem( "hunter_projectile_explosion_1" );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::StickTo( CBaseEntity *pOther, trace_t &tr )
+{
+ EmitSound( "NPC_Hunter.FlechetteHitWorld" );
+
+ SetMoveType( MOVETYPE_NONE );
+
+ if ( !pOther->IsWorld() )
+ {
+ SetParent( pOther );
+ SetSolid( SOLID_NONE );
+ SetSolidFlags( FSOLID_NOT_SOLID );
+ }
+
+ // Do an impact effect.
+ //Vector vecDir = GetAbsVelocity();
+ //float speed = VectorNormalize( vecDir );
+
+ //Vector vForward;
+ //AngleVectors( GetAbsAngles(), &vForward );
+ //VectorNormalize ( vForward );
+
+ //CEffectData data;
+ //data.m_vOrigin = tr.endpos;
+ //data.m_vNormal = vForward;
+ //data.m_nEntIndex = 0;
+ //DispatchEffect( "BoltImpact", data );
+
+ Vector vecVelocity = GetAbsVelocity();
+ bool bAttachedToBuster = StriderBuster_OnFlechetteAttach( pOther, vecVelocity );
+
+ SetTouch( NULL );
+
+ // We're no longer flying. Stop checking for water volumes.
+ SetContextThink( NULL, 0, s_szHunterFlechetteBubbles );
+
+ // Stop seeking.
+ m_hSeekTarget = NULL;
+ SetContextThink( NULL, 0, s_szHunterFlechetteSeekThink );
+
+ // Get ready to explode.
+ if ( !bAttachedToBuster )
+ {
+ SetThink( &CHunterFlechette::DangerSoundThink );
+ SetNextThink( gpGlobals->curtime + (hunter_flechette_explode_delay.GetFloat() - HUNTER_FLECHETTE_WARN_TIME) );
+ }
+ else
+ {
+ DangerSoundThink();
+ }
+
+ // Play our impact animation.
+ ResetSequence( s_nHunterFlechetteImpact );
+
+ static int s_nImpactCount = 0;
+ s_nImpactCount++;
+ if ( s_nImpactCount & 0x01 )
+ {
+ UTIL_ImpactTrace( &tr, DMG_BULLET );
+
+ // Shoot some sparks
+ if ( UTIL_PointContents( GetAbsOrigin() ) != CONTENTS_WATER)
+ {
+ g_pEffects->Sparks( GetAbsOrigin() );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::FlechetteTouch( CBaseEntity *pOther )
+{
+ if ( pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS | FSOLID_TRIGGER) )
+ {
+ // Some NPCs are triggers that can take damage (like antlion grubs). We should hit them.
+ if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) )
+ return;
+ }
+
+ if ( FClassnameIs( pOther, "hunter_flechette" ) )
+ return;
+
+ trace_t tr;
+ tr = BaseClass::GetTouchTrace();
+
+ if ( pOther->m_takedamage != DAMAGE_NO )
+ {
+ Vector vecNormalizedVel = GetAbsVelocity();
+
+ ClearMultiDamage();
+ VectorNormalize( vecNormalizedVel );
+
+ float flDamage = sk_hunter_dmg_flechette.GetFloat();
+ CBreakable *pBreak = dynamic_cast <CBreakable *>(pOther);
+ if ( pBreak && ( pBreak->GetMaterialType() == matGlass ) )
+ {
+ flDamage = MAX( pOther->GetHealth(), flDamage );
+ }
+
+ CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), flDamage, DMG_DISSOLVE | DMG_NEVERGIB );
+ CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f );
+ dmgInfo.SetDamagePosition( tr.endpos );
+ pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr );
+
+ ApplyMultiDamage();
+
+ // Keep going through breakable glass.
+ if ( pOther->GetCollisionGroup() == COLLISION_GROUP_BREAKABLE_GLASS )
+ return;
+
+ SetAbsVelocity( Vector( 0, 0, 0 ) );
+
+ // play body "thwack" sound
+ EmitSound( "NPC_Hunter.FlechetteHitBody" );
+
+ StopParticleEffects( this );
+
+ Vector vForward;
+ AngleVectors( GetAbsAngles(), &vForward );
+ VectorNormalize ( vForward );
+
+ trace_t tr2;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vForward * 128, MASK_BLOCKLOS, pOther, COLLISION_GROUP_NONE, &tr2 );
+
+ if ( tr2.fraction != 1.0f )
+ {
+ //NDebugOverlay::Box( tr2.endpos, Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 255, 0, 0, 10 );
+ //NDebugOverlay::Box( GetAbsOrigin(), Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 0, 255, 0, 10 );
+
+ if ( tr2.m_pEnt == NULL || ( tr2.m_pEnt && tr2.m_pEnt->GetMoveType() == MOVETYPE_NONE ) )
+ {
+ CEffectData data;
+
+ data.m_vOrigin = tr2.endpos;
+ data.m_vNormal = vForward;
+ data.m_nEntIndex = tr2.fraction != 1.0f;
+
+ //DispatchEffect( "BoltImpact", data );
+ }
+ }
+
+ if ( ( ( pOther->GetMoveType() == MOVETYPE_VPHYSICS ) || ( pOther->GetMoveType() == MOVETYPE_PUSH ) ) && ( ( pOther->GetHealth() > 0 ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) ) )
+ {
+ CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>( pOther );
+ if ( pProp )
+ {
+ pProp->SetInteraction( PROPINTER_PHYSGUN_NOTIFY_CHILDREN );
+ }
+
+ // We hit a physics object that survived the impact. Stick to it.
+ StickTo( pOther, tr );
+ }
+ else
+ {
+ SetTouch( NULL );
+ SetThink( NULL );
+ SetContextThink( NULL, 0, s_szHunterFlechetteBubbles );
+
+ UTIL_Remove( this );
+ }
+ }
+ else
+ {
+ // See if we struck the world
+ if ( pOther->GetMoveType() == MOVETYPE_NONE && !( tr.surface.flags & SURF_SKY ) )
+ {
+ // We hit a physics object that survived the impact. Stick to it.
+ StickTo( pOther, tr );
+ }
+ else if( pOther->GetMoveType() == MOVETYPE_PUSH && FClassnameIs(pOther, "func_breakable") )
+ {
+ // We hit a func_breakable, stick to it.
+ // The MOVETYPE_PUSH is a micro-optimization to cut down on the classname checks.
+ StickTo( pOther, tr );
+ }
+ else
+ {
+ // Put a mark unless we've hit the sky
+ if ( ( tr.surface.flags & SURF_SKY ) == false )
+ {
+ UTIL_ImpactTrace( &tr, DMG_BULLET );
+ }
+
+ UTIL_Remove( this );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Fixup flechette position when seeking towards a striderbuster.
+//-----------------------------------------------------------------------------
+void CHunterFlechette::SeekThink()
+{
+ if ( m_hSeekTarget )
+ {
+ Vector vecBodyTarget = m_hSeekTarget->BodyTarget( GetAbsOrigin() );
+
+ Vector vecClosest;
+ CalcClosestPointOnLineSegment( GetAbsOrigin(), m_vecShootPosition, vecBodyTarget, vecClosest, NULL );
+
+ Vector vecDelta = vecBodyTarget - m_vecShootPosition;
+ VectorNormalize( vecDelta );
+
+ QAngle angShoot;
+ VectorAngles( vecDelta, angShoot );
+
+ float flSpeed = hunter_flechette_speed.GetFloat();
+ if ( !flSpeed )
+ {
+ flSpeed = 2500.0f;
+ }
+
+ Vector vecVelocity = vecDelta * flSpeed;
+ Teleport( &vecClosest, &angShoot, &vecVelocity );
+
+ SetNextThink( gpGlobals->curtime, s_szHunterFlechetteSeekThink );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Play a near miss sound as we travel past the player.
+//-----------------------------------------------------------------------------
+void CHunterFlechette::DopplerThink()
+{
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ if ( !pPlayer )
+ return;
+
+ Vector vecVelocity = GetAbsVelocity();
+ VectorNormalize( vecVelocity );
+
+ float flMyDot = DotProduct( vecVelocity, GetAbsOrigin() );
+ float flPlayerDot = DotProduct( vecVelocity, pPlayer->GetAbsOrigin() );
+
+ if ( flPlayerDot <= flMyDot )
+ {
+ EmitSound( "NPC_Hunter.FlechetteNearMiss" );
+
+ // We've played the near miss sound and we're not seeking. Stop thinking.
+ SetThink( NULL );
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Think every 0.1 seconds to make bubbles if we're flying through water.
+//-----------------------------------------------------------------------------
+void CHunterFlechette::BubbleThink()
+{
+ SetNextThink( gpGlobals->curtime + 0.1f, s_szHunterFlechetteBubbles );
+
+ if ( GetWaterLevel() == 0 )
+ return;
+
+ UTIL_BubbleTrail( GetAbsOrigin() - GetAbsVelocity() * 0.1f, GetAbsOrigin(), 5 );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::Shoot( Vector &vecVelocity, bool bBrightFX )
+{
+ CreateSprites( bBrightFX );
+
+ m_vecShootPosition = GetAbsOrigin();
+
+ SetAbsVelocity( vecVelocity );
+
+ SetThink( &CHunterFlechette::DopplerThink );
+ SetNextThink( gpGlobals->curtime );
+
+ SetContextThink( &CHunterFlechette::BubbleThink, gpGlobals->curtime + 0.1, s_szHunterFlechetteBubbles );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::DangerSoundThink()
+{
+ EmitSound( "NPC_Hunter.FlechettePreExplode" );
+
+ CSoundEnt::InsertSound( SOUND_DANGER|SOUND_CONTEXT_EXCLUDE_COMBINE, GetAbsOrigin(), 150.0f, 0.5, this );
+ SetThink( &CHunterFlechette::ExplodeThink );
+ SetNextThink( gpGlobals->curtime + HUNTER_FLECHETTE_WARN_TIME );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::ExplodeThink()
+{
+ Explode();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHunterFlechette::Explode()
+{
+ SetSolid( SOLID_NONE );
+
+ // Don't catch self in own explosion!
+ m_takedamage = DAMAGE_NO;
+
+ EmitSound( "NPC_Hunter.FlechetteExplode" );
+
+ // Move the explosion effect to the tip to reduce intersection with the world.
+ Vector vecFuse;
+ GetAttachment( s_nFlechetteFuseAttach, vecFuse );
+ DispatchParticleEffect( "hunter_projectile_explosion_1", vecFuse, GetAbsAngles(), NULL );
+
+ int nDamageType = DMG_DISSOLVE;
+
+ // Perf optimization - only every other explosion makes a physics force. This is
+ // hardly noticeable since flechettes usually explode in clumps.
+ static int s_nExplosionCount = 0;
+ s_nExplosionCount++;
+ if ( ( s_nExplosionCount & 0x01 ) && hunter_cheap_explosions.GetBool() )
+ {
+ nDamageType |= DMG_PREVENT_PHYSICS_FORCE;
+ }
+
+ RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), sk_hunter_flechette_explode_dmg.GetFloat(), nDamageType ), GetAbsOrigin(), sk_hunter_flechette_explode_radius.GetFloat(), CLASS_NONE, NULL );
+
+ AddEffects( EF_NODRAW );
+
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+
+//-----------------------------------------------------------------------------
+// Calculate & apply damage & force for a charge to a target.
+// Done outside of the hunter because we need to do this inside a trace filter.
+//-----------------------------------------------------------------------------
+void Hunter_ApplyChargeDamage( CBaseEntity *pHunter, CBaseEntity *pTarget, float flDamage )
+{
+ Vector attackDir = ( pTarget->WorldSpaceCenter() - pHunter->WorldSpaceCenter() );
+ VectorNormalize( attackDir );
+ Vector offset = RandomVector( -32, 32 ) + pTarget->WorldSpaceCenter();
+
+ // Generate enough force to make a 75kg guy move away at 700 in/sec
+ Vector vecForce = attackDir * ImpulseScale( 75, 700 );
+
+ // Deal the damage
+ CTakeDamageInfo info( pHunter, pHunter, vecForce, offset, flDamage, DMG_CLUB );
+ pTarget->TakeDamage( info );
+}
+
+
+//-----------------------------------------------------------------------------
+// A simple trace filter class to skip small moveable physics objects
+//-----------------------------------------------------------------------------
+class CHunterTraceFilterSkipPhysics : public CTraceFilter
+{
+public:
+ // It does have a base, but we'll never network anything below here..
+ DECLARE_CLASS_NOBASE( CHunterTraceFilterSkipPhysics );
+
+ CHunterTraceFilterSkipPhysics( const IHandleEntity *passentity, int collisionGroup, float minMass )
+ : m_pPassEnt(passentity), m_collisionGroup(collisionGroup), m_minMass(minMass)
+ {
+ }
+ virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
+ {
+ if ( !StandardFilterRules( pHandleEntity, contentsMask ) )
+ return false;
+
+ if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) )
+ return false;
+
+ // Don't test if the game code tells us we should ignore this collision...
+ CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
+ if ( pEntity )
+ {
+ if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) )
+ return false;
+
+ if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) )
+ return false;
+
+ // don't test small moveable physics objects (unless it's an NPC)
+ if ( !pEntity->IsNPC() && pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ float entMass = PhysGetEntityMass( pEntity ) ;
+ if ( entMass < m_minMass )
+ {
+ if ( entMass < m_minMass * 0.666f || pEntity->CollisionProp()->BoundingRadius() < (assert_cast<const CAI_BaseNPC *>(EntityFromEntityHandle( m_pPassEnt )))->GetHullHeight() )
+ {
+ return false;
+ }
+ }
+ }
+
+ // If we hit an antlion, don't stop, but kill it
+ if ( pEntity->Classify() == CLASS_ANTLION )
+ {
+ CBaseEntity *pHunter = (CBaseEntity *)EntityFromEntityHandle( m_pPassEnt );
+ Hunter_ApplyChargeDamage( pHunter, pEntity, pEntity->GetHealth() );
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+private:
+ const IHandleEntity *m_pPassEnt;
+ int m_collisionGroup;
+ float m_minMass;
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+inline void HunterTraceHull_SkipPhysics( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin,
+ const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore,
+ int collisionGroup, trace_t *ptr, float minMass )
+{
+ Ray_t ray;
+ ray.Init( vecAbsStart, vecAbsEnd, hullMin, hullMax );
+ CHunterTraceFilterSkipPhysics traceFilter( ignore, collisionGroup, minMass );
+ enginetrace->TraceRay( ray, mask, &traceFilter, ptr );
+}
+
+
+//-----------------------------------------------------------------------------
+// Hunter follow behavior
+//-----------------------------------------------------------------------------
+class CAI_HunterEscortBehavior : public CAI_FollowBehavior
+{
+public:
+ DECLARE_CLASS( CAI_HunterEscortBehavior, CAI_FollowBehavior );
+
+ CAI_HunterEscortBehavior() :
+ BaseClass( AI_FollowParams_t( AIF_HUNTER, true ) ),
+ m_flTimeEscortReturn( 0 ),
+ m_bEnabled( false )
+ {
+ }
+
+ CNPC_Hunter *GetOuter() { return (CNPC_Hunter *)( BaseClass::GetOuter() ); }
+
+ void SetEscortTarget( CNPC_Strider *pLeader, bool fFinishCurSchedule = false );
+ CNPC_Strider * GetEscortTarget() { return (CNPC_Strider *)GetFollowTarget(); }
+
+ bool FarFromFollowTarget()
+ {
+ return ( GetFollowTarget() && (GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin()).LengthSqr() > HUNTER_FOLLOW_DISTANCE_SQR );
+ }
+
+ void DrawDebugGeometryOverlays();
+ bool ShouldFollow();
+ void BuildScheduleTestBits();
+
+ void BeginScheduleSelection();
+
+ void GatherConditions();
+ void GatherConditionsNotActive();
+ int SelectSchedule();
+ int FollowCallBaseSelectSchedule();
+ void StartTask( const Task_t *pTask );
+ void RunTask( const Task_t *pTask );
+
+ void CheckBreakEscort();
+
+ void OnDamage( const CTakeDamageInfo &info );
+ static void DistributeFreeHunters();
+ static void FindFreeHunters( CUtlVector<CNPC_Hunter *> *pFreeHunters );
+
+ float m_flTimeEscortReturn;
+ CSimpleSimTimer m_FollowAttackTimer;
+ bool m_bEnabled;
+
+ static float gm_flLastDefendSound; // not saved and loaded, it's okay to yell again after a load
+
+ //---------------------------------
+
+ DECLARE_DATADESC();
+};
+
+
+BEGIN_DATADESC( CAI_HunterEscortBehavior )
+ DEFINE_FIELD( m_flTimeEscortReturn, FIELD_TIME ),
+ DEFINE_EMBEDDED( m_FollowAttackTimer ),
+ DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
+END_DATADESC();
+
+float CAI_HunterEscortBehavior::gm_flLastDefendSound;
+
+//-----------------------------------------------------------------------------
+// Hunter PHYSICS DAMAGE TABLE
+//-----------------------------------------------------------------------------
+#define HUNTER_MIN_PHYSICS_DAMAGE 10
+
+static impactentry_t s_HunterLinearTable[] =
+{
+ { 150*150, 75 },
+ { 350*350, 105 },
+ { 1000*1000, 300 },
+};
+
+static impactentry_t s_HunterAngularTable[] =
+{
+ { 100*100, 75 },
+ { 200*200, 105 },
+ { 300*300, 300 },
+};
+
+impactdamagetable_t s_HunterImpactDamageTable =
+{
+ s_HunterLinearTable,
+ s_HunterAngularTable,
+
+ ARRAYSIZE(s_HunterLinearTable),
+ ARRAYSIZE(s_HunterAngularTable),
+
+ 24*24, // minimum linear speed squared
+ 360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage)
+ 5, // can't take damage from anything under 5kg
+
+ 10, // anything less than 10kg is "small"
+ HUNTER_MIN_PHYSICS_DAMAGE, // never take more than 10 pts of damage from anything under 10kg
+ 36*36, // <10kg objects must go faster than 36 in/s to do damage
+
+ VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg
+ 4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
+ 5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway)
+ 0.0f, // min vel
+};
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+class CNPC_Hunter : public CAI_BaseActor
+{
+ DECLARE_CLASS( CNPC_Hunter, CAI_BaseActor );
+
+public:
+ CNPC_Hunter();
+ ~CNPC_Hunter();
+
+ //---------------------------------
+
+ void Precache();
+ void Spawn();
+ void PostNPCInit();
+ void Activate();
+ void UpdateOnRemove();
+ void OnRestore();
+ bool CreateBehaviors();
+ void IdleSound();
+ bool ShouldPlayIdleSound();
+ bool CanBecomeRagdoll();
+ Activity GetDeathActivity();
+ void StopLoopingSounds();
+
+ const impactdamagetable_t &GetPhysicsImpactDamageTable();
+
+ Class_T Classify();
+ Vector BodyTarget( const Vector &posSrc, bool bNoisy /*= true*/ );
+
+ int DrawDebugTextOverlays();
+ void DrawDebugGeometryOverlays();
+
+ void UpdateEfficiency( bool bInPVS );
+
+ //---------------------------------
+
+ virtual Vector GetNodeViewOffset() { return BaseClass::GetDefaultEyeOffset(); }
+
+ int GetSoundInterests();
+
+ bool IsInLargeOutdoorMap();
+
+ //---------------------------------
+ // CAI_BaseActor
+ //---------------------------------
+ const char *SelectRandomExpressionForState( NPC_STATE state );
+ void PlayExpressionForState( NPC_STATE state );
+
+ //---------------------------------
+ // CBaseAnimating
+ //---------------------------------
+ float GetIdealAccel() const { return GetIdealSpeed(); }
+
+ //---------------------------------
+ // Behavior
+ //---------------------------------
+ void NPCThink();
+ void PrescheduleThink();
+ void GatherConditions();
+ void CollectSiegeTargets();
+ void ManageSiegeTargets();
+ void KillCurrentSiegeTarget();
+ bool QueryHearSound( CSound *pSound );
+ void OnSeeEntity( CBaseEntity *pEntity );
+ void CheckFlinches() {} // Hunter handles on own
+ void BuildScheduleTestBits();
+ NPC_STATE SelectIdealState();
+ int SelectSchedule();
+ int SelectCombatSchedule();
+ int SelectSiegeSchedule();
+ int TranslateSchedule( int scheduleType );
+ void StartTask( const Task_t *pTask );
+ void RunTask( const Task_t *pTask );
+ Activity NPC_TranslateActivity( Activity baseAct );
+ void OnChangeActivity( Activity eNewActivity );
+
+ void HandleAnimEvent( animevent_t *pEvent );
+ bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *pSourceEnt);
+
+ void PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot );
+
+ void AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority );
+ float EnemyDistTolerance() { return 100.0f; }
+
+ bool ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity );
+
+ void OnChangeHintGroup( string_t oldGroup, string_t newGroup );
+
+ bool IsUsingSiegeTargets() { return m_iszSiegeTargetName != NULL_STRING; }
+
+ //---------------------------------
+ // Inputs
+ //---------------------------------
+ void InputDodge( inputdata_t &inputdata );
+ void InputFlankEnemy( inputdata_t &inputdata );
+ void InputDisableShooting( inputdata_t &inputdata );
+ void InputEnableShooting( inputdata_t &inputdata );
+ void InputFollowStrider( inputdata_t &inputdata );
+ void InputUseSiegeTargets( inputdata_t &inputdata );
+ void InputEnableSquadShootDelay( inputdata_t &inputdata );
+ void InputDisableSquadShootDelay( inputdata_t &inputdata );
+ void InputEnableUnplantedShooting( inputdata_t &inputdata );
+ void InputDisableUnplantedShooting( inputdata_t &inputdata );
+
+ //---------------------------------
+ // Combat
+ //---------------------------------
+ bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
+ bool IsValidEnemy( CBaseEntity *pEnemy );
+
+ Disposition_t IRelationType( CBaseEntity *pTarget );
+ int IRelationPriority( CBaseEntity *pTarget );
+
+ void SetSquad( CAI_Squad *pSquad );
+
+ bool UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer = NULL );
+
+ int RangeAttack1Conditions( float flDot, float flDist );
+ int RangeAttack2Conditions( float flDot, float flDist );
+
+ int MeleeAttack1Conditions ( float flDot, float flDist );
+ int MeleeAttack1ConditionsVsEnemyInVehicle( CBaseCombatCharacter *pEnemy, float flDot );
+
+ int MeleeAttack2Conditions( float flDot, float flDist );
+
+ bool WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions);
+ bool TestShootPosition(const Vector &vecShootPos, const Vector &targetPos );
+
+ Vector Weapon_ShootPosition();
+
+ CBaseEntity * MeleeAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin );
+
+ void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
+ void DoMuzzleFlash( int nAttachment );
+
+ bool CanShootThrough( const trace_t &tr, const Vector &vecTarget );
+
+ int CountRangedAttackers();
+ void DelayRangedAttackers( float minDelay, float maxDelay, bool bForced = false );
+
+ //---------------------------------
+ // Sounds & speech
+ //---------------------------------
+ void AlertSound();
+ void PainSound( const CTakeDamageInfo &info );
+ void DeathSound( const CTakeDamageInfo &info );
+
+ //---------------------------------
+ // Damage handling
+ //---------------------------------
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+ bool IsHeavyDamage( const CTakeDamageInfo &info );
+ int OnTakeDamage( const CTakeDamageInfo &info );
+ int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ void Event_Killed( const CTakeDamageInfo &info );
+
+ void StartBleeding();
+ inline bool IsBleeding() { return m_bIsBleeding; }
+ void Explode();
+
+ void SetupGlobalModelData();
+
+ //---------------------------------
+ // Navigation & Movement
+ //---------------------------------
+ bool OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval );
+ float MaxYawSpeed();
+ bool IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const;
+ float GetJumpGravity() const { return 3.0f; }
+ bool ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity );
+ void TaskFail( AI_TaskFailureCode_t code );
+ void TaskFail( const char *pszGeneralFailText ) { TaskFail( MakeFailCode( pszGeneralFailText ) ); }
+
+ CAI_BaseNPC * GetEntity() { return this; }
+
+ //---------------------------------
+ // Magnade
+ //---------------------------------
+ void StriderBusterAttached( CBaseEntity *pAttached );
+ void StriderBusterDetached( CBaseEntity *pAttached );
+
+private:
+
+ void ConsiderFlinching( const CTakeDamageInfo &info );
+
+ void TaskFindDodgeActivity();
+
+ void GatherChargeConditions();
+ void GatherIndoorOutdoorConditions();
+
+ // Charge attack.
+ bool ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel );
+ void ChargeLookAhead();
+ float ChargeSteer();
+ bool EnemyIsRightInFrontOfMe( CBaseEntity **pEntity );
+ void ChargeDamage( CBaseEntity *pTarget );
+ bool HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity );
+
+ void BeginVolley( int nNum, float flStartTime );
+ bool ShootFlechette( CBaseEntity *pTargetEntity, bool bSingleShot );
+ bool ShouldSeekTarget( CBaseEntity *pTargetEntity, bool bStriderBuster );
+ void GetShootDir( Vector &vecDir, const Vector &vecSrc, CBaseEntity *pTargetEntity, bool bStriderbuster, int nShotNum, bool bSingleShot );
+ bool ClampShootDir( Vector &vecDir );
+
+ void SetAim( const Vector &aimDir, float flInterval );
+ void RelaxAim( float flInterval );
+ void UpdateAim();
+ void UpdateEyes();
+ void LockBothEyes( float flDuration );
+ void UnlockBothEyes( float flDuration );
+
+ void TeslaThink();
+ void BleedThink();
+ void JostleVehicleThink();
+
+ void FollowStrider( const char *szStrider );
+ void FollowStrider( CNPC_Strider * pStrider );
+ int NumHuntersInMySquad();
+
+ bool CanPlantHere( const Vector &vecPos );
+
+ //---------------------------------
+ // Foot handling
+ //---------------------------------
+ Vector LeftFootHit( float eventtime );
+ Vector RightFootHit( float eventtime );
+ Vector BackFootHit( float eventtime );
+
+ void FootFX( const Vector &origin );
+
+ CBaseEntity *GetEnemyVehicle();
+ bool IsCorporealEnemy( CBaseEntity *pEnemy );
+
+ void PhysicsDamageEffect( const Vector &vecPos, const Vector &vecDir );
+ bool PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer );
+
+ //-----------------------------------------------------
+ // Conditions, Schedules, Tasks
+ //-----------------------------------------------------
+ enum
+ {
+ SCHED_HUNTER_RANGE_ATTACK1 = BaseClass::NEXT_SCHEDULE,
+ SCHED_HUNTER_RANGE_ATTACK2,
+ SCHED_HUNTER_MELEE_ATTACK1,
+ SCHED_HUNTER_DODGE,
+ SCHED_HUNTER_CHASE_ENEMY,
+ SCHED_HUNTER_CHASE_ENEMY_MELEE,
+ SCHED_HUNTER_COMBAT_FACE,
+ SCHED_HUNTER_FLANK_ENEMY,
+ SCHED_HUNTER_CHANGE_POSITION,
+ SCHED_HUNTER_CHANGE_POSITION_FINISH,
+ SCHED_HUNTER_SIDESTEP,
+ SCHED_HUNTER_PATROL,
+ SCHED_HUNTER_FLINCH_STICKYBOMB,
+ SCHED_HUNTER_STAGGER,
+ SCHED_HUNTER_PATROL_RUN,
+ SCHED_HUNTER_TAKE_COVER_FROM_ENEMY,
+ SCHED_HUNTER_HIDE_UNDER_COVER,
+ SCHED_HUNTER_FAIL_IMMEDIATE, // instant fail without waiting
+ SCHED_HUNTER_CHARGE_ENEMY,
+ SCHED_HUNTER_FAIL_CHARGE_ENEMY,
+ SCHED_HUNTER_FOUND_ENEMY,
+ SCHED_HUNTER_FOUND_ENEMY_ACK,
+ SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER,
+ SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER_LATENT,
+ SCHED_HUNTER_GOTO_HINT,
+ SCHED_HUNTER_CLEAR_HINTNODE,
+ SCHED_HUNTER_FAIL_DODGE,
+ SCHED_HUNTER_SIEGE_STAND,
+ SCHED_HUNTER_CHANGE_POSITION_SIEGE,
+
+ TASK_HUNTER_AIM = BaseClass::NEXT_TASK,
+ TASK_HUNTER_FIND_DODGE_POSITION,
+ TASK_HUNTER_DODGE,
+ TASK_HUNTER_PRE_RANGE_ATTACK2,
+ TASK_HUNTER_SHOOT_COMMIT,
+ TASK_HUNTER_BEGIN_FLANK,
+ TASK_HUNTER_ANNOUNCE_FLANK,
+ TASK_HUNTER_STAGGER,
+ TASK_HUNTER_CORNERED_TIMER,
+ TASK_HUNTER_FIND_SIDESTEP_POSITION,
+ TASK_HUNTER_CHARGE,
+ TASK_HUNTER_CHARGE_DELAY,
+ TASK_HUNTER_FINISH_RANGE_ATTACK,
+ TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY,
+
+ COND_HUNTER_SHOULD_PATROL = BaseClass::NEXT_CONDITION,
+ COND_HUNTER_FORCED_FLANK_ENEMY,
+ COND_HUNTER_FORCED_DODGE,
+ COND_HUNTER_CAN_CHARGE_ENEMY,
+ COND_HUNTER_HIT_BY_STICKYBOMB,
+ COND_HUNTER_STAGGERED,
+ COND_HUNTER_IS_INDOORS,
+ COND_HUNTER_SEE_STRIDERBUSTER,
+ COND_HUNTER_INCOMING_VEHICLE,
+ COND_HUNTER_NEW_HINTGROUP,
+ COND_HUNTER_CANT_PLANT,
+ COND_HUNTER_SQUADMATE_FOUND_ENEMY,
+ };
+
+ enum HunterEyeStates_t
+ {
+ HUNTER_EYE_STATE_TOP_LOCKED = 0,
+ HUNTER_EYE_STATE_BOTTOM_LOCKED,
+ HUNTER_EYE_STATE_BOTH_LOCKED,
+ HUNTER_EYE_STATE_BOTH_UNLOCKED,
+ };
+
+ string_t m_iszFollowTarget; // Name of the strider we should follow.
+ CSimpleStopwatch m_BeginFollowDelay;
+
+ int m_nKillingDamageType;
+ HunterEyeStates_t m_eEyeState;
+
+ float m_aimYaw;
+ float m_aimPitch;
+
+ float m_flShootAllowInterruptTime;
+ float m_flNextChargeTime; // Prevents us from doing our threat display too often.
+ float m_flNextDamageTime;
+ float m_flNextSideStepTime;
+
+ CSimpleSimTimer m_HeavyDamageDelay;
+ CSimpleSimTimer m_FlinchTimer;
+ CSimpleSimTimer m_EyeSwitchTimer; // Controls how often we switch which eye is focusing on our enemy.
+
+ bool m_bTopMuzzle; // Used to alternate between top muzzle FX and bottom muzzle FX.
+ bool m_bEnableSquadShootDelay;
+ bool m_bIsBleeding;
+
+ Activity m_eDodgeActivity;
+ CSimpleSimTimer m_RundownDelay;
+ CSimpleSimTimer m_IgnoreVehicleTimer;
+
+ bool m_bDisableShooting; // Range attack disabled via an input. Used for scripting melee attacks.
+
+ bool m_bFlashlightInEyes; // The player is shining the flashlight on our eyes.
+ float m_flPupilDilateTime; // When to dilate our pupils if the flashlight is no longer on our eyes.
+
+ Vector m_vecEnemyLastSeen;
+ Vector m_vecLastCanPlantHerePos;
+ Vector m_vecStaggerDir;
+
+ bool m_bPlanted;
+ bool m_bLastCanPlantHere;
+ bool m_bMissLeft;
+ bool m_bEnableUnplantedShooting;
+
+ static float gm_flMinigunDistZ;
+ static Vector gm_vecLocalRelativePositionMinigun;
+
+ static int gm_nTopGunAttachment;
+ static int gm_nBottomGunAttachment;
+ static int gm_nAimYawPoseParam;
+ static int gm_nAimPitchPoseParam;
+ static int gm_nBodyYawPoseParam;
+ static int gm_nBodyPitchPoseParam;
+ static int gm_nStaggerYawPoseParam;
+ static int gm_nHeadCenterAttachment;
+ static int gm_nHeadBottomAttachment;
+ static float gm_flHeadRadius;
+
+ static int gm_nUnplantedNode;
+ static int gm_nPlantedNode;
+
+ CAI_HunterEscortBehavior m_EscortBehavior;
+
+ int m_nFlechettesQueued;
+ int m_nClampedShots; // The number of consecutive shots fired at an out-of-max yaw target.
+
+ float m_flNextRangeAttack2Time; // Time when we can fire another volley of flechettes.
+ float m_flNextFlechetteTime; // Time to fire the next flechette in this volley.
+
+ float m_flNextMeleeTime;
+ float m_flTeslaStopTime;
+
+ string_t m_iszCurrentExpression;
+
+ // buster fu
+ CUtlVector< EHANDLE > m_hAttachedBusters; // List of busters attached to us
+ float m_fCorneredTimer; ///< hunter was cornered when fleeing player; it won't flee again until this time
+
+ CSimpleSimTimer m_CheckHintGroupTimer;
+
+ DEFINE_CUSTOM_AI;
+
+ DECLARE_DATADESC();
+
+ friend class CAI_HunterEscortBehavior;
+ friend class CHunterMaker;
+
+ bool m_bInLargeOutdoorMap;
+ float m_flTimeSawEnemyAgain;
+
+ // Sounds
+ //CSoundPatch *m_pGunFiringSound;
+
+ CUtlVector<EHANDLE> m_pSiegeTargets;
+ string_t m_iszSiegeTargetName;
+ float m_flTimeNextSiegeTargetAttack;
+ EHANDLE m_hCurrentSiegeTarget;
+
+ EHANDLE m_hHitByVehicle;
+};
+
+
+LINK_ENTITY_TO_CLASS( npc_hunter, CNPC_Hunter );
+
+
+BEGIN_DATADESC( CNPC_Hunter )
+
+ DEFINE_KEYFIELD( m_iszFollowTarget, FIELD_STRING, "FollowTarget" ),
+
+ DEFINE_FIELD( m_aimYaw, FIELD_FLOAT ),
+ DEFINE_FIELD( m_aimPitch, FIELD_FLOAT ),
+
+ DEFINE_FIELD( m_flShootAllowInterruptTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextChargeTime, FIELD_TIME ),
+ //DEFINE_FIELD( m_flNextDamageTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextSideStepTime, FIELD_TIME ),
+
+ DEFINE_EMBEDDED( m_HeavyDamageDelay ),
+ DEFINE_EMBEDDED( m_FlinchTimer ),
+
+ DEFINE_FIELD( m_eEyeState, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_bTopMuzzle, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bEnableSquadShootDelay, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bIsBleeding, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_bDisableShooting, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bFlashlightInEyes, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flPupilDilateTime, FIELD_TIME ),
+
+ DEFINE_FIELD( m_vecEnemyLastSeen, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecLastCanPlantHerePos, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecStaggerDir, FIELD_VECTOR ),
+
+ DEFINE_FIELD( m_bPlanted, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bLastCanPlantHere, FIELD_BOOLEAN ),
+ //DEFINE_FIELD( m_bMissLeft, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bEnableUnplantedShooting, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_nKillingDamageType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_eDodgeActivity, FIELD_INTEGER ),
+ DEFINE_EMBEDDED( m_RundownDelay ),
+ DEFINE_EMBEDDED( m_IgnoreVehicleTimer ),
+
+ DEFINE_FIELD( m_flNextMeleeTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flTeslaStopTime, FIELD_TIME ),
+
+ // (auto saved by AI)
+ //DEFINE_FIELD( m_EscortBehavior, FIELD_EMBEDDED ),
+
+ DEFINE_FIELD( m_iszCurrentExpression, FIELD_STRING ),
+
+ DEFINE_FIELD( m_fCorneredTimer, FIELD_TIME),
+
+ DEFINE_EMBEDDED( m_CheckHintGroupTimer ),
+
+ // (Recomputed in Precache())
+ //DEFINE_FIELD( m_bInLargeOutdoorMap, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flTimeSawEnemyAgain, FIELD_TIME ),
+
+ //DEFINE_SOUNDPATCH( m_pGunFiringSound ),
+
+ //DEFINE_UTLVECTOR( m_pSiegeTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_iszSiegeTargetName, FIELD_STRING ),
+ DEFINE_FIELD( m_flTimeNextSiegeTargetAttack, FIELD_TIME ),
+ DEFINE_FIELD( m_hCurrentSiegeTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hHitByVehicle, FIELD_EHANDLE ),
+
+ DEFINE_EMBEDDED( m_BeginFollowDelay ),
+ DEFINE_EMBEDDED( m_EyeSwitchTimer ),
+
+ DEFINE_FIELD( m_nFlechettesQueued, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nClampedShots, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flNextRangeAttack2Time, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextFlechetteTime, FIELD_TIME ),
+ DEFINE_UTLVECTOR( m_hAttachedBusters, FIELD_EHANDLE ),
+ DEFINE_UTLVECTOR( m_pSiegeTargets, FIELD_EHANDLE ),
+
+ // inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Dodge", InputDodge ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "FlankEnemy", InputFlankEnemy ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "DisableShooting", InputDisableShooting ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "EnableShooting", InputEnableShooting ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "FollowStrider", InputFollowStrider ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "UseSiegeTargets", InputUseSiegeTargets ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableSquadShootDelay", InputEnableSquadShootDelay ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableSquadShootDelay", InputDisableSquadShootDelay ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableUnplantedShooting", InputEnableUnplantedShooting ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableUnplantedShooting", InputDisableUnplantedShooting ),
+
+ // Function Pointers
+ DEFINE_THINKFUNC( TeslaThink ),
+ DEFINE_THINKFUNC( BleedThink ),
+ DEFINE_THINKFUNC( JostleVehicleThink ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+
+int CNPC_Hunter::gm_nUnplantedNode = 0;
+int CNPC_Hunter::gm_nPlantedNode = 0;
+
+int CNPC_Hunter::gm_nAimYawPoseParam = -1;
+int CNPC_Hunter::gm_nAimPitchPoseParam = -1;
+int CNPC_Hunter::gm_nBodyYawPoseParam = -1;
+int CNPC_Hunter::gm_nBodyPitchPoseParam = -1;
+int CNPC_Hunter::gm_nStaggerYawPoseParam = -1;
+int CNPC_Hunter::gm_nHeadCenterAttachment = -1;
+int CNPC_Hunter::gm_nHeadBottomAttachment = -1;
+float CNPC_Hunter::gm_flHeadRadius = 0;
+
+int CNPC_Hunter::gm_nTopGunAttachment = -1;
+int CNPC_Hunter::gm_nBottomGunAttachment = -1;
+
+float CNPC_Hunter::gm_flMinigunDistZ;
+Vector CNPC_Hunter::gm_vecLocalRelativePositionMinigun;
+
+//-----------------------------------------------------------------------------
+
+static CUtlVector<CNPC_Hunter *> g_Hunters;
+float g_TimeLastDistributeFreeHunters = -1;
+const float FREE_HUNTER_DISTRIBUTE_INTERVAL = 2;
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CNPC_Hunter::CNPC_Hunter()
+{
+ g_Hunters.AddToTail( this );
+ g_TimeLastDistributeFreeHunters = -1;
+ m_flTimeSawEnemyAgain = HUNTER_SEE_ENEMY_TIME_INVALID;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CNPC_Hunter::~CNPC_Hunter()
+{
+ g_Hunters.FindAndRemove( this );
+ g_TimeLastDistributeFreeHunters = -1;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::Precache()
+{
+ PrecacheModel( "models/hunter.mdl" );
+ PropBreakablePrecacheAll( MAKE_STRING("models/hunter.mdl") );
+
+ PrecacheScriptSound( "NPC_Hunter.Idle" );
+ PrecacheScriptSound( "NPC_Hunter.Scan" );
+ PrecacheScriptSound( "NPC_Hunter.Alert" );
+ PrecacheScriptSound( "NPC_Hunter.Pain" );
+ PrecacheScriptSound( "NPC_Hunter.PreCharge" );
+ PrecacheScriptSound( "NPC_Hunter.Angry" );
+ PrecacheScriptSound( "NPC_Hunter.Death" );
+ PrecacheScriptSound( "NPC_Hunter.FireMinigun" );
+ PrecacheScriptSound( "NPC_Hunter.Footstep" );
+ PrecacheScriptSound( "NPC_Hunter.BackFootstep" );
+ PrecacheScriptSound( "NPC_Hunter.FlechetteVolleyWarn" );
+ PrecacheScriptSound( "NPC_Hunter.FlechetteShoot" );
+ PrecacheScriptSound( "NPC_Hunter.FlechetteShootLoop" );
+ PrecacheScriptSound( "NPC_Hunter.FlankAnnounce" );
+ PrecacheScriptSound( "NPC_Hunter.MeleeAnnounce" );
+ PrecacheScriptSound( "NPC_Hunter.MeleeHit" );
+ PrecacheScriptSound( "NPC_Hunter.TackleAnnounce" );
+ PrecacheScriptSound( "NPC_Hunter.TackleHit" );
+ PrecacheScriptSound( "NPC_Hunter.ChargeHitEnemy" );
+ PrecacheScriptSound( "NPC_Hunter.ChargeHitWorld" );
+ PrecacheScriptSound( "NPC_Hunter.FoundEnemy" );
+ PrecacheScriptSound( "NPC_Hunter.FoundEnemyAck" );
+ PrecacheScriptSound( "NPC_Hunter.DefendStrider" );
+ PrecacheScriptSound( "NPC_Hunter.HitByVehicle" );
+
+ PrecacheParticleSystem( "hunter_muzzle_flash" );
+ PrecacheParticleSystem( "blood_impact_synth_01" );
+ PrecacheParticleSystem( "blood_impact_synth_01_arc_parent" );
+ PrecacheParticleSystem( "blood_spurt_synth_01" );
+ PrecacheParticleSystem( "blood_drip_synth_01" );
+
+ PrecacheInstancedScene( "scenes/npc/hunter/hunter_scan.vcd" );
+ PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyeclose.vcd" );
+ PrecacheInstancedScene( "scenes/npc/hunter/hunter_roar.vcd" );
+ PrecacheInstancedScene( "scenes/npc/hunter/hunter_pain.vcd" );
+ PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyedarts_top.vcd" );
+ PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyedarts_bottom.vcd" );
+
+ PrecacheMaterial( "effects/water_highlight" );
+
+ UTIL_PrecacheOther( "hunter_flechette" );
+ UTIL_PrecacheOther( "sparktrail" );
+
+ m_bInLargeOutdoorMap = false;
+ if( !Q_strnicmp( STRING(gpGlobals->mapname), "ep2_outland_12", 14) )
+ {
+ m_bInLargeOutdoorMap = true;
+ }
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::Spawn()
+{
+ Precache();
+
+ SetModel( "models/hunter.mdl" );
+ BaseClass::Spawn();
+
+ //m_debugOverlays |= OVERLAY_NPC_ROUTE_BIT | OVERLAY_BBOX_BIT | OVERLAY_PIVOT_BIT;
+
+ SetHullType( HULL_MEDIUM_TALL );
+ SetHullSizeNormal();
+ SetDefaultEyeOffset();
+
+ SetNavType( NAV_GROUND );
+ m_flGroundSpeed = 500;
+ m_NPCState = NPC_STATE_NONE;
+
+ SetBloodColor( DONT_BLEED );
+
+ m_iHealth = m_iMaxHealth = sk_hunter_health.GetInt();
+
+ m_flFieldOfView = HUNTER_FOV_DOT;
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+
+ SetupGlobalModelData();
+
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_SQUAD | bits_CAP_ANIMATEDFACE );
+ CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_INNATE_MELEE_ATTACK1 );
+ CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK );
+
+ if ( !hunter_allow_dissolve.GetBool() )
+ {
+ AddEFlags( EFL_NO_DISSOLVE );
+ }
+
+ if( hunter_allow_nav_jump.GetBool() )
+ {
+ CapabilitiesAdd( bits_CAP_MOVE_JUMP );
+ }
+
+ NPCInit();
+
+ m_bEnableSquadShootDelay = true;
+
+ m_flDistTooFar = hunter_flechette_max_range.GetFloat();
+
+ // Discard time must be greater than free knowledge duration. Make it double.
+ float freeKnowledge = hunter_free_knowledge.GetFloat();
+ if ( freeKnowledge < GetEnemies()->GetEnemyDiscardTime() )
+ {
+ GetEnemies()->SetEnemyDiscardTime( MAX( freeKnowledge + 0.1, AI_DEF_ENEMY_DISCARD_TIME ) );
+ }
+ GetEnemies()->SetFreeKnowledgeDuration( freeKnowledge );
+
+ // Find out what strider we should follow, if any.
+ if ( m_iszFollowTarget != NULL_STRING )
+ {
+ m_BeginFollowDelay.Set( .1 ); // Allow time for strider to spawn
+ }
+
+ //if ( !m_pGunFiringSound )
+ //{
+ // CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ // CPASAttenuationFilter filter( this );
+ //
+ // m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "NPC_Hunter.FlechetteShootLoop" );
+ // controller.Play( m_pGunFiringSound, 0.0, 100 );
+ //}
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::UpdateEfficiency( bool bInPVS )
+{
+ SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL );
+ SetMoveEfficiency( AIME_NORMAL );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::CreateBehaviors()
+{
+ AddBehavior( &m_EscortBehavior );
+
+ return BaseClass::CreateBehaviors();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::SetupGlobalModelData()
+{
+ if ( gm_nBodyYawPoseParam != -1 )
+ return;
+
+ gm_nAimYawPoseParam = LookupPoseParameter( "aim_yaw" );
+ gm_nAimPitchPoseParam = LookupPoseParameter( "aim_pitch" );
+
+ gm_nBodyYawPoseParam = LookupPoseParameter( "body_yaw" );
+ gm_nBodyPitchPoseParam = LookupPoseParameter( "body_pitch" );
+
+ gm_nTopGunAttachment = LookupAttachment( "top_eye" );
+ gm_nBottomGunAttachment = LookupAttachment( "bottom_eye" );
+ gm_nStaggerYawPoseParam = LookupAttachment( "stagger_yaw" );
+
+ gm_nHeadCenterAttachment = LookupAttachment( "head_center" );
+ gm_nHeadBottomAttachment = LookupAttachment( "head_radius_measure" );
+
+ // Measure the radius of the head.
+ Vector vecHeadCenter;
+ Vector vecHeadBottom;
+ GetAttachment( gm_nHeadCenterAttachment, vecHeadCenter );
+ GetAttachment( gm_nHeadBottomAttachment, vecHeadBottom );
+ gm_flHeadRadius = ( vecHeadCenter - vecHeadBottom ).Length();
+
+ int nSequence = SelectWeightedSequence( ACT_HUNTER_RANGE_ATTACK2_UNPLANTED );
+ gm_nUnplantedNode = GetEntryNode( nSequence );
+
+ nSequence = SelectWeightedSequence( ACT_RANGE_ATTACK2 );
+ gm_nPlantedNode = GetEntryNode( nSequence );
+
+ CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES );
+}
+
+
+//-----------------------------------------------------------------------------
+// Shuts down looping sounds when we are killed in combat or deleted.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::StopLoopingSounds()
+{
+ BaseClass::StopLoopingSounds();
+
+ //if ( m_pGunFiringSound )
+ //{
+ // CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ // controller.SoundDestroy( m_pGunFiringSound );
+ // m_pGunFiringSound = NULL;
+ //}
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::OnRestore()
+{
+ BaseClass::OnRestore();
+ SetupGlobalModelData();
+ CreateVPhysics();
+
+ if ( IsBleeding() )
+ {
+ StartBleeding();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::IdleSound()
+{
+ if ( HasCondition( COND_LOST_ENEMY ) )
+ {
+ EmitSound( "NPC_Hunter.Scan" );
+ }
+ else
+ {
+ EmitSound( "NPC_Hunter.Idle" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ShouldPlayIdleSound()
+{
+ if ( random->RandomInt(0, 99) == 0 && !HasSpawnFlags( SF_NPC_GAG ) )
+ return true;
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Stay facing our enemy when close enough.
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
+{
+ if ( GetActivity() == ACT_TRANSITION )
+ {
+ // No turning while in transitions.
+ return true;
+ }
+
+ bool bSideStepping = IsCurSchedule( SCHED_HUNTER_SIDESTEP, false );
+
+ // FIXME: this will break scripted sequences that walk when they have an enemy
+ if ( GetEnemy() &&
+ ( bSideStepping ||
+ ( ( ( GetNavigator()->GetMovementActivity() == ACT_RUN ) || ( GetNavigator()->GetMovementActivity() == ACT_WALK ) ) &&
+ !IsCurSchedule( SCHED_HUNTER_TAKE_COVER_FROM_ENEMY, false ) ) ) )
+ {
+ Vector vecEnemyLKP = GetEnemyLKP();
+
+ // Face my enemy if we're close enough
+ if ( bSideStepping || UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST )
+ {
+ AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.2 );
+ }
+ }
+
+ return BaseClass::OverrideMoveFacing( move, flInterval );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::PostNPCInit()
+{
+ BaseClass::PostNPCInit();
+
+ IPhysicsObject *pPhysObject = VPhysicsGetObject();
+ Assert( pPhysObject );
+ if ( pPhysObject )
+ {
+ pPhysObject->SetMass( 600.0 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::Activate()
+{
+ BaseClass::Activate();
+
+ s_iszStriderBusterClassname = AllocPooledString( "weapon_striderbuster" );
+ s_iszStriderClassname = AllocPooledString( "npc_strider" );
+ s_iszMagnadeClassname = AllocPooledString( "npc_grenade_magna" );
+ s_iszPhysPropClassname = AllocPooledString( "prop_physics" );
+ s_iszHuntersToRunOver = AllocPooledString( "hunters_to_run_over" );
+
+ // If no one has initialized the hunters to run over counter, just zero it out.
+ if ( !GlobalEntity_IsInTable( s_iszHuntersToRunOver ) )
+ {
+ GlobalEntity_Add( s_iszHuntersToRunOver, gpGlobals->mapname, GLOBAL_ON );
+ GlobalEntity_SetCounter( s_iszHuntersToRunOver, 0 );
+ }
+
+ CMissile::AddCustomDetonator( this, ( GetHullMaxs().AsVector2D() - GetHullMins().AsVector2D() ).Length() * 0.5, GetHullHeight() );
+
+ SetupGlobalModelData();
+
+ if ( gm_flMinigunDistZ == 0 )
+ {
+ // Have to create a virgin hunter to ensure proper pose
+ CNPC_Hunter *pHunter = (CNPC_Hunter *)CreateEntityByName( "npc_hunter" );
+ Assert(pHunter);
+ pHunter->Spawn();
+
+ pHunter->SetActivity( ACT_WALK );
+ pHunter->InvalidateBoneCache();
+
+ // Currently just using the gun for the vertical component!
+ Vector defEyePos;
+ pHunter->GetAttachment( "minigunbase", defEyePos );
+ gm_flMinigunDistZ = defEyePos.z - pHunter->GetAbsOrigin().z;
+
+ Vector position;
+ pHunter->GetAttachment( gm_nTopGunAttachment, position );
+ VectorITransform( position, pHunter->EntityToWorldTransform(), gm_vecLocalRelativePositionMinigun );
+ UTIL_Remove( pHunter );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::UpdateOnRemove()
+{
+ CMissile::RemoveCustomDetonator( this );
+ BaseClass::UpdateOnRemove();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Class_T CNPC_Hunter::Classify()
+{
+ return CLASS_COMBINE_HUNTER;
+}
+
+//-----------------------------------------------------------------------------
+// Compensate for the hunter's long legs by moving the bodytarget up to his head.
+//-----------------------------------------------------------------------------
+Vector CNPC_Hunter::BodyTarget( const Vector &posSrc, bool bNoisy /*= true*/ )
+{
+ Vector vecResult;
+ QAngle vecAngle;
+ GetAttachment( gm_nHeadCenterAttachment, vecResult, vecAngle );
+
+ if ( bNoisy )
+ {
+ float rand1 = random->RandomFloat( 0, gm_flHeadRadius ) + random->RandomFloat( 0, gm_flHeadRadius );
+ return vecResult + RandomVector( -rand1, rand1 );
+ }
+
+ return vecResult;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::DrawDebugTextOverlays()
+{
+ int text_offset = BaseClass::DrawDebugTextOverlays();
+
+ if (m_debugOverlays & OVERLAY_TEXT_BIT)
+ {
+ EntityText( text_offset, CFmtStr("%s", m_bPlanted ? "Planted" : "Unplanted" ), 0 );
+ text_offset++;
+
+ EntityText( text_offset, CFmtStr("Eye state: %d", m_eEyeState ), 0 );
+ text_offset++;
+
+ if( IsUsingSiegeTargets() )
+ {
+ EntityText( text_offset, CFmtStr("Next Siege Attempt:%f", m_flTimeNextSiegeTargetAttack - gpGlobals->curtime ), 0 );
+ text_offset++;
+ }
+ }
+
+ return text_offset;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::LockBothEyes( float flDuration )
+{
+ m_eEyeState = HUNTER_EYE_STATE_BOTH_LOCKED;
+ m_EyeSwitchTimer.Set( flDuration );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::UnlockBothEyes( float flDuration )
+{
+ m_eEyeState = HUNTER_EYE_STATE_BOTH_UNLOCKED;
+ m_EyeSwitchTimer.Set( flDuration );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::OnChangeActivity( Activity eNewActivity )
+{
+ m_EyeSwitchTimer.Force();
+
+ BaseClass::OnChangeActivity( eNewActivity );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::UpdateEyes()
+{
+ // If the eyes are controlled by a script, do nothing.
+ if ( GetState() == NPC_STATE_SCRIPT )
+ return;
+
+ if ( m_EyeSwitchTimer.Expired() )
+ {
+ RemoveActorFromScriptedScenes( this, false );
+
+ if ( GetActivity() == ACT_IDLE )
+ {
+ // Idles have eye motion baked in.
+ m_eEyeState = HUNTER_EYE_STATE_BOTH_LOCKED;
+ }
+ else if ( GetEnemy() == NULL )
+ {
+ m_eEyeState = HUNTER_EYE_STATE_BOTH_UNLOCKED;
+ }
+ else if ( m_eEyeState == HUNTER_EYE_STATE_BOTH_LOCKED )
+ {
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ m_eEyeState = HUNTER_EYE_STATE_TOP_LOCKED;
+ }
+ else
+ {
+ m_eEyeState = HUNTER_EYE_STATE_BOTTOM_LOCKED;
+ }
+ }
+ else if ( m_eEyeState == HUNTER_EYE_STATE_TOP_LOCKED )
+ {
+ m_eEyeState = HUNTER_EYE_STATE_BOTTOM_LOCKED;
+ }
+ else if ( m_eEyeState == HUNTER_EYE_STATE_BOTTOM_LOCKED )
+ {
+ m_eEyeState = HUNTER_EYE_STATE_TOP_LOCKED;
+ }
+
+ if ( ( m_eEyeState == HUNTER_EYE_STATE_BOTTOM_LOCKED ) || ( m_eEyeState == HUNTER_EYE_STATE_BOTH_UNLOCKED ) )
+ {
+ SetExpression( "scenes/npc/hunter/hunter_eyedarts_top.vcd" );
+ }
+
+ if ( ( m_eEyeState == HUNTER_EYE_STATE_TOP_LOCKED ) || ( m_eEyeState == HUNTER_EYE_STATE_BOTH_UNLOCKED ) )
+ {
+ SetExpression( "scenes/npc/hunter/hunter_eyedarts_bottom.vcd" );
+ }
+
+ m_EyeSwitchTimer.Set( random->RandomFloat( 1.0f, 3.0f ) );
+ }
+
+ /*Vector vecEyePos;
+ Vector vecEyeDir;
+
+ GetAttachment( gm_nTopGunAttachment, vecEyePos, &vecEyeDir );
+ NDebugOverlay::Line( vecEyePos, vecEyePos + vecEyeDir * 36, 255, 0, 0, 0, 0.1 );
+
+ GetAttachment( gm_nBottomGunAttachment, vecEyePos, &vecEyeDir );
+ NDebugOverlay::Line( vecEyePos, vecEyePos + vecEyeDir * 36, 255, 0, 0, 0, 0.1 );*/
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::NPCThink()
+{
+ BaseClass::NPCThink();
+
+ // Update our planted/unplanted state.
+ m_bPlanted = ( GetEntryNode( GetSequence() ) == gm_nPlantedNode );
+
+ UpdateAim();
+ UpdateEyes();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::PrescheduleThink()
+{
+ BaseClass::PrescheduleThink();
+
+ if ( m_BeginFollowDelay.Expired() )
+ {
+ FollowStrider( STRING( m_iszFollowTarget ) );
+ m_BeginFollowDelay.Stop();
+ }
+
+ m_EscortBehavior.CheckBreakEscort();
+
+ // If we're being blinded by the flashlight, see if we should stop
+ if ( m_bFlashlightInEyes )
+ {
+ if ( m_flPupilDilateTime < gpGlobals->curtime )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
+ if ( ( pPlayer && !pPlayer->IsIlluminatedByFlashlight( this, NULL ) ) || !PlayerFlashlightOnMyEyes( pPlayer ) )
+ {
+ //Msg( "NOT SHINING FLASHLIGHT ON ME\n" );
+
+ // Remove the actor from the flashlight scene
+ RemoveActorFromScriptedScenes( this, true, false, "scenes/npc/hunter/hunter_eyeclose.vcd" );
+ m_bFlashlightInEyes = false;
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::GatherChargeConditions()
+{
+ ClearCondition( COND_HUNTER_CAN_CHARGE_ENEMY );
+
+ if ( !hunter_charge.GetBool() )
+ return;
+
+ if ( !GetEnemy() )
+ return;
+
+ if ( GetHintGroup() != NULL_STRING )
+ return;
+
+ if ( !HasCondition( COND_SEE_ENEMY ) )
+ return;
+
+ if ( !hunter_charge_test.GetBool() && gpGlobals->curtime < m_flNextChargeTime )
+ return;
+
+ // No charging Alyx or Barney
+ if( GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ return;
+
+ if ( m_EscortBehavior.GetEscortTarget() && GetEnemy()->MyCombatCharacterPointer() && !GetEnemy()->MyCombatCharacterPointer()->FInViewCone( this ) )
+ return;
+
+ if ( ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), true, false ) )
+ {
+ SetCondition( COND_HUNTER_CAN_CHARGE_ENEMY );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::GatherConditions()
+{
+ GatherIndoorOutdoorConditions();
+ GatherChargeConditions();
+
+ BaseClass::GatherConditions();
+
+ // Enemy LKP that doesn't get updated by the free knowledge code.
+ // Used for shooting at where our enemy was when we can't see them.
+ ClearCondition( COND_HUNTER_INCOMING_VEHICLE );
+ if ( m_IgnoreVehicleTimer.Expired() && GetEnemy() && HasCondition( COND_SEE_ENEMY ) )
+ {
+ CBaseEntity *pVehicle = GetEnemyVehicle();
+ if ( ( pVehicle ) && ( GlobalEntity_GetCounter( s_iszHuntersToRunOver ) <= 0 ) )
+ {
+ static float timeDrawnArrow;
+
+ // Extrapolate the position of the vehicle and see if it's heading toward us.
+ float predictTime = hunter_dodge_warning.GetFloat();
+ Vector2D vecFuturePos = pVehicle->GetAbsOrigin().AsVector2D() + pVehicle->GetSmoothedVelocity().AsVector2D() * predictTime;
+ if ( pVehicle->GetSmoothedVelocity().LengthSqr() > Square( 200 ) )
+ {
+ float t = 0;
+ Vector2D vDirMovement = pVehicle->GetSmoothedVelocity().AsVector2D();
+ if ( hunter_dodge_debug.GetBool() )
+ {
+ NDebugOverlay::Line( pVehicle->GetAbsOrigin(), pVehicle->GetAbsOrigin() + pVehicle->GetSmoothedVelocity(), 255, 255, 255, true, .1 );
+ }
+ vDirMovement.NormalizeInPlace();
+ Vector2D vDirToHunter = GetAbsOrigin().AsVector2D() - pVehicle->GetAbsOrigin().AsVector2D();
+ vDirToHunter.NormalizeInPlace();
+ if ( DotProduct2D( vDirMovement, vDirToHunter ) > hunter_dodge_warning_cone.GetFloat() &&
+ CalcDistanceSqrToLine2D( GetAbsOrigin().AsVector2D(), pVehicle->GetAbsOrigin().AsVector2D(), vecFuturePos, &t ) < Square( hunter_dodge_warning_width.GetFloat() * .5 ) &&
+ t > 0.0 && t < 1.0 )
+ {
+ if ( fabs( predictTime - hunter_dodge_warning.GetFloat() ) < .05 || random->RandomInt( 0, 3 ) )
+ {
+ SetCondition( COND_HUNTER_INCOMING_VEHICLE );
+ }
+ else
+ {
+ if ( hunter_dodge_debug. GetBool() )
+ {
+ Msg( "Hunter %d failing dodge (ignore)\n", entindex() );
+ }
+ }
+
+ if ( hunter_dodge_debug. GetBool() )
+ {
+ NDebugOverlay::Cross3D( EyePosition(), 100, 255, 255, 255, true, .1 );
+ if ( timeDrawnArrow != gpGlobals->curtime )
+ {
+ timeDrawnArrow = gpGlobals->curtime;
+ Vector vEndpoint( vecFuturePos.x, vecFuturePos.y, UTIL_GetLocalPlayer()->WorldSpaceCenter().z - 24 );
+ NDebugOverlay::HorzArrow( UTIL_GetLocalPlayer()->WorldSpaceCenter() - Vector(0, 0, 24), vEndpoint, hunter_dodge_warning_width.GetFloat(), 255, 0, 0, 64, true, .1 );
+ }
+ }
+ }
+ else if ( hunter_dodge_debug.GetBool() )
+ {
+ if ( t <= 0 )
+ {
+ NDebugOverlay::Cross3D( EyePosition(), 100, 0, 0, 255, true, .1 );
+ }
+ else
+ {
+ NDebugOverlay::Cross3D( EyePosition(), 100, 0, 255, 255, true, .1 );
+ }
+ }
+ }
+ else if ( hunter_dodge_debug.GetBool() )
+ {
+ NDebugOverlay::Cross3D( EyePosition(), 100, 0, 255, 0, true, .1 );
+ }
+ if ( hunter_dodge_debug. GetBool() )
+ {
+ if ( timeDrawnArrow != gpGlobals->curtime )
+ {
+ timeDrawnArrow = gpGlobals->curtime;
+ Vector vEndpoint( vecFuturePos.x, vecFuturePos.y, UTIL_GetLocalPlayer()->WorldSpaceCenter().z - 24 );
+ NDebugOverlay::HorzArrow( UTIL_GetLocalPlayer()->WorldSpaceCenter() - Vector(0, 0, 24), vEndpoint, hunter_dodge_warning_width.GetFloat(), 127, 127, 127, 64, true, .1 );
+ }
+ }
+
+ }
+
+ m_vecEnemyLastSeen = GetEnemy()->GetAbsOrigin();
+ }
+
+ if( !HasCondition(COND_ENEMY_OCCLUDED) )
+ {
+ // m_flTimeSawEnemyAgain always tells us what time I first saw this
+ // enemy again after some period of not seeing them. This is used to
+ // compute how long the enemy has been visible to me THIS TIME.
+ // Every time I lose sight of the enemy this time is set invalid until
+ // I see the enemy again and record that time.
+ if( m_flTimeSawEnemyAgain == HUNTER_SEE_ENEMY_TIME_INVALID )
+ {
+ m_flTimeSawEnemyAgain = gpGlobals->curtime;
+ }
+ }
+ else
+ {
+ m_flTimeSawEnemyAgain = HUNTER_SEE_ENEMY_TIME_INVALID;
+ }
+
+ ManageSiegeTargets();
+}
+
+//-----------------------------------------------------------------------------
+// Search all entities in the map
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::CollectSiegeTargets()
+{
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_iszSiegeTargetName );
+
+ while( pTarget != NULL )
+ {
+ if( pTarget->Classify() == CLASS_BULLSEYE )
+ {
+ m_pSiegeTargets.AddToTail( pTarget );
+ }
+
+ pTarget = gEntList.FindEntityByName( pTarget, m_iszSiegeTargetName );
+ };
+
+ if( m_pSiegeTargets.Count() < 1 )
+ {
+ m_iszSiegeTargetName = NULL_STRING; // And stop trying!
+ }
+}
+
+//-----------------------------------------------------------------------------
+// For use when Hunters are outside and the player is inside a structure
+// Create a temporary bullseye in a location that makes it seem like
+// I am aware of the location of a player I cannot see. (Then fire at
+// at this bullseye, thus laying 'siege' to the part of the building he
+// is in.) The locations are copied from suitable info_target entities.
+// (these should be placed in exterior windows and doorways so that
+// the Hunter fires into the building through these apertures)
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::ManageSiegeTargets()
+{
+ if( gpGlobals->curtime < m_flTimeNextSiegeTargetAttack )
+ return;
+
+ if( m_pSiegeTargets.Count() == 0 )
+ {
+ // If my list of siege targets is empty, go and cache all of them now
+ // so that I don't have to search the world every time.
+ CollectSiegeTargets();
+
+ if( m_pSiegeTargets.Count() == 0 )
+ return;
+ }
+
+ m_flTimeNextSiegeTargetAttack = gpGlobals->curtime + (hunter_siege_frequency.GetFloat() * RandomFloat( 0.8f, 1.2f) );
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+
+ // Start by assuming we are not going to create a siege target
+ bool bCreateSiegeTarget = false;
+ if( GetEnemy() == NULL )
+ {
+ // If I have no enemy at all, give it a try.
+ bCreateSiegeTarget = true;
+ }
+
+ if( bCreateSiegeTarget )
+ {
+ // We've decided that the situation calls for a siege target. So, we dig through all of my siege targets and
+ // take the closest one to the player that the player can see! (Obey they bullseye's FOV)
+ float flClosestDistSqr = Square( 1200.0f ); // Only use siege targets within 100 feet of player
+ CBaseEntity *pSiegeTargetLocation = NULL;
+ int iTraces = 0;
+ for( int i = 0 ; i < m_pSiegeTargets.Count() ; i++ )
+ {
+ CBaseEntity *pCandidate = m_pSiegeTargets[i];
+ if ( pCandidate == NULL )
+ continue;
+
+ float flDistSqr = pCandidate->GetAbsOrigin().DistToSqr(pPlayer->GetAbsOrigin());
+
+ if( flDistSqr < flClosestDistSqr )
+ {
+ // CollectSiegeTargets() guarantees my list is populated only with bullseye entities.
+ CNPC_Bullseye *pBullseye = dynamic_cast<CNPC_Bullseye*>(pCandidate);
+ if( !pBullseye->FInViewCone(this) )
+ continue;
+
+ if( pPlayer->FVisible(pCandidate) )
+ {
+ iTraces++;// Only counting these as a loose perf measurement
+ flClosestDistSqr = flDistSqr;
+ pSiegeTargetLocation = pCandidate;
+ }
+ }
+ }
+
+ if( pSiegeTargetLocation != NULL )
+ {
+ // Ditch any leftover siege target.
+ KillCurrentSiegeTarget();
+
+ // Create a bullseye that will live for 20 seconds. If we can't attack it within 20 seconds, it's probably
+ // out of reach anyone, so have it clean itself up after that long.
+ CBaseEntity *pSiegeTarget = CreateCustomTarget( pSiegeTargetLocation->GetAbsOrigin(), 20.0f );
+ pSiegeTarget->SetName( MAKE_STRING("siegetarget") );
+
+ m_hCurrentSiegeTarget.Set( pSiegeTarget );
+
+ AddEntityRelationship( pSiegeTarget, D_HT, 1 );
+ GetEnemies()->UpdateMemory( GetNavigator()->GetNetwork(), pSiegeTarget, pSiegeTarget->GetAbsOrigin(), 0.0f, true );
+ AI_EnemyInfo_t *pMemory = GetEnemies()->Find( pSiegeTarget );
+
+ if( pMemory )
+ {
+ // Pretend we've known about this target longer than we really have so that our AI doesn't waste time running ALERT schedules.
+ pMemory->timeFirstSeen = gpGlobals->curtime - 5.0f;
+ pMemory->timeLastSeen = gpGlobals->curtime - 1.0f;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Destroy the bullseye that we're using as a temporary target
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::KillCurrentSiegeTarget()
+{
+ if ( m_hCurrentSiegeTarget )
+ {
+ GetEnemies()->ClearMemory( m_hCurrentSiegeTarget );
+
+ UTIL_Remove( m_hCurrentSiegeTarget );
+ m_hCurrentSiegeTarget.Set( NULL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Return true if this NPC can hear the specified sound
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::QueryHearSound( CSound *pSound )
+{
+ if ( pSound->SoundContext() & SOUND_CONTEXT_EXCLUDE_COMBINE )
+ return false;
+
+ if ( pSound->SoundContext() & SOUND_CONTEXT_PLAYER_VEHICLE )
+ return false;
+
+ return BaseClass::QueryHearSound( pSound );
+}
+
+
+//-----------------------------------------------------------------------------
+// This is a fairly bogus heuristic right now, but it works on 06a and 12 (sjb)
+//
+// Better options: Trace infinitely and check the material we hit for sky
+// Put some leaf info in the BSP
+// Use volumes in the levels? (yucky for designers)
+//-----------------------------------------------------------------------------
+// TODO: use this or nuke it!
+void CNPC_Hunter::GatherIndoorOutdoorConditions()
+{
+ // Check indoor/outdoor before calling base class, since base class calls our
+ // RangeAttackConditions() functions, and we want those functions to know
+ // whether we're indoors or out.
+ trace_t tr;
+
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 40.0f * 12.0f ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ if( tr.fraction < 1.0f )
+ {
+ SetCondition( COND_HUNTER_IS_INDOORS );
+ }
+ else
+ {
+ ClearCondition( COND_HUNTER_IS_INDOORS );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::BuildScheduleTestBits()
+{
+ BaseClass::BuildScheduleTestBits();
+
+ if ( m_lifeState != LIFE_ALIVE )
+ {
+ return;
+ }
+
+ // Our range attack is uninterruptable for the first few seconds.
+ if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2, false ) && ( gpGlobals->curtime < m_flShootAllowInterruptTime ) )
+ {
+ ClearCustomInterruptConditions();
+ SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
+ }
+ else if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2, false ) && ( GetActivity() == ACT_TRANSITION ) )
+ {
+ // Don't stop unplanting just because we can range attack again.
+ ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
+ ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
+ }
+ else if ( !IsInLargeOutdoorMap() && IsCurSchedule( SCHED_HUNTER_FLANK_ENEMY, false ) && GetEnemy() != NULL )
+ {
+ if( HasCondition(COND_CAN_RANGE_ATTACK2) && m_flTimeSawEnemyAgain != HUNTER_SEE_ENEMY_TIME_INVALID )
+ {
+ if( (gpGlobals->curtime - m_flTimeSawEnemyAgain) >= 2.0f )
+ {
+ // When we're running flank behavior, wait a moment AFTER being able to see the enemy before
+ // breaking my schedule to range attack. This helps assure that the hunter gets well inside
+ // the room before stopping to attack. Otherwise the Hunter may stop immediately in the doorway
+ // and stop the progress of any hunters behind it.
+ SetCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
+ }
+ }
+ }
+
+ // If our enemy is anything but a striderbuster, drop everything if we see one.
+ if ( !IsStriderBuster( GetEnemy() ) )
+ {
+ SetCustomInterruptCondition( COND_HUNTER_SEE_STRIDERBUSTER );
+ }
+
+ // If we're not too busy, allow ourselves to ACK found enemy signals.
+ if ( !GetEnemy() )
+ {
+ SetCustomInterruptCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY );
+ }
+
+ // Interrupt everything if we need to dodge.
+ if ( !IsCurSchedule( SCHED_HUNTER_DODGE, false ) &&
+ !IsCurSchedule( SCHED_HUNTER_STAGGER, false ) &&
+ !IsCurSchedule( SCHED_ALERT_FACE_BESTSOUND, false ) )
+ {
+ SetCustomInterruptCondition( COND_HUNTER_INCOMING_VEHICLE );
+ SetCustomInterruptCondition( COND_HEAR_PHYSICS_DANGER );
+ SetCustomInterruptCondition( COND_HUNTER_FORCED_DODGE );
+ }
+
+ // Always interrupt on a flank command.
+ SetCustomInterruptCondition( COND_HUNTER_FORCED_FLANK_ENEMY );
+
+ // Always interrupt if staggered.
+ SetCustomInterruptCondition( COND_HUNTER_STAGGERED );
+
+ // Always interrupt if hit by a sticky bomb.
+ SetCustomInterruptCondition( COND_HUNTER_HIT_BY_STICKYBOMB );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+static bool IsMovablePhysicsObject( CBaseEntity *pEntity )
+{
+ return pEntity && pEntity->GetMoveType() == MOVETYPE_VPHYSICS && pEntity->VPhysicsGetObject() && pEntity->VPhysicsGetObject()->IsMoveable();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+NPC_STATE CNPC_Hunter::SelectIdealState()
+{
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_COMBAT:
+ {
+ if ( GetEnemy() == NULL )
+ {
+ if ( !HasCondition( COND_ENEMY_DEAD ) && !hunter_disable_patrol.GetBool() )
+ {
+ // Lost track of my enemy. Patrol.
+ SetCondition( COND_HUNTER_SHOULD_PATROL );
+ }
+
+ return NPC_STATE_ALERT;
+ }
+ else if ( HasCondition( COND_ENEMY_DEAD ) )
+ {
+ // dvs: TODO: announce enemy kills?
+ //AnnounceEnemyKill(GetEnemy());
+ }
+ }
+
+ default:
+ {
+ return BaseClass::SelectIdealState();
+ }
+ }
+
+ return GetIdealState();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel )
+{
+ // Must have a target
+ if ( !GetEnemy() )
+ return false;
+
+ // Don't check the distance once we start charging
+ if ( !bCheckForCancel && !hunter_charge_test.GetBool() )
+ {
+ float distance = ( startPos.AsVector2D() - endPos.AsVector2D() ).LengthSqr();
+
+ // Must be within our tolerance range
+ if ( ( distance < Square(HUNTER_CHARGE_MIN) ) || ( distance > Square(HUNTER_CHARGE_MAX) ) )
+ return false;
+ }
+
+ // FIXME: We'd like to exclude small physics objects from this check!
+
+ // We only need to hit the endpos with the edge of our bounding box
+ Vector vecDir = endPos - startPos;
+ VectorNormalize( vecDir );
+ float flWidth = WorldAlignSize().x * 0.5;
+ Vector vecTargetPos = endPos - (vecDir * flWidth);
+
+ // See if we can directly move there
+ AIMoveTrace_t moveTrace;
+ GetMoveProbe()->MoveLimit( NAV_GROUND, startPos, vecTargetPos, MASK_NPCSOLID_BRUSHONLY, GetEnemy(), &moveTrace );
+
+ // Draw the probe
+ if ( g_debug_hunter_charge.GetInt() == 1 )
+ {
+ Vector enemyDir = (vecTargetPos - startPos);
+ float enemyDist = VectorNormalize( enemyDir );
+
+ NDebugOverlay::BoxDirection( startPos, GetHullMins(), GetHullMaxs() + Vector(enemyDist,0,0), enemyDir, 0, 255, 0, 8, 1.0f );
+ }
+
+ // If we're not blocked, charge
+ if ( IsMoveBlocked( moveTrace ) )
+ {
+ // Don't allow it if it's too close to us
+ if ( UTIL_DistApprox( WorldSpaceCenter(), moveTrace.vEndPosition ) < HUNTER_CHARGE_MIN )
+ return false;
+
+ // Allow some special cases to not block us
+ if ( moveTrace.pObstruction != NULL )
+ {
+ // If we've hit the world, see if it's a cliff
+ if ( moveTrace.pObstruction == GetContainingEntity( INDEXENT(0) ) )
+ {
+ // Can't be too far above/below the target
+ if ( fabs( moveTrace.vEndPosition.z - vecTargetPos.z ) > StepHeight() )
+ return false;
+
+ // Allow it if we got pretty close
+ if ( UTIL_DistApprox( moveTrace.vEndPosition, vecTargetPos ) < 64 )
+ return true;
+ }
+
+ // Hit things that will take damage
+ if ( moveTrace.pObstruction->m_takedamage != DAMAGE_NO )
+ return true;
+
+ // Hit things that will move
+ if ( moveTrace.pObstruction->GetMoveType() == MOVETYPE_VPHYSICS )
+ return true;
+ }
+
+ return false;
+ }
+
+ float zDelta = endPos.z - moveTrace.vEndPosition.z;
+ if ( fabsf(zDelta) > GetHullHeight() * 0.7)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *pSourceEnt)
+{
+ if ( ( pSourceEnt != this ) && ( interactionType == g_interactionHunterFoundEnemy ) )
+ {
+ SetCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY );
+ return true;
+ }
+
+ return BaseClass::HandleInteraction( interactionType, data, pSourceEnt );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::SelectCombatSchedule()
+{
+ // If we're here with no enemy, patrol and hope we find one.
+ CBaseEntity *pEnemy = GetEnemy();
+ if ( pEnemy == NULL )
+ {
+ if ( !hunter_disable_patrol.GetBool() )
+ return SCHED_HUNTER_PATROL_RUN;
+ else
+ return SCHED_ALERT_STAND;
+ }
+
+ if ( hunter_flechette_test.GetBool() )
+ {
+ if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
+ {
+ return SCHED_HUNTER_RANGE_ATTACK2;
+ }
+ return SCHED_COMBAT_FACE;
+ }
+
+ bool bStriderBuster = IsStriderBuster( pEnemy );
+ if ( bStriderBuster )
+ {
+ if ( gpGlobals->curtime - CAI_HunterEscortBehavior::gm_flLastDefendSound > 10.0 )
+ {
+ EmitSound( "NPC_Hunter.DefendStrider" );
+ CAI_HunterEscortBehavior::gm_flLastDefendSound = gpGlobals->curtime;
+ }
+
+ if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) || HasCondition( COND_NOT_FACING_ATTACK ) )
+ {
+ return SCHED_HUNTER_RANGE_ATTACK2;
+ }
+ return SCHED_ESTABLISH_LINE_OF_FIRE;
+ }
+
+ // Certain behaviors, like flanking and melee attacks, only make sense on visible,
+ // corporeal enemies (NOT bullseyes).
+ bool bIsCorporealEnemy = IsCorporealEnemy( pEnemy );
+
+ // Take a quick swipe at our enemy if able to do so.
+ if ( bIsCorporealEnemy && HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ {
+ return SCHED_HUNTER_MELEE_ATTACK1;
+ }
+
+ // React to newly acquired enemies.
+ if ( bIsCorporealEnemy && HasCondition( COND_NEW_ENEMY ) )
+ {
+ AI_EnemyInfo_t *pEnemyInfo = GetEnemies()->Find( pEnemy );
+
+ if ( GetSquad() && pEnemyInfo && ( pEnemyInfo->timeFirstSeen == pEnemyInfo->timeAtFirstHand ) )
+ {
+ GetSquad()->BroadcastInteraction( g_interactionHunterFoundEnemy, NULL, this );
+
+ // First contact for my squad.
+ return SCHED_HUNTER_FOUND_ENEMY;
+ }
+ }
+
+ if ( HasCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY ) )
+ {
+ // A squadmate found an enemy. Respond to their call.
+ return SCHED_HUNTER_FOUND_ENEMY_ACK;
+ }
+
+ // Fire a flechette volley. Ignore squad slots if we're attacking a striderbuster.
+ // See if there is an opportunity to charge.
+ if ( !bStriderBuster && bIsCorporealEnemy && HasCondition( COND_HUNTER_CAN_CHARGE_ENEMY ) )
+ {
+ if ( hunter_charge_test.GetBool() || random->RandomInt( 1, 100 ) < hunter_charge_pct.GetInt() )
+ {
+ if ( hunter_charge_test.GetBool() || OccupyStrategySlot( SQUAD_SLOT_HUNTER_CHARGE ) )
+ {
+ return SCHED_HUNTER_CHARGE_ENEMY;
+ }
+ }
+ }
+
+ if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
+ {
+ if ( bStriderBuster || CountRangedAttackers() < hunter_flechette_max_concurrent_volleys.GetInt() )
+ {
+ DelayRangedAttackers( hunter_flechette_volley_start_min_delay.GetFloat(), hunter_flechette_volley_start_max_delay.GetFloat(), true );
+ return SCHED_HUNTER_RANGE_ATTACK2;
+ }
+ }
+
+ if ( pEnemy->GetGroundEntity() == this )
+ {
+ return SCHED_HUNTER_MELEE_ATTACK1;
+ }
+
+ if ( HasCondition( COND_TOO_CLOSE_TO_ATTACK ) )
+ {
+ return SCHED_MOVE_AWAY_FROM_ENEMY;
+ }
+
+ // Sidestep every so often if my enemy is nearby and facing me.
+/*
+ if ( gpGlobals->curtime > m_flNextSideStepTime )
+ {
+ if ( HasCondition( COND_ENEMY_FACING_ME ) && ( UTIL_DistApprox( GetEnemy()->GetAbsOrigin(), GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST ) )
+ {
+ m_flNextSideStepTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
+ return SCHED_HUNTER_SIDESTEP;
+ }
+ }
+*/
+ if ( HasCondition( COND_HEAVY_DAMAGE ) && ( gpGlobals->curtime > m_flNextSideStepTime ) )
+ {
+ m_flNextSideStepTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
+ return SCHED_HUNTER_SIDESTEP;
+ }
+
+ if ( !bStriderBuster && bIsCorporealEnemy )
+ {
+ if ( HasCondition( COND_HUNTER_CAN_CHARGE_ENEMY ) )
+ {
+ if ( OccupyStrategySlot( SQUAD_SLOT_HUNTER_CHARGE ) )
+ {
+ return SCHED_HUNTER_CHARGE_ENEMY;
+ }
+/*
+ else
+ {
+ return SCHED_HUNTER_SIDESTEP;
+ }
+*/
+ }
+
+ // Try to be a flanker.
+ if ( ( NumHuntersInMySquad() > 1 ) && OccupyStrategySlotRange( SQUAD_SLOT_HUNTER_FLANK_FIRST, SQUAD_SLOT_HUNTER_FLANK_LAST ) )
+ {
+ return SCHED_HUNTER_FLANK_ENEMY;
+ }
+ }
+
+ // Can't see my enemy.
+ if ( HasCondition( COND_ENEMY_OCCLUDED ) || HasCondition( COND_ENEMY_TOO_FAR ) || HasCondition( COND_TOO_FAR_TO_ATTACK ) || HasCondition( COND_NOT_FACING_ATTACK ) )
+ {
+ return SCHED_HUNTER_CHASE_ENEMY;
+ }
+
+ if ( HasCondition( COND_HUNTER_CANT_PLANT ) )
+ {
+ return SCHED_ESTABLISH_LINE_OF_FIRE;
+ }
+
+ //if ( HasCondition( COND_ENEMY_OCCLUDED ) && IsCurSchedule( SCHED_RANGE_ATTACK1, false ) )
+ //{
+ // return SCHED_HUNTER_COMBAT_FACE;
+ //}
+
+ return SCHED_HUNTER_CHANGE_POSITION;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::SelectSiegeSchedule()
+{
+ bool bHasEnemy = (GetEnemy() != NULL);
+
+ if( bHasEnemy )
+ {
+ // We have an enemy, so we should be making every effort to attack it.
+ if( !HasCondition(COND_SEE_ENEMY) || !HasCondition(COND_CAN_RANGE_ATTACK2) )
+ return SCHED_ESTABLISH_LINE_OF_FIRE;
+
+ if( HasCondition(COND_CAN_RANGE_ATTACK2) )
+ return SCHED_HUNTER_RANGE_ATTACK2;
+
+ return SCHED_HUNTER_SIEGE_STAND;
+ }
+ else
+ {
+ // Otherwise we are loitering in siege mode. Break line of sight with the player
+ // if they expose our position.
+ if( HasCondition( COND_SEE_PLAYER ) )
+ return SCHED_HUNTER_CHANGE_POSITION_SIEGE;
+ }
+
+ return SCHED_HUNTER_SIEGE_STAND;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::SelectSchedule()
+{
+ if ( hunter_stand_still.GetBool() )
+ {
+ m_bPlanted = false;
+ return SCHED_IDLE_STAND;
+ }
+
+ if ( HasCondition( COND_HUNTER_FORCED_DODGE ) )
+ return SCHED_HUNTER_DODGE;
+
+ if ( HasCondition( COND_HUNTER_NEW_HINTGROUP ) || ( GetHintGroup() != NULL_STRING && m_CheckHintGroupTimer.Expired() ) )
+ {
+ CAI_Hint *pHint;
+ CHintCriteria criteria;
+ criteria.SetGroup( GetHintGroup() );
+ criteria.SetFlag( bits_HINT_NODE_NEAREST );
+
+ if ( HasCondition( COND_HUNTER_NEW_HINTGROUP ) )
+ {
+ ClearCondition( COND_HUNTER_NEW_HINTGROUP );
+ if ( GetEnemy() )
+ {
+ pHint = CAI_HintManager::FindHint( NULL, GetEnemy()->GetAbsOrigin(), criteria );
+ }
+ else
+ {
+ pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
+ }
+
+ if ( pHint )
+ {
+ pHint->Lock( this );
+ }
+ }
+ else
+ {
+ pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
+ if ( pHint )
+ {
+ if ( (pHint->GetAbsOrigin() - GetAbsOrigin()).Length2DSqr() < Square( 20*12 ) )
+ {
+ m_CheckHintGroupTimer.Set( 5 );
+ pHint = NULL;
+ }
+ else
+ {
+ m_CheckHintGroupTimer.Set( 15 );
+ }
+ }
+ }
+
+ if ( pHint )
+ {
+ SetHintNode( pHint );
+ return SCHED_HUNTER_GOTO_HINT;
+ }
+ }
+
+ if ( HasCondition( COND_HUNTER_INCOMING_VEHICLE ) )
+ {
+ if ( m_RundownDelay.Expired() )
+ {
+ int iRundownCounter = 0;
+ if ( GetSquad() )
+ {
+ GetSquad()->GetSquadData( HUNTER_RUNDOWN_SQUADDATA, &iRundownCounter );
+ }
+
+ if ( iRundownCounter % 2 == 0 )
+ {
+ for ( int i = 0; i < g_Hunters.Count(); i++ )
+ {
+ if ( g_Hunters[i] != this )
+ {
+ g_Hunters[i]->m_RundownDelay.Set( 3 );
+ g_Hunters[i]->m_IgnoreVehicleTimer.Force();
+ }
+ }
+ m_IgnoreVehicleTimer.Set( hunter_dodge_warning.GetFloat() * 4 );
+ if ( hunter_dodge_debug.GetBool() )
+ {
+ Msg( "Hunter %d rundown\n", entindex() );
+ }
+
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ if ( m_bPlanted && HasCondition( COND_CAN_RANGE_ATTACK2 ) )
+ {
+ return SCHED_HUNTER_RANGE_ATTACK2;
+ }
+ else if ( random->RandomInt( 0, 1 ) )
+ {
+ return SCHED_HUNTER_CHARGE_ENEMY;
+ }
+ else
+ {
+ return SCHED_MOVE_AWAY;
+ }
+ }
+ else
+ {
+ SetTarget( UTIL_GetLocalPlayer() );
+ return SCHED_TARGET_FACE;
+ }
+ }
+ else
+ {
+ if ( hunter_dodge_debug.GetBool() )
+ {
+ Msg( "Hunter %d safe from rundown\n", entindex() );
+ }
+ for ( int i = 0; i < g_Hunters.Count(); i++ )
+ {
+ g_Hunters[i]->m_RundownDelay.Set( 4 );
+ g_Hunters[i]->m_IgnoreVehicleTimer.Force();
+ }
+ if ( GetSquad() )
+ {
+ GetSquad()->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, iRundownCounter + 1 );
+ }
+ }
+ }
+
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ if ( hunter_dodge_debug.GetBool() )
+ {
+ Msg( "Hunter %d try dodge\n", entindex() );
+ }
+ return SCHED_HUNTER_DODGE;
+ }
+ else
+ {
+ SetTarget( UTIL_GetLocalPlayer() );
+ return SCHED_TARGET_FACE;
+ }
+
+ CSound *pBestSound = GetBestSound( SOUND_PHYSICS_DANGER );
+ if ( pBestSound && ( pBestSound->SoundContext() & SOUND_CONTEXT_PLAYER_VEHICLE ) )
+ {
+ return SCHED_ALERT_FACE_BESTSOUND;
+ }
+ }
+
+ if ( HasCondition( COND_HUNTER_FORCED_FLANK_ENEMY ) )
+ {
+ return SCHED_HUNTER_FLANK_ENEMY;
+ }
+
+ if ( HasCondition( COND_HUNTER_STAGGERED ) /*|| HasCondition( COND_HUNTER_HIT_BY_STICKYBOMB )*/ )
+ {
+ return SCHED_HUNTER_STAGGER;
+ }
+
+ // Now that we're past all of the forced reactions to things, if we're running the siege
+ // behavior, go pick an appropriate siege schedule UNLESS we have an enemy. If we have
+ // an enemy, we should focus on attacking that enemy.
+ if( IsUsingSiegeTargets() )
+ {
+ return SelectSiegeSchedule();
+ }
+
+ // back away if there's a magnade glued to my head.
+ if ( hunter_retreat_striderbusters.GetBool() /*&& GetEnemy() && ( GetEnemy()->IsPlayer() )*/
+ && (m_hAttachedBusters.Count() > 0)
+ && m_fCorneredTimer < gpGlobals->curtime)
+ {
+ return SCHED_HUNTER_TAKE_COVER_FROM_ENEMY;
+ }
+
+ if ( !BehaviorSelectSchedule() )
+ {
+ switch ( GetState() )
+ {
+ case NPC_STATE_IDLE:
+ {
+ return SCHED_HUNTER_PATROL;
+ }
+
+ case NPC_STATE_ALERT:
+ {
+ if ( HasCondition( COND_HUNTER_SHOULD_PATROL ) )
+ return SCHED_HUNTER_PATROL;
+
+ break;
+ }
+
+ case NPC_STATE_COMBAT:
+ {
+ return SelectCombatSchedule();
+ }
+ }
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::TranslateSchedule( int scheduleType )
+{
+ switch ( scheduleType )
+ {
+ case SCHED_RANGE_ATTACK1:
+ {
+ return SCHED_HUNTER_RANGE_ATTACK1;
+ }
+
+ case SCHED_RANGE_ATTACK2:
+ case SCHED_HUNTER_RANGE_ATTACK2:
+ {
+ if ( scheduleType == SCHED_RANGE_ATTACK2 )
+ {
+ Msg( "HUNTER IGNORING SQUAD SLOTS\n" );
+ }
+
+ if ( IsStriderBuster( GetEnemy() ) )
+ {
+ // Attack as FAST as possible. The point is to shoot down the buster.
+ return SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER;
+ }
+
+ return SCHED_HUNTER_RANGE_ATTACK2;
+ }
+
+ case SCHED_MELEE_ATTACK1:
+ {
+ return SCHED_HUNTER_MELEE_ATTACK1;
+ }
+
+ case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK:
+ {
+ return SCHED_HUNTER_CHANGE_POSITION;
+ }
+
+ case SCHED_ALERT_STAND:
+ {
+ if ( !hunter_disable_patrol.GetBool() )
+ return SCHED_HUNTER_PATROL_RUN;
+ break;
+ }
+
+ case SCHED_COMBAT_FACE:
+ {
+ return SCHED_HUNTER_COMBAT_FACE;
+ }
+
+ case SCHED_HUNTER_PATROL:
+ {
+ if ( hunter_disable_patrol.GetBool() )
+ {
+ return SCHED_IDLE_STAND;
+ }
+ break;
+ }
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+
+//-----------------------------------------------------------------------------
+// catch blockage while escaping magnade
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::TaskFail( AI_TaskFailureCode_t code )
+{
+ if ( IsCurSchedule( SCHED_HUNTER_TAKE_COVER_FROM_ENEMY, false ) && ( code == FAIL_NO_ROUTE_BLOCKED ) )
+ {
+ // cornered!
+ if ( m_fCorneredTimer < gpGlobals->curtime )
+ {
+ m_fCorneredTimer = gpGlobals->curtime + 6.0f;
+ }
+ }
+
+ BaseClass::TaskFail( code );
+}
+
+
+//-----------------------------------------------------------------------------
+// The player is speeding toward us in a vehicle! Find a good activity for dodging.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::TaskFindDodgeActivity()
+{
+ if ( GetEnemy() == NULL )
+ {
+ TaskFail( "No enemy to dodge" );
+ return;
+ }
+
+ Vector vecUp;
+ Vector vecRight;
+ GetVectors( NULL, &vecRight, &vecUp );
+
+ // TODO: find most perpendicular 8-way dodge when we get the anims
+ Vector vecEnemyDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
+ //Vector vecDir = CrossProduct( vecEnemyDir, vecUp );
+ VectorNormalize( vecEnemyDir );
+ if ( fabs( DotProduct( vecEnemyDir, vecRight ) ) > 0.7 )
+ {
+ TaskFail( "Can't dodge, enemy approaching perpendicularly" );
+ return;
+ }
+
+ // Check left or right randomly first.
+ bool bDodgeLeft = false;
+ CBaseEntity *pVehicle = GetEnemyVehicle();
+ if ( pVehicle )
+ {
+ Ray_t enemyRay;
+ Ray_t perpendicularRay;
+ enemyRay.Init( pVehicle->GetAbsOrigin(), pVehicle->GetAbsOrigin() + pVehicle->GetSmoothedVelocity() );
+ Vector vPerpendicularPt = vecEnemyDir;
+ vPerpendicularPt.y = -vPerpendicularPt.y;
+ perpendicularRay.Init( GetAbsOrigin(), GetAbsOrigin() + vPerpendicularPt );
+
+ enemyRay.m_Start.z = enemyRay.m_Delta.z = enemyRay.m_StartOffset.z;
+ perpendicularRay.m_Start.z = perpendicularRay.m_Delta.z = perpendicularRay.m_StartOffset.z;
+
+ float t, s;
+
+ IntersectRayWithRay( perpendicularRay, enemyRay, t, s );
+
+ if ( t > 0 )
+ {
+ bDodgeLeft = true;
+ }
+ }
+ else if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ bDodgeLeft = true;
+ }
+
+ bool bFoundDir = false;
+ int nTries = 0;
+
+ while ( !bFoundDir && ( nTries < 2 ) )
+ {
+ // Pick a dodge activity to try.
+ if ( bDodgeLeft )
+ {
+ m_eDodgeActivity = ACT_HUNTER_DODGEL;
+ }
+ else
+ {
+ m_eDodgeActivity = ACT_HUNTER_DODGER;
+ }
+
+ // See where the dodge will put us.
+ Vector vecLocalDelta;
+ int nSeq = SelectWeightedSequence( m_eDodgeActivity );
+ GetSequenceLinearMotion( nSeq, &vecLocalDelta );
+
+ // Transform the sequence delta into local space.
+ matrix3x4_t fRotateMatrix;
+ AngleMatrix( GetLocalAngles(), fRotateMatrix );
+ Vector vecDelta;
+ VectorRotate( vecLocalDelta, fRotateMatrix, vecDelta );
+
+ // Trace a bit high so this works better on uneven terrain.
+ Vector testHullMins = GetHullMins();
+ testHullMins.z += ( StepHeight() * 2 );
+
+ // See if all is clear in that direction.
+ trace_t tr;
+ HunterTraceHull_SkipPhysics( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, GetCollisionGroup(), &tr, VPhysicsGetObject()->GetMass() * 0.5f );
+
+ // TODO: dodge anyway if we'll make it a certain percentage of the way through the dodge?
+ if ( tr.fraction == 1.0f )
+ {
+ //NDebugOverlay::SweptBox( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), QAngle( 0, 0, 0 ), 0, 255, 0, 128, 5 );
+ bFoundDir = true;
+ TaskComplete();
+ }
+ else
+ {
+ //NDebugOverlay::SweptBox( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), QAngle( 0, 0, 0 ), 255, 0, 0, 128, 5 );
+ nTries++;
+ bDodgeLeft = !bDodgeLeft;
+ }
+ }
+
+ if ( nTries < 2 )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ TaskFail( "Couldn't find dodge position\n" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_HUNTER_FINISH_RANGE_ATTACK:
+ {
+ if( GetEnemy() != NULL && GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ {
+ // Just finished shooting at Alyx! So forget her for a little while and get back on the player
+ // !!!LATER - make sure there's someone else in enemy memory to go bother.
+ GetEnemies()->SetTimeValidEnemy( GetEnemy(), gpGlobals->curtime + 10.0f );
+ }
+
+ if( m_hCurrentSiegeTarget )
+ {
+ // We probably just fired at our siege target, so dump it.
+ KillCurrentSiegeTarget();
+ }
+
+ TaskComplete();
+ }
+
+ case TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY:
+ {
+ ChainStartTask( TASK_WAIT_FOR_MOVEMENT, pTask->flTaskData );
+ break;
+ }
+
+ case TASK_HUNTER_BEGIN_FLANK:
+ {
+ if ( IsInSquad() && GetSquad()->NumMembers() > 1 )
+ {
+ // Flank relative to the other shooter in our squad.
+ // If there's no other shooter, just flank relative to any squad member.
+ AISquadIter_t iter;
+ CAI_BaseNPC *pNPC = GetSquad()->GetFirstMember( &iter );
+ while ( pNPC == this )
+ {
+ pNPC = GetSquad()->GetNextMember( &iter );
+ }
+
+ m_vSavePosition = pNPC->GetAbsOrigin();
+ }
+ else
+ {
+ // Flank relative to our current position.
+ m_vSavePosition = GetAbsOrigin();
+ }
+
+ TaskComplete();
+ break;
+ }
+
+ case TASK_HUNTER_ANNOUNCE_FLANK:
+ {
+ EmitSound( "NPC_Hunter.FlankAnnounce" );
+ TaskComplete();
+ break;
+ }
+
+ case TASK_HUNTER_DODGE:
+ {
+ if ( hunter_dodge_debug. GetBool() )
+ {
+ Msg( "Hunter %d dodging\n", entindex() );
+ }
+ SetIdealActivity( m_eDodgeActivity );
+ break;
+ }
+
+ // Guarantee a certain delay between volleys. If we aren't already planted,
+ // the plant transition animation will take care of that.
+ case TASK_HUNTER_PRE_RANGE_ATTACK2:
+ {
+ if ( !m_bPlanted || ( GetEnemy() && IsStriderBuster( GetEnemy() ) ) )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ SetIdealActivity( ACT_HUNTER_ANGRY );
+ }
+ break;
+ }
+
+ case TASK_HUNTER_SHOOT_COMMIT:
+ {
+ // We're committing to shooting. Don't allow interrupts until after we've shot a bit (see TASK_RANGE_ATTACK1).
+ m_flShootAllowInterruptTime = gpGlobals->curtime + 100.0f;
+ TaskComplete();
+ break;
+ }
+
+ case TASK_RANGE_ATTACK2:
+ {
+ if ( GetEnemy() )
+ {
+ bool bIsBuster = IsStriderBuster( GetEnemy() );
+ if ( bIsBuster )
+ {
+ AddFacingTarget( GetEnemy(), GetEnemy()->GetAbsOrigin() + GetEnemy()->GetSmoothedVelocity() * .5, 1.0, 0.8 );
+ }
+
+ // Start the firing sound.
+ //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ //controller.SoundChangeVolume( m_pGunFiringSound, 1.0, hunter_first_flechette_delay.GetFloat() );
+
+ SetIdealActivity( ACT_RANGE_ATTACK2 );
+
+ // Decide how many shots to fire.
+ int nShots = hunter_flechette_volley_size.GetInt();
+ if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
+ {
+ nShots--;
+ }
+
+ // Decide when to fire the first shot.
+ float initialDelay = hunter_first_flechette_delay.GetFloat();
+ if ( bIsBuster )
+ {
+ initialDelay = 0; //*= 0.5;
+ }
+
+ BeginVolley( nShots, gpGlobals->curtime + initialDelay );
+
+ // In case we need to miss on purpose, pick a direction now.
+ m_bMissLeft = false;
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ m_bMissLeft = true;
+ }
+
+ LockBothEyes( initialDelay + ( nShots * hunter_flechette_delay.GetFloat() ) );
+ }
+ else
+ {
+ TaskFail( FAIL_NO_ENEMY );
+ }
+
+ break;
+ }
+
+ case TASK_HUNTER_STAGGER:
+ {
+ // Stagger in the direction the impact force would push us.
+ VMatrix worldToLocalRotation = EntityToWorldTransform();
+ Vector vecLocalStaggerDir = worldToLocalRotation.InverseTR().ApplyRotation( m_vecStaggerDir );
+
+ float flStaggerYaw = VecToYaw( vecLocalStaggerDir );
+ SetPoseParameter( gm_nStaggerYawPoseParam, flStaggerYaw );
+
+ // Go straight there!
+ SetActivity( ACT_RESET );
+ SetActivity( ( Activity )ACT_HUNTER_STAGGER );
+ break;
+ }
+
+ case TASK_MELEE_ATTACK1:
+ {
+ SetLastAttackTime( gpGlobals->curtime );
+
+ if ( GetEnemy() && GetEnemy()->IsPlayer() )
+ {
+ ResetIdealActivity( ( Activity )ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER );
+ }
+ else
+ {
+ ResetIdealActivity( ACT_MELEE_ATTACK1 );
+ }
+
+ break;
+ }
+
+ case TASK_HUNTER_CORNERED_TIMER:
+ {
+ m_fCorneredTimer = gpGlobals->curtime + pTask->flTaskData;
+
+ break;
+ }
+
+ case TASK_HUNTER_FIND_SIDESTEP_POSITION:
+ {
+ if ( GetEnemy() == NULL )
+ {
+ TaskFail( "No enemy to sidestep" );
+ }
+ else
+ {
+ Vector vecUp;
+ GetVectors( NULL, NULL, &vecUp );
+
+ Vector vecEnemyDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
+ Vector vecDir = CrossProduct( vecEnemyDir, vecUp );
+ VectorNormalize( vecDir );
+
+ // Sidestep left or right randomly.
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ vecDir *= -1;
+ }
+
+ // Start high and then trace down so that it works on uneven terrain.
+ Vector vecPos = GetAbsOrigin() + Vector( 0, 0, 64 ) + random->RandomFloat( 120, 200 ) * vecDir;
+
+ // Try to find the ground at the sidestep position.
+ trace_t tr;
+ UTIL_TraceLine( vecPos, vecPos + Vector( 0, 0, -128 ), MASK_NPCSOLID, NULL, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction < 1.0f )
+ {
+ //NDebugOverlay::Line( vecPos, tr.endpos, 0, 255, 0, true, 10 );
+
+ m_vSavePosition = tr.endpos;
+
+ TaskComplete();
+ }
+ else
+ {
+ TaskFail( "Couldn't find sidestep position\n" );
+ }
+ }
+
+ break;
+ }
+
+ case TASK_HUNTER_FIND_DODGE_POSITION:
+ {
+ TaskFindDodgeActivity();
+ break;
+ }
+
+ case TASK_HUNTER_CHARGE:
+ {
+ SetIdealActivity( ( Activity )ACT_HUNTER_CHARGE_START );
+ break;
+ }
+
+ case TASK_HUNTER_CHARGE_DELAY:
+ {
+ m_flNextChargeTime = gpGlobals->curtime + pTask->flTaskData;
+ TaskComplete();
+ break;
+ }
+
+ case TASK_DIE:
+ {
+ GetNavigator()->StopMoving();
+ ResetActivity();
+ SetIdealActivity( GetDeathActivity() );
+ m_lifeState = LIFE_DYING;
+
+ break;
+ }
+
+ //case TASK_HUNTER_END_FLANK:
+ //{
+ //
+ //}
+
+ default:
+ {
+ BaseClass::StartTask( pTask );
+ break;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_HUNTER_PRE_RANGE_ATTACK2:
+ {
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+
+ case TASK_RANGE_ATTACK2:
+ {
+ if( !hunter_hate_thrown_striderbusters.GetBool() && GetEnemy() != NULL && IsStriderBuster( GetEnemy() ) )
+ {
+ if( !IsValidEnemy(GetEnemy()) )
+ {
+ TaskFail("No longer hate this StriderBuster");
+ }
+ }
+
+ bool bIsBuster = IsStriderBuster( GetEnemy() );
+ if ( bIsBuster )
+ {
+ Vector vFuturePosition = GetEnemy()->GetAbsOrigin() + GetEnemy()->GetSmoothedVelocity() * .3;
+ AddFacingTarget( GetEnemy(), vFuturePosition, 1.0, 0.8 );
+
+ Vector2D vToFuturePositon = ( vFuturePosition.AsVector2D() - GetAbsOrigin().AsVector2D() );
+ vToFuturePositon.NormalizeInPlace();
+ Vector2D facingDir = BodyDirection2D().AsVector2D();
+
+ float flDot = DotProduct2D( vToFuturePositon, facingDir );
+
+ if ( flDot < .4 )
+ {
+ GetMotor()->SetIdealYawToTarget( vFuturePosition );
+ GetMotor()->UpdateYaw();
+ break;
+ }
+ }
+
+ if ( gpGlobals->curtime >= m_flNextFlechetteTime )
+ {
+ // Must have an enemy and a shot queued up.
+ bool bDone = false;
+ if ( GetEnemy() != NULL && m_nFlechettesQueued > 0 )
+ {
+ if ( ShootFlechette( GetEnemy(), false ) )
+ {
+ m_nClampedShots++;
+ }
+ else
+ {
+ m_nClampedShots = 0;
+ }
+
+ m_nFlechettesQueued--;
+
+ // If we fired three or more clamped shots in a row, call it quits so we don't look dumb.
+ if ( ( m_nClampedShots >= 3 ) || ( m_nFlechettesQueued == 0 ) )
+ {
+ bDone = true;
+ }
+ else
+ {
+ // More shooting to do. Schedule our next flechette.
+ m_flNextFlechetteTime = gpGlobals->curtime + hunter_flechette_delay.GetFloat();
+ }
+ }
+ else
+ {
+ bDone = true;
+ }
+
+ if ( bDone )
+ {
+ // Stop the firing sound.
+ //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ //controller.SoundChangeVolume( m_pGunFiringSound, 0.0f, 0.1f );
+
+ DelayRangedAttackers( hunter_flechette_volley_end_min_delay.GetFloat(), hunter_flechette_volley_end_max_delay.GetFloat(), true );
+ TaskComplete();
+ }
+ }
+
+ break;
+ }
+
+ case TASK_GET_PATH_TO_ENEMY_LOS:
+ {
+ ChainRunTask( TASK_GET_PATH_TO_ENEMY_LKP_LOS, pTask->flTaskData );
+ break;
+ }
+
+ case TASK_HUNTER_DODGE:
+ {
+ AutoMovement();
+
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+
+ case TASK_HUNTER_CORNERED_TIMER:
+ {
+ TaskComplete();
+ break;
+ }
+
+ case TASK_HUNTER_STAGGER:
+ {
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+
+ case TASK_HUNTER_CHARGE:
+ {
+ Activity eActivity = GetActivity();
+
+ // See if we're trying to stop after hitting/missing our target
+ if ( eActivity == ACT_HUNTER_CHARGE_STOP || eActivity == ACT_HUNTER_CHARGE_CRASH )
+ {
+ if ( IsActivityFinished() )
+ {
+ m_flNextChargeTime = gpGlobals->curtime + hunter_charge_min_delay.GetFloat() + random->RandomFloat( 0, 2.5 ) + random->RandomFloat( 0, 2.5 );
+ float delayMultiplier = ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) ? 1.5 : 1.0;
+ float groupDelay = gpGlobals->curtime + ( 2.0 + random->RandomFloat( 0, 2 ) ) * delayMultiplier;
+ for ( int i = 0; i < g_Hunters.Count(); i++ )
+ {
+ if ( g_Hunters[i] != this && g_Hunters[i]->m_flNextChargeTime < groupDelay )
+ {
+ g_Hunters[i]->m_flNextChargeTime = groupDelay;
+ }
+ }
+ TaskComplete();
+ return;
+ }
+
+ // Still in the process of slowing down. Run movement until it's done.
+ AutoMovement();
+ return;
+ }
+
+ // Check for manual transition
+ if ( ( eActivity == ACT_HUNTER_CHARGE_START ) && ( IsActivityFinished() ) )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_RUN );
+ }
+
+ // See if we're still running
+ if ( eActivity == ACT_HUNTER_CHARGE_RUN || eActivity == ACT_HUNTER_CHARGE_START )
+ {
+ if ( HasCondition( COND_NEW_ENEMY ) || HasCondition( COND_LOST_ENEMY ) || HasCondition( COND_ENEMY_DEAD ) )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
+ return;
+ }
+ else
+ {
+ if ( GetEnemy() != NULL )
+ {
+ Vector goalDir = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
+ VectorNormalize( goalDir );
+
+ if ( DotProduct( BodyDirection2D(), goalDir ) < 0.25f )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
+ }
+ }
+ }
+ }
+
+ // Steer towards our target
+ float idealYaw;
+ if ( GetEnemy() == NULL )
+ {
+ idealYaw = GetMotor()->GetIdealYaw();
+ }
+ else
+ {
+ idealYaw = CalcIdealYaw( GetEnemy()->GetAbsOrigin() );
+ }
+
+ // Add in our steering offset
+ idealYaw += ChargeSteer();
+
+ // Turn to face
+ GetMotor()->SetIdealYawAndUpdate( idealYaw );
+
+ // See if we're going to run into anything soon
+ ChargeLookAhead();
+
+ // Let our animations simply move us forward. Keep the result
+ // of the movement so we know whether we've hit our target.
+ AIMoveTrace_t moveTrace;
+ if ( AutoMovement( GetEnemy(), &moveTrace ) == false )
+ {
+ // Only stop if we hit the world
+ if ( HandleChargeImpact( moveTrace.vEndPosition, moveTrace.pObstruction ) )
+ {
+ // If we're starting up, this is an error
+ if ( eActivity == ACT_HUNTER_CHARGE_START )
+ {
+ TaskFail( "Unable to make initial movement of charge\n" );
+ return;
+ }
+
+ // Crash unless we're trying to stop already
+ if ( eActivity != ACT_HUNTER_CHARGE_STOP )
+ {
+ if ( moveTrace.fStatus == AIMR_BLOCKED_WORLD && moveTrace.vHitNormal == vec3_origin )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
+ }
+ else
+ {
+ // Shake the screen
+ if ( moveTrace.fStatus != AIMR_BLOCKED_NPC )
+ {
+ EmitSound( "NPC_Hunter.ChargeHitWorld" );
+ UTIL_ScreenShake( GetAbsOrigin(), 16.0f, 4.0f, 1.0f, 400.0f, SHAKE_START );
+ }
+ SetIdealActivity( ACT_HUNTER_CHARGE_CRASH );
+ }
+ }
+ }
+ else if ( moveTrace.pObstruction )
+ {
+ // If we hit another hunter, stop
+ if ( moveTrace.pObstruction->Classify() == CLASS_COMBINE_HUNTER )
+ {
+ // Crash unless we're trying to stop already
+ if ( eActivity != ACT_HUNTER_CHARGE_STOP )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
+ }
+ }
+ // If we hit an antlion, don't stop, but kill it
+ // We never have hunters and antlions together, but you never know.
+ else if (moveTrace.pObstruction->Classify() == CLASS_ANTLION )
+ {
+ if ( FClassnameIs( moveTrace.pObstruction, "npc_antlionguard" ) )
+ {
+ // Crash unless we're trying to stop already
+ if ( eActivity != ACT_HUNTER_CHARGE_STOP )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
+ }
+ }
+ else
+ {
+ Hunter_ApplyChargeDamage( this, moveTrace.pObstruction, moveTrace.pObstruction->GetHealth() );
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ case TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY:
+ {
+ if ( GetEnemy() )
+ {
+ Vector vecEnemyLKP = GetEnemyLKP();
+ AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.8 );
+ }
+ ChainRunTask( TASK_WAIT_FOR_MOVEMENT, pTask->flTaskData );
+ break;
+ }
+
+ default:
+ {
+ BaseClass::RunTask( pTask );
+ break;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Return true if our charge target is right in front of the hunter.
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::EnemyIsRightInFrontOfMe( CBaseEntity **pEntity )
+{
+ if ( !GetEnemy() )
+ return false;
+
+ if ( (GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter()).LengthSqr() < (156*156) )
+ {
+ Vector vecLOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
+ vecLOS.z = 0;
+ VectorNormalize( vecLOS );
+ Vector vBodyDir = BodyDirection2D();
+ if ( DotProduct( vecLOS, vBodyDir ) > 0.8 )
+ {
+ // He's in front of me, and close. Make sure he's not behind a wall.
+ trace_t tr;
+ UTIL_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), GetHullMins() * 0.5, GetHullMaxs() * 0.5, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.m_pEnt == GetEnemy() )
+ {
+ *pEntity = tr.m_pEnt;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// While charging, look ahead and see if we're going to run into anything.
+// If we are, start the gesture so it looks like we're anticipating the hit.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::ChargeLookAhead( void )
+{
+#if 0
+ trace_t tr;
+ Vector vecForward;
+ GetVectors( &vecForward, NULL, NULL );
+ Vector vecTestPos = GetAbsOrigin() + ( vecForward * m_flGroundSpeed * 0.75 );
+ Vector testHullMins = GetHullMins();
+ testHullMins.z += (StepHeight() * 2);
+ HunterTraceHull_SkipPhysics( GetAbsOrigin(), vecTestPos, testHullMins, GetHullMaxs(), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5 );
+
+ //NDebugOverlay::Box( tr.startpos, testHullMins, GetHullMaxs(), 0, 255, 0, true, 0.1f );
+ //NDebugOverlay::Box( vecTestPos, testHullMins, GetHullMaxs(), 255, 0, 0, true, 0.1f );
+
+ if ( tr.fraction != 1.0 )
+ {
+ // dvs: TODO:
+ // Start playing the hit animation
+ //AddGesture( ACT_HUNTER_CHARGE_ANTICIPATION );
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+float CNPC_Hunter::ChargeSteer()
+{
+ trace_t tr;
+ Vector testPos, steer, forward, right;
+ QAngle angles;
+ const float testLength = m_flGroundSpeed * 0.15f;
+
+ //Get our facing
+ GetVectors( &forward, &right, NULL );
+
+ steer = forward;
+
+ const float faceYaw = UTIL_VecToYaw( forward );
+
+ //Offset right
+ VectorAngles( forward, angles );
+ angles[YAW] += 45.0f;
+ AngleVectors( angles, &forward );
+
+ // Probe out
+ testPos = GetAbsOrigin() + ( forward * testLength );
+
+ // Offset by step height
+ Vector testHullMins = GetHullMins();
+ testHullMins.z += (StepHeight() * 2);
+
+ // Probe
+ HunterTraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );
+
+ // Debug info
+ if ( g_debug_hunter_charge.GetInt() == 1 )
+ {
+ if ( tr.fraction == 1.0f )
+ {
+ NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
+ }
+ else
+ {
+ NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
+ }
+ }
+
+ // Add in this component
+ steer += ( right * 0.5f ) * ( 1.0f - tr.fraction );
+
+ // Offset left
+ angles[YAW] -= 90.0f;
+ AngleVectors( angles, &forward );
+
+ // Probe out
+ testPos = GetAbsOrigin() + ( forward * testLength );
+ HunterTraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );
+
+ // Debug
+ if ( g_debug_hunter_charge.GetInt() == 1 )
+ {
+ if ( tr.fraction == 1.0f )
+ {
+ NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
+ }
+ else
+ {
+ NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
+ }
+ }
+
+ // Add in this component
+ steer -= ( right * 0.5f ) * ( 1.0f - tr.fraction );
+
+ // Debug
+ if ( g_debug_hunter_charge.GetInt() == 1 )
+ {
+ NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( steer * 512.0f ), 255, 255, 0, true, 0.1f );
+ NDebugOverlay::Cross3D( GetAbsOrigin() + ( steer * 512.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 255, 0, true, 0.1f );
+
+ NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), 255, 0, 255, true, 0.1f );
+ NDebugOverlay::Cross3D( GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 0, 255, true, 0.1f );
+ }
+
+ return UTIL_AngleDiff( UTIL_VecToYaw( steer ), faceYaw );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::ChargeDamage( CBaseEntity *pTarget )
+{
+ if ( pTarget == NULL )
+ return;
+
+ CBasePlayer *pPlayer = ToBasePlayer( pTarget );
+
+ if ( pPlayer != NULL )
+ {
+ //Kick the player angles
+ pPlayer->ViewPunch( QAngle( 20, 20, -30 ) );
+
+ Vector dir = pPlayer->WorldSpaceCenter() - WorldSpaceCenter();
+ VectorNormalize( dir );
+ dir.z = 0.0f;
+
+ Vector vecNewVelocity = dir * 250.0f;
+ vecNewVelocity[2] += 128.0f;
+ pPlayer->SetAbsVelocity( vecNewVelocity );
+
+ color32 red = {128,0,0,128};
+ UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN );
+ }
+
+ // Player takes less damage
+ float flDamage = ( pPlayer == NULL ) ? 250 : sk_hunter_dmg_charge.GetFloat();
+
+ // If it's being held by the player, break that bond
+ Pickup_ForcePlayerToDropThisObject( pTarget );
+
+ // Calculate the physics force
+ Hunter_ApplyChargeDamage( this, pTarget, flDamage );
+}
+
+
+//-----------------------------------------------------------------------------
+// Handles the hunter charging into something. Returns true if it hit the world.
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity )
+{
+ // Cause a shock wave from this point which will disrupt nearby physics objects
+ //ImpactShock( vecImpact, 128, 350 );
+
+ // Did we hit anything interesting?
+ if ( !pEntity || pEntity->IsWorld() )
+ {
+ // Robin: Due to some of the finicky details in the motor, the hunter will hit
+ // the world when it is blocked by our enemy when trying to step up
+ // during a moveprobe. To get around this, we see if the enemy's within
+ // a volume in front of the hunter when we hit the world, and if he is,
+ // we hit him anyway.
+ EnemyIsRightInFrontOfMe( &pEntity );
+
+ // Did we manage to find him? If not, increment our charge miss count and abort.
+ if ( pEntity->IsWorld() )
+ {
+ return true;
+ }
+ }
+
+ // Hit anything we don't like
+ if ( IRelationType( pEntity ) == D_HT && ( GetNextAttack() < gpGlobals->curtime ) )
+ {
+ EmitSound( "NPC_Hunter.ChargeHitEnemy" );
+
+ // dvs: TODO:
+ //if ( !IsPlayingGesture( ACT_HUNTER_CHARGE_HIT ) )
+ //{
+ // RestartGesture( ACT_HUNTER_CHARGE_HIT );
+ //}
+
+ ChargeDamage( pEntity );
+
+ if ( !pEntity->IsNPC() )
+ {
+ pEntity->ApplyAbsVelocityImpulse( ( BodyDirection2D() * 400 ) + Vector( 0, 0, 200 ) );
+ }
+
+ if ( !pEntity->IsAlive() && GetEnemy() == pEntity )
+ {
+ SetEnemy( NULL );
+ }
+
+ SetNextAttack( gpGlobals->curtime + 2.0f );
+
+ if ( !pEntity->IsAlive() || !pEntity->IsNPC() )
+ {
+ SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
+ return false;
+ }
+ else
+ return true;
+
+ }
+
+ // Hit something we don't hate. If it's not moveable, crash into it.
+ if ( pEntity->GetMoveType() == MOVETYPE_NONE || pEntity->GetMoveType() == MOVETYPE_PUSH )
+ {
+ CBreakable *pBreakable = dynamic_cast<CBreakable *>(pEntity);
+ if ( pBreakable && pBreakable->IsBreakable() && pBreakable->m_takedamage == DAMAGE_YES && pBreakable->GetHealth() > 0 )
+ {
+ ChargeDamage( pEntity );
+ }
+ return true;
+ }
+
+ // If it's a vphysics object that's too heavy, crash into it too.
+ if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
+ if ( pPhysics )
+ {
+ // If the object is being held by the player, knock it out of his hands
+ if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
+ {
+ Pickup_ForcePlayerToDropThisObject( pEntity );
+ return false;
+ }
+
+ if ( !pPhysics->IsMoveable() )
+ return true;
+
+ float entMass = PhysGetEntityMass( pEntity ) ;
+ float minMass = VPhysicsGetObject()->GetMass() * 0.5f;
+ if ( entMass < minMass )
+ {
+ if ( entMass < minMass * 0.666f || pEntity->CollisionProp()->BoundingRadius() < GetHullHeight() )
+ {
+ if ( pEntity->GetHealth() > 0 )
+ {
+ CBreakableProp *pBreakable = dynamic_cast<CBreakableProp *>(pEntity);
+ if ( pBreakable && pBreakable->m_takedamage == DAMAGE_YES && pBreakable->GetHealth() > 0 && pBreakable->GetHealth() <= 50 )
+ {
+ ChargeDamage( pEntity );
+ }
+ }
+ pEntity->SetNavIgnore( 2.0 );
+ return false;
+ }
+ }
+ return true;
+
+ }
+ }
+
+ return false;
+}
+
+
+//-------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------
+void CNPC_Hunter::Explode()
+{
+ Vector velocity = vec3_origin;
+ AngularImpulse angVelocity = RandomAngularImpulse( -150, 150 );
+
+ PropBreakableCreateAll( GetModelIndex(), NULL, EyePosition(), GetAbsAngles(), velocity, angVelocity, 1.0, 150, COLLISION_GROUP_NPC, this );
+
+ ExplosionCreate( EyePosition(), GetAbsAngles(), this, 500, 256, (SF_ENVEXPLOSION_NOPARTICLES|SF_ENVEXPLOSION_NOSPARKS|SF_ENVEXPLOSION_NODLIGHTS|SF_ENVEXPLOSION_NODAMAGE|SF_ENVEXPLOSION_NOSMOKE), false );
+
+ // Create liquid fountain gushtacular effect here!
+ CEffectData data;
+
+ data.m_vOrigin = EyePosition();
+ data.m_vNormal = Vector( 0, 0, 1 );
+ data.m_flScale = 4.0f;
+
+ DispatchEffect( "StriderBlood", data );
+
+ // Go away
+ m_lifeState = LIFE_DEAD;
+
+ SetThink( &CNPC_Hunter::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ AddEffects( EF_NODRAW );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Activity CNPC_Hunter::NPC_TranslateActivity( Activity baseAct )
+{
+ if ( ( baseAct == ACT_WALK ) || ( baseAct == ACT_RUN ) )
+ {
+ if ( GetEnemy() )
+ {
+ Vector vecEnemyLKP = GetEnemyLKP();
+
+ // Only start facing when we're close enough
+ if ( UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST )
+ {
+ return (Activity)ACT_HUNTER_WALK_ANGRY;
+ }
+ }
+ }
+ else if ( ( baseAct == ACT_IDLE ) && m_bPlanted )
+ {
+ return ( Activity )ACT_HUNTER_IDLE_PLANTED;
+ }
+ else if ( baseAct == ACT_RANGE_ATTACK2 )
+ {
+ if ( !m_bPlanted && ( m_bEnableUnplantedShooting || IsStriderBuster( GetEnemy() ) ) )
+ {
+ return (Activity)ACT_HUNTER_RANGE_ATTACK2_UNPLANTED;
+ }
+ }
+
+ return BaseClass::NPC_TranslateActivity( baseAct );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::HandleAnimEvent( animevent_t *pEvent )
+{
+ Vector footPosition;
+ QAngle angles;
+
+ if ( pEvent->event == AE_HUNTER_FOOTSTEP_LEFT )
+ {
+ LeftFootHit( pEvent->eventtime );
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_FOOTSTEP_RIGHT )
+ {
+ RightFootHit( pEvent->eventtime );
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_FOOTSTEP_BACK )
+ {
+ BackFootHit( pEvent->eventtime );
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_START_EXPRESSION )
+ {
+ if ( pEvent->options && Q_strlen( pEvent->options ) )
+ {
+ //m_iszCurrentExpression = AllocPooledString( pEvent->options );
+ //SetExpression( pEvent->options );
+ }
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_END_EXPRESSION )
+ {
+ if ( pEvent->options && Q_strlen( pEvent->options ) )
+ {
+ //m_iszCurrentExpression = NULL_STRING;
+ //RemoveActorFromScriptedScenes( this, true, false, pEvent->options );
+ }
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_MELEE_ANNOUNCE )
+ {
+ EmitSound( "NPC_Hunter.MeleeAnnounce" );
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_MELEE_ATTACK_LEFT )
+ {
+ Vector right, forward, dir;
+ AngleVectors( GetLocalAngles(), &forward, &right, NULL );
+
+ right = right * -100;
+ forward = forward * 600;
+ dir = right + forward;
+ QAngle angle( 25, 30, -20 );
+
+ MeleeAttack( HUNTER_MELEE_REACH, sk_hunter_dmg_one_slash.GetFloat(), angle, dir, HUNTER_BLOOD_LEFT_FOOT );
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_MELEE_ATTACK_RIGHT )
+ {
+ Vector right, forward,dir;
+ AngleVectors( GetLocalAngles(), &forward, &right, NULL );
+
+ right = right * 100;
+ forward = forward * 600;
+ dir = right + forward;
+
+ QAngle angle( 25, -30, 20 );
+
+ MeleeAttack( HUNTER_MELEE_REACH, sk_hunter_dmg_one_slash.GetFloat(), angle, dir, HUNTER_BLOOD_LEFT_FOOT );
+ return;
+ }
+
+ if ( pEvent->event == AE_HUNTER_SPRAY_BLOOD )
+ {
+ Vector vecOrigin;
+ Vector vecDir;
+
+ // spray blood from the attachment point
+ bool bGotAttachment = false;
+ if ( pEvent->options )
+ {
+ QAngle angDir;
+ if ( GetAttachment( pEvent->options, vecOrigin, angDir ) )
+ {
+ bGotAttachment = true;
+ AngleVectors( angDir, &vecDir, NULL, NULL );
+ }
+ }
+
+ // fall back to our center, tracing forward
+ if ( !bGotAttachment )
+ {
+ vecOrigin = WorldSpaceCenter();
+ GetVectors( &vecDir, NULL, NULL );
+ }
+
+ UTIL_BloodSpray( vecOrigin, vecDir, BLOOD_COLOR_RED, 4, FX_BLOODSPRAY_ALL );
+
+ for ( int i = 0 ; i < 3 ; i++ )
+ {
+ Vector vecTraceDir = vecDir;
+ vecTraceDir.x += random->RandomFloat( -0.1, 0.1 );
+ vecTraceDir.y += random->RandomFloat( -0.1, 0.1 );
+ vecTraceDir.z += random->RandomFloat( -0.1, 0.1 );
+
+ trace_t tr;
+ AI_TraceLine( vecOrigin, vecOrigin + ( vecTraceDir * 192.0f ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction != 1.0 )
+ {
+ UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED );
+ }
+ }
+
+ return;
+ }
+
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority )
+{
+ if ( nDisposition == D_HT && pEntity->ClassMatches("npc_bullseye") )
+ UpdateEnemyMemory( pEntity, pEntity->GetAbsOrigin() );
+ BaseClass::AddEntityRelationship( pEntity, nDisposition, nPriority );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity )
+{
+ if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK1, false ) )
+ {
+ SetGoalEnt( pGoalEntity );
+ return true;
+ }
+ return BaseClass::ScheduledMoveToGoalEntity( scheduleType, pGoalEntity, movementActivity );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::OnChangeHintGroup( string_t oldGroup, string_t newGroup )
+{
+ SetCondition( COND_HUNTER_NEW_HINTGROUP );
+ m_CheckHintGroupTimer.Set( 10 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Tells whether any given hunter is in a squad that contains other hunters.
+// This is useful for preventing timid behavior for Hunters that are not
+// supported by other hunters.
+//
+// NOTE: This counts the self! So a hunter that is alone in his squad
+// receives a result of 1.
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::NumHuntersInMySquad()
+{
+ AISquadIter_t iter;
+ CAI_BaseNPC *pSquadmate = m_pSquad ? m_pSquad->GetFirstMember( &iter ) : NULL;
+
+ if( !pSquadmate )
+ {
+ // Not in a squad at all, but the caller is not concerned with that. Just
+ // tell them that we're in a squad of one (ourself)
+ return 1;
+ }
+
+ int count = 0;
+
+ while ( pSquadmate )
+ {
+ if( pSquadmate->m_iClassname == m_iClassname )
+ count++;
+
+ pSquadmate = m_pSquad->GetNextMember( &iter );
+ }
+
+ return count;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::FollowStrider( const char *szStrider )
+{
+ if ( !szStrider )
+ return;
+
+ CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, szStrider, this );
+ CNPC_Strider *pStrider = dynamic_cast <CNPC_Strider *>( pEnt );
+ FollowStrider(pStrider);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::FollowStrider( CNPC_Strider * pStrider )
+{
+ if ( !IsAlive() )
+ {
+ return;
+ }
+
+ if ( pStrider )
+ {
+ if ( m_EscortBehavior.GetFollowTarget() != pStrider )
+ {
+ m_iszFollowTarget = pStrider->GetEntityName();
+ if ( m_iszFollowTarget == NULL_STRING )
+ {
+ m_iszFollowTarget = AllocPooledString( "unnamed_strider" );
+ }
+ m_EscortBehavior.SetEscortTarget( pStrider );
+ }
+ }
+ else
+ {
+ DevWarning("Hunter set to follow entity %s that is not a strider\n", STRING( m_iszFollowTarget ) );
+ m_iszFollowTarget = AllocPooledString( "unknown_strider" );
+ }
+}
+
+void CAI_HunterEscortBehavior::SetEscortTarget( CNPC_Strider *pStrider, bool fFinishCurSchedule )
+{
+ m_bEnabled = true;
+
+ if ( GetOuter()->GetSquad() )
+ {
+ GetOuter()->GetSquad()->RemoveFromSquad( GetOuter() );
+ }
+
+ for ( int i = 0; i < g_Hunters.Count(); i++ )
+ {
+ if ( g_Hunters[i]->m_EscortBehavior.GetFollowTarget() == pStrider )
+ {
+ Assert( g_Hunters[i]->GetSquad() );
+ g_Hunters[i]->GetSquad()->AddToSquad( GetOuter() );
+ break;
+ }
+ }
+
+ if ( !GetOuter()->GetSquad() )
+ {
+ GetOuter()->AddToSquad( AllocPooledString( CFmtStr( "%s_hunter_squad", STRING( pStrider->GetEntityName() ) ) ) );
+ }
+
+ BaseClass::SetFollowTarget( pStrider );
+ m_flTimeEscortReturn = gpGlobals->curtime;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputEnableUnplantedShooting( inputdata_t &inputdata )
+{
+ m_bEnableUnplantedShooting = true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputDisableUnplantedShooting( inputdata_t &inputdata )
+{
+ m_bEnableUnplantedShooting = false;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputFollowStrider( inputdata_t &inputdata )
+{
+ m_iszFollowTarget = inputdata.value.StringID();
+ if ( m_iszFollowTarget == s_iszStriderClassname )
+ {
+ m_EscortBehavior.m_bEnabled = true;
+ m_iszFollowTarget = NULL_STRING;
+ }
+ m_BeginFollowDelay.Start( .1 ); // Allow time for strider to spawn
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputUseSiegeTargets( inputdata_t &inputdata )
+{
+ m_iszSiegeTargetName = inputdata.value.StringID();
+ m_flTimeNextSiegeTargetAttack = gpGlobals->curtime + random->RandomFloat( 1, hunter_siege_frequency.GetFloat() );
+
+ if( m_iszSiegeTargetName == NULL_STRING )
+ {
+ // Turning the feature off. Restore m_flDistTooFar to default.
+ m_flDistTooFar = hunter_flechette_max_range.GetFloat();
+ m_pSiegeTargets.RemoveAll();
+ }
+ else
+ {
+ // We're going into siege mode. Adjust range accordingly.
+ m_flDistTooFar = hunter_flechette_max_range.GetFloat() * HUNTER_SIEGE_MAX_DIST_MODIFIER;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputDodge( inputdata_t &inputdata )
+{
+ SetCondition( COND_HUNTER_FORCED_DODGE );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputFlankEnemy( inputdata_t &inputdata )
+{
+ SetCondition( COND_HUNTER_FORCED_FLANK_ENEMY );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputDisableShooting( inputdata_t &inputdata )
+{
+ m_bDisableShooting = true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputEnableShooting( inputdata_t &inputdata )
+{
+ m_bDisableShooting = false;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputEnableSquadShootDelay( inputdata_t &inputdata )
+{
+ m_bEnableSquadShootDelay = true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::InputDisableSquadShootDelay( inputdata_t &inputdata )
+{
+ m_bEnableSquadShootDelay = false;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
+{
+ return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::IsValidEnemy( CBaseEntity *pTarget )
+{
+ if ( IsStriderBuster( pTarget) )
+ {
+ if ( !m_EscortBehavior.m_bEnabled || !m_EscortBehavior.GetEscortTarget() )
+ {
+ // We only hate striderbusters when we are actively protecting a strider.
+ return false;
+ }
+
+ if ( pTarget->VPhysicsGetObject() )
+ {
+ if ( ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) &&
+ hunter_hate_held_striderbusters.GetBool() )
+ {
+ if ( gpGlobals->curtime - StriderBuster_GetPickupTime( pTarget ) > hunter_hate_held_striderbusters_delay.GetFloat())
+ {
+ if ( StriderBuster_NumFlechettesAttached( pTarget ) <= 2 )
+ {
+ if ( m_EscortBehavior.GetEscortTarget() &&
+ ( m_EscortBehavior.GetEscortTarget()->GetAbsOrigin().AsVector2D() - pTarget->GetAbsOrigin().AsVector2D() ).LengthSqr() < Square( hunter_hate_held_striderbusters_tolerance.GetFloat() ) )
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ bool bThrown = ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_WAS_THROWN ) != 0;
+ bool bAttached = StriderBuster_IsAttachedStriderBuster( pTarget );
+
+ if ( ( bThrown && !bAttached ) && hunter_hate_thrown_striderbusters.GetBool() )
+ {
+ float t;
+ float dist = CalcDistanceSqrToLineSegment2D( m_EscortBehavior.GetEscortTarget()->GetAbsOrigin().AsVector2D(),
+ pTarget->GetAbsOrigin().AsVector2D(),
+ pTarget->GetAbsOrigin().AsVector2D() + pTarget->GetSmoothedVelocity().AsVector2D(), &t );
+
+ if ( t > 0 && dist < Square( hunter_hate_thrown_striderbusters_tolerance.GetFloat() ))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ if ( bAttached && StriderBuster_IsAttachedStriderBuster( pTarget, m_EscortBehavior.GetEscortTarget() ) && hunter_hate_attached_striderbusters.GetBool() )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return BaseClass::IsValidEnemy( pTarget );
+}
+
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Disposition_t CNPC_Hunter::IRelationType( CBaseEntity *pTarget )
+{
+ if ( !pTarget )
+ return D_NU;
+
+ if ( IsStriderBuster( pTarget ) )
+ {
+ if ( HateThisStriderBuster( pTarget ) )
+ return D_HT;
+
+ return D_NU;
+ }
+
+ if ( hunter_retreat_striderbusters.GetBool() )
+ {
+ if ( pTarget->IsPlayer() && (m_hAttachedBusters.Count() > 0) )
+ {
+ return D_FR;
+ }
+ }
+
+ return BaseClass::IRelationType( pTarget );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::IRelationPriority( CBaseEntity *pTarget )
+{
+ if ( IsStriderBuster( pTarget ) )
+ {
+ // If we're here, we already know that we hate striderbusters.
+ return 1000.0f;
+ }
+
+ return BaseClass::IRelationPriority( pTarget );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::SetSquad( CAI_Squad *pSquad )
+{
+ BaseClass::SetSquad( pSquad );
+ if ( pSquad && pSquad->NumMembers() == 1 )
+ {
+ pSquad->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, 0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::OnSeeEntity( CBaseEntity *pEntity )
+{
+ BaseClass::OnSeeEntity(pEntity);
+
+ if ( IsStriderBuster( pEntity ) && IsValidEnemy( pEntity ) )
+ {
+ SetCondition( COND_HUNTER_SEE_STRIDERBUSTER );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer )
+{
+ //EmitSound( "NPC_Hunter.Alert" );
+ return BaseClass::UpdateEnemyMemory( pEnemy, position, pInformer );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::CanPlantHere( const Vector &vecPos )
+{
+ // TODO: cache results?
+ //if ( vecPos == m_vecLastCanPlantHerePos )
+ //{
+ // return m_bLastCanPlantHere;
+ //}
+
+ Vector vecMins = GetHullMins();
+ Vector vecMaxs = GetHullMaxs();
+
+ vecMins.x -= 16;
+ vecMins.y -= 16;
+
+ vecMaxs.x += 16;
+ vecMaxs.y += 16;
+ vecMaxs.z -= hunter_plant_adjust_z.GetInt();
+
+ bool bResult = false;
+
+ trace_t tr;
+ UTIL_TraceHull( vecPos, vecPos, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.startsolid )
+ {
+ // Try again, tracing down from above.
+ Vector vecStart = vecPos;
+ vecStart.z += hunter_plant_adjust_z.GetInt();
+
+ UTIL_TraceHull( vecStart, vecPos, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+ }
+
+ if ( tr.startsolid )
+ {
+ //NDebugOverlay::Box( vecPos, vecMins, vecMaxs, 255, 0, 0, 0, 0 );
+ }
+ else
+ {
+ //NDebugOverlay::Box( vecPos, vecMins, vecMaxs, 0, 255, 0, 0, 0 );
+ bResult = true;
+ }
+
+ // Cache the results in case we ask again for the same spot.
+ //m_vecLastCanPlantHerePos = vecPos;
+ //m_bLastCanPlantHere = bResult;
+
+ return bResult;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::MeleeAttack1ConditionsVsEnemyInVehicle( CBaseCombatCharacter *pEnemy, float flDot )
+{
+ if( !IsCorporealEnemy( GetEnemy() ) )
+ return COND_NONE;
+
+ // Try and trace a box to the player, and if I hit the vehicle, attack it
+ Vector vecDelta = (pEnemy->WorldSpaceCenter() - WorldSpaceCenter());
+ VectorNormalize( vecDelta );
+ trace_t tr;
+ AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + (vecDelta * 64), -Vector(8,8,8), Vector(8,8,8), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction != 1.0 && tr.m_pEnt == pEnemy->GetVehicleEntity() )
+ {
+ // We're near the vehicle. Are we facing it?
+ if (flDot < 0.7)
+ return COND_NOT_FACING_ATTACK;
+
+ return COND_CAN_MELEE_ATTACK1;
+ }
+
+ return COND_TOO_FAR_TO_ATTACK;
+}
+
+
+//-----------------------------------------------------------------------------
+// For innate melee attack
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::MeleeAttack1Conditions ( float flDot, float flDist )
+{
+ if ( !IsCorporealEnemy( GetEnemy() ) )
+ return COND_NONE;
+
+ if ( ( gpGlobals->curtime < m_flNextMeleeTime ) && // allow berzerk bashing if cornered
+ !( m_hAttachedBusters.Count() > 0 && gpGlobals->curtime < m_fCorneredTimer ) )
+ {
+ return COND_NONE;
+ }
+
+ if ( GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ {
+ return COND_NONE;
+ }
+
+ if ( flDist > HUNTER_MELEE_REACH )
+ {
+ // Translate a hit vehicle into its passenger if found
+ if ( GetEnemy() != NULL )
+ {
+ CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
+ if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
+ {
+ return MeleeAttack1ConditionsVsEnemyInVehicle( pCCEnemy, flDot );
+ }
+
+#if defined(HL2_DLL) && !defined(HL2MP)
+ // If the player is holding an object, knock it down.
+ if ( GetEnemy()->IsPlayer() )
+ {
+ CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() );
+
+ Assert( pPlayer != NULL );
+
+ // Is the player carrying something?
+ CBaseEntity *pObject = GetPlayerHeldEntity(pPlayer);
+
+ if ( !pObject )
+ {
+ pObject = PhysCannonGetHeldEntity( pPlayer->GetActiveWeapon() );
+ }
+
+ if ( pObject )
+ {
+ float flDist = pObject->WorldSpaceCenter().DistTo( WorldSpaceCenter() );
+
+ if ( flDist <= HUNTER_MELEE_REACH )
+ {
+ return COND_CAN_MELEE_ATTACK1;
+ }
+ }
+ }
+#endif
+ }
+
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+
+ if (flDot < 0.7)
+ {
+ return COND_NOT_FACING_ATTACK;
+ }
+
+ // Build a cube-shaped hull, the same hull that MeleeAttack is going to use.
+ Vector vecMins = GetHullMins();
+ Vector vecMaxs = GetHullMaxs();
+ vecMins.z = vecMins.x;
+ vecMaxs.z = vecMaxs.x;
+
+ Vector forward;
+ GetVectors( &forward, NULL, NULL );
+
+ trace_t tr;
+ AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + forward * HUNTER_MELEE_REACH, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction == 1.0 || !tr.m_pEnt )
+ {
+ // This attack would miss completely. Trick the hunter into moving around some more.
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+
+ if ( tr.m_pEnt == GetEnemy() || tr.m_pEnt->IsNPC() || (tr.m_pEnt->m_takedamage == DAMAGE_YES && (dynamic_cast<CBreakableProp*>(tr.m_pEnt))) )
+ {
+ // Let the hunter swipe at his enemy if he's going to hit them.
+ // Also let him swipe at NPC's that happen to be between the hunter and the enemy.
+ // This makes mobs of hunters seem more rowdy since it doesn't leave guys in the back row standing around.
+ // Also let him swipe at things that takedamage, under the assumptions that they can be broken.
+ return COND_CAN_MELEE_ATTACK1;
+ }
+
+ // dvs TODO: incorporate this
+ /*if ( tr.m_pEnt->IsBSPModel() )
+ {
+ // The trace hit something solid, but it's not the enemy. If this item is closer to the hunter than
+ // the enemy is, treat this as an obstruction.
+ Vector vecToEnemy = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter();
+ Vector vecTrace = tr.endpos - tr.startpos;
+
+ if ( vecTrace.Length2DSqr() < vecToEnemy.Length2DSqr() )
+ {
+ return COND_HUNTER_LOCAL_MELEE_OBSTRUCTION;
+ }
+ }*/
+
+ if ( !tr.m_pEnt->IsWorld() && GetEnemy() && GetEnemy()->GetGroundEntity() == tr.m_pEnt )
+ {
+ // Try to swat whatever the player is standing on instead of acting like a dill.
+ return COND_CAN_MELEE_ATTACK1;
+ }
+
+ // Move around some more
+ return COND_TOO_FAR_TO_ATTACK;
+}
+
+
+//-----------------------------------------------------------------------------
+// For innate melee attack
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::MeleeAttack2Conditions ( float flDot, float flDist )
+{
+ return COND_NONE;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::IsCorporealEnemy( CBaseEntity *pEnemy )
+{
+ if( !pEnemy )
+ return false;
+
+ // Generally speaking, don't melee attack anything the player can't see.
+ if( pEnemy->IsEffectActive( EF_NODRAW ) )
+ return false;
+
+ // Don't flank, melee attack striderbusters.
+ if ( IsStriderBuster( pEnemy ) )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::RangeAttack1Conditions( float flDot, float flDist )
+{
+ return COND_NONE;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::RangeAttack2Conditions( float flDot, float flDist )
+{
+ bool bIsBuster = IsStriderBuster( GetEnemy() );
+ bool bIsPerfectBullseye = ( GetEnemy() && dynamic_cast<CNPC_Bullseye *>(GetEnemy()) && ((CNPC_Bullseye *)GetEnemy())->UsePerfectAccuracy() );
+
+ if ( !bIsPerfectBullseye && !bIsBuster && !hunter_flechette_test.GetBool() && ( gpGlobals->curtime < m_flNextRangeAttack2Time ) )
+ {
+ return COND_NONE;
+ }
+
+ if ( m_bDisableShooting )
+ {
+ return COND_NONE;
+ }
+
+ if ( !HasCondition( COND_SEE_ENEMY ) )
+ {
+ return COND_NONE;
+ }
+
+ float flMaxFlechetteRange = hunter_flechette_max_range.GetFloat();
+
+ if ( IsUsingSiegeTargets() )
+ {
+ flMaxFlechetteRange *= HUNTER_SIEGE_MAX_DIST_MODIFIER;
+ }
+
+ if ( !bIsBuster && ( flDist > flMaxFlechetteRange ) )
+ {
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+ else if ( !bIsBuster && ( !GetEnemy() || !GetEnemy()->ClassMatches( "npc_bullseye" ) ) && flDist < hunter_flechette_min_range.GetFloat() )
+ {
+ return COND_TOO_CLOSE_TO_ATTACK;
+ }
+ else if ( flDot < HUNTER_FACING_DOT )
+ {
+ return COND_NOT_FACING_ATTACK;
+ }
+
+ if ( !bIsBuster && !m_bEnableUnplantedShooting && !hunter_flechette_test.GetBool() && !CanPlantHere( GetAbsOrigin() ) )
+ {
+ return COND_HUNTER_CANT_PLANT;
+ }
+
+ return COND_CAN_RANGE_ATTACK2;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions)
+{
+ CBaseEntity *pTargetEnt;
+
+ pTargetEnt = GetEnemy();
+
+ trace_t tr;
+ Vector vFrom = ownerPos + GetViewOffset();
+ AI_TraceLine( vFrom, targetPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( ( pTargetEnt && tr.m_pEnt == pTargetEnt) || tr.fraction == 1.0 || CanShootThrough( tr, targetPos ) )
+ {
+ static Vector vMins( -2.0, -2.0, -2.0 );
+ static Vector vMaxs( -vMins);
+ // Hit the enemy, or hit nothing (traced all the way to a nonsolid enemy like a bullseye)
+ AI_TraceHull( vFrom - Vector( 0, 0, 18 ), targetPos, vMins, vMaxs, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
+
+ if ( ( pTargetEnt && tr.m_pEnt == pTargetEnt) || tr.fraction == 1.0 || CanShootThrough( tr, targetPos ) )
+ {
+ if ( hunter_show_weapon_los_condition.GetBool() )
+ {
+ NDebugOverlay::Line( vFrom, targetPos, 255, 0, 255, false, 0.1 );
+ NDebugOverlay::Line( vFrom - Vector( 0, 0, 18 ), targetPos, 0, 0, 255, false, 0.1 );
+ }
+ return true;
+ }
+ }
+ else if ( bSetConditions )
+ {
+ SetCondition( COND_WEAPON_SIGHT_OCCLUDED );
+ SetEnemyOccluder( tr.m_pEnt );
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Look in front and see if the claw hit anything.
+//
+// Input : flDist distance to trace
+// iDamage damage to do if attack hits
+// vecViewPunch camera punch (if attack hits player)
+// vecVelocityPunch velocity punch (if attack hits player)
+//
+// Output : The entity hit by claws. NULL if nothing.
+//-----------------------------------------------------------------------------
+CBaseEntity *CNPC_Hunter::MeleeAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin )
+{
+ // Added test because claw attack anim sometimes used when for cases other than melee
+ if ( GetEnemy() )
+ {
+ trace_t tr;
+ AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0f )
+ return NULL;
+ }
+
+ //
+ // Trace out a cubic section of our hull and see what we hit.
+ //
+ Vector vecMins = GetHullMins();
+ Vector vecMaxs = GetHullMaxs();
+ vecMins.z = vecMins.x;
+ vecMaxs.z = vecMaxs.x;
+
+ CBaseEntity *pHurt = CheckTraceHullAttack( flDist, vecMins, vecMaxs, iDamage, DMG_SLASH );
+
+ if ( pHurt )
+ {
+ EmitSound( "NPC_Hunter.MeleeHit" );
+ EmitSound( "NPC_Hunter.TackleHit" );
+
+ CBasePlayer *pPlayer = ToBasePlayer( pHurt );
+
+ if ( pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE ) )
+ {
+ pPlayer->ViewPunch( qaViewPunch );
+ pPlayer->VelocityPunch( vecVelocityPunch );
+
+ // Shake the screen
+ UTIL_ScreenShake( pPlayer->GetAbsOrigin(), 100.0, 1.5, 1.0, 2, SHAKE_START );
+
+ // Red damage indicator
+ color32 red = { 128, 0, 0, 128 };
+ UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN );
+
+ /*if ( UTIL_ShouldShowBlood( pPlayer->BloodColor() ) )
+ {
+ // Spray some of the player's blood on the hunter.
+ trace_t tr;
+
+ Vector vecHunterEyePos; // = EyePosition();
+ QAngle angDiscard;
+ GetBonePosition( LookupBone( "MiniStrider.top_eye_bone" ), vecHunterEyePos, angDiscard );
+
+ Vector vecPlayerEyePos = pPlayer->EyePosition();
+
+ Vector vecDir = vecHunterEyePos - vecPlayerEyePos;
+ float flLen = VectorNormalize( vecDir );
+
+ Vector vecStart = vecPlayerEyePos - ( vecDir * 64 );
+ Vector vecEnd = vecPlayerEyePos + ( vecDir * ( flLen + 64 ) );
+
+ NDebugOverlay::HorzArrow( vecStart, vecEnd, 16, 255, 255, 0, 255, false, 10 );
+
+ UTIL_TraceLine( vecStart, vecEnd, MASK_SHOT, pPlayer, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.m_pEnt )
+ {
+ Msg( "Hit %s!!!\n", tr.m_pEnt->GetDebugName() );
+ UTIL_DecalTrace( &tr, "Blood" );
+ }
+ }*/
+ }
+ else if ( !pPlayer )
+ {
+ if ( IsMovablePhysicsObject( pHurt ) )
+ {
+ // If it's a vphysics object that's too heavy, crash into it too.
+ IPhysicsObject *pPhysics = pHurt->VPhysicsGetObject();
+ if ( pPhysics )
+ {
+ // If the object is being held by the player, break it or make them drop it.
+ if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
+ {
+ // If it's breakable, break it.
+ if ( pHurt->m_takedamage == DAMAGE_YES )
+ {
+ CBreakableProp *pBreak = dynamic_cast<CBreakableProp*>(pHurt);
+ if ( pBreak )
+ {
+ CTakeDamageInfo info( this, this, 20, DMG_SLASH );
+ pBreak->Break( this, info );
+ }
+ }
+ }
+ }
+ }
+
+ if ( UTIL_ShouldShowBlood(pHurt->BloodColor()) )
+ {
+ // Hit an NPC. Bleed them!
+ Vector vecBloodPos;
+
+ switch ( BloodOrigin )
+ {
+ case HUNTER_BLOOD_LEFT_FOOT:
+ {
+ if ( GetAttachment( "blood_left", vecBloodPos ) )
+ {
+ SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) );
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // TODO:
+ //AttackMissSound();
+ }
+
+ m_flNextMeleeTime = gpGlobals->curtime + hunter_melee_delay.GetFloat();
+
+ return pHurt;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::TestShootPosition(const Vector &vecShootPos, const Vector &targetPos )
+{
+ if ( !CanPlantHere(vecShootPos ) )
+ {
+ return false;
+ }
+
+ return BaseClass::TestShootPosition( vecShootPos, targetPos );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Vector CNPC_Hunter::Weapon_ShootPosition( )
+{
+ matrix3x4_t gunMatrix;
+ GetAttachment( gm_nTopGunAttachment, gunMatrix );
+
+ Vector vecShootPos;
+ MatrixGetColumn( gunMatrix, 3, vecShootPos );
+
+ return vecShootPos;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
+{
+ float flTracerDist;
+ Vector vecDir;
+ Vector vecEndPos;
+
+ vecDir = tr.endpos - vecTracerSrc;
+
+ flTracerDist = VectorNormalize( vecDir );
+
+ int nAttachment = LookupAttachment( "MiniGun" );
+
+ UTIL_Tracer( vecTracerSrc, tr.endpos, nAttachment, TRACER_FLAG_USEATTACHMENT, 5000, true, "HunterTracer" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Trace didn't hit the intended target, but should the hunter
+// shoot anyway? We use this to get the hunter to destroy
+// breakables that are between him and his target.
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::CanShootThrough( const trace_t &tr, const Vector &vecTarget )
+{
+ if ( !tr.m_pEnt )
+ {
+ return false;
+ }
+
+ if ( !tr.m_pEnt->GetHealth() )
+ {
+ return false;
+ }
+
+ // Don't try to shoot through allies.
+ CAI_BaseNPC *pNPC = tr.m_pEnt->MyNPCPointer();
+ if ( pNPC && ( IRelationType( pNPC ) == D_LI ) )
+ {
+ return false;
+ }
+
+ // Would a trace ignoring this entity continue to the target?
+ trace_t continuedTrace;
+ AI_TraceLine( tr.endpos, vecTarget, MASK_SHOT, tr.m_pEnt, COLLISION_GROUP_NONE, &continuedTrace );
+
+ if ( continuedTrace.fraction != 1.0 )
+ {
+ if ( continuedTrace.m_pEnt != GetEnemy() )
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::GetSoundInterests()
+{
+ return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_DANGER | SOUND_PHYSICS_DANGER | SOUND_PLAYER_VEHICLE | SOUND_BULLET_IMPACT | SOUND_MOVE_AWAY;
+}
+
+//-----------------------------------------------------------------------------
+// Tells us whether the Hunter is acting in a large, outdoor map,
+// currently only ep2_outland_12. This allows us to create logic
+// branches here in the AI code so that we can make choices that
+// tailor behavior to larger and smaller maps.
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::IsInLargeOutdoorMap()
+{
+ return m_bInLargeOutdoorMap;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::AlertSound()
+{
+ EmitSound( "NPC_Hunter.Alert" );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::PainSound( const CTakeDamageInfo &info )
+{
+ if ( gpGlobals->curtime > m_flNextDamageTime )
+ {
+ EmitSound( "NPC_Hunter.Pain" );
+ m_flNextDamageTime = gpGlobals->curtime + random->RandomFloat( 0.5, 1.2 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::DeathSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_Hunter.Death" );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo info = inputInfo;
+
+ // Even though the damage might not hurt us, we want to react to it
+ // if it's from the player.
+ if ( info.GetAttacker()->IsPlayer() )
+ {
+ if ( !HasMemory( bits_MEMORY_PROVOKED ) )
+ {
+ GetEnemies()->ClearMemory( info.GetAttacker() );
+ Remember( bits_MEMORY_PROVOKED );
+ SetCondition( COND_LIGHT_DAMAGE );
+ }
+ }
+
+ // HUnters have special resisitance to some types of damage.
+ if ( ( info.GetDamageType() & DMG_BULLET ) ||
+ ( info.GetDamageType() & DMG_BUCKSHOT ) ||
+ ( info.GetDamageType() & DMG_CLUB ) ||
+ ( info.GetDamageType() & DMG_NEVERGIB ) )
+ {
+ float flScale = 1.0;
+
+ if ( info.GetDamageType() & DMG_BUCKSHOT )
+ {
+ flScale = sk_hunter_buckshot_damage_scale.GetFloat();
+ }
+ else if ( ( info.GetDamageType() & DMG_BULLET ) || ( info.GetDamageType() & DMG_NEVERGIB ) )
+ {
+ // Hunters resist most bullet damage, but they are actually vulnerable to .357 rounds,
+ // since players regard that weapon as one of the game's truly powerful weapons.
+ if( info.GetAmmoType() == GetAmmoDef()->Index("357") )
+ {
+ flScale = 1.16f;
+ }
+ else
+ {
+ flScale = sk_hunter_bullet_damage_scale.GetFloat();
+ }
+ }
+
+ if ( GetActivity() == ACT_HUNTER_CHARGE_RUN )
+ {
+ flScale *= sk_hunter_charge_damage_scale.GetFloat();
+ }
+
+ if ( flScale != 0 )
+ {
+ float flDamage = info.GetDamage() * flScale;
+ info.SetDamage( flDamage );
+ }
+
+ QAngle vecAngles;
+ VectorAngles( ptr->plane.normal, vecAngles );
+ DispatchParticleEffect( "blood_impact_synth_01", ptr->endpos, vecAngles );
+ DispatchParticleEffect( "blood_impact_synth_01_arc_parent", PATTACH_POINT_FOLLOW, this, gm_nHeadCenterAttachment );
+ }
+
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+const impactdamagetable_t &CNPC_Hunter::GetPhysicsImpactDamageTable()
+{
+ return s_HunterImpactDamageTable;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::PhysicsDamageEffect( const Vector &vecPos, const Vector &vecDir )
+{
+ CEffectData data;
+ data.m_vOrigin = vecPos;
+ data.m_vNormal = vecDir;
+ DispatchEffect( "HunterDamage", data );
+
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ CBaseEntity *pTrail = CreateEntityByName( "sparktrail" );
+ pTrail->SetOwnerEntity( this );
+ pTrail->Spawn();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// We were hit by a strider buster. Do the tesla effect on our hitboxes.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::TeslaThink()
+{
+ CEffectData data;
+ data.m_nEntIndex = entindex();
+ data.m_flMagnitude = 3;
+ data.m_flScale = 0.5f;
+ DispatchEffect( "TeslaHitboxes", data );
+ EmitSound( "RagdollBoogie.Zap" );
+
+ if ( gpGlobals->curtime < m_flTeslaStopTime )
+ {
+ SetContextThink( &CNPC_Hunter::TeslaThink, gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ), HUNTER_ZAP_THINK );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Our health is low. Show damage effects.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::BleedThink()
+{
+ // Spurt blood from random points on the hunter's head.
+ Vector vecOrigin;
+ QAngle angDir;
+ GetAttachment( gm_nHeadCenterAttachment, vecOrigin, angDir );
+
+ Vector vecDir = RandomVector( -1, 1 );
+ VectorNormalize( vecDir );
+ VectorAngles( vecDir, Vector( 0, 0, 1 ), angDir );
+
+ vecDir *= gm_flHeadRadius;
+ DispatchParticleEffect( "blood_spurt_synth_01", vecOrigin + vecDir, angDir );
+
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.6, 1.5 ), HUNTER_BLEED_THINK );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::IsHeavyDamage( const CTakeDamageInfo &info )
+{
+ if ( info.GetDamage() < 45 )
+ {
+ return false;
+ }
+
+ if ( info.GetDamage() < 180 )
+ {
+ if ( !m_HeavyDamageDelay.Expired() || !BaseClass::IsHeavyDamage( info ) )
+ {
+ return false;
+ }
+ }
+
+ m_HeavyDamageDelay.Set( 15, 25 );
+ return true;
+
+}
+
+
+//-----------------------------------------------------------------------------
+// We've taken some damage. Maybe we should flinch because of it.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::ConsiderFlinching( const CTakeDamageInfo &info )
+{
+ if ( !m_FlinchTimer.Expired() )
+ {
+ // Someone is whaling on us. Push out the timer so we don't keep flinching.
+ m_FlinchTimer.Set( random->RandomFloat( 0.3 ) );
+ return;
+ }
+
+ if ( GetState() == NPC_STATE_SCRIPT )
+ {
+ return;
+ }
+
+ Activity eGesture = ACT_HUNTER_FLINCH_N;
+
+ Vector forward;
+ GetVectors( &forward, NULL, NULL );
+
+ Vector vecForceDir = info.GetDamageForce();
+ VectorNormalize( vecForceDir );
+
+ float flDot = DotProduct( forward, vecForceDir );
+
+ if ( flDot > 0.707 )
+ {
+ // flinch forward
+ eGesture = ACT_HUNTER_FLINCH_N;
+ }
+ else if ( flDot < -0.707 )
+ {
+ // flinch back
+ eGesture = ACT_HUNTER_FLINCH_S;
+ }
+ else
+ {
+ // flinch left or right
+ Vector cross = CrossProduct( forward, vecForceDir );
+
+ if ( cross.z > 0 )
+ {
+ eGesture = ACT_HUNTER_FLINCH_W;
+ }
+ else
+ {
+ eGesture = ACT_HUNTER_FLINCH_E;
+ }
+ }
+
+ if ( !IsPlayingGesture( eGesture ) )
+ {
+ RestartGesture( eGesture );
+ m_FlinchTimer.Set( random->RandomFloat( 0.3, 1.0 ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// This is done from a think function because when the hunter is killed,
+// the physics code puts the vehicle's pre-collision velocity back so the jostle
+// is basically discared.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::JostleVehicleThink()
+{
+ CBaseEntity *pInflictor = m_hHitByVehicle;
+ if ( !pInflictor )
+ return;
+
+ Vector vecVelDir = pInflictor->GetSmoothedVelocity();
+ float flSpeed = VectorNormalize( vecVelDir );
+ Vector vecForce = CrossProduct( vecVelDir, Vector( 0, 0, 1 ) );
+ if ( DotProduct( vecForce, GetAbsOrigin() ) < DotProduct( vecForce, pInflictor->GetAbsOrigin() ) )
+ {
+ // We're to the left of the vehicle that's hitting us.
+ vecForce *= -1;
+ }
+
+ VectorNormalize( vecForce );
+ vecForce.z = 1.0;
+
+ float flForceScale = RemapValClamped( flSpeed, hunter_jostle_car_min_speed.GetFloat(), hunter_jostle_car_max_speed.GetFloat(), 50.0f, 150.0f );
+
+ vecForce *= ( flForceScale * pInflictor->VPhysicsGetObject()->GetMass() );
+
+ pInflictor->VPhysicsGetObject()->ApplyForceOffset( vecForce, WorldSpaceCenter() );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ CTakeDamageInfo myInfo = info;
+
+ if ( ( info.GetDamageType() & DMG_CRUSH ) && !( info.GetDamageType() & DMG_VEHICLE ) )
+ {
+ // Don't take damage from physics objects that weren't thrown by the player.
+ CBaseEntity *pInflictor = info.GetInflictor();
+
+ IPhysicsObject *pObj = pInflictor->VPhysicsGetObject();
+ //Assert( pObj );
+
+ if ( !pObj || !pInflictor->HasPhysicsAttacker( 4.0 ) )
+ {
+ myInfo.SetDamage( 0 );
+ }
+ else
+ {
+ // Physics objects that have flechettes stuck in them spoof
+ // a flechette hitting us so we dissolve when killed and award
+ // the achievement of killing a hunter with its flechettes.
+ CUtlVector<CBaseEntity *> children;
+ GetAllChildren( pInflictor, children );
+ for (int i = 0; i < children.Count(); i++ )
+ {
+ CBaseEntity *pent = children.Element( i );
+ if ( dynamic_cast<CHunterFlechette *>( pent ) )
+ {
+ myInfo.SetInflictor( pent );
+ myInfo.SetDamageType( myInfo.GetDamageType() | DMG_DISSOLVE );
+ }
+ }
+ }
+ }
+
+ return BaseClass::OnTakeDamage( myInfo );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ CTakeDamageInfo myInfo = info;
+
+ // don't take damage from my own weapons!!!
+ // Exception: I "own" a magnade if it's glued to me.
+ CBaseEntity *pInflictor = info.GetInflictor();
+ CBaseEntity *pAttacker = info.GetAttacker();
+ if ( pInflictor )
+ {
+ if ( IsStriderBuster( pInflictor ) )
+ {
+ // Get a tesla effect on our hitboxes for a little while.
+ SetContextThink( &CNPC_Hunter::TeslaThink, gpGlobals->curtime, HUNTER_ZAP_THINK );
+ m_flTeslaStopTime = gpGlobals->curtime + 2.0f;
+
+ myInfo.SetDamage( sk_hunter_dmg_from_striderbuster.GetFloat() ) ;
+
+ SetCondition( COND_HUNTER_STAGGERED );
+ }
+ else if ( pInflictor->ClassMatches( GetClassname() ) && !( info.GetDamageType() == DMG_GENERIC ) )
+ {
+ return 0;
+ }
+ else if ( pInflictor->ClassMatches( "hunter_flechette" ) )
+ {
+ if ( !( ( CHunterFlechette *)pInflictor )->WasThrownBack() )
+ {
+ // Flechettes only hurt us if they were thrown back at us by the player. This prevents
+ // hunters from hurting themselves when they walk into their own flechette clusters.
+ return 0;
+ }
+ }
+ }
+
+ if ( m_EscortBehavior.GetFollowTarget() && m_EscortBehavior.GetFollowTarget() == pAttacker )
+ {
+ return 0;
+ }
+
+ bool bHitByUnoccupiedCar = false;
+ if ( ( ( info.GetDamageType() & DMG_CRUSH ) && ( pAttacker && pAttacker->IsPlayer() ) ) ||
+ ( info.GetDamageType() & DMG_VEHICLE ) )
+ {
+ // myInfo, not info! it may have been modified above.
+ float flDamage = myInfo.GetDamage();
+ if ( flDamage < HUNTER_MIN_PHYSICS_DAMAGE )
+ {
+ //DevMsg( "hunter: <<<< ZERO PHYSICS DAMAGE: %f\n", flDamage );
+ myInfo.SetDamage( 0 );
+ }
+ else
+ {
+ CBaseEntity *pInflictor = info.GetInflictor();
+ if ( ( info.GetDamageType() & DMG_VEHICLE ) ||
+ ( pInflictor && pInflictor->GetServerVehicle() &&
+ ( ( bHitByUnoccupiedCar = ( dynamic_cast<CPropVehicleDriveable *>(pInflictor) && static_cast<CPropVehicleDriveable *>(pInflictor)->GetDriver() == NULL ) ) == false ) ) )
+ {
+ // Adjust the damage from vehicles.
+ flDamage *= sk_hunter_vehicle_damage_scale.GetFloat();
+ myInfo.SetDamage( flDamage );
+
+ // Apply a force to jostle the vehicle that hit us.
+ // Pick a force direction based on which side we're on relative to the vehicle's motion.
+ Vector vecVelDir = pInflictor->GetSmoothedVelocity();
+ if ( vecVelDir.Length() >= hunter_jostle_car_min_speed.GetFloat() )
+ {
+ EmitSound( "NPC_Hunter.HitByVehicle" );
+ m_hHitByVehicle = pInflictor;
+ SetContextThink( &CNPC_Hunter::JostleVehicleThink, gpGlobals->curtime, HUNTER_JOSTLE_VEHICLE_THINK );
+ }
+ }
+
+ if ( !bHitByUnoccupiedCar )
+ {
+ SetCondition( COND_HUNTER_STAGGERED );
+ }
+ }
+
+ //DevMsg( "hunter: >>>> PHYSICS DAMAGE: %f (was %f)\n", flDamage, info.GetDamage() );
+ }
+
+ // Show damage effects if we actually took damage.
+ if ( ( myInfo.GetDamageType() & ( DMG_CRUSH | DMG_BLAST ) ) && ( myInfo.GetDamage() > 0 ) )
+ {
+ if ( !bHitByUnoccupiedCar )
+ SetCondition( COND_HUNTER_STAGGERED );
+ }
+
+ if ( HasCondition( COND_HUNTER_STAGGERED ) )
+ {
+ // Throw a bunch of gibs out
+ Vector vecForceDir = -myInfo.GetDamageForce();
+ VectorNormalize( vecForceDir );
+ PhysicsDamageEffect( myInfo.GetDamagePosition(), vecForceDir );
+
+ // Stagger away from the direction the damage came from.
+ m_vecStaggerDir = myInfo.GetDamageForce();
+ VectorNormalize( m_vecStaggerDir );
+ }
+
+ // Take less damage from citizens and Alyx, otherwise hunters go down too easily.
+ float flScale = 1.0;
+
+ if ( pAttacker &&
+ ( ( pAttacker->Classify() == CLASS_CITIZEN_REBEL ) ||
+ ( pAttacker->Classify() == CLASS_PLAYER_ALLY ) ||
+ ( pAttacker->Classify() == CLASS_PLAYER_ALLY_VITAL ) ) )
+ {
+ flScale *= sk_hunter_citizen_damage_scale.GetFloat();
+ }
+
+ if ( flScale != 0 )
+ {
+ // We're taking a nonzero amount of damage.
+
+ // If we're not staggering, consider flinching!
+ if ( !HasCondition( COND_HUNTER_STAGGERED ) )
+ {
+ ConsiderFlinching( info );
+ }
+
+ if( pAttacker && pAttacker->IsPlayer() )
+ {
+ // This block of code will distract the Hunter back to the player if the
+ // player does harm to the Hunter but is not the Hunter's current enemy.
+ // This is achieved by updating the Hunter's enemy memory of the player and
+ // making the Hunter's current enemy invalid for a short time.
+ if( !GetEnemy() || !GetEnemy()->IsPlayer() )
+ {
+ UpdateEnemyMemory( pAttacker, pAttacker->GetAbsOrigin(), this );
+
+ if( GetEnemy() )
+ {
+ // Gotta forget about this person for a little bit.
+ GetEnemies()->SetTimeValidEnemy( GetEnemy(), gpGlobals->curtime + HUNTER_IGNORE_ENEMY_TIME );
+ }
+ }
+ }
+
+ float flDamage = myInfo.GetDamage() * flScale;
+ myInfo.SetDamage( flDamage );
+ }
+
+ int nRet = BaseClass::OnTakeDamage_Alive( myInfo );
+
+ m_EscortBehavior.OnDamage( myInfo );
+
+ // Spark at 30% health.
+ if ( !IsBleeding() && ( GetHealth() <= sk_hunter_health.GetInt() * 0.3 ) )
+ {
+ StartBleeding();
+ }
+
+ if ( IsUsingSiegeTargets() && info.GetAttacker() != NULL && info.GetAttacker()->IsPlayer() )
+ {
+ // Defend myself. Try to siege attack immediately.
+ m_flTimeNextSiegeTargetAttack = gpGlobals->curtime;
+ }
+
+ return nRet;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::Event_Killed( const CTakeDamageInfo &info )
+{
+ // Remember the killing blow to make decisions about ragdolling.
+ m_nKillingDamageType = info.GetDamageType();
+
+ if ( m_EscortBehavior.GetFollowTarget() )
+ {
+ if ( AIGetNumFollowers( m_EscortBehavior.GetFollowTarget(), m_iClassname ) == 1 )
+ {
+ m_EscortBehavior.GetEscortTarget()->AlertSound();
+ if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
+ {
+ m_EscortBehavior.GetEscortTarget()->UpdateEnemyMemory( UTIL_GetLocalPlayer(), UTIL_GetLocalPlayer()->GetAbsOrigin(), this );
+ }
+ }
+ }
+
+ if ( info.GetDamageType() & DMG_VEHICLE )
+ {
+ bool bWasRunDown = false;
+ int iRundownCounter = 0;
+ if ( GetSquad() )
+ {
+ if ( !m_IgnoreVehicleTimer.Expired() )
+ {
+ GetSquad()->GetSquadData( HUNTER_RUNDOWN_SQUADDATA, &iRundownCounter );
+ GetSquad()->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, iRundownCounter + 1 );
+ bWasRunDown = true;
+ }
+ }
+
+ if ( hunter_dodge_debug.GetBool() )
+ Msg( "Hunter %d was%s run down\n", entindex(), ( bWasRunDown ) ? "" : " not" );
+
+ // Death by vehicle! Decrement the hunters to run over counter.
+ // When the counter reaches zero hunters will start dodging.
+ if ( GlobalEntity_GetCounter( s_iszHuntersToRunOver ) > 0 )
+ {
+ GlobalEntity_AddToCounter( s_iszHuntersToRunOver, -1 );
+ }
+ }
+
+ // Stop all our thinks
+ SetContextThink( NULL, 0, HUNTER_BLEED_THINK );
+
+ StopParticleEffects( this );
+
+ BaseClass::Event_Killed( info );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::StartBleeding()
+{
+ // Do this even if we're already bleeding (see OnRestore).
+ m_bIsBleeding = true;
+
+ // Start gushing blood from our... anus or something.
+ DispatchParticleEffect( "blood_drip_synth_01", PATTACH_POINT_FOLLOW, this, gm_nHeadBottomAttachment );
+
+ // Emit spurts of our blood
+ SetContextThink( &CNPC_Hunter::BleedThink, gpGlobals->curtime + 0.1, HUNTER_BLEED_THINK );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+float CNPC_Hunter::MaxYawSpeed()
+{
+ if ( IsStriderBuster( GetEnemy() ) )
+ {
+ return 60;
+ }
+
+ if ( GetActivity() == ACT_HUNTER_ANGRY )
+ return 0;
+
+ if ( GetActivity() == ACT_HUNTER_CHARGE_RUN )
+ return 5;
+
+ if ( GetActivity() == ACT_HUNTER_IDLE_PLANTED )
+ return 0;
+
+ if ( GetActivity() == ACT_HUNTER_RANGE_ATTACK2_UNPLANTED )
+ return 180;
+
+ switch ( GetActivity() )
+ {
+ case ACT_RANGE_ATTACK2:
+ {
+ return 0;
+ }
+
+ case ACT_90_LEFT:
+ case ACT_90_RIGHT:
+ {
+ return 45;
+ }
+
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ {
+ return 45;
+ }
+
+ case ACT_WALK:
+ {
+ return 25;
+ }
+
+ default:
+ {
+ return 35;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const
+{
+ float MAX_JUMP_RISE = 220.0f;
+ float MAX_JUMP_DISTANCE = 512.0f;
+ float MAX_JUMP_DROP = 384.0f;
+
+ trace_t tr;
+ UTIL_TraceHull( startPos, startPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.startsolid )
+ {
+ // Trying to start a jump in solid! Consider checking for this in CAI_MoveProbe::JumpMoveLimit.
+ Assert( 0 );
+ return false;
+ }
+
+ if ( BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE ) )
+ {
+ return true;
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Let the probe know I can run through small debris
+// Stolen shamelessly from the Antlion Guard
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity )
+{
+ if ( s_iszPhysPropClassname != pEntity->m_iClassname )
+ return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );
+
+ if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
+ {
+ IPhysicsObject *pPhysObj = pEntity->VPhysicsGetObject();
+
+ if( pPhysObj && pPhysObj->GetMass() <= 500.0f )
+ {
+ return false;
+ }
+ }
+
+ return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::DoMuzzleFlash( int nAttachment )
+{
+ BaseClass::DoMuzzleFlash();
+
+ DispatchParticleEffect( "hunter_muzzle_flash", PATTACH_POINT_FOLLOW, this, nAttachment );
+
+ // Dispatch the elight
+ CEffectData data;
+ data.m_nAttachmentIndex = nAttachment;
+ data.m_nEntIndex = entindex();
+ DispatchEffect( "HunterMuzzleFlash", data );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Hunter::CountRangedAttackers()
+{
+ CBaseEntity *pEnemy = GetEnemy();
+ if ( !pEnemy )
+ {
+ return 0;
+ }
+
+ int nAttackers = 0;
+ for ( int i = 0; i < g_Hunters.Count(); i++ )
+ {
+ CNPC_Hunter *pOtherHunter = g_Hunters[i];
+ if ( pOtherHunter->GetEnemy() == pEnemy )
+ {
+ if ( pOtherHunter->IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2 ) )
+ {
+ nAttackers++;
+ }
+ }
+ }
+ return nAttackers;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::DelayRangedAttackers( float minDelay, float maxDelay, bool bForced )
+{
+ float delayMultiplier = ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) ? 1.25 : 1.0;
+ if ( !m_bEnableSquadShootDelay && !bForced )
+ {
+ m_flNextRangeAttack2Time = gpGlobals->curtime + random->RandomFloat( minDelay, maxDelay ) * delayMultiplier;
+ return;
+ }
+
+ CBaseEntity *pEnemy = GetEnemy();
+ for ( int i = 0; i < g_Hunters.Count(); i++ )
+ {
+ CNPC_Hunter *pOtherHunter = g_Hunters[i];
+ if ( pOtherHunter->GetEnemy() == pEnemy )
+ {
+ float nextTime = gpGlobals->curtime + random->RandomFloat( minDelay, maxDelay ) * delayMultiplier;
+ if ( nextTime > pOtherHunter->m_flNextRangeAttack2Time )
+ pOtherHunter->m_flNextRangeAttack2Time = nextTime;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Given a target to shoot at, decide where to aim.
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::GetShootDir( Vector &vecDir, const Vector &vecSrc, CBaseEntity *pTargetEntity, bool bStriderBuster, int nShotNum, bool bSingleShot )
+{
+ //RestartGesture( ACT_HUNTER_GESTURE_SHOOT );
+
+ EmitSound( "NPC_Hunter.FlechetteShoot" );
+
+ Vector vecBodyTarget;
+
+ if( pTargetEntity->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ {
+ // Shooting at Alyx, most likely (in EP2). The attack is designed to displace
+ // her, not necessarily actually harm her. So shoot at the area around her feet.
+ vecBodyTarget = pTargetEntity->GetAbsOrigin();
+ }
+ else
+ {
+ vecBodyTarget = pTargetEntity->BodyTarget( vecSrc );
+ }
+
+ Vector vecTarget = vecBodyTarget;
+
+ Vector vecDelta = pTargetEntity->GetAbsOrigin() - GetAbsOrigin();
+ float flDist = vecDelta.Length();
+
+ if ( !bStriderBuster )
+ {
+ // If we're not firing at a strider buster, miss in an entertaining way for the
+ // first three shots of each volley.
+ if ( ( nShotNum < 3 ) && ( flDist > 200 ) )
+ {
+ Vector vecTargetForward;
+ Vector vecTargetRight;
+ pTargetEntity->GetVectors( &vecTargetForward, &vecTargetRight, NULL );
+
+ Vector vecForward;
+ GetVectors( &vecForward, NULL, NULL );
+
+ float flDot = DotProduct( vecTargetForward, vecForward );
+
+ if ( flDot < -0.8f )
+ {
+ // Our target is facing us, shoot the ground between us.
+ float flPerc = 0.7 + ( 0.1 * nShotNum );
+ vecTarget = GetAbsOrigin() + ( flPerc * ( pTargetEntity->GetAbsOrigin() - GetAbsOrigin() ) );
+ }
+ else if ( flDot > 0.8f )
+ {
+ // Our target is facing away from us, shoot to the left or right.
+ Vector vecMissDir = vecTargetRight;
+ if ( m_bMissLeft )
+ {
+ vecMissDir *= -1.0f;
+ }
+
+ vecTarget = pTargetEntity->EyePosition() + ( 36.0f * ( 3 - nShotNum ) ) * vecMissDir;
+ }
+ else
+ {
+ // Our target is facing vaguely perpendicular to us, shoot across their view.
+ vecTarget = pTargetEntity->EyePosition() + ( 36.0f * ( 3 - nShotNum ) ) * vecTargetForward;
+ }
+ }
+ // If we can't see them, shoot where we last saw them.
+ else if ( !HasCondition( COND_SEE_ENEMY ) )
+ {
+ Vector vecDelta = vecTarget - pTargetEntity->GetAbsOrigin();
+ vecTarget = m_vecEnemyLastSeen + vecDelta;
+ }
+ }
+ else
+ {
+ // If we're firing at a striderbuster, lead it.
+ float flSpeed = hunter_flechette_speed.GetFloat();
+ if ( !flSpeed )
+ {
+ flSpeed = 2500.0f;
+ }
+
+ flSpeed *= 1.5;
+
+ float flDeltaTime = flDist / flSpeed;
+ vecTarget = vecTarget + flDeltaTime * pTargetEntity->GetSmoothedVelocity();
+ }
+
+ vecDir = vecTarget - vecSrc;
+ VectorNormalize( vecDir );
+}
+
+
+//-----------------------------------------------------------------------------
+// Ensures that we don't exceed our pitch/yaw limits when shooting flechettes.
+// Returns true if we had to clamp, false if not.
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ClampShootDir( Vector &vecDir )
+{
+ Vector vecDir2D = vecDir;
+ vecDir2D.z = 0;
+
+ Vector vecForward;
+ GetVectors( &vecForward, NULL, NULL );
+
+ Vector vecForward2D = vecForward;
+ vecForward2D.z = 0;
+
+ float flDot = DotProduct( vecForward2D, vecDir2D );
+ if ( flDot >= HUNTER_SHOOT_MAX_YAW_COS )
+ {
+ // No need to clamp.
+ return false;
+ }
+
+ Vector vecAxis;
+ CrossProduct( vecDir, vecForward, vecAxis );
+ VectorNormalize( vecAxis );
+
+ Quaternion q;
+ AxisAngleQuaternion( vecAxis, -HUNTER_SHOOT_MAX_YAW_DEG, q );
+
+ matrix3x4_t rot;
+ QuaternionMatrix( q, rot );
+ VectorRotate( vecForward, rot, vecDir );
+ VectorNormalize( vecDir );
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ShouldSeekTarget( CBaseEntity *pTargetEntity, bool bStriderBuster )
+{
+ bool bSeek = false;
+
+ if ( bStriderBuster )
+ {
+ bool bSeek = false;
+
+ if ( pTargetEntity->VPhysicsGetObject() && ( pTargetEntity->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) )
+ {
+ bSeek = true;
+ }
+ else if ( StriderBuster_NumFlechettesAttached( pTargetEntity ) == 0 )
+ {
+ if ( StriderBuster_IsAttachedStriderBuster(pTargetEntity) )
+ {
+ bSeek = true;
+ }
+ else if ( hunter_seek_thrown_striderbusters_tolerance.GetFloat() > 0.0 )
+ {
+ CNPC_Strider *pEscortTarget = m_EscortBehavior.GetEscortTarget();
+ if ( pEscortTarget && ( pEscortTarget->GetAbsOrigin() - pTargetEntity->GetAbsOrigin() ).LengthSqr() < Square( hunter_seek_thrown_striderbusters_tolerance.GetFloat() ) )
+ {
+ bSeek = true;
+ }
+ }
+ }
+ }
+
+ return bSeek;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::BeginVolley( int nNum, float flStartTime )
+{
+ m_nFlechettesQueued = nNum;
+ m_nClampedShots = 0;
+ m_flNextFlechetteTime = flStartTime;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::ShootFlechette( CBaseEntity *pTargetEntity, bool bSingleShot )
+{
+ if ( !pTargetEntity )
+ {
+ Assert( false );
+ return false;
+ }
+
+ int nShotNum = hunter_flechette_volley_size.GetInt() - m_nFlechettesQueued;
+
+ bool bStriderBuster = IsStriderBuster( pTargetEntity );
+
+ // Choose the next muzzle to shoot from.
+ Vector vecSrc;
+ QAngle angMuzzle;
+
+ if ( m_bTopMuzzle )
+ {
+ GetAttachment( gm_nTopGunAttachment, vecSrc, angMuzzle );
+ DoMuzzleFlash( gm_nTopGunAttachment );
+ }
+ else
+ {
+ GetAttachment( gm_nBottomGunAttachment, vecSrc, angMuzzle );
+ DoMuzzleFlash( gm_nBottomGunAttachment );
+ }
+
+ m_bTopMuzzle = !m_bTopMuzzle;
+
+ Vector vecDir;
+ GetShootDir( vecDir, vecSrc, pTargetEntity, bStriderBuster, nShotNum, bSingleShot );
+
+ bool bClamped = false;
+ if ( hunter_clamp_shots.GetBool() )
+ {
+ bClamped = ClampShootDir( vecDir );
+ }
+
+ CShotManipulator manipulator( vecDir );
+ Vector vecShoot;
+
+ if( IsUsingSiegeTargets() && nShotNum >= 2 && (nShotNum % 2) == 0 )
+ {
+ // Near perfect accuracy for these three shots, so they are likely to fly right into the windows.
+ // NOTE! In siege behavior in the map that this behavior was designed for (ep2_outland_10), the
+ // Hunters will only ever shoot at siege targets in siege mode. If you allow Hunters in siege mode
+ // to attack players or other NPCs, this accuracy bonus will apply unless we apply a bit more logic to it.
+ vecShoot = manipulator.ApplySpread( VECTOR_CONE_1DEGREES * 0.5, 1.0f );
+ }
+ else
+ {
+ vecShoot = manipulator.ApplySpread( VECTOR_CONE_4DEGREES, 1.0f );
+ }
+
+ QAngle angShoot;
+ VectorAngles( vecShoot, angShoot );
+
+ CHunterFlechette *pFlechette = CHunterFlechette::FlechetteCreate( vecSrc, angShoot, this );
+
+ pFlechette->AddEffects( EF_NOSHADOW );
+
+ vecShoot *= hunter_flechette_speed.GetFloat();
+
+ pFlechette->Shoot( vecShoot, bStriderBuster );
+
+ if ( ShouldSeekTarget( pTargetEntity, bStriderBuster ) )
+ {
+ pFlechette->SetSeekTarget( pTargetEntity );
+ }
+
+ if( nShotNum == 1 && pTargetEntity->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ {
+ // Make this person afraid and react to ME, not to the flechettes.
+ // Otherwise they could be scared into running towards the hunter.
+ CSoundEnt::InsertSound( SOUND_DANGER|SOUND_CONTEXT_REACT_TO_SOURCE|SOUND_CONTEXT_EXCLUDE_COMBINE, pTargetEntity->EyePosition(), 180.0f, 2.0f, this );
+ }
+
+ return bClamped;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Vector CNPC_Hunter::LeftFootHit( float eventtime )
+{
+ Vector footPosition;
+
+ GetAttachment( "left foot", footPosition );
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "NPC_Hunter.Footstep", &footPosition, eventtime );
+
+ FootFX( footPosition );
+
+ return footPosition;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Vector CNPC_Hunter::RightFootHit( float eventtime )
+{
+ Vector footPosition;
+
+ GetAttachment( "right foot", footPosition );
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "NPC_Hunter.Footstep", &footPosition, eventtime );
+ FootFX( footPosition );
+
+ return footPosition;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Vector CNPC_Hunter::BackFootHit( float eventtime )
+{
+ Vector footPosition;
+
+ GetAttachment( "back foot", footPosition );
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "NPC_Hunter.BackFootstep", &footPosition, eventtime );
+ FootFX( footPosition );
+
+ return footPosition;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::FootFX( const Vector &origin )
+{
+ return;
+
+ // dvs TODO: foot dust? probably too expensive for these guys
+ /*trace_t tr;
+ AI_TraceLine( origin, origin - Vector(0,0,100), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+ float yaw = random->RandomInt(0,120);
+ for ( int i = 0; i < 3; i++ )
+ {
+ Vector dir = UTIL_YawToVector( yaw + i*120 ) * 10;
+ VectorNormalize( dir );
+ dir.z = 0.25;
+ VectorNormalize( dir );
+ g_pEffects->Dust( tr.endpos, dir, 12, 50 );
+ }*/
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CBaseEntity *CNPC_Hunter::GetEnemyVehicle()
+{
+ if ( GetEnemy() == NULL )
+ return NULL;
+
+ CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
+ if ( pCCEnemy != NULL )
+ return pCCEnemy->GetVehicleEntity();
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::DrawDebugGeometryOverlays()
+{
+ if (m_debugOverlays & OVERLAY_BBOX_BIT)
+ {
+ float flViewRange = acos(0.8);
+ Vector vEyeDir = EyeDirection2D( );
+ Vector vLeftDir, vRightDir;
+ float fSin, fCos;
+ SinCos( flViewRange, &fSin, &fCos );
+
+ vLeftDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
+ vLeftDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
+ vLeftDir.z = vEyeDir.z;
+ fSin = sin(-flViewRange);
+ fCos = cos(-flViewRange);
+ vRightDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
+ vRightDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
+ vRightDir.z = vEyeDir.z;
+
+ int nSeq = GetSequence();
+ if ( ( GetEntryNode( nSeq ) == gm_nPlantedNode ) && ( GetExitNode( nSeq ) == gm_nPlantedNode ) )
+ {
+ // planted - green
+ NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 0, 128, 0 );
+ }
+ else if ( ( GetEntryNode( nSeq ) == gm_nUnplantedNode ) && ( GetExitNode( nSeq ) == gm_nUnplantedNode ) )
+ {
+ // unplanted - blue
+ NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 0, 255, 128, 0 );
+ }
+ else if ( ( GetEntryNode( nSeq ) == gm_nUnplantedNode ) && ( GetExitNode( nSeq ) == gm_nPlantedNode ) )
+ {
+ // planting transition - cyan
+ NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 255, 128, 0 );
+ }
+ else if ( ( GetEntryNode( nSeq ) == gm_nPlantedNode ) && ( GetExitNode( nSeq ) == gm_nUnplantedNode ) )
+ {
+ // unplanting transition - purple
+ NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 255, 128, 0 );
+ }
+ else
+ {
+ // unknown / other node - red
+ Msg( "UNKNOWN: %s\n", GetSequenceName( GetSequence() ) );
+ NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 0, 128, 0 );
+ }
+
+ NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vLeftDir, 255, 0, 0, 50, 0 );
+ NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vRightDir, 255, 0, 0, 50, 0 );
+ NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vEyeDir, 0, 255, 0, 50, 0 );
+ NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 128, 0 );
+ }
+
+ m_EscortBehavior.DrawDebugGeometryOverlays();
+
+ BaseClass::DrawDebugGeometryOverlays();
+}
+
+
+//-----------------------------------------------------------------------------
+// Player has illuminated this NPC with the flashlight
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
+{
+ if ( m_bFlashlightInEyes )
+ return;
+
+ // Ignore the flashlight if it's not shining at my eyes
+ if ( PlayerFlashlightOnMyEyes( pPlayer ) )
+ {
+ //Msg( ">>>> SHINING FLASHLIGHT ON ME\n" );
+ m_bFlashlightInEyes = true;
+ SetExpression( "scenes/npc/hunter/hunter_eyeclose.vcd" );
+ m_flPupilDilateTime = gpGlobals->curtime + 0.2f;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer )
+{
+ Vector vecEyes, vecEyeForward, vecPlayerForward;
+ GetAttachment( gm_nTopGunAttachment, vecEyes, &vecEyeForward );
+ pPlayer->EyeVectors( &vecPlayerForward );
+
+ Vector vecToEyes = (vecEyes - pPlayer->EyePosition());
+ //float flDist = VectorNormalize( vecToEyes );
+
+ float flDot = DotProduct( vecPlayerForward, vecToEyes );
+ if ( flDot < 0.98 )
+ return false;
+
+ // Check facing to ensure we're in front of her
+ Vector los = ( pPlayer->EyePosition() - EyePosition() );
+ los.z = 0;
+ VectorNormalize( los );
+ Vector facingDir = EyeDirection2D();
+ flDot = DotProduct( los, facingDir );
+ return ( flDot > 0.3 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Return a random expression for the specified state to play over
+// the state's expression loop.
+//-----------------------------------------------------------------------------
+const char *CNPC_Hunter::SelectRandomExpressionForState( NPC_STATE state )
+{
+ if ( m_bFlashlightInEyes )
+ return NULL;
+
+ if ( !hunter_random_expressions.GetBool() )
+ return NULL;
+
+ char *szExpressions[4] =
+ {
+ "scenes/npc/hunter/hunter_scan.vcd",
+ "scenes/npc/hunter/hunter_eyeclose.vcd",
+ "scenes/npc/hunter/hunter_roar.vcd",
+ "scenes/npc/hunter/hunter_pain.vcd"
+ };
+
+ int nIndex = random->RandomInt( 0, 3 );
+ //Msg( "RANDOM Expression: %s\n", szExpressions[nIndex] );
+ return szExpressions[nIndex];
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::PlayExpressionForState( NPC_STATE state )
+{
+ if ( m_bFlashlightInEyes )
+ {
+ return;
+ }
+
+ BaseClass::PlayExpressionForState( state );
+}
+
+
+//-----------------------------------------------------------------------------
+// TODO: remove if we're not doing striderbuster stuff
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::StriderBusterAttached( CBaseEntity *pAttached )
+{
+ // Add another to the list
+ m_hAttachedBusters.AddToTail( pAttached );
+
+ SetCondition( COND_HUNTER_HIT_BY_STICKYBOMB );
+ if (m_hAttachedBusters.Count() == 1)
+ {
+ EmitSound( "NPC_Hunter.Alert" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::StriderBusterDetached( CBaseEntity *pAttached )
+{
+ int elem = m_hAttachedBusters.Find(pAttached);
+ if (elem >= 0)
+ {
+ m_hAttachedBusters.FastRemove(elem);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Set direction that the hunter aims his body and eyes (guns).
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::SetAim( const Vector &aimDir, float flInterval )
+{
+ QAngle angDir;
+ VectorAngles( aimDir, angDir );
+ float curPitch = GetPoseParameter( gm_nBodyPitchPoseParam );
+ float curYaw = GetPoseParameter( gm_nBodyYawPoseParam );
+
+ float newPitch;
+ float newYaw;
+
+ if ( GetEnemy() )
+ {
+ // clamp and dampen movement
+ newPitch = curPitch + 0.8 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
+
+ float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
+ newYaw = curYaw + UTIL_AngleDiff( flRelativeYaw, curYaw );
+ }
+ else
+ {
+ // Sweep your weapon more slowly if you're not fighting someone
+ newPitch = curPitch + 0.6 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
+
+ float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
+ newYaw = curYaw + 0.6 * UTIL_AngleDiff( flRelativeYaw, curYaw );
+ }
+
+ newPitch = AngleNormalize( newPitch );
+ newYaw = AngleNormalize( newYaw );
+
+ //Msg( "pitch=%f, yaw=%f\n", newPitch, newYaw );
+
+ SetPoseParameter( gm_nAimPitchPoseParam, 0 );
+ SetPoseParameter( gm_nAimYawPoseParam, 0 );
+
+ SetPoseParameter( gm_nBodyPitchPoseParam, clamp( newPitch, -45, 45 ) );
+ SetPoseParameter( gm_nBodyYawPoseParam, clamp( newYaw, -45, 45 ) );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::RelaxAim( float flInterval )
+{
+ float curPitch = GetPoseParameter( gm_nBodyPitchPoseParam );
+ float curYaw = GetPoseParameter( gm_nBodyYawPoseParam );
+
+ // dampen existing aim
+ float newPitch = AngleNormalize( UTIL_ApproachAngle( 0, curPitch, 3 ) );
+ float newYaw = AngleNormalize( UTIL_ApproachAngle( 0, curYaw, 2 ) );
+
+ SetPoseParameter( gm_nAimPitchPoseParam, 0 );
+ SetPoseParameter( gm_nAimYawPoseParam, 0 );
+
+ SetPoseParameter( gm_nBodyPitchPoseParam, clamp( newPitch, -45, 45 ) );
+ SetPoseParameter( gm_nBodyYawPoseParam, clamp( newYaw, -45, 45 ) );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Hunter::UpdateAim()
+{
+ if ( !GetModelPtr() || !GetModelPtr()->SequencesAvailable() )
+ return;
+
+ float flInterval = GetAnimTimeInterval();
+
+ // Some activities look bad if we're giving our enemy the stinkeye.
+ int eActivity = GetActivity();
+
+ if ( GetEnemy() &&
+ GetState() != NPC_STATE_SCRIPT &&
+ ( eActivity != ACT_HUNTER_CHARGE_CRASH ) &&
+ ( eActivity != ACT_HUNTER_CHARGE_HIT ) )
+ {
+ Vector vecShootOrigin;
+
+ vecShootOrigin = Weapon_ShootPosition();
+ Vector vecShootDir = GetShootEnemyDir( vecShootOrigin, false );
+
+ SetAim( vecShootDir, flInterval );
+ }
+ else
+ {
+ RelaxAim( flInterval );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Don't become a ragdoll until we've finished our death anim
+//-----------------------------------------------------------------------------
+bool CNPC_Hunter::CanBecomeRagdoll()
+{
+ return ( m_nKillingDamageType & DMG_CRUSH ) ||
+ IsCurSchedule( SCHED_DIE, false ) || // Finished playing death anim, time to ragdoll
+ IsCurSchedule( SCHED_HUNTER_CHARGE_ENEMY, false ) || // While moving, it looks better to ragdoll instantly
+ IsCurSchedule( SCHED_SCRIPTED_RUN, false ) ||
+ ( GetActivity() == ACT_WALK ) || ( GetActivity() == ACT_RUN ) ||
+ GetCurSchedule() == NULL; // Failsafe
+}
+
+
+//-----------------------------------------------------------------------------
+// Determines the best type of death anim to play based on how we died.
+//-----------------------------------------------------------------------------
+Activity CNPC_Hunter::GetDeathActivity()
+{
+ return ACT_DIESIMPLE;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::OnDamage( const CTakeDamageInfo &info )
+{
+ if ( info.GetDamage() > 0 && info.GetAttacker()->IsPlayer() &&
+ GetFollowTarget() && ( AIGetNumFollowers( GetFollowTarget() ) > 1 ) &&
+ ( GetOuter()->GetSquad()->GetSquadSoundWaitTime() <= gpGlobals->curtime ) ) // && !FarFromFollowTarget()
+ {
+ // Start the clock ticking. We'll return the the strider when the timer elapses.
+ m_flTimeEscortReturn = gpGlobals->curtime + random->RandomFloat( 15.0f, 25.0f );
+ GetOuter()->GetSquad()->SetSquadSoundWaitTime( m_flTimeEscortReturn + 1.0 ); // prevent others from breaking escort
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::BuildScheduleTestBits()
+{
+ BaseClass::BuildScheduleTestBits();
+
+ if ( ( m_flTimeEscortReturn != 0 ) && ( gpGlobals->curtime > m_flTimeEscortReturn ) )
+ {
+ // We're delinquent! Return to strider!
+ GetOuter()->ClearCustomInterruptCondition( COND_NEW_ENEMY );
+ GetOuter()->ClearCustomInterruptCondition( COND_SEE_ENEMY );
+ GetOuter()->ClearCustomInterruptCondition( COND_SEE_HATE );
+ GetOuter()->ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
+ GetOuter()->ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::CheckBreakEscort()
+{
+ if ( m_flTimeEscortReturn != 0 && ( FarFromFollowTarget() || gpGlobals->curtime >= m_flTimeEscortReturn ) )
+ {
+ if ( FarFromFollowTarget() )
+ {
+ m_flTimeEscortReturn = gpGlobals->curtime;
+ }
+ else
+ {
+ m_flTimeEscortReturn = 0;
+ }
+ if ( GetOuter()->GetSquad() )
+ {
+ GetOuter()->GetSquad()->SetSquadSoundWaitTime( gpGlobals->curtime + random->RandomFloat( 5.0f, 12.0f ) );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::GatherConditionsNotActive( void )
+{
+ if ( m_bEnabled )
+ {
+ DistributeFreeHunters();
+ }
+
+ BaseClass::GatherConditionsNotActive();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::GatherConditions( void )
+{
+ m_bEnabled = true;
+
+ DistributeFreeHunters();
+
+ BaseClass::GatherConditions();
+
+ if ( GetEnemy() && GetEnemy()->IsPlayer() && HasCondition( COND_SEE_ENEMY ) )
+ {
+ if ( GetOuter()->GetSquad()->GetSquadSoundWaitTime() <= gpGlobals->curtime && ((CBasePlayer *)GetEnemy())->IsInAVehicle() )
+ {
+ m_flTimeEscortReturn = gpGlobals->curtime + random->RandomFloat( 15.0f, 25.0f );
+ GetOuter()->GetSquad()->SetSquadSoundWaitTime( m_flTimeEscortReturn + 1.0 ); // prevent others from breaking escort
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CAI_HunterEscortBehavior::ShouldFollow()
+{
+ if ( IsStriderBuster( GetEnemy() ) )
+ return false;
+
+ if ( HasCondition( COND_HEAR_PHYSICS_DANGER ) )
+ return false;
+
+ if ( m_flTimeEscortReturn <= gpGlobals->curtime )
+ {
+ return CAI_FollowBehavior::ShouldFollow();
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::BeginScheduleSelection()
+{
+ BaseClass::BeginScheduleSelection();
+ Assert( m_SavedDistTooFar == GetOuter()->m_flDistTooFar );
+ GetOuter()->m_flDistTooFar *= 2;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAI_HunterEscortBehavior::SelectSchedule()
+{
+ if( m_FollowDelay.IsRunning() && !m_FollowDelay.Expired() )
+ {
+ return FollowCallBaseSelectSchedule();
+ }
+ return BaseClass::SelectSchedule();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAI_HunterEscortBehavior::FollowCallBaseSelectSchedule()
+{
+ if ( GetOuter()->GetState() == NPC_STATE_COMBAT )
+ {
+ return GetOuter()->SelectCombatSchedule();
+ }
+
+ return BaseClass::FollowCallBaseSelectSchedule();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_MOVE_TO_FOLLOW_POSITION:
+ {
+ if ( GetEnemy() )
+ {
+ if ( GetOuter()->OccupyStrategySlot( SQUAD_SLOT_RUN_SHOOT ) )
+ {
+ if ( GetOuter()->GetSquad()->GetSquadMemberNearestTo( GetEnemy()->GetAbsOrigin() ) == GetOuter() )
+ {
+ GetOuter()->BeginVolley( NUM_FLECHETTE_VOLLEY_ON_FOLLOW, gpGlobals->curtime + 1.0 + random->RandomFloat( 0, .25 ) + random->RandomFloat( 0, .25 ) );
+ }
+ else
+ {
+ GetOuter()->VacateStrategySlot();
+ }
+ }
+ }
+ BaseClass::StartTask( pTask );
+ break;
+ }
+
+ default:
+ BaseClass::StartTask( pTask );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::RunTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_MOVE_TO_FOLLOW_POSITION:
+ {
+ if ( !GetFollowTarget() )
+ {
+ TaskFail( FAIL_NO_TARGET );
+ }
+ else
+ {
+ if ( GetEnemy() )
+ {
+ CNPC_Hunter *pHunter = GetOuter();
+ Vector vecEnemyLKP = pHunter->GetEnemyLKP();
+ pHunter->AddFacingTarget( pHunter->GetEnemy(), vecEnemyLKP, 1.0, 0.8 );
+ bool bVacate = false;
+
+ bool bHasSlot = pHunter->HasStrategySlot( SQUAD_SLOT_RUN_SHOOT );
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ float maxDist = hunter_flechette_max_range.GetFloat() * 3;
+ float distSq = ( pHunter->GetAbsOrigin() - pHunter->GetEnemy()->GetAbsOrigin() ).Length2DSqr();
+
+ if ( distSq < Square( maxDist ) )
+ {
+ if ( gpGlobals->curtime >= pHunter->m_flNextFlechetteTime )
+ {
+ if ( !bHasSlot )
+ {
+ if ( GetOuter()->OccupyStrategySlot( SQUAD_SLOT_RUN_SHOOT ) )
+ {
+ if ( GetOuter()->GetSquad()->GetSquadMemberNearestTo( GetEnemy()->GetAbsOrigin() ) == GetOuter() )
+ {
+ bHasSlot = true;
+ }
+ else
+ {
+ GetOuter()->VacateStrategySlot();
+ }
+ }
+ }
+
+ if ( bHasSlot )
+ {
+ // Start the firing sound.
+ //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ //if ( controller.SoundGetVolume( pHunter->m_pGunFiringSound ) == 0.0f )
+ //{
+ // controller.SoundChangeVolume( pHunter->m_pGunFiringSound, 1.0f, 0.0f );
+ //}
+
+ pHunter->ShootFlechette( GetEnemy(), true );
+
+ if ( --pHunter->m_nFlechettesQueued > 0 )
+ {
+ pHunter->m_flNextFlechetteTime = gpGlobals->curtime + hunter_flechette_delay.GetFloat();
+ }
+ else
+ {
+ // Stop the firing sound.
+ //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ //controller.SoundChangeVolume( pHunter->m_pGunFiringSound, 0, 0.01f );
+
+ bVacate = true;
+ pHunter->BeginVolley( NUM_FLECHETTE_VOLLEY_ON_FOLLOW, gpGlobals->curtime + 1.0 + random->RandomFloat( 0, .25 ) + random->RandomFloat( 0, .25 ) );
+ }
+ }
+ }
+ }
+ else if ( bHasSlot )
+ {
+ bVacate = true;
+ }
+ }
+ else if ( bHasSlot )
+ {
+ bVacate = true;
+ }
+
+ if ( bVacate )
+ {
+ pHunter->VacateStrategySlot();
+ }
+ }
+
+ if ( m_FollowAttackTimer.Expired() && IsFollowTargetInRange( .8 ))
+ {
+ m_FollowAttackTimer.Set( 8, 24 );
+ TaskComplete();
+ }
+ else
+ {
+ BaseClass::RunTask( pTask );
+ }
+ }
+ break;
+ }
+
+ default:
+ BaseClass::RunTask( pTask );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::FindFreeHunters( CUtlVector<CNPC_Hunter *> *pFreeHunters )
+{
+ pFreeHunters->EnsureCapacity( g_Hunters.Count() );
+ int i;
+
+ for ( i = 0; i < g_Hunters.Count(); i++ )
+ {
+ CNPC_Hunter *pHunter = g_Hunters[i];
+ if ( pHunter->IsAlive() && pHunter->m_EscortBehavior.m_bEnabled )
+ {
+ if ( pHunter->m_EscortBehavior.GetFollowTarget() == NULL || !pHunter->m_EscortBehavior.GetFollowTarget()->IsAlive() )
+ {
+ pFreeHunters->AddToTail( pHunter);
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::DistributeFreeHunters()
+{
+ if ( g_TimeLastDistributeFreeHunters != -1 && gpGlobals->curtime - g_TimeLastDistributeFreeHunters < FREE_HUNTER_DISTRIBUTE_INTERVAL )
+ {
+ return;
+ }
+
+ g_TimeLastDistributeFreeHunters = gpGlobals->curtime;
+
+ CUtlVector<CNPC_Hunter *> freeHunters;
+ int i;
+ FindFreeHunters( &freeHunters );
+
+ CAI_BaseNPC **ppNPCs = g_AI_Manager.AccessAIs();
+ for ( i = 0; i < g_AI_Manager.NumAIs() && freeHunters.Count(); i++ )
+ {
+ int nToAdd;
+ CNPC_Strider *pStrider = ( ppNPCs[i]->IsAlive() ) ? dynamic_cast<CNPC_Strider *>( ppNPCs[i] ) : NULL;
+ if ( pStrider && !pStrider->CarriedByDropship() )
+ {
+ if ( ( nToAdd = 3 - AIGetNumFollowers( pStrider ) ) > 0 )
+ {
+ for ( int j = freeHunters.Count() - 1; j >= 0 && nToAdd > 0; --j )
+ {
+ DevMsg( "npc_hunter %d assigned to npc_strider %d\n", freeHunters[j]->entindex(), pStrider->entindex() );
+ freeHunters[j]->FollowStrider( pStrider );
+ freeHunters.FastRemove( j );
+ nToAdd--;
+ }
+ }
+ }
+ }
+
+ for ( i = 0; i < freeHunters.Count(); i++ )
+ {
+ //DevMsg( "npc_hunter %d assigned to free_hunters_squad\n", freeHunters[i]->entindex() );
+ freeHunters[i]->m_EscortBehavior.SetFollowTarget( NULL );
+ freeHunters[i]->AddToSquad( AllocPooledString( "free_hunters_squad" ) );
+ }
+
+#if 0
+ CBaseEntity *pHunterMaker = gEntList.FindEntityByClassname( NULL, "npc_hunter_maker" ); // TODO: this picks the same one every time!
+ if ( pHunterMaker )
+ {
+ for ( i = 0; i < freeHunters.Count(); i++ )
+ {
+ freeHunters[i]->m_EscortBehavior.SetFollowTarget( pHunterMaker );
+ }
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CAI_HunterEscortBehavior::DrawDebugGeometryOverlays()
+{
+ if ( !GetFollowTarget() )
+ return;
+
+ Vector vecFollowPos = GetGoalPosition();
+ if ( FarFromFollowTarget() )
+ {
+ if ( gpGlobals->curtime >= m_flTimeEscortReturn )
+ {
+ NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 255, 0, 0, 0, true, 0 );
+ }
+ else
+ {
+ NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 255, 255, 0, 0, true, 0 );
+ }
+ }
+ else
+ {
+ NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 0, 255, 0, 0, true, 0 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool Hunter_IsHunter(CBaseEntity *pEnt)
+{
+ return dynamic_cast<CNPC_Hunter *>(pEnt) != NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void Hunter_StriderBusterLaunched( CBaseEntity *pBuster )
+{
+ CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
+ int nAIs = g_AI_Manager.NumAIs();
+
+ for ( int i = 0; i < nAIs; i++ )
+ {
+ CAI_BaseNPC *pNPC = ppAIs[ i ];
+ if ( pNPC && ( pNPC->Classify() == CLASS_COMBINE_HUNTER ) && pNPC->m_lifeState == LIFE_ALIVE )
+ {
+ if ( !pNPC->GetEnemy() || !IsStriderBuster( pNPC->GetEnemy() ) )
+ {
+ Vector vecDelta = pNPC->GetAbsOrigin() - pBuster->GetAbsOrigin();
+ if ( vecDelta.Length2DSqr() < 9437184.0f ) // 3072 * 3072
+ {
+ pNPC->SetEnemy( pBuster );
+ pNPC->SetState( NPC_STATE_COMBAT );
+ pNPC->UpdateEnemyMemory( pBuster, pBuster->GetAbsOrigin() );
+
+ // Stop whatever we're doing.
+ pNPC->SetCondition( COND_SCHEDULE_DONE );
+ }
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void Hunter_StriderBusterAttached( CBaseEntity *pHunter, CBaseEntity *pAttached )
+{
+ Assert(dynamic_cast<CNPC_Hunter *>(pHunter));
+
+ static_cast<CNPC_Hunter *>(pHunter)->StriderBusterAttached(pAttached);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void Hunter_StriderBusterDetached( CBaseEntity *pHunter, CBaseEntity *pAttached )
+{
+ Assert(dynamic_cast<CNPC_Hunter *>(pHunter));
+
+ static_cast<CNPC_Hunter *>(pHunter)->StriderBusterDetached(pAttached);
+}
+
+//-------------------------------------------------------------------------------------------------
+//
+// ep2_outland_12 custom npc makers
+//
+//-------------------------------------------------------------------------------------------------
+
+class CHunterMaker : public CTemplateNPCMaker
+{
+ typedef CTemplateNPCMaker BaseClass;
+public:
+ void MakeMultipleNPCS( int nNPCs )
+ {
+ const float MIN_HEALTH_PCT = 0.2;
+
+ CUtlVector<CNPC_Hunter *> candidates;
+ CUtlVectorFixed<CNPC_Hunter *, 3> freeHunters;
+ CAI_HunterEscortBehavior::FindFreeHunters( &candidates );
+
+ freeHunters.EnsureCapacity( 3 );
+ int i;
+
+ for ( i = 0; i < candidates.Count() && freeHunters.Count() < 3; i++ )
+ {
+ if ( candidates[i]->GetHealth() > candidates[i]->GetMaxHealth() * MIN_HEALTH_PCT )
+ {
+ freeHunters.AddToTail( candidates[i] );
+ }
+ }
+
+ int nRequested = nNPCs;
+ if ( nNPCs < 3 )
+ {
+ nNPCs = MIN( 3, nNPCs + freeHunters.Count() );
+ }
+
+ int nSummoned = 0;
+ for ( i = 0; i < freeHunters.Count() && nNPCs; i++ )
+ {
+ freeHunters[i]->m_EscortBehavior.SetFollowTarget( this ); // this will make them not "free"
+ freeHunters[i]->SetName( m_iszTemplateName ); // this will force the hunter to get the FollowStrider input
+ nNPCs--;
+ nSummoned++;
+ }
+
+ DevMsg( "Requested %d to spawn, Summoning %d free hunters, spawning %d new hunters\n", nRequested, nSummoned, nNPCs );
+ if ( nNPCs )
+ {
+ BaseClass::MakeMultipleNPCS( nNPCs );
+ }
+ }
+};
+
+LINK_ENTITY_TO_CLASS( npc_hunter_maker, CHunterMaker );
+
+
+//-------------------------------------------------------------------------------------------------
+//
+// Schedules
+//
+//-------------------------------------------------------------------------------------------------
+AI_BEGIN_CUSTOM_NPC( npc_hunter, CNPC_Hunter )
+
+ DECLARE_TASK( TASK_HUNTER_AIM )
+ DECLARE_TASK( TASK_HUNTER_FIND_DODGE_POSITION )
+ DECLARE_TASK( TASK_HUNTER_DODGE )
+ DECLARE_TASK( TASK_HUNTER_PRE_RANGE_ATTACK2 )
+ DECLARE_TASK( TASK_HUNTER_SHOOT_COMMIT )
+ DECLARE_TASK( TASK_HUNTER_ANNOUNCE_FLANK )
+ DECLARE_TASK( TASK_HUNTER_BEGIN_FLANK )
+ DECLARE_TASK( TASK_HUNTER_STAGGER )
+ DECLARE_TASK( TASK_HUNTER_CORNERED_TIMER )
+ DECLARE_TASK( TASK_HUNTER_FIND_SIDESTEP_POSITION )
+ DECLARE_TASK( TASK_HUNTER_CHARGE )
+ DECLARE_TASK( TASK_HUNTER_FINISH_RANGE_ATTACK )
+ DECLARE_TASK( TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY )
+ DECLARE_TASK( TASK_HUNTER_CHARGE_DELAY )
+
+ DECLARE_ACTIVITY( ACT_HUNTER_DEPLOYRA2 )
+ DECLARE_ACTIVITY( ACT_HUNTER_DODGER )
+ DECLARE_ACTIVITY( ACT_HUNTER_DODGEL )
+ DECLARE_ACTIVITY( ACT_HUNTER_GESTURE_SHOOT )
+ DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_STICKYBOMB )
+ DECLARE_ACTIVITY( ACT_HUNTER_STAGGER )
+ DECLARE_ACTIVITY( ACT_DI_HUNTER_MELEE )
+ DECLARE_ACTIVITY( ACT_DI_HUNTER_THROW )
+ DECLARE_ACTIVITY( ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER )
+ DECLARE_ACTIVITY( ACT_HUNTER_ANGRY )
+ DECLARE_ACTIVITY( ACT_HUNTER_WALK_ANGRY )
+ DECLARE_ACTIVITY( ACT_HUNTER_FOUND_ENEMY )
+ DECLARE_ACTIVITY( ACT_HUNTER_FOUND_ENEMY_ACK )
+ DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_START )
+ DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_RUN )
+ DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_STOP )
+ DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_CRASH )
+ DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_HIT )
+ DECLARE_ACTIVITY( ACT_HUNTER_RANGE_ATTACK2_UNPLANTED )
+ DECLARE_ACTIVITY( ACT_HUNTER_IDLE_PLANTED )
+ DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_N )
+ DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_S )
+ DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_E )
+ DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_W )
+
+ DECLARE_INTERACTION( g_interactionHunterFoundEnemy );
+
+ DECLARE_SQUADSLOT( SQUAD_SLOT_HUNTER_CHARGE )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_HUNTER_FLANK_FIRST )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_RUN_SHOOT )
+
+ DECLARE_CONDITION( COND_HUNTER_SHOULD_PATROL )
+ DECLARE_CONDITION( COND_HUNTER_FORCED_FLANK_ENEMY )
+ DECLARE_CONDITION( COND_HUNTER_CAN_CHARGE_ENEMY )
+ DECLARE_CONDITION( COND_HUNTER_STAGGERED )
+ DECLARE_CONDITION( COND_HUNTER_IS_INDOORS )
+ DECLARE_CONDITION( COND_HUNTER_HIT_BY_STICKYBOMB )
+ DECLARE_CONDITION( COND_HUNTER_SEE_STRIDERBUSTER )
+ DECLARE_CONDITION( COND_HUNTER_FORCED_DODGE )
+ DECLARE_CONDITION( COND_HUNTER_INCOMING_VEHICLE )
+ DECLARE_CONDITION( COND_HUNTER_NEW_HINTGROUP )
+ DECLARE_CONDITION( COND_HUNTER_CANT_PLANT )
+ DECLARE_CONDITION( COND_HUNTER_SQUADMATE_FOUND_ENEMY )
+
+ DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_LEFT )
+ DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_RIGHT )
+ DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_BACK )
+ DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ANNOUNCE )
+ DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ATTACK_LEFT )
+ DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ATTACK_RIGHT )
+ DECLARE_ANIMEVENT( AE_HUNTER_DIE )
+ DECLARE_ANIMEVENT( AE_HUNTER_SPRAY_BLOOD )
+ DECLARE_ANIMEVENT( AE_HUNTER_START_EXPRESSION )
+ DECLARE_ANIMEVENT( AE_HUNTER_END_EXPRESSION )
+
+ //=========================================================
+ // Attack (Deploy/shoot/finish)
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_RANGE_ATTACK1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HUNTER_SHOOT_COMMIT 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_LOST_ENEMY"
+ " COND_ENEMY_OCCLUDED"
+ " COND_WEAPON_SIGHT_OCCLUDED"
+ " COND_TOO_CLOSE_TO_ATTACK"
+ " COND_TOO_FAR_TO_ATTACK"
+ " COND_NOT_FACING_ATTACK"
+ )
+
+ //=========================================================
+ // Attack (Deploy/shoot/finish)
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_RANGE_ATTACK2,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HUNTER_PRE_RANGE_ATTACK2 0"
+ " TASK_HUNTER_SHOOT_COMMIT 0"
+ " TASK_RANGE_ATTACK2 0"
+ " TASK_HUNTER_FINISH_RANGE_ATTACK 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 0.4"
+ " TASK_WAIT_RANDOM 0.2"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // Shoot at striderbuster. Distinct from generic range attack
+ // because of BuildScheduleTestBits.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HUNTER_SHOOT_COMMIT 0"
+ " TASK_RANGE_ATTACK2 0"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // Shoot at striderbuster with a little latency beforehand
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER_LATENT,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HUNTER_SHOOT_COMMIT 0"
+ " TASK_WAIT 0.2"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_RANGE_ATTACK2"
+ " TASK_RANGE_ATTACK2 0"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // Dodge Incoming vehicle
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_DODGE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_DODGE"
+ " TASK_HUNTER_FIND_DODGE_POSITION 0"
+ " TASK_HUNTER_DODGE 0"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // Dodge Incoming vehicle
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_FAIL_DODGE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ )
+
+ //==================================================
+ // > SCHED_HUNTER_CHARGE_ENEMY
+ // Rush at my enemy and head-butt them.
+ //==================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CHARGE_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_CHARGE_ENEMY"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_HUNTER_CHARGE 0"
+ ""
+ " Interrupts"
+ " COND_TASK_FAILED"
+ " COND_ENEMY_DEAD"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_FAIL_CHARGE_ENEMY,
+
+ " Tasks"
+ " TASK_HUNTER_CHARGE_DELAY 10"
+ )
+
+ //=========================================================
+ // Chase the enemy with intent to claw
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CHASE_ENEMY_MELEE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
+ " TASK_STOP_MOVING 0"
+ " TASK_GET_CHASE_PATH_TO_ENEMY 300"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_ENEMY_UNREACHABLE"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ //" COND_TOO_CLOSE_TO_ATTACK"
+ " COND_LOST_ENEMY"
+ )
+
+ //=========================================================
+ // Chase my enemy, shoot or claw when possible to do so.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CHASE_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
+ " TASK_STOP_MOVING 0"
+ " TASK_GET_CHASE_PATH_TO_ENEMY 300"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_ENEMY_UNREACHABLE"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_TOO_CLOSE_TO_ATTACK"
+ " COND_LOST_ENEMY"
+ )
+
+ //=========================================================
+ // Move to a flanking position, then shoot if possible.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_FLANK_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
+ " TASK_STOP_MOVING 0"
+ " TASK_HUNTER_BEGIN_FLANK 0"
+ " TASK_GET_FLANK_ARC_PATH_TO_ENEMY_LOS 30"
+ " TASK_HUNTER_ANNOUNCE_FLANK 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ //" TASK_HUNTER_END_FLANK 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ //" COND_CAN_RANGE_ATTACK1"
+ //" COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_ENEMY_DEAD"
+ " COND_ENEMY_UNREACHABLE"
+ " COND_TOO_CLOSE_TO_ATTACK"
+ " COND_LOST_ENEMY"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_COMBAT_FACE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_FACE_ENEMY 1"
+ ""
+ " Interrupts"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ )
+
+ //=========================================================
+ // Like the base class, only don't stop in the middle of
+ // swinging if the enemy is killed, hides, or new enemy.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_MELEE_ATTACK1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_MELEE_ATTACK1 0"
+ //" TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_POST_MELEE_WAIT"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // In a fight with nothing to do. Make busy!
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CHANGE_POSITION,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WANDER 720432" // 6 feet to 36 feet
+ " TASK_RUN_PATH 0"
+ " TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_CHANGE_POSITION_FINISH"
+ ""
+ " Interrupts"
+ " COND_ENEMY_DEAD"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_MOVE_AWAY"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // In a fight with nothing to do. Make busy!
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CHANGE_POSITION_FINISH,
+
+ " Tasks"
+ " TASK_FACE_ENEMY 0"
+ " TASK_WAIT_FACE_ENEMY_RANDOM 5"
+ ""
+ " Interrupts"
+ " COND_ENEMY_DEAD"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_MOVE_AWAY"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // In a fight with nothing to do. Make busy!
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_SIDESTEP,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE" // used because sched_fail includes a one second pause. ick!
+ " TASK_STOP_MOVING 0"
+ " TASK_HUNTER_FIND_SIDESTEP_POSITION 0"
+ " TASK_GET_PATH_TO_SAVEPOSITION 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_PATROL,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WANDER 720432" // 6 feet to 36 feet
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_REASONABLE 0"
+ " TASK_WAIT_RANDOM 3"
+ ""
+ " Interrupts"
+ " COND_ENEMY_DEAD"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_PLAYER"
+ " COND_HEAR_BULLET_IMPACT"
+ " COND_HEAR_MOVE_AWAY"
+ " COND_NEW_ENEMY"
+ " COND_SEE_ENEMY"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ )
+
+ //=========================================================
+ // Stagger because I got hit by something heavy
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_STAGGER,
+
+ " Tasks"
+ " TASK_HUNTER_STAGGER 0"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // Run around randomly until we detect an enemy
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_PATROL_RUN,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE"
+ " TASK_SET_ROUTE_SEARCH_TIME 5" // Spend 5 seconds trying to build a path if stuck
+ " TASK_GET_PATH_TO_RANDOM_NODE 200"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ " COND_CAN_RANGE_ATTACK1 "
+ " COND_CAN_RANGE_ATTACK2 "
+ " COND_CAN_MELEE_ATTACK1 "
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_GIVE_WAY"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_PLAYER"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_TAKE_COVER_FROM_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_CHASE_ENEMY_MELEE"
+ " TASK_HUNTER_CORNERED_TIMER 10.0"
+ " TASK_WAIT 0.0"
+ // " TASK_SET_TOLERANCE_DISTANCE 24"
+ // " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_FIND_FAR_NODE_COVER_FROM_ENEMY 200.0"
+ " TASK_RUN_PATH 0"
+ " TASK_HUNTER_CORNERED_TIMER 0.0"
+ // " TASK_CLEAR_FAIL_SCHEDULE 0" // not used because sched_fail includes a one second pause. ick!
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_REMEMBER MEMORY:INCOVER"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_HIDE_UNDER_COVER"
+ /*
+ " TASK_FACE_ENEMY 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
+ " TASK_WAIT 1"
+ */
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_HIDE_UNDER_COVER,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE" // used because sched_fail includes a one second pause. ick!
+ " TASK_FACE_ENEMY 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
+ " TASK_WAIT 1"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ " COND_HAVE_ENEMY_LOS"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_FOUND_ENEMY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_HUNTER_FOUND_ENEMY"
+ ""
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_FOUND_ENEMY_ACK,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WAIT_RANDOM 0.75"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_HUNTER_FOUND_ENEMY_ACK"
+ ""
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // An empty schedule that immediately bails out, faster than
+ // SCHED_FAIL which stops moving and waits for one second.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_FAIL_IMMEDIATE,
+
+ " Tasks"
+ " TASK_WAIT 0"
+
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_GOTO_HINT,
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_CLEAR_HINTNODE" // used because sched_fail includes a one second pause. ick!
+ " TASK_GET_PATH_TO_HINTNODE 1"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_CLEAR_HINTNODE 0"
+ ""
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CLEAR_HINTNODE,
+ " Tasks"
+ " TASK_CLEAR_HINTNODE 0"
+ ""
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_SIEGE_STAND,
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_PLAYER 0"
+ " TASK_WAIT 10"
+ " TASK_WAIT_RANDOM 2"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_CHANGE_POSITION_SIEGE"
+ ""
+ ""
+ " Interrupts"
+ " COND_SEE_PLAYER"
+ " COND_NEW_ENEMY"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HUNTER_CHANGE_POSITION_SIEGE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WANDER 2400480"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_PLAYER 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ // formula is MIN_DIST * 10000 + MAX_DIST
+
+AI_END_CUSTOM_NPC()