using System.Collections.Generic; using System.Linq; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Utility; namespace Robust.Shared.GameObjects { public abstract class SharedTransformSystem : EntitySystem { [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IEntityLookup _entityLookup = default!; private readonly Queue _gridMoves = new(); private readonly Queue _otherMoves = new(); public override void Initialize() { base.Initialize(); UpdatesOutsidePrediction = true; _mapManager.TileChanged += MapManagerOnTileChanged; } public override void Shutdown() { _mapManager.TileChanged -= MapManagerOnTileChanged; base.Shutdown(); } private void MapManagerOnTileChanged(object? sender, TileChangedEventArgs e) { if(e.NewTile.Tile != Tile.Empty) return; DeparentAllEntsOnTile(e.NewTile.GridIndex, e.NewTile.GridIndices); } /// /// De-parents and unanchors all entities on a grid-tile. /// /// /// Used when a tile on a grid is removed (becomes space). Only de-parents entities if they are actually /// parented to that grid. No more disemboweling mobs. /// private void DeparentAllEntsOnTile(GridId gridId, Vector2i tileIndices) { var grid = _mapManager.GetGrid(gridId); var gridUid = grid.GridEntityId; var mapTransform = Transform(_mapManager.GetMapEntityId(grid.ParentMapId)); var aabb = _entityLookup.GetLocalBounds(tileIndices, grid.TileSize); foreach (var entity in _entityLookup.GetEntitiesIntersecting(gridId, tileIndices).ToList()) { // If a tile is being removed due to an explosion or somesuch, some entities are likely being deleted. // Avoid unnecessary entity updates. if (EntityManager.IsQueuedForDeletion(entity)) continue; var transform = Transform(entity); if (transform.ParentUid == gridUid && aabb.Contains(transform.LocalPosition)) transform.AttachParent(mapTransform); } } public void DeferMoveEvent(ref MoveEvent moveEvent) { if (EntityManager.HasComponent(moveEvent.Sender)) _gridMoves.Enqueue(moveEvent); else _otherMoves.Enqueue(moveEvent); } public override void Update(float frameTime) { base.Update(frameTime); // Process grid moves first. Process(_gridMoves); Process(_otherMoves); void Process(Queue queue) { while (queue.TryDequeue(out var ev)) { if (EntityManager.Deleted(ev.Sender)) { continue; } // Hopefully we can remove this when PVS gets updated to not use NaNs if (!ev.NewPosition.IsValid(EntityManager)) { continue; } RaiseLocalEvent(ev.Sender, ref ev); } } } } }