// Only unused on .NET Core due to KeyValuePair.Deconstruct // ReSharper disable once RedundantUsingDirective using Robust.Shared.Utility; using System; using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.EntitySystems; using Content.Shared.GameObjects; using Robust.Server.GameObjects.Components.Container; using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects.Components; using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.ViewVariables; using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; using static Content.Shared.GameObjects.SharedInventoryComponent.ClientInventoryMessage; namespace Content.Server.GameObjects { [RegisterComponent] public class InventoryComponent : SharedInventoryComponent { #pragma warning disable 649 [Dependency] private readonly IEntitySystemManager _entitySystemManager; #pragma warning restore 649 [ViewVariables] private readonly Dictionary SlotContainers = new Dictionary(); public override void Initialize() { base.Initialize(); foreach (var slotName in InventoryInstance.SlotMasks) { if (slotName != Slots.NONE) { AddSlot(slotName); } } } public override void OnRemove() { var slots = SlotContainers.Keys.ToList(); foreach (var slot in slots) { RemoveSlot(slot); } base.OnRemove(); } /// /// Helper to get container name for specified slot on this component /// /// /// private string GetSlotString(Slots slot) { return Name + "_" + Enum.GetName(typeof(Slots), slot); } /// /// Gets the clothing equipped to the specified slot. /// /// The slot to get the item for. /// Null if the slot is empty, otherwise the item. public ItemComponent GetSlotItem(Slots slot) { return GetSlotItem(slot); } public T GetSlotItem(Slots slot) where T : ItemComponent { return SlotContainers[slot].ContainedEntity?.GetComponent(); } public bool TryGetSlotItem(Slots slot, out T itemComponent) where T : ItemComponent { itemComponent = GetSlotItem(slot); return itemComponent != null; } /// /// Equips slothing to the specified slot. /// /// /// This will fail if there is already an item in the specified slot. /// /// The slot to put the item in. /// The item to insert into the slot. /// True if the item was successfully inserted, false otherwise. public bool Equip(Slots slot, ClothingComponent clothing) { if (clothing == null) { throw new ArgumentNullException(nameof(clothing), "Clothing must be passed here. To remove some clothing from a slot, use Unequip()"); } if (clothing.SlotFlags == SlotFlags.PREVENTEQUIP //Flag to prevent equipping at all || (clothing.SlotFlags & SlotMasks[slot]) == 0 ) //Does the clothing flag have any of our requested slot flags { return false; } var inventorySlot = SlotContainers[slot]; if (!inventorySlot.Insert(clothing.Owner)) { return false; } clothing.EquippedToSlot(); Dirty(); return true; } /// /// Checks whether an item can be put in the specified slot. /// /// The slot to check for. /// The item to check for. /// True if the item can be inserted into the specified slot. public bool CanEquip(Slots slot, ClothingComponent item) { return SlotContainers[slot].CanInsert(item.Owner); } /// /// Drops the item in a slot. /// /// The slot to drop the item from. /// True if an item was dropped, false otherwise. public bool Unequip(Slots slot) { if (!CanUnequip(slot)) { return false; } var inventorySlot = SlotContainers[slot]; var item = inventorySlot.ContainedEntity.GetComponent(); if (!inventorySlot.Remove(inventorySlot.ContainedEntity)) { return false; } item.RemovedFromSlot(); // TODO: The item should be dropped to the container our owner is in, if any. var itemTransform = item.Owner.GetComponent(); itemTransform.GridPosition = Owner.GetComponent().GridPosition; Dirty(); return true; } /// /// Checks whether an item can be dropped from the specified slot. /// /// The slot to check for. /// /// True if there is an item in the slot and it can be dropped, false otherwise. /// public bool CanUnequip(Slots slot) { var InventorySlot = SlotContainers[slot]; return InventorySlot.ContainedEntity != null && InventorySlot.CanRemove(InventorySlot.ContainedEntity); } /// /// Adds a new slot to this inventory component. /// /// The name of the slot to add. /// /// Thrown if the slot with specified name already exists. /// public ContainerSlot AddSlot(Slots slot) { if (HasSlot(slot)) { throw new InvalidOperationException($"Slot '{slot}' already exists."); } Dirty(); return SlotContainers[slot] = ContainerManagerComponent.Create(GetSlotString(slot), Owner); } /// /// Removes a slot from this inventory component. /// /// /// If the slot contains an item, the item is dropped. /// /// The name of the slot to remove. public void RemoveSlot(Slots slot) { if (!HasSlot(slot)) { throw new InvalidOperationException($"Slow '{slot}' does not exist."); } if (GetSlotItem(slot) != null && !Unequip(slot)) { // TODO: Handle this potential failiure better. throw new InvalidOperationException( "Unable to remove slot as the contained clothing could not be dropped"); } SlotContainers.Remove(slot); Dirty(); } /// /// Checks whether a slot with the specified name exists. /// /// The slot name to check. /// True if the slot exists, false otherwise. public bool HasSlot(Slots slot) { return SlotContainers.ContainsKey(slot); } /// /// The underlying Container System just notified us that an entity was removed from it. /// We need to make sure we process that removed entity as being unequpped from the slot. /// private void ForceUnequip(IContainer container, IEntity entity) { // make sure this is one of our containers. // Technically the correct way would be to enumerate the possible slot names // comparing with this container, but I might as well put the dictionary to good use. if (!(container is ContainerSlot slot) || !SlotContainers.ContainsValue(slot)) return; if (entity.TryGetComponent(out ItemComponent itemComp)) itemComp.RemovedFromSlot(); Dirty(); } /// /// Message that tells us to equip or unequip items from the inventory slots /// /// private void HandleInventoryMessage(ClientInventoryMessage msg) { switch (msg.Updatetype) { case ClientInventoryUpdate.Equip: { var hands = Owner.GetComponent(); var activeHand = hands.GetActiveHand; if (activeHand != null && activeHand.Owner.TryGetComponent(out ClothingComponent clothing)) { hands.Drop(hands.ActiveIndex); if (!Equip(msg.Inventoryslot, clothing)) { hands.PutInHand(clothing); } } break; } case ClientInventoryUpdate.Use: { var interactionSystem = _entitySystemManager.GetEntitySystem(); var hands = Owner.GetComponent(); var activeHand = hands.GetActiveHand; var itemContainedInSlot = GetSlotItem(msg.Inventoryslot); if (itemContainedInSlot != null) { if (activeHand != null) { interactionSystem.Interaction(Owner, activeHand.Owner, itemContainedInSlot.Owner, new GridCoordinates()); } else if (Unequip(msg.Inventoryslot)) { hands.PutInHand(itemContainedInSlot); } } break; } } } /// public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null) { base.HandleMessage(message, netChannel, component); switch (message) { case ClientInventoryMessage msg: var playerMan = IoCManager.Resolve(); var session = playerMan.GetSessionByChannel(netChannel); var playerentity = session.AttachedEntity; if (playerentity == Owner) HandleInventoryMessage(msg); break; case OpenSlotStorageUIMessage msg: if (!HasSlot(msg.Slot)) // client input sanitization return; var item = GetSlotItem(msg.Slot); if (item != null && item.Owner.TryGetComponent(out ServerStorageComponent storage)) storage.OpenStorageUI(Owner); break; case ContainerContentsModifiedMessage msg: if (msg.Removed) ForceUnequip(msg.Container, msg.Entity); break; } } public override ComponentState GetComponentState() { var list = new List>(); foreach (var (slot, container) in SlotContainers) { if (container.ContainedEntity != null) { list.Add(new KeyValuePair(slot, container.ContainedEntity.Uid)); } } return new InventoryComponentState(list); } } }