SharedHandsSystem.Drop.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. using System.Numerics;
  2. using Content.Shared.Database;
  3. using Content.Shared.Hands.Components;
  4. using Content.Shared.Interaction;
  5. using Content.Shared.Inventory.VirtualItem;
  6. using Content.Shared.Tag;
  7. using Robust.Shared.Containers;
  8. using Robust.Shared.Map;
  9. namespace Content.Shared.Hands.EntitySystems;
  10. public abstract partial class SharedHandsSystem
  11. {
  12. [Dependency] private readonly TagSystem _tagSystem = default!;
  13. private void InitializeDrop()
  14. {
  15. SubscribeLocalEvent<HandsComponent, EntRemovedFromContainerMessage>(HandleEntityRemoved);
  16. }
  17. protected virtual void HandleEntityRemoved(EntityUid uid, HandsComponent hands, EntRemovedFromContainerMessage args)
  18. {
  19. if (!TryGetHand(uid, args.Container.ID, out var hand))
  20. {
  21. return;
  22. }
  23. var gotUnequipped = new GotUnequippedHandEvent(uid, args.Entity, hand);
  24. RaiseLocalEvent(args.Entity, gotUnequipped);
  25. var didUnequip = new DidUnequipHandEvent(uid, args.Entity, hand);
  26. RaiseLocalEvent(uid, didUnequip);
  27. if (TryComp(args.Entity, out VirtualItemComponent? @virtual))
  28. _virtualSystem.DeleteVirtualItem((args.Entity, @virtual), uid);
  29. }
  30. private bool ShouldIgnoreRestrictions(EntityUid user)
  31. {
  32. //Checks if the Entity is something that shouldn't care about drop distance or walls ie Aghost
  33. return !_tagSystem.HasTag(user, "BypassDropChecks");
  34. }
  35. /// <summary>
  36. /// Checks whether an entity can drop a given entity. Will return false if they are not holding the entity.
  37. /// </summary>
  38. public bool CanDrop(EntityUid uid, EntityUid entity, HandsComponent? handsComp = null, bool checkActionBlocker = true)
  39. {
  40. if (!Resolve(uid, ref handsComp))
  41. return false;
  42. if (!IsHolding(uid, entity, out var hand, handsComp))
  43. return false;
  44. return CanDropHeld(uid, hand, checkActionBlocker);
  45. }
  46. /// <summary>
  47. /// Checks if the contents of a hand is able to be removed from its container.
  48. /// </summary>
  49. public bool CanDropHeld(EntityUid uid, Hand hand, bool checkActionBlocker = true)
  50. {
  51. if (hand.Container?.ContainedEntity is not {} held)
  52. return false;
  53. if (!ContainerSystem.CanRemove(held, hand.Container))
  54. return false;
  55. if (checkActionBlocker && !_actionBlocker.CanDrop(uid))
  56. return false;
  57. return true;
  58. }
  59. /// <summary>
  60. /// Attempts to drop the item in the currently active hand.
  61. /// </summary>
  62. public bool TryDrop(EntityUid uid, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, HandsComponent? handsComp = null)
  63. {
  64. if (!Resolve(uid, ref handsComp))
  65. return false;
  66. if (handsComp.ActiveHand == null)
  67. return false;
  68. return TryDrop(uid, handsComp.ActiveHand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp);
  69. }
  70. /// <summary>
  71. /// Drops an item at the target location.
  72. /// </summary>
  73. public bool TryDrop(EntityUid uid, EntityUid entity, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, HandsComponent? handsComp = null)
  74. {
  75. if (!Resolve(uid, ref handsComp))
  76. return false;
  77. if (!IsHolding(uid, entity, out var hand, handsComp))
  78. return false;
  79. return TryDrop(uid, hand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp);
  80. }
  81. /// <summary>
  82. /// Drops a hands contents at the target location.
  83. /// </summary>
  84. public bool TryDrop(EntityUid uid, Hand hand, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, HandsComponent? handsComp = null)
  85. {
  86. if (!Resolve(uid, ref handsComp))
  87. return false;
  88. if (!CanDropHeld(uid, hand, checkActionBlocker))
  89. return false;
  90. var entity = hand.HeldEntity!.Value;
  91. // if item is a fake item (like with pulling), just delete it rather than bothering with trying to drop it into the world
  92. if (TryComp(entity, out VirtualItemComponent? @virtual))
  93. _virtualSystem.DeleteVirtualItem((entity, @virtual), uid);
  94. if (TerminatingOrDeleted(entity))
  95. return true;
  96. var itemXform = Transform(entity);
  97. if (itemXform.MapUid == null)
  98. return true;
  99. var userXform = Transform(uid);
  100. var isInContainer = ContainerSystem.IsEntityOrParentInContainer(uid, xform: userXform);
  101. // if the user is in a container, drop the item inside the container
  102. if (isInContainer) {
  103. TransformSystem.DropNextTo((entity, itemXform), (uid, userXform));
  104. return true;
  105. }
  106. // drop the item with heavy calculations from their hands and place it at the calculated interaction range position
  107. // The DoDrop is handle if there's no drop target
  108. DoDrop(uid, hand, doDropInteraction: doDropInteraction, handsComp);
  109. // if there's no drop location stop here
  110. if (targetDropLocation == null)
  111. return true;
  112. // otherwise, also move dropped item and rotate it properly according to grid/map
  113. var (itemPos, itemRot) = TransformSystem.GetWorldPositionRotation(entity);
  114. var origin = new MapCoordinates(itemPos, itemXform.MapID);
  115. var target = TransformSystem.ToMapCoordinates(targetDropLocation.Value);
  116. TransformSystem.SetWorldPositionRotation(entity, GetFinalDropCoordinates(uid, origin, target, entity), itemRot);
  117. return true;
  118. }
  119. /// <summary>
  120. /// Attempts to move a held item from a hand into a container that is not another hand, without dropping it on the floor in-between.
  121. /// </summary>
  122. public bool TryDropIntoContainer(EntityUid uid, EntityUid entity, BaseContainer targetContainer, bool checkActionBlocker = true, HandsComponent? handsComp = null)
  123. {
  124. if (!Resolve(uid, ref handsComp))
  125. return false;
  126. if (!IsHolding(uid, entity, out var hand, handsComp))
  127. return false;
  128. if (!CanDropHeld(uid, hand, checkActionBlocker))
  129. return false;
  130. if (!ContainerSystem.CanInsert(entity, targetContainer))
  131. return false;
  132. DoDrop(uid, hand, false, handsComp);
  133. ContainerSystem.Insert(entity, targetContainer);
  134. return true;
  135. }
  136. /// <summary>
  137. /// Calculates the final location a dropped item will end up at, accounting for max drop range and collision along the targeted drop path, Does a check to see if a user should bypass those checks as well.
  138. /// </summary>
  139. private Vector2 GetFinalDropCoordinates(EntityUid user, MapCoordinates origin, MapCoordinates target, EntityUid held)
  140. {
  141. var dropVector = target.Position - origin.Position;
  142. var requestedDropDistance = dropVector.Length();
  143. var dropLength = dropVector.Length();
  144. if (ShouldIgnoreRestrictions(user))
  145. {
  146. if (dropVector.Length() > SharedInteractionSystem.InteractionRange)
  147. {
  148. dropVector = dropVector.Normalized() * SharedInteractionSystem.InteractionRange;
  149. target = new MapCoordinates(origin.Position + dropVector, target.MapId);
  150. }
  151. dropLength = _interactionSystem.UnobstructedDistance(origin, target, predicate: e => e == user || e == held);
  152. }
  153. if (dropLength < requestedDropDistance)
  154. return origin.Position + dropVector.Normalized() * dropLength;
  155. return target.Position;
  156. }
  157. /// <summary>
  158. /// Removes the contents of a hand from its container. Assumes that the removal is allowed. In general, you should not be calling this directly.
  159. /// </summary>
  160. public virtual void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? handsComp = null, bool log = true)
  161. {
  162. if (!Resolve(uid, ref handsComp))
  163. return;
  164. if (hand.Container?.ContainedEntity == null)
  165. return;
  166. var entity = hand.Container.ContainedEntity.Value;
  167. if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(entity))
  168. return;
  169. if (!ContainerSystem.Remove(entity, hand.Container))
  170. {
  171. Log.Error($"Failed to remove {ToPrettyString(entity)} from users hand container when dropping. User: {ToPrettyString(uid)}. Hand: {hand.Name}.");
  172. return;
  173. }
  174. Dirty(uid, handsComp);
  175. if (doDropInteraction)
  176. _interactionSystem.DroppedInteraction(uid, entity);
  177. if (log)
  178. _adminLogger.Add(LogType.Drop, LogImpact.Low, $"{ToPrettyString(uid):user} dropped {ToPrettyString(entity):entity}");
  179. if (hand == handsComp.ActiveHand)
  180. RaiseLocalEvent(entity, new HandDeselectedEvent(uid));
  181. }
  182. }