diff --git a/Robust.Shared/GameObjects/Systems/SharedGridTraversalSystem.cs b/Robust.Shared/GameObjects/Systems/SharedGridTraversalSystem.cs index 6774a628f..d16068c95 100644 --- a/Robust.Shared/GameObjects/Systems/SharedGridTraversalSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedGridTraversalSystem.cs @@ -25,9 +25,15 @@ internal sealed class SharedGridTraversalSystem : EntitySystem public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnStartup); _transform.OnGlobalMoveEvent += OnMove; } + private void OnStartup(ref TransformStartupEvent ev) + { + CheckTraverse(ev.Entity.Owner, ev.Entity.Comp); + } + public override void Shutdown() { _transform.OnGlobalMoveEvent -= OnMove; @@ -92,7 +98,7 @@ internal sealed class SharedGridTraversalSystem : EntitySystem : Transform(xform.ParentUid).LocalMatrix.Transform(xform.LocalPosition); // Change parent if necessary - if (_mapManager.TryFindGridAt(xform.MapID, mapPos, out var gridUid, out _)) + if (_mapManager.TryFindGridAt(map, mapPos, out var gridUid, out _)) { // Some minor duplication here with AttachParent but only happens when going on/off grid so not a big deal ATM. if (gridUid != xform.GridUid) diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs index 62b91a514..d0717528f 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs @@ -332,7 +332,7 @@ public abstract partial class SharedTransformSystem var parentEv = new EntParentChangedMessage(uid, null, MapId.Nullspace, xform); RaiseLocalEvent(uid, ref parentEv, true); - var ev = new TransformStartupEvent(xform); + var ev = new TransformStartupEvent((uid, xform)); RaiseLocalEvent(uid, ref ev, true); DebugTools.Assert(!xform.NoLocalRotation || xform.LocalRotation == 0, $"NoRot entity has a non-zero local rotation. entity: {ToPrettyString(uid)}"); @@ -1334,22 +1334,15 @@ public abstract partial class SharedTransformSystem if (!xform.ParentUid.IsValid()) return false; - EntityUid newParent; - var oldPos = GetWorldPosition(xform, XformQuery); - if (_mapManager.TryFindGridAt(xform.MapID, oldPos, XformQuery, out var gridUid, out _)) - { - newParent = gridUid; - } - else if (_mapManager.GetMapEntityId(xform.MapID) is { Valid: true } mapEnt) - { - newParent = mapEnt; - } - else - { + if (xform.MapUid is not { } map) return false; - } - coordinates = new(newParent, GetInvWorldMatrix(newParent, XformQuery).Transform(oldPos)); + var newParent = map; + var oldPos = GetWorldPosition(xform); + if (_mapManager.TryFindGridAt(map, oldPos, out var gridUid, out _)) + newParent = gridUid; + + coordinates = new(newParent, GetInvWorldMatrix(newParent).Transform(oldPos)); return true; } #endregion diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs index ba0ef1690..ffff66a18 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs @@ -256,14 +256,10 @@ namespace Robust.Shared.GameObjects } [ByRefEvent] - public readonly struct TransformStartupEvent + public readonly struct TransformStartupEvent(Entity entity) { - public readonly TransformComponent Component; - - public TransformStartupEvent(TransformComponent component) - { - Component = component; - } + public readonly Entity Entity = entity; + public TransformComponent Component => Entity.Comp; } /// diff --git a/Robust.Shared/Map/IMapManager.cs b/Robust.Shared/Map/IMapManager.cs index 3484f08fb..7d4dff651 100644 --- a/Robust.Shared/Map/IMapManager.cs +++ b/Robust.Shared/Map/IMapManager.cs @@ -114,6 +114,18 @@ namespace Robust.Shared.Map /// Returns true when a grid was found under the location. bool TryFindGridAt(MapId mapId, Vector2 worldPos, out EntityUid uid, [NotNullWhen(true)] out MapGridComponent? grid); + /// + /// Attempts to find the map grid under the map location. + /// + /// + /// This method will never return the map's default grid. + /// + /// Map to search. + /// Location on the map to check for a grid. + /// Grid that was found, if any. + /// Returns true when a grid was found under the location. + bool TryFindGridAt(EntityUid mapId, Vector2 worldPos, out EntityUid uid, [NotNullWhen(true)] out MapGridComponent? grid); + /// /// Attempts to find the map grid under the map location. /// diff --git a/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs b/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs index f71e1f0dd..6a22e5271 100644 --- a/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs +++ b/Robust.UnitTesting/Server/Maps/MapLoaderTest.cs @@ -97,6 +97,8 @@ entities: entMan.EnsureComponent(mapUid); entMan.EnsureComponent(mapUid); + var traversal = entMan.System(); + traversal.Enabled = false; var mapLoad = IoCManager.Resolve().GetEntitySystem(); if (!mapLoad.TryLoad(mapId, "/TestMap.yml", out var root) || root.FirstOrDefault() is not { Valid:true } geid) @@ -107,6 +109,7 @@ entities: var entity = entMan.GetComponent(geid)._children.Single(); var c = entMan.GetComponent(entity); + traversal.Enabled = true; Assert.That(c.Bar, Is.EqualTo(2)); Assert.That(c.Foo, Is.EqualTo(3)); diff --git a/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs b/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs index 7d4c59866..3f24e6893 100644 --- a/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs +++ b/Robust.UnitTesting/Shared/GameObjects/Systems/AnchoredSystemTests.cs @@ -157,6 +157,9 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems var grid = mapMan.GetGrid(gridId); grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); + var traversal = entMan.System(); + traversal.Enabled = false; + var subscriber = new Subscriber(); int calledCount = 0; var ent1 = entMan.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after @@ -171,7 +174,9 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems { Assert.That(ev.Entity, Is.EqualTo(ent1)); calledCount++; + } + traversal.Enabled = true; } /// @@ -510,6 +515,9 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems var grid = mapMan.GetGrid(gridId); grid.SetTile(grid.TileIndicesFor(coordinates), new Tile(1)); + var traversal = entMan.System(); + traversal.Enabled = false; + var subscriber = new Subscriber(); int calledCount = 0; var ent1 = entMan.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after @@ -525,6 +533,7 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems Assert.That(ev.Entity, Is.EqualTo(ent1)); calledCount++; } + traversal.Enabled = true; } /// diff --git a/Robust.UnitTesting/Shared/Physics/MapVelocity_Test.cs b/Robust.UnitTesting/Shared/Physics/MapVelocity_Test.cs index 4dd3ea6a0..84633036c 100644 --- a/Robust.UnitTesting/Shared/Physics/MapVelocity_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/MapVelocity_Test.cs @@ -38,6 +38,8 @@ namespace Robust.UnitTesting.Shared.Physics var system = entityManager.EntitySysManager; var physicsSys = system.GetEntitySystem(); var xformSystem = system.GetEntitySystem(); + var traversal = entityManager.System(); + traversal.Enabled = false; await server.WaitAssertion(() => { @@ -93,6 +95,7 @@ namespace Robust.UnitTesting.Shared.Physics Assert.That(velocities.Item1, Is.Approximately(linearVelocity, 1e-6)); Assert.That(velocities.Item2, Is.Approximately(angularVelocity, 1e-6)); }); + traversal.Enabled = true; } // Check that if something has more than one parent, the velocities are properly added @@ -107,6 +110,8 @@ namespace Robust.UnitTesting.Shared.Physics var system = entityManager.EntitySysManager; var physicsSys = system.GetEntitySystem(); var xformSystem = system.GetEntitySystem(); + var traversal = entityManager.System(); + traversal.Enabled = false; await server.WaitAssertion(() => { @@ -166,6 +171,7 @@ namespace Robust.UnitTesting.Shared.Physics Assert.That(velocities.Item1, Is.Approximately(linearVelocity, 1e-6)); Assert.That(velocities.Item2, Is.Approximately(angularVelocity, 1e-6)); }); + traversal.Enabled = true; } } } diff --git a/Robust.UnitTesting/Shared/TransformTests/GridTraversalTest.cs b/Robust.UnitTesting/Shared/TransformTests/GridTraversalTest.cs new file mode 100644 index 000000000..b6001f9f6 --- /dev/null +++ b/Robust.UnitTesting/Shared/TransformTests/GridTraversalTest.cs @@ -0,0 +1,90 @@ +using System.Numerics; +using System.Threading.Tasks; +using NUnit.Framework; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Maths; + +namespace Robust.UnitTesting.Shared.TransformTests; + +public sealed class GridTraversalTest : RobustIntegrationTest +{ + [Test] + public async Task TestSpawnTraversal() + { + var server = StartServer(); + await server.WaitIdleAsync(); + + var mapMan = server.ResolveDependency(); + var sEntMan = server.ResolveDependency(); + var xforms = sEntMan.System(); + var mapSys = sEntMan.System(); + + // Set up entities + MapId mapId = default!; + EntityUid map = default; + EntityUid grid = default; + Vector2 gridMapPos = default; + await server.WaitPost(() => + { + mapId = mapMan.CreateMap(); + map = mapMan.GetMapEntityId(mapId); + + var gridComp = mapMan.CreateGridEntity(mapId); + grid = gridComp.Owner; + mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1)); + var gridCentre = new EntityCoordinates(grid, .5f, .5f); + gridMapPos = gridCentre.ToMap(sEntMan, xforms).Position; + }); + + for (int i = 0; i < 10; i++) + { + await server.WaitRunTicks(1); + } + + await server.WaitPost(() => + { + // Spawn an entity using map coordinates will get parented to the grid when spawning on the grid. + var entity = sEntMan.SpawnEntity(null, new MapCoordinates(gridMapPos, mapId)); + Assert.That(sEntMan.GetComponent(entity).MapUid, Is.EqualTo(map)); + Assert.That(sEntMan.GetComponent(entity).GridUid, Is.EqualTo(grid)); + Assert.That(sEntMan.GetComponent(entity).ParentUid, Is.EqualTo(grid)); + sEntMan.Deleted(entity); + + // Spawning using map entity coords will still parent to the grid when spawning on the grid. + entity = sEntMan.SpawnEntity(null, new EntityCoordinates(map, gridMapPos)); + Assert.That(sEntMan.GetComponent(entity).MapUid, Is.EqualTo(map)); + Assert.That(sEntMan.GetComponent(entity).GridUid, Is.EqualTo(grid)); + Assert.That(sEntMan.GetComponent(entity).ParentUid, Is.EqualTo(grid)); + sEntMan.Deleted(entity); + + // and using local grid coords also works. + entity = sEntMan.SpawnEntity(null, new EntityCoordinates(grid, 0.5f, 0.5f)); + Assert.That(sEntMan.GetComponent(entity).MapUid, Is.EqualTo(map)); + Assert.That(sEntMan.GetComponent(entity).GridUid, Is.EqualTo(grid)); + Assert.That(sEntMan.GetComponent(entity).ParentUid, Is.EqualTo(grid)); + sEntMan.Deleted(entity); + + // Spawning an entity far away from the grid will leave it parented to the map. + entity = sEntMan.SpawnEntity(null, new MapCoordinates(new Vector2(100f, 100f), mapId)); + Assert.That(sEntMan.GetComponent(entity).MapUid, Is.EqualTo(map)); + Assert.Null(sEntMan.GetComponent(entity).GridUid); + Assert.That(sEntMan.GetComponent(entity).ParentUid, Is.EqualTo(map)); + sEntMan.Deleted(entity); + + entity = sEntMan.SpawnEntity(null, new EntityCoordinates(map, new Vector2(100f, 100f))); + Assert.That(sEntMan.GetComponent(entity).MapUid, Is.EqualTo(map)); + Assert.Null(sEntMan.GetComponent(entity).GridUid); + Assert.That(sEntMan.GetComponent(entity).ParentUid, Is.EqualTo(map)); + sEntMan.Deleted(entity); + + entity = sEntMan.SpawnEntity(null, new EntityCoordinates(grid, 100f, 100f)); + Assert.That(sEntMan.GetComponent(entity).MapUid, Is.EqualTo(map)); + Assert.Null(sEntMan.GetComponent(entity).GridUid); + Assert.That(sEntMan.GetComponent(entity).ParentUid, Is.EqualTo(map)); + sEntMan.Deleted(entity); + }); + } +} +