using System.Linq; using System.Numerics; using System.Threading.Tasks; using NUnit.Framework; using Robust.Client.GameObjects; using Robust.Client.Timing; using Robust.Server.Player; using Robust.Shared.Containers; using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Robust.UnitTesting.Shared.GameObjects { public sealed class ContainerTests : RobustIntegrationTest { /// /// Tests container states with children that do not exist on the client /// and tests that said children are added to the container when they do arrive on the client. /// /// [Test] public async Task TestContainerNonexistantItems() { var server = StartServer(); var client = StartClient(); await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()); var cEntManager = client.ResolveDependency(); var clientNetManager = client.ResolveDependency(); var sEntManager = server.ResolveDependency(); var sPlayerManager = server.ResolveDependency(); Assert.DoesNotThrow(() => client.SetConnectTarget(server)); client.Post(() => { clientNetManager.ClientConnect(null!, 0, null!); }); for (int i = 0; i < 10; i++) { await server.WaitRunTicks(1); await client.WaitRunTicks(1); } // Setup var mapId = MapId.Nullspace; var mapPos = MapCoordinates.Nullspace; EntityUid entityUid = default!; var cContainerSys = cEntManager.System(); var sContainerSys = sEntManager.System(); var sMetadataSys = sEntManager.System(); await server.WaitAssertion(() => { sEntManager.System().CreateMap(out mapId); mapPos = new MapCoordinates(new Vector2(0, 0), mapId); entityUid = sEntManager.SpawnEntity(null, mapPos); sMetadataSys.SetEntityName(entityUid, "Container"); sContainerSys.EnsureContainer(entityUid, "dummy"); // Setup PVS sEntManager.AddComponent(entityUid); var player = sPlayerManager.Sessions.First(); server.PlayerMan.SetAttachedEntity(player, entityUid); sPlayerManager.JoinGame(player); }); for (int i = 0; i < 10; i++) { await server.WaitRunTicks(1); await client.WaitRunTicks(1); } EntityUid itemUid = default!; await server.WaitAssertion(() => { itemUid = sEntManager.SpawnEntity(null, mapPos); sMetadataSys.SetEntityName(itemUid, "Item"); var container = sContainerSys.EnsureContainer(entityUid, "dummy"); Assert.That(sContainerSys.Insert(itemUid, container)); // Modify visibility layer so that the item does not get sent ot the player sEntManager.System().AddLayer(itemUid, 10 ); }); // Needs minimum 4 to sync to client because buffer size is 3 await server.WaitRunTicks(4); await client.WaitRunTicks(10); EntityUid cEntityUid = default!; await client.WaitAssertion(() => { cEntityUid = client.EntMan.GetEntity(server.EntMan.GetNetEntity(entityUid)); if (!cEntManager.TryGetComponent(cEntityUid, out var containerManagerComp)) { Assert.Fail(); return; } var container = cContainerSys.GetContainer(cEntityUid, "dummy", containerManagerComp); Assert.That(container.ContainedEntities.Count, Is.EqualTo(0)); Assert.That(container.ExpectedEntities.Count, Is.EqualTo(1)); Assert.That(cContainerSys.ExpectedEntities.ContainsKey(sEntManager.GetNetEntity(itemUid))); Assert.That(cContainerSys.ExpectedEntities.Count, Is.EqualTo(1)); }); await server.WaitAssertion(() => { // Modify visibility layer so it now gets sent to the client sEntManager.System().RemoveLayer(itemUid, 10 ); }); await server.WaitRunTicks(1); await client.WaitRunTicks(4); await client.WaitAssertion(() => { if (!cEntManager.TryGetComponent(cEntityUid, out var containerManagerComp)) { Assert.Fail(); return; } var container = cContainerSys.GetContainer(cEntityUid, "dummy", containerManagerComp); Assert.That(container.ContainedEntities.Count, Is.EqualTo(1)); Assert.That(container.ExpectedEntities.Count, Is.EqualTo(0)); Assert.That(!cContainerSys.ExpectedEntities.ContainsKey(sEntManager.GetNetEntity(itemUid))); Assert.That(cContainerSys.ExpectedEntities, Is.Empty); }); await client.WaitPost(() => clientNetManager.ClientDisconnect("")); await server.WaitRunTicks(5); await client.WaitRunTicks(5); } /// /// Tests container states with children that do not exist on the client /// and that if those children are deleted that they get properly removed from the expected entities list. /// /// [Test] public async Task TestContainerExpectedEntityDeleted() { var server = StartServer(); var client = StartClient(); await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()); var cEntManager = client.ResolveDependency(); var clientTime = client.ResolveDependency(); var clientNetManager = client.ResolveDependency(); var sMapManager = server.ResolveDependency(); var sEntManager = server.ResolveDependency(); var sPlayerManager = server.ResolveDependency(); var serverTime = server.ResolveDependency(); Assert.DoesNotThrow(() => client.SetConnectTarget(server)); await client.WaitPost(() => { clientNetManager.ClientConnect(null!, 0, null!); }); for (int i = 0; i < 10; i++) { await server.WaitRunTicks(1); await client.WaitRunTicks(1); } // Setup MapId mapId; var mapPos = MapCoordinates.Nullspace; EntityUid sEntityUid = default!; EntityUid sItemUid = default!; NetEntity netEnt = default; var cContainerSys = cEntManager.System(); var sContainerSys = sEntManager.System(); var sMetadataSys = sEntManager.System(); await server.WaitAssertion(() => { sEntManager.System().CreateMap(out mapId); mapPos = new MapCoordinates(new Vector2(0, 0), mapId); sEntityUid = sEntManager.SpawnEntity(null, mapPos); sMetadataSys.SetEntityName(sEntityUid, "Container"); sContainerSys.EnsureContainer(sEntityUid, "dummy"); // Setup PVS sEntManager.AddComponent(sEntityUid); var player = sPlayerManager.Sessions.First(); server.PlayerMan.SetAttachedEntity(player, sEntityUid); sPlayerManager.JoinGame(player); }); for (int i = 0; i < 10; i++) { await server.WaitRunTicks(1); await client.WaitRunTicks(1); } await server.WaitAssertion(() => { sItemUid = sEntManager.SpawnEntity(null, mapPos); netEnt = sEntManager.GetNetEntity(sItemUid); sMetadataSys.SetEntityName(sItemUid, "Item"); var container = sContainerSys.GetContainer(sEntityUid, "dummy"); sContainerSys.Insert(sItemUid, container); // Modify visibility layer so that the item does not get sent ot the player sEntManager.System().AddLayer(sItemUid, 10 ); }); await server.WaitRunTicks(1); while (clientTime.LastRealTick < serverTime.CurTick - 1) { await client.WaitRunTicks(1); } var cUid = cEntManager.GetEntity(sEntManager.GetNetEntity(sEntityUid)); await client.WaitAssertion(() => { if (!cEntManager.TryGetComponent(cUid, out var containerManagerComp)) { Assert.Fail(); return; } var container = cContainerSys.GetContainer(cUid, "dummy", containerManagerComp); Assert.That(container.ContainedEntities.Count, Is.EqualTo(0)); Assert.That(container.ExpectedEntities.Count, Is.EqualTo(1)); Assert.That(cContainerSys.ExpectedEntities.ContainsKey(netEnt)); Assert.That(cContainerSys.ExpectedEntities.Count, Is.EqualTo(1)); }); await server.WaitAssertion(() => { // If possible it'd be best to only have the DeleteEntity, but right now // the entity deleted event is not played on the client if the entity does not exist on the client. if (sEntManager.EntityExists(sItemUid) // && itemUid.TryGetContainer(out var container)) && sContainerSys.TryGetContainingContainer(sItemUid, out var container)) { sContainerSys.Remove(sItemUid, container, force: true); } sEntManager.DeleteEntity(sItemUid); }); await server.WaitRunTicks(1); await client.WaitRunTicks(4); await client.WaitAssertion(() => { if (!cEntManager.TryGetComponent(cUid, out var containerManagerComp)) { Assert.Fail(); return; } var container = cContainerSys.GetContainer(cUid, "dummy", containerManagerComp); Assert.That(container.ContainedEntities.Count, Is.EqualTo(0)); Assert.That(container.ExpectedEntities.Count, Is.EqualTo(0)); Assert.That(!cContainerSys.ExpectedEntities.ContainsKey(netEnt)); Assert.That(cContainerSys.ExpectedEntities.Count, Is.EqualTo(0)); }); await client.WaitPost(() => clientNetManager.ClientDisconnect("")); await server.WaitRunTicks(5); await client.WaitRunTicks(5); } /// /// Sets up a new container, initializes map, saves the map, then loads it again on another map. The contained entity should still /// be inside the container. /// [Test] public async Task Container_DeserializeGrid_IsStillContained() { var server = StartServer(); await Task.WhenAll(server.WaitIdleAsync()); var sEntManager = server.ResolveDependency(); var mapSys = sEntManager.System(); var sContainerSys = sEntManager.System(); var sMetadataSys = sEntManager.System(); var path = new ResPath("container_test.yml"); await server.WaitAssertion(() => { // build the map sEntManager.System().CreateMap(out var mapIdOne); Assert.That(mapSys.IsInitialized(mapIdOne), Is.True); var containerEnt = sEntManager.SpawnEntity(null, new MapCoordinates(1, 1, mapIdOne)); sMetadataSys.SetEntityName(containerEnt, "ContainerEnt"); var containeeEnt = sEntManager.SpawnEntity(null, new MapCoordinates(2, 2, mapIdOne)); sMetadataSys.SetEntityName(containeeEnt, "ContaineeEnt"); var container = sContainerSys.MakeContainer(containerEnt, "testContainer"); container.OccludesLight = true; container.ShowContents = true; sContainerSys.Insert(containeeEnt, container); // save the map var mapLoader = sEntManager.EntitySysManager.GetEntitySystem(); Assert.That(mapLoader.TrySaveMap(mapIdOne, path)); mapSys.DeleteMap(mapIdOne); }); // A few moments later... await server.WaitRunTicks(10); await server.WaitAssertion(() => { var mapLoader = sEntManager.System(); // load the map Assert.That(mapLoader.TryLoadMap(path, out var map, out _)); Assert.That(mapSys.IsInitialized(map), Is.True); // Map Initialize-ness is saved in the map file. }); await server.WaitRunTicks(1); await server.WaitAssertion(() => { // verify container Entity container = default; var query = sEntManager.EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var containerComp)) { container = (uid, containerComp); } var containerEnt = container.Owner; Assert.That(container.Comp, Is.Not.Null); Assert.That(sEntManager.GetComponent(containerEnt).EntityName, Is.EqualTo("ContainerEnt")); Assert.That(container.Comp!.Containers.ContainsKey("testContainer")); var baseContainer = sContainerSys.GetContainer(containerEnt, "testContainer", container.Comp); Assert.That(baseContainer.ContainedEntities, Has.Count.EqualTo(1)); var containeeEnt = baseContainer.ContainedEntities[0]; Assert.That(sEntManager.GetComponent(containeeEnt).EntityName, Is.EqualTo("ContaineeEnt")); }); } } }