using System.Numerics;
using Robust.Shared.Audio.Components;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
///
/// Handles moving entities between grids as they move around.
///
public sealed partial class SharedGridTraversalSystem : EntitySystem
{
[Dependency] private SharedMapSystem _mapSystem = default!;
[Dependency] private SharedTransformSystem _transform = default!;
[Dependency] private IGameTiming _timing = default!;
private EntityUid _recursionGuard;
///
/// Enables or disables changing grid / map uid upon moving.
/// WARNING: If you do this in a live-game. You need to make sure that the parented entity
/// doesn't move too far away from the grid. As it will cause Entity Lookups to not see it
/// (because the grid its parented to is not close enough and all parented entities are assumed
/// to be on the grid through the broadphase component)
///
public bool Enabled = true;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnStartup);
}
private void OnStartup(ref TransformStartupEvent ev)
{
CheckTraverse(ev.Entity);
}
internal void CheckTraverse(Entity entity)
{
if (!Enabled || _timing.ApplyingState)
return;
var uid = entity.Owner;
var xform = entity.Comp;
// Grid-traversal can result in a stack overflow. This is probably because of rounding errors when checking
// grid intersections using the map vs grid coordinates.
if (uid == _recursionGuard)
return;
// Don't run logic if:
// - Current parent is not a grid / map
// - Is anchored
// - Is a grid/map
// - Is in nullspace
if ((xform.GridUid != xform.ParentUid && xform.MapUid != xform.ParentUid)
|| xform.Anchored
|| uid == xform.GridUid
|| uid == xform.MapUid
|| xform.MapUid is not {} map
|| !xform.GridTraversal)
{
return;
}
if (_recursionGuard != EntityUid.Invalid)
{
Log.Error($"Grid traversal attempted to handle movement of {ToPrettyString(uid)} while moving {ToPrettyString(_recursionGuard)}");
return;
}
_recursionGuard = uid;
try
{
CheckTraversal(uid, xform, map);
}
finally
{
_recursionGuard = default;
}
}
public void CheckTraversal(EntityUid entity, TransformComponent xform, EntityUid map)
{
// TODO: This should probably check center of mass.
DebugTools.Assert(!HasComp(entity));
DebugTools.Assert(!HasComp(entity));
var mapPos = xform.ParentUid == xform.MapUid
? xform.LocalPosition
: Vector2.Transform(xform.LocalPosition, Transform(xform.ParentUid).LocalMatrix);
// Change parent if necessary
if (_mapSystem.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 && !TerminatingOrDeleted(gridUid))
_transform.SetParent(entity, xform, gridUid);
return;
}
// Attach them to map / they are on an invalid grid
if (xform.GridUid != null)
_transform.SetParent(entity, xform, map);
}
}