From fdf718e58607271c58294b37a4e4ca37e20079e3 Mon Sep 17 00:00:00 2001 From: Kara Date: Thu, 21 Dec 2023 07:17:09 -0700 Subject: [PATCH] Reimplement smart equipping (#22815) * Reimplement smart equipping * inv prediction fix * oops --- Content.Server/Hands/Systems/HandsSystem.cs | 80 ------- .../Containers/ItemSlot/ItemSlotsSystem.cs | 14 +- .../Interaction/SmartEquipSystem.cs | 215 ++++++++++++++++++ Resources/Locale/en-US/hands/hands-system.ftl | 5 - .../en-US/interaction/smart-equip-system.ftl | 4 + 5 files changed, 226 insertions(+), 92 deletions(-) create mode 100644 Content.Shared/Interaction/SmartEquipSystem.cs create mode 100644 Resources/Locale/en-US/interaction/smart-equip-system.ftl diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index 5c750e7544..15190c81c7 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -59,8 +59,6 @@ namespace Content.Server.Hands.Systems CommandBinds.Builder .Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem)) - .Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack)) - .Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt)) .Register(); } @@ -217,85 +215,7 @@ namespace Content.Server.Hands.Systems return true; } - private void HandleSmartEquipBackpack(ICommonSession? session) - { - HandleSmartEquip(session, "back"); - } - - private void HandleSmartEquipBelt(ICommonSession? session) - { - HandleSmartEquip(session, "belt"); - } - - // why tf is this even in hands system. - // TODO: move to storage or inventory - private void HandleSmartEquip(ICommonSession? session, string equipmentSlot) - { - if (session is not { } playerSession) - return; - - if (playerSession.AttachedEntity is not {Valid: true} plyEnt || !Exists(plyEnt)) - return; - - if (!_actionBlockerSystem.CanInteract(plyEnt, null)) - return; - if (!TryComp(plyEnt, out var hands) || hands.ActiveHand == null) - return; - - if (!_inventorySystem.TryGetSlotEntity(plyEnt, equipmentSlot, out var slotEntity) || - !TryComp(slotEntity, out StorageComponent? storageComponent)) - { - if (_inventorySystem.HasSlot(plyEnt, equipmentSlot)) - { - if (hands.ActiveHand.HeldEntity == null && slotEntity != null) - { - _inventorySystem.TryUnequip(plyEnt, equipmentSlot); - PickupOrDrop(plyEnt, slotEntity.Value); - return; - } - if (hands.ActiveHand.HeldEntity == null) - return; - if (!_inventorySystem.CanEquip(plyEnt, hands.ActiveHand.HeldEntity.Value, equipmentSlot, out var reason)) - { - _popupSystem.PopupEntity(Loc.GetString(reason), plyEnt, session); - return; - } - if (slotEntity == null) - { - _inventorySystem.TryEquip(plyEnt, hands.ActiveHand.HeldEntity.Value, equipmentSlot); - return; - } - _inventorySystem.TryUnequip(plyEnt, equipmentSlot); - _inventorySystem.TryEquip(plyEnt, hands.ActiveHand.HeldEntity.Value, equipmentSlot); - PickupOrDrop(plyEnt, slotEntity.Value); - return; - } - _popupSystem.PopupEntity(Loc.GetString("hands-system-missing-equipment-slot", ("slotName", equipmentSlot)), plyEnt, session); - return; - } - - if (hands.ActiveHand.HeldEntity != null) - { - _storageSystem.PlayerInsertHeldEntity(slotEntity.Value, plyEnt, storageComponent); - } - else - { - if (!storageComponent.Container.ContainedEntities.Any()) - { - _popupSystem.PopupEntity(Loc.GetString("hands-system-empty-equipment-slot", ("slotName", equipmentSlot)), plyEnt, session); - } - else - { - var lastStoredEntity = storageComponent.Container.ContainedEntities[^1]; - - if (storageComponent.Container.Remove(lastStoredEntity)) - { - PickupOrDrop(plyEnt, lastStoredEntity, animateUser: true, handsComp: hands); - } - } - } - } #endregion } } diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index ad27101cc1..b272cc182e 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -277,7 +277,7 @@ namespace Content.Shared.Containers.ItemSlots /// Tries to insert item into a specific slot. /// /// False if failed to insert item - public bool TryInsert(EntityUid uid, string id, EntityUid item, EntityUid? user, ItemSlotsComponent? itemSlots = null) + public bool TryInsert(EntityUid uid, string id, EntityUid item, EntityUid? user, ItemSlotsComponent? itemSlots = null, bool excludeUserAudio = false) { if (!Resolve(uid, ref itemSlots)) return false; @@ -285,19 +285,19 @@ namespace Content.Shared.Containers.ItemSlots if (!itemSlots.Slots.TryGetValue(id, out var slot)) return false; - return TryInsert(uid, slot, item, user); + return TryInsert(uid, slot, item, user, excludeUserAudio: excludeUserAudio); } /// /// Tries to insert item into a specific slot. /// /// False if failed to insert item - public bool TryInsert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user) + public bool TryInsert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user, bool excludeUserAudio = false) { if (!CanInsert(uid, item, user, slot)) return false; - Insert(uid, slot, item, user); + Insert(uid, slot, item, user, excludeUserAudio: excludeUserAudio); return true; } @@ -306,12 +306,12 @@ namespace Content.Shared.Containers.ItemSlots /// Does not check action blockers. /// /// False if failed to insert item - public bool TryInsertFromHand(EntityUid uid, ItemSlot slot, EntityUid user, HandsComponent? hands = null) + public bool TryInsertFromHand(EntityUid uid, ItemSlot slot, EntityUid user, HandsComponent? hands = null, bool excludeUserAudio = false) { if (!Resolve(user, ref hands, false)) return false; - if (hands.ActiveHand?.HeldEntity is not EntityUid held) + if (hands.ActiveHand?.HeldEntity is not { } held) return false; if (!CanInsert(uid, held, user, slot)) @@ -321,7 +321,7 @@ namespace Content.Shared.Containers.ItemSlots if (!_handsSystem.TryDrop(user, hands.ActiveHand)) return false; - Insert(uid, slot, held, user); + Insert(uid, slot, held, user, excludeUserAudio: excludeUserAudio); return true; } #endregion diff --git a/Content.Shared/Interaction/SmartEquipSystem.cs b/Content.Shared/Interaction/SmartEquipSystem.cs new file mode 100644 index 0000000000..17c8f2e511 --- /dev/null +++ b/Content.Shared/Interaction/SmartEquipSystem.cs @@ -0,0 +1,215 @@ +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.Storage; +using Content.Shared.Storage.EntitySystems; +using Robust.Shared.Containers; +using Robust.Shared.Input.Binding; +using Robust.Shared.Player; + +namespace Content.Shared.Interaction; + +/// +/// This handles smart equipping or inserting/ejecting from slots through keybinds--generally shift+E and shift+B +/// +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!; + + /// + 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(); + } + + public override void Shutdown() + { + base.Shutdown(); + + CommandBinds.Unregister(); + } + + 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; + + if (!_actionBlocker.CanInteract(uid, null)) + return; + + // early out if we don't have any hands or a valid inventory slot + if (!TryComp(uid, out var hands) || hands.ActiveHand == null) + return; + + if (!TryComp(uid, out var inventory) || !_inventory.HasSlot(uid, equipmentSlot, inventory)) + { + _popup.PopupClient(Loc.GetString("smart-equip-missing-equipment-slot", ("slotName", equipmentSlot)), uid, uid); + return; + } + + var handItem = hands.ActiveHand.HeldEntity; + + // 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); + return; + } + + // case 2 (storage item): + if (TryComp(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 (stacked != null) + _hands.TryPickup(uid, stacked.Value, handsComp: hands); + + return; + } + + // case 3 (itemslot item): + if (TryComp(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 + && (slot.Whitelist?.IsValid(handItem.Value, EntityManager) ?? true) + && 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); + _hands.TryPickup(uid, slotItem, handsComp: hands); + } +} diff --git a/Resources/Locale/en-US/hands/hands-system.ftl b/Resources/Locale/en-US/hands/hands-system.ftl index 7761b0c0ce..6b7859fe17 100644 --- a/Resources/Locale/en-US/hands/hands-system.ftl +++ b/Resources/Locale/en-US/hands/hands-system.ftl @@ -1,8 +1,3 @@ -## HandsSystem -hands-system-missing-equipment-slot = You have no {$slotName} to take something out of! -hands-system-empty-equipment-slot = There's nothing in your {$slotName} to take out! - - # Examine text after when they're holding something (in-hand) comp-hands-examine = { CAPITALIZE(SUBJECT($user)) } { CONJUGATE-BE($user) } holding { $items }. comp-hands-examine-empty = { CAPITALIZE(SUBJECT($user)) } { CONJUGATE-BE($user) } not holding anything. diff --git a/Resources/Locale/en-US/interaction/smart-equip-system.ftl b/Resources/Locale/en-US/interaction/smart-equip-system.ftl new file mode 100644 index 0000000000..8c61116caa --- /dev/null +++ b/Resources/Locale/en-US/interaction/smart-equip-system.ftl @@ -0,0 +1,4 @@ +smart-equip-missing-equipment-slot = You have no {$slotName} slot to interact with! +smart-equip-empty-equipment-slot = There's nothing in your {$slotName} slot to take out! +smart-equip-no-valid-item-slot-insert = There's no valid item slot for {THE($item)} to go into! +smart-equip-cant-drop = You can't drop that! -- 2.51.2