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.

562 lines
16 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Used to fire events based on the orientation of a given entity.
  4. //
  5. // Looks at its target's angles every frame and fires an output if its
  6. // target's forward vector points at a specified lookat entity for more
  7. // than a specified length of time.
  8. //
  9. // It also fires an output whenever the target's angles change.
  10. //
  11. //=============================================================================//
  12. #include "cbase.h"
  13. #include "entityinput.h"
  14. #include "entityoutput.h"
  15. #include "eventqueue.h"
  16. #include "mathlib/mathlib.h"
  17. // memdbgon must be the last include file in a .cpp file!!!
  18. #include "tier0/memdbgon.h"
  19. #define SF_USE_TARGET_FACING (1<<0) // Use the target entity's direction instead of position
  20. class CPointAngleSensor : public CPointEntity
  21. {
  22. DECLARE_CLASS(CPointAngleSensor, CPointEntity);
  23. public:
  24. bool KeyValue(const char *szKeyName, const char *szValue);
  25. void Activate(void);
  26. void Spawn(void);
  27. void Think(void);
  28. int DrawDebugTextOverlays(void);
  29. protected:
  30. void Enable();
  31. void Disable();
  32. // Input handlers
  33. void InputEnable(inputdata_t &inputdata);
  34. void InputDisable(inputdata_t &inputdata);
  35. void InputToggle(inputdata_t &inputdata);
  36. void InputTest(inputdata_t &inputdata);
  37. void InputSetTargetEntity(inputdata_t &inputdata);
  38. bool IsFacingWithinTolerance(CBaseEntity *pEntity, CBaseEntity *pTarget, float flTolerance, float *pflDot = NULL);
  39. bool m_bDisabled; // When disabled, we do not think or fire outputs.
  40. string_t m_nLookAtName; // Name of the entity that the target must point at to fire the OnTrue output.
  41. EHANDLE m_hTargetEntity; // Entity whose angles are being monitored.
  42. EHANDLE m_hLookAtEntity; // Entity that the target must look at to fire the OnTrue output.
  43. float m_flDuration; // Time in seconds for which the entity must point at the target.
  44. float m_flDotTolerance; // Degrees of error allowed to satisfy the condition, expressed as a dot product.
  45. float m_flFacingTime; // The time at which the target entity pointed at the lookat entity.
  46. bool m_bFired; // Latches the output so it only fires once per true.
  47. // Outputs
  48. COutputEvent m_OnFacingLookat; // Fired when the target points at the lookat entity.
  49. COutputEvent m_OnNotFacingLookat; // Fired in response to a Test input if the target is not looking at the lookat entity.
  50. COutputVector m_TargetDir;
  51. COutputFloat m_FacingPercentage; // Normalize value representing how close the entity is to facing directly at the target
  52. DECLARE_DATADESC();
  53. };
  54. LINK_ENTITY_TO_CLASS(point_anglesensor, CPointAngleSensor);
  55. BEGIN_DATADESC(CPointAngleSensor)
  56. // Keys
  57. DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled"),
  58. DEFINE_KEYFIELD(m_nLookAtName, FIELD_STRING, "lookatname"),
  59. DEFINE_FIELD(m_hTargetEntity, FIELD_EHANDLE),
  60. DEFINE_FIELD(m_hLookAtEntity, FIELD_EHANDLE),
  61. DEFINE_KEYFIELD(m_flDuration, FIELD_FLOAT, "duration"),
  62. DEFINE_FIELD(m_flDotTolerance, FIELD_FLOAT),
  63. DEFINE_FIELD(m_flFacingTime, FIELD_TIME),
  64. DEFINE_FIELD(m_bFired, FIELD_BOOLEAN),
  65. // Outputs
  66. DEFINE_OUTPUT(m_OnFacingLookat, "OnFacingLookat"),
  67. DEFINE_OUTPUT(m_OnNotFacingLookat, "OnNotFacingLookat"),
  68. DEFINE_OUTPUT(m_TargetDir, "TargetDir"),
  69. DEFINE_OUTPUT(m_FacingPercentage, "FacingPercentage"),
  70. // Inputs
  71. DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable),
  72. DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable),
  73. DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle),
  74. DEFINE_INPUTFUNC(FIELD_VOID, "Test", InputTest),
  75. DEFINE_INPUTFUNC(FIELD_STRING, "SetTargetEntity", InputSetTargetEntity),
  76. END_DATADESC()
  77. //-----------------------------------------------------------------------------
  78. // Purpose: Handles keyvalues that require special processing.
  79. // Output : Returns true if handled, false if not.
  80. //-----------------------------------------------------------------------------
  81. bool CPointAngleSensor::KeyValue(const char *szKeyName, const char *szValue)
  82. {
  83. if (FStrEq(szKeyName, "tolerance"))
  84. {
  85. float flTolerance = atof(szValue);
  86. m_flDotTolerance = cos(DEG2RAD(flTolerance));
  87. }
  88. else
  89. {
  90. return(BaseClass::KeyValue(szKeyName, szValue));
  91. }
  92. return(true);
  93. }
  94. //-----------------------------------------------------------------------------
  95. // Purpose: Called when spawning after parsing keyvalues.
  96. //-----------------------------------------------------------------------------
  97. void CPointAngleSensor::Spawn(void)
  98. {
  99. BaseClass::Spawn();
  100. }
  101. //-----------------------------------------------------------------------------
  102. // Purpose: Called after all entities have spawned on new map or savegame load.
  103. //-----------------------------------------------------------------------------
  104. void CPointAngleSensor::Activate(void)
  105. {
  106. BaseClass::Activate();
  107. if (!m_hTargetEntity)
  108. {
  109. m_hTargetEntity = gEntList.FindEntityByName( NULL, m_target );
  110. }
  111. if (!m_hLookAtEntity && (m_nLookAtName != NULL_STRING))
  112. {
  113. m_hLookAtEntity = gEntList.FindEntityByName( NULL, m_nLookAtName );
  114. if (!m_hLookAtEntity)
  115. {
  116. DevMsg(1, "Angle sensor '%s' could not find look at entity '%s'.\n", GetDebugName(), STRING(m_nLookAtName));
  117. }
  118. }
  119. // It's okay to not have a look at entity, it just means we measure and output the angles
  120. // of the target entity without testing them against the look at entity.
  121. if (!m_bDisabled && m_hTargetEntity)
  122. {
  123. SetNextThink( gpGlobals->curtime );
  124. }
  125. }
  126. //-----------------------------------------------------------------------------
  127. // Purpose: Determines if one entity is facing within a given tolerance of another
  128. // Input : pEntity -
  129. // pTarget -
  130. // flTolerance -
  131. // Output : Returns true on success, false on failure.
  132. //-----------------------------------------------------------------------------
  133. bool CPointAngleSensor::IsFacingWithinTolerance(CBaseEntity *pEntity, CBaseEntity *pTarget, float flTolerance, float *pflDot)
  134. {
  135. if (pflDot)
  136. {
  137. *pflDot = 0;
  138. }
  139. if ((pEntity == NULL) || (pTarget == NULL))
  140. {
  141. return(false);
  142. }
  143. Vector forward;
  144. pEntity->GetVectors(&forward, NULL, NULL);
  145. Vector dir;
  146. // Use either our position relative to the target, or the target's raw facing
  147. if ( HasSpawnFlags( SF_USE_TARGET_FACING ) )
  148. {
  149. pTarget->GetVectors(&dir, NULL, NULL);
  150. }
  151. else
  152. {
  153. dir = pTarget->GetAbsOrigin() - pEntity->GetAbsOrigin();
  154. VectorNormalize(dir);
  155. }
  156. //
  157. // Larger dot product corresponds to a smaller angle.
  158. //
  159. float flDot = dir.Dot(forward);
  160. if (pflDot)
  161. {
  162. *pflDot = flDot;
  163. }
  164. if (flDot >= m_flDotTolerance)
  165. {
  166. return(true);
  167. }
  168. return(false);
  169. }
  170. //-----------------------------------------------------------------------------
  171. // Purpose: Called every frame.
  172. //-----------------------------------------------------------------------------
  173. void CPointAngleSensor::Think(void)
  174. {
  175. if (m_hTargetEntity != NULL)
  176. {
  177. Vector forward;
  178. m_hTargetEntity->GetVectors(&forward, NULL, NULL);
  179. m_TargetDir.Set(forward, this, this);
  180. if (m_hLookAtEntity != NULL)
  181. {
  182. //
  183. // Check to see if the measure entity's forward vector has been within
  184. // given tolerance of the target entity for the given period of time.
  185. //
  186. float flDot;
  187. if (IsFacingWithinTolerance(m_hTargetEntity, m_hLookAtEntity, m_flDotTolerance, &flDot ))
  188. {
  189. if (!m_bFired)
  190. {
  191. if (!m_flFacingTime)
  192. {
  193. m_flFacingTime = gpGlobals->curtime;
  194. }
  195. if (gpGlobals->curtime >= m_flFacingTime + m_flDuration)
  196. {
  197. m_OnFacingLookat.FireOutput(this, this);
  198. m_bFired = true;
  199. }
  200. }
  201. }
  202. else
  203. {
  204. // Reset the fired state
  205. if ( m_bFired )
  206. {
  207. m_bFired = false;
  208. }
  209. // Always reset the time when we've lost our facing
  210. m_flFacingTime = 0;
  211. }
  212. // Output the angle range we're in
  213. float flPerc = RemapValClamped( flDot, 1.0f, m_flDotTolerance, 1.0f, 0.0f );
  214. m_FacingPercentage.Set( flPerc, this, this );
  215. }
  216. SetNextThink( gpGlobals->curtime );
  217. }
  218. }
  219. //-----------------------------------------------------------------------------
  220. // Purpose: Input handler for forcing an instantaneous test of the condition.
  221. //-----------------------------------------------------------------------------
  222. void CPointAngleSensor::InputTest(inputdata_t &inputdata)
  223. {
  224. if (IsFacingWithinTolerance(m_hTargetEntity, m_hLookAtEntity, m_flDotTolerance))
  225. {
  226. m_OnFacingLookat.FireOutput(inputdata.pActivator, this);
  227. }
  228. else
  229. {
  230. m_OnNotFacingLookat.FireOutput(inputdata.pActivator, this);
  231. }
  232. }
  233. //-----------------------------------------------------------------------------
  234. // Purpose:
  235. //-----------------------------------------------------------------------------
  236. void CPointAngleSensor::InputSetTargetEntity(inputdata_t &inputdata)
  237. {
  238. if ((inputdata.value.String() == NULL) || (inputdata.value.StringID() == NULL_STRING) || (inputdata.value.String()[0] == '\0'))
  239. {
  240. m_target = NULL_STRING;
  241. m_hTargetEntity = NULL;
  242. SetNextThink( TICK_NEVER_THINK );
  243. }
  244. else
  245. {
  246. m_target = AllocPooledString(inputdata.value.String());
  247. m_hTargetEntity = gEntList.FindEntityByName( NULL, m_target, NULL, inputdata.pActivator, inputdata.pCaller );
  248. if (!m_bDisabled && m_hTargetEntity)
  249. {
  250. SetNextThink( gpGlobals->curtime );
  251. }
  252. }
  253. }
  254. //-----------------------------------------------------------------------------
  255. // Purpose:
  256. //-----------------------------------------------------------------------------
  257. void CPointAngleSensor::InputEnable(inputdata_t &inputdata)
  258. {
  259. Enable();
  260. }
  261. //-----------------------------------------------------------------------------
  262. // Purpose:
  263. //-----------------------------------------------------------------------------
  264. void CPointAngleSensor::InputDisable(inputdata_t &inputdata)
  265. {
  266. Disable();
  267. }
  268. //-----------------------------------------------------------------------------
  269. // Purpose: I like separators between my functions.
  270. //-----------------------------------------------------------------------------
  271. void CPointAngleSensor::InputToggle(inputdata_t &inputdata)
  272. {
  273. if (m_bDisabled)
  274. {
  275. Enable();
  276. }
  277. else
  278. {
  279. Disable();
  280. }
  281. }
  282. //-----------------------------------------------------------------------------
  283. // Purpose:
  284. //-----------------------------------------------------------------------------
  285. void CPointAngleSensor::Enable()
  286. {
  287. m_bDisabled = false;
  288. if (m_hTargetEntity)
  289. {
  290. SetNextThink(gpGlobals->curtime);
  291. }
  292. }
  293. //-----------------------------------------------------------------------------
  294. // Purpose:
  295. //-----------------------------------------------------------------------------
  296. void CPointAngleSensor::Disable()
  297. {
  298. m_bDisabled = true;
  299. SetNextThink(TICK_NEVER_THINK);
  300. }
  301. //-----------------------------------------------------------------------------
  302. // Purpose:
  303. //-----------------------------------------------------------------------------
  304. int CPointAngleSensor::DrawDebugTextOverlays(void)
  305. {
  306. int nOffset = BaseClass::DrawDebugTextOverlays();
  307. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  308. {
  309. float flDot;
  310. bool bFacing = IsFacingWithinTolerance(m_hTargetEntity, m_hLookAtEntity, m_flDotTolerance, &flDot);
  311. char tempstr[512];
  312. Q_snprintf(tempstr, sizeof(tempstr), "delta ang (dot) : %.2f (%f)", RAD2DEG(acos(flDot)), flDot);
  313. EntityText( nOffset, tempstr, 0);
  314. nOffset++;
  315. Q_snprintf(tempstr, sizeof(tempstr), "tolerance ang (dot): %.2f (%f)", RAD2DEG(acos(m_flDotTolerance)), m_flDotTolerance);
  316. EntityText( nOffset, tempstr, 0);
  317. nOffset++;
  318. Q_snprintf(tempstr, sizeof(tempstr), "facing: %s", bFacing ? "yes" : "no");
  319. EntityText( nOffset, tempstr, 0);
  320. nOffset++;
  321. }
  322. return nOffset;
  323. }
  324. // ====================================================================
  325. // Proximity sensor
  326. // ====================================================================
  327. #define SF_PROXIMITY_TEST_AGAINST_AXIS (1<<0)
  328. class CPointProximitySensor : public CPointEntity
  329. {
  330. DECLARE_CLASS( CPointProximitySensor, CPointEntity );
  331. public:
  332. virtual void Activate( void );
  333. protected:
  334. void Think( void );
  335. void Enable( void );
  336. void Disable( void );
  337. // Input handlers
  338. void InputEnable(inputdata_t &inputdata);
  339. void InputDisable(inputdata_t &inputdata);
  340. void InputToggle(inputdata_t &inputdata);
  341. void InputSetTargetEntity(inputdata_t &inputdata);
  342. private:
  343. bool m_bDisabled; // When disabled, we do not think or fire outputs.
  344. EHANDLE m_hTargetEntity; // Entity whose angles are being monitored.
  345. COutputFloat m_Distance;
  346. DECLARE_DATADESC();
  347. };
  348. LINK_ENTITY_TO_CLASS( point_proximity_sensor, CPointProximitySensor );
  349. BEGIN_DATADESC( CPointProximitySensor )
  350. // Keys
  351. DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
  352. DEFINE_FIELD( m_hTargetEntity, FIELD_EHANDLE ),
  353. // Outputs
  354. DEFINE_OUTPUT( m_Distance, "Distance"),
  355. // Inputs
  356. DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable),
  357. DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable),
  358. DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle),
  359. DEFINE_INPUTFUNC(FIELD_STRING, "SetTargetEntity", InputSetTargetEntity),
  360. END_DATADESC()
  361. //-----------------------------------------------------------------------------
  362. // Purpose: Called after all entities have spawned on new map or savegame load.
  363. //-----------------------------------------------------------------------------
  364. void CPointProximitySensor::Activate( void )
  365. {
  366. BaseClass::Activate();
  367. if ( m_hTargetEntity == NULL )
  368. {
  369. m_hTargetEntity = gEntList.FindEntityByName( NULL, m_target );
  370. }
  371. if ( m_bDisabled == false && m_hTargetEntity != NULL )
  372. {
  373. SetNextThink( gpGlobals->curtime );
  374. }
  375. }
  376. //-----------------------------------------------------------------------------
  377. // Purpose:
  378. //-----------------------------------------------------------------------------
  379. void CPointProximitySensor::InputSetTargetEntity(inputdata_t &inputdata)
  380. {
  381. if ((inputdata.value.String() == NULL) || (inputdata.value.StringID() == NULL_STRING) || (inputdata.value.String()[0] == '\0'))
  382. {
  383. m_target = NULL_STRING;
  384. m_hTargetEntity = NULL;
  385. SetNextThink( TICK_NEVER_THINK );
  386. }
  387. else
  388. {
  389. m_target = AllocPooledString(inputdata.value.String());
  390. m_hTargetEntity = gEntList.FindEntityByName( NULL, m_target, NULL, inputdata.pActivator, inputdata.pCaller );
  391. if (!m_bDisabled && m_hTargetEntity)
  392. {
  393. SetNextThink( gpGlobals->curtime );
  394. }
  395. }
  396. }
  397. //-----------------------------------------------------------------------------
  398. // Purpose:
  399. //-----------------------------------------------------------------------------
  400. void CPointProximitySensor::InputEnable( inputdata_t &inputdata )
  401. {
  402. Enable();
  403. }
  404. //-----------------------------------------------------------------------------
  405. // Purpose:
  406. //-----------------------------------------------------------------------------
  407. void CPointProximitySensor::InputDisable( inputdata_t &inputdata )
  408. {
  409. Disable();
  410. }
  411. //-----------------------------------------------------------------------------
  412. // Purpose:
  413. //-----------------------------------------------------------------------------
  414. void CPointProximitySensor::InputToggle( inputdata_t &inputdata )
  415. {
  416. if ( m_bDisabled )
  417. {
  418. Enable();
  419. }
  420. else
  421. {
  422. Disable();
  423. }
  424. }
  425. //-----------------------------------------------------------------------------
  426. // Purpose:
  427. //-----------------------------------------------------------------------------
  428. void CPointProximitySensor::Enable( void )
  429. {
  430. m_bDisabled = false;
  431. if ( m_hTargetEntity )
  432. {
  433. SetNextThink( gpGlobals->curtime );
  434. }
  435. }
  436. //-----------------------------------------------------------------------------
  437. // Purpose:
  438. //-----------------------------------------------------------------------------
  439. void CPointProximitySensor::Disable( void )
  440. {
  441. m_bDisabled = true;
  442. SetNextThink( TICK_NEVER_THINK );
  443. }
  444. //-----------------------------------------------------------------------------
  445. // Purpose: Called every frame
  446. //-----------------------------------------------------------------------------
  447. void CPointProximitySensor::Think( void )
  448. {
  449. if ( m_hTargetEntity != NULL )
  450. {
  451. Vector vecTestDir = ( m_hTargetEntity->GetAbsOrigin() - GetAbsOrigin() );
  452. float flDist = VectorNormalize( vecTestDir );
  453. // If we're only interested in the distance along a vector, modify the length the accomodate that
  454. if ( HasSpawnFlags( SF_PROXIMITY_TEST_AGAINST_AXIS ) )
  455. {
  456. Vector vecDir;
  457. GetVectors( &vecDir, NULL, NULL );
  458. float flDot = DotProduct( vecTestDir, vecDir );
  459. flDist *= fabs( flDot );
  460. }
  461. m_Distance.Set( flDist, this, this );
  462. SetNextThink( gpGlobals->curtime );
  463. }
  464. }