mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-01 09:37:03 +02:00
2032 lines
71 KiB
C#
2032 lines
71 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Diagnostics.Contracts;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
using Robust.Shared.Collections;
|
|
using Robust.Shared.GameStates;
|
|
using Robust.Shared.Map;
|
|
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.Physics.Components;
|
|
using Robust.Shared.Physics.Dynamics;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Shared.GameObjects;
|
|
|
|
public abstract partial class SharedMapSystem
|
|
{
|
|
#region CreateGrid
|
|
|
|
/// <summary>
|
|
/// Creates a new grid entity on a given map.
|
|
/// </summary>
|
|
public Entity<MapGridComponent> CreateGridEntity(MapId mapId, GridCreateOptions? options = null)
|
|
{
|
|
return CreateGridEntity(GetMap(mapId), options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new grid entity on a given map.
|
|
/// </summary>
|
|
public Entity<MapGridComponent> CreateGridEntity(EntityUid mapEnt, GridCreateOptions? options = null)
|
|
{
|
|
options ??= GridCreateOptions.Default;
|
|
return CreateGridInternal(mapEnt, options.Value);
|
|
}
|
|
|
|
protected Entity<MapGridComponent> CreateGridInternal(EntityUid mapEnt, GridCreateOptions options)
|
|
{
|
|
var gridEnt = EntityManager.CreateEntityUninitialized(null);
|
|
|
|
var grid = EnsureComp<MapGridComponent>(gridEnt);
|
|
grid.ChunkSize = options.ChunkSize;
|
|
|
|
Log.Debug("Binding new grid {gridEnt}");
|
|
|
|
//TODO: This is a hack to get TransformComponent.MapId working before entity states
|
|
//are applied. After they are applied the parent may be different, but the MapId will
|
|
//be the same. This causes TransformComponent.ParentUid of a grid to be unsafe to
|
|
//use in transform states anytime before the state parent is properly set.
|
|
_transform.SetParent(gridEnt, mapEnt);
|
|
|
|
var meta = _metaQuery.GetComponent(gridEnt);
|
|
EntityManager.System<MetaDataSystem>().SetEntityName(gridEnt, $"grid", meta);
|
|
EntityManager.InitializeComponents(gridEnt, meta);
|
|
EntityManager.StartComponents(gridEnt);
|
|
// Note that this does not actually map-initialize the grid entity, even if the map its being spawn on has already been initialized.
|
|
// I don't know whether that is intentional or not.
|
|
|
|
return (gridEnt, grid);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Chunk helpers
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static Vector2i GetChunkIndices(Vector2 tile, int chunkSize)
|
|
{
|
|
return new Vector2i ((int) Math.Floor(tile.X / chunkSize), (int) Math.Floor(tile.Y / chunkSize));
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static Vector2i GetChunkIndices(Vector2 tile, byte chunkSize)
|
|
{
|
|
return new Vector2i ((int) Math.Floor(tile.X / chunkSize), (int) Math.Floor(tile.Y / chunkSize));
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static Vector2i GetChunkIndices(Vector2i tile, int chunkSize)
|
|
{
|
|
return new Vector2i ((int) Math.Floor(tile.X / (float) chunkSize), (int) Math.Floor(tile.Y / (float) chunkSize));
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static Vector2i GetChunkIndices(Vector2i tile, byte chunkSize)
|
|
{
|
|
return new Vector2i ((int) Math.Floor(tile.X / (float) chunkSize), (int) Math.Floor(tile.Y / (float) chunkSize));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the tile offset to a chunk origin based on the provided size.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static Vector2i GetChunkRelative(Vector2 tile, int chunkSize)
|
|
{
|
|
var x = MathHelper.Mod((int) Math.Floor(tile.X), chunkSize);
|
|
var y = MathHelper.Mod((int) Math.Floor(tile.Y), chunkSize);
|
|
return new Vector2i(x, y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the tile offset to a chunk origin based on the provided size.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static Vector2i GetChunkRelative(Vector2 tile, byte chunkSize)
|
|
{
|
|
var x = MathHelper.Mod((int) Math.Floor(tile.X), chunkSize);
|
|
var y = MathHelper.Mod((int) Math.Floor(tile.Y), chunkSize);
|
|
return new Vector2i(x, y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the tile offset to a chunk origin based on the provided size.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static Vector2i GetChunkRelative(Vector2i tile, int chunkSize)
|
|
{
|
|
var x = MathHelper.Mod(tile.X, chunkSize);
|
|
var y = MathHelper.Mod(tile.Y, chunkSize);
|
|
return new Vector2i(x, y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the tile offset to a chunk origin based on the provided size.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static Vector2i GetChunkRelative(Vector2i tile, byte chunkSize)
|
|
{
|
|
var x = MathHelper.Mod(tile.X, chunkSize);
|
|
var y = MathHelper.Mod(tile.Y, chunkSize);
|
|
return new Vector2i(x, y);
|
|
}
|
|
|
|
#endregion
|
|
|
|
public static Vector2i GetDirection(Vector2i position, Direction dir, int dist = 1)
|
|
{
|
|
switch (dir)
|
|
{
|
|
case Direction.East:
|
|
return position + new Vector2i(dist, 0);
|
|
case Direction.SouthEast:
|
|
return position + new Vector2i(dist, -dist);
|
|
case Direction.South:
|
|
return position + new Vector2i(0, -dist);
|
|
case Direction.SouthWest:
|
|
return position + new Vector2i(-dist, -dist);
|
|
case Direction.West:
|
|
return position + new Vector2i(-dist, 0);
|
|
case Direction.NorthWest:
|
|
return position + new Vector2i(-dist, dist);
|
|
case Direction.North:
|
|
return position + new Vector2i(0, dist);
|
|
case Direction.NorthEast:
|
|
return position + new Vector2i(dist, dist);
|
|
default:
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
private void InitializeGrid()
|
|
{
|
|
SubscribeLocalEvent<MapGridComponent, ComponentGetState>(OnGridGetState);
|
|
SubscribeLocalEvent<MapGridComponent, ComponentHandleState>(OnGridHandleState);
|
|
SubscribeLocalEvent<MapGridComponent, ComponentAdd>(OnGridAdd);
|
|
SubscribeLocalEvent<MapGridComponent, ComponentInit>(OnGridInit);
|
|
SubscribeLocalEvent<MapGridComponent, ComponentStartup>(OnGridStartup);
|
|
SubscribeLocalEvent<MapGridComponent, ComponentShutdown>(OnGridRemove);
|
|
SubscribeLocalEvent<MapGridComponent, MoveEvent>(OnGridMove);
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="GetGridPosition(Robust.Shared.GameObjects.Entity{Robust.Shared.Physics.Components.PhysicsComponent?},System.Numerics.Vector2,Robust.Shared.Maths.Angle)"/>
|
|
/// </summary>
|
|
public Vector2 GetGridPosition(Entity<PhysicsComponent?> grid, Vector2 worldPos, Angle worldRot)
|
|
{
|
|
if (!Resolve(grid.Owner, ref grid.Comp))
|
|
return Vector2.Zero;
|
|
|
|
return worldPos + worldRot.RotateVec(grid.Comp.LocalCenter);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the mapgrid's position considering its local physics center.
|
|
/// </summary>
|
|
public Vector2 GetGridPosition(Entity<PhysicsComponent?, TransformComponent?> grid)
|
|
{
|
|
if (!Resolve(grid.Owner, ref grid.Comp1, ref grid.Comp2))
|
|
return Vector2.Zero;
|
|
|
|
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(grid.Comp2);
|
|
|
|
return GetGridPosition((grid.Owner, grid.Comp1), worldPos, worldRot);
|
|
}
|
|
|
|
private void OnGridBoundsChange(EntityUid uid, MapGridComponent component)
|
|
{
|
|
// Just MapLoader things.
|
|
if (component.MapProxy == DynamicTree.Proxy.Free) return;
|
|
|
|
var xform = Comp<TransformComponent>(uid);
|
|
var aabb = GetWorldAABB(uid, component, xform);
|
|
|
|
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
|
|
{
|
|
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
|
|
}
|
|
|
|
_physics.MovedGrids.Add(uid);
|
|
}
|
|
|
|
private void OnGridMove(EntityUid uid, MapGridComponent component, ref MoveEvent args)
|
|
{
|
|
if (args.ParentChanged)
|
|
{
|
|
OnParentChange(uid, component, ref args);
|
|
return;
|
|
}
|
|
|
|
// Just maploader / test things
|
|
if (component.MapProxy == DynamicTree.Proxy.Free) return;
|
|
|
|
var xform = args.Component;
|
|
var aabb = GetWorldAABB(uid, component, xform);
|
|
|
|
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
|
|
{
|
|
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
|
|
}
|
|
|
|
_physics.MovedGrids.Add(uid);
|
|
}
|
|
|
|
private void OnParentChange(EntityUid uid, MapGridComponent component, ref MoveEvent args)
|
|
{
|
|
UpdatePvsChunks(args.Entity);
|
|
|
|
var (_, xform, meta) = args.Entity;
|
|
|
|
// oh boy
|
|
// Want gridinit to handle this hence specialcase those situations.
|
|
// oh boy oh boy, its even worse now.
|
|
// transform now raises parent change events on startup, because container code is a POS.
|
|
if (meta.EntityLifeStage < EntityLifeStage.Initialized || args.Component.LifeStage == ComponentLifeStage.Starting)
|
|
return;
|
|
|
|
Log.Info($"Grid {ToPrettyString(uid, meta)} changed parent. Old parent: {ToPrettyString(args.OldPosition.EntityId)}. New parent: {ToPrettyString(xform.ParentUid)}");
|
|
|
|
DebugTools.Assert(!_mapQuery.HasComponent(uid));
|
|
|
|
if (xform.MapUid != null &&
|
|
xform.ParentUid != xform.MapUid &&
|
|
meta.EntityLifeStage < EntityLifeStage.Terminating &&
|
|
_netManager.IsServer)
|
|
{
|
|
Log.Error($"Grid {ToPrettyString(uid, meta)} is parented to {ToPrettyString(xform._parent)} which is not a map. y'all need jesus. {Environment.StackTrace}");
|
|
return;
|
|
}
|
|
|
|
if (component.MapProxy != DynamicTree.Proxy.Free)
|
|
{
|
|
// Make sure we cleanup old map for moved grid stuff. When returning from nullspace there is no old map
|
|
// proxy to remove.
|
|
if (args.OldPosition.EntityId.IsValid())
|
|
{
|
|
var oldMap = _transform.ToMapCoordinates(args.OldPosition);
|
|
var oldMapUid = GetMapOrInvalid(oldMap.MapId);
|
|
_physics.MovedGrids.Remove(uid);
|
|
RemoveGrid(uid, component, oldMapUid);
|
|
}
|
|
else
|
|
{
|
|
component.MapProxy = DynamicTree.Proxy.Free;
|
|
}
|
|
}
|
|
|
|
DebugTools.Assert(component.MapProxy == DynamicTree.Proxy.Free);
|
|
if (xform.MapUid != null)
|
|
{
|
|
_physics.MovedGrids.Add(uid);
|
|
AddGrid(uid, component);
|
|
}
|
|
}
|
|
|
|
protected virtual void UpdatePvsChunks(Entity<TransformComponent, MetaDataComponent> grid)
|
|
{
|
|
}
|
|
|
|
private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref ComponentHandleState args)
|
|
{
|
|
switch (args.Current)
|
|
{
|
|
case MapGridComponentDeltaState delta:
|
|
{
|
|
DebugTools.Assert(component.ChunkSize == delta.ChunkSize || component.Chunks.Count == 0,
|
|
"Can't modify chunk size of an existing grid.");
|
|
|
|
component.ChunkSize = delta.ChunkSize;
|
|
if (delta.ChunkData == null)
|
|
return;
|
|
|
|
foreach (var (index, chunkData) in delta.ChunkData)
|
|
{
|
|
ApplyChunkData(uid, component, index, chunkData);
|
|
}
|
|
|
|
component.LastTileModifiedTick = delta.LastTileModifiedTick;
|
|
break;
|
|
}
|
|
case MapGridComponentState state:
|
|
{
|
|
DebugTools.Assert(component.ChunkSize == state.ChunkSize || component.Chunks.Count == 0,
|
|
"Can't modify chunk size of an existing grid.");
|
|
|
|
component.LastTileModifiedTick = state.LastTileModifiedTick;
|
|
component.ChunkSize = state.ChunkSize;
|
|
|
|
foreach (var index in component.Chunks.Keys)
|
|
{
|
|
if (!state.FullGridData.ContainsKey(index))
|
|
ApplyChunkData(uid, component, index, ChunkDatum.Empty);
|
|
}
|
|
|
|
foreach (var (index, data) in state.FullGridData)
|
|
{
|
|
DebugTools.Assert(!data.IsDeleted());
|
|
ApplyChunkData(uid, component, index, data);
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
return;
|
|
}
|
|
|
|
RegenerateAabb(component);
|
|
OnGridBoundsChange(uid, component);
|
|
|
|
#if DEBUG
|
|
foreach (var chunk in component.Chunks.Values)
|
|
{
|
|
chunk.ValidateChunk();
|
|
DebugTools.Assert(chunk.FilledTiles > 0);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private void ApplyChunkData(
|
|
EntityUid uid,
|
|
MapGridComponent component,
|
|
Vector2i index,
|
|
ChunkDatum data)
|
|
{
|
|
var counter = 0;
|
|
var gridEnt = new Entity<MapGridComponent>(uid, component);
|
|
|
|
if (data.IsDeleted())
|
|
{
|
|
if (!component.Chunks.Remove(index, out var deletedChunk))
|
|
return;
|
|
|
|
// Deleted chunks still need to raise tile-changed events.
|
|
deletedChunk.SuppressCollisionRegeneration = true;
|
|
for (ushort x = 0; x < component.ChunkSize; x++)
|
|
{
|
|
for (ushort y = 0; y < component.ChunkSize; y++)
|
|
{
|
|
if (!deletedChunk.TrySetTile(x, y, Tile.Empty, out var oldTile, out _))
|
|
continue;
|
|
|
|
var gridIndices = deletedChunk.ChunkTileToGridTile((x, y));
|
|
var newTileRef = new TileRef(uid, gridIndices, Tile.Empty);
|
|
RaiseOnTileChanged(gridEnt, newTileRef, oldTile, index);
|
|
}
|
|
}
|
|
|
|
deletedChunk.CachedBounds = Box2i.Empty;
|
|
deletedChunk.SuppressCollisionRegeneration = false;
|
|
return;
|
|
}
|
|
|
|
var chunk = GetOrAddChunk(uid, component, index);
|
|
chunk.Fixtures.Clear();
|
|
chunk.Fixtures.UnionWith(data.Fixtures);
|
|
|
|
chunk.SuppressCollisionRegeneration = true;
|
|
DebugTools.Assert(data.TileData.Any(x => !x.IsEmpty));
|
|
DebugTools.Assert(data.TileData.Length == component.ChunkSize * component.ChunkSize);
|
|
var changedEntry = new ValueList<TileChangedEntry>();
|
|
|
|
for (ushort x = 0; x < component.ChunkSize; x++)
|
|
{
|
|
for (ushort y = 0; y < component.ChunkSize; y++)
|
|
{
|
|
var tile = data.TileData[counter++];
|
|
if (!chunk.TrySetTile(x, y, tile, out var oldTile, out _))
|
|
continue;
|
|
|
|
|
|
var chunkIndex = new Vector2i(x, y);
|
|
var gridIndices = chunk.ChunkTileToGridTile(chunkIndex);
|
|
changedEntry.Add(new TileChangedEntry(tile, oldTile, chunk.Indices, gridIndices));
|
|
}
|
|
}
|
|
|
|
var ev = new TileChangedEvent(gridEnt, changedEntry.ToArray());
|
|
EntityManager.EventBus.RaiseLocalEvent(gridEnt.Owner, ref ev, true);
|
|
DebugTools.Assert(chunk.Fixtures.SetEquals(data.Fixtures));
|
|
|
|
// These should never refer to the same object
|
|
DebugTools.AssertNotEqual(chunk.Fixtures, data.Fixtures);
|
|
|
|
chunk.CachedBounds = data.CachedBounds!.Value;
|
|
chunk.SuppressCollisionRegeneration = false;
|
|
}
|
|
|
|
private void OnGridGetState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
|
|
{
|
|
if (args.FromTick <= component.CreationTick)
|
|
{
|
|
GetFullState(uid, component, ref args);
|
|
return;
|
|
}
|
|
|
|
Dictionary<Vector2i, ChunkDatum>? chunkData;
|
|
var fromTick = args.FromTick;
|
|
|
|
if (component.LastTileModifiedTick < fromTick)
|
|
{
|
|
chunkData = null;
|
|
}
|
|
else
|
|
{
|
|
chunkData = new Dictionary<Vector2i, ChunkDatum>();
|
|
|
|
foreach (var (tick, indices) in component.ChunkDeletionHistory)
|
|
{
|
|
if (tick < fromTick && fromTick != GameTick.Zero)
|
|
continue;
|
|
|
|
// Chunk may have been re-added sometime after it was deleted, but before deletion history was culled.
|
|
if (!component.Chunks.TryGetValue(indices, out var chunk))
|
|
{
|
|
chunkData.Add(indices, ChunkDatum.Empty);
|
|
continue;
|
|
}
|
|
|
|
if (chunk.LastTileModifiedTick < fromTick)
|
|
Log.Error($"Encountered un-deleted chunk with an old last-modified tick on grid {ToPrettyString(uid)}");
|
|
}
|
|
|
|
foreach (var (index, chunk) in GetMapChunks(uid, component))
|
|
{
|
|
if (chunk.LastTileModifiedTick < fromTick)
|
|
continue;
|
|
|
|
var tileBuffer = new Tile[component.ChunkSize * (uint) component.ChunkSize];
|
|
|
|
// Flatten the tile array.
|
|
// NetSerializer doesn't do multi-dimensional arrays.
|
|
// This is probably really expensive.
|
|
for (var x = 0; x < component.ChunkSize; x++)
|
|
{
|
|
for (var y = 0; y < component.ChunkSize; y++)
|
|
{
|
|
tileBuffer[x * component.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
|
|
}
|
|
}
|
|
|
|
// The client needs to clone the fixture set instead of storing a reference.
|
|
// TODO Game State
|
|
// Force the client to serialize & de-serialize implicitly generated component states.
|
|
var fixtures = chunk.Fixtures;
|
|
if (_netManager.IsClient)
|
|
fixtures = new(fixtures);
|
|
|
|
chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, fixtures, chunk.CachedBounds));
|
|
}
|
|
}
|
|
|
|
args.State = new MapGridComponentDeltaState(component.ChunkSize, chunkData, component.LastTileModifiedTick);
|
|
|
|
#if DEBUG
|
|
if (chunkData == null)
|
|
return;
|
|
|
|
HashSet<Vector2> keys = new();
|
|
foreach (var (index, chunk) in chunkData)
|
|
{
|
|
if (chunk.IsDeleted())
|
|
continue;
|
|
|
|
DebugTools.Assert(keys.Add(index), "Duplicate chunk");
|
|
DebugTools.Assert(chunk.TileData.Any(x => !x.IsEmpty), "Empty non-deleted chunk");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private void GetFullState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
|
|
{
|
|
var chunkData = new Dictionary<Vector2i, ChunkDatum>();
|
|
|
|
foreach (var (index, chunk) in GetMapChunks(uid, component))
|
|
{
|
|
var tileBuffer = new Tile[component.ChunkSize * (uint)component.ChunkSize];
|
|
|
|
for (var x = 0; x < component.ChunkSize; x++)
|
|
{
|
|
for (var y = 0; y < component.ChunkSize; y++)
|
|
{
|
|
tileBuffer[x * component.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
|
|
}
|
|
}
|
|
|
|
// The client needs to clone the fixture set instead of storing a reference.
|
|
// TODO Game State
|
|
// Force the client to serialize & de-serialize implicitly generated component states.
|
|
var fixtures = chunk.Fixtures;
|
|
if (_netManager.IsClient)
|
|
fixtures = new(fixtures);
|
|
|
|
chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, fixtures, chunk.CachedBounds));
|
|
}
|
|
|
|
args.State = new MapGridComponentState(component.ChunkSize, chunkData, component.LastTileModifiedTick);
|
|
|
|
#if DEBUG
|
|
foreach (var chunk in chunkData.Values)
|
|
{
|
|
DebugTools.Assert(chunk.TileData!.Any(x => !x.IsEmpty));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prunes tracked grid chunk deletions older than some given game tick.
|
|
/// </summary>
|
|
public void CullDeletionHistory(GameTick upToTick)
|
|
{
|
|
var query = AllEntityQuery<MapGridComponent>();
|
|
|
|
while (query.MoveNext(out var grid))
|
|
{
|
|
var chunks = grid.ChunkDeletionHistory;
|
|
chunks.RemoveAll(t => t.tick < upToTick);
|
|
}
|
|
}
|
|
|
|
private void OnGridAdd(EntityUid uid, MapGridComponent component, ComponentAdd args)
|
|
{
|
|
var msg = new GridAddEvent(uid);
|
|
RaiseLocalEvent(uid, msg, true);
|
|
}
|
|
|
|
private void OnGridInit(EntityUid uid, MapGridComponent component, ComponentInit args)
|
|
{
|
|
var xform = _xformQuery.GetComponent(uid);
|
|
|
|
// Force networkedmapmanager to send it due to non-ECS legacy code.
|
|
var curTick = _timing.CurTick;
|
|
|
|
foreach (var chunk in component.Chunks.Values)
|
|
{
|
|
chunk.LastTileModifiedTick = curTick;
|
|
}
|
|
|
|
component.LastTileModifiedTick = curTick;
|
|
|
|
if (xform.MapUid != null && xform.MapUid != uid)
|
|
_transform.SetParent(uid, xform, xform.MapUid.Value);
|
|
|
|
if (!_mapQuery.HasComponent(uid))
|
|
{
|
|
var aabb = GetWorldAABB(uid, component);
|
|
|
|
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
|
|
{
|
|
var proxy = gridTree.Tree.CreateProxy(in aabb, uint.MaxValue, (uid, _fixturesQuery.Comp(uid), component));
|
|
DebugTools.Assert(component.MapProxy == DynamicTree.Proxy.Free);
|
|
component.MapProxy = proxy;
|
|
}
|
|
|
|
if (xform.MapUid != null)
|
|
{
|
|
_physics.MovedGrids.Add(uid);
|
|
}
|
|
}
|
|
|
|
var msg = new GridInitializeEvent(uid, component);
|
|
RaiseLocalEvent(uid, msg, true);
|
|
}
|
|
|
|
private void OnGridStartup(EntityUid uid, MapGridComponent component, ComponentStartup args)
|
|
{
|
|
var msg = new GridStartupEvent(uid);
|
|
RaiseLocalEvent(uid, msg, true);
|
|
}
|
|
|
|
private void OnGridRemove(EntityUid uid, MapGridComponent component, ComponentShutdown args)
|
|
{
|
|
Log.Info($"Removing grid {ToPrettyString(uid)}");
|
|
if (TryComp(uid, out TransformComponent? xform) && xform.MapUid != null)
|
|
{
|
|
RemoveGrid(uid, component, xform.MapUid.Value);
|
|
}
|
|
|
|
component.MapProxy = DynamicTree.Proxy.Free;
|
|
RaiseLocalEvent(uid, new GridRemovalEvent(uid), true);
|
|
}
|
|
|
|
private Box2 GetWorldAABB(EntityUid uid, MapGridComponent grid, TransformComponent? xform = null)
|
|
{
|
|
if (!Resolve(uid, ref xform))
|
|
return new Box2();
|
|
|
|
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
|
|
var aabb = grid.LocalAABB.Translated(worldPos);
|
|
|
|
return new Box2Rotated(aabb, worldRot, worldPos).CalcBoundingBox();
|
|
}
|
|
|
|
private void AddGrid(EntityUid uid, MapGridComponent grid)
|
|
{
|
|
DebugTools.Assert(!_mapQuery.HasComponent(uid));
|
|
var aabb = GetWorldAABB(uid, grid);
|
|
|
|
if (!_xformQuery.TryGetComponent(uid, out var xform))
|
|
return;
|
|
|
|
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
|
|
{
|
|
var proxy = gridTree.Tree.CreateProxy(in aabb, uint.MaxValue, (uid, _fixturesQuery.Comp(uid), grid));
|
|
DebugTools.Assert(grid.MapProxy == DynamicTree.Proxy.Free);
|
|
grid.MapProxy = proxy;
|
|
}
|
|
|
|
if (xform.MapUid != null)
|
|
{
|
|
_physics.MovedGrids.Add(uid);
|
|
}
|
|
}
|
|
|
|
private void RemoveGrid(EntityUid uid, MapGridComponent grid, EntityUid mapUid)
|
|
{
|
|
if (grid.MapProxy != DynamicTree.Proxy.Free && TryComp<GridTreeComponent>(mapUid, out var gridTree))
|
|
{
|
|
gridTree.Tree.DestroyProxy(grid.MapProxy);
|
|
}
|
|
|
|
grid.MapProxy = DynamicTree.Proxy.Free;
|
|
|
|
if (mapUid.IsValid())
|
|
{
|
|
_physics.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))
|
|
{
|
|
ClearEmptyMapChunks(uid, grid, chunks);
|
|
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 id in mapChunk.Fixtures)
|
|
{
|
|
mapChunk.Fixtures.Remove(id);
|
|
_fixtures.DestroyFixture(uid, id, false, manager: manager, body: body, xform: xform);
|
|
}
|
|
|
|
RemoveChunk(uid, grid, mapChunk.Indices);
|
|
DebugTools.AssertEqual(mapChunk.Fixtures.Count, 0);
|
|
removedChunks.Add(mapChunk);
|
|
}
|
|
}
|
|
|
|
RegenerateAabb(grid);
|
|
|
|
// 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, grid);
|
|
RaiseLocalEvent(ref ev);
|
|
}
|
|
|
|
private void RegenerateAabb(MapGridComponent grid)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Variation of <see cref="RegenerateCollision(Robust.Shared.GameObjects.EntityUid,Robust.Shared.Map.Components.MapGridComponent,Robust.Shared.Map.MapChunk)"/>
|
|
/// that only simply removes empty chunks. Intended for use with "planet-maps", which have no grid fixtures.
|
|
/// </summary>
|
|
private void ClearEmptyMapChunks(EntityUid uid, MapGridComponent grid, IReadOnlySet<MapChunk> modified)
|
|
{
|
|
foreach (var chunk in modified)
|
|
{
|
|
DebugTools.Assert(chunk.FilledTiles >= 0);
|
|
if (chunk.FilledTiles > 0)
|
|
continue;
|
|
|
|
DebugTools.AssertEqual(chunk.Fixtures.Count, 0, "maps should not have grid-chunk fixtures");
|
|
RemoveChunk(uid, grid, chunk.Indices);
|
|
}
|
|
}
|
|
|
|
#region TileAccess
|
|
|
|
public TileRef GetTileRef(Entity<MapGridComponent> grid, MapCoordinates coords)
|
|
{
|
|
return GetTileRef(grid.Owner, grid.Comp, coords);
|
|
}
|
|
|
|
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
|
|
{
|
|
return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords));
|
|
}
|
|
|
|
public TileRef GetTileRef(Entity<MapGridComponent> grid, EntityCoordinates coords)
|
|
{
|
|
return GetTileRef(grid.Owner, grid.Comp, coords);
|
|
}
|
|
|
|
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
|
|
{
|
|
return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords));
|
|
}
|
|
|
|
public TileRef GetTileRef(Entity<MapGridComponent> grid, Vector2i tileCoordinates)
|
|
{
|
|
return GetTileRef(grid.Owner, grid.Comp, tileCoordinates);
|
|
}
|
|
|
|
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 GridTileEnumerator GetAllTiles(EntityUid uid, MapGridComponent grid, bool ignoreEmpty = true)
|
|
{
|
|
return new GridTileEnumerator(uid, grid.Chunks.GetEnumerator(), grid.ChunkSize, ignoreEmpty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the total number of tiles on a grid.
|
|
/// by summing the counts of filled tiles in each chunk.
|
|
/// </summary>
|
|
/// <param name="ent">The target map grid entity</param>
|
|
/// <returns>The total number of filled tiles in <paramref name="ent"/>.</returns>
|
|
[Pure]
|
|
public int GetFilledTileCount(Entity<MapGridComponent> ent)
|
|
{
|
|
return ent.Comp.Chunks.Values.Sum(chunk => chunk.FilledTiles);
|
|
}
|
|
|
|
[Obsolete("Use GetAllTiles instead.")]
|
|
public GridTileEnumerator GetAllTilesEnumerator(EntityUid uid, MapGridComponent grid, bool ignoreEmpty = true)
|
|
{
|
|
return GetAllTiles(uid, grid, ignoreEmpty);
|
|
}
|
|
|
|
public void SetTile(Entity<MapGridComponent> grid, EntityCoordinates coordinates, Tile tile)
|
|
{
|
|
SetTile(grid.Owner, grid.Comp, coordinates, tile);
|
|
}
|
|
|
|
public void SetTile(Entity<MapGridComponent> grid, Vector2i gridIndices, Tile tile)
|
|
{
|
|
SetTile(grid.Owner, grid.Comp, gridIndices, tile);
|
|
}
|
|
|
|
public void SetTiles(Entity<MapGridComponent> grid, List<(Vector2i GridIndices, Tile Tile)> tiles)
|
|
{
|
|
SetTiles(grid.Owner, grid.Comp, tiles);
|
|
}
|
|
|
|
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 chunkIndex = GridTileToChunkIndices(uid, grid, gridIndices);
|
|
if (!grid.Chunks.TryGetValue(chunkIndex, out var chunk))
|
|
{
|
|
if (tile.IsEmpty)
|
|
return;
|
|
|
|
grid.Chunks[chunkIndex] = chunk = new MapChunk(chunkIndex.X, chunkIndex.Y, grid.ChunkSize)
|
|
{
|
|
LastTileModifiedTick = _timing.CurTick
|
|
};
|
|
}
|
|
|
|
var offset = chunk.GridTileToChunkTile(gridIndices);
|
|
SetChunkTile(uid, grid, chunk, (ushort)offset.X, (ushort)offset.Y, tile, out _);
|
|
}
|
|
|
|
public void SetTiles(EntityUid uid, MapGridComponent grid, List<(Vector2i GridIndices, Tile Tile)> tiles)
|
|
{
|
|
if (tiles.Count == 0)
|
|
return;
|
|
|
|
var modified = new HashSet<MapChunk>(Math.Max(1, tiles.Count / grid.ChunkSize));
|
|
var tileChanges = new ValueList<TileChangedEntry>(tiles.Count);
|
|
|
|
// Suppress sending out events for each tile changed
|
|
// We're going to send them all out together at the end
|
|
SuppressOnTileChanged = true;
|
|
|
|
foreach (var (gridIndices, tile) in tiles)
|
|
{
|
|
var chunkIndex = GridTileToChunkIndices(uid, grid, gridIndices);
|
|
if (!grid.Chunks.TryGetValue(chunkIndex, out var chunk))
|
|
{
|
|
if (tile.IsEmpty)
|
|
continue;
|
|
|
|
grid.Chunks[chunkIndex] = chunk = new MapChunk(chunkIndex.X, chunkIndex.Y, grid.ChunkSize)
|
|
{
|
|
LastTileModifiedTick = _timing.CurTick
|
|
};
|
|
}
|
|
|
|
var offset = chunk.GridTileToChunkTile(gridIndices);
|
|
chunk.SuppressCollisionRegeneration = true;
|
|
if (SetChunkTile(uid, grid, chunk, (ushort)offset.X, (ushort)offset.Y, tile, out var oldTile))
|
|
{
|
|
modified.Add(chunk);
|
|
tileChanges.Add(new TileChangedEntry(tile, oldTile, offset, gridIndices));
|
|
}
|
|
}
|
|
|
|
foreach (var chunk in modified)
|
|
{
|
|
chunk.SuppressCollisionRegeneration = false;
|
|
}
|
|
|
|
// Notify of all tile changes in one event
|
|
var ev = new TileChangedEvent((uid, grid), tileChanges.ToArray());
|
|
RaiseLocalEvent(uid, ref ev, true);
|
|
|
|
RegenerateCollision(uid, grid, modified);
|
|
|
|
// Back to normal
|
|
SuppressOnTileChanged = false;
|
|
}
|
|
|
|
[Obsolete("Use GetLocalTilesIntersecting instead.")]
|
|
public TilesEnumerator GetLocalTilesEnumerator(EntityUid uid, MapGridComponent grid, Box2 aabb,
|
|
bool ignoreEmpty = true,
|
|
Predicate<TileRef>? predicate = null)
|
|
{
|
|
return GetLocalTilesIntersecting(uid, grid, aabb, ignoreEmpty, predicate);
|
|
}
|
|
|
|
[Obsolete("Use GetTilesIntersecting instead.")]
|
|
public TilesEnumerator GetTilesEnumerator(EntityUid uid, MapGridComponent grid, Box2 aabb, bool ignoreEmpty = true,
|
|
Predicate<TileRef>? predicate = null)
|
|
{
|
|
return GetTilesIntersecting(uid, grid, aabb, ignoreEmpty, predicate);
|
|
}
|
|
|
|
[Obsolete("Use GetTilesIntersecting instead.")]
|
|
public TilesEnumerator GetTilesEnumerator(EntityUid uid, MapGridComponent grid, Box2Rotated bounds, bool ignoreEmpty = true,
|
|
Predicate<TileRef>? predicate = null)
|
|
{
|
|
return GetTilesIntersecting(uid, grid, bounds, ignoreEmpty, predicate);
|
|
}
|
|
|
|
public TilesEnumerator GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2 localAABB, bool ignoreEmpty = true,
|
|
Predicate<TileRef>? predicate = null)
|
|
{
|
|
return new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, localAABB);
|
|
}
|
|
|
|
public TilesEnumerator GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated localArea, bool ignoreEmpty = true,
|
|
Predicate<TileRef>? predicate = null)
|
|
{
|
|
var localAABB = localArea.CalcBoundingBox();
|
|
return new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, localAABB);
|
|
}
|
|
|
|
public TilesEnumerator GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea, bool ignoreEmpty = true,
|
|
Predicate<TileRef>? predicate = null)
|
|
{
|
|
var matrix = _transform.GetInvWorldMatrix(uid);
|
|
var localArea = matrix.TransformBox(worldArea);
|
|
return new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, localArea);
|
|
}
|
|
|
|
public TilesEnumerator GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2 worldArea, bool ignoreEmpty = true,
|
|
Predicate<TileRef>? predicate = null)
|
|
{
|
|
var matrix = _transform.GetInvWorldMatrix(uid);
|
|
var localArea = matrix.TransformBox(worldArea);
|
|
return new TilesEnumerator(this, ignoreEmpty, predicate, uid, grid, localArea);
|
|
}
|
|
|
|
public CircleTilesEnumerator GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Circle localCircle, bool ignoreEmpty = true,
|
|
Predicate<TileRef>? predicate = null)
|
|
{
|
|
var aabb = new Box2(localCircle.Position.X - localCircle.Radius, localCircle.Position.Y - localCircle.Radius,
|
|
localCircle.Position.X + localCircle.Radius, localCircle.Position.Y + localCircle.Radius);
|
|
|
|
var tileEnumerator = GetLocalTilesIntersecting(uid, grid, aabb, ignoreEmpty, predicate);
|
|
return new CircleTilesEnumerator(tileEnumerator, grid, localCircle.Position, localCircle.Radius);
|
|
}
|
|
|
|
public CircleTilesEnumerator GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Circle worldArea, bool ignoreEmpty = true,
|
|
Predicate<TileRef>? predicate = null)
|
|
{
|
|
var localPosition = WorldToLocal(uid, grid, worldArea.Position);
|
|
var aabb = new Box2(localPosition.X - worldArea.Radius, localPosition.Y - worldArea.Radius,
|
|
localPosition.X + worldArea.Radius, localPosition.Y + worldArea.Radius);
|
|
|
|
var tileEnumerator = GetLocalTilesIntersecting(uid, grid, aabb, ignoreEmpty, predicate);
|
|
return new CircleTilesEnumerator(tileEnumerator, grid, localPosition, worldArea.Radius);
|
|
}
|
|
|
|
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 GetLocalMapChunks(uid, grid, localAABB);
|
|
}
|
|
|
|
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea)
|
|
{
|
|
var matrix = _transform.GetInvWorldMatrix(uid);
|
|
var localArea = matrix.TransformBox(worldArea);
|
|
return GetLocalMapChunks(uid, grid, localArea);
|
|
}
|
|
|
|
internal ChunkEnumerator GetLocalMapChunks(EntityUid uid, MapGridComponent grid, Box2 localAABB)
|
|
{
|
|
Box2 compAABB;
|
|
|
|
// The entire area intersects.
|
|
if (_mapQuery.HasComponent(uid))
|
|
{
|
|
compAABB = localAABB;
|
|
}
|
|
else
|
|
{
|
|
compAABB = grid.LocalAABB.Intersect(localAABB);
|
|
}
|
|
|
|
return new ChunkEnumerator(grid.Chunks, compAABB, 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 AnchoredEntitiesEnumerator GetAnchoredEntities(Entity<MapGridComponent> grid, MapCoordinates coords)
|
|
{
|
|
return GetAnchoredEntities(grid.Owner, grid.Comp, coords);
|
|
}
|
|
|
|
public AnchoredEntitiesEnumerator GetAnchoredEntities(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
|
|
{
|
|
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
|
|
}
|
|
|
|
public AnchoredEntitiesEnumerator GetAnchoredEntities(Entity<MapGridComponent> grid, EntityCoordinates coords)
|
|
{
|
|
return GetAnchoredEntities(grid.Owner, grid.Comp, coords);
|
|
}
|
|
|
|
public AnchoredEntitiesEnumerator GetAnchoredEntities(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
|
|
{
|
|
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
|
|
}
|
|
|
|
public AnchoredEntitiesEnumerator GetAnchoredEntities(Entity<MapGridComponent> grid, Vector2i pos)
|
|
{
|
|
return GetAnchoredEntities(grid.Owner, grid.Comp, pos);
|
|
}
|
|
|
|
public AnchoredEntitiesEnumerator 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 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 void GetAnchoredEntities(Entity<MapGridComponent> grid, Vector2i pos, List<EntityUid> list)
|
|
{
|
|
var gridChunkPos = GridTileToChunkIndices(grid.Owner, grid.Comp, pos);
|
|
if (!grid.Comp.Chunks.TryGetValue(gridChunkPos, out var chunk))
|
|
return;
|
|
|
|
var chunkTile = chunk.GridTileToChunkTile(pos);
|
|
var anchored = chunk.GetSnapGrid((ushort) chunkTile.X, (ushort) chunkTile.Y);
|
|
if (anchored != null)
|
|
list.AddRange(anchored);
|
|
}
|
|
|
|
[Obsolete("Use GetAnchoredEntities instead.")]
|
|
public AnchoredEntitiesEnumerator GetAnchoredEntitiesEnumerator(EntityUid uid, MapGridComponent grid, Vector2i pos)
|
|
{
|
|
return GetAnchoredEntities(uid, grid, pos);
|
|
}
|
|
|
|
public AnchoredEntitiesInTilesEnumerator GetLocalAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 localAABB)
|
|
{
|
|
var enumerator = new TilesEnumerator(this, true, null, uid, grid, localAABB);
|
|
return new AnchoredEntitiesInTilesEnumerator(this, uid, grid, enumerator);
|
|
}
|
|
|
|
public AnchoredEntitiesInTilesEnumerator GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
|
|
{
|
|
var invWorldMatrix = _transform.GetInvWorldMatrix(uid);
|
|
var localAABB = invWorldMatrix.TransformBox(worldAABB);
|
|
var enumerator = new TilesEnumerator(this, true, null, uid, grid, localAABB);
|
|
return new AnchoredEntitiesInTilesEnumerator(this, uid, grid, enumerator);
|
|
}
|
|
|
|
public AnchoredEntitiesInTilesEnumerator GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2Rotated worldBounds)
|
|
{
|
|
var tiles = GetTilesIntersecting(uid, grid, worldBounds);
|
|
return new AnchoredEntitiesInTilesEnumerator(this, uid, grid, tiles);
|
|
}
|
|
|
|
public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
|
|
{
|
|
#if DEBUG
|
|
var mapId = _xformQuery.GetComponent(uid).MapID;
|
|
DebugTools.Assert(mapId == _transform.GetMapId(coords));
|
|
#endif
|
|
|
|
return SnapGridLocalCellFor(uid, grid, LocalToGrid(uid, grid, coords));
|
|
}
|
|
|
|
public Vector2i TileIndicesFor(Entity<MapGridComponent> grid, EntityCoordinates coords)
|
|
{
|
|
return TileIndicesFor(grid.Owner, grid.Comp, 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);
|
|
}
|
|
|
|
public Vector2i TileIndicesFor(Entity<MapGridComponent> grid, MapCoordinates coords)
|
|
{
|
|
return TileIndicesFor(grid.Owner, grid.Comp, coords);
|
|
}
|
|
|
|
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);
|
|
|
|
if (!TryChunkAndOffsetForTile(uid, grid, tilePos, out var chunk, out var chunkTile))
|
|
return false;
|
|
|
|
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)
|
|
{
|
|
if (!TryChunkAndOffsetForTile(gridUid, grid, pos, out var chunk, out var chunkTile))
|
|
return false;
|
|
|
|
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 gridChunkIndices = GridTileToChunkIndices(gridUid, grid, pos);
|
|
|
|
if (!grid.Chunks.TryGetValue(gridChunkIndices, out var chunk))
|
|
return;
|
|
|
|
var chunkTile = chunk.GridTileToChunkTile(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 bool TryChunkAndOffsetForTile(EntityUid uid, MapGridComponent grid, Vector2i pos,
|
|
[NotNullWhen(true)]out MapChunk? chunk, out Vector2i offset)
|
|
{
|
|
var gridChunkIndices = GridTileToChunkIndices(uid, grid, pos);
|
|
if (!grid.Chunks.TryGetValue(gridChunkIndices, out chunk))
|
|
{
|
|
offset = default;
|
|
return false;
|
|
}
|
|
|
|
offset = chunk.GridTileToChunkTile(pos);
|
|
return true;
|
|
}
|
|
|
|
public AnchoredEntitiesEnumerator GetInDir(EntityUid uid, MapGridComponent grid, EntityCoordinates position, Direction dir)
|
|
{
|
|
var pos = GetDirection(TileIndicesFor(uid, grid, position), dir);
|
|
return GetAnchoredEntities(uid, grid, pos);
|
|
}
|
|
|
|
public AnchoredEntitiesEnumerator GetOffset(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, Vector2i offset)
|
|
{
|
|
var pos = TileIndicesFor(uid, grid, coords) + offset;
|
|
return GetAnchoredEntities(uid, grid, pos);
|
|
}
|
|
|
|
public AnchoredEntitiesEnumerator 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 AnchoredEntitiesInTileOffsetsEnumerator GetCardinalNeighborCells(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
|
|
{
|
|
var position = TileIndicesFor(uid, grid, coords);
|
|
return AnchoredEntitiesInTileOffsetsEnumerator.Cardinal(this, uid, grid, position);
|
|
}
|
|
|
|
public AnchoredEntitiesInTileOffsetsEnumerator GetCellsInSquareArea(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, int n)
|
|
{
|
|
var position = TileIndicesFor(uid, grid, coords);
|
|
return AnchoredEntitiesInTileOffsetsEnumerator.Square(this, uid, grid, position, n);
|
|
}
|
|
|
|
public struct AnchoredEntitiesInTileOffsetsEnumerator : IEnumerable<EntityUid>, IEnumerator<EntityUid>
|
|
{
|
|
private readonly SharedMapSystem _map;
|
|
private readonly EntityUid _uid;
|
|
private readonly MapGridComponent _grid;
|
|
private readonly Vector2i _position;
|
|
private readonly int _n;
|
|
private readonly bool _cardinal;
|
|
private int _index;
|
|
private int _x;
|
|
private int _y;
|
|
private AnchoredEntitiesEnumerator _anchored;
|
|
private EntityUid _current;
|
|
|
|
private AnchoredEntitiesInTileOffsetsEnumerator(
|
|
SharedMapSystem map,
|
|
EntityUid uid,
|
|
MapGridComponent grid,
|
|
Vector2i position,
|
|
int n,
|
|
bool cardinal)
|
|
{
|
|
_map = map;
|
|
_uid = uid;
|
|
_grid = grid;
|
|
_position = position;
|
|
_n = n;
|
|
_cardinal = cardinal;
|
|
_index = -1;
|
|
_x = -n - 1;
|
|
_y = -n;
|
|
_anchored = default;
|
|
_current = default;
|
|
}
|
|
|
|
internal static AnchoredEntitiesInTileOffsetsEnumerator Cardinal(
|
|
SharedMapSystem map,
|
|
EntityUid uid,
|
|
MapGridComponent grid,
|
|
Vector2i position)
|
|
{
|
|
return new AnchoredEntitiesInTileOffsetsEnumerator(map, uid, grid, position, 0, true);
|
|
}
|
|
|
|
internal static AnchoredEntitiesInTileOffsetsEnumerator Square(
|
|
SharedMapSystem map,
|
|
EntityUid uid,
|
|
MapGridComponent grid,
|
|
Vector2i position,
|
|
int n)
|
|
{
|
|
return new AnchoredEntitiesInTileOffsetsEnumerator(map, uid, grid, position, n, false);
|
|
}
|
|
|
|
public readonly AnchoredEntitiesInTileOffsetsEnumerator GetEnumerator() => this;
|
|
|
|
public readonly EntityUid Current => _current;
|
|
|
|
readonly object IEnumerator.Current => Current;
|
|
|
|
public bool MoveNext()
|
|
{
|
|
while (true)
|
|
{
|
|
if (_anchored.MoveNext(out var current))
|
|
{
|
|
_current = current.Value;
|
|
return true;
|
|
}
|
|
|
|
if (!TryGetNextOffset(out var offset))
|
|
return false;
|
|
|
|
_anchored = _map.GetAnchoredEntities(_uid, _grid, _position + offset);
|
|
}
|
|
}
|
|
|
|
private bool TryGetNextOffset(out Vector2i offset)
|
|
{
|
|
if (_cardinal)
|
|
{
|
|
_index++;
|
|
offset = _index switch
|
|
{
|
|
0 => Vector2i.Zero,
|
|
1 => new Vector2i(0, 1),
|
|
2 => new Vector2i(0, -1),
|
|
3 => new Vector2i(1, 0),
|
|
4 => new Vector2i(-1, 0),
|
|
_ => default,
|
|
};
|
|
|
|
return _index < 5;
|
|
}
|
|
|
|
_x++;
|
|
if (_x > _n)
|
|
{
|
|
_x = -_n;
|
|
_y++;
|
|
}
|
|
|
|
if (_y > _n)
|
|
{
|
|
offset = default;
|
|
return false;
|
|
}
|
|
|
|
offset = new Vector2i(_x, _y);
|
|
return true;
|
|
}
|
|
|
|
readonly IEnumerator<EntityUid> IEnumerable<EntityUid>.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
readonly IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
public readonly void Dispose()
|
|
{
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
}
|
|
|
|
[Obsolete("Use AnchoredEntitiesInTileOffsetsEnumerator instead.")]
|
|
public struct CardinalNeighborCellsEnumerator
|
|
{
|
|
}
|
|
|
|
[Obsolete("Use AnchoredEntitiesInTileOffsetsEnumerator instead.")]
|
|
public struct SquareAreaCellsEnumerator
|
|
{
|
|
}
|
|
|
|
public struct AnchoredEntitiesInTilesEnumerator : IEnumerable<EntityUid>, IEnumerator<EntityUid>
|
|
{
|
|
private readonly SharedMapSystem _map;
|
|
private readonly EntityUid _uid;
|
|
private readonly MapGridComponent _grid;
|
|
private TilesEnumerator _tiles;
|
|
private AnchoredEntitiesEnumerator _anchored;
|
|
private EntityUid _current;
|
|
|
|
internal AnchoredEntitiesInTilesEnumerator(SharedMapSystem map, EntityUid uid, MapGridComponent grid, TilesEnumerator tiles)
|
|
{
|
|
_map = map;
|
|
_uid = uid;
|
|
_grid = grid;
|
|
_tiles = tiles;
|
|
_anchored = default;
|
|
_current = default;
|
|
}
|
|
|
|
public readonly AnchoredEntitiesInTilesEnumerator GetEnumerator() => this;
|
|
|
|
public readonly EntityUid Current => _current;
|
|
|
|
readonly object IEnumerator.Current => Current;
|
|
|
|
public bool MoveNext()
|
|
{
|
|
while (true)
|
|
{
|
|
if (_anchored.MoveNext(out var current))
|
|
{
|
|
_current = current.Value;
|
|
return true;
|
|
}
|
|
|
|
if (!_tiles.MoveNext(out var tile))
|
|
return false;
|
|
|
|
_anchored = _map.GetAnchoredEntities(_uid, _grid, tile.GridIndices);
|
|
}
|
|
}
|
|
|
|
readonly IEnumerator<EntityUid> IEnumerable<EntityUid>.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
readonly IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
public readonly void Dispose()
|
|
{
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Transforms
|
|
|
|
public Vector2 WorldToLocal(EntityUid uid, MapGridComponent grid, Vector2 posWorld)
|
|
{
|
|
var matrix = _transform.GetInvWorldMatrix(uid);
|
|
return Vector2.Transform(posWorld, matrix);
|
|
}
|
|
|
|
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 (!_gridQuery.TryGetComponent(uid, out var grid))
|
|
{
|
|
return new EntityCoordinates(GetMapOrInvalid(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 Vector2.Transform(posLocal, matrix);
|
|
}
|
|
|
|
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 == _transform.GetMapId(coords));
|
|
#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, _transform.ToMapCoordinates(position).Position);
|
|
}
|
|
|
|
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)
|
|
=> GridTileToChunkIndices(grid, gridTile);
|
|
|
|
public Vector2i GridTileToChunkIndices(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)
|
|
{
|
|
var position = TileCenterToVector(uid, grid, gridTile);
|
|
|
|
return new(uid, position);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Turns a gridtile origin into a Vector2, accounting for tile size.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public Vector2 TileToVector(Entity<MapGridComponent> grid, Vector2i gridTile)
|
|
{
|
|
return new Vector2(gridTile.X * grid.Comp.TileSize, gridTile.Y * grid.Comp.TileSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Turns a gridtile center into a Vector2, accounting for tile size.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public Vector2 TileCenterToVector(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
|
|
{
|
|
return TileCenterToVector((uid, grid), gridTile);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Turns a gridtile center into a Vector2, accounting for tile size.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public Vector2 TileCenterToVector(Entity<MapGridComponent> grid, Vector2i gridTile)
|
|
{
|
|
return new Vector2(gridTile.X * grid.Comp.TileSize, gridTile.Y * grid.Comp.TileSize) + grid.Comp.TileSizeHalfVector;
|
|
}
|
|
|
|
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 Vector2.Transform(new Vector2(locX, locY), _transform.GetWorldMatrix(uid));
|
|
}
|
|
|
|
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 TryGetTile(MapGridComponent grid, Vector2i indices, out Tile tile)
|
|
{
|
|
var chunkIndices = GridTileToChunkIndices(grid, indices);
|
|
if (!grid.Chunks.TryGetValue(chunkIndices, out var chunk))
|
|
{
|
|
tile = default;
|
|
return false;
|
|
}
|
|
|
|
var cTileIndices = chunk.GridTileToChunkTile(indices);
|
|
tile = chunk.Tiles[cTileIndices.X, cTileIndices.Y];
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to get the <see cref="ITileDefinition"/> for the tile at the given grid indices. This will throw an
|
|
/// exception if the tile at this location has no registered tile definition.
|
|
/// </summary>
|
|
public bool TryGetTileDef(MapGridComponent grid, Vector2i indices, [NotNullWhen(true)] out ITileDefinition? tileDef)
|
|
{
|
|
if (!TryGetTile(grid, indices, out var tile))
|
|
{
|
|
tileDef = null;
|
|
return false;
|
|
}
|
|
|
|
tileDef = _tileMan[tile.TypeId];
|
|
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(uid, 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 (!SuppressOnTileChanged)
|
|
{
|
|
var newTileRef = new TileRef(uid, gridTile, newTile);
|
|
RaiseOnTileChanged((uid, grid), newTileRef, oldTile, mapChunk.Indices);
|
|
}
|
|
|
|
if (shapeChanged && !mapChunk.SuppressCollisionRegeneration)
|
|
{
|
|
RegenerateCollision(uid, grid, mapChunk);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises <see cref="TileChangedEvent"/> on the provided grid unless <see cref="SuppressOnTileChanged"/> is set.
|
|
/// </summary>
|
|
internal void RaiseOnTileChanged(Entity<MapGridComponent> entity, TileRef tileRef, Tile oldTile, Vector2i chunk)
|
|
{
|
|
if (SuppressOnTileChanged)
|
|
return;
|
|
|
|
var ev = new TileChangedEvent(entity, tileRef, oldTile, chunk);
|
|
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, ref ev, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Iterates the local tiles of the specified data.
|
|
/// </summary>
|
|
public struct TilesEnumerator : IEnumerable<TileRef>, IEnumerator<TileRef>
|
|
{
|
|
private readonly SharedMapSystem _mapSystem;
|
|
|
|
private readonly EntityUid _uid;
|
|
private readonly MapGridComponent _grid;
|
|
private readonly bool _ignoreEmpty;
|
|
private readonly Predicate<TileRef>? _predicate;
|
|
|
|
private readonly int _lowerY;
|
|
private readonly int _upperX;
|
|
private readonly int _upperY;
|
|
|
|
private int _x;
|
|
private int _y;
|
|
private TileRef _current;
|
|
|
|
public TilesEnumerator(
|
|
SharedMapSystem mapSystem,
|
|
bool ignoreEmpty,
|
|
Predicate<TileRef>? predicate,
|
|
EntityUid uid,
|
|
MapGridComponent grid,
|
|
Box2 aabb)
|
|
{
|
|
_mapSystem = mapSystem;
|
|
|
|
_uid = uid;
|
|
_grid = grid;
|
|
_ignoreEmpty = ignoreEmpty;
|
|
_predicate = predicate;
|
|
|
|
// 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(aabb.Left), (int)Math.Floor(aabb.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(aabb.Right), (int)Math.Ceiling(aabb.Top));
|
|
|
|
_x = gridTileLb.X;
|
|
_y = gridTileLb.Y;
|
|
_lowerY = gridTileLb.Y;
|
|
_upperX = gridTileRt.X;
|
|
_upperY = gridTileRt.Y;
|
|
_current = default;
|
|
}
|
|
|
|
public readonly TilesEnumerator GetEnumerator() => this;
|
|
|
|
public readonly TileRef Current => _current;
|
|
|
|
readonly object IEnumerator.Current => Current;
|
|
|
|
public bool MoveNext()
|
|
{
|
|
return MoveNext(out _);
|
|
}
|
|
|
|
public bool MoveNext(out TileRef tile)
|
|
{
|
|
while (true)
|
|
{
|
|
if (_x >= _upperX)
|
|
{
|
|
tile = TileRef.Zero;
|
|
return false;
|
|
}
|
|
|
|
var gridTile = new Vector2i(_x, _y);
|
|
|
|
_y++;
|
|
|
|
if (_y >= _upperY)
|
|
{
|
|
_x++;
|
|
_y = _lowerY;
|
|
}
|
|
|
|
var gridChunk = _mapSystem.GridTileToChunkIndices(_uid, _grid, gridTile);
|
|
|
|
if (_grid.Chunks.TryGetValue(gridChunk, out var chunk))
|
|
{
|
|
var chunkTile = chunk.GridTileToChunkTile(gridTile);
|
|
tile = _mapSystem.GetTileRef(_uid, _grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
|
|
|
|
if (_ignoreEmpty && tile.Tile.IsEmpty)
|
|
continue;
|
|
|
|
if (_predicate == null || _predicate(tile))
|
|
{
|
|
_current = tile;
|
|
return true;
|
|
}
|
|
}
|
|
else if (!_ignoreEmpty)
|
|
{
|
|
tile = new TileRef(_uid, gridTile.X, gridTile.Y, Tile.Empty);
|
|
|
|
if (_predicate == null || _predicate(tile))
|
|
{
|
|
_current = tile;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
readonly IEnumerator<TileRef> IEnumerable<TileRef>.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
readonly IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
public readonly void Dispose()
|
|
{
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
}
|
|
|
|
public struct CircleTilesEnumerator : IEnumerable<TileRef>, IEnumerator<TileRef>
|
|
{
|
|
private TilesEnumerator _tiles;
|
|
private readonly MapGridComponent _grid;
|
|
private readonly Vector2 _center;
|
|
private readonly float _radius;
|
|
private TileRef _current;
|
|
|
|
internal CircleTilesEnumerator(TilesEnumerator tiles, MapGridComponent grid, Vector2 center, float radius)
|
|
{
|
|
_tiles = tiles;
|
|
_grid = grid;
|
|
_center = center;
|
|
_radius = radius;
|
|
_current = default;
|
|
}
|
|
|
|
public readonly CircleTilesEnumerator GetEnumerator() => this;
|
|
|
|
public readonly TileRef Current => _current;
|
|
|
|
readonly object IEnumerator.Current => Current;
|
|
|
|
public bool MoveNext()
|
|
{
|
|
while (_tiles.MoveNext(out var tile))
|
|
{
|
|
var tileCenter = tile.GridIndices + _grid.TileSizeHalfVector;
|
|
var direction = tileCenter - _center;
|
|
|
|
if (!direction.IsShorterThanOrEqualTo(_radius))
|
|
continue;
|
|
|
|
_current = tile;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
readonly IEnumerator<TileRef> IEnumerable<TileRef>.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
readonly IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
public readonly void Dispose()
|
|
{
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Additional parameters used when creating a new grid entity.
|
|
/// </summary>
|
|
/// <param name="ChunkSize">The number of tiles long/wide the grids chunks should be.</param>
|
|
public record struct GridCreateOptions(ushort ChunkSize)
|
|
{
|
|
public readonly static GridCreateOptions Default = new(ChunkSize: 16);
|
|
}
|