diff --git a/Content.Server/GameObjects/Components/GUI/HumanInventoryControllerComponent.cs b/Content.Server/GameObjects/Components/GUI/HumanInventoryControllerComponent.cs new file mode 100644 index 0000000000..7422355f79 --- /dev/null +++ b/Content.Server/GameObjects/Components/GUI/HumanInventoryControllerComponent.cs @@ -0,0 +1,87 @@ +using Robust.Server.GameObjects.Components.Container; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.Timers; +using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; + +namespace Content.Server.GameObjects +{ + // Handles the special behavior of pockets/ID card slot and their relation to uniforms. + [RegisterComponent] + [ComponentReference(typeof(IInventoryController))] + public class HumanInventoryControllerComponent : Component, IInventoryController + { + public override string Name => "HumanInventoryController"; + + private InventoryComponent _inventory; + + public override void Initialize() + { + base.Initialize(); + + _inventory = Owner.GetComponent(); + } + + bool IInventoryController.CanEquip(Slots slot, IEntity entity, bool flagsCheck) + { + var slotMask = SlotMasks[slot]; + + if ((slotMask & (SlotFlags.POCKET | SlotFlags.IDCARD)) != SlotFlags.NONE) + { + // Can't wear stuff in ID card or pockets unless you have a uniform. + if (_inventory.GetSlotItem(Slots.INNERCLOTHING) == null) + { + return false; + } + + if (slotMask == SlotFlags.POCKET) + { + var itemComponent = entity.GetComponent(); + + // If this item is small enough then it always fits in pockets. + if (itemComponent.ObjectSize <= (int) ReferenceSizes.Pocket) + { + return true; + } + } + } + + // Standard flag check. + return flagsCheck; + } + + public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null) + { + base.HandleMessage(message, netChannel, component); + + switch (message) + { + case ContainerContentsModifiedMessage contentsModified: + Timer.Spawn(0, DropIdAndPocketsIfWeNoLongerHaveAUniform); + break; + } + } + + // Hey, it's descriptive. + private void DropIdAndPocketsIfWeNoLongerHaveAUniform() + { + if (_inventory.GetSlotItem(Slots.INNERCLOTHING) != null) + { + return; + } + + void DropMaybe(Slots slot) + { + if (_inventory.GetSlotItem(slot) != null) + { + _inventory.Unequip(slot); + } + } + + DropMaybe(Slots.POCKET1); + DropMaybe(Slots.POCKET2); + DropMaybe(Slots.IDCARD); + } + } +} diff --git a/Content.Server/GameObjects/Components/GUI/IInventoryController.cs b/Content.Server/GameObjects/Components/GUI/IInventoryController.cs new file mode 100644 index 0000000000..ade34691ef --- /dev/null +++ b/Content.Server/GameObjects/Components/GUI/IInventoryController.cs @@ -0,0 +1,20 @@ +using Robust.Shared.Interfaces.GameObjects; +using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; + +namespace Content.Server.GameObjects +{ + /// + /// Allows for overriding inventory-related behavior on an entity. + /// + public interface IInventoryController + { + /// + /// Can be implemented to override "can this item be equipped" behavior. + /// + /// The slot to be equipped into. + /// The entity to equip. + /// Whether the entity passes default slot masks & flags checks. + /// True if the entity can be equipped, false otherwise + bool CanEquip(Slots slot, IEntity entity, bool flagsCheck) => flagsCheck; + } +} diff --git a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs index 43cfa70518..a212ca9e4b 100644 --- a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs @@ -1,13 +1,9 @@ -// Only unused on .NET Core due to KeyValuePair.Deconstruct -// ReSharper disable once RedundantUsingDirective -using Robust.Shared.Utility; -using System; +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; @@ -92,30 +88,43 @@ namespace Content.Server.GameObjects /// 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. + /// The item to insert into the slot. /// True if the item was successfully inserted, false otherwise. - public bool Equip(Slots slot, ClothingComponent clothing) + public bool Equip(Slots slot, ItemComponent item) { - if (clothing == null) + if (item == null) { - throw new ArgumentNullException(nameof(clothing), + throw new ArgumentNullException(nameof(item), "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 + var pass = false; + + if (item is ClothingComponent clothing) + { + if (clothing.SlotFlags != SlotFlags.PREVENTEQUIP && (clothing.SlotFlags & SlotMasks[slot]) != 0) + { + pass = true; + } + } + + if (Owner.TryGetComponent(out IInventoryController controller)) + { + pass = controller.CanEquip(slot, item.Owner, pass); + } + + if (!pass) { return false; } var inventorySlot = SlotContainers[slot]; - if (!inventorySlot.Insert(clothing.Owner)) + if (!inventorySlot.Insert(item.Owner)) { return false; } - clothing.EquippedToSlot(); + item.EquippedToSlot(); Dirty(); return true; @@ -256,7 +265,7 @@ namespace Content.Server.GameObjects { var hands = Owner.GetComponent(); var activeHand = hands.GetActiveHand; - if (activeHand != null && activeHand.Owner.TryGetComponent(out ClothingComponent clothing)) + if (activeHand != null && activeHand.Owner.TryGetComponent(out ItemComponent clothing)) { hands.Drop(hands.ActiveIndex); if (!Equip(msg.Inventoryslot, clothing)) diff --git a/Content.Shared/GameObjects/Components/Inventory/InventoryTemplates.cs b/Content.Shared/GameObjects/Components/Inventory/InventoryTemplates.cs index 84effb02ea..32ef2ea30e 100644 --- a/Content.Shared/GameObjects/Components/Inventory/InventoryTemplates.cs +++ b/Content.Shared/GameObjects/Components/Inventory/InventoryTemplates.cs @@ -28,6 +28,8 @@ namespace Content.Shared.GameObjects private static readonly Dictionary _slotDrawingOrder = new Dictionary { + {Slots.POCKET1, 12}, + {Slots.POCKET2, 11}, {Slots.HEAD, 10}, {Slots.MASK, 9}, {Slots.EARS, 8}, @@ -46,7 +48,7 @@ namespace Content.Shared.GameObjects Slots.EYES, Slots.HEAD, Slots.EARS, Slots.OUTERCLOTHING, Slots.MASK, Slots.INNERCLOTHING, Slots.BACKPACK, Slots.BELT, Slots.GLOVES, - Slots.NONE, Slots.SHOES, Slots.IDCARD + Slots.NONE, Slots.SHOES, Slots.IDCARD, Slots.POCKET1, Slots.POCKET2 }; public override int SlotDrawingOrder(Slots slot) diff --git a/Resources/Prototypes/Entities/mobs/human.yml b/Resources/Prototypes/Entities/mobs/human.yml index 78a55e8c66..c0f4198e47 100644 --- a/Resources/Prototypes/Entities/mobs/human.yml +++ b/Resources/Prototypes/Entities/mobs/human.yml @@ -84,6 +84,7 @@ - type: CharacterInfo - type: FootstepSound - type: HumanoidAppearance + - type: HumanInventoryController - type: entity save: false