Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7764 lines
229 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Small, fast version of the strider. Goes where striders cannot, such
  4. // as into buildings. Best killed with physics objects and explosives.
  5. //
  6. //=============================================================================
  7. #include "cbase.h"
  8. #include "npc_strider.h"
  9. #include "npc_hunter.h"
  10. #include "ai_behavior_follow.h"
  11. #include "ai_moveprobe.h"
  12. #include "ai_senses.h"
  13. #include "ai_speech.h"
  14. #include "ai_task.h"
  15. #include "ai_default.h"
  16. #include "ai_schedule.h"
  17. #include "ai_hull.h"
  18. #include "ai_baseactor.h"
  19. #include "ai_waypoint.h"
  20. #include "ai_link.h"
  21. #include "ai_hint.h"
  22. #include "ai_squadslot.h"
  23. #include "ai_squad.h"
  24. #include "ai_tacticalservices.h"
  25. #include "beam_shared.h"
  26. #include "datacache/imdlcache.h"
  27. #include "eventqueue.h"
  28. #include "gib.h"
  29. #include "globalstate.h"
  30. #include "hierarchy.h"
  31. #include "movevars_shared.h"
  32. #include "npcevent.h"
  33. #include "saverestore_utlvector.h"
  34. #include "particle_parse.h"
  35. #include "te_particlesystem.h"
  36. #include "sceneentity.h"
  37. #include "shake.h"
  38. #include "soundenvelope.h"
  39. #include "soundent.h"
  40. #include "SpriteTrail.h"
  41. #include "IEffects.h"
  42. #include "engine/IEngineSound.h"
  43. #include "bone_setup.h"
  44. #include "studio.h"
  45. #include "ai_route.h"
  46. #include "ammodef.h"
  47. #include "npc_bullseye.h"
  48. #include "physobj.h"
  49. #include "ai_memory.h"
  50. #include "collisionutils.h"
  51. #include "shot_manipulator.h"
  52. #include "steamjet.h"
  53. #include "physics_prop_ragdoll.h"
  54. #include "vehicle_base.h"
  55. #include "coordsize.h"
  56. #include "hl2_shareddefs.h"
  57. #include "te_effect_dispatch.h"
  58. #include "beam_flags.h"
  59. #include "prop_combine_ball.h"
  60. #include "explode.h"
  61. #include "weapon_physcannon.h"
  62. #include "weapon_striderbuster.h"
  63. #include "monstermaker.h"
  64. #include "weapon_rpg.h"
  65. // memdbgon must be the last include file in a .cpp file!!!
  66. #include "tier0/memdbgon.h"
  67. class CNPC_Hunter;
  68. static const char *HUNTER_FLECHETTE_MODEL = "models/weapons/hunter_flechette.mdl";
  69. // Think contexts
  70. static const char *HUNTER_BLEED_THINK = "HunterBleed";
  71. static const char *HUNTER_ZAP_THINK = "HunterZap";
  72. static const char *HUNTER_JOSTLE_VEHICLE_THINK = "HunterJostle";
  73. ConVar sk_hunter_health( "sk_hunter_health", "210" );
  74. // Melee attacks
  75. ConVar sk_hunter_dmg_one_slash( "sk_hunter_dmg_one_slash", "20" );
  76. ConVar sk_hunter_dmg_charge( "sk_hunter_dmg_charge", "20" );
  77. // Flechette volley attack
  78. ConVar hunter_flechette_max_range( "hunter_flechette_max_range", "1200" );
  79. ConVar hunter_flechette_min_range( "hunter_flechette_min_range", "100" );
  80. ConVar hunter_flechette_volley_size( "hunter_flechette_volley_size", "8" );
  81. ConVar hunter_flechette_speed( "hunter_flechette_speed", "2000" );
  82. ConVar sk_hunter_dmg_flechette( "sk_hunter_dmg_flechette", "4.0" );
  83. ConVar sk_hunter_flechette_explode_dmg( "sk_hunter_flechette_explode_dmg", "12.0" );
  84. ConVar sk_hunter_flechette_explode_radius( "sk_hunter_flechette_explode_radius", "128.0" );
  85. ConVar hunter_flechette_explode_delay( "hunter_flechette_explode_delay", "2.5" );
  86. ConVar hunter_flechette_delay( "hunter_flechette_delay", "0.1" );
  87. ConVar hunter_first_flechette_delay( "hunter_first_flechette_delay", "0.5" );
  88. ConVar hunter_flechette_max_concurrent_volleys( "hunter_flechette_max_concurrent_volleys", "2" );
  89. ConVar hunter_flechette_volley_start_min_delay( "hunter_flechette_volley_start_min_delay", ".25" );
  90. ConVar hunter_flechette_volley_start_max_delay( "hunter_flechette_volley_start_max_delay", ".95" );
  91. ConVar hunter_flechette_volley_end_min_delay( "hunter_flechette_volley_end_min_delay", "1" );
  92. ConVar hunter_flechette_volley_end_max_delay( "hunter_flechette_volley_end_max_delay", "2" );
  93. ConVar hunter_flechette_test( "hunter_flechette_test", "0" );
  94. ConVar hunter_clamp_shots( "hunter_clamp_shots", "1" );
  95. ConVar hunter_cheap_explosions( "hunter_cheap_explosions", "1" );
  96. // Damage received
  97. ConVar sk_hunter_bullet_damage_scale( "sk_hunter_bullet_damage_scale", "0.6" );
  98. ConVar sk_hunter_charge_damage_scale( "sk_hunter_charge_damage_scale", "2.0" );
  99. ConVar sk_hunter_buckshot_damage_scale( "sk_hunter_buckshot_damage_scale", "0.5" );
  100. ConVar sk_hunter_vehicle_damage_scale( "sk_hunter_vehicle_damage_scale", "2.2" );
  101. ConVar sk_hunter_dmg_from_striderbuster( "sk_hunter_dmg_from_striderbuster", "150" );
  102. ConVar sk_hunter_citizen_damage_scale( "sk_hunter_citizen_damage_scale", "0.3" );
  103. ConVar hunter_allow_dissolve( "hunter_allow_dissolve", "1" );
  104. ConVar hunter_random_expressions( "hunter_random_expressions", "0" );
  105. ConVar hunter_show_weapon_los_z( "hunter_show_weapon_los_z", "0" );
  106. ConVar hunter_show_weapon_los_condition( "hunter_show_weapon_los_condition", "0" );
  107. ConVar hunter_melee_delay( "hunter_melee_delay", "2.0" );
  108. // Bullrush charge.
  109. ConVar hunter_charge( "hunter_charge", "1" );
  110. ConVar hunter_charge_min_delay( "hunter_charge_min_delay", "10.0" );
  111. ConVar hunter_charge_pct( "hunter_charge_pct", "25" );
  112. ConVar hunter_charge_test( "hunter_charge_test", "0" );
  113. // Vehicle dodging.
  114. ConVar hunter_dodge_warning( "hunter_dodge_warning", "1.1" );
  115. ConVar hunter_dodge_warning_width( "hunter_dodge_warning_width", "180" );
  116. ConVar hunter_dodge_warning_cone( "hunter_dodge_warning_cone", ".5" );
  117. ConVar hunter_dodge_debug( "hunter_dodge_debug", "0" );
  118. // Jostle vehicles when hit by them
  119. 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
  120. ConVar hunter_jostle_car_max_speed( "hunter_jostle_car_max_speed", "600" ); // Used for determining jostle scale
  121. ConVar hunter_free_knowledge( "hunter_free_knowledge", "10.0" );
  122. ConVar hunter_plant_adjust_z( "hunter_plant_adjust_z", "12" );
  123. ConVar hunter_disable_patrol( "hunter_disable_patrol", "0" );
  124. // Dealing with striderbusters
  125. ConVar hunter_hate_held_striderbusters( "hunter_hate_held_striderbusters", "1" );
  126. ConVar hunter_hate_thrown_striderbusters( "hunter_hate_thrown_striderbusters", "1" );
  127. ConVar hunter_hate_attached_striderbusters( "hunter_hate_attached_striderbusters", "1" );
  128. ConVar hunter_hate_held_striderbusters_delay( "hunter_hate_held_striderbusters_delay", "0.5" );
  129. ConVar hunter_hate_held_striderbusters_tolerance( "hunter_hate_held_striderbusters_tolerance", "2000.0" );
  130. ConVar hunter_hate_thrown_striderbusters_tolerance( "hunter_hate_thrown_striderbusters_tolerance", "300.0" );
  131. ConVar hunter_seek_thrown_striderbusters_tolerance( "hunter_seek_thrown_striderbusters_tolerance", "400.0" );
  132. ConVar hunter_retreat_striderbusters( "hunter_retreat_striderbusters", "1", FCVAR_NONE, "If true, the hunter will retreat when a buster is glued to him." );
  133. ConVar hunter_allow_nav_jump( "hunter_allow_nav_jump", "0" );
  134. ConVar g_debug_hunter_charge( "g_debug_hunter_charge", "0" );
  135. ConVar hunter_stand_still( "hunter_stand_still", "0" ); // used for debugging, keeps them rooted in place
  136. ConVar hunter_siege_frequency( "hunter_siege_frequency", "12" );
  137. #define HUNTER_FOV_DOT 0.0 // 180 degree field of view
  138. #define HUNTER_CHARGE_MIN 256
  139. #define HUNTER_CHARGE_MAX 1024
  140. #define HUNTER_FACE_ENEMY_DIST 512.0f
  141. #define HUNTER_MELEE_REACH 80
  142. #define HUNTER_BLOOD_LEFT_FOOT 0
  143. #define HUNTER_IGNORE_ENEMY_TIME 5 // How long the hunter will ignore another enemy when distracted by the player.
  144. #define HUNTER_FACING_DOT 0.8 // The angle within which we start shooting
  145. #define HUNTER_SHOOT_MAX_YAW_DEG 60.0f // Once shooting, clamp to +/- these degrees of yaw deflection as our target moves
  146. #define HUNTER_SHOOT_MAX_YAW_COS 0.5f // The cosine of the above angle
  147. #define HUNTER_FLECHETTE_WARN_TIME 1.0f
  148. #define HUNTER_SEE_ENEMY_TIME_INVALID -1
  149. #define NUM_FLECHETTE_VOLLEY_ON_FOLLOW 4
  150. #define HUNTER_SIEGE_MAX_DIST_MODIFIER 2.0f
  151. //-----------------------------------------------------------------------------
  152. // Animation events
  153. //-----------------------------------------------------------------------------
  154. int AE_HUNTER_FOOTSTEP_LEFT;
  155. int AE_HUNTER_FOOTSTEP_RIGHT;
  156. int AE_HUNTER_FOOTSTEP_BACK;
  157. int AE_HUNTER_MELEE_ANNOUNCE;
  158. int AE_HUNTER_MELEE_ATTACK_LEFT;
  159. int AE_HUNTER_MELEE_ATTACK_RIGHT;
  160. int AE_HUNTER_DIE;
  161. int AE_HUNTER_SPRAY_BLOOD;
  162. int AE_HUNTER_START_EXPRESSION;
  163. int AE_HUNTER_END_EXPRESSION;
  164. //-----------------------------------------------------------------------------
  165. // Interactions.
  166. //-----------------------------------------------------------------------------
  167. int g_interactionHunterFoundEnemy = 0;
  168. //-----------------------------------------------------------------------------
  169. // Local stuff.
  170. //-----------------------------------------------------------------------------
  171. static string_t s_iszStriderClassname;
  172. static string_t s_iszStriderBusterClassname;
  173. static string_t s_iszMagnadeClassname;
  174. static string_t s_iszPhysPropClassname;
  175. static string_t s_iszHuntersToRunOver;
  176. //-----------------------------------------------------------------------------
  177. // Custom Activities
  178. //-----------------------------------------------------------------------------
  179. Activity ACT_HUNTER_DEPLOYRA2;
  180. Activity ACT_HUNTER_DODGER;
  181. Activity ACT_HUNTER_DODGEL;
  182. Activity ACT_HUNTER_GESTURE_SHOOT;
  183. Activity ACT_HUNTER_FLINCH_STICKYBOMB;
  184. Activity ACT_HUNTER_STAGGER;
  185. Activity ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER;
  186. Activity ACT_DI_HUNTER_MELEE;
  187. Activity ACT_DI_HUNTER_THROW;
  188. Activity ACT_HUNTER_ANGRY;
  189. Activity ACT_HUNTER_WALK_ANGRY;
  190. Activity ACT_HUNTER_FOUND_ENEMY;
  191. Activity ACT_HUNTER_FOUND_ENEMY_ACK;
  192. Activity ACT_HUNTER_CHARGE_START;
  193. Activity ACT_HUNTER_CHARGE_RUN;
  194. Activity ACT_HUNTER_CHARGE_STOP;
  195. Activity ACT_HUNTER_CHARGE_CRASH;
  196. Activity ACT_HUNTER_CHARGE_HIT;
  197. Activity ACT_HUNTER_RANGE_ATTACK2_UNPLANTED;
  198. Activity ACT_HUNTER_IDLE_PLANTED;
  199. Activity ACT_HUNTER_FLINCH_N;
  200. Activity ACT_HUNTER_FLINCH_S;
  201. Activity ACT_HUNTER_FLINCH_E;
  202. Activity ACT_HUNTER_FLINCH_W;
  203. //-----------------------------------------------------------------------------
  204. // Squad slots
  205. //-----------------------------------------------------------------------------
  206. enum SquadSlot_t
  207. {
  208. SQUAD_SLOT_HUNTER_CHARGE = LAST_SHARED_SQUADSLOT,
  209. SQUAD_SLOT_HUNTER_FLANK_FIRST,
  210. SQUAD_SLOT_HUNTER_FLANK_LAST = SQUAD_SLOT_HUNTER_FLANK_FIRST,
  211. SQUAD_SLOT_RUN_SHOOT,
  212. };
  213. #define HUNTER_FOLLOW_DISTANCE 2000.0f
  214. #define HUNTER_FOLLOW_DISTANCE_SQR (HUNTER_FOLLOW_DISTANCE * HUNTER_FOLLOW_DISTANCE)
  215. #define HUNTER_RUNDOWN_SQUADDATA 0
  216. //-----------------------------------------------------------------------------
  217. // We're doing this quite a lot, so this makes the check a lot faster since
  218. // we don't have to compare strings.
  219. //-----------------------------------------------------------------------------
  220. bool IsStriderBuster( CBaseEntity *pEntity )
  221. {
  222. if ( !pEntity )
  223. return false;
  224. if( pEntity->m_iClassname == s_iszStriderBusterClassname ||
  225. pEntity->m_iClassname == s_iszMagnadeClassname)
  226. return true;
  227. return false;
  228. }
  229. //-----------------------------------------------------------------------------
  230. //-----------------------------------------------------------------------------
  231. bool HateThisStriderBuster( CBaseEntity *pTarget )
  232. {
  233. if ( StriderBuster_WasKnockedOffStrider(pTarget) )
  234. return false;
  235. if ( pTarget->VPhysicsGetObject() )
  236. {
  237. if ( hunter_hate_held_striderbusters.GetBool() ||
  238. hunter_hate_thrown_striderbusters.GetBool() ||
  239. hunter_hate_attached_striderbusters.GetBool() )
  240. {
  241. if ( ( pTarget->VPhysicsGetObject()->GetGameFlags() & ( FVPHYSICS_PLAYER_HELD | FVPHYSICS_WAS_THROWN ) ) )
  242. {
  243. return true;
  244. }
  245. if ( StriderBuster_IsAttachedStriderBuster( pTarget ) )
  246. {
  247. return true;
  248. }
  249. }
  250. }
  251. return false;
  252. }
  253. //-----------------------------------------------------------------------------
  254. // The hunter can fire a volley of explosive flechettes.
  255. //-----------------------------------------------------------------------------
  256. static const char *s_szHunterFlechetteBubbles = "HunterFlechetteBubbles";
  257. static const char *s_szHunterFlechetteSeekThink = "HunterFlechetteSeekThink";
  258. static const char *s_szHunterFlechetteDangerSoundThink = "HunterFlechetteDangerSoundThink";
  259. static const char *s_szHunterFlechetteSpriteTrail = "sprites/bluelaser1.vmt";
  260. static int s_nHunterFlechetteImpact = -2;
  261. static int s_nFlechetteFuseAttach = -1;
  262. #define FLECHETTE_AIR_VELOCITY 2500
  263. class CHunterFlechette : public CPhysicsProp, public IParentPropInteraction
  264. {
  265. DECLARE_CLASS( CHunterFlechette, CPhysicsProp );
  266. public:
  267. CHunterFlechette();
  268. ~CHunterFlechette();
  269. Class_T Classify() { return CLASS_NONE; }
  270. bool WasThrownBack()
  271. {
  272. return m_bThrownBack;
  273. }
  274. public:
  275. void Spawn();
  276. void Activate();
  277. void Precache();
  278. void Shoot( Vector &vecVelocity, bool bBright );
  279. void SetSeekTarget( CBaseEntity *pTargetEntity );
  280. void Explode();
  281. bool CreateVPhysics();
  282. unsigned int PhysicsSolidMaskForEntity() const;
  283. static CHunterFlechette *FlechetteCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner = NULL );
  284. // IParentPropInteraction
  285. void OnParentCollisionInteraction( parentCollisionInteraction_t eType, int index, gamevcollisionevent_t *pEvent );
  286. void OnParentPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason );
  287. protected:
  288. void SetupGlobalModelData();
  289. void StickTo( CBaseEntity *pOther, trace_t &tr );
  290. void BubbleThink();
  291. void DangerSoundThink();
  292. void ExplodeThink();
  293. void DopplerThink();
  294. void SeekThink();
  295. bool CreateSprites( bool bBright );
  296. void FlechetteTouch( CBaseEntity *pOther );
  297. Vector m_vecShootPosition;
  298. EHANDLE m_hSeekTarget;
  299. bool m_bThrownBack;
  300. DECLARE_DATADESC();
  301. //DECLARE_SERVERCLASS();
  302. };
  303. LINK_ENTITY_TO_CLASS( hunter_flechette, CHunterFlechette );
  304. BEGIN_DATADESC( CHunterFlechette )
  305. DEFINE_THINKFUNC( BubbleThink ),
  306. DEFINE_THINKFUNC( DangerSoundThink ),
  307. DEFINE_THINKFUNC( ExplodeThink ),
  308. DEFINE_THINKFUNC( DopplerThink ),
  309. DEFINE_THINKFUNC( SeekThink ),
  310. DEFINE_ENTITYFUNC( FlechetteTouch ),
  311. DEFINE_FIELD( m_vecShootPosition, FIELD_POSITION_VECTOR ),
  312. DEFINE_FIELD( m_hSeekTarget, FIELD_EHANDLE ),
  313. DEFINE_FIELD( m_bThrownBack, FIELD_BOOLEAN ),
  314. END_DATADESC()
  315. //IMPLEMENT_SERVERCLASS_ST( CHunterFlechette, DT_HunterFlechette )
  316. //END_SEND_TABLE()
  317. //-----------------------------------------------------------------------------
  318. //-----------------------------------------------------------------------------
  319. CHunterFlechette *CHunterFlechette::FlechetteCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseEntity *pentOwner )
  320. {
  321. // Create a new entity with CHunterFlechette private data
  322. CHunterFlechette *pFlechette = (CHunterFlechette *)CreateEntityByName( "hunter_flechette" );
  323. UTIL_SetOrigin( pFlechette, vecOrigin );
  324. pFlechette->SetAbsAngles( angAngles );
  325. pFlechette->Spawn();
  326. pFlechette->Activate();
  327. pFlechette->SetOwnerEntity( pentOwner );
  328. return pFlechette;
  329. }
  330. //------------------------------------------------------------------------------
  331. //------------------------------------------------------------------------------
  332. void CC_Hunter_Shoot_Flechette( const CCommand& args )
  333. {
  334. MDLCACHE_CRITICAL_SECTION();
  335. bool allowPrecache = CBaseEntity::IsPrecacheAllowed();
  336. CBaseEntity::SetAllowPrecache( true );
  337. CBasePlayer *pPlayer = UTIL_GetCommandClient();
  338. QAngle angEye = pPlayer->EyeAngles();
  339. CHunterFlechette *entity = CHunterFlechette::FlechetteCreate( pPlayer->EyePosition(), angEye, pPlayer );
  340. if ( entity )
  341. {
  342. entity->Precache();
  343. DispatchSpawn( entity );
  344. // Shoot the flechette.
  345. Vector forward;
  346. pPlayer->EyeVectors( &forward );
  347. forward *= 2000.0f;
  348. entity->Shoot( forward, false );
  349. }
  350. CBaseEntity::SetAllowPrecache( allowPrecache );
  351. }
  352. static ConCommand ent_create("hunter_shoot_flechette", CC_Hunter_Shoot_Flechette, "Fires a hunter flechette where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT);
  353. //-----------------------------------------------------------------------------
  354. //-----------------------------------------------------------------------------
  355. CHunterFlechette::CHunterFlechette()
  356. {
  357. UseClientSideAnimation();
  358. }
  359. //-----------------------------------------------------------------------------
  360. //-----------------------------------------------------------------------------
  361. CHunterFlechette::~CHunterFlechette()
  362. {
  363. }
  364. //-----------------------------------------------------------------------------
  365. // If set, the flechette will seek unerringly toward the target as it flies.
  366. //-----------------------------------------------------------------------------
  367. void CHunterFlechette::SetSeekTarget( CBaseEntity *pTargetEntity )
  368. {
  369. if ( pTargetEntity )
  370. {
  371. m_hSeekTarget = pTargetEntity;
  372. SetContextThink( &CHunterFlechette::SeekThink, gpGlobals->curtime, s_szHunterFlechetteSeekThink );
  373. }
  374. }
  375. //-----------------------------------------------------------------------------
  376. //-----------------------------------------------------------------------------
  377. bool CHunterFlechette::CreateVPhysics()
  378. {
  379. // Create the object in the physics system
  380. VPhysicsInitNormal( SOLID_BBOX, FSOLID_NOT_STANDABLE, false );
  381. return true;
  382. }
  383. //-----------------------------------------------------------------------------
  384. //-----------------------------------------------------------------------------
  385. unsigned int CHunterFlechette::PhysicsSolidMaskForEntity() const
  386. {
  387. return ( BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX ) & ~CONTENTS_GRATE;
  388. }
  389. //-----------------------------------------------------------------------------
  390. // Called from CPropPhysics code when we're attached to a physics object.
  391. //-----------------------------------------------------------------------------
  392. void CHunterFlechette::OnParentCollisionInteraction( parentCollisionInteraction_t eType, int index, gamevcollisionevent_t *pEvent )
  393. {
  394. if ( eType == COLLISIONINTER_PARENT_FIRST_IMPACT )
  395. {
  396. m_bThrownBack = true;
  397. Explode();
  398. }
  399. }
  400. void CHunterFlechette::OnParentPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
  401. {
  402. m_bThrownBack = true;
  403. }
  404. //-----------------------------------------------------------------------------
  405. //-----------------------------------------------------------------------------
  406. bool CHunterFlechette::CreateSprites( bool bBright )
  407. {
  408. if ( bBright )
  409. {
  410. DispatchParticleEffect( "hunter_flechette_trail_striderbuster", PATTACH_ABSORIGIN_FOLLOW, this );
  411. }
  412. else
  413. {
  414. DispatchParticleEffect( "hunter_flechette_trail", PATTACH_ABSORIGIN_FOLLOW, this );
  415. }
  416. return true;
  417. }
  418. //-----------------------------------------------------------------------------
  419. //-----------------------------------------------------------------------------
  420. void CHunterFlechette::Spawn()
  421. {
  422. Precache( );
  423. SetModel( HUNTER_FLECHETTE_MODEL );
  424. SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
  425. UTIL_SetSize( this, -Vector(1,1,1), Vector(1,1,1) );
  426. SetSolid( SOLID_BBOX );
  427. SetGravity( 0.05f );
  428. SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
  429. // Make sure we're updated if we're underwater
  430. UpdateWaterState();
  431. SetTouch( &CHunterFlechette::FlechetteTouch );
  432. // Make us glow until we've hit the wall
  433. m_nSkin = 1;
  434. }
  435. //-----------------------------------------------------------------------------
  436. //-----------------------------------------------------------------------------
  437. void CHunterFlechette::Activate()
  438. {
  439. BaseClass::Activate();
  440. SetupGlobalModelData();
  441. }
  442. //-----------------------------------------------------------------------------
  443. //-----------------------------------------------------------------------------
  444. void CHunterFlechette::SetupGlobalModelData()
  445. {
  446. if ( s_nHunterFlechetteImpact == -2 )
  447. {
  448. s_nHunterFlechetteImpact = LookupSequence( "impact" );
  449. s_nFlechetteFuseAttach = LookupAttachment( "attach_fuse" );
  450. }
  451. }
  452. //-----------------------------------------------------------------------------
  453. //-----------------------------------------------------------------------------
  454. void CHunterFlechette::Precache()
  455. {
  456. PrecacheModel( HUNTER_FLECHETTE_MODEL );
  457. PrecacheModel( "sprites/light_glow02_noz.vmt" );
  458. PrecacheScriptSound( "NPC_Hunter.FlechetteNearmiss" );
  459. PrecacheScriptSound( "NPC_Hunter.FlechetteHitBody" );
  460. PrecacheScriptSound( "NPC_Hunter.FlechetteHitWorld" );
  461. PrecacheScriptSound( "NPC_Hunter.FlechettePreExplode" );
  462. PrecacheScriptSound( "NPC_Hunter.FlechetteExplode" );
  463. PrecacheParticleSystem( "hunter_flechette_trail_striderbuster" );
  464. PrecacheParticleSystem( "hunter_flechette_trail" );
  465. PrecacheParticleSystem( "hunter_projectile_explosion_1" );
  466. }
  467. //-----------------------------------------------------------------------------
  468. //-----------------------------------------------------------------------------
  469. void CHunterFlechette::StickTo( CBaseEntity *pOther, trace_t &tr )
  470. {
  471. EmitSound( "NPC_Hunter.FlechetteHitWorld" );
  472. SetMoveType( MOVETYPE_NONE );
  473. if ( !pOther->IsWorld() )
  474. {
  475. SetParent( pOther );
  476. SetSolid( SOLID_NONE );
  477. SetSolidFlags( FSOLID_NOT_SOLID );
  478. }
  479. // Do an impact effect.
  480. //Vector vecDir = GetAbsVelocity();
  481. //float speed = VectorNormalize( vecDir );
  482. //Vector vForward;
  483. //AngleVectors( GetAbsAngles(), &vForward );
  484. //VectorNormalize ( vForward );
  485. //CEffectData data;
  486. //data.m_vOrigin = tr.endpos;
  487. //data.m_vNormal = vForward;
  488. //data.m_nEntIndex = 0;
  489. //DispatchEffect( "BoltImpact", data );
  490. Vector vecVelocity = GetAbsVelocity();
  491. bool bAttachedToBuster = StriderBuster_OnFlechetteAttach( pOther, vecVelocity );
  492. SetTouch( NULL );
  493. // We're no longer flying. Stop checking for water volumes.
  494. SetContextThink( NULL, 0, s_szHunterFlechetteBubbles );
  495. // Stop seeking.
  496. m_hSeekTarget = NULL;
  497. SetContextThink( NULL, 0, s_szHunterFlechetteSeekThink );
  498. // Get ready to explode.
  499. if ( !bAttachedToBuster )
  500. {
  501. SetThink( &CHunterFlechette::DangerSoundThink );
  502. SetNextThink( gpGlobals->curtime + (hunter_flechette_explode_delay.GetFloat() - HUNTER_FLECHETTE_WARN_TIME) );
  503. }
  504. else
  505. {
  506. DangerSoundThink();
  507. }
  508. // Play our impact animation.
  509. ResetSequence( s_nHunterFlechetteImpact );
  510. static int s_nImpactCount = 0;
  511. s_nImpactCount++;
  512. if ( s_nImpactCount & 0x01 )
  513. {
  514. UTIL_ImpactTrace( &tr, DMG_BULLET );
  515. // Shoot some sparks
  516. if ( UTIL_PointContents( GetAbsOrigin() ) != CONTENTS_WATER)
  517. {
  518. g_pEffects->Sparks( GetAbsOrigin() );
  519. }
  520. }
  521. }
  522. //-----------------------------------------------------------------------------
  523. //-----------------------------------------------------------------------------
  524. void CHunterFlechette::FlechetteTouch( CBaseEntity *pOther )
  525. {
  526. if ( pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS | FSOLID_TRIGGER) )
  527. {
  528. // Some NPCs are triggers that can take damage (like antlion grubs). We should hit them.
  529. if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) )
  530. return;
  531. }
  532. if ( FClassnameIs( pOther, "hunter_flechette" ) )
  533. return;
  534. trace_t tr;
  535. tr = BaseClass::GetTouchTrace();
  536. if ( pOther->m_takedamage != DAMAGE_NO )
  537. {
  538. Vector vecNormalizedVel = GetAbsVelocity();
  539. ClearMultiDamage();
  540. VectorNormalize( vecNormalizedVel );
  541. float flDamage = sk_hunter_dmg_flechette.GetFloat();
  542. CBreakable *pBreak = dynamic_cast <CBreakable *>(pOther);
  543. if ( pBreak && ( pBreak->GetMaterialType() == matGlass ) )
  544. {
  545. flDamage = MAX( pOther->GetHealth(), flDamage );
  546. }
  547. CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), flDamage, DMG_DISSOLVE | DMG_NEVERGIB );
  548. CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f );
  549. dmgInfo.SetDamagePosition( tr.endpos );
  550. pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr );
  551. ApplyMultiDamage();
  552. // Keep going through breakable glass.
  553. if ( pOther->GetCollisionGroup() == COLLISION_GROUP_BREAKABLE_GLASS )
  554. return;
  555. SetAbsVelocity( Vector( 0, 0, 0 ) );
  556. // play body "thwack" sound
  557. EmitSound( "NPC_Hunter.FlechetteHitBody" );
  558. StopParticleEffects( this );
  559. Vector vForward;
  560. AngleVectors( GetAbsAngles(), &vForward );
  561. VectorNormalize ( vForward );
  562. trace_t tr2;
  563. UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vForward * 128, MASK_BLOCKLOS, pOther, COLLISION_GROUP_NONE, &tr2 );
  564. if ( tr2.fraction != 1.0f )
  565. {
  566. //NDebugOverlay::Box( tr2.endpos, Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 255, 0, 0, 10 );
  567. //NDebugOverlay::Box( GetAbsOrigin(), Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 0, 255, 0, 10 );
  568. if ( tr2.m_pEnt == NULL || ( tr2.m_pEnt && tr2.m_pEnt->GetMoveType() == MOVETYPE_NONE ) )
  569. {
  570. CEffectData data;
  571. data.m_vOrigin = tr2.endpos;
  572. data.m_vNormal = vForward;
  573. data.m_nEntIndex = tr2.fraction != 1.0f;
  574. //DispatchEffect( "BoltImpact", data );
  575. }
  576. }
  577. if ( ( ( pOther->GetMoveType() == MOVETYPE_VPHYSICS ) || ( pOther->GetMoveType() == MOVETYPE_PUSH ) ) && ( ( pOther->GetHealth() > 0 ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) ) )
  578. {
  579. CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>( pOther );
  580. if ( pProp )
  581. {
  582. pProp->SetInteraction( PROPINTER_PHYSGUN_NOTIFY_CHILDREN );
  583. }
  584. // We hit a physics object that survived the impact. Stick to it.
  585. StickTo( pOther, tr );
  586. }
  587. else
  588. {
  589. SetTouch( NULL );
  590. SetThink( NULL );
  591. SetContextThink( NULL, 0, s_szHunterFlechetteBubbles );
  592. UTIL_Remove( this );
  593. }
  594. }
  595. else
  596. {
  597. // See if we struck the world
  598. if ( pOther->GetMoveType() == MOVETYPE_NONE && !( tr.surface.flags & SURF_SKY ) )
  599. {
  600. // We hit a physics object that survived the impact. Stick to it.
  601. StickTo( pOther, tr );
  602. }
  603. else if( pOther->GetMoveType() == MOVETYPE_PUSH && FClassnameIs(pOther, "func_breakable") )
  604. {
  605. // We hit a func_breakable, stick to it.
  606. // The MOVETYPE_PUSH is a micro-optimization to cut down on the classname checks.
  607. StickTo( pOther, tr );
  608. }
  609. else
  610. {
  611. // Put a mark unless we've hit the sky
  612. if ( ( tr.surface.flags & SURF_SKY ) == false )
  613. {
  614. UTIL_ImpactTrace( &tr, DMG_BULLET );
  615. }
  616. UTIL_Remove( this );
  617. }
  618. }
  619. }
  620. //-----------------------------------------------------------------------------
  621. // Fixup flechette position when seeking towards a striderbuster.
  622. //-----------------------------------------------------------------------------
  623. void CHunterFlechette::SeekThink()
  624. {
  625. if ( m_hSeekTarget )
  626. {
  627. Vector vecBodyTarget = m_hSeekTarget->BodyTarget( GetAbsOrigin() );
  628. Vector vecClosest;
  629. CalcClosestPointOnLineSegment( GetAbsOrigin(), m_vecShootPosition, vecBodyTarget, vecClosest, NULL );
  630. Vector vecDelta = vecBodyTarget - m_vecShootPosition;
  631. VectorNormalize( vecDelta );
  632. QAngle angShoot;
  633. VectorAngles( vecDelta, angShoot );
  634. float flSpeed = hunter_flechette_speed.GetFloat();
  635. if ( !flSpeed )
  636. {
  637. flSpeed = 2500.0f;
  638. }
  639. Vector vecVelocity = vecDelta * flSpeed;
  640. Teleport( &vecClosest, &angShoot, &vecVelocity );
  641. SetNextThink( gpGlobals->curtime, s_szHunterFlechetteSeekThink );
  642. }
  643. }
  644. //-----------------------------------------------------------------------------
  645. // Play a near miss sound as we travel past the player.
  646. //-----------------------------------------------------------------------------
  647. void CHunterFlechette::DopplerThink()
  648. {
  649. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  650. if ( !pPlayer )
  651. return;
  652. Vector vecVelocity = GetAbsVelocity();
  653. VectorNormalize( vecVelocity );
  654. float flMyDot = DotProduct( vecVelocity, GetAbsOrigin() );
  655. float flPlayerDot = DotProduct( vecVelocity, pPlayer->GetAbsOrigin() );
  656. if ( flPlayerDot <= flMyDot )
  657. {
  658. EmitSound( "NPC_Hunter.FlechetteNearMiss" );
  659. // We've played the near miss sound and we're not seeking. Stop thinking.
  660. SetThink( NULL );
  661. }
  662. else
  663. {
  664. SetNextThink( gpGlobals->curtime );
  665. }
  666. }
  667. //-----------------------------------------------------------------------------
  668. // Think every 0.1 seconds to make bubbles if we're flying through water.
  669. //-----------------------------------------------------------------------------
  670. void CHunterFlechette::BubbleThink()
  671. {
  672. SetNextThink( gpGlobals->curtime + 0.1f, s_szHunterFlechetteBubbles );
  673. if ( GetWaterLevel() == 0 )
  674. return;
  675. UTIL_BubbleTrail( GetAbsOrigin() - GetAbsVelocity() * 0.1f, GetAbsOrigin(), 5 );
  676. }
  677. //-----------------------------------------------------------------------------
  678. //-----------------------------------------------------------------------------
  679. void CHunterFlechette::Shoot( Vector &vecVelocity, bool bBrightFX )
  680. {
  681. CreateSprites( bBrightFX );
  682. m_vecShootPosition = GetAbsOrigin();
  683. SetAbsVelocity( vecVelocity );
  684. SetThink( &CHunterFlechette::DopplerThink );
  685. SetNextThink( gpGlobals->curtime );
  686. SetContextThink( &CHunterFlechette::BubbleThink, gpGlobals->curtime + 0.1, s_szHunterFlechetteBubbles );
  687. }
  688. //-----------------------------------------------------------------------------
  689. //-----------------------------------------------------------------------------
  690. void CHunterFlechette::DangerSoundThink()
  691. {
  692. EmitSound( "NPC_Hunter.FlechettePreExplode" );
  693. CSoundEnt::InsertSound( SOUND_DANGER|SOUND_CONTEXT_EXCLUDE_COMBINE, GetAbsOrigin(), 150.0f, 0.5, this );
  694. SetThink( &CHunterFlechette::ExplodeThink );
  695. SetNextThink( gpGlobals->curtime + HUNTER_FLECHETTE_WARN_TIME );
  696. }
  697. //-----------------------------------------------------------------------------
  698. //-----------------------------------------------------------------------------
  699. void CHunterFlechette::ExplodeThink()
  700. {
  701. Explode();
  702. }
  703. //-----------------------------------------------------------------------------
  704. //-----------------------------------------------------------------------------
  705. void CHunterFlechette::Explode()
  706. {
  707. SetSolid( SOLID_NONE );
  708. // Don't catch self in own explosion!
  709. m_takedamage = DAMAGE_NO;
  710. EmitSound( "NPC_Hunter.FlechetteExplode" );
  711. // Move the explosion effect to the tip to reduce intersection with the world.
  712. Vector vecFuse;
  713. GetAttachment( s_nFlechetteFuseAttach, vecFuse );
  714. DispatchParticleEffect( "hunter_projectile_explosion_1", vecFuse, GetAbsAngles(), NULL );
  715. int nDamageType = DMG_DISSOLVE;
  716. // Perf optimization - only every other explosion makes a physics force. This is
  717. // hardly noticeable since flechettes usually explode in clumps.
  718. static int s_nExplosionCount = 0;
  719. s_nExplosionCount++;
  720. if ( ( s_nExplosionCount & 0x01 ) && hunter_cheap_explosions.GetBool() )
  721. {
  722. nDamageType |= DMG_PREVENT_PHYSICS_FORCE;
  723. }
  724. RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), sk_hunter_flechette_explode_dmg.GetFloat(), nDamageType ), GetAbsOrigin(), sk_hunter_flechette_explode_radius.GetFloat(), CLASS_NONE, NULL );
  725. AddEffects( EF_NODRAW );
  726. SetThink( &CBaseEntity::SUB_Remove );
  727. SetNextThink( gpGlobals->curtime + 0.1f );
  728. }
  729. //-----------------------------------------------------------------------------
  730. // Calculate & apply damage & force for a charge to a target.
  731. // Done outside of the hunter because we need to do this inside a trace filter.
  732. //-----------------------------------------------------------------------------
  733. void Hunter_ApplyChargeDamage( CBaseEntity *pHunter, CBaseEntity *pTarget, float flDamage )
  734. {
  735. Vector attackDir = ( pTarget->WorldSpaceCenter() - pHunter->WorldSpaceCenter() );
  736. VectorNormalize( attackDir );
  737. Vector offset = RandomVector( -32, 32 ) + pTarget->WorldSpaceCenter();
  738. // Generate enough force to make a 75kg guy move away at 700 in/sec
  739. Vector vecForce = attackDir * ImpulseScale( 75, 700 );
  740. // Deal the damage
  741. CTakeDamageInfo info( pHunter, pHunter, vecForce, offset, flDamage, DMG_CLUB );
  742. pTarget->TakeDamage( info );
  743. }
  744. //-----------------------------------------------------------------------------
  745. // A simple trace filter class to skip small moveable physics objects
  746. //-----------------------------------------------------------------------------
  747. class CHunterTraceFilterSkipPhysics : public CTraceFilter
  748. {
  749. public:
  750. // It does have a base, but we'll never network anything below here..
  751. DECLARE_CLASS_NOBASE( CHunterTraceFilterSkipPhysics );
  752. CHunterTraceFilterSkipPhysics( const IHandleEntity *passentity, int collisionGroup, float minMass )
  753. : m_pPassEnt(passentity), m_collisionGroup(collisionGroup), m_minMass(minMass)
  754. {
  755. }
  756. virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
  757. {
  758. if ( !StandardFilterRules( pHandleEntity, contentsMask ) )
  759. return false;
  760. if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) )
  761. return false;
  762. // Don't test if the game code tells us we should ignore this collision...
  763. CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
  764. if ( pEntity )
  765. {
  766. if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) )
  767. return false;
  768. if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) )
  769. return false;
  770. // don't test small moveable physics objects (unless it's an NPC)
  771. if ( !pEntity->IsNPC() && pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
  772. {
  773. float entMass = PhysGetEntityMass( pEntity ) ;
  774. if ( entMass < m_minMass )
  775. {
  776. if ( entMass < m_minMass * 0.666f || pEntity->CollisionProp()->BoundingRadius() < (assert_cast<const CAI_BaseNPC *>(EntityFromEntityHandle( m_pPassEnt )))->GetHullHeight() )
  777. {
  778. return false;
  779. }
  780. }
  781. }
  782. // If we hit an antlion, don't stop, but kill it
  783. if ( pEntity->Classify() == CLASS_ANTLION )
  784. {
  785. CBaseEntity *pHunter = (CBaseEntity *)EntityFromEntityHandle( m_pPassEnt );
  786. Hunter_ApplyChargeDamage( pHunter, pEntity, pEntity->GetHealth() );
  787. return false;
  788. }
  789. }
  790. return true;
  791. }
  792. private:
  793. const IHandleEntity *m_pPassEnt;
  794. int m_collisionGroup;
  795. float m_minMass;
  796. };
  797. //-----------------------------------------------------------------------------
  798. //-----------------------------------------------------------------------------
  799. inline void HunterTraceHull_SkipPhysics( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin,
  800. const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore,
  801. int collisionGroup, trace_t *ptr, float minMass )
  802. {
  803. Ray_t ray;
  804. ray.Init( vecAbsStart, vecAbsEnd, hullMin, hullMax );
  805. CHunterTraceFilterSkipPhysics traceFilter( ignore, collisionGroup, minMass );
  806. enginetrace->TraceRay( ray, mask, &traceFilter, ptr );
  807. }
  808. //-----------------------------------------------------------------------------
  809. // Hunter follow behavior
  810. //-----------------------------------------------------------------------------
  811. class CAI_HunterEscortBehavior : public CAI_FollowBehavior
  812. {
  813. public:
  814. DECLARE_CLASS( CAI_HunterEscortBehavior, CAI_FollowBehavior );
  815. CAI_HunterEscortBehavior() :
  816. BaseClass( AI_FollowParams_t( AIF_HUNTER, true ) ),
  817. m_flTimeEscortReturn( 0 ),
  818. m_bEnabled( false )
  819. {
  820. }
  821. CNPC_Hunter *GetOuter() { return (CNPC_Hunter *)( BaseClass::GetOuter() ); }
  822. void SetEscortTarget( CNPC_Strider *pLeader, bool fFinishCurSchedule = false );
  823. CNPC_Strider * GetEscortTarget() { return (CNPC_Strider *)GetFollowTarget(); }
  824. bool FarFromFollowTarget()
  825. {
  826. return ( GetFollowTarget() && (GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin()).LengthSqr() > HUNTER_FOLLOW_DISTANCE_SQR );
  827. }
  828. void DrawDebugGeometryOverlays();
  829. bool ShouldFollow();
  830. void BuildScheduleTestBits();
  831. void BeginScheduleSelection();
  832. void GatherConditions();
  833. void GatherConditionsNotActive();
  834. int SelectSchedule();
  835. int FollowCallBaseSelectSchedule();
  836. void StartTask( const Task_t *pTask );
  837. void RunTask( const Task_t *pTask );
  838. void CheckBreakEscort();
  839. void OnDamage( const CTakeDamageInfo &info );
  840. static void DistributeFreeHunters();
  841. static void FindFreeHunters( CUtlVector<CNPC_Hunter *> *pFreeHunters );
  842. float m_flTimeEscortReturn;
  843. CSimpleSimTimer m_FollowAttackTimer;
  844. bool m_bEnabled;
  845. static float gm_flLastDefendSound; // not saved and loaded, it's okay to yell again after a load
  846. //---------------------------------
  847. DECLARE_DATADESC();
  848. };
  849. BEGIN_DATADESC( CAI_HunterEscortBehavior )
  850. DEFINE_FIELD( m_flTimeEscortReturn, FIELD_TIME ),
  851. DEFINE_EMBEDDED( m_FollowAttackTimer ),
  852. DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
  853. END_DATADESC();
  854. float CAI_HunterEscortBehavior::gm_flLastDefendSound;
  855. //-----------------------------------------------------------------------------
  856. // Hunter PHYSICS DAMAGE TABLE
  857. //-----------------------------------------------------------------------------
  858. #define HUNTER_MIN_PHYSICS_DAMAGE 10
  859. static impactentry_t s_HunterLinearTable[] =
  860. {
  861. { 150*150, 75 },
  862. { 350*350, 105 },
  863. { 1000*1000, 300 },
  864. };
  865. static impactentry_t s_HunterAngularTable[] =
  866. {
  867. { 100*100, 75 },
  868. { 200*200, 105 },
  869. { 300*300, 300 },
  870. };
  871. impactdamagetable_t s_HunterImpactDamageTable =
  872. {
  873. s_HunterLinearTable,
  874. s_HunterAngularTable,
  875. ARRAYSIZE(s_HunterLinearTable),
  876. ARRAYSIZE(s_HunterAngularTable),
  877. 24*24, // minimum linear speed squared
  878. 360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage)
  879. 5, // can't take damage from anything under 5kg
  880. 10, // anything less than 10kg is "small"
  881. HUNTER_MIN_PHYSICS_DAMAGE, // never take more than 10 pts of damage from anything under 10kg
  882. 36*36, // <10kg objects must go faster than 36 in/s to do damage
  883. VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg
  884. 4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
  885. 5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway)
  886. 0.0f, // min vel
  887. };
  888. //-----------------------------------------------------------------------------
  889. //-----------------------------------------------------------------------------
  890. class CNPC_Hunter : public CAI_BaseActor
  891. {
  892. DECLARE_CLASS( CNPC_Hunter, CAI_BaseActor );
  893. public:
  894. CNPC_Hunter();
  895. ~CNPC_Hunter();
  896. //---------------------------------
  897. void Precache();
  898. void Spawn();
  899. void PostNPCInit();
  900. void Activate();
  901. void UpdateOnRemove();
  902. void OnRestore();
  903. bool CreateBehaviors();
  904. void IdleSound();
  905. bool ShouldPlayIdleSound();
  906. bool CanBecomeRagdoll();
  907. Activity GetDeathActivity();
  908. void StopLoopingSounds();
  909. const impactdamagetable_t &GetPhysicsImpactDamageTable();
  910. Class_T Classify();
  911. Vector BodyTarget( const Vector &posSrc, bool bNoisy /*= true*/ );
  912. int DrawDebugTextOverlays();
  913. void DrawDebugGeometryOverlays();
  914. void UpdateEfficiency( bool bInPVS );
  915. //---------------------------------
  916. virtual Vector GetNodeViewOffset() { return BaseClass::GetDefaultEyeOffset(); }
  917. int GetSoundInterests();
  918. bool IsInLargeOutdoorMap();
  919. //---------------------------------
  920. // CAI_BaseActor
  921. //---------------------------------
  922. const char *SelectRandomExpressionForState( NPC_STATE state );
  923. void PlayExpressionForState( NPC_STATE state );
  924. //---------------------------------
  925. // CBaseAnimating
  926. //---------------------------------
  927. float GetIdealAccel() const { return GetIdealSpeed(); }
  928. //---------------------------------
  929. // Behavior
  930. //---------------------------------
  931. void NPCThink();
  932. void PrescheduleThink();
  933. void GatherConditions();
  934. void CollectSiegeTargets();
  935. void ManageSiegeTargets();
  936. void KillCurrentSiegeTarget();
  937. bool QueryHearSound( CSound *pSound );
  938. void OnSeeEntity( CBaseEntity *pEntity );
  939. void CheckFlinches() {} // Hunter handles on own
  940. void BuildScheduleTestBits();
  941. NPC_STATE SelectIdealState();
  942. int SelectSchedule();
  943. int SelectCombatSchedule();
  944. int SelectSiegeSchedule();
  945. int TranslateSchedule( int scheduleType );
  946. void StartTask( const Task_t *pTask );
  947. void RunTask( const Task_t *pTask );
  948. Activity NPC_TranslateActivity( Activity baseAct );
  949. void OnChangeActivity( Activity eNewActivity );
  950. void HandleAnimEvent( animevent_t *pEvent );
  951. bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *pSourceEnt);
  952. void PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot );
  953. void AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority );
  954. float EnemyDistTolerance() { return 100.0f; }
  955. bool ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity );
  956. void OnChangeHintGroup( string_t oldGroup, string_t newGroup );
  957. bool IsUsingSiegeTargets() { return m_iszSiegeTargetName != NULL_STRING; }
  958. //---------------------------------
  959. // Inputs
  960. //---------------------------------
  961. void InputDodge( inputdata_t &inputdata );
  962. void InputFlankEnemy( inputdata_t &inputdata );
  963. void InputDisableShooting( inputdata_t &inputdata );
  964. void InputEnableShooting( inputdata_t &inputdata );
  965. void InputFollowStrider( inputdata_t &inputdata );
  966. void InputUseSiegeTargets( inputdata_t &inputdata );
  967. void InputEnableSquadShootDelay( inputdata_t &inputdata );
  968. void InputDisableSquadShootDelay( inputdata_t &inputdata );
  969. void InputEnableUnplantedShooting( inputdata_t &inputdata );
  970. void InputDisableUnplantedShooting( inputdata_t &inputdata );
  971. //---------------------------------
  972. // Combat
  973. //---------------------------------
  974. bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
  975. bool IsValidEnemy( CBaseEntity *pEnemy );
  976. Disposition_t IRelationType( CBaseEntity *pTarget );
  977. int IRelationPriority( CBaseEntity *pTarget );
  978. void SetSquad( CAI_Squad *pSquad );
  979. bool UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer = NULL );
  980. int RangeAttack1Conditions( float flDot, float flDist );
  981. int RangeAttack2Conditions( float flDot, float flDist );
  982. int MeleeAttack1Conditions ( float flDot, float flDist );
  983. int MeleeAttack1ConditionsVsEnemyInVehicle( CBaseCombatCharacter *pEnemy, float flDot );
  984. int MeleeAttack2Conditions( float flDot, float flDist );
  985. bool WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions);
  986. bool TestShootPosition(const Vector &vecShootPos, const Vector &targetPos );
  987. Vector Weapon_ShootPosition();
  988. CBaseEntity * MeleeAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin );
  989. void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
  990. void DoMuzzleFlash( int nAttachment );
  991. bool CanShootThrough( const trace_t &tr, const Vector &vecTarget );
  992. int CountRangedAttackers();
  993. void DelayRangedAttackers( float minDelay, float maxDelay, bool bForced = false );
  994. //---------------------------------
  995. // Sounds & speech
  996. //---------------------------------
  997. void AlertSound();
  998. void PainSound( const CTakeDamageInfo &info );
  999. void DeathSound( const CTakeDamageInfo &info );
  1000. //---------------------------------
  1001. // Damage handling
  1002. //---------------------------------
  1003. void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
  1004. bool IsHeavyDamage( const CTakeDamageInfo &info );
  1005. int OnTakeDamage( const CTakeDamageInfo &info );
  1006. int OnTakeDamage_Alive( const CTakeDamageInfo &info );
  1007. void Event_Killed( const CTakeDamageInfo &info );
  1008. void StartBleeding();
  1009. inline bool IsBleeding() { return m_bIsBleeding; }
  1010. void Explode();
  1011. void SetupGlobalModelData();
  1012. //---------------------------------
  1013. // Navigation & Movement
  1014. //---------------------------------
  1015. bool OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval );
  1016. float MaxYawSpeed();
  1017. bool IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const;
  1018. float GetJumpGravity() const { return 3.0f; }
  1019. bool ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity );
  1020. void TaskFail( AI_TaskFailureCode_t code );
  1021. void TaskFail( const char *pszGeneralFailText ) { TaskFail( MakeFailCode( pszGeneralFailText ) ); }
  1022. CAI_BaseNPC * GetEntity() { return this; }
  1023. //---------------------------------
  1024. // Magnade
  1025. //---------------------------------
  1026. void StriderBusterAttached( CBaseEntity *pAttached );
  1027. void StriderBusterDetached( CBaseEntity *pAttached );
  1028. private:
  1029. void ConsiderFlinching( const CTakeDamageInfo &info );
  1030. void TaskFindDodgeActivity();
  1031. void GatherChargeConditions();
  1032. void GatherIndoorOutdoorConditions();
  1033. // Charge attack.
  1034. bool ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel );
  1035. void ChargeLookAhead();
  1036. float ChargeSteer();
  1037. bool EnemyIsRightInFrontOfMe( CBaseEntity **pEntity );
  1038. void ChargeDamage( CBaseEntity *pTarget );
  1039. bool HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity );
  1040. void BeginVolley( int nNum, float flStartTime );
  1041. bool ShootFlechette( CBaseEntity *pTargetEntity, bool bSingleShot );
  1042. bool ShouldSeekTarget( CBaseEntity *pTargetEntity, bool bStriderBuster );
  1043. void GetShootDir( Vector &vecDir, const Vector &vecSrc, CBaseEntity *pTargetEntity, bool bStriderbuster, int nShotNum, bool bSingleShot );
  1044. bool ClampShootDir( Vector &vecDir );
  1045. void SetAim( const Vector &aimDir, float flInterval );
  1046. void RelaxAim( float flInterval );
  1047. void UpdateAim();
  1048. void UpdateEyes();
  1049. void LockBothEyes( float flDuration );
  1050. void UnlockBothEyes( float flDuration );
  1051. void TeslaThink();
  1052. void BleedThink();
  1053. void JostleVehicleThink();
  1054. void FollowStrider( const char *szStrider );
  1055. void FollowStrider( CNPC_Strider * pStrider );
  1056. int NumHuntersInMySquad();
  1057. bool CanPlantHere( const Vector &vecPos );
  1058. //---------------------------------
  1059. // Foot handling
  1060. //---------------------------------
  1061. Vector LeftFootHit( float eventtime );
  1062. Vector RightFootHit( float eventtime );
  1063. Vector BackFootHit( float eventtime );
  1064. void FootFX( const Vector &origin );
  1065. CBaseEntity *GetEnemyVehicle();
  1066. bool IsCorporealEnemy( CBaseEntity *pEnemy );
  1067. void PhysicsDamageEffect( const Vector &vecPos, const Vector &vecDir );
  1068. bool PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer );
  1069. //-----------------------------------------------------
  1070. // Conditions, Schedules, Tasks
  1071. //-----------------------------------------------------
  1072. enum
  1073. {
  1074. SCHED_HUNTER_RANGE_ATTACK1 = BaseClass::NEXT_SCHEDULE,
  1075. SCHED_HUNTER_RANGE_ATTACK2,
  1076. SCHED_HUNTER_MELEE_ATTACK1,
  1077. SCHED_HUNTER_DODGE,
  1078. SCHED_HUNTER_CHASE_ENEMY,
  1079. SCHED_HUNTER_CHASE_ENEMY_MELEE,
  1080. SCHED_HUNTER_COMBAT_FACE,
  1081. SCHED_HUNTER_FLANK_ENEMY,
  1082. SCHED_HUNTER_CHANGE_POSITION,
  1083. SCHED_HUNTER_CHANGE_POSITION_FINISH,
  1084. SCHED_HUNTER_SIDESTEP,
  1085. SCHED_HUNTER_PATROL,
  1086. SCHED_HUNTER_FLINCH_STICKYBOMB,
  1087. SCHED_HUNTER_STAGGER,
  1088. SCHED_HUNTER_PATROL_RUN,
  1089. SCHED_HUNTER_TAKE_COVER_FROM_ENEMY,
  1090. SCHED_HUNTER_HIDE_UNDER_COVER,
  1091. SCHED_HUNTER_FAIL_IMMEDIATE, // instant fail without waiting
  1092. SCHED_HUNTER_CHARGE_ENEMY,
  1093. SCHED_HUNTER_FAIL_CHARGE_ENEMY,
  1094. SCHED_HUNTER_FOUND_ENEMY,
  1095. SCHED_HUNTER_FOUND_ENEMY_ACK,
  1096. SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER,
  1097. SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER_LATENT,
  1098. SCHED_HUNTER_GOTO_HINT,
  1099. SCHED_HUNTER_CLEAR_HINTNODE,
  1100. SCHED_HUNTER_FAIL_DODGE,
  1101. SCHED_HUNTER_SIEGE_STAND,
  1102. SCHED_HUNTER_CHANGE_POSITION_SIEGE,
  1103. TASK_HUNTER_AIM = BaseClass::NEXT_TASK,
  1104. TASK_HUNTER_FIND_DODGE_POSITION,
  1105. TASK_HUNTER_DODGE,
  1106. TASK_HUNTER_PRE_RANGE_ATTACK2,
  1107. TASK_HUNTER_SHOOT_COMMIT,
  1108. TASK_HUNTER_BEGIN_FLANK,
  1109. TASK_HUNTER_ANNOUNCE_FLANK,
  1110. TASK_HUNTER_STAGGER,
  1111. TASK_HUNTER_CORNERED_TIMER,
  1112. TASK_HUNTER_FIND_SIDESTEP_POSITION,
  1113. TASK_HUNTER_CHARGE,
  1114. TASK_HUNTER_CHARGE_DELAY,
  1115. TASK_HUNTER_FINISH_RANGE_ATTACK,
  1116. TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY,
  1117. COND_HUNTER_SHOULD_PATROL = BaseClass::NEXT_CONDITION,
  1118. COND_HUNTER_FORCED_FLANK_ENEMY,
  1119. COND_HUNTER_FORCED_DODGE,
  1120. COND_HUNTER_CAN_CHARGE_ENEMY,
  1121. COND_HUNTER_HIT_BY_STICKYBOMB,
  1122. COND_HUNTER_STAGGERED,
  1123. COND_HUNTER_IS_INDOORS,
  1124. COND_HUNTER_SEE_STRIDERBUSTER,
  1125. COND_HUNTER_INCOMING_VEHICLE,
  1126. COND_HUNTER_NEW_HINTGROUP,
  1127. COND_HUNTER_CANT_PLANT,
  1128. COND_HUNTER_SQUADMATE_FOUND_ENEMY,
  1129. };
  1130. enum HunterEyeStates_t
  1131. {
  1132. HUNTER_EYE_STATE_TOP_LOCKED = 0,
  1133. HUNTER_EYE_STATE_BOTTOM_LOCKED,
  1134. HUNTER_EYE_STATE_BOTH_LOCKED,
  1135. HUNTER_EYE_STATE_BOTH_UNLOCKED,
  1136. };
  1137. string_t m_iszFollowTarget; // Name of the strider we should follow.
  1138. CSimpleStopwatch m_BeginFollowDelay;
  1139. int m_nKillingDamageType;
  1140. HunterEyeStates_t m_eEyeState;
  1141. float m_aimYaw;
  1142. float m_aimPitch;
  1143. float m_flShootAllowInterruptTime;
  1144. float m_flNextChargeTime; // Prevents us from doing our threat display too often.
  1145. float m_flNextDamageTime;
  1146. float m_flNextSideStepTime;
  1147. CSimpleSimTimer m_HeavyDamageDelay;
  1148. CSimpleSimTimer m_FlinchTimer;
  1149. CSimpleSimTimer m_EyeSwitchTimer; // Controls how often we switch which eye is focusing on our enemy.
  1150. bool m_bTopMuzzle; // Used to alternate between top muzzle FX and bottom muzzle FX.
  1151. bool m_bEnableSquadShootDelay;
  1152. bool m_bIsBleeding;
  1153. Activity m_eDodgeActivity;
  1154. CSimpleSimTimer m_RundownDelay;
  1155. CSimpleSimTimer m_IgnoreVehicleTimer;
  1156. bool m_bDisableShooting; // Range attack disabled via an input. Used for scripting melee attacks.
  1157. bool m_bFlashlightInEyes; // The player is shining the flashlight on our eyes.
  1158. float m_flPupilDilateTime; // When to dilate our pupils if the flashlight is no longer on our eyes.
  1159. Vector m_vecEnemyLastSeen;
  1160. Vector m_vecLastCanPlantHerePos;
  1161. Vector m_vecStaggerDir;
  1162. bool m_bPlanted;
  1163. bool m_bLastCanPlantHere;
  1164. bool m_bMissLeft;
  1165. bool m_bEnableUnplantedShooting;
  1166. static float gm_flMinigunDistZ;
  1167. static Vector gm_vecLocalRelativePositionMinigun;
  1168. static int gm_nTopGunAttachment;
  1169. static int gm_nBottomGunAttachment;
  1170. static int gm_nAimYawPoseParam;
  1171. static int gm_nAimPitchPoseParam;
  1172. static int gm_nBodyYawPoseParam;
  1173. static int gm_nBodyPitchPoseParam;
  1174. static int gm_nStaggerYawPoseParam;
  1175. static int gm_nHeadCenterAttachment;
  1176. static int gm_nHeadBottomAttachment;
  1177. static float gm_flHeadRadius;
  1178. static int gm_nUnplantedNode;
  1179. static int gm_nPlantedNode;
  1180. CAI_HunterEscortBehavior m_EscortBehavior;
  1181. int m_nFlechettesQueued;
  1182. int m_nClampedShots; // The number of consecutive shots fired at an out-of-max yaw target.
  1183. float m_flNextRangeAttack2Time; // Time when we can fire another volley of flechettes.
  1184. float m_flNextFlechetteTime; // Time to fire the next flechette in this volley.
  1185. float m_flNextMeleeTime;
  1186. float m_flTeslaStopTime;
  1187. string_t m_iszCurrentExpression;
  1188. // buster fu
  1189. CUtlVector< EHANDLE > m_hAttachedBusters; // List of busters attached to us
  1190. float m_fCorneredTimer; ///< hunter was cornered when fleeing player; it won't flee again until this time
  1191. CSimpleSimTimer m_CheckHintGroupTimer;
  1192. DEFINE_CUSTOM_AI;
  1193. DECLARE_DATADESC();
  1194. friend class CAI_HunterEscortBehavior;
  1195. friend class CHunterMaker;
  1196. bool m_bInLargeOutdoorMap;
  1197. float m_flTimeSawEnemyAgain;
  1198. // Sounds
  1199. //CSoundPatch *m_pGunFiringSound;
  1200. CUtlVector<EHANDLE> m_pSiegeTargets;
  1201. string_t m_iszSiegeTargetName;
  1202. float m_flTimeNextSiegeTargetAttack;
  1203. EHANDLE m_hCurrentSiegeTarget;
  1204. EHANDLE m_hHitByVehicle;
  1205. };
  1206. LINK_ENTITY_TO_CLASS( npc_hunter, CNPC_Hunter );
  1207. BEGIN_DATADESC( CNPC_Hunter )
  1208. DEFINE_KEYFIELD( m_iszFollowTarget, FIELD_STRING, "FollowTarget" ),
  1209. DEFINE_FIELD( m_aimYaw, FIELD_FLOAT ),
  1210. DEFINE_FIELD( m_aimPitch, FIELD_FLOAT ),
  1211. DEFINE_FIELD( m_flShootAllowInterruptTime, FIELD_TIME ),
  1212. DEFINE_FIELD( m_flNextChargeTime, FIELD_TIME ),
  1213. //DEFINE_FIELD( m_flNextDamageTime, FIELD_TIME ),
  1214. DEFINE_FIELD( m_flNextSideStepTime, FIELD_TIME ),
  1215. DEFINE_EMBEDDED( m_HeavyDamageDelay ),
  1216. DEFINE_EMBEDDED( m_FlinchTimer ),
  1217. DEFINE_FIELD( m_eEyeState, FIELD_INTEGER ),
  1218. DEFINE_FIELD( m_bTopMuzzle, FIELD_BOOLEAN ),
  1219. DEFINE_FIELD( m_bEnableSquadShootDelay, FIELD_BOOLEAN ),
  1220. DEFINE_FIELD( m_bIsBleeding, FIELD_BOOLEAN ),
  1221. DEFINE_FIELD( m_bDisableShooting, FIELD_BOOLEAN ),
  1222. DEFINE_FIELD( m_bFlashlightInEyes, FIELD_BOOLEAN ),
  1223. DEFINE_FIELD( m_flPupilDilateTime, FIELD_TIME ),
  1224. DEFINE_FIELD( m_vecEnemyLastSeen, FIELD_POSITION_VECTOR ),
  1225. DEFINE_FIELD( m_vecLastCanPlantHerePos, FIELD_POSITION_VECTOR ),
  1226. DEFINE_FIELD( m_vecStaggerDir, FIELD_VECTOR ),
  1227. DEFINE_FIELD( m_bPlanted, FIELD_BOOLEAN ),
  1228. DEFINE_FIELD( m_bLastCanPlantHere, FIELD_BOOLEAN ),
  1229. //DEFINE_FIELD( m_bMissLeft, FIELD_BOOLEAN ),
  1230. DEFINE_FIELD( m_bEnableUnplantedShooting, FIELD_BOOLEAN ),
  1231. DEFINE_FIELD( m_nKillingDamageType, FIELD_INTEGER ),
  1232. DEFINE_FIELD( m_eDodgeActivity, FIELD_INTEGER ),
  1233. DEFINE_EMBEDDED( m_RundownDelay ),
  1234. DEFINE_EMBEDDED( m_IgnoreVehicleTimer ),
  1235. DEFINE_FIELD( m_flNextMeleeTime, FIELD_TIME ),
  1236. DEFINE_FIELD( m_flTeslaStopTime, FIELD_TIME ),
  1237. // (auto saved by AI)
  1238. //DEFINE_FIELD( m_EscortBehavior, FIELD_EMBEDDED ),
  1239. DEFINE_FIELD( m_iszCurrentExpression, FIELD_STRING ),
  1240. DEFINE_FIELD( m_fCorneredTimer, FIELD_TIME),
  1241. DEFINE_EMBEDDED( m_CheckHintGroupTimer ),
  1242. // (Recomputed in Precache())
  1243. //DEFINE_FIELD( m_bInLargeOutdoorMap, FIELD_BOOLEAN ),
  1244. DEFINE_FIELD( m_flTimeSawEnemyAgain, FIELD_TIME ),
  1245. //DEFINE_SOUNDPATCH( m_pGunFiringSound ),
  1246. //DEFINE_UTLVECTOR( m_pSiegeTarget, FIELD_EHANDLE ),
  1247. DEFINE_FIELD( m_iszSiegeTargetName, FIELD_STRING ),
  1248. DEFINE_FIELD( m_flTimeNextSiegeTargetAttack, FIELD_TIME ),
  1249. DEFINE_FIELD( m_hCurrentSiegeTarget, FIELD_EHANDLE ),
  1250. DEFINE_FIELD( m_hHitByVehicle, FIELD_EHANDLE ),
  1251. DEFINE_EMBEDDED( m_BeginFollowDelay ),
  1252. DEFINE_EMBEDDED( m_EyeSwitchTimer ),
  1253. DEFINE_FIELD( m_nFlechettesQueued, FIELD_INTEGER ),
  1254. DEFINE_FIELD( m_nClampedShots, FIELD_INTEGER ),
  1255. DEFINE_FIELD( m_flNextRangeAttack2Time, FIELD_TIME ),
  1256. DEFINE_FIELD( m_flNextFlechetteTime, FIELD_TIME ),
  1257. DEFINE_UTLVECTOR( m_hAttachedBusters, FIELD_EHANDLE ),
  1258. DEFINE_UTLVECTOR( m_pSiegeTargets, FIELD_EHANDLE ),
  1259. // inputs
  1260. DEFINE_INPUTFUNC( FIELD_VOID, "Dodge", InputDodge ),
  1261. DEFINE_INPUTFUNC( FIELD_VOID, "FlankEnemy", InputFlankEnemy ),
  1262. DEFINE_INPUTFUNC( FIELD_STRING, "DisableShooting", InputDisableShooting ),
  1263. DEFINE_INPUTFUNC( FIELD_STRING, "EnableShooting", InputEnableShooting ),
  1264. DEFINE_INPUTFUNC( FIELD_STRING, "FollowStrider", InputFollowStrider ),
  1265. DEFINE_INPUTFUNC( FIELD_STRING, "UseSiegeTargets", InputUseSiegeTargets ),
  1266. DEFINE_INPUTFUNC( FIELD_VOID, "EnableSquadShootDelay", InputEnableSquadShootDelay ),
  1267. DEFINE_INPUTFUNC( FIELD_VOID, "DisableSquadShootDelay", InputDisableSquadShootDelay ),
  1268. DEFINE_INPUTFUNC( FIELD_VOID, "EnableUnplantedShooting", InputEnableUnplantedShooting ),
  1269. DEFINE_INPUTFUNC( FIELD_VOID, "DisableUnplantedShooting", InputDisableUnplantedShooting ),
  1270. // Function Pointers
  1271. DEFINE_THINKFUNC( TeslaThink ),
  1272. DEFINE_THINKFUNC( BleedThink ),
  1273. DEFINE_THINKFUNC( JostleVehicleThink ),
  1274. END_DATADESC()
  1275. //-----------------------------------------------------------------------------
  1276. int CNPC_Hunter::gm_nUnplantedNode = 0;
  1277. int CNPC_Hunter::gm_nPlantedNode = 0;
  1278. int CNPC_Hunter::gm_nAimYawPoseParam = -1;
  1279. int CNPC_Hunter::gm_nAimPitchPoseParam = -1;
  1280. int CNPC_Hunter::gm_nBodyYawPoseParam = -1;
  1281. int CNPC_Hunter::gm_nBodyPitchPoseParam = -1;
  1282. int CNPC_Hunter::gm_nStaggerYawPoseParam = -1;
  1283. int CNPC_Hunter::gm_nHeadCenterAttachment = -1;
  1284. int CNPC_Hunter::gm_nHeadBottomAttachment = -1;
  1285. float CNPC_Hunter::gm_flHeadRadius = 0;
  1286. int CNPC_Hunter::gm_nTopGunAttachment = -1;
  1287. int CNPC_Hunter::gm_nBottomGunAttachment = -1;
  1288. float CNPC_Hunter::gm_flMinigunDistZ;
  1289. Vector CNPC_Hunter::gm_vecLocalRelativePositionMinigun;
  1290. //-----------------------------------------------------------------------------
  1291. static CUtlVector<CNPC_Hunter *> g_Hunters;
  1292. float g_TimeLastDistributeFreeHunters = -1;
  1293. const float FREE_HUNTER_DISTRIBUTE_INTERVAL = 2;
  1294. //-----------------------------------------------------------------------------
  1295. //-----------------------------------------------------------------------------
  1296. CNPC_Hunter::CNPC_Hunter()
  1297. {
  1298. g_Hunters.AddToTail( this );
  1299. g_TimeLastDistributeFreeHunters = -1;
  1300. m_flTimeSawEnemyAgain = HUNTER_SEE_ENEMY_TIME_INVALID;
  1301. }
  1302. //-----------------------------------------------------------------------------
  1303. //-----------------------------------------------------------------------------
  1304. CNPC_Hunter::~CNPC_Hunter()
  1305. {
  1306. g_Hunters.FindAndRemove( this );
  1307. g_TimeLastDistributeFreeHunters = -1;
  1308. }
  1309. //-----------------------------------------------------------------------------
  1310. //-----------------------------------------------------------------------------
  1311. void CNPC_Hunter::Precache()
  1312. {
  1313. PrecacheModel( "models/hunter.mdl" );
  1314. PropBreakablePrecacheAll( MAKE_STRING("models/hunter.mdl") );
  1315. PrecacheScriptSound( "NPC_Hunter.Idle" );
  1316. PrecacheScriptSound( "NPC_Hunter.Scan" );
  1317. PrecacheScriptSound( "NPC_Hunter.Alert" );
  1318. PrecacheScriptSound( "NPC_Hunter.Pain" );
  1319. PrecacheScriptSound( "NPC_Hunter.PreCharge" );
  1320. PrecacheScriptSound( "NPC_Hunter.Angry" );
  1321. PrecacheScriptSound( "NPC_Hunter.Death" );
  1322. PrecacheScriptSound( "NPC_Hunter.FireMinigun" );
  1323. PrecacheScriptSound( "NPC_Hunter.Footstep" );
  1324. PrecacheScriptSound( "NPC_Hunter.BackFootstep" );
  1325. PrecacheScriptSound( "NPC_Hunter.FlechetteVolleyWarn" );
  1326. PrecacheScriptSound( "NPC_Hunter.FlechetteShoot" );
  1327. PrecacheScriptSound( "NPC_Hunter.FlechetteShootLoop" );
  1328. PrecacheScriptSound( "NPC_Hunter.FlankAnnounce" );
  1329. PrecacheScriptSound( "NPC_Hunter.MeleeAnnounce" );
  1330. PrecacheScriptSound( "NPC_Hunter.MeleeHit" );
  1331. PrecacheScriptSound( "NPC_Hunter.TackleAnnounce" );
  1332. PrecacheScriptSound( "NPC_Hunter.TackleHit" );
  1333. PrecacheScriptSound( "NPC_Hunter.ChargeHitEnemy" );
  1334. PrecacheScriptSound( "NPC_Hunter.ChargeHitWorld" );
  1335. PrecacheScriptSound( "NPC_Hunter.FoundEnemy" );
  1336. PrecacheScriptSound( "NPC_Hunter.FoundEnemyAck" );
  1337. PrecacheScriptSound( "NPC_Hunter.DefendStrider" );
  1338. PrecacheScriptSound( "NPC_Hunter.HitByVehicle" );
  1339. PrecacheParticleSystem( "hunter_muzzle_flash" );
  1340. PrecacheParticleSystem( "blood_impact_synth_01" );
  1341. PrecacheParticleSystem( "blood_impact_synth_01_arc_parent" );
  1342. PrecacheParticleSystem( "blood_spurt_synth_01" );
  1343. PrecacheParticleSystem( "blood_drip_synth_01" );
  1344. PrecacheInstancedScene( "scenes/npc/hunter/hunter_scan.vcd" );
  1345. PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyeclose.vcd" );
  1346. PrecacheInstancedScene( "scenes/npc/hunter/hunter_roar.vcd" );
  1347. PrecacheInstancedScene( "scenes/npc/hunter/hunter_pain.vcd" );
  1348. PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyedarts_top.vcd" );
  1349. PrecacheInstancedScene( "scenes/npc/hunter/hunter_eyedarts_bottom.vcd" );
  1350. PrecacheMaterial( "effects/water_highlight" );
  1351. UTIL_PrecacheOther( "hunter_flechette" );
  1352. UTIL_PrecacheOther( "sparktrail" );
  1353. m_bInLargeOutdoorMap = false;
  1354. if( !Q_strnicmp( STRING(gpGlobals->mapname), "ep2_outland_12", 14) )
  1355. {
  1356. m_bInLargeOutdoorMap = true;
  1357. }
  1358. BaseClass::Precache();
  1359. }
  1360. //-----------------------------------------------------------------------------
  1361. //-----------------------------------------------------------------------------
  1362. void CNPC_Hunter::Spawn()
  1363. {
  1364. Precache();
  1365. SetModel( "models/hunter.mdl" );
  1366. BaseClass::Spawn();
  1367. //m_debugOverlays |= OVERLAY_NPC_ROUTE_BIT | OVERLAY_BBOX_BIT | OVERLAY_PIVOT_BIT;
  1368. SetHullType( HULL_MEDIUM_TALL );
  1369. SetHullSizeNormal();
  1370. SetDefaultEyeOffset();
  1371. SetNavType( NAV_GROUND );
  1372. m_flGroundSpeed = 500;
  1373. m_NPCState = NPC_STATE_NONE;
  1374. SetBloodColor( DONT_BLEED );
  1375. m_iHealth = m_iMaxHealth = sk_hunter_health.GetInt();
  1376. m_flFieldOfView = HUNTER_FOV_DOT;
  1377. SetSolid( SOLID_BBOX );
  1378. AddSolidFlags( FSOLID_NOT_STANDABLE );
  1379. SetMoveType( MOVETYPE_STEP );
  1380. SetupGlobalModelData();
  1381. CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_SQUAD | bits_CAP_ANIMATEDFACE );
  1382. CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_INNATE_MELEE_ATTACK1 );
  1383. CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK );
  1384. if ( !hunter_allow_dissolve.GetBool() )
  1385. {
  1386. AddEFlags( EFL_NO_DISSOLVE );
  1387. }
  1388. if( hunter_allow_nav_jump.GetBool() )
  1389. {
  1390. CapabilitiesAdd( bits_CAP_MOVE_JUMP );
  1391. }
  1392. NPCInit();
  1393. m_bEnableSquadShootDelay = true;
  1394. m_flDistTooFar = hunter_flechette_max_range.GetFloat();
  1395. // Discard time must be greater than free knowledge duration. Make it double.
  1396. float freeKnowledge = hunter_free_knowledge.GetFloat();
  1397. if ( freeKnowledge < GetEnemies()->GetEnemyDiscardTime() )
  1398. {
  1399. GetEnemies()->SetEnemyDiscardTime( MAX( freeKnowledge + 0.1, AI_DEF_ENEMY_DISCARD_TIME ) );
  1400. }
  1401. GetEnemies()->SetFreeKnowledgeDuration( freeKnowledge );
  1402. // Find out what strider we should follow, if any.
  1403. if ( m_iszFollowTarget != NULL_STRING )
  1404. {
  1405. m_BeginFollowDelay.Set( .1 ); // Allow time for strider to spawn
  1406. }
  1407. //if ( !m_pGunFiringSound )
  1408. //{
  1409. // CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1410. // CPASAttenuationFilter filter( this );
  1411. //
  1412. // m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "NPC_Hunter.FlechetteShootLoop" );
  1413. // controller.Play( m_pGunFiringSound, 0.0, 100 );
  1414. //}
  1415. }
  1416. //-----------------------------------------------------------------------------
  1417. //-----------------------------------------------------------------------------
  1418. void CNPC_Hunter::UpdateEfficiency( bool bInPVS )
  1419. {
  1420. SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL );
  1421. SetMoveEfficiency( AIME_NORMAL );
  1422. }
  1423. //-----------------------------------------------------------------------------
  1424. //-----------------------------------------------------------------------------
  1425. bool CNPC_Hunter::CreateBehaviors()
  1426. {
  1427. AddBehavior( &m_EscortBehavior );
  1428. return BaseClass::CreateBehaviors();
  1429. }
  1430. //-----------------------------------------------------------------------------
  1431. //-----------------------------------------------------------------------------
  1432. void CNPC_Hunter::SetupGlobalModelData()
  1433. {
  1434. if ( gm_nBodyYawPoseParam != -1 )
  1435. return;
  1436. gm_nAimYawPoseParam = LookupPoseParameter( "aim_yaw" );
  1437. gm_nAimPitchPoseParam = LookupPoseParameter( "aim_pitch" );
  1438. gm_nBodyYawPoseParam = LookupPoseParameter( "body_yaw" );
  1439. gm_nBodyPitchPoseParam = LookupPoseParameter( "body_pitch" );
  1440. gm_nTopGunAttachment = LookupAttachment( "top_eye" );
  1441. gm_nBottomGunAttachment = LookupAttachment( "bottom_eye" );
  1442. gm_nStaggerYawPoseParam = LookupAttachment( "stagger_yaw" );
  1443. gm_nHeadCenterAttachment = LookupAttachment( "head_center" );
  1444. gm_nHeadBottomAttachment = LookupAttachment( "head_radius_measure" );
  1445. // Measure the radius of the head.
  1446. Vector vecHeadCenter;
  1447. Vector vecHeadBottom;
  1448. GetAttachment( gm_nHeadCenterAttachment, vecHeadCenter );
  1449. GetAttachment( gm_nHeadBottomAttachment, vecHeadBottom );
  1450. gm_flHeadRadius = ( vecHeadCenter - vecHeadBottom ).Length();
  1451. int nSequence = SelectWeightedSequence( ACT_HUNTER_RANGE_ATTACK2_UNPLANTED );
  1452. gm_nUnplantedNode = GetEntryNode( nSequence );
  1453. nSequence = SelectWeightedSequence( ACT_RANGE_ATTACK2 );
  1454. gm_nPlantedNode = GetEntryNode( nSequence );
  1455. CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES );
  1456. }
  1457. //-----------------------------------------------------------------------------
  1458. // Shuts down looping sounds when we are killed in combat or deleted.
  1459. //-----------------------------------------------------------------------------
  1460. void CNPC_Hunter::StopLoopingSounds()
  1461. {
  1462. BaseClass::StopLoopingSounds();
  1463. //if ( m_pGunFiringSound )
  1464. //{
  1465. // CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1466. // controller.SoundDestroy( m_pGunFiringSound );
  1467. // m_pGunFiringSound = NULL;
  1468. //}
  1469. }
  1470. //-----------------------------------------------------------------------------
  1471. //-----------------------------------------------------------------------------
  1472. void CNPC_Hunter::OnRestore()
  1473. {
  1474. BaseClass::OnRestore();
  1475. SetupGlobalModelData();
  1476. CreateVPhysics();
  1477. if ( IsBleeding() )
  1478. {
  1479. StartBleeding();
  1480. }
  1481. }
  1482. //-----------------------------------------------------------------------------
  1483. //-----------------------------------------------------------------------------
  1484. void CNPC_Hunter::IdleSound()
  1485. {
  1486. if ( HasCondition( COND_LOST_ENEMY ) )
  1487. {
  1488. EmitSound( "NPC_Hunter.Scan" );
  1489. }
  1490. else
  1491. {
  1492. EmitSound( "NPC_Hunter.Idle" );
  1493. }
  1494. }
  1495. //-----------------------------------------------------------------------------
  1496. //-----------------------------------------------------------------------------
  1497. bool CNPC_Hunter::ShouldPlayIdleSound()
  1498. {
  1499. if ( random->RandomInt(0, 99) == 0 && !HasSpawnFlags( SF_NPC_GAG ) )
  1500. return true;
  1501. return false;
  1502. }
  1503. //-----------------------------------------------------------------------------
  1504. // Stay facing our enemy when close enough.
  1505. //-----------------------------------------------------------------------------
  1506. bool CNPC_Hunter::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
  1507. {
  1508. if ( GetActivity() == ACT_TRANSITION )
  1509. {
  1510. // No turning while in transitions.
  1511. return true;
  1512. }
  1513. bool bSideStepping = IsCurSchedule( SCHED_HUNTER_SIDESTEP, false );
  1514. // FIXME: this will break scripted sequences that walk when they have an enemy
  1515. if ( GetEnemy() &&
  1516. ( bSideStepping ||
  1517. ( ( ( GetNavigator()->GetMovementActivity() == ACT_RUN ) || ( GetNavigator()->GetMovementActivity() == ACT_WALK ) ) &&
  1518. !IsCurSchedule( SCHED_HUNTER_TAKE_COVER_FROM_ENEMY, false ) ) ) )
  1519. {
  1520. Vector vecEnemyLKP = GetEnemyLKP();
  1521. // Face my enemy if we're close enough
  1522. if ( bSideStepping || UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST )
  1523. {
  1524. AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.2 );
  1525. }
  1526. }
  1527. return BaseClass::OverrideMoveFacing( move, flInterval );
  1528. }
  1529. //-----------------------------------------------------------------------------
  1530. //-----------------------------------------------------------------------------
  1531. void CNPC_Hunter::PostNPCInit()
  1532. {
  1533. BaseClass::PostNPCInit();
  1534. IPhysicsObject *pPhysObject = VPhysicsGetObject();
  1535. Assert( pPhysObject );
  1536. if ( pPhysObject )
  1537. {
  1538. pPhysObject->SetMass( 600.0 );
  1539. }
  1540. }
  1541. //-----------------------------------------------------------------------------
  1542. //-----------------------------------------------------------------------------
  1543. void CNPC_Hunter::Activate()
  1544. {
  1545. BaseClass::Activate();
  1546. s_iszStriderBusterClassname = AllocPooledString( "weapon_striderbuster" );
  1547. s_iszStriderClassname = AllocPooledString( "npc_strider" );
  1548. s_iszMagnadeClassname = AllocPooledString( "npc_grenade_magna" );
  1549. s_iszPhysPropClassname = AllocPooledString( "prop_physics" );
  1550. s_iszHuntersToRunOver = AllocPooledString( "hunters_to_run_over" );
  1551. // If no one has initialized the hunters to run over counter, just zero it out.
  1552. if ( !GlobalEntity_IsInTable( s_iszHuntersToRunOver ) )
  1553. {
  1554. GlobalEntity_Add( s_iszHuntersToRunOver, gpGlobals->mapname, GLOBAL_ON );
  1555. GlobalEntity_SetCounter( s_iszHuntersToRunOver, 0 );
  1556. }
  1557. CMissile::AddCustomDetonator( this, ( GetHullMaxs().AsVector2D() - GetHullMins().AsVector2D() ).Length() * 0.5, GetHullHeight() );
  1558. SetupGlobalModelData();
  1559. if ( gm_flMinigunDistZ == 0 )
  1560. {
  1561. // Have to create a virgin hunter to ensure proper pose
  1562. CNPC_Hunter *pHunter = (CNPC_Hunter *)CreateEntityByName( "npc_hunter" );
  1563. Assert(pHunter);
  1564. pHunter->Spawn();
  1565. pHunter->SetActivity( ACT_WALK );
  1566. pHunter->InvalidateBoneCache();
  1567. // Currently just using the gun for the vertical component!
  1568. Vector defEyePos;
  1569. pHunter->GetAttachment( "minigunbase", defEyePos );
  1570. gm_flMinigunDistZ = defEyePos.z - pHunter->GetAbsOrigin().z;
  1571. Vector position;
  1572. pHunter->GetAttachment( gm_nTopGunAttachment, position );
  1573. VectorITransform( position, pHunter->EntityToWorldTransform(), gm_vecLocalRelativePositionMinigun );
  1574. UTIL_Remove( pHunter );
  1575. }
  1576. }
  1577. //-----------------------------------------------------------------------------
  1578. //-----------------------------------------------------------------------------
  1579. void CNPC_Hunter::UpdateOnRemove()
  1580. {
  1581. CMissile::RemoveCustomDetonator( this );
  1582. BaseClass::UpdateOnRemove();
  1583. }
  1584. //-----------------------------------------------------------------------------
  1585. //-----------------------------------------------------------------------------
  1586. Class_T CNPC_Hunter::Classify()
  1587. {
  1588. return CLASS_COMBINE_HUNTER;
  1589. }
  1590. //-----------------------------------------------------------------------------
  1591. // Compensate for the hunter's long legs by moving the bodytarget up to his head.
  1592. //-----------------------------------------------------------------------------
  1593. Vector CNPC_Hunter::BodyTarget( const Vector &posSrc, bool bNoisy /*= true*/ )
  1594. {
  1595. Vector vecResult;
  1596. QAngle vecAngle;
  1597. GetAttachment( gm_nHeadCenterAttachment, vecResult, vecAngle );
  1598. if ( bNoisy )
  1599. {
  1600. float rand1 = random->RandomFloat( 0, gm_flHeadRadius ) + random->RandomFloat( 0, gm_flHeadRadius );
  1601. return vecResult + RandomVector( -rand1, rand1 );
  1602. }
  1603. return vecResult;
  1604. }
  1605. //-----------------------------------------------------------------------------
  1606. //-----------------------------------------------------------------------------
  1607. int CNPC_Hunter::DrawDebugTextOverlays()
  1608. {
  1609. int text_offset = BaseClass::DrawDebugTextOverlays();
  1610. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  1611. {
  1612. EntityText( text_offset, CFmtStr("%s", m_bPlanted ? "Planted" : "Unplanted" ), 0 );
  1613. text_offset++;
  1614. EntityText( text_offset, CFmtStr("Eye state: %d", m_eEyeState ), 0 );
  1615. text_offset++;
  1616. if( IsUsingSiegeTargets() )
  1617. {
  1618. EntityText( text_offset, CFmtStr("Next Siege Attempt:%f", m_flTimeNextSiegeTargetAttack - gpGlobals->curtime ), 0 );
  1619. text_offset++;
  1620. }
  1621. }
  1622. return text_offset;
  1623. }
  1624. //-----------------------------------------------------------------------------
  1625. //-----------------------------------------------------------------------------
  1626. void CNPC_Hunter::LockBothEyes( float flDuration )
  1627. {
  1628. m_eEyeState = HUNTER_EYE_STATE_BOTH_LOCKED;
  1629. m_EyeSwitchTimer.Set( flDuration );
  1630. }
  1631. //-----------------------------------------------------------------------------
  1632. //-----------------------------------------------------------------------------
  1633. void CNPC_Hunter::UnlockBothEyes( float flDuration )
  1634. {
  1635. m_eEyeState = HUNTER_EYE_STATE_BOTH_UNLOCKED;
  1636. m_EyeSwitchTimer.Set( flDuration );
  1637. }
  1638. //-----------------------------------------------------------------------------
  1639. //-----------------------------------------------------------------------------
  1640. void CNPC_Hunter::OnChangeActivity( Activity eNewActivity )
  1641. {
  1642. m_EyeSwitchTimer.Force();
  1643. BaseClass::OnChangeActivity( eNewActivity );
  1644. }
  1645. //-----------------------------------------------------------------------------
  1646. //-----------------------------------------------------------------------------
  1647. void CNPC_Hunter::UpdateEyes()
  1648. {
  1649. // If the eyes are controlled by a script, do nothing.
  1650. if ( GetState() == NPC_STATE_SCRIPT )
  1651. return;
  1652. if ( m_EyeSwitchTimer.Expired() )
  1653. {
  1654. RemoveActorFromScriptedScenes( this, false );
  1655. if ( GetActivity() == ACT_IDLE )
  1656. {
  1657. // Idles have eye motion baked in.
  1658. m_eEyeState = HUNTER_EYE_STATE_BOTH_LOCKED;
  1659. }
  1660. else if ( GetEnemy() == NULL )
  1661. {
  1662. m_eEyeState = HUNTER_EYE_STATE_BOTH_UNLOCKED;
  1663. }
  1664. else if ( m_eEyeState == HUNTER_EYE_STATE_BOTH_LOCKED )
  1665. {
  1666. if ( random->RandomInt( 0, 1 ) == 0 )
  1667. {
  1668. m_eEyeState = HUNTER_EYE_STATE_TOP_LOCKED;
  1669. }
  1670. else
  1671. {
  1672. m_eEyeState = HUNTER_EYE_STATE_BOTTOM_LOCKED;
  1673. }
  1674. }
  1675. else if ( m_eEyeState == HUNTER_EYE_STATE_TOP_LOCKED )
  1676. {
  1677. m_eEyeState = HUNTER_EYE_STATE_BOTTOM_LOCKED;
  1678. }
  1679. else if ( m_eEyeState == HUNTER_EYE_STATE_BOTTOM_LOCKED )
  1680. {
  1681. m_eEyeState = HUNTER_EYE_STATE_TOP_LOCKED;
  1682. }
  1683. if ( ( m_eEyeState == HUNTER_EYE_STATE_BOTTOM_LOCKED ) || ( m_eEyeState == HUNTER_EYE_STATE_BOTH_UNLOCKED ) )
  1684. {
  1685. SetExpression( "scenes/npc/hunter/hunter_eyedarts_top.vcd" );
  1686. }
  1687. if ( ( m_eEyeState == HUNTER_EYE_STATE_TOP_LOCKED ) || ( m_eEyeState == HUNTER_EYE_STATE_BOTH_UNLOCKED ) )
  1688. {
  1689. SetExpression( "scenes/npc/hunter/hunter_eyedarts_bottom.vcd" );
  1690. }
  1691. m_EyeSwitchTimer.Set( random->RandomFloat( 1.0f, 3.0f ) );
  1692. }
  1693. /*Vector vecEyePos;
  1694. Vector vecEyeDir;
  1695. GetAttachment( gm_nTopGunAttachment, vecEyePos, &vecEyeDir );
  1696. NDebugOverlay::Line( vecEyePos, vecEyePos + vecEyeDir * 36, 255, 0, 0, 0, 0.1 );
  1697. GetAttachment( gm_nBottomGunAttachment, vecEyePos, &vecEyeDir );
  1698. NDebugOverlay::Line( vecEyePos, vecEyePos + vecEyeDir * 36, 255, 0, 0, 0, 0.1 );*/
  1699. }
  1700. //-----------------------------------------------------------------------------
  1701. //-----------------------------------------------------------------------------
  1702. void CNPC_Hunter::NPCThink()
  1703. {
  1704. BaseClass::NPCThink();
  1705. // Update our planted/unplanted state.
  1706. m_bPlanted = ( GetEntryNode( GetSequence() ) == gm_nPlantedNode );
  1707. UpdateAim();
  1708. UpdateEyes();
  1709. }
  1710. //-----------------------------------------------------------------------------
  1711. //-----------------------------------------------------------------------------
  1712. void CNPC_Hunter::PrescheduleThink()
  1713. {
  1714. BaseClass::PrescheduleThink();
  1715. if ( m_BeginFollowDelay.Expired() )
  1716. {
  1717. FollowStrider( STRING( m_iszFollowTarget ) );
  1718. m_BeginFollowDelay.Stop();
  1719. }
  1720. m_EscortBehavior.CheckBreakEscort();
  1721. // If we're being blinded by the flashlight, see if we should stop
  1722. if ( m_bFlashlightInEyes )
  1723. {
  1724. if ( m_flPupilDilateTime < gpGlobals->curtime )
  1725. {
  1726. CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
  1727. if ( ( pPlayer && !pPlayer->IsIlluminatedByFlashlight( this, NULL ) ) || !PlayerFlashlightOnMyEyes( pPlayer ) )
  1728. {
  1729. //Msg( "NOT SHINING FLASHLIGHT ON ME\n" );
  1730. // Remove the actor from the flashlight scene
  1731. RemoveActorFromScriptedScenes( this, true, false, "scenes/npc/hunter/hunter_eyeclose.vcd" );
  1732. m_bFlashlightInEyes = false;
  1733. }
  1734. }
  1735. }
  1736. }
  1737. //-----------------------------------------------------------------------------
  1738. //-----------------------------------------------------------------------------
  1739. void CNPC_Hunter::GatherChargeConditions()
  1740. {
  1741. ClearCondition( COND_HUNTER_CAN_CHARGE_ENEMY );
  1742. if ( !hunter_charge.GetBool() )
  1743. return;
  1744. if ( !GetEnemy() )
  1745. return;
  1746. if ( GetHintGroup() != NULL_STRING )
  1747. return;
  1748. if ( !HasCondition( COND_SEE_ENEMY ) )
  1749. return;
  1750. if ( !hunter_charge_test.GetBool() && gpGlobals->curtime < m_flNextChargeTime )
  1751. return;
  1752. // No charging Alyx or Barney
  1753. if( GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
  1754. return;
  1755. if ( m_EscortBehavior.GetEscortTarget() && GetEnemy()->MyCombatCharacterPointer() && !GetEnemy()->MyCombatCharacterPointer()->FInViewCone( this ) )
  1756. return;
  1757. if ( ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), true, false ) )
  1758. {
  1759. SetCondition( COND_HUNTER_CAN_CHARGE_ENEMY );
  1760. }
  1761. }
  1762. //-----------------------------------------------------------------------------
  1763. //-----------------------------------------------------------------------------
  1764. void CNPC_Hunter::GatherConditions()
  1765. {
  1766. GatherIndoorOutdoorConditions();
  1767. GatherChargeConditions();
  1768. BaseClass::GatherConditions();
  1769. // Enemy LKP that doesn't get updated by the free knowledge code.
  1770. // Used for shooting at where our enemy was when we can't see them.
  1771. ClearCondition( COND_HUNTER_INCOMING_VEHICLE );
  1772. if ( m_IgnoreVehicleTimer.Expired() && GetEnemy() && HasCondition( COND_SEE_ENEMY ) )
  1773. {
  1774. CBaseEntity *pVehicle = GetEnemyVehicle();
  1775. if ( ( pVehicle ) && ( GlobalEntity_GetCounter( s_iszHuntersToRunOver ) <= 0 ) )
  1776. {
  1777. static float timeDrawnArrow;
  1778. // Extrapolate the position of the vehicle and see if it's heading toward us.
  1779. float predictTime = hunter_dodge_warning.GetFloat();
  1780. Vector2D vecFuturePos = pVehicle->GetAbsOrigin().AsVector2D() + pVehicle->GetSmoothedVelocity().AsVector2D() * predictTime;
  1781. if ( pVehicle->GetSmoothedVelocity().LengthSqr() > Square( 200 ) )
  1782. {
  1783. float t = 0;
  1784. Vector2D vDirMovement = pVehicle->GetSmoothedVelocity().AsVector2D();
  1785. if ( hunter_dodge_debug.GetBool() )
  1786. {
  1787. NDebugOverlay::Line( pVehicle->GetAbsOrigin(), pVehicle->GetAbsOrigin() + pVehicle->GetSmoothedVelocity(), 255, 255, 255, true, .1 );
  1788. }
  1789. vDirMovement.NormalizeInPlace();
  1790. Vector2D vDirToHunter = GetAbsOrigin().AsVector2D() - pVehicle->GetAbsOrigin().AsVector2D();
  1791. vDirToHunter.NormalizeInPlace();
  1792. if ( DotProduct2D( vDirMovement, vDirToHunter ) > hunter_dodge_warning_cone.GetFloat() &&
  1793. CalcDistanceSqrToLine2D( GetAbsOrigin().AsVector2D(), pVehicle->GetAbsOrigin().AsVector2D(), vecFuturePos, &t ) < Square( hunter_dodge_warning_width.GetFloat() * .5 ) &&
  1794. t > 0.0 && t < 1.0 )
  1795. {
  1796. if ( fabs( predictTime - hunter_dodge_warning.GetFloat() ) < .05 || random->RandomInt( 0, 3 ) )
  1797. {
  1798. SetCondition( COND_HUNTER_INCOMING_VEHICLE );
  1799. }
  1800. else
  1801. {
  1802. if ( hunter_dodge_debug. GetBool() )
  1803. {
  1804. Msg( "Hunter %d failing dodge (ignore)\n", entindex() );
  1805. }
  1806. }
  1807. if ( hunter_dodge_debug. GetBool() )
  1808. {
  1809. NDebugOverlay::Cross3D( EyePosition(), 100, 255, 255, 255, true, .1 );
  1810. if ( timeDrawnArrow != gpGlobals->curtime )
  1811. {
  1812. timeDrawnArrow = gpGlobals->curtime;
  1813. Vector vEndpoint( vecFuturePos.x, vecFuturePos.y, UTIL_GetLocalPlayer()->WorldSpaceCenter().z - 24 );
  1814. NDebugOverlay::HorzArrow( UTIL_GetLocalPlayer()->WorldSpaceCenter() - Vector(0, 0, 24), vEndpoint, hunter_dodge_warning_width.GetFloat(), 255, 0, 0, 64, true, .1 );
  1815. }
  1816. }
  1817. }
  1818. else if ( hunter_dodge_debug.GetBool() )
  1819. {
  1820. if ( t <= 0 )
  1821. {
  1822. NDebugOverlay::Cross3D( EyePosition(), 100, 0, 0, 255, true, .1 );
  1823. }
  1824. else
  1825. {
  1826. NDebugOverlay::Cross3D( EyePosition(), 100, 0, 255, 255, true, .1 );
  1827. }
  1828. }
  1829. }
  1830. else if ( hunter_dodge_debug.GetBool() )
  1831. {
  1832. NDebugOverlay::Cross3D( EyePosition(), 100, 0, 255, 0, true, .1 );
  1833. }
  1834. if ( hunter_dodge_debug. GetBool() )
  1835. {
  1836. if ( timeDrawnArrow != gpGlobals->curtime )
  1837. {
  1838. timeDrawnArrow = gpGlobals->curtime;
  1839. Vector vEndpoint( vecFuturePos.x, vecFuturePos.y, UTIL_GetLocalPlayer()->WorldSpaceCenter().z - 24 );
  1840. NDebugOverlay::HorzArrow( UTIL_GetLocalPlayer()->WorldSpaceCenter() - Vector(0, 0, 24), vEndpoint, hunter_dodge_warning_width.GetFloat(), 127, 127, 127, 64, true, .1 );
  1841. }
  1842. }
  1843. }
  1844. m_vecEnemyLastSeen = GetEnemy()->GetAbsOrigin();
  1845. }
  1846. if( !HasCondition(COND_ENEMY_OCCLUDED) )
  1847. {
  1848. // m_flTimeSawEnemyAgain always tells us what time I first saw this
  1849. // enemy again after some period of not seeing them. This is used to
  1850. // compute how long the enemy has been visible to me THIS TIME.
  1851. // Every time I lose sight of the enemy this time is set invalid until
  1852. // I see the enemy again and record that time.
  1853. if( m_flTimeSawEnemyAgain == HUNTER_SEE_ENEMY_TIME_INVALID )
  1854. {
  1855. m_flTimeSawEnemyAgain = gpGlobals->curtime;
  1856. }
  1857. }
  1858. else
  1859. {
  1860. m_flTimeSawEnemyAgain = HUNTER_SEE_ENEMY_TIME_INVALID;
  1861. }
  1862. ManageSiegeTargets();
  1863. }
  1864. //-----------------------------------------------------------------------------
  1865. // Search all entities in the map
  1866. //-----------------------------------------------------------------------------
  1867. void CNPC_Hunter::CollectSiegeTargets()
  1868. {
  1869. CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_iszSiegeTargetName );
  1870. while( pTarget != NULL )
  1871. {
  1872. if( pTarget->Classify() == CLASS_BULLSEYE )
  1873. {
  1874. m_pSiegeTargets.AddToTail( pTarget );
  1875. }
  1876. pTarget = gEntList.FindEntityByName( pTarget, m_iszSiegeTargetName );
  1877. };
  1878. if( m_pSiegeTargets.Count() < 1 )
  1879. {
  1880. m_iszSiegeTargetName = NULL_STRING; // And stop trying!
  1881. }
  1882. }
  1883. //-----------------------------------------------------------------------------
  1884. // For use when Hunters are outside and the player is inside a structure
  1885. // Create a temporary bullseye in a location that makes it seem like
  1886. // I am aware of the location of a player I cannot see. (Then fire at
  1887. // at this bullseye, thus laying 'siege' to the part of the building he
  1888. // is in.) The locations are copied from suitable info_target entities.
  1889. // (these should be placed in exterior windows and doorways so that
  1890. // the Hunter fires into the building through these apertures)
  1891. //-----------------------------------------------------------------------------
  1892. void CNPC_Hunter::ManageSiegeTargets()
  1893. {
  1894. if( gpGlobals->curtime < m_flTimeNextSiegeTargetAttack )
  1895. return;
  1896. if( m_pSiegeTargets.Count() == 0 )
  1897. {
  1898. // If my list of siege targets is empty, go and cache all of them now
  1899. // so that I don't have to search the world every time.
  1900. CollectSiegeTargets();
  1901. if( m_pSiegeTargets.Count() == 0 )
  1902. return;
  1903. }
  1904. m_flTimeNextSiegeTargetAttack = gpGlobals->curtime + (hunter_siege_frequency.GetFloat() * RandomFloat( 0.8f, 1.2f) );
  1905. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  1906. // Start by assuming we are not going to create a siege target
  1907. bool bCreateSiegeTarget = false;
  1908. if( GetEnemy() == NULL )
  1909. {
  1910. // If I have no enemy at all, give it a try.
  1911. bCreateSiegeTarget = true;
  1912. }
  1913. if( bCreateSiegeTarget )
  1914. {
  1915. // We've decided that the situation calls for a siege target. So, we dig through all of my siege targets and
  1916. // take the closest one to the player that the player can see! (Obey they bullseye's FOV)
  1917. float flClosestDistSqr = Square( 1200.0f ); // Only use siege targets within 100 feet of player
  1918. CBaseEntity *pSiegeTargetLocation = NULL;
  1919. int iTraces = 0;
  1920. for( int i = 0 ; i < m_pSiegeTargets.Count() ; i++ )
  1921. {
  1922. CBaseEntity *pCandidate = m_pSiegeTargets[i];
  1923. if ( pCandidate == NULL )
  1924. continue;
  1925. float flDistSqr = pCandidate->GetAbsOrigin().DistToSqr(pPlayer->GetAbsOrigin());
  1926. if( flDistSqr < flClosestDistSqr )
  1927. {
  1928. // CollectSiegeTargets() guarantees my list is populated only with bullseye entities.
  1929. CNPC_Bullseye *pBullseye = dynamic_cast<CNPC_Bullseye*>(pCandidate);
  1930. if( !pBullseye->FInViewCone(this) )
  1931. continue;
  1932. if( pPlayer->FVisible(pCandidate) )
  1933. {
  1934. iTraces++;// Only counting these as a loose perf measurement
  1935. flClosestDistSqr = flDistSqr;
  1936. pSiegeTargetLocation = pCandidate;
  1937. }
  1938. }
  1939. }
  1940. if( pSiegeTargetLocation != NULL )
  1941. {
  1942. // Ditch any leftover siege target.
  1943. KillCurrentSiegeTarget();
  1944. // Create a bullseye that will live for 20 seconds. If we can't attack it within 20 seconds, it's probably
  1945. // out of reach anyone, so have it clean itself up after that long.
  1946. CBaseEntity *pSiegeTarget = CreateCustomTarget( pSiegeTargetLocation->GetAbsOrigin(), 20.0f );
  1947. pSiegeTarget->SetName( MAKE_STRING("siegetarget") );
  1948. m_hCurrentSiegeTarget.Set( pSiegeTarget );
  1949. AddEntityRelationship( pSiegeTarget, D_HT, 1 );
  1950. GetEnemies()->UpdateMemory( GetNavigator()->GetNetwork(), pSiegeTarget, pSiegeTarget->GetAbsOrigin(), 0.0f, true );
  1951. AI_EnemyInfo_t *pMemory = GetEnemies()->Find( pSiegeTarget );
  1952. if( pMemory )
  1953. {
  1954. // Pretend we've known about this target longer than we really have so that our AI doesn't waste time running ALERT schedules.
  1955. pMemory->timeFirstSeen = gpGlobals->curtime - 5.0f;
  1956. pMemory->timeLastSeen = gpGlobals->curtime - 1.0f;
  1957. }
  1958. }
  1959. }
  1960. }
  1961. //-----------------------------------------------------------------------------
  1962. // Destroy the bullseye that we're using as a temporary target
  1963. //-----------------------------------------------------------------------------
  1964. void CNPC_Hunter::KillCurrentSiegeTarget()
  1965. {
  1966. if ( m_hCurrentSiegeTarget )
  1967. {
  1968. GetEnemies()->ClearMemory( m_hCurrentSiegeTarget );
  1969. UTIL_Remove( m_hCurrentSiegeTarget );
  1970. m_hCurrentSiegeTarget.Set( NULL );
  1971. }
  1972. }
  1973. //-----------------------------------------------------------------------------
  1974. // Return true if this NPC can hear the specified sound
  1975. //-----------------------------------------------------------------------------
  1976. bool CNPC_Hunter::QueryHearSound( CSound *pSound )
  1977. {
  1978. if ( pSound->SoundContext() & SOUND_CONTEXT_EXCLUDE_COMBINE )
  1979. return false;
  1980. if ( pSound->SoundContext() & SOUND_CONTEXT_PLAYER_VEHICLE )
  1981. return false;
  1982. return BaseClass::QueryHearSound( pSound );
  1983. }
  1984. //-----------------------------------------------------------------------------
  1985. // This is a fairly bogus heuristic right now, but it works on 06a and 12 (sjb)
  1986. //
  1987. // Better options: Trace infinitely and check the material we hit for sky
  1988. // Put some leaf info in the BSP
  1989. // Use volumes in the levels? (yucky for designers)
  1990. //-----------------------------------------------------------------------------
  1991. // TODO: use this or nuke it!
  1992. void CNPC_Hunter::GatherIndoorOutdoorConditions()
  1993. {
  1994. // Check indoor/outdoor before calling base class, since base class calls our
  1995. // RangeAttackConditions() functions, and we want those functions to know
  1996. // whether we're indoors or out.
  1997. trace_t tr;
  1998. UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 40.0f * 12.0f ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  1999. if( tr.fraction < 1.0f )
  2000. {
  2001. SetCondition( COND_HUNTER_IS_INDOORS );
  2002. }
  2003. else
  2004. {
  2005. ClearCondition( COND_HUNTER_IS_INDOORS );
  2006. }
  2007. }
  2008. //-----------------------------------------------------------------------------
  2009. //-----------------------------------------------------------------------------
  2010. void CNPC_Hunter::BuildScheduleTestBits()
  2011. {
  2012. BaseClass::BuildScheduleTestBits();
  2013. if ( m_lifeState != LIFE_ALIVE )
  2014. {
  2015. return;
  2016. }
  2017. // Our range attack is uninterruptable for the first few seconds.
  2018. if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2, false ) && ( gpGlobals->curtime < m_flShootAllowInterruptTime ) )
  2019. {
  2020. ClearCustomInterruptConditions();
  2021. SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
  2022. }
  2023. else if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2, false ) && ( GetActivity() == ACT_TRANSITION ) )
  2024. {
  2025. // Don't stop unplanting just because we can range attack again.
  2026. ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
  2027. ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
  2028. }
  2029. else if ( !IsInLargeOutdoorMap() && IsCurSchedule( SCHED_HUNTER_FLANK_ENEMY, false ) && GetEnemy() != NULL )
  2030. {
  2031. if( HasCondition(COND_CAN_RANGE_ATTACK2) && m_flTimeSawEnemyAgain != HUNTER_SEE_ENEMY_TIME_INVALID )
  2032. {
  2033. if( (gpGlobals->curtime - m_flTimeSawEnemyAgain) >= 2.0f )
  2034. {
  2035. // When we're running flank behavior, wait a moment AFTER being able to see the enemy before
  2036. // breaking my schedule to range attack. This helps assure that the hunter gets well inside
  2037. // the room before stopping to attack. Otherwise the Hunter may stop immediately in the doorway
  2038. // and stop the progress of any hunters behind it.
  2039. SetCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
  2040. }
  2041. }
  2042. }
  2043. // If our enemy is anything but a striderbuster, drop everything if we see one.
  2044. if ( !IsStriderBuster( GetEnemy() ) )
  2045. {
  2046. SetCustomInterruptCondition( COND_HUNTER_SEE_STRIDERBUSTER );
  2047. }
  2048. // If we're not too busy, allow ourselves to ACK found enemy signals.
  2049. if ( !GetEnemy() )
  2050. {
  2051. SetCustomInterruptCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY );
  2052. }
  2053. // Interrupt everything if we need to dodge.
  2054. if ( !IsCurSchedule( SCHED_HUNTER_DODGE, false ) &&
  2055. !IsCurSchedule( SCHED_HUNTER_STAGGER, false ) &&
  2056. !IsCurSchedule( SCHED_ALERT_FACE_BESTSOUND, false ) )
  2057. {
  2058. SetCustomInterruptCondition( COND_HUNTER_INCOMING_VEHICLE );
  2059. SetCustomInterruptCondition( COND_HEAR_PHYSICS_DANGER );
  2060. SetCustomInterruptCondition( COND_HUNTER_FORCED_DODGE );
  2061. }
  2062. // Always interrupt on a flank command.
  2063. SetCustomInterruptCondition( COND_HUNTER_FORCED_FLANK_ENEMY );
  2064. // Always interrupt if staggered.
  2065. SetCustomInterruptCondition( COND_HUNTER_STAGGERED );
  2066. // Always interrupt if hit by a sticky bomb.
  2067. SetCustomInterruptCondition( COND_HUNTER_HIT_BY_STICKYBOMB );
  2068. }
  2069. //-----------------------------------------------------------------------------
  2070. //-----------------------------------------------------------------------------
  2071. static bool IsMovablePhysicsObject( CBaseEntity *pEntity )
  2072. {
  2073. return pEntity && pEntity->GetMoveType() == MOVETYPE_VPHYSICS && pEntity->VPhysicsGetObject() && pEntity->VPhysicsGetObject()->IsMoveable();
  2074. }
  2075. //-----------------------------------------------------------------------------
  2076. //-----------------------------------------------------------------------------
  2077. NPC_STATE CNPC_Hunter::SelectIdealState()
  2078. {
  2079. switch ( m_NPCState )
  2080. {
  2081. case NPC_STATE_COMBAT:
  2082. {
  2083. if ( GetEnemy() == NULL )
  2084. {
  2085. if ( !HasCondition( COND_ENEMY_DEAD ) && !hunter_disable_patrol.GetBool() )
  2086. {
  2087. // Lost track of my enemy. Patrol.
  2088. SetCondition( COND_HUNTER_SHOULD_PATROL );
  2089. }
  2090. return NPC_STATE_ALERT;
  2091. }
  2092. else if ( HasCondition( COND_ENEMY_DEAD ) )
  2093. {
  2094. // dvs: TODO: announce enemy kills?
  2095. //AnnounceEnemyKill(GetEnemy());
  2096. }
  2097. }
  2098. default:
  2099. {
  2100. return BaseClass::SelectIdealState();
  2101. }
  2102. }
  2103. return GetIdealState();
  2104. }
  2105. //-----------------------------------------------------------------------------
  2106. //-----------------------------------------------------------------------------
  2107. bool CNPC_Hunter::ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel )
  2108. {
  2109. // Must have a target
  2110. if ( !GetEnemy() )
  2111. return false;
  2112. // Don't check the distance once we start charging
  2113. if ( !bCheckForCancel && !hunter_charge_test.GetBool() )
  2114. {
  2115. float distance = ( startPos.AsVector2D() - endPos.AsVector2D() ).LengthSqr();
  2116. // Must be within our tolerance range
  2117. if ( ( distance < Square(HUNTER_CHARGE_MIN) ) || ( distance > Square(HUNTER_CHARGE_MAX) ) )
  2118. return false;
  2119. }
  2120. // FIXME: We'd like to exclude small physics objects from this check!
  2121. // We only need to hit the endpos with the edge of our bounding box
  2122. Vector vecDir = endPos - startPos;
  2123. VectorNormalize( vecDir );
  2124. float flWidth = WorldAlignSize().x * 0.5;
  2125. Vector vecTargetPos = endPos - (vecDir * flWidth);
  2126. // See if we can directly move there
  2127. AIMoveTrace_t moveTrace;
  2128. GetMoveProbe()->MoveLimit( NAV_GROUND, startPos, vecTargetPos, MASK_NPCSOLID_BRUSHONLY, GetEnemy(), &moveTrace );
  2129. // Draw the probe
  2130. if ( g_debug_hunter_charge.GetInt() == 1 )
  2131. {
  2132. Vector enemyDir = (vecTargetPos - startPos);
  2133. float enemyDist = VectorNormalize( enemyDir );
  2134. NDebugOverlay::BoxDirection( startPos, GetHullMins(), GetHullMaxs() + Vector(enemyDist,0,0), enemyDir, 0, 255, 0, 8, 1.0f );
  2135. }
  2136. // If we're not blocked, charge
  2137. if ( IsMoveBlocked( moveTrace ) )
  2138. {
  2139. // Don't allow it if it's too close to us
  2140. if ( UTIL_DistApprox( WorldSpaceCenter(), moveTrace.vEndPosition ) < HUNTER_CHARGE_MIN )
  2141. return false;
  2142. // Allow some special cases to not block us
  2143. if ( moveTrace.pObstruction != NULL )
  2144. {
  2145. // If we've hit the world, see if it's a cliff
  2146. if ( moveTrace.pObstruction == GetContainingEntity( INDEXENT(0) ) )
  2147. {
  2148. // Can't be too far above/below the target
  2149. if ( fabs( moveTrace.vEndPosition.z - vecTargetPos.z ) > StepHeight() )
  2150. return false;
  2151. // Allow it if we got pretty close
  2152. if ( UTIL_DistApprox( moveTrace.vEndPosition, vecTargetPos ) < 64 )
  2153. return true;
  2154. }
  2155. // Hit things that will take damage
  2156. if ( moveTrace.pObstruction->m_takedamage != DAMAGE_NO )
  2157. return true;
  2158. // Hit things that will move
  2159. if ( moveTrace.pObstruction->GetMoveType() == MOVETYPE_VPHYSICS )
  2160. return true;
  2161. }
  2162. return false;
  2163. }
  2164. float zDelta = endPos.z - moveTrace.vEndPosition.z;
  2165. if ( fabsf(zDelta) > GetHullHeight() * 0.7)
  2166. {
  2167. return false;
  2168. }
  2169. return true;
  2170. }
  2171. //-----------------------------------------------------------------------------
  2172. //-----------------------------------------------------------------------------
  2173. bool CNPC_Hunter::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *pSourceEnt)
  2174. {
  2175. if ( ( pSourceEnt != this ) && ( interactionType == g_interactionHunterFoundEnemy ) )
  2176. {
  2177. SetCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY );
  2178. return true;
  2179. }
  2180. return BaseClass::HandleInteraction( interactionType, data, pSourceEnt );
  2181. }
  2182. //-----------------------------------------------------------------------------
  2183. //-----------------------------------------------------------------------------
  2184. int CNPC_Hunter::SelectCombatSchedule()
  2185. {
  2186. // If we're here with no enemy, patrol and hope we find one.
  2187. CBaseEntity *pEnemy = GetEnemy();
  2188. if ( pEnemy == NULL )
  2189. {
  2190. if ( !hunter_disable_patrol.GetBool() )
  2191. return SCHED_HUNTER_PATROL_RUN;
  2192. else
  2193. return SCHED_ALERT_STAND;
  2194. }
  2195. if ( hunter_flechette_test.GetBool() )
  2196. {
  2197. if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
  2198. {
  2199. return SCHED_HUNTER_RANGE_ATTACK2;
  2200. }
  2201. return SCHED_COMBAT_FACE;
  2202. }
  2203. bool bStriderBuster = IsStriderBuster( pEnemy );
  2204. if ( bStriderBuster )
  2205. {
  2206. if ( gpGlobals->curtime - CAI_HunterEscortBehavior::gm_flLastDefendSound > 10.0 )
  2207. {
  2208. EmitSound( "NPC_Hunter.DefendStrider" );
  2209. CAI_HunterEscortBehavior::gm_flLastDefendSound = gpGlobals->curtime;
  2210. }
  2211. if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) || HasCondition( COND_NOT_FACING_ATTACK ) )
  2212. {
  2213. return SCHED_HUNTER_RANGE_ATTACK2;
  2214. }
  2215. return SCHED_ESTABLISH_LINE_OF_FIRE;
  2216. }
  2217. // Certain behaviors, like flanking and melee attacks, only make sense on visible,
  2218. // corporeal enemies (NOT bullseyes).
  2219. bool bIsCorporealEnemy = IsCorporealEnemy( pEnemy );
  2220. // Take a quick swipe at our enemy if able to do so.
  2221. if ( bIsCorporealEnemy && HasCondition( COND_CAN_MELEE_ATTACK1 ) )
  2222. {
  2223. return SCHED_HUNTER_MELEE_ATTACK1;
  2224. }
  2225. // React to newly acquired enemies.
  2226. if ( bIsCorporealEnemy && HasCondition( COND_NEW_ENEMY ) )
  2227. {
  2228. AI_EnemyInfo_t *pEnemyInfo = GetEnemies()->Find( pEnemy );
  2229. if ( GetSquad() && pEnemyInfo && ( pEnemyInfo->timeFirstSeen == pEnemyInfo->timeAtFirstHand ) )
  2230. {
  2231. GetSquad()->BroadcastInteraction( g_interactionHunterFoundEnemy, NULL, this );
  2232. // First contact for my squad.
  2233. return SCHED_HUNTER_FOUND_ENEMY;
  2234. }
  2235. }
  2236. if ( HasCondition( COND_HUNTER_SQUADMATE_FOUND_ENEMY ) )
  2237. {
  2238. // A squadmate found an enemy. Respond to their call.
  2239. return SCHED_HUNTER_FOUND_ENEMY_ACK;
  2240. }
  2241. // Fire a flechette volley. Ignore squad slots if we're attacking a striderbuster.
  2242. // See if there is an opportunity to charge.
  2243. if ( !bStriderBuster && bIsCorporealEnemy && HasCondition( COND_HUNTER_CAN_CHARGE_ENEMY ) )
  2244. {
  2245. if ( hunter_charge_test.GetBool() || random->RandomInt( 1, 100 ) < hunter_charge_pct.GetInt() )
  2246. {
  2247. if ( hunter_charge_test.GetBool() || OccupyStrategySlot( SQUAD_SLOT_HUNTER_CHARGE ) )
  2248. {
  2249. return SCHED_HUNTER_CHARGE_ENEMY;
  2250. }
  2251. }
  2252. }
  2253. if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
  2254. {
  2255. if ( bStriderBuster || CountRangedAttackers() < hunter_flechette_max_concurrent_volleys.GetInt() )
  2256. {
  2257. DelayRangedAttackers( hunter_flechette_volley_start_min_delay.GetFloat(), hunter_flechette_volley_start_max_delay.GetFloat(), true );
  2258. return SCHED_HUNTER_RANGE_ATTACK2;
  2259. }
  2260. }
  2261. if ( pEnemy->GetGroundEntity() == this )
  2262. {
  2263. return SCHED_HUNTER_MELEE_ATTACK1;
  2264. }
  2265. if ( HasCondition( COND_TOO_CLOSE_TO_ATTACK ) )
  2266. {
  2267. return SCHED_MOVE_AWAY_FROM_ENEMY;
  2268. }
  2269. // Sidestep every so often if my enemy is nearby and facing me.
  2270. /*
  2271. if ( gpGlobals->curtime > m_flNextSideStepTime )
  2272. {
  2273. if ( HasCondition( COND_ENEMY_FACING_ME ) && ( UTIL_DistApprox( GetEnemy()->GetAbsOrigin(), GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST ) )
  2274. {
  2275. m_flNextSideStepTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
  2276. return SCHED_HUNTER_SIDESTEP;
  2277. }
  2278. }
  2279. */
  2280. if ( HasCondition( COND_HEAVY_DAMAGE ) && ( gpGlobals->curtime > m_flNextSideStepTime ) )
  2281. {
  2282. m_flNextSideStepTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
  2283. return SCHED_HUNTER_SIDESTEP;
  2284. }
  2285. if ( !bStriderBuster && bIsCorporealEnemy )
  2286. {
  2287. if ( HasCondition( COND_HUNTER_CAN_CHARGE_ENEMY ) )
  2288. {
  2289. if ( OccupyStrategySlot( SQUAD_SLOT_HUNTER_CHARGE ) )
  2290. {
  2291. return SCHED_HUNTER_CHARGE_ENEMY;
  2292. }
  2293. /*
  2294. else
  2295. {
  2296. return SCHED_HUNTER_SIDESTEP;
  2297. }
  2298. */
  2299. }
  2300. // Try to be a flanker.
  2301. if ( ( NumHuntersInMySquad() > 1 ) && OccupyStrategySlotRange( SQUAD_SLOT_HUNTER_FLANK_FIRST, SQUAD_SLOT_HUNTER_FLANK_LAST ) )
  2302. {
  2303. return SCHED_HUNTER_FLANK_ENEMY;
  2304. }
  2305. }
  2306. // Can't see my enemy.
  2307. if ( HasCondition( COND_ENEMY_OCCLUDED ) || HasCondition( COND_ENEMY_TOO_FAR ) || HasCondition( COND_TOO_FAR_TO_ATTACK ) || HasCondition( COND_NOT_FACING_ATTACK ) )
  2308. {
  2309. return SCHED_HUNTER_CHASE_ENEMY;
  2310. }
  2311. if ( HasCondition( COND_HUNTER_CANT_PLANT ) )
  2312. {
  2313. return SCHED_ESTABLISH_LINE_OF_FIRE;
  2314. }
  2315. //if ( HasCondition( COND_ENEMY_OCCLUDED ) && IsCurSchedule( SCHED_RANGE_ATTACK1, false ) )
  2316. //{
  2317. // return SCHED_HUNTER_COMBAT_FACE;
  2318. //}
  2319. return SCHED_HUNTER_CHANGE_POSITION;
  2320. }
  2321. //-----------------------------------------------------------------------------
  2322. //-----------------------------------------------------------------------------
  2323. int CNPC_Hunter::SelectSiegeSchedule()
  2324. {
  2325. bool bHasEnemy = (GetEnemy() != NULL);
  2326. if( bHasEnemy )
  2327. {
  2328. // We have an enemy, so we should be making every effort to attack it.
  2329. if( !HasCondition(COND_SEE_ENEMY) || !HasCondition(COND_CAN_RANGE_ATTACK2) )
  2330. return SCHED_ESTABLISH_LINE_OF_FIRE;
  2331. if( HasCondition(COND_CAN_RANGE_ATTACK2) )
  2332. return SCHED_HUNTER_RANGE_ATTACK2;
  2333. return SCHED_HUNTER_SIEGE_STAND;
  2334. }
  2335. else
  2336. {
  2337. // Otherwise we are loitering in siege mode. Break line of sight with the player
  2338. // if they expose our position.
  2339. if( HasCondition( COND_SEE_PLAYER ) )
  2340. return SCHED_HUNTER_CHANGE_POSITION_SIEGE;
  2341. }
  2342. return SCHED_HUNTER_SIEGE_STAND;
  2343. }
  2344. //-----------------------------------------------------------------------------
  2345. //-----------------------------------------------------------------------------
  2346. int CNPC_Hunter::SelectSchedule()
  2347. {
  2348. if ( hunter_stand_still.GetBool() )
  2349. {
  2350. m_bPlanted = false;
  2351. return SCHED_IDLE_STAND;
  2352. }
  2353. if ( HasCondition( COND_HUNTER_FORCED_DODGE ) )
  2354. return SCHED_HUNTER_DODGE;
  2355. if ( HasCondition( COND_HUNTER_NEW_HINTGROUP ) || ( GetHintGroup() != NULL_STRING && m_CheckHintGroupTimer.Expired() ) )
  2356. {
  2357. CAI_Hint *pHint;
  2358. CHintCriteria criteria;
  2359. criteria.SetGroup( GetHintGroup() );
  2360. criteria.SetFlag( bits_HINT_NODE_NEAREST );
  2361. if ( HasCondition( COND_HUNTER_NEW_HINTGROUP ) )
  2362. {
  2363. ClearCondition( COND_HUNTER_NEW_HINTGROUP );
  2364. if ( GetEnemy() )
  2365. {
  2366. pHint = CAI_HintManager::FindHint( NULL, GetEnemy()->GetAbsOrigin(), criteria );
  2367. }
  2368. else
  2369. {
  2370. pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
  2371. }
  2372. if ( pHint )
  2373. {
  2374. pHint->Lock( this );
  2375. }
  2376. }
  2377. else
  2378. {
  2379. pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
  2380. if ( pHint )
  2381. {
  2382. if ( (pHint->GetAbsOrigin() - GetAbsOrigin()).Length2DSqr() < Square( 20*12 ) )
  2383. {
  2384. m_CheckHintGroupTimer.Set( 5 );
  2385. pHint = NULL;
  2386. }
  2387. else
  2388. {
  2389. m_CheckHintGroupTimer.Set( 15 );
  2390. }
  2391. }
  2392. }
  2393. if ( pHint )
  2394. {
  2395. SetHintNode( pHint );
  2396. return SCHED_HUNTER_GOTO_HINT;
  2397. }
  2398. }
  2399. if ( HasCondition( COND_HUNTER_INCOMING_VEHICLE ) )
  2400. {
  2401. if ( m_RundownDelay.Expired() )
  2402. {
  2403. int iRundownCounter = 0;
  2404. if ( GetSquad() )
  2405. {
  2406. GetSquad()->GetSquadData( HUNTER_RUNDOWN_SQUADDATA, &iRundownCounter );
  2407. }
  2408. if ( iRundownCounter % 2 == 0 )
  2409. {
  2410. for ( int i = 0; i < g_Hunters.Count(); i++ )
  2411. {
  2412. if ( g_Hunters[i] != this )
  2413. {
  2414. g_Hunters[i]->m_RundownDelay.Set( 3 );
  2415. g_Hunters[i]->m_IgnoreVehicleTimer.Force();
  2416. }
  2417. }
  2418. m_IgnoreVehicleTimer.Set( hunter_dodge_warning.GetFloat() * 4 );
  2419. if ( hunter_dodge_debug.GetBool() )
  2420. {
  2421. Msg( "Hunter %d rundown\n", entindex() );
  2422. }
  2423. if ( HasCondition( COND_SEE_ENEMY ) )
  2424. {
  2425. if ( m_bPlanted && HasCondition( COND_CAN_RANGE_ATTACK2 ) )
  2426. {
  2427. return SCHED_HUNTER_RANGE_ATTACK2;
  2428. }
  2429. else if ( random->RandomInt( 0, 1 ) )
  2430. {
  2431. return SCHED_HUNTER_CHARGE_ENEMY;
  2432. }
  2433. else
  2434. {
  2435. return SCHED_MOVE_AWAY;
  2436. }
  2437. }
  2438. else
  2439. {
  2440. SetTarget( UTIL_GetLocalPlayer() );
  2441. return SCHED_TARGET_FACE;
  2442. }
  2443. }
  2444. else
  2445. {
  2446. if ( hunter_dodge_debug.GetBool() )
  2447. {
  2448. Msg( "Hunter %d safe from rundown\n", entindex() );
  2449. }
  2450. for ( int i = 0; i < g_Hunters.Count(); i++ )
  2451. {
  2452. g_Hunters[i]->m_RundownDelay.Set( 4 );
  2453. g_Hunters[i]->m_IgnoreVehicleTimer.Force();
  2454. }
  2455. if ( GetSquad() )
  2456. {
  2457. GetSquad()->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, iRundownCounter + 1 );
  2458. }
  2459. }
  2460. }
  2461. if ( HasCondition( COND_SEE_ENEMY ) )
  2462. {
  2463. if ( hunter_dodge_debug.GetBool() )
  2464. {
  2465. Msg( "Hunter %d try dodge\n", entindex() );
  2466. }
  2467. return SCHED_HUNTER_DODGE;
  2468. }
  2469. else
  2470. {
  2471. SetTarget( UTIL_GetLocalPlayer() );
  2472. return SCHED_TARGET_FACE;
  2473. }
  2474. CSound *pBestSound = GetBestSound( SOUND_PHYSICS_DANGER );
  2475. if ( pBestSound && ( pBestSound->SoundContext() & SOUND_CONTEXT_PLAYER_VEHICLE ) )
  2476. {
  2477. return SCHED_ALERT_FACE_BESTSOUND;
  2478. }
  2479. }
  2480. if ( HasCondition( COND_HUNTER_FORCED_FLANK_ENEMY ) )
  2481. {
  2482. return SCHED_HUNTER_FLANK_ENEMY;
  2483. }
  2484. if ( HasCondition( COND_HUNTER_STAGGERED ) /*|| HasCondition( COND_HUNTER_HIT_BY_STICKYBOMB )*/ )
  2485. {
  2486. return SCHED_HUNTER_STAGGER;
  2487. }
  2488. // Now that we're past all of the forced reactions to things, if we're running the siege
  2489. // behavior, go pick an appropriate siege schedule UNLESS we have an enemy. If we have
  2490. // an enemy, we should focus on attacking that enemy.
  2491. if( IsUsingSiegeTargets() )
  2492. {
  2493. return SelectSiegeSchedule();
  2494. }
  2495. // back away if there's a magnade glued to my head.
  2496. if ( hunter_retreat_striderbusters.GetBool() /*&& GetEnemy() && ( GetEnemy()->IsPlayer() )*/
  2497. && (m_hAttachedBusters.Count() > 0)
  2498. && m_fCorneredTimer < gpGlobals->curtime)
  2499. {
  2500. return SCHED_HUNTER_TAKE_COVER_FROM_ENEMY;
  2501. }
  2502. if ( !BehaviorSelectSchedule() )
  2503. {
  2504. switch ( GetState() )
  2505. {
  2506. case NPC_STATE_IDLE:
  2507. {
  2508. return SCHED_HUNTER_PATROL;
  2509. }
  2510. case NPC_STATE_ALERT:
  2511. {
  2512. if ( HasCondition( COND_HUNTER_SHOULD_PATROL ) )
  2513. return SCHED_HUNTER_PATROL;
  2514. break;
  2515. }
  2516. case NPC_STATE_COMBAT:
  2517. {
  2518. return SelectCombatSchedule();
  2519. }
  2520. }
  2521. }
  2522. return BaseClass::SelectSchedule();
  2523. }
  2524. //-----------------------------------------------------------------------------
  2525. //-----------------------------------------------------------------------------
  2526. int CNPC_Hunter::TranslateSchedule( int scheduleType )
  2527. {
  2528. switch ( scheduleType )
  2529. {
  2530. case SCHED_RANGE_ATTACK1:
  2531. {
  2532. return SCHED_HUNTER_RANGE_ATTACK1;
  2533. }
  2534. case SCHED_RANGE_ATTACK2:
  2535. case SCHED_HUNTER_RANGE_ATTACK2:
  2536. {
  2537. if ( scheduleType == SCHED_RANGE_ATTACK2 )
  2538. {
  2539. Msg( "HUNTER IGNORING SQUAD SLOTS\n" );
  2540. }
  2541. if ( IsStriderBuster( GetEnemy() ) )
  2542. {
  2543. // Attack as FAST as possible. The point is to shoot down the buster.
  2544. return SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER;
  2545. }
  2546. return SCHED_HUNTER_RANGE_ATTACK2;
  2547. }
  2548. case SCHED_MELEE_ATTACK1:
  2549. {
  2550. return SCHED_HUNTER_MELEE_ATTACK1;
  2551. }
  2552. case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK:
  2553. {
  2554. return SCHED_HUNTER_CHANGE_POSITION;
  2555. }
  2556. case SCHED_ALERT_STAND:
  2557. {
  2558. if ( !hunter_disable_patrol.GetBool() )
  2559. return SCHED_HUNTER_PATROL_RUN;
  2560. break;
  2561. }
  2562. case SCHED_COMBAT_FACE:
  2563. {
  2564. return SCHED_HUNTER_COMBAT_FACE;
  2565. }
  2566. case SCHED_HUNTER_PATROL:
  2567. {
  2568. if ( hunter_disable_patrol.GetBool() )
  2569. {
  2570. return SCHED_IDLE_STAND;
  2571. }
  2572. break;
  2573. }
  2574. }
  2575. return BaseClass::TranslateSchedule( scheduleType );
  2576. }
  2577. //-----------------------------------------------------------------------------
  2578. // catch blockage while escaping magnade
  2579. //-----------------------------------------------------------------------------
  2580. void CNPC_Hunter::TaskFail( AI_TaskFailureCode_t code )
  2581. {
  2582. if ( IsCurSchedule( SCHED_HUNTER_TAKE_COVER_FROM_ENEMY, false ) && ( code == FAIL_NO_ROUTE_BLOCKED ) )
  2583. {
  2584. // cornered!
  2585. if ( m_fCorneredTimer < gpGlobals->curtime )
  2586. {
  2587. m_fCorneredTimer = gpGlobals->curtime + 6.0f;
  2588. }
  2589. }
  2590. BaseClass::TaskFail( code );
  2591. }
  2592. //-----------------------------------------------------------------------------
  2593. // The player is speeding toward us in a vehicle! Find a good activity for dodging.
  2594. //-----------------------------------------------------------------------------
  2595. void CNPC_Hunter::TaskFindDodgeActivity()
  2596. {
  2597. if ( GetEnemy() == NULL )
  2598. {
  2599. TaskFail( "No enemy to dodge" );
  2600. return;
  2601. }
  2602. Vector vecUp;
  2603. Vector vecRight;
  2604. GetVectors( NULL, &vecRight, &vecUp );
  2605. // TODO: find most perpendicular 8-way dodge when we get the anims
  2606. Vector vecEnemyDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
  2607. //Vector vecDir = CrossProduct( vecEnemyDir, vecUp );
  2608. VectorNormalize( vecEnemyDir );
  2609. if ( fabs( DotProduct( vecEnemyDir, vecRight ) ) > 0.7 )
  2610. {
  2611. TaskFail( "Can't dodge, enemy approaching perpendicularly" );
  2612. return;
  2613. }
  2614. // Check left or right randomly first.
  2615. bool bDodgeLeft = false;
  2616. CBaseEntity *pVehicle = GetEnemyVehicle();
  2617. if ( pVehicle )
  2618. {
  2619. Ray_t enemyRay;
  2620. Ray_t perpendicularRay;
  2621. enemyRay.Init( pVehicle->GetAbsOrigin(), pVehicle->GetAbsOrigin() + pVehicle->GetSmoothedVelocity() );
  2622. Vector vPerpendicularPt = vecEnemyDir;
  2623. vPerpendicularPt.y = -vPerpendicularPt.y;
  2624. perpendicularRay.Init( GetAbsOrigin(), GetAbsOrigin() + vPerpendicularPt );
  2625. enemyRay.m_Start.z = enemyRay.m_Delta.z = enemyRay.m_StartOffset.z;
  2626. perpendicularRay.m_Start.z = perpendicularRay.m_Delta.z = perpendicularRay.m_StartOffset.z;
  2627. float t, s;
  2628. IntersectRayWithRay( perpendicularRay, enemyRay, t, s );
  2629. if ( t > 0 )
  2630. {
  2631. bDodgeLeft = true;
  2632. }
  2633. }
  2634. else if ( random->RandomInt( 0, 1 ) == 0 )
  2635. {
  2636. bDodgeLeft = true;
  2637. }
  2638. bool bFoundDir = false;
  2639. int nTries = 0;
  2640. while ( !bFoundDir && ( nTries < 2 ) )
  2641. {
  2642. // Pick a dodge activity to try.
  2643. if ( bDodgeLeft )
  2644. {
  2645. m_eDodgeActivity = ACT_HUNTER_DODGEL;
  2646. }
  2647. else
  2648. {
  2649. m_eDodgeActivity = ACT_HUNTER_DODGER;
  2650. }
  2651. // See where the dodge will put us.
  2652. Vector vecLocalDelta;
  2653. int nSeq = SelectWeightedSequence( m_eDodgeActivity );
  2654. GetSequenceLinearMotion( nSeq, &vecLocalDelta );
  2655. // Transform the sequence delta into local space.
  2656. matrix3x4_t fRotateMatrix;
  2657. AngleMatrix( GetLocalAngles(), fRotateMatrix );
  2658. Vector vecDelta;
  2659. VectorRotate( vecLocalDelta, fRotateMatrix, vecDelta );
  2660. // Trace a bit high so this works better on uneven terrain.
  2661. Vector testHullMins = GetHullMins();
  2662. testHullMins.z += ( StepHeight() * 2 );
  2663. // See if all is clear in that direction.
  2664. trace_t tr;
  2665. HunterTraceHull_SkipPhysics( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, GetCollisionGroup(), &tr, VPhysicsGetObject()->GetMass() * 0.5f );
  2666. // TODO: dodge anyway if we'll make it a certain percentage of the way through the dodge?
  2667. if ( tr.fraction == 1.0f )
  2668. {
  2669. //NDebugOverlay::SweptBox( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), QAngle( 0, 0, 0 ), 0, 255, 0, 128, 5 );
  2670. bFoundDir = true;
  2671. TaskComplete();
  2672. }
  2673. else
  2674. {
  2675. //NDebugOverlay::SweptBox( GetAbsOrigin(), GetAbsOrigin() + vecDelta, testHullMins, GetHullMaxs(), QAngle( 0, 0, 0 ), 255, 0, 0, 128, 5 );
  2676. nTries++;
  2677. bDodgeLeft = !bDodgeLeft;
  2678. }
  2679. }
  2680. if ( nTries < 2 )
  2681. {
  2682. TaskComplete();
  2683. }
  2684. else
  2685. {
  2686. TaskFail( "Couldn't find dodge position\n" );
  2687. }
  2688. }
  2689. //-----------------------------------------------------------------------------
  2690. //-----------------------------------------------------------------------------
  2691. void CNPC_Hunter::StartTask( const Task_t *pTask )
  2692. {
  2693. switch ( pTask->iTask )
  2694. {
  2695. case TASK_HUNTER_FINISH_RANGE_ATTACK:
  2696. {
  2697. if( GetEnemy() != NULL && GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
  2698. {
  2699. // Just finished shooting at Alyx! So forget her for a little while and get back on the player
  2700. // !!!LATER - make sure there's someone else in enemy memory to go bother.
  2701. GetEnemies()->SetTimeValidEnemy( GetEnemy(), gpGlobals->curtime + 10.0f );
  2702. }
  2703. if( m_hCurrentSiegeTarget )
  2704. {
  2705. // We probably just fired at our siege target, so dump it.
  2706. KillCurrentSiegeTarget();
  2707. }
  2708. TaskComplete();
  2709. }
  2710. case TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY:
  2711. {
  2712. ChainStartTask( TASK_WAIT_FOR_MOVEMENT, pTask->flTaskData );
  2713. break;
  2714. }
  2715. case TASK_HUNTER_BEGIN_FLANK:
  2716. {
  2717. if ( IsInSquad() && GetSquad()->NumMembers() > 1 )
  2718. {
  2719. // Flank relative to the other shooter in our squad.
  2720. // If there's no other shooter, just flank relative to any squad member.
  2721. AISquadIter_t iter;
  2722. CAI_BaseNPC *pNPC = GetSquad()->GetFirstMember( &iter );
  2723. while ( pNPC == this )
  2724. {
  2725. pNPC = GetSquad()->GetNextMember( &iter );
  2726. }
  2727. m_vSavePosition = pNPC->GetAbsOrigin();
  2728. }
  2729. else
  2730. {
  2731. // Flank relative to our current position.
  2732. m_vSavePosition = GetAbsOrigin();
  2733. }
  2734. TaskComplete();
  2735. break;
  2736. }
  2737. case TASK_HUNTER_ANNOUNCE_FLANK:
  2738. {
  2739. EmitSound( "NPC_Hunter.FlankAnnounce" );
  2740. TaskComplete();
  2741. break;
  2742. }
  2743. case TASK_HUNTER_DODGE:
  2744. {
  2745. if ( hunter_dodge_debug. GetBool() )
  2746. {
  2747. Msg( "Hunter %d dodging\n", entindex() );
  2748. }
  2749. SetIdealActivity( m_eDodgeActivity );
  2750. break;
  2751. }
  2752. // Guarantee a certain delay between volleys. If we aren't already planted,
  2753. // the plant transition animation will take care of that.
  2754. case TASK_HUNTER_PRE_RANGE_ATTACK2:
  2755. {
  2756. if ( !m_bPlanted || ( GetEnemy() && IsStriderBuster( GetEnemy() ) ) )
  2757. {
  2758. TaskComplete();
  2759. }
  2760. else
  2761. {
  2762. SetIdealActivity( ACT_HUNTER_ANGRY );
  2763. }
  2764. break;
  2765. }
  2766. case TASK_HUNTER_SHOOT_COMMIT:
  2767. {
  2768. // We're committing to shooting. Don't allow interrupts until after we've shot a bit (see TASK_RANGE_ATTACK1).
  2769. m_flShootAllowInterruptTime = gpGlobals->curtime + 100.0f;
  2770. TaskComplete();
  2771. break;
  2772. }
  2773. case TASK_RANGE_ATTACK2:
  2774. {
  2775. if ( GetEnemy() )
  2776. {
  2777. bool bIsBuster = IsStriderBuster( GetEnemy() );
  2778. if ( bIsBuster )
  2779. {
  2780. AddFacingTarget( GetEnemy(), GetEnemy()->GetAbsOrigin() + GetEnemy()->GetSmoothedVelocity() * .5, 1.0, 0.8 );
  2781. }
  2782. // Start the firing sound.
  2783. //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  2784. //controller.SoundChangeVolume( m_pGunFiringSound, 1.0, hunter_first_flechette_delay.GetFloat() );
  2785. SetIdealActivity( ACT_RANGE_ATTACK2 );
  2786. // Decide how many shots to fire.
  2787. int nShots = hunter_flechette_volley_size.GetInt();
  2788. if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) )
  2789. {
  2790. nShots--;
  2791. }
  2792. // Decide when to fire the first shot.
  2793. float initialDelay = hunter_first_flechette_delay.GetFloat();
  2794. if ( bIsBuster )
  2795. {
  2796. initialDelay = 0; //*= 0.5;
  2797. }
  2798. BeginVolley( nShots, gpGlobals->curtime + initialDelay );
  2799. // In case we need to miss on purpose, pick a direction now.
  2800. m_bMissLeft = false;
  2801. if ( random->RandomInt( 0, 1 ) == 0 )
  2802. {
  2803. m_bMissLeft = true;
  2804. }
  2805. LockBothEyes( initialDelay + ( nShots * hunter_flechette_delay.GetFloat() ) );
  2806. }
  2807. else
  2808. {
  2809. TaskFail( FAIL_NO_ENEMY );
  2810. }
  2811. break;
  2812. }
  2813. case TASK_HUNTER_STAGGER:
  2814. {
  2815. // Stagger in the direction the impact force would push us.
  2816. VMatrix worldToLocalRotation = EntityToWorldTransform();
  2817. Vector vecLocalStaggerDir = worldToLocalRotation.InverseTR().ApplyRotation( m_vecStaggerDir );
  2818. float flStaggerYaw = VecToYaw( vecLocalStaggerDir );
  2819. SetPoseParameter( gm_nStaggerYawPoseParam, flStaggerYaw );
  2820. // Go straight there!
  2821. SetActivity( ACT_RESET );
  2822. SetActivity( ( Activity )ACT_HUNTER_STAGGER );
  2823. break;
  2824. }
  2825. case TASK_MELEE_ATTACK1:
  2826. {
  2827. SetLastAttackTime( gpGlobals->curtime );
  2828. if ( GetEnemy() && GetEnemy()->IsPlayer() )
  2829. {
  2830. ResetIdealActivity( ( Activity )ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER );
  2831. }
  2832. else
  2833. {
  2834. ResetIdealActivity( ACT_MELEE_ATTACK1 );
  2835. }
  2836. break;
  2837. }
  2838. case TASK_HUNTER_CORNERED_TIMER:
  2839. {
  2840. m_fCorneredTimer = gpGlobals->curtime + pTask->flTaskData;
  2841. break;
  2842. }
  2843. case TASK_HUNTER_FIND_SIDESTEP_POSITION:
  2844. {
  2845. if ( GetEnemy() == NULL )
  2846. {
  2847. TaskFail( "No enemy to sidestep" );
  2848. }
  2849. else
  2850. {
  2851. Vector vecUp;
  2852. GetVectors( NULL, NULL, &vecUp );
  2853. Vector vecEnemyDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
  2854. Vector vecDir = CrossProduct( vecEnemyDir, vecUp );
  2855. VectorNormalize( vecDir );
  2856. // Sidestep left or right randomly.
  2857. if ( random->RandomInt( 0, 1 ) == 0 )
  2858. {
  2859. vecDir *= -1;
  2860. }
  2861. // Start high and then trace down so that it works on uneven terrain.
  2862. Vector vecPos = GetAbsOrigin() + Vector( 0, 0, 64 ) + random->RandomFloat( 120, 200 ) * vecDir;
  2863. // Try to find the ground at the sidestep position.
  2864. trace_t tr;
  2865. UTIL_TraceLine( vecPos, vecPos + Vector( 0, 0, -128 ), MASK_NPCSOLID, NULL, COLLISION_GROUP_NONE, &tr );
  2866. if ( tr.fraction < 1.0f )
  2867. {
  2868. //NDebugOverlay::Line( vecPos, tr.endpos, 0, 255, 0, true, 10 );
  2869. m_vSavePosition = tr.endpos;
  2870. TaskComplete();
  2871. }
  2872. else
  2873. {
  2874. TaskFail( "Couldn't find sidestep position\n" );
  2875. }
  2876. }
  2877. break;
  2878. }
  2879. case TASK_HUNTER_FIND_DODGE_POSITION:
  2880. {
  2881. TaskFindDodgeActivity();
  2882. break;
  2883. }
  2884. case TASK_HUNTER_CHARGE:
  2885. {
  2886. SetIdealActivity( ( Activity )ACT_HUNTER_CHARGE_START );
  2887. break;
  2888. }
  2889. case TASK_HUNTER_CHARGE_DELAY:
  2890. {
  2891. m_flNextChargeTime = gpGlobals->curtime + pTask->flTaskData;
  2892. TaskComplete();
  2893. break;
  2894. }
  2895. case TASK_DIE:
  2896. {
  2897. GetNavigator()->StopMoving();
  2898. ResetActivity();
  2899. SetIdealActivity( GetDeathActivity() );
  2900. m_lifeState = LIFE_DYING;
  2901. break;
  2902. }
  2903. //case TASK_HUNTER_END_FLANK:
  2904. //{
  2905. //
  2906. //}
  2907. default:
  2908. {
  2909. BaseClass::StartTask( pTask );
  2910. break;
  2911. }
  2912. }
  2913. }
  2914. //-----------------------------------------------------------------------------
  2915. //-----------------------------------------------------------------------------
  2916. void CNPC_Hunter::RunTask( const Task_t *pTask )
  2917. {
  2918. switch ( pTask->iTask )
  2919. {
  2920. case TASK_HUNTER_PRE_RANGE_ATTACK2:
  2921. {
  2922. if ( IsActivityFinished() )
  2923. {
  2924. TaskComplete();
  2925. }
  2926. break;
  2927. }
  2928. case TASK_RANGE_ATTACK2:
  2929. {
  2930. if( !hunter_hate_thrown_striderbusters.GetBool() && GetEnemy() != NULL && IsStriderBuster( GetEnemy() ) )
  2931. {
  2932. if( !IsValidEnemy(GetEnemy()) )
  2933. {
  2934. TaskFail("No longer hate this StriderBuster");
  2935. }
  2936. }
  2937. bool bIsBuster = IsStriderBuster( GetEnemy() );
  2938. if ( bIsBuster )
  2939. {
  2940. Vector vFuturePosition = GetEnemy()->GetAbsOrigin() + GetEnemy()->GetSmoothedVelocity() * .3;
  2941. AddFacingTarget( GetEnemy(), vFuturePosition, 1.0, 0.8 );
  2942. Vector2D vToFuturePositon = ( vFuturePosition.AsVector2D() - GetAbsOrigin().AsVector2D() );
  2943. vToFuturePositon.NormalizeInPlace();
  2944. Vector2D facingDir = BodyDirection2D().AsVector2D();
  2945. float flDot = DotProduct2D( vToFuturePositon, facingDir );
  2946. if ( flDot < .4 )
  2947. {
  2948. GetMotor()->SetIdealYawToTarget( vFuturePosition );
  2949. GetMotor()->UpdateYaw();
  2950. break;
  2951. }
  2952. }
  2953. if ( gpGlobals->curtime >= m_flNextFlechetteTime )
  2954. {
  2955. // Must have an enemy and a shot queued up.
  2956. bool bDone = false;
  2957. if ( GetEnemy() != NULL && m_nFlechettesQueued > 0 )
  2958. {
  2959. if ( ShootFlechette( GetEnemy(), false ) )
  2960. {
  2961. m_nClampedShots++;
  2962. }
  2963. else
  2964. {
  2965. m_nClampedShots = 0;
  2966. }
  2967. m_nFlechettesQueued--;
  2968. // If we fired three or more clamped shots in a row, call it quits so we don't look dumb.
  2969. if ( ( m_nClampedShots >= 3 ) || ( m_nFlechettesQueued == 0 ) )
  2970. {
  2971. bDone = true;
  2972. }
  2973. else
  2974. {
  2975. // More shooting to do. Schedule our next flechette.
  2976. m_flNextFlechetteTime = gpGlobals->curtime + hunter_flechette_delay.GetFloat();
  2977. }
  2978. }
  2979. else
  2980. {
  2981. bDone = true;
  2982. }
  2983. if ( bDone )
  2984. {
  2985. // Stop the firing sound.
  2986. //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  2987. //controller.SoundChangeVolume( m_pGunFiringSound, 0.0f, 0.1f );
  2988. DelayRangedAttackers( hunter_flechette_volley_end_min_delay.GetFloat(), hunter_flechette_volley_end_max_delay.GetFloat(), true );
  2989. TaskComplete();
  2990. }
  2991. }
  2992. break;
  2993. }
  2994. case TASK_GET_PATH_TO_ENEMY_LOS:
  2995. {
  2996. ChainRunTask( TASK_GET_PATH_TO_ENEMY_LKP_LOS, pTask->flTaskData );
  2997. break;
  2998. }
  2999. case TASK_HUNTER_DODGE:
  3000. {
  3001. AutoMovement();
  3002. if ( IsActivityFinished() )
  3003. {
  3004. TaskComplete();
  3005. }
  3006. break;
  3007. }
  3008. case TASK_HUNTER_CORNERED_TIMER:
  3009. {
  3010. TaskComplete();
  3011. break;
  3012. }
  3013. case TASK_HUNTER_STAGGER:
  3014. {
  3015. if ( IsActivityFinished() )
  3016. {
  3017. TaskComplete();
  3018. }
  3019. break;
  3020. }
  3021. case TASK_HUNTER_CHARGE:
  3022. {
  3023. Activity eActivity = GetActivity();
  3024. // See if we're trying to stop after hitting/missing our target
  3025. if ( eActivity == ACT_HUNTER_CHARGE_STOP || eActivity == ACT_HUNTER_CHARGE_CRASH )
  3026. {
  3027. if ( IsActivityFinished() )
  3028. {
  3029. m_flNextChargeTime = gpGlobals->curtime + hunter_charge_min_delay.GetFloat() + random->RandomFloat( 0, 2.5 ) + random->RandomFloat( 0, 2.5 );
  3030. float delayMultiplier = ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) ? 1.5 : 1.0;
  3031. float groupDelay = gpGlobals->curtime + ( 2.0 + random->RandomFloat( 0, 2 ) ) * delayMultiplier;
  3032. for ( int i = 0; i < g_Hunters.Count(); i++ )
  3033. {
  3034. if ( g_Hunters[i] != this && g_Hunters[i]->m_flNextChargeTime < groupDelay )
  3035. {
  3036. g_Hunters[i]->m_flNextChargeTime = groupDelay;
  3037. }
  3038. }
  3039. TaskComplete();
  3040. return;
  3041. }
  3042. // Still in the process of slowing down. Run movement until it's done.
  3043. AutoMovement();
  3044. return;
  3045. }
  3046. // Check for manual transition
  3047. if ( ( eActivity == ACT_HUNTER_CHARGE_START ) && ( IsActivityFinished() ) )
  3048. {
  3049. SetIdealActivity( ACT_HUNTER_CHARGE_RUN );
  3050. }
  3051. // See if we're still running
  3052. if ( eActivity == ACT_HUNTER_CHARGE_RUN || eActivity == ACT_HUNTER_CHARGE_START )
  3053. {
  3054. if ( HasCondition( COND_NEW_ENEMY ) || HasCondition( COND_LOST_ENEMY ) || HasCondition( COND_ENEMY_DEAD ) )
  3055. {
  3056. SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
  3057. return;
  3058. }
  3059. else
  3060. {
  3061. if ( GetEnemy() != NULL )
  3062. {
  3063. Vector goalDir = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
  3064. VectorNormalize( goalDir );
  3065. if ( DotProduct( BodyDirection2D(), goalDir ) < 0.25f )
  3066. {
  3067. SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
  3068. }
  3069. }
  3070. }
  3071. }
  3072. // Steer towards our target
  3073. float idealYaw;
  3074. if ( GetEnemy() == NULL )
  3075. {
  3076. idealYaw = GetMotor()->GetIdealYaw();
  3077. }
  3078. else
  3079. {
  3080. idealYaw = CalcIdealYaw( GetEnemy()->GetAbsOrigin() );
  3081. }
  3082. // Add in our steering offset
  3083. idealYaw += ChargeSteer();
  3084. // Turn to face
  3085. GetMotor()->SetIdealYawAndUpdate( idealYaw );
  3086. // See if we're going to run into anything soon
  3087. ChargeLookAhead();
  3088. // Let our animations simply move us forward. Keep the result
  3089. // of the movement so we know whether we've hit our target.
  3090. AIMoveTrace_t moveTrace;
  3091. if ( AutoMovement( GetEnemy(), &moveTrace ) == false )
  3092. {
  3093. // Only stop if we hit the world
  3094. if ( HandleChargeImpact( moveTrace.vEndPosition, moveTrace.pObstruction ) )
  3095. {
  3096. // If we're starting up, this is an error
  3097. if ( eActivity == ACT_HUNTER_CHARGE_START )
  3098. {
  3099. TaskFail( "Unable to make initial movement of charge\n" );
  3100. return;
  3101. }
  3102. // Crash unless we're trying to stop already
  3103. if ( eActivity != ACT_HUNTER_CHARGE_STOP )
  3104. {
  3105. if ( moveTrace.fStatus == AIMR_BLOCKED_WORLD && moveTrace.vHitNormal == vec3_origin )
  3106. {
  3107. SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
  3108. }
  3109. else
  3110. {
  3111. // Shake the screen
  3112. if ( moveTrace.fStatus != AIMR_BLOCKED_NPC )
  3113. {
  3114. EmitSound( "NPC_Hunter.ChargeHitWorld" );
  3115. UTIL_ScreenShake( GetAbsOrigin(), 16.0f, 4.0f, 1.0f, 400.0f, SHAKE_START );
  3116. }
  3117. SetIdealActivity( ACT_HUNTER_CHARGE_CRASH );
  3118. }
  3119. }
  3120. }
  3121. else if ( moveTrace.pObstruction )
  3122. {
  3123. // If we hit another hunter, stop
  3124. if ( moveTrace.pObstruction->Classify() == CLASS_COMBINE_HUNTER )
  3125. {
  3126. // Crash unless we're trying to stop already
  3127. if ( eActivity != ACT_HUNTER_CHARGE_STOP )
  3128. {
  3129. SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
  3130. }
  3131. }
  3132. // If we hit an antlion, don't stop, but kill it
  3133. // We never have hunters and antlions together, but you never know.
  3134. else if (moveTrace.pObstruction->Classify() == CLASS_ANTLION )
  3135. {
  3136. if ( FClassnameIs( moveTrace.pObstruction, "npc_antlionguard" ) )
  3137. {
  3138. // Crash unless we're trying to stop already
  3139. if ( eActivity != ACT_HUNTER_CHARGE_STOP )
  3140. {
  3141. SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
  3142. }
  3143. }
  3144. else
  3145. {
  3146. Hunter_ApplyChargeDamage( this, moveTrace.pObstruction, moveTrace.pObstruction->GetHealth() );
  3147. }
  3148. }
  3149. }
  3150. }
  3151. break;
  3152. }
  3153. case TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY:
  3154. {
  3155. if ( GetEnemy() )
  3156. {
  3157. Vector vecEnemyLKP = GetEnemyLKP();
  3158. AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.8 );
  3159. }
  3160. ChainRunTask( TASK_WAIT_FOR_MOVEMENT, pTask->flTaskData );
  3161. break;
  3162. }
  3163. default:
  3164. {
  3165. BaseClass::RunTask( pTask );
  3166. break;
  3167. }
  3168. }
  3169. }
  3170. //-----------------------------------------------------------------------------
  3171. // Return true if our charge target is right in front of the hunter.
  3172. //-----------------------------------------------------------------------------
  3173. bool CNPC_Hunter::EnemyIsRightInFrontOfMe( CBaseEntity **pEntity )
  3174. {
  3175. if ( !GetEnemy() )
  3176. return false;
  3177. if ( (GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter()).LengthSqr() < (156*156) )
  3178. {
  3179. Vector vecLOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
  3180. vecLOS.z = 0;
  3181. VectorNormalize( vecLOS );
  3182. Vector vBodyDir = BodyDirection2D();
  3183. if ( DotProduct( vecLOS, vBodyDir ) > 0.8 )
  3184. {
  3185. // He's in front of me, and close. Make sure he's not behind a wall.
  3186. trace_t tr;
  3187. UTIL_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), GetHullMins() * 0.5, GetHullMaxs() * 0.5, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
  3188. if ( tr.m_pEnt == GetEnemy() )
  3189. {
  3190. *pEntity = tr.m_pEnt;
  3191. return true;
  3192. }
  3193. }
  3194. }
  3195. return false;
  3196. }
  3197. //-----------------------------------------------------------------------------
  3198. // While charging, look ahead and see if we're going to run into anything.
  3199. // If we are, start the gesture so it looks like we're anticipating the hit.
  3200. //-----------------------------------------------------------------------------
  3201. void CNPC_Hunter::ChargeLookAhead( void )
  3202. {
  3203. #if 0
  3204. trace_t tr;
  3205. Vector vecForward;
  3206. GetVectors( &vecForward, NULL, NULL );
  3207. Vector vecTestPos = GetAbsOrigin() + ( vecForward * m_flGroundSpeed * 0.75 );
  3208. Vector testHullMins = GetHullMins();
  3209. testHullMins.z += (StepHeight() * 2);
  3210. HunterTraceHull_SkipPhysics( GetAbsOrigin(), vecTestPos, testHullMins, GetHullMaxs(), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5 );
  3211. //NDebugOverlay::Box( tr.startpos, testHullMins, GetHullMaxs(), 0, 255, 0, true, 0.1f );
  3212. //NDebugOverlay::Box( vecTestPos, testHullMins, GetHullMaxs(), 255, 0, 0, true, 0.1f );
  3213. if ( tr.fraction != 1.0 )
  3214. {
  3215. // dvs: TODO:
  3216. // Start playing the hit animation
  3217. //AddGesture( ACT_HUNTER_CHARGE_ANTICIPATION );
  3218. }
  3219. #endif
  3220. }
  3221. //-----------------------------------------------------------------------------
  3222. //-----------------------------------------------------------------------------
  3223. float CNPC_Hunter::ChargeSteer()
  3224. {
  3225. trace_t tr;
  3226. Vector testPos, steer, forward, right;
  3227. QAngle angles;
  3228. const float testLength = m_flGroundSpeed * 0.15f;
  3229. //Get our facing
  3230. GetVectors( &forward, &right, NULL );
  3231. steer = forward;
  3232. const float faceYaw = UTIL_VecToYaw( forward );
  3233. //Offset right
  3234. VectorAngles( forward, angles );
  3235. angles[YAW] += 45.0f;
  3236. AngleVectors( angles, &forward );
  3237. // Probe out
  3238. testPos = GetAbsOrigin() + ( forward * testLength );
  3239. // Offset by step height
  3240. Vector testHullMins = GetHullMins();
  3241. testHullMins.z += (StepHeight() * 2);
  3242. // Probe
  3243. HunterTraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );
  3244. // Debug info
  3245. if ( g_debug_hunter_charge.GetInt() == 1 )
  3246. {
  3247. if ( tr.fraction == 1.0f )
  3248. {
  3249. NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
  3250. }
  3251. else
  3252. {
  3253. NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
  3254. }
  3255. }
  3256. // Add in this component
  3257. steer += ( right * 0.5f ) * ( 1.0f - tr.fraction );
  3258. // Offset left
  3259. angles[YAW] -= 90.0f;
  3260. AngleVectors( angles, &forward );
  3261. // Probe out
  3262. testPos = GetAbsOrigin() + ( forward * testLength );
  3263. HunterTraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f );
  3264. // Debug
  3265. if ( g_debug_hunter_charge.GetInt() == 1 )
  3266. {
  3267. if ( tr.fraction == 1.0f )
  3268. {
  3269. NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
  3270. }
  3271. else
  3272. {
  3273. NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
  3274. }
  3275. }
  3276. // Add in this component
  3277. steer -= ( right * 0.5f ) * ( 1.0f - tr.fraction );
  3278. // Debug
  3279. if ( g_debug_hunter_charge.GetInt() == 1 )
  3280. {
  3281. NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( steer * 512.0f ), 255, 255, 0, true, 0.1f );
  3282. NDebugOverlay::Cross3D( GetAbsOrigin() + ( steer * 512.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 255, 0, true, 0.1f );
  3283. NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), 255, 0, 255, true, 0.1f );
  3284. NDebugOverlay::Cross3D( GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 0, 255, true, 0.1f );
  3285. }
  3286. return UTIL_AngleDiff( UTIL_VecToYaw( steer ), faceYaw );
  3287. }
  3288. //-----------------------------------------------------------------------------
  3289. //-----------------------------------------------------------------------------
  3290. void CNPC_Hunter::ChargeDamage( CBaseEntity *pTarget )
  3291. {
  3292. if ( pTarget == NULL )
  3293. return;
  3294. CBasePlayer *pPlayer = ToBasePlayer( pTarget );
  3295. if ( pPlayer != NULL )
  3296. {
  3297. //Kick the player angles
  3298. pPlayer->ViewPunch( QAngle( 20, 20, -30 ) );
  3299. Vector dir = pPlayer->WorldSpaceCenter() - WorldSpaceCenter();
  3300. VectorNormalize( dir );
  3301. dir.z = 0.0f;
  3302. Vector vecNewVelocity = dir * 250.0f;
  3303. vecNewVelocity[2] += 128.0f;
  3304. pPlayer->SetAbsVelocity( vecNewVelocity );
  3305. color32 red = {128,0,0,128};
  3306. UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN );
  3307. }
  3308. // Player takes less damage
  3309. float flDamage = ( pPlayer == NULL ) ? 250 : sk_hunter_dmg_charge.GetFloat();
  3310. // If it's being held by the player, break that bond
  3311. Pickup_ForcePlayerToDropThisObject( pTarget );
  3312. // Calculate the physics force
  3313. Hunter_ApplyChargeDamage( this, pTarget, flDamage );
  3314. }
  3315. //-----------------------------------------------------------------------------
  3316. // Handles the hunter charging into something. Returns true if it hit the world.
  3317. //-----------------------------------------------------------------------------
  3318. bool CNPC_Hunter::HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity )
  3319. {
  3320. // Cause a shock wave from this point which will disrupt nearby physics objects
  3321. //ImpactShock( vecImpact, 128, 350 );
  3322. // Did we hit anything interesting?
  3323. if ( !pEntity || pEntity->IsWorld() )
  3324. {
  3325. // Robin: Due to some of the finicky details in the motor, the hunter will hit
  3326. // the world when it is blocked by our enemy when trying to step up
  3327. // during a moveprobe. To get around this, we see if the enemy's within
  3328. // a volume in front of the hunter when we hit the world, and if he is,
  3329. // we hit him anyway.
  3330. EnemyIsRightInFrontOfMe( &pEntity );
  3331. // Did we manage to find him? If not, increment our charge miss count and abort.
  3332. if ( pEntity->IsWorld() )
  3333. {
  3334. return true;
  3335. }
  3336. }
  3337. // Hit anything we don't like
  3338. if ( IRelationType( pEntity ) == D_HT && ( GetNextAttack() < gpGlobals->curtime ) )
  3339. {
  3340. EmitSound( "NPC_Hunter.ChargeHitEnemy" );
  3341. // dvs: TODO:
  3342. //if ( !IsPlayingGesture( ACT_HUNTER_CHARGE_HIT ) )
  3343. //{
  3344. // RestartGesture( ACT_HUNTER_CHARGE_HIT );
  3345. //}
  3346. ChargeDamage( pEntity );
  3347. if ( !pEntity->IsNPC() )
  3348. {
  3349. pEntity->ApplyAbsVelocityImpulse( ( BodyDirection2D() * 400 ) + Vector( 0, 0, 200 ) );
  3350. }
  3351. if ( !pEntity->IsAlive() && GetEnemy() == pEntity )
  3352. {
  3353. SetEnemy( NULL );
  3354. }
  3355. SetNextAttack( gpGlobals->curtime + 2.0f );
  3356. if ( !pEntity->IsAlive() || !pEntity->IsNPC() )
  3357. {
  3358. SetIdealActivity( ACT_HUNTER_CHARGE_STOP );
  3359. return false;
  3360. }
  3361. else
  3362. return true;
  3363. }
  3364. // Hit something we don't hate. If it's not moveable, crash into it.
  3365. if ( pEntity->GetMoveType() == MOVETYPE_NONE || pEntity->GetMoveType() == MOVETYPE_PUSH )
  3366. {
  3367. CBreakable *pBreakable = dynamic_cast<CBreakable *>(pEntity);
  3368. if ( pBreakable && pBreakable->IsBreakable() && pBreakable->m_takedamage == DAMAGE_YES && pBreakable->GetHealth() > 0 )
  3369. {
  3370. ChargeDamage( pEntity );
  3371. }
  3372. return true;
  3373. }
  3374. // If it's a vphysics object that's too heavy, crash into it too.
  3375. if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
  3376. {
  3377. IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
  3378. if ( pPhysics )
  3379. {
  3380. // If the object is being held by the player, knock it out of his hands
  3381. if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
  3382. {
  3383. Pickup_ForcePlayerToDropThisObject( pEntity );
  3384. return false;
  3385. }
  3386. if ( !pPhysics->IsMoveable() )
  3387. return true;
  3388. float entMass = PhysGetEntityMass( pEntity ) ;
  3389. float minMass = VPhysicsGetObject()->GetMass() * 0.5f;
  3390. if ( entMass < minMass )
  3391. {
  3392. if ( entMass < minMass * 0.666f || pEntity->CollisionProp()->BoundingRadius() < GetHullHeight() )
  3393. {
  3394. if ( pEntity->GetHealth() > 0 )
  3395. {
  3396. CBreakableProp *pBreakable = dynamic_cast<CBreakableProp *>(pEntity);
  3397. if ( pBreakable && pBreakable->m_takedamage == DAMAGE_YES && pBreakable->GetHealth() > 0 && pBreakable->GetHealth() <= 50 )
  3398. {
  3399. ChargeDamage( pEntity );
  3400. }
  3401. }
  3402. pEntity->SetNavIgnore( 2.0 );
  3403. return false;
  3404. }
  3405. }
  3406. return true;
  3407. }
  3408. }
  3409. return false;
  3410. }
  3411. //-------------------------------------------------------------------------------------------------
  3412. //-------------------------------------------------------------------------------------------------
  3413. void CNPC_Hunter::Explode()
  3414. {
  3415. Vector velocity = vec3_origin;
  3416. AngularImpulse angVelocity = RandomAngularImpulse( -150, 150 );
  3417. PropBreakableCreateAll( GetModelIndex(), NULL, EyePosition(), GetAbsAngles(), velocity, angVelocity, 1.0, 150, COLLISION_GROUP_NPC, this );
  3418. ExplosionCreate( EyePosition(), GetAbsAngles(), this, 500, 256, (SF_ENVEXPLOSION_NOPARTICLES|SF_ENVEXPLOSION_NOSPARKS|SF_ENVEXPLOSION_NODLIGHTS|SF_ENVEXPLOSION_NODAMAGE|SF_ENVEXPLOSION_NOSMOKE), false );
  3419. // Create liquid fountain gushtacular effect here!
  3420. CEffectData data;
  3421. data.m_vOrigin = EyePosition();
  3422. data.m_vNormal = Vector( 0, 0, 1 );
  3423. data.m_flScale = 4.0f;
  3424. DispatchEffect( "StriderBlood", data );
  3425. // Go away
  3426. m_lifeState = LIFE_DEAD;
  3427. SetThink( &CNPC_Hunter::SUB_Remove );
  3428. SetNextThink( gpGlobals->curtime + 0.1f );
  3429. AddEffects( EF_NODRAW );
  3430. }
  3431. //-----------------------------------------------------------------------------
  3432. //-----------------------------------------------------------------------------
  3433. Activity CNPC_Hunter::NPC_TranslateActivity( Activity baseAct )
  3434. {
  3435. if ( ( baseAct == ACT_WALK ) || ( baseAct == ACT_RUN ) )
  3436. {
  3437. if ( GetEnemy() )
  3438. {
  3439. Vector vecEnemyLKP = GetEnemyLKP();
  3440. // Only start facing when we're close enough
  3441. if ( UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < HUNTER_FACE_ENEMY_DIST )
  3442. {
  3443. return (Activity)ACT_HUNTER_WALK_ANGRY;
  3444. }
  3445. }
  3446. }
  3447. else if ( ( baseAct == ACT_IDLE ) && m_bPlanted )
  3448. {
  3449. return ( Activity )ACT_HUNTER_IDLE_PLANTED;
  3450. }
  3451. else if ( baseAct == ACT_RANGE_ATTACK2 )
  3452. {
  3453. if ( !m_bPlanted && ( m_bEnableUnplantedShooting || IsStriderBuster( GetEnemy() ) ) )
  3454. {
  3455. return (Activity)ACT_HUNTER_RANGE_ATTACK2_UNPLANTED;
  3456. }
  3457. }
  3458. return BaseClass::NPC_TranslateActivity( baseAct );
  3459. }
  3460. //-----------------------------------------------------------------------------
  3461. //-----------------------------------------------------------------------------
  3462. void CNPC_Hunter::HandleAnimEvent( animevent_t *pEvent )
  3463. {
  3464. Vector footPosition;
  3465. QAngle angles;
  3466. if ( pEvent->event == AE_HUNTER_FOOTSTEP_LEFT )
  3467. {
  3468. LeftFootHit( pEvent->eventtime );
  3469. return;
  3470. }
  3471. if ( pEvent->event == AE_HUNTER_FOOTSTEP_RIGHT )
  3472. {
  3473. RightFootHit( pEvent->eventtime );
  3474. return;
  3475. }
  3476. if ( pEvent->event == AE_HUNTER_FOOTSTEP_BACK )
  3477. {
  3478. BackFootHit( pEvent->eventtime );
  3479. return;
  3480. }
  3481. if ( pEvent->event == AE_HUNTER_START_EXPRESSION )
  3482. {
  3483. if ( pEvent->options && Q_strlen( pEvent->options ) )
  3484. {
  3485. //m_iszCurrentExpression = AllocPooledString( pEvent->options );
  3486. //SetExpression( pEvent->options );
  3487. }
  3488. return;
  3489. }
  3490. if ( pEvent->event == AE_HUNTER_END_EXPRESSION )
  3491. {
  3492. if ( pEvent->options && Q_strlen( pEvent->options ) )
  3493. {
  3494. //m_iszCurrentExpression = NULL_STRING;
  3495. //RemoveActorFromScriptedScenes( this, true, false, pEvent->options );
  3496. }
  3497. return;
  3498. }
  3499. if ( pEvent->event == AE_HUNTER_MELEE_ANNOUNCE )
  3500. {
  3501. EmitSound( "NPC_Hunter.MeleeAnnounce" );
  3502. return;
  3503. }
  3504. if ( pEvent->event == AE_HUNTER_MELEE_ATTACK_LEFT )
  3505. {
  3506. Vector right, forward, dir;
  3507. AngleVectors( GetLocalAngles(), &forward, &right, NULL );
  3508. right = right * -100;
  3509. forward = forward * 600;
  3510. dir = right + forward;
  3511. QAngle angle( 25, 30, -20 );
  3512. MeleeAttack( HUNTER_MELEE_REACH, sk_hunter_dmg_one_slash.GetFloat(), angle, dir, HUNTER_BLOOD_LEFT_FOOT );
  3513. return;
  3514. }
  3515. if ( pEvent->event == AE_HUNTER_MELEE_ATTACK_RIGHT )
  3516. {
  3517. Vector right, forward,dir;
  3518. AngleVectors( GetLocalAngles(), &forward, &right, NULL );
  3519. right = right * 100;
  3520. forward = forward * 600;
  3521. dir = right + forward;
  3522. QAngle angle( 25, -30, 20 );
  3523. MeleeAttack( HUNTER_MELEE_REACH, sk_hunter_dmg_one_slash.GetFloat(), angle, dir, HUNTER_BLOOD_LEFT_FOOT );
  3524. return;
  3525. }
  3526. if ( pEvent->event == AE_HUNTER_SPRAY_BLOOD )
  3527. {
  3528. Vector vecOrigin;
  3529. Vector vecDir;
  3530. // spray blood from the attachment point
  3531. bool bGotAttachment = false;
  3532. if ( pEvent->options )
  3533. {
  3534. QAngle angDir;
  3535. if ( GetAttachment( pEvent->options, vecOrigin, angDir ) )
  3536. {
  3537. bGotAttachment = true;
  3538. AngleVectors( angDir, &vecDir, NULL, NULL );
  3539. }
  3540. }
  3541. // fall back to our center, tracing forward
  3542. if ( !bGotAttachment )
  3543. {
  3544. vecOrigin = WorldSpaceCenter();
  3545. GetVectors( &vecDir, NULL, NULL );
  3546. }
  3547. UTIL_BloodSpray( vecOrigin, vecDir, BLOOD_COLOR_RED, 4, FX_BLOODSPRAY_ALL );
  3548. for ( int i = 0 ; i < 3 ; i++ )
  3549. {
  3550. Vector vecTraceDir = vecDir;
  3551. vecTraceDir.x += random->RandomFloat( -0.1, 0.1 );
  3552. vecTraceDir.y += random->RandomFloat( -0.1, 0.1 );
  3553. vecTraceDir.z += random->RandomFloat( -0.1, 0.1 );
  3554. trace_t tr;
  3555. AI_TraceLine( vecOrigin, vecOrigin + ( vecTraceDir * 192.0f ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  3556. if ( tr.fraction != 1.0 )
  3557. {
  3558. UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED );
  3559. }
  3560. }
  3561. return;
  3562. }
  3563. BaseClass::HandleAnimEvent( pEvent );
  3564. }
  3565. //-----------------------------------------------------------------------------
  3566. //-----------------------------------------------------------------------------
  3567. void CNPC_Hunter::AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority )
  3568. {
  3569. if ( nDisposition == D_HT && pEntity->ClassMatches("npc_bullseye") )
  3570. UpdateEnemyMemory( pEntity, pEntity->GetAbsOrigin() );
  3571. BaseClass::AddEntityRelationship( pEntity, nDisposition, nPriority );
  3572. }
  3573. //-----------------------------------------------------------------------------
  3574. //-----------------------------------------------------------------------------
  3575. bool CNPC_Hunter::ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity )
  3576. {
  3577. if ( IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK1, false ) )
  3578. {
  3579. SetGoalEnt( pGoalEntity );
  3580. return true;
  3581. }
  3582. return BaseClass::ScheduledMoveToGoalEntity( scheduleType, pGoalEntity, movementActivity );
  3583. }
  3584. //-----------------------------------------------------------------------------
  3585. //-----------------------------------------------------------------------------
  3586. void CNPC_Hunter::OnChangeHintGroup( string_t oldGroup, string_t newGroup )
  3587. {
  3588. SetCondition( COND_HUNTER_NEW_HINTGROUP );
  3589. m_CheckHintGroupTimer.Set( 10 );
  3590. }
  3591. //-----------------------------------------------------------------------------
  3592. // Tells whether any given hunter is in a squad that contains other hunters.
  3593. // This is useful for preventing timid behavior for Hunters that are not
  3594. // supported by other hunters.
  3595. //
  3596. // NOTE: This counts the self! So a hunter that is alone in his squad
  3597. // receives a result of 1.
  3598. //-----------------------------------------------------------------------------
  3599. int CNPC_Hunter::NumHuntersInMySquad()
  3600. {
  3601. AISquadIter_t iter;
  3602. CAI_BaseNPC *pSquadmate = m_pSquad ? m_pSquad->GetFirstMember( &iter ) : NULL;
  3603. if( !pSquadmate )
  3604. {
  3605. // Not in a squad at all, but the caller is not concerned with that. Just
  3606. // tell them that we're in a squad of one (ourself)
  3607. return 1;
  3608. }
  3609. int count = 0;
  3610. while ( pSquadmate )
  3611. {
  3612. if( pSquadmate->m_iClassname == m_iClassname )
  3613. count++;
  3614. pSquadmate = m_pSquad->GetNextMember( &iter );
  3615. }
  3616. return count;
  3617. }
  3618. //-----------------------------------------------------------------------------
  3619. //-----------------------------------------------------------------------------
  3620. void CNPC_Hunter::FollowStrider( const char *szStrider )
  3621. {
  3622. if ( !szStrider )
  3623. return;
  3624. CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, szStrider, this );
  3625. CNPC_Strider *pStrider = dynamic_cast <CNPC_Strider *>( pEnt );
  3626. FollowStrider(pStrider);
  3627. }
  3628. //-----------------------------------------------------------------------------
  3629. //-----------------------------------------------------------------------------
  3630. void CNPC_Hunter::FollowStrider( CNPC_Strider * pStrider )
  3631. {
  3632. if ( !IsAlive() )
  3633. {
  3634. return;
  3635. }
  3636. if ( pStrider )
  3637. {
  3638. if ( m_EscortBehavior.GetFollowTarget() != pStrider )
  3639. {
  3640. m_iszFollowTarget = pStrider->GetEntityName();
  3641. if ( m_iszFollowTarget == NULL_STRING )
  3642. {
  3643. m_iszFollowTarget = AllocPooledString( "unnamed_strider" );
  3644. }
  3645. m_EscortBehavior.SetEscortTarget( pStrider );
  3646. }
  3647. }
  3648. else
  3649. {
  3650. DevWarning("Hunter set to follow entity %s that is not a strider\n", STRING( m_iszFollowTarget ) );
  3651. m_iszFollowTarget = AllocPooledString( "unknown_strider" );
  3652. }
  3653. }
  3654. void CAI_HunterEscortBehavior::SetEscortTarget( CNPC_Strider *pStrider, bool fFinishCurSchedule )
  3655. {
  3656. m_bEnabled = true;
  3657. if ( GetOuter()->GetSquad() )
  3658. {
  3659. GetOuter()->GetSquad()->RemoveFromSquad( GetOuter() );
  3660. }
  3661. for ( int i = 0; i < g_Hunters.Count(); i++ )
  3662. {
  3663. if ( g_Hunters[i]->m_EscortBehavior.GetFollowTarget() == pStrider )
  3664. {
  3665. Assert( g_Hunters[i]->GetSquad() );
  3666. g_Hunters[i]->GetSquad()->AddToSquad( GetOuter() );
  3667. break;
  3668. }
  3669. }
  3670. if ( !GetOuter()->GetSquad() )
  3671. {
  3672. GetOuter()->AddToSquad( AllocPooledString( CFmtStr( "%s_hunter_squad", STRING( pStrider->GetEntityName() ) ) ) );
  3673. }
  3674. BaseClass::SetFollowTarget( pStrider );
  3675. m_flTimeEscortReturn = gpGlobals->curtime;
  3676. }
  3677. //-----------------------------------------------------------------------------
  3678. //-----------------------------------------------------------------------------
  3679. void CNPC_Hunter::InputEnableUnplantedShooting( inputdata_t &inputdata )
  3680. {
  3681. m_bEnableUnplantedShooting = true;
  3682. }
  3683. //-----------------------------------------------------------------------------
  3684. //-----------------------------------------------------------------------------
  3685. void CNPC_Hunter::InputDisableUnplantedShooting( inputdata_t &inputdata )
  3686. {
  3687. m_bEnableUnplantedShooting = false;
  3688. }
  3689. //-----------------------------------------------------------------------------
  3690. //-----------------------------------------------------------------------------
  3691. void CNPC_Hunter::InputFollowStrider( inputdata_t &inputdata )
  3692. {
  3693. m_iszFollowTarget = inputdata.value.StringID();
  3694. if ( m_iszFollowTarget == s_iszStriderClassname )
  3695. {
  3696. m_EscortBehavior.m_bEnabled = true;
  3697. m_iszFollowTarget = NULL_STRING;
  3698. }
  3699. m_BeginFollowDelay.Start( .1 ); // Allow time for strider to spawn
  3700. }
  3701. //-----------------------------------------------------------------------------
  3702. //-----------------------------------------------------------------------------
  3703. void CNPC_Hunter::InputUseSiegeTargets( inputdata_t &inputdata )
  3704. {
  3705. m_iszSiegeTargetName = inputdata.value.StringID();
  3706. m_flTimeNextSiegeTargetAttack = gpGlobals->curtime + random->RandomFloat( 1, hunter_siege_frequency.GetFloat() );
  3707. if( m_iszSiegeTargetName == NULL_STRING )
  3708. {
  3709. // Turning the feature off. Restore m_flDistTooFar to default.
  3710. m_flDistTooFar = hunter_flechette_max_range.GetFloat();
  3711. m_pSiegeTargets.RemoveAll();
  3712. }
  3713. else
  3714. {
  3715. // We're going into siege mode. Adjust range accordingly.
  3716. m_flDistTooFar = hunter_flechette_max_range.GetFloat() * HUNTER_SIEGE_MAX_DIST_MODIFIER;
  3717. }
  3718. }
  3719. //-----------------------------------------------------------------------------
  3720. //-----------------------------------------------------------------------------
  3721. void CNPC_Hunter::InputDodge( inputdata_t &inputdata )
  3722. {
  3723. SetCondition( COND_HUNTER_FORCED_DODGE );
  3724. }
  3725. //-----------------------------------------------------------------------------
  3726. //-----------------------------------------------------------------------------
  3727. void CNPC_Hunter::InputFlankEnemy( inputdata_t &inputdata )
  3728. {
  3729. SetCondition( COND_HUNTER_FORCED_FLANK_ENEMY );
  3730. }
  3731. //-----------------------------------------------------------------------------
  3732. //-----------------------------------------------------------------------------
  3733. void CNPC_Hunter::InputDisableShooting( inputdata_t &inputdata )
  3734. {
  3735. m_bDisableShooting = true;
  3736. }
  3737. //-----------------------------------------------------------------------------
  3738. //-----------------------------------------------------------------------------
  3739. void CNPC_Hunter::InputEnableShooting( inputdata_t &inputdata )
  3740. {
  3741. m_bDisableShooting = false;
  3742. }
  3743. //-----------------------------------------------------------------------------
  3744. //-----------------------------------------------------------------------------
  3745. void CNPC_Hunter::InputEnableSquadShootDelay( inputdata_t &inputdata )
  3746. {
  3747. m_bEnableSquadShootDelay = true;
  3748. }
  3749. //-----------------------------------------------------------------------------
  3750. //-----------------------------------------------------------------------------
  3751. void CNPC_Hunter::InputDisableSquadShootDelay( inputdata_t &inputdata )
  3752. {
  3753. m_bEnableSquadShootDelay = false;
  3754. }
  3755. //-----------------------------------------------------------------------------
  3756. //-----------------------------------------------------------------------------
  3757. bool CNPC_Hunter::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
  3758. {
  3759. return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
  3760. }
  3761. //-----------------------------------------------------------------------------
  3762. //-----------------------------------------------------------------------------
  3763. bool CNPC_Hunter::IsValidEnemy( CBaseEntity *pTarget )
  3764. {
  3765. if ( IsStriderBuster( pTarget) )
  3766. {
  3767. if ( !m_EscortBehavior.m_bEnabled || !m_EscortBehavior.GetEscortTarget() )
  3768. {
  3769. // We only hate striderbusters when we are actively protecting a strider.
  3770. return false;
  3771. }
  3772. if ( pTarget->VPhysicsGetObject() )
  3773. {
  3774. if ( ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) &&
  3775. hunter_hate_held_striderbusters.GetBool() )
  3776. {
  3777. if ( gpGlobals->curtime - StriderBuster_GetPickupTime( pTarget ) > hunter_hate_held_striderbusters_delay.GetFloat())
  3778. {
  3779. if ( StriderBuster_NumFlechettesAttached( pTarget ) <= 2 )
  3780. {
  3781. if ( m_EscortBehavior.GetEscortTarget() &&
  3782. ( m_EscortBehavior.GetEscortTarget()->GetAbsOrigin().AsVector2D() - pTarget->GetAbsOrigin().AsVector2D() ).LengthSqr() < Square( hunter_hate_held_striderbusters_tolerance.GetFloat() ) )
  3783. {
  3784. return true;
  3785. }
  3786. }
  3787. }
  3788. return false;
  3789. }
  3790. bool bThrown = ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_WAS_THROWN ) != 0;
  3791. bool bAttached = StriderBuster_IsAttachedStriderBuster( pTarget );
  3792. if ( ( bThrown && !bAttached ) && hunter_hate_thrown_striderbusters.GetBool() )
  3793. {
  3794. float t;
  3795. float dist = CalcDistanceSqrToLineSegment2D( m_EscortBehavior.GetEscortTarget()->GetAbsOrigin().AsVector2D(),
  3796. pTarget->GetAbsOrigin().AsVector2D(),
  3797. pTarget->GetAbsOrigin().AsVector2D() + pTarget->GetSmoothedVelocity().AsVector2D(), &t );
  3798. if ( t > 0 && dist < Square( hunter_hate_thrown_striderbusters_tolerance.GetFloat() ))
  3799. {
  3800. return true;
  3801. }
  3802. return false;
  3803. }
  3804. if ( bAttached && StriderBuster_IsAttachedStriderBuster( pTarget, m_EscortBehavior.GetEscortTarget() ) && hunter_hate_attached_striderbusters.GetBool() )
  3805. {
  3806. return true;
  3807. }
  3808. }
  3809. return false;
  3810. }
  3811. return BaseClass::IsValidEnemy( pTarget );
  3812. }
  3813. //-----------------------------------------------------------------------------
  3814. //-----------------------------------------------------------------------------
  3815. Disposition_t CNPC_Hunter::IRelationType( CBaseEntity *pTarget )
  3816. {
  3817. if ( !pTarget )
  3818. return D_NU;
  3819. if ( IsStriderBuster( pTarget ) )
  3820. {
  3821. if ( HateThisStriderBuster( pTarget ) )
  3822. return D_HT;
  3823. return D_NU;
  3824. }
  3825. if ( hunter_retreat_striderbusters.GetBool() )
  3826. {
  3827. if ( pTarget->IsPlayer() && (m_hAttachedBusters.Count() > 0) )
  3828. {
  3829. return D_FR;
  3830. }
  3831. }
  3832. return BaseClass::IRelationType( pTarget );
  3833. }
  3834. //-----------------------------------------------------------------------------
  3835. //-----------------------------------------------------------------------------
  3836. int CNPC_Hunter::IRelationPriority( CBaseEntity *pTarget )
  3837. {
  3838. if ( IsStriderBuster( pTarget ) )
  3839. {
  3840. // If we're here, we already know that we hate striderbusters.
  3841. return 1000.0f;
  3842. }
  3843. return BaseClass::IRelationPriority( pTarget );
  3844. }
  3845. //-----------------------------------------------------------------------------
  3846. //-----------------------------------------------------------------------------
  3847. void CNPC_Hunter::SetSquad( CAI_Squad *pSquad )
  3848. {
  3849. BaseClass::SetSquad( pSquad );
  3850. if ( pSquad && pSquad->NumMembers() == 1 )
  3851. {
  3852. pSquad->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, 0 );
  3853. }
  3854. }
  3855. //-----------------------------------------------------------------------------
  3856. //-----------------------------------------------------------------------------
  3857. void CNPC_Hunter::OnSeeEntity( CBaseEntity *pEntity )
  3858. {
  3859. BaseClass::OnSeeEntity(pEntity);
  3860. if ( IsStriderBuster( pEntity ) && IsValidEnemy( pEntity ) )
  3861. {
  3862. SetCondition( COND_HUNTER_SEE_STRIDERBUSTER );
  3863. }
  3864. }
  3865. //-----------------------------------------------------------------------------
  3866. //-----------------------------------------------------------------------------
  3867. bool CNPC_Hunter::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer )
  3868. {
  3869. //EmitSound( "NPC_Hunter.Alert" );
  3870. return BaseClass::UpdateEnemyMemory( pEnemy, position, pInformer );
  3871. }
  3872. //-----------------------------------------------------------------------------
  3873. //-----------------------------------------------------------------------------
  3874. bool CNPC_Hunter::CanPlantHere( const Vector &vecPos )
  3875. {
  3876. // TODO: cache results?
  3877. //if ( vecPos == m_vecLastCanPlantHerePos )
  3878. //{
  3879. // return m_bLastCanPlantHere;
  3880. //}
  3881. Vector vecMins = GetHullMins();
  3882. Vector vecMaxs = GetHullMaxs();
  3883. vecMins.x -= 16;
  3884. vecMins.y -= 16;
  3885. vecMaxs.x += 16;
  3886. vecMaxs.y += 16;
  3887. vecMaxs.z -= hunter_plant_adjust_z.GetInt();
  3888. bool bResult = false;
  3889. trace_t tr;
  3890. UTIL_TraceHull( vecPos, vecPos, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
  3891. if ( tr.startsolid )
  3892. {
  3893. // Try again, tracing down from above.
  3894. Vector vecStart = vecPos;
  3895. vecStart.z += hunter_plant_adjust_z.GetInt();
  3896. UTIL_TraceHull( vecStart, vecPos, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
  3897. }
  3898. if ( tr.startsolid )
  3899. {
  3900. //NDebugOverlay::Box( vecPos, vecMins, vecMaxs, 255, 0, 0, 0, 0 );
  3901. }
  3902. else
  3903. {
  3904. //NDebugOverlay::Box( vecPos, vecMins, vecMaxs, 0, 255, 0, 0, 0 );
  3905. bResult = true;
  3906. }
  3907. // Cache the results in case we ask again for the same spot.
  3908. //m_vecLastCanPlantHerePos = vecPos;
  3909. //m_bLastCanPlantHere = bResult;
  3910. return bResult;
  3911. }
  3912. //-----------------------------------------------------------------------------
  3913. //-----------------------------------------------------------------------------
  3914. int CNPC_Hunter::MeleeAttack1ConditionsVsEnemyInVehicle( CBaseCombatCharacter *pEnemy, float flDot )
  3915. {
  3916. if( !IsCorporealEnemy( GetEnemy() ) )
  3917. return COND_NONE;
  3918. // Try and trace a box to the player, and if I hit the vehicle, attack it
  3919. Vector vecDelta = (pEnemy->WorldSpaceCenter() - WorldSpaceCenter());
  3920. VectorNormalize( vecDelta );
  3921. trace_t tr;
  3922. AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + (vecDelta * 64), -Vector(8,8,8), Vector(8,8,8), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  3923. if ( tr.fraction != 1.0 && tr.m_pEnt == pEnemy->GetVehicleEntity() )
  3924. {
  3925. // We're near the vehicle. Are we facing it?
  3926. if (flDot < 0.7)
  3927. return COND_NOT_FACING_ATTACK;
  3928. return COND_CAN_MELEE_ATTACK1;
  3929. }
  3930. return COND_TOO_FAR_TO_ATTACK;
  3931. }
  3932. //-----------------------------------------------------------------------------
  3933. // For innate melee attack
  3934. //-----------------------------------------------------------------------------
  3935. int CNPC_Hunter::MeleeAttack1Conditions ( float flDot, float flDist )
  3936. {
  3937. if ( !IsCorporealEnemy( GetEnemy() ) )
  3938. return COND_NONE;
  3939. if ( ( gpGlobals->curtime < m_flNextMeleeTime ) && // allow berzerk bashing if cornered
  3940. !( m_hAttachedBusters.Count() > 0 && gpGlobals->curtime < m_fCorneredTimer ) )
  3941. {
  3942. return COND_NONE;
  3943. }
  3944. if ( GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL )
  3945. {
  3946. return COND_NONE;
  3947. }
  3948. if ( flDist > HUNTER_MELEE_REACH )
  3949. {
  3950. // Translate a hit vehicle into its passenger if found
  3951. if ( GetEnemy() != NULL )
  3952. {
  3953. CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
  3954. if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
  3955. {
  3956. return MeleeAttack1ConditionsVsEnemyInVehicle( pCCEnemy, flDot );
  3957. }
  3958. #if defined(HL2_DLL) && !defined(HL2MP)
  3959. // If the player is holding an object, knock it down.
  3960. if ( GetEnemy()->IsPlayer() )
  3961. {
  3962. CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() );
  3963. Assert( pPlayer != NULL );
  3964. // Is the player carrying something?
  3965. CBaseEntity *pObject = GetPlayerHeldEntity(pPlayer);
  3966. if ( !pObject )
  3967. {
  3968. pObject = PhysCannonGetHeldEntity( pPlayer->GetActiveWeapon() );
  3969. }
  3970. if ( pObject )
  3971. {
  3972. float flDist = pObject->WorldSpaceCenter().DistTo( WorldSpaceCenter() );
  3973. if ( flDist <= HUNTER_MELEE_REACH )
  3974. {
  3975. return COND_CAN_MELEE_ATTACK1;
  3976. }
  3977. }
  3978. }
  3979. #endif
  3980. }
  3981. return COND_TOO_FAR_TO_ATTACK;
  3982. }
  3983. if (flDot < 0.7)
  3984. {
  3985. return COND_NOT_FACING_ATTACK;
  3986. }
  3987. // Build a cube-shaped hull, the same hull that MeleeAttack is going to use.
  3988. Vector vecMins = GetHullMins();
  3989. Vector vecMaxs = GetHullMaxs();
  3990. vecMins.z = vecMins.x;
  3991. vecMaxs.z = vecMaxs.x;
  3992. Vector forward;
  3993. GetVectors( &forward, NULL, NULL );
  3994. trace_t tr;
  3995. AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + forward * HUNTER_MELEE_REACH, vecMins, vecMaxs, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
  3996. if ( tr.fraction == 1.0 || !tr.m_pEnt )
  3997. {
  3998. // This attack would miss completely. Trick the hunter into moving around some more.
  3999. return COND_TOO_FAR_TO_ATTACK;
  4000. }
  4001. if ( tr.m_pEnt == GetEnemy() || tr.m_pEnt->IsNPC() || (tr.m_pEnt->m_takedamage == DAMAGE_YES && (dynamic_cast<CBreakableProp*>(tr.m_pEnt))) )
  4002. {
  4003. // Let the hunter swipe at his enemy if he's going to hit them.
  4004. // Also let him swipe at NPC's that happen to be between the hunter and the enemy.
  4005. // This makes mobs of hunters seem more rowdy since it doesn't leave guys in the back row standing around.
  4006. // Also let him swipe at things that takedamage, under the assumptions that they can be broken.
  4007. return COND_CAN_MELEE_ATTACK1;
  4008. }
  4009. // dvs TODO: incorporate this
  4010. /*if ( tr.m_pEnt->IsBSPModel() )
  4011. {
  4012. // The trace hit something solid, but it's not the enemy. If this item is closer to the hunter than
  4013. // the enemy is, treat this as an obstruction.
  4014. Vector vecToEnemy = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter();
  4015. Vector vecTrace = tr.endpos - tr.startpos;
  4016. if ( vecTrace.Length2DSqr() < vecToEnemy.Length2DSqr() )
  4017. {
  4018. return COND_HUNTER_LOCAL_MELEE_OBSTRUCTION;
  4019. }
  4020. }*/
  4021. if ( !tr.m_pEnt->IsWorld() && GetEnemy() && GetEnemy()->GetGroundEntity() == tr.m_pEnt )
  4022. {
  4023. // Try to swat whatever the player is standing on instead of acting like a dill.
  4024. return COND_CAN_MELEE_ATTACK1;
  4025. }
  4026. // Move around some more
  4027. return COND_TOO_FAR_TO_ATTACK;
  4028. }
  4029. //-----------------------------------------------------------------------------
  4030. // For innate melee attack
  4031. //-----------------------------------------------------------------------------
  4032. int CNPC_Hunter::MeleeAttack2Conditions ( float flDot, float flDist )
  4033. {
  4034. return COND_NONE;
  4035. }
  4036. //-----------------------------------------------------------------------------
  4037. //-----------------------------------------------------------------------------
  4038. bool CNPC_Hunter::IsCorporealEnemy( CBaseEntity *pEnemy )
  4039. {
  4040. if( !pEnemy )
  4041. return false;
  4042. // Generally speaking, don't melee attack anything the player can't see.
  4043. if( pEnemy->IsEffectActive( EF_NODRAW ) )
  4044. return false;
  4045. // Don't flank, melee attack striderbusters.
  4046. if ( IsStriderBuster( pEnemy ) )
  4047. return false;
  4048. return true;
  4049. }
  4050. //-----------------------------------------------------------------------------
  4051. //-----------------------------------------------------------------------------
  4052. int CNPC_Hunter::RangeAttack1Conditions( float flDot, float flDist )
  4053. {
  4054. return COND_NONE;
  4055. }
  4056. //-----------------------------------------------------------------------------
  4057. //-----------------------------------------------------------------------------
  4058. int CNPC_Hunter::RangeAttack2Conditions( float flDot, float flDist )
  4059. {
  4060. bool bIsBuster = IsStriderBuster( GetEnemy() );
  4061. bool bIsPerfectBullseye = ( GetEnemy() && dynamic_cast<CNPC_Bullseye *>(GetEnemy()) && ((CNPC_Bullseye *)GetEnemy())->UsePerfectAccuracy() );
  4062. if ( !bIsPerfectBullseye && !bIsBuster && !hunter_flechette_test.GetBool() && ( gpGlobals->curtime < m_flNextRangeAttack2Time ) )
  4063. {
  4064. return COND_NONE;
  4065. }
  4066. if ( m_bDisableShooting )
  4067. {
  4068. return COND_NONE;
  4069. }
  4070. if ( !HasCondition( COND_SEE_ENEMY ) )
  4071. {
  4072. return COND_NONE;
  4073. }
  4074. float flMaxFlechetteRange = hunter_flechette_max_range.GetFloat();
  4075. if ( IsUsingSiegeTargets() )
  4076. {
  4077. flMaxFlechetteRange *= HUNTER_SIEGE_MAX_DIST_MODIFIER;
  4078. }
  4079. if ( !bIsBuster && ( flDist > flMaxFlechetteRange ) )
  4080. {
  4081. return COND_TOO_FAR_TO_ATTACK;
  4082. }
  4083. else if ( !bIsBuster && ( !GetEnemy() || !GetEnemy()->ClassMatches( "npc_bullseye" ) ) && flDist < hunter_flechette_min_range.GetFloat() )
  4084. {
  4085. return COND_TOO_CLOSE_TO_ATTACK;
  4086. }
  4087. else if ( flDot < HUNTER_FACING_DOT )
  4088. {
  4089. return COND_NOT_FACING_ATTACK;
  4090. }
  4091. if ( !bIsBuster && !m_bEnableUnplantedShooting && !hunter_flechette_test.GetBool() && !CanPlantHere( GetAbsOrigin() ) )
  4092. {
  4093. return COND_HUNTER_CANT_PLANT;
  4094. }
  4095. return COND_CAN_RANGE_ATTACK2;
  4096. }
  4097. //-----------------------------------------------------------------------------
  4098. //-----------------------------------------------------------------------------
  4099. bool CNPC_Hunter::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions)
  4100. {
  4101. CBaseEntity *pTargetEnt;
  4102. pTargetEnt = GetEnemy();
  4103. trace_t tr;
  4104. Vector vFrom = ownerPos + GetViewOffset();
  4105. AI_TraceLine( vFrom, targetPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  4106. if ( ( pTargetEnt && tr.m_pEnt == pTargetEnt) || tr.fraction == 1.0 || CanShootThrough( tr, targetPos ) )
  4107. {
  4108. static Vector vMins( -2.0, -2.0, -2.0 );
  4109. static Vector vMaxs( -vMins);
  4110. // Hit the enemy, or hit nothing (traced all the way to a nonsolid enemy like a bullseye)
  4111. AI_TraceHull( vFrom - Vector( 0, 0, 18 ), targetPos, vMins, vMaxs, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
  4112. if ( ( pTargetEnt && tr.m_pEnt == pTargetEnt) || tr.fraction == 1.0 || CanShootThrough( tr, targetPos ) )
  4113. {
  4114. if ( hunter_show_weapon_los_condition.GetBool() )
  4115. {
  4116. NDebugOverlay::Line( vFrom, targetPos, 255, 0, 255, false, 0.1 );
  4117. NDebugOverlay::Line( vFrom - Vector( 0, 0, 18 ), targetPos, 0, 0, 255, false, 0.1 );
  4118. }
  4119. return true;
  4120. }
  4121. }
  4122. else if ( bSetConditions )
  4123. {
  4124. SetCondition( COND_WEAPON_SIGHT_OCCLUDED );
  4125. SetEnemyOccluder( tr.m_pEnt );
  4126. }
  4127. return false;
  4128. }
  4129. //-----------------------------------------------------------------------------
  4130. // Look in front and see if the claw hit anything.
  4131. //
  4132. // Input : flDist distance to trace
  4133. // iDamage damage to do if attack hits
  4134. // vecViewPunch camera punch (if attack hits player)
  4135. // vecVelocityPunch velocity punch (if attack hits player)
  4136. //
  4137. // Output : The entity hit by claws. NULL if nothing.
  4138. //-----------------------------------------------------------------------------
  4139. CBaseEntity *CNPC_Hunter::MeleeAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin )
  4140. {
  4141. // Added test because claw attack anim sometimes used when for cases other than melee
  4142. if ( GetEnemy() )
  4143. {
  4144. trace_t tr;
  4145. AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  4146. if ( tr.fraction < 1.0f )
  4147. return NULL;
  4148. }
  4149. //
  4150. // Trace out a cubic section of our hull and see what we hit.
  4151. //
  4152. Vector vecMins = GetHullMins();
  4153. Vector vecMaxs = GetHullMaxs();
  4154. vecMins.z = vecMins.x;
  4155. vecMaxs.z = vecMaxs.x;
  4156. CBaseEntity *pHurt = CheckTraceHullAttack( flDist, vecMins, vecMaxs, iDamage, DMG_SLASH );
  4157. if ( pHurt )
  4158. {
  4159. EmitSound( "NPC_Hunter.MeleeHit" );
  4160. EmitSound( "NPC_Hunter.TackleHit" );
  4161. CBasePlayer *pPlayer = ToBasePlayer( pHurt );
  4162. if ( pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE ) )
  4163. {
  4164. pPlayer->ViewPunch( qaViewPunch );
  4165. pPlayer->VelocityPunch( vecVelocityPunch );
  4166. // Shake the screen
  4167. UTIL_ScreenShake( pPlayer->GetAbsOrigin(), 100.0, 1.5, 1.0, 2, SHAKE_START );
  4168. // Red damage indicator
  4169. color32 red = { 128, 0, 0, 128 };
  4170. UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN );
  4171. /*if ( UTIL_ShouldShowBlood( pPlayer->BloodColor() ) )
  4172. {
  4173. // Spray some of the player's blood on the hunter.
  4174. trace_t tr;
  4175. Vector vecHunterEyePos; // = EyePosition();
  4176. QAngle angDiscard;
  4177. GetBonePosition( LookupBone( "MiniStrider.top_eye_bone" ), vecHunterEyePos, angDiscard );
  4178. Vector vecPlayerEyePos = pPlayer->EyePosition();
  4179. Vector vecDir = vecHunterEyePos - vecPlayerEyePos;
  4180. float flLen = VectorNormalize( vecDir );
  4181. Vector vecStart = vecPlayerEyePos - ( vecDir * 64 );
  4182. Vector vecEnd = vecPlayerEyePos + ( vecDir * ( flLen + 64 ) );
  4183. NDebugOverlay::HorzArrow( vecStart, vecEnd, 16, 255, 255, 0, 255, false, 10 );
  4184. UTIL_TraceLine( vecStart, vecEnd, MASK_SHOT, pPlayer, COLLISION_GROUP_NONE, &tr );
  4185. if ( tr.m_pEnt )
  4186. {
  4187. Msg( "Hit %s!!!\n", tr.m_pEnt->GetDebugName() );
  4188. UTIL_DecalTrace( &tr, "Blood" );
  4189. }
  4190. }*/
  4191. }
  4192. else if ( !pPlayer )
  4193. {
  4194. if ( IsMovablePhysicsObject( pHurt ) )
  4195. {
  4196. // If it's a vphysics object that's too heavy, crash into it too.
  4197. IPhysicsObject *pPhysics = pHurt->VPhysicsGetObject();
  4198. if ( pPhysics )
  4199. {
  4200. // If the object is being held by the player, break it or make them drop it.
  4201. if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
  4202. {
  4203. // If it's breakable, break it.
  4204. if ( pHurt->m_takedamage == DAMAGE_YES )
  4205. {
  4206. CBreakableProp *pBreak = dynamic_cast<CBreakableProp*>(pHurt);
  4207. if ( pBreak )
  4208. {
  4209. CTakeDamageInfo info( this, this, 20, DMG_SLASH );
  4210. pBreak->Break( this, info );
  4211. }
  4212. }
  4213. }
  4214. }
  4215. }
  4216. if ( UTIL_ShouldShowBlood(pHurt->BloodColor()) )
  4217. {
  4218. // Hit an NPC. Bleed them!
  4219. Vector vecBloodPos;
  4220. switch ( BloodOrigin )
  4221. {
  4222. case HUNTER_BLOOD_LEFT_FOOT:
  4223. {
  4224. if ( GetAttachment( "blood_left", vecBloodPos ) )
  4225. {
  4226. SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) );
  4227. }
  4228. break;
  4229. }
  4230. }
  4231. }
  4232. }
  4233. }
  4234. else
  4235. {
  4236. // TODO:
  4237. //AttackMissSound();
  4238. }
  4239. m_flNextMeleeTime = gpGlobals->curtime + hunter_melee_delay.GetFloat();
  4240. return pHurt;
  4241. }
  4242. //-----------------------------------------------------------------------------
  4243. //-----------------------------------------------------------------------------
  4244. bool CNPC_Hunter::TestShootPosition(const Vector &vecShootPos, const Vector &targetPos )
  4245. {
  4246. if ( !CanPlantHere(vecShootPos ) )
  4247. {
  4248. return false;
  4249. }
  4250. return BaseClass::TestShootPosition( vecShootPos, targetPos );
  4251. }
  4252. //-----------------------------------------------------------------------------
  4253. //-----------------------------------------------------------------------------
  4254. Vector CNPC_Hunter::Weapon_ShootPosition( )
  4255. {
  4256. matrix3x4_t gunMatrix;
  4257. GetAttachment( gm_nTopGunAttachment, gunMatrix );
  4258. Vector vecShootPos;
  4259. MatrixGetColumn( gunMatrix, 3, vecShootPos );
  4260. return vecShootPos;
  4261. }
  4262. //-----------------------------------------------------------------------------
  4263. //-----------------------------------------------------------------------------
  4264. void CNPC_Hunter::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
  4265. {
  4266. float flTracerDist;
  4267. Vector vecDir;
  4268. Vector vecEndPos;
  4269. vecDir = tr.endpos - vecTracerSrc;
  4270. flTracerDist = VectorNormalize( vecDir );
  4271. int nAttachment = LookupAttachment( "MiniGun" );
  4272. UTIL_Tracer( vecTracerSrc, tr.endpos, nAttachment, TRACER_FLAG_USEATTACHMENT, 5000, true, "HunterTracer" );
  4273. }
  4274. //-----------------------------------------------------------------------------
  4275. // Trace didn't hit the intended target, but should the hunter
  4276. // shoot anyway? We use this to get the hunter to destroy
  4277. // breakables that are between him and his target.
  4278. //-----------------------------------------------------------------------------
  4279. bool CNPC_Hunter::CanShootThrough( const trace_t &tr, const Vector &vecTarget )
  4280. {
  4281. if ( !tr.m_pEnt )
  4282. {
  4283. return false;
  4284. }
  4285. if ( !tr.m_pEnt->GetHealth() )
  4286. {
  4287. return false;
  4288. }
  4289. // Don't try to shoot through allies.
  4290. CAI_BaseNPC *pNPC = tr.m_pEnt->MyNPCPointer();
  4291. if ( pNPC && ( IRelationType( pNPC ) == D_LI ) )
  4292. {
  4293. return false;
  4294. }
  4295. // Would a trace ignoring this entity continue to the target?
  4296. trace_t continuedTrace;
  4297. AI_TraceLine( tr.endpos, vecTarget, MASK_SHOT, tr.m_pEnt, COLLISION_GROUP_NONE, &continuedTrace );
  4298. if ( continuedTrace.fraction != 1.0 )
  4299. {
  4300. if ( continuedTrace.m_pEnt != GetEnemy() )
  4301. {
  4302. return false;
  4303. }
  4304. }
  4305. return true;
  4306. }
  4307. //-----------------------------------------------------------------------------
  4308. //-----------------------------------------------------------------------------
  4309. int CNPC_Hunter::GetSoundInterests()
  4310. {
  4311. return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_DANGER | SOUND_PHYSICS_DANGER | SOUND_PLAYER_VEHICLE | SOUND_BULLET_IMPACT | SOUND_MOVE_AWAY;
  4312. }
  4313. //-----------------------------------------------------------------------------
  4314. // Tells us whether the Hunter is acting in a large, outdoor map,
  4315. // currently only ep2_outland_12. This allows us to create logic
  4316. // branches here in the AI code so that we can make choices that
  4317. // tailor behavior to larger and smaller maps.
  4318. //-----------------------------------------------------------------------------
  4319. bool CNPC_Hunter::IsInLargeOutdoorMap()
  4320. {
  4321. return m_bInLargeOutdoorMap;
  4322. }
  4323. //-----------------------------------------------------------------------------
  4324. //-----------------------------------------------------------------------------
  4325. void CNPC_Hunter::AlertSound()
  4326. {
  4327. EmitSound( "NPC_Hunter.Alert" );
  4328. }
  4329. //-----------------------------------------------------------------------------
  4330. //-----------------------------------------------------------------------------
  4331. void CNPC_Hunter::PainSound( const CTakeDamageInfo &info )
  4332. {
  4333. if ( gpGlobals->curtime > m_flNextDamageTime )
  4334. {
  4335. EmitSound( "NPC_Hunter.Pain" );
  4336. m_flNextDamageTime = gpGlobals->curtime + random->RandomFloat( 0.5, 1.2 );
  4337. }
  4338. }
  4339. //-----------------------------------------------------------------------------
  4340. //-----------------------------------------------------------------------------
  4341. void CNPC_Hunter::DeathSound( const CTakeDamageInfo &info )
  4342. {
  4343. EmitSound( "NPC_Hunter.Death" );
  4344. }
  4345. //-----------------------------------------------------------------------------
  4346. //-----------------------------------------------------------------------------
  4347. void CNPC_Hunter::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
  4348. {
  4349. CTakeDamageInfo info = inputInfo;
  4350. // Even though the damage might not hurt us, we want to react to it
  4351. // if it's from the player.
  4352. if ( info.GetAttacker()->IsPlayer() )
  4353. {
  4354. if ( !HasMemory( bits_MEMORY_PROVOKED ) )
  4355. {
  4356. GetEnemies()->ClearMemory( info.GetAttacker() );
  4357. Remember( bits_MEMORY_PROVOKED );
  4358. SetCondition( COND_LIGHT_DAMAGE );
  4359. }
  4360. }
  4361. // HUnters have special resisitance to some types of damage.
  4362. if ( ( info.GetDamageType() & DMG_BULLET ) ||
  4363. ( info.GetDamageType() & DMG_BUCKSHOT ) ||
  4364. ( info.GetDamageType() & DMG_CLUB ) ||
  4365. ( info.GetDamageType() & DMG_NEVERGIB ) )
  4366. {
  4367. float flScale = 1.0;
  4368. if ( info.GetDamageType() & DMG_BUCKSHOT )
  4369. {
  4370. flScale = sk_hunter_buckshot_damage_scale.GetFloat();
  4371. }
  4372. else if ( ( info.GetDamageType() & DMG_BULLET ) || ( info.GetDamageType() & DMG_NEVERGIB ) )
  4373. {
  4374. // Hunters resist most bullet damage, but they are actually vulnerable to .357 rounds,
  4375. // since players regard that weapon as one of the game's truly powerful weapons.
  4376. if( info.GetAmmoType() == GetAmmoDef()->Index("357") )
  4377. {
  4378. flScale = 1.16f;
  4379. }
  4380. else
  4381. {
  4382. flScale = sk_hunter_bullet_damage_scale.GetFloat();
  4383. }
  4384. }
  4385. if ( GetActivity() == ACT_HUNTER_CHARGE_RUN )
  4386. {
  4387. flScale *= sk_hunter_charge_damage_scale.GetFloat();
  4388. }
  4389. if ( flScale != 0 )
  4390. {
  4391. float flDamage = info.GetDamage() * flScale;
  4392. info.SetDamage( flDamage );
  4393. }
  4394. QAngle vecAngles;
  4395. VectorAngles( ptr->plane.normal, vecAngles );
  4396. DispatchParticleEffect( "blood_impact_synth_01", ptr->endpos, vecAngles );
  4397. DispatchParticleEffect( "blood_impact_synth_01_arc_parent", PATTACH_POINT_FOLLOW, this, gm_nHeadCenterAttachment );
  4398. }
  4399. BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
  4400. }
  4401. //-----------------------------------------------------------------------------
  4402. //-----------------------------------------------------------------------------
  4403. const impactdamagetable_t &CNPC_Hunter::GetPhysicsImpactDamageTable()
  4404. {
  4405. return s_HunterImpactDamageTable;
  4406. }
  4407. //-----------------------------------------------------------------------------
  4408. //-----------------------------------------------------------------------------
  4409. void CNPC_Hunter::PhysicsDamageEffect( const Vector &vecPos, const Vector &vecDir )
  4410. {
  4411. CEffectData data;
  4412. data.m_vOrigin = vecPos;
  4413. data.m_vNormal = vecDir;
  4414. DispatchEffect( "HunterDamage", data );
  4415. if ( random->RandomInt( 0, 1 ) == 0 )
  4416. {
  4417. CBaseEntity *pTrail = CreateEntityByName( "sparktrail" );
  4418. pTrail->SetOwnerEntity( this );
  4419. pTrail->Spawn();
  4420. }
  4421. }
  4422. //-----------------------------------------------------------------------------
  4423. // We were hit by a strider buster. Do the tesla effect on our hitboxes.
  4424. //-----------------------------------------------------------------------------
  4425. void CNPC_Hunter::TeslaThink()
  4426. {
  4427. CEffectData data;
  4428. data.m_nEntIndex = entindex();
  4429. data.m_flMagnitude = 3;
  4430. data.m_flScale = 0.5f;
  4431. DispatchEffect( "TeslaHitboxes", data );
  4432. EmitSound( "RagdollBoogie.Zap" );
  4433. if ( gpGlobals->curtime < m_flTeslaStopTime )
  4434. {
  4435. SetContextThink( &CNPC_Hunter::TeslaThink, gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ), HUNTER_ZAP_THINK );
  4436. }
  4437. }
  4438. //-----------------------------------------------------------------------------
  4439. // Our health is low. Show damage effects.
  4440. //-----------------------------------------------------------------------------
  4441. void CNPC_Hunter::BleedThink()
  4442. {
  4443. // Spurt blood from random points on the hunter's head.
  4444. Vector vecOrigin;
  4445. QAngle angDir;
  4446. GetAttachment( gm_nHeadCenterAttachment, vecOrigin, angDir );
  4447. Vector vecDir = RandomVector( -1, 1 );
  4448. VectorNormalize( vecDir );
  4449. VectorAngles( vecDir, Vector( 0, 0, 1 ), angDir );
  4450. vecDir *= gm_flHeadRadius;
  4451. DispatchParticleEffect( "blood_spurt_synth_01", vecOrigin + vecDir, angDir );
  4452. SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.6, 1.5 ), HUNTER_BLEED_THINK );
  4453. }
  4454. //-----------------------------------------------------------------------------
  4455. //-----------------------------------------------------------------------------
  4456. bool CNPC_Hunter::IsHeavyDamage( const CTakeDamageInfo &info )
  4457. {
  4458. if ( info.GetDamage() < 45 )
  4459. {
  4460. return false;
  4461. }
  4462. if ( info.GetDamage() < 180 )
  4463. {
  4464. if ( !m_HeavyDamageDelay.Expired() || !BaseClass::IsHeavyDamage( info ) )
  4465. {
  4466. return false;
  4467. }
  4468. }
  4469. m_HeavyDamageDelay.Set( 15, 25 );
  4470. return true;
  4471. }
  4472. //-----------------------------------------------------------------------------
  4473. // We've taken some damage. Maybe we should flinch because of it.
  4474. //-----------------------------------------------------------------------------
  4475. void CNPC_Hunter::ConsiderFlinching( const CTakeDamageInfo &info )
  4476. {
  4477. if ( !m_FlinchTimer.Expired() )
  4478. {
  4479. // Someone is whaling on us. Push out the timer so we don't keep flinching.
  4480. m_FlinchTimer.Set( random->RandomFloat( 0.3 ) );
  4481. return;
  4482. }
  4483. if ( GetState() == NPC_STATE_SCRIPT )
  4484. {
  4485. return;
  4486. }
  4487. Activity eGesture = ACT_HUNTER_FLINCH_N;
  4488. Vector forward;
  4489. GetVectors( &forward, NULL, NULL );
  4490. Vector vecForceDir = info.GetDamageForce();
  4491. VectorNormalize( vecForceDir );
  4492. float flDot = DotProduct( forward, vecForceDir );
  4493. if ( flDot > 0.707 )
  4494. {
  4495. // flinch forward
  4496. eGesture = ACT_HUNTER_FLINCH_N;
  4497. }
  4498. else if ( flDot < -0.707 )
  4499. {
  4500. // flinch back
  4501. eGesture = ACT_HUNTER_FLINCH_S;
  4502. }
  4503. else
  4504. {
  4505. // flinch left or right
  4506. Vector cross = CrossProduct( forward, vecForceDir );
  4507. if ( cross.z > 0 )
  4508. {
  4509. eGesture = ACT_HUNTER_FLINCH_W;
  4510. }
  4511. else
  4512. {
  4513. eGesture = ACT_HUNTER_FLINCH_E;
  4514. }
  4515. }
  4516. if ( !IsPlayingGesture( eGesture ) )
  4517. {
  4518. RestartGesture( eGesture );
  4519. m_FlinchTimer.Set( random->RandomFloat( 0.3, 1.0 ) );
  4520. }
  4521. }
  4522. //-----------------------------------------------------------------------------
  4523. // This is done from a think function because when the hunter is killed,
  4524. // the physics code puts the vehicle's pre-collision velocity back so the jostle
  4525. // is basically discared.
  4526. //-----------------------------------------------------------------------------
  4527. void CNPC_Hunter::JostleVehicleThink()
  4528. {
  4529. CBaseEntity *pInflictor = m_hHitByVehicle;
  4530. if ( !pInflictor )
  4531. return;
  4532. Vector vecVelDir = pInflictor->GetSmoothedVelocity();
  4533. float flSpeed = VectorNormalize( vecVelDir );
  4534. Vector vecForce = CrossProduct( vecVelDir, Vector( 0, 0, 1 ) );
  4535. if ( DotProduct( vecForce, GetAbsOrigin() ) < DotProduct( vecForce, pInflictor->GetAbsOrigin() ) )
  4536. {
  4537. // We're to the left of the vehicle that's hitting us.
  4538. vecForce *= -1;
  4539. }
  4540. VectorNormalize( vecForce );
  4541. vecForce.z = 1.0;
  4542. float flForceScale = RemapValClamped( flSpeed, hunter_jostle_car_min_speed.GetFloat(), hunter_jostle_car_max_speed.GetFloat(), 50.0f, 150.0f );
  4543. vecForce *= ( flForceScale * pInflictor->VPhysicsGetObject()->GetMass() );
  4544. pInflictor->VPhysicsGetObject()->ApplyForceOffset( vecForce, WorldSpaceCenter() );
  4545. }
  4546. //-----------------------------------------------------------------------------
  4547. //-----------------------------------------------------------------------------
  4548. int CNPC_Hunter::OnTakeDamage( const CTakeDamageInfo &info )
  4549. {
  4550. CTakeDamageInfo myInfo = info;
  4551. if ( ( info.GetDamageType() & DMG_CRUSH ) && !( info.GetDamageType() & DMG_VEHICLE ) )
  4552. {
  4553. // Don't take damage from physics objects that weren't thrown by the player.
  4554. CBaseEntity *pInflictor = info.GetInflictor();
  4555. IPhysicsObject *pObj = pInflictor->VPhysicsGetObject();
  4556. //Assert( pObj );
  4557. if ( !pObj || !pInflictor->HasPhysicsAttacker( 4.0 ) )
  4558. {
  4559. myInfo.SetDamage( 0 );
  4560. }
  4561. else
  4562. {
  4563. // Physics objects that have flechettes stuck in them spoof
  4564. // a flechette hitting us so we dissolve when killed and award
  4565. // the achievement of killing a hunter with its flechettes.
  4566. CUtlVector<CBaseEntity *> children;
  4567. GetAllChildren( pInflictor, children );
  4568. for (int i = 0; i < children.Count(); i++ )
  4569. {
  4570. CBaseEntity *pent = children.Element( i );
  4571. if ( dynamic_cast<CHunterFlechette *>( pent ) )
  4572. {
  4573. myInfo.SetInflictor( pent );
  4574. myInfo.SetDamageType( myInfo.GetDamageType() | DMG_DISSOLVE );
  4575. }
  4576. }
  4577. }
  4578. }
  4579. return BaseClass::OnTakeDamage( myInfo );
  4580. }
  4581. //-----------------------------------------------------------------------------
  4582. //-----------------------------------------------------------------------------
  4583. int CNPC_Hunter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  4584. {
  4585. CTakeDamageInfo myInfo = info;
  4586. // don't take damage from my own weapons!!!
  4587. // Exception: I "own" a magnade if it's glued to me.
  4588. CBaseEntity *pInflictor = info.GetInflictor();
  4589. CBaseEntity *pAttacker = info.GetAttacker();
  4590. if ( pInflictor )
  4591. {
  4592. if ( IsStriderBuster( pInflictor ) )
  4593. {
  4594. // Get a tesla effect on our hitboxes for a little while.
  4595. SetContextThink( &CNPC_Hunter::TeslaThink, gpGlobals->curtime, HUNTER_ZAP_THINK );
  4596. m_flTeslaStopTime = gpGlobals->curtime + 2.0f;
  4597. myInfo.SetDamage( sk_hunter_dmg_from_striderbuster.GetFloat() ) ;
  4598. SetCondition( COND_HUNTER_STAGGERED );
  4599. }
  4600. else if ( pInflictor->ClassMatches( GetClassname() ) && !( info.GetDamageType() == DMG_GENERIC ) )
  4601. {
  4602. return 0;
  4603. }
  4604. else if ( pInflictor->ClassMatches( "hunter_flechette" ) )
  4605. {
  4606. if ( !( ( CHunterFlechette *)pInflictor )->WasThrownBack() )
  4607. {
  4608. // Flechettes only hurt us if they were thrown back at us by the player. This prevents
  4609. // hunters from hurting themselves when they walk into their own flechette clusters.
  4610. return 0;
  4611. }
  4612. }
  4613. }
  4614. if ( m_EscortBehavior.GetFollowTarget() && m_EscortBehavior.GetFollowTarget() == pAttacker )
  4615. {
  4616. return 0;
  4617. }
  4618. bool bHitByUnoccupiedCar = false;
  4619. if ( ( ( info.GetDamageType() & DMG_CRUSH ) && ( pAttacker && pAttacker->IsPlayer() ) ) ||
  4620. ( info.GetDamageType() & DMG_VEHICLE ) )
  4621. {
  4622. // myInfo, not info! it may have been modified above.
  4623. float flDamage = myInfo.GetDamage();
  4624. if ( flDamage < HUNTER_MIN_PHYSICS_DAMAGE )
  4625. {
  4626. //DevMsg( "hunter: <<<< ZERO PHYSICS DAMAGE: %f\n", flDamage );
  4627. myInfo.SetDamage( 0 );
  4628. }
  4629. else
  4630. {
  4631. CBaseEntity *pInflictor = info.GetInflictor();
  4632. if ( ( info.GetDamageType() & DMG_VEHICLE ) ||
  4633. ( pInflictor && pInflictor->GetServerVehicle() &&
  4634. ( ( bHitByUnoccupiedCar = ( dynamic_cast<CPropVehicleDriveable *>(pInflictor) && static_cast<CPropVehicleDriveable *>(pInflictor)->GetDriver() == NULL ) ) == false ) ) )
  4635. {
  4636. // Adjust the damage from vehicles.
  4637. flDamage *= sk_hunter_vehicle_damage_scale.GetFloat();
  4638. myInfo.SetDamage( flDamage );
  4639. // Apply a force to jostle the vehicle that hit us.
  4640. // Pick a force direction based on which side we're on relative to the vehicle's motion.
  4641. Vector vecVelDir = pInflictor->GetSmoothedVelocity();
  4642. if ( vecVelDir.Length() >= hunter_jostle_car_min_speed.GetFloat() )
  4643. {
  4644. EmitSound( "NPC_Hunter.HitByVehicle" );
  4645. m_hHitByVehicle = pInflictor;
  4646. SetContextThink( &CNPC_Hunter::JostleVehicleThink, gpGlobals->curtime, HUNTER_JOSTLE_VEHICLE_THINK );
  4647. }
  4648. }
  4649. if ( !bHitByUnoccupiedCar )
  4650. {
  4651. SetCondition( COND_HUNTER_STAGGERED );
  4652. }
  4653. }
  4654. //DevMsg( "hunter: >>>> PHYSICS DAMAGE: %f (was %f)\n", flDamage, info.GetDamage() );
  4655. }
  4656. // Show damage effects if we actually took damage.
  4657. if ( ( myInfo.GetDamageType() & ( DMG_CRUSH | DMG_BLAST ) ) && ( myInfo.GetDamage() > 0 ) )
  4658. {
  4659. if ( !bHitByUnoccupiedCar )
  4660. SetCondition( COND_HUNTER_STAGGERED );
  4661. }
  4662. if ( HasCondition( COND_HUNTER_STAGGERED ) )
  4663. {
  4664. // Throw a bunch of gibs out
  4665. Vector vecForceDir = -myInfo.GetDamageForce();
  4666. VectorNormalize( vecForceDir );
  4667. PhysicsDamageEffect( myInfo.GetDamagePosition(), vecForceDir );
  4668. // Stagger away from the direction the damage came from.
  4669. m_vecStaggerDir = myInfo.GetDamageForce();
  4670. VectorNormalize( m_vecStaggerDir );
  4671. }
  4672. // Take less damage from citizens and Alyx, otherwise hunters go down too easily.
  4673. float flScale = 1.0;
  4674. if ( pAttacker &&
  4675. ( ( pAttacker->Classify() == CLASS_CITIZEN_REBEL ) ||
  4676. ( pAttacker->Classify() == CLASS_PLAYER_ALLY ) ||
  4677. ( pAttacker->Classify() == CLASS_PLAYER_ALLY_VITAL ) ) )
  4678. {
  4679. flScale *= sk_hunter_citizen_damage_scale.GetFloat();
  4680. }
  4681. if ( flScale != 0 )
  4682. {
  4683. // We're taking a nonzero amount of damage.
  4684. // If we're not staggering, consider flinching!
  4685. if ( !HasCondition( COND_HUNTER_STAGGERED ) )
  4686. {
  4687. ConsiderFlinching( info );
  4688. }
  4689. if( pAttacker && pAttacker->IsPlayer() )
  4690. {
  4691. // This block of code will distract the Hunter back to the player if the
  4692. // player does harm to the Hunter but is not the Hunter's current enemy.
  4693. // This is achieved by updating the Hunter's enemy memory of the player and
  4694. // making the Hunter's current enemy invalid for a short time.
  4695. if( !GetEnemy() || !GetEnemy()->IsPlayer() )
  4696. {
  4697. UpdateEnemyMemory( pAttacker, pAttacker->GetAbsOrigin(), this );
  4698. if( GetEnemy() )
  4699. {
  4700. // Gotta forget about this person for a little bit.
  4701. GetEnemies()->SetTimeValidEnemy( GetEnemy(), gpGlobals->curtime + HUNTER_IGNORE_ENEMY_TIME );
  4702. }
  4703. }
  4704. }
  4705. float flDamage = myInfo.GetDamage() * flScale;
  4706. myInfo.SetDamage( flDamage );
  4707. }
  4708. int nRet = BaseClass::OnTakeDamage_Alive( myInfo );
  4709. m_EscortBehavior.OnDamage( myInfo );
  4710. // Spark at 30% health.
  4711. if ( !IsBleeding() && ( GetHealth() <= sk_hunter_health.GetInt() * 0.3 ) )
  4712. {
  4713. StartBleeding();
  4714. }
  4715. if ( IsUsingSiegeTargets() && info.GetAttacker() != NULL && info.GetAttacker()->IsPlayer() )
  4716. {
  4717. // Defend myself. Try to siege attack immediately.
  4718. m_flTimeNextSiegeTargetAttack = gpGlobals->curtime;
  4719. }
  4720. return nRet;
  4721. }
  4722. //-----------------------------------------------------------------------------
  4723. //-----------------------------------------------------------------------------
  4724. void CNPC_Hunter::Event_Killed( const CTakeDamageInfo &info )
  4725. {
  4726. // Remember the killing blow to make decisions about ragdolling.
  4727. m_nKillingDamageType = info.GetDamageType();
  4728. if ( m_EscortBehavior.GetFollowTarget() )
  4729. {
  4730. if ( AIGetNumFollowers( m_EscortBehavior.GetFollowTarget(), m_iClassname ) == 1 )
  4731. {
  4732. m_EscortBehavior.GetEscortTarget()->AlertSound();
  4733. if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
  4734. {
  4735. m_EscortBehavior.GetEscortTarget()->UpdateEnemyMemory( UTIL_GetLocalPlayer(), UTIL_GetLocalPlayer()->GetAbsOrigin(), this );
  4736. }
  4737. }
  4738. }
  4739. if ( info.GetDamageType() & DMG_VEHICLE )
  4740. {
  4741. bool bWasRunDown = false;
  4742. int iRundownCounter = 0;
  4743. if ( GetSquad() )
  4744. {
  4745. if ( !m_IgnoreVehicleTimer.Expired() )
  4746. {
  4747. GetSquad()->GetSquadData( HUNTER_RUNDOWN_SQUADDATA, &iRundownCounter );
  4748. GetSquad()->SetSquadData( HUNTER_RUNDOWN_SQUADDATA, iRundownCounter + 1 );
  4749. bWasRunDown = true;
  4750. }
  4751. }
  4752. if ( hunter_dodge_debug.GetBool() )
  4753. Msg( "Hunter %d was%s run down\n", entindex(), ( bWasRunDown ) ? "" : " not" );
  4754. // Death by vehicle! Decrement the hunters to run over counter.
  4755. // When the counter reaches zero hunters will start dodging.
  4756. if ( GlobalEntity_GetCounter( s_iszHuntersToRunOver ) > 0 )
  4757. {
  4758. GlobalEntity_AddToCounter( s_iszHuntersToRunOver, -1 );
  4759. }
  4760. }
  4761. // Stop all our thinks
  4762. SetContextThink( NULL, 0, HUNTER_BLEED_THINK );
  4763. StopParticleEffects( this );
  4764. BaseClass::Event_Killed( info );
  4765. }
  4766. //-----------------------------------------------------------------------------
  4767. //-----------------------------------------------------------------------------
  4768. void CNPC_Hunter::StartBleeding()
  4769. {
  4770. // Do this even if we're already bleeding (see OnRestore).
  4771. m_bIsBleeding = true;
  4772. // Start gushing blood from our... anus or something.
  4773. DispatchParticleEffect( "blood_drip_synth_01", PATTACH_POINT_FOLLOW, this, gm_nHeadBottomAttachment );
  4774. // Emit spurts of our blood
  4775. SetContextThink( &CNPC_Hunter::BleedThink, gpGlobals->curtime + 0.1, HUNTER_BLEED_THINK );
  4776. }
  4777. //-----------------------------------------------------------------------------
  4778. //-----------------------------------------------------------------------------
  4779. float CNPC_Hunter::MaxYawSpeed()
  4780. {
  4781. if ( IsStriderBuster( GetEnemy() ) )
  4782. {
  4783. return 60;
  4784. }
  4785. if ( GetActivity() == ACT_HUNTER_ANGRY )
  4786. return 0;
  4787. if ( GetActivity() == ACT_HUNTER_CHARGE_RUN )
  4788. return 5;
  4789. if ( GetActivity() == ACT_HUNTER_IDLE_PLANTED )
  4790. return 0;
  4791. if ( GetActivity() == ACT_HUNTER_RANGE_ATTACK2_UNPLANTED )
  4792. return 180;
  4793. switch ( GetActivity() )
  4794. {
  4795. case ACT_RANGE_ATTACK2:
  4796. {
  4797. return 0;
  4798. }
  4799. case ACT_90_LEFT:
  4800. case ACT_90_RIGHT:
  4801. {
  4802. return 45;
  4803. }
  4804. case ACT_TURN_LEFT:
  4805. case ACT_TURN_RIGHT:
  4806. {
  4807. return 45;
  4808. }
  4809. case ACT_WALK:
  4810. {
  4811. return 25;
  4812. }
  4813. default:
  4814. {
  4815. return 35;
  4816. }
  4817. }
  4818. }
  4819. //-----------------------------------------------------------------------------
  4820. //-----------------------------------------------------------------------------
  4821. bool CNPC_Hunter::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const
  4822. {
  4823. float MAX_JUMP_RISE = 220.0f;
  4824. float MAX_JUMP_DISTANCE = 512.0f;
  4825. float MAX_JUMP_DROP = 384.0f;
  4826. trace_t tr;
  4827. UTIL_TraceHull( startPos, startPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
  4828. if ( tr.startsolid )
  4829. {
  4830. // Trying to start a jump in solid! Consider checking for this in CAI_MoveProbe::JumpMoveLimit.
  4831. Assert( 0 );
  4832. return false;
  4833. }
  4834. if ( BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE ) )
  4835. {
  4836. return true;
  4837. }
  4838. return false;
  4839. }
  4840. //-----------------------------------------------------------------------------
  4841. // Let the probe know I can run through small debris
  4842. // Stolen shamelessly from the Antlion Guard
  4843. //-----------------------------------------------------------------------------
  4844. bool CNPC_Hunter::ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity )
  4845. {
  4846. if ( s_iszPhysPropClassname != pEntity->m_iClassname )
  4847. return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );
  4848. if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
  4849. {
  4850. IPhysicsObject *pPhysObj = pEntity->VPhysicsGetObject();
  4851. if( pPhysObj && pPhysObj->GetMass() <= 500.0f )
  4852. {
  4853. return false;
  4854. }
  4855. }
  4856. return BaseClass::ShouldProbeCollideAgainstEntity( pEntity );
  4857. }
  4858. //-----------------------------------------------------------------------------
  4859. //-----------------------------------------------------------------------------
  4860. void CNPC_Hunter::DoMuzzleFlash( int nAttachment )
  4861. {
  4862. BaseClass::DoMuzzleFlash();
  4863. DispatchParticleEffect( "hunter_muzzle_flash", PATTACH_POINT_FOLLOW, this, nAttachment );
  4864. // Dispatch the elight
  4865. CEffectData data;
  4866. data.m_nAttachmentIndex = nAttachment;
  4867. data.m_nEntIndex = entindex();
  4868. DispatchEffect( "HunterMuzzleFlash", data );
  4869. }
  4870. //-----------------------------------------------------------------------------
  4871. //-----------------------------------------------------------------------------
  4872. int CNPC_Hunter::CountRangedAttackers()
  4873. {
  4874. CBaseEntity *pEnemy = GetEnemy();
  4875. if ( !pEnemy )
  4876. {
  4877. return 0;
  4878. }
  4879. int nAttackers = 0;
  4880. for ( int i = 0; i < g_Hunters.Count(); i++ )
  4881. {
  4882. CNPC_Hunter *pOtherHunter = g_Hunters[i];
  4883. if ( pOtherHunter->GetEnemy() == pEnemy )
  4884. {
  4885. if ( pOtherHunter->IsCurSchedule( SCHED_HUNTER_RANGE_ATTACK2 ) )
  4886. {
  4887. nAttackers++;
  4888. }
  4889. }
  4890. }
  4891. return nAttackers;
  4892. }
  4893. //-----------------------------------------------------------------------------
  4894. //-----------------------------------------------------------------------------
  4895. void CNPC_Hunter::DelayRangedAttackers( float minDelay, float maxDelay, bool bForced )
  4896. {
  4897. float delayMultiplier = ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) ? 1.25 : 1.0;
  4898. if ( !m_bEnableSquadShootDelay && !bForced )
  4899. {
  4900. m_flNextRangeAttack2Time = gpGlobals->curtime + random->RandomFloat( minDelay, maxDelay ) * delayMultiplier;
  4901. return;
  4902. }
  4903. CBaseEntity *pEnemy = GetEnemy();
  4904. for ( int i = 0; i < g_Hunters.Count(); i++ )
  4905. {
  4906. CNPC_Hunter *pOtherHunter = g_Hunters[i];
  4907. if ( pOtherHunter->GetEnemy() == pEnemy )
  4908. {
  4909. float nextTime = gpGlobals->curtime + random->RandomFloat( minDelay, maxDelay ) * delayMultiplier;
  4910. if ( nextTime > pOtherHunter->m_flNextRangeAttack2Time )
  4911. pOtherHunter->m_flNextRangeAttack2Time = nextTime;
  4912. }
  4913. }
  4914. }
  4915. //-----------------------------------------------------------------------------
  4916. // Given a target to shoot at, decide where to aim.
  4917. //-----------------------------------------------------------------------------
  4918. void CNPC_Hunter::GetShootDir( Vector &vecDir, const Vector &vecSrc, CBaseEntity *pTargetEntity, bool bStriderBuster, int nShotNum, bool bSingleShot )
  4919. {
  4920. //RestartGesture( ACT_HUNTER_GESTURE_SHOOT );
  4921. EmitSound( "NPC_Hunter.FlechetteShoot" );
  4922. Vector vecBodyTarget;
  4923. if( pTargetEntity->Classify() == CLASS_PLAYER_ALLY_VITAL )
  4924. {
  4925. // Shooting at Alyx, most likely (in EP2). The attack is designed to displace
  4926. // her, not necessarily actually harm her. So shoot at the area around her feet.
  4927. vecBodyTarget = pTargetEntity->GetAbsOrigin();
  4928. }
  4929. else
  4930. {
  4931. vecBodyTarget = pTargetEntity->BodyTarget( vecSrc );
  4932. }
  4933. Vector vecTarget = vecBodyTarget;
  4934. Vector vecDelta = pTargetEntity->GetAbsOrigin() - GetAbsOrigin();
  4935. float flDist = vecDelta.Length();
  4936. if ( !bStriderBuster )
  4937. {
  4938. // If we're not firing at a strider buster, miss in an entertaining way for the
  4939. // first three shots of each volley.
  4940. if ( ( nShotNum < 3 ) && ( flDist > 200 ) )
  4941. {
  4942. Vector vecTargetForward;
  4943. Vector vecTargetRight;
  4944. pTargetEntity->GetVectors( &vecTargetForward, &vecTargetRight, NULL );
  4945. Vector vecForward;
  4946. GetVectors( &vecForward, NULL, NULL );
  4947. float flDot = DotProduct( vecTargetForward, vecForward );
  4948. if ( flDot < -0.8f )
  4949. {
  4950. // Our target is facing us, shoot the ground between us.
  4951. float flPerc = 0.7 + ( 0.1 * nShotNum );
  4952. vecTarget = GetAbsOrigin() + ( flPerc * ( pTargetEntity->GetAbsOrigin() - GetAbsOrigin() ) );
  4953. }
  4954. else if ( flDot > 0.8f )
  4955. {
  4956. // Our target is facing away from us, shoot to the left or right.
  4957. Vector vecMissDir = vecTargetRight;
  4958. if ( m_bMissLeft )
  4959. {
  4960. vecMissDir *= -1.0f;
  4961. }
  4962. vecTarget = pTargetEntity->EyePosition() + ( 36.0f * ( 3 - nShotNum ) ) * vecMissDir;
  4963. }
  4964. else
  4965. {
  4966. // Our target is facing vaguely perpendicular to us, shoot across their view.
  4967. vecTarget = pTargetEntity->EyePosition() + ( 36.0f * ( 3 - nShotNum ) ) * vecTargetForward;
  4968. }
  4969. }
  4970. // If we can't see them, shoot where we last saw them.
  4971. else if ( !HasCondition( COND_SEE_ENEMY ) )
  4972. {
  4973. Vector vecDelta = vecTarget - pTargetEntity->GetAbsOrigin();
  4974. vecTarget = m_vecEnemyLastSeen + vecDelta;
  4975. }
  4976. }
  4977. else
  4978. {
  4979. // If we're firing at a striderbuster, lead it.
  4980. float flSpeed = hunter_flechette_speed.GetFloat();
  4981. if ( !flSpeed )
  4982. {
  4983. flSpeed = 2500.0f;
  4984. }
  4985. flSpeed *= 1.5;
  4986. float flDeltaTime = flDist / flSpeed;
  4987. vecTarget = vecTarget + flDeltaTime * pTargetEntity->GetSmoothedVelocity();
  4988. }
  4989. vecDir = vecTarget - vecSrc;
  4990. VectorNormalize( vecDir );
  4991. }
  4992. //-----------------------------------------------------------------------------
  4993. // Ensures that we don't exceed our pitch/yaw limits when shooting flechettes.
  4994. // Returns true if we had to clamp, false if not.
  4995. //-----------------------------------------------------------------------------
  4996. bool CNPC_Hunter::ClampShootDir( Vector &vecDir )
  4997. {
  4998. Vector vecDir2D = vecDir;
  4999. vecDir2D.z = 0;
  5000. Vector vecForward;
  5001. GetVectors( &vecForward, NULL, NULL );
  5002. Vector vecForward2D = vecForward;
  5003. vecForward2D.z = 0;
  5004. float flDot = DotProduct( vecForward2D, vecDir2D );
  5005. if ( flDot >= HUNTER_SHOOT_MAX_YAW_COS )
  5006. {
  5007. // No need to clamp.
  5008. return false;
  5009. }
  5010. Vector vecAxis;
  5011. CrossProduct( vecDir, vecForward, vecAxis );
  5012. VectorNormalize( vecAxis );
  5013. Quaternion q;
  5014. AxisAngleQuaternion( vecAxis, -HUNTER_SHOOT_MAX_YAW_DEG, q );
  5015. matrix3x4_t rot;
  5016. QuaternionMatrix( q, rot );
  5017. VectorRotate( vecForward, rot, vecDir );
  5018. VectorNormalize( vecDir );
  5019. return true;
  5020. }
  5021. //-----------------------------------------------------------------------------
  5022. //-----------------------------------------------------------------------------
  5023. bool CNPC_Hunter::ShouldSeekTarget( CBaseEntity *pTargetEntity, bool bStriderBuster )
  5024. {
  5025. bool bSeek = false;
  5026. if ( bStriderBuster )
  5027. {
  5028. bool bSeek = false;
  5029. if ( pTargetEntity->VPhysicsGetObject() && ( pTargetEntity->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) )
  5030. {
  5031. bSeek = true;
  5032. }
  5033. else if ( StriderBuster_NumFlechettesAttached( pTargetEntity ) == 0 )
  5034. {
  5035. if ( StriderBuster_IsAttachedStriderBuster(pTargetEntity) )
  5036. {
  5037. bSeek = true;
  5038. }
  5039. else if ( hunter_seek_thrown_striderbusters_tolerance.GetFloat() > 0.0 )
  5040. {
  5041. CNPC_Strider *pEscortTarget = m_EscortBehavior.GetEscortTarget();
  5042. if ( pEscortTarget && ( pEscortTarget->GetAbsOrigin() - pTargetEntity->GetAbsOrigin() ).LengthSqr() < Square( hunter_seek_thrown_striderbusters_tolerance.GetFloat() ) )
  5043. {
  5044. bSeek = true;
  5045. }
  5046. }
  5047. }
  5048. }
  5049. return bSeek;
  5050. }
  5051. //-----------------------------------------------------------------------------
  5052. //-----------------------------------------------------------------------------
  5053. void CNPC_Hunter::BeginVolley( int nNum, float flStartTime )
  5054. {
  5055. m_nFlechettesQueued = nNum;
  5056. m_nClampedShots = 0;
  5057. m_flNextFlechetteTime = flStartTime;
  5058. }
  5059. //-----------------------------------------------------------------------------
  5060. //-----------------------------------------------------------------------------
  5061. bool CNPC_Hunter::ShootFlechette( CBaseEntity *pTargetEntity, bool bSingleShot )
  5062. {
  5063. if ( !pTargetEntity )
  5064. {
  5065. Assert( false );
  5066. return false;
  5067. }
  5068. int nShotNum = hunter_flechette_volley_size.GetInt() - m_nFlechettesQueued;
  5069. bool bStriderBuster = IsStriderBuster( pTargetEntity );
  5070. // Choose the next muzzle to shoot from.
  5071. Vector vecSrc;
  5072. QAngle angMuzzle;
  5073. if ( m_bTopMuzzle )
  5074. {
  5075. GetAttachment( gm_nTopGunAttachment, vecSrc, angMuzzle );
  5076. DoMuzzleFlash( gm_nTopGunAttachment );
  5077. }
  5078. else
  5079. {
  5080. GetAttachment( gm_nBottomGunAttachment, vecSrc, angMuzzle );
  5081. DoMuzzleFlash( gm_nBottomGunAttachment );
  5082. }
  5083. m_bTopMuzzle = !m_bTopMuzzle;
  5084. Vector vecDir;
  5085. GetShootDir( vecDir, vecSrc, pTargetEntity, bStriderBuster, nShotNum, bSingleShot );
  5086. bool bClamped = false;
  5087. if ( hunter_clamp_shots.GetBool() )
  5088. {
  5089. bClamped = ClampShootDir( vecDir );
  5090. }
  5091. CShotManipulator manipulator( vecDir );
  5092. Vector vecShoot;
  5093. if( IsUsingSiegeTargets() && nShotNum >= 2 && (nShotNum % 2) == 0 )
  5094. {
  5095. // Near perfect accuracy for these three shots, so they are likely to fly right into the windows.
  5096. // NOTE! In siege behavior in the map that this behavior was designed for (ep2_outland_10), the
  5097. // Hunters will only ever shoot at siege targets in siege mode. If you allow Hunters in siege mode
  5098. // to attack players or other NPCs, this accuracy bonus will apply unless we apply a bit more logic to it.
  5099. vecShoot = manipulator.ApplySpread( VECTOR_CONE_1DEGREES * 0.5, 1.0f );
  5100. }
  5101. else
  5102. {
  5103. vecShoot = manipulator.ApplySpread( VECTOR_CONE_4DEGREES, 1.0f );
  5104. }
  5105. QAngle angShoot;
  5106. VectorAngles( vecShoot, angShoot );
  5107. CHunterFlechette *pFlechette = CHunterFlechette::FlechetteCreate( vecSrc, angShoot, this );
  5108. pFlechette->AddEffects( EF_NOSHADOW );
  5109. vecShoot *= hunter_flechette_speed.GetFloat();
  5110. pFlechette->Shoot( vecShoot, bStriderBuster );
  5111. if ( ShouldSeekTarget( pTargetEntity, bStriderBuster ) )
  5112. {
  5113. pFlechette->SetSeekTarget( pTargetEntity );
  5114. }
  5115. if( nShotNum == 1 && pTargetEntity->Classify() == CLASS_PLAYER_ALLY_VITAL )
  5116. {
  5117. // Make this person afraid and react to ME, not to the flechettes.
  5118. // Otherwise they could be scared into running towards the hunter.
  5119. CSoundEnt::InsertSound( SOUND_DANGER|SOUND_CONTEXT_REACT_TO_SOURCE|SOUND_CONTEXT_EXCLUDE_COMBINE, pTargetEntity->EyePosition(), 180.0f, 2.0f, this );
  5120. }
  5121. return bClamped;
  5122. }
  5123. //-----------------------------------------------------------------------------
  5124. //-----------------------------------------------------------------------------
  5125. Vector CNPC_Hunter::LeftFootHit( float eventtime )
  5126. {
  5127. Vector footPosition;
  5128. GetAttachment( "left foot", footPosition );
  5129. CPASAttenuationFilter filter( this );
  5130. EmitSound( filter, entindex(), "NPC_Hunter.Footstep", &footPosition, eventtime );
  5131. FootFX( footPosition );
  5132. return footPosition;
  5133. }
  5134. //-----------------------------------------------------------------------------
  5135. //-----------------------------------------------------------------------------
  5136. Vector CNPC_Hunter::RightFootHit( float eventtime )
  5137. {
  5138. Vector footPosition;
  5139. GetAttachment( "right foot", footPosition );
  5140. CPASAttenuationFilter filter( this );
  5141. EmitSound( filter, entindex(), "NPC_Hunter.Footstep", &footPosition, eventtime );
  5142. FootFX( footPosition );
  5143. return footPosition;
  5144. }
  5145. //-----------------------------------------------------------------------------
  5146. //-----------------------------------------------------------------------------
  5147. Vector CNPC_Hunter::BackFootHit( float eventtime )
  5148. {
  5149. Vector footPosition;
  5150. GetAttachment( "back foot", footPosition );
  5151. CPASAttenuationFilter filter( this );
  5152. EmitSound( filter, entindex(), "NPC_Hunter.BackFootstep", &footPosition, eventtime );
  5153. FootFX( footPosition );
  5154. return footPosition;
  5155. }
  5156. //-----------------------------------------------------------------------------
  5157. //-----------------------------------------------------------------------------
  5158. void CNPC_Hunter::FootFX( const Vector &origin )
  5159. {
  5160. return;
  5161. // dvs TODO: foot dust? probably too expensive for these guys
  5162. /*trace_t tr;
  5163. AI_TraceLine( origin, origin - Vector(0,0,100), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  5164. float yaw = random->RandomInt(0,120);
  5165. for ( int i = 0; i < 3; i++ )
  5166. {
  5167. Vector dir = UTIL_YawToVector( yaw + i*120 ) * 10;
  5168. VectorNormalize( dir );
  5169. dir.z = 0.25;
  5170. VectorNormalize( dir );
  5171. g_pEffects->Dust( tr.endpos, dir, 12, 50 );
  5172. }*/
  5173. }
  5174. //-----------------------------------------------------------------------------
  5175. //-----------------------------------------------------------------------------
  5176. CBaseEntity *CNPC_Hunter::GetEnemyVehicle()
  5177. {
  5178. if ( GetEnemy() == NULL )
  5179. return NULL;
  5180. CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
  5181. if ( pCCEnemy != NULL )
  5182. return pCCEnemy->GetVehicleEntity();
  5183. return NULL;
  5184. }
  5185. //-----------------------------------------------------------------------------
  5186. //-----------------------------------------------------------------------------
  5187. void CNPC_Hunter::DrawDebugGeometryOverlays()
  5188. {
  5189. if (m_debugOverlays & OVERLAY_BBOX_BIT)
  5190. {
  5191. float flViewRange = acos(0.8);
  5192. Vector vEyeDir = EyeDirection2D( );
  5193. Vector vLeftDir, vRightDir;
  5194. float fSin, fCos;
  5195. SinCos( flViewRange, &fSin, &fCos );
  5196. vLeftDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
  5197. vLeftDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
  5198. vLeftDir.z = vEyeDir.z;
  5199. fSin = sin(-flViewRange);
  5200. fCos = cos(-flViewRange);
  5201. vRightDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
  5202. vRightDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
  5203. vRightDir.z = vEyeDir.z;
  5204. int nSeq = GetSequence();
  5205. if ( ( GetEntryNode( nSeq ) == gm_nPlantedNode ) && ( GetExitNode( nSeq ) == gm_nPlantedNode ) )
  5206. {
  5207. // planted - green
  5208. NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 0, 128, 0 );
  5209. }
  5210. else if ( ( GetEntryNode( nSeq ) == gm_nUnplantedNode ) && ( GetExitNode( nSeq ) == gm_nUnplantedNode ) )
  5211. {
  5212. // unplanted - blue
  5213. NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 0, 255, 128, 0 );
  5214. }
  5215. else if ( ( GetEntryNode( nSeq ) == gm_nUnplantedNode ) && ( GetExitNode( nSeq ) == gm_nPlantedNode ) )
  5216. {
  5217. // planting transition - cyan
  5218. NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 255, 128, 0 );
  5219. }
  5220. else if ( ( GetEntryNode( nSeq ) == gm_nPlantedNode ) && ( GetExitNode( nSeq ) == gm_nUnplantedNode ) )
  5221. {
  5222. // unplanting transition - purple
  5223. NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 255, 128, 0 );
  5224. }
  5225. else
  5226. {
  5227. // unknown / other node - red
  5228. Msg( "UNKNOWN: %s\n", GetSequenceName( GetSequence() ) );
  5229. NDebugOverlay::Box( GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 0, 128, 0 );
  5230. }
  5231. NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vLeftDir, 255, 0, 0, 50, 0 );
  5232. NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vRightDir, 255, 0, 0, 50, 0 );
  5233. NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-1), Vector(200,0,1), vEyeDir, 0, 255, 0, 50, 0 );
  5234. NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 128, 0 );
  5235. }
  5236. m_EscortBehavior.DrawDebugGeometryOverlays();
  5237. BaseClass::DrawDebugGeometryOverlays();
  5238. }
  5239. //-----------------------------------------------------------------------------
  5240. // Player has illuminated this NPC with the flashlight
  5241. //-----------------------------------------------------------------------------
  5242. void CNPC_Hunter::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
  5243. {
  5244. if ( m_bFlashlightInEyes )
  5245. return;
  5246. // Ignore the flashlight if it's not shining at my eyes
  5247. if ( PlayerFlashlightOnMyEyes( pPlayer ) )
  5248. {
  5249. //Msg( ">>>> SHINING FLASHLIGHT ON ME\n" );
  5250. m_bFlashlightInEyes = true;
  5251. SetExpression( "scenes/npc/hunter/hunter_eyeclose.vcd" );
  5252. m_flPupilDilateTime = gpGlobals->curtime + 0.2f;
  5253. }
  5254. }
  5255. //-----------------------------------------------------------------------------
  5256. //-----------------------------------------------------------------------------
  5257. bool CNPC_Hunter::PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer )
  5258. {
  5259. Vector vecEyes, vecEyeForward, vecPlayerForward;
  5260. GetAttachment( gm_nTopGunAttachment, vecEyes, &vecEyeForward );
  5261. pPlayer->EyeVectors( &vecPlayerForward );
  5262. Vector vecToEyes = (vecEyes - pPlayer->EyePosition());
  5263. //float flDist = VectorNormalize( vecToEyes );
  5264. float flDot = DotProduct( vecPlayerForward, vecToEyes );
  5265. if ( flDot < 0.98 )
  5266. return false;
  5267. // Check facing to ensure we're in front of her
  5268. Vector los = ( pPlayer->EyePosition() - EyePosition() );
  5269. los.z = 0;
  5270. VectorNormalize( los );
  5271. Vector facingDir = EyeDirection2D();
  5272. flDot = DotProduct( los, facingDir );
  5273. return ( flDot > 0.3 );
  5274. }
  5275. //-----------------------------------------------------------------------------
  5276. // Return a random expression for the specified state to play over
  5277. // the state's expression loop.
  5278. //-----------------------------------------------------------------------------
  5279. const char *CNPC_Hunter::SelectRandomExpressionForState( NPC_STATE state )
  5280. {
  5281. if ( m_bFlashlightInEyes )
  5282. return NULL;
  5283. if ( !hunter_random_expressions.GetBool() )
  5284. return NULL;
  5285. char *szExpressions[4] =
  5286. {
  5287. "scenes/npc/hunter/hunter_scan.vcd",
  5288. "scenes/npc/hunter/hunter_eyeclose.vcd",
  5289. "scenes/npc/hunter/hunter_roar.vcd",
  5290. "scenes/npc/hunter/hunter_pain.vcd"
  5291. };
  5292. int nIndex = random->RandomInt( 0, 3 );
  5293. //Msg( "RANDOM Expression: %s\n", szExpressions[nIndex] );
  5294. return szExpressions[nIndex];
  5295. }
  5296. //-----------------------------------------------------------------------------
  5297. //-----------------------------------------------------------------------------
  5298. void CNPC_Hunter::PlayExpressionForState( NPC_STATE state )
  5299. {
  5300. if ( m_bFlashlightInEyes )
  5301. {
  5302. return;
  5303. }
  5304. BaseClass::PlayExpressionForState( state );
  5305. }
  5306. //-----------------------------------------------------------------------------
  5307. // TODO: remove if we're not doing striderbuster stuff
  5308. //-----------------------------------------------------------------------------
  5309. void CNPC_Hunter::StriderBusterAttached( CBaseEntity *pAttached )
  5310. {
  5311. // Add another to the list
  5312. m_hAttachedBusters.AddToTail( pAttached );
  5313. SetCondition( COND_HUNTER_HIT_BY_STICKYBOMB );
  5314. if (m_hAttachedBusters.Count() == 1)
  5315. {
  5316. EmitSound( "NPC_Hunter.Alert" );
  5317. }
  5318. }
  5319. //-----------------------------------------------------------------------------
  5320. //-----------------------------------------------------------------------------
  5321. void CNPC_Hunter::StriderBusterDetached( CBaseEntity *pAttached )
  5322. {
  5323. int elem = m_hAttachedBusters.Find(pAttached);
  5324. if (elem >= 0)
  5325. {
  5326. m_hAttachedBusters.FastRemove(elem);
  5327. }
  5328. }
  5329. //-----------------------------------------------------------------------------
  5330. // Set direction that the hunter aims his body and eyes (guns).
  5331. //-----------------------------------------------------------------------------
  5332. void CNPC_Hunter::SetAim( const Vector &aimDir, float flInterval )
  5333. {
  5334. QAngle angDir;
  5335. VectorAngles( aimDir, angDir );
  5336. float curPitch = GetPoseParameter( gm_nBodyPitchPoseParam );
  5337. float curYaw = GetPoseParameter( gm_nBodyYawPoseParam );
  5338. float newPitch;
  5339. float newYaw;
  5340. if ( GetEnemy() )
  5341. {
  5342. // clamp and dampen movement
  5343. newPitch = curPitch + 0.8 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
  5344. float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
  5345. newYaw = curYaw + UTIL_AngleDiff( flRelativeYaw, curYaw );
  5346. }
  5347. else
  5348. {
  5349. // Sweep your weapon more slowly if you're not fighting someone
  5350. newPitch = curPitch + 0.6 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
  5351. float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
  5352. newYaw = curYaw + 0.6 * UTIL_AngleDiff( flRelativeYaw, curYaw );
  5353. }
  5354. newPitch = AngleNormalize( newPitch );
  5355. newYaw = AngleNormalize( newYaw );
  5356. //Msg( "pitch=%f, yaw=%f\n", newPitch, newYaw );
  5357. SetPoseParameter( gm_nAimPitchPoseParam, 0 );
  5358. SetPoseParameter( gm_nAimYawPoseParam, 0 );
  5359. SetPoseParameter( gm_nBodyPitchPoseParam, clamp( newPitch, -45, 45 ) );
  5360. SetPoseParameter( gm_nBodyYawPoseParam, clamp( newYaw, -45, 45 ) );
  5361. }
  5362. //-----------------------------------------------------------------------------
  5363. //-----------------------------------------------------------------------------
  5364. void CNPC_Hunter::RelaxAim( float flInterval )
  5365. {
  5366. float curPitch = GetPoseParameter( gm_nBodyPitchPoseParam );
  5367. float curYaw = GetPoseParameter( gm_nBodyYawPoseParam );
  5368. // dampen existing aim
  5369. float newPitch = AngleNormalize( UTIL_ApproachAngle( 0, curPitch, 3 ) );
  5370. float newYaw = AngleNormalize( UTIL_ApproachAngle( 0, curYaw, 2 ) );
  5371. SetPoseParameter( gm_nAimPitchPoseParam, 0 );
  5372. SetPoseParameter( gm_nAimYawPoseParam, 0 );
  5373. SetPoseParameter( gm_nBodyPitchPoseParam, clamp( newPitch, -45, 45 ) );
  5374. SetPoseParameter( gm_nBodyYawPoseParam, clamp( newYaw, -45, 45 ) );
  5375. }
  5376. //-----------------------------------------------------------------------------
  5377. //-----------------------------------------------------------------------------
  5378. void CNPC_Hunter::UpdateAim()
  5379. {
  5380. if ( !GetModelPtr() || !GetModelPtr()->SequencesAvailable() )
  5381. return;
  5382. float flInterval = GetAnimTimeInterval();
  5383. // Some activities look bad if we're giving our enemy the stinkeye.
  5384. int eActivity = GetActivity();
  5385. if ( GetEnemy() &&
  5386. GetState() != NPC_STATE_SCRIPT &&
  5387. ( eActivity != ACT_HUNTER_CHARGE_CRASH ) &&
  5388. ( eActivity != ACT_HUNTER_CHARGE_HIT ) )
  5389. {
  5390. Vector vecShootOrigin;
  5391. vecShootOrigin = Weapon_ShootPosition();
  5392. Vector vecShootDir = GetShootEnemyDir( vecShootOrigin, false );
  5393. SetAim( vecShootDir, flInterval );
  5394. }
  5395. else
  5396. {
  5397. RelaxAim( flInterval );
  5398. }
  5399. }
  5400. //-----------------------------------------------------------------------------
  5401. // Don't become a ragdoll until we've finished our death anim
  5402. //-----------------------------------------------------------------------------
  5403. bool CNPC_Hunter::CanBecomeRagdoll()
  5404. {
  5405. return ( m_nKillingDamageType & DMG_CRUSH ) ||
  5406. IsCurSchedule( SCHED_DIE, false ) || // Finished playing death anim, time to ragdoll
  5407. IsCurSchedule( SCHED_HUNTER_CHARGE_ENEMY, false ) || // While moving, it looks better to ragdoll instantly
  5408. IsCurSchedule( SCHED_SCRIPTED_RUN, false ) ||
  5409. ( GetActivity() == ACT_WALK ) || ( GetActivity() == ACT_RUN ) ||
  5410. GetCurSchedule() == NULL; // Failsafe
  5411. }
  5412. //-----------------------------------------------------------------------------
  5413. // Determines the best type of death anim to play based on how we died.
  5414. //-----------------------------------------------------------------------------
  5415. Activity CNPC_Hunter::GetDeathActivity()
  5416. {
  5417. return ACT_DIESIMPLE;
  5418. }
  5419. //-----------------------------------------------------------------------------
  5420. //-----------------------------------------------------------------------------
  5421. void CAI_HunterEscortBehavior::OnDamage( const CTakeDamageInfo &info )
  5422. {
  5423. if ( info.GetDamage() > 0 && info.GetAttacker()->IsPlayer() &&
  5424. GetFollowTarget() && ( AIGetNumFollowers( GetFollowTarget() ) > 1 ) &&
  5425. ( GetOuter()->GetSquad()->GetSquadSoundWaitTime() <= gpGlobals->curtime ) ) // && !FarFromFollowTarget()
  5426. {
  5427. // Start the clock ticking. We'll return the the strider when the timer elapses.
  5428. m_flTimeEscortReturn = gpGlobals->curtime + random->RandomFloat( 15.0f, 25.0f );
  5429. GetOuter()->GetSquad()->SetSquadSoundWaitTime( m_flTimeEscortReturn + 1.0 ); // prevent others from breaking escort
  5430. }
  5431. }
  5432. //-----------------------------------------------------------------------------
  5433. //-----------------------------------------------------------------------------
  5434. void CAI_HunterEscortBehavior::BuildScheduleTestBits()
  5435. {
  5436. BaseClass::BuildScheduleTestBits();
  5437. if ( ( m_flTimeEscortReturn != 0 ) && ( gpGlobals->curtime > m_flTimeEscortReturn ) )
  5438. {
  5439. // We're delinquent! Return to strider!
  5440. GetOuter()->ClearCustomInterruptCondition( COND_NEW_ENEMY );
  5441. GetOuter()->ClearCustomInterruptCondition( COND_SEE_ENEMY );
  5442. GetOuter()->ClearCustomInterruptCondition( COND_SEE_HATE );
  5443. GetOuter()->ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
  5444. GetOuter()->ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 );
  5445. }
  5446. }
  5447. //-----------------------------------------------------------------------------
  5448. //-----------------------------------------------------------------------------
  5449. void CAI_HunterEscortBehavior::CheckBreakEscort()
  5450. {
  5451. if ( m_flTimeEscortReturn != 0 && ( FarFromFollowTarget() || gpGlobals->curtime >= m_flTimeEscortReturn ) )
  5452. {
  5453. if ( FarFromFollowTarget() )
  5454. {
  5455. m_flTimeEscortReturn = gpGlobals->curtime;
  5456. }
  5457. else
  5458. {
  5459. m_flTimeEscortReturn = 0;
  5460. }
  5461. if ( GetOuter()->GetSquad() )
  5462. {
  5463. GetOuter()->GetSquad()->SetSquadSoundWaitTime( gpGlobals->curtime + random->RandomFloat( 5.0f, 12.0f ) );
  5464. }
  5465. }
  5466. }
  5467. //-----------------------------------------------------------------------------
  5468. //-----------------------------------------------------------------------------
  5469. void CAI_HunterEscortBehavior::GatherConditionsNotActive( void )
  5470. {
  5471. if ( m_bEnabled )
  5472. {
  5473. DistributeFreeHunters();
  5474. }
  5475. BaseClass::GatherConditionsNotActive();
  5476. }
  5477. //-----------------------------------------------------------------------------
  5478. //-----------------------------------------------------------------------------
  5479. void CAI_HunterEscortBehavior::GatherConditions( void )
  5480. {
  5481. m_bEnabled = true;
  5482. DistributeFreeHunters();
  5483. BaseClass::GatherConditions();
  5484. if ( GetEnemy() && GetEnemy()->IsPlayer() && HasCondition( COND_SEE_ENEMY ) )
  5485. {
  5486. if ( GetOuter()->GetSquad()->GetSquadSoundWaitTime() <= gpGlobals->curtime && ((CBasePlayer *)GetEnemy())->IsInAVehicle() )
  5487. {
  5488. m_flTimeEscortReturn = gpGlobals->curtime + random->RandomFloat( 15.0f, 25.0f );
  5489. GetOuter()->GetSquad()->SetSquadSoundWaitTime( m_flTimeEscortReturn + 1.0 ); // prevent others from breaking escort
  5490. }
  5491. }
  5492. }
  5493. //-----------------------------------------------------------------------------
  5494. //-----------------------------------------------------------------------------
  5495. bool CAI_HunterEscortBehavior::ShouldFollow()
  5496. {
  5497. if ( IsStriderBuster( GetEnemy() ) )
  5498. return false;
  5499. if ( HasCondition( COND_HEAR_PHYSICS_DANGER ) )
  5500. return false;
  5501. if ( m_flTimeEscortReturn <= gpGlobals->curtime )
  5502. {
  5503. return CAI_FollowBehavior::ShouldFollow();
  5504. }
  5505. return false;
  5506. }
  5507. //-----------------------------------------------------------------------------
  5508. //-----------------------------------------------------------------------------
  5509. void CAI_HunterEscortBehavior::BeginScheduleSelection()
  5510. {
  5511. BaseClass::BeginScheduleSelection();
  5512. Assert( m_SavedDistTooFar == GetOuter()->m_flDistTooFar );
  5513. GetOuter()->m_flDistTooFar *= 2;
  5514. }
  5515. //-----------------------------------------------------------------------------
  5516. //-----------------------------------------------------------------------------
  5517. int CAI_HunterEscortBehavior::SelectSchedule()
  5518. {
  5519. if( m_FollowDelay.IsRunning() && !m_FollowDelay.Expired() )
  5520. {
  5521. return FollowCallBaseSelectSchedule();
  5522. }
  5523. return BaseClass::SelectSchedule();
  5524. }
  5525. //-----------------------------------------------------------------------------
  5526. //-----------------------------------------------------------------------------
  5527. int CAI_HunterEscortBehavior::FollowCallBaseSelectSchedule()
  5528. {
  5529. if ( GetOuter()->GetState() == NPC_STATE_COMBAT )
  5530. {
  5531. return GetOuter()->SelectCombatSchedule();
  5532. }
  5533. return BaseClass::FollowCallBaseSelectSchedule();
  5534. }
  5535. //-----------------------------------------------------------------------------
  5536. //-----------------------------------------------------------------------------
  5537. void CAI_HunterEscortBehavior::StartTask( const Task_t *pTask )
  5538. {
  5539. switch( pTask->iTask )
  5540. {
  5541. case TASK_MOVE_TO_FOLLOW_POSITION:
  5542. {
  5543. if ( GetEnemy() )
  5544. {
  5545. if ( GetOuter()->OccupyStrategySlot( SQUAD_SLOT_RUN_SHOOT ) )
  5546. {
  5547. if ( GetOuter()->GetSquad()->GetSquadMemberNearestTo( GetEnemy()->GetAbsOrigin() ) == GetOuter() )
  5548. {
  5549. GetOuter()->BeginVolley( NUM_FLECHETTE_VOLLEY_ON_FOLLOW, gpGlobals->curtime + 1.0 + random->RandomFloat( 0, .25 ) + random->RandomFloat( 0, .25 ) );
  5550. }
  5551. else
  5552. {
  5553. GetOuter()->VacateStrategySlot();
  5554. }
  5555. }
  5556. }
  5557. BaseClass::StartTask( pTask );
  5558. break;
  5559. }
  5560. default:
  5561. BaseClass::StartTask( pTask );
  5562. }
  5563. }
  5564. //-----------------------------------------------------------------------------
  5565. //-----------------------------------------------------------------------------
  5566. void CAI_HunterEscortBehavior::RunTask( const Task_t *pTask )
  5567. {
  5568. switch( pTask->iTask )
  5569. {
  5570. case TASK_MOVE_TO_FOLLOW_POSITION:
  5571. {
  5572. if ( !GetFollowTarget() )
  5573. {
  5574. TaskFail( FAIL_NO_TARGET );
  5575. }
  5576. else
  5577. {
  5578. if ( GetEnemy() )
  5579. {
  5580. CNPC_Hunter *pHunter = GetOuter();
  5581. Vector vecEnemyLKP = pHunter->GetEnemyLKP();
  5582. pHunter->AddFacingTarget( pHunter->GetEnemy(), vecEnemyLKP, 1.0, 0.8 );
  5583. bool bVacate = false;
  5584. bool bHasSlot = pHunter->HasStrategySlot( SQUAD_SLOT_RUN_SHOOT );
  5585. if ( HasCondition( COND_SEE_ENEMY ) )
  5586. {
  5587. float maxDist = hunter_flechette_max_range.GetFloat() * 3;
  5588. float distSq = ( pHunter->GetAbsOrigin() - pHunter->GetEnemy()->GetAbsOrigin() ).Length2DSqr();
  5589. if ( distSq < Square( maxDist ) )
  5590. {
  5591. if ( gpGlobals->curtime >= pHunter->m_flNextFlechetteTime )
  5592. {
  5593. if ( !bHasSlot )
  5594. {
  5595. if ( GetOuter()->OccupyStrategySlot( SQUAD_SLOT_RUN_SHOOT ) )
  5596. {
  5597. if ( GetOuter()->GetSquad()->GetSquadMemberNearestTo( GetEnemy()->GetAbsOrigin() ) == GetOuter() )
  5598. {
  5599. bHasSlot = true;
  5600. }
  5601. else
  5602. {
  5603. GetOuter()->VacateStrategySlot();
  5604. }
  5605. }
  5606. }
  5607. if ( bHasSlot )
  5608. {
  5609. // Start the firing sound.
  5610. //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  5611. //if ( controller.SoundGetVolume( pHunter->m_pGunFiringSound ) == 0.0f )
  5612. //{
  5613. // controller.SoundChangeVolume( pHunter->m_pGunFiringSound, 1.0f, 0.0f );
  5614. //}
  5615. pHunter->ShootFlechette( GetEnemy(), true );
  5616. if ( --pHunter->m_nFlechettesQueued > 0 )
  5617. {
  5618. pHunter->m_flNextFlechetteTime = gpGlobals->curtime + hunter_flechette_delay.GetFloat();
  5619. }
  5620. else
  5621. {
  5622. // Stop the firing sound.
  5623. //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  5624. //controller.SoundChangeVolume( pHunter->m_pGunFiringSound, 0, 0.01f );
  5625. bVacate = true;
  5626. pHunter->BeginVolley( NUM_FLECHETTE_VOLLEY_ON_FOLLOW, gpGlobals->curtime + 1.0 + random->RandomFloat( 0, .25 ) + random->RandomFloat( 0, .25 ) );
  5627. }
  5628. }
  5629. }
  5630. }
  5631. else if ( bHasSlot )
  5632. {
  5633. bVacate = true;
  5634. }
  5635. }
  5636. else if ( bHasSlot )
  5637. {
  5638. bVacate = true;
  5639. }
  5640. if ( bVacate )
  5641. {
  5642. pHunter->VacateStrategySlot();
  5643. }
  5644. }
  5645. if ( m_FollowAttackTimer.Expired() && IsFollowTargetInRange( .8 ))
  5646. {
  5647. m_FollowAttackTimer.Set( 8, 24 );
  5648. TaskComplete();
  5649. }
  5650. else
  5651. {
  5652. BaseClass::RunTask( pTask );
  5653. }
  5654. }
  5655. break;
  5656. }
  5657. default:
  5658. BaseClass::RunTask( pTask );
  5659. }
  5660. }
  5661. //-----------------------------------------------------------------------------
  5662. //-----------------------------------------------------------------------------
  5663. void CAI_HunterEscortBehavior::FindFreeHunters( CUtlVector<CNPC_Hunter *> *pFreeHunters )
  5664. {
  5665. pFreeHunters->EnsureCapacity( g_Hunters.Count() );
  5666. int i;
  5667. for ( i = 0; i < g_Hunters.Count(); i++ )
  5668. {
  5669. CNPC_Hunter *pHunter = g_Hunters[i];
  5670. if ( pHunter->IsAlive() && pHunter->m_EscortBehavior.m_bEnabled )
  5671. {
  5672. if ( pHunter->m_EscortBehavior.GetFollowTarget() == NULL || !pHunter->m_EscortBehavior.GetFollowTarget()->IsAlive() )
  5673. {
  5674. pFreeHunters->AddToTail( pHunter);
  5675. }
  5676. }
  5677. }
  5678. }
  5679. //-----------------------------------------------------------------------------
  5680. //-----------------------------------------------------------------------------
  5681. void CAI_HunterEscortBehavior::DistributeFreeHunters()
  5682. {
  5683. if ( g_TimeLastDistributeFreeHunters != -1 && gpGlobals->curtime - g_TimeLastDistributeFreeHunters < FREE_HUNTER_DISTRIBUTE_INTERVAL )
  5684. {
  5685. return;
  5686. }
  5687. g_TimeLastDistributeFreeHunters = gpGlobals->curtime;
  5688. CUtlVector<CNPC_Hunter *> freeHunters;
  5689. int i;
  5690. FindFreeHunters( &freeHunters );
  5691. CAI_BaseNPC **ppNPCs = g_AI_Manager.AccessAIs();
  5692. for ( i = 0; i < g_AI_Manager.NumAIs() && freeHunters.Count(); i++ )
  5693. {
  5694. int nToAdd;
  5695. CNPC_Strider *pStrider = ( ppNPCs[i]->IsAlive() ) ? dynamic_cast<CNPC_Strider *>( ppNPCs[i] ) : NULL;
  5696. if ( pStrider && !pStrider->CarriedByDropship() )
  5697. {
  5698. if ( ( nToAdd = 3 - AIGetNumFollowers( pStrider ) ) > 0 )
  5699. {
  5700. for ( int j = freeHunters.Count() - 1; j >= 0 && nToAdd > 0; --j )
  5701. {
  5702. DevMsg( "npc_hunter %d assigned to npc_strider %d\n", freeHunters[j]->entindex(), pStrider->entindex() );
  5703. freeHunters[j]->FollowStrider( pStrider );
  5704. freeHunters.FastRemove( j );
  5705. nToAdd--;
  5706. }
  5707. }
  5708. }
  5709. }
  5710. for ( i = 0; i < freeHunters.Count(); i++ )
  5711. {
  5712. //DevMsg( "npc_hunter %d assigned to free_hunters_squad\n", freeHunters[i]->entindex() );
  5713. freeHunters[i]->m_EscortBehavior.SetFollowTarget( NULL );
  5714. freeHunters[i]->AddToSquad( AllocPooledString( "free_hunters_squad" ) );
  5715. }
  5716. #if 0
  5717. CBaseEntity *pHunterMaker = gEntList.FindEntityByClassname( NULL, "npc_hunter_maker" ); // TODO: this picks the same one every time!
  5718. if ( pHunterMaker )
  5719. {
  5720. for ( i = 0; i < freeHunters.Count(); i++ )
  5721. {
  5722. freeHunters[i]->m_EscortBehavior.SetFollowTarget( pHunterMaker );
  5723. }
  5724. }
  5725. #endif
  5726. }
  5727. //-----------------------------------------------------------------------------
  5728. //-----------------------------------------------------------------------------
  5729. void CAI_HunterEscortBehavior::DrawDebugGeometryOverlays()
  5730. {
  5731. if ( !GetFollowTarget() )
  5732. return;
  5733. Vector vecFollowPos = GetGoalPosition();
  5734. if ( FarFromFollowTarget() )
  5735. {
  5736. if ( gpGlobals->curtime >= m_flTimeEscortReturn )
  5737. {
  5738. NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 255, 0, 0, 0, true, 0 );
  5739. }
  5740. else
  5741. {
  5742. NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 255, 255, 0, 0, true, 0 );
  5743. }
  5744. }
  5745. else
  5746. {
  5747. NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 0, 255, 0, 0, true, 0 );
  5748. }
  5749. }
  5750. //-----------------------------------------------------------------------------
  5751. //-----------------------------------------------------------------------------
  5752. bool Hunter_IsHunter(CBaseEntity *pEnt)
  5753. {
  5754. return dynamic_cast<CNPC_Hunter *>(pEnt) != NULL;
  5755. }
  5756. //-----------------------------------------------------------------------------
  5757. //-----------------------------------------------------------------------------
  5758. void Hunter_StriderBusterLaunched( CBaseEntity *pBuster )
  5759. {
  5760. CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
  5761. int nAIs = g_AI_Manager.NumAIs();
  5762. for ( int i = 0; i < nAIs; i++ )
  5763. {
  5764. CAI_BaseNPC *pNPC = ppAIs[ i ];
  5765. if ( pNPC && ( pNPC->Classify() == CLASS_COMBINE_HUNTER ) && pNPC->m_lifeState == LIFE_ALIVE )
  5766. {
  5767. if ( !pNPC->GetEnemy() || !IsStriderBuster( pNPC->GetEnemy() ) )
  5768. {
  5769. Vector vecDelta = pNPC->GetAbsOrigin() - pBuster->GetAbsOrigin();
  5770. if ( vecDelta.Length2DSqr() < 9437184.0f ) // 3072 * 3072
  5771. {
  5772. pNPC->SetEnemy( pBuster );
  5773. pNPC->SetState( NPC_STATE_COMBAT );
  5774. pNPC->UpdateEnemyMemory( pBuster, pBuster->GetAbsOrigin() );
  5775. // Stop whatever we're doing.
  5776. pNPC->SetCondition( COND_SCHEDULE_DONE );
  5777. }
  5778. }
  5779. }
  5780. }
  5781. }
  5782. //-----------------------------------------------------------------------------
  5783. //-----------------------------------------------------------------------------
  5784. void Hunter_StriderBusterAttached( CBaseEntity *pHunter, CBaseEntity *pAttached )
  5785. {
  5786. Assert(dynamic_cast<CNPC_Hunter *>(pHunter));
  5787. static_cast<CNPC_Hunter *>(pHunter)->StriderBusterAttached(pAttached);
  5788. }
  5789. //-----------------------------------------------------------------------------
  5790. //-----------------------------------------------------------------------------
  5791. void Hunter_StriderBusterDetached( CBaseEntity *pHunter, CBaseEntity *pAttached )
  5792. {
  5793. Assert(dynamic_cast<CNPC_Hunter *>(pHunter));
  5794. static_cast<CNPC_Hunter *>(pHunter)->StriderBusterDetached(pAttached);
  5795. }
  5796. //-------------------------------------------------------------------------------------------------
  5797. //
  5798. // ep2_outland_12 custom npc makers
  5799. //
  5800. //-------------------------------------------------------------------------------------------------
  5801. class CHunterMaker : public CTemplateNPCMaker
  5802. {
  5803. typedef CTemplateNPCMaker BaseClass;
  5804. public:
  5805. void MakeMultipleNPCS( int nNPCs )
  5806. {
  5807. const float MIN_HEALTH_PCT = 0.2;
  5808. CUtlVector<CNPC_Hunter *> candidates;
  5809. CUtlVectorFixed<CNPC_Hunter *, 3> freeHunters;
  5810. CAI_HunterEscortBehavior::FindFreeHunters( &candidates );
  5811. freeHunters.EnsureCapacity( 3 );
  5812. int i;
  5813. for ( i = 0; i < candidates.Count() && freeHunters.Count() < 3; i++ )
  5814. {
  5815. if ( candidates[i]->GetHealth() > candidates[i]->GetMaxHealth() * MIN_HEALTH_PCT )
  5816. {
  5817. freeHunters.AddToTail( candidates[i] );
  5818. }
  5819. }
  5820. int nRequested = nNPCs;
  5821. if ( nNPCs < 3 )
  5822. {
  5823. nNPCs = MIN( 3, nNPCs + freeHunters.Count() );
  5824. }
  5825. int nSummoned = 0;
  5826. for ( i = 0; i < freeHunters.Count() && nNPCs; i++ )
  5827. {
  5828. freeHunters[i]->m_EscortBehavior.SetFollowTarget( this ); // this will make them not "free"
  5829. freeHunters[i]->SetName( m_iszTemplateName ); // this will force the hunter to get the FollowStrider input
  5830. nNPCs--;
  5831. nSummoned++;
  5832. }
  5833. DevMsg( "Requested %d to spawn, Summoning %d free hunters, spawning %d new hunters\n", nRequested, nSummoned, nNPCs );
  5834. if ( nNPCs )
  5835. {
  5836. BaseClass::MakeMultipleNPCS( nNPCs );
  5837. }
  5838. }
  5839. };
  5840. LINK_ENTITY_TO_CLASS( npc_hunter_maker, CHunterMaker );
  5841. //-------------------------------------------------------------------------------------------------
  5842. //
  5843. // Schedules
  5844. //
  5845. //-------------------------------------------------------------------------------------------------
  5846. AI_BEGIN_CUSTOM_NPC( npc_hunter, CNPC_Hunter )
  5847. DECLARE_TASK( TASK_HUNTER_AIM )
  5848. DECLARE_TASK( TASK_HUNTER_FIND_DODGE_POSITION )
  5849. DECLARE_TASK( TASK_HUNTER_DODGE )
  5850. DECLARE_TASK( TASK_HUNTER_PRE_RANGE_ATTACK2 )
  5851. DECLARE_TASK( TASK_HUNTER_SHOOT_COMMIT )
  5852. DECLARE_TASK( TASK_HUNTER_ANNOUNCE_FLANK )
  5853. DECLARE_TASK( TASK_HUNTER_BEGIN_FLANK )
  5854. DECLARE_TASK( TASK_HUNTER_STAGGER )
  5855. DECLARE_TASK( TASK_HUNTER_CORNERED_TIMER )
  5856. DECLARE_TASK( TASK_HUNTER_FIND_SIDESTEP_POSITION )
  5857. DECLARE_TASK( TASK_HUNTER_CHARGE )
  5858. DECLARE_TASK( TASK_HUNTER_FINISH_RANGE_ATTACK )
  5859. DECLARE_TASK( TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY )
  5860. DECLARE_TASK( TASK_HUNTER_CHARGE_DELAY )
  5861. DECLARE_ACTIVITY( ACT_HUNTER_DEPLOYRA2 )
  5862. DECLARE_ACTIVITY( ACT_HUNTER_DODGER )
  5863. DECLARE_ACTIVITY( ACT_HUNTER_DODGEL )
  5864. DECLARE_ACTIVITY( ACT_HUNTER_GESTURE_SHOOT )
  5865. DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_STICKYBOMB )
  5866. DECLARE_ACTIVITY( ACT_HUNTER_STAGGER )
  5867. DECLARE_ACTIVITY( ACT_DI_HUNTER_MELEE )
  5868. DECLARE_ACTIVITY( ACT_DI_HUNTER_THROW )
  5869. DECLARE_ACTIVITY( ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER )
  5870. DECLARE_ACTIVITY( ACT_HUNTER_ANGRY )
  5871. DECLARE_ACTIVITY( ACT_HUNTER_WALK_ANGRY )
  5872. DECLARE_ACTIVITY( ACT_HUNTER_FOUND_ENEMY )
  5873. DECLARE_ACTIVITY( ACT_HUNTER_FOUND_ENEMY_ACK )
  5874. DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_START )
  5875. DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_RUN )
  5876. DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_STOP )
  5877. DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_CRASH )
  5878. DECLARE_ACTIVITY( ACT_HUNTER_CHARGE_HIT )
  5879. DECLARE_ACTIVITY( ACT_HUNTER_RANGE_ATTACK2_UNPLANTED )
  5880. DECLARE_ACTIVITY( ACT_HUNTER_IDLE_PLANTED )
  5881. DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_N )
  5882. DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_S )
  5883. DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_E )
  5884. DECLARE_ACTIVITY( ACT_HUNTER_FLINCH_W )
  5885. DECLARE_INTERACTION( g_interactionHunterFoundEnemy );
  5886. DECLARE_SQUADSLOT( SQUAD_SLOT_HUNTER_CHARGE )
  5887. DECLARE_SQUADSLOT( SQUAD_SLOT_HUNTER_FLANK_FIRST )
  5888. DECLARE_SQUADSLOT( SQUAD_SLOT_RUN_SHOOT )
  5889. DECLARE_CONDITION( COND_HUNTER_SHOULD_PATROL )
  5890. DECLARE_CONDITION( COND_HUNTER_FORCED_FLANK_ENEMY )
  5891. DECLARE_CONDITION( COND_HUNTER_CAN_CHARGE_ENEMY )
  5892. DECLARE_CONDITION( COND_HUNTER_STAGGERED )
  5893. DECLARE_CONDITION( COND_HUNTER_IS_INDOORS )
  5894. DECLARE_CONDITION( COND_HUNTER_HIT_BY_STICKYBOMB )
  5895. DECLARE_CONDITION( COND_HUNTER_SEE_STRIDERBUSTER )
  5896. DECLARE_CONDITION( COND_HUNTER_FORCED_DODGE )
  5897. DECLARE_CONDITION( COND_HUNTER_INCOMING_VEHICLE )
  5898. DECLARE_CONDITION( COND_HUNTER_NEW_HINTGROUP )
  5899. DECLARE_CONDITION( COND_HUNTER_CANT_PLANT )
  5900. DECLARE_CONDITION( COND_HUNTER_SQUADMATE_FOUND_ENEMY )
  5901. DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_LEFT )
  5902. DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_RIGHT )
  5903. DECLARE_ANIMEVENT( AE_HUNTER_FOOTSTEP_BACK )
  5904. DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ANNOUNCE )
  5905. DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ATTACK_LEFT )
  5906. DECLARE_ANIMEVENT( AE_HUNTER_MELEE_ATTACK_RIGHT )
  5907. DECLARE_ANIMEVENT( AE_HUNTER_DIE )
  5908. DECLARE_ANIMEVENT( AE_HUNTER_SPRAY_BLOOD )
  5909. DECLARE_ANIMEVENT( AE_HUNTER_START_EXPRESSION )
  5910. DECLARE_ANIMEVENT( AE_HUNTER_END_EXPRESSION )
  5911. //=========================================================
  5912. // Attack (Deploy/shoot/finish)
  5913. //=========================================================
  5914. DEFINE_SCHEDULE
  5915. (
  5916. SCHED_HUNTER_RANGE_ATTACK1,
  5917. " Tasks"
  5918. " TASK_STOP_MOVING 0"
  5919. " TASK_HUNTER_SHOOT_COMMIT 0"
  5920. " TASK_RANGE_ATTACK1 0"
  5921. " "
  5922. " Interrupts"
  5923. " COND_NEW_ENEMY"
  5924. " COND_ENEMY_DEAD"
  5925. " COND_LOST_ENEMY"
  5926. " COND_ENEMY_OCCLUDED"
  5927. " COND_WEAPON_SIGHT_OCCLUDED"
  5928. " COND_TOO_CLOSE_TO_ATTACK"
  5929. " COND_TOO_FAR_TO_ATTACK"
  5930. " COND_NOT_FACING_ATTACK"
  5931. )
  5932. //=========================================================
  5933. // Attack (Deploy/shoot/finish)
  5934. //=========================================================
  5935. DEFINE_SCHEDULE
  5936. (
  5937. SCHED_HUNTER_RANGE_ATTACK2,
  5938. " Tasks"
  5939. " TASK_STOP_MOVING 0"
  5940. " TASK_HUNTER_PRE_RANGE_ATTACK2 0"
  5941. " TASK_HUNTER_SHOOT_COMMIT 0"
  5942. " TASK_RANGE_ATTACK2 0"
  5943. " TASK_HUNTER_FINISH_RANGE_ATTACK 0"
  5944. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  5945. " TASK_WAIT 0.4"
  5946. " TASK_WAIT_RANDOM 0.2"
  5947. " "
  5948. " Interrupts"
  5949. " COND_NEW_ENEMY"
  5950. )
  5951. //=========================================================
  5952. // Shoot at striderbuster. Distinct from generic range attack
  5953. // because of BuildScheduleTestBits.
  5954. //=========================================================
  5955. DEFINE_SCHEDULE
  5956. (
  5957. SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER,
  5958. " Tasks"
  5959. " TASK_STOP_MOVING 0"
  5960. " TASK_HUNTER_SHOOT_COMMIT 0"
  5961. " TASK_RANGE_ATTACK2 0"
  5962. " "
  5963. " Interrupts"
  5964. )
  5965. //=========================================================
  5966. // Shoot at striderbuster with a little latency beforehand
  5967. //=========================================================
  5968. DEFINE_SCHEDULE
  5969. (
  5970. SCHED_HUNTER_RANGE_ATTACK2_VS_STRIDERBUSTER_LATENT,
  5971. " Tasks"
  5972. " TASK_STOP_MOVING 0"
  5973. " TASK_HUNTER_SHOOT_COMMIT 0"
  5974. " TASK_WAIT 0.2"
  5975. " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_RANGE_ATTACK2"
  5976. " TASK_RANGE_ATTACK2 0"
  5977. " "
  5978. " Interrupts"
  5979. )
  5980. //=========================================================
  5981. // Dodge Incoming vehicle
  5982. //=========================================================
  5983. DEFINE_SCHEDULE
  5984. (
  5985. SCHED_HUNTER_DODGE,
  5986. " Tasks"
  5987. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_DODGE"
  5988. " TASK_HUNTER_FIND_DODGE_POSITION 0"
  5989. " TASK_HUNTER_DODGE 0"
  5990. ""
  5991. " Interrupts"
  5992. )
  5993. //=========================================================
  5994. // Dodge Incoming vehicle
  5995. //=========================================================
  5996. DEFINE_SCHEDULE
  5997. (
  5998. SCHED_HUNTER_FAIL_DODGE,
  5999. " Tasks"
  6000. " TASK_STOP_MOVING 0"
  6001. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  6002. " TASK_FACE_ENEMY 0"
  6003. ""
  6004. " Interrupts"
  6005. )
  6006. //==================================================
  6007. // > SCHED_HUNTER_CHARGE_ENEMY
  6008. // Rush at my enemy and head-butt them.
  6009. //==================================================
  6010. DEFINE_SCHEDULE
  6011. (
  6012. SCHED_HUNTER_CHARGE_ENEMY,
  6013. " Tasks"
  6014. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_CHARGE_ENEMY"
  6015. " TASK_STOP_MOVING 0"
  6016. " TASK_FACE_ENEMY 0"
  6017. " TASK_HUNTER_CHARGE 0"
  6018. ""
  6019. " Interrupts"
  6020. " COND_TASK_FAILED"
  6021. " COND_ENEMY_DEAD"
  6022. )
  6023. DEFINE_SCHEDULE
  6024. (
  6025. SCHED_HUNTER_FAIL_CHARGE_ENEMY,
  6026. " Tasks"
  6027. " TASK_HUNTER_CHARGE_DELAY 10"
  6028. )
  6029. //=========================================================
  6030. // Chase the enemy with intent to claw
  6031. //=========================================================
  6032. DEFINE_SCHEDULE
  6033. (
  6034. SCHED_HUNTER_CHASE_ENEMY_MELEE,
  6035. " Tasks"
  6036. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
  6037. " TASK_STOP_MOVING 0"
  6038. " TASK_GET_CHASE_PATH_TO_ENEMY 300"
  6039. " TASK_RUN_PATH 0"
  6040. " TASK_WAIT_FOR_MOVEMENT 0"
  6041. " TASK_FACE_ENEMY 0"
  6042. ""
  6043. " Interrupts"
  6044. " COND_NEW_ENEMY"
  6045. " COND_ENEMY_DEAD"
  6046. " COND_ENEMY_UNREACHABLE"
  6047. " COND_CAN_MELEE_ATTACK1"
  6048. " COND_CAN_MELEE_ATTACK2"
  6049. //" COND_TOO_CLOSE_TO_ATTACK"
  6050. " COND_LOST_ENEMY"
  6051. )
  6052. //=========================================================
  6053. // Chase my enemy, shoot or claw when possible to do so.
  6054. //=========================================================
  6055. DEFINE_SCHEDULE
  6056. (
  6057. SCHED_HUNTER_CHASE_ENEMY,
  6058. " Tasks"
  6059. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
  6060. " TASK_STOP_MOVING 0"
  6061. " TASK_GET_CHASE_PATH_TO_ENEMY 300"
  6062. " TASK_RUN_PATH 0"
  6063. " TASK_WAIT_FOR_MOVEMENT 0"
  6064. " TASK_FACE_ENEMY 0"
  6065. ""
  6066. " Interrupts"
  6067. " COND_NEW_ENEMY"
  6068. " COND_ENEMY_DEAD"
  6069. " COND_ENEMY_UNREACHABLE"
  6070. " COND_CAN_RANGE_ATTACK1"
  6071. " COND_CAN_RANGE_ATTACK2"
  6072. " COND_CAN_MELEE_ATTACK1"
  6073. " COND_CAN_MELEE_ATTACK2"
  6074. " COND_TOO_CLOSE_TO_ATTACK"
  6075. " COND_LOST_ENEMY"
  6076. )
  6077. //=========================================================
  6078. // Move to a flanking position, then shoot if possible.
  6079. //=========================================================
  6080. DEFINE_SCHEDULE
  6081. (
  6082. SCHED_HUNTER_FLANK_ENEMY,
  6083. " Tasks"
  6084. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ESTABLISH_LINE_OF_FIRE"
  6085. " TASK_STOP_MOVING 0"
  6086. " TASK_HUNTER_BEGIN_FLANK 0"
  6087. " TASK_GET_FLANK_ARC_PATH_TO_ENEMY_LOS 30"
  6088. " TASK_HUNTER_ANNOUNCE_FLANK 0"
  6089. " TASK_RUN_PATH 0"
  6090. " TASK_WAIT_FOR_MOVEMENT 0"
  6091. " TASK_FACE_ENEMY 0"
  6092. //" TASK_HUNTER_END_FLANK 0"
  6093. ""
  6094. " Interrupts"
  6095. " COND_NEW_ENEMY"
  6096. //" COND_CAN_RANGE_ATTACK1"
  6097. //" COND_CAN_RANGE_ATTACK2"
  6098. " COND_CAN_MELEE_ATTACK1"
  6099. " COND_CAN_MELEE_ATTACK2"
  6100. " COND_ENEMY_DEAD"
  6101. " COND_ENEMY_UNREACHABLE"
  6102. " COND_TOO_CLOSE_TO_ATTACK"
  6103. " COND_LOST_ENEMY"
  6104. )
  6105. //=========================================================
  6106. //=========================================================
  6107. DEFINE_SCHEDULE
  6108. (
  6109. SCHED_HUNTER_COMBAT_FACE,
  6110. " Tasks"
  6111. " TASK_STOP_MOVING 0"
  6112. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  6113. " TASK_WAIT_FACE_ENEMY 1"
  6114. ""
  6115. " Interrupts"
  6116. " COND_CAN_RANGE_ATTACK1"
  6117. " COND_CAN_RANGE_ATTACK2"
  6118. " COND_CAN_MELEE_ATTACK1"
  6119. " COND_CAN_MELEE_ATTACK2"
  6120. " COND_NEW_ENEMY"
  6121. " COND_ENEMY_DEAD"
  6122. )
  6123. //=========================================================
  6124. // Like the base class, only don't stop in the middle of
  6125. // swinging if the enemy is killed, hides, or new enemy.
  6126. //=========================================================
  6127. DEFINE_SCHEDULE
  6128. (
  6129. SCHED_HUNTER_MELEE_ATTACK1,
  6130. " Tasks"
  6131. " TASK_STOP_MOVING 0"
  6132. " TASK_FACE_ENEMY 0"
  6133. " TASK_MELEE_ATTACK1 0"
  6134. //" TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_POST_MELEE_WAIT"
  6135. ""
  6136. " Interrupts"
  6137. )
  6138. //=========================================================
  6139. // In a fight with nothing to do. Make busy!
  6140. //=========================================================
  6141. DEFINE_SCHEDULE
  6142. (
  6143. SCHED_HUNTER_CHANGE_POSITION,
  6144. " Tasks"
  6145. " TASK_STOP_MOVING 0"
  6146. " TASK_WANDER 720432" // 6 feet to 36 feet
  6147. " TASK_RUN_PATH 0"
  6148. " TASK_HUNTER_WAIT_FOR_MOVEMENT_FACING_ENEMY 0"
  6149. " TASK_STOP_MOVING 0"
  6150. " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_CHANGE_POSITION_FINISH"
  6151. ""
  6152. " Interrupts"
  6153. " COND_ENEMY_DEAD"
  6154. " COND_CAN_MELEE_ATTACK1"
  6155. " COND_CAN_MELEE_ATTACK2"
  6156. " COND_HEAR_DANGER"
  6157. " COND_HEAR_MOVE_AWAY"
  6158. " COND_NEW_ENEMY"
  6159. )
  6160. //=========================================================
  6161. // In a fight with nothing to do. Make busy!
  6162. //=========================================================
  6163. DEFINE_SCHEDULE
  6164. (
  6165. SCHED_HUNTER_CHANGE_POSITION_FINISH,
  6166. " Tasks"
  6167. " TASK_FACE_ENEMY 0"
  6168. " TASK_WAIT_FACE_ENEMY_RANDOM 5"
  6169. ""
  6170. " Interrupts"
  6171. " COND_ENEMY_DEAD"
  6172. " COND_CAN_RANGE_ATTACK1"
  6173. " COND_CAN_RANGE_ATTACK2"
  6174. " COND_CAN_MELEE_ATTACK1"
  6175. " COND_CAN_MELEE_ATTACK2"
  6176. " COND_HEAR_DANGER"
  6177. " COND_HEAR_MOVE_AWAY"
  6178. " COND_NEW_ENEMY"
  6179. )
  6180. //=========================================================
  6181. // In a fight with nothing to do. Make busy!
  6182. //=========================================================
  6183. DEFINE_SCHEDULE
  6184. (
  6185. SCHED_HUNTER_SIDESTEP,
  6186. " Tasks"
  6187. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE" // used because sched_fail includes a one second pause. ick!
  6188. " TASK_STOP_MOVING 0"
  6189. " TASK_HUNTER_FIND_SIDESTEP_POSITION 0"
  6190. " TASK_GET_PATH_TO_SAVEPOSITION 0"
  6191. " TASK_RUN_PATH 0"
  6192. " TASK_WAIT_FOR_MOVEMENT 0"
  6193. " TASK_FACE_ENEMY 0"
  6194. ""
  6195. " Interrupts"
  6196. )
  6197. //=========================================================
  6198. //=========================================================
  6199. DEFINE_SCHEDULE
  6200. (
  6201. SCHED_HUNTER_PATROL,
  6202. " Tasks"
  6203. " TASK_STOP_MOVING 0"
  6204. " TASK_WANDER 720432" // 6 feet to 36 feet
  6205. " TASK_WALK_PATH 0"
  6206. " TASK_WAIT_FOR_MOVEMENT 0"
  6207. " TASK_STOP_MOVING 0"
  6208. " TASK_FACE_REASONABLE 0"
  6209. " TASK_WAIT_RANDOM 3"
  6210. ""
  6211. " Interrupts"
  6212. " COND_ENEMY_DEAD"
  6213. " COND_LIGHT_DAMAGE"
  6214. " COND_HEAVY_DAMAGE"
  6215. " COND_HEAR_DANGER"
  6216. " COND_HEAR_COMBAT"
  6217. " COND_HEAR_PLAYER"
  6218. " COND_HEAR_BULLET_IMPACT"
  6219. " COND_HEAR_MOVE_AWAY"
  6220. " COND_NEW_ENEMY"
  6221. " COND_SEE_ENEMY"
  6222. " COND_CAN_RANGE_ATTACK1"
  6223. " COND_CAN_RANGE_ATTACK2"
  6224. " COND_CAN_MELEE_ATTACK1"
  6225. " COND_CAN_MELEE_ATTACK2"
  6226. )
  6227. //=========================================================
  6228. // Stagger because I got hit by something heavy
  6229. //=========================================================
  6230. DEFINE_SCHEDULE
  6231. (
  6232. SCHED_HUNTER_STAGGER,
  6233. " Tasks"
  6234. " TASK_HUNTER_STAGGER 0"
  6235. ""
  6236. " Interrupts"
  6237. )
  6238. //=========================================================
  6239. // Run around randomly until we detect an enemy
  6240. //=========================================================
  6241. DEFINE_SCHEDULE
  6242. (
  6243. SCHED_HUNTER_PATROL_RUN,
  6244. " Tasks"
  6245. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE"
  6246. " TASK_SET_ROUTE_SEARCH_TIME 5" // Spend 5 seconds trying to build a path if stuck
  6247. " TASK_GET_PATH_TO_RANDOM_NODE 200"
  6248. " TASK_RUN_PATH 0"
  6249. " TASK_WAIT_FOR_MOVEMENT 0"
  6250. ""
  6251. " Interrupts"
  6252. " COND_CAN_RANGE_ATTACK1 "
  6253. " COND_CAN_RANGE_ATTACK2 "
  6254. " COND_CAN_MELEE_ATTACK1 "
  6255. " COND_CAN_MELEE_ATTACK2"
  6256. " COND_GIVE_WAY"
  6257. " COND_NEW_ENEMY"
  6258. " COND_HEAR_COMBAT"
  6259. " COND_HEAR_DANGER"
  6260. " COND_HEAR_PLAYER"
  6261. " COND_LIGHT_DAMAGE"
  6262. " COND_HEAVY_DAMAGE"
  6263. )
  6264. //=========================================================
  6265. //=========================================================
  6266. DEFINE_SCHEDULE
  6267. (
  6268. SCHED_HUNTER_TAKE_COVER_FROM_ENEMY,
  6269. " Tasks"
  6270. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_CHASE_ENEMY_MELEE"
  6271. " TASK_HUNTER_CORNERED_TIMER 10.0"
  6272. " TASK_WAIT 0.0"
  6273. // " TASK_SET_TOLERANCE_DISTANCE 24"
  6274. // " TASK_FIND_COVER_FROM_ENEMY 0"
  6275. " TASK_FIND_FAR_NODE_COVER_FROM_ENEMY 200.0"
  6276. " TASK_RUN_PATH 0"
  6277. " TASK_HUNTER_CORNERED_TIMER 0.0"
  6278. // " TASK_CLEAR_FAIL_SCHEDULE 0" // not used because sched_fail includes a one second pause. ick!
  6279. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE"
  6280. " TASK_WAIT_FOR_MOVEMENT 0"
  6281. " TASK_REMEMBER MEMORY:INCOVER"
  6282. " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_HIDE_UNDER_COVER"
  6283. /*
  6284. " TASK_FACE_ENEMY 0"
  6285. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
  6286. " TASK_WAIT 1"
  6287. */
  6288. ""
  6289. " Interrupts"
  6290. " COND_NEW_ENEMY"
  6291. " COND_HEAR_DANGER"
  6292. )
  6293. //=========================================================
  6294. //=========================================================
  6295. DEFINE_SCHEDULE
  6296. (
  6297. SCHED_HUNTER_HIDE_UNDER_COVER,
  6298. " Tasks"
  6299. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_FAIL_IMMEDIATE" // used because sched_fail includes a one second pause. ick!
  6300. " TASK_FACE_ENEMY 0"
  6301. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
  6302. " TASK_WAIT 1"
  6303. ""
  6304. " Interrupts"
  6305. " COND_NEW_ENEMY"
  6306. " COND_HEAR_DANGER"
  6307. " COND_HAVE_ENEMY_LOS"
  6308. )
  6309. //=========================================================
  6310. //=========================================================
  6311. DEFINE_SCHEDULE
  6312. (
  6313. SCHED_HUNTER_FOUND_ENEMY,
  6314. " Tasks"
  6315. " TASK_STOP_MOVING 0"
  6316. " TASK_FACE_ENEMY 0"
  6317. " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_HUNTER_FOUND_ENEMY"
  6318. ""
  6319. " Interrupts"
  6320. " COND_LIGHT_DAMAGE"
  6321. " COND_HEAVY_DAMAGE"
  6322. )
  6323. //=========================================================
  6324. //=========================================================
  6325. DEFINE_SCHEDULE
  6326. (
  6327. SCHED_HUNTER_FOUND_ENEMY_ACK,
  6328. " Tasks"
  6329. " TASK_STOP_MOVING 0"
  6330. " TASK_WAIT_RANDOM 0.75"
  6331. " TASK_FACE_ENEMY 0"
  6332. " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_HUNTER_FOUND_ENEMY_ACK"
  6333. ""
  6334. " Interrupts"
  6335. " COND_LIGHT_DAMAGE"
  6336. " COND_HEAVY_DAMAGE"
  6337. )
  6338. //=========================================================
  6339. // An empty schedule that immediately bails out, faster than
  6340. // SCHED_FAIL which stops moving and waits for one second.
  6341. //=========================================================
  6342. DEFINE_SCHEDULE
  6343. (
  6344. SCHED_HUNTER_FAIL_IMMEDIATE,
  6345. " Tasks"
  6346. " TASK_WAIT 0"
  6347. )
  6348. DEFINE_SCHEDULE
  6349. (
  6350. SCHED_HUNTER_GOTO_HINT,
  6351. " Tasks"
  6352. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HUNTER_CLEAR_HINTNODE" // used because sched_fail includes a one second pause. ick!
  6353. " TASK_GET_PATH_TO_HINTNODE 1"
  6354. " TASK_WAIT_FOR_MOVEMENT 0"
  6355. " TASK_CLEAR_HINTNODE 0"
  6356. ""
  6357. ""
  6358. " Interrupts"
  6359. )
  6360. DEFINE_SCHEDULE
  6361. (
  6362. SCHED_HUNTER_CLEAR_HINTNODE,
  6363. " Tasks"
  6364. " TASK_CLEAR_HINTNODE 0"
  6365. ""
  6366. ""
  6367. " Interrupts"
  6368. )
  6369. DEFINE_SCHEDULE
  6370. (
  6371. SCHED_HUNTER_SIEGE_STAND,
  6372. " Tasks"
  6373. " TASK_STOP_MOVING 0"
  6374. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  6375. " TASK_FACE_PLAYER 0"
  6376. " TASK_WAIT 10"
  6377. " TASK_WAIT_RANDOM 2"
  6378. " TASK_SET_SCHEDULE SCHEDULE:SCHED_HUNTER_CHANGE_POSITION_SIEGE"
  6379. ""
  6380. ""
  6381. " Interrupts"
  6382. " COND_SEE_PLAYER"
  6383. " COND_NEW_ENEMY"
  6384. )
  6385. DEFINE_SCHEDULE
  6386. (
  6387. SCHED_HUNTER_CHANGE_POSITION_SIEGE,
  6388. " Tasks"
  6389. " TASK_STOP_MOVING 0"
  6390. " TASK_WANDER 2400480"
  6391. " TASK_RUN_PATH 0"
  6392. " TASK_WAIT_FOR_MOVEMENT 0"
  6393. " TASK_STOP_MOVING 0"
  6394. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  6395. " TASK_FACE_PLAYER 0"
  6396. ""
  6397. " Interrupts"
  6398. " COND_NEW_ENEMY"
  6399. )
  6400. // formula is MIN_DIST * 10000 + MAX_DIST
  6401. AI_END_CUSTOM_NPC()