From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Mon, 24 Nov 2025 07:51:14 +0000 (+0100) Subject: Add test checking for multiple container fills, mark StorageFillComponent as obsolete... X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=b4f3c4472fb4033fe81cd8ad49fc05e5e2eb2f16;p=space-station-14.git Add test checking for multiple container fills, mark StorageFillComponent as obsolete (#41562) add test, obsolete storagefill --- diff --git a/Content.IntegrationTests/Tests/Storage/StorageTest.cs b/Content.IntegrationTests/Tests/Storage/StorageTest.cs new file mode 100644 index 0000000000..95d94906bb --- /dev/null +++ b/Content.IntegrationTests/Tests/Storage/StorageTest.cs @@ -0,0 +1,263 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using Content.Shared.Containers; +using Content.Shared.Item; +using Content.Shared.Prototypes; +using Content.Shared.Storage; +using Content.Shared.Storage.Components; +using Content.Shared.Storage.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests.Storage; + +public sealed class StorageTest +{ + /// + /// Can an item store more than itself weighs. + /// In an ideal world this test wouldn't need to exist because sizes would be recursive. + /// + [Test] + public async Task StorageSizeArbitrageTest() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var protoManager = server.ResolveDependency(); + var entMan = server.ResolveDependency(); + + var itemSys = entMan.System(); + + await server.WaitAssertion(() => + { + foreach (var proto in protoManager.EnumeratePrototypes()) + { + if (!proto.TryGetComponent("Storage", out var storage) || + storage.Whitelist != null || + storage.MaxItemSize == null || + !proto.TryGetComponent("Item", out var item)) + continue; + + Assert.That(itemSys.GetSizePrototype(storage.MaxItemSize.Value).Weight, + Is.LessThanOrEqualTo(itemSys.GetSizePrototype(item.Size).Weight), + $"Found storage arbitrage on {proto.ID}"); + } + }); + await pair.CleanReturnAsync(); + } + + [Test] + public async Task TestStorageFillPrototypes() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var protoManager = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + Assert.Multiple(() => + { + foreach (var proto in protoManager.EnumeratePrototypes()) + { + if (!proto.TryGetComponent("StorageFill", out var storage)) + continue; + + foreach (var entry in storage.Contents) + { + Assert.That(entry.Amount, Is.GreaterThan(0), $"Specified invalid amount of {entry.Amount} for prototype {proto.ID}"); + Assert.That(entry.SpawnProbability, Is.GreaterThan(0), $"Specified invalid probability of {entry.SpawnProbability} for prototype {proto.ID}"); + } + } + }); + }); + await pair.CleanReturnAsync(); + } + + [Test] + public async Task TestSufficientSpaceForFill() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var entMan = server.ResolveDependency(); + var protoMan = server.ResolveDependency(); + var compFact = server.ResolveDependency(); + var id = compFact.GetComponentName(); + + var itemSys = entMan.System(); + + var allSizes = protoMan.EnumeratePrototypes().ToList(); + allSizes.Sort(); + + await Assert.MultipleAsync(async () => + { + foreach (var (proto, fill) in pair.GetPrototypesWithComponent()) + { + if (proto.HasComponent(compFact)) + continue; + + StorageComponent? storage = null; + ItemComponent? item = null; + var size = 0; + await server.WaitAssertion(() => + { + if (!proto.TryGetComponent("Storage", out storage)) + { + Assert.Fail($"Entity {proto.ID} has storage-fill without a storage component!"); + return; + } + + proto.TryGetComponent("Item", out item); + size = GetFillSize(fill, false, protoMan, itemSys); + }); + + if (storage == null) + continue; + + var maxSize = storage.MaxItemSize; + if (storage.MaxItemSize == null) + { + if (item?.Size == null) + { + maxSize = SharedStorageSystem.DefaultStorageMaxItemSize; + } + else + { + var curIndex = allSizes.IndexOf(protoMan.Index(item.Size)); + var index = Math.Max(0, curIndex - 1); + maxSize = allSizes[index].ID; + } + } + + if (maxSize == null) + continue; + + Assert.That(size, Is.LessThanOrEqualTo(storage.Grid.GetArea()), $"{proto.ID} storage fill is too large."); + + foreach (var entry in fill.Contents) + { + if (entry.PrototypeId == null) + continue; + + if (!protoMan.TryIndex(entry.PrototypeId, out var fillItem)) + continue; + + ItemComponent? entryItem = null; + await server.WaitPost(() => + { + fillItem.TryGetComponent("Item", out entryItem); + }); + + if (entryItem == null) + continue; + + Assert.That(protoMan.Index(entryItem.Size).Weight, + Is.LessThanOrEqualTo(protoMan.Index(maxSize.Value).Weight), + $"Entity {proto.ID} has storage-fill item, {entry.PrototypeId}, that is too large"); + } + } + }); + + await pair.CleanReturnAsync(); + } + + [Test] + public async Task TestSufficientSpaceForEntityStorageFill() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var entMan = server.ResolveDependency(); + var protoMan = server.ResolveDependency(); + var compFact = server.ResolveDependency(); + var id = compFact.GetComponentName(); + + var itemSys = entMan.System(); + + foreach (var (proto, fill) in pair.GetPrototypesWithComponent()) + { + if (proto.HasComponent(compFact)) + continue; + + await server.WaitAssertion(() => + { + if (!proto.TryGetComponent("EntityStorage", out EntityStorageComponent? entStorage)) + Assert.Fail($"Entity {proto.ID} has storage-fill without a storage component!"); + + if (entStorage == null) + return; + + var size = GetFillSize(fill, true, protoMan, itemSys); + Assert.That(size, Is.LessThanOrEqualTo(entStorage.Capacity), + $"{proto.ID} storage fill is too large."); + }); + } + await pair.CleanReturnAsync(); + } + + private int GetEntrySize(EntitySpawnEntry entry, bool getCount, IPrototypeManager protoMan, SharedItemSystem itemSystem) + { + if (entry.PrototypeId == null) + return 0; + + if (!protoMan.TryIndex(entry.PrototypeId, out var proto)) + { + Assert.Fail($"Unknown prototype: {entry.PrototypeId}"); + return 0; + } + + if (getCount) + return entry.Amount; + + + if (proto.TryGetComponent("Item", out var item)) + return itemSystem.GetItemShape(item).GetArea() * entry.Amount; + + Assert.Fail($"Prototype is missing item comp: {entry.PrototypeId}"); + return 0; + } + + private int GetFillSize(StorageFillComponent fill, bool getCount, IPrototypeManager protoMan, SharedItemSystem itemSystem) + { + var totalSize = 0; + var groups = new Dictionary(); + foreach (var entry in fill.Contents) + { + var size = GetEntrySize(entry, getCount, protoMan, itemSystem); + + if (entry.GroupId == null) + totalSize += size; + else + groups[entry.GroupId] = Math.Max(size, groups.GetValueOrDefault(entry.GroupId)); + } + + return totalSize + groups.Values.Sum(); + } + + /// + /// Tests that prototypes are not using multiple container fill components at the same time. + /// + [Test] + public async Task NoMultipleContainerFillsTest() + { + await using var pair = await PoolManager.GetServerClient(); + var compFact = pair.Server.ResolveDependency(); + + Assert.Multiple(() => + { + foreach (var (proto, fill) in pair.GetPrototypesWithComponent()) + { + Assert.That(!proto.HasComponent(compFact), $"Prototype {proto.ID} has both {nameof(EntityTableContainerFillComponent)} and {nameof(StorageFillComponent)}."); + Assert.That(!proto.HasComponent(compFact), $"Prototype {proto.ID} has both {nameof(EntityTableContainerFillComponent)} and {nameof(ContainerFillComponent)}."); + } + + foreach (var (proto, fill) in pair.GetPrototypesWithComponent()) + { + Assert.That(!proto.HasComponent(compFact), $"Prototype {proto.ID} has both {nameof(ContainerFillComponent)} and {nameof(StorageFillComponent)}."); + } + }); + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/StorageTest.cs b/Content.IntegrationTests/Tests/StorageTest.cs deleted file mode 100644 index 13ddc56567..0000000000 --- a/Content.IntegrationTests/Tests/StorageTest.cs +++ /dev/null @@ -1,240 +0,0 @@ -#nullable enable -using System.Collections.Generic; -using System.Linq; -using Content.Server.Storage.Components; -using Content.Shared.Item; -using Content.Shared.Prototypes; -using Content.Shared.Storage; -using Content.Shared.Storage.Components; -using Content.Shared.Storage.EntitySystems; -using Robust.Shared.GameObjects; -using Robust.Shared.Prototypes; - -namespace Content.IntegrationTests.Tests -{ - [TestFixture] - public sealed class StorageTest - { - /// - /// Can an item store more than itself weighs. - /// In an ideal world this test wouldn't need to exist because sizes would be recursive. - /// - [Test] - public async Task StorageSizeArbitrageTest() - { - await using var pair = await PoolManager.GetServerClient(); - var server = pair.Server; - - var protoManager = server.ResolveDependency(); - var entMan = server.ResolveDependency(); - - var itemSys = entMan.System(); - - await server.WaitAssertion(() => - { - foreach (var proto in protoManager.EnumeratePrototypes()) - { - if (!proto.TryGetComponent("Storage", out var storage) || - storage.Whitelist != null || - storage.MaxItemSize == null || - !proto.TryGetComponent("Item", out var item)) - continue; - - Assert.That(itemSys.GetSizePrototype(storage.MaxItemSize.Value).Weight, - Is.LessThanOrEqualTo(itemSys.GetSizePrototype(item.Size).Weight), - $"Found storage arbitrage on {proto.ID}"); - } - }); - await pair.CleanReturnAsync(); - } - - [Test] - public async Task TestStorageFillPrototypes() - { - await using var pair = await PoolManager.GetServerClient(); - var server = pair.Server; - - var protoManager = server.ResolveDependency(); - - await server.WaitAssertion(() => - { - Assert.Multiple(() => - { - foreach (var proto in protoManager.EnumeratePrototypes()) - { - if (!proto.TryGetComponent("StorageFill", out var storage)) - continue; - - foreach (var entry in storage.Contents) - { - Assert.That(entry.Amount, Is.GreaterThan(0), $"Specified invalid amount of {entry.Amount} for prototype {proto.ID}"); - Assert.That(entry.SpawnProbability, Is.GreaterThan(0), $"Specified invalid probability of {entry.SpawnProbability} for prototype {proto.ID}"); - } - } - }); - }); - await pair.CleanReturnAsync(); - } - - [Test] - public async Task TestSufficientSpaceForFill() - { - await using var pair = await PoolManager.GetServerClient(); - var server = pair.Server; - - var entMan = server.ResolveDependency(); - var protoMan = server.ResolveDependency(); - var compFact = server.ResolveDependency(); - var id = compFact.GetComponentName(); - - var itemSys = entMan.System(); - - var allSizes = protoMan.EnumeratePrototypes().ToList(); - allSizes.Sort(); - - await Assert.MultipleAsync(async () => - { - foreach (var (proto, fill) in pair.GetPrototypesWithComponent()) - { - if (proto.HasComponent(compFact)) - continue; - - StorageComponent? storage = null; - ItemComponent? item = null; - var size = 0; - await server.WaitAssertion(() => - { - if (!proto.TryGetComponent("Storage", out storage)) - { - Assert.Fail($"Entity {proto.ID} has storage-fill without a storage component!"); - return; - } - - proto.TryGetComponent("Item", out item); - size = GetFillSize(fill, false, protoMan, itemSys); - }); - - if (storage == null) - continue; - - var maxSize = storage.MaxItemSize; - if (storage.MaxItemSize == null) - { - if (item?.Size == null) - { - maxSize = SharedStorageSystem.DefaultStorageMaxItemSize; - } - else - { - var curIndex = allSizes.IndexOf(protoMan.Index(item.Size)); - var index = Math.Max(0, curIndex - 1); - maxSize = allSizes[index].ID; - } - } - - if (maxSize == null) - continue; - - Assert.That(size, Is.LessThanOrEqualTo(storage.Grid.GetArea()), $"{proto.ID} storage fill is too large."); - - foreach (var entry in fill.Contents) - { - if (entry.PrototypeId == null) - continue; - - if (!protoMan.TryIndex(entry.PrototypeId, out var fillItem)) - continue; - - ItemComponent? entryItem = null; - await server.WaitPost(() => - { - fillItem.TryGetComponent("Item", out entryItem); - }); - - if (entryItem == null) - continue; - - Assert.That(protoMan.Index(entryItem.Size).Weight, - Is.LessThanOrEqualTo(protoMan.Index(maxSize.Value).Weight), - $"Entity {proto.ID} has storage-fill item, {entry.PrototypeId}, that is too large"); - } - } - }); - - await pair.CleanReturnAsync(); - } - - [Test] - public async Task TestSufficientSpaceForEntityStorageFill() - { - await using var pair = await PoolManager.GetServerClient(); - var server = pair.Server; - - var entMan = server.ResolveDependency(); - var protoMan = server.ResolveDependency(); - var compFact = server.ResolveDependency(); - var id = compFact.GetComponentName(); - - var itemSys = entMan.System(); - - foreach (var (proto, fill) in pair.GetPrototypesWithComponent()) - { - if (proto.HasComponent(compFact)) - continue; - - await server.WaitAssertion(() => - { - if (!proto.TryGetComponent("EntityStorage", out EntityStorageComponent? entStorage)) - Assert.Fail($"Entity {proto.ID} has storage-fill without a storage component!"); - - if (entStorage == null) - return; - - var size = GetFillSize(fill, true, protoMan, itemSys); - Assert.That(size, Is.LessThanOrEqualTo(entStorage.Capacity), - $"{proto.ID} storage fill is too large."); - }); - } - await pair.CleanReturnAsync(); - } - - private int GetEntrySize(EntitySpawnEntry entry, bool getCount, IPrototypeManager protoMan, SharedItemSystem itemSystem) - { - if (entry.PrototypeId == null) - return 0; - - if (!protoMan.TryIndex(entry.PrototypeId, out var proto)) - { - Assert.Fail($"Unknown prototype: {entry.PrototypeId}"); - return 0; - } - - if (getCount) - return entry.Amount; - - - if (proto.TryGetComponent("Item", out var item)) - return itemSystem.GetItemShape(item).GetArea() * entry.Amount; - - Assert.Fail($"Prototype is missing item comp: {entry.PrototypeId}"); - return 0; - } - - private int GetFillSize(StorageFillComponent fill, bool getCount, IPrototypeManager protoMan, SharedItemSystem itemSystem) - { - var totalSize = 0; - var groups = new Dictionary(); - foreach (var entry in fill.Contents) - { - var size = GetEntrySize(entry, getCount, protoMan, itemSystem); - - if (entry.GroupId == null) - totalSize += size; - else - groups[entry.GroupId] = Math.Max(size, groups.GetValueOrDefault(entry.GroupId)); - } - - return totalSize + groups.Values.Sum(); - } - } -} diff --git a/Content.Shared/Storage/Components/StorageFillComponent.cs b/Content.Shared/Storage/Components/StorageFillComponent.cs index e112368a49..22ae32c4a8 100644 --- a/Content.Shared/Storage/Components/StorageFillComponent.cs +++ b/Content.Shared/Storage/Components/StorageFillComponent.cs @@ -3,8 +3,9 @@ using Robust.Shared.GameStates; namespace Content.Shared.Storage.Components; -// TODO: -// REPLACE THIS WITH CONTAINERFILL +// Don't remove before December 2026. +// A lot of forks still use this for their prototypes. +[Obsolete("Use ContainerFillComponent or EntityTableContainerFillComponent instead")] [RegisterComponent, NetworkedComponent, Access(typeof(SharedStorageSystem))] public sealed partial class StorageFillComponent : Component {