diff --git a/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs b/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs new file mode 100644 index 0000000000..060c0efc29 --- /dev/null +++ b/Content.IntegrationTests/Tests/Utility/EntitySystemExtensionsTest.cs @@ -0,0 +1,82 @@ +#nullable enable +using System.Threading.Tasks; +using Content.Shared.Physics; +using Content.Shared.Utility; +using NUnit.Framework; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; + +namespace Content.IntegrationTests.Tests.Utility +{ + [TestFixture] + [TestOf(typeof(EntitySystemExtensions))] + public class EntitySystemExtensionsTest : ContentIntegrationTest + { + private const string BlockerDummyId = "BlockerDummy"; + + private static readonly string Prototypes = $@" +- type: entity + id: {BlockerDummyId} + name: {BlockerDummyId} + components: + - type: Physics + shapes: + - !type:PhysShapeAabb + bounds: ""-0.49,-0.49,0.49,0.49"" + mask: + - Impassable +"; + + [Test] + public async Task Test() + { + var serverOptions = new ServerContentIntegrationOption {ExtraPrototypes = Prototypes}; + var server = StartServer(serverOptions); + + await server.WaitIdleAsync(); + + var sMapManager = server.ResolveDependency(); + var sEntityManager = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + var grid = sMapManager.GetGrid(new GridId(1)); + var entityCoordinates = new EntityCoordinates(grid.GridEntityId, 0, 0); + + // Nothing blocking it, only entity is the grid + Assert.NotNull(sEntityManager.SpawnIfUnobstructed(null, entityCoordinates, CollisionGroup.Impassable)); + Assert.True(sEntityManager.TrySpawnIfUnobstructed(null, entityCoordinates, CollisionGroup.Impassable, out var entity)); + Assert.NotNull(entity); + + var mapId = new MapId(1); + var mapCoordinates = new MapCoordinates(0, 0, mapId); + + // Nothing blocking it, only entity is the grid + Assert.NotNull(sEntityManager.SpawnIfUnobstructed(null, mapCoordinates, CollisionGroup.Impassable)); + Assert.True(sEntityManager.TrySpawnIfUnobstructed(null, mapCoordinates, CollisionGroup.Impassable, out entity)); + Assert.NotNull(entity); + + // Spawn a blocker with an Impassable mask + sEntityManager.SpawnEntity(BlockerDummyId, entityCoordinates); + + // Cannot spawn something with an Impassable layer + Assert.Null(sEntityManager.SpawnIfUnobstructed(null, entityCoordinates, CollisionGroup.Impassable)); + Assert.False(sEntityManager.TrySpawnIfUnobstructed(null, entityCoordinates, CollisionGroup.Impassable, out entity)); + Assert.Null(entity); + + Assert.Null(sEntityManager.SpawnIfUnobstructed(null, mapCoordinates, CollisionGroup.Impassable)); + Assert.False(sEntityManager.TrySpawnIfUnobstructed(null, mapCoordinates, CollisionGroup.Impassable, out entity)); + Assert.Null(entity); + + // Other layers are fine + Assert.NotNull(sEntityManager.SpawnIfUnobstructed(null, entityCoordinates, CollisionGroup.MobImpassable)); + Assert.True(sEntityManager.TrySpawnIfUnobstructed(null, entityCoordinates, CollisionGroup.MobImpassable, out entity)); + Assert.NotNull(entity); + + Assert.NotNull(sEntityManager.SpawnIfUnobstructed(null, mapCoordinates, CollisionGroup.MobImpassable)); + Assert.True(sEntityManager.TrySpawnIfUnobstructed(null, mapCoordinates, CollisionGroup.MobImpassable, out entity)); + Assert.NotNull(entity); + }); + } + } +} diff --git a/Content.Shared/Utility/EntitySystemExtensions.cs b/Content.Shared/Utility/EntitySystemExtensions.cs new file mode 100644 index 0000000000..b06d595583 --- /dev/null +++ b/Content.Shared/Utility/EntitySystemExtensions.cs @@ -0,0 +1,85 @@ +#nullable enable +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Physics; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Physics; + +namespace Content.Shared.Utility +{ + public static class EntitySystemExtensions + { + public static IEntity? SpawnIfUnobstructed( + this IEntityManager entityManager, + string? prototypeName, + EntityCoordinates coordinates, + CollisionGroup collisionLayer, + in Box2? box = null, + IPhysicsManager? physicsManager = null) + { + physicsManager ??= IoCManager.Resolve(); + var mapCoordinates = coordinates.ToMap(entityManager); + + return entityManager.SpawnIfUnobstructed(prototypeName, mapCoordinates, collisionLayer, box, physicsManager); + } + + public static IEntity? SpawnIfUnobstructed( + this IEntityManager entityManager, + string? prototypeName, + MapCoordinates coordinates, + CollisionGroup collisionLayer, + in Box2? box = null, + IPhysicsManager? physicsManager = null) + { + var boxOrDefault = box.GetValueOrDefault(Box2.UnitCentered); + physicsManager ??= IoCManager.Resolve(); + + foreach (var body in physicsManager.GetCollidingEntities(coordinates.MapId, in boxOrDefault)) + { + if (!body.Hard) + { + continue; + } + + if (collisionLayer == 0 || (body.CollisionMask & (int) collisionLayer) == 0) + { + continue; + } + + return null; + } + + return entityManager.SpawnEntity(prototypeName, coordinates); + } + + public static bool TrySpawnIfUnobstructed( + this IEntityManager entityManager, + string? prototypeName, + EntityCoordinates coordinates, + CollisionGroup collisionLayer, + [NotNullWhen(true)] out IEntity? entity, + Box2? box = null, + IPhysicsManager? physicsManager = null) + { + entity = entityManager.SpawnIfUnobstructed(prototypeName, coordinates, collisionLayer, box, physicsManager); + + return entity != null; + } + + public static bool TrySpawnIfUnobstructed( + this IEntityManager entityManager, + string? prototypeName, + MapCoordinates coordinates, + CollisionGroup collisionLayer, + [NotNullWhen(true)] out IEntity? entity, + in Box2? box = null, + IPhysicsManager? physicsManager = null) + { + entity = entityManager.SpawnIfUnobstructed(prototypeName, coordinates, collisionLayer, box, physicsManager); + + return entity != null; + } + } +}