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.

1544 lines
45 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "ai_basenpc.h"
  9. #include "AI_Default.h"
  10. #include "AI_Senses.h"
  11. #include "ai_node.h" // for hint defintions
  12. #include "ai_network.h"
  13. #include "AI_Hint.h"
  14. #include "ai_squad.h"
  15. #include "beam_shared.h"
  16. #include "globalstate.h"
  17. #include "soundent.h"
  18. #include "ndebugoverlay.h"
  19. #include "entitylist.h"
  20. #include "npc_citizen17.h"
  21. #include "scriptedtarget.h"
  22. #include "ai_interactions.h"
  23. #include "spotlightend.h"
  24. #include "beam_flags.h"
  25. // memdbgon must be the last include file in a .cpp file!!!
  26. #include "tier0/memdbgon.h"
  27. #define SPOTLIGHT_SWING_FORWARD 1
  28. #define SPOTLIGHT_SWING_BACK -1
  29. //------------------------------------
  30. // Spawnflags
  31. //------------------------------------
  32. #define SF_SPOTLIGHT_START_TRACK_ON (1 << 16)
  33. #define SF_SPOTLIGHT_START_LIGHT_ON (1 << 17)
  34. #define SF_SPOTLIGHT_NO_DYNAMIC_LIGHT (1 << 18)
  35. #define SF_SPOTLIGHT_NEVER_MOVE (1 << 19)
  36. //-----------------------------------------------------------------------------
  37. // Parameters for how the spotlight behaves
  38. //-----------------------------------------------------------------------------
  39. #define SPOTLIGHT_ENTITY_INSPECT_LENGTH 15 // How long does the inspection last
  40. #define SPOTLIGHT_HINT_INSPECT_LENGTH 15 // How long does the inspection last
  41. #define SPOTLIGHT_SOUND_INSPECT_LENGTH 1 // How long does the inspection last
  42. #define SPOTLIGHT_HINT_INSPECT_DELAY 20 // Check for hint nodes this often
  43. #define SPOTLIGHT_ENTITY_INSPECT_DELAY 1 // Check for citizens this often
  44. #define SPOTLIGHT_HINT_SEARCH_DIST 1000 // How far from self do I look for hints?
  45. #define SPOTLIGHT_ENTITY_SEARCH_DIST 100 // How far from spotlight do I look for entities?
  46. #define SPOTLIGHT_ACTIVE_SEARCH_DIST 200 // How far from spotlight do I look when already have entity
  47. #define SPOTLIGHT_BURN_TARGET_THRESH 60 // How close need to get to burn target
  48. #define SPOTLIGHT_MAX_SPEED_SCALE 2
  49. //#define SPOTLIGHT_DEBUG
  50. // -----------------------------------
  51. // Spotlight flags
  52. // -----------------------------------
  53. enum SpotlightFlags_t
  54. {
  55. BITS_SPOTLIGHT_LIGHT_ON = 0x00000001, // Light is on
  56. BITS_SPOTLIGHT_TRACK_ON = 0x00000002, // Tracking targets / scanning
  57. BITS_SPOTLIGHT_SMOOTH_RETURN = 0x00001000, // If out of range, don't pop back to position
  58. };
  59. class CBeam;
  60. class CNPC_Spotlight : public CAI_BaseNPC
  61. {
  62. DECLARE_CLASS( CNPC_Spotlight, CAI_BaseNPC );
  63. public:
  64. CNPC_Spotlight();
  65. Class_T Classify(void);
  66. int UpdateTransmitState(void);
  67. void Event_Killed( const CTakeDamageInfo &info );
  68. int OnTakeDamage_Alive( const CTakeDamageInfo &info );
  69. int GetSoundInterests( void );
  70. bool FValidateHintType(CAI_Hint *pHint);
  71. Disposition_t IRelationType(CBaseEntity *pTarget);
  72. float HearingSensitivity( void ) { return 4.0; };
  73. void NPCThink(void);
  74. bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt);
  75. void UpdateTargets(void);
  76. void Precache(void);
  77. void Spawn(void);
  78. public:
  79. int m_fSpotlightFlags;
  80. // ------------------------------
  81. // Scripted Spotlight Motion
  82. // ------------------------------
  83. CScriptedTarget* m_pScriptedTarget; // My current scripted target
  84. void SetScriptedTarget( CScriptedTarget *pScriptedTarget );
  85. // ------------------------------
  86. // Inspecting
  87. // ------------------------------
  88. Vector m_vInspectPos;
  89. float m_flInspectEndTime;
  90. float m_flNextEntitySearchTime;
  91. float m_flNextHintSearchTime; // Time to look for hints to inspect
  92. void SetInspectTargetToEntity(CBaseEntity *pEntity, float fInspectDuration);
  93. void SetInspectTargetToEnemy(CBaseEntity *pEntity);
  94. void SetInspectTargetToPos(const Vector &vInspectPos, float fInspectDuration);
  95. void SetInspectTargetToHint(CAI_Hint *pHint, float fInspectDuration);
  96. void ClearInspectTarget(void);
  97. bool HaveInspectTarget(void);
  98. Vector InspectTargetPosition(void);
  99. CBaseEntity* BestInspectTarget(void);
  100. void RequestInspectSupport(void);
  101. // -------------------------------
  102. // Outputs
  103. // -------------------------------
  104. bool m_bHadEnemy; // Had an enemy
  105. COutputEvent m_pOutputAlert; // Alerted by sound
  106. COutputEHANDLE m_pOutputDetect; // Found enemy, output it's name
  107. COutputEHANDLE m_pOutputLost; // Lost enemy
  108. COutputEHANDLE m_pOutputSquadDetect; // Squad Found enemy
  109. COutputEHANDLE m_pOutputSquadLost; // Squad Lost enemy
  110. COutputVector m_pOutputPosition; // End position of spotlight beam
  111. // ------------------------------
  112. // Spotlight
  113. // ------------------------------
  114. float m_flYaw;
  115. float m_flYawCenter;
  116. float m_flYawRange; // +/- around center
  117. float m_flYawSpeed;
  118. float m_flYawDir;
  119. float m_flPitch;
  120. float m_flPitchCenter;
  121. float m_flPitchMin;
  122. float m_flPitchMax;
  123. float m_flPitchSpeed;
  124. float m_flPitchDir;
  125. float m_flIdleSpeed; // Speed when no enemy
  126. float m_flAlertSpeed; // Speed when found enemy
  127. Vector m_vSpotlightTargetPos;
  128. Vector m_vSpotlightCurrentPos;
  129. CBeam* m_pSpotlight;
  130. CSpotlightEnd* m_pSpotlightTarget;
  131. Vector m_vSpotlightDir;
  132. int m_nHaloSprite;
  133. float m_flSpotlightMaxLength;
  134. float m_flSpotlightCurLength;
  135. float m_flSpotlightGoalWidth;
  136. void SpotlightUpdate(void);
  137. Vector SpotlightCurrentPos(void);
  138. void SpotlightSetTargetYawAndPitch(void);
  139. float SpotlightSpeed(void);
  140. void SpotlightCreate(void);
  141. void SpotlightDestroy(void);
  142. bool SpotlightIsPositionLegal(const Vector &vTestPos);
  143. // ------------------------------
  144. // Inputs
  145. // ------------------------------
  146. void InputLightOn( inputdata_t &inputdata );
  147. void InputLightOff( inputdata_t &inputdata );
  148. void InputTrackOn( inputdata_t &inputdata );
  149. void InputTrackOff( inputdata_t &inputdata );
  150. protected:
  151. DECLARE_DATADESC();
  152. };
  153. BEGIN_DATADESC( CNPC_Spotlight )
  154. DEFINE_FIELD( m_vInspectPos, FIELD_POSITION_VECTOR ),
  155. DEFINE_FIELD( m_flYaw, FIELD_FLOAT ),
  156. DEFINE_FIELD( m_flYawCenter, FIELD_FLOAT ),
  157. DEFINE_FIELD( m_flYawSpeed, FIELD_FLOAT ),
  158. DEFINE_FIELD( m_flYawDir, FIELD_FLOAT ),
  159. DEFINE_FIELD( m_flPitch, FIELD_FLOAT ),
  160. DEFINE_FIELD( m_flPitchCenter, FIELD_FLOAT ),
  161. DEFINE_FIELD( m_flPitchSpeed, FIELD_FLOAT ),
  162. DEFINE_FIELD( m_flPitchDir, FIELD_FLOAT ),
  163. DEFINE_FIELD( m_flSpotlightCurLength, FIELD_FLOAT ),
  164. DEFINE_FIELD( m_fSpotlightFlags, FIELD_INTEGER ),
  165. DEFINE_FIELD( m_flInspectEndTime, FIELD_TIME ),
  166. DEFINE_FIELD( m_flNextEntitySearchTime, FIELD_TIME ),
  167. DEFINE_FIELD( m_flNextHintSearchTime, FIELD_TIME ),
  168. DEFINE_FIELD( m_bHadEnemy, FIELD_BOOLEAN ),
  169. DEFINE_FIELD( m_vSpotlightTargetPos, FIELD_POSITION_VECTOR ),
  170. DEFINE_FIELD( m_vSpotlightCurrentPos, FIELD_POSITION_VECTOR ),
  171. DEFINE_FIELD( m_pSpotlight, FIELD_CLASSPTR ),
  172. DEFINE_FIELD( m_pSpotlightTarget, FIELD_CLASSPTR ),
  173. DEFINE_FIELD( m_vSpotlightDir, FIELD_VECTOR ),
  174. DEFINE_FIELD( m_nHaloSprite, FIELD_INTEGER ),
  175. DEFINE_FIELD( m_pScriptedTarget, FIELD_CLASSPTR ),
  176. DEFINE_KEYFIELD( m_flYawRange, FIELD_FLOAT, "YawRange"),
  177. DEFINE_KEYFIELD( m_flPitchMin, FIELD_FLOAT, "PitchMin"),
  178. DEFINE_KEYFIELD( m_flPitchMax, FIELD_FLOAT, "PitchMax"),
  179. DEFINE_KEYFIELD( m_flIdleSpeed, FIELD_FLOAT, "IdleSpeed"),
  180. DEFINE_KEYFIELD( m_flAlertSpeed, FIELD_FLOAT, "AlertSpeed"),
  181. DEFINE_KEYFIELD( m_flSpotlightMaxLength,FIELD_FLOAT, "SpotlightLength"),
  182. DEFINE_KEYFIELD( m_flSpotlightGoalWidth,FIELD_FLOAT, "SpotlightWidth"),
  183. // DEBUG m_pScriptedTarget
  184. // Inputs
  185. DEFINE_INPUTFUNC( FIELD_VOID, "LightOn", InputLightOn ),
  186. DEFINE_INPUTFUNC( FIELD_VOID, "LightOff", InputLightOff ),
  187. DEFINE_INPUTFUNC( FIELD_VOID, "TrackOn", InputTrackOn ),
  188. DEFINE_INPUTFUNC( FIELD_VOID, "TrackOff", InputTrackOff ),
  189. // Outputs
  190. DEFINE_OUTPUT(m_pOutputAlert, "OnAlert" ),
  191. DEFINE_OUTPUT(m_pOutputDetect, "DetectedEnemy" ),
  192. DEFINE_OUTPUT(m_pOutputLost, "LostEnemy" ),
  193. DEFINE_OUTPUT(m_pOutputSquadDetect, "SquadDetectedEnemy" ),
  194. DEFINE_OUTPUT(m_pOutputSquadLost, "SquadLostEnemy" ),
  195. DEFINE_OUTPUT(m_pOutputPosition, "LightPosition" ),
  196. END_DATADESC()
  197. LINK_ENTITY_TO_CLASS(npc_spotlight, CNPC_Spotlight);
  198. CNPC_Spotlight::CNPC_Spotlight()
  199. {
  200. #ifdef _DEBUG
  201. m_vInspectPos.Init();
  202. m_vSpotlightTargetPos.Init();
  203. m_vSpotlightCurrentPos.Init();
  204. m_vSpotlightDir.Init();
  205. #endif
  206. }
  207. //-----------------------------------------------------------------------------
  208. // Purpose: Indicates this NPC's place in the relationship table.
  209. //-----------------------------------------------------------------------------
  210. Class_T CNPC_Spotlight::Classify(void)
  211. {
  212. return(CLASS_MILITARY);
  213. }
  214. //-------------------------------------------------------------------------------------
  215. // Purpose : Send even though we don't have a model so spotlight gets proper position
  216. // Input :
  217. // Output :
  218. //-------------------------------------------------------------------------------------
  219. int CNPC_Spotlight::UpdateTransmitState(void)
  220. {
  221. return SetTransmitState( FL_EDICT_PVSCHECK );
  222. }
  223. //------------------------------------------------------------------------------
  224. // Purpose :
  225. // Input :
  226. // Output :
  227. //------------------------------------------------------------------------------
  228. int CNPC_Spotlight::GetSoundInterests( void )
  229. {
  230. return (SOUND_COMBAT | SOUND_DANGER);
  231. }
  232. //------------------------------------------------------------------------------
  233. // Purpose : Override to split in two when attacked
  234. // Input :
  235. // Output :
  236. //------------------------------------------------------------------------------
  237. int CNPC_Spotlight::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  238. {
  239. // Deflect spotlight
  240. Vector vCrossProduct;
  241. CrossProduct(m_vSpotlightDir,g_vecAttackDir, vCrossProduct);
  242. if (vCrossProduct.y > 0)
  243. {
  244. m_flYaw += random->RandomInt(10,20);
  245. }
  246. else
  247. {
  248. m_flYaw -= random->RandomInt(10,20);
  249. }
  250. return (BaseClass::OnTakeDamage_Alive( info ));
  251. }
  252. //-----------------------------------------------------------------------------
  253. // Purpose:
  254. // Input : pInflictor -
  255. // pAttacker -
  256. // flDamage -
  257. // bitsDamageType -
  258. //-----------------------------------------------------------------------------
  259. void CNPC_Spotlight::Event_Killed( const CTakeDamageInfo &info )
  260. {
  261. // Interrupt whatever schedule I'm on
  262. SetCondition(COND_SCHEDULE_DONE);
  263. // Remove spotlight
  264. SpotlightDestroy();
  265. // Otherwise, turn into a physics object and fall to the ground
  266. CBaseCombatCharacter::Event_Killed( info );
  267. }
  268. //-----------------------------------------------------------------------------
  269. // Purpose: Tells use whether or not the NPC cares about a given type of hint node.
  270. // Input : sHint -
  271. // Output : TRUE if the NPC is interested in this hint type, FALSE if not.
  272. //-----------------------------------------------------------------------------
  273. bool CNPC_Spotlight::FValidateHintType(CAI_Hint *pHint)
  274. {
  275. if (pHint->HintType() == HINT_WORLD_WINDOW)
  276. {
  277. Vector vHintPos;
  278. pHint->GetPosition(this,&vHintPos);
  279. if (SpotlightIsPositionLegal(vHintPos))
  280. {
  281. return true;
  282. }
  283. }
  284. return false;
  285. }
  286. //-----------------------------------------------------------------------------
  287. // Purpose: Plays the engine sound.
  288. //-----------------------------------------------------------------------------
  289. void CNPC_Spotlight::NPCThink(void)
  290. {
  291. SetNextThink( gpGlobals->curtime + 0.1f );// keep npc thinking.
  292. if (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI)
  293. {
  294. if (m_pSpotlightTarget)
  295. {
  296. m_pSpotlightTarget->SetAbsVelocity( vec3_origin );
  297. }
  298. }
  299. else if (IsAlive())
  300. {
  301. GetSenses()->Listen();
  302. UpdateTargets();
  303. SpotlightUpdate();
  304. }
  305. }
  306. //-----------------------------------------------------------------------------
  307. // Purpose:
  308. //-----------------------------------------------------------------------------
  309. void CNPC_Spotlight::Precache(void)
  310. {
  311. //
  312. // Model.
  313. //
  314. PrecacheModel("models/combot.mdl");
  315. PrecacheModel("models/gibs/combot_gibs.mdl");
  316. //
  317. // Sprites.
  318. //
  319. PrecacheModel("sprites/spotlight.vmt");
  320. m_nHaloSprite = PrecacheModel("sprites/blueflare1.vmt");
  321. BaseClass::Precache();
  322. }
  323. //------------------------------------------------------------------------------
  324. // Purpose :
  325. // Input :
  326. // Output :
  327. //------------------------------------------------------------------------------
  328. CBaseEntity* CNPC_Spotlight::BestInspectTarget(void)
  329. {
  330. // Only look for inspect targets when spotlight it on
  331. if (m_pSpotlightTarget == NULL)
  332. {
  333. return NULL;
  334. }
  335. float fBestDistance = MAX_COORD_RANGE;
  336. int nBestPriority = -1000;
  337. int nBestRelationship = D_NU;
  338. // Get my best enemy first
  339. CBaseEntity* pBestEntity = BestEnemy();
  340. if (pBestEntity)
  341. {
  342. // If the enemy isn't visibile
  343. if (!FVisible(pBestEntity))
  344. {
  345. // If he hasn't been seen in a while and hasn't already eluded
  346. // the squad, make the enemy as eluded and fire a lost squad output
  347. float flTimeLastSeen = GetEnemies()->LastTimeSeen(pBestEntity);
  348. if (!GetEnemies()->HasEludedMe(pBestEntity) &&
  349. flTimeLastSeen + 0.5 < gpGlobals->curtime)
  350. {
  351. GetEnemies()->MarkAsEluded(pBestEntity);
  352. m_pOutputSquadLost.Set(*((EHANDLE *)pBestEntity),this,this);
  353. }
  354. pBestEntity = NULL;
  355. }
  356. // If he has eluded me or isn't in the legal range of my spotligth reject
  357. else if (GetEnemies()->HasEludedMe(pBestEntity) ||
  358. !SpotlightIsPositionLegal(GetEnemies()->LastKnownPosition(pBestEntity)) )
  359. {
  360. pBestEntity = NULL;
  361. }
  362. }
  363. CBaseEntity *pEntity = NULL;
  364. // Search from the spotlight position
  365. Vector vSearchOrigin = m_pSpotlightTarget->GetAbsOrigin();
  366. float flSearchDist;
  367. if (HaveInspectTarget())
  368. {
  369. flSearchDist = SPOTLIGHT_ACTIVE_SEARCH_DIST;
  370. }
  371. else
  372. {
  373. flSearchDist = SPOTLIGHT_ENTITY_SEARCH_DIST;
  374. }
  375. for ( CEntitySphereQuery sphere( vSearchOrigin, SPOTLIGHT_ENTITY_SEARCH_DIST ); pEntity = sphere.GetCurrentEntity(); sphere.NextEntity() )
  376. {
  377. if (pEntity->GetFlags() & (FL_CLIENT|FL_NPC))
  378. {
  379. if (pEntity->GetFlags() & FL_NOTARGET)
  380. {
  381. continue;
  382. }
  383. if (!pEntity->IsAlive())
  384. {
  385. continue;
  386. }
  387. if ((pEntity->Classify() == CLASS_MILITARY)||
  388. (pEntity->Classify() == CLASS_BULLSEYE))
  389. {
  390. continue;
  391. }
  392. if (m_pSquad && m_pSquad->SquadIsMember(pEntity))
  393. {
  394. continue;
  395. }
  396. // Disregard if the entity is out of the view cone, occluded,
  397. if( !FVisible( pEntity ) )
  398. {
  399. continue;
  400. }
  401. // If it's a new enemy or one that had eluded me
  402. if (!GetEnemies()->HasMemory(pEntity) ||
  403. GetEnemies()->HasEludedMe(pEntity) )
  404. {
  405. m_pOutputSquadDetect.Set(*((EHANDLE *)pEntity),this,this);
  406. }
  407. UpdateEnemyMemory(pEntity,pEntity->GetAbsOrigin());
  408. CBaseCombatCharacter* pBCC = (CBaseCombatCharacter*)pEntity;
  409. float fTestDistance = (GetAbsOrigin() - pBCC->EyePosition()).Length();
  410. int nTestRelationship = IRelationType(pBCC);
  411. int nTestPriority = IRelationPriority ( pBCC );
  412. // Only follow hated entities if I'm not in idle state
  413. if (nTestRelationship != D_HT && m_NPCState != NPC_STATE_IDLE)
  414. {
  415. continue;
  416. }
  417. // -------------------------------------------
  418. // Choose hated entites over everything else
  419. // -------------------------------------------
  420. if (nTestRelationship == D_HT && nBestRelationship != D_HT)
  421. {
  422. pBestEntity = pBCC;
  423. fBestDistance = fTestDistance;
  424. nBestPriority = nTestPriority;
  425. nBestRelationship = nTestRelationship;
  426. }
  427. // -------------------------------------------
  428. // If both are hated, or both are not
  429. // -------------------------------------------
  430. else if( (nTestRelationship != D_HT && nBestRelationship != D_HT) ||
  431. (nTestRelationship == D_HT && nBestRelationship == D_HT) )
  432. {
  433. // --------------------------------------
  434. // Pick one with the higher priority
  435. // --------------------------------------
  436. if (nTestPriority > nBestPriority)
  437. {
  438. pBestEntity = pBCC;
  439. fBestDistance = fTestDistance;
  440. nBestPriority = nTestPriority;
  441. nBestRelationship = nTestRelationship;
  442. }
  443. // -----------------------------------------
  444. // If priority the same pick best distance
  445. // -----------------------------------------
  446. else if (nTestPriority == nBestPriority)
  447. {
  448. if (fTestDistance < fBestDistance)
  449. {
  450. pBestEntity = pBCC;
  451. fBestDistance = fTestDistance;
  452. nBestPriority = nTestPriority;
  453. nBestRelationship = nTestRelationship;
  454. }
  455. }
  456. }
  457. }
  458. }
  459. return pBestEntity;
  460. }
  461. //------------------------------------------------------------------------------
  462. // Purpose : Clears any previous inspect target and set inspect target to
  463. // the given entity and set the durection of the inspection
  464. // Input :
  465. // Output :
  466. //------------------------------------------------------------------------------
  467. void CNPC_Spotlight::SetInspectTargetToEntity(CBaseEntity *pEntity, float fInspectDuration)
  468. {
  469. ClearInspectTarget();
  470. SetTarget(pEntity);
  471. m_flInspectEndTime = gpGlobals->curtime + fInspectDuration;
  472. }
  473. //------------------------------------------------------------------------------
  474. // Purpose : Clears any previous inspect target and set inspect target to
  475. // the given entity and set the durection of the inspection
  476. // Input :
  477. // Output :
  478. //------------------------------------------------------------------------------
  479. void CNPC_Spotlight::SetInspectTargetToEnemy(CBaseEntity *pEntity)
  480. {
  481. ClearInspectTarget();
  482. SetEnemy( pEntity );
  483. m_bHadEnemy = true;
  484. m_flInspectEndTime = 0;
  485. SetState(NPC_STATE_COMBAT);
  486. EHANDLE hEnemy;
  487. hEnemy.Set( GetEnemy() );
  488. m_pOutputDetect.Set(hEnemy, NULL, this);
  489. }
  490. //------------------------------------------------------------------------------
  491. // Purpose : Clears any previous inspect target and set inspect target to
  492. // the given hint node and set the durection of the inspection
  493. // Input :
  494. // Output :
  495. //------------------------------------------------------------------------------
  496. void CNPC_Spotlight::SetInspectTargetToHint(CAI_Hint *pHintNode, float fInspectDuration)
  497. {
  498. ClearInspectTarget();
  499. // --------------------------------------------
  500. // Figure out the location that the hint hits
  501. // --------------------------------------------
  502. float fHintYaw = DEG2RAD(pHintNode->Yaw());
  503. Vector vHintDir = Vector(cos(fHintYaw),sin(fHintYaw),0);
  504. Vector vHintOrigin;
  505. pHintNode->GetPosition(this,&vHintOrigin);
  506. Vector vHintEnd = vHintOrigin + (vHintDir * 500);
  507. trace_t tr;
  508. AI_TraceLine ( vHintOrigin, vHintEnd, MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr);
  509. if (tr.fraction == 1.0)
  510. {
  511. DevMsg("ERROR: Scanner hint node not facing a surface!\n");
  512. }
  513. else
  514. {
  515. SetHintNode( pHintNode );
  516. m_vInspectPos = tr.endpos;
  517. pHintNode->Lock(this);
  518. m_flInspectEndTime = gpGlobals->curtime + fInspectDuration;
  519. }
  520. }
  521. //------------------------------------------------------------------------------
  522. // Purpose : Clears any previous inspect target and set inspect target to
  523. // the given position and set the durection of the inspection
  524. // Input :
  525. // Output :
  526. //------------------------------------------------------------------------------
  527. void CNPC_Spotlight::SetInspectTargetToPos(const Vector &vInspectPos, float fInspectDuration)
  528. {
  529. ClearInspectTarget();
  530. m_vInspectPos = vInspectPos;
  531. m_flInspectEndTime = gpGlobals->curtime + fInspectDuration;
  532. }
  533. //------------------------------------------------------------------------------
  534. // Purpose : Clears out any previous inspection targets
  535. // Input :
  536. // Output :
  537. //------------------------------------------------------------------------------
  538. void CNPC_Spotlight::ClearInspectTarget(void)
  539. {
  540. // If I'm losing an enemy, fire a message
  541. if (m_bHadEnemy)
  542. {
  543. m_bHadEnemy = false;
  544. EHANDLE hEnemy;
  545. hEnemy.Set( GetEnemy() );
  546. m_pOutputLost.Set(hEnemy,this,this);
  547. }
  548. // If I'm in combat state, go to alert
  549. if (m_NPCState == NPC_STATE_COMBAT)
  550. {
  551. SetState(NPC_STATE_ALERT);
  552. }
  553. SetTarget( NULL );
  554. SetEnemy( NULL );
  555. ClearHintNode( SPOTLIGHT_HINT_INSPECT_LENGTH );
  556. m_vInspectPos = vec3_origin;
  557. m_flYawDir = random->RandomInt(0,1) ? 1 : -1;
  558. m_flPitchDir = random->RandomInt(0,1) ? 1 : -1;
  559. }
  560. //------------------------------------------------------------------------------
  561. // Purpose : Returns true if there is a position to be inspected and sets
  562. // vTargetPos to the inspection position
  563. // Input :
  564. // Output :
  565. //------------------------------------------------------------------------------
  566. bool CNPC_Spotlight::HaveInspectTarget(void)
  567. {
  568. if (GetEnemy() != NULL)
  569. {
  570. return true;
  571. }
  572. else if (GetTarget() != NULL)
  573. {
  574. return true;
  575. }
  576. if (m_vInspectPos != vec3_origin)
  577. {
  578. return true;
  579. }
  580. return false;
  581. }
  582. //------------------------------------------------------------------------------
  583. // Purpose : Returns true if there is a position to be inspected and sets
  584. // vTargetPos to the inspection position
  585. // Input :
  586. // Output :
  587. //------------------------------------------------------------------------------
  588. Vector CNPC_Spotlight::InspectTargetPosition(void)
  589. {
  590. if (GetEnemy() != NULL)
  591. {
  592. // If in spotlight mode, aim for ground below target unless is client
  593. if (!(GetEnemy()->GetFlags() & FL_CLIENT))
  594. {
  595. Vector vInspectPos;
  596. vInspectPos.x = GetEnemy()->GetAbsOrigin().x;
  597. vInspectPos.y = GetEnemy()->GetAbsOrigin().y;
  598. vInspectPos.z = GetFloorZ(GetEnemy()->GetAbsOrigin()+Vector(0,0,1));
  599. return vInspectPos;
  600. }
  601. // Otherwise aim for eyes
  602. else
  603. {
  604. return GetEnemy()->EyePosition();
  605. }
  606. }
  607. else if (GetTarget() != NULL)
  608. {
  609. // If in spotlight mode, aim for ground below target unless is client
  610. if (!(GetTarget()->GetFlags() & FL_CLIENT))
  611. {
  612. Vector vInspectPos;
  613. vInspectPos.x = GetTarget()->GetAbsOrigin().x;
  614. vInspectPos.y = GetTarget()->GetAbsOrigin().y;
  615. vInspectPos.z = GetFloorZ(GetTarget()->GetAbsOrigin());
  616. return vInspectPos;
  617. }
  618. // Otherwise aim for eyes
  619. else
  620. {
  621. return GetTarget()->EyePosition();
  622. }
  623. }
  624. else if (m_vInspectPos != vec3_origin)
  625. {
  626. return m_vInspectPos;
  627. }
  628. else
  629. {
  630. DevMsg("InspectTargetPosition called with no target!\n");
  631. return m_vInspectPos;
  632. }
  633. }
  634. //------------------------------------------------------------------------------
  635. // Purpose :
  636. // Input :
  637. // Output :
  638. //------------------------------------------------------------------------------
  639. void CNPC_Spotlight::UpdateTargets(void)
  640. {
  641. if (m_fSpotlightFlags & BITS_SPOTLIGHT_TRACK_ON)
  642. {
  643. // --------------------------------------------------------------------------
  644. // Look for a nearby entity to inspect
  645. // --------------------------------------------------------------------------
  646. CBaseEntity *pBestEntity = BestInspectTarget();
  647. // If I found one
  648. if (pBestEntity)
  649. {
  650. // If it's an enemy
  651. if (IRelationType(pBestEntity) == D_HT)
  652. {
  653. // If I'm not already inspecting an enemy take it
  654. if (GetEnemy() == NULL)
  655. {
  656. SetInspectTargetToEnemy(pBestEntity);
  657. if (m_pSquad)
  658. {
  659. AISquadIter_t iter;
  660. for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
  661. {
  662. // reset members who aren't activly engaged in fighting
  663. if (pSquadMember->GetEnemy() != pBestEntity && !pSquadMember->HasCondition( COND_SEE_ENEMY))
  664. {
  665. // give them a new enemy
  666. pSquadMember->SetLastAttackTime( 0 );
  667. pSquadMember->SetCondition ( COND_NEW_ENEMY );
  668. }
  669. }
  670. }
  671. }
  672. // If I am inspecting an enemy, take it if priority is higher
  673. else
  674. {
  675. if (IRelationPriority(pBestEntity) > IRelationPriority(GetEnemy()))
  676. {
  677. SetInspectTargetToEnemy(pBestEntity);
  678. }
  679. }
  680. }
  681. // If its not an enemy
  682. else
  683. {
  684. // If I'm not already inspeting something take it
  685. if (GetTarget() == NULL)
  686. {
  687. SetInspectTargetToEntity(pBestEntity,SPOTLIGHT_ENTITY_INSPECT_LENGTH);
  688. }
  689. // If I am inspecting somethin, take if priority is higher
  690. else
  691. {
  692. if (IRelationPriority(pBestEntity) > IRelationPriority(GetTarget()))
  693. {
  694. SetInspectTargetToEntity(pBestEntity,SPOTLIGHT_ENTITY_INSPECT_LENGTH);
  695. }
  696. }
  697. }
  698. }
  699. // ---------------------------------------
  700. // If I'm not current inspecting an enemy
  701. // ---------------------------------------
  702. if (GetEnemy() == NULL)
  703. {
  704. // -----------------------------------------------------------
  705. // If my inspection over clear my inspect target.
  706. // -----------------------------------------------------------
  707. if (HaveInspectTarget() &&
  708. gpGlobals->curtime > m_flInspectEndTime )
  709. {
  710. m_flNextEntitySearchTime = gpGlobals->curtime + SPOTLIGHT_ENTITY_INSPECT_DELAY;
  711. m_flNextHintSearchTime = gpGlobals->curtime + SPOTLIGHT_HINT_INSPECT_DELAY;
  712. ClearInspectTarget();
  713. }
  714. // --------------------------------------------------------------
  715. // If I heard a sound inspect it
  716. // --------------------------------------------------------------
  717. if (HasCondition(COND_HEAR_COMBAT) || HasCondition(COND_HEAR_DANGER) )
  718. {
  719. CSound *pSound = GetBestSound();
  720. if (pSound)
  721. {
  722. Vector vSoundPos = pSound->GetSoundOrigin();
  723. // Only alert to sound if in my swing range
  724. if (SpotlightIsPositionLegal(vSoundPos))
  725. {
  726. SetInspectTargetToPos(vSoundPos,SPOTLIGHT_SOUND_INSPECT_LENGTH);
  727. // Fire alert output
  728. m_pOutputAlert.FireOutput(NULL,this);
  729. SetState(NPC_STATE_ALERT);
  730. }
  731. }
  732. }
  733. // --------------------------------------
  734. // Check for hints to inspect
  735. // --------------------------------------
  736. if (gpGlobals->curtime > m_flNextHintSearchTime &&
  737. !HaveInspectTarget() )
  738. {
  739. SetHintNode(CAI_HintManager::FindHint(this, HINT_NONE, 0, SPOTLIGHT_HINT_SEARCH_DIST));
  740. if (GetHintNode())
  741. {
  742. m_flNextHintSearchTime = gpGlobals->curtime + SPOTLIGHT_HINT_INSPECT_LENGTH;
  743. SetInspectTargetToHint(GetHintNode(),SPOTLIGHT_HINT_INSPECT_LENGTH);
  744. }
  745. }
  746. }
  747. // -------------------------------------------------------
  748. // Make sure inspect target is still in a legal position
  749. // (Don't care about enemies)
  750. // -------------------------------------------------------
  751. if (GetTarget())
  752. {
  753. if (!SpotlightIsPositionLegal(GetEnemies()->LastKnownPosition(GetTarget())))
  754. {
  755. ClearInspectTarget();
  756. }
  757. else if (!FVisible(GetTarget()))
  758. {
  759. ClearInspectTarget();
  760. }
  761. }
  762. if (GetEnemy())
  763. {
  764. if (!FVisible(GetEnemy()))
  765. {
  766. ClearInspectTarget();
  767. }
  768. // If enemy is dead inspect for a couple of seconds on move on
  769. else if (!GetEnemy()->IsAlive())
  770. {
  771. SetInspectTargetToPos( GetEnemy()->GetAbsOrigin(), 1.0);
  772. }
  773. else
  774. {
  775. UpdateEnemyMemory(GetEnemy(),GetEnemy()->GetAbsOrigin());
  776. }
  777. }
  778. // -----------------------------------------
  779. // See if I'm at my burn target
  780. // ------------------------------------------
  781. if (!HaveInspectTarget() &&
  782. m_pScriptedTarget &&
  783. m_pSpotlightTarget != NULL )
  784. {
  785. float fTargetDist = (m_vSpotlightTargetPos - m_vSpotlightCurrentPos).Length();
  786. if (fTargetDist < SPOTLIGHT_BURN_TARGET_THRESH )
  787. {
  788. // Update scripted target
  789. SetScriptedTarget( m_pScriptedTarget->NextScriptedTarget());
  790. }
  791. else
  792. {
  793. Vector vTargetDir = m_vSpotlightTargetPos - m_vSpotlightCurrentPos;
  794. VectorNormalize(vTargetDir);
  795. float flDot = DotProduct(m_vSpotlightDir,vTargetDir);
  796. if (flDot > 0.99 )
  797. {
  798. // Update scripted target
  799. SetScriptedTarget( m_pScriptedTarget->NextScriptedTarget());
  800. }
  801. }
  802. }
  803. }
  804. }
  805. //-----------------------------------------------------------------------------
  806. // Purpose: Overridden because if the player is a criminal, we hate them.
  807. // Input : pTarget - Entity with which to determine relationship.
  808. // Output : Returns relationship value.
  809. //-----------------------------------------------------------------------------
  810. Disposition_t CNPC_Spotlight::IRelationType(CBaseEntity *pTarget)
  811. {
  812. //
  813. // If it's the player and they are a criminal, we hate them.
  814. //
  815. if (pTarget->Classify() == CLASS_PLAYER)
  816. {
  817. if (GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON)
  818. {
  819. return(D_NU);
  820. }
  821. }
  822. return(CBaseCombatCharacter::IRelationType(pTarget));
  823. }
  824. //------------------------------------------------------------------------------
  825. // Purpose :
  826. // Input :
  827. // Output :
  828. //------------------------------------------------------------------------------
  829. void CNPC_Spotlight::SpotlightDestroy(void)
  830. {
  831. if (m_pSpotlight)
  832. {
  833. UTIL_Remove(m_pSpotlight);
  834. m_pSpotlight = NULL;
  835. UTIL_Remove(m_pSpotlightTarget);
  836. m_pSpotlightTarget = NULL;
  837. }
  838. }
  839. //------------------------------------------------------------------------------
  840. // Purpose :
  841. // Input :
  842. // Output :
  843. //------------------------------------------------------------------------------
  844. void CNPC_Spotlight::SpotlightCreate(void)
  845. {
  846. // If I have an enemy, start spotlight on my enemy
  847. if (GetEnemy() != NULL)
  848. {
  849. Vector vEnemyPos = GetEnemyLKP();
  850. Vector vTargetPos = vEnemyPos;
  851. vTargetPos.z = GetFloorZ(vEnemyPos);
  852. m_vSpotlightDir = vTargetPos - GetAbsOrigin();
  853. VectorNormalize(m_vSpotlightDir);
  854. }
  855. // If I have an target, start spotlight on my target
  856. else if (GetTarget() != NULL)
  857. {
  858. Vector vTargetPos = GetTarget()->GetAbsOrigin();
  859. vTargetPos.z = GetFloorZ(GetTarget()->GetAbsOrigin());
  860. m_vSpotlightDir = vTargetPos - GetAbsOrigin();
  861. VectorNormalize(m_vSpotlightDir);
  862. }
  863. else
  864. {
  865. AngleVectors( GetAbsAngles(), &m_vSpotlightDir );
  866. }
  867. trace_t tr;
  868. AI_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + m_vSpotlightDir * m_flSpotlightMaxLength,
  869. MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr);
  870. m_pSpotlightTarget = (CSpotlightEnd*)CreateEntityByName( "spotlight_end" );
  871. m_pSpotlightTarget->Spawn();
  872. m_pSpotlightTarget->SetLocalOrigin( tr.endpos );
  873. m_pSpotlightTarget->SetOwnerEntity( this );
  874. m_pSpotlightTarget->m_clrRender = m_clrRender;
  875. m_pSpotlightTarget->m_Radius = m_flSpotlightMaxLength;
  876. if ( FBitSet (m_spawnflags, SF_SPOTLIGHT_NO_DYNAMIC_LIGHT) )
  877. {
  878. m_pSpotlightTarget->m_flLightScale = 0.0;
  879. }
  880. m_pSpotlight = CBeam::BeamCreate( "sprites/spotlight.vmt", 2.0 );
  881. m_pSpotlight->SetColor( m_clrRender->r, m_clrRender->g, m_clrRender->b );
  882. m_pSpotlight->SetHaloTexture(m_nHaloSprite);
  883. m_pSpotlight->SetHaloScale(40);
  884. m_pSpotlight->SetEndWidth(m_flSpotlightGoalWidth);
  885. m_pSpotlight->SetBeamFlags(FBEAM_SHADEOUT);
  886. m_pSpotlight->SetBrightness( 80 );
  887. m_pSpotlight->SetNoise( 0 );
  888. m_pSpotlight->EntsInit( this, m_pSpotlightTarget );
  889. }
  890. //------------------------------------------------------------------------------
  891. // Purpose : Returns true is spotlight can reach position
  892. // Input :
  893. // Output :
  894. //------------------------------------------------------------------------------
  895. bool CNPC_Spotlight::SpotlightIsPositionLegal(const Vector &vTestPos)
  896. {
  897. Vector vTargetDir = vTestPos - GetAbsOrigin();
  898. VectorNormalize(vTargetDir);
  899. QAngle vTargetAngles;
  900. VectorAngles(vTargetDir,vTargetAngles);
  901. // Make sure target is in a legal position
  902. if (UTIL_AngleDistance( vTargetAngles[YAW], m_flYawCenter ) > m_flYawRange)
  903. {
  904. return false;
  905. }
  906. else if (UTIL_AngleDistance( vTargetAngles[YAW], m_flYawCenter ) < -m_flYawRange)
  907. {
  908. return false;
  909. }
  910. if (UTIL_AngleDistance( vTargetAngles[PITCH], m_flPitchCenter ) > m_flPitchMax)
  911. {
  912. return false;
  913. }
  914. else if (UTIL_AngleDistance( vTargetAngles[PITCH], m_flPitchCenter ) < m_flPitchMin)
  915. {
  916. return false;
  917. }
  918. return true;
  919. }
  920. //------------------------------------------------------------------------------
  921. // Purpose : Converts spotlight target position into desired yaw and pitch
  922. // directions to reach target
  923. // Input :
  924. // Output :
  925. //------------------------------------------------------------------------------
  926. void CNPC_Spotlight::SpotlightSetTargetYawAndPitch(void)
  927. {
  928. Vector vTargetDir = m_vSpotlightTargetPos - GetAbsOrigin();
  929. VectorNormalize(vTargetDir);
  930. QAngle vTargetAngles;
  931. VectorAngles(vTargetDir,vTargetAngles);
  932. float flYawDiff = UTIL_AngleDistance(vTargetAngles[YAW], m_flYaw);
  933. if ( flYawDiff > 0)
  934. {
  935. m_flYawDir = SPOTLIGHT_SWING_FORWARD;
  936. }
  937. else
  938. {
  939. m_flYawDir = SPOTLIGHT_SWING_BACK;
  940. }
  941. //DevMsg("%f %f (%f)\n",vTargetAngles[YAW], m_flYaw,flYawDiff);
  942. float flPitchDiff = UTIL_AngleDistance(vTargetAngles[PITCH], m_flPitch);
  943. if (flPitchDiff > 0)
  944. {
  945. m_flPitchDir = SPOTLIGHT_SWING_FORWARD;
  946. }
  947. else
  948. {
  949. m_flPitchDir = SPOTLIGHT_SWING_BACK;
  950. }
  951. //DevMsg("%f %f (%f)\n",vTargetAngles[PITCH], m_flPitch,flPitchDiff);
  952. if ( fabs(flYawDiff) < 2)
  953. {
  954. m_flYawDir *= 0.5;
  955. }
  956. if ( fabs(flPitchDiff) < 2)
  957. {
  958. m_flPitchDir *= 0.5;
  959. }
  960. }
  961. //------------------------------------------------------------------------------
  962. // Purpose :
  963. // Input :
  964. // Output :
  965. //------------------------------------------------------------------------------
  966. float CNPC_Spotlight::SpotlightSpeed(void)
  967. {
  968. float fSpeedScale = 1.0;
  969. float fInspectDist = (m_vSpotlightTargetPos - m_vSpotlightCurrentPos).Length();
  970. if (fInspectDist < 100)
  971. {
  972. fSpeedScale = 0.25;
  973. }
  974. if (!HaveInspectTarget() && m_pScriptedTarget)
  975. {
  976. return (fSpeedScale * m_pScriptedTarget->MoveSpeed());
  977. }
  978. else if (m_NPCState == NPC_STATE_COMBAT ||
  979. m_NPCState == NPC_STATE_ALERT )
  980. {
  981. return (fSpeedScale * m_flAlertSpeed);
  982. }
  983. else
  984. {
  985. return (fSpeedScale * m_flIdleSpeed);
  986. }
  987. }
  988. //------------------------------------------------------------------------------
  989. // Purpose :
  990. // Input :
  991. // Output :
  992. //------------------------------------------------------------------------------
  993. Vector CNPC_Spotlight::SpotlightCurrentPos(void)
  994. {
  995. if (!m_pSpotlight)
  996. {
  997. DevMsg("Spotlight pos. called w/o spotlight!\n");
  998. return vec3_origin;
  999. }
  1000. if (HaveInspectTarget())
  1001. {
  1002. m_vSpotlightTargetPos = InspectTargetPosition();
  1003. SpotlightSetTargetYawAndPitch();
  1004. }
  1005. else if (m_pScriptedTarget)
  1006. {
  1007. m_vSpotlightTargetPos = m_pScriptedTarget->GetAbsOrigin();
  1008. SpotlightSetTargetYawAndPitch();
  1009. // I'm allowed to move outside my normal range when
  1010. // tracking burn targets. Return smoothly when I'm done
  1011. m_fSpotlightFlags |= BITS_SPOTLIGHT_SMOOTH_RETURN;
  1012. }
  1013. else
  1014. {
  1015. // Make random movement frame independent
  1016. if (random->RandomInt(0,10) == 0)
  1017. {
  1018. m_flYawDir *= -1;
  1019. }
  1020. if (random->RandomInt(0,10) == 0)
  1021. {
  1022. m_flPitchDir *= -1;
  1023. }
  1024. }
  1025. // Calculate new pitch and yaw velocity
  1026. float flSpeed = SpotlightSpeed();
  1027. float flNewYawSpeed = m_flYawDir * flSpeed;
  1028. float flNewPitchSpeed = m_flPitchDir * flSpeed;
  1029. // Adjust current velocity
  1030. float myYawDecay = 0.8;
  1031. float myPitchDecay = 0.7;
  1032. m_flYawSpeed = (myYawDecay * m_flYawSpeed + (1-myYawDecay) * flNewYawSpeed );
  1033. m_flPitchSpeed = (myPitchDecay * m_flPitchSpeed + (1-myPitchDecay) * flNewPitchSpeed);
  1034. // Keep speed with in bounds
  1035. float flMaxSpeed = SPOTLIGHT_MAX_SPEED_SCALE * SpotlightSpeed();
  1036. if (m_flYawSpeed > flMaxSpeed) m_flYawSpeed = flMaxSpeed;
  1037. else if (m_flYawSpeed < -flMaxSpeed) m_flYawSpeed = -flMaxSpeed;
  1038. if (m_flPitchSpeed > flMaxSpeed) m_flPitchSpeed = flMaxSpeed;
  1039. else if (m_flPitchSpeed < -flMaxSpeed) m_flPitchSpeed = -flMaxSpeed;
  1040. // Calculate new pitch and yaw positions
  1041. m_flYaw += m_flYawSpeed;
  1042. m_flPitch += m_flPitchSpeed;
  1043. // Keep yaw in 0/360 range
  1044. if (m_flYaw < 0 ) m_flYaw +=360;
  1045. if (m_flYaw > 360) m_flYaw -=360;
  1046. // ---------------------------------------------
  1047. // Check yaw and pitch boundaries unless I have
  1048. // a burn target, or an enemy
  1049. // ---------------------------------------------
  1050. if (( HaveInspectTarget() && GetEnemy() == NULL ) ||
  1051. (!HaveInspectTarget() && !m_pScriptedTarget ) )
  1052. {
  1053. bool bInRange = true;
  1054. if (UTIL_AngleDistance( m_flYaw, m_flYawCenter ) > m_flYawRange)
  1055. {
  1056. if (m_fSpotlightFlags & BITS_SPOTLIGHT_SMOOTH_RETURN)
  1057. {
  1058. bInRange = false;
  1059. }
  1060. else
  1061. {
  1062. m_flYaw = m_flYawCenter + m_flYawRange;
  1063. }
  1064. m_flYawDir = SPOTLIGHT_SWING_BACK;
  1065. }
  1066. else if (UTIL_AngleDistance( m_flYaw, m_flYawCenter ) < -m_flYawRange)
  1067. {
  1068. if (m_fSpotlightFlags & BITS_SPOTLIGHT_SMOOTH_RETURN)
  1069. {
  1070. bInRange = false;
  1071. }
  1072. else
  1073. {
  1074. m_flYaw = m_flYawCenter - m_flYawRange;
  1075. }
  1076. m_flYawDir = SPOTLIGHT_SWING_FORWARD;
  1077. }
  1078. if (UTIL_AngleDistance( m_flPitch, m_flPitchCenter ) > m_flPitchMax)
  1079. {
  1080. if (m_fSpotlightFlags & BITS_SPOTLIGHT_SMOOTH_RETURN)
  1081. {
  1082. bInRange = false;
  1083. }
  1084. else
  1085. {
  1086. m_flPitch = m_flPitchCenter + m_flPitchMax;
  1087. }
  1088. m_flPitchDir = SPOTLIGHT_SWING_BACK;
  1089. }
  1090. else if (UTIL_AngleDistance( m_flPitch, m_flPitchCenter ) < m_flPitchMin)
  1091. {
  1092. if (m_fSpotlightFlags & BITS_SPOTLIGHT_SMOOTH_RETURN)
  1093. {
  1094. bInRange = false;
  1095. }
  1096. else
  1097. {
  1098. m_flPitch = m_flPitchCenter + m_flPitchMin;
  1099. }
  1100. m_flPitchDir = SPOTLIGHT_SWING_FORWARD;
  1101. }
  1102. // If in range I'm done doing a smooth return
  1103. if (bInRange)
  1104. {
  1105. m_fSpotlightFlags &= ~BITS_SPOTLIGHT_SMOOTH_RETURN;
  1106. }
  1107. }
  1108. // Convert pitch and yaw to forward angle
  1109. QAngle vAngle = vec3_angle;
  1110. vAngle[YAW] = m_flYaw;
  1111. vAngle[PITCH] = m_flPitch;
  1112. AngleVectors( vAngle, &m_vSpotlightDir );
  1113. // ---------------------------------------------
  1114. // Get beam end point. Only collide with
  1115. // solid objects, not npcs
  1116. // ---------------------------------------------
  1117. trace_t tr;
  1118. AI_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + (m_vSpotlightDir * 2 * m_flSpotlightMaxLength),
  1119. (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_MONSTER|CONTENTS_DEBRIS),
  1120. this, COLLISION_GROUP_NONE, &tr);
  1121. return (tr.endpos);
  1122. }
  1123. //------------------------------------------------------------------------------
  1124. // Purpose : Update the direction and position of my spotlight
  1125. // Input :
  1126. // Output :
  1127. //------------------------------------------------------------------------------
  1128. void CNPC_Spotlight::SpotlightUpdate(void)
  1129. {
  1130. // ---------------------------------------------------
  1131. // Go back to idle state after a while
  1132. // ---------------------------------------------------
  1133. if (m_NPCState == NPC_STATE_ALERT &&
  1134. m_flLastStateChangeTime + 30 < gpGlobals->curtime )
  1135. {
  1136. SetState(NPC_STATE_IDLE);
  1137. }
  1138. // ---------------------------------------------------
  1139. // If I don't have a spotlight attempt to create one
  1140. // ---------------------------------------------------
  1141. if (!m_pSpotlight &&
  1142. m_fSpotlightFlags & BITS_SPOTLIGHT_LIGHT_ON )
  1143. {
  1144. SpotlightCreate();
  1145. }
  1146. if (!m_pSpotlight)
  1147. {
  1148. return;
  1149. }
  1150. // -----------------------------------------------------
  1151. // If spotlight flag is off destroy spotlight and exit
  1152. // -----------------------------------------------------
  1153. if (!(m_fSpotlightFlags & BITS_SPOTLIGHT_LIGHT_ON))
  1154. {
  1155. if (m_pSpotlight)
  1156. {
  1157. SpotlightDestroy();
  1158. return;
  1159. }
  1160. }
  1161. if (m_fSpotlightFlags & BITS_SPOTLIGHT_TRACK_ON)
  1162. {
  1163. // -------------------------------------------
  1164. // Calculate the new spotlight position
  1165. // --------------------------------------------
  1166. m_vSpotlightCurrentPos = SpotlightCurrentPos();
  1167. }
  1168. // --------------------------------------------------------------
  1169. // Update spotlight target velocity
  1170. // --------------------------------------------------------------
  1171. Vector vTargetDir = (m_vSpotlightCurrentPos - m_pSpotlightTarget->GetAbsOrigin());
  1172. float vTargetDist = vTargetDir.Length();
  1173. Vector vecNewVelocity = vTargetDir;
  1174. VectorNormalize(vecNewVelocity);
  1175. vecNewVelocity *= (10 * vTargetDist);
  1176. // If a large move is requested, just jump to final spot as we
  1177. // probably hit a discontinuity
  1178. if (vecNewVelocity.Length() > 200)
  1179. {
  1180. VectorNormalize(vecNewVelocity);
  1181. vecNewVelocity *= 200;
  1182. VectorNormalize(vTargetDir);
  1183. m_pSpotlightTarget->SetLocalOrigin( m_vSpotlightCurrentPos );
  1184. }
  1185. m_pSpotlightTarget->SetAbsVelocity( vecNewVelocity );
  1186. m_pSpotlightTarget->m_vSpotlightOrg = GetAbsOrigin();
  1187. // Avoid sudden change in where beam fades out when cross disconinuities
  1188. m_pSpotlightTarget->m_vSpotlightDir = m_pSpotlightTarget->GetLocalOrigin() - m_pSpotlightTarget->m_vSpotlightOrg;
  1189. float flBeamLength = VectorNormalize( m_pSpotlightTarget->m_vSpotlightDir );
  1190. m_flSpotlightCurLength = (0.60*m_flSpotlightCurLength) + (0.4*flBeamLength);
  1191. // Fade out spotlight end if past max length.
  1192. if (m_flSpotlightCurLength > 2*m_flSpotlightMaxLength)
  1193. {
  1194. m_pSpotlightTarget->SetRenderColorA( 0 );
  1195. m_pSpotlight->SetFadeLength(m_flSpotlightMaxLength);
  1196. }
  1197. else if (m_flSpotlightCurLength > m_flSpotlightMaxLength)
  1198. {
  1199. m_pSpotlightTarget->SetRenderColorA( (1-((m_flSpotlightCurLength-m_flSpotlightMaxLength)/m_flSpotlightMaxLength)) );
  1200. m_pSpotlight->SetFadeLength(m_flSpotlightMaxLength);
  1201. }
  1202. else
  1203. {
  1204. m_pSpotlightTarget->SetRenderColorA( 1.0 );
  1205. m_pSpotlight->SetFadeLength(m_flSpotlightCurLength);
  1206. }
  1207. // Adjust end width to keep beam width constant
  1208. float flNewWidth = m_flSpotlightGoalWidth*(flBeamLength/m_flSpotlightMaxLength);
  1209. m_pSpotlight->SetEndWidth(flNewWidth);
  1210. // Adjust width of light on the end.
  1211. if ( FBitSet (m_spawnflags, SF_SPOTLIGHT_NO_DYNAMIC_LIGHT) )
  1212. {
  1213. m_pSpotlightTarget->m_flLightScale = 0.0;
  1214. }
  1215. else
  1216. {
  1217. // <<TODO>> - magic number 1.8 depends on sprite size
  1218. m_pSpotlightTarget->m_flLightScale = 1.8*flNewWidth;
  1219. }
  1220. m_pOutputPosition.Set(m_pSpotlightTarget->GetLocalOrigin(),this,this);
  1221. #ifdef SPOTLIGHT_DEBUG
  1222. NDebugOverlay::Cross3D(m_vSpotlightCurrentPos,Vector(-5,-5,-5),Vector(5,5,5),0,255,0,true,0.1);
  1223. NDebugOverlay::Cross3D(m_vSpotlightTargetPos,Vector(-5,-5,-5),Vector(5,5,5),255,0,0,true,0.1);
  1224. #endif
  1225. }
  1226. //-----------------------------------------------------------------------------
  1227. // Purpose:
  1228. //-----------------------------------------------------------------------------
  1229. void CNPC_Spotlight::Spawn(void)
  1230. {
  1231. // Check for user error
  1232. if (m_flSpotlightMaxLength <= 0)
  1233. {
  1234. DevMsg("CNPC_Spotlight::Spawn: Invalid spotlight length <= 0, setting to 500\n");
  1235. m_flSpotlightMaxLength = 500;
  1236. }
  1237. if (m_flSpotlightGoalWidth <= 0)
  1238. {
  1239. DevMsg("CNPC_Spotlight::Spawn: Invalid spotlight width <= 0, setting to 10\n");
  1240. m_flSpotlightGoalWidth = 10;
  1241. }
  1242. if (m_flSpotlightGoalWidth > MAX_BEAM_WIDTH)
  1243. {
  1244. DevMsg("CNPC_Spotlight::Spawn: Invalid spotlight width %.1f (max %.1f)\n", m_flSpotlightGoalWidth, MAX_BEAM_WIDTH );
  1245. m_flSpotlightGoalWidth = MAX_BEAM_WIDTH;
  1246. }
  1247. Precache();
  1248. // This is a dummy model that is never used!
  1249. SetModel( "models/player.mdl" );
  1250. // No Hull for now
  1251. UTIL_SetSize(this,vec3_origin,vec3_origin);
  1252. SetSolid( SOLID_BBOX );
  1253. AddSolidFlags( FSOLID_NOT_STANDABLE );
  1254. m_bloodColor = DONT_BLEED;
  1255. SetViewOffset( Vector(0, 0, 10) ); // Position of the eyes relative to NPC's origin.
  1256. m_flFieldOfView = VIEW_FIELD_FULL;
  1257. m_NPCState = NPC_STATE_IDLE;
  1258. CapabilitiesAdd( bits_CAP_SQUAD);
  1259. // ------------------------------------
  1260. // Init all class vars
  1261. // ------------------------------------
  1262. m_vInspectPos = vec3_origin;
  1263. m_flInspectEndTime = 0;
  1264. m_flNextEntitySearchTime= gpGlobals->curtime + SPOTLIGHT_ENTITY_INSPECT_DELAY;
  1265. m_flNextHintSearchTime = gpGlobals->curtime + SPOTLIGHT_HINT_INSPECT_DELAY;
  1266. m_bHadEnemy = false;
  1267. m_vSpotlightTargetPos = vec3_origin;
  1268. m_vSpotlightCurrentPos = vec3_origin;
  1269. m_pSpotlight = NULL;
  1270. m_pSpotlightTarget = NULL;
  1271. m_vSpotlightDir = vec3_origin;
  1272. //m_nHaloSprite // Set in precache
  1273. m_flSpotlightCurLength = m_flSpotlightMaxLength;
  1274. m_flYaw = 0;
  1275. m_flYawSpeed = 0;
  1276. m_flYawCenter = GetLocalAngles().y;
  1277. m_flYawDir = random->RandomInt(0,1) ? 1 : -1;
  1278. //m_flYawRange = 90; // Keyfield in WC
  1279. m_flPitch = 0;
  1280. m_flPitchSpeed = 0;
  1281. m_flPitchCenter = GetLocalAngles().x;
  1282. m_flPitchDir = random->RandomInt(0,1) ? 1 : -1;
  1283. //m_flPitchMin = 35; // Keyfield in WC
  1284. //m_flPitchMax = 50; // Keyfield in WC
  1285. //m_flIdleSpeed = 2; // Keyfield in WC
  1286. //m_flAlertSpeed = 5; // Keyfield in WC
  1287. m_fSpotlightFlags = 0;
  1288. if (FBitSet ( m_spawnflags, SF_SPOTLIGHT_START_TRACK_ON ))
  1289. {
  1290. m_fSpotlightFlags |= BITS_SPOTLIGHT_TRACK_ON;
  1291. }
  1292. if (FBitSet ( m_spawnflags, SF_SPOTLIGHT_START_LIGHT_ON ))
  1293. {
  1294. m_fSpotlightFlags |= BITS_SPOTLIGHT_LIGHT_ON;
  1295. }
  1296. // If I'm never moving just turn on the spotlight and don't think again
  1297. if (FBitSet ( m_spawnflags, SF_SPOTLIGHT_NEVER_MOVE ))
  1298. {
  1299. SpotlightCreate();
  1300. }
  1301. else
  1302. {
  1303. NPCInit();
  1304. SetThink(CallNPCThink);
  1305. }
  1306. AddEffects( EF_NODRAW );
  1307. SetMoveType( MOVETYPE_NONE );
  1308. SetGravity( 0.0 );
  1309. }
  1310. //------------------------------------------------------------------------------
  1311. // Purpose: Inputs
  1312. //------------------------------------------------------------------------------
  1313. void CNPC_Spotlight::InputLightOn( inputdata_t &inputdata )
  1314. {
  1315. m_fSpotlightFlags |= BITS_SPOTLIGHT_LIGHT_ON;
  1316. }
  1317. void CNPC_Spotlight::InputLightOff( inputdata_t &inputdata )
  1318. {
  1319. m_fSpotlightFlags &= ~BITS_SPOTLIGHT_LIGHT_ON;
  1320. }
  1321. void CNPC_Spotlight::InputTrackOn( inputdata_t &inputdata )
  1322. {
  1323. m_fSpotlightFlags |= BITS_SPOTLIGHT_TRACK_ON;
  1324. }
  1325. void CNPC_Spotlight::InputTrackOff( inputdata_t &inputdata )
  1326. {
  1327. m_fSpotlightFlags &= ~BITS_SPOTLIGHT_TRACK_ON;
  1328. }
  1329. //------------------------------------------------------------------------------
  1330. // Purpose : Starts cremator doing scripted burn to a location
  1331. //------------------------------------------------------------------------------
  1332. void CNPC_Spotlight::SetScriptedTarget( CScriptedTarget *pScriptedTarget )
  1333. {
  1334. if (pScriptedTarget)
  1335. {
  1336. m_pScriptedTarget = pScriptedTarget;
  1337. m_vSpotlightTargetPos = m_pScriptedTarget->GetAbsOrigin();
  1338. }
  1339. else
  1340. {
  1341. m_pScriptedTarget = NULL;
  1342. }
  1343. }
  1344. //-----------------------------------------------------------------------------
  1345. // Purpose: This is a generic function (to be implemented by sub-classes) to
  1346. // handle specific interactions between different types of characters
  1347. // (For example the barnacle grabbing an NPC)
  1348. // Input : Constant for the type of interaction
  1349. // Output : true - if sub-class has a response for the interaction
  1350. // false - if sub-class has no response
  1351. //-----------------------------------------------------------------------------
  1352. bool CNPC_Spotlight::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
  1353. {
  1354. if (interactionType == g_interactionScriptedTarget)
  1355. {
  1356. // If I already have a scripted target, reject the new one
  1357. if (m_pScriptedTarget && sourceEnt)
  1358. {
  1359. return false;
  1360. }
  1361. else
  1362. {
  1363. SetScriptedTarget((CScriptedTarget*)sourceEnt);
  1364. return true;
  1365. }
  1366. }
  1367. return false;
  1368. }