From 56371c3acba425406a2fdfb501b49fedfaa98e35 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 1 Jun 2023 00:09:14 +1000 Subject: [PATCH] Docking config changes (#16945) * Docking config changes - Should be more flexible with tight bounds. - Arrivals should always go for the 4-way dock. - Don't think it fixes Omega. * weh * Basic test --- Content.IntegrationTests/PoolManager.cs | 8 +- .../Tests/Shuttle/DockTest.cs | 82 +++++++++++++++++++ Content.Server/Shuttles/DockingConfig.cs | 5 -- .../Shuttles/Systems/DockingSystem.Shuttle.cs | 68 ++++++++++----- .../Systems/EmergencyShuttleSystem.cs | 3 +- 5 files changed, 136 insertions(+), 30 deletions(-) create mode 100644 Content.IntegrationTests/Tests/Shuttle/DockTest.cs diff --git a/Content.IntegrationTests/PoolManager.cs b/Content.IntegrationTests/PoolManager.cs index 8e2d2d3453..026ac68a0a 100644 --- a/Content.IntegrationTests/PoolManager.cs +++ b/Content.IntegrationTests/PoolManager.cs @@ -559,18 +559,22 @@ we are just going to end this here to save a lot of time. This is the exception public static async Task CreateTestMap(PairTracker pairTracker) { var server = pairTracker.Pair.Server; + + await server.WaitIdleAsync(); + var settings = pairTracker.Pair.Settings; + var mapManager = server.ResolveDependency(); + var tileDefinitionManager = server.ResolveDependency(); + if (settings.NoServer) throw new Exception("Cannot setup test map without server"); var mapData = new TestMapData(); await server.WaitPost(() => { - var mapManager = IoCManager.Resolve(); mapData.MapId = mapManager.CreateMap(); mapData.MapUid = mapManager.GetMapEntityId(mapData.MapId); mapData.MapGrid = mapManager.CreateGrid(mapData.MapId); mapData.GridUid = mapData.MapGrid.Owner; mapData.GridCoords = new EntityCoordinates(mapData.GridUid, 0, 0); - var tileDefinitionManager = IoCManager.Resolve(); var plating = tileDefinitionManager["Plating"]; var platingTile = new Tile(plating.TileId); mapData.MapGrid.SetTile(mapData.GridCoords, platingTile); diff --git a/Content.IntegrationTests/Tests/Shuttle/DockTest.cs b/Content.IntegrationTests/Tests/Shuttle/DockTest.cs new file mode 100644 index 0000000000..e4fcb09746 --- /dev/null +++ b/Content.IntegrationTests/Tests/Shuttle/DockTest.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Content.Server.Shuttles.Systems; +using Content.Tests; +using NUnit.Framework; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Maths; + +namespace Content.IntegrationTests.Tests.Shuttle; + +public sealed class DockTest : ContentUnitTest +{ + static IEnumerable TestSource() + { + // I-shape for grid1, T-shape for grid2 + yield return new object[] { new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Angle.Zero, Angle.Zero, true }; + yield return new object[] { new Vector2(0.5f, 1.5f), new Vector2(0.5f, 1.5f), Angle.Zero, Angle.Zero, false }; + } + + [Test] + [TestCaseSource(nameof(TestSource))] + public async Task TestDockingConfig(Vector2 dock1Pos, Vector2 dock2Pos, Angle dock1Angle, Angle dock2Angle, bool result) + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings() { NoClient = true }); + var server = pair.Pair.Server; + + var map = await PoolManager.CreateTestMap(pair); + + var entManager = server.ResolveDependency(); + var mapManager = server.ResolveDependency(); + var dockingSystem = entManager.System(); + + var mapId = map.MapId; + + await server.WaitAssertion(() => + { + entManager.DeleteEntity(map.GridUid); + var grid1 = mapManager.CreateGrid(mapId); + var grid2 = mapManager.CreateGrid(mapId); + var grid2Offset = new Vector2(50f, 50f); + entManager.GetComponent(grid2.Owner).LocalPosition = grid2Offset; + + // Tetris tests + // Grid1 is a vertical I + // Grid2 is a T + + var tiles1 = new List<(Vector2i Index, Tile Tile)>() + { + new(new Vector2i(0, 0), new Tile(1)), + new(new Vector2i(0, 1), new Tile(1)), + new(new Vector2i(0, 2), new Tile(1)), + }; + + grid1.SetTiles(tiles1); + var dock1 = entManager.SpawnEntity("AirlockShuttle", new EntityCoordinates(grid1.Owner, dock1Pos)); + var dock1Xform = entManager.GetComponent(dock1); + dock1Xform.LocalRotation = dock1Angle; + + var tiles2 = new List<(Vector2i Index, Tile Tile)>() + { + new(new Vector2i(0, 0), new Tile(1)), + new(new Vector2i(0, 1), new Tile(1)), + new(new Vector2i(0, 2), new Tile(1)), + new(new Vector2i(-1, 2), new Tile(1)), + new(new Vector2i(1, 2), new Tile(1)), + }; + + grid2.SetTiles(tiles2); + var dock2 = entManager.SpawnEntity("AirlockShuttle", new EntityCoordinates(grid2.Owner, dock2Pos)); + var dock2Xform = entManager.GetComponent(dock2); + dock2Xform.LocalRotation = dock2Angle; + + var config = dockingSystem.GetDockingConfig(grid1.Owner, grid2.Owner); + + Assert.That(result, Is.EqualTo(config != null)); + }); + + await pair.CleanReturnAsync(); + } +} diff --git a/Content.Server/Shuttles/DockingConfig.cs b/Content.Server/Shuttles/DockingConfig.cs index 446f832b59..c89250cdce 100644 --- a/Content.Server/Shuttles/DockingConfig.cs +++ b/Content.Server/Shuttles/DockingConfig.cs @@ -13,11 +13,6 @@ public sealed class DockingConfig /// public List<(EntityUid DockAUid, EntityUid DockBUid, DockingComponent DockA, DockingComponent DockB)> Docks = new(); - /// - /// Area relative to the target grid the emergency shuttle will spawn in on. - /// - public Box2 Area; - /// /// Target grid for docking. /// diff --git a/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs b/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs index f1bb320780..b11a084615 100644 --- a/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs +++ b/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs @@ -4,6 +4,7 @@ using Content.Server.Shuttles.Components; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics; +using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Components; namespace Content.Server.Shuttles.Systems; @@ -38,16 +39,17 @@ public sealed partial class DockingSystem TransformComponent shuttleDockXform, DockingComponent gridDock, TransformComponent gridDockXform, - Angle targetGridRotation, Box2 shuttleAABB, + Angle targetGridRotation, + FixturesComponent shuttleFixtures, MapGridComponent grid, - [NotNullWhen(true)] out Box2? shuttleDockedAABB, out Matrix3 matty, + [NotNullWhen(true)] out Box2? shuttleDockedAABB, out Angle gridRotation) { + shuttleDockedAABB = null; gridRotation = Angle.Zero; matty = Matrix3.Identity; - shuttleDockedAABB = null; if (shuttleDock.Docked || gridDock.Docked || @@ -64,18 +66,17 @@ public sealed partial class DockingSystem // Need to invert the grid's angle. var shuttleDockAngle = shuttleDockXform.LocalRotation; var gridDockAngle = gridDockXform.LocalRotation.Opposite(); + var offsetAngle = gridDockAngle - shuttleDockAngle; var stationDockMatrix = Matrix3.CreateInverseTransform(stationDockPos, shuttleDockAngle); var gridXformMatrix = Matrix3.CreateTransform(gridDockXform.LocalPosition, gridDockAngle); Matrix3.Multiply(in stationDockMatrix, in gridXformMatrix, out matty); - shuttleDockedAABB = matty.TransformBox(shuttleAABB); - // Rounding moment - shuttleDockedAABB = shuttleDockedAABB.Value.Enlarged(-0.01f); - if (!ValidSpawn(grid, shuttleDockedAABB.Value)) + if (!ValidSpawn(grid, matty, offsetAngle, shuttleFixtures)) return false; - gridRotation = (targetGridRotation + gridDockAngle - shuttleDockAngle).Reduced(); + shuttleDockedAABB = matty.TransformBox(shuttleAABB); + gridRotation = (targetGridRotation + offsetAngle).Reduced(); return true; } @@ -129,16 +130,19 @@ public sealed partial class DockingSystem var targetGridGrid = Comp(targetGrid); var targetGridXform = xformQuery.GetComponent(targetGrid); var targetGridAngle = _transform.GetWorldRotation(targetGridXform).Reduced(); - + var shuttleFixturesComp = Comp(shuttleUid); var shuttleAABB = Comp(shuttleUid).LocalAABB; + var foundDocks = new HashSet(); var validDockConfigs = new List(); - if (shuttleDocks.Count > 0) { // We'll try all combinations of shuttle docks and see which one is most suitable foreach (var (dockUid, shuttleDock) in shuttleDocks) { + if (foundDocks.Contains(dockUid)) + continue; + var shuttleDockXform = xformQuery.GetComponent(dockUid); foreach (var (gridDockUid, gridDock) in gridDocks) @@ -148,11 +152,12 @@ public sealed partial class DockingSystem if (!CanDock( shuttleDock, shuttleDockXform, gridDock, gridXform, - targetGridAngle, shuttleAABB, + targetGridAngle, + shuttleFixturesComp, targetGridGrid, - out var dockedAABB, out var matty, + out var dockedAABB, out var targetAngle)) { continue; @@ -162,6 +167,7 @@ public sealed partial class DockingSystem var spawnPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero)); spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, spawnPosition.ToMapPos(EntityManager, _transform)); + // TODO: use tight bounds var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetAngle, spawnPosition.Position); // Check if there's no intersecting grids (AKA oh god it's docking at cargo). @@ -182,7 +188,7 @@ public sealed partial class DockingSystem foreach (var (otherUid, other) in shuttleDocks) { - if (other == shuttleDock) + if (other == shuttleDock || foundDocks.Contains(otherUid)) continue; foreach (var (otherGridUid, otherGrid) in gridDocks) @@ -195,13 +201,14 @@ public sealed partial class DockingSystem xformQuery.GetComponent(otherUid), otherGrid, xformQuery.GetComponent(otherGridUid), + shuttleAABB, targetGridAngle, - shuttleAABB, targetGridGrid, - out var otherDockedAABB, + shuttleFixturesComp, targetGridGrid, out _, + out var otherdockedAABB, out var otherTargetAngle) || - !otherDockedAABB.Equals(dockedAABB) || - !targetAngle.Equals(otherTargetAngle)) + !targetAngle.Equals(otherTargetAngle) || + !dockedAABB.Equals(otherdockedAABB)) { continue; } @@ -213,21 +220,25 @@ public sealed partial class DockingSystem validDockConfigs.Add(new DockingConfig() { Docks = dockedPorts, - Area = dockedAABB.Value, Coordinates = spawnPosition, Angle = targetAngle, }); + + foreach (var dock in dockedPorts) + { + foundDocks.Add(dock.DockAUid); + } } } } if (validDockConfigs.Count <= 0) - return null; + return null; // Prioritise by priority docks, then by maximum connected ports, then by most similar angle. validDockConfigs = validDockConfigs .OrderByDescending(x => x.Docks.Any(docks => - TryComp(docks.DockB.Owner, out var priority) && + TryComp(docks.DockBUid, out var priority) && priority.Tag?.Equals(priorityTag) == true)) .ThenByDescending(x => x.Docks.Count) .ThenBy(x => Math.Abs(Angle.ShortestDistance(x.Angle.Reduced(), targetGridAngle).Theta)).ToList(); @@ -242,9 +253,22 @@ public sealed partial class DockingSystem /// /// Checks whether the emergency shuttle can warp to the specified position. /// - private bool ValidSpawn(MapGridComponent grid, Box2 area) + private bool ValidSpawn(MapGridComponent grid, Matrix3 matty, Angle angle, FixturesComponent shuttleFixturesComp) { - return !grid.GetLocalTilesIntersecting(area).Any(); + var transform = new Transform(matty.Transform(Vector2.Zero), angle); + + // Because some docking bounds are tight af need to check each chunk individually + foreach (var fix in shuttleFixturesComp.Fixtures.Values) + { + var polyShape = (PolygonShape) fix.Shape; + var aabb = polyShape.ComputeAABB(transform, 0); + aabb = aabb.Enlarged(-0.01f); + + if (grid.GetLocalTilesIntersecting(aabb).Any()) + return false; + } + + return true; } public List<(EntityUid Uid, DockingComponent Component)> GetDocks(EntityUid uid) diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index 7b83ed15b1..6b100cf508 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -20,6 +20,7 @@ using Robust.Server.Maps; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -161,7 +162,7 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem RaiseNetworkEvent(new EmergencyShuttlePositionMessage() { StationUid = targetGrid, - Position = config.Area, + Position = Comp(stationShuttle.EmergencyShuttle.Value).LocalAABB.Translated(config.Coordinates.Position) }); }