using System; using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Items.Clothing; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems.Click; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects; using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.GameObjects.EntitySystems; using Robust.Server.GameObjects.Components.Container; using Robust.Shared.Containers; 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.Localization; using Robust.Shared.Map; using Robust.Shared.Players; using Robust.Shared.ViewVariables; using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; using static Content.Shared.GameObjects.Components.Inventory.SharedInventoryComponent.ClientInventoryMessage; namespace Content.Server.GameObjects.Components.GUI { [RegisterComponent] public class InventoryComponent : SharedInventoryComponent, IExAct, IEffectBlocker, IPressureProtection { [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly IServerNotifyManager _serverNotifyManager = default!; [ViewVariables] private readonly Dictionary _slotContainers = new Dictionary(); private KeyValuePair? _hoverEntity; public IEnumerable Slots => _slotContainers.Keys; public event Action OnItemChanged; public override void Initialize() { base.Initialize(); foreach (var slotName in InventoryInstance.SlotMasks) { if (slotName != EquipmentSlotDefines.Slots.NONE) { AddSlot(slotName); } } } // Optimization: Cache this [ViewVariables] public float HighPressureMultiplier { get { var multiplier = 1f; foreach (var (slot, containerSlot) in _slotContainers) { foreach (var entity in containerSlot.ContainedEntities) { foreach (var protection in entity.GetAllComponents()) { multiplier *= protection.HighPressureMultiplier; } } } return multiplier; } } // Optimization: Cache this [ViewVariables] public float LowPressureMultiplier { get { var multiplier = 1f; foreach (var (slot, containerSlot) in _slotContainers) { foreach (var entity in containerSlot.ContainedEntities) { foreach (var protection in entity.GetAllComponents()) { multiplier *= protection.LowPressureMultiplier; } } } return multiplier; } } bool IEffectBlocker.CanSlip() { if(Owner.TryGetComponent(out InventoryComponent inventoryComponent) && inventoryComponent.TryGetSlotItem(EquipmentSlotDefines.Slots.SHOES, out ItemComponent shoes) ) { return EffectBlockerSystem.CanSlip(shoes.Owner); } return true; } public override void OnRemove() { var slots = _slotContainers.Keys.ToList(); foreach (var slot in slots) { if (TryGetSlotItem(slot, out ItemComponent item)) { item.Owner.Delete(); } 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 { if (!_slotContainers.ContainsKey(slot)) { return null; } var containedEntity = _slotContainers[slot].ContainedEntity; if (containedEntity?.Deleted == true) { _slotContainers[slot] = null; containedEntity = null; Dirty(); } return 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. /// Whether to perform an ActionBlocker check to the entity. /// The translated reason why the item cannot be equipped, if this function returns false. Can be null. /// True if the item was successfully inserted, false otherwise. public bool Equip(Slots slot, ItemComponent item, bool mobCheck, out string reason) { if (item == null) { throw new ArgumentNullException(nameof(item), "Clothing must be passed here. To remove some clothing from a slot, use Unequip()"); } if (!CanEquip(slot, item, mobCheck, out reason)) { return false; } var inventorySlot = _slotContainers[slot]; if (!inventorySlot.Insert(item.Owner)) { return false; } _entitySystemManager.GetEntitySystem().EquippedInteraction(Owner, item.Owner, slot); OnItemChanged?.Invoke(); Dirty(); return true; } public bool Equip(Slots slot, ItemComponent item, bool mobCheck = true) => Equip(slot, item, mobCheck, out var _); public bool Equip(Slots slot, IEntity entity, bool mobCheck = true) => Equip(slot, entity.GetComponent(), mobCheck); /// /// Checks whether an item can be put in the specified slot. /// /// The slot to check for. /// The item to check for. /// The translated reason why the item cannot be equiped, if this function returns false. Can be null. /// True if the item can be inserted into the specified slot. public bool CanEquip(Slots slot, ItemComponent item, bool mobCheck, out string reason) { var pass = false; reason = null; if (mobCheck && !ActionBlockerSystem.CanEquip(Owner)) return false; if (item is ClothingComponent clothing) { if (clothing.SlotFlags != SlotFlags.PREVENTEQUIP && (clothing.SlotFlags & SlotMasks[slot]) != 0) { pass = true; } else { reason = Loc.GetString("This doesn't fit."); } } if (Owner.TryGetComponent(out IInventoryController controller)) { pass = controller.CanEquip(slot, item.Owner, pass, out var controllerReason); reason = controllerReason ?? reason; } if (!pass && reason == null) { reason = Loc.GetString("You can't equip this!"); } return pass && _slotContainers[slot].CanInsert(item.Owner); } public bool CanEquip(Slots slot, ItemComponent item, bool mobCheck = true) => CanEquip(slot, item, mobCheck, out var _); public bool CanEquip(Slots slot, IEntity entity, bool mobCheck = true) => CanEquip(slot, entity.GetComponent(), mobCheck); /// /// Drops the item in a slot. /// /// The slot to drop the item from. /// True if an item was dropped, false otherwise. /// Whether to perform an ActionBlocker check to the entity. public bool Unequip(Slots slot, bool mobCheck = true) { if (!CanUnequip(slot, mobCheck)) { return false; } var inventorySlot = _slotContainers[slot]; var entity = inventorySlot.ContainedEntity; var item = entity.GetComponent(); if (!inventorySlot.Remove(entity)) { return false; } // TODO: The item should be dropped to the container our owner is in, if any. ContainerHelpers.AttachParentToContainerOrGrid(entity.Transform); _entitySystemManager.GetEntitySystem().UnequippedInteraction(Owner, entity, slot); OnItemChanged?.Invoke(); Dirty(); return true; } public void ForceUnequip(Slots slot) { var inventorySlot = _slotContainers[slot]; var entity = inventorySlot.ContainedEntity; if (entity == null) { return; } var item = entity.GetComponent(); inventorySlot.ForceRemove(entity); var itemTransform = entity.Transform; ContainerHelpers.AttachParentToContainerOrGrid(itemTransform); _entitySystemManager.GetEntitySystem().UnequippedInteraction(Owner, item.Owner, slot); OnItemChanged?.Invoke(); Dirty(); } /// /// Checks whether an item can be dropped from the specified slot. /// /// The slot to check for. /// Whether to perform an ActionBlocker check to the entity. /// /// True if there is an item in the slot and it can be dropped, false otherwise. /// public bool CanUnequip(Slots slot, bool mobCheck = true) { if (mobCheck && !ActionBlockerSystem.CanUnequip(Owner)) return false; 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(); _slotContainers[slot] = ContainerManagerComponent.Create(GetSlotString(slot), Owner); OnItemChanged?.Invoke(); return _slotContainers[slot]; } /// /// 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."); } ForceUnequip(slot); var container = _slotContainers[slot]; container.Shutdown(); _slotContainers.Remove(slot); OnItemChanged?.Invoke(); 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 unequipped 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(); } OnItemChanged?.Invoke(); 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 ItemComponent clothing)) { hands.Drop(hands.ActiveHand); if (!Equip(msg.Inventoryslot, clothing, true, out var reason)) { hands.PutInHand(clothing); if (reason != null) _serverNotifyManager.PopupMessageCursor(Owner, reason); } } 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; } case ClientInventoryUpdate.Hover: { var hands = Owner.GetComponent(); var activeHand = hands.GetActiveHand; if (activeHand != null && GetSlotItem(msg.Inventoryslot) == null) { var canEquip = CanEquip(msg.Inventoryslot, activeHand, true, out var reason); _hoverEntity = new KeyValuePair(msg.Inventoryslot, (activeHand.Owner.Uid, canEquip)); Dirty(); } break; } } } /// public override void HandleMessage(ComponentMessage message, IComponent component) { base.HandleMessage(message, component); switch (message) { case ContainerContentsModifiedMessage msg: if (msg.Removed) ForceUnequip(msg.Container, msg.Entity); break; default: break; } } /// public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession session = null) { base.HandleNetworkMessage(message, netChannel, session); if (session == null) { throw new ArgumentNullException(nameof(session)); } switch (message) { case ClientInventoryMessage msg: 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; } } 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)); } } var hover = _hoverEntity; _hoverEntity = null; return new InventoryComponentState(list, hover); } void IExAct.OnExplosion(ExplosionEventArgs eventArgs) { if (eventArgs.Severity < ExplosionSeverity.Heavy) { return; } foreach (var slot in _slotContainers.Values.ToList()) { foreach (var entity in slot.ContainedEntities) { var exActs = entity.GetAllComponents().ToList(); foreach (var exAct in exActs) { exAct.OnExplosion(eventArgs); } } } } } }