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.

1509 lines
39 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "soundent.h"
  8. #include "game.h"
  9. #include "beam_shared.h"
  10. #include "Sprite.h"
  11. #include "npcevent.h"
  12. #include "npc_stalker.h"
  13. #include "ai_hull.h"
  14. #include "ai_default.h"
  15. #include "ai_node.h"
  16. #include "ai_network.h"
  17. #include "ai_hint.h"
  18. #include "ai_link.h"
  19. #include "ai_waypoint.h"
  20. #include "ai_navigator.h"
  21. #include "ai_senses.h"
  22. #include "ai_squadslot.h"
  23. #include "ai_squad.h"
  24. #include "ai_memory.h"
  25. #include "ai_tacticalservices.h"
  26. #include "ai_moveprobe.h"
  27. #include "npc_talker.h"
  28. #include "activitylist.h"
  29. #include "bitstring.h"
  30. #include "decals.h"
  31. #include "player.h"
  32. #include "entitylist.h"
  33. #include "ndebugoverlay.h"
  34. #include "ai_interactions.h"
  35. #include "animation.h"
  36. #include "scriptedtarget.h"
  37. #include "vstdlib/random.h"
  38. #include "engine/IEngineSound.h"
  39. #include "world.h"
  40. // memdbgon must be the last include file in a .cpp file!!!
  41. #include "tier0/memdbgon.h"
  42. //#define STALKER_DEBUG
  43. #define MIN_STALKER_FIRE_RANGE 64
  44. #define MAX_STALKER_FIRE_RANGE 3600 // 3600 feet.
  45. #define STALKER_LASER_ATTACHMENT 1
  46. #define STALKER_TRIGGER_DIST 200 // Enemy dist. that wakes up the stalker
  47. #define STALKER_SENTENCE_VOLUME (float)0.35
  48. #define STALKER_LASER_DURATION 99999
  49. #define STALKER_LASER_RECHARGE 1
  50. #define STALKER_PLAYER_AGGRESSION 1
  51. enum StalkerBeamPower_e
  52. {
  53. STALKER_BEAM_LOW,
  54. STALKER_BEAM_MED,
  55. STALKER_BEAM_HIGH,
  56. };
  57. //Animation events
  58. #define STALKER_AE_MELEE_HIT 1
  59. ConVar sk_stalker_health( "sk_stalker_health","0");
  60. ConVar sk_stalker_melee_dmg( "sk_stalker_melee_dmg","0");
  61. extern void SpawnBlood(Vector vecSpot, const Vector &vAttackDir, int bloodColor, float flDamage);
  62. LINK_ENTITY_TO_CLASS( npc_stalker, CNPC_Stalker );
  63. float g_StalkerBeamThinkTime = 0.0; //0.025;
  64. //=========================================================
  65. // Private activities.
  66. //=========================================================
  67. static int ACT_STALKER_WORK = 0;
  68. //=========================================================
  69. // Stalker schedules
  70. //=========================================================
  71. enum
  72. {
  73. SCHED_STALKER_CHASE_ENEMY = LAST_SHARED_SCHEDULE,
  74. SCHED_STALKER_RANGE_ATTACK,
  75. SCHED_STALKER_ACQUIRE_PLAYER,
  76. SCHED_STALKER_PATROL,
  77. };
  78. //=========================================================
  79. // Stalker Tasks
  80. //=========================================================
  81. enum
  82. {
  83. TASK_STALKER_ZIGZAG = LAST_SHARED_TASK,
  84. TASK_STALKER_SCREAM,
  85. };
  86. // -----------------------------------------------
  87. // > Squad slots
  88. // -----------------------------------------------
  89. enum SquadSlot_T
  90. {
  91. SQUAD_SLOT_CHASE_ENEMY_1 = LAST_SHARED_SQUADSLOT,
  92. SQUAD_SLOT_CHASE_ENEMY_2,
  93. };
  94. //-----------------------------------------------------------------------------
  95. BEGIN_DATADESC( CNPC_Stalker )
  96. DEFINE_KEYFIELD( m_eBeamPower, FIELD_INTEGER, "BeamPower" ),
  97. DEFINE_FIELD( m_vLaserDir, FIELD_VECTOR),
  98. DEFINE_FIELD( m_vLaserTargetPos, FIELD_POSITION_VECTOR),
  99. DEFINE_FIELD( m_fBeamEndTime, FIELD_FLOAT),
  100. DEFINE_FIELD( m_fBeamRechargeTime, FIELD_FLOAT),
  101. DEFINE_FIELD( m_fNextDamageTime, FIELD_FLOAT),
  102. DEFINE_FIELD( m_bPlayingHitWall, FIELD_FLOAT),
  103. DEFINE_FIELD( m_bPlayingHitFlesh, FIELD_FLOAT),
  104. DEFINE_FIELD( m_pBeam, FIELD_CLASSPTR),
  105. DEFINE_FIELD( m_pLightGlow, FIELD_CLASSPTR),
  106. DEFINE_FIELD( m_flNextNPCThink, FIELD_FLOAT),
  107. DEFINE_FIELD( m_vLaserCurPos, FIELD_POSITION_VECTOR),
  108. DEFINE_FIELD( m_flNextAttackSoundTime, FIELD_TIME ),
  109. DEFINE_FIELD( m_flNextBreatheSoundTime, FIELD_TIME ),
  110. DEFINE_FIELD( m_flNextScrambleSoundTime, FIELD_TIME ),
  111. DEFINE_FIELD( m_nextSmokeTime, FIELD_TIME ),
  112. DEFINE_FIELD( m_iPlayerAggression, FIELD_INTEGER ),
  113. DEFINE_FIELD( m_flNextScreamTime, FIELD_TIME ),
  114. // Function Pointers
  115. DEFINE_THINKFUNC( StalkerThink ),
  116. END_DATADESC()
  117. //-----------------------------------------------------------------------------
  118. // Purpose:
  119. // Input :
  120. // Output :
  121. //-----------------------------------------------------------------------------
  122. int CNPC_Stalker::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
  123. {
  124. CTakeDamageInfo info = inputInfo;
  125. // --------------------------------------------
  126. // Don't take a lot of damage from Vortigaunt
  127. // --------------------------------------------
  128. if (info.GetAttacker()->Classify() == CLASS_VORTIGAUNT)
  129. {
  130. info.ScaleDamage( 0.25 );
  131. }
  132. int ret = BaseClass::OnTakeDamage_Alive( info );
  133. // If player shot me make sure I'm mad at him even if I wasn't earlier
  134. if ( (info.GetAttacker()->GetFlags() & FL_CLIENT) )
  135. {
  136. AddClassRelationship( CLASS_PLAYER, D_HT, 0 );
  137. }
  138. return ret;
  139. }
  140. //-----------------------------------------------------------------------------
  141. // Purpose:
  142. //-----------------------------------------------------------------------------
  143. float CNPC_Stalker::MaxYawSpeed( void )
  144. {
  145. #ifdef HL2_EPISODIC
  146. return 10.0f;
  147. #else
  148. switch( GetActivity() )
  149. {
  150. case ACT_TURN_LEFT:
  151. case ACT_TURN_RIGHT:
  152. return 160;
  153. break;
  154. case ACT_RUN:
  155. case ACT_RUN_HURT:
  156. return 280;
  157. break;
  158. default:
  159. return 160;
  160. break;
  161. }
  162. #endif
  163. }
  164. //-----------------------------------------------------------------------------
  165. // Purpose:
  166. //
  167. //
  168. // Output : int
  169. //-----------------------------------------------------------------------------
  170. Class_T CNPC_Stalker::Classify( void )
  171. {
  172. return CLASS_STALKER;
  173. }
  174. //-----------------------------------------------------------------------------
  175. //-----------------------------------------------------------------------------
  176. void CNPC_Stalker::PrescheduleThink()
  177. {
  178. if (gpGlobals->curtime > m_flNextBreatheSoundTime)
  179. {
  180. EmitSound( "NPC_Stalker.Ambient01" );
  181. m_flNextBreatheSoundTime = gpGlobals->curtime + 3.0 + random->RandomFloat( 0.0, 5.0 );
  182. }
  183. }
  184. //-----------------------------------------------------------------------------
  185. //-----------------------------------------------------------------------------
  186. bool CNPC_Stalker::IsValidEnemy( CBaseEntity *pEnemy )
  187. {
  188. Class_T enemyClass = pEnemy->Classify();
  189. if( enemyClass == CLASS_PLAYER || enemyClass == CLASS_PLAYER_ALLY || enemyClass == CLASS_PLAYER_ALLY_VITAL )
  190. {
  191. // Don't get angry at these folks unless provoked.
  192. if( m_iPlayerAggression < STALKER_PLAYER_AGGRESSION )
  193. {
  194. return false;
  195. }
  196. }
  197. if( enemyClass == CLASS_BULLSEYE && pEnemy->GetParent() )
  198. {
  199. // This bullseye is in heirarchy with something. If that
  200. // something is held by the physcannon, this bullseye is
  201. // NOT a valid enemy.
  202. IPhysicsObject *pPhys = pEnemy->GetParent()->VPhysicsGetObject();
  203. if( pPhys && (pPhys->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
  204. {
  205. return false;
  206. }
  207. }
  208. if( GetEnemy() && HasCondition(COND_SEE_ENEMY) )
  209. {
  210. // Short attention span. If I have an enemy, stick with it.
  211. if( GetEnemy() != pEnemy )
  212. {
  213. return false;
  214. }
  215. }
  216. if( IsStrategySlotRangeOccupied( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) && !HasStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
  217. {
  218. return false;
  219. }
  220. if( !FVisible(pEnemy) )
  221. {
  222. // Don't take an enemy you can't see. Since stalkers move way too slowly to
  223. // establish line of fire, usually an enemy acquired by means other than
  224. // the Stalker's own eyesight will always get away while the stalker plods
  225. // slowly to their last known position. So don't take enemies you can't see.
  226. return false;
  227. }
  228. return BaseClass::IsValidEnemy(pEnemy);
  229. }
  230. //-----------------------------------------------------------------------------
  231. // Purpose:
  232. //
  233. //
  234. //-----------------------------------------------------------------------------
  235. void CNPC_Stalker::Spawn( void )
  236. {
  237. Precache( );
  238. SetModel( "models/stalker.mdl" );
  239. SetHullType(HULL_HUMAN);
  240. SetHullSizeNormal();
  241. SetSolid( SOLID_BBOX );
  242. AddSolidFlags( FSOLID_NOT_STANDABLE );
  243. SetMoveType( MOVETYPE_STEP );
  244. m_bloodColor = DONT_BLEED;
  245. m_iHealth = sk_stalker_health.GetFloat();
  246. m_flFieldOfView = 0.1;// indicates the width of this NPC's forward view cone ( as a dotproduct result )
  247. m_NPCState = NPC_STATE_NONE;
  248. CapabilitiesAdd( bits_CAP_SQUAD | bits_CAP_MOVE_GROUND );
  249. CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1);
  250. m_flNextAttackSoundTime = 0;
  251. m_flNextBreatheSoundTime = gpGlobals->curtime + random->RandomFloat( 0.0, 10.0 );
  252. m_flNextScrambleSoundTime = 0;
  253. m_nextSmokeTime = 0;
  254. m_bPlayingHitWall = false;
  255. m_bPlayingHitFlesh = false;
  256. m_fBeamEndTime = 0;
  257. m_fBeamRechargeTime = 0;
  258. m_fNextDamageTime = 0;
  259. NPCInit();
  260. m_flDistTooFar = MAX_STALKER_FIRE_RANGE;
  261. m_iPlayerAggression = 0;
  262. GetSenses()->SetDistLook(MAX_STALKER_FIRE_RANGE - 1);
  263. }
  264. //-----------------------------------------------------------------------------
  265. // Purpose:
  266. //
  267. //
  268. //-----------------------------------------------------------------------------
  269. void CNPC_Stalker::Precache( void )
  270. {
  271. PrecacheModel("models/stalker.mdl");
  272. PrecacheModel("sprites/laser.vmt");
  273. PrecacheModel("sprites/redglow1.vmt");
  274. PrecacheModel("sprites/orangeglow1.vmt");
  275. PrecacheModel("sprites/yellowglow1.vmt");
  276. PrecacheScriptSound( "NPC_Stalker.BurnFlesh" );
  277. PrecacheScriptSound( "NPC_Stalker.BurnWall" );
  278. PrecacheScriptSound( "NPC_Stalker.FootstepLeft" );
  279. PrecacheScriptSound( "NPC_Stalker.FootstepRight" );
  280. PrecacheScriptSound( "NPC_Stalker.Hit" );
  281. PrecacheScriptSound( "NPC_Stalker.Ambient01" );
  282. PrecacheScriptSound( "NPC_Stalker.Scream" );
  283. PrecacheScriptSound( "NPC_Stalker.Pain" );
  284. PrecacheScriptSound( "NPC_Stalker.Die" );
  285. BaseClass::Precache();
  286. }
  287. //-----------------------------------------------------------------------------
  288. // Purpose:
  289. // Input : -
  290. // Output : Returns true on success, false on failure.
  291. //-----------------------------------------------------------------------------
  292. bool CNPC_Stalker::CreateBehaviors()
  293. {
  294. AddBehavior( &m_ActBusyBehavior );
  295. return BaseClass::CreateBehaviors();
  296. }
  297. //-----------------------------------------------------------------------------
  298. // Purpose:
  299. // Input :
  300. // Output :
  301. //-----------------------------------------------------------------------------
  302. void CNPC_Stalker::IdleSound ( void )
  303. {
  304. }
  305. void CNPC_Stalker::OnScheduleChange()
  306. {
  307. KillAttackBeam();
  308. BaseClass::OnScheduleChange();
  309. }
  310. //-----------------------------------------------------------------------------
  311. // Purpose:
  312. // Input : pInflictor -
  313. // pAttacker -
  314. // flDamage -
  315. // bitsDamageType -
  316. //-----------------------------------------------------------------------------
  317. void CNPC_Stalker::Event_Killed( const CTakeDamageInfo &info )
  318. {
  319. if( IsInSquad() && info.GetAttacker()->IsPlayer() )
  320. {
  321. AISquadIter_t iter;
  322. for ( CAI_BaseNPC *pSquadMember = GetSquad()->GetFirstMember( &iter ); pSquadMember; pSquadMember = GetSquad()->GetNextMember( &iter ) )
  323. {
  324. if ( pSquadMember->IsAlive() && pSquadMember != this )
  325. {
  326. CNPC_Stalker *pStalker = dynamic_cast <CNPC_Stalker*>(pSquadMember);
  327. if( pStalker && pStalker->FVisible(info.GetAttacker()) )
  328. {
  329. pStalker->m_iPlayerAggression++;
  330. }
  331. }
  332. }
  333. }
  334. KillAttackBeam();
  335. BaseClass::Event_Killed( info );
  336. }
  337. //-----------------------------------------------------------------------------
  338. // Purpose:
  339. // Input :
  340. // Output :
  341. //-----------------------------------------------------------------------------
  342. void CNPC_Stalker::DeathSound( const CTakeDamageInfo &info )
  343. {
  344. EmitSound( "NPC_Stalker.Die" );
  345. };
  346. //-----------------------------------------------------------------------------
  347. // Purpose:
  348. // Input :
  349. // Output :
  350. //-----------------------------------------------------------------------------
  351. void CNPC_Stalker::PainSound( const CTakeDamageInfo &info )
  352. {
  353. EmitSound( "NPC_Stalker.Pain" );
  354. m_flNextScrambleSoundTime = gpGlobals->curtime + 1.5;
  355. m_flNextBreatheSoundTime = gpGlobals->curtime + 1.5;
  356. m_flNextAttackSoundTime = gpGlobals->curtime + 1.5;
  357. };
  358. //-----------------------------------------------------------------------------
  359. // Purpose: Translates squad slot positions into schedules
  360. // Input :
  361. // Output :
  362. //-----------------------------------------------------------------------------
  363. #if 0
  364. // @TODO (toml 07-18-03): this function is never called. Presumably what it is trying to do still needs to be done...
  365. int CNPC_Stalker::GetSlotSchedule(int slotID)
  366. {
  367. switch (slotID)
  368. {
  369. case SQUAD_SLOT_CHASE_ENEMY_1:
  370. case SQUAD_SLOT_CHASE_ENEMY_2:
  371. return SCHED_STALKER_CHASE_ENEMY;
  372. break;
  373. }
  374. return SCHED_NONE;
  375. }
  376. #endif
  377. void CNPC_Stalker::UpdateAttackBeam( void )
  378. {
  379. CBaseEntity *pEnemy = GetEnemy();
  380. // If not burning at a target
  381. if (pEnemy)
  382. {
  383. if (gpGlobals->curtime > m_fBeamEndTime)
  384. {
  385. TaskComplete();
  386. }
  387. else
  388. {
  389. Vector enemyLKP = GetEnemyLKP();
  390. m_vLaserTargetPos = enemyLKP + pEnemy->GetViewOffset();
  391. // Face my enemy
  392. GetMotor()->SetIdealYawToTargetAndUpdate( enemyLKP );
  393. // ---------------------------------------------
  394. // Get beam end point
  395. // ---------------------------------------------
  396. Vector vecSrc = LaserStartPosition(GetAbsOrigin());
  397. Vector targetDir = m_vLaserTargetPos - vecSrc;
  398. VectorNormalize(targetDir);
  399. // --------------------------------------------------------
  400. // If beam position and laser dir are way off, end attack
  401. // --------------------------------------------------------
  402. if ( DotProduct(targetDir,m_vLaserDir) < 0.5 )
  403. {
  404. TaskComplete();
  405. return;
  406. }
  407. trace_t tr;
  408. AI_TraceLine( vecSrc, vecSrc + m_vLaserDir * MAX_STALKER_FIRE_RANGE, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
  409. // ---------------------------------------------
  410. // If beam not long enough, stop attacking
  411. // ---------------------------------------------
  412. if (tr.fraction == 1.0)
  413. {
  414. TaskComplete();
  415. return;
  416. }
  417. CSoundEnt::InsertSound(SOUND_DANGER, tr.endpos, 60, 0.025, this);
  418. }
  419. }
  420. else
  421. {
  422. TaskFail(FAIL_NO_ENEMY);
  423. }
  424. }
  425. //=========================================================
  426. // start task
  427. //=========================================================
  428. void CNPC_Stalker::StartTask( const Task_t *pTask )
  429. {
  430. switch ( pTask->iTask )
  431. {
  432. case TASK_STALKER_SCREAM:
  433. {
  434. if( gpGlobals->curtime > m_flNextScreamTime )
  435. {
  436. EmitSound( "NPC_Stalker.Scream" );
  437. m_flNextScreamTime = gpGlobals->curtime + random->RandomFloat( 10.0, 15.0 );
  438. }
  439. TaskComplete();
  440. }
  441. case TASK_ANNOUNCE_ATTACK:
  442. {
  443. // If enemy isn't facing me and I haven't attacked in a while
  444. // annouce my attack before I start wailing away
  445. CBaseCombatCharacter *pBCC = GetEnemyCombatCharacterPointer();
  446. if (pBCC && (!pBCC->FInViewCone ( this )) &&
  447. (gpGlobals->curtime - m_flLastAttackTime > 1.0) )
  448. {
  449. m_flLastAttackTime = gpGlobals->curtime;
  450. // Always play this sound
  451. EmitSound( "NPC_Stalker.Scream" );
  452. m_flNextScrambleSoundTime = gpGlobals->curtime + 2;
  453. m_flNextBreatheSoundTime = gpGlobals->curtime + 2;
  454. // Wait two seconds
  455. SetWait( 2.0 );
  456. SetActivity(ACT_IDLE);
  457. }
  458. break;
  459. }
  460. case TASK_STALKER_ZIGZAG:
  461. break;
  462. case TASK_RANGE_ATTACK1:
  463. {
  464. CBaseEntity *pEnemy = GetEnemy();
  465. if (pEnemy)
  466. {
  467. m_vLaserTargetPos = GetEnemyLKP() + pEnemy->GetViewOffset();
  468. // Never hit target on first try
  469. Vector missPos = m_vLaserTargetPos;
  470. if( pEnemy->Classify() == CLASS_BULLSEYE && hl2_episodic.GetBool() )
  471. {
  472. missPos.x += 60 + 120*random->RandomInt(-1,1);
  473. missPos.y += 60 + 120*random->RandomInt(-1,1);
  474. }
  475. else
  476. {
  477. missPos.x += 80*random->RandomInt(-1,1);
  478. missPos.y += 80*random->RandomInt(-1,1);
  479. }
  480. // ----------------------------------------------------------------------
  481. // If target is facing me and not running towards me shoot below his feet
  482. // so he can see the laser coming
  483. // ----------------------------------------------------------------------
  484. CBaseCombatCharacter *pBCC = ToBaseCombatCharacter(pEnemy);
  485. if (pBCC)
  486. {
  487. Vector targetToMe = (pBCC->GetAbsOrigin() - GetAbsOrigin());
  488. Vector vBCCFacing = pBCC->BodyDirection2D( );
  489. if ((DotProduct(vBCCFacing,targetToMe) < 0) &&
  490. (pBCC->GetSmoothedVelocity().Length() < 50))
  491. {
  492. missPos.z -= 150;
  493. }
  494. // --------------------------------------------------------
  495. // If facing away or running towards laser,
  496. // shoot above target's head
  497. // --------------------------------------------------------
  498. else
  499. {
  500. missPos.z += 60;
  501. }
  502. }
  503. m_vLaserDir = missPos - LaserStartPosition(GetAbsOrigin());
  504. VectorNormalize(m_vLaserDir);
  505. }
  506. else
  507. {
  508. TaskFail(FAIL_NO_ENEMY);
  509. return;
  510. }
  511. StartAttackBeam();
  512. SetActivity(ACT_RANGE_ATTACK1);
  513. break;
  514. }
  515. case TASK_GET_PATH_TO_ENEMY_LOS:
  516. {
  517. if ( GetEnemy() != NULL )
  518. {
  519. BaseClass:: StartTask( pTask );
  520. return;
  521. }
  522. Vector posLos;
  523. if (GetTacticalServices()->FindLos(m_vLaserCurPos, m_vLaserCurPos, MIN_STALKER_FIRE_RANGE, MAX_STALKER_FIRE_RANGE, 1.0, &posLos))
  524. {
  525. AI_NavGoal_t goal( posLos, ACT_RUN, AIN_HULL_TOLERANCE );
  526. GetNavigator()->SetGoal( goal );
  527. }
  528. else
  529. {
  530. TaskFail(FAIL_NO_SHOOT);
  531. }
  532. break;
  533. }
  534. case TASK_FACE_ENEMY:
  535. {
  536. if ( GetEnemy() != NULL )
  537. {
  538. BaseClass:: StartTask( pTask );
  539. return;
  540. }
  541. GetMotor()->SetIdealYawToTarget( m_vLaserCurPos );
  542. break;
  543. }
  544. default:
  545. BaseClass:: StartTask( pTask );
  546. break;
  547. }
  548. }
  549. //=========================================================
  550. // RunTask
  551. //=========================================================
  552. void CNPC_Stalker::RunTask( const Task_t *pTask )
  553. {
  554. switch ( pTask->iTask )
  555. {
  556. case TASK_ANNOUNCE_ATTACK:
  557. {
  558. // Stop waiting if enemy facing me or lost enemy
  559. CBaseCombatCharacter* pBCC = GetEnemyCombatCharacterPointer();
  560. if (!pBCC || pBCC->FInViewCone( this ))
  561. {
  562. TaskComplete();
  563. }
  564. if ( IsWaitFinished() )
  565. {
  566. TaskComplete();
  567. }
  568. break;
  569. }
  570. case TASK_STALKER_ZIGZAG :
  571. {
  572. if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
  573. {
  574. TaskComplete();
  575. GetNavigator()->StopMoving(); // Stop moving
  576. }
  577. else if (!GetNavigator()->IsGoalActive())
  578. {
  579. SetIdealActivity( GetStoppedActivity() );
  580. }
  581. else if (ValidateNavGoal())
  582. {
  583. SetIdealActivity( GetNavigator()->GetMovementActivity() );
  584. AddZigZagToPath();
  585. }
  586. break;
  587. }
  588. case TASK_RANGE_ATTACK1:
  589. UpdateAttackBeam();
  590. if ( !TaskIsRunning() || HasCondition( COND_TASK_FAILED ))
  591. {
  592. KillAttackBeam();
  593. }
  594. break;
  595. case TASK_FACE_ENEMY:
  596. {
  597. if ( GetEnemy() != NULL )
  598. {
  599. BaseClass:: RunTask( pTask );
  600. return;
  601. }
  602. GetMotor()->SetIdealYawToTargetAndUpdate( m_vLaserCurPos );
  603. if ( FacingIdeal() )
  604. {
  605. TaskComplete();
  606. }
  607. break;
  608. }
  609. default:
  610. {
  611. BaseClass::RunTask( pTask );
  612. break;
  613. }
  614. }
  615. }
  616. //-----------------------------------------------------------------------------
  617. // Purpose:
  618. // Input :
  619. // Output :
  620. //-----------------------------------------------------------------------------
  621. int CNPC_Stalker::SelectSchedule( void )
  622. {
  623. if ( BehaviorSelectSchedule() )
  624. {
  625. return BaseClass::SelectSchedule();
  626. }
  627. switch ( m_NPCState )
  628. {
  629. case NPC_STATE_IDLE:
  630. case NPC_STATE_ALERT:
  631. {
  632. if( HasCondition(COND_IN_PVS) )
  633. {
  634. return SCHED_STALKER_PATROL;
  635. }
  636. return SCHED_IDLE_STAND;
  637. break;
  638. }
  639. case NPC_STATE_COMBAT:
  640. {
  641. // -----------
  642. // new enemy
  643. // -----------
  644. if( HasCondition( COND_NEW_ENEMY ) )
  645. {
  646. if( GetEnemy()->IsPlayer() )
  647. {
  648. return SCHED_STALKER_ACQUIRE_PLAYER;
  649. }
  650. }
  651. if( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  652. {
  653. if( OccupyStrategySlotRange(SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2) )
  654. {
  655. return SCHED_RANGE_ATTACK1;
  656. }
  657. else
  658. {
  659. return SCHED_STALKER_PATROL;
  660. }
  661. }
  662. if( !HasCondition(COND_SEE_ENEMY) )
  663. {
  664. return SCHED_STALKER_PATROL;
  665. }
  666. return SCHED_COMBAT_FACE;
  667. break;
  668. }
  669. }
  670. // no special cases here, call the base class
  671. return BaseClass::SelectSchedule();
  672. }
  673. //-----------------------------------------------------------------------------
  674. // Purpose:
  675. // Input :
  676. // Output :
  677. //-----------------------------------------------------------------------------
  678. int CNPC_Stalker::TranslateSchedule( int scheduleType )
  679. {
  680. switch( scheduleType )
  681. {
  682. case SCHED_RANGE_ATTACK1:
  683. {
  684. return SCHED_STALKER_RANGE_ATTACK;
  685. }
  686. case SCHED_FAIL_ESTABLISH_LINE_OF_FIRE:
  687. {
  688. return SCHED_COMBAT_STAND;
  689. break;
  690. }
  691. case SCHED_FAIL_TAKE_COVER:
  692. {
  693. return SCHED_RUN_RANDOM;
  694. break;
  695. }
  696. }
  697. return BaseClass::TranslateSchedule( scheduleType );
  698. }
  699. //------------------------------------------------------------------------------
  700. // Purpose : Returns position of laser for any given position of the staler
  701. // Input :
  702. // Output :
  703. //------------------------------------------------------------------------------
  704. Vector CNPC_Stalker::LaserStartPosition(Vector vStalkerPos)
  705. {
  706. // Get attachment position
  707. Vector vAttachPos;
  708. GetAttachment(STALKER_LASER_ATTACHMENT,vAttachPos);
  709. // Now convert to vStalkerPos
  710. vAttachPos = vAttachPos - GetAbsOrigin() + vStalkerPos;
  711. return vAttachPos;
  712. }
  713. //------------------------------------------------------------------------------
  714. // Purpose : Calculate position of beam
  715. // Input :
  716. // Output :
  717. //------------------------------------------------------------------------------
  718. void CNPC_Stalker::CalcBeamPosition(void)
  719. {
  720. Vector targetDir = m_vLaserTargetPos - LaserStartPosition(GetAbsOrigin());
  721. VectorNormalize(targetDir);
  722. // ---------------------------------------
  723. // Otherwise if burning towards an enemy
  724. // ---------------------------------------
  725. if ( GetEnemy() )
  726. {
  727. // ---------------------------------------
  728. // Integrate towards target position
  729. // ---------------------------------------
  730. float iRate = 0.95;
  731. if( GetEnemy()->Classify() == CLASS_BULLSEYE )
  732. {
  733. // Seek bullseyes faster
  734. iRate = 0.8;
  735. }
  736. m_vLaserDir.x = (iRate * m_vLaserDir.x + (1-iRate) * targetDir.x);
  737. m_vLaserDir.y = (iRate * m_vLaserDir.y + (1-iRate) * targetDir.y);
  738. m_vLaserDir.z = (iRate * m_vLaserDir.z + (1-iRate) * targetDir.z);
  739. VectorNormalize( m_vLaserDir );
  740. // -----------------------------------------
  741. // Add time-coherent noise to the position
  742. // Must be scaled with distance
  743. // -----------------------------------------
  744. float fTargetDist = (GetAbsOrigin() - m_vLaserTargetPos).Length();
  745. float noiseScale = atan(0.2/fTargetDist);
  746. float m_fNoiseModX = 5;
  747. float m_fNoiseModY = 5;
  748. float m_fNoiseModZ = 5;
  749. m_vLaserDir.x += 5*noiseScale*sin(m_fNoiseModX * gpGlobals->curtime + m_fNoiseModX);
  750. m_vLaserDir.y += 5*noiseScale*sin(m_fNoiseModY * gpGlobals->curtime + m_fNoiseModY);
  751. m_vLaserDir.z += 5*noiseScale*sin(m_fNoiseModZ * gpGlobals->curtime + m_fNoiseModZ);
  752. }
  753. }
  754. void CNPC_Stalker::StartAttackBeam( void )
  755. {
  756. if ( m_fBeamEndTime > gpGlobals->curtime || m_fBeamRechargeTime > gpGlobals->curtime )
  757. {
  758. // UNDONE: Debug this and fix!?!?!
  759. m_fBeamRechargeTime = gpGlobals->curtime;
  760. }
  761. // ---------------------------------------------
  762. // If I don't have a beam yet, create one
  763. // ---------------------------------------------
  764. // UNDONE: Why would I ever have a beam already?!?!?!
  765. if (!m_pBeam)
  766. {
  767. Vector vecSrc = LaserStartPosition(GetAbsOrigin());
  768. trace_t tr;
  769. AI_TraceLine ( vecSrc, vecSrc + m_vLaserDir * MAX_STALKER_FIRE_RANGE, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
  770. if ( tr.fraction >= 1.0 )
  771. {
  772. // too far
  773. TaskComplete();
  774. return;
  775. }
  776. m_pBeam = CBeam::BeamCreate( "sprites/laser.vmt", 2.0 );
  777. m_pBeam->PointEntInit( tr.endpos, this );
  778. m_pBeam->SetEndAttachment( STALKER_LASER_ATTACHMENT );
  779. m_pBeam->SetBrightness( 255 );
  780. m_pBeam->SetNoise( 0 );
  781. switch (m_eBeamPower)
  782. {
  783. case STALKER_BEAM_LOW:
  784. m_pBeam->SetColor( 255, 0, 0 );
  785. m_pLightGlow = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetAbsOrigin(), FALSE );
  786. break;
  787. case STALKER_BEAM_MED:
  788. m_pBeam->SetColor( 255, 50, 0 );
  789. m_pLightGlow = CSprite::SpriteCreate( "sprites/orangeglow1.vmt", GetAbsOrigin(), FALSE );
  790. break;
  791. case STALKER_BEAM_HIGH:
  792. m_pBeam->SetColor( 255, 150, 0 );
  793. m_pLightGlow = CSprite::SpriteCreate( "sprites/yellowglow1.vmt", GetAbsOrigin(), FALSE );
  794. break;
  795. }
  796. // ----------------------------
  797. // Light myself in a red glow
  798. // ----------------------------
  799. m_pLightGlow->SetTransparency( kRenderGlow, 255, 200, 200, 0, kRenderFxNoDissipation );
  800. m_pLightGlow->SetAttachment( this, 1 );
  801. m_pLightGlow->SetBrightness( 255 );
  802. m_pLightGlow->SetScale( 0.65 );
  803. #if 0
  804. CBaseEntity *pEnemy = GetEnemy();
  805. // --------------------------------------------------------
  806. // Play start up sound - client should always hear this!
  807. // --------------------------------------------------------
  808. if (pEnemy != NULL && (pEnemy->IsPlayer()) )
  809. {
  810. EmitAmbientSound( 0, pEnemy->GetAbsOrigin(), "NPC_Stalker.AmbientLaserStart" );
  811. }
  812. else
  813. {
  814. EmitAmbientSound( 0, GetAbsOrigin(), "NPC_Stalker.AmbientLaserStart" );
  815. }
  816. #endif
  817. }
  818. SetThink( &CNPC_Stalker::StalkerThink );
  819. m_flNextNPCThink = GetNextThink();
  820. SetNextThink( gpGlobals->curtime + g_StalkerBeamThinkTime );
  821. m_fBeamEndTime = gpGlobals->curtime + STALKER_LASER_DURATION;
  822. }
  823. //------------------------------------------------------------------------------
  824. // Purpose : Update beam more often then regular NPC think so it doesn't
  825. // move so jumpily over the ground
  826. // Input :
  827. // Output :
  828. //------------------------------------------------------------------------------
  829. void CNPC_Stalker::StalkerThink(void)
  830. {
  831. DrawAttackBeam();
  832. if (gpGlobals->curtime >= m_flNextNPCThink)
  833. {
  834. NPCThink();
  835. m_flNextNPCThink = GetNextThink();
  836. }
  837. if ( m_pBeam )
  838. {
  839. SetNextThink( gpGlobals->curtime + g_StalkerBeamThinkTime );
  840. // sanity check?!
  841. const Task_t *pTask = GetTask();
  842. if ( !pTask || pTask->iTask != TASK_RANGE_ATTACK1 || !TaskIsRunning() )
  843. {
  844. KillAttackBeam();
  845. }
  846. }
  847. else
  848. {
  849. DevMsg( 2, "In StalkerThink() but no stalker beam found?\n" );
  850. SetNextThink( m_flNextNPCThink );
  851. }
  852. }
  853. //------------------------------------------------------------------------------
  854. //------------------------------------------------------------------------------
  855. void CNPC_Stalker::NotifyDeadFriend( CBaseEntity *pFriend )
  856. {
  857. BaseClass::NotifyDeadFriend(pFriend);
  858. }
  859. void CNPC_Stalker::DoSmokeEffect( const Vector &position )
  860. {
  861. if ( gpGlobals->curtime > m_nextSmokeTime )
  862. {
  863. m_nextSmokeTime = gpGlobals->curtime + 0.5;
  864. UTIL_Smoke(position, random->RandomInt(5, 10), 10);
  865. }
  866. }
  867. //------------------------------------------------------------------------------
  868. // Purpose : Draw attack beam and do damage / decals
  869. // Input :
  870. // Output :
  871. //------------------------------------------------------------------------------
  872. void CNPC_Stalker::DrawAttackBeam(void)
  873. {
  874. if (!m_pBeam)
  875. return;
  876. // ---------------------------------------------
  877. // Get beam end point
  878. // ---------------------------------------------
  879. Vector vecSrc = LaserStartPosition(GetAbsOrigin());
  880. trace_t tr;
  881. AI_TraceLine( vecSrc, vecSrc + m_vLaserDir * MAX_STALKER_FIRE_RANGE, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
  882. CalcBeamPosition();
  883. bool bInWater = (UTIL_PointContents ( tr.endpos ) & MASK_WATER)?true:false;
  884. // ---------------------------------------------
  885. // Update the beam position
  886. // ---------------------------------------------
  887. m_pBeam->SetStartPos( tr.endpos );
  888. m_pBeam->RelinkBeam();
  889. Vector vAttachPos;
  890. GetAttachment(STALKER_LASER_ATTACHMENT,vAttachPos);
  891. Vector vecAimDir = tr.endpos - vAttachPos;
  892. VectorNormalize( vecAimDir );
  893. SetAim( vecAimDir );
  894. // --------------------------------------------
  895. // Play burn sounds
  896. // --------------------------------------------
  897. CBaseCombatCharacter *pBCC = ToBaseCombatCharacter( tr.m_pEnt );
  898. if (pBCC)
  899. {
  900. if (gpGlobals->curtime > m_fNextDamageTime)
  901. {
  902. ClearMultiDamage();
  903. float damage = 0.0;
  904. switch (m_eBeamPower)
  905. {
  906. case STALKER_BEAM_LOW:
  907. damage = 1;
  908. break;
  909. case STALKER_BEAM_MED:
  910. damage = 3;
  911. break;
  912. case STALKER_BEAM_HIGH:
  913. damage = 10;
  914. break;
  915. }
  916. CTakeDamageInfo info( this, this, damage, DMG_SHOCK );
  917. CalculateMeleeDamageForce( &info, m_vLaserDir, tr.endpos );
  918. pBCC->DispatchTraceAttack( info, m_vLaserDir, &tr );
  919. ApplyMultiDamage();
  920. m_fNextDamageTime = gpGlobals->curtime + 0.1;
  921. }
  922. if (pBCC->Classify()!=CLASS_BULLSEYE)
  923. {
  924. if (!m_bPlayingHitFlesh)
  925. {
  926. CPASAttenuationFilter filter( m_pBeam,"NPC_Stalker.BurnFlesh" );
  927. filter.MakeReliable();
  928. EmitSound( filter, m_pBeam->entindex(),"NPC_Stalker.BurnFlesh" );
  929. m_bPlayingHitFlesh = true;
  930. }
  931. if (m_bPlayingHitWall)
  932. {
  933. StopSound( m_pBeam->entindex(), "NPC_Stalker.BurnWall" );
  934. m_bPlayingHitWall = false;
  935. }
  936. tr.endpos.z -= 24.0f;
  937. if (!bInWater)
  938. {
  939. DoSmokeEffect(tr.endpos + tr.plane.normal * 8);
  940. }
  941. }
  942. }
  943. if (!pBCC || pBCC->Classify()==CLASS_BULLSEYE)
  944. {
  945. if (!m_bPlayingHitWall)
  946. {
  947. CPASAttenuationFilter filter( m_pBeam, "NPC_Stalker.BurnWall" );
  948. filter.MakeReliable();
  949. EmitSound( filter, m_pBeam->entindex(), "NPC_Stalker.BurnWall" );
  950. m_bPlayingHitWall = true;
  951. }
  952. if (m_bPlayingHitFlesh)
  953. {
  954. StopSound(m_pBeam->entindex(), "NPC_Stalker.BurnFlesh" );
  955. m_bPlayingHitFlesh = false;
  956. }
  957. UTIL_DecalTrace( &tr, "RedGlowFade");
  958. UTIL_DecalTrace( &tr, "FadingScorch" );
  959. tr.endpos.z -= 24.0f;
  960. if (!bInWater)
  961. {
  962. DoSmokeEffect(tr.endpos + tr.plane.normal * 8);
  963. }
  964. }
  965. if (bInWater)
  966. {
  967. UTIL_Bubbles(tr.endpos-Vector(3,3,3),tr.endpos+Vector(3,3,3),10);
  968. }
  969. /*
  970. CBroadcastRecipientFilter filter;
  971. TE_DynamicLight( filter, 0.0, EyePosition(), 255, 0, 0, 5, 0.2, 0 );
  972. */
  973. }
  974. //------------------------------------------------------------------------------
  975. // Purpose : Draw attack beam and do damage / decals
  976. // Input :
  977. // Output :
  978. //------------------------------------------------------------------------------
  979. void CNPC_Stalker::KillAttackBeam(void)
  980. {
  981. if ( !m_pBeam )
  982. return;
  983. // Kill sound
  984. StopSound(m_pBeam->entindex(), "NPC_Stalker.BurnWall" );
  985. StopSound(m_pBeam->entindex(), "NPC_Stalker.BurnFlesh" );
  986. UTIL_Remove( m_pLightGlow );
  987. UTIL_Remove( m_pBeam);
  988. m_pBeam = NULL;
  989. m_bPlayingHitWall = false;
  990. m_bPlayingHitFlesh = false;
  991. SetThink(&CNPC_Stalker::CallNPCThink);
  992. if ( m_flNextNPCThink > gpGlobals->curtime )
  993. {
  994. SetNextThink( m_flNextNPCThink );
  995. }
  996. // Beam has to recharge
  997. m_fBeamRechargeTime = gpGlobals->curtime + STALKER_LASER_RECHARGE;
  998. ClearCondition( COND_CAN_RANGE_ATTACK1 );
  999. RelaxAim();
  1000. }
  1001. //-----------------------------------------------------------------------------
  1002. // Purpose: Override so can handle LOS to m_pScriptedTarget
  1003. // Input :
  1004. // Output :
  1005. //-----------------------------------------------------------------------------
  1006. bool CNPC_Stalker::InnateWeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions )
  1007. {
  1008. // --------------------
  1009. // Check for occlusion
  1010. // --------------------
  1011. // Base class version assumes innate weapon position is at eye level
  1012. Vector barrelPos = LaserStartPosition(ownerPos);
  1013. trace_t tr;
  1014. AI_TraceLine( barrelPos, targetPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
  1015. if ( tr.fraction == 1.0 )
  1016. {
  1017. return true;
  1018. }
  1019. CBaseEntity *pBE = tr.m_pEnt;
  1020. CBaseCombatCharacter *pBCC = ToBaseCombatCharacter( pBE );
  1021. if ( pBE == GetEnemy() )
  1022. {
  1023. return true;
  1024. }
  1025. else if (pBCC)
  1026. {
  1027. if (IRelationType( pBCC ) == D_HT)
  1028. {
  1029. return true;
  1030. }
  1031. else if (bSetConditions)
  1032. {
  1033. SetCondition(COND_WEAPON_BLOCKED_BY_FRIEND);
  1034. }
  1035. }
  1036. else if (bSetConditions)
  1037. {
  1038. SetCondition(COND_WEAPON_SIGHT_OCCLUDED);
  1039. SetEnemyOccluder(pBE);
  1040. }
  1041. return false;
  1042. }
  1043. //-----------------------------------------------------------------------------
  1044. // Purpose: For innate melee attack
  1045. // Input :
  1046. // Output :
  1047. //-----------------------------------------------------------------------------
  1048. int CNPC_Stalker::MeleeAttack1Conditions ( float flDot, float flDist )
  1049. {
  1050. if (flDist > MIN_STALKER_FIRE_RANGE)
  1051. {
  1052. return COND_TOO_FAR_TO_ATTACK;
  1053. }
  1054. else if (flDot < 0.7)
  1055. {
  1056. return COND_NOT_FACING_ATTACK;
  1057. }
  1058. return COND_CAN_MELEE_ATTACK1;
  1059. }
  1060. //-----------------------------------------------------------------------------
  1061. // Purpose: For innate range attack
  1062. // Input :
  1063. // Output :
  1064. //-----------------------------------------------------------------------------
  1065. int CNPC_Stalker::RangeAttack1Conditions( float flDot, float flDist )
  1066. {
  1067. if (gpGlobals->curtime < m_fBeamRechargeTime )
  1068. {
  1069. return COND_NONE;
  1070. }
  1071. if( IsStrategySlotRangeOccupied( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
  1072. {
  1073. // Couldn't attack if I wanted to.
  1074. return COND_NONE;
  1075. }
  1076. if (flDist <= MIN_STALKER_FIRE_RANGE)
  1077. {
  1078. return COND_TOO_CLOSE_TO_ATTACK;
  1079. }
  1080. else if (flDist > (MAX_STALKER_FIRE_RANGE * 0.66f) )
  1081. {
  1082. return COND_TOO_FAR_TO_ATTACK;
  1083. }
  1084. else if (flDot < 0.7)
  1085. {
  1086. return COND_NOT_FACING_ATTACK;
  1087. }
  1088. return COND_CAN_RANGE_ATTACK1;
  1089. }
  1090. //-----------------------------------------------------------------------------
  1091. // Purpose: Catch stalker specific messages
  1092. // Input :
  1093. // Output :
  1094. //-----------------------------------------------------------------------------
  1095. void CNPC_Stalker::HandleAnimEvent( animevent_t *pEvent )
  1096. {
  1097. switch( pEvent->event )
  1098. {
  1099. case NPC_EVENT_LEFTFOOT:
  1100. {
  1101. EmitSound( "NPC_Stalker.FootstepLeft", pEvent->eventtime );
  1102. }
  1103. break;
  1104. case NPC_EVENT_RIGHTFOOT:
  1105. {
  1106. EmitSound( "NPC_Stalker.FootstepRight", pEvent->eventtime );
  1107. }
  1108. break;
  1109. case STALKER_AE_MELEE_HIT:
  1110. {
  1111. CBaseEntity *pHurt;
  1112. pHurt = CheckTraceHullAttack( 32, Vector(-16,-16,-16), Vector(16,16,16), sk_stalker_melee_dmg.GetFloat(), DMG_SLASH );
  1113. if ( pHurt )
  1114. {
  1115. if ( pHurt->GetFlags() & (FL_NPC|FL_CLIENT) )
  1116. {
  1117. pHurt->ViewPunch( QAngle( 5, 0, random->RandomInt(-10,10)) );
  1118. }
  1119. // Spawn some extra blood if we hit a BCC
  1120. CBaseCombatCharacter* pBCC = ToBaseCombatCharacter( pHurt );
  1121. if (pBCC)
  1122. {
  1123. SpawnBlood(pBCC->EyePosition(), g_vecAttackDir, pBCC->BloodColor(), sk_stalker_melee_dmg.GetFloat());
  1124. }
  1125. // Play a attack hit sound
  1126. EmitSound( "NPC_Stalker.Hit" );
  1127. }
  1128. break;
  1129. }
  1130. default:
  1131. BaseClass::HandleAnimEvent( pEvent );
  1132. break;
  1133. }
  1134. }
  1135. //-----------------------------------------------------------------------------
  1136. // Purpose: Tells use whether or not the NPC cares about a given type of hint node.
  1137. // Input : sHint -
  1138. // Output : TRUE if the NPC is interested in this hint type, FALSE if not.
  1139. //-----------------------------------------------------------------------------
  1140. bool CNPC_Stalker::FValidateHintType(CAI_Hint *pHint)
  1141. {
  1142. return(pHint->HintType() == HINT_WORLD_WORK_POSITION);
  1143. }
  1144. //-----------------------------------------------------------------------------
  1145. // Purpose: Override in subclasses to associate specific hint types
  1146. // with activities
  1147. // Input :
  1148. // Output :
  1149. //-----------------------------------------------------------------------------
  1150. Activity CNPC_Stalker::GetHintActivity( short sHintType, Activity HintsActivity )
  1151. {
  1152. if (sHintType == HINT_WORLD_WORK_POSITION)
  1153. {
  1154. return ( Activity )ACT_STALKER_WORK;
  1155. }
  1156. return BaseClass::GetHintActivity( sHintType, HintsActivity );
  1157. }
  1158. //-----------------------------------------------------------------------------
  1159. // Purpose: Override in subclasses to give specific hint types delays
  1160. // before they can be used again
  1161. // Input :
  1162. // Output :
  1163. //-----------------------------------------------------------------------------
  1164. float CNPC_Stalker::GetHintDelay( short sHintType )
  1165. {
  1166. if (sHintType == HINT_WORLD_WORK_POSITION)
  1167. {
  1168. return 2.0;
  1169. }
  1170. return 0;
  1171. }
  1172. //-----------------------------------------------------------------------------
  1173. // Purpose:
  1174. // Input :
  1175. // Output :
  1176. //-----------------------------------------------------------------------------
  1177. #define ZIG_ZAG_SIZE 3600
  1178. void CNPC_Stalker::AddZigZagToPath(void)
  1179. {
  1180. // If already on a detour don't add a zigzag
  1181. if (GetNavigator()->GetCurWaypointFlags() & bits_WP_TO_DETOUR)
  1182. {
  1183. return;
  1184. }
  1185. // If enemy isn't facing me or occluded, don't add a zigzag
  1186. if (HasCondition(COND_ENEMY_OCCLUDED) || !HasCondition ( COND_ENEMY_FACING_ME ))
  1187. {
  1188. return;
  1189. }
  1190. Vector waypointPos = GetNavigator()->GetCurWaypointPos();
  1191. Vector waypointDir = (waypointPos - GetAbsOrigin());
  1192. // If the distance to the next node is greater than ZIG_ZAG_SIZE
  1193. // then add a random zig/zag to the path
  1194. if (waypointDir.LengthSqr() > ZIG_ZAG_SIZE)
  1195. {
  1196. // Pick a random distance for the zigzag (less that sqrt(ZIG_ZAG_SIZE)
  1197. float distance = random->RandomFloat( 30, 60 );
  1198. // Get me a vector orthogonal to the direction of motion
  1199. VectorNormalize( waypointDir );
  1200. Vector vDirUp(0,0,1);
  1201. Vector vDir;
  1202. CrossProduct( waypointDir, vDirUp, vDir);
  1203. // Pick a random direction (left/right) for the zigzag
  1204. if (random->RandomInt(0,1))
  1205. {
  1206. vDir = -1 * vDir;
  1207. }
  1208. // Get zigzag position in direction of target waypoint
  1209. Vector zigZagPos = GetAbsOrigin() + waypointDir * 60;
  1210. // Now offset
  1211. zigZagPos = zigZagPos + (vDir * distance);
  1212. // Now make sure that we can still get to the zigzag position and the waypoint
  1213. AIMoveTrace_t moveTrace1, moveTrace2;
  1214. GetMoveProbe()->MoveLimit( NAV_GROUND, GetAbsOrigin(), zigZagPos, MASK_NPCSOLID, NULL, &moveTrace1);
  1215. GetMoveProbe()->MoveLimit( NAV_GROUND, zigZagPos, waypointPos, MASK_NPCSOLID, NULL, &moveTrace2);
  1216. if ( !IsMoveBlocked( moveTrace1 ) && !IsMoveBlocked( moveTrace2 ) )
  1217. {
  1218. GetNavigator()->PrependWaypoint( zigZagPos, NAV_GROUND, bits_WP_TO_DETOUR );
  1219. }
  1220. }
  1221. }
  1222. //------------------------------------------------------------------------------
  1223. // Purpose :
  1224. // Input :
  1225. // Output :
  1226. //------------------------------------------------------------------------------
  1227. CNPC_Stalker::CNPC_Stalker(void)
  1228. {
  1229. #ifdef _DEBUG
  1230. m_vLaserDir.Init();
  1231. m_vLaserTargetPos.Init();
  1232. m_vLaserCurPos.Init();
  1233. #endif
  1234. }
  1235. //------------------------------------------------------------------------------
  1236. //
  1237. // Schedules
  1238. //
  1239. //------------------------------------------------------------------------------
  1240. AI_BEGIN_CUSTOM_NPC( npc_stalker, CNPC_Stalker )
  1241. DECLARE_TASK(TASK_STALKER_ZIGZAG)
  1242. DECLARE_TASK(TASK_STALKER_SCREAM)
  1243. DECLARE_ACTIVITY(ACT_STALKER_WORK)
  1244. DECLARE_SQUADSLOT(SQUAD_SLOT_CHASE_ENEMY_1)
  1245. DECLARE_SQUADSLOT(SQUAD_SLOT_CHASE_ENEMY_2)
  1246. //=========================================================
  1247. // > SCHED_STALKER_RANGE_ATTACK
  1248. //=========================================================
  1249. DEFINE_SCHEDULE
  1250. (
  1251. SCHED_STALKER_RANGE_ATTACK,
  1252. " Tasks"
  1253. " TASK_STOP_MOVING 0"
  1254. " TASK_FACE_ENEMY 0"
  1255. " TASK_RANGE_ATTACK1 0"
  1256. ""
  1257. " Interrupts"
  1258. " COND_CAN_MELEE_ATTACK1"
  1259. " COND_HEAVY_DAMAGE"
  1260. " COND_REPEATED_DAMAGE"
  1261. " COND_HEAR_DANGER"
  1262. " COND_NEW_ENEMY"
  1263. " COND_ENEMY_DEAD"
  1264. " COND_ENEMY_OCCLUDED" // Don't break on this. Keep shooting at last location
  1265. )
  1266. //=========================================================
  1267. // > SCHED_STALKER_CHASE_ENEMY
  1268. //=========================================================
  1269. DEFINE_SCHEDULE
  1270. (
  1271. SCHED_STALKER_CHASE_ENEMY,
  1272. " Tasks"
  1273. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
  1274. " TASK_SET_TOLERANCE_DISTANCE 24"
  1275. " TASK_GET_PATH_TO_ENEMY 0"
  1276. " TASK_RUN_PATH 0"
  1277. " TASK_STALKER_ZIGZAG 0"
  1278. ""
  1279. " Interrupts"
  1280. " COND_NEW_ENEMY"
  1281. " COND_ENEMY_DEAD"
  1282. " COND_CAN_RANGE_ATTACK1"
  1283. " COND_CAN_MELEE_ATTACK1"
  1284. " COND_CAN_RANGE_ATTACK2"
  1285. " COND_CAN_MELEE_ATTACK2"
  1286. " COND_TOO_CLOSE_TO_ATTACK"
  1287. " COND_TASK_FAILED"
  1288. " COND_HEAR_DANGER"
  1289. )
  1290. DEFINE_SCHEDULE
  1291. (
  1292. SCHED_STALKER_ACQUIRE_PLAYER,
  1293. " Tasks"
  1294. " TASK_STOP_MOVING 0"
  1295. " TASK_FACE_ENEMY 0"
  1296. " TASK_WAIT_RANDOM 0.5"
  1297. " TASK_STALKER_SCREAM 0"
  1298. " TASK_WAIT 0.5"
  1299. " TASK_WAIT_RANDOM 0.5"
  1300. ""
  1301. " Interrupts"
  1302. )
  1303. DEFINE_SCHEDULE
  1304. (
  1305. SCHED_STALKER_PATROL,
  1306. " Tasks"
  1307. " TASK_STOP_MOVING 0"
  1308. " TASK_WAIT 0.5"// This makes them look a bit more vigilant, instead of INSTANTLY patrolling after some other action.
  1309. " TASK_WAIT_RANDOM 0.5"
  1310. " TASK_WANDER 18000600"
  1311. " TASK_FACE_PATH 0"
  1312. " TASK_WALK_PATH 0"
  1313. " TASK_WAIT_FOR_MOVEMENT 0"
  1314. " TASK_STOP_MOVING 0"
  1315. " TASK_FACE_REASONABLE 0"
  1316. " TASK_SET_SCHEDULE SCHEDULE:SCHED_STALKER_PATROL"
  1317. ""
  1318. " Interrupts"
  1319. " COND_NEW_ENEMY"
  1320. " COND_CAN_RANGE_ATTACK1"
  1321. " COND_SEE_ENEMY"
  1322. )
  1323. AI_END_CUSTOM_NPC()