| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- using Content.Shared.ActionBlocker;
- using Content.Shared.Containers.ItemSlots;
- using Content.Shared.Hands.Components;
- using Content.Shared.Hands.EntitySystems;
- using Content.Shared.Input;
- using Content.Shared.Inventory;
- using Content.Shared.Popups;
- using Content.Shared.Stacks;
- using Content.Shared.Storage;
- using Content.Shared.Storage.EntitySystems;
- using Content.Shared.Whitelist;
- using Robust.Shared.Containers;
- using Robust.Shared.Input.Binding;
- using Robust.Shared.Player;
- namespace Content.Shared.Interaction;
- /// <summary>
- /// This handles smart equipping or inserting/ejecting from slots through keybinds--generally shift+E and shift+B
- /// </summary>
- public sealed class SmartEquipSystem : EntitySystem
- {
- [Dependency] private readonly SharedHandsSystem _hands = default!;
- [Dependency] private readonly SharedStorageSystem _storage = default!;
- [Dependency] private readonly InventorySystem _inventory = default!;
- [Dependency] private readonly ItemSlotsSystem _slots = default!;
- [Dependency] private readonly SharedContainerSystem _container = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
- [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
- /// <inheritdoc/>
- public override void Initialize()
- {
- CommandBinds.Builder
- .Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack, handle: false, outsidePrediction: false))
- .Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt, handle: false, outsidePrediction: false))
- .Register<SmartEquipSystem>();
- }
- public override void Shutdown()
- {
- base.Shutdown();
- CommandBinds.Unregister<SmartEquipSystem>();
- }
- private void HandleSmartEquipBackpack(ICommonSession? session)
- {
- HandleSmartEquip(session, "back");
- }
- private void HandleSmartEquipBelt(ICommonSession? session)
- {
- HandleSmartEquip(session, "belt");
- }
- private void HandleSmartEquip(ICommonSession? session, string equipmentSlot)
- {
- if (session is not { } playerSession)
- return;
- if (playerSession.AttachedEntity is not { Valid: true } uid || !Exists(uid))
- return;
- // early out if we don't have any hands or a valid inventory slot
- if (!TryComp<HandsComponent>(uid, out var hands) || hands.ActiveHand == null)
- return;
- var handItem = hands.ActiveHand.HeldEntity;
- // can the user interact, and is the item interactable? e.g. virtual items
- if (!_actionBlocker.CanInteract(uid, handItem))
- return;
- if (!TryComp<InventoryComponent>(uid, out var inventory) || !_inventory.HasSlot(uid, equipmentSlot, inventory))
- {
- _popup.PopupClient(Loc.GetString("smart-equip-missing-equipment-slot", ("slotName", equipmentSlot)), uid, uid);
- return;
- }
- // early out if we have an item and cant drop it at all
- if (handItem != null && !_hands.CanDropHeld(uid, hands.ActiveHand))
- {
- _popup.PopupClient(Loc.GetString("smart-equip-cant-drop"), uid, uid);
- return;
- }
- // There are eight main cases we want to handle here,
- // so let's write them out
- // if the slot we're trying to smart equip from:
- // 1) doesn't have an item
- // - with hand item: try to put it in the slot
- // - without hand item: fail
- // 2) has an item, and that item is a storage item
- // - with hand item: try to put it in storage
- // - without hand item: try to take the last stored item and put it in our hands
- // 3) has an item, and that item is an item slots holder
- // - with hand item: get the highest priority item slot with a valid whitelist and try to insert it
- // - without hand item: get the highest priority item slot with an item and try to eject it
- // 4) has an item, with no special storage components
- // - with hand item: fail
- // - without hand item: try to put the item into your hand
- _inventory.TryGetSlotEntity(uid, equipmentSlot, out var slotEntity);
- var emptyEquipmentSlotString = Loc.GetString("smart-equip-empty-equipment-slot", ("slotName", equipmentSlot));
- // case 1 (no slot item):
- if (slotEntity is not { } slotItem)
- {
- if (handItem == null)
- {
- _popup.PopupClient(emptyEquipmentSlotString, uid, uid);
- return;
- }
- if (!_inventory.CanEquip(uid, handItem.Value, equipmentSlot, out var reason))
- {
- _popup.PopupClient(Loc.GetString(reason), uid, uid);
- return;
- }
- _hands.TryDrop(uid, hands.ActiveHand, handsComp: hands);
- _inventory.TryEquip(uid, handItem.Value, equipmentSlot, predicted: true, checkDoafter:true);
- return;
- }
- // case 2 (storage item):
- if (TryComp<StorageComponent>(slotItem, out var storage))
- {
- switch (handItem)
- {
- case null when storage.Container.ContainedEntities.Count == 0:
- _popup.PopupClient(emptyEquipmentSlotString, uid, uid);
- return;
- case null:
- var removing = storage.Container.ContainedEntities[^1];
- _container.RemoveEntity(slotItem, removing);
- _hands.TryPickup(uid, removing, handsComp: hands);
- return;
- }
- if (!_storage.CanInsert(slotItem, handItem.Value, out var reason))
- {
- if (reason != null)
- _popup.PopupClient(Loc.GetString(reason), uid, uid);
- return;
- }
- _hands.TryDrop(uid, hands.ActiveHand, handsComp: hands);
- _storage.Insert(slotItem, handItem.Value, out var stacked, out _);
- // if the hand item stacked with the things in inventory, but there's no more space left for the rest
- // of the stack, place the stack back in hand rather than dropping it on the floor
- if (stacked != null && !_storage.CanInsert(slotItem, handItem.Value, out _))
- {
- if (TryComp<StackComponent>(handItem.Value, out var handStack) && handStack.Count > 0)
- _hands.TryPickup(uid, handItem.Value, handsComp: hands);
- }
- return;
- }
- // case 3 (itemslot item):
- if (TryComp<ItemSlotsComponent>(slotItem, out var slots))
- {
- if (handItem == null)
- {
- ItemSlot? toEjectFrom = null;
- foreach (var slot in slots.Slots.Values)
- {
- if (slot.HasItem && slot.Priority > (toEjectFrom?.Priority ?? int.MinValue))
- toEjectFrom = slot;
- }
- if (toEjectFrom == null)
- {
- _popup.PopupClient(emptyEquipmentSlotString, uid, uid);
- return;
- }
- _slots.TryEjectToHands(slotItem, toEjectFrom, uid, excludeUserAudio: true);
- return;
- }
- ItemSlot? toInsertTo = null;
- foreach (var slot in slots.Slots.Values)
- {
- if (!slot.HasItem
- && _whitelistSystem.IsWhitelistPassOrNull(slot.Whitelist, handItem.Value)
- && slot.Priority > (toInsertTo?.Priority ?? int.MinValue))
- {
- toInsertTo = slot;
- }
- }
- if (toInsertTo == null)
- {
- _popup.PopupClient(Loc.GetString("smart-equip-no-valid-item-slot-insert", ("item", handItem.Value)), uid, uid);
- return;
- }
- _slots.TryInsertFromHand(slotItem, toInsertTo, uid, hands, excludeUserAudio: true);
- return;
- }
- // case 4 (just an item):
- if (handItem != null)
- return;
- if (!_inventory.CanUnequip(uid, equipmentSlot, out var inventoryReason))
- {
- _popup.PopupClient(Loc.GetString(inventoryReason), uid, uid);
- return;
- }
- _inventory.TryUnequip(uid, equipmentSlot, inventory: inventory, predicted: true, checkDoafter: true);
- _hands.TryPickup(uid, slotItem, handsComp: hands);
- }
- }
|