diff --git a/Content.Client/Clothing/ClientClothingSystem.cs b/Content.Client/Clothing/ClientClothingSystem.cs index b16e14d653..979f7430e1 100644 --- a/Content.Client/Clothing/ClientClothingSystem.cs +++ b/Content.Client/Clothing/ClientClothingSystem.cs @@ -202,17 +202,15 @@ public sealed class ClientClothingSystem : ClothingSystem revealedLayers.Clear(); } - public void InitClothing(EntityUid uid, InventoryComponent? component = null, SpriteComponent? sprite = null) + public void InitClothing(EntityUid uid, InventoryComponent component) { - if (!Resolve(uid, ref sprite, ref component) || !_inventorySystem.TryGetSlots(uid, out var slots, component)) + if (!TryComp(uid, out SpriteComponent? sprite)) return; - foreach (var slot in slots) + var enumerator = _inventorySystem.GetSlotEnumerator((uid, component)); + while (enumerator.NextItem(out var item, out var slot)) { - if (!_inventorySystem.TryGetSlotContainer(uid, slot.Name, out var containerSlot, out _, component) || - !containerSlot.ContainedEntity.HasValue) continue; - - RenderEquipment(uid, containerSlot.ContainedEntity.Value, slot.Name, component, sprite); + RenderEquipment(uid, item, slot.Name, component, sprite); } } diff --git a/Content.Client/Inventory/ClientInventorySystem.cs b/Content.Client/Inventory/ClientInventorySystem.cs index 6976a8b5bc..d4615210f2 100644 --- a/Content.Client/Inventory/ClientInventorySystem.cs +++ b/Content.Client/Inventory/ClientInventorySystem.cs @@ -1,9 +1,7 @@ using Content.Client.Clothing; using Content.Client.Examine; -using Content.Client.UserInterface.Controls; using Content.Client.Verbs.UI; using Content.Shared.Clothing.Components; -using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Inventory; @@ -15,14 +13,12 @@ using Robust.Client.UserInterface; using Robust.Shared.Containers; using Robust.Shared.Input.Binding; using Robust.Shared.Player; -using Robust.Shared.Prototypes; namespace Content.Client.Inventory { [UsedImplicitly] public sealed class ClientInventorySystem : InventorySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IUserInterfaceManager _ui = default!; @@ -89,7 +85,7 @@ namespace Content.Client.Inventory private void OnDidUnequip(InventorySlotsComponent component, DidUnequipEvent args) { UpdateSlot(args.Equipee, component, args.Slot); - if (args.Equipee != _playerManager.LocalPlayer?.ControlledEntity) + if (args.Equipee != _playerManager.LocalEntity) return; var update = new SlotSpriteUpdate(null, args.SlotGroup, args.Slot, false); OnSpriteUpdate?.Invoke(update); @@ -98,7 +94,7 @@ namespace Content.Client.Inventory private void OnDidEquip(InventorySlotsComponent component, DidEquipEvent args) { UpdateSlot(args.Equipee, component, args.Slot); - if (args.Equipee != _playerManager.LocalPlayer?.ControlledEntity) + if (args.Equipee != _playerManager.LocalEntity) return; var update = new SlotSpriteUpdate(args.Equipment, args.SlotGroup, args.Slot, HasComp(args.Equipment)); @@ -107,10 +103,8 @@ namespace Content.Client.Inventory private void OnShutdown(EntityUid uid, InventoryComponent component, ComponentShutdown args) { - if (uid != _playerManager.LocalPlayer?.ControlledEntity) - return; - - OnUnlinkInventory?.Invoke(); + if (uid == _playerManager.LocalEntity) + OnUnlinkInventory?.Invoke(); } private void OnPlayerDetached(EntityUid uid, InventorySlotsComponent component, LocalPlayerDetachedEvent args) @@ -151,13 +145,10 @@ namespace Content.Client.Inventory base.OnInit(uid, component, args); _clothingVisualsSystem.InitClothing(uid, component); - if (!_prototypeManager.TryIndex(component.TemplateId, out InventoryTemplatePrototype? invTemplate) || - !TryComp(uid, out InventorySlotsComponent? inventorySlots)) - { + if (!TryComp(uid, out InventorySlotsComponent? inventorySlots)) return; - } - foreach (var slot in invTemplate.Slots) + foreach (var slot in component.Slots) { TryAddSlotDef(uid, inventorySlots, slot); } @@ -165,7 +156,7 @@ namespace Content.Client.Inventory public void ReloadInventory(InventorySlotsComponent? component = null) { - var player = _playerManager.LocalPlayer?.ControlledEntity; + var player = _playerManager.LocalEntity; if (player == null || !Resolve(player.Value, ref component, false)) { return; @@ -179,7 +170,7 @@ namespace Content.Client.Inventory { var oldData = component.SlotData[slotName]; var newData = component.SlotData[slotName] = new SlotData(oldData, state); - if (owner == _playerManager.LocalPlayer?.ControlledEntity) + if (owner == _playerManager.LocalEntity) EntitySlotUpdate?.Invoke(newData); } @@ -198,7 +189,7 @@ namespace Content.Client.Inventory var newData = component.SlotData[slotName] = new SlotData(component.SlotData[slotName], newHighlight, newBlocked); - if (owner == _playerManager.LocalPlayer?.ControlledEntity) + if (owner == _playerManager.LocalEntity) EntitySlotUpdate?.Invoke(newData); } @@ -208,48 +199,11 @@ namespace Content.Client.Inventory if (!component.SlotData.TryAdd(newSlotDef.Name, newSlotData)) return false; - if (owner == _playerManager.LocalPlayer?.ControlledEntity) + if (owner == _playerManager.LocalEntity) OnSlotAdded?.Invoke(newSlotData); return true; } - public void RemoveSlotDef(EntityUid owner, InventorySlotsComponent component, SlotData slotData) - { - if (component.SlotData.Remove(slotData.SlotName)) - { - if (owner == _playerManager.LocalPlayer?.ControlledEntity) - OnSlotRemoved?.Invoke(slotData); - } - } - - public void RemoveSlotDef(EntityUid owner, InventorySlotsComponent component, string slotName) - { - if (!component.SlotData.TryGetValue(slotName, out var slotData)) - return; - - component.SlotData.Remove(slotName); - - if (owner == _playerManager.LocalPlayer?.ControlledEntity) - OnSlotRemoved?.Invoke(slotData); - } - - // TODO hud refactor This should also live in a UI Controller - private void HoverInSlotButton(EntityUid uid, string slot, SlotControl control, - InventoryComponent? inventoryComponent = null, HandsComponent? hands = null) - { - if (!Resolve(uid, ref inventoryComponent)) - return; - - if (!Resolve(uid, ref hands, false)) - return; - - if (hands.ActiveHandEntity is not EntityUid heldEntity) - return; - - if (!TryGetSlotContainer(uid, slot, out var containerSlot, out var slotDef, inventoryComponent)) - return; - } - public void UIInventoryActivate(string slot) { EntityManager.RaisePredictiveEvent(new UseSlotNetworkMessage(slot)); diff --git a/Content.Client/Inventory/StrippableBoundUserInterface.cs b/Content.Client/Inventory/StrippableBoundUserInterface.cs index 6dbb8f3c22..9fbb64309f 100644 --- a/Content.Client/Inventory/StrippableBoundUserInterface.cs +++ b/Content.Client/Inventory/StrippableBoundUserInterface.cs @@ -90,11 +90,11 @@ namespace Content.Client.Inventory _strippingMenu.ClearButtons(); - if (EntMan.TryGetComponent(Owner, out var inv) && _protoMan.TryIndex(inv.TemplateId, out var template)) + if (EntMan.TryGetComponent(Owner, out var inv)) { - foreach (var slot in template.Slots) + foreach (var slot in inv.Slots) { - AddInventoryButton(Owner, slot.Name, template, inv); + AddInventoryButton(Owner, slot.Name, inv); } } @@ -190,7 +190,7 @@ namespace Content.Client.Inventory _ui.GetUIController().OpenVerbMenu(slot.Entity.Value); } - private void AddInventoryButton(EntityUid invUid, string slotId, InventoryTemplatePrototype _, InventoryComponent inv) + private void AddInventoryButton(EntityUid invUid, string slotId, InventoryComponent inv) { if (!_inv.TryGetSlotContainer(invUid, slotId, out var container, out var slotDef, inv)) return; diff --git a/Content.Server/Administration/Commands/SetOutfitCommand.cs b/Content.Server/Administration/Commands/SetOutfitCommand.cs index 97c1fa0656..72ff9ff9b6 100644 --- a/Content.Server/Administration/Commands/SetOutfitCommand.cs +++ b/Content.Server/Administration/Commands/SetOutfitCommand.cs @@ -92,9 +92,9 @@ namespace Content.Server.Administration.Commands } var invSystem = entityManager.System(); - if (invSystem.TryGetSlots(target, out var slotDefinitions, inventoryComponent)) + if (invSystem.TryGetSlots(target, out var slots)) { - foreach (var slot in slotDefinitions) + foreach (var slot in slots) { invSystem.TryUnequip(target, slot.Name, true, true, false, inventoryComponent); var gearStr = startingGear.GetGear(slot.Name, profile); diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index cc31071fe8..966bff2f71 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -368,15 +368,12 @@ namespace Content.Server.Administration.Systems } } - if (TryComp(entity.Value, out InventoryComponent? inventory) && - _inventory.TryGetSlots(entity.Value, out var slots, inventory)) + if (_inventory.TryGetContainerSlotEnumerator(entity.Value, out var enumerator)) { - foreach (var slot in slots) + while (enumerator.NextItem(out var item, out var slot)) { - if (_inventory.TryUnequip(entity.Value, entity.Value, slot.Name, out var item, true, true)) - { - _physics.ApplyAngularImpulse(item.Value, ThrowingSystem.ThrowAngularImpulse); - } + if (_inventory.TryUnequip(entity.Value, entity.Value, slot.Name, true, true)) + _physics.ApplyAngularImpulse(item, ThrowingSystem.ThrowAngularImpulse); } } diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs index d7df3faee2..8d754d1b3a 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs @@ -285,27 +285,7 @@ public sealed partial class AdminVerbSystem Text = "Refill Internals Oxygen", Category = VerbCategory.Tricks, Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/oxygen.rsi"), "icon"), - Act = () => - { - foreach (var slot in _inventorySystem.GetSlots(args.Target)) - { - if (!_inventorySystem.TryGetSlotEntity(args.Target, slot.Name, out var entity)) - continue; - - if (!TryComp(entity, out tank)) - continue; - - RefillGasTank(entity.Value, Gas.Oxygen, tank); - } - - foreach (var held in _handsSystem.EnumerateHeld(args.Target)) - { - if (!TryComp(held, out tank)) - continue; - - RefillGasTank(held, Gas.Oxygen, tank); - } - }, + Act = () => RefillEquippedTanks(args.User, Gas.Oxygen), Impact = LogImpact.Extreme, Message = Loc.GetString("admin-trick-internals-refill-oxygen-description"), Priority = (int) TricksVerbPriorities.RefillOxygen, @@ -317,27 +297,7 @@ public sealed partial class AdminVerbSystem Text = "Refill Internals Nitrogen", Category = VerbCategory.Tricks, Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/red.rsi"), "icon"), - Act = () => - { - foreach (var slot in _inventorySystem.GetSlots(args.Target)) - { - if (!_inventorySystem.TryGetSlotEntity(args.Target, slot.Name, out var entity)) - continue; - - if (!TryComp(entity, out tank)) - continue; - - RefillGasTank(entity.Value, Gas.Nitrogen, tank); - } - - foreach (var held in _handsSystem.EnumerateHeld(args.Target)) - { - if (!TryComp(held, out tank)) - continue; - - RefillGasTank(held, Gas.Nitrogen, tank); - } - }, + Act = () =>RefillEquippedTanks(args.User, Gas.Nitrogen), Impact = LogImpact.Extreme, Message = Loc.GetString("admin-trick-internals-refill-nitrogen-description"), Priority = (int) TricksVerbPriorities.RefillNitrogen, @@ -349,27 +309,7 @@ public sealed partial class AdminVerbSystem Text = "Refill Internals Plasma", Category = VerbCategory.Tricks, Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Tanks/plasma.rsi"), "icon"), - Act = () => - { - foreach (var slot in _inventorySystem.GetSlots(args.Target)) - { - if (!_inventorySystem.TryGetSlotEntity(args.Target, slot.Name, out var entity)) - continue; - - if (!TryComp(entity, out tank)) - continue; - - RefillGasTank(entity.Value, Gas.Plasma, tank); - } - - foreach (var held in _handsSystem.EnumerateHeld(args.Target)) - { - if (!TryComp(held, out tank)) - continue; - - RefillGasTank(held, Gas.Plasma, tank); - } - }, + Act = () => RefillEquippedTanks(args.User, Gas.Plasma), Impact = LogImpact.Extreme, Message = Loc.GetString("admin-trick-internals-refill-plasma-description"), Priority = (int) TricksVerbPriorities.RefillPlasma, @@ -792,9 +732,17 @@ public sealed partial class AdminVerbSystem } } - private void RefillGasTank(EntityUid tank, Gas gasType, GasTankComponent? tankComponent) + private void RefillEquippedTanks(EntityUid target, Gas plasma) { - if (!Resolve(tank, ref tankComponent)) + foreach (var held in _inventorySystem.GetHandOrInventoryEntities(target)) + { + RefillGasTank(held, Gas.Plasma); + } + } + + private void RefillGasTank(EntityUid tank, Gas gasType, GasTankComponent? tankComponent = null) + { + if (!Resolve(tank, ref tankComponent, false)) return; var mixSize = tankComponent.Air.Volume; diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs index 17a6544976..b96e108968 100644 --- a/Content.Server/Body/Systems/InternalsSystem.cs +++ b/Content.Server/Body/Systems/InternalsSystem.cs @@ -6,6 +6,7 @@ using Content.Server.Popups; using Content.Shared.Alert; using Content.Shared.Atmos; using Content.Shared.DoAfter; +using Content.Shared.Hands.Components; using Content.Shared.Internals; using Content.Shared.Inventory; using Content.Shared.Verbs; @@ -26,6 +27,8 @@ public sealed class InternalsSystem : EntitySystem [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; + public const SlotFlags InventorySlots = SlotFlags.POCKET | SlotFlags.BELT; + public override void Initialize() { base.Initialize(); @@ -81,7 +84,7 @@ public sealed class InternalsSystem : EntitySystem return; } - var tank = FindBestGasTank(uid, internals); + var tank = FindBestGasTank(uid); if (tank == null) { @@ -224,59 +227,35 @@ public sealed class InternalsSystem : EntitySystem return 1; } - public Entity? FindBestGasTank(EntityUid internalsOwner, InternalsComponent component) + public Entity? FindBestGasTank(Entity user) { // Prioritise // 1. back equipped tanks // 2. exo-slot tanks // 3. in-hand tanks // 4. pocket/belt tanks - InventoryComponent? inventory = null; - ContainerManagerComponent? containerManager = null; - if (_inventory.TryGetSlotEntity(internalsOwner, "back", out var backEntity, inventory, containerManager) && + if (!Resolve(user.Owner, ref user.Comp1, ref user.Comp2, ref user.Comp3)) + return null; + + if (_inventory.TryGetSlotEntity(user.Owner, "back", out var backEntity, user.Comp2, user.Comp3) && TryComp(backEntity, out var backGasTank) && _gasTank.CanConnectToInternals(backGasTank)) { return (backEntity.Value, backGasTank); } - if (_inventory.TryGetSlotEntity(internalsOwner, "suitstorage", out var entity, inventory, containerManager) && + if (_inventory.TryGetSlotEntity(user.Owner, "suitstorage", out var entity, user.Comp2, user.Comp3) && TryComp(entity, out var gasTank) && _gasTank.CanConnectToInternals(gasTank)) { return (entity.Value, gasTank); } - var tanks = new List>(); - - foreach (var hand in _hands.EnumerateHands(internalsOwner)) + foreach (var item in _inventory.GetHandOrInventoryEntities((user.Owner, user.Comp1, user.Comp2))) { - if (TryComp(hand.HeldEntity, out gasTank) && _gasTank.CanConnectToInternals(gasTank)) - tanks.Add((hand.HeldEntity.Value, gasTank)); - } - - if (tanks.Count > 0) - { - tanks.Sort((x, y) => y.Comp.Air.TotalMoles.CompareTo(x.Comp.Air.TotalMoles)); - return tanks[0]; - } - - if (Resolve(internalsOwner, ref inventory, false)) - { - var enumerator = new InventorySystem.ContainerSlotEnumerator(internalsOwner, inventory.TemplateId, _protoManager, _inventory, SlotFlags.POCKET | SlotFlags.BELT); - - while (enumerator.MoveNext(out var container)) - { - if (TryComp(container.ContainedEntity, out gasTank) && _gasTank.CanConnectToInternals(gasTank)) - tanks.Add((container.ContainedEntity.Value, gasTank)); - } - - if (tanks.Count > 0) - { - tanks.Sort((x, y) => y.Comp.Air.TotalMoles.CompareTo(x.Comp.Air.TotalMoles)); - return tanks[0]; - } + if (TryComp(item, out gasTank) && _gasTank.CanConnectToInternals(gasTank)) + return (item, gasTank); } return null; diff --git a/Content.Server/Chemistry/EntitySystems/SolutionInjectOnCollideSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionInjectOnCollideSystem.cs index dcfc57a9b2..e053ce9720 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionInjectOnCollideSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionInjectOnCollideSystem.cs @@ -35,15 +35,13 @@ namespace Content.Server.Chemistry.EntitySystems return; } - if (component.BlockSlots != 0x0 && TryComp(target, out var inventory)) + if (component.BlockSlots != 0x0) { - var containerEnumerator = new InventorySystem.ContainerSlotEnumerator(target, inventory.TemplateId, _protoManager, _inventorySystem, component.BlockSlots); + var containerEnumerator = _inventorySystem.GetSlotEnumerator(target, component.BlockSlots); - while (containerEnumerator.MoveNext(out var container)) - { - if (!container.ContainedEntity.HasValue) continue; + // TODO add a helper method for this? + if (containerEnumerator.MoveNext(out _)) return; - } } var solRemoved = solution.SplitSolution(component.TransferAmount); diff --git a/Content.Server/Inventory/ServerInventorySystem.cs b/Content.Server/Inventory/ServerInventorySystem.cs index f8d4bd3a1f..7e3d9b3c7d 100644 --- a/Content.Server/Inventory/ServerInventorySystem.cs +++ b/Content.Server/Inventory/ServerInventorySystem.cs @@ -25,10 +25,8 @@ namespace Content.Server.Inventory private void OnExploded(Entity ent, ref BeforeExplodeEvent args) { - if (!TryGetContainerSlotEnumerator(ent, out var slots, ent.Comp)) - return; - // explode each item in their inventory too + var slots = new InventorySlotEnumerator(ent); while (slots.MoveNext(out var slot)) { if (slot.ContainedEntity != null) @@ -55,33 +53,16 @@ namespace Content.Server.Inventory } } - public void TransferEntityInventories(EntityUid uid, EntityUid target) + public void TransferEntityInventories(Entity source, Entity target) { - if (!TryGetContainerSlotEnumerator(uid, out var enumerator)) + if (!Resolve(source.Owner, ref source.Comp) || !Resolve(target.Owner, ref target.Comp)) return; - Dictionary inventoryEntities = new(); - var slots = GetSlots(uid); - while (enumerator.MoveNext(out var containerSlot)) + var enumerator = new InventorySlotEnumerator(source.Comp); + while (enumerator.NextItem(out var item, out var slot)) { - //records all the entities stored in each of the target's slots - foreach (var slot in slots) - { - if (TryGetSlotContainer(target, slot.Name, out var conslot, out _) && - conslot.ID == containerSlot.ID && - containerSlot.ContainedEntity is { } containedEntity) - { - inventoryEntities.Add(slot.Name, containedEntity); - } - } - //drops everything in the target's inventory on the ground - TryUnequip(uid, containerSlot.ID, true, true); - } - // This takes the objects we removed and stored earlier - // and actually equips all of it to the new entity - foreach (var (slot, item) in inventoryEntities) - { - TryEquip(target, item, slot , true, true); + if (TryUnequip(source, slot.Name, true, true, inventory: source.Comp)) + TryEquip(target, item, slot.Name , true, true, inventory: target.Comp); } } } diff --git a/Content.Server/Movement/StressTestMovementSystem.cs b/Content.Server/Movement/StressTestMovementSystem.cs index 632af9cecf..81ea6840e3 100644 --- a/Content.Server/Movement/StressTestMovementSystem.cs +++ b/Content.Server/Movement/StressTestMovementSystem.cs @@ -26,6 +26,9 @@ public sealed class StressTestMovementSystem : EntitySystem while (query.MoveNext(out var uid, out var stressTest, out var transform)) { + if (!transform.ParentUid.IsValid()) + continue; + stressTest.Progress += frameTime; if (stressTest.Progress > 1) diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index 59edad6b4a..1009e0a294 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -40,6 +40,16 @@ namespace Content.Server.Zombies [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + public const SlotFlags ProtectiveSlots = + SlotFlags.FEET | + SlotFlags.HEAD | + SlotFlags.EYES | + SlotFlags.GLOVES | + SlotFlags.MASK | + SlotFlags.NECK | + SlotFlags.INNERCLOTHING | + SlotFlags.OUTERCLOTHING; + public override void Initialize() { base.Initialize(); @@ -164,33 +174,27 @@ namespace Content.Server.Zombies private float GetZombieInfectionChance(EntityUid uid, ZombieComponent component) { - var baseChance = component.MaxZombieInfectionChance; + var max = component.MaxZombieInfectionChance; - if (!TryComp(uid, out var inventoryComponent)) - return baseChance; - - var enumerator = - new InventorySystem.ContainerSlotEnumerator(uid, inventoryComponent.TemplateId, _protoManager, _inv, - SlotFlags.FEET | - SlotFlags.HEAD | - SlotFlags.EYES | - SlotFlags.GLOVES | - SlotFlags.MASK | - SlotFlags.NECK | - SlotFlags.INNERCLOTHING | - SlotFlags.OUTERCLOTHING); + if (!_inventory.TryGetContainerSlotEnumerator(uid, out var enumerator, ProtectiveSlots)) + return max; var items = 0f; var total = 0f; while (enumerator.MoveNext(out var con)) { total++; - if (con.ContainedEntity != null) items++; } - var max = component.MaxZombieInfectionChance; + if (total == 0) + return max; + + // Everyone knows that when it comes to zombies, socks & sandals provide just as much protection as an + // armored vest. Maybe these should be weighted per-item. I.e. some kind of coverage/protection component. + // Or at the very least different weights per slot. + var min = component.MinZombieInfectionChance; //gets a value between the max and min based on how many items the entity is wearing var chance = (max-min) * ((total - items)/total) + min; diff --git a/Content.Shared/Inventory/InventoryComponent.cs b/Content.Shared/Inventory/InventoryComponent.cs index 07a2463d12..2a8710f0f2 100644 --- a/Content.Shared/Inventory/InventoryComponent.cs +++ b/Content.Shared/Inventory/InventoryComponent.cs @@ -1,4 +1,5 @@ -using Robust.Shared.GameStates; +using Robust.Shared.Containers; +using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Inventory; @@ -11,4 +12,7 @@ public sealed partial class InventoryComponent : Component public string TemplateId { get; private set; } = "human"; [DataField("speciesId")] public string? SpeciesId { get; set; } + + public SlotDefinition[] Slots = Array.Empty(); + public ContainerSlot[] Containers = Array.Empty(); } diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index 70083fbfeb..a6c818ca7e 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -17,6 +17,7 @@ using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Shared.Inventory; @@ -47,12 +48,10 @@ public abstract partial class InventorySystem protected void QuickEquip(EntityUid uid, ClothingComponent component, UseInHandEvent args) { - if (!TryComp(args.User, out InventoryComponent? inv) - || !TryComp(args.User, out HandsComponent? hands) - || !_prototypeManager.TryIndex(inv.TemplateId, out var prototype)) + if (!TryComp(args.User, out InventoryComponent? inv) || !HasComp(args.User)) return; - foreach (var slotDef in prototype.Slots) + foreach (var slotDef in inv.Slots) { if (!CanEquip(args.User, uid, slotDef.Name, out _, slotDef, inv)) continue; @@ -255,6 +254,7 @@ public abstract partial class InventorySystem if (slotDefinition == null && !TryGetSlot(target, slot, out slotDefinition, inventory: inventory)) return false; + DebugTools.Assert(slotDefinition.Name == slot); if (slotDefinition.DependsOn != null && !TryGetSlotEntity(target, slotDefinition.DependsOn, out _, inventory)) return false; @@ -347,7 +347,8 @@ public abstract partial class InventorySystem removedItem = slotContainer.ContainedEntity; - if (!removedItem.HasValue) return false; + if (!removedItem.HasValue) + return false; if (!force && !CanUnequip(actor, target, slot, out var reason, slotContainer, slotDefinition, inventory)) { @@ -360,7 +361,7 @@ public abstract partial class InventorySystem if (!force && !_containerSystem.CanRemove(removedItem.Value, slotContainer)) return false; - foreach (var slotDef in GetSlots(target, inventory)) + foreach (var slotDef in inventory.Slots) { if (slotDef != slotDefinition && slotDef.DependsOn == slotDefinition.Name) { diff --git a/Content.Shared/Inventory/InventorySystem.Helpers.cs b/Content.Shared/Inventory/InventorySystem.Helpers.cs index 0e24d2ec24..b1212abe67 100644 --- a/Content.Shared/Inventory/InventorySystem.Helpers.cs +++ b/Content.Shared/Inventory/InventorySystem.Helpers.cs @@ -10,11 +10,11 @@ public partial class InventorySystem /// /// Yields all entities in hands or inventory slots with the specific flags. /// - public IEnumerable GetHandOrInventoryEntities(EntityUid user, SlotFlags flags = SlotFlags.All) + public IEnumerable GetHandOrInventoryEntities(Entity user, SlotFlags flags = SlotFlags.All) { - if (TryComp(user, out var handsComp)) + if (Resolve(user.Owner, ref user.Comp1, false)) { - foreach (var hand in handsComp.Hands.Values) + foreach (var hand in user.Comp1.Hands.Values) { if (hand.HeldEntity == null) continue; @@ -23,27 +23,22 @@ public partial class InventorySystem } } - if (TryComp(user, out var inventoryComp)) + if (!Resolve(user.Owner, ref user.Comp2, false)) + yield break; + + var slotEnumerator = new InventorySlotEnumerator(user.Comp2, flags); + while (slotEnumerator.NextItem(out var item)) { - var slotEnumerator = new ContainerSlotEnumerator(user, inventoryComp.TemplateId, - _prototypeManager, this, flags); - - while (slotEnumerator.MoveNext(out var slot)) - { - if (slot.ContainedEntity == null) - continue; - - yield return slot.ContainedEntity.Value; - } + yield return item; } } /// /// Returns the definition of the inventory slot that the given entity is currently in.. /// - public bool TryGetContainingSlot(EntityUid uid, [NotNullWhen(true)] out SlotDefinition? slot) + public bool TryGetContainingSlot(Entity entity, [NotNullWhen(true)] out SlotDefinition? slot) { - if (!_containerSystem.TryGetContainingContainer(uid, out var container)) + if (!_containerSystem.TryGetContainingContainer(entity.Owner, out var container, entity.Comp2, entity.Comp1)) { slot = null; return false; @@ -55,9 +50,10 @@ public partial class InventorySystem /// /// Returns true if the given entity is equipped to an inventory slot with the given inventory slot flags. /// - public bool InSlotWithFlags(EntityUid uid, SlotFlags flags) + public bool InSlotWithFlags(Entity entity, SlotFlags flags) { - return TryGetContainingSlot(uid, out var slot) && ((slot.SlotFlags & flags) == flags); + return TryGetContainingSlot(entity, out var slot) + && (slot.SlotFlags & flags) == flags; } public bool SpawnItemInSlot(EntityUid uid, string slot, string prototype, bool silent = false, bool force = false, InventoryComponent? inventory = null) diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index 773128a12b..fb27811073 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -12,7 +12,6 @@ using Content.Shared.Slippery; using Content.Shared.Strip.Components; using Content.Shared.Temperature; using Content.Shared.Verbs; -using Robust.Shared.Containers; namespace Content.Shared.Inventory; @@ -59,17 +58,15 @@ public partial class InventorySystem public void RelayEvent(Entity inventory, ref T args) where T : IInventoryRelayEvent { - var containerEnumerator = new ContainerSlotEnumerator(inventory, inventory.Comp.TemplateId, _prototypeManager, this, args.TargetSlots); + if (args.TargetSlots == SlotFlags.NONE) + return; // this copies the by-ref event if it is a struct var ev = new InventoryRelayedEvent(args); - - while (containerEnumerator.MoveNext(out var container)) + var enumerator = new InventorySlotEnumerator(inventory, args.TargetSlots); + while (enumerator.NextItem(out var item)) { - if (!container.ContainedEntity.HasValue) - continue; - - RaiseLocalEvent(container.ContainedEntity.Value, ev); + RaiseLocalEvent(item, ev); } // and now we copy it back @@ -81,40 +78,23 @@ public partial class InventorySystem if (args.TargetSlots == SlotFlags.NONE) return; - var containerEnumerator = new ContainerSlotEnumerator(inventory, inventory.Comp.TemplateId, _prototypeManager, this, args.TargetSlots); var ev = new InventoryRelayedEvent(args); - while (containerEnumerator.MoveNext(out var container)) + var enumerator = new InventorySlotEnumerator(inventory, args.TargetSlots); + while (enumerator.NextItem(out var item)) { - if (!container.ContainedEntity.HasValue) - continue; - - RaiseLocalEvent(container.ContainedEntity.Value, ev); + RaiseLocalEvent(item, ev); } } private void OnGetEquipmentVerbs(EntityUid uid, InventoryComponent component, GetVerbsEvent args) { // Automatically relay stripping related verbs to all equipped clothing. - - if (!_prototypeManager.TryIndex(component.TemplateId, out InventoryTemplatePrototype? proto)) - return; - - if (!TryComp(uid, out ContainerManagerComponent? containers)) - return; - var ev = new InventoryRelayedEvent>(args); - foreach (var slotDef in proto.Slots) + var enumerator = new InventorySlotEnumerator(component); + while (enumerator.NextItem(out var item, out var slotDef)) { - if (slotDef.StripHidden && args.User != uid) - continue; - - if (!containers.TryGetContainer(slotDef.Name, out var container)) - continue; - - if (container is not ContainerSlot slot || slot.ContainedEntity is not { } ent) - continue; - - RaiseLocalEvent(ent, ev); + if (!slotDef.StripHidden || args.User == uid) + RaiseLocalEvent(item, ev); } } diff --git a/Content.Shared/Inventory/InventorySystem.Slots.cs b/Content.Shared/Inventory/InventorySystem.Slots.cs index 49915f186b..68b659c0e9 100644 --- a/Content.Shared/Inventory/InventorySystem.Slots.cs +++ b/Content.Shared/Inventory/InventorySystem.Slots.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Robust.Shared.Containers; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Shared.Inventory; @@ -28,9 +29,14 @@ public partial class InventorySystem : EntitySystem if (!_prototypeManager.TryIndex(component.TemplateId, out InventoryTemplatePrototype? invTemplate)) return; - foreach (var slot in invTemplate.Slots) + component.Slots = invTemplate.Slots; + component.Containers = new ContainerSlot[component.Slots.Length]; + for (var i = 0; i < component.Containers.Length; i++) { - _containerSystem.EnsureContainer(uid, slot.Name).OccludesLight = false; + var slot = component.Slots[i]; + var container = _containerSystem.EnsureContainer(uid, slot.Name); + container.OccludesLight = false; + component.Containers[i] = container; } } @@ -52,7 +58,8 @@ public partial class InventorySystem : EntitySystem return false; } - if (container is not ContainerSlot containerSlotChecked) return false; + if (container is not ContainerSlot containerSlotChecked) + return false; containerSlot = containerSlotChecked; return true; @@ -67,12 +74,10 @@ public partial class InventorySystem : EntitySystem if (!Resolve(uid, ref inventory, false)) return false; - if (!_prototypeManager.TryIndex(inventory.TemplateId, out var templatePrototype)) - return false; - - foreach (var slotDef in templatePrototype.Slots) + foreach (var slotDef in inventory.Slots) { - if (!slotDef.Name.Equals(slot)) continue; + if (!slotDef.Name.Equals(slot)) + continue; slotDefinition = slotDef; return true; } @@ -80,33 +85,36 @@ public partial class InventorySystem : EntitySystem return false; } - public bool TryGetContainerSlotEnumerator(EntityUid uid, out ContainerSlotEnumerator containerSlotEnumerator, InventoryComponent? component = null) + public bool TryGetContainerSlotEnumerator(Entity entity, out InventorySlotEnumerator containerSlotEnumerator, SlotFlags flags = SlotFlags.All) { - containerSlotEnumerator = default; - if (!Resolve(uid, ref component, false)) + if (!Resolve(entity.Owner, ref entity.Comp)) + { + containerSlotEnumerator = default; return false; + } - containerSlotEnumerator = new ContainerSlotEnumerator(uid, component.TemplateId, _prototypeManager, this); + containerSlotEnumerator = new InventorySlotEnumerator(entity.Comp, flags); return true; } - public bool TryGetSlots(EntityUid uid, [NotNullWhen(true)] out SlotDefinition[]? slotDefinitions, InventoryComponent? inventoryComponent = null) + public InventorySlotEnumerator GetSlotEnumerator(Entity entity, SlotFlags flags = SlotFlags.All) { - slotDefinitions = null; - if (!Resolve(uid, ref inventoryComponent, false)) - return false; + if (!Resolve(entity.Owner, ref entity.Comp)) + return InventorySlotEnumerator.Empty; - if (!_prototypeManager.TryIndex(inventoryComponent.TemplateId, out var templatePrototype)) - return false; - - slotDefinitions = templatePrototype.Slots; - return true; + return new InventorySlotEnumerator(entity.Comp, flags); } - public SlotDefinition[] GetSlots(EntityUid uid, InventoryComponent? inventoryComponent = null) + public bool TryGetSlots(EntityUid uid, [NotNullWhen(true)] out SlotDefinition[]? slotDefinitions) { - if (!Resolve(uid, ref inventoryComponent)) throw new InvalidOperationException(); - return _prototypeManager.Index(inventoryComponent.TemplateId).Slots; + if (!TryComp(uid, out InventoryComponent? inv)) + { + slotDefinitions = null; + return false; + } + + slotDefinitions = inv.Slots; + return true; } private ViewVariablesPath? HandleViewVariablesSlots(EntityUid uid, InventoryComponent comp, string relativePath) @@ -118,48 +126,98 @@ public partial class InventorySystem : EntitySystem private IEnumerable ListViewVariablesSlots(EntityUid uid, InventoryComponent comp) { - foreach (var slotDef in GetSlots(uid, comp)) + foreach (var slotDef in comp.Slots) { yield return slotDef.Name; } } - public struct ContainerSlotEnumerator + /// + /// Enumerator for iterating over an inventory's slot containers. Also has methods that skip empty containers. + /// It should be safe to add or remove items while enumerating. + /// + public struct InventorySlotEnumerator { - private readonly InventorySystem _inventorySystem; - private readonly EntityUid _uid; private readonly SlotDefinition[] _slots; + private readonly ContainerSlot[] _containers; private readonly SlotFlags _flags; private int _nextIdx = 0; + public static InventorySlotEnumerator Empty = new(Array.Empty(), Array.Empty()); - public ContainerSlotEnumerator(EntityUid uid, string prototypeId, IPrototypeManager prototypeManager, InventorySystem inventorySystem, SlotFlags flags = SlotFlags.All) + public InventorySlotEnumerator(InventoryComponent inventory, SlotFlags flags = SlotFlags.All) + : this(inventory.Slots, inventory.Containers, flags) { - _uid = uid; - _inventorySystem = inventorySystem; - _flags = flags; + } - if (prototypeManager.TryIndex(prototypeId, out var prototype)) - _slots = prototype.Slots; - else - _slots = Array.Empty(); + public InventorySlotEnumerator(SlotDefinition[] slots, ContainerSlot[] containers, SlotFlags flags = SlotFlags.All) + { + DebugTools.Assert(flags != SlotFlags.NONE); + DebugTools.AssertEqual(slots.Length, containers.Length); + _flags = flags; + _slots = slots; + _containers = containers; } public bool MoveNext([NotNullWhen(true)] out ContainerSlot? container) { - container = null; - while (_nextIdx < _slots.Length) { - var slot = _slots[_nextIdx]; - _nextIdx++; + var i = _nextIdx++; + var slot = _slots[i]; if ((slot.SlotFlags & _flags) == 0) continue; - if (_inventorySystem.TryGetSlotContainer(_uid, slot.Name, out container, out _)) - return true; + container = _containers[i]; + return true; } + container = null; + return false; + } + + public bool NextItem(out EntityUid item) + { + while (_nextIdx < _slots.Length) + { + var i = _nextIdx++; + var slot = _slots[i]; + + if ((slot.SlotFlags & _flags) == 0) + continue; + + var container = _containers[i]; + if (container.ContainedEntity is { } uid) + { + item = uid; + return true; + } + } + + item = default; + return false; + } + + public bool NextItem(out EntityUid item, [NotNullWhen(true)] out SlotDefinition? slot) + { + while (_nextIdx < _slots.Length) + { + var i = _nextIdx++; + slot = _slots[i]; + + if ((slot.SlotFlags & _flags) == 0) + continue; + + var container = _containers[i]; + if (container.ContainedEntity is { } uid) + { + item = uid; + return true; + } + } + + item = default; + slot = null; return false; } } diff --git a/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs b/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs index c7505ab3c0..1703db25f3 100644 --- a/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs +++ b/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs @@ -42,26 +42,26 @@ public sealed class MagnetPickupSystem : EntitySystem public override void Update(float frameTime) { base.Update(frameTime); - var query = EntityQueryEnumerator(); + var query = EntityQueryEnumerator(); var currentTime = _timing.CurTime; - while (query.MoveNext(out var uid, out var comp, out var storage, out var xform)) + while (query.MoveNext(out var uid, out var comp, out var storage, out var xform, out var meta)) { if (comp.NextScan > currentTime) continue; comp.NextScan += ScanDelay; - // No space - if (!_storage.HasSpace((uid, storage))) - continue; - - if (!_inventory.TryGetContainingSlot(uid, out var slotDef)) + if (!_inventory.TryGetContainingSlot((uid, xform, meta), out var slotDef)) continue; if ((slotDef.SlotFlags & comp.SlotFlags) == 0x0) continue; + // No space + if (!_storage.HasSpace((uid, storage))) + continue; + var parentUid = xform.ParentUid; var playedSound = false; var finalCoords = xform.Coordinates; diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs index 0f2a631644..b9b21a4798 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs @@ -32,24 +32,19 @@ public partial class SharedGunSystem private bool TryGetClothingSlotEntity(EntityUid uid, ClothingSlotAmmoProviderComponent component, [NotNullWhen(true)] out EntityUid? slotEntity) { slotEntity = null; - if (!Containers.TryGetContainingContainer(uid, out var container)) - return false; - var user = container.Owner; - if (!TryComp(user, out var inventory)) + if (!_inventory.TryGetContainerSlotEnumerator(uid, out var enumerator, component.TargetSlot)) return false; - var slots = _inventory.GetSlots(user, inventory); - foreach (var slot in slots) + + while (enumerator.NextItem(out var item)) { - if (slot.SlotFlags != component.TargetSlot) + if (component.ProviderWhitelist == null || component.ProviderWhitelist.IsValid(item, EntityManager)) continue; - if (!_inventory.TryGetSlotEntity(user, slot.Name, out var e, inventory)) - continue; - if (component.ProviderWhitelist != null && !component.ProviderWhitelist.IsValid(e.Value, EntityManager)) - continue; - slotEntity = e; + + slotEntity = item; + return true; } - return slotEntity != null; + return false; } }