SharedEnsnareableSystem.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. using System.Linq;
  2. using Content.Shared.Alert;
  3. using Content.Shared.Body.Part;
  4. using Content.Shared.Body.Systems;
  5. using Content.Shared.CombatMode.Pacification;
  6. using Content.Shared.Damage.Components;
  7. using Content.Shared.Damage.Systems;
  8. using Content.Shared.DoAfter;
  9. using Content.Shared.Ensnaring.Components;
  10. using Content.Shared.Hands.EntitySystems;
  11. using Content.Shared.IdentityManagement;
  12. using Content.Shared.Movement.Systems;
  13. using Content.Shared.Popups;
  14. using Content.Shared.StepTrigger.Systems;
  15. using Content.Shared.Strip.Components;
  16. using Content.Shared.Throwing;
  17. using Robust.Shared.Audio.Systems;
  18. using Robust.Shared.Containers;
  19. using Robust.Shared.Serialization;
  20. namespace Content.Shared.Ensnaring;
  21. [Serializable, NetSerializable]
  22. public sealed partial class EnsnareableDoAfterEvent : SimpleDoAfterEvent
  23. {
  24. }
  25. public abstract class SharedEnsnareableSystem : EntitySystem
  26. {
  27. [Dependency] private readonly AlertsSystem _alerts = default!;
  28. [Dependency] private readonly MovementSpeedModifierSystem _speedModifier = default!;
  29. [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
  30. [Dependency] private readonly SharedAudioSystem _audio = default!;
  31. [Dependency] private readonly SharedBodySystem _body = default!;
  32. [Dependency] protected readonly SharedContainerSystem Container = default!;
  33. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  34. [Dependency] private readonly SharedHandsSystem _hands = default!;
  35. [Dependency] protected readonly SharedPopupSystem Popup = default!;
  36. [Dependency] private readonly StaminaSystem _stamina = default!;
  37. /// <summary>
  38. /// Subscribes to local events related to ensnaring and ensnareable components to initialise the system's event handling.
  39. /// </summary>
  40. public override void Initialize()
  41. {
  42. base.Initialize();
  43. SubscribeLocalEvent<EnsnareableComponent, ComponentInit>(OnEnsnareInit);
  44. SubscribeLocalEvent<EnsnareableComponent, RefreshMovementSpeedModifiersEvent>(MovementSpeedModify);
  45. SubscribeLocalEvent<EnsnareableComponent, EnsnareEvent>(OnEnsnare);
  46. SubscribeLocalEvent<EnsnareableComponent, EnsnareRemoveEvent>(OnEnsnareRemove);
  47. SubscribeLocalEvent<EnsnareableComponent, EnsnaredChangedEvent>(OnEnsnareChange);
  48. SubscribeLocalEvent<EnsnareableComponent, AfterAutoHandleStateEvent>(OnHandleState);
  49. SubscribeLocalEvent<EnsnareableComponent, StrippingEnsnareButtonPressed>(OnStripEnsnareMessage);
  50. SubscribeLocalEvent<EnsnareableComponent, RemoveEnsnareAlertEvent>(OnRemoveEnsnareAlert);
  51. SubscribeLocalEvent<EnsnareableComponent, EnsnareableDoAfterEvent>(OnDoAfter);
  52. SubscribeLocalEvent<EnsnaringComponent, ComponentRemove>(OnComponentRemove);
  53. SubscribeLocalEvent<EnsnaringComponent, StepTriggerAttemptEvent>(AttemptStepTrigger);
  54. SubscribeLocalEvent<EnsnaringComponent, StepTriggeredOffEvent>(OnStepTrigger);
  55. SubscribeLocalEvent<EnsnaringComponent, ThrowDoHitEvent>(OnThrowHit);
  56. SubscribeLocalEvent<EnsnaringComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
  57. }
  58. protected virtual void OnEnsnareInit(Entity<EnsnareableComponent> ent, ref ComponentInit args)
  59. {
  60. ent.Comp.Container = Container.EnsureContainer<Container>(ent.Owner, "ensnare");
  61. }
  62. private void OnHandleState(EntityUid uid, EnsnareableComponent component, ref AfterAutoHandleStateEvent args)
  63. {
  64. RaiseLocalEvent(uid, new EnsnaredChangedEvent(component.IsEnsnared));
  65. }
  66. private void OnDoAfter(EntityUid uid, EnsnareableComponent component, DoAfterEvent args)
  67. {
  68. if (args.Args.Target == null)
  69. return;
  70. if (args.Handled || !TryComp<EnsnaringComponent>(args.Args.Used, out var ensnaring))
  71. return;
  72. if (args.Cancelled || !Container.Remove(args.Args.Used.Value, component.Container))
  73. {
  74. if (args.User == args.Target)
  75. Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-fail", ("ensnare", args.Args.Used)), uid, args.User, PopupType.MediumCaution);
  76. else if (args.Target != null)
  77. Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-fail-other", ("ensnare", args.Args.Used), ("user", args.Target)), uid, args.User, PopupType.MediumCaution);
  78. return;
  79. }
  80. component.IsEnsnared = component.Container.ContainedEntities.Count > 0;
  81. Dirty(uid, component);
  82. ensnaring.Ensnared = null;
  83. _hands.PickupOrDrop(args.Args.User, args.Args.Used.Value);
  84. if (args.User == args.Target)
  85. Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-complete", ("ensnare", args.Args.Used)), uid, args.User, PopupType.Medium);
  86. else if (args.Target != null)
  87. Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-complete-other", ("ensnare", args.Args.Used), ("user", args.Target)), uid, args.User, PopupType.Medium);
  88. UpdateAlert(args.Args.Target.Value, component);
  89. var ev = new EnsnareRemoveEvent(ensnaring.WalkSpeed, ensnaring.SprintSpeed);
  90. RaiseLocalEvent(uid, ev);
  91. args.Handled = true;
  92. }
  93. private void OnEnsnare(EntityUid uid, EnsnareableComponent component, EnsnareEvent args)
  94. {
  95. component.WalkSpeed *= args.WalkSpeed;
  96. component.SprintSpeed *= args.SprintSpeed;
  97. _speedModifier.RefreshMovementSpeedModifiers(uid);
  98. var ev = new EnsnaredChangedEvent(component.IsEnsnared);
  99. RaiseLocalEvent(uid, ev);
  100. }
  101. private void OnEnsnareRemove(EntityUid uid, EnsnareableComponent component, EnsnareRemoveEvent args)
  102. {
  103. component.WalkSpeed /= args.WalkSpeed;
  104. component.SprintSpeed /= args.SprintSpeed;
  105. _speedModifier.RefreshMovementSpeedModifiers(uid);
  106. var ev = new EnsnaredChangedEvent(component.IsEnsnared);
  107. RaiseLocalEvent(uid, ev);
  108. }
  109. private void OnEnsnareChange(EntityUid uid, EnsnareableComponent component, EnsnaredChangedEvent args)
  110. {
  111. UpdateAppearance(uid, component);
  112. }
  113. private void UpdateAppearance(EntityUid uid, EnsnareableComponent component, AppearanceComponent? appearance = null)
  114. {
  115. Appearance.SetData(uid, EnsnareableVisuals.IsEnsnared, component.IsEnsnared, appearance);
  116. }
  117. private void MovementSpeedModify(EntityUid uid, EnsnareableComponent component,
  118. RefreshMovementSpeedModifiersEvent args)
  119. {
  120. if (!component.IsEnsnared)
  121. return;
  122. args.ModifySpeed(component.WalkSpeed, component.SprintSpeed);
  123. }
  124. /// <summary>
  125. /// Used where you want to try to free an entity with the <see cref="EnsnareableComponent"/>
  126. /// </summary>
  127. /// <param name="target">The entity that will be freed</param>
  128. /// <param name="user">The entity that is freeing the target</param>
  129. /// <param name="ensnare">The entity used to ensnare</param>
  130. /// <param name="component">The ensnaring component</param>
  131. public void TryFree(EntityUid target, EntityUid user, EntityUid ensnare, EnsnaringComponent component)
  132. {
  133. // Don't do anything if they don't have the ensnareable component.
  134. if (!HasComp<EnsnareableComponent>(target))
  135. return;
  136. var freeTime = user == target ? component.BreakoutTime : component.FreeTime;
  137. var breakOnMove = !component.CanMoveBreakout;
  138. var doAfterEventArgs = new DoAfterArgs(EntityManager, user, freeTime, new EnsnareableDoAfterEvent(), target, target: target, used: ensnare)
  139. {
  140. BreakOnMove = breakOnMove,
  141. BreakOnDamage = false,
  142. NeedHand = true,
  143. BreakOnDropItem = false,
  144. };
  145. if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
  146. return;
  147. if (user == target)
  148. Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free", ("ensnare", ensnare)), target, target);
  149. else
  150. Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-other", ("ensnare", ensnare), ("user", Identity.Entity(target, EntityManager))), user, user);
  151. }
  152. private void OnStripEnsnareMessage(EntityUid uid, EnsnareableComponent component, StrippingEnsnareButtonPressed args)
  153. {
  154. foreach (var entity in component.Container.ContainedEntities)
  155. {
  156. if (!TryComp<EnsnaringComponent>(entity, out var ensnaring))
  157. continue;
  158. TryFree(uid, args.Actor, entity, ensnaring);
  159. return;
  160. }
  161. }
  162. private void OnAttemptPacifiedThrow(Entity<EnsnaringComponent> ent, ref AttemptPacifiedThrowEvent args)
  163. {
  164. args.Cancel("pacified-cannot-throw-snare");
  165. }
  166. private void OnRemoveEnsnareAlert(Entity<EnsnareableComponent> ent, ref RemoveEnsnareAlertEvent args)
  167. {
  168. if (args.Handled)
  169. return;
  170. foreach (var ensnare in ent.Comp.Container.ContainedEntities)
  171. {
  172. if (!TryComp<EnsnaringComponent>(ensnare, out var ensnaringComponent))
  173. continue;
  174. TryFree(ent, ent, ensnare, ensnaringComponent);
  175. args.Handled = true;
  176. // Only one snare at a time.
  177. break;
  178. }
  179. }
  180. private void OnComponentRemove(EntityUid uid, EnsnaringComponent component, ComponentRemove args)
  181. {
  182. if (!TryComp<EnsnareableComponent>(component.Ensnared, out var ensnared))
  183. return;
  184. if (ensnared.IsEnsnared)
  185. ForceFree(uid, component);
  186. }
  187. private void AttemptStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggerAttemptEvent args)
  188. {
  189. args.Continue = true;
  190. }
  191. private void OnStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggeredOffEvent args)
  192. {
  193. TryEnsnare(args.Tripper, uid, component);
  194. }
  195. private void OnThrowHit(EntityUid uid, EnsnaringComponent component, ThrowDoHitEvent args)
  196. {
  197. if (!component.CanThrowTrigger)
  198. return;
  199. if (TryEnsnare(args.Target, uid, component))
  200. {
  201. _audio.PlayPvs(component.EnsnareSound, uid);
  202. }
  203. }
  204. /// <summary>
  205. /// Used where you want to try to ensnare an entity with the <see cref="EnsnareableComponent"/>
  206. /// </summary>
  207. /// <param name="target">The entity that will be ensnared</param>
  208. /// <paramref name="ensnare"> The entity that is used to ensnare</param>
  209. /// <summary>
  210. /// Attempts to ensnare a target entity using the specified ensnaring entity.
  211. /// </summary>
  212. /// <param name="target">The entity to be ensnared.</param>
  213. /// <param name="ensnare">The ensnaring entity to apply.</param>
  214. /// <param name="component">The ensnaring component.</param>
  215. /// <returns>True if the target was successfully ensnared; otherwise, false.</returns>
  216. public bool TryEnsnare(EntityUid target, EntityUid ensnare, EnsnaringComponent component)
  217. {
  218. //Don't do anything if they don't have the ensnareable component.
  219. if (!TryComp<EnsnareableComponent>(target, out var ensnareable))
  220. return false;
  221. var numEnsnares = ensnareable.Container.ContainedEntities.Count;
  222. //Don't do anything if the maximum number of ensnares is applied.
  223. if (numEnsnares >= component.MaxEnsnares)
  224. return false;
  225. Container.Insert(ensnare, ensnareable.Container);
  226. // Apply stamina damage to target
  227. if (TryComp<StaminaComponent>(target, out var stamina))
  228. {
  229. _stamina.TakeStaminaDamage(target, component.StaminaDamage, with: ensnare, component: stamina, immediate: true);
  230. }
  231. component.Ensnared = target;
  232. ensnareable.IsEnsnared = true;
  233. Dirty(target, ensnareable);
  234. UpdateAlert(target, ensnareable);
  235. var ev = new EnsnareEvent(component.WalkSpeed, component.SprintSpeed);
  236. RaiseLocalEvent(target, ev);
  237. return true;
  238. }
  239. /// <summary>
  240. /// Used to force free someone for things like if the <see cref="EnsnaringComponent"/> is removed
  241. /// </summary>
  242. public void ForceFree(EntityUid ensnare, EnsnaringComponent component)
  243. {
  244. if (component.Ensnared == null)
  245. return;
  246. if (!TryComp<EnsnareableComponent>(component.Ensnared, out var ensnareable))
  247. return;
  248. var target = component.Ensnared.Value;
  249. Container.Remove(ensnare, ensnareable.Container, force: true);
  250. ensnareable.IsEnsnared = ensnareable.Container.ContainedEntities.Count > 0;
  251. Dirty(component.Ensnared.Value, ensnareable);
  252. component.Ensnared = null;
  253. UpdateAlert(target, ensnareable);
  254. var ev = new EnsnareRemoveEvent(component.WalkSpeed, component.SprintSpeed);
  255. RaiseLocalEvent(ensnare, ev);
  256. }
  257. /// <summary>
  258. /// Update the Ensnared alert for an entity.
  259. /// </summary>
  260. /// <param name="target">The entity that has been affected by a snare</param>
  261. public void UpdateAlert(EntityUid target, EnsnareableComponent component)
  262. {
  263. if (!component.IsEnsnared)
  264. _alerts.ClearAlert(target, component.EnsnaredAlert);
  265. else
  266. _alerts.ShowAlert(target, component.EnsnaredAlert);
  267. }
  268. }