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.

1127 lines
29 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Bullseyes act as targets for other NPC's to attack and to trigger
  4. // events
  5. //
  6. // $Workfile: $
  7. // $Date: $
  8. //
  9. //-----------------------------------------------------------------------------
  10. // $Log: $
  11. //
  12. // $NoKeywords: $
  13. //=============================================================================//
  14. #include "cbase.h"
  15. #include "ai_default.h"
  16. #include "ai_task.h"
  17. #include "ai_schedule.h"
  18. #include "ai_node.h"
  19. #include "ai_hull.h"
  20. #include "ai_hint.h"
  21. #include "ai_memory.h"
  22. #include "ai_route.h"
  23. #include "ai_motor.h"
  24. #include "ai_squadslot.h"
  25. #include "soundent.h"
  26. #include "game.h"
  27. #include "npcevent.h"
  28. #include "entitylist.h"
  29. #include "activitylist.h"
  30. #include "animation.h"
  31. #include "basecombatweapon.h"
  32. #include "IEffects.h"
  33. #include "vstdlib/random.h"
  34. #include "engine/IEngineSound.h"
  35. #include "ammodef.h"
  36. #include "te.h"
  37. #include "hl1_ai_basenpc.h"
  38. ConVar sk_agrunt_health( "sk_agrunt_health", "0" );
  39. ConVar sk_agrunt_dmg_punch( "sk_agrunt_dmg_punch", "0" );
  40. //=========================================================
  41. // Monster's Anim Events Go Here
  42. //=========================================================
  43. #define AGRUNT_AE_HORNET1 ( 1 )
  44. #define AGRUNT_AE_HORNET2 ( 2 )
  45. #define AGRUNT_AE_HORNET3 ( 3 )
  46. #define AGRUNT_AE_HORNET4 ( 4 )
  47. #define AGRUNT_AE_HORNET5 ( 5 )
  48. // some events are set up in the QC file that aren't recognized by the code yet.
  49. #define AGRUNT_AE_PUNCH ( 6 )
  50. #define AGRUNT_AE_BITE ( 7 )
  51. #define AGRUNT_AE_LEFT_FOOT ( 10 )
  52. #define AGRUNT_AE_RIGHT_FOOT ( 11 )
  53. #define AGRUNT_AE_LEFT_PUNCH ( 12 )
  54. #define AGRUNT_AE_RIGHT_PUNCH ( 13 )
  55. #define AGRUNT_MELEE_DIST 100
  56. int iAgruntMuzzleFlash;
  57. int ACT_THREAT_DISPLAY;
  58. // -----------------------------------------------
  59. // > Squad slots
  60. // -----------------------------------------------
  61. enum AGruntSquadSlot_T
  62. {
  63. AGRUNT_SQUAD_SLOT_HORNET1 = LAST_SHARED_SQUADSLOT,
  64. AGRUNT_SQUAD_SLOT_HORNET2,
  65. AGRUNT_SQUAD_SLOT_CHASE,
  66. };
  67. enum
  68. {
  69. SCHED_AGRUNT_FAIL = LAST_SHARED_SCHEDULE,
  70. SCHED_AGRUNT_COMBAT_FAIL,
  71. SCHED_AGRUNT_STANDOFF,
  72. SCHED_AGRUNT_SUPPRESS_HORNET,
  73. SCHED_AGRUNT_RANGE_ATTACK,
  74. SCHED_AGRUNT_HIDDEN_RANGE_ATTACK,
  75. SCHED_AGRUNT_TAKE_COVER_FROM_ENEMY,
  76. SCHED_AGRUNT_VICTORY_DANCE,
  77. SCHED_AGRUNT_THREAT_DISPLAY,
  78. };
  79. //=========================================================
  80. // monster-specific tasks
  81. //=========================================================
  82. enum
  83. {
  84. TASK_AGRUNT_SETUP_HIDE_ATTACK = LAST_SHARED_TASK,
  85. TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE,
  86. TASK_AGRUNT_RANGE_ATTACK1_NOTURN,
  87. };
  88. class CNPC_AlienGrunt : public CHL1BaseNPC
  89. {
  90. DECLARE_CLASS( CNPC_AlienGrunt, CHL1BaseNPC );
  91. public:
  92. void Spawn( void );
  93. void Precache( void );
  94. float MaxYawSpeed( void );
  95. Class_T Classify ( void ){ return CLASS_ALIEN_MILITARY; }
  96. int GetSoundInterests ( void );
  97. void HandleAnimEvent( animevent_t *pEvent );
  98. void AlertSound( void );
  99. void DeathSound( const CTakeDamageInfo &info );
  100. void PainSound( const CTakeDamageInfo &info );
  101. void AttackSound( void );
  102. bool ShouldSpeak( void );
  103. void PrescheduleThink ( void );
  104. bool FCanCheckAttacks ( void );
  105. int MeleeAttack1Conditions ( float flDot, float flDist );
  106. int RangeAttack1Conditions ( float flDot, float flDist );
  107. void StopTalking ( void );
  108. void StartTask( const Task_t *pTask );
  109. void RunTask( const Task_t *pTask );
  110. int TranslateSchedule( int scheduleType ); //GetScheduleOfType
  111. int SelectSchedule( void ); // GetSchedule
  112. void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
  113. int IRelationPriority( CBaseEntity *pTarget );
  114. /*
  115. int IRelationship( CBaseEntity *pTarget );
  116. */
  117. public:
  118. DECLARE_DATADESC();
  119. DEFINE_CUSTOM_AI;
  120. bool m_fCanHornetAttack;
  121. float m_flNextHornetAttackCheck;
  122. float m_flNextPainTime;
  123. // three hacky fields for speech stuff. These don't really need to be saved.
  124. float m_flNextSpeakTime;
  125. float m_flNextWordTime;
  126. float m_flDamageTime;
  127. };
  128. LINK_ENTITY_TO_CLASS( monster_alien_grunt, CNPC_AlienGrunt );
  129. BEGIN_DATADESC( CNPC_AlienGrunt )
  130. DEFINE_FIELD( m_fCanHornetAttack, FIELD_BOOLEAN ),
  131. DEFINE_FIELD( m_flNextHornetAttackCheck, FIELD_TIME ),
  132. DEFINE_FIELD( m_flNextPainTime, FIELD_TIME ),
  133. DEFINE_FIELD( m_flNextSpeakTime, FIELD_TIME ),
  134. DEFINE_FIELD( m_flNextWordTime, FIELD_TIME ),
  135. DEFINE_FIELD( m_flDamageTime, FIELD_TIME ),
  136. END_DATADESC()
  137. int CNPC_AlienGrunt::IRelationPriority( CBaseEntity *pTarget )
  138. {
  139. //I hate grunts more than anything.
  140. if ( pTarget->Classify() == CLASS_HUMAN_MILITARY )
  141. {
  142. if ( FClassnameIs( pTarget, "monster_human_grunt" ) )
  143. {
  144. return ( BaseClass::IRelationPriority ( pTarget ) + 1 );
  145. }
  146. }
  147. return BaseClass::IRelationPriority( pTarget );
  148. }
  149. void CNPC_AlienGrunt::Spawn()
  150. {
  151. Precache();
  152. SetModel( "models/agrunt.mdl");
  153. UTIL_SetSize( this, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) );
  154. SetSolid( SOLID_BBOX );
  155. AddSolidFlags( FSOLID_NOT_STANDABLE );
  156. Vector vecSurroundingMins( -32, -32, 0 );
  157. Vector vecSurroundingMaxs( 32, 32, 85 );
  158. CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs );
  159. SetMoveType( MOVETYPE_STEP );
  160. m_bloodColor = BLOOD_COLOR_GREEN;
  161. ClearEffects();
  162. m_iHealth = sk_agrunt_health.GetFloat();
  163. m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result )
  164. m_NPCState = NPC_STATE_NONE;
  165. CapabilitiesClear();
  166. CapabilitiesAdd ( bits_CAP_SQUAD | bits_CAP_MOVE_GROUND );
  167. CapabilitiesAdd(bits_CAP_INNATE_RANGE_ATTACK1 );
  168. // Innate range attack for kicking
  169. CapabilitiesAdd(bits_CAP_INNATE_MELEE_ATTACK1 );
  170. m_HackedGunPos = Vector( 24, 64, 48 );
  171. m_flNextSpeakTime = m_flNextWordTime = gpGlobals->curtime + 10 + random->RandomInt( 0, 10 );
  172. SetHullType(HULL_WIDE_HUMAN);
  173. SetHullSizeNormal();
  174. SetRenderColor( 255, 255, 255, 255 );
  175. NPCInit();
  176. BaseClass::Spawn();
  177. }
  178. //=========================================================
  179. // Precache - precaches all resources this monster needs
  180. //=========================================================
  181. void CNPC_AlienGrunt::Precache()
  182. {
  183. PrecacheModel("models/agrunt.mdl");
  184. iAgruntMuzzleFlash = PrecacheModel( "sprites/muz4.vmt" );
  185. UTIL_PrecacheOther( "hornet" );
  186. PrecacheScriptSound( "Weapon_Hornetgun.Single" );
  187. PrecacheScriptSound( "AlienGrunt.LeftFoot" );
  188. PrecacheScriptSound( "AlienGrunt.RightFoot" );
  189. PrecacheScriptSound( "AlienGrunt.AttackHit" );
  190. PrecacheScriptSound( "AlienGrunt.AttackMiss" );
  191. PrecacheScriptSound( "AlienGrunt.Die" );
  192. PrecacheScriptSound( "AlienGrunt.Alert" );
  193. PrecacheScriptSound( "AlienGrunt.Attack" );
  194. PrecacheScriptSound( "AlienGrunt.Pain" );
  195. PrecacheScriptSound( "AlienGrunt.Idle" );
  196. }
  197. float CNPC_AlienGrunt::MaxYawSpeed( void )
  198. {
  199. float ys;
  200. switch ( GetActivity() )
  201. {
  202. case ACT_TURN_LEFT:
  203. case ACT_TURN_RIGHT:
  204. ys = 110;
  205. break;
  206. default:
  207. ys = 100;
  208. }
  209. return ys;
  210. }
  211. int CNPC_AlienGrunt::GetSoundInterests ( void )
  212. {
  213. return SOUND_WORLD |
  214. SOUND_COMBAT |
  215. SOUND_PLAYER |
  216. SOUND_DANGER;
  217. }
  218. //=========================================================
  219. // HandleAnimEvent - catches the monster-specific messages
  220. // that occur when tagged animation frames are played.
  221. //
  222. // Returns number of events handled, 0 if none.
  223. //=========================================================
  224. void CNPC_AlienGrunt::HandleAnimEvent( animevent_t *pEvent )
  225. {
  226. switch( pEvent->event )
  227. {
  228. case AGRUNT_AE_HORNET1:
  229. case AGRUNT_AE_HORNET2:
  230. case AGRUNT_AE_HORNET3:
  231. case AGRUNT_AE_HORNET4:
  232. case AGRUNT_AE_HORNET5:
  233. {
  234. // m_vecEnemyLKP should be center of enemy body
  235. Vector vecArmPos;
  236. QAngle angArmDir;
  237. Vector vecDirToEnemy;
  238. QAngle angDir;
  239. if (HasCondition( COND_SEE_ENEMY) && GetEnemy())
  240. {
  241. Vector vecEnemyLKP = GetEnemy()->GetAbsOrigin();
  242. vecDirToEnemy = ( ( vecEnemyLKP ) - GetAbsOrigin() );
  243. VectorAngles( vecDirToEnemy, angDir );
  244. VectorNormalize( vecDirToEnemy );
  245. }
  246. else
  247. {
  248. angDir = GetAbsAngles();
  249. angDir.x = -angDir.x;
  250. Vector vForward;
  251. AngleVectors( angDir, &vForward );
  252. vecDirToEnemy = vForward;
  253. }
  254. DoMuzzleFlash();
  255. // make angles +-180
  256. if (angDir.x > 180)
  257. {
  258. angDir.x = angDir.x - 360;
  259. }
  260. // SetBlending( 0, angDir.x );
  261. GetAttachment( "0", vecArmPos, angArmDir );
  262. vecArmPos = vecArmPos + vecDirToEnemy * 32;
  263. CPVSFilter filter( GetAbsOrigin() );
  264. te->Sprite( filter, 0.0,
  265. &vecArmPos, iAgruntMuzzleFlash, random->RandomFloat( 0.4, 0.8 ), 128 );
  266. CBaseEntity *pHornet = CBaseEntity::Create( "hornet", vecArmPos, QAngle( 0, 0, 0 ), this );
  267. Vector vForward;
  268. AngleVectors( angDir, &vForward );
  269. pHornet->SetAbsVelocity( vForward * 300 );
  270. pHornet->SetOwnerEntity( this );
  271. EmitSound( "Weapon_Hornetgun.Single" );
  272. CHL1BaseNPC *pHornetMonster = (CHL1BaseNPC *)pHornet->MyNPCPointer();
  273. if ( pHornetMonster )
  274. {
  275. pHornetMonster->SetEnemy( GetEnemy() );
  276. }
  277. }
  278. break;
  279. case AGRUNT_AE_LEFT_FOOT:
  280. // left foot
  281. {
  282. CPASAttenuationFilter filter2( this );
  283. EmitSound( filter2, entindex(), "AlienGrunt.LeftFoot" );
  284. }
  285. break;
  286. case AGRUNT_AE_RIGHT_FOOT:
  287. // right foot
  288. {
  289. CPASAttenuationFilter filter3( this );
  290. EmitSound( filter3, entindex(), "AlienGrunt.RightFoot" );
  291. }
  292. break;
  293. case AGRUNT_AE_LEFT_PUNCH:
  294. {
  295. Vector vecMins = GetHullMins();
  296. Vector vecMaxs = GetHullMaxs();
  297. vecMins.z = vecMins.x;
  298. vecMaxs.z = vecMaxs.x;
  299. CBaseEntity *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, vecMins, vecMaxs, sk_agrunt_dmg_punch.GetFloat(), DMG_CLUB );
  300. CPASAttenuationFilter filter4( this );
  301. if ( pHurt )
  302. {
  303. if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
  304. pHurt->ViewPunch( QAngle( -25, 8, 0) );
  305. Vector vRight;
  306. AngleVectors( GetAbsAngles(), NULL, &vRight, NULL );
  307. // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above.
  308. if ( pHurt->IsPlayer() )
  309. {
  310. // this is a player. Knock him around.
  311. pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + vRight * 250 );
  312. }
  313. EmitSound(filter4, entindex(), "AlienGrunt.AttackHit" );
  314. Vector vecArmPos;
  315. QAngle angArmAng;
  316. GetAttachment( 0, vecArmPos, angArmAng );
  317. SpawnBlood(vecArmPos, g_vecAttackDir, pHurt->BloodColor(), 25);// a little surface blood.
  318. }
  319. else
  320. {
  321. // Play a random attack miss sound
  322. EmitSound(filter4, entindex(), "AlienGrunt.AttackMiss" );
  323. }
  324. }
  325. break;
  326. case AGRUNT_AE_RIGHT_PUNCH:
  327. {
  328. Vector vecMins = GetHullMins();
  329. Vector vecMaxs = GetHullMaxs();
  330. vecMins.z = vecMins.x;
  331. vecMaxs.z = vecMaxs.x;
  332. CBaseEntity *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, vecMins, vecMaxs, sk_agrunt_dmg_punch.GetFloat(), DMG_CLUB );
  333. CPASAttenuationFilter filter5( this );
  334. if ( pHurt )
  335. {
  336. if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
  337. pHurt->ViewPunch( QAngle( 25, 8, 0) );
  338. // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above.
  339. if ( pHurt->IsPlayer() )
  340. {
  341. // this is a player. Knock him around.
  342. Vector vRight;
  343. AngleVectors( GetAbsAngles(), NULL, &vRight, NULL );
  344. pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + vRight * -250 );
  345. }
  346. EmitSound( filter5, entindex(), "AlienGrunt.AttackHit" );
  347. Vector vecArmPos;
  348. QAngle angArmAng;
  349. GetAttachment( 0, vecArmPos, angArmAng );
  350. SpawnBlood(vecArmPos, g_vecAttackDir, pHurt->BloodColor(), 25);// a little surface blood.
  351. }
  352. else
  353. {
  354. // Play a random attack miss sound
  355. EmitSound( filter5, entindex(), "AlienGrunt.AttackMiss" );
  356. }
  357. }
  358. break;
  359. default:
  360. BaseClass::HandleAnimEvent( pEvent );
  361. break;
  362. }
  363. }
  364. //=========================================================
  365. // DieSound
  366. //=========================================================
  367. void CNPC_AlienGrunt::DeathSound( const CTakeDamageInfo &info )
  368. {
  369. StopTalking();
  370. CPASAttenuationFilter filter( this );
  371. EmitSound( filter, entindex(), "AlienGrunt.Die" );
  372. }
  373. //=========================================================
  374. // AlertSound
  375. //=========================================================
  376. void CNPC_AlienGrunt::AlertSound( void )
  377. {
  378. StopTalking();
  379. CPASAttenuationFilter filter( this );
  380. EmitSound( filter, entindex(), "AlienGrunt.Alert" );
  381. }
  382. //=========================================================
  383. // AttackSound
  384. //=========================================================
  385. void CNPC_AlienGrunt::AttackSound( void )
  386. {
  387. StopTalking();
  388. CPASAttenuationFilter filter( this );
  389. EmitSound( filter, entindex(), "AlienGrunt.Attack" );
  390. }
  391. //=========================================================
  392. // PainSound
  393. //=========================================================
  394. void CNPC_AlienGrunt::PainSound( const CTakeDamageInfo &info )
  395. {
  396. if ( m_flNextPainTime > gpGlobals->curtime )
  397. {
  398. return;
  399. }
  400. m_flNextPainTime = gpGlobals->curtime + 0.6;
  401. StopTalking();
  402. CPASAttenuationFilter filter( this );
  403. EmitSound( filter, entindex(),"AlienGrunt.Pain" );
  404. }
  405. //=========================================================
  406. // ShouldSpeak - Should this agrunt be talking?
  407. //=========================================================
  408. bool CNPC_AlienGrunt::ShouldSpeak( void )
  409. {
  410. if ( m_flNextSpeakTime > gpGlobals->curtime )
  411. {
  412. // my time to talk is still in the future.
  413. return FALSE;
  414. }
  415. if ( m_spawnflags & SF_NPC_GAG )
  416. {
  417. if ( m_NPCState != NPC_STATE_COMBAT )
  418. {
  419. // if gagged, don't talk outside of combat.
  420. // if not going to talk because of this, put the talk time
  421. // into the future a bit, so we don't talk immediately after
  422. // going into combat
  423. m_flNextSpeakTime = gpGlobals->curtime + 3;
  424. return FALSE;
  425. }
  426. }
  427. return TRUE;
  428. }
  429. //=========================================================
  430. // PrescheduleThink
  431. //=========================================================
  432. void CNPC_AlienGrunt::PrescheduleThink ( void )
  433. {
  434. BaseClass::PrescheduleThink();
  435. if ( ShouldSpeak() )
  436. {
  437. if ( m_flNextWordTime < gpGlobals->curtime )
  438. {
  439. // play a new sound
  440. CPASAttenuationFilter filter( this );
  441. EmitSound( filter, entindex(), "AlienGrunt.Idle" );
  442. // is this word our last?
  443. if ( random->RandomInt( 1, 10 ) <= 1 )
  444. {
  445. // stop talking.
  446. StopTalking();
  447. }
  448. else
  449. {
  450. m_flNextWordTime = gpGlobals->curtime + random->RandomFloat( 0.5, 1 );
  451. }
  452. }
  453. }
  454. }
  455. //=========================================================
  456. // FCanCheckAttacks - this is overridden for alien grunts
  457. // because they can use their smart weapons against unseen
  458. // enemies. Base class doesn't attack anyone it can't see.
  459. //=========================================================
  460. bool CNPC_AlienGrunt::FCanCheckAttacks ( void )
  461. {
  462. if ( !HasCondition( COND_ENEMY_TOO_FAR ) )
  463. return true;
  464. else
  465. return false;
  466. }
  467. //=========================================================
  468. // CheckMeleeAttack1 - alien grunts zap the crap out of
  469. // any enemy that gets too close.
  470. //=========================================================
  471. int CNPC_AlienGrunt::MeleeAttack1Conditions ( float flDot, float flDist )
  472. {
  473. if ( flDist > AGRUNT_MELEE_DIST )
  474. return COND_NONE;
  475. if ( flDot < 0.6 )
  476. return COND_NONE;
  477. if ( HasCondition ( COND_SEE_ENEMY ) && GetEnemy() != NULL )
  478. return COND_CAN_MELEE_ATTACK1;
  479. return COND_NONE;
  480. }
  481. //=========================================================
  482. // CheckRangeAttack1
  483. //
  484. // !!!LATER - we may want to load balance this. Several
  485. // tracelines are done, so we may not want to do this every
  486. // server frame. Definitely not while firing.
  487. //=========================================================
  488. int CNPC_AlienGrunt::RangeAttack1Conditions ( float flDot, float flDist )
  489. {
  490. if ( gpGlobals->curtime < m_flNextHornetAttackCheck )
  491. {
  492. if ( HasCondition( COND_SEE_ENEMY ) )
  493. {
  494. if ( m_fCanHornetAttack == true )
  495. {
  496. return COND_CAN_RANGE_ATTACK1;
  497. }
  498. else
  499. {
  500. return COND_NONE;
  501. }
  502. }
  503. else
  504. return COND_NONE;
  505. }
  506. if ( flDist < AGRUNT_MELEE_DIST )
  507. return COND_NONE;
  508. if ( flDist > 1024 )
  509. return COND_NONE;
  510. if ( flDot < 0.5 )
  511. return COND_NONE;
  512. if ( HasCondition( COND_SEE_ENEMY ) )
  513. {
  514. trace_t tr;
  515. Vector vecArmPos;
  516. QAngle angArmDir;
  517. // verify that a shot fired from the gun will hit the enemy before the world.
  518. // !!!LATER - we may wish to do something different for projectile weapons as opposed to instant-hit
  519. GetAttachment( "0", vecArmPos, angArmDir );
  520. UTIL_TraceLine( vecArmPos, GetEnemy()->BodyTarget( vecArmPos ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
  521. if ( tr.fraction == 1.0 || tr.m_pEnt == GetEnemy() )
  522. {
  523. m_flNextHornetAttackCheck = gpGlobals->curtime + random->RandomFloat( 2, 5 );
  524. m_fCanHornetAttack = true;
  525. return COND_CAN_RANGE_ATTACK1;
  526. }
  527. }
  528. m_flNextHornetAttackCheck = gpGlobals->curtime + 0.2;// don't check for half second if this check wasn't successful
  529. m_fCanHornetAttack = false;
  530. return COND_NONE;
  531. }
  532. //=========================================================
  533. // StopTalking - won't speak again for 10-20 seconds.
  534. //=========================================================
  535. void CNPC_AlienGrunt::StopTalking( void )
  536. {
  537. m_flNextWordTime = m_flNextSpeakTime = gpGlobals->curtime + 10 + random->RandomInt(0, 10);
  538. }
  539. void CNPC_AlienGrunt::StartTask ( const Task_t *pTask )
  540. {
  541. switch ( pTask->iTask )
  542. {
  543. case TASK_AGRUNT_RANGE_ATTACK1_NOTURN:
  544. {
  545. SetLastAttackTime( gpGlobals->curtime );
  546. ResetIdealActivity( ACT_RANGE_ATTACK1 );
  547. }
  548. break;
  549. case TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE:
  550. {
  551. Vector forward;
  552. AngleVectors( GetAbsAngles(), &forward );
  553. Vector flEnemyLKP = GetEnemyLKP();
  554. GetNavigator()->SetGoal( flEnemyLKP - forward * 64, AIN_CLEAR_TARGET);
  555. if ( GetNavigator()->SetGoal( flEnemyLKP - forward * 64, AIN_CLEAR_TARGET) )
  556. {
  557. TaskComplete();
  558. }
  559. else
  560. {
  561. Msg ( "AGruntGetPathToEnemyCorpse failed!!\n" );
  562. TaskFail( FAIL_NO_ROUTE );
  563. }
  564. }
  565. break;
  566. case TASK_AGRUNT_SETUP_HIDE_ATTACK:
  567. // alien grunt shoots hornets back out into the open from a concealed location.
  568. // try to find a spot to throw that gives the smart weapon a good chance of finding the enemy.
  569. // ideally, this spot is along a line that is perpendicular to a line drawn from the agrunt to the enemy.
  570. CHL1BaseNPC *pEnemyMonsterPtr;
  571. pEnemyMonsterPtr = (CHL1BaseNPC *)GetEnemy()->MyNPCPointer();
  572. if ( pEnemyMonsterPtr )
  573. {
  574. Vector vecCenter, vForward, vRight, vecEnemyLKP;
  575. QAngle angTmp;
  576. trace_t tr;
  577. BOOL fSkip;
  578. fSkip = FALSE;
  579. vecCenter = WorldSpaceCenter();
  580. vecEnemyLKP = GetEnemyLKP();
  581. VectorAngles( vecEnemyLKP - GetAbsOrigin(), angTmp );
  582. SetAbsAngles( angTmp );
  583. AngleVectors( GetAbsAngles(), &vForward, &vRight, NULL );
  584. UTIL_TraceLine( WorldSpaceCenter() + vForward * 128, vecEnemyLKP, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
  585. if ( tr.fraction == 1.0 )
  586. {
  587. GetMotor()->SetIdealYawToTargetAndUpdate ( GetAbsOrigin() + vRight * 128 );
  588. fSkip = TRUE;
  589. TaskComplete();
  590. }
  591. if ( !fSkip )
  592. {
  593. UTIL_TraceLine( WorldSpaceCenter() - vForward * 128, vecEnemyLKP, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
  594. if ( tr.fraction == 1.0 )
  595. {
  596. GetMotor()->SetIdealYawToTargetAndUpdate ( GetAbsOrigin() - vRight * 128 );
  597. fSkip = TRUE;
  598. TaskComplete();
  599. }
  600. }
  601. if ( !fSkip )
  602. {
  603. UTIL_TraceLine( WorldSpaceCenter() + vForward * 256, vecEnemyLKP, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
  604. if ( tr.fraction == 1.0 )
  605. {
  606. GetMotor()->SetIdealYawToTargetAndUpdate ( GetAbsOrigin() + vRight * 256 );
  607. fSkip = TRUE;
  608. TaskComplete();
  609. }
  610. }
  611. if ( !fSkip )
  612. {
  613. UTIL_TraceLine( WorldSpaceCenter() - vForward * 256, vecEnemyLKP, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
  614. if ( tr.fraction == 1.0 )
  615. {
  616. GetMotor()->SetIdealYawToTargetAndUpdate ( GetAbsOrigin() - vRight * 256 );
  617. fSkip = TRUE;
  618. TaskComplete();
  619. }
  620. }
  621. if ( !fSkip )
  622. {
  623. TaskFail( FAIL_NO_COVER );
  624. }
  625. }
  626. else
  627. {
  628. Msg ( "AGRunt - no enemy monster ptr!!!\n" );
  629. TaskFail( FAIL_NO_ENEMY );
  630. }
  631. break;
  632. default:
  633. BaseClass::StartTask ( pTask );
  634. break;
  635. }
  636. }
  637. void CNPC_AlienGrunt::RunTask( const Task_t *pTask )
  638. {
  639. switch ( pTask->iTask )
  640. {
  641. // NOTE: This is obsolete. Don't use it for HL2 code
  642. case TASK_AGRUNT_RANGE_ATTACK1_NOTURN:
  643. {
  644. AutoMovement( );
  645. if ( IsActivityFinished() )
  646. {
  647. TaskComplete();
  648. }
  649. break;
  650. }
  651. default:
  652. BaseClass::RunTask( pTask );
  653. }
  654. }
  655. //=========================================================
  656. // GetSchedule - Decides which type of schedule best suits
  657. // the monster's current state and conditions. Then calls
  658. // monster's member function to get a pointer to a schedule
  659. // of the proper type.
  660. //=========================================================
  661. int CNPC_AlienGrunt::SelectSchedule( void )
  662. {
  663. if ( HasCondition( COND_HEAR_DANGER ) )
  664. {
  665. return SCHED_TAKE_COVER_FROM_BEST_SOUND;
  666. }
  667. switch ( m_NPCState )
  668. {
  669. case NPC_STATE_COMBAT:
  670. {
  671. // dead enemy
  672. if ( HasCondition( COND_ENEMY_DEAD ) )
  673. {
  674. // call base class, all code to handle dead enemies is centralized there.
  675. return BaseClass::SelectSchedule();
  676. }
  677. if ( HasCondition( COND_NEW_ENEMY) )
  678. {
  679. return SCHED_WAKE_ANGRY;
  680. }
  681. // zap player!
  682. if ( HasCondition ( COND_CAN_MELEE_ATTACK1 ) )
  683. {
  684. AttackSound();// this is a total hack. Should be parto f the schedule
  685. return SCHED_MELEE_ATTACK1;
  686. }
  687. if ( HasCondition ( COND_HEAVY_DAMAGE ) )
  688. {
  689. return SCHED_SMALL_FLINCH;
  690. }
  691. // can attack
  692. if ( HasCondition ( COND_CAN_RANGE_ATTACK1 ) && OccupyStrategySlotRange( AGRUNT_SQUAD_SLOT_HORNET1, AGRUNT_SQUAD_SLOT_HORNET2 ) )
  693. {
  694. return SCHED_RANGE_ATTACK1;
  695. }
  696. if ( OccupyStrategySlot ( AGRUNT_SQUAD_SLOT_CHASE ) )
  697. {
  698. return SCHED_CHASE_ENEMY;
  699. }
  700. return SCHED_STANDOFF;
  701. }
  702. }
  703. return BaseClass::SelectSchedule();
  704. }
  705. int CNPC_AlienGrunt::TranslateSchedule( int scheduleType )
  706. {
  707. switch ( scheduleType )
  708. {
  709. case SCHED_TAKE_COVER_FROM_ENEMY:
  710. return SCHED_AGRUNT_TAKE_COVER_FROM_ENEMY;
  711. break;
  712. /*case SCHED_RANGE_ATTACK1:
  713. if ( HasCondition( COND_SEE_ENEMY ) )
  714. return SCHED_AGRUNT_RANGE_ATTACK;
  715. // else
  716. // return SCHED_AGRUNT_HIDDEN_RANGE_ATTACK;
  717. break;*/
  718. case SCHED_STANDOFF:
  719. return SCHED_AGRUNT_STANDOFF;
  720. break;
  721. case SCHED_VICTORY_DANCE:
  722. return SCHED_AGRUNT_VICTORY_DANCE;
  723. break;
  724. case SCHED_FAIL:
  725. // no fail schedule specified, so pick a good generic one.
  726. {
  727. if ( GetEnemy() != NULL )
  728. {
  729. // I have an enemy
  730. // !!!LATER - what if this enemy is really far away and i'm chasing him?
  731. // this schedule will make me stop, face his last known position for 2
  732. // seconds, and then try to move again
  733. return SCHED_AGRUNT_COMBAT_FAIL;
  734. }
  735. return SCHED_AGRUNT_FAIL;
  736. }
  737. break;
  738. }
  739. return BaseClass::TranslateSchedule( scheduleType );
  740. }
  741. void CNPC_AlienGrunt::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
  742. {
  743. CTakeDamageInfo ainfo = info;
  744. float flDamage = ainfo.GetDamage();
  745. if ( ptr->hitgroup == 10 && (ainfo.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_CLUB)))
  746. {
  747. // hit armor
  748. if ( m_flDamageTime != gpGlobals->curtime || (random->RandomInt(0,10) < 1) )
  749. {
  750. CPVSFilter filter( ptr->endpos );
  751. te->ArmorRicochet( filter, 0.0, &ptr->endpos, &ptr->plane.normal );
  752. m_flDamageTime = gpGlobals->curtime;
  753. }
  754. if ( random->RandomInt( 0, 1 ) == 0 )
  755. {
  756. Vector vecTracerDir = vecDir;
  757. vecTracerDir.x += random->RandomFloat( -0.3, 0.3 );
  758. vecTracerDir.y += random->RandomFloat( -0.3, 0.3 );
  759. vecTracerDir.z += random->RandomFloat( -0.3, 0.3 );
  760. vecTracerDir = vecTracerDir * -512;
  761. Vector vEndPos = ptr->endpos + vecTracerDir;
  762. UTIL_Tracer( ptr->endpos, vEndPos, ENTINDEX( edict() ) );
  763. }
  764. flDamage -= 20;
  765. if (flDamage <= 0)
  766. flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated
  767. ainfo.SetDamage( flDamage );
  768. }
  769. else
  770. {
  771. SpawnBlood( ptr->endpos, vecDir, BloodColor(), flDamage);// a little surface blood.
  772. TraceBleed( flDamage, vecDir, ptr, ainfo.GetDamageType() );
  773. }
  774. AddMultiDamage( ainfo, this );
  775. }
  776. //=========================================================
  777. // AI Schedules Specific to this monster
  778. //=========================================================
  779. AI_BEGIN_CUSTOM_NPC( monster_alien_grunt, CNPC_AlienGrunt )
  780. DECLARE_ACTIVITY( ACT_THREAT_DISPLAY )
  781. DECLARE_TASK ( TASK_AGRUNT_SETUP_HIDE_ATTACK )
  782. DECLARE_TASK ( TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE )
  783. DECLARE_TASK ( TASK_AGRUNT_RANGE_ATTACK1_NOTURN )
  784. DECLARE_SQUADSLOT( AGRUNT_SQUAD_SLOT_HORNET1 )
  785. DECLARE_SQUADSLOT( AGRUNT_SQUAD_SLOT_HORNET2 )
  786. DECLARE_SQUADSLOT( AGRUNT_SQUAD_SLOT_CHASE )
  787. //=========================================================
  788. // Fail Schedule
  789. //=========================================================
  790. DEFINE_SCHEDULE
  791. (
  792. SCHED_AGRUNT_FAIL,
  793. " Tasks"
  794. " TASK_STOP_MOVING 0"
  795. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  796. " TASK_WAIT 2"
  797. " TASK_WAIT_PVS 0"
  798. " "
  799. " Interrupts"
  800. " COND_CAN_RANGE_ATTACK1"
  801. " COND_CAN_MELEE_ATTACK1"
  802. )
  803. //=========================================================
  804. // Combat Fail Schedule
  805. //=========================================================
  806. DEFINE_SCHEDULE
  807. (
  808. SCHED_AGRUNT_COMBAT_FAIL,
  809. " Tasks"
  810. " TASK_STOP_MOVING 0"
  811. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  812. " TASK_WAIT_FACE_ENEMY 2"
  813. " TASK_WAIT_PVS 0"
  814. " "
  815. " Interrupts"
  816. " COND_CAN_RANGE_ATTACK1"
  817. " COND_CAN_MELEE_ATTACK1"
  818. )
  819. //=========================================================
  820. // Standoff schedule. Used in combat when a monster is
  821. // hiding in cover or the enemy has moved out of sight.
  822. // Should we look around in this schedule?
  823. //=========================================================
  824. DEFINE_SCHEDULE
  825. (
  826. SCHED_AGRUNT_STANDOFF,
  827. " Tasks"
  828. " TASK_STOP_MOVING 0"
  829. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  830. " TASK_WAIT_FACE_ENEMY 2"
  831. " "
  832. " Interrupts"
  833. " COND_CAN_RANGE_ATTACK1"
  834. " COND_CAN_MELEE_ATTACK1"
  835. " COND_SEE_ENEMY"
  836. " COND_NEW_ENEMY"
  837. " COND_HEAR_DANGER"
  838. )
  839. //=========================================================
  840. // Suppress
  841. //=========================================================
  842. DEFINE_SCHEDULE
  843. (
  844. SCHED_AGRUNT_SUPPRESS_HORNET,
  845. " Tasks"
  846. " TASK_STOP_MOVING 0"
  847. " TASK_RANGE_ATTACK1 0"
  848. )
  849. //=========================================================
  850. // primary range attacks
  851. //=========================================================
  852. DEFINE_SCHEDULE
  853. (
  854. SCHED_AGRUNT_RANGE_ATTACK,
  855. " Tasks"
  856. " TASK_STOP_MOVING 0"
  857. " TASK_FACE_ENEMY 0"
  858. " TASK_RANGE_ATTACK1 0"
  859. " "
  860. " Interrupts"
  861. " COND_NEW_ENEMY"
  862. " COND_ENEMY_DEAD"
  863. " COND_HEAVY_DAMAGE"
  864. )
  865. DEFINE_SCHEDULE
  866. (
  867. SCHED_AGRUNT_HIDDEN_RANGE_ATTACK,
  868. " Tasks"
  869. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_AGRUNT_STANDOFF"
  870. " TASK_AGRUNT_SETUP_HIDE_ATTACK 0"
  871. " TASK_STOP_MOVING 0"
  872. " TASK_FACE_IDEAL 0"
  873. " TASK_AGRUNT_RANGE_ATTACK1_NOTURN 0"
  874. " "
  875. " Interrupts"
  876. " COND_NEW_ENEMY"
  877. " COND_HEAVY_DAMAGE"
  878. " COND_HEAR_DANGER"
  879. )
  880. //=========================================================
  881. // Take cover from enemy! Tries lateral cover before node
  882. // cover!
  883. //=========================================================
  884. DEFINE_SCHEDULE
  885. (
  886. SCHED_AGRUNT_TAKE_COVER_FROM_ENEMY,
  887. " Tasks"
  888. " TASK_STOP_MOVING 0"
  889. " TASK_WAIT 0.2"
  890. " TASK_FIND_COVER_FROM_ENEMY 0"
  891. " TASK_RUN_PATH 0"
  892. " TASK_WAIT_FOR_MOVEMENT 0"
  893. " TASK_REMEMBER MEMORY:INCOVER"
  894. " TASK_FACE_ENEMY 0"
  895. " "
  896. " Interrupts"
  897. " COND_NEW_ENEMY"
  898. )
  899. //=========================================================
  900. // Victory dance!
  901. //=========================================================
  902. DEFINE_SCHEDULE
  903. (
  904. SCHED_AGRUNT_VICTORY_DANCE,
  905. " Tasks"
  906. " TASK_STOP_MOVING 0"
  907. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_AGRUNT_THREAT_DISPLAY"
  908. " TASK_WAIT 0.2"
  909. " TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE 0"
  910. " TASK_WALK_PATH 0"
  911. " TASK_WAIT_FOR_MOVEMENT 0"
  912. " TASK_FACE_ENEMY 0"
  913. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_CROUCH"
  914. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
  915. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
  916. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_STAND"
  917. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_THREAT_DISPLAY"
  918. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_CROUCH"
  919. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
  920. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
  921. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
  922. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
  923. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
  924. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_STAND"
  925. " "
  926. " Interrupts"
  927. " COND_NEW_ENEMY"
  928. " COND_LIGHT_DAMAGE"
  929. " COND_HEAVY_DAMAGE"
  930. )
  931. //=========================================================
  932. //=========================================================
  933. DEFINE_SCHEDULE
  934. (
  935. SCHED_AGRUNT_THREAT_DISPLAY,
  936. " Tasks"
  937. " TASK_STOP_MOVING 0"
  938. " TASK_FACE_ENEMY 0"
  939. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_THREAT_DISPLAY"
  940. " "
  941. " Interrupts"
  942. " COND_NEW_ENEMY"
  943. " COND_LIGHT_DAMAGE"
  944. " COND_HEAVY_DAMAGE"
  945. " COND_HEAR_PLAYER"
  946. " COND_HEAR_COMBAT"
  947. " COND_HEAR_WORLD"
  948. )
  949. AI_END_CUSTOM_NPC()