trigger.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. //! The object which is in a trigger and its metadata
  2. class TriggerInsider
  3. {
  4. ref OLinkT insider; // DEPRECATED
  5. //! Object that data belongs to
  6. protected Object m_Object;
  7. //! Last time the object was seen in ms
  8. int timeStamp;
  9. //! Time the object was first seen in seconds
  10. float timeEntered;
  11. //! Last time the object was updated in seconds, is used for calculating deltaTime
  12. float lastUpdated;
  13. void TriggerInsider(Object obj)
  14. {
  15. insider = new OLinkT(obj);
  16. m_Object = obj;
  17. }
  18. void ~TriggerInsider()
  19. {
  20. }
  21. Object GetObject()
  22. {
  23. return m_Object;
  24. }
  25. };
  26. #ifdef DIAG_DEVELOPER
  27. typedef Param7<vector, vector, vector, vector, float, string, array<ref TriggerInsider>> DebugTriggerInfo;
  28. #endif
  29. //! Scripted Trigger
  30. class Trigger : TriggerEvents
  31. {
  32. //! DEPRECATED
  33. const int TIMEOUT = 1000;
  34. //! The objects and their metadata which are currently inside the Trigger
  35. ref array<ref TriggerInsider> m_insiders;
  36. #ifdef DIAG_DEVELOPER
  37. bool m_Local;//is this trigger spawning on client only ?
  38. string m_DebugAreaType;
  39. ref array<ref TriggerInsider> m_dbgInsiders;
  40. #endif
  41. //! ctor
  42. private void Trigger()
  43. {
  44. SetEventMask(EntityEvent.INIT /*| EntityEvent.TOUCH*/ | EntityEvent.FRAME | EntityEvent.ENTER | EntityEvent.LEAVE );
  45. SetFlags(EntityFlags.TRIGGER, false);
  46. m_insiders = new array<ref TriggerInsider>;
  47. }
  48. //! dtor
  49. private void ~Trigger()
  50. {
  51. #ifdef DIAG_DEVELOPER
  52. CleanupDebugShapes(dbgTargets);
  53. #endif
  54. }
  55. /** \name IEntity events
  56. Usage of IEntity events
  57. */
  58. //@{
  59. //! Set the default extents of the Trigger only once it is properly initialized
  60. override void EOnInit(IEntity other, int extra)
  61. {
  62. SetExtents("-2 -4 -2", "2 4 2");
  63. }
  64. //! When an Object touches the Trigger, we want to register it being inside the Trigger -> Replaced by EOnEnter
  65. /*override void EOnTouch(IEntity other, int extra)
  66. {
  67. Object obj;
  68. if (Class.CastTo(obj, other) && CanAddObjectAsInsider(obj))
  69. AddInsider(obj);
  70. }*/
  71. //! We want to update the insiders every frame (or do we?)
  72. override void EOnFrame(IEntity other, float timeSlice)
  73. {
  74. UpdateInsiders(TIMEOUT);
  75. }
  76. //! When an Object enters the trigger add it to Insiders
  77. override void EOnEnter(IEntity other, int extra)
  78. {
  79. Object obj;
  80. if (Class.CastTo(obj, other) && CanAddObjectAsInsider(obj))
  81. AddInsider(obj);
  82. }
  83. //! When an Object exits the trigger remove it from Insiders
  84. override void EOnLeave(IEntity other, int extra)
  85. {
  86. Object obj;
  87. if (Class.CastTo(obj, other))
  88. RemoveInsiderByObject(obj);
  89. }
  90. //@}
  91. /** \name Trigger configuration
  92. Basic configuration and data API
  93. */
  94. //@{
  95. //! Set the size of the Trigger, avoid using SetCollisionBox directly
  96. void SetExtents(vector mins, vector maxs)
  97. {
  98. SetCollisionBox(mins, maxs);
  99. }
  100. //! Get the radius of the CollisionBox, simply left for backwards compatibility
  101. float GetRadius(vector min, vector max)
  102. {
  103. return GetCollisionRadius();
  104. }
  105. //! Get the current TriggerInsider array, left for backwards compatibility, moved down from ManTrigger
  106. array<ref TriggerInsider> GetInsiders()
  107. {
  108. return m_insiders;
  109. }
  110. //! Gets the TriggerInsider for the Object if it exists
  111. TriggerInsider GetInsiderForObject(Object object)
  112. {
  113. TriggerInsider ins;
  114. for ( int n = 0; n < m_insiders.Count(); ++n )
  115. {
  116. ins = m_insiders[n];
  117. if (ins.GetObject() == object)
  118. return ins;
  119. }
  120. return null;
  121. }
  122. //! Gets the index in m_insiders for the Object
  123. int GetInsiderIndexForObject(Object object)
  124. {
  125. TriggerInsider ins;
  126. for ( int n = 0; n < m_insiders.Count(); ++n )
  127. {
  128. ins = m_insiders[n];
  129. if (ins.GetObject() == object)
  130. return n;
  131. }
  132. return -1;
  133. }
  134. //@}
  135. /** \name TriggerEvents, backwards compatibility calling
  136. Implementation for backwards compatibility with old Trigger system
  137. */
  138. //@{
  139. override protected void OnEnterBeginEvent(TriggerInsider insider)
  140. {
  141. // Call the old event for backwards compatibility
  142. OnEnter(insider.GetObject());
  143. }
  144. override protected void OnLeaveBeginEvent(TriggerInsider insider)
  145. {
  146. // Call the old event for backwards compatibility
  147. OnLeave(insider.GetObject());
  148. }
  149. //@}
  150. /** \name DEPRECATED Events
  151. left for backwards compatibility
  152. */
  153. //@{
  154. void OnEnter(Object obj) {}
  155. void OnLeave(Object obj) {}
  156. //@}
  157. /** \name TriggerInsider conditions
  158. Conditions applied to TriggerInsider for if it can be added or should be removed
  159. */
  160. //@{
  161. //! Condition whether an Object can be added as TriggerInsider (checked before calling AddInsider)
  162. protected bool CanAddObjectAsInsider(Object object)
  163. {
  164. return true;
  165. }
  166. //! Condition whether a TriggerInsider should still be updated or not (checked in update loop and before adding)
  167. protected bool ShouldRemoveInsider(TriggerInsider insider)
  168. {
  169. return false;
  170. }
  171. //! Condition whether a TriggerInsider should still be updated or not, skips OnLeaveEvent (checked in update loop and before adding)
  172. protected bool ShouldRemoveInsiderNoLeave(TriggerInsider insider)
  173. {
  174. return false;
  175. }
  176. //@}
  177. /** \name TriggerInsider processing
  178. Logic and processing of adding, removing and updating a TriggerInsider
  179. */
  180. //@{
  181. //! Used for easily overriding TriggerInsider creation without rewriting AddInsider
  182. protected TriggerInsider CreateInsider(Object obj)
  183. {
  184. return new TriggerInsider(obj);
  185. }
  186. //! Adding of new TriggerInsider
  187. protected void AddInsider(Object obj)
  188. {
  189. if ( !obj )
  190. return;
  191. // Already in?
  192. if ( GetInsiderForObject( obj ) )
  193. {
  194. Error(string.Format("[WARNING] :: [Trigger] :: [%1] :: Insider (%2) is already inside.", GetDebugName(this), GetDebugName(obj)));
  195. return;
  196. }
  197. // New Object entered! Fill the data.
  198. TriggerInsider insider = CreateInsider(obj);
  199. insider.timeStamp = g_Game.GetTime();
  200. insider.timeEntered = g_Game.GetTickTime();
  201. insider.lastUpdated = insider.timeEntered;
  202. // Don't add if it is going to be removed anyways..
  203. if ( ShouldRemoveInsider(insider) || ShouldRemoveInsiderNoLeave(insider) )
  204. return;
  205. // Keep track of the Object as long as it is inside the Trigger
  206. int index = m_insiders.Insert(insider);
  207. // Call the enter event to signal this Object entered
  208. Enter(insider);
  209. obj.OnEnterTrigger(this);
  210. #ifdef TRIGGER_DEBUG_NORMAL
  211. Debug.TriggerLog(string.Format("%1: inserted at index %2", GetDebugName(obj), index), "Trigger", "", "AddInsider", GetDebugName(this));
  212. #endif
  213. }
  214. //! Removing of TriggerInsider
  215. protected void RemoveInsider(TriggerInsider insider, int index = -1)
  216. {
  217. Leave(insider);
  218. insider.GetObject().OnLeaveTrigger(this);
  219. #ifdef TRIGGER_DEBUG_NORMAL
  220. Debug.TriggerLog(string.Format("%1: removing at index %2", GetDebugName(insider.GetObject()), index), "Trigger", "", "RemoveInsider", GetDebugName(this));
  221. #endif
  222. if (index >= 0)
  223. m_insiders.Remove(index);
  224. else
  225. m_insiders.RemoveItemUnOrdered(insider);
  226. }
  227. //! Removing of TriggerInsider through Object
  228. protected void RemoveInsiderByObject(Object object)
  229. {
  230. TriggerInsider ins;
  231. for ( int n = 0; n < m_insiders.Count(); ++n )
  232. {
  233. ins = m_insiders[n];
  234. if (ins.GetObject() == object)
  235. {
  236. RemoveInsider(ins, n);
  237. return;
  238. }
  239. }
  240. // As EOnLeave can call this, it is perfectly valid that this Object is not found on Script side
  241. // because of "ShouldRemoveInsider" and "ShouldRemoveInsiderNoLeave"
  242. }
  243. //! Update the current TriggerInsider inside the Trigger, timeout paramter is deprecated
  244. protected void UpdateInsiders(int timeout)
  245. {
  246. #ifdef DIAG_DEVELOPER
  247. DebugSendDmgTrigger();
  248. #endif
  249. // Don't do anything if there aren't any insiders
  250. if ( m_insiders.Count() == 0 )
  251. return;
  252. // Mark the beginning of the update loop
  253. StayStart(m_insiders.Count());
  254. // Iterate over the current insiders, backwards because we are deleting
  255. for ( int n = m_insiders.Count() - 1; n >= 0 ; --n)
  256. {
  257. TriggerInsider insider = m_insiders.Get(n);
  258. Object obj = insider.GetObject();
  259. // Check if the Object still exists or should be removed without calling OnLeaveEvent
  260. if ( !obj || ShouldRemoveInsiderNoLeave(insider) )
  261. {
  262. #ifdef TRIGGER_DEBUG_BASIC
  263. Debug.TriggerLog(string.Format("%1: removed with no Leave.", GetDebugName(obj)), "Trigger", "", "UpdateInsiders", GetDebugName(this));
  264. #endif
  265. m_insiders.Remove(n);
  266. continue;
  267. }
  268. // Check if Object left the Trigger or should be removed regardless
  269. if ( ShouldRemoveInsider(insider) )
  270. {
  271. RemoveInsider(insider, n);
  272. continue;
  273. }
  274. // Call the OnStayEvent, Object is still inside the Trigger and can be updated
  275. // Pass in the time since the Object was last updated (or entered)
  276. float currentTime = g_Game.GetTickTime();
  277. Stay(insider, currentTime - insider.lastUpdated);
  278. insider.lastUpdated = currentTime;
  279. }
  280. // Mark the end of the update loop
  281. StayFinish();
  282. }
  283. //@}
  284. /** \name DEBUGGING
  285. General internal debugging functionality
  286. */
  287. //@{
  288. override void OnRPC(PlayerIdentity sender, int rpc_type, ParamsReadContext ctx)
  289. {
  290. super.OnRPC(sender, rpc_type, ctx);
  291. #ifdef DIAG_DEVELOPER
  292. switch (rpc_type)
  293. {
  294. case ERPCs.DIAG_TRIGGER_DEBUG:
  295. DebugTriggerInfo data = new DebugTriggerInfo(vector.Zero, vector.Zero, vector.Zero, vector.Zero, 0, "", null);
  296. if (ctx.Read(data))
  297. DebugDmgTrigger(data.param1, data.param2, data.param3, data.param4, data.param5, data.param6, data.param7);
  298. break;
  299. }
  300. #endif
  301. }
  302. #ifdef DIAG_DEVELOPER
  303. void DebugSendDmgTrigger()
  304. {
  305. vector minmax[2];
  306. GetCollisionBox(minmax);
  307. DebugTriggerInfo data = new DebugTriggerInfo(vector.Zero, vector.Zero, vector.Zero, vector.Zero, 0, "", null);
  308. data.param1 = GetWorldPosition();
  309. data.param2 = GetOrientation();
  310. data.param3 = minmax[0];
  311. data.param4 = minmax[1];
  312. data.param5 = GetCollisionRadius();
  313. data.param6 = m_DebugAreaType;
  314. data.param7 = m_insiders;
  315. if (GetGame().IsMultiplayer() && GetGame().IsServer())
  316. PluginDiagMenuServer.SendDataToSubscribersServer(this, ESubscriberSystems.TRIGGERS, ERPCs.DIAG_TRIGGER_DEBUG, data, false);
  317. else if (!GetGame().IsMultiplayer() || m_Local)
  318. DebugDmgTrigger(data.param1, data.param2, data.param3, data.param4, data.param5, data.param6, data.param7);
  319. }
  320. protected ref array<Shape> dbgTargets = new array<Shape>();
  321. void DebugDmgTrigger( vector pos, vector orientation, vector min, vector max, float radius, string dmgType, array<ref TriggerInsider> insiders)
  322. {
  323. CleanupDebugShapes(dbgTargets);
  324. bool enableDebug = DiagMenu.GetBool(DiagMenuIDs.TRIGGER_DEBUG);
  325. if (enableDebug)
  326. {
  327. if (GetGame().IsMultiplayer() && GetGame().IsServer())
  328. {
  329. return;
  330. }
  331. vector w_pos, w_pos_sphr, w_pos_lend;
  332. w_pos = pos;
  333. // sphere pos tweaks
  334. w_pos_sphr = w_pos;
  335. // line pos tweaks
  336. w_pos_lend = w_pos;
  337. //Find way to change colour of box depending on ammoType in a more elegant fashion
  338. m_DebugAreaType = dmgType;
  339. Shape dbgShape;
  340. switch ( m_DebugAreaType )
  341. {
  342. case "FireDamage":
  343. dbgShape = DrawDebugShape(pos, min, max, radius, COLOR_RED_A);
  344. break;
  345. case "BarbedWireHit":
  346. dbgShape = DrawDebugShape(pos, min, max, radius, COLOR_BLUE_A);
  347. break;
  348. default:
  349. dbgShape = DrawDebugShape(pos, min, max, radius, COLOR_GREEN_A);
  350. break;
  351. }
  352. if (GetGame().IsMultiplayer() || GetGame().IsServer())
  353. m_dbgInsiders = insiders;
  354. if (m_dbgInsiders.Count() > 0)
  355. {
  356. //Change colour to make state clearer
  357. dbgShape.SetColor(COLOR_YELLOW_A);
  358. for (int i = 0; i < m_dbgInsiders.Count(); i++)
  359. {
  360. EntityAI insider_EAI = EntityAI.Cast(m_dbgInsiders[i].GetObject());
  361. if (insider_EAI)
  362. {
  363. vector insiderPos = insider_EAI.GetWorldPosition() + "0 0.1 0";
  364. dbgTargets.Insert(Debug.DrawArrow(w_pos, insiderPos));
  365. }
  366. }
  367. }
  368. }
  369. }
  370. protected Shape DrawDebugShape(vector pos, vector min, vector max, float radius, int color)
  371. {
  372. Shape dbgShape;
  373. switch (GetTriggerShape())
  374. {
  375. case TriggerShape.BOX:
  376. dbgShape = Debug.DrawBoxEx(min, max, color, ShapeFlags.TRANSP|ShapeFlags.NOZWRITE);
  377. vector mat[4];
  378. GetTransform(mat);
  379. dbgShape.CreateMatrix(mat);
  380. dbgShape.SetMatrix(mat);
  381. break;
  382. case TriggerShape.CYLINDER:
  383. dbgShape = Debug.DrawCylinder(pos, radius, max[1], color, ShapeFlags.TRANSP|ShapeFlags.NOZWRITE);
  384. break;
  385. case TriggerShape.SPHERE:
  386. dbgShape = Debug.DrawSphere(pos, radius, color, ShapeFlags.TRANSP|ShapeFlags.NOZWRITE);
  387. break;
  388. default:
  389. ErrorEx("TriggerShape not found", ErrorExSeverity.WARNING);
  390. break;
  391. }
  392. dbgTargets.Insert(dbgShape);
  393. return dbgShape;
  394. }
  395. protected void CleanupDebugShapes(array<Shape> shapes)
  396. {
  397. foreach (Shape shape : shapes)
  398. {
  399. Debug.RemoveShape(shape);
  400. }
  401. shapes.Clear();
  402. }
  403. #endif
  404. //@}
  405. };