SharedStunSystem.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. using Content.Shared.ActionBlocker;
  2. using Content.Shared.Administration.Logs;
  3. using Content.Shared.Interaction;
  4. using Content.Shared.Interaction.Events;
  5. using Content.Shared.Inventory.Events;
  6. using Content.Shared.Item;
  7. using Content.Shared.Bed.Sleep;
  8. using Content.Shared.Database;
  9. using Content.Shared.Hands;
  10. using Content.Shared.Mobs;
  11. using Content.Shared.Mobs.Components;
  12. using Content.Shared.Movement.Events;
  13. using Content.Shared.Movement.Systems;
  14. using Content.Shared.Standing;
  15. using Content.Shared.StatusEffect;
  16. using Content.Shared.Throwing;
  17. using Content.Shared.Whitelist;
  18. using Robust.Shared.Audio.Systems;
  19. using Robust.Shared.Physics.Components;
  20. using Robust.Shared.Physics.Events;
  21. using Robust.Shared.Physics.Systems;
  22. namespace Content.Shared.Stunnable;
  23. public abstract class SharedStunSystem : EntitySystem
  24. {
  25. [Dependency] private readonly ActionBlockerSystem _blocker = default!;
  26. [Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
  27. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  28. [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
  29. [Dependency] private readonly SharedAudioSystem _audio = default!;
  30. [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
  31. [Dependency] private readonly StandingStateSystem _standingState = default!;
  32. [Dependency] private readonly StatusEffectsSystem _statusEffect = default!;
  33. /// <summary>
  34. /// Friction modifier for knocked down players.
  35. /// Doesn't make them faster but makes them slow down... slower.
  36. /// </summary>
  37. public const float KnockDownModifier = 0.4f;
  38. public override void Initialize()
  39. {
  40. SubscribeLocalEvent<KnockedDownComponent, ComponentInit>(OnKnockInit);
  41. SubscribeLocalEvent<KnockedDownComponent, ComponentShutdown>(OnKnockShutdown);
  42. SubscribeLocalEvent<KnockedDownComponent, StandAttemptEvent>(OnStandAttempt);
  43. SubscribeLocalEvent<SlowedDownComponent, ComponentInit>(OnSlowInit);
  44. SubscribeLocalEvent<SlowedDownComponent, ComponentShutdown>(OnSlowRemove);
  45. SubscribeLocalEvent<StunnedComponent, ComponentStartup>(UpdateCanMove);
  46. SubscribeLocalEvent<StunnedComponent, ComponentShutdown>(UpdateCanMove);
  47. SubscribeLocalEvent<StunOnContactComponent, ComponentStartup>(OnStunOnContactStartup);
  48. SubscribeLocalEvent<StunOnContactComponent, StartCollideEvent>(OnStunOnContactCollide);
  49. // helping people up if they're knocked down
  50. SubscribeLocalEvent<KnockedDownComponent, InteractHandEvent>(OnInteractHand);
  51. SubscribeLocalEvent<SlowedDownComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
  52. SubscribeLocalEvent<KnockedDownComponent, TileFrictionEvent>(OnKnockedTileFriction);
  53. // Attempt event subscriptions.
  54. SubscribeLocalEvent<StunnedComponent, ChangeDirectionAttemptEvent>(OnAttempt);
  55. SubscribeLocalEvent<StunnedComponent, UpdateCanMoveEvent>(OnMoveAttempt);
  56. SubscribeLocalEvent<StunnedComponent, InteractionAttemptEvent>(OnAttemptInteract);
  57. SubscribeLocalEvent<StunnedComponent, UseAttemptEvent>(OnAttempt);
  58. SubscribeLocalEvent<StunnedComponent, ThrowAttemptEvent>(OnAttempt);
  59. SubscribeLocalEvent<StunnedComponent, DropAttemptEvent>(OnAttempt);
  60. SubscribeLocalEvent<StunnedComponent, AttackAttemptEvent>(OnAttempt);
  61. SubscribeLocalEvent<StunnedComponent, PickupAttemptEvent>(OnAttempt);
  62. SubscribeLocalEvent<StunnedComponent, IsEquippingAttemptEvent>(OnEquipAttempt);
  63. SubscribeLocalEvent<StunnedComponent, IsUnequippingAttemptEvent>(OnUnequipAttempt);
  64. SubscribeLocalEvent<MobStateComponent, MobStateChangedEvent>(OnMobStateChanged);
  65. }
  66. private void OnAttemptInteract(Entity<StunnedComponent> ent, ref InteractionAttemptEvent args)
  67. {
  68. args.Cancelled = true;
  69. }
  70. private void OnMobStateChanged(EntityUid uid, MobStateComponent component, MobStateChangedEvent args)
  71. {
  72. if (!TryComp<StatusEffectsComponent>(uid, out var status))
  73. {
  74. return;
  75. }
  76. switch (args.NewMobState)
  77. {
  78. case MobState.Alive:
  79. {
  80. break;
  81. }
  82. case MobState.Critical:
  83. {
  84. _statusEffect.TryRemoveStatusEffect(uid, "Stun");
  85. break;
  86. }
  87. case MobState.Dead:
  88. {
  89. _statusEffect.TryRemoveStatusEffect(uid, "Stun");
  90. break;
  91. }
  92. case MobState.Invalid:
  93. default:
  94. return;
  95. }
  96. }
  97. private void UpdateCanMove(EntityUid uid, StunnedComponent component, EntityEventArgs args)
  98. {
  99. _blocker.UpdateCanMove(uid);
  100. }
  101. private void OnStunOnContactStartup(Entity<StunOnContactComponent> ent, ref ComponentStartup args)
  102. {
  103. if (TryComp<PhysicsComponent>(ent, out var body))
  104. _broadphase.RegenerateContacts((ent, body));
  105. }
  106. private void OnStunOnContactCollide(Entity<StunOnContactComponent> ent, ref StartCollideEvent args)
  107. {
  108. if (args.OurFixtureId != ent.Comp.FixtureId)
  109. return;
  110. if (_entityWhitelist.IsBlacklistPass(ent.Comp.Blacklist, args.OtherEntity))
  111. return;
  112. if (!TryComp<StatusEffectsComponent>(args.OtherEntity, out var status))
  113. return;
  114. TryStun(args.OtherEntity, ent.Comp.Duration, true, status);
  115. TryKnockdown(args.OtherEntity, ent.Comp.Duration, true, status);
  116. }
  117. private void OnKnockInit(EntityUid uid, KnockedDownComponent component, ComponentInit args)
  118. {
  119. _standingState.Down(uid);
  120. }
  121. private void OnKnockShutdown(EntityUid uid, KnockedDownComponent component, ComponentShutdown args)
  122. {
  123. _standingState.Stand(uid);
  124. }
  125. private void OnStandAttempt(EntityUid uid, KnockedDownComponent component, StandAttemptEvent args)
  126. {
  127. if (component.LifeStage <= ComponentLifeStage.Running)
  128. args.Cancel();
  129. }
  130. private void OnSlowInit(EntityUid uid, SlowedDownComponent component, ComponentInit args)
  131. {
  132. _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
  133. }
  134. private void OnSlowRemove(EntityUid uid, SlowedDownComponent component, ComponentShutdown args)
  135. {
  136. component.SprintSpeedModifier = 1f;
  137. component.WalkSpeedModifier = 1f;
  138. _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
  139. }
  140. private void OnRefreshMovespeed(EntityUid uid, SlowedDownComponent component, RefreshMovementSpeedModifiersEvent args)
  141. {
  142. args.ModifySpeed(component.WalkSpeedModifier, component.SprintSpeedModifier);
  143. }
  144. // TODO STUN: Make events for different things. (Getting modifiers, attempt events, informative events...)
  145. /// <summary>
  146. /// Stuns the entity, disallowing it from doing many interactions temporarily.
  147. /// </summary>
  148. public bool TryStun(EntityUid uid, TimeSpan time, bool refresh,
  149. StatusEffectsComponent? status = null)
  150. {
  151. if (time <= TimeSpan.Zero)
  152. return false;
  153. if (!Resolve(uid, ref status, false))
  154. return false;
  155. if (!_statusEffect.TryAddStatusEffect<StunnedComponent>(uid, "Stun", time, refresh))
  156. return false;
  157. var ev = new StunnedEvent();
  158. RaiseLocalEvent(uid, ref ev);
  159. _adminLogger.Add(LogType.Stamina, LogImpact.Medium, $"{ToPrettyString(uid):user} stunned for {time.Seconds} seconds");
  160. return true;
  161. }
  162. /// <summary>
  163. /// Knocks down the entity, making it fall to the ground.
  164. /// </summary>
  165. public bool TryKnockdown(EntityUid uid, TimeSpan time, bool refresh,
  166. StatusEffectsComponent? status = null)
  167. {
  168. if (time <= TimeSpan.Zero)
  169. return false;
  170. if (!Resolve(uid, ref status, false))
  171. return false;
  172. if (!_statusEffect.TryAddStatusEffect<KnockedDownComponent>(uid, "KnockedDown", time, refresh))
  173. return false;
  174. var ev = new KnockedDownEvent();
  175. RaiseLocalEvent(uid, ref ev);
  176. return true;
  177. }
  178. /// <summary>
  179. /// Applies knockdown and stun to the entity temporarily.
  180. /// </summary>
  181. public bool TryParalyze(EntityUid uid, TimeSpan time, bool refresh,
  182. StatusEffectsComponent? status = null)
  183. {
  184. if (!Resolve(uid, ref status, false))
  185. return false;
  186. return TryKnockdown(uid, time, refresh, status) && TryStun(uid, time, refresh, status);
  187. }
  188. /// <summary>
  189. /// Slows down the mob's walking/running speed temporarily
  190. /// </summary>
  191. public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh,
  192. float walkSpeedMultiplier = 1f, float runSpeedMultiplier = 1f,
  193. StatusEffectsComponent? status = null)
  194. {
  195. if (!Resolve(uid, ref status, false))
  196. return false;
  197. if (time <= TimeSpan.Zero)
  198. return false;
  199. if (_statusEffect.TryAddStatusEffect<SlowedDownComponent>(uid, "SlowedDown", time, refresh, status))
  200. {
  201. var slowed = Comp<SlowedDownComponent>(uid);
  202. // Doesn't make much sense to have the "TrySlowdown" method speed up entities now does it?
  203. walkSpeedMultiplier = Math.Clamp(walkSpeedMultiplier, 0f, 1f);
  204. runSpeedMultiplier = Math.Clamp(runSpeedMultiplier, 0f, 1f);
  205. slowed.WalkSpeedModifier *= walkSpeedMultiplier;
  206. slowed.SprintSpeedModifier *= runSpeedMultiplier;
  207. _movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
  208. return true;
  209. }
  210. return false;
  211. }
  212. private void OnInteractHand(EntityUid uid, KnockedDownComponent knocked, InteractHandEvent args)
  213. {
  214. if (args.Handled || knocked.HelpTimer > 0f)
  215. return;
  216. // TODO: This should be an event.
  217. if (HasComp<SleepingComponent>(uid))
  218. return;
  219. // Set it to half the help interval so helping is actually useful...
  220. knocked.HelpTimer = knocked.HelpInterval / 2f;
  221. _statusEffect.TryRemoveTime(uid, "KnockedDown", TimeSpan.FromSeconds(knocked.HelpInterval));
  222. _audio.PlayPredicted(knocked.StunAttemptSound, uid, args.User);
  223. Dirty(uid, knocked);
  224. args.Handled = true;
  225. }
  226. private void OnKnockedTileFriction(EntityUid uid, KnockedDownComponent component, ref TileFrictionEvent args)
  227. {
  228. args.Modifier *= KnockDownModifier;
  229. }
  230. #region Attempt Event Handling
  231. private void OnMoveAttempt(EntityUid uid, StunnedComponent stunned, UpdateCanMoveEvent args)
  232. {
  233. if (stunned.LifeStage > ComponentLifeStage.Running)
  234. return;
  235. args.Cancel();
  236. }
  237. private void OnAttempt(EntityUid uid, StunnedComponent stunned, CancellableEntityEventArgs args)
  238. {
  239. args.Cancel();
  240. }
  241. private void OnEquipAttempt(EntityUid uid, StunnedComponent stunned, IsEquippingAttemptEvent args)
  242. {
  243. // is this a self-equip, or are they being stripped?
  244. if (args.Equipee == uid)
  245. args.Cancel();
  246. }
  247. private void OnUnequipAttempt(EntityUid uid, StunnedComponent stunned, IsUnequippingAttemptEvent args)
  248. {
  249. // is this a self-equip, or are they being stripped?
  250. if (args.Unequipee == uid)
  251. args.Cancel();
  252. }
  253. #endregion
  254. }
  255. /// <summary>
  256. /// Raised directed on an entity when it is stunned.
  257. /// </summary>
  258. [ByRefEvent]
  259. public record struct StunnedEvent;
  260. /// <summary>
  261. /// Raised directed on an entity when it is knocked down.
  262. /// </summary>
  263. [ByRefEvent]
  264. public record struct KnockedDownEvent;