ShakeableSystem.cs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. using Content.Shared.DoAfter;
  2. using Content.Shared.Hands.EntitySystems;
  3. using Content.Shared.IdentityManagement;
  4. using Content.Shared.Nutrition.Components;
  5. using Content.Shared.Popups;
  6. using Content.Shared.Verbs;
  7. using Robust.Shared.Audio.Systems;
  8. using Robust.Shared.Serialization;
  9. namespace Content.Shared.Nutrition.EntitySystems;
  10. public sealed partial class ShakeableSystem : EntitySystem
  11. {
  12. [Dependency] private readonly SharedPopupSystem _popup = default!;
  13. [Dependency] private readonly SharedAudioSystem _audio = default!;
  14. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  15. [Dependency] private readonly SharedHandsSystem _hands = default!;
  16. public override void Initialize()
  17. {
  18. base.Initialize();
  19. SubscribeLocalEvent<ShakeableComponent, GetVerbsEvent<Verb>>(AddShakeVerb);
  20. SubscribeLocalEvent<ShakeableComponent, ShakeDoAfterEvent>(OnShakeDoAfter);
  21. }
  22. private void AddShakeVerb(EntityUid uid, ShakeableComponent component, GetVerbsEvent<Verb> args)
  23. {
  24. if (args.Hands == null || !args.CanAccess || !args.CanInteract)
  25. return;
  26. if (!CanShake((uid, component), args.User))
  27. return;
  28. var shakeVerb = new Verb()
  29. {
  30. Text = Loc.GetString(component.ShakeVerbText),
  31. Act = () => TryStartShake((args.Target, component), args.User)
  32. };
  33. args.Verbs.Add(shakeVerb);
  34. }
  35. private void OnShakeDoAfter(Entity<ShakeableComponent> entity, ref ShakeDoAfterEvent args)
  36. {
  37. if (args.Handled || args.Cancelled)
  38. return;
  39. TryShake((entity, entity.Comp), args.User);
  40. }
  41. /// <summary>
  42. /// Attempts to start the doAfter to shake the entity.
  43. /// Fails and returns false if the entity cannot be shaken for any reason.
  44. /// If successful, displays popup messages, plays shake sound, and starts the doAfter.
  45. /// </summary>
  46. public bool TryStartShake(Entity<ShakeableComponent?> entity, EntityUid user)
  47. {
  48. if (!Resolve(entity, ref entity.Comp))
  49. return false;
  50. if (!CanShake(entity, user))
  51. return false;
  52. var doAfterArgs = new DoAfterArgs(EntityManager,
  53. user,
  54. entity.Comp.ShakeDuration,
  55. new ShakeDoAfterEvent(),
  56. eventTarget: entity,
  57. target: user,
  58. used: entity)
  59. {
  60. NeedHand = true,
  61. BreakOnDamage = true,
  62. DistanceThreshold = 1,
  63. MovementThreshold = 0.01f,
  64. BreakOnHandChange = entity.Comp.RequireInHand,
  65. };
  66. if (entity.Comp.RequireInHand)
  67. doAfterArgs.BreakOnHandChange = true;
  68. if (!_doAfter.TryStartDoAfter(doAfterArgs))
  69. return false;
  70. var userName = Identity.Entity(user, EntityManager);
  71. var shakeableName = Identity.Entity(entity, EntityManager);
  72. var selfMessage = Loc.GetString(entity.Comp.ShakePopupMessageSelf, ("user", userName), ("shakeable", shakeableName));
  73. var othersMessage = Loc.GetString(entity.Comp.ShakePopupMessageOthers, ("user", userName), ("shakeable", shakeableName));
  74. _popup.PopupPredicted(selfMessage, othersMessage, user, user);
  75. _audio.PlayPredicted(entity.Comp.ShakeSound, entity, user);
  76. return true;
  77. }
  78. /// <summary>
  79. /// Attempts to shake the entity, skipping the doAfter.
  80. /// Fails and returns false if the entity cannot be shaken for any reason.
  81. /// If successful, raises a ShakeEvent on the entity.
  82. /// </summary>
  83. public bool TryShake(Entity<ShakeableComponent?> entity, EntityUid? user = null)
  84. {
  85. if (!Resolve(entity, ref entity.Comp))
  86. return false;
  87. if (!CanShake(entity, user))
  88. return false;
  89. var ev = new ShakeEvent(user);
  90. RaiseLocalEvent(entity, ref ev);
  91. return true;
  92. }
  93. /// <summary>
  94. /// Is it possible for the given user to shake the entity?
  95. /// </summary>
  96. public bool CanShake(Entity<ShakeableComponent?> entity, EntityUid? user = null)
  97. {
  98. if (!Resolve(entity, ref entity.Comp, false))
  99. return false;
  100. // If required to be in hand, fail if the user is not holding this entity
  101. if (user != null && entity.Comp.RequireInHand && !_hands.IsHolding(user.Value, entity, out _))
  102. return false;
  103. var attemptEv = new AttemptShakeEvent();
  104. RaiseLocalEvent(entity, ref attemptEv);
  105. if (attemptEv.Cancelled)
  106. return false;
  107. return true;
  108. }
  109. }
  110. /// <summary>
  111. /// Raised when a ShakeableComponent is shaken, after the doAfter completes.
  112. /// </summary>
  113. [ByRefEvent]
  114. public record struct ShakeEvent(EntityUid? Shaker);
  115. /// <summary>
  116. /// Raised when trying to shake a ShakeableComponent. If cancelled, the
  117. /// entity will not be shaken.
  118. /// </summary>
  119. [ByRefEvent]
  120. public record struct AttemptShakeEvent()
  121. {
  122. public bool Cancelled;
  123. }
  124. [Serializable, NetSerializable]
  125. public sealed partial class ShakeDoAfterEvent : SimpleDoAfterEvent
  126. {
  127. }