StickySystem.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. using Content.Shared.DoAfter;
  2. using Content.Shared.Hands.EntitySystems;
  3. using Content.Shared.Interaction;
  4. using Content.Shared.Popups;
  5. using Content.Shared.Sticky.Components;
  6. using Content.Shared.Verbs;
  7. using Content.Shared.Whitelist;
  8. using Robust.Shared.Containers;
  9. namespace Content.Shared.Sticky.Systems;
  10. public sealed class StickySystem : EntitySystem
  11. {
  12. [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
  13. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  14. [Dependency] private readonly SharedContainerSystem _container = default!;
  15. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  16. [Dependency] private readonly SharedHandsSystem _hands = default!;
  17. [Dependency] private readonly SharedInteractionSystem _interaction = default!;
  18. [Dependency] private readonly SharedPopupSystem _popup = default!;
  19. private const string StickerSlotId = "stickers_container";
  20. public override void Initialize()
  21. {
  22. base.Initialize();
  23. SubscribeLocalEvent<StickyComponent, AfterInteractEvent>(OnAfterInteract);
  24. SubscribeLocalEvent<StickyComponent, StickyDoAfterEvent>(OnStickyDoAfter);
  25. SubscribeLocalEvent<StickyComponent, GetVerbsEvent<Verb>>(OnGetVerbs);
  26. }
  27. private void OnAfterInteract(Entity<StickyComponent> ent, ref AfterInteractEvent args)
  28. {
  29. if (args.Handled || !args.CanReach || args.Target is not {} target)
  30. return;
  31. // try stick object to a clicked target entity
  32. args.Handled = StartSticking(ent, target, args.User);
  33. }
  34. private void OnGetVerbs(Entity<StickyComponent> ent, ref GetVerbsEvent<Verb> args)
  35. {
  36. var (uid, comp) = ent;
  37. if (comp.StuckTo == null || !comp.CanUnstick || !args.CanInteract || args.Hands == null)
  38. return;
  39. // we can't use args.CanAccess, because it stuck in another container
  40. // we also need to ignore entity that it stuck to
  41. var user = args.User;
  42. var inRange = _interaction.InRangeUnobstructed(uid, user,
  43. predicate: entity => comp.StuckTo == entity);
  44. if (!inRange)
  45. return;
  46. args.Verbs.Add(new Verb
  47. {
  48. DoContactInteraction = true,
  49. Text = Loc.GetString(comp.VerbText),
  50. Icon = comp.VerbIcon,
  51. Act = () => StartUnsticking(ent, user)
  52. });
  53. }
  54. private bool StartSticking(Entity<StickyComponent> ent, EntityUid target, EntityUid user)
  55. {
  56. var (uid, comp) = ent;
  57. // check whitelist and blacklist
  58. if (_whitelist.IsWhitelistFail(comp.Whitelist, target) ||
  59. _whitelist.IsBlacklistPass(comp.Blacklist, target))
  60. return false;
  61. var attemptEv = new AttemptEntityStickEvent(target, user);
  62. RaiseLocalEvent(uid, ref attemptEv);
  63. if (attemptEv.Cancelled)
  64. return false;
  65. // skip doafter and popup if it's instant
  66. if (comp.StickDelay <= TimeSpan.Zero)
  67. {
  68. StickToEntity(ent, target, user);
  69. return true;
  70. }
  71. // show message to user
  72. if (comp.StickPopupStart != null)
  73. {
  74. var msg = Loc.GetString(comp.StickPopupStart);
  75. _popup.PopupClient(msg, user, user);
  76. }
  77. // start sticking object to target
  78. _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, comp.StickDelay, new StickyDoAfterEvent(), uid, target: target, used: uid)
  79. {
  80. BreakOnMove = true,
  81. NeedHand = true,
  82. });
  83. return true;
  84. }
  85. private void OnStickyDoAfter(Entity<StickyComponent> ent, ref StickyDoAfterEvent args)
  86. {
  87. // target is the sticky item when unsticking and the surface when sticking, it will never be null
  88. if (args.Handled || args.Cancelled || args.Args.Target is not {} target)
  89. return;
  90. var user = args.User;
  91. if (ent.Comp.StuckTo == null)
  92. StickToEntity(ent, target, user);
  93. else
  94. UnstickFromEntity(ent, user);
  95. args.Handled = true;
  96. }
  97. private void StartUnsticking(Entity<StickyComponent> ent, EntityUid user)
  98. {
  99. var (uid, comp) = ent;
  100. if (comp.StuckTo is not {} stuckTo)
  101. return;
  102. var attemptEv = new AttemptEntityUnstickEvent(stuckTo, user);
  103. RaiseLocalEvent(uid, ref attemptEv);
  104. if (attemptEv.Cancelled)
  105. return;
  106. // skip doafter and popup if it's instant
  107. if (comp.UnstickDelay <= TimeSpan.Zero)
  108. {
  109. UnstickFromEntity(ent, user);
  110. return;
  111. }
  112. // show message to user
  113. if (comp.UnstickPopupStart != null)
  114. {
  115. var msg = Loc.GetString(comp.UnstickPopupStart);
  116. _popup.PopupClient(msg, user, user);
  117. }
  118. // start unsticking object
  119. _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, comp.UnstickDelay, new StickyDoAfterEvent(), uid, target: uid)
  120. {
  121. BreakOnMove = true,
  122. NeedHand = true,
  123. });
  124. }
  125. public void StickToEntity(Entity<StickyComponent> ent, EntityUid target, EntityUid user)
  126. {
  127. var (uid, comp) = ent;
  128. var attemptEv = new AttemptEntityStickEvent(target, user);
  129. RaiseLocalEvent(uid, ref attemptEv);
  130. if (attemptEv.Cancelled)
  131. return;
  132. // add container to entity and insert sticker into it
  133. var container = _container.EnsureContainer<Container>(target, StickerSlotId);
  134. container.ShowContents = true;
  135. if (!_container.Insert(uid, container))
  136. return;
  137. // show message to user
  138. if (comp.StickPopupSuccess != null)
  139. {
  140. var msg = Loc.GetString(comp.StickPopupSuccess);
  141. _popup.PopupClient(msg, user, user);
  142. }
  143. // send information to appearance that entity is stuck
  144. _appearance.SetData(uid, StickyVisuals.IsStuck, true);
  145. comp.StuckTo = target;
  146. Dirty(uid, comp);
  147. var ev = new EntityStuckEvent(target, user);
  148. RaiseLocalEvent(uid, ref ev);
  149. }
  150. public void UnstickFromEntity(Entity<StickyComponent> ent, EntityUid user)
  151. {
  152. var (uid, comp) = ent;
  153. if (comp.StuckTo is not {} stuckTo)
  154. return;
  155. var attemptEv = new AttemptEntityUnstickEvent(stuckTo, user);
  156. RaiseLocalEvent(uid, ref attemptEv);
  157. if (attemptEv.Cancelled)
  158. return;
  159. // try to remove sticky item from target container
  160. if (!_container.TryGetContainer(stuckTo, StickerSlotId, out var container) || !_container.Remove(uid, container))
  161. return;
  162. // delete container if it's now empty
  163. if (container.ContainedEntities.Count == 0)
  164. _container.ShutdownContainer(container);
  165. // try place dropped entity into user hands
  166. _hands.PickupOrDrop(user, uid);
  167. // send information to appearance that entity isn't stuck
  168. _appearance.SetData(uid, StickyVisuals.IsStuck, false);
  169. // show message to user
  170. if (comp.UnstickPopupSuccess != null)
  171. {
  172. var msg = Loc.GetString(comp.UnstickPopupSuccess);
  173. _popup.PopupClient(msg, user, user);
  174. }
  175. comp.StuckTo = null;
  176. Dirty(uid, comp);
  177. var ev = new EntityUnstuckEvent(stuckTo, user);
  178. RaiseLocalEvent(uid, ref ev);
  179. }
  180. }