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.

1188 lines
32 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Security cameras will track a default target (if they have one)
  4. // until they either acquire an enemy to track or are told to track
  5. // an entity via an input. If they lose their target they will
  6. // revert to tracking their default target. They acquire enemies
  7. // using the relationship table just like any other NPC.
  8. //
  9. // Cameras have two zones of awareness, an inner zone formed by the
  10. // intersection of an inner FOV and an inner radius. The camera is
  11. // fully aware of entities in the inner zone and will acquire enemies
  12. // seen there.
  13. //
  14. // The outer zone of awareness is formed by the intersection of an
  15. // outer FOV and an outer radius. The camera is only vaguely aware
  16. // of entities in the outer zone and will flash amber when enemies
  17. // are there, but will otherwise ignore them.
  18. //
  19. // They can be made angry via an input, at which time they sound an
  20. // alarm and snap a few pictures of whatever they are tracking. They
  21. // can also be set to become angry anytime they acquire an enemy.
  22. //
  23. //=============================================================================//
  24. #include "cbase.h"
  25. #include "ai_basenpc.h"
  26. #include "ai_senses.h"
  27. #include "ai_memory.h"
  28. #include "engine/IEngineSound.h"
  29. #include "ammodef.h"
  30. #include "Sprite.h"
  31. #include "hl2/hl2_player.h"
  32. #include "soundenvelope.h"
  33. #include "explode.h"
  34. #include "IEffects.h"
  35. #include "animation.h"
  36. // memdbgon must be the last include file in a .cpp file!!!
  37. #include "tier0/memdbgon.h"
  38. // Debug visualization
  39. ConVar g_debug_combine_camera("g_debug_combine_camera", "0");
  40. #define COMBINE_CAMERA_MODEL "models/combine_camera/combine_camera.mdl"
  41. #define COMBINE_CAMERA_GLOW_SPRITE "sprites/glow1.vmt"
  42. #define COMBINE_CAMERA_FLASH_SPRITE "sprites/light_glow03.vmt"
  43. #define COMBINE_CAMERA_BC_YAW "aim_yaw"
  44. #define COMBINE_CAMERA_BC_PITCH "aim_pitch"
  45. #define COMBINE_CAMERA_SPREAD VECTOR_CONE_2DEGREES
  46. #define COMBINE_CAMERA_MAX_WAIT 5
  47. #define COMBINE_CAMERA_PING_TIME 1.0f
  48. // Spawnflags
  49. #define SF_COMBINE_CAMERA_BECOMEANGRY 0x00000020
  50. #define SF_COMBINE_CAMERA_IGNOREENEMIES 0x00000040
  51. #define SF_COMBINE_CAMERA_STARTINACTIVE 0x00000080
  52. // Heights
  53. #define COMBINE_CAMERA_RETRACT_HEIGHT 24
  54. #define COMBINE_CAMERA_DEPLOY_HEIGHT 64
  55. // Activities
  56. int ACT_COMBINE_CAMERA_OPEN;
  57. int ACT_COMBINE_CAMERA_CLOSE;
  58. int ACT_COMBINE_CAMERA_OPEN_IDLE;
  59. int ACT_COMBINE_CAMERA_CLOSED_IDLE;
  60. int ACT_COMBINE_CAMERA_FIRE;
  61. const float CAMERA_CLICK_INTERVAL = 0.5f;
  62. const float CAMERA_MOVE_INTERVAL = 1.0f;
  63. //
  64. // The camera has two FOVs - a wide one for becoming slightly aware of someone,
  65. // a narrow one for becoming totally aware of them.
  66. //
  67. const float CAMERA_FOV_WIDE = 0.5;
  68. const float CAMERA_FOV_NARROW = 0.707;
  69. // Camera states
  70. enum cameraState_e
  71. {
  72. CAMERA_SEARCHING,
  73. CAMERA_AUTO_SEARCHING,
  74. CAMERA_ACTIVE,
  75. CAMERA_DEAD,
  76. };
  77. // Eye states
  78. enum eyeState_t
  79. {
  80. CAMERA_EYE_IDLE, // Nothing abnormal in the inner or outer viewcone, dim green.
  81. CAMERA_EYE_SEEKING_TARGET, // Something in the outer viewcone, flashes amber as it converges on the target.
  82. CAMERA_EYE_FOUND_TARGET, // Something in the inner viewcone, bright amber.
  83. CAMERA_EYE_ANGRY, // Found a target that we don't like: angry, bright red.
  84. CAMERA_EYE_DORMANT, // Not active
  85. CAMERA_EYE_DEAD, // Completely invisible
  86. CAMERA_EYE_DISABLED, // Turned off, must be reactivated before it'll deploy again (completely invisible)
  87. CAMERA_EYE_HAPPY, // Found a target that we like: go green for a second
  88. };
  89. //-----------------------------------------------------------------------------
  90. // Purpose:
  91. //-----------------------------------------------------------------------------
  92. class CNPC_CombineCamera : public CAI_BaseNPC
  93. {
  94. DECLARE_CLASS(CNPC_CombineCamera, CAI_BaseNPC);
  95. public:
  96. CNPC_CombineCamera();
  97. ~CNPC_CombineCamera();
  98. void Precache();
  99. void Spawn();
  100. Vector HeadDirection2D();
  101. int DrawDebugTextOverlays();
  102. void Deploy();
  103. void ActiveThink();
  104. void SearchThink();
  105. void DeathThink();
  106. void InputToggle(inputdata_t &inputdata);
  107. void InputEnable(inputdata_t &inputdata);
  108. void InputDisable(inputdata_t &inputdata);
  109. void InputSetAngry(inputdata_t &inputdata);
  110. void InputSetIdle(inputdata_t &inputdata);
  111. void DrawDebugGeometryOverlays(void);
  112. float MaxYawSpeed();
  113. int OnTakeDamage(const CTakeDamageInfo &inputInfo);
  114. Class_T Classify() { return (m_bEnabled) ? CLASS_MILITARY : CLASS_NONE; }
  115. bool IsValidEnemy( CBaseEntity *pEnemy );
  116. bool FVisible(CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL);
  117. Vector EyeOffset(Activity nActivity)
  118. {
  119. Vector vecEyeOffset(0,0,-64);
  120. GetEyePosition(GetModelPtr(), vecEyeOffset);
  121. return vecEyeOffset;
  122. }
  123. Vector EyePosition()
  124. {
  125. return GetAbsOrigin() + EyeOffset(GetActivity());
  126. }
  127. protected:
  128. CBaseEntity *GetTarget();
  129. bool UpdateFacing();
  130. void TrackTarget(CBaseEntity *pTarget);
  131. bool PreThink(cameraState_e state);
  132. void SetEyeState(eyeState_t state);
  133. void MaintainEye();
  134. void Ping();
  135. void Toggle();
  136. void Enable();
  137. void Disable();
  138. void SetHeight(float height);
  139. CBaseEntity *MaintainEnemy();
  140. void SetAngry(bool bAngry);
  141. protected:
  142. int m_iAmmoType;
  143. int m_iMinHealthDmg;
  144. int m_nInnerRadius; // The camera will only lock onto enemies that are within the inner radius.
  145. int m_nOuterRadius; // The camera will flash amber when enemies are within the outer radius, but outside the inner radius.
  146. bool m_bActive; // The camera is deployed and looking for targets
  147. bool m_bAngry; // The camera has gotten angry at someone and sounded an alarm.
  148. bool m_bBlinkState;
  149. bool m_bEnabled; // Denotes whether the camera is able to deploy or not
  150. string_t m_sDefaultTarget;
  151. EHANDLE m_hEnemyTarget; // Entity we acquired as an enemy.
  152. float m_flPingTime;
  153. float m_flClickTime; // Time to take next picture while angry.
  154. int m_nClickCount; // Counts pictures taken since we last became angry.
  155. float m_flMoveSoundTime;
  156. float m_flTurnOffEyeFlashTime;
  157. float m_flEyeHappyTime;
  158. QAngle m_vecGoalAngles;
  159. CSprite *m_pEyeGlow;
  160. CSprite *m_pEyeFlash;
  161. DECLARE_DATADESC();
  162. };
  163. BEGIN_DATADESC(CNPC_CombineCamera)
  164. DEFINE_FIELD(m_iAmmoType, FIELD_INTEGER),
  165. DEFINE_KEYFIELD(m_iMinHealthDmg, FIELD_INTEGER, "minhealthdmg"),
  166. DEFINE_KEYFIELD(m_nInnerRadius, FIELD_INTEGER, "innerradius"),
  167. DEFINE_KEYFIELD(m_nOuterRadius, FIELD_INTEGER, "outerradius"),
  168. DEFINE_FIELD(m_bActive, FIELD_BOOLEAN),
  169. DEFINE_FIELD(m_bAngry, FIELD_BOOLEAN),
  170. DEFINE_FIELD(m_bBlinkState, FIELD_BOOLEAN),
  171. DEFINE_FIELD(m_bEnabled, FIELD_BOOLEAN),
  172. DEFINE_KEYFIELD(m_sDefaultTarget, FIELD_STRING, "defaulttarget"),
  173. DEFINE_FIELD(m_hEnemyTarget, FIELD_EHANDLE),
  174. DEFINE_FIELD(m_flPingTime, FIELD_TIME),
  175. DEFINE_FIELD(m_flClickTime, FIELD_TIME),
  176. DEFINE_FIELD(m_nClickCount, FIELD_INTEGER ),
  177. DEFINE_FIELD(m_flMoveSoundTime, FIELD_TIME),
  178. DEFINE_FIELD(m_flTurnOffEyeFlashTime, FIELD_TIME),
  179. DEFINE_FIELD(m_flEyeHappyTime, FIELD_TIME),
  180. DEFINE_FIELD(m_vecGoalAngles, FIELD_VECTOR),
  181. DEFINE_FIELD(m_pEyeGlow, FIELD_CLASSPTR),
  182. DEFINE_FIELD(m_pEyeFlash, FIELD_CLASSPTR),
  183. DEFINE_THINKFUNC(Deploy),
  184. DEFINE_THINKFUNC(ActiveThink),
  185. DEFINE_THINKFUNC(SearchThink),
  186. DEFINE_THINKFUNC(DeathThink),
  187. // Inputs
  188. DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle),
  189. DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable),
  190. DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable),
  191. DEFINE_INPUTFUNC(FIELD_VOID, "SetAngry", InputSetAngry),
  192. DEFINE_INPUTFUNC(FIELD_VOID, "SetIdle", InputSetIdle),
  193. END_DATADESC()
  194. LINK_ENTITY_TO_CLASS(npc_combine_camera, CNPC_CombineCamera);
  195. //-----------------------------------------------------------------------------
  196. // Constructor
  197. //-----------------------------------------------------------------------------
  198. CNPC_CombineCamera::CNPC_CombineCamera()
  199. {
  200. m_bActive = false;
  201. m_pEyeGlow = NULL;
  202. m_pEyeFlash = NULL;
  203. m_iAmmoType = -1;
  204. m_iMinHealthDmg = 0;
  205. m_flPingTime = 0;
  206. m_bBlinkState = false;
  207. m_bEnabled = false;
  208. m_vecGoalAngles.Init();
  209. }
  210. //-----------------------------------------------------------------------------
  211. // Purpose:
  212. //-----------------------------------------------------------------------------
  213. CNPC_CombineCamera::~CNPC_CombineCamera()
  214. {
  215. }
  216. //-----------------------------------------------------------------------------
  217. // Purpose: Precache
  218. //-----------------------------------------------------------------------------
  219. void CNPC_CombineCamera::Precache()
  220. {
  221. PrecacheModel(COMBINE_CAMERA_MODEL);
  222. PrecacheModel(COMBINE_CAMERA_GLOW_SPRITE);
  223. PrecacheModel(COMBINE_CAMERA_FLASH_SPRITE);
  224. // Activities
  225. ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_OPEN);
  226. ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_CLOSE);
  227. ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_CLOSED_IDLE);
  228. ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_OPEN_IDLE);
  229. ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_FIRE);
  230. PrecacheScriptSound( "NPC_CombineCamera.Move" );
  231. PrecacheScriptSound( "NPC_CombineCamera.BecomeIdle" );
  232. PrecacheScriptSound( "NPC_CombineCamera.Active" );
  233. PrecacheScriptSound( "NPC_CombineCamera.Click" );
  234. PrecacheScriptSound( "NPC_CombineCamera.Ping" );
  235. PrecacheScriptSound( "NPC_CombineCamera.Angry" );
  236. PrecacheScriptSound( "NPC_CombineCamera.Die" );
  237. BaseClass::Precache();
  238. }
  239. //-----------------------------------------------------------------------------
  240. // Purpose: Spawn the entity
  241. //-----------------------------------------------------------------------------
  242. void CNPC_CombineCamera::Spawn()
  243. {
  244. Precache();
  245. SetModel(COMBINE_CAMERA_MODEL);
  246. m_pEyeFlash = CSprite::SpriteCreate(COMBINE_CAMERA_FLASH_SPRITE, GetLocalOrigin(), FALSE);
  247. m_pEyeFlash->SetTransparency(kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation);
  248. m_pEyeFlash->SetAttachment(this, 2);
  249. m_pEyeFlash->SetBrightness(0);
  250. m_pEyeFlash->SetScale(1.0);
  251. BaseClass::Spawn();
  252. m_HackedGunPos = Vector(0, 0, 12.75);
  253. SetViewOffset(EyeOffset(ACT_IDLE));
  254. m_flFieldOfView = CAMERA_FOV_WIDE;
  255. m_takedamage = DAMAGE_YES;
  256. m_iHealth = 50;
  257. m_bloodColor = BLOOD_COLOR_MECH;
  258. SetSolid(SOLID_BBOX);
  259. AddSolidFlags(FSOLID_NOT_STANDABLE);
  260. SetHeight(COMBINE_CAMERA_RETRACT_HEIGHT);
  261. AddFlag(FL_AIMTARGET);
  262. SetPoseParameter(COMBINE_CAMERA_BC_YAW, 0);
  263. SetPoseParameter(COMBINE_CAMERA_BC_PITCH, 0);
  264. m_iAmmoType = GetAmmoDef()->Index("Pistol");
  265. // Create our eye sprite
  266. m_pEyeGlow = CSprite::SpriteCreate(COMBINE_CAMERA_GLOW_SPRITE, GetLocalOrigin(), false);
  267. m_pEyeGlow->SetTransparency(kRenderWorldGlow, 255, 0, 0, 128, kRenderFxNoDissipation);
  268. m_pEyeGlow->SetAttachment(this, 2);
  269. // Set our enabled state
  270. m_bEnabled = ((m_spawnflags & SF_COMBINE_CAMERA_STARTINACTIVE) == false);
  271. // Make sure the radii are sane.
  272. if (m_nOuterRadius <= 0)
  273. {
  274. m_nOuterRadius = 300;
  275. }
  276. if (m_nInnerRadius <= 0)
  277. {
  278. m_nInnerRadius = 450;
  279. }
  280. if (m_nOuterRadius < m_nInnerRadius)
  281. {
  282. V_swap(m_nOuterRadius, m_nInnerRadius);
  283. }
  284. // Do we start active?
  285. if (m_bEnabled)
  286. {
  287. Deploy();
  288. }
  289. else
  290. {
  291. SetEyeState(CAMERA_EYE_DISABLED);
  292. }
  293. //Adrian: No shadows on these guys.
  294. AddEffects( EF_NOSHADOW );
  295. // Stagger our starting times
  296. SetNextThink( gpGlobals->curtime + random->RandomFloat(0.1f, 0.3f) );
  297. // Don't allow us to skip animation setup because our attachments are critical to us!
  298. SetBoneCacheFlags( BCF_NO_ANIMATION_SKIP );
  299. }
  300. //-----------------------------------------------------------------------------
  301. // Purpose:
  302. //-----------------------------------------------------------------------------
  303. CBaseEntity *CNPC_CombineCamera::GetTarget()
  304. {
  305. return m_hEnemyTarget;
  306. }
  307. //-----------------------------------------------------------------------------
  308. // Purpose:
  309. //-----------------------------------------------------------------------------
  310. int CNPC_CombineCamera::OnTakeDamage(const CTakeDamageInfo &inputInfo)
  311. {
  312. if (!m_takedamage)
  313. return 0;
  314. CTakeDamageInfo info = inputInfo;
  315. if (m_bActive == false)
  316. info.ScaleDamage(0.1f);
  317. // If attacker can't do at least the min required damage to us, don't take any damage from them
  318. if (info.GetDamage() < m_iMinHealthDmg)
  319. return 0;
  320. m_iHealth -= info.GetDamage();
  321. if (m_iHealth <= 0)
  322. {
  323. m_iHealth = 0;
  324. m_takedamage = DAMAGE_NO;
  325. RemoveFlag(FL_NPC); // why are they set in the first place???
  326. // FIXME: This needs to throw a ragdoll gib or something other than animating the retraction -- jdw
  327. ExplosionCreate(GetAbsOrigin(), GetLocalAngles(), this, 100, 100, false);
  328. SetThink(&CNPC_CombineCamera::DeathThink);
  329. StopSound("Alert");
  330. m_OnDamaged.FireOutput(info.GetInflictor(), this);
  331. SetNextThink( gpGlobals->curtime + 0.1f );
  332. return 0;
  333. }
  334. return 1;
  335. }
  336. //-----------------------------------------------------------------------------
  337. // Purpose: Deploy and start searching for targets.
  338. //-----------------------------------------------------------------------------
  339. void CNPC_CombineCamera::Deploy()
  340. {
  341. m_vecGoalAngles = GetAbsAngles();
  342. SetNextThink( gpGlobals->curtime );
  343. SetEyeState(CAMERA_EYE_IDLE);
  344. m_bActive = true;
  345. SetHeight(COMBINE_CAMERA_DEPLOY_HEIGHT);
  346. SetIdealActivity((Activity) ACT_COMBINE_CAMERA_OPEN_IDLE);
  347. m_flPlaybackRate = 0;
  348. SetThink(&CNPC_CombineCamera::SearchThink);
  349. EmitSound("NPC_CombineCamera.Move");
  350. }
  351. //-----------------------------------------------------------------------------
  352. // Purpose: Returns the speed at which the camera can face a target
  353. //-----------------------------------------------------------------------------
  354. float CNPC_CombineCamera::MaxYawSpeed()
  355. {
  356. if (m_hEnemyTarget)
  357. return 180.0f;
  358. return 60.0f;
  359. }
  360. //-----------------------------------------------------------------------------
  361. // Purpose: Causes the camera to face its desired angles
  362. //-----------------------------------------------------------------------------
  363. bool CNPC_CombineCamera::UpdateFacing()
  364. {
  365. bool bMoved = false;
  366. matrix3x4_t localToWorld;
  367. GetAttachment(LookupAttachment("eyes"), localToWorld);
  368. Vector vecGoalDir;
  369. AngleVectors(m_vecGoalAngles, &vecGoalDir );
  370. Vector vecGoalLocalDir;
  371. VectorIRotate(vecGoalDir, localToWorld, vecGoalLocalDir);
  372. QAngle vecGoalLocalAngles;
  373. VectorAngles(vecGoalLocalDir, vecGoalLocalAngles);
  374. // Update pitch
  375. float flDiff = AngleNormalize(UTIL_ApproachAngle( vecGoalLocalAngles.x, 0.0, 0.1f * MaxYawSpeed()));
  376. int iPose = LookupPoseParameter(COMBINE_CAMERA_BC_PITCH);
  377. SetPoseParameter(iPose, GetPoseParameter(iPose) + (flDiff / 1.5f));
  378. if (fabs(flDiff) > 0.1f)
  379. {
  380. bMoved = true;
  381. }
  382. // Update yaw
  383. flDiff = AngleNormalize(UTIL_ApproachAngle( vecGoalLocalAngles.y, 0.0, 0.1f * MaxYawSpeed()));
  384. iPose = LookupPoseParameter(COMBINE_CAMERA_BC_YAW);
  385. SetPoseParameter(iPose, GetPoseParameter(iPose) + (flDiff / 1.5f));
  386. if (fabs(flDiff) > 0.1f)
  387. {
  388. bMoved = true;
  389. }
  390. if (bMoved && (m_flMoveSoundTime < gpGlobals->curtime))
  391. {
  392. EmitSound("NPC_CombineCamera.Move");
  393. m_flMoveSoundTime = gpGlobals->curtime + CAMERA_MOVE_INTERVAL;
  394. }
  395. // You're going to make decisions based on this info. So bump the bone cache after you calculate everything
  396. InvalidateBoneCache();
  397. return bMoved;
  398. }
  399. //-----------------------------------------------------------------------------
  400. // Purpose:
  401. //-----------------------------------------------------------------------------
  402. Vector CNPC_CombineCamera::HeadDirection2D()
  403. {
  404. Vector vecMuzzle, vecMuzzleDir;
  405. GetAttachment("eyes", vecMuzzle, &vecMuzzleDir );
  406. vecMuzzleDir.z = 0;
  407. VectorNormalize(vecMuzzleDir);
  408. return vecMuzzleDir;
  409. }
  410. //-----------------------------------------------------------------------------
  411. // Purpose:
  412. // Input : *pEntity -
  413. // Output : Returns true on success, false on failure.
  414. //-----------------------------------------------------------------------------
  415. bool CNPC_CombineCamera::FVisible(CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker)
  416. {
  417. CBaseEntity *pHitEntity = NULL;
  418. if ( BaseClass::FVisible( pEntity, traceMask, &pHitEntity ) )
  419. return true;
  420. // If we hit something that's okay to hit anyway, still fire
  421. if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() )
  422. {
  423. if (IRelationType(pHitEntity) == D_HT)
  424. return true;
  425. }
  426. if (ppBlocker)
  427. {
  428. *ppBlocker = pHitEntity;
  429. }
  430. return false;
  431. }
  432. //-----------------------------------------------------------------------------
  433. // Purpose: Enemies are only valid if they're inside our radius
  434. //-----------------------------------------------------------------------------
  435. bool CNPC_CombineCamera::IsValidEnemy( CBaseEntity *pEnemy )
  436. {
  437. Vector vecDelta = pEnemy->GetAbsOrigin() - GetAbsOrigin();
  438. float flDist = vecDelta.Length();
  439. if ( (flDist > m_nOuterRadius) || !FInViewCone(pEnemy) )
  440. return false;
  441. return BaseClass::IsValidEnemy( pEnemy );
  442. }
  443. //-----------------------------------------------------------------------------
  444. // Purpose: Called when we have no scripted target. Looks for new enemies to track.
  445. //-----------------------------------------------------------------------------
  446. CBaseEntity *CNPC_CombineCamera::MaintainEnemy()
  447. {
  448. if (HasSpawnFlags(SF_COMBINE_CAMERA_IGNOREENEMIES))
  449. return NULL;
  450. GetSenses()->Look(m_nOuterRadius);
  451. CBaseEntity *pEnemy = BestEnemy();
  452. if (pEnemy)
  453. {
  454. // See if our best enemy is too far away to care about.
  455. Vector vecDelta = pEnemy->GetAbsOrigin() - GetAbsOrigin();
  456. float flDist = vecDelta.Length();
  457. if (flDist < m_nOuterRadius)
  458. {
  459. if (FInViewCone(pEnemy))
  460. {
  461. // dvs: HACK: for checking multiple view cones
  462. float flSaveFieldOfView = m_flFieldOfView;
  463. m_flFieldOfView = CAMERA_FOV_NARROW;
  464. // Is the target visible?
  465. bool bVisible = FVisible(pEnemy);
  466. m_flFieldOfView = flSaveFieldOfView;
  467. if ( bVisible )
  468. return pEnemy;
  469. }
  470. }
  471. }
  472. return NULL;
  473. }
  474. //-----------------------------------------------------------------------------
  475. // Purpose: Think while actively tracking a target.
  476. //-----------------------------------------------------------------------------
  477. void CNPC_CombineCamera::ActiveThink()
  478. {
  479. // Allow descended classes a chance to do something before the think function
  480. if (PreThink(CAMERA_ACTIVE))
  481. return;
  482. // No active target, look for suspicious characters.
  483. CBaseEntity *pTarget = MaintainEnemy();
  484. if ( !pTarget )
  485. {
  486. // Nobody suspicious. Go back to being idle.
  487. m_hEnemyTarget = NULL;
  488. EmitSound("NPC_CombineCamera.BecomeIdle");
  489. SetAngry(false);
  490. SetThink(&CNPC_CombineCamera::SearchThink);
  491. SetNextThink( gpGlobals->curtime );
  492. return;
  493. }
  494. // Examine the target until it reaches our inner radius
  495. if ( pTarget != m_hEnemyTarget )
  496. {
  497. Vector vecDelta = pTarget->GetAbsOrigin() - GetAbsOrigin();
  498. float flDist = vecDelta.Length();
  499. if ( (flDist < m_nInnerRadius) && FInViewCone(pTarget) )
  500. {
  501. m_OnFoundEnemy.Set(pTarget, pTarget, this);
  502. // If it's a citizen, it's ok. If it's the player, it's not ok.
  503. if ( pTarget->IsPlayer() )
  504. {
  505. SetEyeState(CAMERA_EYE_FOUND_TARGET);
  506. if (HasSpawnFlags(SF_COMBINE_CAMERA_BECOMEANGRY))
  507. {
  508. SetAngry(true);
  509. }
  510. else
  511. {
  512. EmitSound("NPC_CombineCamera.Active");
  513. }
  514. m_OnFoundPlayer.Set(pTarget, pTarget, this);
  515. m_hEnemyTarget = pTarget;
  516. }
  517. else
  518. {
  519. SetEyeState(CAMERA_EYE_HAPPY);
  520. m_flEyeHappyTime = gpGlobals->curtime + 2.0;
  521. // Now forget about this target forever
  522. AddEntityRelationship( pTarget, D_NU, 99 );
  523. }
  524. }
  525. else
  526. {
  527. // If we get angry automatically, we get un-angry automatically
  528. if ( HasSpawnFlags(SF_COMBINE_CAMERA_BECOMEANGRY) && m_bAngry )
  529. {
  530. SetAngry(false);
  531. }
  532. m_hEnemyTarget = NULL;
  533. // We don't quite see this guy, but we sense him.
  534. SetEyeState(CAMERA_EYE_SEEKING_TARGET);
  535. }
  536. }
  537. // Update our think time
  538. SetNextThink( gpGlobals->curtime + 0.1f );
  539. TrackTarget(pTarget);
  540. MaintainEye();
  541. }
  542. //-----------------------------------------------------------------------------
  543. // Purpose:
  544. // Input : pTarget -
  545. //-----------------------------------------------------------------------------
  546. void CNPC_CombineCamera::TrackTarget( CBaseEntity *pTarget )
  547. {
  548. if (!pTarget)
  549. return;
  550. // Calculate direction to target
  551. Vector vecMid = EyePosition();
  552. Vector vecMidTarget = pTarget->BodyTarget(vecMid);
  553. Vector vecDirToTarget = vecMidTarget - vecMid;
  554. // We want to look at the target's eyes so we don't jitter
  555. Vector vecDirToTargetEyes = pTarget->WorldSpaceCenter() - vecMid;
  556. VectorNormalize(vecDirToTargetEyes);
  557. QAngle vecAnglesToTarget;
  558. VectorAngles(vecDirToTargetEyes, vecAnglesToTarget);
  559. // Draw debug info
  560. if (g_debug_combine_camera.GetBool())
  561. {
  562. NDebugOverlay::Cross3D(vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05);
  563. NDebugOverlay::Cross3D(pTarget->WorldSpaceCenter(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05);
  564. NDebugOverlay::Line(vecMid, pTarget->WorldSpaceCenter(), 0, 255, 0, false, 0.05);
  565. NDebugOverlay::Cross3D(vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05);
  566. NDebugOverlay::Cross3D(vecMidTarget, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05);
  567. NDebugOverlay::Line(vecMid, vecMidTarget, 0, 255, 0, false, 0.05f);
  568. }
  569. Vector vecMuzzle, vecMuzzleDir;
  570. QAngle vecMuzzleAng;
  571. GetAttachment("eyes", vecMuzzle, &vecMuzzleDir);
  572. SetIdealActivity((Activity) ACT_COMBINE_CAMERA_OPEN_IDLE);
  573. m_vecGoalAngles.y = vecAnglesToTarget.y;
  574. m_vecGoalAngles.x = vecAnglesToTarget.x;
  575. // Turn to face
  576. UpdateFacing();
  577. }
  578. //-----------------------------------------------------------------------------
  579. // Purpose:
  580. //-----------------------------------------------------------------------------
  581. void CNPC_CombineCamera::MaintainEye()
  582. {
  583. // Angry cameras take a few pictures of their target.
  584. if ((m_bAngry) && (m_nClickCount <= 3))
  585. {
  586. if ((m_flClickTime != 0) && (m_flClickTime < gpGlobals->curtime))
  587. {
  588. m_pEyeFlash->SetScale(1.0);
  589. m_pEyeFlash->SetBrightness(255);
  590. m_pEyeFlash->SetColor(255,255,255);
  591. EmitSound("NPC_CombineCamera.Click");
  592. m_flTurnOffEyeFlashTime = gpGlobals->curtime + 0.1;
  593. m_flClickTime = gpGlobals->curtime + CAMERA_CLICK_INTERVAL;
  594. }
  595. else if ((m_flTurnOffEyeFlashTime != 0) && (m_flTurnOffEyeFlashTime < gpGlobals->curtime))
  596. {
  597. m_flTurnOffEyeFlashTime = 0;
  598. m_pEyeFlash->SetBrightness( 0, 0.25f );
  599. m_nClickCount++;
  600. }
  601. }
  602. }
  603. //-----------------------------------------------------------------------------
  604. // Purpose: Target doesn't exist or has eluded us, so search for one
  605. //-----------------------------------------------------------------------------
  606. void CNPC_CombineCamera::SearchThink()
  607. {
  608. // Allow descended classes a chance to do something before the think function
  609. if (PreThink(CAMERA_SEARCHING))
  610. return;
  611. SetNextThink( gpGlobals->curtime + 0.05f );
  612. SetIdealActivity((Activity) ACT_COMBINE_CAMERA_OPEN_IDLE);
  613. if ( !GetTarget() )
  614. {
  615. // Try to acquire a new target
  616. if (MaintainEnemy())
  617. {
  618. SetThink( &CNPC_CombineCamera::ActiveThink );
  619. return;
  620. }
  621. }
  622. // Display that we're scanning
  623. m_vecGoalAngles.x = 15.0f;
  624. m_vecGoalAngles.y = GetAbsAngles().y + (sin(gpGlobals->curtime * 2.0f) * 45.0f);
  625. // Turn and ping
  626. UpdateFacing();
  627. Ping();
  628. SetEyeState(CAMERA_EYE_IDLE);
  629. }
  630. //-----------------------------------------------------------------------------
  631. // Purpose: Allows a generic think function before the others are called
  632. // Input : state - which state the camera is currently in
  633. //-----------------------------------------------------------------------------
  634. bool CNPC_CombineCamera::PreThink(cameraState_e state)
  635. {
  636. CheckPVSCondition();
  637. MaintainActivity();
  638. StudioFrameAdvance();
  639. // If we're disabled, shut down
  640. if ( !m_bEnabled )
  641. {
  642. SetIdealActivity((Activity) ACT_COMBINE_CAMERA_CLOSED_IDLE);
  643. SetNextThink( gpGlobals->curtime + 0.1f );
  644. return true;
  645. }
  646. // Do not interrupt current think function
  647. return false;
  648. }
  649. //-----------------------------------------------------------------------------
  650. // Purpose: Sets the state of the glowing eye attached to the camera
  651. // Input : state - state the eye should be in
  652. //-----------------------------------------------------------------------------
  653. void CNPC_CombineCamera::SetEyeState(eyeState_t state)
  654. {
  655. // Must have a valid eye to affect
  656. if (m_pEyeGlow == NULL)
  657. return;
  658. if (m_bAngry)
  659. {
  660. m_pEyeGlow->SetColor(255, 0, 0);
  661. m_pEyeGlow->SetBrightness(164, 0.1f);
  662. m_pEyeGlow->SetScale(0.4f, 0.1f);
  663. return;
  664. }
  665. // If we're switching to IDLE, and we're still happy, use happy instead
  666. if ( state == CAMERA_EYE_IDLE && m_flEyeHappyTime > gpGlobals->curtime )
  667. {
  668. state = CAMERA_EYE_HAPPY;
  669. }
  670. // Set the state
  671. switch (state)
  672. {
  673. default:
  674. case CAMERA_EYE_IDLE:
  675. {
  676. m_pEyeGlow->SetColor(0, 255, 0);
  677. m_pEyeGlow->SetBrightness(164, 0.1f);
  678. m_pEyeGlow->SetScale(0.4f, 0.1f);
  679. break;
  680. }
  681. case CAMERA_EYE_SEEKING_TARGET:
  682. {
  683. // Toggle our state
  684. m_bBlinkState = !m_bBlinkState;
  685. // Amber
  686. m_pEyeGlow->SetColor(255, 128, 0);
  687. if (m_bBlinkState)
  688. {
  689. // Fade up and scale up
  690. m_pEyeGlow->SetScale(0.25f, 0.1f);
  691. m_pEyeGlow->SetBrightness(164, 0.1f);
  692. }
  693. else
  694. {
  695. // Fade down and scale down
  696. m_pEyeGlow->SetScale(0.2f, 0.1f);
  697. m_pEyeGlow->SetBrightness(64, 0.1f);
  698. }
  699. break;
  700. }
  701. case CAMERA_EYE_FOUND_TARGET:
  702. {
  703. if (!m_bAngry)
  704. {
  705. // Amber
  706. m_pEyeGlow->SetColor(255, 128, 0);
  707. // Fade up and scale up
  708. m_pEyeGlow->SetScale(0.45f, 0.1f);
  709. m_pEyeGlow->SetBrightness(220, 0.1f);
  710. }
  711. else
  712. {
  713. m_pEyeGlow->SetColor(255, 0, 0);
  714. m_pEyeGlow->SetBrightness(164, 0.1f);
  715. m_pEyeGlow->SetScale(0.4f, 0.1f);
  716. }
  717. break;
  718. }
  719. case CAMERA_EYE_DORMANT: // Fade out and scale down
  720. {
  721. m_pEyeGlow->SetColor(0, 255, 0);
  722. m_pEyeGlow->SetScale(0.1f, 0.5f);
  723. m_pEyeGlow->SetBrightness(64, 0.5f);
  724. break;
  725. }
  726. case CAMERA_EYE_DEAD: // Fade out slowly
  727. {
  728. m_pEyeGlow->SetColor(255, 0, 0);
  729. m_pEyeGlow->SetScale(0.1f, 3.0f);
  730. m_pEyeGlow->SetBrightness(0, 3.0f);
  731. break;
  732. }
  733. case CAMERA_EYE_DISABLED:
  734. {
  735. m_pEyeGlow->SetColor(0, 255, 0);
  736. m_pEyeGlow->SetScale(0.1f, 1.0f);
  737. m_pEyeGlow->SetBrightness(0, 1.0f);
  738. break;
  739. }
  740. case CAMERA_EYE_HAPPY:
  741. {
  742. m_pEyeGlow->SetColor(0, 255, 0);
  743. m_pEyeGlow->SetBrightness(255, 0.1f);
  744. m_pEyeGlow->SetScale(0.5f, 0.1f);
  745. break;
  746. }
  747. }
  748. }
  749. //-----------------------------------------------------------------------------
  750. // Purpose: Make a pinging noise so the player knows where we are
  751. //-----------------------------------------------------------------------------
  752. void CNPC_CombineCamera::Ping()
  753. {
  754. // See if it's time to ping again
  755. if (m_flPingTime > gpGlobals->curtime)
  756. return;
  757. // Ping!
  758. EmitSound("NPC_CombineCamera.Ping");
  759. m_flPingTime = gpGlobals->curtime + COMBINE_CAMERA_PING_TIME;
  760. }
  761. //-----------------------------------------------------------------------------
  762. // Purpose: Toggle the camera's state
  763. //-----------------------------------------------------------------------------
  764. void CNPC_CombineCamera::Toggle()
  765. {
  766. if (m_bEnabled)
  767. {
  768. Disable();
  769. }
  770. else
  771. {
  772. Enable();
  773. }
  774. }
  775. //-----------------------------------------------------------------------------
  776. // Purpose: Enable the camera and deploy
  777. //-----------------------------------------------------------------------------
  778. void CNPC_CombineCamera::Enable()
  779. {
  780. m_bEnabled = true;
  781. SetThink(&CNPC_CombineCamera::Deploy);
  782. SetNextThink( gpGlobals->curtime + 0.05f );
  783. }
  784. //-----------------------------------------------------------------------------
  785. // Purpose: Retire the camera until enabled again
  786. //-----------------------------------------------------------------------------
  787. void CNPC_CombineCamera::Disable()
  788. {
  789. m_bEnabled = false;
  790. m_hEnemyTarget = NULL;
  791. SetNextThink( gpGlobals->curtime + 0.1f );
  792. }
  793. //-----------------------------------------------------------------------------
  794. // Purpose: Toggle the camera's state via input function
  795. //-----------------------------------------------------------------------------
  796. void CNPC_CombineCamera::InputToggle(inputdata_t &inputdata)
  797. {
  798. Toggle();
  799. }
  800. //-----------------------------------------------------------------------------
  801. // Purpose: Input handler to enable the camera.
  802. //-----------------------------------------------------------------------------
  803. void CNPC_CombineCamera::InputEnable(inputdata_t &inputdata)
  804. {
  805. Enable();
  806. }
  807. //-----------------------------------------------------------------------------
  808. // Purpose: Input handler to disable the camera.
  809. //-----------------------------------------------------------------------------
  810. void CNPC_CombineCamera::InputDisable(inputdata_t &inputdata)
  811. {
  812. Disable();
  813. }
  814. //-----------------------------------------------------------------------------
  815. // Purpose: When we become angry, we make an angry sound and start photographing
  816. // whatever target we are tracking.
  817. //-----------------------------------------------------------------------------
  818. void CNPC_CombineCamera::SetAngry(bool bAngry)
  819. {
  820. if ((bAngry) && (!m_bAngry))
  821. {
  822. m_bAngry = true;
  823. m_nClickCount = 0;
  824. m_flClickTime = gpGlobals->curtime + 0.4;
  825. EmitSound("NPC_CombineCamera.Angry");
  826. SetEyeState(CAMERA_EYE_ANGRY);
  827. }
  828. else if ((!bAngry) && (m_bAngry))
  829. {
  830. m_bAngry = false;
  831. // make sure the flash is off (we might be in mid-flash)
  832. m_pEyeFlash->SetBrightness(0);
  833. SetEyeState(GetTarget() ? CAMERA_EYE_SEEKING_TARGET : CAMERA_EYE_IDLE);
  834. }
  835. }
  836. //-----------------------------------------------------------------------------
  837. // Purpose:
  838. //-----------------------------------------------------------------------------
  839. void CNPC_CombineCamera::InputSetAngry(inputdata_t &inputdata)
  840. {
  841. SetAngry(true);
  842. }
  843. //-----------------------------------------------------------------------------
  844. // Purpose:
  845. //-----------------------------------------------------------------------------
  846. void CNPC_CombineCamera::InputSetIdle(inputdata_t &inputdata)
  847. {
  848. SetAngry(false);
  849. }
  850. //-----------------------------------------------------------------------------
  851. // Purpose:
  852. //-----------------------------------------------------------------------------
  853. void CNPC_CombineCamera::DeathThink()
  854. {
  855. if (PreThink(CAMERA_DEAD))
  856. return;
  857. // Level out our angles
  858. m_vecGoalAngles = GetAbsAngles();
  859. SetNextThink( gpGlobals->curtime + 0.1f );
  860. if (m_lifeState != LIFE_DEAD)
  861. {
  862. m_lifeState = LIFE_DEAD;
  863. EmitSound("NPC_CombineCamera.Die");
  864. // lots of smoke
  865. Vector pos;
  866. CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &pos );
  867. CBroadcastRecipientFilter filter;
  868. te->Smoke(filter, 0.0, &pos, g_sModelIndexSmoke, 2.5, 10);
  869. g_pEffects->Sparks(pos);
  870. SetActivity((Activity) ACT_COMBINE_CAMERA_CLOSE);
  871. }
  872. StudioFrameAdvance();
  873. if (IsActivityFinished() && (UpdateFacing() == false))
  874. {
  875. SetHeight(COMBINE_CAMERA_RETRACT_HEIGHT);
  876. m_flPlaybackRate = 0;
  877. SetThink(NULL);
  878. }
  879. }
  880. //-----------------------------------------------------------------------------
  881. // Purpose:
  882. // Input : height -
  883. //-----------------------------------------------------------------------------
  884. void CNPC_CombineCamera::SetHeight(float height)
  885. {
  886. Vector forward, right, up;
  887. AngleVectors(GetLocalAngles(), &forward, &right, &up);
  888. Vector mins = (forward * -16.0f) + (right * -16.0f);
  889. Vector maxs = (forward * 16.0f) + (right * 16.0f) + (up * -height);
  890. if (mins.x > maxs.x)
  891. {
  892. V_swap(mins.x, maxs.x);
  893. }
  894. if (mins.y > maxs.y)
  895. {
  896. V_swap(mins.y, maxs.y);
  897. }
  898. if (mins.z > maxs.z)
  899. {
  900. V_swap(mins.z, maxs.z);
  901. }
  902. SetCollisionBounds(mins, maxs);
  903. UTIL_SetSize(this, mins, maxs);
  904. }
  905. //-----------------------------------------------------------------------------
  906. // Purpose: Draw any debug text overlays
  907. //-----------------------------------------------------------------------------
  908. int CNPC_CombineCamera::DrawDebugTextOverlays(void)
  909. {
  910. int text_offset = BaseClass::DrawDebugTextOverlays();
  911. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  912. {
  913. char tempstr[512];
  914. Q_snprintf( tempstr, sizeof( tempstr ),"Enemy : %s", m_hEnemyTarget ? m_hEnemyTarget->GetDebugName() : "<none>");
  915. EntityText(text_offset,tempstr,0);
  916. text_offset++;
  917. }
  918. return text_offset;
  919. }
  920. //-----------------------------------------------------------------------------
  921. // Purpose:
  922. //-----------------------------------------------------------------------------
  923. void CNPC_CombineCamera::DrawDebugGeometryOverlays(void)
  924. {
  925. // ------------------------------
  926. // Draw viewcone if selected
  927. // ------------------------------
  928. if ((m_debugOverlays & OVERLAY_NPC_VIEWCONE_BIT))
  929. {
  930. float flViewRange = acos(CAMERA_FOV_NARROW);
  931. Vector vEyeDir = EyeDirection2D( );
  932. Vector vLeftDir, vRightDir;
  933. float fSin, fCos;
  934. SinCos( flViewRange, &fSin, &fCos );
  935. vLeftDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
  936. vLeftDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
  937. vLeftDir.z = vEyeDir.z;
  938. fSin = sin(-flViewRange);
  939. fCos = cos(-flViewRange);
  940. vRightDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
  941. vRightDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
  942. vRightDir.z = vEyeDir.z;
  943. NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-40), Vector(200,0,40), vLeftDir, 255, 255, 0, 50, 0 );
  944. NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-40), Vector(200,0,40), vRightDir, 255, 255, 0, 50, 0 );
  945. NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, 128, 0 );
  946. }
  947. BaseClass::DrawDebugGeometryOverlays();
  948. }