HandsSystem.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. using System.Numerics;
  2. using Content.Server.Inventory;
  3. using Content.Server.Stack;
  4. using Content.Server.Stunnable;
  5. using Content.Shared.ActionBlocker;
  6. using Content.Shared.Body.Part;
  7. using Content.Shared.CombatMode;
  8. using Content.Shared.Damage.Systems;
  9. using Content.Shared.Explosion;
  10. using Content.Shared.Hands.Components;
  11. using Content.Shared.Hands.EntitySystems;
  12. using Content.Shared.Input;
  13. using Content.Shared.Inventory.VirtualItem;
  14. using Content.Shared.Movement.Pulling.Components;
  15. using Content.Shared.Movement.Pulling.Events;
  16. using Content.Shared.Movement.Pulling.Systems;
  17. using Content.Shared.Stacks;
  18. using Content.Shared.Throwing;
  19. using Robust.Shared.GameStates;
  20. using Robust.Shared.Input.Binding;
  21. using Robust.Shared.Map;
  22. using Robust.Shared.Player;
  23. using Robust.Shared.Random;
  24. using Robust.Shared.Timing;
  25. using Robust.Shared.Utility;
  26. namespace Content.Server.Hands.Systems
  27. {
  28. public sealed class HandsSystem : SharedHandsSystem
  29. {
  30. [Dependency] private readonly IGameTiming _timing = default!;
  31. [Dependency] private readonly IRobustRandom _random = default!;
  32. [Dependency] private readonly StackSystem _stackSystem = default!;
  33. [Dependency] private readonly VirtualItemSystem _virtualItemSystem = default!;
  34. [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
  35. [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
  36. [Dependency] private readonly PullingSystem _pullingSystem = default!;
  37. [Dependency] private readonly ThrowingSystem _throwingSystem = default!;
  38. public override void Initialize()
  39. {
  40. base.Initialize();
  41. SubscribeLocalEvent<HandsComponent, DisarmedEvent>(OnDisarmed, before: new[] {typeof(StunSystem), typeof(StaminaSystem)});
  42. SubscribeLocalEvent<HandsComponent, PullStartedMessage>(HandlePullStarted);
  43. SubscribeLocalEvent<HandsComponent, PullStoppedMessage>(HandlePullStopped);
  44. SubscribeLocalEvent<HandsComponent, BodyPartAddedEvent>(HandleBodyPartAdded);
  45. SubscribeLocalEvent<HandsComponent, BodyPartRemovedEvent>(HandleBodyPartRemoved);
  46. SubscribeLocalEvent<HandsComponent, ComponentGetState>(GetComponentState);
  47. SubscribeLocalEvent<HandsComponent, BeforeExplodeEvent>(OnExploded);
  48. CommandBinds.Builder
  49. .Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem))
  50. .Register<HandsSystem>();
  51. }
  52. public override void Shutdown()
  53. {
  54. base.Shutdown();
  55. CommandBinds.Unregister<HandsSystem>();
  56. }
  57. private void GetComponentState(EntityUid uid, HandsComponent hands, ref ComponentGetState args)
  58. {
  59. args.State = new HandsComponentState(hands);
  60. }
  61. private void OnExploded(Entity<HandsComponent> ent, ref BeforeExplodeEvent args)
  62. {
  63. if (ent.Comp.DisableExplosionRecursion)
  64. return;
  65. foreach (var hand in ent.Comp.Hands.Values)
  66. {
  67. if (hand.HeldEntity is { } uid)
  68. args.Contents.Add(uid);
  69. }
  70. }
  71. private void OnDisarmed(EntityUid uid, HandsComponent component, DisarmedEvent args)
  72. {
  73. if (args.Handled)
  74. return;
  75. // Break any pulls
  76. if (TryComp(uid, out PullerComponent? puller) && TryComp(puller.Pulling, out PullableComponent? pullable))
  77. _pullingSystem.TryStopPull(puller.Pulling.Value, pullable);
  78. var offsetRandomCoordinates = _transformSystem.GetMoverCoordinates(args.Target).Offset(_random.NextVector2(1f, 1.5f));
  79. if (!ThrowHeldItem(args.Target, offsetRandomCoordinates))
  80. return;
  81. args.PopupPrefix = "disarm-action-";
  82. args.Handled = true; // no shove/stun.
  83. }
  84. private void HandleBodyPartAdded(EntityUid uid, HandsComponent component, ref BodyPartAddedEvent args)
  85. {
  86. if (args.Part.Comp.PartType != BodyPartType.Hand)
  87. return;
  88. // If this annoys you, which it should.
  89. // Ping Smugleaf.
  90. var location = args.Part.Comp.Symmetry switch
  91. {
  92. BodyPartSymmetry.None => HandLocation.Middle,
  93. BodyPartSymmetry.Left => HandLocation.Left,
  94. BodyPartSymmetry.Right => HandLocation.Right,
  95. _ => throw new ArgumentOutOfRangeException(nameof(args.Part.Comp.Symmetry))
  96. };
  97. AddHand(uid, args.Slot, location);
  98. }
  99. private void HandleBodyPartRemoved(EntityUid uid, HandsComponent component, ref BodyPartRemovedEvent args)
  100. {
  101. if (args.Part.Comp.PartType != BodyPartType.Hand)
  102. return;
  103. RemoveHand(uid, args.Slot);
  104. }
  105. #region pulling
  106. private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args)
  107. {
  108. if (args.PullerUid != uid)
  109. return;
  110. if (TryComp<PullerComponent>(args.PullerUid, out var pullerComp) && !pullerComp.NeedsHands)
  111. return;
  112. if (!_virtualItemSystem.TrySpawnVirtualItemInHand(args.PulledUid, uid))
  113. {
  114. DebugTools.Assert("Unable to find available hand when starting pulling??");
  115. }
  116. }
  117. private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStoppedMessage args)
  118. {
  119. if (args.PullerUid != uid)
  120. return;
  121. // Try find hand that is doing this pull.
  122. // and clear it.
  123. foreach (var hand in component.Hands.Values)
  124. {
  125. if (hand.HeldEntity == null
  126. || !TryComp(hand.HeldEntity, out VirtualItemComponent? virtualItem)
  127. || virtualItem.BlockingEntity != args.PulledUid)
  128. {
  129. continue;
  130. }
  131. TryDrop(args.PullerUid, hand, handsComp: component);
  132. break;
  133. }
  134. }
  135. #endregion
  136. #region interactions
  137. private bool HandleThrowItem(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity)
  138. {
  139. if (playerSession?.AttachedEntity is not {Valid: true} player || !Exists(player) || !coordinates.IsValid(EntityManager))
  140. return false;
  141. return ThrowHeldItem(player, coordinates);
  142. }
  143. /// <summary>
  144. /// Throw the player's currently held item.
  145. /// </summary>
  146. public bool ThrowHeldItem(EntityUid player, EntityCoordinates coordinates, float minDistance = 0.1f)
  147. {
  148. if (ContainerSystem.IsEntityInContainer(player) ||
  149. !TryComp(player, out HandsComponent? hands) ||
  150. hands.ActiveHandEntity is not { } throwEnt ||
  151. !_actionBlockerSystem.CanThrow(player, throwEnt))
  152. return false;
  153. if (_timing.CurTime < hands.NextThrowTime)
  154. return false;
  155. hands.NextThrowTime = _timing.CurTime + hands.ThrowCooldown;
  156. if (EntityManager.TryGetComponent(throwEnt, out StackComponent? stack) && stack.Count > 1 && stack.ThrowIndividually)
  157. {
  158. var splitStack = _stackSystem.Split(throwEnt, 1, EntityManager.GetComponent<TransformComponent>(player).Coordinates, stack);
  159. if (splitStack is not {Valid: true})
  160. return false;
  161. throwEnt = splitStack.Value;
  162. }
  163. var direction = _transformSystem.ToMapCoordinates(coordinates).Position - _transformSystem.GetWorldPosition(player);
  164. if (direction == Vector2.Zero)
  165. return true;
  166. var length = direction.Length();
  167. var distance = Math.Clamp(length, minDistance, hands.ThrowRange);
  168. direction *= distance / length;
  169. var throwSpeed = hands.BaseThrowspeed;
  170. // Let other systems change the thrown entity (useful for virtual items)
  171. // or the throw strength.
  172. var ev = new BeforeThrowEvent(throwEnt, direction, throwSpeed, player);
  173. RaiseLocalEvent(player, ref ev);
  174. if (ev.Cancelled)
  175. return true;
  176. // This can grief the above event so we raise it afterwards
  177. if (IsHolding(player, throwEnt, out _, hands) && !TryDrop(player, throwEnt, handsComp: hands))
  178. return false;
  179. _throwingSystem.TryThrow(ev.ItemUid, ev.Direction, ev.ThrowSpeed, ev.PlayerUid, compensateFriction: !HasComp<LandAtCursorComponent>(ev.ItemUid));
  180. return true;
  181. }
  182. #endregion
  183. }
  184. }