2013-12-02 19:31:46 -08:00
//========= 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"
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
# include "mapbase/GlobalStrings.h"
# endif
2013-12-02 19:31:46 -08:00
// 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.
//-----------------------------------------------------------------------------
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
# define s_iszStriderClassname gm_isz_class_Strider
# define s_iszPhysPropClassname gm_isz_class_PropPhysics
static string_t s_iszStriderBusterClassname ;
static string_t s_iszMagnadeClassname ;
static string_t s_iszHuntersToRunOver ;
# else
2013-12-02 19:31:46 -08:00
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 ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
//-----------------------------------------------------------------------------
// 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 ( ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
s_iszStriderBusterClassname = AllocPooledString ( " weapon_striderbuster " ) ;
s_iszMagnadeClassname = AllocPooledString ( " npc_grenade_magna " ) ;
s_iszHuntersToRunOver = AllocPooledString ( " hunters_to_run_over " ) ;
# else
2013-12-02 19:31:46 -08:00
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 " ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
// 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 ) ;
Mapbase v2.0; bulk commit
- Added custom map compile tools (vbsp, vvis, vrad)
- Changed blink fix (shouldn't change anything in-game)
- Added auto-completion to ent_create, npc_create, and the main set of "npc_" debug commands
- Added ent_create_aimed, an ent_create equivalent of npc_create_aimed
- Made hunters start using the "vs. player" melee animation against smaller NPCs that look weird with the "stab" attack
- Added "explosion_sparks" convar, which fixes broken code for giving explosions sparks (disabled by default because of how different it looks)
- Made interaction code capable of being dispatched on any entity, not just combat characters
- Added npc_barnacle_ignite convar, which lets barnacles be ignited by flares
- Fixed certain NPCs getting out of the way for the player when they hate them
- Fixed auto-generated "speak" scene responses not using parameters that work on real VCDs
- Made "stop_on_nonidle" capable of being used in any mod, not just HL2 episodic mods
- Selectable color for ragdoll boogie/point_ragdollboogie
- Fixed PickupWeaponInstant not firing weapon pickup outputs
- Introduced inputs and keyvalues for "lerping" to math_counter_advanced
- Fixed ClearConsole on logic_console
- logic_convar should now detect client convars correctly
- New NormalizeAngles input on math_vector
- logic_modelinfo LookupActivity input
- math_generate fixed and expanded to be more like math_counter
- Added a WIP game logging system for playtesting maps
- Introduced logic_playerinfo, an entity that can read a player's name or ID
- Fixed some new filters not working with filter_multi
- Added radius pickup spawnflag to func_physbox
- Added "Preserve name" spawnflag to weapons
- Added cc_achievement_debug message for when an achievement doesn't exist
- Made npc_combine_s not speak while in logic_choreographed_scenes
- Fixed zombie torsos/legs/headcrabs not being serverside when zombie is forced to server ragdoll
- Expanded and cleaned up npc_zombie_custom
- Fixed func_commandredirects not cleaning up correctly and sometimes crashing the game
- Allowed player squad commands to go through +USE-held objects
- Added a bunch of I/O/KV to trigger_waterydeath for better configuration
- Changed save comment system to use the chapter title from world properties, and the ability to suppress the title popup that normally results from it
- Adjusted game_convar_mod for MP planning
- Removed the func_precipitation custom particle/splash code for now, as it was causing problems
- Fixed env_global_light not accepting lightcolor
- Added "Additional Buttons" to player_speedmod
- Added save comment to RPC
- Added env_projectedtexture attenuation
- Added scripted_sequence OnPreIdleSequence
- Added OnCrab to zombies
- Added skill_changed game event (may need further testing)
- Added a fix for viewmodels flipping under extreme FOV values
- Added code that allows mappers to change the skin on shotgunners without it usually flipping back randomly
- Fixed a very, very, very major shader performance issue
- New SetAbsOrigin/Angles inputs on all entities, analogous to SetLocalOrigin/Angles
- Code improvements for I/O involving angles
- logic_entity_position improvements/fixes, including a new OutAngles output that outputs the angles on position calls
- Alternate collision/player avoidance spawnflag obsoletion enforcement disabled
- Enable/DisableHazardLights inputs on the EP2 jalopy, equivalent to the keyvalue
- Miscellaneous shader formatting adjustments and fixes
- Fixed AlwaysDrawOff on env_projectedtexture not being a valid input
2019-12-14 04:20:02 +00:00
# ifdef MAPBASE
// The "VS_PLAYER" animation looks better than the regular animation when used against non-humans
if ( GetEnemy ( ) & & ( GetEnemy ( ) - > IsPlayer ( ) | |
( GetEnemy ( ) - > IsCombatCharacter ( ) & & GetEnemy ( ) - > MyCombatCharacterPointer ( ) - > GetHullType ( ) ! = HULL_HUMAN ) ) )
# else
2013-12-02 19:31:46 -08:00
if ( GetEnemy ( ) & & GetEnemy ( ) - > IsPlayer ( ) )
Mapbase v2.0; bulk commit
- Added custom map compile tools (vbsp, vvis, vrad)
- Changed blink fix (shouldn't change anything in-game)
- Added auto-completion to ent_create, npc_create, and the main set of "npc_" debug commands
- Added ent_create_aimed, an ent_create equivalent of npc_create_aimed
- Made hunters start using the "vs. player" melee animation against smaller NPCs that look weird with the "stab" attack
- Added "explosion_sparks" convar, which fixes broken code for giving explosions sparks (disabled by default because of how different it looks)
- Made interaction code capable of being dispatched on any entity, not just combat characters
- Added npc_barnacle_ignite convar, which lets barnacles be ignited by flares
- Fixed certain NPCs getting out of the way for the player when they hate them
- Fixed auto-generated "speak" scene responses not using parameters that work on real VCDs
- Made "stop_on_nonidle" capable of being used in any mod, not just HL2 episodic mods
- Selectable color for ragdoll boogie/point_ragdollboogie
- Fixed PickupWeaponInstant not firing weapon pickup outputs
- Introduced inputs and keyvalues for "lerping" to math_counter_advanced
- Fixed ClearConsole on logic_console
- logic_convar should now detect client convars correctly
- New NormalizeAngles input on math_vector
- logic_modelinfo LookupActivity input
- math_generate fixed and expanded to be more like math_counter
- Added a WIP game logging system for playtesting maps
- Introduced logic_playerinfo, an entity that can read a player's name or ID
- Fixed some new filters not working with filter_multi
- Added radius pickup spawnflag to func_physbox
- Added "Preserve name" spawnflag to weapons
- Added cc_achievement_debug message for when an achievement doesn't exist
- Made npc_combine_s not speak while in logic_choreographed_scenes
- Fixed zombie torsos/legs/headcrabs not being serverside when zombie is forced to server ragdoll
- Expanded and cleaned up npc_zombie_custom
- Fixed func_commandredirects not cleaning up correctly and sometimes crashing the game
- Allowed player squad commands to go through +USE-held objects
- Added a bunch of I/O/KV to trigger_waterydeath for better configuration
- Changed save comment system to use the chapter title from world properties, and the ability to suppress the title popup that normally results from it
- Adjusted game_convar_mod for MP planning
- Removed the func_precipitation custom particle/splash code for now, as it was causing problems
- Fixed env_global_light not accepting lightcolor
- Added "Additional Buttons" to player_speedmod
- Added save comment to RPC
- Added env_projectedtexture attenuation
- Added scripted_sequence OnPreIdleSequence
- Added OnCrab to zombies
- Added skill_changed game event (may need further testing)
- Added a fix for viewmodels flipping under extreme FOV values
- Added code that allows mappers to change the skin on shotgunners without it usually flipping back randomly
- Fixed a very, very, very major shader performance issue
- New SetAbsOrigin/Angles inputs on all entities, analogous to SetLocalOrigin/Angles
- Code improvements for I/O involving angles
- logic_entity_position improvements/fixes, including a new OutAngles output that outputs the angles on position calls
- Alternate collision/player avoidance spawnflag obsoletion enforcement disabled
- Enable/DisableHazardLights inputs on the EP2 jalopy, equivalent to the keyvalue
- Miscellaneous shader formatting adjustments and fixes
- Fixed AlwaysDrawOff on env_projectedtexture not being a valid input
2019-12-14 04:20:02 +00:00
# endif
2013-12-02 19:31:46 -08:00
{
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
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
if ( IRelationType ( pEntity ) < = D_FR & & ( GetNextAttack ( ) < gpGlobals - > curtime ) )
# else
2013-12-02 19:31:46 -08:00
if ( IRelationType ( pEntity ) = = D_HT & & ( GetNextAttack ( ) < gpGlobals - > curtime ) )
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
{
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 )
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
if ( nDisposition = = D_HT & & pEntity - > ClassMatches ( gm_isz_class_Bullseye ) )
# else
2013-12-02 19:31:46 -08:00
if ( nDisposition = = D_HT & & pEntity - > ClassMatches ( " npc_bullseye " ) )
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 ;
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
else if ( ! bIsBuster & & ( ! GetEnemy ( ) | | ! GetEnemy ( ) - > ClassMatches ( gm_isz_class_Bullseye ) ) & & flDist < hunter_flechette_min_range . GetFloat ( ) )
# else
2013-12-02 19:31:46 -08:00
else if ( ! bIsBuster & & ( ! GetEnemy ( ) | | ! GetEnemy ( ) - > ClassMatches ( " npc_bullseye " ) ) & & flDist < hunter_flechette_min_range . GetFloat ( ) )
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
{
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 ) ;
}
}
}
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
else if ( info . GetDamageType ( ) = = DMG_CLUB & &
info . GetInflictor ( ) & & info . GetInflictor ( ) - > IsNPC ( ) )
{
// If the *inflictor* is a NPC doing club damage, it's most likely an antlion guard or even another hunter charging us.
// Add DMG_CRUSH so we ragdoll immediately if we die.
myInfo . AddDamageType ( DMG_CRUSH ) ;
}
// "So, do you know what the alternative fire method does on the AR2? It kills hunters. How did--"
// "No, only Freeman's does it!"
// "What do you mean 'Only Freeman's does it'?"
// "Only energy balls fired by the player can dissolve hunters. Energy balls fired by NPCs only do a metered amount of damage."
// "...Huh. Well, in that case, we'll just use rocket launchers."
//
// That instructor was straight-up lying to those rebels, but now he's telling the truth.
// Hunters die to NPC balls instantly and act like it was a player ball.
// Implementation is sketchy, but it was the best I could do.
if ( myInfo . GetDamageType ( ) & DMG_DISSOLVE & &
info . GetAttacker ( ) & & info . GetAttacker ( ) - > IsNPC ( ) & &
info . GetInflictor ( ) & & info . GetInflictor ( ) - > ClassMatches ( " prop_combine_ball " ) )
{
// We divide by the ally damage scale to counter its usage in OnTakeDamage_Alive.
myInfo . SetDamage ( ( float ) GetMaxHealth ( ) / sk_hunter_citizen_damage_scale . GetFloat ( ) ) ;
myInfo . AddDamageType ( DMG_CRUSH ) ;
//myInfo.SetDamagePosition( info.GetInflictor()->GetAbsOrigin() );
//myInfo.SetDamageForce( info.GetInflictor()->GetAbsVelocity() );
// Make the NPC's ball explode
info . GetInflictor ( ) - > AcceptInput ( " Explode " , this , this , variant_t ( ) , 0 ) ;
}
# endif
2013-12-02 19:31:46 -08:00
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 ( )