1
0

SmartEquipSystem.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. using Content.Shared.ActionBlocker;
  2. using Content.Shared.Containers.ItemSlots;
  3. using Content.Shared.Hands.Components;
  4. using Content.Shared.Hands.EntitySystems;
  5. using Content.Shared.Input;
  6. using Content.Shared.Inventory;
  7. using Content.Shared.Popups;
  8. using Content.Shared.Stacks;
  9. using Content.Shared.Storage;
  10. using Content.Shared.Storage.EntitySystems;
  11. using Content.Shared.Whitelist;
  12. using Robust.Shared.Containers;
  13. using Robust.Shared.Input.Binding;
  14. using Robust.Shared.Player;
  15. namespace Content.Shared.Interaction;
  16. /// <summary>
  17. /// This handles smart equipping or inserting/ejecting from slots through keybinds--generally shift+E and shift+B
  18. /// </summary>
  19. public sealed class SmartEquipSystem : EntitySystem
  20. {
  21. [Dependency] private readonly SharedHandsSystem _hands = default!;
  22. [Dependency] private readonly SharedStorageSystem _storage = default!;
  23. [Dependency] private readonly InventorySystem _inventory = default!;
  24. [Dependency] private readonly ItemSlotsSystem _slots = default!;
  25. [Dependency] private readonly SharedContainerSystem _container = default!;
  26. [Dependency] private readonly SharedPopupSystem _popup = default!;
  27. [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
  28. [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
  29. /// <inheritdoc/>
  30. public override void Initialize()
  31. {
  32. CommandBinds.Builder
  33. .Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack, handle: false, outsidePrediction: false))
  34. .Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt, handle: false, outsidePrediction: false))
  35. .Register<SmartEquipSystem>();
  36. }
  37. public override void Shutdown()
  38. {
  39. base.Shutdown();
  40. CommandBinds.Unregister<SmartEquipSystem>();
  41. }
  42. private void HandleSmartEquipBackpack(ICommonSession? session)
  43. {
  44. HandleSmartEquip(session, "back");
  45. }
  46. private void HandleSmartEquipBelt(ICommonSession? session)
  47. {
  48. HandleSmartEquip(session, "belt");
  49. }
  50. private void HandleSmartEquip(ICommonSession? session, string equipmentSlot)
  51. {
  52. if (session is not { } playerSession)
  53. return;
  54. if (playerSession.AttachedEntity is not { Valid: true } uid || !Exists(uid))
  55. return;
  56. // early out if we don't have any hands or a valid inventory slot
  57. if (!TryComp<HandsComponent>(uid, out var hands) || hands.ActiveHand == null)
  58. return;
  59. var handItem = hands.ActiveHand.HeldEntity;
  60. // can the user interact, and is the item interactable? e.g. virtual items
  61. if (!_actionBlocker.CanInteract(uid, handItem))
  62. return;
  63. if (!TryComp<InventoryComponent>(uid, out var inventory) || !_inventory.HasSlot(uid, equipmentSlot, inventory))
  64. {
  65. _popup.PopupClient(Loc.GetString("smart-equip-missing-equipment-slot", ("slotName", equipmentSlot)), uid, uid);
  66. return;
  67. }
  68. // early out if we have an item and cant drop it at all
  69. if (handItem != null && !_hands.CanDropHeld(uid, hands.ActiveHand))
  70. {
  71. _popup.PopupClient(Loc.GetString("smart-equip-cant-drop"), uid, uid);
  72. return;
  73. }
  74. // There are eight main cases we want to handle here,
  75. // so let's write them out
  76. // if the slot we're trying to smart equip from:
  77. // 1) doesn't have an item
  78. // - with hand item: try to put it in the slot
  79. // - without hand item: fail
  80. // 2) has an item, and that item is a storage item
  81. // - with hand item: try to put it in storage
  82. // - without hand item: try to take the last stored item and put it in our hands
  83. // 3) has an item, and that item is an item slots holder
  84. // - with hand item: get the highest priority item slot with a valid whitelist and try to insert it
  85. // - without hand item: get the highest priority item slot with an item and try to eject it
  86. // 4) has an item, with no special storage components
  87. // - with hand item: fail
  88. // - without hand item: try to put the item into your hand
  89. _inventory.TryGetSlotEntity(uid, equipmentSlot, out var slotEntity);
  90. var emptyEquipmentSlotString = Loc.GetString("smart-equip-empty-equipment-slot", ("slotName", equipmentSlot));
  91. // case 1 (no slot item):
  92. if (slotEntity is not { } slotItem)
  93. {
  94. if (handItem == null)
  95. {
  96. _popup.PopupClient(emptyEquipmentSlotString, uid, uid);
  97. return;
  98. }
  99. if (!_inventory.CanEquip(uid, handItem.Value, equipmentSlot, out var reason))
  100. {
  101. _popup.PopupClient(Loc.GetString(reason), uid, uid);
  102. return;
  103. }
  104. _hands.TryDrop(uid, hands.ActiveHand, handsComp: hands);
  105. _inventory.TryEquip(uid, handItem.Value, equipmentSlot, predicted: true, checkDoafter:true);
  106. return;
  107. }
  108. // case 2 (storage item):
  109. if (TryComp<StorageComponent>(slotItem, out var storage))
  110. {
  111. switch (handItem)
  112. {
  113. case null when storage.Container.ContainedEntities.Count == 0:
  114. _popup.PopupClient(emptyEquipmentSlotString, uid, uid);
  115. return;
  116. case null:
  117. var removing = storage.Container.ContainedEntities[^1];
  118. _container.RemoveEntity(slotItem, removing);
  119. _hands.TryPickup(uid, removing, handsComp: hands);
  120. return;
  121. }
  122. if (!_storage.CanInsert(slotItem, handItem.Value, out var reason))
  123. {
  124. if (reason != null)
  125. _popup.PopupClient(Loc.GetString(reason), uid, uid);
  126. return;
  127. }
  128. _hands.TryDrop(uid, hands.ActiveHand, handsComp: hands);
  129. _storage.Insert(slotItem, handItem.Value, out var stacked, out _);
  130. // if the hand item stacked with the things in inventory, but there's no more space left for the rest
  131. // of the stack, place the stack back in hand rather than dropping it on the floor
  132. if (stacked != null && !_storage.CanInsert(slotItem, handItem.Value, out _))
  133. {
  134. if (TryComp<StackComponent>(handItem.Value, out var handStack) && handStack.Count > 0)
  135. _hands.TryPickup(uid, handItem.Value, handsComp: hands);
  136. }
  137. return;
  138. }
  139. // case 3 (itemslot item):
  140. if (TryComp<ItemSlotsComponent>(slotItem, out var slots))
  141. {
  142. if (handItem == null)
  143. {
  144. ItemSlot? toEjectFrom = null;
  145. foreach (var slot in slots.Slots.Values)
  146. {
  147. if (slot.HasItem && slot.Priority > (toEjectFrom?.Priority ?? int.MinValue))
  148. toEjectFrom = slot;
  149. }
  150. if (toEjectFrom == null)
  151. {
  152. _popup.PopupClient(emptyEquipmentSlotString, uid, uid);
  153. return;
  154. }
  155. _slots.TryEjectToHands(slotItem, toEjectFrom, uid, excludeUserAudio: true);
  156. return;
  157. }
  158. ItemSlot? toInsertTo = null;
  159. foreach (var slot in slots.Slots.Values)
  160. {
  161. if (!slot.HasItem
  162. && _whitelistSystem.IsWhitelistPassOrNull(slot.Whitelist, handItem.Value)
  163. && slot.Priority > (toInsertTo?.Priority ?? int.MinValue))
  164. {
  165. toInsertTo = slot;
  166. }
  167. }
  168. if (toInsertTo == null)
  169. {
  170. _popup.PopupClient(Loc.GetString("smart-equip-no-valid-item-slot-insert", ("item", handItem.Value)), uid, uid);
  171. return;
  172. }
  173. _slots.TryInsertFromHand(slotItem, toInsertTo, uid, hands, excludeUserAudio: true);
  174. return;
  175. }
  176. // case 4 (just an item):
  177. if (handItem != null)
  178. return;
  179. if (!_inventory.CanUnequip(uid, equipmentSlot, out var inventoryReason))
  180. {
  181. _popup.PopupClient(Loc.GetString(inventoryReason), uid, uid);
  182. return;
  183. }
  184. _inventory.TryUnequip(uid, equipmentSlot, inventory: inventory, predicted: true, checkDoafter: true);
  185. _hands.TryPickup(uid, slotItem, handsComp: hands);
  186. }
  187. }