ItemToggleSystem.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. using Content.Shared.Interaction;
  2. using Content.Shared.Interaction.Events;
  3. using Content.Shared.Item.ItemToggle.Components;
  4. using Content.Shared.Popups;
  5. using Content.Shared.Temperature;
  6. using Content.Shared.Toggleable;
  7. using Content.Shared.Verbs;
  8. using Content.Shared.Wieldable;
  9. using Robust.Shared.Audio;
  10. using Robust.Shared.Audio.Systems;
  11. using Robust.Shared.Network;
  12. namespace Content.Shared.Item.ItemToggle;
  13. /// <summary>
  14. /// Handles generic item toggles, like a welder turning on and off, or an e-sword.
  15. /// </summary>
  16. /// <remarks>
  17. /// If you need extended functionality (e.g. requiring power) then add a new component and use events.
  18. /// </remarks>
  19. public sealed class ItemToggleSystem : EntitySystem
  20. {
  21. [Dependency] private readonly INetManager _netManager = default!;
  22. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  23. [Dependency] private readonly SharedAudioSystem _audio = default!;
  24. [Dependency] private readonly SharedPopupSystem _popup = default!;
  25. private EntityQuery<ItemToggleComponent> _query;
  26. public override void Initialize()
  27. {
  28. base.Initialize();
  29. _query = GetEntityQuery<ItemToggleComponent>();
  30. SubscribeLocalEvent<ItemToggleComponent, ComponentStartup>(OnStartup);
  31. SubscribeLocalEvent<ItemToggleComponent, MapInitEvent>(OnMapInit);
  32. SubscribeLocalEvent<ItemToggleComponent, ItemUnwieldedEvent>(TurnOffOnUnwielded);
  33. SubscribeLocalEvent<ItemToggleComponent, ItemWieldedEvent>(TurnOnOnWielded);
  34. SubscribeLocalEvent<ItemToggleComponent, UseInHandEvent>(OnUseInHand);
  35. SubscribeLocalEvent<ItemToggleComponent, GetVerbsEvent<ActivationVerb>>(OnActivateVerb);
  36. SubscribeLocalEvent<ItemToggleComponent, ActivateInWorldEvent>(OnActivate);
  37. SubscribeLocalEvent<ItemToggleHotComponent, IsHotEvent>(OnIsHotEvent);
  38. SubscribeLocalEvent<ItemToggleActiveSoundComponent, ItemToggledEvent>(UpdateActiveSound);
  39. }
  40. private void OnStartup(Entity<ItemToggleComponent> ent, ref ComponentStartup args)
  41. {
  42. UpdateVisuals(ent);
  43. }
  44. private void OnMapInit(Entity<ItemToggleComponent> ent, ref MapInitEvent args)
  45. {
  46. if (!ent.Comp.Activated)
  47. return;
  48. var ev = new ItemToggledEvent(Predicted: ent.Comp.Predictable, Activated: ent.Comp.Activated, User: null);
  49. RaiseLocalEvent(ent, ref ev);
  50. }
  51. private void OnUseInHand(Entity<ItemToggleComponent> ent, ref UseInHandEvent args)
  52. {
  53. if (args.Handled || !ent.Comp.OnUse)
  54. return;
  55. args.Handled = true;
  56. Toggle((ent, ent.Comp), args.User, predicted: ent.Comp.Predictable);
  57. }
  58. private void OnActivateVerb(Entity<ItemToggleComponent> ent, ref GetVerbsEvent<ActivationVerb> args)
  59. {
  60. if (!args.CanAccess || !args.CanInteract || !ent.Comp.OnActivate)
  61. return;
  62. var user = args.User;
  63. args.Verbs.Add(new ActivationVerb()
  64. {
  65. Text = !ent.Comp.Activated ? Loc.GetString(ent.Comp.VerbToggleOn) : Loc.GetString(ent.Comp.VerbToggleOff),
  66. Act = () =>
  67. {
  68. Toggle((ent.Owner, ent.Comp), user, predicted: ent.Comp.Predictable);
  69. }
  70. });
  71. }
  72. private void OnActivate(Entity<ItemToggleComponent> ent, ref ActivateInWorldEvent args)
  73. {
  74. if (args.Handled || !ent.Comp.OnActivate)
  75. return;
  76. args.Handled = true;
  77. Toggle((ent.Owner, ent.Comp), args.User, predicted: ent.Comp.Predictable);
  78. }
  79. /// <summary>
  80. /// Used when an item is attempted to be toggled.
  81. /// Sets its state to the opposite of what it is.
  82. /// </summary>
  83. /// <returns>Same as <see cref="TrySetActive"/></returns>
  84. public bool Toggle(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
  85. {
  86. if (!_query.Resolve(ent, ref ent.Comp, false))
  87. return false;
  88. return TrySetActive(ent, !ent.Comp.Activated, user, predicted);
  89. }
  90. /// <summary>
  91. /// Tries to set the activated bool from a value.
  92. /// </summary>
  93. /// <returns>false if the attempt fails for any reason</returns>
  94. public bool TrySetActive(Entity<ItemToggleComponent?> ent, bool active, EntityUid? user = null, bool predicted = true)
  95. {
  96. if (active)
  97. return TryActivate(ent, user, predicted: predicted);
  98. else
  99. return TryDeactivate(ent, user, predicted: predicted);
  100. }
  101. /// <summary>
  102. /// Used when an item is attempting to be activated. It returns false if the attempt fails any reason, interrupting the activation.
  103. /// </summary>
  104. public bool TryActivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
  105. {
  106. if (!_query.Resolve(ent, ref ent.Comp, false))
  107. return false;
  108. var uid = ent.Owner;
  109. var comp = ent.Comp;
  110. if (comp.Activated)
  111. return true;
  112. if (!comp.Predictable && _netManager.IsClient)
  113. return true;
  114. var attempt = new ItemToggleActivateAttemptEvent(user);
  115. RaiseLocalEvent(uid, ref attempt);
  116. if (!comp.Predictable) predicted = false;
  117. if (attempt.Cancelled)
  118. {
  119. if (predicted)
  120. _audio.PlayPredicted(comp.SoundFailToActivate, uid, user);
  121. else
  122. _audio.PlayPvs(comp.SoundFailToActivate, uid);
  123. if (attempt.Popup != null && user != null)
  124. {
  125. if (predicted)
  126. _popup.PopupClient(attempt.Popup, uid, user.Value);
  127. else
  128. _popup.PopupEntity(attempt.Popup, uid, user.Value);
  129. }
  130. return false;
  131. }
  132. Activate((uid, comp), predicted, user);
  133. return true;
  134. }
  135. /// <summary>
  136. /// Used when an item is attempting to be deactivated. It returns false if the attempt fails any reason, interrupting the deactivation.
  137. /// </summary>
  138. public bool TryDeactivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
  139. {
  140. if (!_query.Resolve(ent, ref ent.Comp, false))
  141. return false;
  142. var uid = ent.Owner;
  143. var comp = ent.Comp;
  144. if (!comp.Activated)
  145. return true;
  146. if (!comp.Predictable && _netManager.IsClient)
  147. return true;
  148. var attempt = new ItemToggleDeactivateAttemptEvent(user);
  149. RaiseLocalEvent(uid, ref attempt);
  150. if (attempt.Cancelled)
  151. return false;
  152. if (!comp.Predictable) predicted = false;
  153. Deactivate((uid, comp), predicted, user);
  154. return true;
  155. }
  156. private void Activate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null)
  157. {
  158. var (uid, comp) = ent;
  159. var soundToPlay = comp.SoundActivate;
  160. if (predicted)
  161. _audio.PlayPredicted(soundToPlay, uid, user);
  162. else
  163. _audio.PlayPvs(soundToPlay, uid);
  164. comp.Activated = true;
  165. UpdateVisuals((uid, comp));
  166. Dirty(uid, comp);
  167. var toggleUsed = new ItemToggledEvent(predicted, Activated: true, user);
  168. RaiseLocalEvent(uid, ref toggleUsed);
  169. }
  170. /// <summary>
  171. /// Used to make the actual changes to the item's components on deactivation.
  172. /// </summary>
  173. private void Deactivate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null)
  174. {
  175. var (uid, comp) = ent;
  176. var soundToPlay = comp.SoundDeactivate;
  177. if (predicted)
  178. _audio.PlayPredicted(soundToPlay, uid, user);
  179. else
  180. _audio.PlayPvs(soundToPlay, uid);
  181. comp.Activated = false;
  182. UpdateVisuals((uid, comp));
  183. Dirty(uid, comp);
  184. var toggleUsed = new ItemToggledEvent(predicted, Activated: false, user);
  185. RaiseLocalEvent(uid, ref toggleUsed);
  186. }
  187. private void UpdateVisuals(Entity<ItemToggleComponent> ent)
  188. {
  189. if (TryComp(ent, out AppearanceComponent? appearance))
  190. {
  191. _appearance.SetData(ent, ToggleVisuals.Toggled, ent.Comp.Activated, appearance);
  192. }
  193. }
  194. /// <summary>
  195. /// Used for items that require to be wielded in both hands to activate. For instance the dual energy sword will turn off if not wielded.
  196. /// </summary>
  197. private void TurnOffOnUnwielded(Entity<ItemToggleComponent> ent, ref ItemUnwieldedEvent args)
  198. {
  199. TryDeactivate((ent, ent.Comp), args.User);
  200. }
  201. /// <summary>
  202. /// Wieldable items will automatically turn on when wielded.
  203. /// </summary>
  204. private void TurnOnOnWielded(Entity<ItemToggleComponent> ent, ref ItemWieldedEvent args)
  205. {
  206. // FIXME: for some reason both client and server play sound
  207. TryActivate((ent, ent.Comp));
  208. }
  209. public bool IsActivated(Entity<ItemToggleComponent?> ent)
  210. {
  211. if (!_query.Resolve(ent, ref ent.Comp, false))
  212. return true; // assume always activated if no component
  213. return ent.Comp.Activated;
  214. }
  215. /// <summary>
  216. /// Used to make the item hot when activated.
  217. /// </summary>
  218. private void OnIsHotEvent(Entity<ItemToggleHotComponent> ent, ref IsHotEvent args)
  219. {
  220. args.IsHot |= IsActivated(ent.Owner);
  221. }
  222. /// <summary>
  223. /// Used to update the looping active sound linked to the entity.
  224. /// </summary>
  225. private void UpdateActiveSound(Entity<ItemToggleActiveSoundComponent> ent, ref ItemToggledEvent args)
  226. {
  227. var (uid, comp) = ent;
  228. if (!args.Activated)
  229. {
  230. comp.PlayingStream = _audio.Stop(comp.PlayingStream);
  231. return;
  232. }
  233. if (comp.ActiveSound != null && comp.PlayingStream == null)
  234. {
  235. var loop = comp.ActiveSound.Params.WithLoop(true);
  236. var stream = args.Predicted
  237. ? _audio.PlayPredicted(comp.ActiveSound, uid, args.User, loop)
  238. : _audio.PlayPvs(comp.ActiveSound, uid, loop);
  239. if (stream?.Entity is {} entity)
  240. comp.PlayingStream = entity;
  241. }
  242. }
  243. }