SharedFultonSystem.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. using System.Numerics;
  2. using Content.Shared.DoAfter;
  3. using Content.Shared.Examine;
  4. using Content.Shared.Foldable;
  5. using Content.Shared.Interaction;
  6. using Content.Shared.Popups;
  7. using Content.Shared.Stacks;
  8. using Content.Shared.Verbs;
  9. using Content.Shared.Whitelist;
  10. using Robust.Shared.Audio;
  11. using Robust.Shared.Audio.Systems;
  12. using Robust.Shared.Containers;
  13. using Robust.Shared.Map;
  14. using Robust.Shared.Prototypes;
  15. using Robust.Shared.Serialization;
  16. using Robust.Shared.Timing;
  17. namespace Content.Shared.Salvage.Fulton;
  18. /// <summary>
  19. /// Provides extraction devices that teleports the attached entity after <see cref="FultonDuration"/> elapses to the linked beacon.
  20. /// </summary>
  21. public abstract partial class SharedFultonSystem : EntitySystem
  22. {
  23. [Dependency] protected readonly IGameTiming Timing = default!;
  24. [Dependency] private readonly MetaDataSystem _metadata = default!;
  25. [Dependency] protected readonly SharedAudioSystem Audio = default!;
  26. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  27. [Dependency] private readonly FoldableSystem _foldable = default!;
  28. [Dependency] protected readonly SharedContainerSystem Container = default!;
  29. [Dependency] private readonly SharedPopupSystem _popup = default!;
  30. [Dependency] private readonly SharedStackSystem _stack = default!;
  31. [Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
  32. [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
  33. [ValidatePrototypeId<EntityPrototype>] public const string EffectProto = "FultonEffect";
  34. protected static readonly Vector2 EffectOffset = Vector2.Zero;
  35. public override void Initialize()
  36. {
  37. base.Initialize();
  38. SubscribeLocalEvent<FultonedDoAfterEvent>(OnFultonDoAfter);
  39. SubscribeLocalEvent<FultonedComponent, GetVerbsEvent<InteractionVerb>>(OnFultonedGetVerbs);
  40. SubscribeLocalEvent<FultonedComponent, ExaminedEvent>(OnFultonedExamine);
  41. SubscribeLocalEvent<FultonedComponent, EntGotInsertedIntoContainerMessage>(OnFultonContainerInserted);
  42. SubscribeLocalEvent<FultonComponent, AfterInteractEvent>(OnFultonInteract);
  43. SubscribeLocalEvent<FultonComponent, StackSplitEvent>(OnFultonSplit);
  44. }
  45. private void OnFultonContainerInserted(EntityUid uid, FultonedComponent component, EntGotInsertedIntoContainerMessage args)
  46. {
  47. RemCompDeferred<FultonedComponent>(uid);
  48. }
  49. private void OnFultonedExamine(EntityUid uid, FultonedComponent component, ExaminedEvent args)
  50. {
  51. var remaining = component.NextFulton + _metadata.GetPauseTime(uid) - Timing.CurTime;
  52. var message = Loc.GetString("fulton-examine", ("time", $"{remaining.TotalSeconds:0.00}"));
  53. args.PushText(message);
  54. }
  55. private void OnFultonedGetVerbs(EntityUid uid, FultonedComponent component, GetVerbsEvent<InteractionVerb> args)
  56. {
  57. if (!args.CanAccess || !args.CanInteract)
  58. return;
  59. args.Verbs.Add(new InteractionVerb()
  60. {
  61. Text = Loc.GetString("fulton-remove"),
  62. Act = () =>
  63. {
  64. Unfulton(uid);
  65. }
  66. });
  67. }
  68. private void Unfulton(EntityUid uid, FultonedComponent? component = null)
  69. {
  70. if (!Resolve(uid, ref component, false) || !component.Removeable)
  71. return;
  72. RemCompDeferred<FultonedComponent>(uid);
  73. }
  74. private void OnFultonDoAfter(FultonedDoAfterEvent args)
  75. {
  76. if (args.Cancelled || args.Target == null || !TryComp<FultonComponent>(args.Used, out var fulton))
  77. return;
  78. if (!_stack.Use(args.Used.Value, 1))
  79. {
  80. return;
  81. }
  82. var fultoned = AddComp<FultonedComponent>(args.Target.Value);
  83. fultoned.Beacon = fulton.Beacon;
  84. fultoned.NextFulton = Timing.CurTime + fulton.FultonDuration;
  85. fultoned.FultonDuration = fulton.FultonDuration;
  86. fultoned.Removeable = fulton.Removeable;
  87. UpdateAppearance(args.Target.Value, fultoned);
  88. Dirty(args.Target.Value, fultoned);
  89. Audio.PlayPredicted(fulton.FultonSound, args.Target.Value, args.User);
  90. }
  91. private void OnFultonInteract(EntityUid uid, FultonComponent component, AfterInteractEvent args)
  92. {
  93. if (args.Target == null || args.Handled || !args.CanReach)
  94. return;
  95. if (TryComp<FultonBeaconComponent>(args.Target, out var beacon))
  96. {
  97. if (!_foldable.IsFolded(args.Target.Value))
  98. {
  99. component.Beacon = args.Target.Value;
  100. Audio.PlayPredicted(beacon.LinkSound, uid, args.User);
  101. _popup.PopupClient(Loc.GetString("fulton-linked"), uid, args.User);
  102. }
  103. else
  104. {
  105. component.Beacon = EntityUid.Invalid;
  106. _popup.PopupClient(Loc.GetString("fulton-folded"), uid, args.User);
  107. }
  108. return;
  109. }
  110. if (Deleted(component.Beacon))
  111. {
  112. _popup.PopupClient(Loc.GetString("fulton-not-found"), uid, args.User);
  113. return;
  114. }
  115. if (!CanApplyFulton(args.Target.Value, component))
  116. {
  117. _popup.PopupClient(Loc.GetString("fulton-invalid"), uid, uid);
  118. return;
  119. }
  120. if (HasComp<FultonedComponent>(args.Target))
  121. {
  122. _popup.PopupClient(Loc.GetString("fulton-fultoned"), uid, uid);
  123. return;
  124. }
  125. args.Handled = true;
  126. var ev = new FultonedDoAfterEvent();
  127. _doAfter.TryStartDoAfter(
  128. new DoAfterArgs(EntityManager, args.User, component.ApplyFultonDuration, ev, args.Target, args.Target, args.Used)
  129. {
  130. MovementThreshold = 0.5f,
  131. BreakOnMove = true,
  132. Broadcast = true,
  133. NeedHand = true,
  134. });
  135. }
  136. private void OnFultonSplit(EntityUid uid, FultonComponent component, ref StackSplitEvent args)
  137. {
  138. var newFulton = EnsureComp<FultonComponent>(args.NewId);
  139. newFulton.Beacon = component.Beacon;
  140. Dirty(args.NewId, newFulton);
  141. }
  142. protected virtual void UpdateAppearance(EntityUid uid, FultonedComponent fultoned)
  143. {
  144. return;
  145. }
  146. protected bool CanApplyFulton(EntityUid targetUid, FultonComponent component)
  147. {
  148. if (!CanFulton(targetUid))
  149. return false;
  150. if (_whitelistSystem.IsWhitelistFailOrNull(component.Whitelist, targetUid))
  151. return false;
  152. return true;
  153. }
  154. protected bool CanFulton(EntityUid uid)
  155. {
  156. var xform = Transform(uid);
  157. if (xform.Anchored)
  158. return false;
  159. // Shouldn't need recursive container checks I think.
  160. if (Container.IsEntityInContainer(uid))
  161. return false;
  162. return true;
  163. }
  164. [Serializable, NetSerializable]
  165. private sealed partial class FultonedDoAfterEvent : SimpleDoAfterEvent
  166. {
  167. }
  168. // Animations aren't really good for networking hence this.
  169. /// <summary>
  170. /// Tells clients to play the fulton animation.
  171. /// </summary>
  172. [Serializable, NetSerializable]
  173. protected sealed class FultonAnimationMessage : EntityEventArgs
  174. {
  175. public NetEntity Entity;
  176. public NetCoordinates Coordinates;
  177. }
  178. }