Start MapGrid ECS (#4185)

This commit is contained in:
metalgearsloth
2023-07-23 20:50:23 +10:00
committed by GitHub
parent c52db4d3f2
commit e4a14d1ec8
32 changed files with 1444 additions and 1183 deletions

View File

@@ -621,6 +621,7 @@ namespace Robust.Client.Console.Commands
internal sealed class ChunkInfoCommand : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IEyeManager _eye = default!;
[Dependency] private readonly IInputManager _input = default!;
@@ -631,16 +632,17 @@ namespace Robust.Client.Console.Commands
{
var mousePos = _eye.ScreenToMap(_input.MouseScreenPosition);
if (!_map.TryFindGridAt(mousePos, out _, out var grid))
if (!_map.TryFindGridAt(mousePos, out var gridUid, out var grid))
{
shell.WriteLine("No grid under your mouse cursor.");
return;
}
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
var chunk = grid.GetOrAddChunk(chunkIndex);
var mapSystem = _entManager.System<SharedMapSystem>();
var chunkIndex = mapSystem.LocalToChunkIndices(gridUid, grid, grid.MapToGrid(mousePos));
var chunk = mapSystem.GetOrAddChunk(gridUid, grid, chunkIndex);
shell.WriteLine($"worldBounds: {grid.CalcWorldAABB(chunk)} localBounds: {chunk.CachedBounds}");
shell.WriteLine($"worldBounds: {mapSystem.CalcWorldAABB(gridUid, grid, chunk)} localBounds: {chunk.CachedBounds}");
}
}

View File

@@ -38,7 +38,8 @@ public sealed class DebugEntityLookupSystem : EntitySystem
IoCManager.Resolve<IOverlayManager>().AddOverlay(
new EntityLookupOverlay(
EntityManager,
Get<EntityLookupSystem>()));
EntityManager.System<EntityLookupSystem>(),
EntityManager.System<SharedTransformSystem>()));
}
else
{
@@ -52,31 +53,35 @@ public sealed class DebugEntityLookupSystem : EntitySystem
public sealed class EntityLookupOverlay : Overlay
{
private IEntityManager _entityManager = default!;
private EntityLookupSystem _lookup = default!;
private readonly IEntityManager _entityManager;
private readonly EntityLookupSystem _lookup;
private readonly SharedTransformSystem _transform;
private EntityQuery<TransformComponent> _xformQuery;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityLookupOverlay(IEntityManager entManager, EntityLookupSystem lookup)
public EntityLookupOverlay(IEntityManager entManager, EntityLookupSystem lookup, SharedTransformSystem transform)
{
_entityManager = entManager;
_lookup = lookup;
_xformQuery = entManager.GetEntityQuery<TransformComponent>();
_transform = transform;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var worldHandle = args.WorldHandle;
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
var worldBounds = args.WorldBounds;
foreach (var lookup in _lookup.FindLookupsIntersecting(args.MapId, args.WorldBounds))
// TODO: Static version
_lookup.FindLookupsIntersecting(args.MapId, worldBounds, (uid, lookup) =>
{
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var (_, rotation, matrix, invMatrix) = lookupXform.GetWorldPositionRotationMatrixWithInv();
var (_, rotation, matrix, invMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(uid);
worldHandle.SetTransform(matrix);
var lookupAABB = invMatrix.TransformBox(args.WorldBounds);
var lookupAABB = invMatrix.TransformBox(worldBounds);
var ents = new List<EntityUid>();
lookup.DynamicTree.QueryAabb(ref ents, static (ref List<EntityUid> state, in FixtureProxy value) =>
@@ -105,20 +110,22 @@ public sealed class EntityLookupOverlay : Overlay
foreach (var ent in ents)
{
if (_entityManager.Deleted(ent)) continue;
var xform = xformQuery.GetComponent(ent);
if (_entityManager.Deleted(ent))
continue;
var xform = _xformQuery.GetComponent(ent);
//DebugTools.Assert(!ent.IsInContainer(_entityManager));
var (entPos, entRot) = xform.GetWorldPositionRotation();
var (entPos, entRot) = _transform.GetWorldPositionRotation(ent);
var lookupPos = invMatrix.Transform(entPos);
var lookupRot = entRot - rotation;
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, xformQuery);
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, _xformQuery);
worldHandle.DrawRect(aabb, Color.Blue.WithAlpha(0.2f));
}
}
});
worldHandle.SetTransform(Matrix3.Identity);
}

View File

@@ -23,7 +23,7 @@ namespace Robust.Client.Physics
{
if (_enableDebug == value) return;
Sawmill.Info($"Set grid fixture debug to {value}");
Log.Info($"Set grid fixture debug to {value}");
_enableDebug = value;
if (_enableDebug)
@@ -59,7 +59,7 @@ namespace Robust.Client.Physics
private void OnDebugMessage(ChunkSplitDebugMessage ev)
{
Sawmill.Info($"Received grid fixture debug data");
Log.Info($"Received grid fixture debug data");
if (!_enableDebug) return;
_nodes[ev.Grid] = ev.Nodes;

View File

@@ -62,18 +62,19 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
var screenSize = _displayManager.ScreenSize;
var screenScale = _displayManager.MainWindow.ContentScale;
MapCoordinates mouseWorldMap;
EntityCoordinates mouseGridPos;
TileRef tile;
mouseWorldMap = _eyeManager.ScreenToMap(mouseScreenPos);
var mouseWorldMap = _eyeManager.ScreenToMap(mouseScreenPos);
if (mouseWorldMap == MapCoordinates.Nullspace)
return;
if (_mapManager.TryFindGridAt(mouseWorldMap, out _, out var mouseGrid))
var mapSystem = _entityManager.System<SharedMapSystem>();
if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid))
{
mouseGridPos = mouseGrid.MapToGrid(mouseWorldMap);
tile = mouseGrid.GetTileRef(mouseGridPos);
mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap);
tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos);
}
else
{

View File

@@ -44,6 +44,7 @@ public sealed class MapLoaderSystem : EntitySystem
private IServerEntityManagerInternal _serverEntityManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private ISawmill _logLoader = default!;
@@ -764,11 +765,12 @@ public sealed class MapLoaderSystem : EntitySystem
SequenceDataNode yamlGridChunks = (SequenceDataNode)yamlGrid["chunks"];
var grid = AllocateMapGrid(gridComp, yamlGridInfo);
var gridUid = grid.Owner;
foreach (var chunkNode in yamlGridChunks.Cast<MappingDataNode>())
{
var (chunkOffsetX, chunkOffsetY) = _serManager.Read<Vector2i>(chunkNode["ind"]);
_serManager.Read(chunkNode, _context, instanceProvider: () => grid.GetOrAddChunk(chunkOffsetX, chunkOffsetY), notNullableOverride: true);
_serManager.Read(chunkNode, _context, instanceProvider: () => _mapSystem.GetOrAddChunk(gridUid, grid, chunkOffsetX, chunkOffsetY), notNullableOverride: true);
}
}
}

View File

@@ -107,15 +107,12 @@ internal sealed partial class PvsSystem : EntitySystem
private EntityQuery<EyeComponent> _eyeQuery;
private EntityQuery<TransformComponent> _xformQuery;
private ISawmill _sawmill = default!;
public override void Initialize()
{
base.Initialize();
_eyeQuery = GetEntityQuery<EyeComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_sawmill = Logger.GetSawmill("PVS");
_entityPvsCollection = RegisterPVSCollection<EntityUid>();
@@ -182,7 +179,7 @@ internal sealed partial class PvsSystem : EntitySystem
sb.Append($" Entity last sent: {lastSeenTick.Value}");
}
_sawmill.Warning(sb.ToString());
Log.Warning(sb.ToString());
sessionData.LastSeenAt.Clear();
@@ -231,7 +228,7 @@ internal sealed partial class PvsSystem : EntitySystem
private PVSCollection<TIndex> RegisterPVSCollection<TIndex>() where TIndex : IComparable<TIndex>, IEquatable<TIndex>
{
var collection = new PVSCollection<TIndex>(_sawmill, EntityManager, _transform);
var collection = new PVSCollection<TIndex>(Log, EntityManager, _transform);
_pvsCollections.Add(collection);
return collection;
}
@@ -334,12 +331,12 @@ internal sealed partial class PvsSystem : EntitySystem
if (e.NewStatus == SessionStatus.InGame)
{
if (!PlayerData.TryAdd(e.Session, new()))
_sawmill.Error($"Attempted to add player to _playerVisibleSets, but they were already present? Session:{e.Session}");
Log.Error($"Attempted to add player to _playerVisibleSets, but they were already present? Session:{e.Session}");
foreach (var pvsCollection in _pvsCollections)
{
if (!pvsCollection.AddPlayer(e.Session))
_sawmill.Error($"Attempted to add player to pvsCollection, but they were already present? Session:{e.Session}");
Log.Error($"Attempted to add player to pvsCollection, but they were already present? Session:{e.Session}");
}
return;
}
@@ -353,7 +350,7 @@ internal sealed partial class PvsSystem : EntitySystem
foreach (var pvsCollection in _pvsCollections)
{
if (!pvsCollection.RemovePlayer(e.Session))
_sawmill.Error($"Attempted to remove player from pvsCollection, but they were already removed? Session:{e.Session}");
Log.Error($"Attempted to remove player from pvsCollection, but they were already removed? Session:{e.Session}");
}
if (data.Overflow != null)
@@ -822,7 +819,7 @@ internal sealed partial class PvsSystem : EntitySystem
#if DEBUG
// This happens relatively frequently for the current TickBuffer value, and doesn't really provide any
// useful info when not debugging/testing locally. Hence only enable on DEBUG.
_sawmill.Debug($"Client {session} exceeded tick buffer.");
Log.Debug($"Client {session} exceeded tick buffer.");
#endif
}
else if (oldEntry.Value.Value != lastAcked)
@@ -980,7 +977,7 @@ internal sealed partial class PvsSystem : EntitySystem
if (metaDataComponent.EntityLifeStage >= EntityLifeStage.Terminating)
{
var rep = new EntityStringRepresentation(uid, metaDataComponent.EntityDeleted, metaDataComponent.EntityName, metaDataComponent.EntityPrototype?.ID);
_sawmill.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Trace:\n{Environment.StackTrace}");
Log.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Trace:\n{Environment.StackTrace}");
return;
}
@@ -1110,7 +1107,7 @@ internal sealed partial class PvsSystem : EntitySystem
if (component.Deleted || !component.Initialized)
{
_sawmill.Error("Entity manager returned deleted or uninitialized components while sending entity data");
Log.Error("Entity manager returned deleted or uninitialized components while sending entity data");
continue;
}

View File

@@ -77,7 +77,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
id = tileDefinitionManager[defName].TileId;
var tile = new Tile(id, flags, variant);
chunk.SetTile(x, y, tile);
chunk.TrySetTile(x, y, tile, out _, out _);
}
}
@@ -143,7 +143,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
{
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
chunk.SetTile(x, y, source.GetTile(x, y));
chunk.TrySetTile(x, y, source.GetTile(x, y), out _, out _);
}
}

View File

@@ -15,8 +15,16 @@ internal sealed class RecursiveMoveSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
private EntityQuery<TransformComponent> _xformQuery;
bool Subscribed = false;
public override void Initialize()
{
base.Initialize();
_xformQuery = GetEntityQuery<TransformComponent>();
}
internal void AddSubscription()
{
if (Subscribed)
@@ -34,14 +42,12 @@ internal sealed class RecursiveMoveSystem : EntitySystem
DebugTools.Assert(!_mapManager.IsMap(args.Sender));
DebugTools.Assert(!_mapManager.IsGrid(args.Sender));
var xformQuery = GetEntityQuery<TransformComponent>();
AnythingMovedSubHandler(args.Sender, args.Component, xformQuery);
AnythingMovedSubHandler(args.Sender, args.Component);
}
private void AnythingMovedSubHandler(
EntityUid uid,
TransformComponent xform,
EntityQuery<TransformComponent> xformQuery)
TransformComponent xform)
{
// TODO maybe use a c# event? This event gets raised a lot.
// Would probably help with server performance and is also the main bottleneck for replay scrubbing.
@@ -55,8 +61,8 @@ internal sealed class RecursiveMoveSystem : EntitySystem
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
if (xformQuery.TryGetComponent(child.Value, out var childXform))
AnythingMovedSubHandler(child.Value, childXform, xformQuery);
if (_xformQuery.TryGetComponent(child.Value, out var childXform))
AnythingMovedSubHandler(child.Value, childXform);
}
}
}

View File

@@ -54,7 +54,7 @@ internal sealed class TeleportCommand : LocalizedCommands
if (_map.TryFindGridAt(mapId, position, out var gridUid, out var grid))
{
var gridPos = grid.WorldToLocal(position);
var gridPos = xformSystem.GetInvWorldMatrix(gridUid).Transform(position);
xformSystem.SetCoordinates(entity, transform, new EntityCoordinates(gridUid, gridPos));
}

View File

@@ -49,8 +49,6 @@ namespace Robust.Shared.GameObjects
private UniqueIndexHkm<EntityUid, Component> _entCompIndex =
new(ComponentCollectionCapacity);
private EntityQuery<MetaDataComponent> _metaQuery;
/// <inheritdoc />
public event Action<AddedComponentEventArgs>? ComponentAdded;
@@ -68,7 +66,6 @@ namespace Robust.Shared.GameObjects
FillComponentDict();
_componentFactory.ComponentAdded += OnComponentAdded;
_componentFactory.ComponentReferenceAdded += OnComponentReferenceAdded;
_metaQuery = GetEntityQuery<MetaDataComponent>();
}
/// <summary>

View File

@@ -18,6 +18,8 @@ namespace Robust.Shared.GameObjects
{
public delegate void EntityUidQueryCallback(EntityUid uid);
public delegate void ComponentQueryCallback<T>(EntityUid uid, T component) where T : Component;
/// <inheritdoc />
[Virtual]
public partial class EntityManager : IEntityManager
@@ -36,6 +38,9 @@ namespace Robust.Shared.GameObjects
// positions on spawn....
private SharedTransformSystem _xforms = default!;
private EntityQuery<MetaDataComponent> _metaQuery;
private EntityQuery<TransformComponent> _xformQuery;
#endregion Dependencies
/// <inheritdoc />
@@ -76,6 +81,8 @@ namespace Robust.Shared.GameObjects
private string _xformName = string.Empty;
private SharedMapSystem _mapSystem = default!;
private ISawmill _sawmill = default!;
private ISawmill _resolveSawmill = default!;
@@ -219,7 +226,10 @@ namespace Robust.Shared.GameObjects
_entitySystemManager.Initialize();
Started = true;
_eventBus.CalcOrdering();
_xforms = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
_mapSystem = System<SharedMapSystem>();
_xforms = System<SharedTransformSystem>();
_metaQuery = GetEntityQuery<MetaDataComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
}
public virtual void Shutdown()
@@ -306,7 +316,7 @@ namespace Robust.Shared.GameObjects
if (coordinates.IsValid(this))
{
_xforms.SetCoordinates(newEntity, GetComponent<TransformComponent>(newEntity), coordinates, unanchor: false);
_xforms.SetCoordinates(newEntity, _xformQuery.GetComponent(newEntity), coordinates, unanchor: false);
}
return newEntity;
@@ -316,7 +326,7 @@ namespace Robust.Shared.GameObjects
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
{
var newEntity = CreateEntity(prototypeName, default, overrides);
var transform = GetComponent<TransformComponent>(newEntity);
var transform = _xformQuery.GetComponent(newEntity);
if (coordinates.MapId == MapId.Nullspace)
{
@@ -333,7 +343,7 @@ namespace Robust.Shared.GameObjects
EntityCoordinates coords;
if (transform.Anchored && _mapManager.TryFindGridAt(coordinates, out var gridUid, out var grid))
{
coords = new EntityCoordinates(gridUid, grid.WorldToLocal(coordinates.Position));
coords = new EntityCoordinates(gridUid, _mapSystem.WorldToLocal(gridUid, grid, coordinates.Position));
_xforms.SetCoordinates(newEntity, transform, coords, unanchor: false);
}
else
@@ -743,7 +753,7 @@ namespace Robust.Shared.GameObjects
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context)
{
EntityPrototype.LoadEntity(GetComponent<MetaDataComponent>(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context);
EntityPrototype.LoadEntity(_metaQuery.GetComponent(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context);
}
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context, EntityPrototype? prototype)
@@ -755,12 +765,12 @@ namespace Robust.Shared.GameObjects
{
try
{
var meta = GetComponent<MetaDataComponent>(entity);
var meta = _metaQuery.GetComponent(entity);
InitializeEntity(entity, meta);
StartEntity(entity);
// If the map we're initializing the entity on is initialized, run map init on it.
if (_mapManager.IsMapInitialized(mapId ?? GetComponent<TransformComponent>(entity).MapID))
if (_mapManager.IsMapInitialized(mapId ?? _xformQuery.GetComponent(entity).MapID))
RunMapInit(entity, meta);
}
catch (Exception e)

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Dynamics;
@@ -26,12 +26,10 @@ public sealed partial class EntityLookupSystem
EntityUid lookupUid,
HashSet<EntityUid> intersecting,
Box2 worldAABB,
LookupFlags flags,
EntityQuery<BroadphaseComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery)
LookupFlags flags)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var invMatrix = _transform.GetInvWorldMatrix(lookupUid, xformQuery);
var lookup = _broadQuery.GetComponent(lookupUid);
var invMatrix = _transform.GetInvWorldMatrix(lookupUid);
var localAABB = invMatrix.TransformBox(worldAABB);
if ((flags & LookupFlags.Dynamic) != 0x0)
@@ -79,12 +77,10 @@ public sealed partial class EntityLookupSystem
EntityUid lookupUid,
HashSet<EntityUid> intersecting,
Box2Rotated worldBounds,
LookupFlags flags,
EntityQuery<BroadphaseComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery)
LookupFlags flags)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var invMatrix = _transform.GetInvWorldMatrix(lookupUid, xformQuery);
var lookup = _broadQuery.GetComponent(lookupUid);
var invMatrix = _transform.GetInvWorldMatrix(lookupUid);
// We don't just use CalcBoundingBox because the transformed bounds might be tighter.
var localAABB = invMatrix.TransformBox(worldBounds);
@@ -132,12 +128,10 @@ public sealed partial class EntityLookupSystem
private bool AnyEntitiesIntersecting(EntityUid lookupUid,
Box2 worldAABB,
LookupFlags flags,
EntityQuery<BroadphaseComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery,
EntityUid? ignored = null)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldAABB);
var lookup = _broadQuery.GetComponent(lookupUid);
var localAABB = _transform.GetInvWorldMatrix(lookupUid).TransformBox(worldAABB);
var state = (ignored, found: false);
if ((flags & LookupFlags.Dynamic) != 0x0)
@@ -197,12 +191,10 @@ public sealed partial class EntityLookupSystem
private bool AnyEntitiesIntersecting(EntityUid lookupUid,
Box2Rotated worldBounds,
LookupFlags flags,
EntityQuery<BroadphaseComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery,
EntityUid? ignored = null)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldBounds);
var lookup = _broadQuery.GetComponent(lookupUid);
var localAABB = _transform.GetInvWorldMatrix(lookupUid).TransformBox(worldBounds);
var state = (ignored, found: false);
if ((flags & LookupFlags.Dynamic) != 0x0)
@@ -259,35 +251,35 @@ public sealed partial class EntityLookupSystem
return state.found;
}
private void RecursiveAdd(EntityUid uid, ref ValueList<EntityUid> toAdd, EntityQuery<TransformComponent> xformQuery)
private void RecursiveAdd(EntityUid uid, ref ValueList<EntityUid> toAdd)
{
var childEnumerator = xformQuery.GetComponent(uid).ChildEnumerator;
var childEnumerator = _xformQuery.GetComponent(uid).ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
toAdd.Add(child.Value);
RecursiveAdd(child.Value, ref toAdd, xformQuery);
RecursiveAdd(child.Value, ref toAdd);
}
}
private void AddContained(HashSet<EntityUid> intersecting, LookupFlags flags, EntityQuery<TransformComponent> xformQuery)
private void AddContained(HashSet<EntityUid> intersecting, LookupFlags flags)
{
if ((flags & LookupFlags.Contained) == 0x0 || intersecting.Count == 0)
return;
var conQuery = GetEntityQuery<ContainerManagerComponent>();
var toAdd = new ValueList<EntityUid>();
foreach (var uid in intersecting)
{
if (!conQuery.TryGetComponent(uid, out var conManager)) continue;
if (!_containerQuery.TryGetComponent(uid, out var conManager))
continue;
foreach (var con in conManager.GetAllContainers())
{
foreach (var contained in con.ContainedEntities)
{
toAdd.Add(contained);
RecursiveAdd(contained, ref toAdd, xformQuery);
RecursiveAdd(contained, ref toAdd);
}
}
}
@@ -321,11 +313,9 @@ public sealed partial class EntityLookupSystem
float arcWidth,
LookupFlags flags = DefaultFlags)
{
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var entity in GetEntitiesInRange(coordinates, range * 2, flags))
{
var angle = new Angle(_transform.GetWorldPosition(entity, xformQuery) - coordinates.Position);
var angle = new Angle(_transform.GetWorldPosition(entity) - coordinates.Position);
if (angle.Degrees < direction.Degrees + arcWidth / 2 &&
angle.Degrees > direction.Degrees - arcWidth / 2)
yield return entity;
@@ -340,48 +330,63 @@ public sealed partial class EntityLookupSystem
{
if (mapId == MapId.Nullspace) return false;
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
// Don't need to check contained entities as they have the same bounds as the parent.
var found = false;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
if (AnyEntitiesIntersecting(grid.Owner, worldAABB, flags, lookupQuery, xformQuery)) return true;
}
var state = (this, worldAABB, flags, found);
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
static (EntityUid uid, MapGridComponent _, ref (EntityLookupSystem lookup, Box2 worldAABB, LookupFlags flags, bool found) tuple) =>
{
if (!tuple.lookup.AnyEntitiesIntersecting(uid, tuple.worldAABB, tuple.flags))
return true;
tuple.found = true;
return false;
});
if (state.found)
return true;
var mapUid = _mapManager.GetMapEntityId(mapId);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags);
}
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
{
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
// Get grid entities
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
AddEntitiesIntersecting(grid.Owner, intersecting, worldAABB, flags, lookupQuery, xformQuery);
var state = (this, _map, intersecting, worldAABB, flags);
if ((flags & LookupFlags.Static) != 0x0)
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
static (EntityUid gridUid, MapGridComponent grid, ref (
EntityLookupSystem lookup, SharedMapSystem _map, HashSet<EntityUid> intersecting,
Box2 worldAABB, LookupFlags flags) tuple) =>
{
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
tuple.lookup.AddEntitiesIntersecting(gridUid, tuple.intersecting, tuple.worldAABB, tuple.flags);
if ((tuple.flags & LookupFlags.Static) != 0x0)
{
if (Deleted(uid)) continue;
intersecting.Add(uid);
// TODO: Need a struct enumerator version.
foreach (var uid in tuple._map.GetAnchoredEntities(gridUid, grid, tuple.worldAABB))
{
if (tuple.lookup.Deleted(uid))
continue;
tuple.intersecting.Add(uid);
}
}
}
}
return true;
});
// Get map entities
var mapUid = _mapManager.GetMapEntityId(mapId);
AddEntitiesIntersecting(mapUid, intersecting, worldAABB, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
AddEntitiesIntersecting(mapUid, intersecting, worldAABB, flags);
AddContained(intersecting, flags);
return intersecting;
}
@@ -392,39 +397,55 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
// Don't need to check contained entities as they have the same bounds as the parent.
var worldAABB = worldBounds.CalcBoundingBox();
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox()))
{
if (AnyEntitiesIntersecting(grid.Owner, worldBounds, flags, lookupQuery, xformQuery)) return true;
}
const bool found = false;
var state = (this, worldBounds, flags, found);
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
static (EntityUid uid, MapGridComponent grid, ref (EntityLookupSystem lookup, Box2Rotated worldBounds, LookupFlags flags, bool found) tuple) =>
{
if (tuple.lookup.AnyEntitiesIntersecting(uid, tuple.worldBounds, tuple.flags))
{
tuple.found = true;
return false;
}
return true;
});
if (state.found)
return true;
var mapUid = _mapManager.GetMapEntityId(mapId);
return AnyEntitiesIntersecting(mapUid, worldBounds, flags, lookupQuery, xformQuery);
return AnyEntitiesIntersecting(mapUid, worldBounds, flags);
}
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
if (mapId == MapId.Nullspace)
return intersecting;
// Get grid entities
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox()))
var state = (this, intersecting, worldBounds, flags);
_mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox(), ref state, static
(EntityUid uid, MapGridComponent _,
ref (EntityLookupSystem lookup,
HashSet<EntityUid> intersecting,
Box2Rotated worldBounds,
LookupFlags flags) tuple) =>
{
AddEntitiesIntersecting(grid.Owner, intersecting, worldBounds, flags, lookupQuery, xformQuery);
}
tuple.lookup.AddEntitiesIntersecting(uid, tuple.intersecting, tuple.worldBounds, tuple.flags);
return true;
});
// Get map entities
var mapUid = _mapManager.GetMapEntityId(mapId);
AddEntitiesIntersecting(mapUid, intersecting, worldBounds, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
AddEntitiesIntersecting(mapUid, intersecting, worldBounds, flags);
AddContained(intersecting, flags);
return intersecting;
}
@@ -438,50 +459,72 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesIntersecting(EntityUid uid, LookupFlags flags = DefaultFlags)
{
var worldAABB = GetWorldAABB(uid);
var mapID = Transform(uid).MapID;
var mapID = _xformQuery.GetComponent(uid).MapID;
if (mapID == MapId.Nullspace) return false;
if (mapID == MapId.Nullspace)
return false;
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
const bool found = false;
var state = (this, worldAABB, flags, found, uid);
_mapManager.FindGridsIntersecting(mapID, worldAABB, ref state,
static (EntityUid gridUid, MapGridComponent grid,
ref (EntityLookupSystem lookup, Box2 worldAABB, LookupFlags flags, bool found, EntityUid ignored) tuple) =>
{
if (tuple.lookup.AnyEntitiesIntersecting(gridUid, tuple.worldAABB, tuple.flags, tuple.ignored))
{
tuple.found = true;
return false;
}
foreach (var grid in _mapManager.FindGridsIntersecting(mapID, worldAABB))
{
if (AnyEntitiesIntersecting(grid.Owner, worldAABB, flags, lookupQuery, xformQuery, uid))
return true;
}
});
var mapUid = _mapManager.GetMapEntityId(mapID);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery, uid);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, uid);
}
public bool AnyEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags)
{
var mapPos = Transform(uid).MapPosition;
var mapPos = _xformQuery.GetComponent(uid).MapPosition;
if (mapPos.MapId == MapId.Nullspace) return false;
if (mapPos.MapId == MapId.Nullspace)
return false;
var rangeVec = new Vector2(range, range);
var worldAABB = new Box2(mapPos.Position - rangeVec, mapPos.Position + rangeVec);
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var grid in _mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB))
const bool found = false;
var state = (this, worldAABB, flags, found, uid);
_mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB, ref state, static (
EntityUid gridUid,
MapGridComponent _, ref (
EntityLookupSystem lookup,
Box2 worldAABB,
LookupFlags flags,
bool found,
EntityUid ignored) tuple) =>
{
if (AnyEntitiesIntersecting(grid.Owner, worldAABB, flags, lookupQuery, xformQuery, uid))
return true;
}
if (tuple.lookup.AnyEntitiesIntersecting(gridUid, tuple.worldAABB, tuple.flags, tuple.ignored))
{
tuple.found = true;
return false;
}
return true;
});
var mapUid = _mapManager.GetMapEntityId(mapPos.MapId);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery, uid);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, uid);
}
public HashSet<EntityUid> GetEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags)
{
var mapPos = Transform(uid).MapPosition;
var mapPos = _xformQuery.GetComponent(uid).MapPosition;
if (mapPos.MapId == MapId.Nullspace) return new HashSet<EntityUid>();
if (mapPos.MapId == MapId.Nullspace)
return new HashSet<EntityUid>();
var intersecting = GetEntitiesInRange(mapPos, range, flags);
intersecting.Remove(uid);
@@ -490,12 +533,13 @@ public sealed partial class EntityLookupSystem
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid uid, LookupFlags flags = DefaultFlags)
{
var xform = Transform(uid);
var xform = _xformQuery.GetComponent(uid);
var mapId = xform.MapID;
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
if (mapId == MapId.Nullspace)
return new HashSet<EntityUid>();
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
var bounds = GetAABBNoContainer(uid, worldPos, worldRot);
var intersecting = GetEntitiesIntersecting(mapId, bounds, flags);
@@ -509,7 +553,8 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesIntersecting(EntityCoordinates coordinates, LookupFlags flags = DefaultFlags)
{
if (!coordinates.IsValid(EntityManager)) return false;
if (!coordinates.IsValid(EntityManager))
return false;
var mapPos = coordinates.ToMap(EntityManager, _transform);
return AnyEntitiesIntersecting(mapPos, flags);
@@ -517,7 +562,8 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesInRange(EntityCoordinates coordinates, float range, LookupFlags flags = DefaultFlags)
{
if (!coordinates.IsValid(EntityManager)) return false;
if (!coordinates.IsValid(EntityManager))
return false;
var mapPos = coordinates.ToMap(EntityManager, _transform);
return AnyEntitiesInRange(mapPos, range, flags);
@@ -581,7 +627,8 @@ public sealed partial class EntityLookupSystem
{
DebugTools.Assert(range > 0, "Range must be a positive float");
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
if (mapId == MapId.Nullspace)
return new HashSet<EntityUid>();
// TODO: Actual circles
var rangeVec = new Vector2(range, range);
@@ -601,7 +648,7 @@ public sealed partial class EntityLookupSystem
// Technically this doesn't consider anything overlapping from outside the grid but is this an issue?
if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet<EntityUid>();
var lookup = Comp<BroadphaseComponent>(gridId);
var lookup = _broadQuery.GetComponent(gridId);
var intersecting = new HashSet<EntityUid>();
var tileSize = grid.TileSize;
@@ -647,8 +694,7 @@ public sealed partial class EntityLookupSystem
}
}
var xformQuery = GetEntityQuery<TransformComponent>();
AddContained(intersecting, flags, xformQuery);
AddContained(intersecting, flags);
return intersecting;
}
@@ -656,8 +702,10 @@ public sealed partial class EntityLookupSystem
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid gridId, Vector2i gridIndices, LookupFlags flags = DefaultFlags)
{
// Technically this doesn't consider anything overlapping from outside the grid but is this an issue?
if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet<EntityUid>();
var lookup = Comp<BroadphaseComponent>(gridId);
if (!_mapManager.TryGetGrid(gridId, out var grid))
return new HashSet<EntityUid>();
var lookup = _broadQuery.GetComponent(gridId);
var tileSize = grid.TileSize;
var aabb = GetLocalBounds(gridIndices, tileSize);
return GetEntitiesIntersecting(lookup, aabb, flags);
@@ -707,22 +755,20 @@ public sealed partial class EntityLookupSystem
}, aabb);
}
var xformQuery = GetEntityQuery<TransformComponent>();
AddContained(intersecting, flags, xformQuery);
AddContained(intersecting, flags);
return intersecting;
}
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid gridId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
{
if (!_mapManager.GridExists(gridId)) return new HashSet<EntityUid>();
if (!_mapManager.GridExists(gridId))
return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
AddEntitiesIntersecting(gridId, intersecting, worldAABB, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
AddEntitiesIntersecting(gridId, intersecting, worldAABB, flags);
AddContained(intersecting, flags);
return intersecting;
}
@@ -731,12 +777,10 @@ public sealed partial class EntityLookupSystem
{
if (!_mapManager.GridExists(gridId)) return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
AddEntitiesIntersecting(gridId, intersecting, worldBounds, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
AddEntitiesIntersecting(gridId, intersecting, worldBounds, flags);
AddContained(intersecting, flags);
return intersecting;
}
@@ -754,8 +798,7 @@ public sealed partial class EntityLookupSystem
public HashSet<EntityUid> GetEntitiesIntersecting(BroadphaseComponent component, ref Box2 worldAABB, LookupFlags flags = DefaultFlags)
{
var intersecting = new HashSet<EntityUid>();
var xformQuery = GetEntityQuery<TransformComponent>();
var localAABB = xformQuery.GetComponent(component.Owner).InvWorldMatrix.TransformBox(worldAABB);
var localAABB = _transform.GetInvWorldMatrix(component.Owner).TransformBox(worldAABB);
if ((flags & LookupFlags.Dynamic) != 0x0)
{
@@ -793,7 +836,7 @@ public sealed partial class EntityLookupSystem
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
}
AddContained(intersecting, flags, xformQuery);
AddContained(intersecting, flags);
return intersecting;
}
@@ -838,7 +881,7 @@ public sealed partial class EntityLookupSystem
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
}
AddContained(intersecting, flags, GetEntityQuery<TransformComponent>());
AddContained(intersecting, flags);
return intersecting;
}
@@ -850,36 +893,24 @@ public sealed partial class EntityLookupSystem
/// <summary>
/// Gets the relevant <see cref="BroadphaseComponent"/> that intersects the specified area.
/// </summary>
public IEnumerable<BroadphaseComponent> FindLookupsIntersecting(MapId mapId, Box2 worldAABB)
public void FindLookupsIntersecting(MapId mapId, Box2Rotated worldBounds, ComponentQueryCallback<BroadphaseComponent> callback)
{
if (mapId == MapId.Nullspace) yield break;
if (mapId == MapId.Nullspace)
return;
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var mapUid = _mapManager.GetMapEntityId(mapId);
callback(mapUid, _broadQuery.GetComponent(mapUid));
yield return lookupQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
var state = (callback, _broadQuery);
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
yield return lookupQuery.GetComponent(grid.Owner);
}
}
/// <summary>
/// Gets the relevant <see cref="BroadphaseComponent"/> that intersects the specified area.
/// </summary>
public IEnumerable<BroadphaseComponent> FindLookupsIntersecting(MapId mapId, Box2Rotated worldBounds)
{
if (mapId == MapId.Nullspace) yield break;
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
yield return lookupQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
// Copy-paste with above but the query may differ slightly internally.
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
{
yield return lookupQuery.GetComponent(grid.Owner);
}
_mapManager.FindGridsIntersecting(mapId, worldBounds, ref state,
static (EntityUid uid, MapGridComponent grid,
ref (ComponentQueryCallback<BroadphaseComponent> callback, EntityQuery<BroadphaseComponent> _broadQuery)
tuple) =>
{
tuple.callback(uid, tuple._broadQuery.GetComponent(uid));
return true;
});
}
#endregion
@@ -904,8 +935,7 @@ public sealed partial class EntityLookupSystem
if (worldMatrix == null || angle == null)
{
var gridXform = Transform(tileRef.GridUid);
var (_, wAng, wMat) = gridXform.GetWorldPositionRotationMatrix();
var (_, wAng, wMat) = _transform.GetWorldPositionRotationMatrix(tileRef.GridUid);
worldMatrix = wMat;
angle = wAng;
}

View File

@@ -69,6 +69,7 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private EntityQuery<BroadphaseComponent> _broadQuery;

View File

@@ -10,11 +10,12 @@ using Robust.Shared.Random;
namespace Robust.Shared.GameObjects;
public abstract class SharedAudioSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] protected readonly IConfigurationManager CfgManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] protected readonly IRobustRandom RandMan = default!;
[Dependency] protected readonly ISharedPlayerManager PlayerManager = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
/// <summary>
/// Default max range at which the sound can be heard.
@@ -292,7 +293,7 @@ public abstract class SharedAudioSystem : EntitySystem
protected EntityCoordinates GetFallbackCoordinates(MapCoordinates mapCoordinates)
{
if (_mapManager.TryFindGridAt(mapCoordinates, out var gridUid, out var mapGrid))
return new EntityCoordinates(gridUid, mapGrid.WorldToLocal(mapCoordinates.Position));
return new EntityCoordinates(gridUid, _map.WorldToLocal(gridUid, mapGrid, mapCoordinates.Position));
if (_mapManager.HasMapEntity(mapCoordinates.MapId))
return new EntityCoordinates(_mapManager.GetMapEntityId(mapCoordinates.MapId), mapCoordinates.Position);

View File

@@ -7,6 +7,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Map.Events;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
@@ -21,9 +22,9 @@ namespace Robust.Shared.GameObjects
public abstract class SharedGridFixtureSystem : EntitySystem
{
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
protected ISawmill Sawmill = default!;
private bool _enabled;
private float _fixtureEnlargement;
@@ -33,12 +34,17 @@ namespace Robust.Shared.GameObjects
{
base.Initialize();
UpdatesBefore.Add(typeof(SharedBroadphaseSystem));
Sawmill = Logger.GetSawmill("physics");
_cfg.OnValueChanged(CVars.GenerateGridFixtures, SetEnabled, true);
_cfg.OnValueChanged(CVars.GridFixtureEnlargement, SetEnlargement, true);
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
SubscribeLocalEvent<RegenerateGridBoundsEvent>(OnGridBoundsRegenerate);
}
private void OnGridBoundsRegenerate(ref RegenerateGridBoundsEvent ev)
{
RegenerateCollision(ev.Entity, ev.ChunkRectangles, ev.RemovedChunks);
}
protected virtual void OnGridInit(GridInitializeEvent ev)
@@ -48,7 +54,7 @@ namespace Robust.Shared.GameObjects
// This will also check for grid splits if applicable.
var grid = Comp<MapGridComponent>(ev.EntityUid);
grid.RegenerateCollision(grid.GetMapChunks().Values.ToHashSet());
_map.RegenerateCollision(ev.EntityUid, grid, _map.GetMapChunks(ev.EntityUid, grid).Values.ToHashSet());
}
public override void Shutdown()
@@ -68,23 +74,24 @@ namespace Robust.Shared.GameObjects
Dictionary<MapChunk, List<Box2i>> mapChunks,
List<MapChunk> removedChunks)
{
if (!_enabled) return;
if (!_enabled)
return;
if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? body))
{
Sawmill.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(body)}");
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(body)}");
return;
}
if (!EntityManager.TryGetComponent(uid, out FixturesComponent? manager))
{
Sawmill.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(manager)}");
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(manager)}");
return;
}
if (!EntityManager.TryGetComponent(uid, out TransformComponent? xform))
{
Sawmill.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(TransformComponent)}");
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(TransformComponent)}");
return;
}

View File

@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Map.Components;
using Robust.Shared.Map.Enumerators;
using Robust.Shared.Map.Events;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Timing;
@@ -224,7 +227,7 @@ public abstract partial class SharedMapSystem
if (chunkData.IsDeleted())
continue;
var chunk = component.GetOrAddChunk(chunkData.Index);
var chunk = GetOrAddChunk(uid, component, chunkData.Index);
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(chunkData.TileData.Length == component.ChunkSize * component.ChunkSize);
@@ -237,7 +240,7 @@ public abstract partial class SharedMapSystem
if (chunk.GetTile(x, y) == tile)
continue;
chunk.SetTile(x, y, tile);
SetChunkTile(uid, component, chunk, x, y, tile);
modified.Add((new Vector2i(chunk.X * component.ChunkSize + x, chunk.Y * component.ChunkSize + y), tile));
}
}
@@ -247,13 +250,13 @@ public abstract partial class SharedMapSystem
{
if (chunkData.IsDeleted())
{
component.RemoveChunk(chunkData.Index);
RemoveChunk(uid, component, chunkData.Index);
continue;
}
var chunk = component.GetOrAddChunk(chunkData.Index);
var chunk = GetOrAddChunk(uid, component, chunkData.Index);
chunk.SuppressCollisionRegeneration = false;
component.RegenerateCollision(chunk);
RegenerateCollision(uid, component, chunk);
}
}
@@ -263,12 +266,12 @@ public abstract partial class SharedMapSystem
foreach (var index in component.Chunks.Keys)
{
if (!state.FullGridData.ContainsKey(index))
component.RemoveChunk(index);
RemoveChunk(uid, component, index);
}
foreach (var (index, tiles) in state.FullGridData)
{
var chunk = component.GetOrAddChunk(index);
var chunk = GetOrAddChunk(uid, component, index);
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(tiles.Length == component.ChunkSize * component.ChunkSize);
@@ -281,13 +284,13 @@ public abstract partial class SharedMapSystem
if (chunk.GetTile(x, y) == tile)
continue;
chunk.SetTile(x, y, tile);
SetChunkTile(uid, component, chunk, x, y, tile);
modified.Add((new Vector2i(chunk.X * component.ChunkSize + x, chunk.Y * component.ChunkSize + y), tile));
}
}
chunk.SuppressCollisionRegeneration = false;
component.RegenerateCollision(chunk);
RegenerateCollision(uid, component, chunk);
}
}
@@ -300,7 +303,7 @@ public abstract partial class SharedMapSystem
{
if (args.FromTick <= component.CreationTick)
{
GetFullState(component, ref args);
GetFullState(uid, component, ref args);
return;
}
@@ -324,7 +327,7 @@ public abstract partial class SharedMapSystem
chunkData.Add(ChunkDatum.CreateDeleted(indices));
}
foreach (var (index, chunk) in component.GetMapChunks())
foreach (var (index, chunk) in GetMapChunks(uid, component))
{
if (chunk.LastTileModifiedTick < fromTick)
continue;
@@ -348,11 +351,11 @@ public abstract partial class SharedMapSystem
args.State = new MapGridComponentState(component.ChunkSize, chunkData);
}
private void GetFullState(MapGridComponent component, ref ComponentGetState args)
private void GetFullState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
{
var chunkData = new Dictionary<Vector2i, Tile[]>();
foreach (var (index, chunk) in component.GetMapChunks())
foreach (var (index, chunk) in GetMapChunks(uid, component))
{
var tileBuffer = new Tile[component.ChunkSize * (uint)component.ChunkSize];
@@ -385,7 +388,6 @@ public abstract partial class SharedMapSystem
foreach (var chunk in component.Chunks.Values)
{
chunk.TileModified += component.OnTileModified;
chunk.LastTileModifiedTick = curTick;
}
@@ -445,7 +447,7 @@ public abstract partial class SharedMapSystem
if (!Resolve(uid, ref xform))
return new Box2();
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform, GetEntityQuery<TransformComponent>());
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
var aabb = grid.LocalAABB.Translated(worldPos);
return new Box2Rotated(aabb, worldRot, worldPos).CalcBoundingBox();
@@ -456,7 +458,7 @@ public abstract partial class SharedMapSystem
DebugTools.Assert(!EntityManager.HasComponent<MapComponent>(uid));
var aabb = GetWorldAABB(uid, grid);
if (!TryComp<TransformComponent>(uid, out var xform))
if (!_xformQuery.TryGetComponent(uid, out var xform))
return;
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
@@ -486,4 +488,796 @@ public abstract partial class SharedMapSystem
movedGrids.MovedGrids.Remove(uid);
}
}
private void RemoveChunk(EntityUid uid, MapGridComponent grid, Vector2i origin)
{
if (!grid.Chunks.TryGetValue(origin, out var chunk))
return;
if (_netManager.IsServer)
grid.ChunkDeletionHistory.Add((_timing.CurTick, chunk.Indices));
chunk.Fixtures.Clear();
grid.Chunks.Remove(origin);
if (grid.Chunks.Count == 0)
RaiseLocalEvent(uid, new EmptyGridEvent { GridId = uid }, true);
}
/// <summary>
/// Regenerates the chunk local bounds of this chunk.
/// </summary>
private void RegenerateCollision(EntityUid uid, MapGridComponent grid, MapChunk mapChunk)
{
RegenerateCollision(uid, grid, new HashSet<MapChunk> { mapChunk });
}
/// <summary>
/// Regenerate collision for multiple chunks at once; faster than doing it individually.
/// </summary>
internal void RegenerateCollision(EntityUid uid, MapGridComponent grid, IReadOnlySet<MapChunk> chunks)
{
if (HasComp<MapComponent>(uid))
return;
var chunkRectangles = new Dictionary<MapChunk, List<Box2i>>(chunks.Count);
var removedChunks = new List<MapChunk>();
foreach (var mapChunk in chunks)
{
// Even if the chunk is still removed still need to make sure bounds are updated (for now...)
// generate collision rectangles for this chunk based on filled tiles.
GridChunkPartition.PartitionChunk(mapChunk, out var localBounds, out var rectangles);
mapChunk.CachedBounds = localBounds;
if (mapChunk.FilledTiles > 0)
chunkRectangles.Add(mapChunk, rectangles);
else
{
// Gone. Reduced to atoms
// Need to do this before RemoveChunk because it clears fixtures.
FixturesComponent? manager = null;
PhysicsComponent? body = null;
TransformComponent? xform = null;
foreach (var fixture in mapChunk.Fixtures)
{
_fixtures.DestroyFixture(uid, fixture, false, manager: manager, body: body, xform: xform);
}
RemoveChunk(uid, grid, mapChunk.Indices);
removedChunks.Add(mapChunk);
}
}
grid.LocalAABB = new Box2();
foreach (var chunk in grid.Chunks.Values)
{
var chunkBounds = chunk.CachedBounds;
if (chunkBounds.Size.Equals(Vector2i.Zero))
continue;
if (grid.LocalAABB.Size == Vector2.Zero)
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
grid.LocalAABB = gridBounds;
}
else
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
grid.LocalAABB = grid.LocalAABB.Union(gridBounds);
}
}
// May have been deleted from the bulk update above!
if (Deleted(uid))
return;
_physics.WakeBody(uid);
OnGridBoundsChange(uid, grid);
var ev = new RegenerateGridBoundsEvent(uid, chunkRectangles, removedChunks);
RaiseLocalEvent(ref ev);
}
#region TileAccess
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
{
return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords));
}
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords));
}
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, Vector2i tileCoordinates)
{
var chunkIndices = GridTileToChunkIndices(uid, grid, tileCoordinates);
if (!grid.Chunks.TryGetValue(chunkIndices, out var output))
{
// Chunk doesn't exist, return a tileRef to an empty (space) tile.
return new TileRef(uid, tileCoordinates.X, tileCoordinates.Y, default);
}
var chunkTileIndices = output.GridTileToChunkTile(tileCoordinates);
return GetTileRef(uid, grid, output, (ushort)chunkTileIndices.X, (ushort)chunkTileIndices.Y);
}
/// <summary>
/// Returns the tile at the given chunk indices.
/// </summary>
/// <param name="mapChunk"></param>
/// <param name="xIndex">The X tile index relative to the chunk origin.</param>
/// <param name="yIndex">The Y tile index relative to the chunk origin.</param>
/// <returns>A reference to a tile.</returns>
internal TileRef GetTileRef(EntityUid uid, MapGridComponent grid, MapChunk mapChunk, ushort xIndex, ushort yIndex)
{
if (xIndex >= mapChunk.ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= mapChunk.ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
var indices = mapChunk.ChunkTileToGridTile(new Vector2i(xIndex, yIndex));
return new TileRef(uid, indices, mapChunk.GetTile(xIndex, yIndex));
}
public IEnumerable<TileRef> GetAllTiles(EntityUid uid, MapGridComponent grid, bool ignoreEmpty = true)
{
foreach (var kvChunk in grid.Chunks)
{
var chunk = kvChunk.Value;
for (ushort x = 0; x < grid.ChunkSize; x++)
{
for (ushort y = 0; y < grid.ChunkSize; y++)
{
var tile = chunk.GetTile(x, y);
if (ignoreEmpty && tile.IsEmpty)
continue;
var (gridX, gridY) = new Vector2i(x, y) + chunk.Indices * grid.ChunkSize;
yield return new TileRef(uid, gridX, gridY, tile);
}
}
}
}
public GridTileEnumerator GetAllTilesEnumerator(EntityUid uid, MapGridComponent grid, bool ignoreEmpty = true)
{
return new GridTileEnumerator(uid, grid.Chunks.GetEnumerator(), grid.ChunkSize, ignoreEmpty);
}
public void SetTile(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, Tile tile)
{
var localTile = CoordinatesToTile(uid, grid, coords);
SetTile(uid, grid, new Vector2i(localTile.X, localTile.Y), tile);
}
public void SetTile(EntityUid uid, MapGridComponent grid, Vector2i gridIndices, Tile tile)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(uid, grid, gridIndices);
SetChunkTile(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
// Ideally we'd to this here for consistency but apparently tile modified does it or something.
// Yeah it's noodly.
// RegenerateCollision(chunk);
}
public void SetTiles(EntityUid uid, MapGridComponent grid, List<(Vector2i GridIndices, Tile Tile)> tiles)
{
if (tiles.Count == 0)
return;
var chunks = new HashSet<MapChunk>(Math.Max(1, tiles.Count / grid.ChunkSize));
foreach (var (gridIndices, tile) in tiles)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(uid, grid, gridIndices);
chunks.Add(chunk);
chunk.SuppressCollisionRegeneration = true;
SetChunkTile(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
}
foreach (var chunk in chunks)
{
chunk.SuppressCollisionRegeneration = false;
}
RegenerateCollision(uid, grid, chunks);
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var localAABB = localArea.CalcBoundingBox();
return GetLocalTilesIntersecting(uid, grid, localAABB, ignoreEmpty, predicate);
}
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localArea, ignoreEmpty, predicate))
{
yield return tile;
}
}
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2 worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localArea, ignoreEmpty, predicate))
{
yield return tile;
}
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2 localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
// TODO: Should move the intersecting calls onto mapmanager system and then allow people to pass in xform / xformquery
// that way we can avoid the GetComp here.
var gridTileLb = new Vector2i((int)Math.Floor(localArea.Left), (int)Math.Floor(localArea.Bottom));
// If we have 20.1 we want to include that tile but if we have 20 then we don't.
var gridTileRt = new Vector2i((int)Math.Ceiling(localArea.Right), (int)Math.Ceiling(localArea.Top));
for (var x = gridTileLb.X; x < gridTileRt.X; x++)
{
for (var y = gridTileLb.Y; y < gridTileRt.Y; y++)
{
var gridChunk = GridTileToChunkIndices(uid, grid, new Vector2i(x, y));
if (grid.Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(new Vector2i(x, y));
var tile = GetTileRef(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
continue;
if (predicate == null || predicate(tile))
yield return tile;
}
else if (!ignoreEmpty)
{
var tile = new TileRef(uid, x, y, Tile.Empty);
if (predicate == null || predicate(tile))
yield return tile;
}
}
}
}
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Circle worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var aabb = new Box2(worldArea.Position.X - worldArea.Radius, worldArea.Position.Y - worldArea.Radius,
worldArea.Position.X + worldArea.Radius, worldArea.Position.Y + worldArea.Radius);
var circleGridPos = new EntityCoordinates(uid, WorldToLocal(uid, grid, worldArea.Position));
foreach (var tile in GetTilesIntersecting(uid, grid, aabb, ignoreEmpty, predicate))
{
var local = GridTileToLocal(uid, grid, tile.GridIndices);
if (!local.TryDistance(EntityManager, _transform, circleGridPos, out var distance))
{
continue;
}
if (distance <= worldArea.Radius)
{
yield return tile;
}
}
}
private bool TryGetTile(EntityUid uid, MapGridComponent grid, Vector2i indices, bool ignoreEmpty, [NotNullWhen(true)] out TileRef? tileRef, Predicate<TileRef>? predicate = null)
{
// Similar to TryGetTileRef but for the tiles intersecting iterators.
var gridChunk = GridTileToChunkIndices(uid, grid, indices);
if (grid.Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(indices);
var tile = GetTileRef(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
{
tileRef = null;
return false;
}
if (predicate == null || predicate(tile))
{
tileRef = tile;
return true;
}
}
else if (!ignoreEmpty)
{
var tile = new TileRef(uid, indices.X, indices.Y, Tile.Empty);
if (predicate == null || predicate(tile))
{
tileRef = tile;
return true;
}
}
tileRef = null;
return false;
}
#endregion TileAccess
#region ChunkAccess
internal MapChunk GetOrAddChunk(EntityUid uid, MapGridComponent grid, int xIndex, int yIndex)
{
return GetOrAddChunk(uid, grid, new Vector2i(xIndex, yIndex));
}
internal bool TryGetChunk(EntityUid uid, MapGridComponent grid, Vector2i chunkIndices, [NotNullWhen(true)] out MapChunk? chunk)
{
return grid.Chunks.TryGetValue(chunkIndices, out chunk);
}
internal MapChunk GetOrAddChunk(EntityUid uid, MapGridComponent grid, Vector2i chunkIndices)
{
if (grid.Chunks.TryGetValue(chunkIndices, out var output))
return output;
var newChunk = new MapChunk(chunkIndices.X, chunkIndices.Y, grid.ChunkSize)
{
LastTileModifiedTick = _timing.CurTick
};
return grid.Chunks[chunkIndices] = newChunk;
}
public bool HasChunk(EntityUid uid, MapGridComponent grid, Vector2i chunkIndices)
{
return grid.Chunks.ContainsKey(chunkIndices);
}
internal IReadOnlyDictionary<Vector2i, MapChunk> GetMapChunks(EntityUid uid, MapGridComponent grid)
{
return grid.Chunks;
}
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
{
var localAABB = _transform.GetInvWorldMatrix(uid).TransformBox(worldAABB);
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
}
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea)
{
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
return new ChunkEnumerator(grid.Chunks, localArea, grid.ChunkSize);
}
internal ChunkEnumerator GetLocalMapChunks(EntityUid uid, MapGridComponent grid, Box2 localAABB)
{
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
}
#endregion ChunkAccess
#region SnapGridAccess
public int AnchoredEntityCount(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk))
return 0;
var (x, y) = chunk.GridTileToChunkTile(pos);
return chunk.GetSnapGrid((ushort)x, (ushort)y)?.Count ?? 0; // ?
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
{
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
// Because some content stuff checks neighboring tiles (which may not actually exist) we won't just
// create an entire chunk for it.
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk)) return Enumerable.Empty<EntityUid>();
var chunkTile = chunk.GridTileToChunkTile(pos);
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y);
}
public AnchoredEntitiesEnumerator GetAnchoredEntitiesEnumerator(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk)) return AnchoredEntitiesEnumerator.Empty;
var chunkTile = chunk.GridTileToChunkTile(pos);
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
return snapgrid == null
? AnchoredEntitiesEnumerator.Empty
: new AnchoredEntitiesEnumerator(snapgrid.GetEnumerator());
}
public IEnumerable<EntityUid> GetLocalAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 localAABB)
{
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localAABB, true, null))
{
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
{
yield return ent;
}
}
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
{
foreach (var tile in GetTilesIntersecting(uid, grid, worldAABB))
{
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
{
yield return ent;
}
}
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2Rotated worldBounds)
{
foreach (var tile in GetTilesIntersecting(uid, grid, worldBounds))
{
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
{
yield return ent;
}
}
}
public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == coords.GetMapId(EntityManager));
#endif
return SnapGridLocalCellFor(uid, grid, LocalToGrid(uid, grid, coords));
}
public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, MapCoordinates worldPos)
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == worldPos.MapId);
#endif
var localPos = WorldToLocal(uid, grid, worldPos.Position);
return SnapGridLocalCellFor(uid, grid, localPos);
}
private Vector2i SnapGridLocalCellFor(EntityUid uid, MapGridComponent grid, Vector2 localPos)
{
var x = (int)Math.Floor(localPos.X / grid.TileSize);
var y = (int)Math.Floor(localPos.Y / grid.TileSize);
return new Vector2i(x, y);
}
public bool IsAnchored(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, EntityUid euid)
{
var tilePos = TileIndicesFor(uid, grid, coords);
var (chunk, chunkTile) = ChunkAndOffsetForTile(uid, grid, tilePos);
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
return snapgrid?.Contains(euid) == true;
}
public bool AddToSnapGridCell(EntityUid gridUid, MapGridComponent grid, Vector2i pos, EntityUid euid)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridUid, grid, pos);
if (chunk.GetTile((ushort)chunkTile.X, (ushort)chunkTile.Y).IsEmpty)
return false;
chunk.AddToSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
return true;
}
public bool AddToSnapGridCell(EntityUid gridUid, MapGridComponent grid, EntityCoordinates coords, EntityUid euid)
{
return AddToSnapGridCell(gridUid, grid, TileIndicesFor(gridUid, grid, coords), euid);
}
public void RemoveFromSnapGridCell(EntityUid gridUid, MapGridComponent grid, Vector2i pos, EntityUid euid)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridUid, grid, pos);
chunk.RemoveFromSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
}
public void RemoveFromSnapGridCell(EntityUid gridUid, MapGridComponent grid, EntityCoordinates coords, EntityUid euid)
{
RemoveFromSnapGridCell(gridUid, grid, TileIndicesFor(gridUid, grid, coords), euid);
}
private (MapChunk, Vector2i) ChunkAndOffsetForTile(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
var gridChunkIndices = GridTileToChunkIndices(uid, grid, pos);
var chunk = GetOrAddChunk(uid, grid, gridChunkIndices);
var chunkTile = chunk.GridTileToChunkTile(pos);
return (chunk, chunkTile);
}
public IEnumerable<EntityUid> GetInDir(EntityUid uid, MapGridComponent grid, EntityCoordinates position, Direction dir)
{
var pos = GetDirection(TileIndicesFor(uid, grid, position), dir);
return GetAnchoredEntities(uid, grid, pos);
}
public IEnumerable<EntityUid> GetOffset(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, Vector2i offset)
{
var pos = TileIndicesFor(uid, grid, coords) + offset;
return GetAnchoredEntities(uid, grid, pos);
}
public IEnumerable<EntityUid> GetLocal(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
}
public EntityCoordinates DirectionToGrid(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, Direction direction)
{
return GridTileToLocal(uid, grid, GetDirection(TileIndicesFor(uid, grid, coords), direction));
}
public IEnumerable<EntityUid> GetCardinalNeighborCells(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
var position = TileIndicesFor(uid, grid, coords);
foreach (var cell in GetAnchoredEntities(uid, grid, position))
yield return cell;
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(0, 1)))
yield return cell;
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(0, -1)))
yield return cell;
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(1, 0)))
yield return cell;
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(-1, 0)))
yield return cell;
}
public IEnumerable<EntityUid> GetCellsInSquareArea(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, int n)
{
var position = TileIndicesFor(uid, grid, coords);
for (var y = -n; y <= n; ++y)
for (var x = -n; x <= n; ++x)
{
var enumerator = GetAnchoredEntitiesEnumerator(uid, grid, position + new Vector2i(x, y));
while (enumerator.MoveNext(out var cell))
{
yield return cell.Value;
}
}
}
#endregion
#region Transforms
public Vector2 WorldToLocal(EntityUid uid, MapGridComponent grid, Vector2 posWorld)
{
var matrix = _transform.GetInvWorldMatrix(uid);
return matrix.Transform(posWorld);
}
public EntityCoordinates MapToGrid(EntityUid uid, MapCoordinates posWorld)
{
var mapId = _xformQuery.GetComponent(uid).MapID;
if (posWorld.MapId != mapId)
throw new ArgumentException(
$"Grid {uid} is on map {mapId}, but coords are on map {posWorld.MapId}.",
nameof(posWorld));
if (!TryComp<MapGridComponent>(uid, out var grid))
{
return new EntityCoordinates(MapManager.GetMapEntityId(posWorld.MapId), new Vector2(posWorld.X, posWorld.Y));
}
return new EntityCoordinates(uid, WorldToLocal(uid, grid, posWorld.Position));
}
public Vector2 LocalToWorld(EntityUid uid, MapGridComponent grid, Vector2 posLocal)
{
var matrix = _transform.GetWorldMatrix(uid);
return matrix.Transform(posLocal);
}
public Vector2i WorldToTile(EntityUid uid, MapGridComponent grid, Vector2 posWorld)
{
var local = WorldToLocal(uid, grid, posWorld);
var x = (int)Math.Floor(local.X / grid.TileSize);
var y = (int)Math.Floor(local.Y / grid.TileSize);
return new Vector2i(x, y);
}
public Vector2i LocalToTile(EntityUid uid, MapGridComponent grid, EntityCoordinates coordinates)
{
var position = LocalToGrid(uid, grid, coordinates);
return new Vector2i((int) Math.Floor(position.X / grid.TileSize), (int) Math.Floor(position.Y / grid.TileSize));
}
public Vector2i CoordinatesToTile(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == coords.MapId);
#endif
var local = WorldToLocal(uid, grid, coords.Position);
var x = (int)Math.Floor(local.X / grid.TileSize);
var y = (int)Math.Floor(local.Y / grid.TileSize);
return new Vector2i(x, y);
}
public Vector2i CoordinatesToTile(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == coords.GetMapId(EntityManager));
#endif
var local = LocalToGrid(uid, grid, coords);
var x = (int)Math.Floor(local.X / grid.TileSize);
var y = (int)Math.Floor(local.Y / grid.TileSize);
return new Vector2i(x, y);
}
public Vector2i LocalToChunkIndices(EntityUid uid, MapGridComponent grid, EntityCoordinates gridPos)
{
var local = LocalToGrid(uid, grid, gridPos);
var x = (int)Math.Floor(local.X / (grid.TileSize * grid.ChunkSize));
var y = (int)Math.Floor(local.Y / (grid.TileSize * grid.ChunkSize));
return new Vector2i(x, y);
}
public Vector2 LocalToGrid(EntityUid uid, MapGridComponent grid, EntityCoordinates position)
{
return position.EntityId == uid
? position.Position
: WorldToLocal(uid, grid, position.ToMapPos(EntityManager, _transform));
}
public bool CollidesWithGrid(EntityUid uid, MapGridComponent grid, Vector2i indices)
{
var chunkIndices = GridTileToChunkIndices(uid, grid, indices);
if (!grid.Chunks.TryGetValue(chunkIndices, out var chunk))
return false;
var cTileIndices = chunk.GridTileToChunkTile(indices);
return chunk.GetTile((ushort)cTileIndices.X, (ushort)cTileIndices.Y).TypeId != Tile.Empty.TypeId;
}
public Vector2i GridTileToChunkIndices(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
var x = (int)Math.Floor(gridTile.X / (float) grid.ChunkSize);
var y = (int)Math.Floor(gridTile.Y / (float) grid.ChunkSize);
return new Vector2i(x, y);
}
public EntityCoordinates GridTileToLocal(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
return new(uid,
new Vector2(gridTile.X * grid.TileSize + (grid.TileSize / 2f), gridTile.Y * grid.TileSize + (grid.TileSize / 2f)));
}
public Vector2 GridTileToWorldPos(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
var locX = gridTile.X * grid.TileSize + (grid.TileSize / 2f);
var locY = gridTile.Y * grid.TileSize + (grid.TileSize / 2f);
return _transform.GetWorldMatrix(uid).Transform(new Vector2(locX, locY));
}
public MapCoordinates GridTileToWorld(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
var parentMapId = _xformQuery.GetComponent(uid).MapID;
return new(GridTileToWorldPos(uid, grid, gridTile), parentMapId);
}
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, Vector2i indices, out TileRef tile)
{
var chunkIndices = GridTileToChunkIndices(uid, grid, indices);
if (!grid.Chunks.TryGetValue(chunkIndices, out var chunk))
{
tile = default;
return false;
}
var cTileIndices = chunk.GridTileToChunkTile(indices);
tile = GetTileRef(uid, grid, chunk, (ushort)cTileIndices.X, (ushort)cTileIndices.Y);
return true;
}
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, out TileRef tile)
{
return TryGetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords), out tile);
}
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, Vector2 worldPos, out TileRef tile)
{
return TryGetTileRef(uid, grid, WorldToTile(uid, grid, worldPos), out tile);
}
#endregion Transforms
/// <summary>
/// Calculate the world space AABB for this chunk.
/// </summary>
internal Box2 CalcWorldAABB(EntityUid uid, MapGridComponent grid, MapChunk mapChunk)
{
var (position, rotation) =
_transform.GetWorldPositionRotation(uid);
var chunkPosition = mapChunk.Indices;
var tileScale = grid.TileSize;
var chunkScale = mapChunk.ChunkSize;
var worldPos = position + rotation.RotateVec(chunkPosition * tileScale * chunkScale);
return new Box2Rotated(
((Box2)mapChunk.CachedBounds
.Scale(tileScale))
.Translated(worldPos),
rotation, worldPos).CalcBoundingBox();
}
private void OnTileModified(EntityUid uid, MapGridComponent grid, MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile,
bool shapeChanged)
{
// As the collision regeneration can potentially delete the chunk we'll notify of the tile changed first.
var gridTile = mapChunk.ChunkTileToGridTile(tileIndices);
mapChunk.LastTileModifiedTick = _timing.CurTick;
grid.LastTileModifiedTick = _timing.CurTick;
Dirty(grid);
// The map serializer currently sets tiles of unbound grids as part of the deserialization process
// It properly sets SuppressOnTileChanged so that the event isn't spammed for every tile on the grid.
// ParentMapId is not able to be accessed on unbound grids, so we can't even call this function for unbound grids.
if (!MapManager.SuppressOnTileChanged)
{
var newTileRef = new TileRef(uid, gridTile, newTile);
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile);
}
if (shapeChanged && !mapChunk.SuppressCollisionRegeneration)
{
RegenerateCollision(uid, grid, mapChunk);
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
public abstract partial class SharedMapSystem
{
/// <summary>
/// Replaces a single tile inside of the chunk.
/// </summary>
/// <param name="xIndex">The X tile index relative to the chunk.</param>
/// <param name="yIndex">The Y tile index relative to the chunk.</param>
/// <param name="tile">The new tile to insert.</param>
internal void SetChunkTile(EntityUid uid, MapGridComponent grid, MapChunk chunk, ushort xIndex, ushort yIndex, Tile tile)
{
if (!chunk.TrySetTile(xIndex, yIndex, tile, out var oldTile, out var shapeChanged))
return;
var tileIndices = new Vector2i(xIndex, yIndex);
OnTileModified(uid, grid, chunk, tileIndices, tile, oldTile, shapeChanged);
}
}

View File

@@ -6,21 +6,30 @@ using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.Map.Components;
using System.Linq;
using Robust.Shared.Network;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects
{
[UsedImplicitly]
public abstract partial class SharedMapSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] protected readonly IMapManager MapManager = default!;
[Dependency] private readonly IMapManagerInternal _mapInternal = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private EntityQuery<TransformComponent> _xformQuery;
public override void Initialize()
{
base.Initialize();
_xformQuery = GetEntityQuery<TransformComponent>();
InitializeMap();
InitializeGrid();

View File

@@ -33,8 +33,8 @@ public abstract partial class SharedTransformSystem
EntityQuery<TransformComponent> xformQuery)
{
// Bypass some of the expensive stuff in unanchoring / anchoring.
oldGrid.RemoveFromSnapGridCell(tilePos, uid);
newGrid.AddToSnapGridCell(tilePos, uid);
_map.RemoveFromSnapGridCell(oldGridUid, oldGrid, tilePos, uid);
_map.AddToSnapGridCell(newGridUid, newGrid, tilePos, uid);
// TODO: Could do this re-parent way better.
// Unfortunately we don't want any anchoring events to go out hence... this.
xform._anchored = false;
@@ -77,7 +77,7 @@ public abstract partial class SharedTransformSystem
MapGridComponent grid,
Vector2i tileIndices)
{
if (!grid.AddToSnapGridCell(tileIndices, uid))
if (!_map.AddToSnapGridCell(gridUid, grid, tileIndices, uid))
return false;
var wasAnchored = xform._anchored;
@@ -94,7 +94,7 @@ public abstract partial class SharedTransformSystem
}
// Anchor snapping. If there is a coordinate change, it will dirty the component for us.
var pos = new EntityCoordinates(gridUid, grid.GridTileToLocal(tileIndices).Position);
var pos = new EntityCoordinates(gridUid, _map.GridTileToLocal(gridUid, grid, tileIndices).Position);
SetCoordinates(uid, xform, pos, unanchor: false);
return true;
@@ -102,14 +102,14 @@ public abstract partial class SharedTransformSystem
public bool AnchorEntity(EntityUid uid, TransformComponent xform, MapGridComponent grid)
{
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
var tileIndices = _map.TileIndicesFor(grid.Owner, grid, xform.Coordinates);
return AnchorEntity(uid, xform, grid, tileIndices);
}
public bool AnchorEntity(EntityUid uid, TransformComponent xform)
{
return _mapManager.TryGetGrid(xform.GridUid, out var grid)
&& AnchorEntity(uid, xform, grid, grid.TileIndicesFor(xform.Coordinates));
&& AnchorEntity(uid, xform, grid, _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates));
}
public void Unanchor(EntityUid uid, TransformComponent xform, bool setPhysics = true)
@@ -128,8 +128,8 @@ public abstract partial class SharedTransformSystem
if (TryComp(xform.GridUid, out MapGridComponent? grid))
{
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
grid.RemoveFromSnapGridCell(tileIndices, uid);
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
_map.RemoveFromSnapGridCell(xform.GridUid.Value, grid, tileIndices, uid);
}
else if (xform.Initialized)
{
@@ -156,7 +156,7 @@ public abstract partial class SharedTransformSystem
/// </summary>
public bool ContainsEntity(TransformComponent xform, EntityUid entity)
{
return ContainsEntity(xform, entity, GetEntityQuery<TransformComponent>());
return ContainsEntity(xform, entity, _xformQuery);
}
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
@@ -168,7 +168,7 @@ public abstract partial class SharedTransformSystem
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
public bool ContainsEntity(TransformComponent xform, TransformComponent entityTransform)
{
return ContainsEntity(xform, entityTransform, GetEntityQuery<TransformComponent>());
return ContainsEntity(xform, entityTransform, _xformQuery);
}
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
@@ -227,11 +227,9 @@ public abstract partial class SharedTransformSystem
return value;
}
var xformQuery = GetEntityQuery<TransformComponent>();
if (!component._mapIdInitialized)
{
FindMapIdAndSet(uid, component, EntityManager, xformQuery, _mapManager);
FindMapIdAndSet(uid, component, EntityManager, _xformQuery, _mapManager);
component._mapIdInitialized = true;
}
@@ -241,7 +239,7 @@ public abstract partial class SharedTransformSystem
// Note that _children is a HashSet<EntityUid>,
// so duplicate additions (which will happen) don't matter.
var parentXform = xformQuery.GetComponent(component.ParentUid);
var parentXform = _xformQuery.GetComponent(component.ParentUid);
if (parentXform.LifeStage > ComponentLifeStage.Running || LifeStage(component.ParentUid) > EntityLifeStage.MapInitialized)
{
var msg = $"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(component.ParentUid)}, new parent: {ToPrettyString(uid)}";
@@ -365,7 +363,7 @@ public abstract partial class SharedTransformSystem
DebugTools.Assert(!HasComp<MapGridComponent>(uid));
DebugTools.Assert(gridId == null || HasComp<MapGridComponent>(gridId));
xformQuery ??= GetEntityQuery<TransformComponent>();
xformQuery ??= _xformQuery;
SetGridIdRecursive(uid, xform, gridId, xformQuery.Value);
}
@@ -486,8 +484,6 @@ public abstract partial class SharedTransformSystem
// Perform parent change logic
if (value.EntityId != xform._parent)
{
var xformQuery = GetEntityQuery<TransformComponent>();
if (value.EntityId == uid)
{
QueueDel(uid);
@@ -496,7 +492,7 @@ public abstract partial class SharedTransformSystem
if (value.EntityId.IsValid())
{
if (!xformQuery.Resolve(value.EntityId, ref newParent, false))
if (!_xformQuery.Resolve(value.EntityId, ref newParent, false))
{
QueueDel(uid);
throw new InvalidOperationException($"Attempted to parent entity {ToPrettyString(uid)} to non-existent entity {value.EntityId}");
@@ -530,13 +526,13 @@ public abstract partial class SharedTransformSystem
}
recursiveUid = recursiveXform.ParentUid;
recursiveXform = xformQuery.GetComponent(recursiveUid);
recursiveXform = _xformQuery.GetComponent(recursiveUid);
}
}
}
if (xform._parent.IsValid())
xformQuery.Resolve(xform._parent, ref oldParent);
_xformQuery.Resolve(xform._parent, ref oldParent);
oldParent?._children.Remove(uid);
newParent?._children.Add(uid);
@@ -546,7 +542,7 @@ public abstract partial class SharedTransformSystem
if (newParent != null)
{
xform.ChangeMapId(newParent.MapID, xformQuery);
xform.ChangeMapId(newParent.MapID, _xformQuery);
if (!xform._gridInitialized)
InitializeGridUid(uid, xform);
@@ -559,18 +555,18 @@ public abstract partial class SharedTransformSystem
}
else
{
xform.ChangeMapId(MapId.Nullspace, xformQuery);
xform.ChangeMapId(MapId.Nullspace, _xformQuery);
if (!xform._gridInitialized)
InitializeGridUid(uid, xform);
else
SetGridId(uid, xform, null, xformQuery);
SetGridId(uid, xform, null, _xformQuery);
}
if (xform.Initialized)
{
// preserve world rotation
if (rotation == null && oldParent != null && newParent != null && !xform.NoLocalRotation)
xform._localRotation += GetWorldRotation(oldParent, xformQuery) - GetWorldRotation(newParent, xformQuery);
xform._localRotation += GetWorldRotation(oldParent) - GetWorldRotation(newParent);
DebugTools.Assert(!xform.NoLocalRotation || xform.LocalRotation == 0);
@@ -598,7 +594,7 @@ public abstract partial class SharedTransformSystem
public void ReparentChildren(EntityUid oldUid, EntityUid uid)
{
ReparentChildren(oldUid, uid, GetEntityQuery<TransformComponent>());
ReparentChildren(oldUid, uid, _xformQuery);
}
/// <summary>
@@ -625,7 +621,7 @@ public abstract partial class SharedTransformSystem
public TransformComponent? GetParent(EntityUid uid)
{
return GetParent(uid, GetEntityQuery<TransformComponent>());
return GetParent(uid, _xformQuery);
}
public TransformComponent? GetParent(EntityUid uid, EntityQuery<TransformComponent> xformQuery)
@@ -635,7 +631,7 @@ public abstract partial class SharedTransformSystem
public TransformComponent? GetParent(TransformComponent xform)
{
return GetParent(xform, GetEntityQuery<TransformComponent>());
return GetParent(xform, _xformQuery);
}
public TransformComponent? GetParent(TransformComponent xform, EntityQuery<TransformComponent> xformQuery)
@@ -646,13 +642,12 @@ public abstract partial class SharedTransformSystem
public void SetParent(EntityUid uid, EntityUid parent)
{
var query = GetEntityQuery<TransformComponent>();
SetParent(uid, query.GetComponent(uid), parent, query);
SetParent(uid, _xformQuery.GetComponent(uid), parent, _xformQuery);
}
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, TransformComponent? parentXform = null)
{
SetParent(uid, xform, parent, GetEntityQuery<TransformComponent>(), parentXform);
SetParent(uid, xform, parent, _xformQuery, parentXform);
}
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, EntityQuery<TransformComponent> xformQuery, TransformComponent? parentXform = null)
@@ -711,8 +706,8 @@ public abstract partial class SharedTransformSystem
// remove from any old grid lookups
if (xform.Anchored && TryComp(xform.ParentUid, out MapGridComponent? grid))
{
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
grid.RemoveFromSnapGridCell(tileIndices, uid);
var tileIndices = _map.TileIndicesFor(xform.ParentUid, grid, xform.Coordinates);
_map.RemoveFromSnapGridCell(xform.ParentUid, grid, tileIndices, uid);
}
// Set anchor state true during the move event unless the entity wasn't and isn't being anchored. This avoids unnecessary entity lookup changes.
@@ -729,8 +724,8 @@ public abstract partial class SharedTransformSystem
{
if (xform.ParentUid == xform.GridUid && TryComp(xform.GridUid, out MapGridComponent? newGrid))
{
var tileIndices = newGrid.TileIndicesFor(xform.Coordinates);
newGrid.AddToSnapGridCell(tileIndices, uid);
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, newGrid, xform.Coordinates);
_map.AddToSnapGridCell(xform.GridUid.Value, newGrid, tileIndices, uid);
}
else
{
@@ -778,8 +773,7 @@ public abstract partial class SharedTransformSystem
[Pure]
public Matrix3 GetWorldMatrix(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetWorldMatrix(query.GetComponent(uid), query);
return GetWorldMatrix(_xformQuery.GetComponent(uid), _xformQuery);
}
// Temporary until it's moved here
@@ -787,7 +781,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Matrix3 GetWorldMatrix(TransformComponent component)
{
return GetWorldMatrix(component, GetEntityQuery<TransformComponent>());
return GetWorldMatrix(component, _xformQuery);
}
[Pure]
@@ -845,6 +839,12 @@ public abstract partial class SharedTransformSystem
return GetWorldPosition(component);
}
[Pure]
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(EntityUid uid)
{
return GetWorldPositionRotation(_xformQuery.GetComponent(uid));
}
[Pure]
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(TransformComponent component)
{
@@ -947,7 +947,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetWorldPosition(TransformComponent component, Vector2 worldPos)
{
SetWorldPosition(component, worldPos, GetEntityQuery<TransformComponent>());
SetWorldPosition(component, worldPos, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -973,8 +973,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Angle GetWorldRotation(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetWorldRotation(query.GetComponent(uid), query);
return GetWorldRotation(_xformQuery.GetComponent(uid), _xformQuery);
}
// Temporary until it's moved here
@@ -982,7 +981,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Angle GetWorldRotation(TransformComponent component)
{
return GetWorldRotation(component, GetEntityQuery<TransformComponent>());
return GetWorldRotation(component, _xformQuery);
}
[Pure]
@@ -1050,7 +1049,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetWorldPositionRotation(TransformComponent component, Vector2 worldPos, Angle worldRot)
{
SetWorldPositionRotation(component, worldPos, worldRot, GetEntityQuery<TransformComponent>());
SetWorldPositionRotation(component, worldPos, worldRot, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1115,15 +1114,14 @@ public abstract partial class SharedTransformSystem
[Pure]
public Matrix3 GetInvWorldMatrix(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetInvWorldMatrix(query.GetComponent(uid), query);
return GetInvWorldMatrix(_xformQuery.GetComponent(uid), _xformQuery);
}
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Matrix3 GetInvWorldMatrix(TransformComponent component)
{
return GetInvWorldMatrix(component, GetEntityQuery<TransformComponent>());
return GetInvWorldMatrix(component, _xformQuery);
}
[Pure]
@@ -1148,15 +1146,14 @@ public abstract partial class SharedTransformSystem
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix)
GetWorldPositionRotationMatrix(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetWorldPositionRotationMatrix(query.GetComponent(uid), query);
return GetWorldPositionRotationMatrix(_xformQuery.GetComponent(uid), _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix)
GetWorldPositionRotationMatrix(TransformComponent xform)
{
return GetWorldPositionRotationMatrix(xform, GetEntityQuery<TransformComponent>());
return GetWorldPositionRotationMatrix(xform, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1186,7 +1183,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 InvWorldMatrix) GetWorldPositionRotationInvMatrix(TransformComponent xform)
{
return GetWorldPositionRotationInvMatrix(xform, GetEntityQuery<TransformComponent>());
return GetWorldPositionRotationInvMatrix(xform, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1210,15 +1207,14 @@ public abstract partial class SharedTransformSystem
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix)
GetWorldPositionRotationMatrixWithInv(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetWorldPositionRotationMatrixWithInv(query.GetComponent(uid), query);
return GetWorldPositionRotationMatrixWithInv(_xformQuery.GetComponent(uid), _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix)
GetWorldPositionRotationMatrixWithInv(TransformComponent xform)
{
return GetWorldPositionRotationMatrixWithInv(xform, GetEntityQuery<TransformComponent>());
return GetWorldPositionRotationMatrixWithInv(xform, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1241,9 +1237,8 @@ public abstract partial class SharedTransformSystem
#region AttachToGridOrMap
public void AttachToGridOrMap(EntityUid uid, TransformComponent? xform = null)
{
var query = GetEntityQuery<TransformComponent>();
if (query.Resolve(uid, ref xform))
AttachToGridOrMap(uid, xform, query);
if (_xformQuery.Resolve(uid, ref xform))
AttachToGridOrMap(uid, xform, _xformQuery);
}
public void AttachToGridOrMap(EntityUid uid, TransformComponent xform, EntityQuery<TransformComponent> query)
@@ -1281,18 +1276,17 @@ public abstract partial class SharedTransformSystem
public bool TryGetMapOrGridCoordinates(EntityUid uid, [NotNullWhen(true)] out EntityCoordinates? coordinates, TransformComponent? xform = null)
{
var query = GetEntityQuery<TransformComponent>();
coordinates = null;
if (!query.Resolve(uid, ref xform))
if (!_xformQuery.Resolve(uid, ref xform))
return false;
if (!xform.ParentUid.IsValid())
return false;
EntityUid newParent;
var oldPos = GetWorldPosition(xform, query);
if (_mapManager.TryFindGridAt(xform.MapID, oldPos, query, out var gridUid, out _))
var oldPos = GetWorldPosition(xform, _xformQuery);
if (_mapManager.TryFindGridAt(xform.MapID, oldPos, _xformQuery, out var gridUid, out _))
{
newParent = gridUid;
}
@@ -1305,7 +1299,7 @@ public abstract partial class SharedTransformSystem
return false;
}
coordinates = new(newParent, GetInvWorldMatrix(newParent, query).Transform(oldPos));
coordinates = new(newParent, GetInvWorldMatrix(newParent, _xformQuery).Transform(oldPos));
return true;
}
#endregion
@@ -1342,8 +1336,8 @@ public abstract partial class SharedTransformSystem
if (xform.Anchored && _metaQuery.TryGetComponent(xform.GridUid, out var meta) && meta.EntityLifeStage <= EntityLifeStage.MapInitialized)
{
var grid = Comp<MapGridComponent>(xform.GridUid.Value);
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
grid.RemoveFromSnapGridCell(tileIndices, uid);
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
_map.RemoveFromSnapGridCell(xform.GridUid.Value, grid, tileIndices, uid);
xform._anchored = false;
var anchorStateChangedEvent = new AnchorStateChangedEvent(xform, true);
RaiseLocalEvent(uid, ref anchorStateChangedEvent, true);
@@ -1362,7 +1356,7 @@ public abstract partial class SharedTransformSystem
{
if (LifeStage(uid) > EntityLifeStage.Initialized)
{
SetGridId(uid, component, uid, GetEntityQuery<TransformComponent>());
SetGridId(uid, component, uid, _xformQuery);
return;
}
component._gridInitialized = true;

View File

@@ -19,6 +19,7 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
private EntityQuery<MapGridComponent> _gridQuery;
@@ -254,7 +255,7 @@ namespace Robust.Shared.GameObjects
return GetWorldPosition(xform).Floored();
// We're on a grid, need to convert the coordinates to grid tiles.
return _mapManager.GetGrid(xform.GridUid.Value).CoordinatesToTile(xform.Coordinates);
return _map.CoordinatesToTile(xform.GridUid.Value, Comp<MapGridComponent>(xform.GridUid.Value), xform.Coordinates);
}
}

View File

@@ -1,20 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Map.Enumerators;
using Robust.Shared.Map.Events;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
@@ -27,10 +20,8 @@ namespace Robust.Shared.Map.Components
[NetworkedComponent]
public sealed class MapGridComponent : Component
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
private SharedMapSystem MapSystem => _entManager.System<SharedMapSystem>();
// This field is used for deserialization internally in the map loader.
// If you want to remove this, you would have to restructure the map save file.
@@ -40,6 +31,9 @@ namespace Robust.Shared.Map.Components
[DataField("chunkSize")] internal ushort ChunkSize = 16;
[ViewVariables]
public int ChunkCount => Chunks.Count;
/// <summary>
/// The length of the side of a square tile in world units.
/// </summary>
@@ -70,7 +64,7 @@ namespace Robust.Shared.Map.Components
internal readonly Dictionary<Vector2i, MapChunk> Chunks = new();
[ViewVariables]
public Box2 LocalAABB { get; private set; }
public Box2 LocalAABB { get; internal set; }
/// <summary>
/// Set to enable or disable grid splitting.
@@ -79,849 +73,221 @@ namespace Robust.Shared.Map.Components
[ViewVariables(VVAccess.ReadWrite), DataField("canSplit")]
public bool CanSplit = true;
internal void RemoveChunk(Vector2i origin)
{
if (!Chunks.TryGetValue(origin, out var chunk))
return;
if (_netManager.IsServer)
ChunkDeletionHistory.Add((_timing.CurTick, chunk.Indices));
chunk.Fixtures.Clear();
Chunks.Remove(origin);
if (Chunks.Count == 0)
_entMan.EventBus.RaiseLocalEvent(Owner, new EmptyGridEvent { GridId = Owner }, true);
}
/// <summary>
/// Regenerate collision for multiple chunks at once; faster than doing it individually.
/// </summary>
internal void RegenerateCollision(IReadOnlySet<MapChunk> chunks)
{
if (_entMan.HasComponent<MapComponent>(Owner))
return;
var chunkRectangles = new Dictionary<MapChunk, List<Box2i>>(chunks.Count);
var removedChunks = new List<MapChunk>();
var fixtureSystem = _entMan.EntitySysManager.GetEntitySystem<FixtureSystem>();
_entMan.EntitySysManager.TryGetEntitySystem(out SharedGridFixtureSystem? system);
foreach (var mapChunk in chunks)
{
// Even if the chunk is still removed still need to make sure bounds are updated (for now...)
// generate collision rectangles for this chunk based on filled tiles.
GridChunkPartition.PartitionChunk(mapChunk, out var localBounds, out var rectangles);
mapChunk.CachedBounds = localBounds;
if (mapChunk.FilledTiles > 0)
chunkRectangles.Add(mapChunk, rectangles);
else
{
// Gone. Reduced to atoms
// Need to do this before RemoveChunk because it clears fixtures.
FixturesComponent? manager = null;
PhysicsComponent? body = null;
TransformComponent? xform = null;
foreach (var fixture in mapChunk.Fixtures)
{
fixtureSystem.DestroyFixture(Owner, fixture, false, manager: manager, body: body, xform: xform);
}
RemoveChunk(mapChunk.Indices);
removedChunks.Add(mapChunk);
}
}
LocalAABB = new Box2();
foreach (var chunk in Chunks.Values)
{
var chunkBounds = chunk.CachedBounds;
if (chunkBounds.Size.Equals(Vector2i.Zero))
continue;
if (LocalAABB.Size == Vector2.Zero)
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
LocalAABB = gridBounds;
}
else
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
LocalAABB = LocalAABB.Union(gridBounds);
}
}
// May have been deleted from the bulk update above!
if (_entMan.Deleted(Owner))
return;
// TODO: Move this to the component when we combine.
_entMan.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>().WakeBody(Owner);
_entMan.EntitySysManager.GetEntitySystem<SharedMapSystem>().OnGridBoundsChange(Owner, this);
system?.RegenerateCollision(Owner, chunkRectangles, removedChunks);
}
/// <summary>
/// Regenerates the chunk local bounds of this chunk.
/// </summary>
internal void RegenerateCollision(MapChunk mapChunk)
{
RegenerateCollision(new HashSet<MapChunk> { mapChunk });
}
#region TileAccess
/// <inheritdoc />
public TileRef GetTileRef(MapCoordinates coords)
{
return GetTileRef(CoordinatesToTile(coords));
return MapSystem.GetTileRef(Owner, this, coords);
}
/// <inheritdoc />
public TileRef GetTileRef(EntityCoordinates coords)
{
return GetTileRef(CoordinatesToTile(coords));
return MapSystem.GetTileRef(Owner, this, coords);
}
/// <inheritdoc />
public TileRef GetTileRef(Vector2i tileCoordinates)
{
var chunkIndices = GridTileToChunkIndices(tileCoordinates);
if (!Chunks.TryGetValue(chunkIndices, out var output))
{
// Chunk doesn't exist, return a tileRef to an empty (space) tile.
return new TileRef(Owner, tileCoordinates.X, tileCoordinates.Y, default);
}
var chunkTileIndices = output.GridTileToChunkTile(tileCoordinates);
return GetTileRef(output, (ushort)chunkTileIndices.X, (ushort)chunkTileIndices.Y);
return MapSystem.GetTileRef(Owner, this, tileCoordinates);
}
/// <summary>
/// Returns the tile at the given chunk indices.
/// </summary>
/// <param name="mapChunk"></param>
/// <param name="xIndex">The X tile index relative to the chunk origin.</param>
/// <param name="yIndex">The Y tile index relative to the chunk origin.</param>
/// <returns>A reference to a tile.</returns>
internal TileRef GetTileRef(MapChunk mapChunk, ushort xIndex, ushort yIndex)
{
if (xIndex >= mapChunk.ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= mapChunk.ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
var indices = mapChunk.ChunkTileToGridTile(new Vector2i(xIndex, yIndex));
return new TileRef(Owner, indices, mapChunk.GetTile(xIndex, yIndex));
}
/// <inheritdoc />
public IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true)
{
foreach (var kvChunk in Chunks)
{
var chunk = kvChunk.Value;
for (ushort x = 0; x < ChunkSize; x++)
{
for (ushort y = 0; y < ChunkSize; y++)
{
var tile = chunk.GetTile(x, y);
if (ignoreEmpty && tile.IsEmpty)
continue;
var (gridX, gridY) = new Vector2i(x, y) + chunk.Indices * ChunkSize;
yield return new TileRef(Owner, gridX, gridY, tile);
}
}
}
return MapSystem.GetAllTiles(Owner, this, ignoreEmpty);
}
/// <inheritdoc />
public GridTileEnumerator GetAllTilesEnumerator(bool ignoreEmpty = true)
{
return new GridTileEnumerator(Owner, Chunks.GetEnumerator(), ChunkSize, ignoreEmpty);
return MapSystem.GetAllTilesEnumerator(Owner, this, ignoreEmpty);
}
/// <inheritdoc />
public void SetTile(EntityCoordinates coords, Tile tile)
{
var localTile = CoordinatesToTile(coords);
SetTile(new Vector2i(localTile.X, localTile.Y), tile);
MapSystem.SetTile(Owner, this, coords, tile);
}
/// <inheritdoc />
public void SetTile(Vector2i gridIndices, Tile tile)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridIndices);
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
// Ideally we'd to this here for consistency but apparently tile modified does it or something.
// Yeah it's noodly.
// RegenerateCollision(chunk);
MapSystem.SetTile(Owner, this, gridIndices, tile);
}
/// <inheritdoc />
public void SetTiles(List<(Vector2i GridIndices, Tile Tile)> tiles)
{
if (tiles.Count == 0) return;
var chunks = new HashSet<MapChunk>(Math.Max(1, tiles.Count / ChunkSize));
foreach (var (gridIndices, tile) in tiles)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridIndices);
chunks.Add(chunk);
chunk.SuppressCollisionRegeneration = true;
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
}
foreach (var chunk in chunks)
{
chunk.SuppressCollisionRegeneration = false;
}
RegenerateCollision(chunks);
MapSystem.SetTiles(Owner, this, tiles);
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(Box2Rotated localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var localAABB = localArea.CalcBoundingBox();
return GetLocalTilesIntersecting(localAABB, ignoreEmpty, predicate);
return MapSystem.GetLocalTilesIntersecting(Owner, this, localArea, ignoreEmpty, predicate);
}
/// <inheritdoc />
public IEnumerable<TileRef> GetTilesIntersecting(Box2Rotated worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(localArea, ignoreEmpty, predicate))
{
yield return tile;
}
return MapSystem.GetTilesIntersecting(Owner, this, worldArea, ignoreEmpty, predicate);
}
/// <inheritdoc />
public IEnumerable<TileRef> GetTilesIntersecting(Box2 worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(localArea, ignoreEmpty, predicate))
{
yield return tile;
}
return MapSystem.GetTilesIntersecting(Owner, this, worldArea, ignoreEmpty, predicate);
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(Box2 localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
// TODO: Should move the intersecting calls onto mapmanager system and then allow people to pass in xform / xformquery
// that way we can avoid the GetComp here.
var gridTileLb = new Vector2i((int)Math.Floor(localArea.Left), (int)Math.Floor(localArea.Bottom));
// If we have 20.1 we want to include that tile but if we have 20 then we don't.
var gridTileRt = new Vector2i((int)Math.Ceiling(localArea.Right), (int)Math.Ceiling(localArea.Top));
for (var x = gridTileLb.X; x < gridTileRt.X; x++)
{
for (var y = gridTileLb.Y; y < gridTileRt.Y; y++)
{
var gridChunk = GridTileToChunkIndices(new Vector2i(x, y));
if (Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(new Vector2i(x, y));
var tile = GetTileRef(chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
continue;
if (predicate == null || predicate(tile))
yield return tile;
}
else if (!ignoreEmpty)
{
var tile = new TileRef(Owner, x, y, new Tile());
if (predicate == null || predicate(tile))
yield return tile;
}
}
}
return MapSystem.GetLocalTilesIntersecting(Owner, this, localArea, ignoreEmpty, predicate);
}
/// <inheritdoc />
public IEnumerable<TileRef> GetTilesIntersecting(Circle worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var aabb = new Box2(worldArea.Position.X - worldArea.Radius, worldArea.Position.Y - worldArea.Radius,
worldArea.Position.X + worldArea.Radius, worldArea.Position.Y + worldArea.Radius);
var circleGridPos = new EntityCoordinates(Owner, WorldToLocal(worldArea.Position));
foreach (var tile in GetTilesIntersecting(aabb, ignoreEmpty, predicate))
{
var local = GridTileToLocal(tile.GridIndices);
if (!local.TryDistance(_entMan, circleGridPos, out var distance))
{
continue;
}
if (distance <= worldArea.Radius)
{
yield return tile;
}
}
}
private bool TryGetTile(Vector2i indices, bool ignoreEmpty, [NotNullWhen(true)] out TileRef? tileRef, Predicate<TileRef>? predicate = null)
{
// Similar to TryGetTileRef but for the tiles intersecting iterators.
var gridChunk = GridTileToChunkIndices(indices);
if (Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(indices);
var tile = GetTileRef(chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
{
tileRef = null;
return false;
}
if (predicate == null || predicate(tile))
{
tileRef = tile;
return true;
}
}
else if (!ignoreEmpty)
{
var tile = new TileRef(Owner, indices.X, indices.Y, Tile.Empty);
if (predicate == null || predicate(tile))
{
tileRef = tile;
return true;
}
}
tileRef = null;
return false;
return MapSystem.GetTilesIntersecting(Owner, this, worldArea, ignoreEmpty, predicate);
}
#endregion TileAccess
#region ChunkAccess
/// <summary>
/// The total number of allocated chunks in the grid.
/// </summary>
public int ChunkCount => Chunks.Count;
/// <inheritdoc />
internal MapChunk GetOrAddChunk(int xIndex, int yIndex)
{
return GetOrAddChunk(new Vector2i(xIndex, yIndex));
}
internal bool TryGetChunk(Vector2i chunkIndices, [NotNullWhen(true)] out MapChunk? chunk)
{
return Chunks.TryGetValue(chunkIndices, out chunk);
return MapSystem.TryGetChunk(Owner, this, chunkIndices, out chunk);
}
/// <inheritdoc />
internal MapChunk GetOrAddChunk(Vector2i chunkIndices)
{
if (Chunks.TryGetValue(chunkIndices, out var output))
return output;
var newChunk = new MapChunk(chunkIndices.X, chunkIndices.Y, ChunkSize);
newChunk.LastTileModifiedTick = _mapManager.GameTiming.CurTick;
if (Initialized)
newChunk.TileModified += OnTileModified;
return Chunks[chunkIndices] = newChunk;
}
/// <inheritdoc />
public bool HasChunk(Vector2i chunkIndices)
{
return Chunks.ContainsKey(chunkIndices);
}
/// <inheritdoc />
internal IReadOnlyDictionary<Vector2i, MapChunk> GetMapChunks()
{
return Chunks;
return MapSystem.GetMapChunks(Owner, this);
}
/// <inheritdoc />
internal ChunkEnumerator GetMapChunks(Box2 worldAABB)
{
var localAABB = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix
.TransformBox(worldAABB);
return new ChunkEnumerator(Chunks, localAABB, ChunkSize);
}
/// <inheritdoc />
internal ChunkEnumerator GetMapChunks(Box2Rotated worldArea)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
var localArea = matrix.TransformBox(worldArea);
return new ChunkEnumerator(Chunks, localArea, ChunkSize);
}
internal ChunkEnumerator GetLocalMapChunks(Box2 localAABB)
{
return new ChunkEnumerator(Chunks, localAABB, ChunkSize);
return MapSystem.GetMapChunks(Owner, this, worldArea);
}
#endregion ChunkAccess
#region SnapGridAccess
/// <inheritdoc />
public int AnchoredEntityCount(Vector2i pos)
{
var gridChunkPos = GridTileToChunkIndices(pos);
if (!Chunks.TryGetValue(gridChunkPos, out var chunk)) return 0;
var (x, y) = chunk.GridTileToChunkTile(pos);
return chunk.GetSnapGrid((ushort)x, (ushort)y)?.Count ?? 0; // ?
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(MapCoordinates coords)
{
return GetAnchoredEntities(TileIndicesFor(coords));
return MapSystem.GetAnchoredEntities(Owner, this, coords);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(EntityCoordinates coords)
{
return GetAnchoredEntities(TileIndicesFor(coords));
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(Vector2i pos)
{
// Because some content stuff checks neighboring tiles (which may not actually exist) we won't just
// create an entire chunk for it.
var gridChunkPos = GridTileToChunkIndices(pos);
if (!Chunks.TryGetValue(gridChunkPos, out var chunk)) return Enumerable.Empty<EntityUid>();
var chunkTile = chunk.GridTileToChunkTile(pos);
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y);
return MapSystem.GetAnchoredEntities(Owner, this, pos);
}
public AnchoredEntitiesEnumerator GetAnchoredEntitiesEnumerator(Vector2i pos)
{
var gridChunkPos = GridTileToChunkIndices(pos);
if (!Chunks.TryGetValue(gridChunkPos, out var chunk)) return AnchoredEntitiesEnumerator.Empty;
var chunkTile = chunk.GridTileToChunkTile(pos);
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
return snapgrid == null
? AnchoredEntitiesEnumerator.Empty
: new AnchoredEntitiesEnumerator(snapgrid.GetEnumerator());
return MapSystem.GetAnchoredEntitiesEnumerator(Owner, this, pos);
}
public IEnumerable<EntityUid> GetLocalAnchoredEntities(Box2 localAABB)
{
foreach (var tile in GetLocalTilesIntersecting(localAABB, true, null))
{
foreach (var ent in GetAnchoredEntities(tile.GridIndices))
{
yield return ent;
}
}
return MapSystem.GetLocalAnchoredEntities(Owner, this, localAABB);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(Box2 worldAABB)
{
foreach (var tile in GetTilesIntersecting(worldAABB))
{
foreach (var ent in GetAnchoredEntities(tile.GridIndices))
{
yield return ent;
}
}
return MapSystem.GetAnchoredEntities(Owner, this, worldAABB);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(Box2Rotated worldBounds)
{
foreach (var tile in GetTilesIntersecting(worldBounds))
{
foreach (var ent in GetAnchoredEntities(tile.GridIndices))
{
yield return ent;
}
}
}
/// <inheritdoc />
public Vector2i TileIndicesFor(EntityCoordinates coords)
{
#if DEBUG
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
DebugTools.Assert(mapId == coords.GetMapId(_entMan));
#endif
return SnapGridLocalCellFor(LocalToGrid(coords));
return MapSystem.TileIndicesFor(Owner, this, coords);
}
/// <inheritdoc />
public Vector2i TileIndicesFor(MapCoordinates worldPos)
{
#if DEBUG
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
DebugTools.Assert(mapId == worldPos.MapId);
#endif
var localPos = WorldToLocal(worldPos.Position);
return SnapGridLocalCellFor(localPos);
return MapSystem.TileIndicesFor(Owner, this, worldPos);
}
private Vector2i SnapGridLocalCellFor(Vector2 localPos)
{
var x = (int)Math.Floor(localPos.X / TileSize);
var y = (int)Math.Floor(localPos.Y / TileSize);
return new Vector2i(x, y);
}
public bool IsAnchored(EntityCoordinates coords, EntityUid euid)
{
var tilePos = TileIndicesFor(coords);
var (chunk, chunkTile) = ChunkAndOffsetForTile(tilePos);
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
return snapgrid?.Contains(euid) == true;
}
/// <inheritdoc />
public bool AddToSnapGridCell(Vector2i pos, EntityUid euid)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
if (chunk.GetTile((ushort)chunkTile.X, (ushort)chunkTile.Y).IsEmpty)
return false;
chunk.AddToSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
return true;
}
/// <inheritdoc />
public bool AddToSnapGridCell(EntityCoordinates coords, EntityUid euid)
{
return AddToSnapGridCell(TileIndicesFor(coords), euid);
}
/// <inheritdoc />
public void RemoveFromSnapGridCell(Vector2i pos, EntityUid euid)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
chunk.RemoveFromSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
}
/// <inheritdoc />
public void RemoveFromSnapGridCell(EntityCoordinates coords, EntityUid euid)
{
RemoveFromSnapGridCell(TileIndicesFor(coords), euid);
}
private (MapChunk, Vector2i) ChunkAndOffsetForTile(Vector2i pos)
{
var gridChunkIndices = GridTileToChunkIndices(pos);
var chunk = GetOrAddChunk(gridChunkIndices);
var chunkTile = chunk.GridTileToChunkTile(pos);
return (chunk, chunkTile);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetInDir(EntityCoordinates position, Direction dir)
{
var pos = SharedMapSystem.GetDirection(TileIndicesFor(position), dir);
return GetAnchoredEntities(pos);
return MapSystem.GetInDir(Owner, this, position, dir);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetOffset(EntityCoordinates coords, Vector2i offset)
{
var pos = TileIndicesFor(coords) + offset;
return GetAnchoredEntities(pos);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetLocal(EntityCoordinates coords)
{
return GetAnchoredEntities(TileIndicesFor(coords));
return MapSystem.GetLocal(Owner, this, coords);
}
/// <inheritdoc />
public EntityCoordinates DirectionToGrid(EntityCoordinates coords, Direction direction)
{
return GridTileToLocal(SharedMapSystem.GetDirection(TileIndicesFor(coords), direction));
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetCardinalNeighborCells(EntityCoordinates coords)
{
var position = TileIndicesFor(coords);
foreach (var cell in GetAnchoredEntities(position))
yield return cell;
foreach (var cell in GetAnchoredEntities(position + new Vector2i(0, 1)))
yield return cell;
foreach (var cell in GetAnchoredEntities(position + new Vector2i(0, -1)))
yield return cell;
foreach (var cell in GetAnchoredEntities(position + new Vector2i(1, 0)))
yield return cell;
foreach (var cell in GetAnchoredEntities(position + new Vector2i(-1, 0)))
yield return cell;
return MapSystem.GetCardinalNeighborCells(Owner, this, coords);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetCellsInSquareArea(EntityCoordinates coords, int n)
{
var position = TileIndicesFor(coords);
for (var y = -n; y <= n; ++y)
for (var x = -n; x <= n; ++x)
{
var enumerator = GetAnchoredEntitiesEnumerator(position + new Vector2i(x, y));
while (enumerator.MoveNext(out var cell))
{
yield return cell.Value;
}
}
return MapSystem.GetCellsInSquareArea(Owner, this, coords, n);
}
#endregion
#region Transforms
/// <inheritdoc />
public Vector2 WorldToLocal(Vector2 posWorld)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
return matrix.Transform(posWorld);
return MapSystem.WorldToLocal(Owner, this, posWorld);
}
/// <inheritdoc />
public EntityCoordinates MapToGrid(MapCoordinates posWorld)
{
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
if (posWorld.MapId != mapId)
throw new ArgumentException(
$"Grid {Owner} is on map {mapId}, but coords are on map {posWorld.MapId}.",
nameof(posWorld));
if (!_mapManager.TryGetGrid(Owner, out var grid))
{
return new EntityCoordinates(_mapManager.GetMapEntityId(posWorld.MapId), new Vector2(posWorld.X, posWorld.Y));
}
return new EntityCoordinates(((Component) grid).Owner, WorldToLocal(posWorld.Position));
return MapSystem.MapToGrid(Owner, posWorld);
}
/// <inheritdoc />
public Vector2 LocalToWorld(Vector2 posLocal)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).WorldMatrix;
return matrix.Transform(posLocal);
return MapSystem.LocalToWorld(Owner, this, posLocal);
}
public Vector2i WorldToTile(Vector2 posWorld)
{
var local = WorldToLocal(posWorld);
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new Vector2i(x, y);
return MapSystem.WorldToTile(Owner, this, posWorld);
}
public Vector2i LocalToTile(EntityCoordinates coordinates)
{
var position = LocalToGrid(coordinates);
return new Vector2i((int) Math.Floor(position.X / TileSize), (int) Math.Floor(position.Y / TileSize));
return MapSystem.LocalToTile(Owner, this, coordinates);
}
/// <inheritdoc />
public Vector2i CoordinatesToTile(MapCoordinates coords)
{
#if DEBUG
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
DebugTools.Assert(mapId == coords.MapId);
#endif
var local = WorldToLocal(coords.Position);
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new Vector2i(x, y);
}
/// <inheritdoc />
public Vector2i CoordinatesToTile(EntityCoordinates coords)
{
#if DEBUG
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
DebugTools.Assert(mapId == coords.GetMapId(_entMan));
#endif
var local = LocalToGrid(coords);
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new Vector2i(x, y);
}
/// <inheritdoc />
public Vector2i LocalToChunkIndices(EntityCoordinates gridPos)
{
var local = LocalToGrid(gridPos);
var x = (int)Math.Floor(local.X / (TileSize * ChunkSize));
var y = (int)Math.Floor(local.Y / (TileSize * ChunkSize));
return new Vector2i(x, y);
}
public Vector2 LocalToGrid(EntityCoordinates position)
{
return position.EntityId == Owner
? position.Position
: WorldToLocal(position.ToMapPos(_entMan));
return MapSystem.CoordinatesToTile(Owner, this, coords);
}
public bool CollidesWithGrid(Vector2i indices)
{
var chunkIndices = GridTileToChunkIndices(indices);
if (!Chunks.TryGetValue(chunkIndices, out var chunk))
return false;
var cTileIndices = chunk.GridTileToChunkTile(indices);
return chunk.GetTile((ushort)cTileIndices.X, (ushort)cTileIndices.Y).TypeId != Tile.Empty.TypeId;
return MapSystem.CollidesWithGrid(Owner, this, indices);
}
/// <inheritdoc />
public Vector2i GridTileToChunkIndices(Vector2i gridTile)
{
var x = (int)Math.Floor(gridTile.X / (float)ChunkSize);
var y = (int)Math.Floor(gridTile.Y / (float)ChunkSize);
return new Vector2i(x, y);
return MapSystem.GridTileToChunkIndices(Owner, this, gridTile);
}
/// <inheritdoc />
public EntityCoordinates GridTileToLocal(Vector2i gridTile)
{
return new(Owner,
new Vector2(gridTile.X * TileSize + (TileSize / 2f), gridTile.Y * TileSize + (TileSize / 2f)));
return MapSystem.GridTileToLocal(Owner, this, gridTile);
}
public Vector2 GridTileToWorldPos(Vector2i gridTile)
{
var locX = gridTile.X * TileSize + (TileSize / 2f);
var locY = gridTile.Y * TileSize + (TileSize / 2f);
var xform = _entMan.GetComponent<TransformComponent>(Owner);
return xform.WorldMatrix.Transform(new Vector2(locX, locY));
return MapSystem.GridTileToWorldPos(Owner, this, gridTile);
}
public MapCoordinates GridTileToWorld(Vector2i gridTile)
{
var parentMapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
return new(GridTileToWorldPos(gridTile), parentMapId);
return MapSystem.GridTileToWorld(Owner, this, gridTile);
}
/// <inheritdoc />
public bool TryGetTileRef(Vector2i indices, out TileRef tile)
{
var chunkIndices = GridTileToChunkIndices(indices);
if (!Chunks.TryGetValue(chunkIndices, out var chunk))
{
tile = default;
return false;
}
var cTileIndices = chunk.GridTileToChunkTile(indices);
tile = GetTileRef(chunk, (ushort)cTileIndices.X, (ushort)cTileIndices.Y);
return true;
return MapSystem.TryGetTileRef(Owner, this, indices, out tile);
}
/// <inheritdoc />
public bool TryGetTileRef(EntityCoordinates coords, out TileRef tile)
{
return TryGetTileRef(CoordinatesToTile(coords), out tile);
}
/// <inheritdoc />
public bool TryGetTileRef(Vector2 worldPos, out TileRef tile)
{
return TryGetTileRef(WorldToTile(worldPos), out tile);
}
#endregion Transforms
/// <summary>
/// Calculate the world space AABB for this chunk.
/// </summary>
internal Box2 CalcWorldAABB(MapChunk mapChunk)
{
var (position, rotation) =
_entMan.GetComponent<TransformComponent>(Owner).GetWorldPositionRotation();
var chunkPosition = mapChunk.Indices;
var tileScale = TileSize;
var chunkScale = mapChunk.ChunkSize;
var worldPos = position + rotation.RotateVec(chunkPosition * tileScale * chunkScale);
return new Box2Rotated(
((Box2)mapChunk.CachedBounds
.Scale(tileScale))
.Translated(worldPos),
rotation, worldPos).CalcBoundingBox();
}
internal void OnTileModified(MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile,
bool shapeChanged)
{
// As the collision regeneration can potentially delete the chunk we'll notify of the tile changed first.
var gridTile = mapChunk.ChunkTileToGridTile(tileIndices);
mapChunk.LastTileModifiedTick = _mapManager.GameTiming.CurTick;
LastTileModifiedTick = _mapManager.GameTiming.CurTick;
_entMan.Dirty(this);
// The map serializer currently sets tiles of unbound grids as part of the deserialization process
// It properly sets SuppressOnTileChanged so that the event isn't spammed for every tile on the grid.
// ParentMapId is not able to be accessed on unbound grids, so we can't even call this function for unbound grids.
if (!_mapManager.SuppressOnTileChanged)
{
var newTileRef = new TileRef(Owner, gridTile, newTile);
_mapManager.RaiseOnTileChanged(newTileRef, oldTile);
}
if (shapeChanged && !mapChunk.SuppressCollisionRegeneration)
{
RegenerateCollision(mapChunk);
}
return MapSystem.TryGetTileRef(Owner, this, coords, out tile);
}
}

View File

@@ -24,66 +24,67 @@ namespace Robust.Shared.Map
IoCManager.Resolve(ref entityManager, ref mapManager);
var gridId = coords.GetGridUid(entityManager);
var mapSystem = entityManager.System<SharedMapSystem>();
if (mapManager.TryGetGrid(gridId, out var mapGrid))
{
return mapGrid.GridTileToLocal(mapGrid.CoordinatesToTile(coords));
return mapSystem.GridTileToLocal(gridId.Value, mapGrid, mapSystem.CoordinatesToTile(gridId.Value, mapGrid, coords));
}
else
var mapCoords = coords.ToMap(entityManager);
if (mapManager.TryFindGridAt(mapCoords, out var gridUid, out mapGrid))
{
var mapCoords = coords.ToMap(entityManager);
if (mapManager.TryFindGridAt(mapCoords, out _, out mapGrid))
{
return mapGrid.GridTileToLocal(mapGrid.CoordinatesToTile(coords));
}
// create a box around the cursor
var gridSearchBox = Box2.UnitCentered.Scale(searchBoxSize).Translated(mapCoords.Position);
// find grids in search box
var gridsInArea = mapManager.FindGridsIntersecting(mapCoords.MapId, gridSearchBox);
// find closest grid intersecting our search box.
MapGridComponent? closest = null;
var distance = float.PositiveInfinity;
var intersect = new Box2();
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
foreach (var grid in gridsInArea)
{
var gridXform = xformQuery.GetComponent(grid.Owner);
// TODO: Use CollisionManager to get nearest edge.
// figure out closest intersect
var gridIntersect = gridSearchBox.Intersect(gridXform.WorldMatrix.TransformBox(grid.LocalAABB));
var gridDist = (gridIntersect.Center - mapCoords.Position).LengthSquared();
if (gridDist >= distance)
continue;
distance = gridDist;
closest = grid;
intersect = gridIntersect;
}
if (closest != null) // stick to existing grid
{
// round to nearest cardinal dir
var normal = mapCoords.Position - intersect.Center;
// round coords to center of tile
var tileIndices = closest.WorldToTile(intersect.Center);
var tileCenterWorld = closest.GridTileToWorldPos(tileIndices);
// move mouse one tile out along normal
var newTilePos = tileCenterWorld + normal * closest.TileSize;
coords = new EntityCoordinates(closest.Owner, closest.WorldToLocal(newTilePos));
}
//else free place
return mapSystem.GridTileToLocal(gridUid, mapGrid, mapSystem.CoordinatesToTile(gridUid, mapGrid, coords));
}
// create a box around the cursor
var gridSearchBox = Box2.UnitCentered.Scale(searchBoxSize).Translated(mapCoords.Position);
// find grids in search box
var gridsInArea = mapManager.FindGridsIntersecting(mapCoords.MapId, gridSearchBox);
// find closest grid intersecting our search box.
gridUid = EntityUid.Invalid;
MapGridComponent? closest = null;
var distance = float.PositiveInfinity;
var intersect = new Box2();
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
foreach (var grid in gridsInArea)
{
var gridXform = xformQuery.GetComponent(grid.Owner);
// TODO: Use CollisionManager to get nearest edge.
// figure out closest intersect
var gridIntersect = gridSearchBox.Intersect(gridXform.WorldMatrix.TransformBox(grid.LocalAABB));
var gridDist = (gridIntersect.Center - mapCoords.Position).LengthSquared();
if (gridDist >= distance)
continue;
gridUid = grid.Owner;
distance = gridDist;
closest = grid;
intersect = gridIntersect;
}
if (closest != null) // stick to existing grid
{
// round to nearest cardinal dir
var normal = mapCoords.Position - intersect.Center;
// round coords to center of tile
var tileIndices = mapSystem.WorldToTile(gridUid, closest, intersect.Center);
var tileCenterWorld = mapSystem.GridTileToWorldPos(gridUid, closest, tileIndices);
// move mouse one tile out along normal
var newTilePos = tileCenterWorld + normal * closest.TileSize;
coords = new EntityCoordinates(gridUid, mapSystem.WorldToLocal(gridUid, closest, newTilePos));
}
//else free place
return coords;
}
}

View File

@@ -191,10 +191,12 @@ namespace Robust.Shared.Map
if(!IsValid(entityManager))
return new Vector2i();
var mapSystem = entityManager.System<SharedMapSystem>();
var gridIdOpt = GetGridUid(entityManager);
if (gridIdOpt is { } gridId && gridId.IsValid())
{
return mapManager.GetGrid(gridId).GetTileRef(this).GridIndices;
var grid = mapManager.GetGrid(gridId);
return mapSystem.GetTileRef(gridId, grid, this).GridIndices;
}
var vec = ToMapPos(entityManager, transformSystem);

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Robust.Shared.Map.Events;
/// <summary>
/// Raised directed on a grid to get its bounds.
/// </summary>
/// <remarks>
/// Really this exists to get around test dependency creeping.
/// </remarks>
[ByRefEvent]
internal readonly record struct RegenerateGridBoundsEvent(EntityUid Entity, Dictionary<MapChunk, List<Box2i>> ChunkRectangles, List<MapChunk> RemovedChunks)
{
public readonly EntityUid Entity = Entity;
public readonly Dictionary<MapChunk, List<Box2i>> ChunkRectangles = ChunkRectangles;
public readonly List<MapChunk> RemovedChunks = RemovedChunks;
}

View File

@@ -22,15 +22,9 @@ namespace Robust.Shared.Map
private readonly Vector2i _gridIndices;
[ViewVariables]
private readonly Tile[,] _tiles;
[ViewVariables] internal readonly Tile[,] Tiles;
private readonly SnapGridCell[,] _snapGrid;
/// <summary>
/// Invoked when a tile is modified on this chunk.
/// </summary>
public event TileModifiedDelegate? TileModified;
/// <summary>
/// Keeps a running count of the number of filled tiles in this chunk.
/// </summary>
@@ -38,7 +32,7 @@ namespace Robust.Shared.Map
/// This will always be between 1 and <see cref="ChunkSize"/>^2.
/// </remarks>
[ViewVariables]
internal int FilledTiles { get; private set; }
internal int FilledTiles { get; set; }
/// <summary>
/// Chunk-local AABB of this chunk.
@@ -73,7 +67,7 @@ namespace Robust.Shared.Map
_gridIndices = new Vector2i(x, y);
ChunkSize = chunkSize;
_tiles = new Tile[ChunkSize, ChunkSize];
Tiles = new Tile[ChunkSize, ChunkSize];
_snapGrid = new SnapGridCell[ChunkSize, ChunkSize];
}
@@ -113,58 +107,12 @@ namespace Robust.Shared.Map
if (yIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
return _tiles[xIndex, yIndex];
return Tiles[xIndex, yIndex];
}
public Tile GetTile(Vector2i indices)
{
return _tiles[indices.X, indices.Y];
}
/// <summary>
/// Replaces a single tile inside of the chunk.
/// </summary>
/// <param name="xIndex">The X tile index relative to the chunk.</param>
/// <param name="yIndex">The Y tile index relative to the chunk.</param>
/// <param name="tile">The new tile to insert.</param>
public void SetTile(ushort xIndex, ushort yIndex, Tile tile)
{
if (xIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
// same tile, no point to continue
if (_tiles[xIndex, yIndex] == tile)
return;
var oldTile = _tiles[xIndex, yIndex];
var oldFilledTiles = FilledTiles;
if (oldTile.IsEmpty != tile.IsEmpty)
{
if (oldTile.IsEmpty)
{
FilledTiles += 1;
}
else
{
FilledTiles -= 1;
}
}
var shapeChanged = oldFilledTiles != FilledTiles;
DebugTools.Assert(FilledTiles >= 0);
_tiles[xIndex, yIndex] = tile;
var tileIndices = new Vector2i(xIndex, yIndex);
// God I hate C# events sometimes.
DebugTools.Assert(TileModified == null || TileModified.GetInvocationList().Length <= 1);
TileModified?.Invoke(this, tileIndices, tile, oldTile, shapeChanged);
return Tiles[indices.X, indices.Y];
}
/// <summary>
@@ -266,6 +214,48 @@ namespace Robust.Shared.Map
{
public List<EntityUid>? Center;
}
/// <summary>
/// Sets the tile without any callbacks.
/// Do not call this unless you know what you are doing.
/// </summary>
internal bool TrySetTile(ushort xIndex, ushort yIndex, Tile tile, out Tile oldTile, out bool shapeChanged)
{
if (xIndex >= Tiles.Length)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= Tiles.Length)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
// same tile, no point to continue
if (Tiles[xIndex, yIndex] == tile)
{
oldTile = Tile.Empty;
shapeChanged = false;
return false;
}
oldTile = Tiles[xIndex, yIndex];
var oldFilledTiles = FilledTiles;
if (oldTile.IsEmpty != tile.IsEmpty)
{
if (oldTile.IsEmpty)
{
FilledTiles += 1;
}
else
{
FilledTiles -= 1;
}
}
shapeChanged = oldFilledTiles != FilledTiles;
DebugTools.Assert(FilledTiles >= 0);
Tiles[xIndex, yIndex] = tile;
return true;
}
}
/// <summary>
@@ -276,5 +266,5 @@ namespace Robust.Shared.Map
/// <param name="newTile">New version of the tile.</param>
/// <param name="oldTile">Old version of the tile.</param>
/// <param name="chunkShapeChanged">If changing this tile changed the shape of the chunk.</param>
internal delegate void TileModifiedDelegate(MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile, bool chunkShapeChanged);
internal delegate void TileModifiedDelegate(EntityUid uid, MapGridComponent grid, MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile, bool chunkShapeChanged);
}

View File

@@ -1,13 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
namespace Robust.Shared.Map;
@@ -29,24 +27,20 @@ internal partial class MapManager
return;
}
var physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformSystem = EntityManager.System<SharedTransformSystem>();
var state = (worldAABB, gridTree.Tree, callback, approx, physicsQuery, xformQuery, xformSystem);
var state = (worldAABB, gridTree.Tree, callback, approx, this, _transformSystem);
gridTree.Tree.Query(ref state,
static (ref (Box2 worldAABB,
B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> gridTree,
GridCallback callback,
bool approx,
EntityQuery<PhysicsComponent> physicsQuery, EntityQuery<TransformComponent> xformQuery,
MapManager mapManager,
SharedTransformSystem xformSystem) tuple,
DynamicTree.Proxy proxy) =>
{
var data = tuple.gridTree.GetUserData(proxy);
if (!tuple.approx && !IsIntersecting(tuple.worldAABB, data.Uid, data.Grid,
tuple.physicsQuery, tuple.xformQuery, tuple.xformSystem))
if (!tuple.approx && !tuple.mapManager.IsIntersecting(tuple.worldAABB, data.Uid, data.Grid))
{
return true;
}
@@ -77,10 +71,7 @@ internal partial class MapManager
callback(mapUid, grid, ref state);
}
var physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformSystem = EntityManager.System<SharedTransformSystem>();
var state2 = (state, worldAABB, gridTree.Tree, callback, approx, physicsQuery, xformQuery, xformSystem);
var state2 = (state, worldAABB, gridTree.Tree, callback, approx, this, _transformSystem);
gridTree.Tree.Query(ref state2, static (ref (
TState state,
@@ -88,15 +79,13 @@ internal partial class MapManager
B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> gridTree,
GridCallback<TState> callback,
bool approx,
EntityQuery<PhysicsComponent> physicsQuery,
EntityQuery<TransformComponent> xformQuery,
MapManager mapManager,
SharedTransformSystem xformSystem) tuple,
DynamicTree.Proxy proxy) =>
{
var data = tuple.gridTree.GetUserData(proxy);
if (!tuple.approx && !IsIntersecting(tuple.worldAABB, data.Uid, data.Grid,
tuple.physicsQuery, tuple.xformQuery, tuple.xformSystem))
if (!tuple.approx && !tuple.mapManager.IsIntersecting(tuple.worldAABB, data.Uid, data.Grid))
{
return true;
}
@@ -119,22 +108,18 @@ internal partial class MapManager
FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox(), ref state, callback, approx, includeMap);
}
private static bool IsIntersecting(
private bool IsIntersecting(
Box2 aabb,
EntityUid gridUid,
MapGridComponent grid,
EntityQuery<PhysicsComponent> physicsQuery,
EntityQuery<TransformComponent> xformQuery,
SharedTransformSystem xformSystem)
MapGridComponent grid)
{
var xformComp = xformQuery.GetComponent(gridUid);
var (worldPos, worldRot, matrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(xformComp, xformQuery);
var (worldPos, worldRot, matrix, invMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridUid);
var overlap = matrix.TransformBox(grid.LocalAABB).Intersect(aabb);
var localAABB = invMatrix.TransformBox(overlap);
if (physicsQuery.HasComponent(gridUid))
if (_physicsQuery.HasComponent(gridUid))
{
var enumerator = grid.GetLocalMapChunks(localAABB);
var enumerator = _mapSystem.GetLocalMapChunks(gridUid, grid, localAABB);
var transform = new Transform(worldPos, worldRot);
@@ -186,14 +171,13 @@ internal partial class MapManager
uid = EntityUid.Invalid;
grid = null;
var xformSystem = EntityManager.System<SharedTransformSystem>();
var state = (uid, grid, worldPos, xformQuery, xformSystem);
var state = (uid, grid, worldPos, _mapSystem, _transformSystem);
FindGridsIntersecting(mapId, aabb, ref state, static (EntityUid iUid, MapGridComponent iGrid, ref (
EntityUid uid,
MapGridComponent? grid,
Vector2 worldPos,
EntityQuery<TransformComponent> xformQuery,
SharedMapSystem mapSystem,
SharedTransformSystem xformSystem) tuple) =>
{
// Turn the worldPos into a localPos and work out the relevant chunk we need to check
@@ -201,7 +185,7 @@ internal partial class MapManager
// (though now we need some extra calcs up front).
// Doesn't use WorldBounds because it's just an AABB.
var matrix = tuple.xformSystem.GetInvWorldMatrix(iUid, tuple.xformQuery);
var matrix = tuple.xformSystem.GetInvWorldMatrix(iUid);
var localPos = matrix.Transform(tuple.worldPos);
// NOTE:
@@ -209,9 +193,10 @@ internal partial class MapManager
// you account for the fact that fixtures are shrunk slightly!
var chunkIndices = SharedMapSystem.GetChunkIndices(localPos, iGrid.ChunkSize);
if (!iGrid.HasChunk(chunkIndices)) return true;
if (!tuple.mapSystem.HasChunk(iUid, iGrid, chunkIndices))
return true;
var chunk = iGrid.GetOrAddChunk(chunkIndices);
var chunk = tuple.mapSystem.GetOrAddChunk(iUid, iGrid, chunkIndices);
var chunkRelative = SharedMapSystem.GetChunkRelative(localPos, iGrid.ChunkSize);
var chunkTile = chunk.GetTile(chunkRelative);
@@ -241,8 +226,7 @@ internal partial class MapManager
/// </summary>
public bool TryFindGridAt(MapId mapId, Vector2 worldPos, out EntityUid uid, [NotNullWhen(true)] out MapGridComponent? grid)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
return TryFindGridAt(mapId, worldPos, xformQuery, out uid, out grid);
return TryFindGridAt(mapId, worldPos, _xformQuery, out uid, out grid);
}
/// <summary>

View File

@@ -4,6 +4,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -20,9 +21,17 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
private ISawmill _sawmill = default!;
private SharedMapSystem _mapSystem = default!;
private SharedTransformSystem _transformSystem = default!;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<TransformComponent> _xformQuery;
/// <inheritdoc />
public void Initialize()
{
_physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
_xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
_sawmill = Logger.GetSawmill("map");
#if DEBUG
@@ -36,6 +45,9 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
/// <inheritdoc />
public void Startup()
{
_transformSystem = EntityManager.System<SharedTransformSystem>();
_mapSystem = EntityManager.System<SharedMapSystem>();
#if DEBUG
DebugTools.Assert(_dbgGuardInit);
_dbgGuardRunning = true;
@@ -52,9 +64,12 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
#endif
_sawmill.Debug("Stopping...");
foreach (var mapComp in EntityManager.EntityQuery<MapComponent>())
// TODO: AllEntityQuery instead???
var query = EntityManager.EntityQueryEnumerator<MapComponent>();
while (query.MoveNext(out var uid, out _))
{
EntityManager.DeleteEntity(mapComp.Owner);
EntityManager.DeleteEntity(uid);
}
}
@@ -65,9 +80,11 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
// Don't just call Shutdown / Startup because we don't want to touch the subscriptions on gridtrees
// Restart can be called any time during a game, whereas shutdown / startup are typically called upon connection.
foreach (var mapComp in EntityManager.EntityQuery<MapComponent>())
var query = EntityManager.EntityQueryEnumerator<MapComponent>();
while (query.MoveNext(out var uid, out _))
{
EntityManager.DeleteEntity(mapComp.Owner);
EntityManager.DeleteEntity(uid);
}
}

View File

@@ -20,15 +20,19 @@ namespace Robust.Shared.Physics.Systems
{
public abstract class SharedBroadphaseSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
[Dependency] private readonly IParallelManager _parallel = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
[Dependency] private readonly SharedGridTraversalSystem _traversal = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private ISawmill _logger = default!;
private EntityQuery<BroadphaseComponent> _broadphaseQuery;
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<TransformComponent> _xformQuery;
/*
* Okay so Box2D has its own "MoveProxy" stuff so you can easily find new contacts when required.
@@ -53,9 +57,12 @@ namespace Robust.Shared.Physics.Systems
{
base.Initialize();
_logger = Logger.GetSawmill("physics");
UpdatesOutsidePrediction = true;
_broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
UpdatesOutsidePrediction = true;
UpdatesAfter.Add(typeof(SharedTransformSystem));
_cfg.OnValueChanged(CVars.BroadphaseExpand, SetBroadphaseExpand, true);
@@ -79,27 +86,24 @@ namespace Robust.Shared.Physics.Systems
PhysicsMapComponent component,
MapId mapId,
HashSet<EntityUid> movedGrids,
Dictionary<FixtureProxy, Box2> gridMoveBuffer,
EntityQuery<BroadphaseComponent> broadQuery,
EntityQuery<TransformComponent> xformQuery)
Dictionary<FixtureProxy, Box2> gridMoveBuffer)
{
// None moved this tick
if (movedGrids.Count == 0) return;
var mapBroadphase = broadQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
var mapBroadphase = _broadphaseQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
// This is so that if we're on a broadphase that's moving (e.g. a grid) we need to make sure anything
// we move over is getting checked for collisions, and putting it on the movebuffer is the easiest way to do so.
var moveBuffer = component.MoveBuffer;
var gridQuery = GetEntityQuery<MapGridComponent>();
foreach (var gridUid in movedGrids)
{
var grid = gridQuery.GetComponent(gridUid);
var xform = xformQuery.GetComponent(gridUid);
var grid = _gridQuery.GetComponent(gridUid);
var xform = _xformQuery.GetComponent(gridUid);
DebugTools.Assert(xform.MapID == mapId);
var worldAABB = _transform.GetWorldMatrix(xform, xformQuery).TransformBox(grid.LocalAABB);
var worldAABB = _transform.GetWorldMatrix(xform).TransformBox(grid.LocalAABB);
var enlargedAABB = worldAABB.Enlarged(_broadphaseExpand);
var state = (moveBuffer, gridMoveBuffer);
@@ -111,7 +115,7 @@ namespace Robust.Shared.Physics.Systems
{
moveBuffer[proxy] = worldAABB;
// If something is in our AABB then try grid traversal for it
_traversal.CheckTraverse(proxy.Entity, xformQuery.GetComponent(proxy.Entity));
_traversal.CheckTraverse(proxy.Entity, _xformQuery.GetComponent(proxy.Entity));
}
}
@@ -157,12 +161,8 @@ namespace Robust.Shared.Physics.Systems
var movedGrids = Comp<MovedGridsComponent>(mapUid).MovedGrids;
var gridMoveBuffer = new Dictionary<FixtureProxy, Box2>();
var broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
var physicsQuery = GetEntityQuery<PhysicsComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
// Find any entities being driven over that might need to be considered
FindGridContacts(component, mapId, movedGrids, gridMoveBuffer, broadphaseQuery, xformQuery);
FindGridContacts(component, mapId, movedGrids, gridMoveBuffer);
// There is some mariana trench levels of bullshit going on.
// We essentially need to re-create Box2D's FindNewContacts but in a way that allows us to check every
@@ -174,7 +174,7 @@ namespace Robust.Shared.Physics.Systems
// to cache a bunch of stuff to make up for it.
// Handle grids first as they're not stored on map broadphase at all.
HandleGridCollisions(mapId, movedGrids, physicsQuery, xformQuery);
HandleGridCollisions(mapId, movedGrids);
// EZ
if (moveBuffer.Count == 0)
@@ -212,7 +212,7 @@ namespace Robust.Shared.Physics.Systems
var proxyBody = proxy.Body;
DebugTools.Assert(!proxyBody.Deleted);
var state = (this, proxy, worldAABB, buffer, xformQuery, broadphaseQuery);
var state = (this, proxy, worldAABB, buffer);
// Get every broadphase we may be intersecting.
_mapManager.FindGridsIntersecting(mapId, worldAABB.Enlarged(_broadphaseExpand), ref state,
@@ -220,18 +220,16 @@ namespace Robust.Shared.Physics.Systems
SharedBroadphaseSystem system,
FixtureProxy proxy,
Box2 worldAABB,
List<FixtureProxy> pairBuffer,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<BroadphaseComponent> broadphaseQuery) tuple) =>
List<FixtureProxy> pairBuffer) tuple) =>
{
ref var buffer = ref tuple.pairBuffer;
tuple.system.FindPairs(tuple.proxy, tuple.worldAABB, uid, buffer, tuple.xformQuery, tuple.broadphaseQuery);
tuple.system.FindPairs(tuple.proxy, tuple.worldAABB, uid, buffer);
return true;
});
// Struct ref moment, I have no idea what's fastest.
buffer = state.buffer;
FindPairs(proxy, worldAABB, _mapManager.GetMapEntityId(mapId), buffer, xformQuery, broadphaseQuery);
FindPairs(proxy, worldAABB, _mapManager.GetMapEntityId(mapId), buffer);
}
});
@@ -272,27 +270,23 @@ namespace Robust.Shared.Physics.Systems
private void HandleGridCollisions(
MapId mapId,
HashSet<EntityUid> movedGrids,
EntityQuery<PhysicsComponent> physicsQuery,
EntityQuery<TransformComponent> xformQuery)
HashSet<EntityUid> movedGrids)
{
var gridQuery = GetEntityQuery<MapGridComponent>();
foreach (var gridUid in movedGrids)
{
var grid = gridQuery.GetComponent(gridUid);
var xform = xformQuery.GetComponent(gridUid);
var grid = _gridQuery.GetComponent(gridUid);
var xform = _xformQuery.GetComponent(gridUid);
DebugTools.Assert(xform.MapID == mapId);
var (worldPos, worldRot, worldMatrix, invWorldMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(xform, xformQuery);
var (worldPos, worldRot, worldMatrix, invWorldMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(xform);
var aabb = new Box2Rotated(grid.LocalAABB, worldRot).CalcBoundingBox().Translated(worldPos);
// TODO: Need to handle grids colliding with non-grid entities with the same layer
// (nothing in SS14 does this yet).
var transform = _physicsSystem.GetPhysicsTransform(gridUid, xformQuery: xformQuery);
var state = (gridUid, grid, transform, worldMatrix, invWorldMatrix, _physicsSystem, _transform, physicsQuery, xformQuery);
var transform = _physicsSystem.GetPhysicsTransform(gridUid, xformQuery: _xformQuery);
var state = (gridUid, grid, transform, worldMatrix, invWorldMatrix, _map, _physicsSystem, _transform, _physicsQuery, _xformQuery);
_mapManager.FindGridsIntersecting(mapId, aabb, ref state,
static (EntityUid uid, MapGridComponent component,
@@ -301,6 +295,7 @@ namespace Robust.Shared.Physics.Systems
Transform transform,
Matrix3 worldMatrix,
Matrix3 invWorldMatrix,
SharedMapSystem _map,
SharedPhysicsSystem _physicsSystem,
SharedTransformSystem xformSystem,
EntityQuery<PhysicsComponent> physicsQuery,
@@ -320,7 +315,7 @@ namespace Robust.Shared.Physics.Systems
var aabb1 = tuple.grid.LocalAABB.Intersect(tuple.invWorldMatrix.TransformBox(otherGridBounds));
// TODO: AddPair has a nasty check in there that's O(n) but that's also a general physics problem.
var ourChunks = tuple.grid.GetLocalMapChunks(aabb1);
var ourChunks = tuple._map.GetLocalMapChunks(tuple.gridUid, tuple.grid, aabb1);
var physicsA = tuple.physicsQuery.GetComponent(tuple.gridUid);
var physicsB = tuple.physicsQuery.GetComponent(uid);
@@ -331,7 +326,7 @@ namespace Robust.Shared.Physics.Systems
tuple.worldMatrix.TransformBox(
ourChunk.CachedBounds.Translated(ourChunk.Indices * tuple.grid.ChunkSize));
var ourChunkOtherRef = otherGridInvMatrix.TransformBox(ourChunkWorld);
var collidingChunks = component.GetLocalMapChunks(ourChunkOtherRef);
var collidingChunks = tuple._map.GetLocalMapChunks(uid, component, ourChunkOtherRef);
while (collidingChunks.MoveNext(out var collidingChunk))
{
@@ -372,14 +367,12 @@ namespace Robust.Shared.Physics.Systems
FixtureProxy proxy,
Box2 worldAABB,
EntityUid broadphase,
List<FixtureProxy> pairBuffer,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<BroadphaseComponent> broadphaseQuery)
List<FixtureProxy> pairBuffer)
{
DebugTools.Assert(proxy.Body.CanCollide);
// Broadphase can't intersect with entities on itself so skip.
if (proxy.Entity == broadphase || !xformQuery.TryGetComponent(proxy.Entity, out var xform))
if (proxy.Entity == broadphase || !_xformQuery.TryGetComponent(proxy.Entity, out var xform))
{
return;
}
@@ -389,7 +382,7 @@ namespace Robust.Shared.Physics.Systems
DebugTools.AssertNotNull(xform.Broadphase);
if (!_lookup.TryGetCurrentBroadphase(xform, out var proxyBroad))
{
_logger.Error($"Found null broadphase for {ToPrettyString(proxy.Entity)}");
Log.Error($"Found null broadphase for {ToPrettyString(proxy.Entity)}");
DebugTools.Assert(false);
return;
}
@@ -401,10 +394,10 @@ namespace Robust.Shared.Physics.Systems
}
else
{
aabb = _transform.GetInvWorldMatrix(broadphase, xformQuery).TransformBox(worldAABB);
aabb = _transform.GetInvWorldMatrix(broadphase).TransformBox(worldAABB);
}
var broadphaseComp = broadphaseQuery.GetComponent(broadphase);
var broadphaseComp = _broadphaseQuery.GetComponent(broadphase);
var state = (pairBuffer, proxy);
QueryBroadphase(broadphaseComp.DynamicTree, state, aabb);
@@ -512,7 +505,7 @@ namespace Robust.Shared.Physics.Systems
}
// Won't worry about accurate bounds checks as it's probably slower in most use cases.
var chunkEnumerator = mapGrid.GetMapChunks(aabb);
var chunkEnumerator = _map.GetMapChunks(bUid, mapGrid, aabb);
if (chunkEnumerator.MoveNext(out _))
{

View File

@@ -27,6 +27,7 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using EyeComponent = Robust.Server.GameObjects.EyeComponent;
using MapSystem = Robust.Server.GameObjects.MapSystem;
namespace Robust.UnitTesting
{
@@ -86,6 +87,7 @@ namespace Robust.UnitTesting
var systems = deps.Resolve<IEntitySystemManager>();
// Required systems
systems.LoadExtraSystemType<MapSystem>();
systems.LoadExtraSystemType<EntityLookupSystem>();
// uhhh so maybe these are the wrong system for the client, but I CBF adding sprite system and all the rest,

View File

@@ -37,7 +37,6 @@ namespace Robust.UnitTesting.Shared
public void TestAnchoring()
{
var sim = RobustServerSimulation.NewSimulation();
// sim.RegisterEntitySystems(m => m.LoadExtraSystemType<EntityLookupSystem>());
var server = sim.InitializeInstance();
var lookup = server.Resolve<IEntitySystemManager>().GetEntitySystem<EntityLookupSystem>();
@@ -61,7 +60,7 @@ namespace Robust.UnitTesting.Shared
// When anchoring it should still get returned.
xform.Anchored = true;
Assert.That(xform.Anchored);
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(1));
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList(), Has.Count.EqualTo(1));
xform.Anchored = false;
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(1));

View File

@@ -65,7 +65,7 @@ namespace Robust.UnitTesting.Shared.Map
var x = i % size;
var y = i / size;
chunk.SetTile((ushort)x, (ushort)y, new Tile((ushort)tiles[i]));
chunk.TrySetTile((ushort)x, (ushort)y, new Tile((ushort)tiles[i]), out _, out _);
}
return chunk;

View File

@@ -25,6 +25,7 @@ namespace Robust.UnitTesting.Shared.Map
await server.WaitIdleAsync();
var entMan = server.ResolveDependency<IEntityManager>();
var mapMan = server.ResolveDependency<IMapManager>();
await server.WaitAssertion(() =>
@@ -35,18 +36,18 @@ namespace Robust.UnitTesting.Shared.Map
var coordinates = new EntityCoordinates(gridEnt, new Vector2(10, 0));
// if no rotation and 0,0 position should just be the same coordinate.
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).WorldRotation, Is.EqualTo(Angle.Zero));
Assert.That(entMan.GetComponent<TransformComponent>(gridEnt).WorldRotation, Is.EqualTo(Angle.Zero));
Assert.That(grid.WorldToLocal(coordinates.Position), Is.EqualTo(coordinates.Position));
// Rotate 180 degrees should show -10, 0 for the position in map-terms and 10, 0 for the position in entity terms (i.e. no change).
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).WorldRotation += new Angle(MathF.PI);
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).WorldRotation, Is.EqualTo(new Angle(MathF.PI)));
entMan.GetComponent<TransformComponent>(gridEnt).WorldRotation += new Angle(MathF.PI);
Assert.That(entMan.GetComponent<TransformComponent>(gridEnt).WorldRotation, Is.EqualTo(new Angle(MathF.PI)));
// Check the map coordinate rotates correctly
Assert.That(grid.WorldToLocal(new Vector2(10, 0)).EqualsApprox(new Vector2(-10, 0), 0.01f));
Assert.That(grid.LocalToWorld(coordinates.Position).EqualsApprox(new Vector2(-10, 0), 0.01f));
// Now we'll do the same for 180 degrees.
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).WorldRotation += MathF.PI / 2f;
entMan.GetComponent<TransformComponent>(gridEnt).WorldRotation += MathF.PI / 2f;
// If grid facing down then worldpos of 10, 0 gets rotated 90 degrees CCW and hence should be 0, 10
Assert.That(grid.WorldToLocal(new Vector2(10, 0)).EqualsApprox(new Vector2(0, 10), 0.01f));
// If grid facing down then local 10,0 pos should just return 0, -10 given it's aligned with the rotation.
@@ -64,6 +65,7 @@ namespace Robust.UnitTesting.Shared.Map
var entMan = server.ResolveDependency<IEntityManager>();
var mapMan = server.ResolveDependency<IMapManager>();
var mapSystem = entMan.System<SharedMapSystem>();
await server.WaitAssertion(() =>
{
@@ -86,26 +88,26 @@ namespace Robust.UnitTesting.Shared.Map
Assert.That(chunks.Count, Is.EqualTo(1));
var chunk = chunks[0];
var aabb = grid.CalcWorldAABB(chunk);
var aabb = mapSystem.CalcWorldAABB(gridEnt, grid, chunk);
var bounds = new Box2(new Vector2(0, 0), new Vector2(2, 10));
// With all cardinal directions these should align.
Assert.That(aabb, Is.EqualTo(bounds));
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).LocalRotation = new Angle(Math.PI);
aabb = grid.CalcWorldAABB(chunk);
entMan.GetComponent<TransformComponent>(gridEnt).LocalRotation = new Angle(Math.PI);
aabb = mapSystem.CalcWorldAABB(gridEnt, grid, chunk);
bounds = new Box2(new Vector2(-2, -10), new Vector2(0, 0));
Assert.That(aabb.EqualsApprox(bounds), $"Expected bounds of {aabb} and got {bounds}");
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).LocalRotation = new Angle(-Math.PI / 2);
aabb = grid.CalcWorldAABB(chunk);
entMan.GetComponent<TransformComponent>(gridEnt).LocalRotation = new Angle(-Math.PI / 2);
aabb = mapSystem.CalcWorldAABB(gridEnt, grid, chunk);
bounds = new Box2(new Vector2(0, -2), new Vector2(10, 0));
Assert.That(aabb.EqualsApprox(bounds), $"Expected bounds of {aabb} and got {bounds}");
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).LocalRotation = new Angle(-Math.PI / 4);
aabb = grid.CalcWorldAABB(chunk);
entMan.GetComponent<TransformComponent>(gridEnt).LocalRotation = new Angle(-Math.PI / 4);
aabb = mapSystem.CalcWorldAABB(gridEnt, grid, chunk);
bounds = new Box2(new Vector2(0, -1.4142135f), new Vector2(8.485281f, 7.071068f));
Assert.That(aabb.EqualsApprox(bounds), $"Expected bounds of {aabb} and got {bounds}");