ActivatableUISystem.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. using Content.Shared.ActionBlocker;
  2. using Content.Shared.Administration.Managers;
  3. using Content.Shared.Ghost;
  4. using Content.Shared.Hands;
  5. using Content.Shared.Hands.Components;
  6. using Content.Shared.Hands.EntitySystems;
  7. using Content.Shared.Interaction;
  8. using Content.Shared.Interaction.Events;
  9. using Content.Shared.Popups;
  10. using Content.Shared.Verbs;
  11. using Content.Shared.Whitelist;
  12. using Robust.Shared.Utility;
  13. namespace Content.Shared.UserInterface;
  14. public sealed partial class ActivatableUISystem : EntitySystem
  15. {
  16. [Dependency] private readonly ISharedAdminManager _adminManager = default!;
  17. [Dependency] private readonly ActionBlockerSystem _blockerSystem = default!;
  18. [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
  19. [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
  20. [Dependency] private readonly SharedHandsSystem _hands = default!;
  21. [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
  22. public override void Initialize()
  23. {
  24. base.Initialize();
  25. SubscribeLocalEvent<ActivatableUIComponent, ComponentStartup>(OnStartup);
  26. SubscribeLocalEvent<ActivatableUIComponent, UseInHandEvent>(OnUseInHand);
  27. SubscribeLocalEvent<ActivatableUIComponent, ActivateInWorldEvent>(OnActivate);
  28. SubscribeLocalEvent<ActivatableUIComponent, InteractUsingEvent>(OnInteractUsing);
  29. SubscribeLocalEvent<ActivatableUIComponent, HandDeselectedEvent>(OnHandDeselected);
  30. SubscribeLocalEvent<ActivatableUIComponent, GotUnequippedHandEvent>(OnHandUnequipped);
  31. SubscribeLocalEvent<ActivatableUIComponent, BoundUIClosedEvent>(OnUIClose);
  32. SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<ActivationVerb>>(GetActivationVerb);
  33. SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<Verb>>(GetVerb);
  34. SubscribeLocalEvent<UserInterfaceComponent, OpenUiActionEvent>(OnActionPerform);
  35. InitializePower();
  36. }
  37. private void OnStartup(Entity<ActivatableUIComponent> ent, ref ComponentStartup args)
  38. {
  39. if (ent.Comp.Key == null)
  40. {
  41. Log.Error($"Missing UI Key for entity: {ToPrettyString(ent)}");
  42. return;
  43. }
  44. // TODO BUI
  45. // set interaction range to zero to avoid constant range checks.
  46. //
  47. // if (ent.Comp.InHandsOnly && _uiSystem.TryGetInterfaceData(ent.Owner, ent.Comp.Key, out var data))
  48. // data.InteractionRange = 0;
  49. }
  50. private void OnActionPerform(EntityUid uid, UserInterfaceComponent component, OpenUiActionEvent args)
  51. {
  52. if (args.Handled || args.Key == null)
  53. return;
  54. args.Handled = _uiSystem.TryToggleUi(uid, args.Key, args.Performer);
  55. }
  56. private void GetActivationVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<ActivationVerb> args)
  57. {
  58. if (component.VerbOnly || !ShouldAddVerb(uid, component, args))
  59. return;
  60. args.Verbs.Add(new ActivationVerb
  61. {
  62. Act = () => InteractUI(args.User, uid, component),
  63. Text = Loc.GetString(component.VerbText),
  64. // TODO VERB ICON find a better icon
  65. Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
  66. });
  67. }
  68. private void GetVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<Verb> args)
  69. {
  70. if (!component.VerbOnly || !ShouldAddVerb(uid, component, args))
  71. return;
  72. args.Verbs.Add(new Verb
  73. {
  74. Act = () => InteractUI(args.User, uid, component),
  75. Text = Loc.GetString(component.VerbText),
  76. // TODO VERB ICON find a better icon
  77. Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
  78. });
  79. }
  80. private bool ShouldAddVerb<T>(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<T> args) where T : Verb
  81. {
  82. if (!args.CanAccess)
  83. return false;
  84. if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Using ?? default))
  85. return false;
  86. if (component.RequiresComplex)
  87. {
  88. if (args.Hands == null)
  89. return false;
  90. if (component.InHandsOnly)
  91. {
  92. if (!_hands.IsHolding(args.User, uid, out var hand, args.Hands))
  93. return false;
  94. if (component.RequireActiveHand && args.Hands.ActiveHand != hand)
  95. return false;
  96. }
  97. }
  98. return args.CanInteract || HasComp<GhostComponent>(args.User) && !component.BlockSpectators;
  99. }
  100. private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args)
  101. {
  102. if (args.Handled)
  103. return;
  104. if (component.VerbOnly)
  105. return;
  106. if (component.RequiredItems != null)
  107. return;
  108. args.Handled = InteractUI(args.User, uid, component);
  109. }
  110. private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args)
  111. {
  112. if (args.Handled || !args.Complex)
  113. return;
  114. if (component.VerbOnly)
  115. return;
  116. if (component.RequiredItems != null)
  117. return;
  118. args.Handled = InteractUI(args.User, uid, component);
  119. }
  120. private void OnInteractUsing(EntityUid uid, ActivatableUIComponent component, InteractUsingEvent args)
  121. {
  122. if (args.Handled)
  123. return;
  124. if (component.VerbOnly)
  125. return;
  126. if (component.RequiredItems == null)
  127. return;
  128. if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Used))
  129. return;
  130. args.Handled = InteractUI(args.User, uid, component);
  131. }
  132. private void OnUIClose(EntityUid uid, ActivatableUIComponent component, BoundUIClosedEvent args)
  133. {
  134. var user = args.Actor;
  135. if (user != component.CurrentSingleUser)
  136. return;
  137. if (!Equals(args.UiKey, component.Key))
  138. return;
  139. SetCurrentSingleUser(uid, null, component);
  140. }
  141. private bool InteractUI(EntityUid user, EntityUid uiEntity, ActivatableUIComponent aui)
  142. {
  143. if (aui.Key == null || !_uiSystem.HasUi(uiEntity, aui.Key))
  144. return false;
  145. if (_uiSystem.IsUiOpen(uiEntity, aui.Key, user))
  146. {
  147. _uiSystem.CloseUi(uiEntity, aui.Key, user);
  148. return true;
  149. }
  150. if (!_blockerSystem.CanInteract(user, uiEntity) && (!HasComp<GhostComponent>(user) || aui.BlockSpectators))
  151. return false;
  152. if (aui.RequiresComplex)
  153. {
  154. if (!_blockerSystem.CanComplexInteract(user))
  155. return false;
  156. }
  157. if (aui.InHandsOnly)
  158. {
  159. if (!TryComp(user, out HandsComponent? hands))
  160. return false;
  161. if (!_hands.IsHolding(user, uiEntity, out var hand, hands))
  162. return false;
  163. if (aui.RequireActiveHand && hands.ActiveHand != hand)
  164. return false;
  165. }
  166. if (aui.AdminOnly && !_adminManager.IsAdmin(user))
  167. return false;
  168. if (aui.SingleUser && aui.CurrentSingleUser != null && user != aui.CurrentSingleUser)
  169. {
  170. var message = Loc.GetString("machine-already-in-use", ("machine", uiEntity));
  171. _popupSystem.PopupClient(message, uiEntity, user);
  172. if (_uiSystem.IsUiOpen(uiEntity, aui.Key))
  173. return true;
  174. Log.Error($"Activatable UI has user without being opened? Entity: {ToPrettyString(uiEntity)}. User: {aui.CurrentSingleUser}, Key: {aui.Key}");
  175. }
  176. // If we've gotten this far, fire a cancellable event that indicates someone is about to activate this.
  177. // This is so that stuff can require further conditions (like power).
  178. var oae = new ActivatableUIOpenAttemptEvent(user);
  179. var uae = new UserOpenActivatableUIAttemptEvent(user, uiEntity);
  180. RaiseLocalEvent(user, uae);
  181. RaiseLocalEvent(uiEntity, oae);
  182. if (oae.Cancelled || uae.Cancelled)
  183. return false;
  184. // Give the UI an opportunity to prepare itself if it needs to do anything
  185. // before opening
  186. var bae = new BeforeActivatableUIOpenEvent(user);
  187. RaiseLocalEvent(uiEntity, bae);
  188. SetCurrentSingleUser(uiEntity, user, aui);
  189. _uiSystem.OpenUi(uiEntity, aui.Key, user);
  190. //Let the component know a user opened it so it can do whatever it needs to do
  191. var aae = new AfterActivatableUIOpenEvent(user, user);
  192. RaiseLocalEvent(uiEntity, aae);
  193. return true;
  194. }
  195. public void SetCurrentSingleUser(EntityUid uid, EntityUid? user, ActivatableUIComponent? aui = null)
  196. {
  197. if (!Resolve(uid, ref aui))
  198. return;
  199. if (!aui.SingleUser)
  200. return;
  201. aui.CurrentSingleUser = user;
  202. Dirty(uid, aui);
  203. RaiseLocalEvent(uid, new ActivatableUIPlayerChangedEvent());
  204. }
  205. public void CloseAll(EntityUid uid, ActivatableUIComponent? aui = null)
  206. {
  207. if (!Resolve(uid, ref aui, false))
  208. return;
  209. if (aui.Key == null)
  210. {
  211. Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}");
  212. return;
  213. }
  214. _uiSystem.CloseUi(uid, aui.Key);
  215. }
  216. private void OnHandDeselected(Entity<ActivatableUIComponent> ent, ref HandDeselectedEvent args)
  217. {
  218. if (ent.Comp.InHandsOnly && ent.Comp.RequireActiveHand)
  219. CloseAll(ent, ent);
  220. }
  221. private void OnHandUnequipped(Entity<ActivatableUIComponent> ent, ref GotUnequippedHandEvent args)
  222. {
  223. if (ent.Comp.InHandsOnly)
  224. CloseAll(ent, ent);
  225. }
  226. }