SharedEnsnareableSystem.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  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. public override void Initialize()
  38. {
  39. base.Initialize();
  40. SubscribeLocalEvent<EnsnareableComponent, ComponentInit>(OnEnsnareInit);
  41. SubscribeLocalEvent<EnsnareableComponent, RefreshMovementSpeedModifiersEvent>(MovementSpeedModify);
  42. SubscribeLocalEvent<EnsnareableComponent, EnsnareEvent>(OnEnsnare);
  43. SubscribeLocalEvent<EnsnareableComponent, EnsnareRemoveEvent>(OnEnsnareRemove);
  44. SubscribeLocalEvent<EnsnareableComponent, EnsnaredChangedEvent>(OnEnsnareChange);
  45. SubscribeLocalEvent<EnsnareableComponent, AfterAutoHandleStateEvent>(OnHandleState);
  46. SubscribeLocalEvent<EnsnareableComponent, StrippingEnsnareButtonPressed>(OnStripEnsnareMessage);
  47. SubscribeLocalEvent<EnsnareableComponent, RemoveEnsnareAlertEvent>(OnRemoveEnsnareAlert);
  48. SubscribeLocalEvent<EnsnareableComponent, EnsnareableDoAfterEvent>(OnDoAfter);
  49. SubscribeLocalEvent<EnsnaringComponent, ComponentRemove>(OnComponentRemove);
  50. SubscribeLocalEvent<EnsnaringComponent, StepTriggerAttemptEvent>(AttemptStepTrigger);
  51. SubscribeLocalEvent<EnsnaringComponent, StepTriggeredOffEvent>(OnStepTrigger);
  52. SubscribeLocalEvent<EnsnaringComponent, ThrowDoHitEvent>(OnThrowHit);
  53. SubscribeLocalEvent<EnsnaringComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
  54. }
  55. protected virtual void OnEnsnareInit(Entity<EnsnareableComponent> ent, ref ComponentInit args)
  56. {
  57. ent.Comp.Container = Container.EnsureContainer<Container>(ent.Owner, "ensnare");
  58. }
  59. private void OnHandleState(EntityUid uid, EnsnareableComponent component, ref AfterAutoHandleStateEvent args)
  60. {
  61. RaiseLocalEvent(uid, new EnsnaredChangedEvent(component.IsEnsnared));
  62. }
  63. private void OnDoAfter(EntityUid uid, EnsnareableComponent component, DoAfterEvent args)
  64. {
  65. if (args.Args.Target == null)
  66. return;
  67. if (args.Handled || !TryComp<EnsnaringComponent>(args.Args.Used, out var ensnaring))
  68. return;
  69. if (args.Cancelled || !Container.Remove(args.Args.Used.Value, component.Container))
  70. {
  71. if (args.User == args.Target)
  72. Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-fail", ("ensnare", args.Args.Used)), uid, args.User, PopupType.MediumCaution);
  73. else if (args.Target != null)
  74. Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-fail-other", ("ensnare", args.Args.Used), ("user", args.Target)), uid, args.User, PopupType.MediumCaution);
  75. return;
  76. }
  77. component.IsEnsnared = component.Container.ContainedEntities.Count > 0;
  78. Dirty(uid, component);
  79. ensnaring.Ensnared = null;
  80. _hands.PickupOrDrop(args.Args.User, args.Args.Used.Value);
  81. if (args.User == args.Target)
  82. Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-complete", ("ensnare", args.Args.Used)), uid, args.User, PopupType.Medium);
  83. else if (args.Target != null)
  84. Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-complete-other", ("ensnare", args.Args.Used), ("user", args.Target)), uid, args.User, PopupType.Medium);
  85. UpdateAlert(args.Args.Target.Value, component);
  86. var ev = new EnsnareRemoveEvent(ensnaring.WalkSpeed, ensnaring.SprintSpeed);
  87. RaiseLocalEvent(uid, ev);
  88. args.Handled = true;
  89. }
  90. private void OnEnsnare(EntityUid uid, EnsnareableComponent component, EnsnareEvent args)
  91. {
  92. component.WalkSpeed *= args.WalkSpeed;
  93. component.SprintSpeed *= args.SprintSpeed;
  94. _speedModifier.RefreshMovementSpeedModifiers(uid);
  95. var ev = new EnsnaredChangedEvent(component.IsEnsnared);
  96. RaiseLocalEvent(uid, ev);
  97. }
  98. private void OnEnsnareRemove(EntityUid uid, EnsnareableComponent component, EnsnareRemoveEvent args)
  99. {
  100. component.WalkSpeed /= args.WalkSpeed;
  101. component.SprintSpeed /= args.SprintSpeed;
  102. _speedModifier.RefreshMovementSpeedModifiers(uid);
  103. var ev = new EnsnaredChangedEvent(component.IsEnsnared);
  104. RaiseLocalEvent(uid, ev);
  105. }
  106. private void OnEnsnareChange(EntityUid uid, EnsnareableComponent component, EnsnaredChangedEvent args)
  107. {
  108. UpdateAppearance(uid, component);
  109. }
  110. private void UpdateAppearance(EntityUid uid, EnsnareableComponent component, AppearanceComponent? appearance = null)
  111. {
  112. Appearance.SetData(uid, EnsnareableVisuals.IsEnsnared, component.IsEnsnared, appearance);
  113. }
  114. private void MovementSpeedModify(EntityUid uid, EnsnareableComponent component,
  115. RefreshMovementSpeedModifiersEvent args)
  116. {
  117. if (!component.IsEnsnared)
  118. return;
  119. args.ModifySpeed(component.WalkSpeed, component.SprintSpeed);
  120. }
  121. /// <summary>
  122. /// Used where you want to try to free an entity with the <see cref="EnsnareableComponent"/>
  123. /// </summary>
  124. /// <param name="target">The entity that will be freed</param>
  125. /// <param name="user">The entity that is freeing the target</param>
  126. /// <param name="ensnare">The entity used to ensnare</param>
  127. /// <param name="component">The ensnaring component</param>
  128. public void TryFree(EntityUid target, EntityUid user, EntityUid ensnare, EnsnaringComponent component)
  129. {
  130. // Don't do anything if they don't have the ensnareable component.
  131. if (!HasComp<EnsnareableComponent>(target))
  132. return;
  133. var freeTime = user == target ? component.BreakoutTime : component.FreeTime;
  134. var breakOnMove = !component.CanMoveBreakout;
  135. var doAfterEventArgs = new DoAfterArgs(EntityManager, user, freeTime, new EnsnareableDoAfterEvent(), target, target: target, used: ensnare)
  136. {
  137. BreakOnMove = breakOnMove,
  138. BreakOnDamage = false,
  139. NeedHand = true,
  140. BreakOnDropItem = false,
  141. };
  142. if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
  143. return;
  144. if (user == target)
  145. Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free", ("ensnare", ensnare)), target, target);
  146. else
  147. Popup.PopupPredicted(Loc.GetString("ensnare-component-try-free-other", ("ensnare", ensnare), ("user", Identity.Entity(target, EntityManager))), user, user);
  148. }
  149. private void OnStripEnsnareMessage(EntityUid uid, EnsnareableComponent component, StrippingEnsnareButtonPressed args)
  150. {
  151. foreach (var entity in component.Container.ContainedEntities)
  152. {
  153. if (!TryComp<EnsnaringComponent>(entity, out var ensnaring))
  154. continue;
  155. TryFree(uid, args.Actor, entity, ensnaring);
  156. return;
  157. }
  158. }
  159. private void OnAttemptPacifiedThrow(Entity<EnsnaringComponent> ent, ref AttemptPacifiedThrowEvent args)
  160. {
  161. args.Cancel("pacified-cannot-throw-snare");
  162. }
  163. private void OnRemoveEnsnareAlert(Entity<EnsnareableComponent> ent, ref RemoveEnsnareAlertEvent args)
  164. {
  165. if (args.Handled)
  166. return;
  167. foreach (var ensnare in ent.Comp.Container.ContainedEntities)
  168. {
  169. if (!TryComp<EnsnaringComponent>(ensnare, out var ensnaringComponent))
  170. continue;
  171. TryFree(ent, ent, ensnare, ensnaringComponent);
  172. args.Handled = true;
  173. // Only one snare at a time.
  174. break;
  175. }
  176. }
  177. private void OnComponentRemove(EntityUid uid, EnsnaringComponent component, ComponentRemove args)
  178. {
  179. if (!TryComp<EnsnareableComponent>(component.Ensnared, out var ensnared))
  180. return;
  181. if (ensnared.IsEnsnared)
  182. ForceFree(uid, component);
  183. }
  184. private void AttemptStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggerAttemptEvent args)
  185. {
  186. args.Continue = true;
  187. }
  188. private void OnStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggeredOffEvent args)
  189. {
  190. TryEnsnare(args.Tripper, uid, component);
  191. }
  192. private void OnThrowHit(EntityUid uid, EnsnaringComponent component, ThrowDoHitEvent args)
  193. {
  194. if (!component.CanThrowTrigger)
  195. return;
  196. if (TryEnsnare(args.Target, uid, component))
  197. {
  198. _audio.PlayPvs(component.EnsnareSound, uid);
  199. }
  200. }
  201. /// <summary>
  202. /// Used where you want to try to ensnare an entity with the <see cref="EnsnareableComponent"/>
  203. /// </summary>
  204. /// <param name="target">The entity that will be ensnared</param>
  205. /// <paramref name="ensnare"> The entity that is used to ensnare</param>
  206. /// <param name="component">The ensnaring component</param>
  207. public bool TryEnsnare(EntityUid target, EntityUid ensnare, EnsnaringComponent component)
  208. {
  209. //Don't do anything if they don't have the ensnareable component.
  210. if (!TryComp<EnsnareableComponent>(target, out var ensnareable))
  211. return false;
  212. var numEnsnares = ensnareable.Container.ContainedEntities.Count;
  213. //Don't do anything if the maximum number of ensnares is applied.
  214. if (numEnsnares >= component.MaxEnsnares)
  215. return false;
  216. Container.Insert(ensnare, ensnareable.Container);
  217. // Apply stamina damage to target
  218. if (TryComp<StaminaComponent>(target, out var stamina))
  219. {
  220. _stamina.TakeStaminaDamage(target, component.StaminaDamage, with: ensnare, component: stamina);
  221. }
  222. component.Ensnared = target;
  223. ensnareable.IsEnsnared = true;
  224. Dirty(target, ensnareable);
  225. UpdateAlert(target, ensnareable);
  226. var ev = new EnsnareEvent(component.WalkSpeed, component.SprintSpeed);
  227. RaiseLocalEvent(target, ev);
  228. return true;
  229. }
  230. /// <summary>
  231. /// Used to force free someone for things like if the <see cref="EnsnaringComponent"/> is removed
  232. /// </summary>
  233. public void ForceFree(EntityUid ensnare, EnsnaringComponent component)
  234. {
  235. if (component.Ensnared == null)
  236. return;
  237. if (!TryComp<EnsnareableComponent>(component.Ensnared, out var ensnareable))
  238. return;
  239. var target = component.Ensnared.Value;
  240. Container.Remove(ensnare, ensnareable.Container, force: true);
  241. ensnareable.IsEnsnared = ensnareable.Container.ContainedEntities.Count > 0;
  242. Dirty(component.Ensnared.Value, ensnareable);
  243. component.Ensnared = null;
  244. UpdateAlert(target, ensnareable);
  245. var ev = new EnsnareRemoveEvent(component.WalkSpeed, component.SprintSpeed);
  246. RaiseLocalEvent(ensnare, ev);
  247. }
  248. /// <summary>
  249. /// Update the Ensnared alert for an entity.
  250. /// </summary>
  251. /// <param name="target">The entity that has been affected by a snare</param>
  252. public void UpdateAlert(EntityUid target, EnsnareableComponent component)
  253. {
  254. if (!component.IsEnsnared)
  255. _alerts.ClearAlert(target, component.EnsnaredAlert);
  256. else
  257. _alerts.ShowAlert(target, component.EnsnaredAlert);
  258. }
  259. }