SharedHandsSystem.Pickup.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. using Content.Shared.Clothing.Components;
  2. using Content.Shared.Database;
  3. using Content.Shared.Hands.Components;
  4. using Content.Shared.Item;
  5. using Robust.Shared.Containers;
  6. using Robust.Shared.Map;
  7. using Robust.Shared.Physics;
  8. using Robust.Shared.Physics.Components;
  9. namespace Content.Shared.Hands.EntitySystems;
  10. public abstract partial class SharedHandsSystem : EntitySystem
  11. {
  12. private void InitializePickup()
  13. {
  14. SubscribeLocalEvent<HandsComponent, EntInsertedIntoContainerMessage>(HandleEntityInserted);
  15. }
  16. protected virtual void HandleEntityInserted(EntityUid uid, HandsComponent hands, EntInsertedIntoContainerMessage args)
  17. {
  18. if (!TryGetHand(uid, args.Container.ID, out var hand))
  19. {
  20. return;
  21. }
  22. var didEquip = new DidEquipHandEvent(uid, args.Entity, hand);
  23. RaiseLocalEvent(uid, didEquip, false);
  24. var gotEquipped = new GotEquippedHandEvent(uid, args.Entity, hand);
  25. RaiseLocalEvent(args.Entity, gotEquipped, false);
  26. }
  27. /// <summary>
  28. /// Maximum pickup distance for which the pickup animation plays.
  29. /// </summary>
  30. public const float MaxAnimationRange = 10;
  31. /// <summary>
  32. /// Tries to pick up an entity to a specific hand. If no explicit hand is specified, defaults to using the currently active hand.
  33. /// </summary>
  34. public bool TryPickup(
  35. EntityUid uid,
  36. EntityUid entity,
  37. string? handName = null,
  38. bool checkActionBlocker = true,
  39. bool animateUser = false,
  40. bool animate = true,
  41. HandsComponent? handsComp = null,
  42. ItemComponent? item = null)
  43. {
  44. if (!Resolve(uid, ref handsComp, false))
  45. return false;
  46. var hand = handsComp.ActiveHand;
  47. if (handName != null && !handsComp.Hands.TryGetValue(handName, out hand))
  48. return false;
  49. if (hand == null)
  50. return false;
  51. return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item);
  52. }
  53. /// <summary>
  54. /// Attempts to pick up an item into any empty hand. Prioritizes the currently active hand.
  55. /// </summary>
  56. /// <remarks>
  57. /// If one empty hand fails to pick up the item, this will NOT check other hands. If ever hand-specific item
  58. /// restrictions are added, there a might need to be a TryPickupAllHands or something like that.
  59. /// </remarks>
  60. public bool TryPickupAnyHand(
  61. EntityUid uid,
  62. EntityUid entity,
  63. bool checkActionBlocker = true,
  64. bool animateUser = false,
  65. bool animate = true,
  66. HandsComponent? handsComp = null,
  67. ItemComponent? item = null)
  68. {
  69. if (!Resolve(uid, ref handsComp, false))
  70. return false;
  71. if (!TryGetEmptyHand(uid, out var hand, handsComp))
  72. return false;
  73. return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item);
  74. }
  75. public bool TryPickup(
  76. EntityUid uid,
  77. EntityUid entity,
  78. Hand hand,
  79. bool checkActionBlocker = true,
  80. bool animate = true,
  81. HandsComponent? handsComp = null,
  82. ItemComponent? item = null)
  83. {
  84. if (!Resolve(uid, ref handsComp, false))
  85. return false;
  86. if (!Resolve(entity, ref item, false))
  87. return false;
  88. if (!CanPickupToHand(uid, entity, hand, checkActionBlocker, handsComp, item))
  89. return false;
  90. if (animate)
  91. {
  92. var xform = Transform(uid);
  93. var coordinateEntity = xform.ParentUid.IsValid() ? xform.ParentUid : uid;
  94. var itemXform = Transform(entity);
  95. var itemPos = TransformSystem.GetMapCoordinates(entity, xform: itemXform);
  96. if (itemPos.MapId == xform.MapID
  97. && (itemPos.Position - TransformSystem.GetMapCoordinates(uid, xform: xform).Position).Length() <= MaxAnimationRange
  98. && MetaData(entity).VisibilityMask == MetaData(uid).VisibilityMask) // Don't animate aghost pickups.
  99. {
  100. var initialPosition = TransformSystem.ToCoordinates(coordinateEntity, itemPos);
  101. _storage.PlayPickupAnimation(entity, initialPosition, xform.Coordinates, itemXform.LocalRotation, uid);
  102. }
  103. }
  104. DoPickup(uid, hand, entity, handsComp);
  105. return true;
  106. }
  107. /// <summary>
  108. /// Tries to pick up an entity into any hand, forcing to drop an item if there are no free hands
  109. /// By default it does check if it's possible to drop items
  110. /// </summary>
  111. public bool TryForcePickupAnyHand(EntityUid uid, EntityUid entity, bool checkActionBlocker = true, HandsComponent? handsComp = null, ItemComponent? item = null)
  112. {
  113. if (!Resolve(uid, ref handsComp, false))
  114. return false;
  115. if (TryPickupAnyHand(uid, entity, checkActionBlocker: checkActionBlocker, handsComp: handsComp))
  116. return true;
  117. foreach (var hand in handsComp.Hands.Values)
  118. {
  119. if (TryDrop(uid, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp) &&
  120. TryPickup(uid, entity, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp))
  121. {
  122. return true;
  123. }
  124. }
  125. return false;
  126. }
  127. public bool CanPickupAnyHand(EntityUid uid, EntityUid entity, bool checkActionBlocker = true, HandsComponent? handsComp = null, ItemComponent? item = null)
  128. {
  129. if (!Resolve(uid, ref handsComp, false))
  130. return false;
  131. if (!TryGetEmptyHand(uid, out var hand, handsComp))
  132. return false;
  133. return CanPickupToHand(uid, entity, hand, checkActionBlocker, handsComp, item);
  134. }
  135. /// <summary>
  136. /// Checks whether a given item will fit into a specific user's hand. Unless otherwise specified, this will also check the general CanPickup action blocker.
  137. /// </summary>
  138. public bool CanPickupToHand(EntityUid uid, EntityUid entity, Hand hand, bool checkActionBlocker = true, HandsComponent? handsComp = null, ItemComponent? item = null)
  139. {
  140. if (!Resolve(uid, ref handsComp, false))
  141. return false;
  142. var handContainer = hand.Container;
  143. if (handContainer == null || handContainer.ContainedEntity != null)
  144. return false;
  145. if (!Resolve(entity, ref item, false))
  146. return false;
  147. if (TryComp(entity, out PhysicsComponent? physics) && physics.BodyType == BodyType.Static)
  148. return false;
  149. if (checkActionBlocker && !_actionBlocker.CanPickup(uid, entity))
  150. return false;
  151. if (ContainerSystem.TryGetContainingContainer((entity, null, null), out var container))
  152. {
  153. if (!ContainerSystem.CanRemove(entity, container))
  154. return false;
  155. if (_inventory.TryGetSlotEntity(uid, container.ID, out var slotEnt) &&
  156. slotEnt == entity &&
  157. !_inventory.CanUnequip(uid, entity, container.ID, out _))
  158. return false;
  159. }
  160. // check can insert (including raising attempt events).
  161. return ContainerSystem.CanInsert(entity, handContainer);
  162. }
  163. /// <summary>
  164. /// Puts an item into any hand, preferring the active hand, or puts it on the floor.
  165. /// </summary>
  166. /// <param name="dropNear">If true, the item will be dropped near the owner of the hand if possible.</param>
  167. public void PickupOrDrop(
  168. EntityUid? uid,
  169. EntityUid entity,
  170. bool checkActionBlocker = true,
  171. bool animateUser = false,
  172. bool animate = true,
  173. bool dropNear = false,
  174. HandsComponent? handsComp = null,
  175. ItemComponent? item = null)
  176. {
  177. if (uid == null
  178. || !Resolve(uid.Value, ref handsComp, false)
  179. || !TryGetEmptyHand(uid.Value, out var hand, handsComp)
  180. || !TryPickup(uid.Value, entity, hand, checkActionBlocker, animate, handsComp, item))
  181. {
  182. // TODO make this check upwards for any container, and parent to that.
  183. // Currently this just checks the direct parent, so items can still teleport through containers.
  184. ContainerSystem.AttachParentToContainerOrGrid((entity, Transform(entity)));
  185. if (dropNear && uid.HasValue)
  186. {
  187. TransformSystem.PlaceNextTo(entity, uid.Value);
  188. }
  189. }
  190. }
  191. /// <summary>
  192. /// Puts an entity into the player's hand, assumes that the insertion is allowed. In general, you should not be calling this function directly.
  193. /// </summary>
  194. public virtual void DoPickup(EntityUid uid, Hand hand, EntityUid entity, HandsComponent? hands = null, bool log = true)
  195. {
  196. if (!Resolve(uid, ref hands))
  197. return;
  198. var handContainer = hand.Container;
  199. if (handContainer == null || handContainer.ContainedEntity != null)
  200. return;
  201. if (!ContainerSystem.Insert(entity, handContainer))
  202. {
  203. Log.Error($"Failed to insert {ToPrettyString(entity)} into users hand container when picking up. User: {ToPrettyString(uid)}. Hand: {hand.Name}.");
  204. return;
  205. }
  206. if (log)
  207. _adminLogger.Add(LogType.Pickup, LogImpact.Low, $"{ToPrettyString(uid):user} picked up {ToPrettyString(entity):entity}");
  208. Dirty(uid, hands);
  209. if (hand == hands.ActiveHand)
  210. RaiseLocalEvent(entity, new HandSelectedEvent(uid), false);
  211. }
  212. }