mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-01 17:47:24 +02:00
* Move MapManager queries to SharedMapSystem Moves all variants of FindGridsIntersecting/TryFindGridAt to SharedMapSystem Hollows out the MapManager methods and converts them into relays to SharedMapSystem * Move CreateGrid to SharedMapSystem Moves the functionality for CreateGrid and its variants to SharedMapSystem Hollows out the MapManager methods and converts them into relays for the SharedMapSystem methods Obsoletes them too Also moves over the GetAllMapGrids and GetAllGrids methods * Move RaiseOnTileChanged to SharedMapSystem Also moves the SuppressOnTileChanged flag member to SharedMapSystem Hollows out and obsoletes the MapManager versions * Move default value constants to SharedMapSystem * Converts map pausing events into LocalizedEntityCommands * Move MapManager related delegates/structs to SharedMapSystem Moves the GridCreationOptions struct to the same namespace as SharedMapSystem and converts it into a record struct Move the GridCallback delegates to the same namespace as SharedMapSystem * Move CullDeletionHistory to SharedMapSystem Well, that was less painful than I thought it would be * Actually obsolete the NetworkedMapManager method * Rename file * Doc comments for new SharedMapSystem methods * Doc comment * Doc comments * Fix access * Purge all references to IMapManagerInternal * Cull easy IMapManager references * The rest * Purge IMapManager * Private internal FindGridsIntersecting method * please die * 41 * notes --------- Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
367 lines
15 KiB
C#
367 lines
15 KiB
C#
using Robust.Shared.GameStates;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Physics;
|
|
using Robust.Shared.Serialization;
|
|
using Robust.Shared.Utility;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.Map.Components;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Physics.Systems;
|
|
using Robust.Shared.Timing;
|
|
|
|
namespace Robust.Shared.GameObjects
|
|
{
|
|
public abstract partial class SharedTransformSystem : EntitySystem
|
|
{
|
|
[Dependency] private IGameTiming _gameTiming = default!;
|
|
[Dependency] private EntityLookupSystem _lookup = default!;
|
|
[Dependency] private SharedMapSystem _map = default!;
|
|
[Dependency] private MetaDataSystem _metaData = default!;
|
|
[Dependency] private SharedPhysicsSystem _physics = default!;
|
|
[Dependency] private INetManager _netMan = default!;
|
|
[Dependency] private SharedContainerSystem _container = default!;
|
|
[Dependency] private SharedGridTraversalSystem _traversal = default!;
|
|
|
|
private EntityQuery<MapComponent> _mapQuery;
|
|
private EntityQuery<MapGridComponent> _gridQuery;
|
|
private EntityQuery<MetaDataComponent> _metaQuery;
|
|
protected EntityQuery<TransformComponent> XformQuery;
|
|
|
|
public delegate void MoveEventHandler(ref MoveEvent ev);
|
|
|
|
/// <summary>
|
|
/// Invoked as an alternative to broadcasting move events, which can be expensive.
|
|
/// Systems which want to subscribe broadcast to <see cref="MoveEvent"/> (which you probably shouldn't)
|
|
/// should subscribe to this instead
|
|
/// </summary>
|
|
public event MoveEventHandler? OnGlobalMoveEvent;
|
|
|
|
/// <summary>
|
|
/// Internal move event handlers. This gets invoked before the global & directed move events. This is mainly
|
|
/// for exception tolerance, we want to ensure that PVS, physics & entity lookups get updated before some
|
|
/// content code throws an exception.
|
|
/// </summary>
|
|
internal event MoveEventHandler? OnBeforeMoveEvent;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
UpdatesOutsidePrediction = true;
|
|
|
|
_mapQuery = GetEntityQuery<MapComponent>();
|
|
_gridQuery = GetEntityQuery<MapGridComponent>();
|
|
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
|
XformQuery = GetEntityQuery<TransformComponent>();
|
|
|
|
SubscribeLocalEvent<TileChangedEvent>(MapManagerOnTileChanged);
|
|
SubscribeLocalEvent<TransformComponent, ComponentInit>(OnCompInit);
|
|
SubscribeLocalEvent<TransformComponent, ComponentStartup>(OnCompStartup);
|
|
SubscribeLocalEvent<TransformComponent, ComponentGetState>(OnGetState);
|
|
SubscribeLocalEvent<TransformComponent, ComponentHandleState>(OnHandleState);
|
|
SubscribeLocalEvent<TransformComponent, GridAddEvent>(OnGridAdd);
|
|
}
|
|
|
|
private void MapManagerOnTileChanged(ref TileChangedEvent e)
|
|
{
|
|
foreach (var change in e.Changes)
|
|
{
|
|
if(change.NewTile != Tile.Empty)
|
|
continue;
|
|
|
|
// TODO optimize this for when multiple tiles get empties simultaneously (e.g., explosions).
|
|
DeparentAllEntsOnTile(e.Entity, change.GridIndices);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// De-parents and unanchors all entities on a grid-tile.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
private void DeparentAllEntsOnTile(EntityUid gridId, Vector2i tileIndices)
|
|
{
|
|
if (!TryComp(gridId, out BroadphaseComponent? lookup) || !TryComp<MapGridComponent>(gridId, out var grid))
|
|
return;
|
|
|
|
if (!XformQuery.TryGetComponent(gridId, out var gridXform))
|
|
return;
|
|
|
|
if (!XformQuery.TryGetComponent(gridXform.MapUid, out var mapTransform))
|
|
return;
|
|
|
|
var aabb = _lookup.GetLocalBounds(tileIndices, grid.TileSize);
|
|
|
|
foreach (var entity in _lookup.GetLocalEntitiesIntersecting(lookup, aabb, LookupFlags.Uncontained | LookupFlags.Approximate))
|
|
{
|
|
if (!XformQuery.TryGetComponent(entity, out var xform) || xform.ParentUid != gridId)
|
|
continue;
|
|
|
|
if (!aabb.Contains(xform.LocalPosition))
|
|
continue;
|
|
|
|
// 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))
|
|
DetachEntity(entity, xform, MetaData(entity), gridXform);
|
|
else
|
|
SetParent(entity, xform, gridXform.MapUid.Value, mapTransform);
|
|
}
|
|
}
|
|
|
|
public EntityCoordinates GetMoverCoordinates(EntityUid uid)
|
|
{
|
|
return GetMoverCoordinates(uid, XformQuery.GetComponent(uid));
|
|
}
|
|
|
|
public EntityCoordinates GetMoverCoordinates(EntityUid uid, TransformComponent xform)
|
|
{
|
|
// Nullspace (or map)
|
|
if (!xform.ParentUid.IsValid())
|
|
return xform.Coordinates;
|
|
|
|
// GriddUid is only set after init.
|
|
if (!xform._gridInitialized)
|
|
InitializeGridUid(uid, xform);
|
|
|
|
// Is the entity directly parented to the grid?
|
|
if (xform.GridUid == xform.ParentUid)
|
|
return xform.Coordinates;
|
|
|
|
DebugTools.Assert(!_gridQuery.HasComp(uid) && !_mapQuery.HasComp(uid));
|
|
|
|
// Not parented to grid so convert their pos back to the grid.
|
|
var worldPos = GetWorldPosition(xform, XformQuery);
|
|
|
|
return xform.GridUid == null
|
|
? new EntityCoordinates(xform.MapUid ?? xform.ParentUid, worldPos)
|
|
: new EntityCoordinates(xform.GridUid.Value, Vector2.Transform(worldPos, XformQuery.GetComponent(xform.GridUid.Value).InvLocalMatrix));
|
|
}
|
|
|
|
public EntityCoordinates GetMoverCoordinates(EntityCoordinates coordinates, EntityQuery<TransformComponent> xformQuery)
|
|
{
|
|
return GetMoverCoordinates(coordinates);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Variant of <see cref="GetMoverCoordinates"/> that uses a entity coordinates, rather than an entity's transform.
|
|
/// </summary>
|
|
public EntityCoordinates GetMoverCoordinates(EntityCoordinates coordinates)
|
|
{
|
|
var parentUid = coordinates.EntityId;
|
|
|
|
// Nullspace coordinates?
|
|
if (!parentUid.IsValid())
|
|
return coordinates;
|
|
|
|
var parentXform = XformQuery.GetComponent(parentUid);
|
|
|
|
// GriddUid is only set after init.
|
|
if (!parentXform._gridInitialized)
|
|
InitializeGridUid(parentUid, parentXform);
|
|
|
|
// Is the entity directly parented to the grid?
|
|
if (parentXform.GridUid == parentUid)
|
|
return coordinates;
|
|
|
|
// Is the entity directly parented to the map?
|
|
var mapId = parentXform.MapUid;
|
|
if (mapId == parentUid)
|
|
return coordinates;
|
|
|
|
DebugTools.Assert(!HasComp<MapGridComponent>(parentUid) && !HasComp<MapComponent>(parentUid));
|
|
|
|
// Not parented to grid so convert their pos back to the grid.
|
|
var worldPos = Vector2.Transform(coordinates.Position, GetWorldMatrix(parentXform, XformQuery));
|
|
|
|
return parentXform.GridUid == null
|
|
? new EntityCoordinates(mapId ?? parentUid, worldPos)
|
|
: new EntityCoordinates(parentXform.GridUid.Value, Vector2.Transform(worldPos, XformQuery.GetComponent(parentXform.GridUid.Value).InvLocalMatrix));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Variant of <see cref="GetMoverCoordinates()"/> that also returns the entity's world rotation
|
|
/// </summary>
|
|
public (EntityCoordinates Coords, Angle worldRot) GetMoverCoordinateRotation(EntityUid uid, TransformComponent xform)
|
|
{
|
|
// Nullspace (or map)
|
|
if (!xform.ParentUid.IsValid())
|
|
return (xform.Coordinates, xform.LocalRotation);
|
|
|
|
// GriddUid is only set after init.
|
|
if (!xform._gridInitialized)
|
|
InitializeGridUid(uid, xform);
|
|
|
|
// Is the entity directly parented to the grid?
|
|
if (xform.GridUid == xform.ParentUid)
|
|
return (xform.Coordinates, GetWorldRotation(xform, XformQuery));
|
|
|
|
DebugTools.Assert(!HasComp<MapComponent>(uid) && !HasComp<MapComponent>(uid));
|
|
|
|
var (pos, worldRot) = GetWorldPositionRotation(xform, XformQuery);
|
|
|
|
var coords = xform.GridUid == null
|
|
? new EntityCoordinates(xform.MapUid ?? xform.ParentUid, pos)
|
|
: new EntityCoordinates(xform.GridUid.Value, Vector2.Transform(pos, XformQuery.GetComponent(xform.GridUid.Value).InvLocalMatrix));
|
|
|
|
return (coords, worldRot);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method that returns the grid or map tile an entity is on.
|
|
/// </summary>
|
|
public Vector2i GetGridOrMapTilePosition(EntityUid uid, TransformComponent? xform = null)
|
|
{
|
|
if(!Resolve(uid, ref xform, false))
|
|
return Vector2i.Zero;
|
|
|
|
// Fast path, we're not on a grid.
|
|
if (xform.GridUid == null)
|
|
return GetWorldPosition(xform).Floored();
|
|
|
|
// We're on a grid, need to convert the coordinates to grid tiles.
|
|
return _map.CoordinatesToTile(xform.GridUid.Value, Comp<MapGridComponent>(xform.GridUid.Value), xform.Coordinates);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method that returns the grid tile an entity is on.
|
|
/// </summary>
|
|
public Vector2i GetGridTilePositionOrDefault(Entity<TransformComponent?> entity, MapGridComponent? grid = null)
|
|
{
|
|
var xform = entity.Comp;
|
|
if(!Resolve(entity.Owner, ref xform) || xform.GridUid == null)
|
|
return Vector2i.Zero;
|
|
|
|
if (!Resolve(xform.GridUid.Value, ref grid))
|
|
return Vector2i.Zero;
|
|
|
|
return _map.CoordinatesToTile(xform.GridUid.Value, grid, xform.Coordinates);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method that returns the grid tile an entity is on.
|
|
/// </summary>
|
|
public bool TryGetGridTilePosition(Entity<TransformComponent?> entity, out Vector2i indices, MapGridComponent? grid = null)
|
|
{
|
|
indices = default;
|
|
var xform = entity.Comp;
|
|
if(!Resolve(entity.Owner, ref xform) || xform.GridUid == null)
|
|
return false;
|
|
|
|
if (!Resolve(xform.GridUid.Value, ref grid))
|
|
return false;
|
|
|
|
indices = _map.CoordinatesToTile(xform.GridUid.Value, grid, xform.Coordinates);
|
|
return true;
|
|
}
|
|
|
|
internal void RaiseMoveEvent(
|
|
Entity<TransformComponent, MetaDataComponent> ent,
|
|
EntityUid oldParent,
|
|
Vector2 oldPosition,
|
|
Angle oldRotation,
|
|
EntityUid? oldMap,
|
|
bool checkTraversal = true)
|
|
{
|
|
var pos = ent.Comp1._parent == EntityUid.Invalid
|
|
? default
|
|
: new EntityCoordinates(ent.Comp1._parent, ent.Comp1._localPosition);
|
|
|
|
var oldPos = oldParent == EntityUid.Invalid
|
|
? default
|
|
: new EntityCoordinates(oldParent, oldPosition);
|
|
|
|
var ev = new MoveEvent(ent, oldPos, pos, oldRotation, ent.Comp1._localRotation);
|
|
|
|
if (oldParent != ent.Comp1._parent)
|
|
{
|
|
_physics.OnParentChange(ent, oldParent, oldMap);
|
|
OnBeforeMoveEvent?.Invoke(ref ev);
|
|
var entParentChangedMessage = new EntParentChangedMessage(ev.Sender, oldParent, oldMap, ev.Component);
|
|
RaiseLocalEvent(ev.Sender, ref entParentChangedMessage, true);
|
|
}
|
|
else
|
|
{
|
|
OnBeforeMoveEvent?.Invoke(ref ev);
|
|
}
|
|
|
|
RaiseLocalEvent(ev.Sender, ref ev);
|
|
OnGlobalMoveEvent?.Invoke(ref ev);
|
|
|
|
// Finally, handle grid traversal. This is handled separately to avoid out-of-order move events.
|
|
// I.e., if the traversal raises its own move event, this ensures that all the old move event handlers
|
|
// have finished running first. Ideally this shouldn't be required, but this is here just in case
|
|
if (checkTraversal)
|
|
{
|
|
_traversal.CheckTraverse(ent);
|
|
}
|
|
}
|
|
}
|
|
|
|
[ByRefEvent]
|
|
public readonly struct TransformStartupEvent(Entity<TransformComponent> entity)
|
|
{
|
|
public readonly Entity<TransformComponent> Entity = entity;
|
|
public TransformComponent Component => Entity.Comp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialized state of a TransformComponent.
|
|
/// </summary>
|
|
[Serializable, NetSerializable]
|
|
internal readonly record struct TransformComponentState : IComponentState
|
|
{
|
|
/// <summary>
|
|
/// Current parent entity of this entity.
|
|
/// </summary>
|
|
public readonly NetEntity ParentID;
|
|
// TODO Delta-states
|
|
// If the transform component ever gets delta states, then the client state manager needs to be updated.
|
|
// Currently it explicitly looks for a "TransformComponentState" when determining an entity's parent for the
|
|
// sake of sorting the states that need to be applied base on the transform hierarchy.
|
|
|
|
/// <summary>
|
|
/// Current position offset of the entity.
|
|
/// </summary>
|
|
public readonly Vector2 LocalPosition;
|
|
|
|
/// <summary>
|
|
/// Current rotation offset of the entity.
|
|
/// </summary>
|
|
public readonly Angle Rotation;
|
|
|
|
/// <summary>
|
|
/// Is the transform able to be locally rotated?
|
|
/// </summary>
|
|
public readonly bool NoLocalRotation;
|
|
|
|
/// <summary>
|
|
/// True if the transform is anchored to a tile.
|
|
/// </summary>
|
|
public readonly bool Anchored;
|
|
|
|
/// <summary>
|
|
/// Constructs a new state snapshot of a TransformComponent.
|
|
/// </summary>
|
|
/// <param name="localPosition">Current position offset of this entity.</param>
|
|
/// <param name="rotation">Current direction offset of this entity.</param>
|
|
/// <param name="parentId">Current parent transform of this entity.</param>
|
|
/// <param name="noLocalRotation"></param>
|
|
public TransformComponentState(Vector2 localPosition, Angle rotation, NetEntity parentId, bool noLocalRotation, bool anchored)
|
|
{
|
|
LocalPosition = localPosition;
|
|
Rotation = rotation;
|
|
ParentID = parentId;
|
|
NoLocalRotation = noLocalRotation;
|
|
Anchored = anchored;
|
|
}
|
|
}
|
|
}
|